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

main()
{
	thread main_thread();
}
main_thread()
{
	zones = getStructarray( "teleport_world_origin", "targetname" );
	gametype = level.gametype;
	if( !zones.size || !( gameType == "dom" || gametype == "ctf" || gametype == "hp" || gametype == "ball" ) )
		return;
	
	flag_init("teleport_setup_complete");
	level.teleport_minimaps = [];
	level.teleport_allowed = true; //may be turned off by game modes
	level.teleport_to_offset = false; // Don't think we need this for any of the AW levels
	level.teleport_to_nodes = false; // Don't think we need this for any of the AW levels
	level.teleport_include_killsteaks = false; // TODO: make sure this plays nice with the current killstreaks
	level.teleport_include_players = false; // Don't think we need this for any of the AW levels
	level.teleport_gameMode_func = undefined;
	level.teleport_pre_funcs = [];
	level.teleport_post_funcs = [];
	level.teleport_nodes_in_zone = [];
	level.teleport_pathnode_zones = [];
	
	//Hijack onStartGameType to do teleport set up for gamemodes
	level.teleport_onStartGameType = level.onStartGameType;
	level.onStartGameType = ::teleport_onStartGameType;
	level.teleportGetActiveNodesFunc = ::teleport_get_active_nodes;
	level.teleportGetActivePathnodeZonesFunc = ::teleport_get_active_pathnode_zones;
}

teleport_init()
{
	level.teleport_spawn_info = [];
	
	zones = getStructarray("teleport_world_origin", "targetname");
	if(!zones.size)
		return;
	
	level.teleport_zones = [];
	foreach(zone in zones)
	{
		if(!isDefined(zone.script_noteworthy))
		   zone.script_noteworthy = "zone_" + level.teleport_zones.size;
		
		zone.name = zone.script_noteworthy;
		teleport_parse_zone_targets(zone);
		
		level.teleport_nodes_in_zone[zone.name] = [];
		level.teleport_pathnode_zones[zone.name] = [];
		
		level.teleport_zones[zone.script_noteworthy] = zone;
	}
	
	all_nodes = GetAllNodes();
	foreach(node in all_nodes)
	{
		zone = teleport_closest_zone(node.origin);
		level.teleport_nodes_in_zone[zone.name][level.teleport_nodes_in_zone[zone.name].size] = node;
	}
	
	for ( z = 0; z < GetZoneCount(); z++ )
	{
		zone = teleport_closest_zone(GetZoneOrigin(z));
		level.teleport_pathnode_zones[zone.name][level.teleport_pathnode_zones[zone.name].size] = z;
	}
	
	//Default to start zone if it exists
	if(!IsDefined(level.teleport_zone_current))
	{
		if(IsDefined(level.teleport_zones["start"]))
		{
			teleport_set_current_zone("start");
		}
		else
		{
			foreach(key, value in level.teleport_zones)
			{
				teleport_set_current_zone(key);
				break;
			}
		}
	}
	
	/#
	level thread teleport_debug_set_zone();
	#/
}

teleport_onStartGameType()
{
	teleport_init();
	
	pre_onStartGameType_func = undefined;
	post_onStartGameType_func = undefined;
	
	switch(level.gametype)
	{
		case "dom":
			post_onStartGameType_func = ::teleport_onStartGameDOM;
			break;
		case "ctf":
			post_onStartGameType_func = ::teleport_onStartGameCTF;
			break;
		case "hp":
			post_onStartGameType_func = ::teleport_onStartGameHP;
			break;
		case "ball":
			post_onStartGameType_func = ::teleport_onStartGameBALL;
			break;
//		case "conf":
//			post_onStartGameType_func = ::teleport_onStartGameCONF;
//			break;
//		case "sd":
//			pre_onStartGameType_func = ::teleport_pre_onStartGameSD;
//			break;
//		case "sr":
//			pre_onStartGameType_func = ::teleport_pre_onStartGameSR;
//			break;
//		case "horde":
//			pre_onStartGameType_func = ::teleport_onStartGameHORDE;
//			break;
//		//No change needed
//		case "war": 
//		case "dm":
//		case "infect":
//			break;
		default:
			break;
	}
	
	if(IsDefined(pre_onStartGameType_func))
		level [[pre_onStartGameType_func]]();
	
	level [[level.teleport_onStartGameType]]();
	
	if(IsDefined(post_onStartGameType_func))
		level [[post_onStartGameType_func]]();
	
	flag_set("teleport_setup_complete");
}


teleport_pre_onStartGameSR()
{
	teleport_pre_onStartGameSD_and_SR();
}

teleport_pre_onStartGameSD()
{
	teleport_pre_onStartGameSD_and_SR();
}

teleport_pre_onStartGameSD_and_SR()
{
	foreach(zone in level.teleport_zones)
	{
		zone.sd_triggers 	= [];
		zone.sd_bombs 		= [];
		zone.sd_bombZones 	= [];
	}
	
	triggers = GetEntArray("sd_bomb_pickup_trig", "targetname");
	foreach(trigger in triggers)
	{
		closest_zone = teleport_closest_zone(trigger.origin);
		if(IsDefined(closest_zone))
		{
			closest_zone.sd_triggers[closest_zone.sd_triggers.size] = trigger;
			teleport_change_targetname(trigger, closest_zone.name);
		}
	}
	
	
	bombs = GetEntArray( "sd_bomb", "targetname" );
	foreach(bomb in bombs)
	{
		closest_zone = teleport_closest_zone(bomb.origin);
		if(IsDefined(closest_zone))
		{
			closest_zone.sd_bombs[closest_zone.sd_bombs.size] = bomb;
			teleport_change_targetname(bomb, closest_zone.name);
		}
	}
	
	bombZones = GetEntArray( "bombzone", "targetname" );	
	foreach(bombZone in bombZones)
	{
		closest_zone = teleport_closest_zone(bombZone.origin);
		if(IsDefined(closest_zone))
		{
			closest_zone.sd_bombZones[closest_zone.sd_bombZones.size] = bombZone;
			
			teleport_change_targetname(bombZone, closest_zone.name);
		}
	}
	
	valid_zones = [];
	foreach(zone in level.teleport_zones)
	{
		if( zone.sd_triggers.size && zone.sd_triggers.size && zone.sd_triggers.size )
		{
			valid_zones[valid_zones.size] = zone.name;
		}
	}
	
	teleport_gamemode_disable_teleport(valid_zones);
		
	current_zone = level.teleport_zones[level.teleport_zone_current];
	teleport_restore_targetname(current_zone.sd_triggers);
	teleport_restore_targetname(current_zone.sd_bombs);
	teleport_restore_targetname(current_zone.sd_bombZones);
}

teleport_onStartGameHORDE()
{
	foreach(zone in level.teleport_zones)
	{
		zone.horde_drops = [];
	}
	
	hordeDropLocations = getstructarray( "horde_drop", "targetname" );
	foreach(loc in hordeDropLocations)
	{
		closest_zone = teleport_closest_zone(loc.origin);
		if(IsDefined(closest_zone))
		{
			closest_zone.horde_drops[closest_zone.horde_drops.size] = loc;
		}
	}
	
	valid_zones = [];
	foreach(zone in level.teleport_zones)
	{
		if( zone.horde_drops.size )
		{
			valid_zones[valid_zones.size] = zone.name;
		}
	}
	
	teleport_gamemode_disable_teleport(valid_zones);
		
	current_zone = level.teleport_zones[level.teleport_zone_current];
	
	level.struct_class_names[ "targetname" ][ "horde_drop" ] = current_zone.horde_drops;
	
}

teleport_change_targetname(ents, append)
{
	if(!IsArray(ents))
		ents = [ents];
	
	if(!IsDefined(append))
		append = "hide_from_getEnt";
	
	foreach(ent in ents)
	{
		ent.saved_targetname = ent.targetname;
		ent.targetname = ent.targetname + "_" + append;
	}
}

teleport_gamemode_disable_teleport( valid_zones )
{
	if(!IsDefined(valid_zones))
		valid_zones = GetArrayKeys(level.teleport_zones);
	
	game_zone = game["teleport_zone_dom"];
	if(!IsDefined(game_zone))
	{
		game_zone = random(valid_zones);
		game["teleport_zone_dom"] = game_zone;
	}
	
	teleport_to_zone(game_zone, false);
	
	level.teleport_allowed = false;
}

teleport_restore_targetname(ents)
{
	if(!IsArray(ents))
		ents = [ents];
	
	foreach(ent in ents)
	{
		if(IsDefined(ent.saved_targetname))
			ent.targetname = ent.saved_targetname;
	}
}

teleport_onStartGameCTF()
{
	level.teleport_gameMode_func = ::teleport_onTeleportCTF;
}

teleport_onStartGameHP()
{
	if( !IsDefined( level.number_of_hp_zones_pre_teleport ) )
		level.number_of_hp_zones_pre_teleport = 5;

	level.pre_event_hp_zones = [];
	level.post_event_hp_zones = [];
	level.all_hp_zones = level.zones;
	
	foreach( zone in level.zones )
	{
		if( zone.script_index > level.number_of_hp_zones_pre_teleport )
			level.post_event_hp_zones[level.post_event_hp_zones.size] = zone;
		else
			level.pre_event_hp_zones[level.pre_event_hp_zones.size] = zone;
	}
	
	level.zones = level.pre_event_hp_zones;
	
	level.teleport_gameMode_func = ::teleport_onTeleportHP;
}


teleport_onStartGameBALL()
{
	level.teleport_gameMode_func = ::teleport_onTeleportBALL;
}


teleport_onStartGameDOM()
{
	//Init flag arrays
	foreach( zone in level.teleport_zones )
	{
		zone.flags = [];
		zone.domFlags = [];
	}
	
	level.all_dom_flags = level.flags;
	foreach( flag in level.flags )
	{
		closest_zone = teleport_closest_zone( flag.origin );
		if( IsDefined( closest_zone ) )
		{
			flag.teleport_zone = closest_zone.name;
			closest_zone.flags[closest_zone.flags.size] = flag;
			closest_zone.domFlags[closest_zone.domFlags.size] = flag.useObj;
		}
	}
	
	level.dom_flag_data = [];
	foreach( zone in level.teleport_zones )
	{
		foreach( domFlag in zone.flags )
		{
			data = spawnStruct();
			data.trigger_origin = domFlag.origin;
			data.visual_origin = domFlag.useobj.visuals[0].origin;
			data.baseeffectpos = domFlag.useobj.baseeffectpos;
			data.baseeffectforward = domFlag.useobj.baseeffectforward; 
			data.baseeffectright = domFlag.useobj.baseeffectright;
			data.obj_origin = domFlag.useObj.curOrigin;
			
			data.obj3d_origins = [];
			foreach(team in level.teamNameList)
			{
				opName = "objpoint_" + team + "_" + domFlag.useObj.entNum;			
				objPoint = maps\mp\gametypes\_objpoints::getObjPointByName( opName );
				if( IsDefined( objPoint) )
				{
					data.obj3d_origins[team] = ( objPoint.x, objPoint.y, objPoint.z );
				}
			}

			opName = "objpoint_mlg_" + domFlag.useObj.entNum;			
			objPoint = maps\mp\gametypes\_objpoints::getObjPointByName( opName );
			if( IsDefined( objPoint) )
			{
				data.obj3d_origins["mlg"] = ( objPoint.x, objPoint.y, objPoint.z );
			}

			level.dom_flag_data[zone.name][domFlag.useObj.label] = data;
		}
	}
	
	level.flags = level.teleport_zones[level.teleport_zone_current].flags;
	level.domFlags = level.teleport_zones[level.teleport_zone_current].domFlags;
		
	foreach( zone in level.teleport_zones )
	{
		foreach( flag in zone.flags )
		{
			if( zone.name == level.teleport_zone_current )
				continue;
			
			flag.useobj.visuals[0] Delete();
			flag.useObj maps\mp\gametypes\_gameobjects::deleteUseObject();
		}
	}
	
	level.teleport_gameMode_func = ::teleport_onTeleportDOM;
	
	teleport_onTeleportDOM(level.teleport_zone_current);
	
	level.teleport_dom_finished_initializing = true;
	
	level thread teleport_dom_post_bot_cleanup();
}

teleport_dom_post_bot_cleanup()
{
	while(!IsDefined(level.bot_gametype_precaching_done))
		wait 0.05;
	
	foreach(zone in level.teleport_zones)
	{
		foreach(flag in zone.flags)
		{
			data = level.dom_flag_data[zone.name][flag.useObj.label];
			data.nodes = flag.nodes;
			
			if(zone.name != level.teleport_zone_current)
			{
				flag Delete();
			}
		}
	}
}

teleport_onStartGameCONF()
{
	level.teleport_gameMode_func = ::teleport_onTeleportCONF;
}

teleport_onTeleportDOM(next_zone_name)
{
	//Set up and copy values to the zone we are teleporting to
	current_zone = level.teleport_zones[level.teleport_zone_current];
	next_zone = level.teleport_zones[next_zone_name];
	
	if(next_zone_name == level.teleport_zone_current)
		return;
	
	foreach ( domFlag in level.domFlags )
	{
		// reset flag to neutral
		domFlag maps\mp\gametypes\_gameobjects::setOwnerTeam( "neutral" );
		domFlag maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_captureneutral" + domFlag.label );
		domFlag maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_captureneutral" + domFlag.label );
		domFlag maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_captureneutral" + domFlag.label );
		domFlag maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_captureneutral" + domFlag.label );
		domFlag maps\mp\gametypes\_gameobjects::set2DIcon( "mlg", "waypoint_esports_dom_white" + domFlag.label );
		domFlag maps\mp\gametypes\_gameobjects::set3DIcon( "mlg", "waypoint_esports_dom_white" + domFlag.label );
		domFlag.firstCapture = true;
	}
	
	foreach( domFlag in level.flags )
	{
		data = level.dom_flag_data[next_zone_name][domFlag.useObj.label];
		domFlag.origin = data.trigger_origin;
		domFlag.useobj.visuals[0].origin = data.visual_origin;
		domFlag.useobj.baseeffectpos = data.baseeffectpos;
		domFlag.useobj.baseeffectforward = data.baseeffectforward; 
		domFlag.useObj maps\mp\gametypes\dom::updateVisuals();
		domFlag.teleport_zone = next_zone_name;
		domflag.nodes = data.nodes;
		
		if( IsDefined( domflag.useobj.objIDAllies ) )
			Objective_Position(domflag.useobj.objIDAllies, data.obj_origin);
		if( IsDefined( domflag.useobj.objIDAxis ) )
			Objective_Position(domflag.useobj.objIDAxis, data.obj_origin);
		if( IsDefined( domflag.useobj.objIDMLGSpectator ) )
			Objective_Position(domflag.useobj.objIDMLGSpectator, data.obj_origin);
		
		foreach(team in level.teamnamelist)
		{
			opName = "objpoint_" + team + "_" + domFlag.useObj.entNum;
			objPoint = maps\mp\gametypes\_objpoints::getObjPointByName( opName );
			objPoint.x = data.obj3d_origins[team][0];
			objPoint.y = data.obj3d_origins[team][1];
			objPoint.z = data.obj3d_origins[team][2];
		}

		opName = "objpoint_mlg_" + domFlag.useObj.entNum;
		objPoint = maps\mp\gametypes\_objpoints::getObjPointByName( opName );
		objPoint.x = data.obj3d_origins["mlg"][0];
		objPoint.y = data.obj3d_origins["mlg"][1];
		objPoint.z = data.obj3d_origins["mlg"][2];
	}
	
	maps\mp\gametypes\dom::flagSetup();
	
	foreach( flag in level.domFlags )
	{
		label = flag.label;
		foreach( zone_domflag in level.teleport_zones["start"].domFlags )
		{
			if( zone_domflag.label == label )
				flag.levelflag = zone_domflag.levelflag;
		}
	}
	
	foreach( flag in level.flags )
	{
		label = flag.label;
		foreach( zone_flag in level.teleport_zones["start"].flags )
		{
			if( zone_flag.label == label )
				flag.levelflag = zone_flag.levelflag;
		}
	}
}

teleport_get_matching_dom_flag(flag, from_zone)
{
	foreach( dom_flag in level.teleport_zones[from_zone].flags )
	{
		if( flag.useobj.label == dom_flag.useobj.label )
			return dom_flag;
	}
	return undefined;
}


