This commit is contained in:
2025-05-21 16:23:17 +02:00
commit 222e802504
359 changed files with 242229 additions and 0 deletions

3865
raw/maps/mp/bots/_bots.gsc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,747 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
/#
SCR_CONST_BALL_BOTS_IGNORE_HUMAN_PLAYER_ROLES = false;
SCR_CONST_BALL_OVERRIDE_ROLE = undefined;
#/
SCR_CONST_BOT_BALL_OBJ_RADIUS = 180;
SCR_CONST_BALL_NODE_MAX_DIST = 375;
SCR_CONST_BALL_MAX_ENEMY_THROW_DIST = 350;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_ball();
/#
thread bot_ball_debug();
#/
}
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_ball_think;
level.bot_funcs["crate_can_use"] = ::crate_can_use;
}
setup_bot_ball()
{
/#
if ( SCR_CONST_BALL_BOTS_IGNORE_HUMAN_PLAYER_ROLES )
level.bot_gametype_ignore_human_player_roles = true;
#/
level.bot_gametype_attacker_limit_for_team = ::bot_ball_attacker_limit_for_team;
level.bot_gametype_defender_limit_for_team = ::bot_ball_defender_limit_for_team;
level.bot_gametype_allied_attackers_for_team = ::get_allied_attackers_for_team;
level.bot_gametype_allied_defenders_for_team = ::get_allied_defenders_for_team;
bot_waittill_bots_enabled();
while( !IsDefined(level.ball_goals) )
wait(0.05);
level.ball_goals["allies"].script_label = "allies";
level.ball_goals["axis"].script_label = "axis";
bot_setup_ball_jump_nodes();
zone = GetZoneNearest( level.ball_goals["allies"].origin );
if ( IsDefined( zone ) )
BotZoneSetTeam( zone, "allies" );
zone = GetZoneNearest( level.ball_goals["axis"].origin );
if ( IsDefined( zone ) )
BotZoneSetTeam( zone, "axis" );
foreach( ball in level.balls )
ball thread monitor_ball();
use_override_role = false;
thread bot_gametype_attacker_defender_ai_director_update();
/#
if ( IsDefined(SCR_CONST_BALL_OVERRIDE_ROLE) )
{
wait(0.1);
level notify("bot_gametype_attacker_defender_ai_director_update");
}
#/
level.bot_gametype_precaching_done = true;
}
monitor_ball()
{
last_origin = self.visuals[0].origin;
self.nearest_node = GetClosestNodeInSight(last_origin);
while(1)
{
cur_origin = self.visuals[0].origin;
self.ball_at_rest = bot_vectors_are_equal(last_origin,cur_origin);
if ( !self.ball_at_rest )
{
nearest_node = GetClosestNodeInSight( cur_origin );
if ( !IsDefined(nearest_node) )
{
nodes = GetNodesInRadiusSorted( cur_origin, 512, 0, 6000 );
if ( nodes.size > 0 )
nearest_node = nodes[0];
}
if ( IsDefined(nearest_node) )
self.nearest_node = nearest_node;
}
last_origin = cur_origin;
wait(0.2);
}
}
bot_setup_ball_jump_nodes()
{
wait(1.0);
num_traces = 0;
increment = 10; // do 10 traces per frame
foreach( goal in level.ball_goals )
{
goal.ball_jump_nodes = [];
nodes = GetNodesInRadius( goal.origin, SCR_CONST_BALL_NODE_MAX_DIST, 0 );
foreach( node in nodes )
{
if ( node.type == "End" )
continue;
num_traces++;
if ( bot_ball_origin_can_see_goal( node.origin, goal, true ) )
goal.ball_jump_nodes[goal.ball_jump_nodes.size] = node;
if ( (num_traces % increment) == 0 )
wait(0.05);
}
nearest_node_2d_dist_sq = 999999999;
foreach( node in goal.ball_jump_nodes )
{
dist_2d_sq_to_node = Distance2DSquared( node.origin, goal.origin );
if ( dist_2d_sq_to_node < nearest_node_2d_dist_sq )
{
goal.nearest_node = node;
nearest_node_2d_dist_sq = dist_2d_sq_to_node;
}
}
AssertEx( goal.ball_jump_nodes.size > 0, "Uplink goal at " + goal.origin + " needs pathnodes within a " + SCR_CONST_BALL_NODE_MAX_DIST + " unit radius with sight to the goal" );
wait(0.05);
}
}
bot_ball_origin_can_see_goal( origin, goal, thorough )
{
trace_succeeded = self bot_ball_trace_to_origin( origin, goal.origin );
if ( IsDefined(thorough) && thorough )
{
if ( !trace_succeeded )
{
goal_origin = goal.origin - (0,0,goal.radius * 0.5);
trace_succeeded = self bot_ball_trace_to_origin( origin, goal_origin );
}
if ( !trace_succeeded )
{
goal_origin = goal.origin + (0,0,goal.radius * 0.5);
trace_succeeded = self bot_ball_trace_to_origin( origin, goal_origin );
}
}
return trace_succeeded;
}
bot_ball_trace_to_origin( start_origin, end_origin )
{
if ( IsDefined(self) && (IsPlayer(self) || IsAgent(self)) )
hitPos = PlayerPhysicsTrace( start_origin, end_origin, self );
else
hitPos = PlayerPhysicsTrace( start_origin, end_origin );
return (DistanceSquared( hitPos, end_origin ) < 1);
}
/#
bot_ball_debug()
{
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
while(1)
{
if ( GetDvarInt("bot_DrawDebugGametype") == 1 )
{
foreach( goal in level.ball_goals )
{
foreach( node in goal.ball_jump_nodes )
{
color = undefined;
if ( node == goal.nearest_node )
color = (0,1,0);
else if ( goal.team == "allies" )
color = (0,0,1);
else if ( goal.team == "axis" )
color = (1,0,0);
bot_draw_cylinder( node.origin, 10, 10, 0.05, undefined, color, true, 4);
Line( goal.origin, node.origin, color, 1.0, true );
}
}
foreach( ball in level.balls )
{
if ( !IsDefined(ball.carrier) )
{
bot_draw_cylinder( ball.nearest_node.origin, 10, 10, 0.05, undefined, (0,1,0), true, 4 );
Line( ball bot_ball_get_origin(), ball.nearest_node.origin, (0,1,0), 1.0, true );
}
}
}
wait(0.05);
}
}
#/
crate_can_use( crate )
{
// Agents can only pickup boxes normally
if ( IsAgent(self) && !IsDefined( crate.boxType ) )
return false;
if ( self has_ball() )
return false;
return true;
}
bot_ball_think()
{
self notify( "bot_ball_think" );
self endon( "bot_ball_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self endon( "owner_disconnect" );
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
self BotSetFlag("separation",0); // don't slow down when we get close to other bots
can_predict_carrier_loc = RandomInt(100) < (self BotGetDifficultySetting("strategyLevel") * 25);
next_predict_carrier_location_time = 0;
self.last_pass_throw_check = 0;
self.ball_can_pass_ally = RandomInt(100) < (self BotGetDifficultySetting("strategyLevel") * 25);
self.ball_can_pass_enemy = RandomInt(100) < (self BotGetDifficultySetting("strategyLevel") * 25);
self.ball_can_throw = RandomInt(100) < (self BotGetDifficultySetting("strategyLevel") * 25);
my_goal = level.ball_goals[self.team];
enemy_goal = level.ball_goals[get_enemy_team(self.team)];
self childthread watch_ball_pickup_and_loss();
while ( true )
{
if ( self.health <= 0 )
continue;
needs_new_role = !IsDefined(self.role);
/#
if ( needs_new_role && IsDefined(SCR_CONST_BALL_OVERRIDE_ROLE) )
{
needs_new_role = false;
self.role = SCR_CONST_BALL_OVERRIDE_ROLE;
}
#/
if ( needs_new_role )
self bot_gametype_initialize_attacker_defender_role();
self BotSetFlag( "force_sprint", false );
ally_balls_carried = bot_ball_get_balls_carried_by_team(self.team);
enemy_balls_carried = bot_ball_get_balls_carried_by_team(get_enemy_team(self.team));
foreach( ball in enemy_balls_carried )
{
ball_carried_origin = ball bot_ball_get_origin() - (0,0,75);
self BotGetImperfectEnemyInfo( ball.carrier, ball_carried_origin );
}
if ( self has_ball() )
{
// I have the ball, so bring it home
self BotSetFlag( "force_sprint", true );
// First pick a random close jump node
distance_to_goal_sq = DistanceSquared(self.origin, enemy_goal.nearest_node.origin);
if ( distance_to_goal_sq > 600 * 600 )
{
goal_jump_node_picked = enemy_goal.nearest_node;
goal_radius = 600;
}
else
{
goal_jump_node_picked = get_array_of_closest(self.origin, enemy_goal.ball_jump_nodes)[0];
goal_radius = 16;
}
self clear_defend_or_goal_if_necessary();
self BotSetScriptGoal( goal_jump_node_picked.origin, goal_radius, "critical" );
result = self bot_waittill_goal_or_fail(undefined,"bot_no_longer_has_ball");
if ( result == "goal" && DistanceSquared( self.origin, goal_jump_node_picked.origin ) <= 16*16 )
{
// Made it to the jump node chosen. Now, need to make the jump into the goal
self BotClearScriptGoal();
look_dir = VectorNormalize(enemy_goal.origin - self GetEye());
if ( VectorDot(look_dir,(0,0,1)) < 0.93 )
self BotLookAtPoint(enemy_goal.origin, 5.0, "script_forced");
time_waited = 0;
has_dodged_toward_goal = false;
last_height = self.origin[2];
while( time_waited < 4.0 && self has_ball() )
{
self BotSetScriptMove( VectorToYaw(enemy_goal.origin - self.origin), 0.05 );
if ( time_waited == 0.30 || time_waited == 0.75 )
self BotPressButton("jump");
reached_jump_apex = (time_waited > 1.25) && (self.origin[2] < last_height);
last_height = self.origin[2];
if ( !has_dodged_toward_goal )
{
z_dist_to_goal = abs(self.origin[2]-enemy_goal.origin[2]);
dist_to_goal_2d = Distance2D(self.origin,enemy_goal.origin);
if ( z_dist_to_goal < 10 || (reached_jump_apex && dist_to_goal_2d > 200) )
{
self BotPressButton("sprint");
has_dodged_toward_goal = true;
}
}
wait(0.05);
time_waited += 0.05;
if ( !self has_ball() || (time_waited > 0.75 && self IsOnGround()) )
time_waited = 5.0;
}
self BotLookAtPoint( undefined );
}
self BotClearScriptGoal();
}
else if ( self.role == "attacker" )
{
free_balls = bot_ball_get_free_balls();
if ( free_balls.size <= 0 )
{
// No free balls, so lets see if we need to kill or protect a ball carrier
if ( enemy_balls_carried.size > 0 )
{
closest_enemy_ball = self bot_ball_get_closest_ball(enemy_balls_carried);
closest_enemy_ball_loc = closest_enemy_ball bot_ball_get_origin() - (0,0,75);
if ( can_predict_carrier_loc )
{
if ( GetTime() > next_predict_carrier_location_time )
{
next_predict_carrier_location_time = GetTime() + 5000;
predicted_loc = undefined;
path_to_goal = GetNodesOnPath( closest_enemy_ball_loc, my_goal.nearest_node.origin );
if ( IsDefined(path_to_goal) && path_to_goal.size > 0 )
predicted_loc = path_to_goal[Int(path_to_goal.size * RandomFloatRange(0.25,0.75))].origin;
self clear_defend_or_goal_if_necessary();
if ( IsDefined(predicted_loc) && self find_ambush_node( predicted_loc, 512 ) )
self BotSetScriptGoalNode( self.node_ambushing_from, "guard", self.ambush_yaw );
else
self BotSetScriptGoal( closest_enemy_ball_loc, 16, "guard" );
}
}
else
{
self clear_defend_or_goal_if_necessary();
self BotSetScriptGoal( closest_enemy_ball_loc, 16, "guard" );
}
}
else if ( ally_balls_carried.size > 0 )
{
// One of my allies has the ball, so escort him
if ( !self bot_is_bodyguarding() )
{
closest_ally_ball = self bot_ball_get_closest_ball(ally_balls_carried);
self clear_defend_or_goal_if_necessary();
self bot_guard_player(closest_ally_ball.carrier, 500);
}
}
else
{
// No free balls, and no enemies carrying any balls. Means the ball is probably resetting, so just go to the closest ball start and wait for it there
ball_starts_sorted = get_array_of_closest( self.origin, level.ball_starts );
self clear_defend_or_goal_if_necessary();
self BotSetScriptGoal( ball_starts_sorted[0].origin, 16, "guard" );
}
}
else
{
// There are still some free balls, so seek the closest one
ball_chosen = self bot_ball_get_closest_ball(free_balls);
Assert(IsDefined(ball_chosen));
self clear_defend_or_goal_if_necessary( "objective" );
if ( ball_chosen.ball_at_rest )
{
ball_origin = ball_chosen bot_ball_get_origin();
if ( !self BotHasScriptGoal() || !bot_vectors_are_equal( ball_origin, self BotGetScriptGoal() ) )
self BotSetScriptGoal( ball_origin, 16, "objective", undefined, SCR_CONST_BOT_BALL_OBJ_RADIUS );
}
else
{
self BotSetScriptGoal( ball_chosen.nearest_node.origin, 16, "objective", undefined, SCR_CONST_BOT_BALL_OBJ_RADIUS );
}
}
}
else
{
Assert( self.role == "defender" );
ball_to_get_rid_of = undefined;
free_balls = bot_ball_get_free_balls();
foreach( ball in free_balls )
{
ball_dist_to_my_goal_sq = Distance2DSquared( ball bot_ball_get_origin(), my_goal.origin );
if ( ball_dist_to_my_goal_sq < squared(get_ball_goal_protect_radius()) )
{
ball_to_get_rid_of = ball;
break;
}
}
if ( IsDefined(ball_to_get_rid_of) )
{
self clear_defend_or_goal_if_necessary();
if ( ball_to_get_rid_of.ball_at_rest )
self BotSetScriptGoal( ball_to_get_rid_of bot_ball_get_origin(), 16, "guard" );
else
self BotSetScriptGoal( ball_to_get_rid_of.nearest_node.origin, 16, "guard" );
self bot_waittill_goal_or_fail(1.0);
}
else if ( !self bot_is_protecting() )
{
self BotClearScriptGoal();
optional_params["score_flags"] = "strict_los";
optional_params["override_origin_node"] = my_goal.nearest_node;
self bot_protect_point( my_goal.nearest_node.origin, get_ball_goal_protect_radius(), optional_params );
}
}
wait(0.05);
}
}
watch_ball_pickup_and_loss()
{
had_ball = false;
while(1)
{
if ( self has_ball() && !had_ball )
{
self childthread monitor_pass_throw();
had_ball = true;
self BotSetFlag("melee_critical_path",true);
}
else if ( !self has_ball() && had_ball )
{
self notify("bot_no_longer_has_ball");
had_ball = false;
self BotSetFlag("melee_critical_path",false);
}
wait(0.05);
}
}
monitor_pass_throw()
{
self endon("bot_no_longer_has_ball");
my_goal = level.ball_goals[self.team];
enemy_goal = level.ball_goals[get_enemy_team(self.team)];
while(1)
{
if ( self.ball_can_pass_ally )
{
if ( IsDefined(self.pass_target) )
{
can_pass_ally = true;
/#
if ( bot_gametype_ignore_human_player_roles() && !IsAI(self.pass_target) )
can_pass_ally = false;
#/
if ( can_pass_ally )
{
my_dist_to_goal_sq = DistanceSquared( self.origin, enemy_goal.origin );
ally_dist_to_goal_sq = DistanceSquared( self.pass_target.origin, enemy_goal.origin );
if ( ally_dist_to_goal_sq <= my_dist_to_goal_sq )
{
bot_dir = AnglesToForward( self GetPlayerAngles() );
bot_to_ally = VectorNormalize(self.pass_target.origin - self.origin);
dot = VectorDot( bot_dir, bot_to_ally );
if ( dot > 0.70 )
{
self BotLookAtPoint(self.pass_target.origin + (0,0,40),1.25,"script_forced");
wait(0.25);
self BotPressButton("throw");
wait(1.0);
}
}
}
}
}
if ( self.ball_can_pass_enemy )
{
if ( IsDefined(self.enemy) && IsAlive(self.enemy) && self BotCanSeeEntity(self.enemy) )
{
can_throw_enemy = true;
/#
if ( bot_gametype_ignore_human_player_roles() && !IsAI(self.enemy) )
can_throw_enemy = false;
#/
if ( can_throw_enemy )
{
my_dist_to_my_goal_sq = DistanceSquared( self.origin, my_goal.origin );
too_close_to_goal = my_dist_to_my_goal_sq < squared(get_ball_goal_protect_radius());
if ( !too_close_to_goal && DistanceSquared( self.origin, self.enemy.origin ) < squared(SCR_CONST_BALL_MAX_ENEMY_THROW_DIST) )
{
enemy_dir = AnglesToForward( self.enemy GetPlayerAngles() );
enemy_to_bot = VectorNormalize(self.origin - self.enemy.origin);
dot = VectorDot( enemy_dir, enemy_to_bot );
if ( dot > 0.50 )
{
bot_dir = AnglesToForward( self GetPlayerAngles() );
bot_to_enemy = -1 * enemy_to_bot;
dot = VectorDot( bot_dir, bot_to_enemy );
if ( dot > 0.77 )
{
self BotLookAtPoint(self.enemy.origin + (0,0,40),1.25,"script_forced");
wait(0.25);
self BotPressButton("attack");
wait(1.0);
}
}
}
}
}
}
if ( self.ball_can_throw )
{
if ( self.health < 100 && self bot_ball_origin_can_see_goal( self.origin, enemy_goal ) )
{
self BotLookAtPoint( enemy_goal.origin, 1.25, "script_forced" );
wait(0.25);
self BotPressButton("attack");
wait(1.0);
}
else if ( self.role == "defender" )
{
my_dist_to_my_goal_sq = DistanceSquared( self.origin, my_goal.origin );
if ( my_dist_to_my_goal_sq < squared(get_ball_goal_protect_radius()) )
{
ball_throw_dir = AnglesToForward( (self GetPlayerAngles() * (0,1,1)) + (-30,0,0) );
self BotLookAtPoint( self GetEye() + ball_throw_dir * 200, 1.25, "script_forced" );
wait(0.25);
self BotPressButton("attack");
wait(1.0);
}
}
}
wait(0.05);
}
}
ball_carrier_is_almost_visible( ball_carrier )
{
Assert( ball_carrier has_ball() );
nearest_node_self = self GetNearestNode();
nearest_node_carrier = ball_carrier GetNearestNode();
if ( IsDefined(nearest_node_self) && IsDefined(nearest_node_carrier) )
{
if ( NodesVisible(nearest_node_self, nearest_node_carrier, true) )
return nearest_node_carrier;
linked_nodes = GetLinkedNodes(nearest_node_carrier);
foreach( node in linked_nodes )
{
if ( NodesVisible(nearest_node_self, node, true) )
return node;
}
}
return undefined;
}
bot_ball_is_resetting()
{
return (self.compassIcons["friendly"] == "waypoint_ball_download") || (self.compassIcons["friendly"] == "waypoint_ball_upload");
}
bot_ball_get_closest_ball( balls )
{
if ( balls.size == 1 )
return balls[0];
closest_dist_sq = 99999999;
closest_ball = undefined;
foreach ( ball in balls )
{
dist_to_ball_sq = DistanceSquared( self.origin, ball bot_ball_get_origin() );
if ( dist_to_ball_sq < closest_dist_sq )
{
closest_dist_sq = dist_to_ball_sq;
closest_ball = ball;
}
}
return closest_ball;
}
bot_ball_get_origin() // self == ball
{
if ( IsDefined(self.carrier) )
return self.curorigin;
else
return self.visuals[0].origin;
}
clear_defend_or_goal_if_necessary( expected_new_goal_type )
{
if ( self bot_is_defending() )
{
self bot_defend_stop();
}
if ( self BotGetScriptGoalType() == "objective" )
{
new_goal_is_objective = IsDefined(expected_new_goal_type) && expected_new_goal_type == "objective";
if ( !new_goal_is_objective )
self BotClearScriptGoal();
}
}
has_ball()
{
return IsDefined(self.ball_carried);
}
bot_ball_get_free_balls()
{
free_balls = [];
foreach(ball in level.balls)
{
if ( ball bot_ball_is_resetting() )
continue;
if( !IsDefined(ball.carrier) )
free_balls[free_balls.size] = ball;
}
return free_balls;
}
bot_ball_get_balls_carried_by_team(team)
{
team_balls = [];
foreach(ball in level.balls)
{
if ( ball bot_ball_is_resetting() )
continue;
if( IsDefined(ball.carrier) && ball.carrier.team == team )
team_balls[team_balls.size] = ball;
}
return team_balls;
}
bot_ball_attacker_limit_for_team(team)
{
team_limit = bot_gametype_get_num_players_on_team(team);
num_attackers_wanted_raw = team_limit * 0.67;
floor_num = floor(num_attackers_wanted_raw);
ceil_num = ceil(num_attackers_wanted_raw);
dist_to_floor = num_attackers_wanted_raw - floor_num;
dist_to_ceil = ceil_num - num_attackers_wanted_raw;
if ( dist_to_floor < dist_to_ceil )
num_attackers_wanted = int(floor_num);
else
num_attackers_wanted = int(ceil_num);
return num_attackers_wanted;
}
bot_ball_defender_limit_for_team(team)
{
team_limit = bot_gametype_get_num_players_on_team(team);
return ( team_limit - bot_ball_attacker_limit_for_team(team) );
}
get_ball_goal_protect_radius()
{
if ( IsAlive(self) && !IsDefined(level.protect_radius) )
{
// Radius for a ball goal protect will be the minimum of 1000 or (average world size / 5)
worldBounds = self BotGetWorldSize();
average_side = (worldBounds[0] + worldBounds[1]) / 2;
level.protect_radius = min(800,average_side/5.5);
}
if ( !IsDefined(level.protect_radius) )
return 900; // No bot has had a chance to calculate this yet, so just return a good default value
return level.protect_radius;
}
get_allied_attackers_for_team(team)
{
return bot_gametype_get_allied_attackers_for_team(team, level.ball_goals[team].origin, get_ball_goal_protect_radius());
}
get_allied_defenders_for_team(team)
{
return bot_gametype_get_allied_defenders_for_team(team, level.ball_goals[team].origin, get_ball_goal_protect_radius());
}

View File

@ -0,0 +1,531 @@
#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);
}
}

