init
This commit is contained in:
3865
raw/maps/mp/bots/_bots.gsc
Normal file
3865
raw/maps/mp/bots/_bots.gsc
Normal file
File diff suppressed because it is too large
Load Diff
747
raw/maps/mp/bots/_bots_gametype_ball.gsc
Normal file
747
raw/maps/mp/bots/_bots_gametype_ball.gsc
Normal 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());
|
||||
}
|
531
raw/maps/mp/bots/_bots_gametype_common.gsc
Normal file
531
raw/maps/mp/bots/_bots_gametype_common.gsc
Normal 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);
|
||||
}
|
||||
}
|
557
raw/maps/mp/bots/_bots_gametype_conf.gsc
Normal file
557
raw/maps/mp/bots/_bots_gametype_conf.gsc
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
344
raw/maps/mp/bots/_bots_gametype_ctf.gsc
Normal file
344
raw/maps/mp/bots/_bots_gametype_ctf.gsc
Normal 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;
|
||||
}
|
12
raw/maps/mp/bots/_bots_gametype_dm.gsc
Normal file
12
raw/maps/mp/bots/_bots_gametype_dm.gsc
Normal 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...
|
||||
}
|
1348
raw/maps/mp/bots/_bots_gametype_dom.gsc
Normal file
1348
raw/maps/mp/bots/_bots_gametype_dom.gsc
Normal file
File diff suppressed because it is too large
Load Diff
41
raw/maps/mp/bots/_bots_gametype_gun.gsc
Normal file
41
raw/maps/mp/bots/_bots_gametype_gun.gsc
Normal 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);
|
||||
}
|
||||
}
|
15
raw/maps/mp/bots/_bots_gametype_horde.gsc
Normal file
15
raw/maps/mp/bots/_bots_gametype_horde.gsc
Normal 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...
|
||||
}
|
369
raw/maps/mp/bots/_bots_gametype_hp.gsc
Normal file
369
raw/maps/mp/bots/_bots_gametype_hp.gsc
Normal 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);
|
||||
}
|
332
raw/maps/mp/bots/_bots_gametype_infect.gsc
Normal file
332
raw/maps/mp/bots/_bots_gametype_infect.gsc
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
1543
raw/maps/mp/bots/_bots_gametype_sd.gsc
Normal file
1543
raw/maps/mp/bots/_bots_gametype_sd.gsc
Normal file
File diff suppressed because it is too large
Load Diff
319
raw/maps/mp/bots/_bots_gametype_sr.gsc
Normal file
319
raw/maps/mp/bots/_bots_gametype_sr.gsc
Normal 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;
|
||||
}
|
231
raw/maps/mp/bots/_bots_gametype_twar.gsc
Normal file
231
raw/maps/mp/bots/_bots_gametype_twar.gsc
Normal 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;
|
||||
}
|
4
raw/maps/mp/bots/_bots_gametype_vlobby.gsc
Normal file
4
raw/maps/mp/bots/_bots_gametype_vlobby.gsc
Normal file
@ -0,0 +1,4 @@
|
||||
main()
|
||||
{
|
||||
// nothing for now...
|
||||
}
|
41
raw/maps/mp/bots/_bots_gametype_war.gsc
Normal file
41
raw/maps/mp/bots/_bots_gametype_war.gsc
Normal 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);
|
||||
}
|
||||
}
|
874
raw/maps/mp/bots/_bots_ks.gsc
Normal file
874
raw/maps/mp/bots/_bots_ks.gsc
Normal 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;
|
||||
}
|
2439
raw/maps/mp/bots/_bots_ks_remote_vehicle.gsc
Normal file
2439
raw/maps/mp/bots/_bots_ks_remote_vehicle.gsc
Normal file
File diff suppressed because it is too large
Load Diff
1818
raw/maps/mp/bots/_bots_loadout.gsc
Normal file
1818
raw/maps/mp/bots/_bots_loadout.gsc
Normal file
File diff suppressed because it is too large
Load Diff
974
raw/maps/mp/bots/_bots_personality.gsc
Normal file
974
raw/maps/mp/bots/_bots_personality.gsc
Normal 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";
|
||||
}
|
380
raw/maps/mp/bots/_bots_sentry.gsc
Normal file
380
raw/maps/mp/bots/_bots_sentry.gsc
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
|
2249
raw/maps/mp/bots/_bots_strategy.gsc
Normal file
2249
raw/maps/mp/bots/_bots_strategy.gsc
Normal file
File diff suppressed because it is too large
Load Diff
2504
raw/maps/mp/bots/_bots_util.gsc
Normal file
2504
raw/maps/mp/bots/_bots_util.gsc
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user