#include maps\mp\_utility;
#include common_scripts\utility;
/*
	Ball
*/

/*QUAKED mp_ball_spawn (0.0 1.0 0.5) (-16 -16 0) (16 16 72)
Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/

/*QUAKED mp_ball_spawn_axis_start (1.0 0.0 0.5) (-16 -16 0) (16 16 72)
Axis players spawn away from enemies and near their team at one of these positions at the start of a round.*/

/*QUAKED mp_ball_spawn_allies_start (0.0 1.0 0.5) (-16 -16 0) (16 16 72)
Allied players spawn away from enemies and near their team at one of these positions at the start of a round.*/

CONST_BALL_WEAPON 	= "iw5_carrydrone_mp";

CONST_COLOR_WHITE 	= (1.000, 1.000, 1.000);
CONST_COLOR_BLUE 	= (0.157, 0.392, 0.784);
CONST_COLOR_ORANGE 	= (0.784, 0.490, 0.157);

CONST_POINTS_FIELDGOAL = 1;
CONST_POINTS_TOUCHDOWN = 2;

CONST_BALL_RESET_TIME = 15;

CONST_MAX_PASS_DISTANCE = 1000 * 1000;

ballIconHidden = 0;
ballIconSentinelOwned = 1;
ballIconAtlasOwned = 2;
ballIconNeutral = 3;
ballIconSentinelThrown = 4;
ballIconSentinelDropped = 5;
ballIconAtlasThrown = 6;
ballIconAtlasDropped = 7;

main()
{
	if(getdvar("mapname") == "mp_background")
		return;
	
	maps\mp\gametypes\_globallogic::init();
	maps\mp\gametypes\_callbacksetup::SetupCallbacks();
	maps\mp\gametypes\_globallogic::SetupCallbacks();

	if ( isUsingMatchRulesData() )
	{
		level.initializeMatchRules = ::initializeMatchRules;
		[[level.initializeMatchRules]]();
		level thread reInitializeMatchRulesOnMigration();		
	}
	else
	{
		registerRoundSwitchDvar( level.gameType, 0, 0, 9 );
		registerTimeLimitDvar( level.gameType, 10 );
		registerScoreLimitDvar( level.gameType, 75 );
		registerRoundLimitDvar( level.gameType, 1 );
		registerWinLimitDvar( level.gameType, 1 );
		registerNumLivesDvar( level.gameType, 0 );
		registerHalfTimeDvar( level.gameType, 1 );
		SetDynamicDvar( "scr_ball_num_balls", 1);
		
		level.matchRules_damageMultiplier = 0;
		level.matchRules_vampirism = 0;
	}
	
	num_balls = GetDvarInt("scr_ball_num_balls", 1);	
	SetOmnvar( "ui_uplink_num_balls", num_balls);

	setOverTimeLimitDvar( 3 );
	if( IsDefined( game["round_time_to_beat"] ) )
	{
		setOverTimeLimitDvar( game["round_time_to_beat"] );
		game["round_time_to_beat"] = undefined;
	}

	level.teamBased = true;
	level.onTimeLimit = ::onTimeLimit;
	level.halfTimeOnScoreLimit = true;
	level.overtimeScoreWinOverride = true;
	level.onStartGameType = ::onStartGameType;
	level.getSpawnPoint = ::getSpawnPoint;
	level.onNormalDeath = ::onNormalDeath;
	
	level.gamemodeOnUnderWater = ::onUnderWater;
	
	if ( level.matchRules_damageMultiplier || level.matchRules_vampirism )
		level.modifyPlayerDamage = maps\mp\gametypes\_damage::gamemodeModifyPlayerDamage;
	
	game["dialog"]["drone_reset"] = "upl_dronereset";
	game["dialog"]["you_own_drone"] = "upl_allyowndrone";
	game["dialog"]["ally_own_drone"] = "upl_friend_holds";
	game["dialog"]["enemy_own_drone"] = "upl_enemyowndrone";
	game["dialog"]["ally_throw_score"] = "upl_uplinksuccess";
	game["dialog"]["ally_carry_score"] = "upl_dronedelievered";
	game["dialog"]["enemy_throw_score"] = "upl_enm_score_remote";
	game["dialog"]["enemy_carry_score"] = "upl_enm_score";
	game["dialog"]["pass_complete"] = "upl_dronetransfered";
	game["dialog"]["pass_intercepted"] = "upl_droneintercepted";
	game["dialog"]["ally_drop_drone"] = "upl_dronedropped";
	game["dialog"]["enemy_drop_drone"] = "upl_dronedropped";
	
	game["dialog"]["gametype"] = "upl_intro";
	
	if ( getDvarInt( "g_hardcore" ) )
		game["dialog"]["gametype"] = "hc_" + game["dialog"]["gametype"];
	
	game["dialog"]["offense_obj"] = "upl_deliverdrone";
	game["dialog"]["defense_obj"] = "upl_deliverdrone";
	
	level thread ball_on_connect();
}


initializeMatchRules()
{
	//	set common values
	setCommonRulesFromMatchRulesData();
	
	//	set everything else (private match options, default .cfg file values, and what normally is registered in the 'else' below)
	SetDynamicDvar( "scr_ball_roundswitch", 0 );
	registerRoundSwitchDvar( "ball", 0, 0, 9 );
	SetDynamicDvar( "scr_ball_roundlimit", 1 );
	registerRoundLimitDvar( "ball", 1 );		
	SetDynamicDvar( "scr_ball_winlimit", 1 );
	registerWinLimitDvar( "ball", 1 );			
	SetDynamicDvar( "scr_ball_halftime", 1 );
	registerHalfTimeDvar( "ball", 1 );	

	matchRulesBalls = GetMatchRulesData( "ballData", "numBalls" );
	matchRulesBalls = max(1, matchRulesBalls);
	
	SetDynamicDvar( "scr_ball_num_balls", matchRulesBalls);
	
	SetDynamicDvar( "scr_ball_reset_time", GetMatchRulesData( "ballData", "ballResetTime" ));
	SetDynamicDvar( "scr_ball_points_touchdown", GetMatchRulesData( "ballData", "carryScore" ));
	SetDynamicDvar( "scr_ball_points_fieldgoal", GetMatchRulesData( "ballData", "throwScore" ));
	SetDynamicDvar( "scr_ball_armor", GetMatchRulesData( "ballData", "armorValue" ) );
}


onStartGameType()
{
	setClientNameMode("auto_change");
	
	if (game["status"] == "halftime")
		SetOmnvar( "ui_current_round", 2 );
	else if (game["status"] == "overtime")
		SetOmnvar( "ui_current_round", 3 );
	else if (game["status"] == "overtime_halftime")
		SetOmnvar( "ui_current_round", 4 );

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

	if ( game["switchedsides"] )
	{
		oldAttackers = game["attackers"];
		oldDefenders = game["defenders"];
		game["attackers"] = oldDefenders;
		game["defenders"] = oldAttackers;
	}
	
	if( game["status"] == "overtime" )
	{	
		//reset scores at start of over time
		game["teamScores"]["allies"] = 0;
		setTeamScore( "allies", 0 );	
		game["teamScores"]["axis"] = 0;		
		setTeamScore( "axis", 0 );
	}

	setObjectiveText( "allies", &"OBJECTIVES_BALL" );
	setObjectiveText( "axis", &"OBJECTIVES_BALL" );
	
	if ( level.splitscreen )
	{
		setObjectiveScoreText( "allies", &"OBJECTIVES_BALL" );
		setObjectiveScoreText( "axis", &"OBJECTIVES_BALL" );
	}
	else
	{
		setObjectiveScoreText( "allies", &"OBJECTIVES_BALL_SCORE" );
		setObjectiveScoreText( "axis", &"OBJECTIVES_BALL_SCORE" );
	}
	
	setObjectiveHintText( "allies", &"OBJECTIVES_BALL_HINT" );
	setObjectiveHintText( "axis", &"OBJECTIVES_BALL_HINT" );

	ball_default_origins();
	
	allowed[0] = level.gameType;	
	maps\mp\gametypes\_gameobjects::main( allowed );	
	
	level thread run_ball();	
	
	initSpawns();
}

onTimeLimit()
{
	winner = undefined;
	level.finalKillCam_winner = "none";
	
	if (game["status"] == "halftime" || game["status"] == "overtime_halftime" )
	{
		if ( game["teamScores"]["axis"] > game["teamScores"]["allies"] )
		{
			level.finalKillCam_winner = "axis";
			winner = "axis";
		}
		else if ( game["teamScores"]["allies"] > game["teamScores"]["axis"] )
		{
			level.finalKillCam_winner = "allies";
			winner = "allies";
		}
		else
		{
			if(game["status"] == "halftime")
			{
				winner = "overtime";
			}
			else
			{
				assert(game["status"] == "overtime_halftime");
				if(IsDefined(game["ball_overtime_team"]))
				{
					//This team scored in the first half of overtime
					//so if time is expiring they scored faster then the other team
					winner = game["ball_overtime_team"];
				}
				else
				{
					winner = "tie";
				}
			}
		}
	}
	else if ( game["status"] == "overtime" )
	{
		winner = "overtime_halftime";
	}

	
	
	logString( "time limit, win: " + winner + ", allies: " + game["teamScores"]["allies"] + ", opfor: " + game["teamScores"]["axis"] );
	
	level thread maps\mp\gametypes\_gamelogic::endgame( winner, game[ "end_reason" ][ "time_limit_reached" ] );
}


initSpawns()
{
	//TOO MANY DOM SPAWNS
	if ( level.script == "mp_refraction" )
	{
		setdvar("scr_disableClientSpawnTraces", "1");
	}
	
	level.spawnMins = ( 0, 0, 0 );
	level.spawnMaxs = ( 0, 0, 0 );
	
	level.allies_start_spawn_name = "mp_ball_spawn_allies_start";
	level.axis_start_spawn_name = "mp_ball_spawn_axis_start";
	
	if ( !getSpawnArray( level.allies_start_spawn_name ).size )
	{
		level.allies_start_spawn_name = "mp_dom_spawn_allies_start";
		level.axis_start_spawn_name = "mp_dom_spawn_axis_start";
	}
	
	maps\mp\gametypes\_spawnlogic::addStartSpawnPoints( level.allies_start_spawn_name );
	maps\mp\gametypes\_spawnlogic::addStartSpawnPoints( level.axis_start_spawn_name );
	
	level.spawn_name = "mp_ball_spawn";
	if ( !getSpawnArray( level.spawn_name ).size )
		level.spawn_name = "mp_dom_spawn";
	
	maps\mp\gametypes\_spawnlogic::addSpawnPoints( "allies", level.spawn_name );
	maps\mp\gametypes\_spawnlogic::addSpawnPoints( "axis", level.spawn_name );
	
	
	a_origin = level.ball_goals[game["attackers"]].origin;
	
	allies_dist_from_a = 0;
	spawnPoints = maps\mp\gametypes\_spawnlogic::getSpawnpointArray( level.allies_start_spawn_name );
	foreach(point in spawnPoints)
	{
		allies_dist_from_a += Distance(point.origin, a_origin);
	}
	
	axis_dist_from_a = 0;
	spawnPoints = maps\mp\gametypes\_spawnlogic::getSpawnpointArray( level.axis_start_spawn_name );
	foreach(point in spawnPoints)
	{
		axis_dist_from_a += Distance(point.origin, a_origin);
	}
	
	level.start_spawns_near_attackers = allies_dist_from_a<axis_dist_from_a;

		
	level.mapCenter = maps\mp\gametypes\_spawnlogic::findBoxCenter( level.spawnMins, level.spawnMaxs );
	setMapCenter( level.mapCenter );
}


getSpawnPoint()
{
	spawnteam = self.pers["team"];
		
	if ( level.useStartSpawns && level.inGracePeriod )
	{
		if ( game["switchedsides"] )
			spawnTeam = getOtherTeam(spawnTeam);
	
		if ( !level.start_spawns_near_attackers )
			spawnTeam = getOtherTeam(spawnTeam);
		
		start_spawn_name = level.allies_start_spawn_name;
		if( spawnteam == "axis" )
			start_spawn_name = level.axis_start_spawn_name;
		
		spawnPoints = maps\mp\gametypes\_spawnlogic::getSpawnpointArray( start_spawn_name );
		spawnPoint = maps\mp\gametypes\_spawnlogic::getSpawnpoint_startSpawn( spawnPoints );
	}
	else
	{
		spawnPoints = maps\mp\gametypes\_spawnlogic::getTeamSpawnPoints( spawnteam );
		spawnPoint = maps\mp\gametypes\_spawnscoring::getSpawnpoint_awayFromEnemies( spawnPoints, spawnteam );
	}
	
	assert( isDefined(spawnpoint) );
	
	self maps\mp\gametypes\_spawnlogic::recon_set_spawnpoint( spawnpoint );
	
	return spawnpoint;
}

//self == player
onUnderWater( trigger )
{
	foreach(ball in level.balls)
	{
		if( isDefined(ball.carrier) && ball.carrier == self )
		{
			self.water_last_weapon = self maps\mp\gametypes\_gameobjects::getCarrierWeaponCurrent(ball);
			ball thread maps\mp\gametypes\_gameobjects::setDropped();
			return;
		}
	}
	
	if(	self GetCurrentWeapon() == CONST_BALL_WEAPON && isDefined(self.changingWeapon) )
	{
		self.water_last_weapon = self.changingWeapon;
	} 
	else if( IsDefined(self.pass_or_throw_active) && self.pass_or_throw_active ) //handle case of throwing the ball while going under water
	{
		weapons = self GetWeaponsListPrimaries();
		self.water_last_weapon = ter_op(weapons.size, weapons[0], undefined);
	}
}

onNormalDeath( victim, attacker, lifeId )
{
	if ( game["state"] == "postgame" && game["teamScores"][attacker.team] > game["teamScores"][level.otherTeam[attacker.team]] )
		attacker.finalKill = true;
}

ball_connect_watch()
{
	while(1)
	{
		level waittill("connected",player);
		player thread ball_player_on_connect();
		player thread ball_update_extrascore0();
	}
}

ball_player_on_connect()
{
	foreach(ball in level.balls)
	{
		ball ball_fx_start_player(self);
	}
}

run_ball()
{
	level.ball_starts = [];
	level.balls = [];

	num_balls = GetDvarInt("scr_ball_num_balls", 1);
	ball_create_ball_starts(num_balls);
	
	ball_create_team_goal("allies");
	ball_create_team_goal("axis");
	
	level._effect["ball_trail"] = LoadFX("vfx/trail/vfx_uplink_ball_trl");
	level._effect["ball_idle"] = LoadFX("vfx/unique/vfx_uplink_ball_idle");
	level._effect["ball_download"] = LoadFX("vfx/trail/vfx_uplink_ball_trl2");
	level._effect["ball_download_end"] = LoadFX("vfx/unique/vfx_uplink_ball_impact");
	level._effect["ball_goal_enemy"] = LoadFX("vfx/unique/vfx_uplink_goal"); 
	level._effect["ball_goal_friendly"] = LoadFX("vfx/unique/vfx_uplink_goal_friendly");
	level._effect["ball_goal_activated_enemy"] = LoadFX("vfx/unique/vfx_uplink_ball_score");
	level._effect["ball_goal_activated_friendly"] = LoadFX("vfx/unique/vfx_uplink_ball_score_friendly");
	level._effect["ball_teleport"] = LoadFX("vfx/unique/vfx_uplink_ball_glow");
	level._effect["ball_physics_impact"] = LoadFX("vfx/treadfx/footstep_dust");
	
	level thread ball_connect_watch();
	
	ball_init_map_min_max();

	for(i=0; i<num_balls && i<level.ball_starts.size; i++)
	{
		ball_spawn(i);
	}
	ball_goal_useObject();
	ball_goal_fx();
	
	gameFlagWait("prematch_done");
	
	ball_assign_team_spawns();
}

ball_default_origins()
{
	//Use Locations from DOM
	level.default_goal_origins = [];
	flags = getEntArray( "flag_primary", "targetname" );
	foreach ( flag in flags )
	{
		switch(flag.script_label)
		{
			case "_a":
				level.default_goal_origins[game["attackers"]] = flag.origin;
				break;
			case "_b":
				level.default_ball_origin = flag.origin;
				break;
			case "_c":
				level.default_goal_origins[game["defenders"]] = flag.origin;
				break;				
		}
	}
}

ball_init_map_min_max()
{
	level.ball_mins = (1000,1000,1000);
	level.ball_maxs = (-1000,-1000,-1000);
	
	all_nodes = GetAllNodes();
	
	if(all_nodes.size>0)
	{
		foreach(node in all_nodes)
		{
			level.ball_mins = maps\mp\gametypes\_spawnlogic::expandMins( level.ball_mins, node.origin );
			level.ball_maxs = maps\mp\gametypes\_spawnlogic::expandMaxs( level.ball_maxs, node.origin );
		}
	}
	else
	{
		level.ball_mins = level.spawnMins;
		level.ball_maxs = level.spawnMaxs;
	}
}


ball_goal_useObject()
{
	foreach(team, goal in level.ball_goals)
	{
		goal.trigger = spawn( "trigger_radius", goal.origin - (0,0,goal.radius), 0, goal.radius, goal.radius*2 );
		
		goal.useObject = maps\mp\gametypes\_gameobjects::createUseObject(team, goal.trigger, [], (0,0,goal.radius*2.1));
		goal.useObject.goal = goal;
		goal.useObject maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_ball_defend" );
		goal.useObject maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_ball_goal" );
		goal.useObject maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_ball_defend" );
		goal.useObject maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_ball_goal" );
		if (team == "allies")
			setMLGIcons( goal.useObject, "waypoint_esports_uplink_blue" );
		else
			setMLGIcons( goal.useObject, "waypoint_esports_uplink_red" );
		goal.useObject maps\mp\gametypes\_gameobjects::setVisibleTeam( "any" );
		goal.useObject maps\mp\gametypes\_gameobjects::allowUse("enemy");
		goal.useObject maps\mp\gametypes\_gameobjects::setKeyObject( level.balls );
		goal.useObject maps\mp\gametypes\_gameobjects::setUseTime( 0 );
		goal.useObject.onUse = ::ball_carrier_touched_goal;
		goal.useObject.canUseObject = ::ball_goal_can_use;
		
		goal.killcamEnt = spawn( "script_model", goal.origin );
		goal.killcamEnt SetScriptMoverKillCam( "large explosive" );
	}
}

ball_assign_team_spawns()
{
	spawnPoints = maps\mp\gametypes\_spawnlogic::getSpawnpointArray( level.spawn_name );
	
	level.teamSpawnPoints["axis"] 	= [];
	level.teamSpawnPoints["allies"] = [];
	
	allies_goal = level.ball_goals["allies"].ground_origin;
	axis_goal = level.ball_goals["axis"].ground_origin;
	
	foreach(spawnPoint in spawnPoints)
	{
		allies_dist = ball_get_path_dist(allies_goal, spawnPoint.origin);
		axis_dist = ball_get_path_dist(axis_goal, spawnPoint.origin);
		
		spawnPoint.teamBase = ter_op(allies_dist<axis_dist, "allies", "axis");
		
		level.teamSpawnPoints[spawnPoint.teamBase][level.teamSpawnPoints[spawnPoint.teamBase].size] = spawnPoint;

	}
	
	/#
	level thread ball_debug();
	#/
}

ball_get_path_dist(from_origin, to_origin)
{
	if( maps\mp\gametypes\_spawnlogic::isPathDataAvailable() )
	{
		dist = GetPathDist( from_origin, to_origin, 999999 );
		if( IsDefined(dist) && (dist >= 0) )
			return dist;
	}
	
	// fail safe for bad pathing data		
	return distance( from_origin, to_origin );
}

ball_goal_fx()
{
	foreach(team, goal in level.ball_goals)
	{
		goal.score_fx["friendly"] = SpawnFx(getfx("ball_goal_activated_friendly"), goal.origin, (1,0,0));
		goal.score_fx["enemy"] = SpawnFx(getfx("ball_goal_activated_enemy"), goal.origin, (1,0,0));
	}
	
	level thread ball_play_fx_joined_team();
	foreach(player in level.players)
	{
		ball_goal_fx_for_player(player);
	}
}

ball_spawn(index)
{
	assert(!IsDefined(level.ball));
	
	spawn_origin = level.ball_starts[index];
	
	ball = spawn("script_model", spawn_origin.origin );
	ball SetModel("prop_drone_sphere");
	ball thread physics_impact_watch();
	
	radius =  24;
	trigger = GetEnt("ball_pickup_"+(index+1), "targetname");
	if ( IsDefined(trigger) )
		trigger.origin = ball.origin;
	else
		trigger = spawn( "trigger_radius", ball.origin - (0,0,radius/2), 0, radius, radius );
	trigger EnableLinkTo();
	
	trigger LinkTo(ball);
	trigger.no_moving_platfrom_unlink = true;
	
	visuals = [ball];
	
	ball_obj = maps\mp\gametypes\_gameobjects::createCarryObject( "any", trigger, visuals, (0,0,32) );
	ball_obj.objectiveOnVisuals = true;
	ball_obj maps\mp\gametypes\_gameobjects::allowCarry( "any" );
	ball_obj ball_waypoint_neutral();
	ball_obj maps\mp\gametypes\_gameobjects::setVisibleTeam( "any" );
	ball_obj.objIDPingEnemy = true;
	ball_obj.objPingDelay = 1.0;
	ball_obj.allowWeapons = false;
	ball_obj.carryWeapon = CONST_BALL_WEAPON;
	ball_obj.keepCarryWeapon = true;
	ball_obj.waterBadTrigger = false;
	ball_obj.visualGroundOffset = (0,0,30); //Drop height offset
	ball_obj.canUseObject = ::ball_can_pickup;
	ball_obj.onPickup = ::ball_on_pickup;
	ball_obj.setDropped = ::ball_set_dropped;
	ball_obj.onReset = ::ball_on_reset;
	ball_obj.carryWeaponThink = ::ball_pass_or_shoot;
	ball_obj.in_goal = false;
	ball_obj.lastCarrierScored = false;
	ball_obj.requiresLOS = true;
	
	ball_obj ball_assign_start(spawn_origin);

	level.balls[level.balls.size] = ball_obj;
	
	ball_obj ball_fx_start();
	ball_obj thread ball_location_hud(index);

	SetOmnvar( "ui_mlg_game_mode_status_1", -1 );
	SetOmnvar( "ui_mlg_game_mode_status_2", -1 );
	SetOmnvar( "ui_mlg_game_mode_status_3", ballIconNeutral );
}

physics_impact_watch()
{
	self endon("death");
	
	while(1)
	{
		self waittill("physics_impact", position, normal, velocity, surface);
		
		//Print("Impact("+GetTime()+") p:" + position + " n:" + velocity + " v:" + velocity + " s:" + surface + "\n");
		fxID = level._effect["ball_physics_impact"];
		if(IsDefined(surface) && IsDefined(level._effect["ball_physics_impact_"+surface]))
		   fxID = level._effect["ball_physics_impact_"+surface];
		   
		PlayFX(fxID, position, normal );
		
		wait .3;
	}
}

ball_location_hud(index)
{
	if( index > 4 || index < 0 )
	{
		AssertEx( false, "Only 1-5 balls are supported, need to update omnvars to add more." );
		return;
	}
	
	while(1)
	{
		result = self waittill_any_return("pickup_object", "dropped", "reset");
		switch(result)
		{
			case "pickup_object":
				SetOmnvar( "ui_uplink_ball_carrier"+(index + 1), self.carrier GetEntityNumber() );
				break;
			case "dropped":
				SetOmnvar( "ui_uplink_ball_carrier"+(index + 1), -2 );
				break;
			case "reset":
				SetOmnvar( "ui_uplink_ball_carrier"+(index + 1), -1 );
				break;
			default:
				break;
		}
	}
}

ball_waypoint_neutral()
{
	self maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_ball" );
	self maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_ball" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_ball" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_ball" );
	setMLGIcons( self, "waypoint_esports_uplink_ball_white" );
}

ball_waypoint_held()
{
	self maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_ball_friendly" );
	self maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_ball_enemy" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_ball_friendly" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_ball_enemy" );
	if (self.ownerTeam == "allies")
		setMLGIcons( self, "waypoint_esports_uplink_ball_blue" );
	else
		setMLGIcons( self, "waypoint_esports_uplink_ball_red" );
}

ball_waypoint_download()
{
	self maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_ball_download" );
	self maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_ball_download" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_ball_download" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_ball_download" );
	setMLGIcons( self, "waypoint_esports_uplink_ball_white" );
}

ball_waypoint_upload()
{
	self maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_ball_upload" );
	self maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_ball_upload" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_ball_upload" );
	self maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_ball_upload" );
	setMLGIcons( self, "waypoint_esports_uplink_ball_white" );
}

ball_dont_interpolate()
{
	self.visuals[0] DontInterpolate();
	self.ball_fx_active = false; //DontInterpolate kill fx so need to pretent they were turned off.
}

ball_fx_start()
{
	if (!self ball_fx_active())
	{
		ball = self.visuals[0];
		PlayFXOnTag(getfx("ball_trail"), ball, "tag_origin");
		PlayFXOnTag(getfx("ball_idle"), ball, "tag_origin");
		self.ball_fx_active = true;
	}
}

ball_fx_start_player(player)
{
	if(self ball_fx_active())
	{
		ball = self.visuals[0];
		PlayFXOnTagForClients(getfx("ball_trail"), ball, "tag_origin", player);
		PlayFXOnTagForClients(getfx("ball_idle"), ball, "tag_origin", player);
	}
}

ball_fx_stop()
{
	if ( self ball_fx_active() )
	{
		ball = self.visuals[0];
		StopFXOnTag(getfx("ball_trail"), ball, "tag_origin");
		killFXOnTag(getfx("ball_idle"), ball, "tag_origin");
	}
	self.ball_fx_active = false;
}

ball_fx_active()
{
	return IsDefined(self.ball_fx_active) && self.ball_fx_active;
}

ball_pass_or_shoot()
{
	self endon("disconnect");
	
	self thread ball_pass_watch();
	self thread ball_shoot_watch();
	
	self.carryObject waittill("dropped");
}

ball_pass_watch()
{
	level endon ( "game_ended" );

	self endon ( "disconnect" );
	self endon ( "death" );
	self endon ( "drop_object" );

	while(1)
	{
		self waittill("ball_pass", weapon);
		
		if( weapon != CONST_BALL_WEAPON )
			continue;
		
		if ( !isDefined(self.pass_target) )
		{
			self IPrintLnBold("No Pass Target");
			continue;
		}
		break;
	}

	if(isDefined(self.carryObject))
	{
		self thread ball_pass_or_throw_active();
		pass_target = self.pass_target;
		last_target_origin = self.pass_target.origin;
		wait .15;
		
		if ( IsDefined(self.pass_target) )
			pass_target = self.pass_target;
		
		self.carryObject thread ball_pass_projectile(self, pass_target, last_target_origin);
	}
}

ball_pass_projectile(passer, target, last_target_origin)
{
	ball_set_dropped(true);
	
	if(IsDefined(target))
		last_target_origin = target.origin;
	
	offset = (0,0,40);
	pass_dir = VectorNormalize((last_target_origin+offset) - self.visuals[0].origin);
	pass_vel = pass_dir * 1000;
	
	self.projectile = MagicGrenadeManual("gamemode_ball", self.visuals[0].origin, pass_vel, 30, passer, true);
	if(IsDefined(target))
		self.projectile Missile_SetTargetEnt( target );
	self.visuals[0] LinkTo(self.projectile);
	self ball_dont_interpolate();
	
	self ball_create_killcam_ent();
	
	self ball_clear_contents(); //Prevent magic grenade from hitting the ball visuals
	
	self thread ball_on_projectile_hit_client();
	self thread ball_on_projectile_death();
	self thread ball_pass_touch_goal();
}

ball_create_killcam_ent()
{
	if(IsDefined(self.killcamEnt))
		self.killcamEnt Delete();
	self.killcamEnt = spawn( "script_model", self.visuals[0].origin );
	self.killcamEnt linkTo(self.visuals[0]);
	self.killcamEnt SetContents(0);
	self.killcamEnt SetScriptMoverKillCam( "explosive" );
}

ball_clear_contents()
{
	self.visuals[0].old_contents = self.visuals[0] SetContents(0);
}

ball_restore_contents()
{
	if(IsDefined(self.visuals[0].old_contents))
	{
		self.visuals[0] SetContents(self.visuals[0].old_contents);
		self.visuals[0].old_contents = undefined;
	}	
}

ball_on_projectile_hit_client()
{
	self endon("pass_end");
	self.projectile waittill("projectile_impact_player", player);
	self.trigger notify("trigger", player);
}

ball_on_projectile_death()
{
	self.projectile waittill("death");
	if(!IsDefined(self.carrier) && !self.in_goal)
	{
		self ball_physics_launch((0,0,10));
	}
	self ball_restore_contents();
	
	ball = self.visuals[0];
	ball notify("pass_end");
}

ball_shoot_watch()
{
	level endon ( "game_ended" );

	self endon ( "disconnect" );
	self endon ( "death" );
	self endon ( "drop_object" );
	
	SetDevDvarIfUninitialized("scr_ball_shoot_extra_pitch", -12);
	SetDevDvarIfUninitialized("scr_ball_shoot_force", 320);
	
	extra_pitch 	= GetDvarFloat("scr_ball_shoot_extra_pitch", -12);
	force			= GetDvarFloat("scr_ball_shoot_force", 320);
	
	while(1)
	{
		self waittill("weapon_fired", weapon);
		
		if( weapon != CONST_BALL_WEAPON )
			continue;
		
		break;
	}
		
	if ( IsDefined( self.carryObject ) )
	{
		playerAngles = self GetPlayerAngles();
		playerAngles += (extra_pitch,0,0);
		playerAngles = (Clamp(playerAngles[0], -85, 85), playerAngles[1], playerAngles[2]);
		dir = AnglesToForward(playerAngles);
		self thread ball_pass_or_throw_active();
		
		wait 0.25;
		
		self PlaySound("mp_ul_ball_throw");
		self thread ball_check_pass_kill_pickup( self.carryObject );
		self.carryObject ball_create_killcam_ent();
		self.carryObject thread ball_physics_launch_drop(dir*force, self);
	}
}

ball_pass_or_throw_active()
{
	self endon("death");
	self endon("disconnect");
	
	self.pass_or_throw_active = true;
	
	self AllowMelee( false );
	while( CONST_BALL_WEAPON == self GetCurrentWeapon() )
		waitframe();
	
	self AllowMelee( true );
	self.pass_or_throw_active = false;
}

ball_physics_launch_drop(force, droppingPlayer)
{
	ball_set_dropped(true);
	ball_physics_launch(force, droppingPlayer);
}

ball_physics_launch(force, droppingPlayer)
{
	visuals = self.visuals[0];
	visuals.origin_prev = undefined;
	
	self ball_fx_start();
	
	visuals PhysicsLaunchServer( visuals.origin, force );
	
	self thread ball_physics_out_of_level();
	self thread ball_physics_timeout( droppingPlayer );
	self thread ball_physics_bad_trigger_watch();
	self thread ball_physics_touch_goal();
	self thread ball_physics_touch_cant_pickup_player(droppingPlayer);
}

ball_return_home()
{
	self.in_goal = false;
	ball = self.visuals[0];
	playSoundAtPos(ball.origin, "uplink_out_of_bounds");
	PlayFX(getfx("ball_teleport"), ball.origin);
	if(IsDefined(self.carrier))
		self.carrier delayThread(0.05, ::player_update_pass_target_hudoutline);
	thread maps\mp\gametypes\_gameobjects::returnHome();
	
}

ball_physics_out_of_level()
{
	self endon ( "reset" );
	self endon ( "pickup_object" );
	
	ball = self.visuals[0];
	
	buffer_max[0] = 200;
	buffer_max[1] = 200;
	buffer_max[2] = 1000;
	
	buffer_min[0] = 200;
	buffer_min[1] = 200;
	buffer_min[2] = 200;

	while(1)
	{
		for(i=0; i<2; i++)
		{
			if ( ball.origin[i] > (level.ball_maxs[i]+buffer_max[i]) )
			{
				ball_return_home();
				return;
			}
			if ( ball.origin[i] < (level.ball_mins[i]-buffer_min[i]) )
			{
				ball_return_home();
				return;
			}
		}
		waitframe();
	}
}

ball_physics_timeout( droppingPlayer )
{
	self endon( "reset" );
	self endon( "pickup_object" );
	self endon( "score_event" );
	
	waitTime = GetDvarFloat( "scr_ball_reset_time",  CONST_BALL_RESET_TIME );

	minWaitTimeForTransition = 10;
	transitionTime = 3;
	
	if ( waitTime >= minWaitTimeForTransition )
	{
		SetOmnvar( "ui_mlg_game_mode_status_1", transitionTime );
		SetOmnvar( "ui_mlg_game_mode_status_2", -1 );

		// currently droppingPlayer is only defined in one code path: the path taken when the player throws the ball
		// I'm assuming if we didn't throw it, then we died.  
		// Passing and running into the goal area don't seem to go through this code....
		lastValue = GetOmnvar( "ui_mlg_game_mode_status_3" );
		if ( !IsDefined( droppingPlayer ) )
		{
			if ( lastValue == ballIconSentinelOwned || lastValue == ballIconSentinelThrown || lastValue == ballIconSentinelDropped )
				SetOmnvar( "ui_mlg_game_mode_status_3", ballIconSentinelDropped );
			else
				SetOmnvar( "ui_mlg_game_mode_status_3", ballIconAtlasDropped );
		}
		else
		{
			if ( lastValue == ballIconSentinelOwned || lastValue == ballIconSentinelThrown || lastValue == ballIconSentinelDropped )
				SetOmnvar( "ui_mlg_game_mode_status_3", ballIconSentinelThrown );
			else
				SetOmnvar( "ui_mlg_game_mode_status_3", ballIconAtlasThrown );
		}

		wait transitionTime;
		waitTime -= transitionTime;
	}

	SetOmnvar( "ui_mlg_game_mode_status_1", int( waitTime ) );
	SetOmnvar( "ui_mlg_game_mode_status_2", -1 );
	SetOmnvar( "ui_mlg_game_mode_status_3", ballIconHidden );

	wait waitTime;
	ball_return_home();
}

ball_physics_bad_trigger_watch()
{
	self.visuals[0] endon("physics_finished");
	
	self thread ball_physics_bad_trigger_at_rest();
	
	wait .1;
	
	while(1)
	{
		if ( maps\mp\gametypes\_gameobjects::isTouchingBadTrigger() )
		{
			ball_return_home();
			return;
		}
		
		waitframe();
	}
}

ball_physics_bad_trigger_at_rest()
{
	self endon("pickup_object");
	self endon("reset");
	self endon("score_event");
	
	visuals = self.visuals[0];
	visuals endon("death");
	
	visuals waittill("physics_finished");
	
	if ( maps\mp\gametypes\_gameobjects::isTouchingBadTrigger() )
	{
		ball_return_home();
		return;
	}
}

ball_physics_touch_goal()
{
	ball = self.visuals[0];
	
	ball endon("physics_finished");
	
	ball_touch_goal_watch( ball );
}

ball_physics_touch_cant_pickup_player(droppingPlayer)
{
	ball = self.visuals[0];
	trigger = self.trigger;
	
	ball endon("physics_finished");
	
	while(1)
	{
		trigger waittill("trigger", player);
		//Dont stop on the throwing player
		if ( IsDefined(droppingPlayer) && droppingPlayer == player && player player_no_pickup_time() )
		{
			continue;
		}
		
		if ( self.dropTime >= GetTime()  )
		{
			continue;
		}
		
		if(	!ball_can_pickup( player ) )
		{
			self thread ball_physics_fake_bounce();
		}
	}
}

ball_physics_fake_bounce()
{
	ball = self.visuals[0];
	if ( !ball PhysicsIsActive() )
		return;
	
	vel = ball PhysicsGetLinVel();
	bounceForce = Length(vel)/10;
	bounceDir = -1*VectorNormalize(vel);
	
	ball PhysicsStop();
	ball PhysicsLaunchServer(ball.origin, bounceDir*bounceForce);
}

ball_pass_touch_goal()
{
	ball = self.visuals[0];
	
	ball endon("pass_end");
	
	ball_touch_goal_watch( ball );
}

ball_touch_goal_watch( ball )
{
	while(1)
	{
		foreach(team, goal in level.ball_goals)
		{
			if ( self.lastCarrierTeam == team )
				continue;
			
			if( !goal.useObject ball_goal_can_use() )
				continue;
			
			dist = Distance(ball.origin, goal.origin);
			if ( dist <= goal.radius )
			{
				thread ball_touched_goal(goal);
				return;
			}
			
			if ( isdefined(ball.origin_prev) )
			{
				result = line_interect_sphere(ball.origin_prev, ball.origin, goal.origin, goal.radius);
				if ( result )
				{
					thread ball_touched_goal(goal);
					return;
				}
				
			}
		}
		
		waitframe();
	}
}

ball_goal_can_use(player)
{
	goal = self.goal;
	
	if( goal.ball_in_goal )
		return false;
	
	return true;
}

ball_carrier_touched_goal(player)
{
	score = GetDvarInt( "scr_ball_points_touchdown",  CONST_POINTS_TOUCHDOWN );
	
	if ( !IsDefined(player) || !IsDefined(player.carryObject) )
		return;

	player maps\mp\_events::touchdownEvent(score);
	ball_check_assist( player, true );
	player thread ball_update_extrascore0();
	
	team = self.goal.team;
	otherTeam = getOtherTeam(team);
	
	leaderDialog( "enemy_carry_score", team, "status" );
	leaderDialog( "ally_carry_score", otherTeam, "status" );

	if( should_record_final_score_cam(otherTeam, score) )
	{
		killcamentity = self.goal.killcamEnt;
		killcamentityindex = killcamentity GetEntityNumber();
		killcamentitystarttime = killcamentity.birthtime;
		if ( !IsDefined( killcamentitystarttime ) )
			killcamentitystarttime = 0;
		player.deathTime = GetTime();
		maps\mp\gametypes\_damage::recordFinalKillCam( 5.0, player, player, player GetEntityNumber(), killcamentityindex, killcamentitystarttime, "none", 0, 0, undefined, "score" );	
	}
	
	ball_play_score_fx(self.goal);
	
	ball_score_sound(otherTeam);
	
	if(IsDefined(player.shoot_charge_bar))
		player.shoot_charge_bar.inUse = false;
	
	ball = player.carryObject;
	ball.lastCarrierScored = true;
	
	ball ball_set_dropped(true);
	ball thread ball_score_event(self.goal);
	
	ball_give_score( otherTeam, score );
}

ball_update_extrascore0()
{
	waittillframeend;
	fieldgoals = getPlayerStat("fieldgoal");
	touchdowns = getPlayerStat("touchdown"); 
	
	fieldgoal_score = GetDvarInt( "scr_ball_points_fieldgoal",  CONST_POINTS_FIELDGOAL );
	touchdown_score = GetDvarInt( "scr_ball_points_touchdown",  CONST_POINTS_TOUCHDOWN );
	
	self setExtraScore0( fieldgoals*fieldgoal_score + touchdowns*touchdown_score );
}

should_record_final_score_cam(team, score_to_add)
{
	//Don't record kill cam if the team scoring would still be losing
	team_score = maps\mp\gametypes\_gamescore::_getTeamScore( team );
	other_team_score = maps\mp\gametypes\_gamescore::_getTeamScore( getOtherTeam(team) );
	
	return (team_score + score_to_add) >= other_team_score;
}

line_interect_sphere(line_start, line_end, sphere_center, sphere_radius)
{
	dir = VectorNormalize(line_end - line_start);
	
	a = VectorDot(dir,(line_start-sphere_center));
	a*=a;
	b = (line_start-sphere_center);
	b *= b;
	c = sphere_radius*sphere_radius;
	
	return (a-b+c)>=0;
}

ball_touched_goal(goal)
{
	ball_play_score_fx(goal);
	
	score = GetDvarInt( "scr_ball_points_fieldgoal",  CONST_POINTS_FIELDGOAL );
	
	team = goal.team;
	otherTeam = getOtherTeam(team);
	
	leaderDialog( "enemy_throw_score", team, "status" );
	leaderDialog( "ally_throw_score", otherTeam, "status" );
	
	if ( isDefined(self.lastCarrier) )
	{
		self.lastCarrierScored = true;
		//self.lastCarrier IPrintLnBold("" + score + " Point!");
		self.lastcarrier maps\mp\_events::fieldGoalEvent(score);
		ball_check_assist( self.lastcarrier, false );
		
		self.lastcarrier thread ball_update_extrascore0();
		
		if(IsDefined(self.killcamEnt) && should_record_final_score_cam( otherTeam, score) )
		{
			killcamentity = self.killcamEnt;
			killcamentityindex = killcamentity GetEntityNumber();
			killcamentitystarttime = killcamentity.birthtime;
			if ( !IsDefined( killcamentitystarttime ) )
				killcamentitystarttime = 0;
			player = self.lastCarrier;
			goal.killcamEnt.deathTime = GetTime();
			maps\mp\gametypes\_damage::recordFinalKillCam( 5.0, goal.killcamEnt, player, player GetEntityNumber(), killcamentityindex, killcamentitystarttime, "none", 0, 0, undefined, "score" );	
		}
	}
	
	if(IsDefined(self.killcamEnt))
	{
		self.killcamEnt Unlink();
	}
	
	ball_score_sound(otherTeam);
	
	self thread ball_score_event(goal);
	
	ball_give_score( otherTeam, score );

	SetOmnvar( "ui_mlg_game_mode_status_1", -1 );
	
	if ( isDefined(self.lastCarrier) )
		SetOmnvar( "ui_mlg_game_mode_status_2", self.lastCarrier GetEntityNumber() );
	else
		SetOmnvar( "ui_mlg_game_mode_status_2", -1 );

	if ( otherTeam == "allies" )
		SetOmnvar( "ui_mlg_game_mode_status_3", ballIconSentinelOwned );
	else
		SetOmnvar( "ui_mlg_game_mode_status_3", ballIconAtlasOwned );
}

ball_give_score( toTeam, score )
{
	level maps\mp\gametypes\_gamescore::giveTeamScoreForObjective( toTeam, score );
	
	if ( game["status"] == "overtime" )
	{
		game["ball_overtime_team"] = toTeam;
		game["round_time_to_beat"] = getMinutesPassed();
		level thread maps\mp\gametypes\_gamelogic::endGame( "overtime_halftime", game[ "end_reason" ][ "switching_sides" ] );
	}
	else if (game["status"] == "overtime_halftime")
	{
		team_score = maps\mp\gametypes\_gamescore::_getTeamScore( toTeam );
		other_team_score = maps\mp\gametypes\_gamescore::_getTeamScore( getOtherTeam(toTeam) );
	
		if( team_score >= other_team_score)	
			level thread maps\mp\gametypes\_gamelogic::endGame( toTeam, game[ "end_reason" ][ "score_limit_reached" ] );
	}
}

ball_score_event(goal)
{
	self notify("score_event");
	self.in_goal = true;
	goal.ball_in_goal = true;
	
	ball = self.visuals[0];
	
	if(IsDefined(self.projectile))
	{
		self.projectile Delete();
	}
	
	
	ball PhysicsStop();
	self maps\mp\gametypes\_gameobjects::allowCarry( "none" );
	self ball_waypoint_upload();
	
	move_to_center_time = .4;
	move_up_time = 1.2;
	rotate_time = 1.0;
	
	playSoundAtPos(goal.origin, "uplink_goal_point");
	
	in_goal_time = move_to_center_time+rotate_time;
	total_time = in_goal_time+move_up_time;
	ball MoveTo(goal.origin, move_to_center_time, 0, move_to_center_time);
	ball RotateVelocity( (1080,1080,0), total_time, total_time, 0);
	
	wait in_goal_time;
	
	goal.ball_in_goal = false;
	
	ball MoveZ(4000, move_up_time, move_up_time*.1, 0);
	wait move_up_time;
	
	self maps\mp\gametypes\_gameobjects::allowCarry( "any" );
	
	ball_return_home();
}

ball_check_assist( player, wasDunk )
{
	//Was the player passed to
	if(!IsDefined(player.passTime) || !IsDefined(player.passPlayer))
		return;

	//Was it a recent pass
	if(player.passTime+3000 < GetTime())
		return;
	
	player.passPlayer maps\mp\_events::ballScoreAssistEvent();
	
	if( wasDunk )
		player maps\mp\gametypes\_missions::processChallenge( "ch_ball_alleyoop" );
}

ball_play_score_fx(goal)
{
	//Update who can see what fx
	goal.score_fx["friendly"] Hide();
	goal.score_fx["enemy"] Hide();
	
	foreach(player in level.players)
	{
		team = ball_get_view_team(player);
		
		if( team == goal.team )
		{
			goal.score_fx["friendly"] ShowToPlayer(player);
		}
		else
		{
			goal.score_fx["enemy"] ShowToPlayer(player);
		}
	}
	
	TriggerFx(goal.score_fx["friendly"]);
	TriggerFx(goal.score_fx["enemy"]);
}

ball_score_sound(scoring_team)
{
	ball_play_local_team_sound(scoring_team, "mp_obj_notify_pos_lrg", "mp_obj_notify_neg_lrg");
}

ball_play_local_team_sound(team, teamSound, otherTeamSound)
{
	otherTeam = getOtherTeam(team);
	foreach(player in level.players)
	{
		if( player.team == team )
			player PlayLocalSound( teamSound );
		else if ( player.team == otherTeam )
			player PlayLocalSound( otherTeamSound );
	}
}

ball_can_pickup(player)
{
	if(!isDefined(player))
		return false;
	
	if(IsDefined(player.underWater) && player.underWater)
		return false;
	
	if ( IsDefined(self.dropTime) && self.dropTime >= GetTime() )
		return false;
	
	if ( !player isWeaponEnabled() )
		return false;
	
	if ( player IsUsingTurret() )
		return false;
	
	if ( IsDefined(player.manuallyJoiningKillStreak) && player.manuallyJoiningKillStreak )
		return false;
	
	if ( IsDefined( player.using_remote_turret ) && player.using_remote_turret )
		return false;
	
	currentWeapon = player GetCurrentWeapon();
	if(isDefined(currentWeapon))
	{
		if( !valid_ball_pickup_weapon( currentWeapon ) )
			return false;
	}
	
	nextWeapon = player.changingWeapon;
	if(IsDefined(nextWeapon) && player IsSwitchingWeapon() )
	{
		if( !valid_ball_pickup_weapon( nextWeapon ) )
			return false;
	}
	
	if( isDefined(player.exo_shield_on) && player.exo_shield_on == true )
		return false;
	
	if( player isJuggernaut() )
		return false;
	
	if ( player player_no_pickup_time() )
		return false;
	
	return true;
}

valid_ball_pickup_weapon( weaponName )
{
	if( weaponName == "none" )
		return false;
	
	if( weaponName == CONST_BALL_WEAPON )
		return false;
	
	if( isKillstreakWeapon( weaponName ) )
		return false;
	
	//The weapon the player has while entering the goliath
	if ( weaponName == "iw5_combatknifegoliath_mp" )
		return false;
	
	return true;
}

player_no_pickup_time()
{
	return isDefined(self.noPickupTime) && self.noPickupTime > GetTime();
}

ball_on_pickup(player)
{
	level.useStartSpawns = false;
	
	//Physics objects get linked to entities if they come to rest on them
	linkedParent = self.visuals[0] GetLinkedParent();
	if(IsDefined(linkedParent))
		self.visuals[0] unlink();
	
	self.visuals[0] PhysicsStop();
	self.visuals[0] maps\mp\_movers::notify_moving_platform_invalid();
	
	//Undo _gameobjects hide() and use ghost instead
	self.visuals[0] Show();
	self.visuals[0] Ghost();
	
	self.trigger maps\mp\_movers::stop_handling_moving_platforms();
	
	self.current_start.in_use = false;
	
	pass = false;
	if(IsDefined(self.projectile))
	{
		pass = true;
		self.projectile Delete();
	}
	
	team = player.team;
	otherTeam = getOtherTeam(player.team);
	
	if( pass )
	{
		if( self.lastCarrierTeam == player.team )
		{
			leaderDialog( "pass_complete", team, "status" );
			player.passTime = GetTime();
			player.passPlayer = self.lastcarrier;
		}
		else
		{
			leaderDialog( "pass_intercepted", team, "status" );
			player maps\mp\_events::interceptionEvent();
		}
	}
	else
	{
		leaderDialog( "ally_own_drone", team, "status", [player] );
		player leaderDialogOnPlayer( "you_own_drone", "status" );
		
		leaderDialog( "enemy_own_drone", otherTeam, "status" );
	}
	
	player PlaySound("mp_ul_ball_catch");
	ball_play_local_team_sound(team, "mp_obj_notify_pos_sml", "mp_obj_notify_neg_sml");

	self ball_fx_stop();
	
	self.lastCarrierScored = false;
	self.lastcarrier = player;
	self.lastCarrierTeam = player.team;
	
	self.ownerTeam = player.team;
	self ball_waypoint_held();
	
	player SetWeaponAmmoClip( CONST_BALL_WEAPON, 1 );
	
	player givePerk("specialty_ballcarrier", false);
	player.ball_carried = self;
	player.objective = 1;

	SetOmnvar( "ui_mlg_game_mode_status_1", -1 );
	SetOmnvar( "ui_mlg_game_mode_status_2", self.carrier GetEntityNumber() );
	if ( self.carrier.team == "allies" )
		SetOmnvar( "ui_mlg_game_mode_status_3", ballIconSentinelOwned );
	else
		SetOmnvar( "ui_mlg_game_mode_status_3", ballIconAtlasOwned );
	
	player.hasPerkSprintFire = player HasPerk("specialty_sprintfire", true);
	player givePerk("specialty_sprintfire", false );

	player _disableUsability();
	
	player maps\mp\killstreaks\_coop_util::playerStopPromptForStreakSupport();
	
	armorValue = GetDvarInt( "scr_ball_armor", 100 );
	if ( armorValue > 0 )
		player thread maps\mp\perks\_perkfunctions::setLightArmor(armorValue);
	else
		player thread maps\mp\perks\_perkfunctions::unsetLightArmor();
	
	player thread player_update_pass_target(self);
	//maps\mp\_utility::playSoundOnPlayers( game["bomb_recovered_sound"], game["attackers"] );
	
	maps\mp\gametypes\_gamelogic::setHasDoneCombat( player, true );
}

ball_check_pass_kill_pickup( carryObj )
{
	self endon("death");
	self endon("disconnect");
	
	carryObj endon("reset");
	
	timer = spawnStruct();
	timer endon("timer_done");
	
	timer thread timer_run(1.5);	
	carryObj waittill("pickup_object");
	timer timer_cancel();
	
	if(!IsDefined(carryObj.carrier) || carryObj.carrier.team == self.team)
		return;
	
	carryObj.carrier endon("disconnect");
	
	timer thread timer_run(5);
	carryObj.carrier waittill("death", attacker);
	timer timer_cancel();
	
	if(!IsDefined(attacker) || attacker != self)
		return;
	
	timer thread timer_run(2);
	carryObj waittill("pickup_object");
	timer timer_cancel();
	
	if( IsDefined(carryObj.carrier) && carryObj.carrier == self)
		self maps\mp\_events::passKillPickupEvent();
}

timer_run(time)
{
	self endon("cancel_timer");
	wait time;
	self notify("timer_done");
}

timer_cancel()
{
	self notify("cancel_timer");
}

player_update_pass_target(ballObj)
{
	self endon("disconnect");
	self endon("cancel_update_pass_target");
	
	self player_update_pass_target_hudoutline();
	self childthread player_joined_update_pass_target_hudoutline();
	test_dot = 0.8;
	while(1)
	{
		new_target = undefined;
		
		if ( !self IsOnLadder() )
		{
			playerDir = AnglesToForward( self GetPlayerAngles() );
			playerEye = self GetEye();
			
			possible_pass_targets = [];
			foreach(target in level.players)
			{
				if ( target.team != self.team )
					continue;
			
				if ( !isReallyAlive( target ) )
					continue;
				
				if( !ballObj ball_can_pickup(target) )
					continue;
				
				targetEye = target GetEye();
				distSq = DistanceSquared( targetEye, playerEye );
				if ( distSq > CONST_MAX_PASS_DISTANCE )
					continue;

				dirToTarget = VectorNormalize( targetEye - playerEye );
				dot = VectorDot( playerDir, dirToTarget );
				if ( dot > test_dot )
				{
					target.pass_dot = dot;
					target.pass_origin = targetEye;
					possible_pass_targets[possible_pass_targets.size] = target;
				}
			}
			
			possible_pass_targets = quickSort(possible_pass_targets, ::compare_player_pass_dot);
			
			foreach(target in possible_pass_targets)
			{
				if ( SightTracePassed(playerEye, target.pass_origin, false, self, target ) )
				{
					new_target = target;
					break;
				}
			}
		}
		
		self player_set_pass_target(new_target);
		
		waitframe();
	}
}

player_joined_update_pass_target_hudoutline()
{
	while(1)
	{
		level waittill( "joined_team", player );
		self player_update_pass_target_hudoutline();
	}
}

player_update_pass_target_hudoutline()
{
	if(!IsDefined(self))
		return;
	
	self HudOutlineDisableForClients( level.players );
	foreach(player in level.players)
	{
		player HudOutlineDisableForClient(self);
	}
	
	team_players = [];
	other_team_players = [];
	other_team = getOtherTeam(self.team);

	foreach(player in level.players)
	{
		if( player == self )
			continue;
		
		if( player.team == self.team )
			team_players[team_players.size] = player;
		else if ( player.team == other_team )
			other_team_players[other_team_players.size] = player;
	}
	
	if(IsDefined(self.carryObject))
	{
		foreach(player in team_players)
		{
			isPassTarget = IsDefined(self.pass_target) && (self.pass_target == player);
			if(!isPassTarget)
				player HudOutlineEnableForClient(self, 4, false);
		}
		
		if(IsDefined(self.pass_target))
			self.pass_target HudOutlineEnableForClient(self, 5, false);
		
		if(other_team_players.size>0)
			self HudOutlineEnableForClients( other_team_players, 0, true);
		
		if(team_players.size>0)
			self HudOutlineEnableForClients( team_players, 5, false );
	}
}

player_set_pass_target(new_target)
{
	//No Change
	if ( IsDefined(self.pass_target) && IsDefined(new_target) && self.pass_target == new_target )
		return;
	
	if ( !IsDefined(self.pass_target) && !IsRemovedEntity(self.pass_target) && !IsDefined(new_target) )
		return;
	
	self player_clear_pass_target();
	
	if(IsDefined(new_target))
	{
		offset = ( 0, 0, 80 );
		self.pass_icon = new_target maps\mp\_entityheadIcons::setHeadIcon( self, "waypoint_ball_pass", offset, 10, 10, false, 0.05, false, true, false, false, "tag_origin" );
	
		self.pass_target = new_target;
		
		team_players = [];
		foreach(player in level.players)
		{
			if(player.team == self.team && player != self && player != new_target)
				team_players[team_players.size] = player;
		}
		
		self SetClientOmnvar( "ui_uplink_can_pass", true );
		
		self SetBallPassAllowed(true);
	}
	
	self player_update_pass_target_hudoutline();
}

player_clear_pass_target()
{
	if(IsDefined(self.pass_icon))
		self.pass_icon Destroy();
	
	self SetClientOmnvar( "ui_uplink_can_pass", false );
	
	team_players = [];
	foreach(player in level.players)
	{
		if( player.team == self.team && player != self )
			team_players[team_players.size] = player;
	}
	
	self.pass_target = undefined;
	self SetBallPassAllowed(false);
	
	self player_update_pass_target_hudoutline();
}

compare_player_pass_dot(left, right)
{
	return left.pass_dot>=right.pass_dot;
}

ball_set_dropped(skip_physics)
{	
	if(!IsDefined(skip_physics))
		skip_physics = false;
	
	self.isResetting = true;
	self.dropTime = GetTime();
	
	self notify ( "dropped" );

	carrier = self.carrier;
	if(IsDefined(carrier) && carrier.team != "spectator")
	{
		dropOrigin = carrier.origin;
	}
	else
	{
		dropOrigin = self.safeOrigin;
	}
	dropOrigin += (0,0,40);
	dropAngles = (0,0,0);
	
	for ( index = 0; index < self.visuals.size; index++ )
	{
		self.visuals[index].origin = dropOrigin;
		self.visuals[index].angles = dropAngles;
		self.visuals[index] show();
	}
	self.trigger.origin = dropOrigin;
	self ball_dont_interpolate();
	
	self.curOrigin = self.trigger.origin;
	
	ball_carrier_cleanup();
	
	self ball_fx_start();
	
	self.ownerTeam = "any";
	self ball_waypoint_neutral();
	
	self maps\mp\gametypes\_gameobjects::clearCarrier();
	if(isDefined(carrier))
		carrier player_update_pass_target_hudoutline();
	
	self maps\mp\gametypes\_gameobjects::updateCompassIcons();
	self maps\mp\gametypes\_gameobjects::updateWorldIcons();
	
	self.isResetting = false;
	
	if(!skip_physics)
	{
		team = self.lastCarrierTeam;
		otherTeam = getOtherTeam(team);
		leaderDialog( "ally_drop_drone", team, "status" );
		leaderDialog( "enemy_drop_drone", otherTeam, "status" );
		
		ball_physics_launch((0,0,80));
	}
	
	// Moving Platforms
	data = SpawnStruct();
	data.carryobject = self;
	data.deathOverrideCallback = ::ball_overrideMovingPlatformDeath;
	self.trigger thread maps\mp\_movers::handle_moving_platforms( data );

	return true;
}

ball_overrideMovingPlatformDeath( data )
{
	//data.carryobject ball_return_home();
}


ball_assign_random_start()
{	
	new_start = undefined;
	
	rand_starts = array_randomize(level.ball_starts);
	foreach(start in rand_starts)
	{
		if(start.in_use)
			continue;
		
		new_start = start;
		break;
	}

	if(!isDefined(new_start))
		return;
	
	ball_assign_start(new_start);
}

ball_assign_start(start)
{
	foreach(vis in self.visuals)
	{
		vis.baseOrigin = start.origin;
	}
	
	self.trigger.baseOrigin = start.origin;
	
	self.current_start = start;
	start.in_use = true;
}

ball_on_reset()
{
	self ball_assign_random_start();
	
	ball_model = self.visuals[0];
	ball_model maps\mp\_movers::notify_moving_platform_invalid();
	
	//Physics objects get linked to entities if they come to rest on them
	linkedParent = ball_model GetLinkedParent();
	if(IsDefined(linkedParent))
		ball_model unlink();
	
	ball_model PhysicsStop();
	self ball_dont_interpolate();
	
	if(IsDefined(self.projectile))
	{
		self.projectile Delete();
	}
	
	otherTeam = "none";
	team = self.lastCarrierTeam;
	if( IsDefined( team ) )
	otherTeam = getOtherTeam(team);
	
	ball_carrier_cleanup();
	
	SetOmnvar( "ui_mlg_game_mode_status_1", -1 );
	SetOmnvar( "ui_mlg_game_mode_status_2", -1 );
	SetOmnvar( "ui_mlg_game_mode_status_3", ballIconNeutral );

	self.trigger maps\mp\_movers::stop_handling_moving_platforms();
	
	self ball_waypoint_download();
	self maps\mp\gametypes\_gameobjects::setPosition( ball_model.baseOrigin + (0,0,4000), (0,0,0) );
	
	fall_time = 3;
	ball_model MoveTo(ball_model.baseOrigin, fall_time, 0, fall_time);
	ball_model RotateVelocity( (0,720,0), fall_time, 0, fall_time);
	
	playSoundAtPos(ball_model.baseOrigin, "uplink_ball_reset");
	
	if( !self.lastCarrierScored && IsDefined( team ) && IsDefined( otherTeam ) )
	{
		leaderDialog( "drone_reset", team, "status" );
		leaderDialog( "drone_reset", otherTeam, "status" );
		if(IsDefined(self.lastcarrier))
		{
			thread teamPlayerCardSplash( "callout_ballreset", self.lastCarrier );
		}
	}

	self.ownerTeam = "any";
	self ball_waypoint_download();
	self thread ball_download_wait(fall_time);
	
	self thread ball_download_fx(ball_model, fall_time);
}

ball_download_fx(ball_model, waitTime)
{
	PlayFXOnTag( level._effect["ball_download"], ball_model, "tag_origin");
	
	self waittill_notify_or_timeout("pickup_object", waitTime);
	
	StopFXOnTag( level._effect["ball_download"], ball_model, "tag_origin");
}

ball_download_wait(waitTime)
{
	self endon ( "pickup_object" );
	
	wait waitTime;
	self ball_waypoint_neutral();

	PlayFX( level._effect["ball_download_end"], self.current_start.ground_origin );
	
	ball_fx_start();
}

ball_carrier_cleanup()
{
	if(isDefined(self.carrier))
	{
		self.carrier.noPickupTime = GetTime() + 500;
		
		self.carrier player_clear_pass_target();
		self.carrier notify("cancel_update_pass_target");
		
		self.carrier _unsetPerk( "specialty_ballcarrier" );
		self.carrier.ball_carried = undefined;
		self.carrier thread maps\mp\perks\_perkfunctions::unsetLightArmor();
		
		if ( !self.carrier.hasPerkSprintFire )
			self.carrier _unsetPerk( "specialty_sprintfire" );
		
		self.carrier _enableUsability();
		
		self.carrier maps\mp\killstreaks\_coop_util::playerStartPromptForStreakSupport();
		
		self.carrier SetBallPassAllowed(false);
		self.carrier SetClientOmnvar( "ui_uplink_can_pass", false );
		self.carrier.objective = 0;
	}
}

ball_find_ground(z_offset)
{
	traceStart 	= self.origin + (0,0,32);
	traceEnd 	= self.origin + (0,0,-1000);
	trace 		= bulletTrace( traceStart, traceEnd, false, undefined );

	self.ground_origin = trace["position"];
	
	return trace["fraction"] != 0 && trace["fraction"] != 1;
}

ball_create_team_goal(team)
{
	goal_team = team;
	if(	game["switchedsides"] )
		goal_team = getOtherTeam(goal_team);
	
	dropGoal = false;
	//"ball_goal_axis" or "ball_goal_allies"
	goal = getStruct("ball_goal_" + goal_team, "targetname");
	if ( !isAugmentedGameMode() )
	{
		goal_not_augmented = getStruct("ball_goal_non_augmented_" + goal_team, "targetname");
		if ( IsDefined( goal_not_augmented ) )
			goal = goal_not_augmented;
		else
			dropGoal = true;
	}
	
	if( IsDefined(goal) )
	{
		goal ball_find_ground();
		if ( dropGoal )
			goal.origin = goal.ground_origin + (0,0,90);
	}
	else
	{
		goal = SpawnStruct();
		switch(level.script)
		{
			default:
				break;
		}
		
		if(!IsDefined(goal.origin))
			goal.origin = level.default_goal_origins[team];
		
		goal ball_find_ground();
		if ( isAugmentedGameMode() )
			goal.origin = goal.ground_origin + (0,0,220);
		else
			goal.origin = goal.ground_origin + (0,0,90);
		
	}
	
	goal.radius = 70;
	goal.team = team;
	goal.ball_in_goal = false;
	
	level.ball_goals[team] = goal;
}

ball_create_ball_starts(min_starts)
{
	ball_starts = getstructarray("ball_start","targetname");
	if ( !isAugmentedGameMode() )
	{
		ball_starts_not_augmented = getstructarray("ball_start_non_augmented","targetname");
		if ( ball_starts_not_augmented.size > 0  )
			ball_starts = ball_starts_not_augmented;
	}
	
	ball_starts = array_randomize(ball_starts);//So script struct with the same script index get randomized.
	foreach(new_start in ball_starts)
	{
		if(!IsDefined(new_start.script_index))
			new_start.script_index = 100; //Low priority if script index is not set
	}
	
	ball_starts = quickSort(ball_starts, ::compare_script_index);
	foreach(new_start in ball_starts)
	{
		ball_add_start(new_start.origin);
	}
	
	//Add a default start if none exist
	default_ball_height = 30;
	if(ball_starts.size==0)
	{
		origin = (0,0,0);
		//Check for level overrides
		switch(level.script)
		{
			default:
				break;
		}
		
		if(!IsDefined(origin))
			origin = level.default_ball_origin;

		ball_add_start(origin);
	}
	
	//Add extra default starts to support multi ball
	add_num = min_starts - level.ball_starts.size;
	if(add_num<=0)
		return;
	
	default_start = level.ball_starts[0].origin;
	
	near_nodes = GetNodesInRadius(default_start, 200, 20, 50);
	near_nodes = array_randomize(near_nodes);
	
	for ( i = 0; i < add_num && i<near_nodes.size; i++ )
	{
		ball_add_start(near_nodes[i].origin);
	}
}

ball_add_start(origin)
{
	ball_spawn_height = 30;
	
	new_start = SpawnStruct();
	new_start.origin = origin;
	new_start ball_find_ground();
	new_start.origin = new_start.ground_origin + (0,0,ball_spawn_height);
	new_start.in_use = false;
	
	level.ball_starts[level.ball_starts.size] = new_start;
}

compare_script_index(left, right)
{
	return left.script_index<=right.script_index;
}

ball_on_connect()
{
	while ( true )
	{
		level waittill( "connected", player );
		
		player.ball_goal_fx = [];
		
		player thread player_on_disconnect();
	}
}

player_on_disconnect()	// self == player
{
	self waittill ("disconnect" );
	
	player_delete_ball_goal_fx();
}

ball_goal_fx_for_player(player)
{
	viewTeam = ball_get_view_team(player);
	
	player player_delete_ball_goal_fx();
		
	foreach(team, goal in level.ball_goals)
	{
		fx_name = ter_op(team == viewTeam, "ball_goal_friendly", "ball_goal_enemy");
		
		fx = SpawnFXForClient( getfx(fx_name), goal.origin, player, (1,0,0));
		SetFXKillOnDelete( fx, true );
		
		player.ball_goal_fx[fx_name] = fx;
		TriggerFX( fx );
	}
}

ball_get_view_team(player)
{
	viewTeam = player.team;
	if(	viewTeam != "allies" && viewTeam != "axis")
		viewTeam = "allies";
	
	return viewTeam;
}

player_delete_ball_goal_fx()
{
	foreach (effect in self.ball_goal_fx)
	{
		if ( IsDefined( effect ) )
			effect Delete();
	}
}

ball_play_fx_joined_team()
{
	while(1)
	{
		level waittill ("joined_team", player );
		ball_goal_fx_for_player(player);
	}
}

/#
//============================================
// 		 		ball Debug
//============================================
ball_debug()
{
	setDevDvarIfUninitialized( "scr_ball_debug", "0" );
	
	spawnPoints 		= maps\mp\gametypes\_spawnlogic::getSpawnpointArray( level.spawn_name );
	isPathDataAvailable = maps\mp\gametypes\_spawnlogic::isPathDataAvailable();
	heightOffsetLines 	= (0,0,12);
	heightOffsetNames 	= (0,0,64);
	
	while( true )
	{
		if( getdvar("scr_ball_debug") != "1" ) 
		{
			wait( 1 );
			continue;
		}
		
		SetDevDvar( "scr_showspawns", "1" );
		
		while( true )
		{
			if( getdvar("scr_ball_debug") != "1" )
			{
				SetDevDvar( "scr_showspawns", "0" );
				break;
			}
			
			foreach(start in level.ball_starts)
			{
				Sphere( start.origin, 5.0, (1,0,0) );
			}
			
			color 			= [];
			color["axis"] 	= ( 250/255, 247/255, 97/255 );
			color["allies"] = ( 250/255, 97/255, 240/255 );
			
			// draw the path from each spawn point to the nearest gaol
			foreach( spawnPoint in spawnPoints )
			{
				if( isPathDataAvailable )
				{
					if( !IsDefined(spawnPoint.nodeArray) )
					{
						spawnPoint.nodeArray = GetNodesOnPath( spawnPoint.origin, level.ball_goals[spawnPoint.teamBase].origin );
					}
					
					if( !IsDefined(spawnPoint.nodeArray) || (spawnPoint.nodeArray.size == 0) )
					{
						continue;
					}
					
					line( spawnPoint.origin + heightOffsetLines, spawnPoint.nodeArray[0].origin + heightOffsetLines, color[spawnPoint.teamBase] );
					
					for( i = 0; i <  spawnPoint.nodeArray.size - 1; i++ )
					{
						line( spawnPoint.nodeArray[i].origin + heightOffsetLines, spawnPoint.nodeArray[i+1].origin + heightOffsetLines, color[spawnPoint.teamBase] );
					}
				}
				else
				{
					line( level.ball_goals[spawnPoint.teamBase].origin + heightOffsetLines, spawnPoint.origin + heightOffsetLines, color[spawnPoint.teamBase] );
				}
			}
			
			foreach(team, goal in level.ball_goals)
			{
				goalName = ter_op(game["switchedsides"], team, getOtherTeam(team));
				goalName += " goal";
				
				print3d( goal.ground_origin + heightOffsetNames, goalName );
			}
			
			waitframe();
		}
	}
}
#/