View File

@ -0,0 +1,557 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_conf();
}
/#
empty_function_to_force_script_dev_compile() {}
#/
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_conf_think;
}
setup_bot_conf()
{
// Needs to occur regardless of whether bots are enabled / in play, because it is used with the Squad Member
level.bot_tag_obj_radius = 200;
// If a tag is this distance above the bot's eyes, he will try to jump for it
level.bot_tag_allowable_jump_height = 38;
if ( isAugmentedGameMode() )
level.bot_tag_allowable_jump_height += 170;
/#
thread bot_conf_debug();
#/
}
/#
SCR_CONST_DEBUG_SHOW_ALL_TAGS_NAME = "bot_DrawDebugShowAllTagsSeen";
bot_conf_debug()
{
bot_waittill_bots_enabled();
SetDevDvarIfUninitialized( SCR_CONST_DEBUG_SHOW_ALL_TAGS_NAME, "0" );
SetDevDvarIfUninitialized( "bot_DrawDebugTagNearestNodes", "0" );
while(1)
{
if ( GetDvarInt("bot_DrawDebugGametype") == 1 )
{
if ( GetDvar(SCR_CONST_DEBUG_SHOW_ALL_TAGS_NAME) == "0" )
{
foreach( tag in level.dogtags )
{
if ( tag maps\mp\gametypes\_gameobjects::canInteractWith("allies") || tag maps\mp\gametypes\_gameobjects::canInteractWith("axis") )
{
bot_draw_circle( tag.curorigin, level.bot_tag_obj_radius, (1,0,0), false, 16 );
}
}
}
else
{
foreach( tag in level.dogtags )
{
if ( tag maps\mp\gametypes\_gameobjects::canInteractWith("allies") || tag maps\mp\gametypes\_gameobjects::canInteractWith("axis") )
{
bot_draw_circle( tag.curorigin, 10, (0,1,0), true, 16 );
}
}
foreach( player in level.participants )
{
if ( !IsDefined( player.team ) )
continue;
if ( IsAlive(player) && IsDefined(player.tags_seen) )
{
foreach( tag in player.tags_seen )
{
if ( tag.tag maps\mp\gametypes\_gameobjects::canInteractWith(player.team) )
{
// Red line means its an enemy tag, blue line means an ally tag
lineColor = undefined;
if ( player.team != tag.tag.victim.team )
lineColor = (1,0,0);
else
lineColor = (0,0,1);
line( tag.tag.curorigin, player.origin + (0,0,20), lineColor, 1.0, true );
}
}
}
}
}
}
if ( GetDvar("bot_DrawDebugTagNearestNodes") == "1" )
{
foreach( tag in level.dogtags )
{
if ( tag maps\mp\gametypes\_gameobjects::canInteractWith("allies") || tag maps\mp\gametypes\_gameobjects::canInteractWith("axis") )
{
if ( IsDefined(tag.nearest_node) )
{
bot_draw_cylinder(tag.nearest_node.origin, 10, 10, 0.05, undefined, (0,0,1), true, 4);
line(tag.curorigin, tag.nearest_node.origin, (0,0,1), 1.0, true);
}
}
}
}
wait(0.05);
}
}
#/
bot_conf_think()
{
self notify( "bot_conf_think" );
self endon( "bot_conf_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self.next_time_check_tags = GetTime() + 500;
self.tags_seen = [];
self childthread bot_watch_new_tags();
if ( self.personality == "camper" )
{
self.conf_camper_camp_tags = false;
if ( !IsDefined( self.conf_camping_tag ) )
self.conf_camping_tag = false;
}
while ( true )
{
has_curr_tag = IsDefined(self.tag_getting);
needs_to_sprint = false;
if ( has_curr_tag && self BotHasScriptGoal() )
{
script_goal = self BotGetScriptGoal();
if ( bot_vectors_are_equal( self.tag_getting.ground_pos, script_goal ) )
{
// Script goal is this tag
if ( self BotPursuingScriptGoal() )
{
needs_to_sprint = true;
}
}
else if ( self bot_has_tactical_goal( "kill_tag" ) && self.tag_getting maps\mp\gametypes\_gameobjects::canInteractWith(self.team) )
{
// The bot has a goal to grab a tag, but yet his current script goal is not at the tag he's supposed to get
// this can happen if the bot is heading toward a tag, and the tag's owner dies in his sight. Now the tag has moved locations, but since he
// died onscreen, the tag remains in the bot's visible list and so the bot doesn't know he has to switch destinations
self.tag_getting = undefined;
has_curr_tag = false;
}
}
self BotSetFlag( "force_sprint", needs_to_sprint );
self.tags_seen = self bot_remove_invalid_tags( self.tags_seen );
best_tag = self bot_find_best_tag_from_array( self.tags_seen, true );
desired_tag_exists = IsDefined(best_tag);
if ( (has_curr_tag && !desired_tag_exists) || (!has_curr_tag && desired_tag_exists) || (has_curr_tag && desired_tag_exists && self.tag_getting != best_tag) )
{
// We're either setting self.tag_getting, clearing it, or changing it from one tag to a new one
self.tag_getting = best_tag;
self BotClearScriptGoal();
self notify("stop_camping_tag");
self clear_camper_data();
self bot_abort_tactical_goal( "kill_tag" );
}
if ( IsDefined(self.tag_getting) )
{
self.conf_camping_tag = false;
if ( self.personality == "camper" && self.conf_camper_camp_tags )
{
// Camp this tag instead of grabbing it
self.conf_camping_tag = true;
if ( self should_select_new_ambush_point() )
{
if ( find_ambush_node( self.tag_getting.ground_pos, 1000 ) )
{
self childthread bot_camp_tag( self.tag_getting, "camp" );
}
else
{
self.conf_camping_tag = false;
}
}
}
if ( !self.conf_camping_tag )
{
if ( !self bot_has_tactical_goal( "kill_tag" ) )
{
extra_params = SpawnStruct();
extra_params.script_goal_type = "objective";
extra_params.objective_radius = level.bot_tag_obj_radius;
self bot_new_tactical_goal( "kill_tag", self.tag_getting.ground_pos, 25, extra_params );
}
}
}
did_something_else = false;
if ( IsDefined( self.additional_tactical_logic_func ) )
{
did_something_else = self [[ self.additional_tactical_logic_func ]]();
}
if ( !IsDefined(self.tag_getting) )
{
if ( !did_something_else )
{
self [[ self.personality_update_function ]]();
}
}
if ( GetTime() > self.next_time_check_tags )
{
self.next_time_check_tags = GetTime() + 500;
new_visible_tags = self bot_find_visible_tags( true );
self.tags_seen = bot_combine_tag_seen_arrays( new_visible_tags, self.tags_seen );
}
/#
if ( GetDvarInt("bot_DrawDebugGametype") == 1 && GetDvar(SCR_CONST_DEBUG_SHOW_ALL_TAGS_NAME) == "0" )
{
if ( IsDefined(self.tag_getting) && self.health > 0 )
{
// Red line means the bot is camping the tag. Otherwise the line colors don't mean anything, just slightly different shades to distinguish between teams
color = (0.5,0,0.5);
if ( self.team == "allies" )
color = (1,0,1);
if ( IsDefined(self.conf_camper_camp_tags) && self.conf_camper_camp_tags )
color = (1,0,0);
Line( self.origin + (0,0,40), self.tag_getting.curorigin + (0,0,10), color, 1.0, true, 1 );
}
}
#/
wait(0.05);
}
}
bot_check_tag_above_head( tag )
{
if ( IsDefined(tag.on_path_grid) && tag.on_path_grid )
{
self_eye_pos = self.origin + (0,0,55);
if ( Distance2DSquared(tag.curorigin, self_eye_pos) < 12 * 12 )
{
tag_height_over_bot_head = tag.curorigin[2] - self_eye_pos[2];
if ( tag_height_over_bot_head > 0 )
{
if ( tag_height_over_bot_head < level.bot_tag_allowable_jump_height )
{
if ( !IsDefined(self.last_time_jumped_for_tag) )
self.last_time_jumped_for_tag = 0;
if ( GetTime() - self.last_time_jumped_for_tag > 3000 )
{
self.last_time_jumped_for_tag = GetTime();
self thread bot_jump_for_tag();
}
}
else
{
tag.on_path_grid = false;
return true;
}
}
}
}
return false;
}
bot_jump_for_tag()
{
self endon("death");
self endon("disconnect");
self BotSetStance("stand");
wait(1.0);
self BotPressButton("jump");
wait(0.5);
if ( isAugmentedGameMode() )
self BotPressButton("jump");
wait(0.5);
self BotSetStance("none");
}
bot_watch_new_tags()
{
while( 1 )
{
level waittill( "new_tag_spawned", newTag );
// When a new tag spawns, look for them right away
self.next_time_check_tags = -1;
// If I was involved in this tag spawning (as victim or attacker), I automatically know about it
if ( IsDefined( newTag ) )
{
if ( (IsDefined( newTag.victim ) && newTag.victim == self) || (IsDefined( newTag.attacker ) && newTag.attacker == self) )
{
if ( !IsDefined(newTag.on_path_grid) && !IsDefined(newTag.calculations_in_progress) )
{
thread calculate_tag_on_path_grid( newTag );
waittill_tag_calculated_on_path_grid( newTag );
if ( newTag.on_path_grid )
{
new_tag_struct = SpawnStruct();
new_tag_struct.origin = newTag.curorigin;
new_tag_struct.tag = newTag;
new_tag_fake_array[0] = new_tag_struct;
self.tags_seen = bot_combine_tag_seen_arrays( new_tag_fake_array, self.tags_seen );
}
}
}
}
}
}
bot_combine_tag_seen_arrays( new_tag_seen_array, old_tag_seen_array )
{
new_array = old_tag_seen_array;
foreach ( new_tag in new_tag_seen_array )
{
tag_already_exists_in_old_array = false;
foreach ( old_tag in old_tag_seen_array )
{
if ( (new_tag.tag == old_tag.tag) && bot_vectors_are_equal( new_tag.origin, old_tag.origin ) )
{
tag_already_exists_in_old_array = true;
break;
}
}
if ( !tag_already_exists_in_old_array )
new_array = array_add( new_array, new_tag );
}
return new_array;
}
bot_is_tag_visible( tag, nearest_node_self, fov_self )
{
if ( !tag.calculated_nearest_node )
{
tag.nearest_node = GetClosestNodeInSight(tag.curorigin);
tag.calculated_nearest_node = true;
}
if ( IsDefined(tag.calculations_in_progress) )
return false; // ignore this tag while another bot is calculating for it
nearest_node_to_tag = tag.nearest_node;
tag_first_time_ever_seen = !IsDefined(tag.on_path_grid);
if ( IsDefined( nearest_node_to_tag ) && (tag_first_time_ever_seen || tag.on_path_grid) )
{
node_visible = (nearest_node_to_tag == nearest_node_self) || NodesVisible( nearest_node_to_tag, nearest_node_self, true );
if ( node_visible )
{
node_within_fov = within_fov( self.origin, self.angles, tag.curorigin, fov_self );
if ( node_within_fov )
{
if ( tag_first_time_ever_seen )
{
thread calculate_tag_on_path_grid( tag );
waittill_tag_calculated_on_path_grid( tag );
if ( !tag.on_path_grid )
return false; // Subsequent checks will just return immediately at the top since this is now defined
}
return true;
}
}
}
return false;
}
bot_find_visible_tags( require_los, optional_nearest_node_self, optional_fov_self )
{
nearest_node_self = undefined;
if ( IsDefined(optional_nearest_node_self) )
nearest_node_self = optional_nearest_node_self;
else
nearest_node_self = self GetNearestNode();
fov_self = undefined;
if ( IsDefined(optional_fov_self) )
fov_self = optional_fov_self;
else
fov_self = self BotGetFovDot();
visible_tags = [];
if ( IsDefined(nearest_node_self) )
{
foreach( tag in level.dogtags )
{
if ( tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) )
{
add_tag = false;
if ( !require_los || (tag.attacker == self) ) // Can always see tags from your kills on the radar
{
if ( !IsDefined(tag.calculations_in_progress) ) // if the tag is still being calculated, then ignore it
{
if ( !IsDefined(tag.on_path_grid) )
{
level thread calculate_tag_on_path_grid( tag );
waittill_tag_calculated_on_path_grid( tag );
}
add_tag = (DistanceSquared(self.origin, tag.ground_pos) < 1000 * 1000) && tag.on_path_grid;
}
}
else if ( bot_is_tag_visible( tag, nearest_node_self, fov_self ) )
{
add_tag = true;
}
if ( add_tag )
{
new_tag_struct = SpawnStruct();
new_tag_struct.origin = tag.curorigin;
new_tag_struct.tag = tag;
visible_tags = array_add( visible_tags, new_tag_struct );
}
}
}
}
return visible_tags;
}
calculate_tag_on_path_grid( tag )
{
tag endon("reset");
tag.calculations_in_progress = true;
tag.on_path_grid = bot_point_is_on_pathgrid(tag.curorigin, undefined, level.bot_tag_allowable_jump_height + 55);
if ( tag.on_path_grid )
{
tag.ground_pos = GetGroundPosition( tag.curorigin, 32 );
if ( !IsDefined(tag.ground_pos) )
tag.on_path_grid = false;
}
tag.calculations_in_progress = undefined;
}
waittill_tag_calculated_on_path_grid(tag)
{
while( !IsDefined(tag.on_path_grid) )
wait(0.05);
}
bot_find_best_tag_from_array( tag_array, check_allies_getting_tag )
{
best_tag = undefined;
if ( tag_array.size > 0 )
{
// find best tag
best_tag_dist_sq = 99999 * 99999;
foreach( tag_struct in tag_array )
{
num_allies_getting_tag = self get_num_allies_getting_tag( tag_struct.tag );
if ( !check_allies_getting_tag || num_allies_getting_tag < 2 )
{
dist_self_to_tag_sq = DistanceSquared( tag_struct.tag.ground_pos, self.origin );
if ( dist_self_to_tag_sq < best_tag_dist_sq )
{
best_tag = tag_struct.tag;
best_tag_dist_sq = dist_self_to_tag_sq;
}
}
}
}
return best_tag;
}
bot_remove_invalid_tags( tags )
{
valid_tags = [];
foreach ( tag_struct in tags )
{
// Need to check if the tag can still be interacted with and if it is in the same place as where we originally saw it
// This is because the tags are reused in the game, so this is to check if the tag has already been picked up, or if the player whose tag it is
// died again in a different spot, moving the tag to that location
if ( tag_struct.tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) && bot_vectors_are_equal( tag_struct.tag.curorigin, tag_struct.origin ) )
{
if ( !self bot_check_tag_above_head( tag_struct.tag ) && tag_struct.tag.on_path_grid )
valid_tags = array_add( valid_tags, tag_struct );
}
}
return valid_tags;
}
get_num_allies_getting_tag( tag )
{
num = 0;
foreach( player in level.participants )
{
if ( !IsDefined( player.team ) )
continue;
if ( player.team == self.team && player != self )
{
if ( IsAI( player ) )
{
if ( IsDefined(player.tag_getting) && player.tag_getting == tag )
num++;
}
else
{
// If player is within 400 distance from a tag, consider him to be going for it
if ( DistanceSquared( player.origin, tag.curorigin ) < 400 * 400 )
num++;
}
}
}
return num;
}
bot_camp_tag( tag, goal_type, optional_endon )
{
self notify("bot_camp_tag");
self endon("bot_camp_tag");
self endon("stop_camping_tag");
if ( IsDefined(optional_endon) )
self endon(optional_endon);
self BotSetScriptGoalNode( self.node_ambushing_from, goal_type, self.ambush_yaw );
result = self bot_waittill_goal_or_fail();
if ( result == "goal" )
{
nearest_node_to_tag = tag.nearest_node;
if ( IsDefined( nearest_node_to_tag ) )
{
nodes_to_watch = FindEntrances( self.origin );
nodes_to_watch = array_add( nodes_to_watch, nearest_node_to_tag );
self childthread bot_watch_nodes( nodes_to_watch );
}
}
}

