// Common strategy functions for bots #include common_scripts\utility; #include maps\mp\_utility; #include maps\mp\bots\_bots_util; /* ============= ///ScriptDocBegin "Name: bot_defend_get_random_entrance_point_for_current_area()" "Summary: Gets a random entrance point for the bot's current defend area, using self.cur_defend_stance" "Example: entrance_point = self bot_defend_get_random_entrance_point_for_current_area();" ///ScriptDocEnd ============ */ bot_defend_get_random_entrance_point_for_current_area() { all_cached_entrances = self bot_defend_get_precalc_entrances_for_current_area( self.cur_defend_stance ); if ( IsDefined(all_cached_entrances) && all_cached_entrances.size > 0 ) { return random(all_cached_entrances).origin; } return undefined; } /* ============= ///ScriptDocBegin "Name: bot_defend_get_precalc_entrances_for_current_area( )" "Summary: Gets entrance points for the bot's current defend area, with the given stance" "MandatoryArg: : "stand", "crouch", or "prone" " "Example: all_cached_entrances = self bot_defend_get_precalc_entrances_for_current_area( self.cur_defend_stance );" ///ScriptDocEnd ============ */ bot_defend_get_precalc_entrances_for_current_area( stance ) { if ( IsDefined( self.defend_entrance_index ) ) return bot_get_entrances_for_stance_and_index(stance,self.defend_entrance_index); return []; } 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_radio_bottargets() { bot_setup_bot_targets(level.radios); } 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); } } } } /* ============= ///ScriptDocBegin "Name: bot_get_ambush_trap_item( , , )" "Summary: Returns array of options with "item_action" = "lethal" or "tactical" if bot has appropriate trap item in hand" "CallOn: A bot player" "MandatoryArg: : one of "trap_directional", "trap", or "c4" "OptionalArg: : one of "trap_directional", "trap", or "c4" "OptionalArg: : one of "trap_directional", "trap", or "c4" "Example: trap_item = self bot_get_ambush_trap_item();" ///ScriptDocEnd ============ */ bot_get_ambush_trap_item( purposePriority1, purposePriority2, purposePriority3 ) { result = []; purpose_priority = []; purpose_priority[purpose_priority.size] = purposePriority1; if ( IsDefined( purposePriority2 ) ) purpose_priority[purpose_priority.size] = purposePriority2; if ( IsDefined( purposePriority2 ) ) purpose_priority[purpose_priority.size] = purposePriority3; foreach ( purpose in purpose_priority ) { result["purpose"] = purpose; result["item_action"] = self bot_get_grenade_for_purpose( purpose ); if ( IsDefined( result["item_action"] ) ) return result; } } /* ============= ///ScriptDocBegin "Name: bot_set_ambush_trap( , , , , )" "Summary: Instructs the bot to place some hardware to protect a location. Returns true if goal was set in an attempt to set the trap." "CallOn: A bot player" "MandatoryArg: : Result of a previous call to self bot_get_ambush_trap_item()" "MandatoryArg: : The player to protect" "MandatoryArg: : The allowable radius around the player" "OptionalArg: : The direction the bot will be ambushing" "OptionalArg: : If you already know where you want to place it" "Example: self bot_set_optional_ambush_trap( self.ambush_entrances, self.node_ambushing_from, self.ambush_yaw );" ///ScriptDocEnd ============ */ bot_set_ambush_trap( trap_item, ambush_entrances, ambush_node, ambush_yaw, trap_node ) { self notify("bot_set_ambush_trap"); self endon("bot_set_ambush_trap"); if ( !isDefined( trap_item ) ) return false; chosen_entrance = undefined; if ( !IsDefined( trap_node ) && isDefined( ambush_entrances ) && ambush_entrances.size > 0 ) { if ( !isDefined( ambush_node ) ) return false; choose_set = []; fwd = undefined; if ( IsDefined( ambush_yaw ) ) fwd = AnglesToForward( (0, ambush_yaw, 0) ); foreach( entrance in ambush_entrances ) { // If ambush_yaw is defined only choose entrances that are not right next to ambushing location and optionally not out in front of where we are facing if ( !isDefined( fwd ) ) { choose_set[choose_set.size] = entrance; } else if ( DistanceSquared( entrance.origin, ambush_node.origin ) > 300*300 ) { if ( VectorDot( fwd, VectorNormalize( entrance.origin - ambush_node.origin ) ) < 0.4 ) { choose_set[choose_set.size] = entrance; } } } if ( choose_set.size > 0 ) { chosen_entrance = Random(choose_set); trap_choices = GetNodesInRadius( chosen_entrance.origin, 300, 50 ); // Remove any nodes that are an ambush destination tempChoices = []; foreach( node in trap_choices ) { if ( !IsDefined( node.bot_ambush_end ) ) tempChoices[tempChoices.size] = node; } trap_choices = tempChoices; trap_node = self BotNodePick( trap_choices, min( trap_choices.size, 3 ), "node_trap", ambush_node, chosen_entrance ); } } if ( isDefined( trap_node ) ) { yaw = undefined; if ( trap_item["purpose"] == "trap_directional" && isDefined( chosen_entrance ) ) { placeAngles = VectorToAngles( chosen_entrance.origin - trap_node.origin ); yaw = placeAngles[1]; } if ( (self BotHasScriptGoal()) && (self BotGetScriptGoalType() != "critical") && (self BotGetScriptGoalType() != "tactical") ) self BotClearScriptGoal(); goal_succeeded = self BotSetScriptGoalNode( trap_node, "guard", yaw ); if ( goal_succeeded ) { result = self bot_waittill_goal_or_fail(); if ( result == "goal" ) { self thread bot_force_stance_for_time( "stand", 5.0 ); if ( !IsDefined( self.enemy ) || (false == (self BotCanSeeEntity( self.enemy ))) ) { if ( IsDefined( yaw ) ) self BotThrowGrenade( chosen_entrance.origin, trap_item["item_action"] ); else self BotThrowGrenade( self.origin + (AnglesToForward(self.angles) * 50), trap_item["item_action"] ); self.ambush_trap_ent = undefined; self thread bot_set_ambush_trap_wait_fire( "grenade_fire" ); self thread bot_set_ambush_trap_wait_fire( "missile_fire" ); timeToWait = 3.0; if ( trap_item["purpose"] == "tacticalinsertion" ) timeToWait = 6.0; self waittill_any_timeout( timeToWait, "missile_fire", "grenade_fire" ); wait 0.05; self notify( "ambush_trap_ent" ); if ( IsDefined( self.ambush_trap_ent ) && trap_item["purpose"] == "c4" ) self thread bot_watch_manual_detonate( self.ambush_trap_ent, trap_item["item_action"], 300 ); self.ambush_trap_ent = undefined; wait RandomFloat( 0.25 ); self BotSetStance( "none" ); } } return true; } } return false; } bot_set_ambush_trap_wait_fire( waitingfor ) { self endon( "death" ); self endon( "disconnect" ); self endon( "bot_set_ambush_trap" ); self endon( "ambush_trap_ent" ); level endon( "game_ended" ); self waittill( waitingFor, ent ); self.ambush_trap_ent = ent; } /* ============= ///ScriptDocBegin "Name: bot_watch_manual_detonate( , , )" "Summary: If at any time we become aware of an enemy within range of this grenade, detonate it" "CallOn: A bot player" "MandatoryArg: : The grenade ent" "MandatoryArg: : The BotThrowGrenade type to detonate "lethal" or "tactical" " "MandatoryArg: : The maximum range of detonation " "Example: self thread bot_watch_manual_detonate( grenade, "lethal", 300 );" ///ScriptDocEnd ============ */ bot_watch_manual_detonate( grenade, item_action, range ) { self endon( "death" ); self endon( "disconnect" ); grenade endon( "death" ); level endon( "game_ended" ); rangeSq = range * range; while( 1 ) { // Make sure we are out of range first if ( IsDefined( grenade.origin ) && DistanceSquared( self.origin, grenade.origin ) > rangeSq ) { // If we see an enemy, see them on radar, hear of, or in any way know there is an enemy within range, detonate closestEnemySq = self GetClosestEnemySqDist( grenade.origin, 1.0 ); if ( closestEnemySq < rangeSq ) { self BotPressButton( item_action ); return; } } wait RandomFloatRange( 0.25, 1.0 ); } } /* ============= ///ScriptDocBegin "Name: bot_capture_point( , , )" "Summary: Instructs the bot to capture the point while staying within the radius" "CallOn: A bot player" "MandatoryArg: : The point to capture" "MandatoryArg: : The allowable radius around the point" "OptionalArg: : For valid options, see bot_defend_think() "Example: self bot_capture_point(flag.origin, 250);" ///ScriptDocEnd ============ */ bot_capture_point( point, radius, optional_params ) { self thread bot_defend_think( point, radius, "capture", optional_params ); } /* ============= ///ScriptDocBegin "Name: bot_capture_zone( , , , )" "Summary: Instructs the bot to capture an area by sticking to a given set of points" "CallOn: A bot player" "MandatoryArg: : The point to capture" "MandatoryArg: : The array of valid nodes to use" "MandatoryArg: : The trigger to stay in "OptionalArg: : For valid options, see bot_defend_think() "Example: self bot_capture_zone( flag.origin, flag.nodes, flag, "flag" + flag.script_label );" ///ScriptDocEnd ============ */ bot_capture_zone( point, nodes, capture_trigger, optional_params ) { Assert( IsDefined(nodes) && nodes.size > 0 ); /# if ( !IsDefined(nodes) || nodes.size == 0 ) return; // In non-ship, stop the defense to avoid SRE spam #/ optional_params["capture_trigger"] = capture_trigger; self thread bot_defend_think( point, nodes, "capture_zone", optional_params ); } /* ============= ///ScriptDocBegin "Name: bot_protect_point( , , )" "Summary: Instructs the bot to protect the point while staying within the radius" "CallOn: A bot player" "MandatoryArg: : The point to protect" "MandatoryArg: : The allowable radius around the point" "OptionalArg: : For valid options, see bot_defend_think() "Example: self bot_protect_point(flag.origin, 250);" ///ScriptDocEnd ============ */ bot_protect_point( point, radius, optional_params ) { if( !IsDefined(optional_params) || !IsDefined(optional_params["min_goal_time"]) ) optional_params["min_goal_time"] = 12; if( !IsDefined(optional_params) || !IsDefined(optional_params["max_goal_time"]) ) optional_params["max_goal_time"] = 18; self thread bot_defend_think( point, radius, "protect", optional_params ); } /* ============= ///ScriptDocBegin "Name: bot_patrol_area( , , )" "Summary: Instructs the bot to patrol within a radius around a point" "CallOn: A bot player" "MandatoryArg: : The point around which to patrol" "MandatoryArg: : The allowable radius around the point" "OptionalArg: : For valid options, see bot_defend_think() "Example: self bot_patrol_area(flag.origin, 1000);" ///ScriptDocEnd ============ */ bot_patrol_area( point, radius, optional_params ) { if( !IsDefined(optional_params) || !IsDefined(optional_params["min_goal_time"]) ) optional_params["min_goal_time"] = 0.00; if( !IsDefined(optional_params) || !IsDefined(optional_params["max_goal_time"]) ) optional_params["max_goal_time"] = 0.01; self thread bot_defend_think( point, radius, "patrol", optional_params ); } /* ============= ///ScriptDocBegin "Name: bot_guard_player( , , )" "Summary: Instructs the bot to guard the player while staying within the radius" "CallOn: A bot player" "MandatoryArg: : The player to guard" "MandatoryArg: : The allowable radius around the player" "OptionalArg: : For valid options, see bot_defend_think() "Example: self bot_guard_player( level.bomb_carrier, 400 );" ///ScriptDocEnd ============ */ bot_guard_player( player, radius, optional_params ) { if( !IsDefined(optional_params) || !IsDefined(optional_params["min_goal_time"]) ) optional_params["min_goal_time"] = 15; if( !IsDefined(optional_params) || !IsDefined(optional_params["max_goal_time"]) ) optional_params["max_goal_time"] = 20; self thread bot_defend_think( player, radius, "bodyguard", optional_params ); } // Don't call this function directly, use the above accessor functions bot_defend_think( defendCenter, defendRadius, defense_type, optional_params ) { // Valid options for optional_params: // // "entrance_points_index" : The index into level.entrance_points for the area // "override_entrances" : An array of nodes to force bot to use as entrances to the area // "override_goal_type" : The type of goal to use "guard", "critical", etc. // "override_origin_node" : If defined, this node will be used as the "origin" of the protect for calculation purposes (if the actual center is in solid, for example) // "nearest_node_to_center" : The nearest node to the center point. If not defined, it will be calculated automatically. // "capture_trigger" : The trigger to use for capturing (set automatically in bot_capture_zone) // "min_goal_time" : Time in seconds // "max_goal_time" : Time in seconds // "score_flags" : Extra flags to pass into the scoring method self notify( "started_bot_defend_think" ); self endon( "started_bot_defend_think" ); self endon( "death" ); self endon( "disconnect" ); level endon( "game_ended" ); self endon( "defend_stop" ); /# if ( !IsAIGameParticipant(self) ) { AssertMsg("Entity of type <" + self.classname + "> is calling bot_defend_think and is not an entity that can perform this (needs to be a bot or humanoid agent"); return; } #/ self thread defense_death_monitor(); if ( IsDefined(self.bot_defending) || self BotGetScriptGoalType() == "camp" ) { // If we are already defending something, or we are currently camping, we should clear our current script goal self BotClearScriptGoal(); } self.bot_defending = true; self.bot_defending_type = defense_type; if ( defense_type == "capture_zone" ) { self.bot_defending_radius = undefined; self.bot_defending_nodes = defendRadius; self.bot_defending_trigger = optional_params["capture_trigger"]; Assert(IsDefined(self.bot_defending_nodes) && self.bot_defending_nodes.size > 0); /# if ( !IsDefined(self.bot_defending_nodes) || self.bot_defending_nodes.size == 0 ) self bot_defend_stop(); // In non-ship, stop the defense to avoid SRE spam #/ } else { /# if ( IsDefined(optional_params) ) AssertEx(!IsDefined(optional_params["capture_trigger"]), "Only a defense of type 'capture_zone' should have a 'capture_trigger' defined"); #/ self.bot_defending_radius = defendRadius; self.bot_defending_nodes = undefined; self.bot_defending_trigger = undefined; } /# if ( !IsDefined(defendCenter) ) { AssertMsg("Starting bot_defend_think with an undefined "); self bot_defend_stop(); } #/ if ( IsGameParticipant(defendCenter) ) { self.bot_defend_player_guarding = defendCenter; self childthread monitor_defend_player(); } else { self.bot_defend_player_guarding = undefined; self.bot_defending_center = defendCenter; } /# if ( self.bot_defending_type == "bodyguard" ) { if ( !IsDefined(self.bot_defend_player_guarding) || !IsGameParticipant(self.bot_defend_player_guarding) ) AssertMsg( "Bot <" + self.name + "> was told to guard an invalid player" ); } #/ self BotSetStance("none"); goal_type = undefined; min_goal_time = 6; max_goal_time = 10; if ( IsDefined(optional_params) ) { self.defend_entrance_index = optional_params["entrance_points_index"]; self.defense_score_flags = optional_params["score_flags"]; self.bot_defending_override_origin_node = optional_params["override_origin_node"]; if ( IsDefined(optional_params["override_goal_type"]) ) goal_type = optional_params["override_goal_type"]; if ( IsDefined(optional_params["min_goal_time"]) ) min_goal_time = optional_params["min_goal_time"]; if ( IsDefined(optional_params["max_goal_time"]) ) max_goal_time = optional_params["max_goal_time"]; if ( IsDefined(optional_params["override_entrances"]) && optional_params["override_entrances"].size > 0 ) { self.defense_override_entrances = optional_params["override_entrances"]; // Create a unique index for this bot's defense at this time, so that the visibility checks below will be unique to this defense self.defend_entrance_index = self.name + " " + GetTime(); // need to calculate crouch and prone visibility foreach( entrance in self.defense_override_entrances ) { entrance.prone_visible_from[self.defend_entrance_index] = entrance_visible_from( entrance.origin, self defend_valid_center(), "prone" ); wait(0.05); entrance.crouch_visible_from[self.defend_entrance_index] = entrance_visible_from( entrance.origin, self defend_valid_center(), "crouch" ); wait(0.05); } } } if ( !IsDefined(self.bot_defend_player_guarding) ) { // In the case where we are guarding a player, the nearest node is set per frame, so its not done here nearest_node = undefined; // First priority is the specified nearest node that was passed in if ( IsDefined(optional_params) && IsDefined(optional_params["nearest_node_to_center"]) ) nearest_node = optional_params["nearest_node_to_center"]; // Next priority is the specified override origin node, since it is the center in this case and is a valid node if ( !IsDefined(nearest_node) && IsDefined( self.bot_defending_override_origin_node ) ) nearest_node = self.bot_defending_override_origin_node; // Next priority would be the nearest node defined on the trigger that was pre-calculated and passed in if ( !IsDefined(nearest_node) && IsDefined(self.bot_defending_trigger) && IsDefined(self.bot_defending_trigger.nearest_node) ) nearest_node = self.bot_defending_trigger.nearest_node; // If none of these succeeded, then find the nearest node in sight if ( !IsDefined(nearest_node) ) nearest_node = GetClosestNodeInSight( self defend_valid_center() ); // If we couldn't find the nearest node in sight, try to calculate it via nodes in radius and sight traces if ( !IsDefined(nearest_node) ) { defend_center = self defend_valid_center(); nodes = GetNodesInRadiusSorted( defend_center, 256, 0 ); for ( i = 0; i < nodes.size; i++ ) { // Try a sight trace extended out a third from the origin point center_to_node = VectorNormalize(nodes[i].origin - defend_center); trace_start = defend_center + center_to_node * 15; if ( SightTracePassed( trace_start, nodes[i].origin, false, undefined ) ) { nearest_node = nodes[i]; break; } wait(0.05); // Try a similar trace but at standing height if ( SightTracePassed( trace_start + (0,0,55), nodes[i].origin + (0,0,55), false, undefined ) ) { nearest_node = nodes[i]; break; } wait(0.05); } } self.node_closest_to_defend_center = nearest_node; AssertEx( IsDefined(self.node_closest_to_defend_center), "bot_defend_think: Could not calculate a nearest node to defense at origin " + self defend_valid_center() ); } AssertEx( min_goal_time < max_goal_time, "bot_defend_think: must be less than " ); find_node_function = level.bot_find_defend_node_func[defense_type]; if ( !IsDefined(goal_type) ) { goal_type = "guard"; // This is the default type if ( defense_type == "capture" || defense_type == "capture_zone" ) goal_type = "objective"; } random_stance_at_pathnode_dest = ( self bot_is_capturing() ); if ( defense_type == "protect" ) { self childthread protect_watch_allies(); } for(;;) { self.prev_defend_node = self.cur_defend_node; self.cur_defend_node = undefined; self.cur_defend_angle_override = undefined; self.cur_defend_point_override = undefined; self.cur_defend_stance = calculate_defend_stance( random_stance_at_pathnode_dest ); current_goal_type = self BotGetScriptGoalType(); can_override_goal = bot_goal_can_override( goal_type, current_goal_type ); if ( !can_override_goal ) { // Don't allow defense to interrupt a critical move wait(0.25); continue; } cur_min_goal_time = min_goal_time; cur_max_goal_time = max_goal_time; can_plant_trap = true; if ( IsDefined( self.defense_investigate_specific_point ) ) { self.cur_defend_point_override = self.defense_investigate_specific_point; self.defense_investigate_specific_point = undefined; can_plant_trap = false; cur_min_goal_time = 1.0; cur_max_goal_time = 2.0; } else if ( IsDefined( self.defense_force_next_node_goal ) ) { self.cur_defend_node = self.defense_force_next_node_goal; self.defense_force_next_node_goal = undefined; } else { self [[find_node_function]](); } self BotClearScriptGoal(); result = ""; if ( IsDefined( self.cur_defend_node ) || IsDefined( self.cur_defend_point_override ) ) { // If applicable, plant a trap to help defend the area first if ( can_plant_trap && (self bot_is_protecting()) && !IsPlayer( defendCenter ) && IsDefined( self.defend_entrance_index ) ) { trap_item = self bot_get_ambush_trap_item( "trap_directional", "trap", "c4" ); if ( IsDefined( trap_item ) ) { entrances = self bot_get_entrances_for_stance_and_index( undefined, self.defend_entrance_index ); self bot_set_ambush_trap( trap_item, entrances, self.node_closest_to_defend_center ); } } if ( IsDefined( self.cur_defend_point_override ) ) { // If we have an override point (we couldn't find a node destination) yaw = undefined; if ( IsDefined(self.cur_defend_angle_override) ) yaw = self.cur_defend_angle_override[1]; self BotSetScriptGoal(self.cur_defend_point_override, 0, goal_type, yaw); } else if ( !IsDefined( self.cur_defend_angle_override ) ) { // If we have a node destination and we want to use the node's angles self BotSetScriptGoalNode( self.cur_defend_node, goal_type ); } else { // If we have a node destination and we want to force the bot to face a different direction than the node's angles self BotSetScriptGoalNode(self.cur_defend_node, goal_type, self.cur_defend_angle_override[1]); } if ( random_stance_at_pathnode_dest ) { if ( !IsDefined(self.prev_defend_node) || !IsDefined(self.cur_defend_node) || (self.prev_defend_node != self.cur_defend_node) ) { // If we go prone at our destination, and we're not staying at the same location, // then we need to clear our stance (stand up) before we move self BotSetStance("none"); } } previous_goal = self BotGetScriptGoal(); self notify("new_defend_goal"); self watch_nodes_stop(); if ( goal_type == "objective" ) { self defense_cautious_approach(); // intentionally not threaded - behavior needs to take over self BotSetAwareness( 1.0 ); // Reset to 1.0 when cautiousness finishes (regardless of how it finished) self BotSetFlag("cautious", false); } if ( self BotHasScriptGoal() ) { current_goal = self BotGetScriptGoal(); if ( bot_vectors_are_equal( current_goal, previous_goal ) ) { // If the goal changed since we set it (like during a cautious approach) then we don't want to wait result = self bot_waittill_goal_or_fail( 20, "defend_force_node_recalculation" ); } } if ( result == "goal" ) { if ( random_stance_at_pathnode_dest ) { self BotSetStance(self.cur_defend_stance); } self childthread defense_watch_entrances_at_goal(); } } if ( result != "goal" ) { wait 0.25; } else { wait_time = RandomFloatRange( cur_min_goal_time, cur_max_goal_time ); result = self waittill_any_timeout( wait_time, "node_relinquished", "goal_changed", "script_goal_changed", "defend_force_node_recalculation", "bad_path" ); if ( (result == "node_relinquished" || result == "bad_path" || result == "goal_changed" || result == "script_goal_changed" ) && (self.cur_defend_stance == "crouch" || self.cur_defend_stance == "prone") ) { // If we were just kicked out of our spot, and we were crouching / going prone, then need to stand up self BotSetStance("none"); } } } } calculate_defend_stance( random_stance_at_pathnode_dest ) { stance = "stand"; if ( random_stance_at_pathnode_dest ) { // Default values (for dumb bots) chance_to_stand = 100; chance_to_crouch = 0; chance_to_prone = 0; strategy_level = self BotGetDifficultySetting("strategyLevel"); if ( strategy_level == 1 ) { chance_to_stand = 20; chance_to_crouch = 25; chance_to_prone = 55; } else if ( strategy_level >= 2 ) { chance_to_stand = 10; chance_to_crouch = 20; chance_to_prone = 70; } choice = RandomInt(100); if ( choice < chance_to_crouch ) { stance = "crouch"; } else if ( choice < chance_to_crouch + chance_to_prone ) { stance = "prone"; } if ( stance == "prone" ) { // Check number of prone entrances to this zone entrances_to_this_zone_for_prone = self bot_defend_get_precalc_entrances_for_current_area( "prone" ); // Check number of bots already wanting to be prone in this zone bots_prone_at_this_zone = self defend_get_ally_bots_at_zone_for_stance( "prone" ); if ( bots_prone_at_this_zone.size >= entrances_to_this_zone_for_prone.size ) stance = "crouch"; } if ( stance == "crouch" ) { // Check number of crouch entrances to this zone entrances_to_this_zone_for_crouch = self bot_defend_get_precalc_entrances_for_current_area( "crouch" ); // Check number of bots already wanting to be crouched in this zone bots_crouched_at_this_zone = self defend_get_ally_bots_at_zone_for_stance( "crouch" ); if ( bots_crouched_at_this_zone.size >= entrances_to_this_zone_for_crouch.size ) stance = "stand"; } } return stance; } SCR_CONST_frames_needed_visible = 18; should_start_cautious_approach_default( firstCheck ) { distance_start_cautiousness = 1250; distance_start_cautiousness_sq = distance_start_cautiousness * distance_start_cautiousness; // If firstCheck is true, this is called to determine if the bot should even attempt to do a cautious approach // If firstCheck is false, this is called to determine if the bot should start his cautious approach, or keep waiting if ( firstCheck ) { if ( self BotGetDifficultySetting("strategyLevel") == 0 ) return false; if ( self.bot_defending_type == "capture_zone" && self IsTouching( self.bot_defending_trigger ) ) return false; // Don't perform this behavior if we are starting within the radius return ( DistanceSquared(self.origin, self.bot_defending_center) > distance_start_cautiousness_sq * 0.75 * 0.75 ); } else { // Wait until we are within the radius and are pathing toward our goal (vs chasing down enemies) if ( self BotPursuingScriptGoal() && DistanceSquared(self.origin, self.bot_defending_center) < distance_start_cautiousness_sq ) { bot_path_dist = self BotGetPathDist(); return ( 0 <= bot_path_dist && bot_path_dist <= distance_start_cautiousness ); } else { return false; } } } setup_investigate_location( node, optional_location ) { new_location = SpawnStruct(); if ( IsDefined(optional_location) ) new_location.origin = optional_location; else new_location.origin = node.origin; AssertEx( IsDefined(node), "Bot Investigation Location " + new_location.origin + " has no node" ); new_location.node = node; new_location.frames_visible = 0; return new_location; } defense_cautious_approach() { self notify( "defense_cautious_approach" ); self endon( "defense_cautious_approach" ); level endon( "game_ended" ); self endon( "defend_force_node_recalculation" ); self endon( "death" ); self endon( "disconnect" ); self endon( "defend_stop" ); self endon( "started_bot_defend_think" ); if ( ![[ level.bot_funcs["should_start_cautious_approach"] ]] ( true ) ) return; original_script_goal = self BotGetScriptGoal(); original_script_goal_node = self BotGetScriptGoalNode(); /# if ( IsDefined(original_script_goal_node) ) { Assert(self.cur_defend_node == original_script_goal_node); } else { Assert(IsDefined(self.cur_defend_point_override)); // If we don't have a script goal node, we have to have an override point Assert(bot_vectors_are_equal(original_script_goal,self.cur_defend_point_override)); } #/ should_continue_waiting = true; time_since_last_dist_check = 0.2; while( should_continue_waiting ) { wait(0.25); if ( !self BotHasScriptGoal() ) { // Lost our script goal, so bail return; } current_script_goal = self BotGetScriptGoal(); if ( !bot_vectors_are_equal( original_script_goal, current_script_goal ) ) { // Script goal has changed, so bail return; } time_since_last_dist_check += 0.25; if ( time_since_last_dist_check >= 0.5 ) { // Debounce this since it can do an A* pathing search time_since_last_dist_check = 0.0; if ( [[ level.bot_funcs["should_start_cautious_approach"] ]] ( false ) ) should_continue_waiting = false; } } self BotSetAwareness( 1.8 ); self BotSetFlag("cautious", true); // ************************************************** // * Step 1: Build list of locations to investigate * // ************************************************** // Get the current path, we'll be using this to find nodes along the way to hide at current_path_to_goal = self BotGetNodesOnPath(); if ( !IsDefined(current_path_to_goal) || current_path_to_goal.size <= 2 ) return; self.locations_to_investigate = []; radius_around_point = 1000; if ( IsDefined(level.protect_radius) ) radius_around_point = level.protect_radius; radius_around_point_sq = radius_around_point * radius_around_point; nodes_around_defend_center = GetNodesInRadius( self.bot_defending_center, radius_around_point, 0, 500 ); if ( nodes_around_defend_center.size <= 0 ) return; // Locations enemy might be hiding at while defending the point num_nodes_desired = 5 + ( self BotGetDifficultySetting("strategyLevel") * 2 ); num_to_pick = INT(min(num_nodes_desired, nodes_around_defend_center.size )); possible_enemy_locations = self BotNodePickMultiple( nodes_around_defend_center, 15, num_to_pick, "node_protect", self defend_valid_center(), "ignore_occupancy" ); for( i=0; i= SCR_CONST_frames_needed_visible ) { self.locations_to_investigate[i] = self.locations_to_investigate[self.locations_to_investigate.size-1]; self.locations_to_investigate[self.locations_to_investigate.size-1] = undefined; i--; } } } } } wait(0.05); } } protect_watch_allies() { self notify( "protect_watch_allies" ); self endon( "protect_watch_allies" ); next_time_check_this_ally = []; minimap_radius = 1050; // rough estimate minimap_radius_sq = minimap_radius * minimap_radius; radius_around_point = 900; if ( IsDefined(level.protect_radius) ) radius_around_point = level.protect_radius; while(1) { cur_time = GetTime(); // Get all teammates in the radius around our defense point teammates_defending_this_point = self bot_get_teammates_in_radius( self.bot_defending_center, radius_around_point ); foreach( teammate in teammates_defending_this_point ) { teammate_entity_num = teammate.entity_number; if ( !IsDefined(teammate_entity_num) ) teammate_entity_num = teammate GetEntityNumber(); if ( !IsDefined( next_time_check_this_ally[teammate_entity_num] ) ) next_time_check_this_ally[teammate_entity_num] = cur_time - 1; if ( !IsDefined(teammate.last_investigation_time) ) teammate.last_investigation_time = cur_time - 10001; // First, check if teammate is dead and has died within the last five seconds if ( teammate.health == 0 && IsDefined(teammate.deathTime) && (cur_time - teammate.deathTime) < 5000 ) { // Next, see if we are allowed to check on this ally if ( (cur_time - teammate.last_investigation_time) > 10000 && cur_time > next_time_check_this_ally[teammate_entity_num] ) { // Finally, some sanity checks - check if teammate was last attacked by someone on the enemy team if ( IsDefined(teammate.lastAttacker) && IsDefined(teammate.lastAttacker.team) && teammate.lastAttacker.team == get_enemy_team(self.team) ) { // We know that this teammate died protecting the same point that this bot is protecting // So finally, we need to check if this bot could have seen him on his minimap if ( DistanceSquared( teammate.origin, self.origin ) < minimap_radius_sq ) { // Inform the bot that there might be an enemy here self BotGetImperfectEnemyInfo( teammate.lastAttacker, teammate.origin ); // Force the bot to move to that location. Once he gets close enough, the Enemy Search should take over nearest_node_to_teammate = GetClosestNodeInSight(teammate.origin); if ( IsDefined(nearest_node_to_teammate) ) { self.defense_investigate_specific_point = nearest_node_to_teammate.origin; self notify("defend_force_node_recalculation"); } // Make sure that only one bot can react to this death teammate.last_investigation_time = cur_time; } // Regardless of whether we chose to check this out or not, don't take any more actions regarding this guy for 10 seconds next_time_check_this_ally[teammate_entity_num] = cur_time + 10000; } } } } wait( (RandomInt(5)+1) * 0.05 ); } } defense_get_initial_entrances() { if ( IsDefined(self.defense_override_entrances) ) { return self.defense_override_entrances; } else if ( self bot_is_capturing() ) { // If capturing a point or zone, use the pre-calculated entrances for the point return self bot_defend_get_precalc_entrances_for_current_area( self.cur_defend_stance ); } else if ( self bot_is_protecting() || self bot_is_bodyguarding() ) { // If protecting a point, use entrances from the current position entrances = FindEntrances( self.origin ); /# if ( IsDefined( self GetNearestNode() ) ) AssertEx( entrances.size > 0, "Entrance points for bot at location " + self.origin + " could not be calculated. Check pathgrid around that area" ); #/ return entrances; } } defense_watch_entrances_at_goal() { self notify( "defense_watch_entrances_at_goal" ); self endon( "defense_watch_entrances_at_goal" ); self endon("new_defend_goal"); self endon("script_goal_changed"); node_nearest_bot = self GetNearestNode(); entrances_to_watch = undefined; if ( self bot_is_capturing() ) { precalculated_entrances = self defense_get_initial_entrances(); // Extra check to make sure the precalculated entrances are valid for the location this bot is currently at entrances_to_watch = []; if ( IsDefined(node_nearest_bot) ) { foreach( entrance in precalculated_entrances ) { if ( NodesVisible( node_nearest_bot, entrance, true ) ) entrances_to_watch = array_add(entrances_to_watch, entrance ); } } } else if ( self bot_is_protecting() || self bot_is_bodyguarding() ) { entrances_to_watch = self defense_get_initial_entrances(); // Add in node closest to the center (we should be watching that too) if ( IsDefined(node_nearest_bot) && !IsSubStr( self GetCurrentWeapon(), "riotshield" ) ) { if ( NodesVisible( node_nearest_bot, self.node_closest_to_defend_center, true ) ) entrances_to_watch = array_add(entrances_to_watch, self.node_closest_to_defend_center); } } if ( IsDefined( entrances_to_watch ) ) { self childthread bot_watch_nodes( entrances_to_watch ); if ( self bot_is_bodyguarding() ) self childthread bot_monitor_watch_entrances_bodyguard(); else self childthread bot_monitor_watch_entrances_at_goal(); } } bot_monitor_watch_entrances_at_goal() { self notify( "bot_monitor_watch_entrances_at_goal" ); self endon( "bot_monitor_watch_entrances_at_goal" ); self notify( "bot_monitor_watch_entrances" ); self endon( "bot_monitor_watch_entrances" ); while(!IsDefined(self.watch_nodes)) wait(0.05); watch_node_chance_func = level.bot_funcs["get_watch_node_chance"]; while(1) { foreach( node in self.watch_nodes ) { if ( node == self.node_closest_to_defend_center ) node.watch_node_chance[self.entity_number] = 0.8; // Node closest to the center starts out slightly lower priority else node.watch_node_chance[self.entity_number] = 1.0; } has_watch_node_chance_func = IsDefined(watch_node_chance_func); if ( !has_watch_node_chance_func ) { // If the gametype doesn't want to do anything with this node chance, then generally ignore areas // that the bot predicts his allies are controlling prioritize_watch_nodes_toward_enemies(0.5); } foreach( node in self.watch_nodes ) { if ( has_watch_node_chance_func ) { gametype_scalar = self [[watch_node_chance_func]]( node ); node.watch_node_chance[self.entity_number] *= gametype_scalar; } if ( self entrance_watched_by_ally( node ) ) node.watch_node_chance[self.entity_number] *= 0.5; } wait(RandomFloatRange(0.5,0.75)); } } bot_monitor_watch_entrances_bodyguard() { self notify( "bot_monitor_watch_entrances_bodyguard" ); self endon( "bot_monitor_watch_entrances_bodyguard" ); self notify( "bot_monitor_watch_entrances" ); self endon( "bot_monitor_watch_entrances" ); while(!IsDefined(self.watch_nodes)) wait(0.05); while(1) { player_guarding_forward = AnglesToForward(self.bot_defend_player_guarding.angles) * (1,1,0); player_guarding_forward = VectorNormalize(player_guarding_forward); foreach( node in self.watch_nodes ) { node.watch_node_chance[self.entity_number] = 1.0; // Tend to not look at nodes that are visible to the player this bot is guarding player_to_node = node.origin - self.bot_defend_player_guarding.origin; player_to_node = VectorNormalize(player_to_node); player_dot_facing_to_node = VectorDot( player_guarding_forward, player_to_node ); if ( player_dot_facing_to_node > 0.6 ) node.watch_node_chance[self.entity_number] *= 0.33; // node is in player's view else if ( player_dot_facing_to_node > 0 ) node.watch_node_chance[self.entity_number] *= 0.66; // node is not behind player if ( !self entrance_to_enemy_zone( node ) ) node.watch_node_chance[self.entity_number] *= 0.5; } wait(RandomFloatRange(0.4,0.6)); } } entrance_to_enemy_zone( entrance ) { entrance_zone_index = GetNodeZone( entrance ); bot_to_entrance = VectorNormalize( entrance.origin - self.origin ); for ( z = 0; z < level.zoneCount; z++ ) { if ( BotZoneGetCount( z, self.team, "enemy_predict" ) > 0 ) { // We know that the zone has enemies if ( IsDefined(entrance_zone_index) && z == entrance_zone_index ) { // and the entrance node is in this zone, then return true return true; } else { // and the entrance node is not in this zone, then compare the direction from the entrance zone to the entrance, // and the direction from the entrance zone to this zone. If they are in the same direction, then consider // this entrance to be an entrance to an enemy zone bot_to_this_zone_origin = VectorNormalize( GetZoneOrigin(z) - self.origin ); dot = VectorDot( bot_to_entrance, bot_to_this_zone_origin ); if ( dot > 0.2 ) return true; } } } return false; } prioritize_watch_nodes_toward_enemies( scalar ) { if ( self.watch_nodes.size <= 0 ) return; nodes_testing = self.watch_nodes; for ( z = 0; z < level.zoneCount; z++ ) { if ( BotZoneGetCount( z, self.team, "enemy_predict" ) <= 0 ) continue; if ( nodes_testing.size == 0 ) break; bot_to_this_zone_origin = VectorNormalize( GetZoneOrigin(z) - self.origin ); for ( i = 0; i < nodes_testing.size; i++ ) { node_zone_index = GetNodeZone( nodes_testing[i] ); node_is_entrance_to_zone = false; // We know that the zone has enemies if ( IsDefined(node_zone_index) && z == node_zone_index ) { // and the node is in this zone, then return true node_is_entrance_to_zone = true; } else { // and the entrance node is not in this zone, then compare the direction from the entrance zone to the entrance, // and the direction from the entrance zone to this zone. If they are in the same direction, then consider // this entrance to be an entrance to an enemy zone bot_to_node = VectorNormalize( nodes_testing[i].origin - self.origin ); dot = VectorDot( bot_to_node, bot_to_this_zone_origin ); if ( dot > 0.2 ) node_is_entrance_to_zone = true; } if ( node_is_entrance_to_zone ) { nodes_testing[i].watch_node_chance[self.entity_number] *= scalar; nodes_testing[i] = nodes_testing[nodes_testing.size-1]; nodes_testing[nodes_testing.size-1] = undefined; i--; } } } } entrance_watched_by_ally( entrance ) { teammates_defending_this_point = self bot_get_teammates_currently_defending_point( self.bot_defending_center ); foreach ( teammate in teammates_defending_this_point ) { if ( entrance_watched_by_player( teammate, entrance ) ) return true; } return false; } entrance_watched_by_player( player, entrance ) { player_forward = AnglesToForward(player.angles); player_to_node = VectorNormalize(entrance.origin - player.origin); player_dot_facing_to_node = VectorDot( player_forward, player_to_node ); if ( player_dot_facing_to_node > 0.6 ) return true; return false; } bot_get_teammates_currently_defending_point( point, radius_around_point ) { // A human player is considered defending this point if they are within the radius // A bot player is considered defending this point if they are within the radius, and have the location set as their defend target if ( !IsDefined(radius_around_point) ) { // If no radius is defined, use 900 or the level.protect_radius if ( IsDefined(level.protect_radius) ) radius_around_point = level.protect_radius; else radius_around_point = 900; } teammates_defending_point = []; teammates_in_radius = bot_get_teammates_in_radius( point, radius_around_point ); foreach( teammate in teammates_in_radius ) { if ( !IsAI( teammate ) || teammate bot_is_defending_point( point ) ) { teammates_defending_point = array_add(teammates_defending_point, teammate); } } return teammates_defending_point; } bot_get_teammates_in_radius( point, radius_around_point ) { radius_around_point_sq = radius_around_point * radius_around_point; teammates_in_radius = []; for ( i = 0; i < level.participants.size; i++ ) { other_player = level.participants[i]; if ( (other_player != self) && IsDefined(other_player.team) && (other_player.team == self.team) && IsTeamParticipant(other_player) ) { // other_player is on my team and is counted as a team participant if ( DistanceSquared( point, other_player.origin ) < radius_around_point_sq ) teammates_in_radius = array_add(teammates_in_radius, other_player); } } return teammates_in_radius; } defense_death_monitor() { level endon( "game_ended" ); self endon( "started_bot_defend_think" ); self endon( "defend_stop" ); self endon( "disconnect" ); self waittill("death"); if ( IsDefined( self ) ) self thread bot_defend_stop(); } /* ============= ///ScriptDocBegin "Name: bot_defend_stop()" "Summary: Stops any scripted defense the bot might be doing" "CallOn: A bot player" "Example: self bot_defend_stop();" ///ScriptDocEnd ============ */ bot_defend_stop( ) { self notify( "defend_stop" ); self.bot_defending = undefined; self.bot_defending_center = undefined; self.bot_defending_radius = undefined; self.bot_defending_nodes = undefined; self.bot_defending_type = undefined; self.bot_defending_trigger = undefined; self.bot_defending_override_origin_node = undefined; self.bot_defend_player_guarding = undefined; self.defense_score_flags = undefined; self.node_closest_to_defend_center = undefined; self.defense_investigate_specific_point = undefined; self.defense_force_next_node_goal = undefined; self.prev_defend_node = undefined; self.cur_defend_node = undefined; self.cur_defend_angle_override = undefined; self.cur_defend_point_override = undefined; self.defend_entrance_index = undefined; self.defense_override_entrances = undefined; self BotClearScriptGoal(); self BotSetStance("none"); } defend_get_ally_bots_at_zone_for_stance( stance ) { other_players_with_same_stance = []; foreach( other_player in level.participants ) { if ( !IsDefined( other_player.team ) ) continue; if ( other_player.team == self.team && other_player != self && IsAI(other_player) && other_player bot_is_defending() && other_player.cur_defend_stance == stance ) { if ( other_player.bot_defending_type == self.bot_defending_type && self bot_is_defending_point( other_player.bot_defending_center ) ) { other_players_with_same_stance = array_add(other_players_with_same_stance, other_player); } } } return other_players_with_same_stance; } monitor_defend_player() { player_not_moving_time = 0; new_goal_radius = 175; // If player moves this distance, then consider him moving and set a destination in front of him last_player_pos = self.bot_defend_player_guarding.origin; prev_player_velocity = 0; should_reset_player_base_pos_when_still = false; while(1) { if ( !IsDefined(self.bot_defend_player_guarding) ) self thread bot_defend_stop(); if ( self.bot_defend_player_guarding IsLinked() ) { wait(0.05); continue; } self.bot_defending_center = self.bot_defend_player_guarding.origin; self.node_closest_to_defend_center = self.bot_defend_player_guarding GetNearestNode(); if ( !IsDefined(self.node_closest_to_defend_center) ) self.node_closest_to_defend_center = self GetNearestNode(); Assert(IsDefined(self.node_closest_to_defend_center)); if ( self BotGetScriptGoalType() != "none" ) { script_goal = self BotGetScriptGoal(); //bot_draw_cylinder( last_player_pos, new_goal_radius, 40, 0.05, undefined, (0,1,0), true, 15 ); player_guarding_velocity = self.bot_defend_player_guarding GetVelocity(); player_guarding_velocity_len_sq = LengthSquared(player_guarding_velocity); if ( player_guarding_velocity_len_sq > (10 * 10) ) { // If player is moving, check that the node destination is still in front of the player player_not_moving_time = 0; if ( DistanceSquared( last_player_pos, self.bot_defend_player_guarding.origin ) > (new_goal_radius * new_goal_radius) ) { last_player_pos = self.bot_defend_player_guarding.origin; should_reset_player_base_pos_when_still = true; player_to_script_goal = VectorNormalize( script_goal - self.bot_defend_player_guarding.origin ); normalized_velocity = VectorNormalize(player_guarding_velocity); if ( VectorDot( player_to_script_goal, normalized_velocity ) < 0.1 ) { self notify("defend_force_node_recalculation"); // force bot to pick a new spot wait(0.25); // Defend script waits 0.25s after this notification before it can calculate a new goal } } } else { // If player is not moving, check that the node destination lies in the defending radius player_not_moving_time += 0.05; if ( prev_player_velocity > (10*10) && should_reset_player_base_pos_when_still ) { // reset player "base" position if he just stopped moving last_player_pos = self.bot_defend_player_guarding.origin; should_reset_player_base_pos_when_still = false; } if ( player_not_moving_time > 0.5 ) { distSQ = DistanceSquared(script_goal, self.bot_defending_center); if ( distSQ > self.bot_defending_radius * self.bot_defending_radius ) { self notify("defend_force_node_recalculation"); // force bot to pick a new spot wait(0.25); // Defend script waits 0.25s after this notification before it can calculate a new goal } } } prev_player_velocity = player_guarding_velocity_len_sq; if ( abs(self.bot_defend_player_guarding.origin[2] - script_goal[2]) >= 50 ) { self notify("defend_force_node_recalculation"); wait(0.25); // Defend script waits 0.25s after this notification before it can calculate a new goal } } wait(0.05); } } find_defend_node_capture() { entrance_point = self bot_defend_get_random_entrance_point_for_current_area(); node = self bot_find_node_to_capture_point( self defend_valid_center(), self.bot_defending_radius, entrance_point ); if ( IsDefined(node) ) { if ( IsDefined(entrance_point) ) { node_to_entrance = VectorNormalize(entrance_point - node.origin); self.cur_defend_angle_override = VectorToAngles(node_to_entrance); } else { center_to_node = VectorNormalize(node.origin - self defend_valid_center()); self.cur_defend_angle_override = VectorToAngles(center_to_node); } self.cur_defend_node = node; } else { if ( IsDefined(entrance_point) ) { self bot_handle_no_valid_defense_node(entrance_point, undefined); } else { self bot_handle_no_valid_defense_node(undefined, self defend_valid_center()); } } } find_defend_node_capture_zone() { entrance_point = self bot_defend_get_random_entrance_point_for_current_area(); node = self bot_find_node_to_capture_zone( self.bot_defending_nodes, entrance_point ); if ( IsDefined(node) ) { if ( IsDefined(entrance_point) ) { node_to_entrance = VectorNormalize(entrance_point - node.origin); self.cur_defend_angle_override = VectorToAngles(node_to_entrance); } else { center_to_node = VectorNormalize(node.origin - self defend_valid_center()); self.cur_defend_angle_override = VectorToAngles(center_to_node); } self.cur_defend_node = node; } else { if ( IsDefined(entrance_point) ) { self bot_handle_no_valid_defense_node(entrance_point, undefined); } else { self bot_handle_no_valid_defense_node(undefined, self defend_valid_center()); } } } find_defend_node_protect() { node = self bot_find_node_that_protects_point( self defend_valid_center(), self.bot_defending_radius ); if ( IsDefined( node ) ) { node_to_center = VectorNormalize(self defend_valid_center() - node.origin); self.cur_defend_angle_override = VectorToAngles(node_to_center); self.cur_defend_node = node; } else { self bot_handle_no_valid_defense_node(self defend_valid_center(), undefined); } } find_defend_node_bodyguard() { node = self bot_find_node_to_guard_player( self defend_valid_center(), self.bot_defending_radius ); if ( IsDefined(node) ) { self.cur_defend_node = node; } else { nearest_node_bot = self GetNearestNode(); if ( IsDefined(nearest_node_bot) ) self.cur_defend_node = nearest_node_bot; else self.cur_defend_point_override = self.origin; } } find_defend_node_patrol() { // Get a random node within the radius tending to pick ones with highest traffic density node = undefined; nodes = GetNodesInRadius( self defend_valid_center(), self.bot_defending_radius, 0 ); if ( IsDefined( nodes ) && nodes.size > 0 ) node = self BotNodePick( nodes, 1 + (nodes.size * 0.5), "node_traffic" ); if ( IsDefined(node) ) { self.cur_defend_node = node; } else { self bot_handle_no_valid_defense_node(undefined, self defend_valid_center()); } } bot_handle_no_valid_defense_node(face_towards_point, face_away_from_point) { assert( (!IsDefined(face_towards_point) && IsDefined(face_away_from_point)) || (IsDefined(face_towards_point) && !IsDefined(face_away_from_point)) ); if ( self.bot_defending_type == "capture_zone" ) self.cur_defend_point_override = self bot_pick_random_point_from_set(self defend_valid_center(), self.bot_defending_nodes, ::bot_can_use_point_in_defend); else self.cur_defend_point_override = self bot_pick_random_point_in_radius(self defend_valid_center(), self.bot_defending_radius, ::bot_can_use_point_in_defend, 0.15, 0.9); // face towards / away from a point if ( IsDefined(face_towards_point) ) { angle_dir = VectorNormalize(face_towards_point - self.cur_defend_point_override); self.cur_defend_angle_override = VectorToAngles(angle_dir); } else if ( IsDefined(face_away_from_point) ) { angle_dir = VectorNormalize(self.cur_defend_point_override - face_away_from_point); self.cur_defend_angle_override = VectorToAngles(angle_dir); } } bot_can_use_point_in_defend(point) { if ( self bot_check_team_is_using_position( point, true, true, true ) ) return false; return true; } SCR_CONST_player_close_dist = 21; SCR_CONST_player_close_dist_sq = SCR_CONST_player_close_dist * SCR_CONST_player_close_dist; bot_check_team_is_using_position( position, check_human_player_near, check_bot_player_near, check_bot_destination_near ) { for ( i = 0; i < level.participants.size; i++ ) { other_player = level.participants[i]; if ( other_player.team == self.team && other_player != self ) { if ( IsAI( other_player ) ) { if ( check_bot_player_near ) { // Check if a bot player is standing near this node if ( DistanceSquared(position,other_player.origin) < SCR_CONST_player_close_dist_sq ) { return true; } } if ( check_bot_destination_near && other_player BotHasScriptGoal() ) { // Check if bot player has a goal near this point bot_goal = other_player BotGetScriptGoal(); if ( DistanceSquared(position,bot_goal) < SCR_CONST_player_close_dist_sq ) { return true; } } } else { if ( check_human_player_near ) { // Check if a human player is standing near this node if ( DistanceSquared(position,other_player.origin) < SCR_CONST_player_close_dist_sq ) { return true; } } } } } return false; } bot_capture_zone_get_furthest_distance() { furthest_dist = 0; if ( IsDefined(self.bot_defending_nodes) ) { foreach( node in self.bot_defending_nodes ) { dist_to_node = Distance(self.bot_defending_center,node.origin); furthest_dist = max(dist_to_node,furthest_dist); } } return furthest_dist; } // Tactical Goal Explanation // ========================= // Mandatory args // tactical_goal.type The type of goal. Used with bot_has_tactical_goal so different goals can check if they already exist in the list before trying to add a new one // tactical_goal.goal_position The position of the goal // tactical_goal.priority The priority of this goal. 0 to 100, with 100 being highest priority // Optional Args // tactical_goal.object The object we are going after // tactical_goal.goal_type Type of goal pathing to use - "tactical", "critical", "guard", etc." // tactical_goal.goal_yaw The yaw of the goal // tactical_goal.goal_radius The radius of the goal // tactical_goal.objective_radius Radius to use when setting an objective goal (using the goal_type above) // tactical_goal.start_thread This function is called when the goal starts // tactical_goal.end_thread This function is called when the goal ends, regardless of it it was successful or not. // tactical_goal.should_abort This function is called every frame while the goal is active. If it ever returns true, the goal is aborted. // tactical_goal.action_thread This function is called when the goal is reached. It contains some action for the bot to perform. bot_think_tactical_goals() { self notify( "bot_think_tactical_goals" ); self endon( "bot_think_tactical_goals" ); self endon( "death" ); self endon( "disconnect" ); level endon( "game_ended" ); self.tactical_goals = []; while(1) { if ( self.tactical_goals.size > 0 && !self bot_is_remote_or_linked() ) { new_goal = self.tactical_goals[0]; if ( !IsDefined( new_goal.abort ) ) { self notify( "start_tactical_goal" ); if ( IsDefined(new_goal.start_thread) ) { self [[new_goal.start_thread]](new_goal); } self childthread watch_goal_aborted( new_goal ); goal_type = "tactical"; if ( IsDefined(new_goal.goal_type) ) goal_type = new_goal.goal_type; //PrintLn( GetTime() + " Bot <" + self.name + "> starting tactical goal of '" + new_goal.type + "' and goal_type of '" + goal_type + "'"); self BotSetScriptGoal( new_goal.goal_position, new_goal.goal_radius, goal_type, new_goal.goal_yaw, new_goal.objective_radius); result = self bot_waittill_goal_or_fail( undefined, "stop_tactical_goal" ); self notify("stop_goal_aborted_watch"); //PrintLn( GetTime() + " Bot <" + self.name + "> moving toward tactical goal of '" + new_goal.type + "' got notify of '" + result + "'"); if ( result == "goal" ) { //PrintLn( GetTime() + " Bot <" + self.name + "> reached tactical goal of '" + new_goal.type + "' and goal_type of '" + goal_type + "'"); if ( IsDefined(new_goal.action_thread) ) self [[new_goal.action_thread]](new_goal); } if ( result != "script_goal_changed" ) { self BotClearScriptGoal(); } if ( IsDefined(new_goal.end_thread) ) { self [[new_goal.end_thread]](new_goal); } } self.tactical_goals = array_remove(self.tactical_goals, new_goal); } wait(0.05); } } watch_goal_aborted( goal ) { self endon("stop_tactical_goal"); self endon("stop_goal_aborted_watch"); wait(0.05); // allow time for execution to reach the "waittill_any_return" line before calling this for the first time while(1) { if ( IsDefined( goal.abort ) || (IsDefined(goal.should_abort) && self [[goal.should_abort]](goal)) ) self notify("stop_tactical_goal"); wait(0.05); } } /* ============= ///ScriptDocBegin "Name: bot_new_tactical_goal( , , , )" "Summary: Adds a new tactical goal for this bot" "CallOn: A bot player" "MandatoryArg: : Type of tactical goal this is ("seek_dropped_weapon", etc.)" "MandatoryArg: : Position of the goal" "MandatoryArg: : The priority of this goal, from 0 to 100. 100 is the highest priority" "OptionalArg: : A struct with optional parameters. Listed below" "Example: self bot_new_tactical_goal( "kill_tag", self.tag_getting.curorigin, 0, undefined, undefined, 25, self.tag_getting, undefined, ::tag_end, ::goal_tag_picked_up, undefined );" "NoteLine: All of the "threads" listed below are actually function calls - they are not threaded" "NoteLine: : The object of the tactical goal. Used with bot_has_tactical_goal, to determine duplicate goals" "NoteLine: : Type of goal pathing to use - "tactical", "critical", "guard", etc." "NoteLine: : Goal yaw to use when pathing to this goal" "NoteLine: : Goal radius to use when pathing to this goal" "NoteLine: : A function to execute when this tactical goal starts "NoteLine: : A function to execute when this tactical goals ends, regardless of it was successful or not" "NoteLine: : A function that is called to determine if the tactical goal should end early. Should return true in that case" "NoteLine: : A function to execute if the bot reaches his tactical goal" "NoteLine: : Radius to use when setting an objective goal" ///ScriptDocEnd ============ */ bot_new_tactical_goal( type, goal_position, priority, extra_params ) { new_goal = SpawnStruct(); new_goal.type = type; new_goal.goal_position = goal_position; if ( IsDefined(self.only_allowable_tactical_goals) ) { if ( !array_contains(self.only_allowable_tactical_goals,type) ) return; } assert( priority >= 0 && priority <= 100 ); new_goal.priority = priority; new_goal.object = extra_params.object; new_goal.goal_type = extra_params.script_goal_type; new_goal.goal_yaw = extra_params.script_goal_yaw; new_goal.goal_radius = 0; if ( IsDefined( extra_params.script_goal_radius ) ) new_goal.goal_radius = extra_params.script_goal_radius; new_goal.start_thread = extra_params.start_thread; new_goal.end_thread = extra_params.end_thread; new_goal.should_abort = extra_params.should_abort; new_goal.action_thread = extra_params.action_thread; new_goal.objective_radius = extra_params.objective_radius; // iterate through self.tactical_goals and place this goal in the correct priority for ( position_to_add=0; position_to_add self.tactical_goals[position_to_add].priority ) break; } // insert tactical goal at self.tactical_goals[position_to_add], pushing everything (including the element already in position_to_add) back one element for ( i=self.tactical_goals.size-1; i>=position_to_add; i-- ) { self.tactical_goals[i+1] = self.tactical_goals[i]; } self.tactical_goals[position_to_add] = new_goal; } /* ============= ///ScriptDocBegin "Name: bot_has_tactical_goal( , )" "Summary: Checks if a bot already has a tactical goal of this type" "CallOn: A bot player" "OptionalArg: : Type of tactical goal to check" "OptionalArg: : The object of the tactical goal. Used to differentiate two different goals of the same type" "Example: if ( self bot_has_tactical_goal( "seek_dropped_weapon", dropped_weapon ) == false )" ///ScriptDocEnd ============ */ bot_has_tactical_goal( goal_type, object ) { if ( !IsDefined( self.tactical_goals ) ) return false; if ( IsDefined( goal_type ) ) { foreach( goal in self.tactical_goals ) { if ( goal.type == goal_type ) { if ( IsDefined(object) && IsDefined(goal.object) ) { return (goal.object == object); } else { return true; } } } return false; } else { return (self.tactical_goals.size > 0); } } /* ============= ///ScriptDocBegin "Name: bot_abort_tactical_goal( , )" "Summary: Aborts any active tactical goal matching the type (and optionally specific object)" "CallOn: A bot player" "MandatoryArg: : Type of tactical goal to check" "OptionalArg: : The object of the tactical goal. Used to differentiate two different goals of the same type" "Example: self bot_abort_tactical_goal( "seek_dropped_weapon" )" ///ScriptDocEnd ============ */ bot_abort_tactical_goal( goal_type, object ) { assert( IsDefined( goal_type ) ); if ( !IsDefined( self.tactical_goals ) ) return; foreach( goal in self.tactical_goals ) { if ( goal.type == goal_type ) { if ( IsDefined(object) ) { if ( IsDefined(goal.object) && (goal.object == object) ) goal.abort = true; } else { goal.abort = true; } } } } /* ============= ///ScriptDocBegin "Name: bot_disable_tactical_goals()" "Summary: Disables tactical goals for this bot, and clears out any current ones" "CallOn: A bot player" "Example: self bot_disable_tactical_goals())" ///ScriptDocEnd ============ */ bot_disable_tactical_goals() { self.only_allowable_tactical_goals[0] = "map_interactive_object"; // always allow these, otherwise pathing can get blocked foreach( goal in self.tactical_goals ) { if ( goal.type != "map_interactive_object" ) goal.abort = true; } } /* ============= ///ScriptDocBegin "Name: bot_enable_tactical_goals()" "Summary: Enables tactical goals for this bot. Note: Only needs to be called if bot_disable_tactical_goals was previously called on this bot." "CallOn: A bot player" "Example: self bot_enable_tactical_goals())" ///ScriptDocEnd ============ */ bot_enable_tactical_goals() { self.only_allowable_tactical_goals = undefined; } /* ============= ///ScriptDocBegin "Name: bot_melee_tactical_insertion_check()" "Summary: Waits until enemies are nearby but not in view and drops a tac-insert." "CallOn: A bot player" "Example: self bot_melee_tactical_insertion_check();" ///ScriptDocEnd ============ */ bot_melee_tactical_insertion_check() // self = bot { now = GetTime(); if ( !IsDefined( self.last_melee_ti_check ) || (now - self.last_melee_ti_check) > 1000 ) { self.last_melee_ti_check = now; trap_item = self bot_get_ambush_trap_item( "tacticalinsertion" ); if ( !IsDefined( trap_item ) ) return false; if ( IsDefined( self.enemy ) && self BotCanSeeEntity( self.enemy ) ) return false; myZone = GetZoneNearest( self.origin ); if ( !IsDefined( myZone ) ) return false; nearbyEnemyZone = BotZoneNearestCount( myZone, self.team, 1, "enemy_predict", ">", 0 ); if ( !IsDefined( nearbyEnemyZone ) ) return false; nodesAroundMe = GetNodesInRadius( self.origin, 500, 0 ); if ( nodesAroundMe.size <= 0 ) return false; place_node = self BotNodePick( nodesAroundMe, nodesAroundMe.size * 0.15, "node_hide" ); if ( !IsDefined( place_node ) ) return false; return self bot_set_ambush_trap( trap_item, undefined, undefined, undefined, place_node ); } return false; }