#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\gametypes\_gamelogic;
#include maps\mp\bots\_bots_util;
#include maps\mp\bots\_bots_strategy;
#include maps\mp\bots\_bots_personality;

/* 
=============
///ScriptDocBegin
"Name: bot_cache_entrances_to_bombzones()"
"Summary: Caches entrance points using the level.bombZones array"
"Example: bot_cache_entrances_to_bombzones();"
///ScriptDocEnd
============
 */
bot_cache_entrances_to_bombzones()
{
	assert( IsDefined(level.bombZones) );

	entrance_origin_points = [];
	entrance_labels = [];
	
	index = 0;
	foreach( zone in level.bombZones )
	{
		entrance_origin_points[index] = Random(zone.botTargets).origin;
		entrance_labels[index] = "zone" + zone.label;
		index++;
	}
	
	bot_cache_entrances( entrance_origin_points, entrance_labels );	
}

/* 
=============
///ScriptDocBegin
"Name: bot_cache_entrances_to_gametype_array()"
"Summary: Caches entrance points to a gametype array, like flags or ball goals (or any array accessed with .origin and .script_label)"
"MandatoryArg: <array> : An array of objects.  They must have member variables .origin and .script_label"
"MandatoryArg: <label_prefix> : Prefix to use for indices in the level.entrance_points array"
"OptionalArg: <ignore_paths> : If true, will only calculate entrances and will not calculated paths between the entrances"
"OptionalArg: <recalc_all> : If true, will clear out previous arrays and recalculate everything"
"Example: bot_cache_entrances_to_gametype_array( level.flags, "flag" );"
///ScriptDocEnd
============
 */
bot_cache_entrances_to_gametype_array( array, label_prefix, ignore_paths, recalc_all )
{
	assert( IsDefined(array) );
	
	wait(1.0);		// Wait for Path_AutoDisconnectPaths to run
	
	entrance_origin_points = [];
	entrance_labels = [];
	int_array_index = 0;
	
	foreach( i, element in array )
	{
		if ( IsDefined(array[i].botTarget) )
		{
			entrance_origin_points[int_array_index] = array[i].botTarget.origin;
		}
		else
		{
			array[i].nearest_node = GetClosestNodeInSight( array[i].origin );
			if ( !IsDefined( array[i].nearest_node ) )
			{
				nodes = GetNodesInRadiusSorted( array[i].origin, 256, 0 );
				if ( nodes.size > 0 )
					array[i].nearest_node = nodes[0];
			}
			
			if ( !IsDefined( array[i].nearest_node ) )
			{
				AssertMsg("Could not calculate nearest node to flag origin " + array[i].origin);				
				continue;
			}
			
			if ( Distance(array[i].nearest_node.origin, array[i].origin) > 128 )
			{
				AssertMsg("Gametype object origin " + array[i].origin + " is too far away from the nearest pathnode, at origin " + array[i].nearest_node.origin);
				array[i].nearest_node = undefined;
				continue;
			}
			
			entrance_origin_points[int_array_index] = array[i].nearest_node.origin;
		}
		entrance_labels[int_array_index] = label_prefix + array[i].script_label;
		int_array_index++;
	}
	
	bot_cache_entrances( entrance_origin_points, entrance_labels, ignore_paths, recalc_all );
}

/* 
=============
///ScriptDocBegin
"Name: bot_cache_entrances( <origin_array>, <label_array>, <ignore_paths> )"
"Summary: Uses the origin and label array to fill out level.entrance_points"
"MandatoryArg: <origin_array> : An array of origins (the points to find entrances for)"
"MandatoryArg: <label_array> : An array of labels (to use for indices into the entrances array)"
"OptionalArg: <ignore_paths> : If true, will only calculate entrances and will not calculated paths between the entrances"
"OptionalArg: <recalc_all> : If true, will clear out previous arrays and recalculate everything"
"Example: bot_cache_entrances( entrance_origin_points, entrance_labels );"
///ScriptDocEnd
============
 */
bot_cache_entrances( origin_array, label_array, ignore_paths, recalc_all )
{
	assert( IsDefined(origin_array) );
	assert( IsDefined(label_array) );
	assert( origin_array.size > 0 );
	assert( label_array.size > 0 );
	assert( origin_array.size == label_array.size );
	
	calculate_paths = !IsDefined(ignore_paths) || !ignore_paths;
	clear_out_old_data = IsDefined(recalc_all) && recalc_all;
	
	wait(0.1);
	
	if ( clear_out_old_data && calculate_paths )
	{
		nodes = GetAllNodes();
		foreach( node in nodes )
			node.on_path_from = undefined;
	}
	
	entrance_points = [];
	
	for ( i = 0; i < origin_array.size; i++ )
	{
		index = label_array[i];
		entrance_points[index] = FindEntrances( origin_array[i] );
		AssertEx( entrance_points[index].size > 0, "Entrance points for " + index + " at location " + origin_array[i] + " could not be calculated.  Check pathgrid around that area" );
		wait(0.05);
		
		for ( j = 0; j < entrance_points[index].size; j++ )
		{
			entrance = entrance_points[index][j];
			
			// Mark entrance as precalculated (to save checks in other places)
			entrance.is_precalculated_entrance = true;
			
			// Trace the entrance to determine prone visibility
			entrance.prone_visible_from[index] = entrance_visible_from( entrance.origin, origin_array[i], "prone" );
			wait(0.05);
			
			// Trace the entrance to determine crouch visibility
			entrance.crouch_visible_from[index] = entrance_visible_from( entrance.origin, origin_array[i], "crouch" );
			wait(0.05);
		}
	}
	
	precalculated_paths = [];
	if ( calculate_paths )
	{
		for ( i = 0; i < origin_array.size; i++ )
		{
			for ( j = i+1; j < origin_array.size; j++ )
			{
				// Find path from origin_array[i] to origin_array[j]
				path = get_extended_path( origin_array[i], origin_array[j] );
				AssertEx( IsDefined(path), "Error calculating path from " + label_array[i] + " " + origin_array[i] + " to " + label_array[j] + " " + origin_array[j] + ". Check pathgrid around those areas" );
				
/#
				if ( !IsDefined(path) )
					continue;	// avoid SRE spam when path is not defined
#/
				
/#
				precalculated_paths[label_array[i]][label_array[j]] = path;
				precalculated_paths[label_array[j]][label_array[i]] = path;
#/
				foreach( node in path )
					node.on_path_from[label_array[i]][label_array[j]] = true;
			}
		}
	}
	
	// Set the arrays here, so we don't get bots trying to access a partially-defined array while we're still filling it out
/#
	if ( calculate_paths )
	{
		if ( !IsDefined(level.precalculated_paths) )
			level.precalculated_paths = [];
	}
#/
	
	if ( !IsDefined(level.entrance_origin_points) )
		level.entrance_origin_points = [];
	
	if ( !IsDefined(level.entrance_indices) )
		level.entrance_indices = [];
	
	if ( !IsDefined(level.entrance_points) )
		level.entrance_points = [];
	
/#
	if ( calculate_paths )
	{
		if ( clear_out_old_data )
			level.precalculated_paths = precalculated_paths;
		else
			level.precalculated_paths = array_combine_non_integer_indices(level.precalculated_paths, precalculated_paths);
	}
#/
	if ( clear_out_old_data )
	{
		level.entrance_origin_points = origin_array;
		level.entrance_indices = label_array;
		level.entrance_points = entrance_points;
	}
	else
	{
		level.entrance_origin_points = array_combine(level.entrance_origin_points, origin_array);
		level.entrance_indices = array_combine(level.entrance_indices, label_array);
		level.entrance_points = array_combine_non_integer_indices(level.entrance_points, entrance_points);
	}
	
	level.entrance_points_finished_caching = true;	// This line should be the last line in the function
}

/* 
=============
///ScriptDocBegin
"Name: bot_add_missing_nodes( <struct_with_nodes>, trigger )"
"Summary: Finds any nodes that might be near (underneath) the trigger but not technically in it, and adds them to the struct's .nodes array"
"MandatoryArg: <struct_with_nodes> : A struct that contains a .nodes array (can be empty)"
"MandatoryArg: <trigger> The trigger to test: "
"Example: bot_add_missing_nodes(zone, zone.trig);"
///ScriptDocEnd
============
 */
bot_add_missing_nodes( struct_with_nodes, trigger )
{
	Assert(IsDefined(struct_with_nodes.nodes));
	
	if ( trigger.classname == "trigger_radius" )
	{
		test_nodes_in_radius = GetNodesInRadius( trigger.origin, trigger.radius, 0, 100 );
		nodes_in_radius_not_in_volume = array_remove_array(test_nodes_in_radius,struct_with_nodes.nodes);
		if ( nodes_in_radius_not_in_volume.size > 0 )
			struct_with_nodes.nodes = array_combine(struct_with_nodes.nodes,nodes_in_radius_not_in_volume);
	}
	else if ( trigger.classname == "trigger_multiple" || trigger.classname == "trigger_use_touch" )
	{
		// Find farthest bound
		bound_points[0] = trigger GetPointInBounds(  1,  1,  1 );
		bound_points[1] = trigger GetPointInBounds(  1,  1, -1 );
		bound_points[2] = trigger GetPointInBounds(  1, -1,  1 );
		bound_points[3] = trigger GetPointInBounds(  1, -1, -1 );
		bound_points[4] = trigger GetPointInBounds( -1,  1,  1 );
		bound_points[5] = trigger GetPointInBounds( -1,  1, -1 );
		bound_points[6] = trigger GetPointInBounds( -1, -1,  1 );
		bound_points[7] = trigger GetPointInBounds( -1, -1, -1 );
		
		farthest_dist = 0;
		foreach( point in bound_points )
		{
			dist = Distance(point,trigger.origin);
			if ( dist > farthest_dist )
				farthest_dist = dist;
		}
		
		test_nodes_in_radius = GetNodesInRadius( trigger.origin, farthest_dist, 0, 100 );
		foreach( node in test_nodes_in_radius )
		{
			if ( !IsPointInVolume( node.origin, trigger ) )
			{
				if ( IsPointInVolume( node.origin + (0,0,40), trigger ) || IsPointInVolume( node.origin + (0,0,80), trigger ) || IsPointInVolume( node.origin + (0,0,120), trigger ) )
				{
					struct_with_nodes.nodes = array_add(struct_with_nodes.nodes, node);
				}
			}
		}
	}
}

bot_setup_bombzone_bottargets()
{
	wait(1.0);		// Wait for Path_AutoDisconnectPaths to run
	
	bot_setup_bot_targets(level.bombZones);
	level.bot_set_bombzone_bottargets = true;
}

bot_setup_bot_targets(array)
{
	foreach ( element in array )
	{
		if ( !IsDefined( element.botTargets ) )
		{
			element.botTargets = [];
			nodes_in_trigger = GetNodesInTrigger( element.trigger );
			foreach( node in nodes_in_trigger )
			{
				if ( !node NodeIsDisconnected() )
					element.botTargets = array_add(element.botTargets, node);
			}
			
			element.nodes = element.botTargets;
			bot_add_missing_nodes(element,element.trigger);
			element.botTargets = element.nodes;
			element.nodes = undefined;
		}
	}
}

/#
bot_gametype_ignore_human_player_roles()
{
	return ( IsDefined(level.bot_gametype_ignore_human_player_roles) && level.bot_gametype_ignore_human_player_roles );
}
#/

bot_gametype_get_num_players_on_team(team)
{
	num_on_team = 0;
	foreach( player in level.participants )
	{
		if ( IsTeamParticipant(player) && IsDefined(player.team) && player.team == team )
		{
/#
			if ( bot_gametype_ignore_human_player_roles() && !IsAI(player) )
				continue;
#/
			num_on_team++;
		}
	}
	
	return num_on_team;
}

bot_gametype_get_allied_attackers_for_team(team, defend_origin, defend_radius)
{
	attackers = bot_gametype_get_players_by_role( "attacker", team );
	
	foreach ( player in level.players )
	{
		if ( !IsAI(player) && IsDefined(player.team) && player.team == team )
		{
/#
			if ( bot_gametype_ignore_human_player_roles() )
				continue;
#/
			if ( DistanceSquared(defend_origin,player.origin) > squared(defend_radius) )
				attackers = array_add(attackers, player);
		}
	}
	
	return attackers;
}

bot_gametype_get_allied_defenders_for_team(team, defend_origin, defend_radius)
{
	defenders = bot_gametype_get_players_by_role( "defender", team );
	
	foreach ( player in level.players )
	{
		if ( !IsAI(player) && IsDefined(player.team) && player.team == team )
		{
/#
			if ( bot_gametype_ignore_human_player_roles() )
				continue;
#/
			if ( DistanceSquared(defend_origin,player.origin) <= squared(defend_radius) )
				defenders = array_add(defenders, player);
		}
	}
	
	return defenders;
}

bot_gametype_set_role( new_role )
{
	self.role = new_role;
	self BotClearScriptGoal();
	self bot_defend_stop();
}

bot_gametype_get_players_by_role( role, team )
{
	players = [];
	foreach( player in level.participants )
	{
		if ( IsDefined( player.team ) && IsAlive(player) && IsTeamParticipant(player) && player.team == team && IsDefined(player.role) && player.role == role )
			players[players.size] = player;
	}
	
	return players;
}

bot_gametype_initialize_attacker_defender_role()
{
	attackers = [[level.bot_gametype_allied_attackers_for_team]](self.team);
	defenders = [[level.bot_gametype_allied_defenders_for_team]](self.team);
	attacker_limit = [[level.bot_gametype_attacker_limit_for_team]](self.team);
	defender_limit = [[level.bot_gametype_defender_limit_for_team]](self.team);
	
	personality_type = level.bot_personality_type[self.personality];
	if ( personality_type == "active" )
	{
		if ( attackers.size >= attacker_limit )
		{
			// try to kick out a stationary bot
			kicked_out_bot = false;
			foreach ( attacker in attackers )
			{
				if ( IsAI(attacker) && level.bot_personality_type[attacker.personality] == "stationary" )
				{
					attacker.role = undefined;
					kicked_out_bot = true;
					break;
				}
			}
			
			if ( kicked_out_bot )
				self bot_gametype_set_role("attacker");
			else
				self bot_gametype_set_role("defender");
		}
		else
		{
			self bot_gametype_set_role("attacker");
		}
	}
	else if ( personality_type == "stationary" )
	{
		if ( defenders.size >= defender_limit )
		{
			// try to kick out an active bot
			kicked_out_bot = false;
			foreach ( defender in defenders )
			{
				if ( IsAI(defender) && level.bot_personality_type[defender.personality] == "active" )
				{
					defender.role = undefined;
					kicked_out_bot = true;
					break;
				}
			}
			
			if ( kicked_out_bot )
				self bot_gametype_set_role("defender");
			else
				self bot_gametype_set_role("attacker");
		}
		else
		{
			self bot_gametype_set_role("defender");
		}
	}
}

bot_gametype_attacker_defender_ai_director_update()
{
	level notify("bot_gametype_attacker_defender_ai_director_update");
	level endon("bot_gametype_attacker_defender_ai_director_update");
	level endon("game_ended");
	
	teams = ["allies","axis"];
	next_time_manage_roles = GetTime() + 2000;
	
	while(1)
	{
		if ( GetTime() > next_time_manage_roles )
		{
			next_time_manage_roles = GetTime() + 1000;
			foreach( team in teams )
			{
				attackers = [[level.bot_gametype_allied_attackers_for_team]](team);
				defenders = [[level.bot_gametype_allied_defenders_for_team]](team);
				attacker_limit = [[level.bot_gametype_attacker_limit_for_team]](team);
				defender_limit = [[level.bot_gametype_defender_limit_for_team]](team);
				
				if ( attackers.size > attacker_limit )
				{
					ai_attackers = [];
					removed_attacker = false;
					foreach ( attacker in attackers )
					{
						if ( IsAI(attacker) )
						{
							if ( level.bot_personality_type[attacker.personality] == "stationary" )
							{
								attacker bot_gametype_set_role("defender");
								removed_attacker = true;
								break;
							}
							else
							{
								ai_attackers = array_add(ai_attackers, attacker);
							}
						}
					}
					
					if ( !removed_attacker && ai_attackers.size > 0 )
						Random(ai_attackers) bot_gametype_set_role("defender");
				}
				
				if ( defenders.size > defender_limit )
				{
					ai_defenders = [];
					removed_defender = false;
					foreach ( defender in defenders )
					{
						if ( IsAI(defender) )
						{
							if ( level.bot_personality_type[defender.personality] == "active" )
							{
								defender bot_gametype_set_role("attacker");
								removed_defender = true;
								break;
							}
							else
							{
								ai_defenders = array_add(ai_defenders, defender);
							}
						}
					}
					
					if ( !removed_defender && ai_defenders.size > 0 )
						Random(ai_defenders) bot_gametype_set_role("attacker");
				}
			}
		}
		
		wait(0.05);
	}
}