View File

@ -0,0 +1,344 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
/#
SCR_CONST_CTF_BOTS_IGNORE_HUMAN_PLAYER_ROLES = false;
#/
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_ctf();
}
/#
empty_function_to_force_script_dev_compile() {}
#/
setup_callbacks()
{
level.bot_funcs["crate_can_use"] = ::crate_can_use;
level.bot_funcs["gametype_think"] = ::bot_ctf_think;
level.bot_funcs["get_watch_node_chance"] = ::bot_ctf_get_node_chance;
}
setup_bot_ctf()
{
/#
if ( SCR_CONST_CTF_BOTS_IGNORE_HUMAN_PLAYER_ROLES )
level.bot_gametype_ignore_human_player_roles = true;
#/
level.bot_gametype_attacker_limit_for_team = ::ctf_bot_attacker_limit_for_team;
level.bot_gametype_defender_limit_for_team = ::ctf_bot_defender_limit_for_team;
level.bot_gametype_allied_attackers_for_team = ::get_allied_attackers_for_team;
level.bot_gametype_allied_defenders_for_team = ::get_allied_defenders_for_team;
bot_waittill_bots_enabled();
while( !IsDefined(level.teamFlags) )
wait(0.05);
level.teamFlags["allies"].script_label = "allies";
level.teamFlags["axis"].script_label = "axis";
bot_cache_entrances_to_gametype_array( level.teamFlags, "flag_" );
zone = GetZoneNearest( level.teamFlags["allies"].origin );
if ( IsDefined( zone ) )
BotZoneSetTeam( zone, "allies" );
zone = GetZoneNearest( level.teamFlags["axis"].origin );
if ( IsDefined( zone ) )
BotZoneSetTeam( zone, "axis" );
level.capZones["allies"].nearest_node = level.teamFlags["allies"].nearest_node;
level.capZones["axis"].nearest_node = level.teamFlags["axis"].nearest_node;
thread bot_ctf_ai_director_update();
level.bot_gametype_precaching_done = true;
}
crate_can_use( crate )
{
// Agents can only pickup boxes normally
if ( IsAgent(self) && !IsDefined( crate.boxType ) )
return false;
if ( IsDefined(self.carryFlag) )
return false;
return ( level.teamFlags[self.team] maps\mp\gametypes\_gameobjects::isHome() );
}
bot_ctf_think()
{
self notify( "bot_ctf_think" );
self endon( "bot_ctf_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
self.next_time_hunt_carrier = 0;
self.next_flag_hide_time = 0;
self BotSetFlag("separation",0); // don't slow down when we get close to other bots
self BotSetFlag("use_obj_path_style", true); // avoid lines of sight when pursuing objective goals
set_scripted_pathing_style = false;
wants_scripted_pathing_style = false;
for( ;; )
{
wait(0.05);
if ( self.health <= 0 )
continue;
if ( !IsDefined(self.role) )
self bot_gametype_initialize_attacker_defender_role();
// Manage desired sprint
sprint_desired = false;
if ( self.role == "attacker" )
{
if ( IsDefined(self.carryFlag) )
{
// Sprint more frequently if we have the flag
sprint_desired = true;
}
else if ( !IsDefined(level.flag_carriers[self.team]) )
{
// If no one yet has the flag, sprint more frequently when we get near it
sprint_desired = DistanceSquared( self.origin, level.teamFlags[level.otherTeam[self.team]].curorigin ) < squared( get_flag_protect_radius());
}
}
else
{
if ( !level.teamFlags[self.team] maps\mp\gametypes\_gameobjects::isHome() )
{
// Sprint more frequently if trying to pick up my flag from the ground
sprint_desired = !IsDefined(level.flag_carriers[level.otherTeam[self.team]]);
}
}
self BotSetFlag( "force_sprint", sprint_desired );
wants_scripted_pathing_style = false;
if ( IsDefined(self.carryFlag) )
{
// I have the flag, so bring it home
self clear_defend();
if ( !IsDefined(level.flag_carriers[level.otherTeam[self.team]]) )
{
wants_scripted_pathing_style = true;
if ( !set_scripted_pathing_style )
{
set_scripted_pathing_style = true;
self BotSetPathingStyle("scripted");
}
self BotSetScriptGoal( level.capZones[self.team].curorigin, 16, "critical" );
}
else if ( GetTime() > self.next_flag_hide_time )
{
nodes = GetNodesInRadius( level.capZones[self.team].curorigin, 900, 0, 300 );
hide_node = self BotNodePick( nodes, nodes.size * 0.15, "node_hide_anywhere" );
if ( !IsDefined( hide_node ) )
hide_node = level.capZones[self.team].nearest_node;
Assert(IsDefined(hide_node));
success = self BotSetScriptGoalNode(hide_node, "critical");
if ( success )
self.next_flag_hide_time = GetTime() + 15000;
}
}
else if ( self.role == "attacker" )
{
if ( IsDefined(level.flag_carriers[self.team]) )
{
// One of my friends has the flag, so escort him
if ( !self bot_is_bodyguarding() )
{
self clear_defend();
self BotClearScriptGoal();
self bot_guard_player(level.flag_carriers[self.team], 500);
}
}
else
{
// Try to get the flag
self clear_defend();
if ( self BotGetScriptGoalType() == "critical" )
self BotClearScriptGoal();
self BotSetScriptGoal( level.teamFlags[level.otherTeam[self.team]].curorigin, 16, "objective", undefined, 300 );
}
}
else
{
Assert( self.role == "defender" );
if ( !level.teamFlags[self.team] maps\mp\gametypes\_gameobjects::isHome() )
{
// My flag was taken, try to get it back
if ( !IsDefined(level.flag_carriers[level.otherTeam[self.team]]) )
{
// no one is carrying flag, so run to it
self clear_defend();
self BotSetScriptGoal( level.teamFlags[self.team].curorigin, 16, "critical" );
}
else
{
flag_carrier = level.flag_carriers[level.otherTeam[self.team]];
if ( GetTime() > self.next_time_hunt_carrier || self BotCanSeeEntity(flag_carrier) )
{
self clear_defend();
self BotSetScriptGoal( flag_carrier.origin, 16, "critical" );
self.next_time_hunt_carrier = GetTime() + RandomIntRange(4500,5500);
}
}
}
else if ( !self is_protecting_flag() )
{
self BotClearScriptGoal();
optional_params["score_flags"] = "strict_los";
optional_params["entrance_points_index"] = "flag_" + level.teamFlags[self.team].script_label;
optional_params["nearest_node_to_center"] = level.teamFlags[self.team].nearest_node;
self bot_protect_point( level.teamFlags[self.team].curorigin, get_flag_protect_radius(), optional_params );
}
}
if ( set_scripted_pathing_style && !wants_scripted_pathing_style )
{
set_scripted_pathing_style = false;
self BotSetPathingStyle(undefined);
}
}
}
clear_defend()
{
if ( self bot_is_defending() )
{
self bot_defend_stop();
}
}
is_protecting_flag()
{
return ( self bot_is_protecting() );
}
get_flag_protect_radius()
{
if ( IsAlive(self) && !IsDefined(level.protect_radius) )
{
// Radius for a flag protect will be the minimum of 1000 or (average world size / 5)
worldBounds = self BotGetWorldSize();
average_side = (worldBounds[0] + worldBounds[1]) / 2;
level.protect_radius = min(800,average_side/5.5);
}
if ( !IsDefined(level.protect_radius) )
return 900; // No bot has had a chance to calculate this yet, so just return a good default value
return level.protect_radius;
}
ctf_bot_attacker_limit_for_team(team)
{
team_limit = bot_gametype_get_num_players_on_team(team);
num_attackers_wanted_raw = team_limit * 0.67;
floor_num = floor(num_attackers_wanted_raw);
ceil_num = ceil(num_attackers_wanted_raw);
dist_to_floor = num_attackers_wanted_raw - floor_num;
dist_to_ceil = ceil_num - num_attackers_wanted_raw;
if ( dist_to_floor < dist_to_ceil )
num_attackers_wanted = int(floor_num);
else
num_attackers_wanted = int(ceil_num);
my_score = game["teamScores"][team];
enemy_score = game["teamScores"][get_enemy_team(team)];
if ( my_score+1 < enemy_score )
num_attackers_wanted = int(min(num_attackers_wanted+1,team_limit));
return num_attackers_wanted;
}
ctf_bot_defender_limit_for_team(team)
{
team_limit = bot_gametype_get_num_players_on_team(team);
return ( team_limit - ctf_bot_attacker_limit_for_team(team) );
}
get_allied_attackers_for_team(team)
{
return bot_gametype_get_allied_attackers_for_team(team, level.capZones[team].curorigin, get_flag_protect_radius());
}
get_allied_defenders_for_team(team)
{
return bot_gametype_get_allied_defenders_for_team(team, level.capZones[team].curorigin, get_flag_protect_radius());
}
bot_ctf_ai_director_update()
{
level notify("bot_ctf_ai_director_update");
level endon("bot_ctf_ai_director_update");
level endon("game_ended");
level.flag_carriers = [];
thread bot_gametype_attacker_defender_ai_director_update();
while(1)
{
level.flag_carriers["allies"] = undefined;
level.flag_carriers["axis"] = undefined;
foreach ( player in level.participants )
{
if ( IsAlive(player) && IsDefined(player.carryFlag) )
{
assert( IsTeamParticipant( player ) ); // only team participants should be carrying the flag, not squadmembers
level.flag_carriers[player.team] = player;
}
}
wait(0.05);
}
}
bot_ctf_get_node_chance( node )
{
if ( node == self.node_closest_to_defend_center )
{
// Node closest to defend center always has a priority of 1.0
return 1.0;
}
if ( !self is_protecting_flag() )
{
// If we're not defending a flag, then we don't need to modify the priority
return 1.0;
}
node_on_path_to_enemy_flag = node node_is_on_path_from_labels("flag_allies", "flag_axis");
if ( node_on_path_to_enemy_flag )
return 1.0;
return 0.2;
}

View File

@ -0,0 +1,12 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
main()
{
// nothing for now...
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_gun();
}
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_gun_think;
}
setup_bot_gun()
{
}
bot_gun_think()
{
self notify( "bot_gun_think" );
self endon( "bot_gun_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self endon( "owner_disconnect" );
// This game mode just performs normal personality logic
while ( true )
{
self [[ self.personality_update_function ]]();
wait(0.05);
}
}

View File

@ -0,0 +1,15 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
//=======================================================
// main
//=======================================================
main()
{
// nothing for now...
}

View File

@ -0,0 +1,369 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_hp();
/#
thread bot_hp_debug();
#/
}
/#
empty_function_to_force_script_dev_compile() {}
#/
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_hp_think;
level.bot_funcs["should_start_cautious_approach"] = ::should_start_cautious_approach_hp;
}
setup_bot_hp()
{
bot_waittill_bots_enabled();
for( i = 0; i < level.all_hp_zones.size; i++ )
{
zone = level.all_hp_zones[i];
zone.script_label = "zone_" + i;
zone thread monitor_zone_control();
was_off = false;
if ( IsDefined(zone.trig.trigger_off) && zone.trig.trigger_off )
{
zone.trig trigger_on();
was_off = true;
}
zone.nodes = GetNodesInTrigger(zone.trig);
bot_add_missing_nodes(zone, zone.trig);
if ( was_off )
zone.trig trigger_off();
/#
if ( zone.nodes.size < 3 )
{
wait(5); // Wait till level is loaded to display error message
assertmsg( "Zone " + i + " at location " + zone.origin + " needs at least 3 nodes in its trigger_multiple" );
return; // Just don't play (would cause massive SREs if they did)
}
#/
}
// Only block while caching the first active hardpoint
bot_cache_entrances_to_hardpoints( true );
level.bot_hp_allow_predictive_capping = true;
level.bot_gametype_precaching_done = true;
// Allow caching of the other hardpoints to continue while the game is running
thread bot_cache_entrances_to_hardpoints( false );
}
bot_cache_entrances_to_hardpoints( only_initial_active_zone )
{
entrance_origin_points = [];
entrance_labels = [];
global_index = 0;
foreach ( zone in level.all_hp_zones )
{
if ( (only_initial_active_zone && zone != level.zone) || (!only_initial_active_zone && zone == level.zone) )
continue;
per_zone_index = 0;
zone.entrance_indices = [];
zone.zone_bounds = calculate_zone_node_extents(zone);
zone.center_node = zone_get_node_nearest_2d_bounds(zone,0,0);
combinations = [ (0,0,0), (1,1,0), (1,-1,0), (-1,1,0), (-1,-1,0) ];
foreach( bound in combinations )
{
node = zone_get_node_nearest_2d_bounds(zone,bound[0],bound[1]);
entrance_origin_points[global_index] = node.origin;
label_for_entrance_and_zone = zone.script_label + "_" + per_zone_index;
entrance_labels[global_index] = label_for_entrance_and_zone;
zone.entrance_indices[zone.entrance_indices.size] = label_for_entrance_and_zone;
global_index++;
per_zone_index++;
}
}
bot_cache_entrances( entrance_origin_points, entrance_labels, true );
}
calculate_zone_node_extents( zone )
{
Assert(IsDefined(zone.nodes));
bounds = SpawnStruct();
bounds.min_pt = (999999,999999,999999);
bounds.max_pt = (-999999,-999999,-999999);
foreach ( node in zone.nodes )
{
bounds.min_pt = ( min(node.origin[0],bounds.min_pt[0]), min(node.origin[1],bounds.min_pt[1]), min(node.origin[2],bounds.min_pt[2]) );
bounds.max_pt = ( max(node.origin[0],bounds.max_pt[0]), max(node.origin[1],bounds.max_pt[1]), max(node.origin[2],bounds.max_pt[2]) );
}
bounds.center = ( (bounds.min_pt[0] + bounds.max_pt[0]) / 2, (bounds.min_pt[1] + bounds.max_pt[1]) / 2, (bounds.min_pt[2] + bounds.max_pt[2]) / 2 );
bounds.half_size = ( bounds.max_pt[0] - bounds.center[0], bounds.max_pt[1] - bounds.center[1], bounds.max_pt[2] - bounds.center[2] );
bounds.radius = max(bounds.half_size[0],bounds.half_size[1]);
return bounds;
}
zone_get_node_nearest_2d_bounds( zone, bounds_x_desired, bounds_y_desired )
{
Assert(bounds_x_desired >= -1 && bounds_x_desired <= 1);
Assert(bounds_y_desired >= -1 && bounds_y_desired <= 1);
bounds_point = ( zone.zone_bounds.center[0] + bounds_x_desired * zone.zone_bounds.half_size[0], zone.zone_bounds.center[1] + bounds_y_desired * zone.zone_bounds.half_size[1], 0 );
closest_node = undefined;
closest_dist_sq = 9999999;
foreach( node in zone.nodes )
{
dist_sq = Distance2DSquared( node.origin, bounds_point );
if ( dist_sq < closest_dist_sq )
{
closest_dist_sq = dist_sq;
closest_node = node;
}
}
Assert(IsDefined(closest_node));
return closest_node;
}
monitor_zone_control()
{
self notify( "monitor_zone_control" );
self endon( "monitor_zone_control" );
self endon( "death" );
level endon( "game_ended" );
for(;;)
{
team = self.gameobject maps\mp\gametypes\_gameobjects::getOwnerTeam();
if ( team != "neutral" && team != "none" )
{
zone = GetZoneNearest( self.origin );
if ( IsDefined( zone ) )
BotZoneSetTeam( zone, team );
}
wait(1.0);
}
}
/#
bot_hp_debug()
{
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
was_on = false;
while(1)
{
if ( GetDvarInt("bot_DrawDebugGametype") == 1 )
{
was_on = true;
foreach ( zone in level.zones )
{
color = (0,1,0);
if ( zone == level.zone )
color = (1,0,0);
if ( IsDefined(zone.trig.trigger_off) && zone.trig.trigger_off )
zone.trig trigger_on();
// Draw trigger
if ( zone.trig.classname == "trigger_radius" )
bot_draw_cylinder(zone.trig.origin, zone.trig.radius, zone.trig.height, 0.05, undefined, color, true);
else
BotDebugDrawTrigger(true, zone.trig, color, true);
// Draw nodes
foreach( node in zone.nodes )
bot_draw_cylinder(node.origin, 10, 10, 0.05, undefined, color, true, 4);
}
}
else if ( was_on )
{
was_on = false;
foreach ( zone in level.zones )
{
BotDebugDrawTrigger(false, zone.trig);
}
}
wait(0.05);
}
}
#/
bot_hp_think()
{
self notify( "bot_hp_think" );
self endon( "bot_hp_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
self BotSetFlag("separation",0); // don't slow down when we get close to other bots
self BotSetFlag("grenade_objectives",1);
should_force_capture_next_zone = undefined;
last_recorded_zone = level.zone;
while ( true )
{
wait(0.05);
if ( self.health <= 0 )
continue;
if ( last_recorded_zone != level.zone )
{
should_force_capture_next_zone = undefined;
last_recorded_zone = level.zone;
}
if ( !IsDefined(should_force_capture_next_zone) && level.randomZoneSpawn == 0 && level.bot_hp_allow_predictive_capping )
{
// If the zone is going to change in less than 10 seconds, and we can predict the next zone...
time_till_zone_change = level.zoneMoveTime - GetTime();
if ( time_till_zone_change > 0 && time_till_zone_change < 10000 )
{
my_team_owns_zone = level.zone.gameobject maps\mp\gametypes\_gameobjects::getOwnerTeam() == self.team;
if ( !my_team_owns_zone )
{
// My team does not control the current zone
dist_to_keep_trying_to_capture_cur_zone = level.zone.zone_bounds.radius * 6;
if ( time_till_zone_change < 5000 )
dist_to_keep_trying_to_capture_cur_zone = level.zone.zone_bounds.radius * 3;
dist_to_cur_zone = Distance(level.zone.zone_bounds.center, self.origin);
if ( dist_to_cur_zone > dist_to_keep_trying_to_capture_cur_zone )
{
// If we're not very close to the current zone, then have a chance to head directly to the next zone
should_force_capture_next_zone = bot_should_cap_next_zone();
}
}
else
{
// My team controls the current zone
team_limit = bot_get_max_players_on_team( self.team );
num_bots_allowed_to_stay_at_zone = ceil(team_limit / 2);
if ( time_till_zone_change < 5000 )
num_bots_allowed_to_stay_at_zone = ceil(team_limit / 3);
num_bots_currently_at_zone = bot_get_num_teammates_capturing_zone( level.zone );
if ( num_bots_currently_at_zone + 1 > num_bots_allowed_to_stay_at_zone )
{
// If this bot were to capture the current zone it would put it over the limit, so have a chance to head directly to the next zone
should_force_capture_next_zone = bot_should_cap_next_zone();
}
}
}
}
zone_to_capture = level.zone;
if ( IsDefined(should_force_capture_next_zone) && should_force_capture_next_zone )
zone_to_capture = level.zones[ (level.prevZoneIndex + 1) % level.zones.size ];
if ( !self bot_is_capturing_zone(zone_to_capture) )
self bot_capture_hp_zone(zone_to_capture);
}
}
bot_should_cap_next_zone()
{
if ( level.randomZoneSpawn )
{
return false;
}
else
{
strategy_level = self BotGetDifficultySetting("strategyLevel");
chance_to_cap_next_zone = 0;
if ( strategy_level == 1 )
chance_to_cap_next_zone = 0.1;
else if ( strategy_level == 2 )
chance_to_cap_next_zone = 0.5;
else if ( strategy_level == 3 )
chance_to_cap_next_zone = 0.8;
return RandomFloat(1.0) < chance_to_cap_next_zone;
}
}
bot_get_num_teammates_capturing_zone( zone )
{
return (self bot_get_teammates_capturing_zone( zone )).size;
}
bot_get_teammates_capturing_zone( zone )
{
teammates_capturing_zone = [];
foreach( other_player in level.participants )
{
if ( other_player != self && IsTeamParticipant(other_player) && IsAlliedSentient(self, other_player) )
{
if ( other_player IsTouching(level.zone.trig) )
{
if ( !IsAI(other_player) || other_player bot_is_capturing_zone(zone) )
teammates_capturing_zone[teammates_capturing_zone.size] = other_player;
}
}
}
return teammates_capturing_zone;
}
bot_is_capturing_zone( zone )
{
if ( !self bot_is_capturing() )
return false;
return (self.current_zone == zone);
}
bot_capture_hp_zone( zone )
{
self.current_zone = zone;
optional_params["entrance_points_index"] = zone.entrance_indices;
optional_params["override_origin_node"] = zone.center_node;
self bot_capture_zone( zone.origin, zone.nodes, zone.trig, optional_params );
}
should_start_cautious_approach_hp( firstCheck )
{
if ( firstCheck )
{
team = level.zone.gameobject maps\mp\gametypes\_gameobjects::getOwnerTeam();
if ( team == "neutral" || team == self.team )
return false;
}
return should_start_cautious_approach_default(firstCheck);
}

View File

@ -0,0 +1,332 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
INFECTED_BOT_MELEE_DESPERATION_TIME = 3000;
INFECTED_BOT_MAX_NODE_DIST_SQ = (96*96);//larger to account for multiple bots grouping up on a node
INFECTED_BOT_MELEE_MIN_DIST_SQ = (48*48);
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_infect();
}
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_infect_think;
level.bot_funcs["should_pickup_weapons"] = ::bot_should_pickup_weapons_infect;
}
setup_bot_infect()
{
level.bots_gametype_handles_class_choice = true;
level.bots_ignore_team_balance = true;
level.bots_gametype_handles_team_choice = true;
level.infected_knife_name = "exoknife_mp";
Assert( maps\mp\gametypes\_class::isValidEquipment( level.infected_knife_name, false ) );
thread bot_infect_ai_director_update();
}
bot_should_pickup_weapons_infect()
{
if ( level.infect_choseFirstInfected && self.team == "axis" )
return false; // An infected person was chosen and i'm on the infected team
return maps\mp\bots\_bots::bot_should_pickup_weapons();
}
bot_infect_think()
{
self notify( "bot_infect_think" );
self endon( "bot_infect_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self childthread bot_infect_retrieve_knife();
while ( true )
{
if ( level.infect_choseFirstInfected )
{
if ( self.team == "axis" && self BotGetPersonality() != "run_and_gun" )
{
// Infected bots should be run and gun
self bot_set_personality("run_and_gun");
}
}
if ( self.bot_team != self.team )
self.bot_team = self.team; // Bot became infected so update self.bot_team (needed in infect.gsc)
if ( self.team == "axis" )
{
result = self bot_melee_tactical_insertion_check();
if( !IsDefined( result ) || result )
self BotClearScriptGoal();
}
self [[ self.personality_update_function ]]();
wait(0.05);
}
}
SCR_CONST_INFECT_HIDING_CHECK_TIME = 5000;
bot_infect_ai_director_update()
{
level notify("bot_infect_ai_director_update");
level endon("bot_infect_ai_director_update");
level endon("game_ended");
while(1)
{
infected_players = [];
non_infected_players = [];
foreach( player in level.players )
{
if ( !IsDefined(player.initial_spawn_time) && player.health > 0 && IsDefined(player.team) && (player.team == "allies" || player.team == "axis") )
player.initial_spawn_time = GetTime();
if ( IsDefined(player.initial_spawn_time) && GetTime() - player.initial_spawn_time > 5000 )
{
if ( !IsDefined( player.team ) )
continue;
if ( player.team == "axis" )
infected_players[infected_players.size] = player;
else if ( player.team == "allies" )
non_infected_players[non_infected_players.size] = player;
}
}
if ( infected_players.size > 0 && non_infected_players.size > 0 )
{
all_bots_are_infected = true;
foreach( non_infected_player in non_infected_players )
{
if ( IsBot(non_infected_player) )
all_bots_are_infected = false;
}
if ( all_bots_are_infected )
{
// Every player in non_infected_players is a human
foreach ( player in non_infected_players )
{
if ( !IsDefined(player.last_infected_hiding_time) )
{
player.last_infected_hiding_time = GetTime();
player.last_infected_hiding_loc = player.origin;
player.time_spent_hiding = 0;
}
if ( GetTime() >= player.last_infected_hiding_time + SCR_CONST_INFECT_HIDING_CHECK_TIME )
{
player.last_infected_hiding_time = GetTime();
dist_to_last_hiding_loc_sq = DistanceSquared(player.origin, player.last_infected_hiding_loc);
player.last_infected_hiding_loc = player.origin;
if ( dist_to_last_hiding_loc_sq < 300 * 300 )
{
player.time_spent_hiding += SCR_CONST_INFECT_HIDING_CHECK_TIME;
if ( player.time_spent_hiding >= 20000 )
{
infected_players_sorted = get_array_of_closest(player.origin, infected_players );
foreach ( infected_player in infected_players_sorted )
{
if ( IsBot(infected_player) )
{
goal_type = infected_player BotGetScriptGoalType();
if ( goal_type != "tactical" && goal_type != "critical" )
{
infected_player thread hunt_human( player );
break;
}
}
}
}
}
else
{
player.time_spent_hiding = 0;
player.last_infected_hiding_loc = player.origin;
}
}
}
}
}
wait(1.0);
}
}
hunt_human( player_hunting )
{
self endon("disconnect");
self endon("death");
self BotSetScriptGoal( player_hunting.origin, 0, "critical" );
self bot_waittill_goal_or_fail();
self BotClearScriptGoal();
}
bot_infect_retrieve_knife()
{
if ( self.team == "axis" )
{
self.can_melee_enemy_time = 0;
self.melee_enemy = undefined;
self.melee_enemy_node = undefined;
self.melee_enemy_new_node_time = 0;
self.melee_self_node = undefined;
self.melee_self_new_node_time = 0;
// In Infected mode, all bots should be capable of throwing a knife...
throwKnifeChance = self BotGetDifficultySetting( "throwKnifeChance" );
if ( throwKnifeChance < 0.25 )
{
self BotSetDifficultySetting( "throwKnifeChance", 0.25 );
}
self BotSetDifficultySetting( "allowGrenades", 1 );
// If an infected bot has been without his throwing knife for some time, magically give it back to him
while ( true )
{
if ( self HasWeapon( level.infected_knife_name ) )
{
if ( IsGameParticipant( self.enemy ) )
{
time = GetTime();
if ( !IsDefined( self.melee_enemy ) || self.melee_enemy != self.enemy )
{
// New melee enemy, reset tracking info on him
self.melee_enemy = self.enemy;
self.melee_enemy_node = self.enemy GetNearestNode();
self.melee_enemy_new_node_time = time;
}
else
{
meleeDistSq = squared(self BotGetDifficultySetting( "meleeDist" ));
// Track how long it's been since we were in melee range of the enemy
if ( DistanceSquared( self.enemy.origin, self.origin ) <= meleeDistSq )
{
self.can_melee_enemy_time = time;
}
// Track how long you and the enemy have not changed nodes
melee_enemy_node = self.enemy GetNearestNode();
melee_self_node = self GetNearestNode();
if ( !IsDefined( self.melee_enemy_node ) || self.melee_enemy_node != melee_enemy_node )
{
self.melee_enemy_new_node_time = time;
self.melee_enemy_node = melee_enemy_node;
}
if ( !IsDefined( self.melee_self_node ) || self.melee_self_node != melee_self_node )
{
self.melee_self_new_node_time = time;
self.melee_self_node = melee_self_node;
}
else if ( DistanceSquared( self.origin, self.melee_self_node.origin ) > INFECTED_BOT_MAX_NODE_DIST_SQ )
{
// Haven't actually reached the node
self.melee_self_at_same_node_time = time;
}
// See if all conditions are met - can't reach the enemy and neither of you are moving
if ( self.can_melee_enemy_time+INFECTED_BOT_MELEE_DESPERATION_TIME < time )
{
// Can't melee enemy right now
if ( self.melee_self_new_node_time+INFECTED_BOT_MELEE_DESPERATION_TIME < time )
{
// I've been at the same node for a while
if ( self.melee_enemy_new_node_time+INFECTED_BOT_MELEE_DESPERATION_TIME < time )
{
// Enemy has been at the same node for a while
if ( bot_infect_angle_too_steep_for_knife_throw( self.origin, self.enemy.origin ) )
{
// Might be standing right above or below this guy, need to step back before we throw!
self bot_queued_process( "find_node_can_see_ent", ::bot_infect_find_node_can_see_ent, self.enemy, self.melee_self_node );
}
if ( !(self GetAmmoCount( level.infected_knife_name ) ) )
{
self SetWeaponAmmoClip( level.infected_knife_name, 1 );
}
// Don't do this again for a while or we get a new enemy
self waitForTimeOrNotify( 30, "enemy" );
self BotClearScriptGoal();
}
}
}
}
}
}
wait(0.25);
}
}
}
bot_infect_angle_too_steep_for_knife_throw( testOrigin, testDest )
{
if ( abs( testOrigin[2]-testDest[2] ) > 56.0 && Distance2DSquared( testOrigin, testDest ) < INFECTED_BOT_MELEE_MIN_DIST_SQ )
return true;
return false;
}
bot_infect_find_node_can_see_ent( targetEnt, startNode )
{
if ( !IsDefined( targetEnt ) || !IsDefined( startNode ) )
return;
at_begin_node = false;
if ( IsSubStr( startNode.type, "Begin" ) )
at_begin_node = true;
neighborNodes = GetLinkedNodes( startNode );
if ( IsDefined( neighborNodes ) && neighborNodes.size )
{
neighborNodesRandom = array_randomize( neighborNodes );
foreach( nNode in neighborNodesRandom )
{
if ( at_begin_node && IsSubStr( nNode.type, "End" ) )
{
// Don't try to go to my node's end node
continue;
}
if ( bot_infect_angle_too_steep_for_knife_throw( nNode.origin, targetEnt.origin ) )
{
// Angle would be too steep from here, too (NOTE: we use origin compares here since that's how we got into this function above)
continue;
}
eyeOfs = self GetEye()-self.origin;
start = nNode.origin + eyeOfs;
end = targetEnt.origin;
if ( IsPlayer( targetEnt ) )
{
end = targetEnt getStanceCenter();
}
if ( SightTracePassed( start, end, false, self, targetEnt ) )
{
yaw = VectorToYaw( end-start );
self BotSetScriptGoalNode( nNode, "critical", yaw );
self bot_waittill_goal_or_fail( 3.0 );
return;
}
wait( 0.05 );
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,319 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
maps\mp\bots\_bots_gametype_sd::setup_callbacks();
setup_callbacks();
maps\mp\bots\_bots_gametype_conf::setup_bot_conf();
maps\mp\bots\_bots_gametype_sd::bot_sd_start();
/#
thread bot_sr_debug();
#/
}
/#
empty_function_to_force_script_dev_compile() {}
#/
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_sr_think;
}
/#
bot_sr_debug()
{
while(1)
{
if ( GetDvarInt("bot_DrawDebugGametype") == 1 )
{
foreach( tag in level.dogtags )
{
if ( tag maps\mp\gametypes\_gameobjects::canInteractWith("allies") || tag maps\mp\gametypes\_gameobjects::canInteractWith("axis") )
{
if ( IsDefined(tag.bot_picking_up) )
{
if ( IsDefined(tag.bot_picking_up["allies"]) && IsAlive(tag.bot_picking_up["allies"]) )
line( tag.curorigin, tag.bot_picking_up["allies"].origin + (0,0,20), (0,1,0), 1.0, true );
if ( IsDefined(tag.bot_picking_up["axis"]) && IsAlive(tag.bot_picking_up["axis"]) )
line( tag.curorigin, tag.bot_picking_up["axis"].origin + (0,0,20), (0,1,0), 1.0, true );
}
if ( IsDefined(tag.bot_camping) )
{
if ( IsDefined(tag.bot_camping["allies"]) && IsAlive(tag.bot_camping["allies"]) )
line( tag.curorigin, tag.bot_camping["allies"].origin + (0,0,20), (0,1,0), 1.0, true );
if ( IsDefined(tag.bot_camping["axis"]) && IsAlive(tag.bot_camping["axis"]) )
line( tag.curorigin, tag.bot_camping["axis"].origin + (0,0,20), (0,1,0), 1.0, true );
}
}
}
}
wait(0.05);
}
}
#/
bot_sr_think()
{
self notify( "bot_sr_think" );
self endon( "bot_sr_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
self.suspend_sd_role = undefined;
self childthread tag_watcher();
maps\mp\bots\_bots_gametype_sd::bot_sd_think();
}
tag_watcher()
{
while(1)
{
wait(0.05);
if ( self.health <= 0 )
continue;
if ( !IsDefined(self.role) )
continue;
visible_tags = maps\mp\bots\_bots_gametype_conf::bot_find_visible_tags( false );
if ( visible_tags.size > 0 )
{
tag_chosen = Random(visible_tags);
if ( DistanceSquared( self.origin, tag_chosen.tag.curorigin ) < 100 * 100 )
{
// If i'm really close to the tag, just grab it regardless
self sr_pick_up_tag( tag_chosen.tag );
}
else if ( self.team == game["attackers"] )
{
if ( self.role != "atk_bomber" )
{
self sr_pick_up_tag( tag_chosen.tag );
}
}
else
{
if ( self.role != "bomb_defuser" )
{
self sr_pick_up_tag( tag_chosen.tag );
}
}
}
}
}
sr_pick_up_tag( tag )
{
if ( IsDefined(tag.bot_picking_up) && IsDefined(tag.bot_picking_up[self.team]) && IsAlive(tag.bot_picking_up[self.team]) && tag.bot_picking_up[self.team] != self )
return;
if ( self sr_ally_near_tag( tag ) )
return; // No picking up this tag if an ally is a lot closer
if ( !IsDefined(self.role) )
return; // No picking up tags if we don't yet have a role
if ( self bot_is_defending() )
self bot_defend_stop();
tag.bot_picking_up[self.team] = self;
tag thread clear_bot_on_reset();
tag thread clear_bot_on_bot_death( self );
self.suspend_sd_role = true;
self childthread notify_when_tag_picked_up_or_unavailable(tag, "tag_picked_up");
tag_goal = tag.curorigin;
self BotSetScriptGoal( tag_goal, 0, "tactical" );
self childthread watch_tag_destination( tag );
result = self bot_waittill_goal_or_fail( undefined, "tag_picked_up", "new_role" );
self notify("stop_watch_tag_destination");
if ( result == "no_path" )
{
// If the path failed, then try again with a slight offset
tag_goal = tag_goal + (16*rand_pos_or_neg(), 16*rand_pos_or_neg(), 0);
self BotSetScriptGoal( tag_goal, 0, "tactical" );
result = self bot_waittill_goal_or_fail( undefined, "tag_picked_up", "new_role" );
if ( result == "no_path" )
{
// If the path still failed, then as a last ditch attempt try to find a navigable point
tag_goal = bot_queued_process( "BotGetClosestNavigablePoint", ::func_bot_get_closest_navigable_point, tag.curorigin, 32, self );
if ( IsDefined(tag_goal) )
{
self BotSetScriptGoal( tag_goal, 0, "tactical" );
result = self bot_waittill_goal_or_fail( undefined, "tag_picked_up", "new_role" );
}
}
}
else if ( result == "bad_path" )
{
// If "bad_path", possibly the goal couldn't be reached because the tag was a bit too high in the air. So try half the distance down
nodes = GetNodesInRadiusSorted( tag.curorigin, 256, 0, level.bot_tag_allowable_jump_height + 55 );
if ( nodes.size > 0 )
{
new_goal = (tag.curorigin[0], tag.curorigin[1], (nodes[0].origin[2] + tag.curorigin[2]) * 0.5);
self BotSetScriptGoal( new_goal, 0, "tactical" );
result = self bot_waittill_goal_or_fail( undefined, "tag_picked_up", "new_role" );
}
}
if ( result == "goal" && tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) )
{
wait(3.0); // Give time to jump for the tag if necessary
}
if ( self BotHasScriptGoal() && IsDefined(tag_goal) )
{
script_goal = self BotGetScriptGoal();
if ( bot_vectors_are_equal( script_goal, tag_goal ) )
self BotClearScriptGoal();
}
self notify("stop_tag_watcher");
tag.bot_picking_up[self.team] = undefined;
self.suspend_sd_role = undefined;
}
watch_tag_destination( tag )
{
self endon("stop_watch_tag_destination");
while(1)
{
Assert( self BotHasScriptGoal() ); // Ensure the bot didn't somehow lose his goal
if ( !tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) )
{
wait(0.05); // Wait a frame for the notify to trigger before asserting
Assert( tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) ); // Ensure the bot's goal tag is still valid
}
goal = self BotGetScriptGoal();
Assert(bot_vectors_are_equal(goal,tag.curorigin)); // Ensure the bot is still pursing the tag's origin and the tag hasn't been recycled or anything
wait(0.05);
}
}
sr_ally_near_tag( tag )
{
my_dist_to_tag = Distance(self.origin,tag.curorigin);
allies = maps\mp\bots\_bots_gametype_sd::get_living_players_on_team( self.team, true );
foreach( ally in allies )
{
if ( ally != self && IsDefined(ally.role) && ally.role != "atk_bomber" && ally.role != "bomb_defuser" )
{
ally_dist = Distance(ally.origin,tag.curorigin);
if ( ally_dist < my_dist_to_tag * 0.5 )
return true;
}
}
return false;
}
rand_pos_or_neg()
{
return (RandomIntRange(0,2)*2)-1;
}
clear_bot_on_reset()
{
self waittill("reset");
self.bot_picking_up = [];
}
clear_bot_on_bot_death( bot )
{
self endon("reset");
botTeam = bot.team;
bot waittill_any("death","disconnect");
self.bot_picking_up[botTeam] = undefined;
}
notify_when_tag_picked_up_or_unavailable( tag, tag_notify )
{
self endon("stop_tag_watcher");
while( tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) && !self maps\mp\bots\_bots_gametype_conf::bot_check_tag_above_head( tag ) )
{
wait(0.05);
}
self notify(tag_notify);
}
sr_camp_tag( tag )
{
if ( IsDefined(tag.bot_camping) && IsDefined(tag.bot_camping[self.team]) && IsAlive(tag.bot_camping[self.team]) && tag.bot_camping[self.team] != self )
return;
if ( !IsDefined(self.role) )
return; // No picking up tags if we don't yet have a role
if ( self bot_is_defending() )
self bot_defend_stop();
tag.bot_camping[self.team] = self;
tag thread clear_bot_camping_on_reset();
tag thread clear_bot_camping_on_bot_death( self );
self.suspend_sd_role = true;
self clear_camper_data();
role_started = self.role;
while( tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) && self.role == role_started )
{
Assert(!self bot_is_defending());
if ( self should_select_new_ambush_point() )
{
if ( find_ambush_node( tag.curorigin, 1000 ) )
{
self childthread maps\mp\bots\_bots_gametype_conf::bot_camp_tag(tag, "tactical", "new_role");
}
}
wait(0.05);
}
self notify("stop_camping_tag");
self BotClearScriptGoal();
tag.bot_camping[self.team] = undefined;
self.suspend_sd_role = undefined;
}
clear_bot_camping_on_reset()
{
self waittill("reset");
self.bot_camping = [];
}
clear_bot_camping_on_bot_death( bot )
{
self endon("reset");
botTeam = bot.team;
bot waittill_any("death", "disconnect");
self.bot_camping[botTeam] = undefined;
}

