// Helper functions for bots #include common_scripts\utility; #include maps\mp\_utility; #include maps\mp\bots\_bots_personality; #include maps\mp\bots\_bots_strategy; /* ============= ///ScriptDocBegin "Name: bot_get_nodes_in_cone( , , )" "Summary: Gets all nodes in the cone in front of the bot, up to max dist and obeying vector_dot" "CallOn: A bot player" "MandatoryArg: : Max dist of cone" "MandatoryArg: : Nodes within this vector dot of the bot's direction will be in cluded" "MandatoryArg: : Only return nodes visible to the bot" "Example: " ///ScriptDocEnd ============= */ bot_get_nodes_in_cone( max_dist, vector_dot, only_visible_nodes ) { nodes_around_bot = GetNodesInRadius( self.origin, max_dist, 0 ); nodes_in_cone = []; nearest_node_to_bot = self GetNearestNode(); bot_dir = AnglesToForward( self GetPlayerAngles() ); bot_dir_norm = VectorNormalize( bot_dir * (1,1,0) ); foreach( node in nodes_around_bot ) { bot_to_node_norm = VectorNormalize( (node.origin - self.origin) * (1,1,0) ); dot = VectorDot( bot_to_node_norm, bot_dir_norm ); if ( dot > vector_dot ) { if ( !only_visible_nodes || (IsDefined(nearest_node_to_bot) && NodesVisible( node, nearest_node_to_bot, true )) ) nodes_in_cone = array_add( nodes_in_cone, node ); } } return nodes_in_cone; } /* ============= ///ScriptDocBegin "Name: bot_goal_can_override( , )" "Summary: Returns true if goal_type_1 can override goal_type_2" "CallOn: A bot player" "MandatoryArg: : The goal to test" "MandatoryArg: : The goal to test against" "Example: can_override_goal = bot_goal_can_override( goal_type, current_goal_type );" ///ScriptDocEnd ============ */ bot_goal_can_override( goal_type_1, goal_type_2 ) { if ( goal_type_1 == "none" ) { // "none" can only override "none" return (goal_type_2 == "none"); } else if ( goal_type_1 == "hunt" ) { // "hunt" can override "hunt" or "none" return (goal_type_2 == "hunt" || goal_type_2 == "none"); } else if ( goal_type_1 == "guard" ) { // "guard" can override "guard", "hunt", or "none" return (goal_type_2 == "guard" || goal_type_2 == "hunt" || goal_type_2 == "none"); } else if ( goal_type_1 == "objective" ) { // "objective" can override "objective", "guard", "hunt", or "none" return (goal_type_2 == "objective" || goal_type_2 == "guard" || goal_type_2 == "hunt" || goal_type_2 == "none"); } else if ( goal_type_1 == "critical" ) { // "critical" can override "critical", "objective", "guard", "hunt", or "none" return (goal_type_2 == "critical" || goal_type_2 == "objective" || goal_type_2 == "guard" || goal_type_2 == "hunt" || goal_type_2 == "none"); } else if ( goal_type_1 == "tactical" ) { // "tactical" can override everything return true; } AssertEx( false, "Unsupported parameter passed in to bot_goal_can_override()" ); } /* ============= ///ScriptDocBegin "Name: bot_set_personality()" "Summary: Sets the personality for this bot. Necessary because script needs to re-assign function pointers, etc." "CallOn: A bot player" "Example: self bot_set_personality("camper")" ///ScriptDocEnd ============ */ bot_set_personality(personality) { self BotSetPersonality( personality ); self bot_assign_personality_functions(); self BotClearScriptGoal(); } /* ============= ///ScriptDocBegin "Name: bot_set_difficulty()" "Summary: Sets the difficulty for this bot." "CallOn: A bot player" "Example: self bot_set_difficulty("hardened")" ///ScriptDocEnd ============ */ bot_set_difficulty( difficulty ) { assert( IsAI( self ) ); /# if ( IsTeamParticipant(self) ) { // Difficulty cannot be changed when using bot_DebugDifficulty dvar override debugDifficulty = GetDvar( "bot_DebugDifficulty" ); if ( debugDifficulty != "default" ) { difficulty = debugDifficulty; } } #/ // Choose difficulty if need be if ( difficulty == "default" ) difficulty = self bot_choose_difficulty_for_default(); self BotSetDifficulty( difficulty ); if ( IsPlayer( self ) ) { self.pers[ "rankxp" ] = self get_rank_xp_for_bot(); self maps\mp\gametypes\_rank::playerUpdateRank(); } } /* ============= ///ScriptDocBegin "Name: bot_choose_difficulty_for_default()" "Summary: Chooses a difficulty when difficulty is set to "default"" "CallOn: A bot player" "Example: difficulty = self bot_choose_difficulty_for_default();" ///ScriptDocEnd ============ */ bot_choose_difficulty_for_default( ) { if ( !IsDefined( level.bot_difficulty_defaults ) ) { level.bot_difficulty_defaults = []; level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "recruit"; level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "regular"; level.bot_difficulty_defaults[level.bot_difficulty_defaults.size] = "hardened"; } difficulty = self.bot_chosen_difficulty; if ( !IsDefined( difficulty ) ) { inUseCount = []; team = self.team; if ( !IsDefined( team ) ) team = self.bot_team; if ( !IsDefined( team ) ) team = self.pers["team"]; if ( !IsDefined( team ) ) team = "allies"; foreach ( player in level.players ) { if ( player == self ) continue; if ( !isAI( player ) ) continue; usedDifficulty = player BotGetDifficulty(); if ( usedDifficulty == "default" ) continue; otherTeam = player.team; if ( !IsDefined( otherTeam ) ) otherTeam = player.bot_team; if ( !IsDefined( otherTeam ) ) otherTeam = player.pers["team"]; if ( !IsDefined( otherTeam ) ) continue; if ( !IsDefined( inUseCount[otherTeam] ) ) inUseCount[otherTeam] = []; if ( !IsDefined( inUseCount[otherTeam][usedDifficulty] ) ) inUseCount[otherTeam][usedDifficulty] = 1; else inUseCount[otherTeam][usedDifficulty]++; } lowest = -1; foreach ( choice in level.bot_difficulty_defaults ) { if ( !IsDefined( inUseCount[team] ) || !IsDefined( inUseCount[team][choice] ) ) { difficulty = choice; break; } else if ( lowest == -1 || inUseCount[team][choice] < lowest ) { lowest = inUseCount[team][choice]; difficulty = choice; } } } if ( IsDefined( difficulty ) ) self.bot_chosen_difficulty = difficulty; return difficulty; } /* ============= ///ScriptDocBegin "Name: bot_is_capturing()" "Summary: Checks if this bot is capturing a point or zone" "CallOn: A bot player" "Example: if ( self bot_is_capturing() )" ///ScriptDocEnd ============ */ bot_is_capturing() { if ( self bot_is_defending() ) { if ( self.bot_defending_type == "capture" || self.bot_defending_type == "capture_zone" ) { return true; } } return false; } /* ============= ///ScriptDocBegin "Name: bot_is_patrolling()" "Summary: Checks if this bot is patrolling a point" "CallOn: A bot player" "Example: if ( self bot_is_patrolling() )" ///ScriptDocEnd ============ */ bot_is_patrolling() { if ( self bot_is_defending() ) { if ( self.bot_defending_type == "patrol" ) { return true; } } return false; } /* ============= ///ScriptDocBegin "Name: bot_is_protecting()" "Summary: Checks if this bot is protecting a point" "CallOn: A bot player" "Example: if ( self bot_is_protecting() )" ///ScriptDocEnd ============ */ bot_is_protecting() { if ( self bot_is_defending() ) { if ( self.bot_defending_type == "protect" ) { return true; } } return false; } /* ============= ///ScriptDocBegin "Name: bot_is_bodyguarding()" "Summary: Checks if this bot is a bodyguard" "CallOn: A bot player" "Example: if ( self bot_is_bodyguarding() )" ///ScriptDocEnd ============ */ bot_is_bodyguarding() { if ( self bot_is_defending() ) { if ( self.bot_defending_type == "bodyguard" ) { return true; } } return false; } /* ============= ///ScriptDocBegin "Name: bot_is_defending()" "Summary: Checks if this bot is defending" "CallOn: A bot player" "Example: if ( self bot_is_defending() )" ///ScriptDocEnd ============ */ bot_is_defending() { return ( IsDefined( self.bot_defending ) ); } /* ============= ///ScriptDocBegin "Name: bot_is_defending_point( )" "Summary: Checks if this bot is defending a specific point" "CallOn: A bot player" "MandatoryArg: : The point to check" "Example: if ( !self bot_is_defending_point(level.sdBombModel.origin) )" ///ScriptDocEnd ============ */ bot_is_defending_point(point) { if ( self bot_is_defending() ) { if ( bot_vectors_are_equal(self.bot_defending_center,point) ) { return true; } } return false; } /* ============= ///ScriptDocBegin "Name: bot_is_guarding_player( )" "Summary: Checks if this bot is guarding a specific player" "CallOn: A bot player" "MandatoryArg: : The player to check" "Example: if ( !self bot_is_guarding_player( self.owner ) )" ///ScriptDocEnd ============ */ bot_is_guarding_player( player ) { if ( self bot_is_bodyguarding() && self.bot_defend_player_guarding == player ) return true; return false; } /* ============= ///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_flags_or_radios()" "Summary: Caches entrance points using the flags or radios array (or any array accessed with .origin and .script_label)" "MandatoryArg: : An array of objects. They must have member variables .origin and .script_label" "MandatoryArg: : Prefix to use for indices in the level.entrance_points array" "Example: bot_cache_entrances_to_flags_or_radios( level.flags, "flag" );" ///ScriptDocEnd ============ */ bot_cache_entrances_to_flags_or_radios( array, label_prefix ) { assert( IsDefined(array) ); wait(1.0); // Wait for Path_AutoDisconnectPaths to run entrance_origin_points = []; entrance_labels = []; for ( i = 0; i < array.size; i++ ) { if ( IsDefined(array[i].botTarget) ) { entrance_origin_points[i] = array[i].botTarget.origin; } else { array[i].nearest_node = GetClosestNodeInSight( array[i].origin ); /# AssertEx(IsDefined(array[i].nearest_node), "Could not calculate nearest node to flag origin " + array[i].origin); dist_node_to_origin = Distance(array[i].nearest_node.origin, array[i].origin); AssertEx(dist_node_to_origin < 128, "Flag origin " + array[i].origin + " is too far away from the nearest pathnode, at origin " + array[i].nearest_node.origin); #/ entrance_origin_points[i] = array[i].nearest_node.origin; } entrance_labels[i] = label_prefix + array[i].script_label; } bot_cache_entrances( entrance_origin_points, entrance_labels ); } /* ============= ///ScriptDocBegin "Name: entrance_visible_from( , , )" "Summary: Checks if the specified is visible from the with the given " "MandatoryArg: : Origin of the entrance node" "MandatoryArg: : Origin to check visibility from" "MandatoryArg: : The stance at the to check" "Example: entrance.prone_visible_from[index] = entrance_visible_from( entrance.origin, origin_array[i], "prone" );" ///ScriptDocEnd ============ */ entrance_visible_from( entrance_origin, from_origin, stance ) { assert( (stance == "stand") || (stance == "crouch") || stance == ("prone") ); prone_offset = (0,0,11); crouch_offset = (0,0,40); offset = undefined; if ( stance == "stand" ) return true; else if ( stance == "crouch" ) offset = crouch_offset; else if ( stance == "prone" ) offset = prone_offset; return SightTracePassed( from_origin+offset, entrance_origin+offset, false, undefined ); } /* ============= ///ScriptDocBegin "Name: bot_cache_entrances( , )" "Summary: Uses the origin and label array to fill out level.entrance_points" "MandatoryArg: : An array of origins (the points to find entrances for)" "MandatoryArg: : An array of labels (to use for indices into the entrances array)" "Example: bot_cache_entrances( entrance_origin_points, entrance_labels );" ///ScriptDocEnd ============ */ bot_cache_entrances( origin_array, label_array ) { 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 ); wait(0.1); 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); // Initialize "on_path_from" arrays (so we can check them later without having to first check IsDefined) for ( k = 0; k < label_array.size; k++ ) { for( l = k+1; l < label_array.size; l++ ) { entrance.on_path_from[label_array[k]][label_array[l]] = 0; entrance.on_path_from[label_array[l]][label_array[k]] = 0; } } } } precalculated_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; node.on_path_from[label_array[j]][label_array[i]] = 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 ( !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 = []; level.precalculated_paths = array_combine_non_integer_indices(level.precalculated_paths, precalculated_paths); 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: get_extended_path( , )" "Summary: Gets a "wider" path from start to end. Includes all the nodes in the direct path, plus any nodes that are linked along the way" "MandatoryArg: : The start location" "MandatoryArg: : The end location" "Example: path = get_extended_path( start, end );" ///ScriptDocEnd ============ */ get_extended_path( start, end ) { path = func_get_nodes_on_path( start, end ); if ( IsDefined( path ) ) { path = remove_ends_from_path( path ); path = get_all_connected_nodes( path ); } return path; } /* ============= ///ScriptDocBegin "Name: func_get_path_dist( , )" "Summary: threadable call to GetPathDist() native function" "MandatoryArg: : The start location" "MandatoryArg: : The end location" "Example: path = func_get_path_dist( start, end );" ///ScriptDocEnd ============ */ func_get_path_dist( start, end ) { return GetPathDist( start, end ); } /* ============= ///ScriptDocBegin "Name: func_get_nodes_on_path( , )" "Summary: threadable call to GetNodesOnPath() native function" "MandatoryArg: : The start location" "MandatoryArg: : The end location" "Example: path = func_get_nodes_on_path( start, end );" ///ScriptDocEnd ============ */ func_get_nodes_on_path( start, end ) { return GetNodesOnPath( start, end ); } /* ============= ///ScriptDocBegin "Name: func_bot_get_closest_navigable_point( , , )" "Summary: threadable call to BotGetClosestNavigablePoint() native function" "MandatoryArg: : The point to search around" "MandatoryArg: : The max distance around the point to search" "OptionalArg: The entity whose clip mask we will be using" "Example: nearest_point = func_bot_get_closest_navigable_point(crate.origin, player_use_radius);" ///ScriptDocEnd ============ */ func_bot_get_closest_navigable_point( origin, radius, entity ) { return BotGetClosestNavigablePoint( origin, radius, entity ); } /* ============= ///ScriptDocBegin "Name: node_is_on_path_from_labels( , )" "Summary: Checks if the node is contained in the path from label1 to label2" "Summary: The labels correspond to elements in the level.entrance_indices, which match up with origins in level.entrance_origin_points" "MandatoryArg: : The first label" "MandatoryArg: : The second label" "Example: if ( node node_is_on_path_from_labels(self.current_flag, flag_complete_label) )" ///ScriptDocEnd ============ */ node_is_on_path_from_labels( label1, label2 ) { if ( !IsDefined( self.on_path_from ) || !IsDefined( self.on_path_from[label1] ) || !IsDefined( self.on_path_from[label1][label2] ) ) return false; return self.on_path_from[label1][label2]; } /* ============= ///ScriptDocBegin "Name: get_all_connected_nodes( )" "Summary: Returns the array nodes and all nodes connected to them" "MandatoryArg: : The array of nodes" "Example: all_nodes = get_all_connected_nodes(path);" ///ScriptDocEnd ============ */ get_all_connected_nodes( nodes ) { all_nodes = nodes; for ( i = 0; i < nodes.size; i++ ) { //bot_draw_cylinder(nodes[i].origin, 10, 20, 20, undefined, (0,0,1), true, 4); linked_nodes = GetLinkedNodes( nodes[i] ); for ( j = 0; j < linked_nodes.size; j++ ) { if ( !array_contains( all_nodes, linked_nodes[j] ) ) { all_nodes = array_add( all_nodes, linked_nodes[j] ); //line( nodes[i].origin, linked_nodes[j].origin, (0,1,0), 1.0, true, 20*20 ); //bot_draw_cylinder(linked_nodes[j].origin, 10, 10, 20, undefined, (0,1,0), true, 4); } } } return all_nodes; } /* ============= ///ScriptDocBegin "Name: get_visible_nodes_array( , )" "Summary: Returns all nodes in the array that are visible from using node visibility" "MandatoryArg: : The array of nodes" "MandatoryArg: : The node we're looking from" "Example: visible_nodes = get_visible_nodes_array( nodes, current_nearest_node );" ///ScriptDocEnd ============ */ get_visible_nodes_array( nodes, node_from ) { visible_nodes = []; foreach( node in nodes ) { if ( NodesVisible( node, node_from, true ) ) visible_nodes = array_add( visible_nodes, node ); } return visible_nodes; } /* ============= ///ScriptDocBegin "Name: remove_ends_from_path( )" "Summary: Removes the first and last node from the path and returns the resulting array" "MandatoryArg: : The array of nodes in the path" "Example: path = remove_ends_from_path(path);" ///ScriptDocEnd ============ */ remove_ends_from_path( path ) { path[path.size-1] = undefined; path[0] = undefined; return array_removeUndefined( path ); } /* ============= ///ScriptDocBegin "Name: bot_waittill_bots_enabled( )" "Summary: Waits until bots are enabled (until bot_AutoConnectDefault is 1 or bots are added via the devgui)" "OptionalArg: : Only count bots or agents that actually participate in team activities (planting bombs, etc)" "Example: bot_waittill_bots_enabled();" ///ScriptDocEnd ============ */ bot_waittill_bots_enabled( only_team_participants ) { keep_looping = true; while( !bot_bots_enabled_or_added( only_team_participants ) ) { wait(0.5); } } /* ============= ///ScriptDocBegin "Name: bot_bots_enabled_or_added( )" "Summary: Return true if bots are enabled (bot_AutoConnectDefault is 1 or bots have been added via the devgui)" "OptionalArg: : Only count bots or agents that actually participate in team activities (planting bombs, etc)" "Example: if ( bot_bots_enabled_or_added() )" ///ScriptDocEnd ============ */ bot_bots_enabled_or_added( only_team_participants ) { if ( BotAutoConnectEnabled() ) return true; if ( bots_exist( only_team_participants ) ) return true; return false; } /* ============= ///ScriptDocBegin "Name: bot_waittill_out_of_combat_or_time(