teleport_onTeleportCTF( next_zone_name )
{
	// grab the new location for the capZones
	if ( game["switchedsides"] )
	{
		level.ctf_second_zones["axis"]		= getent( "post_event_capzone_allies", "targetname" );
		level.ctf_second_zones["allies"]	= getent( "post_event_capzone_axis",   "targetname" );	
	}
	else
	{
		level.ctf_second_zones["allies"]	= getent( "post_event_capzone_allies", "targetname" );
		level.ctf_second_zones["axis"]		= getent( "post_event_capzone_axis",   "targetname" );
	}
	
	zones = [];
	zones["allies"] 		= level.capZones["allies"];
	zones["axis"  ] 		= level.capZones["axis"  ];
	flags["allies"] 		= level.teamFlags["allies"];
	flags["axis"  ] 		= level.teamFlags["axis"  ];
	zone_dest["allies"] 	= level.ctf_second_zones["allies"].origin;
	zone_dest["axis"  ] 	= level.ctf_second_zones["axis"  ].origin;
			
	// disable and move the capZones and teamFlags
	foreach( zone in zones )
	{
		zone maps\mp\gametypes\_gameobjects::move_use_object( zone_dest[zone.ownerTeam], (0,0,85) );
		zone.trigger trigger_off();
	}
	
	foreach( flag in level.teamFlags )
	{
		flag maps\mp\gametypes\_gameobjects::move_use_object( zone_dest[flag.ownerTeam], (0,0,85) );
		
		if( IsDefined( flag.carrier ) )
		{
			flag maps\mp\gametypes\_gameobjects::setVisibleTeam( "any" );
			flag maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", level.iconKill2D );
			flag maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", level.iconKill3D );
			flag maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", level.iconEscort2D );
			flag maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", level.iconEscort3D );
			
			level.capZones[flag.ownerteam] maps\mp\gametypes\_gameobjects::allowUse( "none" );
			level.capZones[flag.ownerteam] maps\mp\gametypes\_gameobjects::setVisibleTeam( "friendly" );
			level.capZones[flag.ownerteam] maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", level.iconWaitForFlag2D );
			level.capZones[flag.ownerteam] maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", level.iconWaitForFlag3D );
			
			if( flag.ownerteam == "allies" )
			{
				level.capZones[flag.ownerteam] maps\mp\gametypes\_gameobjects::set2DIcon( "mlg", level.iconMissingBlue );
				level.capZones[flag.ownerteam] maps\mp\gametypes\_gameobjects::set3DIcon( "mlg", level.iconMissingBlue );
			}
			else
			{
				level.capZones[flag.ownerteam] maps\mp\gametypes\_gameobjects::set2DIcon( "mlg", level.iconMissingRed );
				level.capZones[flag.ownerteam] maps\mp\gametypes\_gameobjects::set3DIcon( "mlg", level.iconMissingRed );
			}
		}
	}
	// reset base effects for each client
	maps\mp\gametypes\ctf::captureZone_reset_base_effects();
	
	// reassign spawnpoints for teams now that capZones have moved
	maps\mp\gametypes\ctf::reassign_ctf_team_spawns();
	
	// turn the capZones and flags back on
	foreach( zone in zones )
		zone.trigger trigger_on();
}


teleport_onTeleportHP( next_zone_name )
{
	level.zones = level.post_event_hp_zones;
	
	if( level.randomZoneSpawn == false )
	{
		level.prevZoneIndex = level.zones.size - 1;
	}
	else
	{
		level.zoneSpawnQueue = [];
		maps\mp\gametypes\hp::ShuffleZones();
	}
	
	SetOmnvar( "ui_hardpoint_timer", 0 );
	level notify("zone_moved");
}


teleport_onTeleportBALL( next_zone_name )
{
	// grab the new location for the goals
	level.ball_starts_post_event = getstructarray( "ball_start_post_event", "targetname" );
	if ( game["switchedsides"] )
	{
		level.ball_goals_post_event["allies"] = getstruct( "ball_goal_axis_post_event",   "targetname" );
		level.ball_goals_post_event["axis"  ] = getstruct( "ball_goal_allies_post_event", "targetname" );
	}
	else
	{
		level.ball_goals_post_event["axis"  ] = getstruct( "ball_goal_axis_post_event",   "targetname" );
		level.ball_goals_post_event["allies"] = getstruct( "ball_goal_allies_post_event", "targetname" );
	}
	
	goals 					= [];
	goals["allies"] 		= level.ball_goals["allies"];
	goals["axis"  ] 		= level.ball_goals["axis"  ];
	goal_dest				= [];
	goal_dest["allies"] 	= level.ball_goals_post_event["allies"].origin;
	goal_dest["axis"  ] 	= level.ball_goals_post_event["axis"  ].origin;
	
	foreach( goal in goals )
	{
		offset = (0,0,((goal.radius/2)*1.1));
		goal.useobject maps\mp\gametypes\_gameobjects::move_use_object( goal_dest[goal.Team], offset );
		goal maps\mp\gametypes\ball::ball_find_ground();
		
		foreach(player in level.players)
			maps\mp\gametypes\ball::ball_goal_fx_for_player(player);
	}
	
	bot_setup_ball_jump_nodes();
	
	zone = GetZoneNearest( level.ball_goals["allies"].origin );
	if ( IsDefined( zone ) )
		BotZoneSetTeam( zone, "allies" );
	
	zone = GetZoneNearest( level.ball_goals["axis"].origin );
	if ( IsDefined( zone ) )
		BotZoneSetTeam( zone, "axis" );
	
	level.ball_starts = [];
	foreach( ball_start in level.ball_starts_post_event )
		maps\mp\gametypes\ball::ball_add_start( ball_start.origin );
	
	// See if players are carrying any of the balls, move the ones not being carrried to the new start
	foreach( ball in level.balls )
	{
		ball_carried = false;
		foreach ( player in level.players )
		{
			if( IsDefined( player.ball_carried ) && player.ball_carried == ball )
			{
				ball_carried = true;
				break;
			}
		}
		
		if( ball_carried != true )
			ball maps\mp\gametypes\ball::ball_return_home();
	}
}


bot_setup_ball_jump_nodes()
{
	ball_node_max_dist = 400;
	
	wait(1.0);
	
	num_traces = 0;
	increment = 10;	// do 10 traces per frame
	
	foreach( goal in level.ball_goals )
	{
		goal.ball_jump_nodes = [];
		
		nodes = GetNodesInRadius( goal.origin, ball_node_max_dist, 0 );
		foreach( node in nodes )
		{
			if ( node.type == "End" )
				continue;
			
			num_traces++;
			if ( bot_ball_origin_can_see_goal( node.origin, goal, true ) )
				goal.ball_jump_nodes[goal.ball_jump_nodes.size] = node;
			
			if ( (num_traces % increment) == 0 )
				wait(0.05);
		}
		
		nearest_node_2d_dist_sq = 999999999;
		foreach( node in goal.ball_jump_nodes )
		{
			dist_2d_sq_to_node = Distance2DSquared( node.origin, goal.origin );
			if ( dist_2d_sq_to_node < nearest_node_2d_dist_sq )
			{
				goal.nearest_node = node;
				nearest_node_2d_dist_sq = dist_2d_sq_to_node;
			}
		}
		
		AssertEx( goal.ball_jump_nodes.size > 0, "Uplink goal at " + goal.origin + " needs pathnodes within a " + ball_node_max_dist + " unit radius with sight to the goal" );
		wait(0.05);
	}
}

bot_ball_origin_can_see_goal( origin, goal, thorough )
{
	trace_succeeded = self bot_ball_trace_to_origin( origin, goal.origin );
	if ( IsDefined(thorough) && thorough )
	{
		if ( !trace_succeeded )
		{
			goal_origin = goal.origin - (0,0,goal.radius * 0.5);
			trace_succeeded = self bot_ball_trace_to_origin( origin, goal_origin );
		}
		
		if ( !trace_succeeded )
		{
			goal_origin = goal.origin + (0,0,goal.radius * 0.5);
			trace_succeeded = self bot_ball_trace_to_origin( origin, goal_origin );
		}
	}
	
	return trace_succeeded;
}

bot_ball_trace_to_origin( start_origin, end_origin )
{
	if ( IsDefined(self) && (IsPlayer(self) || IsAgent(self)) )
		hitPos = PlayerPhysicsTrace( start_origin, end_origin, self );
	else
		hitPos = PlayerPhysicsTrace( start_origin, end_origin );
	return (DistanceSquared( hitPos, end_origin ) < 1);
}


teleport_onTeleportCONF(next_zone_name)
{
	teleport_delta = get_teleport_delta( next_zone_name );
	
	foreach(dogTag in level.dogtags)
	{
		goal_origin = dogTag.curOrigin + teleport_delta;
		goal_node = teleport_get_safe_node_near(goal_origin);
		
		if(isDefined(goal_node))
		{
			goal_node.last_teleport_time = GetTime();
			delta = goal_node.origin - dogTag.curOrigin;
			
			dogTag.curOrigin += delta;
			dogTag.trigger.origin += delta;
			dogTag.visuals[0].origin += delta;
			dogTag.visuals[1].origin += delta;
		}
		else
		{
			dogTag maps\mp\gametypes\conf::resetTags();
		}
	}
}

teleport_get_safe_node_near(near_to)
{
	current_time = GetTime();
	
	nodes = GetNodesInRadiusSorted( near_to, 300, 0, 200, "Path" );
	for(i=0; i<nodes.size; i++)
	{
		check_node = nodes[i];
		
		if(IsDefined(check_node.last_teleport_time) && check_node.last_teleport_time==current_time)
			continue;
		
		return check_node;
	}
	
	return undefined;
}

teleport_closest_zone(test_origin)
{
	min_dist = undefined;
	closest_zone = undefined;
	foreach(zone in level.teleport_zones)
	{
		dist = DistanceSquared(zone.origin, test_origin);
		if(!isDefined(min_dist) || dist < min_dist)
		{
			min_dist = dist;
			closest_zone = zone;
		}
	}
	
	return closest_zone;
}

teleport_origin_use_nodes(allow)
{
	level.teleport_to_nodes = allow;
}

teleport_origin_use_offset(allow)
{
	level.teleport_to_offset = allow;
}

teleport_include_killstreaks(include)
{
	level.teleport_include_killsteaks = include;
}

teleport_set_minimap_for_zone(zone, map)
{
	level.teleport_minimaps[zone] = map;
}

teleport_set_pre_func( func, zone_name )
{
	level.teleport_pre_funcs[zone_name] = func;
}

teleport_set_post_func( func, zone_name )
{
	level.teleport_post_funcs[zone_name] = func;
}

teleport_parse_zone_targets(zone)
{
	if(IsDefined(zone.origins_pasrsed) && zone.origins_pasrsed)
		return;
	
	zone.teleport_origins = [];
	zone.teleport_origins["none"] = [];
	zone.teleport_origins["allies"] = [];
	zone.teleport_origins["axis"] = [];
	
	
	//Naming convention to allow to include targets without lines across the map
	structs  = getstructarray("teleport_zone_"+zone.name, "targetname");
	
	//Grab all targets
	if(IsDefined(zone.target))
	{
		zone_targets = getstructarray(zone.target, "targetname");
		structs = array_combine(zone_targets, structs);
	}
	
	
	foreach(struct in structs)
	{
		if(!IsDefined(struct.script_noteworthy))
			struct.script_noteworthy = "teleport_origin";
		
		switch(struct.script_noteworthy)
		{
			case "teleport_origin":
				start = struct.origin + (0,0,1);
				end = struct.origin - (0,0,250);
				trace = BulletTrace(start, end, false);
				
				if( trace[ "fraction" ] == 1.0 )
				{
					println( "^3_teleport.gsc: Teleport Origin " + struct.origin + " did not find ground." );
					break;
				}
				
				struct.origin =trace[ "position" ];
				//fallthrough
			case "telport_origin_nodrop":
				if(!IsDefined(struct.script_parameters))
					struct.script_parameters = "none,axis,allies";
				
				toks = strTok(struct.script_parameters, ", ");
				foreach(tok in toks)
				{
					if(!IsDefined(zone.teleport_origins[tok]))
					{
						println( "^3_teleport.gsc: Unknown Team " + tok + " on teleport origin at " + struct.origin );
						continue;
					}
					
					if(!IsDefined(struct.angles))
						struct.angles = (0,0,0);
					
					size = zone.teleport_origins[tok].size;
					zone.teleport_origins[tok][size] = struct;
				}
				break;
			default:
				break;
		}
	}
	
	zone.origins_pasrsed = true;
}

/#
teleport_debug_set_zone()
{
	thread teleport_watch_debug_dvar("teleport_debug_zone");
	thread teleport_watch_debug_dvar("teleport_debug_zone_simple");
	
	while(1)
	{
		level waittill("teleport_dvar_changed", dvar_name, debug_zone);
		
		run_event_funcs = dvar_name != "teleport_debug_zone_simple";
		if(teleport_is_valid_zone(debug_zone))	
		{
			teleport_to_zone(debug_zone, run_event_funcs);
		}
		else
		{
			i=0;
			PrintLn("'"+debug_zone+"' is not a valid zone. Valid Zones:");
			foreach(name,zones in level.teleport_zones)
			{
				i++;
				PrintLn(""+i+") " + name);
			}
		}
	}
}

teleport_watch_debug_dvar(dvar_name, dvar_default)
{
	if(!IsDefined(dvar_default))
		dvar_default = "";
	
	SetDvarIfUninitialized(dvar_name, dvar_default);
	
	while(1)
	{
		current_value = GetDvar(dvar_name, dvar_default);
		
		if(current_value != dvar_default)
		{
			level notify("teleport_dvar_changed", dvar_name, current_value);
			SetDvar(dvar_name, dvar_default);
		}
		
		wait .25;
	}
}
#/

teleport_set_current_zone(zone)
{
	level.teleport_zone_current = zone;
	
	if(IsDefined(level.teleport_minimaps[zone]))
	{
		maps\mp\_compass::setupMiniMap( level.teleport_minimaps[zone] );
	}
}

teleport_filter_spawn_point(spawnPoints, filter_zone)
{
	if(!IsDefined(filter_zone))
		filter_zone = level.teleport_zone_current;
	
	valid_spawns = [];
	foreach(spawnPoint in spawnPoints)
	{
		if ( !IsDefined( spawnPoint.teleport_label ) )
			spawnPoint.teleport_label = "ent_" + spawnPoint GetEntityNumber();
		if(!IsDefined(level.teleport_spawn_info[spawnPoint.teleport_label]))
			teleport_init_spawn_info(spawnPoint);
		
		if(level.teleport_spawn_info[spawnPoint.teleport_label].zone == filter_zone)
			valid_spawns[valid_spawns.size] = spawnPoint;
	}
	
	
	return valid_spawns;
}

teleport_init_spawn_info(spawner)
{
	if ( !IsDefined( spawner.teleport_label ) )
		spawner.teleport_label = "ent_" + spawner GetEntityNumber();
	if(IsDefined(level.teleport_spawn_info[spawner.teleport_label]))
		return;
	
	s = SpawnStruct();
	s.spawner = spawner;
	
	min_dist = undefined;
	foreach(zone in level.teleport_zones)
	{
		dist = Distance(zone.origin, spawner.origin);
		if(!isDefined(min_dist) || dist < min_dist)
		{
			min_dist = dist;
			s.zone = zone.name;
		}
	}
	
	level.teleport_spawn_info[spawner.teleport_label] = s;
}

teleport_is_valid_zone(zone_name)
{
	foreach( name,zones in level.teleport_zones )
	{
		if( name == zone_name )
			return true;
	}
	
	return false;
}

teleport_to_zone( zone_name, run_event_funcs )
{
	if( !level.teleport_allowed )
		return;
	
	if( !IsDefined( run_event_funcs ) )
		run_event_funcs = true;
	
	pre_func = level.teleport_pre_funcs[ zone_name ];
	if( IsDefined( pre_func ) && run_event_funcs )
		[[pre_func]]();

	current = level.teleport_zones[ level.teleport_zone_current ];
	next = level.teleport_zones[zone_name];
	
	if( !IsDefined( current ) || !IsDefined( next ) )
		return;
	
	if( level.teleport_include_players )
	{
		teleport_to_zone_players( zone_name );
		teleport_to_zone_agents( zone_name );
	}

	if( level.teleport_include_killsteaks )
		teleport_to_zone_killstreaks( zone_name );
	
	if( IsDefined( level.teleport_gameMode_func ) )
		[[level.teleport_gameMode_func]]( zone_name );
		
	teleport_set_current_zone( zone_name );
	
	level notify( "teleport_to_zone", zone_name );
	
	post_func = level.teleport_post_funcs[zone_name];
	if( IsDefined( post_func ) && run_event_funcs )
		[[post_func]]();
	
	if( IsDefined( level.bot_funcs ) && IsDefined( level.bot_funcs["post_teleport"] ) )
		[[level.bot_funcs["post_teleport"]]]();
}

teleport_to_zone_agents(zone_name)
{
	//Agents (including dogs)
	agents = maps\mp\agents\_agent_utility::getActiveAgentsOfType( "all" );
	foreach( agent in agents)
	{
		teleport_to_zone_character(zone_name, agent);
	}
}

teleport_to_zone_players(zone_name)
{
	foreach( player in level.players)
	{
		teleport_to_zone_character(zone_name, player);
	}
}

teleport_to_zone_character(zone_name, character)
{
	current_zone = level.teleport_zones[level.teleport_zone_current];
	next_zone = level.teleport_zones[zone_name];
	
	current_time = GetTime();

	if( isPlayer(character) && (character.sessionstate == "intermission" || character.sessionstate == "spectator") )
	{
		spawnPoints = getEntArray( "mp_global_intermission", "classname" );
		spawnPoints = teleport_filter_spawn_point(spawnPoints, zone_name);
		
		spawnPoint = spawnPoints[0];
		character DontInterpolate();
		character SetOrigin( spawnPoint.origin );
		character SetPlayerAngles( spawnPoint.angles );
		return;
	}
	
	
	teleport_origin = undefined;
	teleport_angles = character.angles;
	if(isPlayer(character))
		teleport_angles = character GetPlayerAngles();
	
	
	foreach(set_name,origin_set in next_zone.teleport_origins)
	{
		next_zone.teleport_origins[set_name] = array_randomize(origin_set);
		foreach(org in origin_set)
		{
			org.claimed = false;
		}
	}
	
	//Check if zone has defined teleport spots
	zone_origins = [];
	if(level.teambased)
	{
		if(isDefined(character.team) && IsDefined(next_zone.teleport_origins[character.team]))
		{
			zone_origins = next_zone.teleport_origins[character.team];
		}
	}
	else
	{
		zone_origins = next_zone.teleport_origins["none"];
	}
	
	foreach(org in zone_origins)
	{
		if(!org.claimed)
		{
			teleport_origin = org.origin;
			teleport_angles = org.angles;
			org.claimed = true;
			break;
		}	
	}
	
	delta = next_zone.origin - current_zone.origin;
	desiredOrigin = character.origin+delta;
	if(!IsDefined(teleport_origin) && level.teleport_to_offset)
	{
		if(CanSpawn(desiredOrigin) && !PositionWouldTelefrag(desiredOrigin))
		{
			teleport_origin = desiredOrigin;
		}
	}
	
	if(!IsDefined(teleport_origin) && level.teleport_to_nodes)
	{
		nodes = GetNodesInRadiusSorted( desiredOrigin, 300, 0, 200, "Path" );
		for(i=0; i<nodes.size; i++)
		{
			check_node = nodes[i];
			if(IsDefined(check_node.last_teleport_time) && check_node.last_teleport_time==current_time)
				continue;
			
			org = check_node.origin;
			if(CanSpawn(org) && !PositionWouldTelefrag(org))
			{
				check_node.last_teleport_time = current_time;
				teleport_origin = org;
				break;
			}
		}
	}
	
	if(!IsDefined(teleport_origin))
	{
		character _suicide();
	}
	else
	{
		//TODO: Drop to floor?
		character CancelMantle();
		character DontInterpolate();
		character SetOrigin( teleport_origin );
		character SetPlayerAngles( teleport_angles );
		
		thread teleport_validate_success(character);
	}
}

teleport_validate_success(player)
{
	waitframe();
	if(IsDefined(player))
	{
		player_zone = teleport_closest_zone( player.origin );
		if(player_zone.name != level.teleport_zone_current)
			player _suicide();
	}
}

get_teleport_delta( zone_name )
{
	next_zone = level.teleport_zones[zone_name];
	current_zone = level.teleport_zones[level.teleport_zone_current];
	delta = next_zone.origin - current_zone.origin;
	return delta;	
}

teleport_to_zone_killstreaks(zone_name)
{
//	delta =  get_teleport_delta( zone_name );
//	
//	//AC130
//	teleport_add_delta(level.ac130, delta);
//	
//	//Pave Low
//	//Needs to be level thread to avoid calling teleport_self_add_delta_targets and blowing the stack
//	array_levelthread_safe(level.heli_start_nodes, ::teleport_add_delta_targets, delta);	
//	array_levelthread_safe(level.heli_loop_nodes, ::teleport_add_delta_targets, delta);
//	array_levelthread_safe(level.heli_loop_nodes2, ::teleport_add_delta_targets, delta);
//	array_levelthread_safe(level.strafe_nodes, ::teleport_add_delta_targets, delta);
//	array_levelthread_safe(level.heli_leave_nodes, ::teleport_add_delta_targets, delta);
//	array_levelthread_safe(level.heli_crash_nodes, ::teleport_add_delta_targets, delta);
//	
//	if(IsDefined(level.chopper))
//	{
//		level.chopper.largeProjectileDamage = true;
//		level.chopper notify("death");
//	}
//	
//	teleport_add_delta(level.UAVRig, delta);
//	
//	//Odin, Heli Pilot
//	teleport_add_delta(level.heli_pilot_mesh, delta);
//		
//	//Little Bird/Heli Sniper
//	array_thread_safe(level.air_start_nodes, ::teleport_self_add_delta, delta);
//	foreach( loc in level.air_start_nodes )
//		array_thread_safe(loc.neighbors, ::teleport_self_add_delta, delta);
//	
//	array_thread_safe(level.air_node_mesh, ::teleport_self_add_delta, delta);
//	foreach( loc in level.air_node_mesh )
//		array_thread_safe(loc.neighbors, ::teleport_self_add_delta, delta);
//	
//	//array_thread(level.littleBirds, ::teleport_self_add_delta, delta);
//	array_thread_safe(level.littleBirds, ::teleport_notify_death );
//	
//	teleport_add_delta(level.lbSniper, delta);
//	
//	airstrikeheight = GetEnt( "airstrikeheight", "targetname" );
//	teleport_add_delta(airstrikeheight, delta);
//	
//	points = GetEntArray( "mp_airsupport", "classname" );
//	array_thread_safe(points, ::teleport_self_add_delta, delta); 
//	
//	//Escorted Air Drop
//	heli_attack_area = getEntArray( "heli_attack_area", "targetname" );
//	array_thread_safe(heli_attack_area, ::teleport_self_add_delta, delta); 
//	
//	//Remote UAV
//	remote_uav_range = GetEnt( "remote_uav_range", "targetname" );
//	teleport_add_delta(remote_uav_range, delta); 
//	
//	//Ball drone
//	foreach(participant in level.participants)
//	{
//		if(IsDefined(participant.ballDrone))
//		{
//			participant.ballDrone teleport_notify_death();
//		}
//	}
//	
//	//Uplinks
//	if(IsDefined(level.uplinks))
//	{
//		foreach(uplink in level.uplinks)
//			uplink notify("death");
//	}
//	
//	//Predator 
//	remoteMissileSpawnArray = getEntArray( "remoteMissileSpawn" , "targetname" );
//	array_thread_safe(remoteMissileSpawnArray, ::teleport_self_add_delta, delta);
//	foreach(spawn in remoteMissileSpawnArray)
//	{
//		if ( isDefined( spawn.target ) )
//			spawn.targetEnt = getEnt( spawn.target, "targetname" );	
//		if( IsDefined( spawn.targetEnt ) )
//			teleport_add_delta(spawn.targetEnt, delta);
//	}
//	
//	//Turret
//	foreach(turret in level.turrets)
//	{
//		turret notify("death");
//		teleport_add_delta(turret, delta);
//		if(!teleport_place_on_ground(turret))
//			turret Delete();
//	}
//	
//	//Care Packages
//	packages = teleport_get_care_packages();
//	foreach(package in packages)
//	{
//		package maps\mp\killstreaks\_airdrop::deleteCrate();
//	}
//	
////	//Deployable Box
////	boxes = teleport_get_deployable_boxes();
////	foreach(box in boxes)
////	{
////		box maps\mp\killstreaks\_deployablebox::box_leave();
////	}
//	
//	//Vanguard
//	foreach(uav in level.remote_uav)
//	{
//		uav teleport_notify_death();
//	}
//	
//	//Odin
//	vehicles = Vehicle_GetArray();
//	foreach(vehicle in vehicles)
//	{
//		if(IsDefined(vehicle.model) && vehicle.model == "vehicle_odin_mp")
//		{
//			//teleport_add_delta(vehicle, delta); //Doesn't work
//			vehicle teleport_notify_death();
//		}
//	}
//	
//	//Intel
//	if(IsDefined(level.intelEnt))
//	{
//		level.intelEnt["dropped_time"] = -60000; //Force intel to reset
//	}
//	
}

teleport_notify_death()
{
	if(IsDefined(self))	
		self notify("death");
}

array_thread_safe(entities, process, var1, var2, var3, var4, var5, var6, var7, var8, var9)
{
	if(!IsDefined(entities))
		return;
	array_thread(entities, process, var1, var2, var3, var4, var5, var6, var7, var8, var9);
}

array_levelthread_safe( array, process, var1, var2, var3 )
{
	if(!IsDefined(array))
		return;
	
	array_levelthread( array, process, var1, var2, var3 );
}

teleport_get_care_packages()
{
	return getEntArray("care_package", "targetname");
}

teleport_get_deployable_boxes()
{
	boxes = [];
	script_models = GetEntArray("script_model","classname");
	foreach(mod in script_models)
	{
		if(IsDefined(mod.boxtype))
		{
			boxes[boxes.size] = mod;
		}
	}
	return boxes;
}

teleport_place_on_ground(ent, max_trace)
{
	if(!IsDefined(ent))
		return;
	
	if(!IsDefined(max_trace))
		max_trace = 300;
	
	start = ent.origin;
	end = ent.origin - (0,0,max_trace);
	trace = BulletTrace(start, end, false, ent);
	
	if(trace["fraction"]<1)
	{
		ent.origin = trace[ "position" ];
		return true;
	}
	else
	{
		return false;
	}
}

teleport_add_delta_targets(ent, delta)
{
	if(teleport_delta_this_frame(ent))
		return;
	
	teleport_add_delta(ent, delta);
	if(IsDefined(ent.target))
	{
		ents = GetEntArray(ent.target, "targetname");
		structs = getstructarray(ent.target, "targetname");
		targets = array_combine(ents,structs);
		array_levelthread(targets, ::teleport_add_delta_targets, delta);
	}
}

teleport_self_add_delta_targets(delta)
{
	teleport_add_delta_targets(self, delta);
}

teleport_self_add_delta(delta)
{
	teleport_add_delta(self, delta);
}

teleport_add_delta(ent, delta)
{
	if(IsDefined(ent))
	{
		if(!teleport_delta_this_frame(ent))
		{
			ent.origin += delta;
			ent.last_teleport_time=GetTime();
		}
	}
}

teleport_delta_this_frame(ent)
{
	return IsDefined(ent.last_teleport_time) && ent.last_teleport_time==GetTime();
}

teleport_get_active_nodes()
{
	return level.teleport_nodes_in_zone[level.teleport_zone_current];
}

teleport_get_active_pathnode_zones()
{
	return level.teleport_pathnode_zones[level.teleport_zone_current];
}