View File

@ -0,0 +1,231 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
level.bot_ignore_precalc_paths = false;
if ( level.currentgen )
level.bot_ignore_precalc_paths = true;
setup_callbacks();
setup_bot_twar();
/#
thread bot_twar_debug();
#/
}
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_twar_think;
level.bot_funcs["should_start_cautious_approach"] = ::bot_twar_should_start_cautious_approach;
if ( !level.bot_ignore_precalc_paths )
level.bot_funcs["get_watch_node_chance"] = ::bot_twar_get_node_chance;
}
setup_bot_twar()
{
bot_waittill_bots_enabled(true);
for( i = 0; i < level.twar_zones.size; i++ )
level.twar_zones[i].script_label = "_" + i;
bot_cache_entrances_to_gametype_array( level.twar_zones, "zone", level.bot_ignore_precalc_paths );
zone_z_offset_down = 55;
fail_num = 0;
foreach ( zone in level.twar_zones )
{
if ( !IsDefined(zone.nearest_node) )
return; // Zone didn't have a nearest node, so don't even attempt to play
zone thread monitor_zone_control();
zone_real_middle = ((zone.origin - (0,0,zone_z_offset_down)) + (zone.origin + (0,0,level.zone_height))) / 2.0;
zone_real_half_height = (level.zone_height + zone_z_offset_down) / 2.0;
zone.nodes = GetNodesInRadius( zone_real_middle, level.zone_radius, 0, zone_real_half_height );
if ( zone.nodes.size < 6 )
{
fail_num++;
if ( fail_num == 1 )
wait(5); // Wait till level is loaded to display first error message
else
wait(1);
assertmsg( "Momentum zone in level '" + level.script + "' at location " + zone.origin + " needs at least 6 nodes in its radius" );
}
}
level.bot_gametype_precaching_done = true;
}
monitor_zone_control()
{
self notify( "monitor_zone_control" );
self endon( "monitor_zone_control" );
self endon( "death" );
level endon( "game_ended" );
for(;;)
{
wait(1.0);
team = self.owner;
if ( team == "none" && level.twar_use_obj.useRate > 0 )
{
// This is the active zone
Assert( level.twar_use_obj.zone == self );
// lastclaimteam is who currently has the MOST progress (i.e. color of the bar), but claimteam is the team that is currently moving the bar
team = level.twar_use_obj.claimteam;
}
if ( team != "none" )
{
// Unactive owned zone
zone = GetZoneNearest( self.origin );
if ( IsDefined( zone ) )
BotZoneSetTeam( zone, team );
}
}
}
/#
bot_twar_debug()
{
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
while(1)
{
if ( GetDvarInt("bot_DrawDebugGametype") == 1 )
{
foreach( zone in level.twar_zones )
{
bot_draw_cylinder(zone.origin, level.zone_radius, level.zone_height, 0.05, undefined, (0,1,0), true);
// Draw nodes
foreach( node in zone.nodes )
bot_draw_cylinder(node.origin, 10, 10, 0.05, undefined, (0,1,0), true, 4);
}
}
wait(0.05);
}
}
#/
bot_twar_think()
{
self notify( "bot_twar_think" );
self endon( "bot_twar_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self endon( "owner_disconnect" );
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
self BotSetFlag("separation",0); // don't slow down when we get close to other bots
self BotSetPathingStyle("beeline"); // Always beeline towards goals
self BotSetFlag("force_sprint",1); // Sprint as often as possible
while ( true )
{
if ( !bot_twar_is_capturing_zone( level.twar_use_obj.zone ) )
bot_twar_capture_zone( level.twar_use_obj.zone );
wait(0.05);
}
}
bot_twar_capture_zone( zone )
{
self.current_zone = zone;
optional_params["entrance_points_index"] = bot_twar_get_zone_label(zone);
optional_params["nearest_node_to_center"] = zone.nearest_node;
optional_params["objective_radius"] = 500;
self bot_capture_point( zone.origin, level.zone_radius, optional_params );
}
bot_twar_is_capturing_zone( zone )
{
if ( self bot_is_capturing() )
{
if ( self.current_zone == zone )
return true;
}
return false;
}
bot_twar_get_node_chance( node )
{
node_on_safe_path = false;
self_current_zone_label = bot_twar_get_zone_label(level.twar_use_obj.zone);
ally_zones = bot_twar_get_zones_for_team( self.team );
node_on_safe_path = false;
foreach( ally_zone in ally_zones )
{
Assert( ally_zone != level.twar_use_obj.zone );
// node is safe if it is on the path to an ally zone
if ( node node_is_on_path_from_labels(self_current_zone_label, bot_twar_get_zone_label(ally_zone)) )
{
node_on_safe_path = true;
break;
}
}
if ( node_on_safe_path )
{
// now need to make sure this node isn't also on the path to an enemy zone
enemy_zones = bot_twar_get_zones_for_team( get_enemy_team(self.team) );
foreach( enemy_zone in enemy_zones )
{
Assert( enemy_zone != level.twar_use_obj.zone );
// node is not safe if it is on the path to an enemy zone
if ( node node_is_on_path_from_labels(self_current_zone_label, bot_twar_get_zone_label(enemy_zone)) )
{
node_on_safe_path = false;
break;
}
}
}
if ( node_on_safe_path )
return 0.2;
return 1.0;
}
bot_twar_get_zones_for_team(team)
{
zones = [];
foreach( zone in level.twar_zones )
{
if ( zone.owner == team )
zones[zones.size] = zone;
}
return zones;
}
bot_twar_get_zone_label(zone)
{
return "zone" + zone.script_label;
}
bot_twar_should_start_cautious_approach( firstCheck )
{
return false;
}

View File

@ -0,0 +1,4 @@
main()
{
// nothing for now...
}

View File

@ -0,0 +1,41 @@
#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;
#include maps\mp\bots\_bots_gametype_common;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_war();
}
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_war_think;
}
setup_bot_war()
{
}
bot_war_think()
{
self notify( "bot_war_think" );
self endon( "bot_war_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self endon( "owner_disconnect" );
// This game mode just performs normal personality logic
while ( true )
{
self [[ self.personality_update_function ]]();
wait(0.05);
}
}

View File

@ -0,0 +1,874 @@
#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\bots\_bots;
#include maps\mp\bots\_bots_util;
#include maps\mp\bots\_bots_ks_remote_vehicle;
#include maps\mp\bots\_bots_sentry;
//========================================================
// bot_killstreak_setup
//========================================================
bot_killstreak_setup()
{
time_started = GetTime();
if ( !IsDefined( level.killstreak_botfunc ) )
{
thread bot_setup_map_specific_killstreaks();
// *******************************
// * BLACKSMITH KILLSTREAKS *
// *******************************
// Simple Use ===========================================================================
bot_register_killstreak_func( "uav", ::bot_killstreak_simple_use );
bot_register_killstreak_func( "orbital_carepackage", ::bot_killstreak_simple_use );
bot_register_killstreak_func( "heavy_exosuit", ::bot_killstreak_simple_use );
bot_register_killstreak_func( "nuke", ::bot_killstreak_simple_use );
// Simple Use with test =================================================================
bot_register_killstreak_func( "emp", ::bot_killstreak_simple_use, ::bot_can_use_emp );
bot_register_killstreak_func( "remote_mg_sentry_turret", ::bot_killstreak_sentry, ::bot_can_use_sentry_only_ai_version, "turret" );
bot_register_killstreak_func( "assault_ugv", ::bot_killstreak_simple_use, ::bot_can_use_assault_ugv_only_ai_version );
bot_register_killstreak_func( "warbird", ::bot_killstreak_simple_use, ::bot_can_use_warbird_only_ai_version );
// Aerial Targeting =====================================================================
bot_register_killstreak_func( "strafing_run_airstrike", ::bot_killstreak_choose_loc_enemies, ::bot_can_use_strafing_run );
// Not hooked up yet ====================================================================
bot_register_killstreak_func( "orbitalsupport", ::bot_killstreak_never_use, ::bot_killstreak_do_not_use );
bot_register_killstreak_func( "recon_ugv", ::bot_killstreak_never_use, ::bot_killstreak_do_not_use );
bot_register_killstreak_func( "orbital_strike_laser", ::bot_killstreak_never_use, ::bot_killstreak_do_not_use );
bot_register_killstreak_func( "missile_strike", ::bot_killstreak_never_use, ::bot_killstreak_do_not_use );
/#
thread bot_validate_killstreak_funcs();
#/
}
thread remote_vehicle_setup();
Assert(time_started == GetTime());
}
bot_setup_map_specific_killstreaks()
{
wait(0.5); // Give time for the level.mapCustomBotKillstreakFunc to be defined
if(IsDefined(level.mapCustomBotKillstreakFunc))
[[ level.mapCustomBotKillstreakFunc ]]();
else if ( IsDefined(level.mapKillStreak) )
bot_register_killstreak_func( level.mapKillStreak, maps\mp\bots\_bots_ks::bot_killstreak_never_use, maps\mp\bots\_bots_ks::bot_killstreak_do_not_use );
}
//========================================================
// bot_register_killstreak_func
//========================================================
bot_register_killstreak_func( name, func, can_use, optionalParam )
{
if ( !IsDefined( level.killstreak_botfunc ) )
level.killstreak_botfunc = [];
level.killstreak_botfunc[name] = func;
if ( !IsDefined( level.killstreak_botcanuse ) )
level.killstreak_botcanuse = [];
level.killstreak_botcanuse[name] = can_use;
if ( !IsDefined( level.killstreak_botparm ) )
level.killstreak_botparm = [];
level.killstreak_botparm[name] = optionalParam;
if ( !IsDefined( level.bot_supported_killstreaks ) )
level.bot_supported_killstreaks = [];
level.bot_supported_killstreaks[level.bot_supported_killstreaks.size] = name;
}
/#
assert_streak_valid_for_bots_in_general(streak)
{
if ( !bot_killstreak_valid_for_bots_in_general(streak) )
{
AssertMsg( "Bots do not support killstreak <" + streak + ">" );
}
}
assert_streak_valid_for_specific_bot(streak, bot)
{
if ( !bot_killstreak_valid_for_specific_bot(streak, bot) )
{
AssertMsg( "Bot <" + bot.name + "> does not support killstreak <" + streak + ">" );
}
}
//========================================================
// bot_validate_killstreak_funcs
//========================================================
bot_validate_killstreak_funcs()
{
// Give a second for other killstreaks to be included before testing
// Needed because for example, init() in _dog_killstreak.gsc is called AFTER this function
wait(1);
errors = [];
foreach( streakName in level.bot_supported_killstreaks )
{
if ( !bot_killstreak_valid_for_humans(streakName) )
{
error( "bot_validate_killstreak_funcs() invalid killstreak: " + streakName );
errors[errors.size] = streakName;
}
}
if ( errors.size )
{
temp = level.killstreakFuncs;
level.killStreakFuncs = [];
foreach( streakName in temp )
{
if ( !array_contains( errors, streakName ) )
level.killStreakFuncs[streakName] = temp[streakName];
}
}
}
bot_killstreak_valid_for_humans(streakName)
{
// Checks if the specified killstreak is valid for human players (i.e. set up correctly, human players can use it, etc)
return bot_killstreak_is_valid_internal(streakName, "humans");
}
bot_killstreak_valid_for_bots_in_general(streakName)
{
// Checks if the specified killstreak is valid for bot players. This means it is set up correctly for humans, AND bots also know how to use it
return bot_killstreak_is_valid_internal(streakName, "bots");
}
bot_killstreak_valid_for_specific_bot(streakName, bot)
{
// Checks if the specified killstreak is valid for bot players. This means it is set up correctly for humans, AND bots also know how to use it,
// and this specific bot can use it
return bot_killstreak_is_valid_internal(streakName, "bots", bot);
}
#/
bot_is_valid_killstreak(streakName, assertIt)
{
// Checks if the specified killstreak is valid for bot players. This means it is set up correctly for humans, AND bots also know how to use it.
if ( bot_killstreak_is_valid_internal(streakName, "bots", undefined) )
{
return true;
}
else if ( assertIt )
{
AssertMsg( "Bots do not support killstreak <" + streakName + ">" );
}
return false;
}
bot_killstreak_is_valid_internal(streakName, who_to_check, optional_bot )
{
if ( !bot_killstreak_is_valid_single(streakName, who_to_check) )
{
// Either bots or human players don't have a function handle this killstreak
return false;
}
return true;
}
bot_killstreak_is_valid_single(streakName, who_to_check)
{
if ( who_to_check == "humans" )
{
return ( IsDefined( level.killstreakFuncs[streakName] ) && getKillstreakIndex( streakName ) != -1 );
}
else if ( who_to_check == "bots" )
{
return IsDefined( level.killstreak_botfunc[streakName] );
}
AssertMsg("Unreachable");
}
//========================================================
// bot_think_killstreak
//========================================================
bot_think_killstreak()
{
self notify( "bot_think_killstreak" );
self endon( "bot_think_killstreak" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
while( !IsDefined(level.killstreak_botfunc) )
wait(0.05);
//self childthread bot_start_aa_launcher_tracking();
for(;;)
{
if ( self bot_allowed_to_use_killstreaks() )
{
killstreaks_array = self.pers["killstreaks"];
if ( IsDefined( killstreaks_array ) )
{
restart_loop = false;
for ( ksi = 0; ksi < killstreaks_array.size && !restart_loop; ksi++ )
{
killstreak_info = killstreaks_array[ksi];
if ( IsDefined( killstreak_info.streakName ) && IsDefined( self.bot_killstreak_wait ) && IsDefined( self.bot_killstreak_wait[killstreak_info.streakName] ) && GetTime() < self.bot_killstreak_wait[killstreak_info.streakName] )
continue;
if ( killstreak_info.available )
{
tableName = killstreak_info.streakName;
killstreak_info.weapon = getKillstreakWeapon( killstreak_info.streakName, killstreak_info.modules );
can_use_killstreak_function = level.killstreak_botcanuse[ tableName ];
if ( IsDefined(can_use_killstreak_function) && !self [[ can_use_killstreak_function ]](killstreak_info) )
continue;
if ( !self validateUseStreak( killstreak_info.streakName, true ) )
continue;
bot_killstreak_func = level.killstreak_botfunc[ tableName ];
if ( IsDefined( bot_killstreak_func ) )
{
// Call the function (do NOT thread it)
// This way its easy to manage one killstreak working at a time
// and all these functions will end when any of the above endon conditions are met
result = self [[bot_killstreak_func]]( killstreak_info, killstreaks_array, can_use_killstreak_function, level.killstreak_botparm[ killstreak_info.streakName ] );
if ( !IsDefined( result ) || result == false )
{
// killstreak cannot be used yet, stop trying for a bit and come back to it later
if ( !isdefined( self.bot_killstreak_wait ) )
self.bot_killstreak_wait = [];
self.bot_killstreak_wait[killstreak_info.streakName] = GetTime() + 5000;
}
}
else
{
// Bots dont know how to use this killstreak, just get rid of it so we can use something else on the stack
AssertMsg("Bots do not know how to use killstreak <" + killstreak_info.streakName + ">");
killstreak_info.available = false;
self maps\mp\killstreaks\_killstreaks::updateKillstreaks( false );
}
restart_loop = true;
}
}
}
}
wait( RandomFloatRange( 2.0, 4.0 ) );
}
}
bot_killstreak_never_use()
{
// This function needs to exist because every killstreak for bots needs a function, but it should never be called
AssertMsg( "bot_killstreak_never_use() was somehow called" );
}
bot_killstreak_do_not_use( killstreak_info )
{
return false;
}
bot_killstreak_can_use_weapon_version( killstreak_info )
{
// For now bots can use fire-and-forget killstreaks, not controllable killstreaks ("killstreak_predator_missile_mp")
return ( killstreak_info.weapon == "killstreak_uav_mp" );
}
bot_can_use_warbird_only_ai_version( killstreak_info )
{
if ( !bot_killstreak_can_use_weapon_version( killstreak_info ) )
return false;
if ( !bot_can_use_warbird( killstreak_info ) )
return false;
return true;
}
bot_can_use_warbird( killstreak_info )
{
if ( !self maps\mp\killstreaks\_warbird::CanUseWarbird() )
return false;
if ( vehicle_would_exceed_limit() )
return false;
return true;
}
bot_can_use_assault_ugv_only_ai_version( killstreak_info )
{
if ( !bot_killstreak_can_use_weapon_version( killstreak_info ) )
return false;
if ( !bot_can_use_assault_ugv( killstreak_info ) )
return false;
return true;
}
bot_can_use_assault_ugv( killstreak_info )
{
if ( vehicle_would_exceed_limit() )
return false;
return true;
}
vehicle_would_exceed_limit()
{
return ( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + 1 >= maxVehiclesAllowed() );
}
bot_can_use_emp( killstreak_info )
{
// is there already an active EMP?
if ( IsDefined( level.empPlayer ) )
return false;
otherTeam = level.otherTeam[self.team];
if ( isdefined( level.teamEMPed ) && IsDefined( level.teamEMPed[ otherTeam ] ) && level.teamEMPed[ otherTeam ] )
return false;
return true;
}
bot_can_use_strafing_run( killstreak_info )
{
if ( IsDefined( level.strafing_run_airstrike ) )
return false;
return true;
}
//========================================================
// bot_killstreak_simple_use
//========================================================
bot_killstreak_simple_use( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
wait( RandomIntRange( 3, 5 ) );
if ( !self bot_allowed_to_use_killstreaks() )
{
// This may have become false during the wait, like an enemy appeared while we were waiting
return true;
}
if ( IsDefined( canUseFunc ) && !self [[canUseFunc]]( killstreak_info ) )
return false;
bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, killstreak_info.weapon );
return true;
}
//========================================================
// bot_killstreak_drop_anywhere
//========================================================
bot_killstreak_drop_anywhere( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
bot_killstreak_drop( killstreak_info, killstreaks_array, canUseFunc, optional_param, "anywhere" );
}
//========================================================
// bot_killstreak_drop_outside
//========================================================
bot_killstreak_drop_outside( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
bot_killstreak_drop( killstreak_info, killstreaks_array, canUseFunc, optional_param, "outside" );
}
//========================================================
// bot_killstreak_drop_hidden
//========================================================
bot_killstreak_drop_hidden( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
bot_killstreak_drop( killstreak_info, killstreaks_array, canUseFunc, optional_param, "hidden" );
}
//========================================================
// bot_killstreak_drop
//========================================================
bot_killstreak_drop( killstreak_info, killstreaks_array, canUseFunc, optional_param, drop_where )
{
wait( RandomIntRange( 2, 4 ) );
if ( !isDefined( drop_where ) )
drop_where = "anywhere";
if ( !self bot_allowed_to_use_killstreaks() )
{
// This may have become false during the wait, like an enemy appeared while we were waiting
return true;
}
if ( IsDefined( canUseFunc ) && !self [[canUseFunc]]( killstreak_info ) )
return false;
ammo = self GetWeaponAmmoClip( killstreak_info.weapon ) + self GetWeaponAmmoStock( killstreak_info.weapon );
if ( ammo == 0 )
{
// Trying to use an airdrop but we don't have any ammo
foreach( streak in killstreaks_array )
{
if ( IsDefined(streak.streakName) && streak.streakName == killstreak_info.streakName )
streak.available = 0;
}
self maps\mp\killstreaks\_killstreaks::updateKillstreaks( false );
return true;
}
node_target = undefined;
if ( drop_where == "outside" )
{
outside_nodes = [];
nodes_in_cone = self bot_get_nodes_in_cone( 0, 750, 0.6, true );
foreach ( node in nodes_in_cone )
{
if ( NodeExposedToSky( node ) )
outside_nodes = array_add( outside_nodes, node );
}
if ( (nodes_in_cone.size > 5) && (outside_nodes.size > nodes_in_cone.size * 0.6) )
{
outside_nodes_sorted = get_array_of_closest( self.origin, outside_nodes, undefined, undefined, undefined, 150 );
if ( outside_nodes_sorted.size > 0 )
node_target = random(outside_nodes_sorted);
else
node_target = random(outside_nodes);
}
}
else if ( drop_where == "hidden" )
{
nodes_in_radius = GetNodesInRadius( self.origin, 256, 0, 40 );
node_nearest_bot = self GetNearestNode();
if ( IsDefined(node_nearest_bot) )
{
visible_nodes_in_radius = [];
foreach( node in nodes_in_radius )
{
if ( NodesVisible( node_nearest_bot, node, true ) )
visible_nodes_in_radius = array_add(visible_nodes_in_radius,node);
}
node_target = self BotNodePick( visible_nodes_in_radius, 1, "node_hide" );
}
}
if ( IsDefined(node_target) || drop_where == "anywhere" )
{
self BotSetFlag( "disable_movement", true );
if ( IsDefined(node_target) )
self BotLookAtPoint( node_target.origin, 1.5+0.95, "script_forced" );
bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, killstreak_info.weapon );
wait(2.0);
self BotPressButton( "attack" );
wait(1.5);
self SwitchToWeapon( "none" ); // clears scripted weapon for bots
self BotSetFlag( "disable_movement", false );
}
return true;
}
bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, weapon_name )
{
self bot_notify_streak_used( killstreak_info, killstreaks_array );
wait(0.05); // Wait for the notify to be received and self.killstreakIndexWeapon to be set
self SwitchToWeapon( weapon_name );
}
bot_notify_streak_used( killstreak_info, killstreaks_array )
{
if ( IsDefined( killstreak_info.isgimme ) && killstreak_info.isgimme )
{
self notify("streakUsed1");
}
else
{
for ( index = 0; index < 3; index++ )
{
if ( IsDefined(killstreaks_array[index].streakName) )
{
if ( killstreaks_array[index].streakName == killstreak_info.streakname )
break;
}
}
self notify("streakUsed" + (index+1));
}
}
//========================================================
// bot_killstreak_choose_loc_enemies
//========================================================
bot_killstreak_choose_loc_enemies( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
wait( RandomIntRange( 3, 5 ) );
if ( !self bot_allowed_to_use_killstreaks() )
{
// This may have become false during the wait, like an enemy appeared while we were waiting
return;
}
zone_nearest_bot = GetZoneNearest( self.origin );
if ( !IsDefined(zone_nearest_bot) )
return;
self BotSetFlag( "disable_movement", true );
bot_switch_to_killstreak_weapon( killstreak_info, killstreaks_array, killstreak_info.weapon );
wait 2;
if ( !IsDefined(self.selectingLocation) )
return;
zone_count = level.zoneCount;
best_zone = -1;
best_zone_count = 0;
possible_fallback_zones = [];
iterate_backwards = RandomFloat(100) > 50; // randomly choose to iterate backwards
for ( z = 0; z < zone_count; z++ )
{
if ( iterate_backwards )
zone = zone_count - 1 - z;
else
zone = z;
if ( (zone != zone_nearest_bot) && (BotZoneGetIndoorPercent( zone ) < 0.25) )
{
// This zone is not the current bot's zone, and it is mostly an outside zone
enemies_in_zone = BotZoneGetCount( zone, self.team, "enemy_predict" );
if ( enemies_in_zone > best_zone_count )
{
best_zone = zone;
best_zone_count = enemies_in_zone;
}
possible_fallback_zones = array_add( possible_fallback_zones, zone );
}
}
if ( best_zone >= 0 )
zoneCenter = GetZoneOrigin( best_zone );
else if ( possible_fallback_zones.size > 0 )
zoneCenter = GetZoneOrigin( random(possible_fallback_zones) );
else
zoneCenter = GetZoneOrigin(RandomInt( level.zoneCount ));
randomOffset = (RandomFloatRange(-500, 500), RandomFloatRange(-500, 500), 0);
keep_trying_placement = true;
while( keep_trying_placement )
{
self notify( "confirm_location", zoneCenter + randomOffset, RandomIntRange(0, 360) );
result = self waittill_any_return( "location_selection_complete", "airstrikeShowBlockedHUD" );
if ( result == "location_selection_complete" )
keep_trying_placement = false;
else
wait(0.5);
}
wait( 1.0 );
self BotSetFlag( "disable_movement", false );
}
SCR_CONST_GLOBAL_BP_TIME_BETWEEN_PLACING_MS = 4000;
SCR_CONST_GLOBAL_BP_DURATION_S = 5.0;
//========================================================
// bot_think_watch_aerial_killstreak
//========================================================
bot_think_watch_aerial_killstreak()
{
self notify( "bot_think_watch_aerial_killstreak" );
self endon( "bot_think_watch_aerial_killstreak" );
self endon( "death" );
self endon( "disconnect" );
level endon ( "game_ended" );
if ( !IsDefined(level.last_global_badplace_time) )
level.last_global_badplace_time = -10000;
if ( !IsDefined(level.killstreak_global_bp_exists_for) )
{
level.killstreak_global_bp_exists_for["allies"] = [];
level.killstreak_global_bp_exists_for["axis"] = [];
}
if ( !IsDefined(level.aerial_danger_exists_for) )
{
level.aerial_danger_exists_for["allies"] = false;
level.aerial_danger_exists_for["axis"] = false;
}
currently_hiding = false;
next_wait_time = RandomFloatRange(0.05,4.0);
while(1)
{
wait(next_wait_time);
next_wait_time = RandomFloatRange(0.05,4.0);
if ( self bot_is_remote_or_linked() )
continue;
if ( self BotGetDifficultySetting("strategyLevel") == 0 )
continue; // dumb bots don't try to avoid aerial danger
needs_to_hide = false;
// Enemy called in Warbird
warbird = get_enemy_warbird( self.team );
if ( IsDefined(warbird) )
{
needs_to_hide = true;
if ( !self bot_is_monitoring_aerial_danger(warbird) )
self childthread monitor_aerial_danger(warbird);
}
// Enemy called in Paladin
if ( enemy_paladin_exists( self.team ) )
{
needs_to_hide = true;
if ( !self bot_is_monitoring_aerial_danger(level.orbitalsupport_planemodel) )
self childthread monitor_aerial_danger(level.orbitalsupport_planemodel);
}
// Enemy called in Orbital Laser
if ( enemy_orbital_laser_exists( self.team ) )
{
self try_place_global_badplace( "orbital_laser", ::enemy_orbital_laser_exists );
needs_to_hide = true;
}
// Enemy is using Missile Strike
if ( enemy_missile_strike_exists( self.team ) )
{
self try_place_global_badplace( "missile_strike", ::enemy_missile_strike_exists );
needs_to_hide = true;
}
// Enemy is using Strafing Run
if ( enemy_strafing_run_exists( self.team ) )
{
self try_place_global_badplace( "missile_strike", ::enemy_strafing_run_exists );
needs_to_hide = true;
}
/*
// Enemy called in Attack Helicopter / Pave Low
if ( IsDefined(level.chopper) && level.chopper.team != self.team )
needs_to_hide = true;
// Enemy controlling Heli Sniper
if ( IsDefined(level.lbSniper) && level.lbSniper.team != self.team )
needs_to_hide = true;
// Enemy controlling Heli Pilot
if ( IsDefined(level.heli_pilot) && IsDefined(level.heli_pilot[get_enemy_team(self.team)]) )
needs_to_hide = true;
if ( enemy_mortar_strike_exists( self.team ) )
{
needs_to_hide = true;
self try_place_global_badplace( "mortar_strike", ::enemy_mortar_strike_exists );
}
// Enemy is using Switchblade Cluster
if ( enemy_switchblade_exists( self.team ) )
{
needs_to_hide = true;
self try_place_global_badplace( "switchblade", ::enemy_switchblade_exists );
}
// Enemy is using Odin Assault
if ( enemy_odin_assault_exists( self.team ) )
{
needs_to_hide = true;
self try_place_global_badplace( "odin_assault", ::enemy_odin_assault_exists );
}
// Enemy is using Vanguard
enemy_vanguard = self get_enemy_vanguard();
if ( IsDefined(enemy_vanguard) )
{
botEye = self GetEye();
if ( within_fov( botEye, self GetPlayerAngles(), enemy_vanguard.attackArrow.origin, self BotGetFovDot() ) )
{
if ( SightTracePassed( botEye, enemy_vanguard.attackArrow.origin, false, self, enemy_vanguard.attackArrow ) )
BadPlace_Cylinder("vanguard_" + enemy_vanguard GetEntityNumber(), next_wait_time + 0.5, enemy_vanguard.attackArrow.origin, 200, 100, self.team );
}
}
*/
if ( !currently_hiding && needs_to_hide )
{
currently_hiding = true;
self BotSetFlag( "hide_indoors", 1 );
}
if ( currently_hiding && !needs_to_hide )
{
currently_hiding = false;
self BotSetFlag( "hide_indoors", 0 );
}
level.aerial_danger_exists_for[self.team] = needs_to_hide;
}
}
bot_is_monitoring_aerial_danger( aerial_ent )
{
if ( !IsDefined(self.aerial_dangers_monitoring) )
return false;
return array_contains( self.aerial_dangers_monitoring, aerial_ent );
}
monitor_aerial_danger( aerial_ent )
{
if ( !IsDefined(self.aerial_dangers_monitoring) )
self.aerial_dangers_monitoring = [];
Assert( !array_contains( self.aerial_dangers_monitoring, aerial_ent ) );
self.aerial_dangers_monitoring[self.aerial_dangers_monitoring.size] = aerial_ent;
checked_dir = VectorNormalize((aerial_ent.origin - self.origin) * (1,1,0));
while( IsAlive(aerial_ent) )
{
new_checked_dir = VectorNormalize((aerial_ent.origin - self.origin) * (1,1,0));
dot = VectorDot(checked_dir,new_checked_dir);
if ( dot <= 0 )
{
checked_dir = new_checked_dir;
self notify("defend_force_node_recalculation");
}
wait(0.05);
}
self.aerial_dangers_monitoring = array_remove( self.aerial_dangers_monitoring, aerial_ent );
}
try_place_global_badplace( killstreak_unique_name, killstreak_exists_func )
{
if ( !IsDefined(level.killstreak_global_bp_exists_for[self.team][killstreak_unique_name]) )
level.killstreak_global_bp_exists_for[self.team][killstreak_unique_name] = false;
if ( !level.killstreak_global_bp_exists_for[self.team][killstreak_unique_name] )
{
level.killstreak_global_bp_exists_for[self.team][killstreak_unique_name] = true;
level thread monitor_enemy_dangerous_killstreak(self.team, killstreak_unique_name, killstreak_exists_func);
}
}
monitor_enemy_dangerous_killstreak( my_team, killstreak_unique_name, killstreak_exists_func )
{
Assert( SCR_CONST_GLOBAL_BP_DURATION_S > (SCR_CONST_GLOBAL_BP_TIME_BETWEEN_PLACING_MS / 1000) );
wait_time = (SCR_CONST_GLOBAL_BP_DURATION_S - (SCR_CONST_GLOBAL_BP_TIME_BETWEEN_PLACING_MS / 1000)) * 0.5;
while( [[killstreak_exists_func]](my_team) )
{
if ( GetTime() > level.last_global_badplace_time + SCR_CONST_GLOBAL_BP_TIME_BETWEEN_PLACING_MS )
{
BadPlace_Global( "", SCR_CONST_GLOBAL_BP_DURATION_S, my_team, "only_sky" );
level.last_global_badplace_time = GetTime();
}
wait(wait_time);
}
level.killstreak_global_bp_exists_for[my_team][killstreak_unique_name] = false;
}
get_enemy_warbird( my_team )
{
if ( IsDefined( level.SpawnedWarbirds ) )
{
foreach ( warbird in level.SpawnedWarbirds )
{
if ( !level.teamBased || (warbird.team != my_team) )
return warbird;
}
}
return undefined;
}
enemy_orbital_laser_exists( my_team )
{
if ( IsDefined( level.orbital_lasers ) )
{
foreach ( laser in level.orbital_lasers )
{
if ( !level.teamBased || (laser.team != my_team) )
return true;
}
}
return false;
}
enemy_paladin_exists( my_team )
{
if ( level.orbitalsupportInUse )
{
if ( IsDefined( level.orbitalsupport_planemodel ) && IsDefined( level.orbitalsupport_planemodel.owner ) )
{
if ( !level.teamBased || (level.orbitalsupport_planemodel.owner.team != my_team) )
return true;
}
}
return false;
}
enemy_missile_strike_exists( my_team )
{
if ( IsDefined(level.remoteMissileInProgress) )
{
foreach ( rocket in level.rockets )
{
if ( rocket.type == "remote" && rocket.team != my_team )
return true;
}
}
return false;
}
enemy_strafing_run_exists( my_team )
{
if ( IsDefined(level.artilleryDangerCenters) )
{
foreach ( artilleryDangerCenter in level.artilleryDangerCenters )
{
if ( isStrStart( artilleryDangerCenter.streakname, "strafing_run_airstrike" ) && artilleryDangerCenter.team != my_team )
return true;
}
}
return false;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,974 @@
// Personality functions for bots
#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\bots\_bots_util;
#include maps\mp\bots\_bots_loadout;
#include maps\mp\bots\_bots_strategy;
//=======================================================
// setup_personalities
//=======================================================
setup_personalities()
{
level.bot_personality = [];
level.bot_personality_list = [];
level.bot_personality["active"][0] = "default";
level.bot_personality["active"][1] = "run_and_gun";
level.bot_personality["active"][2] = "cqb";
level.bot_personality["stationary"][0] = "camper";
level.bot_personality_type = [];
foreach( index, personality_array in level.bot_personality )
{
foreach( personality in personality_array )
{
level.bot_personality_type[personality] = index;
level.bot_personality_list[level.bot_personality_list.size] = personality;
}
}
// Desired ratio of personalities
// Currently we would like 4 active bots (run and gun, cqb, etc) for every 1 camper-type bot
level.bot_personality_types_desired = [];
level.bot_personality_types_desired["active"] = 4;
level.bot_personality_types_desired["stationary"] = 1;
level.bot_pers_init = [];
level.bot_pers_init["default"] = ::init_personality_default;
level.bot_pers_init["camper"] = ::init_personality_camper;
level.bot_pers_update["default"] = ::update_personality_default;
level.bot_pers_update["camper"] = ::update_personality_camper;
}
//=======================================================
// assign_personality_functions
//=======================================================
bot_assign_personality_functions()
{
self.personality = self BotGetPersonality();
self.pers["personality"] = self.personality;
self.personality_init_function = level.bot_pers_init[self.personality];
if ( !IsDefined(self.personality_init_function) )
{
self.personality_init_function = level.bot_pers_init["default"];
}
// Call the init function now
self [[ self.personality_init_function ]]();
self.personality_update_function = level.bot_pers_update[self.personality];
if ( !IsDefined(self.personality_update_function) )
{
self.personality_update_function = level.bot_pers_update["default"];
}
}
//=======================================================
// bot_balance_personality
//=======================================================
bot_balance_personality()
{
if( IsDefined(self.personalityManuallySet) && self.personalityManuallySet )
{
return;
}
if ( IsDefined(self.pers["personality"]) )
{
self BotSetPersonality( self.pers["personality"] ); // Already set my personality in a previous round of this game, so keep that
return;
}
my_team = self.team;
if ( !IsDefined(my_team) && !IsDefined(self.bot_team) )
my_team = self.pers["team"]; // Not yet spawned, but we know which team he will choose when he does spawn (for second rounds of dom, ctf, etc)
Assert(level.bot_personality.size == level.bot_personality_types_desired.size);
persCounts = [];
persCountsByType = [];
foreach( personality_type, personality_array in level.bot_personality )
{
persCountsByType[personality_type] = 0;
foreach( personality in personality_array )
persCounts[personality] = 0;
}
// Count up the number of personalities on my team (not including myself), and also the types
foreach( bot in level.participants )
{
if ( IsTeamParticipant(bot) && IsDefined(bot.team) && (bot.team == my_team) && (bot != self) && IsDefined(bot.has_balanced_personality) )
{
personality = bot BotGetPersonality();
personality_type = level.bot_personality_type[personality];
persCounts[personality] = persCounts[personality] + 1;
persCountsByType[personality_type] = persCountsByType[personality_type] + 1;
}
}
// Determine which personality type is needed by looking at level.bot_personality_types_desired, which is the desired ratio of personalities
type_needed = undefined;
while( !IsDefined(type_needed) )
{
personality_types_desired_in_progress = level.bot_personality_types_desired;
while ( personality_types_desired_in_progress.size > 0 )
{
pers_type_picked = bot_get_string_index_for_integer( personality_types_desired_in_progress, RandomInt(personality_types_desired_in_progress.size) );
persCountsByType[pers_type_picked] -= level.bot_personality_types_desired[pers_type_picked];
if ( persCountsByType[pers_type_picked] < 0 )
{
type_needed = pers_type_picked;
break;
}
personality_types_desired_in_progress[pers_type_picked] = undefined;
}
}
// Now that we know which personality type is needed, figure out which personality is needed within that type
personality_needed = undefined;
least_common_personality = undefined;
least_common_personality_uses = 9999;
most_common_personality = undefined;
most_common_personality_uses = -9999;
randomized_personalities_needed = array_randomize(level.bot_personality[type_needed]);
foreach ( personality in randomized_personalities_needed )
{
if ( persCounts[personality] < least_common_personality_uses )
{
least_common_personality = personality;
least_common_personality_uses = persCounts[personality];
}
if ( persCounts[personality] > most_common_personality_uses )
{
most_common_personality = personality;
most_common_personality_uses = persCounts[personality];
}
}
if ( most_common_personality_uses - least_common_personality_uses >= 2 )
personality_needed = least_common_personality;
else
personality_needed = Random(level.bot_personality[type_needed]);
if ( self BotGetPersonality() != personality_needed )
self BotSetPersonality( personality_needed );
self.has_balanced_personality = true;
}
//=======================================================
// init_camper_data
//=======================================================
init_personality_camper()
{
clear_camper_data();
}
//=======================================================
// init_personality_default
//=======================================================
init_personality_default()
{
// clear camper data here too in case bot was changing personality from camper
clear_camper_data();
}
SCR_CONST_CAMPER_HUNT_TIME = 10000;
//=======================================================
// update_personality_camper
//=======================================================
update_personality_camper()
{
if ( should_select_new_ambush_point() && !self bot_is_defending() && !self bot_is_remote_or_linked() )
{
// If we took a detour to "hunt" then wait till that goal gets cleared before camping again
goalType = self BotGetScriptGoalType();
foundCampNode = false;
if ( !IsDefined(self.camper_time_started_hunting) )
self.camper_time_started_hunting = 0;
is_hunting = (goalType == "hunt");
time_to_stop_hunting = GetTime() > self.camper_time_started_hunting + SCR_CONST_CAMPER_HUNT_TIME;
if ( ( !is_hunting || time_to_stop_hunting ) && !self bot_out_of_ammo() ) // Don't look for a camp node if we're out of ammo
{
if ( !(self BotHasScriptGoal()) )
{
// If we dont currently have a script goal act like a run and gunner while we wait for a camping goal
self bot_random_path();
}
foundCampNode = self find_camp_node();
if ( !foundCampNode )
self.camper_time_started_hunting = GetTime();
}
if ( IsDefined( foundCampNode ) && foundCampNode )
{
self.ambush_entrances = self bot_queued_process( "bot_find_ambush_entrances", ::bot_find_ambush_entrances, self.node_ambushing_from, true );
// If applicable, plant a trap to cover our rear first
trap_item = self bot_get_ambush_trap_item( "trap_directional", "trap", "c4" );
if ( IsDefined( trap_item ) )
{
trapTime = GetTime();
self bot_set_ambush_trap( trap_item, self.ambush_entrances, self.node_ambushing_from, self.ambush_yaw );
trapTime = GetTime() - trapTime;
if ( trapTime > 0 && IsDefined( self.ambush_end ) && IsDefined( self.node_ambushing_from ) )
{
self.ambush_end += trapTime;
self.node_ambushing_from.bot_ambush_end = self.ambush_end + 10000;
}
}
if ( !(self bot_has_tactical_goal()) && !self bot_is_defending() && IsDefined(self.node_ambushing_from) )
{
// Go to the ambush point
self BotSetScriptGoalNode( self.node_ambushing_from, "camp", self.ambush_yaw );
self thread clear_script_goal_on( "bad_path", "node_relinquished", "out_of_ammo" );
self thread watch_out_of_ammo();
// When we get there add in the travel time to our camping timer
self thread bot_add_ambush_time_delayed( "clear_camper_data", "goal" );
// When we get there look toward each of the entrances periodically
self thread bot_watch_entrances_delayed( "clear_camper_data", "bot_add_ambush_time_delayed", self.ambush_entrances, self.ambush_yaw );
self childthread bot_try_trap_follower( "clear_camper_data", "goal" );
}
}
else
{
// Hunt until we can find a reasonable camping spot to use
if ( goalType == "camp" )
self BotClearScriptGoal();
update_personality_default();
}
}
}
//=======================================================
// update_personality_default
//=======================================================
update_personality_default()
{
script_goal = undefined;
has_script_goal = self BotHasScriptGoal();
if ( has_script_goal )
script_goal = self BotGetScriptGoal();
if ( GetTime() - self.lastSpawnTime > 5000 )
self bot_try_trap_follower();
if ( !(self bot_has_tactical_goal()) && !self bot_is_remote_or_linked() )
{
distSq = undefined;
goalRadius = undefined;
if ( has_script_goal )
{
distSq = DistanceSquared(self.origin,script_goal);
goalRadius = self BotGetScriptGoalRadius();
goalRadiusDbl = goalRadius * 2;
if ( IsDefined( self.bot_memory_goal ) && distSq < goalRadiusDbl*goalRadiusDbl )
{
flagInvestigated = BotMemoryFlags( "investigated" );
BotFlagMemoryEvents( 0, GetTime() - self.bot_memory_goal_time, 1, self.bot_memory_goal, goalRadiusDbl, "kill", flagInvestigated, self );
BotFlagMemoryEvents( 0, GetTime() - self.bot_memory_goal_time, 1, self.bot_memory_goal, goalRadiusDbl, "death", flagInvestigated, self );
self.bot_memory_goal = undefined;
self.bot_memory_goal_time = undefined;
}
}
if ( !has_script_goal || (distSq < goalRadius * goalRadius) )
{
set_random_path = self bot_random_path();
// Sometimes plant a trap if we have applicable gear
if ( set_random_path && (RandomFloat( 100 ) < 25) )
{
trap_item = self bot_get_ambush_trap_item( "trap_directional", "trap" );
if ( IsDefined( trap_item ) )
{
ambush_point = self BotGetScriptGoal();
if ( IsDefined( ambush_point ) )
{
ambush_node = GetClosestNodeInSight( ambush_point );
if ( IsDefined( ambush_node ) )
{
ambush_entrances = self bot_find_ambush_entrances( ambush_node, false );
set_trap = self bot_set_ambush_trap( trap_item, ambush_entrances, ambush_node );
if( !IsDefined( set_trap ) || set_trap )
{
self BotClearScriptGoal();
set_random_path = self bot_random_path();
}
}
}
}
}
if ( set_random_path )
{
self thread clear_script_goal_on( "enemy", "bad_path", "goal", "node_relinquished", "search_end" );
}
}
}
}
bot_try_trap_follower( endEvent, waitEvent )
{
self notify( "bot_try_trap_follower" );
self endon( "bot_try_trap_follower" );
self endon( "death" );
self endon( "disconnect" );
if ( IsDefined( endEvent ) )
self endon( endEvent );
self endon( "node_relinquished" );
self endon( "bad_path" );
if ( IsDefined(waitEvent) )
self waittill( waitEvent );
trap_item = self bot_get_ambush_trap_item( "trap_follower" );
if ( IsDefined( trap_item ) && self IsOnGround() )
{
nodes_in_cone = self bot_get_nodes_in_cone( 300, 600, 0.7, true );
if ( nodes_in_cone.size > 0 )
{
self BotPressButton( trap_item["item_action"] );
self waittill_any_timeout( 5, "grenade_fire", "missile_fire" );
}
}
}
//=======================================================
// clear_script_goal_on
//=======================================================
clear_script_goal_on( event1, event2, event3, event4, event5 )
{
self notify("clear_script_goal_on");
self endon ("clear_script_goal_on");
self endon( "death" );
self endon( "disconnect" );
self endon( "start_tactical_goal" );
goal_at_start = self BotGetScriptGoal();
keep_looping = true;
while( keep_looping )
{
result = self waittill_any_return( event1, event2, event3, event4, event5, "script_goal_changed" );
keep_looping = false;
should_clear_script_goal = true;
if ( result == "node_relinquished" || result == "goal" || result == "script_goal_changed" )
{
// For these notifies, we only clear the script goal if it was our original goal
if ( !self BotHasScriptGoal() )
{
should_clear_script_goal = false;
}
else
{
goal_at_end = self BotGetScriptGoal();
should_clear_script_goal = bot_vectors_are_equal(goal_at_start,goal_at_end);
}
}
if ( result == "enemy" && IsDefined(self.enemy) )
{
// Switched from one enemy to another, so don't clear script goal
should_clear_script_goal = false;
keep_looping = true; // Continue to wait for a better notify
}
if ( should_clear_script_goal )
self BotClearScriptGoal();
}
}
//=======================================================
// watch_out_of_ammo
//=======================================================
watch_out_of_ammo()
{
self notify("watch_out_of_ammo");
self endon ("watch_out_of_ammo");
self endon( "death" );
self endon( "disconnect" );
while(!self bot_out_of_ammo())
wait(0.5);
self notify("out_of_ammo");
}
//=======================================================
// bot_add_ambush_time_delayed
//=======================================================
bot_add_ambush_time_delayed( endEvent, waitFor )
{
self notify( "bot_add_ambush_time_delayed" );
self endon( "bot_add_ambush_time_delayed" );
self endon( "death" );
self endon( "disconnect" );
if ( IsDefined( endEvent ) )
self endon( endEvent );
self endon( "node_relinquished" );
self endon( "bad_path" );
// Add in how long we waited to the self.ambush_end time
startTime = GetTime();
if ( IsDefined( waitFor ) )
self waittill( waitFor );
if ( IsDefined( self.ambush_end ) && IsDefined( self.node_ambushing_from ) )
{
self.ambush_end += GetTime() - startTime;
self.node_ambushing_from.bot_ambush_end = self.ambush_end + 10000;
}
self notify( "bot_add_ambush_time_delayed" );
}
//=======================================================
// bot_watch_entrances_delayed
//=======================================================
bot_watch_entrances_delayed( endEvent, waitFor, entrances, yaw )
{
self notify( "bot_watch_entrances_delayed" );
if ( entrances.size > 0 )
{
self endon( "bot_watch_entrances_delayed" );
self endon( "death" );
self endon( "disconnect" );
self endon( endEvent );
self endon( "node_relinquished" );
self endon( "bad_path" );
if ( IsDefined( waitFor ) )
self waittill( waitFor );
self endon("path_enemy");
self childthread bot_watch_nodes( entrances, yaw, 0, self.ambush_end );
self childthread bot_monitor_watch_entrances_camp();
}
}
bot_monitor_watch_entrances_camp()
{
self notify( "bot_monitor_watch_entrances_camp" );
self endon( "bot_monitor_watch_entrances_camp" );
self notify( "bot_monitor_watch_entrances" );
self endon( "bot_monitor_watch_entrances" );
self endon( "disconnect" );
self endon( "death" );
while(!IsDefined(self.watch_nodes))
wait(0.05);
while( IsDefined( self.watch_nodes ) )
{
foreach( node in self.watch_nodes )
node.watch_node_chance[self.entity_number] = 1.0;
prioritize_watch_nodes_toward_enemies(0.5);
wait(RandomFloatRange(0.5,0.75));
}
}
//=======================================================
// bot_find_ambush_entrances
//=======================================================
bot_find_ambush_entrances( ambush_node, to_be_occupied )
{
self endon("disconnect");
// Get entrances and filter out any nodes that are too close or don't have exposure to the node in the crouching stance
useEntrances = [];
entrances = FindEntrances( ambush_node.origin );
AssertEx( entrances.size > 0, "Entrance points for node at location " + ambush_node.origin + " could not be calculated. Check pathgrid around that area" );
if ( IsDefined( entrances ) && entrances.size > 0 )
{
wait(0.05);
crouching = ( ambush_node.type != "Cover Stand" && ambush_node.type != "Conceal Stand" );
if ( crouching && to_be_occupied )
entrances = self BotNodeScoreMultiple( entrances, "node_exposure_vis", ambush_node.origin, "crouch" );
foreach ( node in entrances )
{
if ( DistanceSquared( self.origin, node.origin ) < (300 * 300) )
continue;
if ( crouching && to_be_occupied )
{
wait 0.05;
if ( !entrance_visible_from( node.origin, ambush_node.origin, "crouch" ) )
continue;
}
useEntrances[useEntrances.size] = node;
}
}
return useEntrances;
}
//=======================================================
// bot_filter_ambush_inuse
//=======================================================
bot_filter_ambush_inuse( nodes )
{
Assert(IsDefined(nodes));
resultNodes = [];
now = GetTime();
nodesSize = nodes.size;
for( i = 0; i < nodesSize; i++ )
{
node = nodes[i];
if ( !IsDefined( node.bot_ambush_end ) || (now > node.bot_ambush_end) )
resultNodes[resultNodes.size] = node;
}
return resultNodes;
}
//=======================================================
// bot_filter_ambush_vicinity
//=======================================================
bot_filter_ambush_vicinity( nodes, bot, radius )
{
resultNodes = [];
checkPoints = [];
radiusSq = (radius * radius);
if ( level.teamBased )
{
foreach( player in level.participants )
{
if ( !isReallyAlive( player ) )
continue;
if ( !IsDefined( player.team ) )
continue;
if ( (player.team == bot.team) && (player != bot) && IsDefined( player.node_ambushing_from ) )
checkPoints[checkPoints.size] = player.node_ambushing_from.origin;
}
}
checkpointsSize = checkPoints.size;
nodesSize = nodes.size;
for( i = 0; i < nodesSize; i++ )
{
tooClose = false;
node = nodes[i];
for ( j = 0; !tooClose && j < checkpointsSize; j++ )
{
distSq = DistanceSquared( checkPoints[j], node.origin );
tooClose = ( distSq < radiusSq );
}
if ( !tooClose )
resultNodes[resultNodes.size] = node;
}
return resultNodes;
}
//=======================================================
// clear_camper_data
//=======================================================
clear_camper_data()
{
self notify( "clear_camper_data" );
if ( IsDefined( self.node_ambushing_from ) && IsDefined( self.node_ambushing_from.bot_ambush_end ) )
self.node_ambushing_from.bot_ambush_end = undefined;
self.node_ambushing_from = undefined;
self.point_to_ambush = undefined;
self.ambush_yaw = undefined;
self.ambush_entrances = undefined;
self.ambush_duration = RandomIntRange( 20000, 30000 );
self.ambush_end = -1;
}
//=======================================================
// should_select_new_ambush_point
//======================================================
should_select_new_ambush_point()
{
if ( self bot_has_tactical_goal() )
return false;
if ( GetTime() > self.ambush_end )
return true;
if ( !self BotHasScriptGoal() )
return true;
return false;
}
//=======================================================
// find_camp_node
//=======================================================
find_camp_node( )
{
self notify( "find_camp_node" );
self endon( "find_camp_node" );
return self bot_queued_process( "find_camp_node_worker", ::find_camp_node_worker );
}
//=======================================================
// find_camp_node_worker
//=======================================================
find_camp_node_worker( )
{
self notify( "find_camp_node_worker" );
self endon( "find_camp_node_worker" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self clear_camper_data();
if ( level.zoneCount <= 0 )
return false;
myZone = GetZoneNearest( self.origin );
targetZone = undefined;
nextZone = undefined;
faceAngles = self.angles;
if ( IsDefined( myZone ) )
{
// Get nearest zone with predicted enemies but no allies
zoneEnemies = BotZoneNearestCount( myZone, self.team, -1, "enemy_predict", ">", 0, "ally", "<", 1 );
// Fallback to just nearest zone with enemies if that came back empty
if ( !IsDefined( zoneEnemies ) )
zoneEnemies = BotZoneNearestCount( myZone, self.team, -1, "enemy_predict", ">", 0 );
if ( IsDefined(zoneEnemies) )
{
special_zone_node = GetZoneNodeForIndex(zoneEnemies);
linked_zones = GetLinkedNodes(special_zone_node);
if ( linked_zones.size == 0 )
zoneEnemies = undefined; // Enemy zone isn't linked to any other zones, so ignore it
}
// If we have no idea where enemies are then pick the zone furthest from me
if ( !IsDefined( zoneEnemies ) )
{
furthestDist = -1;
furthestZone = -1;
for ( z = 0; z < level.zoneCount; z++ )
{
special_zone_node = GetZoneNodeForIndex(z);
linked_zones = GetLinkedNodes(special_zone_node);
if ( linked_zones.size > 0 )
{
random_node_in_zone = Random(GetZoneNodes(z));
node_not_valid_for_random_path = IsDefined(random_node_in_zone.targetname) && random_node_in_zone.targetname == "no_bot_random_path";
if ( !node_not_valid_for_random_path )
{
dist = Distance2DSquared( GetZoneOrigin( z ), self.origin );
if ( dist > furthestDist )
{
furthestDist = dist;
furthestZone = z;
}
}
}
}
Assert(furthestZone >= 0);
zoneEnemies = furthestZone;
}
Assert( IsDefined( zoneEnemies ) );
zonePath = GetZonePath( myZone, zoneEnemies );
if ( !IsDefined(zonePath) || zonePath.size == 0 )
{
AssertMsg("Bot unable to find path from zone " + myZone + " at origin " + GetZoneOrigin( myZone ) + " to zone " + zoneEnemies + " at origin " + GetZoneOrigin( zoneEnemies ) );
return false;
}
index = 0;
// Pick a point along the path adjacent to predicted enemies but no further than the midpoint
while ( index <= int(zonePath.size / 2) )
{
targetZone = zonePath[index];
nextZone = zonePath[int(min(index+1, zonePath.size - 1))];
if ( BotZoneGetCount( nextZone, self.team, "enemy_predict" ) != 0 )
break;
index++;
}
if ( IsDefined( targetZone ) && IsDefined( nextZone ) && targetZone != nextZone )
{
faceAngles = GetZoneOrigin( nextZone ) - GetZoneOrigin( targetZone );
faceAngles = VectorToAngles( faceAngles );
}
}
node_to_camp = undefined;
if ( IsDefined( targetZone ) )
{
keep_searching = true;
zone_steps = 1;
use_lenient_flag = false;
while( keep_searching )
{
// get set of nodes in the region we want to camp
nodes_to_select_from = GetZoneNodesByDist( targetZone, 800 * zone_steps, true );
if ( nodes_to_select_from.size > 1024 )
nodes_to_select_from = GetZoneNodes( targetZone, 0 );
wait 0.05;
// get direction we want to face in that region
randomRoll = RandomInt( 100 );
if ( randomRoll < 66 && randomRoll >= 33 )
faceAngles = (faceAngles[0], faceAngles[1] + 45, 0);
else if ( randomRoll < 33 )
faceAngles = (faceAngles[0], faceAngles[1] - 45, 0);
// Choose from only the BEST camp spots from within those nodes facing that direction
if ( nodes_to_select_from.size > 0 )
{
while( nodes_to_select_from.size > 1024 )
{
// This is probably because zones haven't been set up so there's one giant zone in the map
// BotNodePickMultiple can't handle an array greater than 1024, so need to truncate it
nodes_to_select_from[nodes_to_select_from.size-1] = undefined;
}
// Only want to pick from the best of the best
selectCount = int( clamp( nodes_to_select_from.size * 0.15, 1, 10 ) );
if ( use_lenient_flag )
nodes_to_select_from = self BotNodePickMultiple( nodes_to_select_from, selectCount, selectCount, "node_camp", AnglesToForward( faceAngles ), "lenient" );
else
nodes_to_select_from = self BotNodePickMultiple( nodes_to_select_from, selectCount, selectCount, "node_camp", AnglesToForward( faceAngles ) );
nodes_to_select_from = bot_filter_ambush_inuse( nodes_to_select_from );
if ( !IsDefined(self.can_camp_near_others) || !self.can_camp_near_others )
{
vicinity_radius = 800;
nodes_to_select_from = bot_filter_ambush_vicinity( nodes_to_select_from, self, vicinity_radius );
}
if ( nodes_to_select_from.size > 0 )
node_to_camp = random_weight_sorted( nodes_to_select_from );
}
if ( IsDefined(node_to_camp) )
{
keep_searching = false;
}
else
{
if ( IsDefined(self.camping_needs_fallback_camp_location) )
{
if ( zone_steps == 1 && !use_lenient_flag )
{
// First try 3 steps away instead of 1
zone_steps = 3;
}
else if ( zone_steps == 3 && !use_lenient_flag )
{
// 3 steps failed, so try using the lenient flag
use_lenient_flag = true;
}
else if ( zone_steps == 3 && use_lenient_flag )
{
// 3 zone steps AND the lenient flag didn't do it, so just bail
keep_searching = false;
}
}
else
{
keep_searching = false;
}
}
if ( keep_searching )
wait 0.05;
}
}
if ( !IsDefined( node_to_camp ) || !self BotNodeAvailable( node_to_camp ) )
return false;
self.node_ambushing_from = node_to_camp;
self.ambush_end = GetTime() + self.ambush_duration;
self.node_ambushing_from.bot_ambush_end = self.ambush_end;
self.ambush_yaw = faceAngles[1];
return true;
}
//=======================================================
// find_ambush_node
//=======================================================
find_ambush_node( optional_point_to_ambush, optional_ambush_radius )
{
self clear_camper_data();
if ( IsDefined(optional_point_to_ambush) )
{
self.point_to_ambush = optional_point_to_ambush;
}
else
{
// get all the high traffic nodes near the bot
node_to_ambush = undefined;
nodes_around_bot = GetNodesInRadius( self.origin, 5000, 0, 2000 );
if ( nodes_around_bot.size > 0 )
{
node_to_ambush = self BotNodePick( nodes_around_bot, nodes_around_bot.size * 0.25, "node_traffic" );
}
if ( IsDefined( node_to_ambush ) )
{
self.point_to_ambush = node_to_ambush.origin;
}
else
{
return false;
}
}
ambush_radius = 2000;
if ( IsDefined(optional_ambush_radius) )
ambush_radius = optional_ambush_radius;
nodes_around_ambush_point = GetNodesInRadius( self.point_to_ambush, ambush_radius, 0, 1000 );
ambush_node_trying = undefined;
Assert(IsDefined(nodes_around_ambush_point));
if ( nodes_around_ambush_point.size > 0 )
{
selectCount = int(max( 1, int( nodes_around_ambush_point.size * 0.15 ) ));
nodes_around_ambush_point = self BotNodePickMultiple( nodes_around_ambush_point, selectCount, selectCount, "node_ambush", self.point_to_ambush );
}
Assert(IsDefined(nodes_around_ambush_point));
nodes_around_ambush_point = bot_filter_ambush_inuse( nodes_around_ambush_point );
if ( nodes_around_ambush_point.size > 0 )
ambush_node_trying = random_weight_sorted( nodes_around_ambush_point );
if( !IsDefined( ambush_node_trying ) || !self BotNodeAvailable( ambush_node_trying ) )
return false;
self.node_ambushing_from = ambush_node_trying;
self.ambush_end = GetTime() + self.ambush_duration;
self.node_ambushing_from.bot_ambush_end = self.ambush_end;
node_to_ambush_point = VectorNormalize( self.point_to_ambush - self.node_ambushing_from.origin );
node_to_ambush_point_angles = VectorToAngles (node_to_ambush_point );
self.ambush_yaw = node_to_ambush_point_angles[1];
return true;
}
bot_random_path()
{
if ( self bot_is_remote_or_linked() )
return false;
random_path_func = level.bot_random_path_function[self.team];
return self [[random_path_func]]();
}
//=======================================================
// bot_random_path_default
//=======================================================
bot_random_path_default()
{
result = false;
chance_to_seek_out_killer = 50;
if ( self.personality == "camper" )
{
chance_to_seek_out_killer = 0;
}
goalPos = undefined;
if ( RandomInt(100) < chance_to_seek_out_killer )
{
goalPos = bot_recent_point_of_interest();
}
if ( !IsDefined( goalPos ) )
{
// Find a random place to go
randomNode = self BotFindNodeRandom();
if ( IsDefined( randomNode ) )
{
goalPos = randomNode.origin;
}
}
if ( IsDefined( goalPos ) )
{
result = self BotSetScriptGoal( goalPos, 128, "hunt" );
}
return result;
}
//=======================================================
// bot_setup_callback_class
//=======================================================
bot_setup_callback_class()
{
if ( practiceRoundGame() )
{
return "practice" + RandomIntRange(1,6);
}
if ( self bot_setup_loadout_callback() )
return "callback";
else
return "class0";
}

View File

@ -0,0 +1,380 @@
#include common_scripts\utility;
#include maps\mp\bots\_bots_strategy;
#include maps\mp\bots\_bots_ks;
#include maps\mp\bots\_bots_util;
//========================================================
// bot_killstreak_sentry
//========================================================
bot_killstreak_sentry( killstreak_info, killstreaks_array, can_use, targetType )
{
self endon( "bot_sentry_exited" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
wait( RandomIntRange( 3, 5 ) );
while ( IsDefined( self.sentry_place_delay ) && GetTime() < self.sentry_place_delay )
{
wait 1;
}
if ( IsDefined( self.enemy ) && (self.enemy.health > 0) && (self BotCanSeeEntity( self.enemy )) )
return true;
targetPoint = self.origin;
if ( targetType != "hide_nonlethal" )
{
// Choose sentry targeting position
targetPoint = bot_sentry_choose_target( targetType );
if ( !IsDefined( targetPoint ) )
return true;
}
self bot_sentry_add_goal( killstreak_info, targetPoint, targetType, killstreaks_array );
while( self bot_has_tactical_goal("sentry_placement") )
{
wait(0.5); // Stay in this thread until the sentry gun is placed, otherwise bots might pick another killstreak instead
}
return true;
}
bot_can_use_sentry_only_ai_version( killstreak_info )
{
foreach ( module in killstreak_info.modules )
{
if ( module == "sentry_guardian" )
return true;
}
return false;
}
bot_sentry_add_goal( killstreak_info, targetOrigin, targetType, killstreaks_array )
{
placement = self bot_sentry_choose_placement( killstreak_info, targetOrigin, targetType, killstreaks_array );
if ( IsDefined( placement ) )
{
self bot_abort_tactical_goal( "sentry_placement" );
extra_params = SpawnStruct();
extra_params.object = placement;
extra_params.script_goal_yaw = placement.yaw;
extra_params.script_goal_radius = 10;
extra_params.start_thread = ::bot_sentry_path_start;
extra_params.end_thread = ::bot_sentry_cancel;
extra_params.should_abort = ::bot_sentry_should_abort;
extra_params.action_thread = ::bot_sentry_activate;
self.placingItemStreakName = killstreak_info.streakName;
self bot_new_tactical_goal( "sentry_placement", placement.node.origin, 0, extra_params );
}
}
bot_sentry_should_abort( tactical_goal )
{
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
if ( IsDefined( self.enemy ) && (self.enemy.health > 0) && (self BotCanSeeEntity( self.enemy )) )
return true;
// As long as we are actively doing a sentry placement, dont start a new one
self.sentry_place_delay = GetTime() + 1000;
return false;
}
bot_sentry_cancel_failsafe( )
{
self endon( "death" );
self endon( "disconnect" );
self endon( "bot_sentry_canceled" );
self endon( "bot_sentry_ensure_exit" );
level endon( "game_ended" );
while ( 1 )
{
if ( IsDefined( self.enemy ) && (self.enemy.health > 0) && (self BotCanSeeEntity( self.enemy )) )
self thread bot_sentry_cancel();
wait 0.05;
}
}
bot_sentry_path_start( tactical_goal )
{
self thread bot_sentry_path_thread( tactical_goal );
}
bot_sentry_path_thread( tactical_goal )
{
self endon( "stop_tactical_goal" );
self endon( "stop_goal_aborted_watch" );
self endon( "bot_sentry_canceled" );
self endon( "bot_sentry_exited" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
// Switch to sentry when we are near goal
while ( IsDefined( tactical_goal.object ) && IsDefined( tactical_goal.object.weapon ) )
{
if ( Distance2D( self.origin, tactical_goal.object.node.origin ) < 400 )
{
self thread bot_force_stance_for_time( "stand", 5.0 );
self thread bot_sentry_cancel_failsafe();
self bot_switch_to_killstreak_weapon( tactical_goal.object.killstreak_info, tactical_goal.object.killstreaks_array, tactical_goal.object.weapon );
return;
}
wait 0.05;
}
}
bot_sentry_choose_target( targetType )
{
// Protect my current defending goal point if I have one
defend_center = self defend_valid_center();
if ( IsDefined(defend_center) )
return defend_center;
// Protect my current ambushing spot if I have one
if ( IsDefined( self.node_ambushing_from ) )
return self.node_ambushing_from.origin;
// Otherwise just return the highest traffic node around me
nodes = GetNodesInRadius( self.origin, 1000, 0, 512 );
nodes_to_select_from = 5;
if ( targetType != "turret" )
{
if ( self BotGetDifficultySetting("strategyLevel") == 1 )
nodes_to_select_from = 10;
else if ( self BotGetDifficultySetting("strategyLevel") == 0 )
nodes_to_select_from = 15;
}
if ( targetType == "turret_air" )
targetNode = self BotNodePick( nodes, nodes_to_select_from, "node_traffic", "ignore_no_sky" );
else
targetNode = self BotNodePick( nodes, nodes_to_select_from, "node_traffic" );
if ( IsDefined( targetNode ) )
return targetNode.origin;
}
bot_sentry_choose_placement( killstreak_info, targetOrigin, targetType, killstreaks_array )
{
placement = undefined;
nodes = GetNodesInRadius( targetOrigin, 1000, 0, 512 );
nodes_to_select_from = 5;
if ( targetType != "turret" )
{
if ( self BotGetDifficultySetting("strategyLevel") == 1 )
nodes_to_select_from = 10;
else if ( self BotGetDifficultySetting("strategyLevel") == 0 )
nodes_to_select_from = 15;
}
if ( targetType == "turret_air" )
placeNode = self BotNodePick( nodes, nodes_to_select_from, "node_sentry", targetOrigin, "ignore_no_sky" );
else if ( targetType == "trap" )
placeNode = self BotNodePick( nodes, nodes_to_select_from, "node_traffic" );
else if ( targetType == "hide_nonlethal" )
placeNode = self BotNodePick( nodes, nodes_to_select_from, "node_hide" );
else
placeNode = self BotNodePick( nodes, nodes_to_select_from, "node_sentry", targetOrigin );
if ( IsDefined( placeNode ) )
{
placement = SpawnStruct();
placement.node = placeNode;
if ( targetOrigin != placeNode.origin && targetType != "hide_nonlethal" )
placement.yaw = VectorToYaw(targetOrigin - placeNode.origin);
else
placement.yaw = undefined;
placement.weapon = killstreak_info.weapon;
placement.killstreak_info = killstreak_info;
placement.killstreaks_array = killstreaks_array;
}
return placement;
}
bot_sentry_carried_obj() // self = bot
{
if ( IsDefined( self.carriedsentry ) )
return self.carriedsentry;
if ( IsDefined( self.carriedTurret ) )
return self.carriedTurret;
//if ( IsDefined( self.carriedIMS ) )
// return self.carriedIMS;
if ( IsDefined( self.carriedItem ) )
return self.carriedItem;
}
bot_sentry_activate( tactical_goal )
{
result = false;
carried_obj = self bot_sentry_carried_obj();
// Place the sentry if it can be placed here, otherwise cancel out of carrying it around
if ( IsDefined( carried_obj ) )
{
abort = false;
if ( !carried_obj.canBePlaced )
{
// Bot cannot currently place the turret, move away from obstruction
time_to_try = 0.75;
start_time = GetTime();
placementYaw = self.angles[1];
if ( IsDefined( tactical_goal.object.yaw ) )
placementYaw = tactical_goal.object.yaw;
moveYaws = [];
moveYaws[0] = placementYaw + 180;
moveYaws[1] = placementYaw + 135;
moveYaws[2] = placementYaw - 135;
minDist = 1000;
foreach ( moveYaw in moveYaws )
{
hitPos = PlayerPhysicsTrace( tactical_goal.object.node.origin, tactical_goal.object.node.origin + AnglesToForward( (0, moveYaw + 180, 0) ) * 100 );
dist = Distance2D( hitpos, tactical_goal.object.node.origin );
if ( dist < minDist )
{
minDist = dist;
self BotSetScriptMove( moveYaw, time_to_try );
self BotLookAtPoint( tactical_goal.object.node.origin, time_to_try, "script_forced" );
}
}
while( !abort && IsDefined( carried_obj ) && !carried_obj.canBePlaced )
{
time_waited = float(GetTime() - start_time) / 1000.0;
if ( !carried_obj.canBePlaced && (time_waited > time_to_try) )
{
abort = true;
// wait a while before attempting another sentry placement
self.sentry_place_delay = GetTime() + 30000;
}
wait 0.05;
}
}
if ( IsDefined( carried_obj ) && carried_obj.canBePlaced )
{
self bot_send_place_notify();
result = true;
}
}
wait 0.25;
self bot_sentry_ensure_exit();
return result;
}
bot_send_place_notify()
{
self notify( "place_sentry" );
self notify( "place_turret" );
//self notify( "place_ims" );
self notify( "placePlaceable" );
}
bot_send_cancel_notify()
{
self SwitchToWeapon( "none" );
self enableWeapons();
self enableWeaponSwitch();
self notify( "cancel_sentry" );
self notify( "cancel_turret" );
//self notify( "cancel_ims" );
self notify( "cancelPlaceable" );
}
bot_sentry_cancel( tactical_goal )
{
// Cancel the sentry
self notify( "bot_sentry_canceled" );
self bot_send_cancel_notify();
self bot_sentry_ensure_exit();
}
bot_sentry_ensure_exit()
{
self notify( "bot_sentry_abort_goal_think" );
self notify( "bot_sentry_ensure_exit" );
self endon( "bot_sentry_ensure_exit" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self SwitchToWeapon( "none" );
self BotClearScriptGoal();
self BotSetStance( "none" );
self enableWeapons();
self enableWeaponSwitch();
wait 0.25;
attempts = 0;
while ( IsDefined( self bot_sentry_carried_obj() ) )
{
attempts++;
self bot_send_cancel_notify();
wait 0.25;
if ( attempts > 2 )
self bot_sentry_force_cancel();
}
self notify( "bot_sentry_exited" );
}
bot_sentry_force_cancel()
{
if ( IsDefined( self.carriedsentry ) )
self.carriedsentry maps\mp\killstreaks\_autosentry::sentry_setCancelled();
if ( IsDefined( self.carriedTurret ) )
self.carriedTurret maps\mp\killstreaks\_remoteturret::turret_setCancelled();
//if ( IsDefined( self.carriedIMS ) )
// self.carriedIMS maps\mp\killstreaks\_ims::ims_setCancelled();
if ( IsDefined( self.carriedItem ) )
self.carriedItem maps\mp\killstreaks\_placeable::onCancel( self.placingItemStreakName, false );
self.carriedsentry = undefined;
self.carriedTurret = undefined;
//self.carriedIMS = undefined;
self.carriedItem = undefined;
self SwitchToWeapon( "none" );
self enableWeapons();
self enableWeaponSwitch();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff