This commit is contained in:
2024-12-11 11:28:08 +01:00
commit 12ac62a956
444 changed files with 303964 additions and 0 deletions

3765
maps/mp/bots/_bots.gsc Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,729 @@
#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\bots\_bots;
#include maps\mp\bots\_bots_ks;
#include maps\mp\bots\_bots_util;
#include maps\mp\bots\_bots_strategy;
#include maps\mp\bots\_bots_personality;
bot_fireteam_setup_callbacks()
{
}
bot_fireteam_init()
{
level.bots_fireteam_num_classes_loaded = [];
level thread bot_fireteam_connect_monitor();
}
//========================================================
// bot_fireteam_connect_monitor
//========================================================
bot_fireteam_connect_monitor()
{
self notify( "bot_connect_monitor" );
self endon( "bot_connect_monitor" );
level.bots_fireteam_humans = [];
while(1)
{
foreach(player in level.players)
{
if ( !IsBot( player ) && !IsDefined(player.processed_for_fireteam) )
{
if ( IsDefined(player.team) && (player.team == "allies" || player.team == "axis") )
{
// Player is already on a team, so skip right to the bot spawning
player.processed_for_fireteam = true;
level.bots_fireteam_humans[player.team] = player;
level.bots_fireteam_num_classes_loaded[player.team] = 0;
team_limit = bot_get_team_limit();
if ( level.bots_fireteam_humans.size == 2 )
{
// TEMP - remove the 6 bots that were already spawned on this team to make room for the player's bots
drop_bots( team_limit-1, player.team );
}
spawn_bots( team_limit-1, player.team, ::bot_fireteam_spawn_callback );
if ( level.bots_fireteam_humans.size == 1 )
{
num_human_players = 0;
foreach( client in level.players )
{
if ( IsDefined( client ) && !IsBot( client ) )
num_human_players++;
}
if ( num_human_players == 1 )
{
// TEMP - spawn 6 bots on the other team for enemies
spawn_bots( team_limit-1, get_enemy_team( player.team ) );
}
}
}
}
}
wait(0.25);
}
}
bot_fireteam_spawn_callback()
{
// Make sure the fireteam code is called to determine this bot's class, not the personality or default class selection code
self.override_class_function = ::bot_fireteam_setup_callback_class;
// Set fireteam commander to the human player on this team
self.fireteam_commander = level.bots_fireteam_humans[self.bot_team];
// Monitor earning killstreaks and let our commander know
self thread bot_fireteam_monitor_killstreak_earned();
}
bot_fireteam_setup_callback_class()
{
// Set the bot as "callback" class and set up the callback function for when he chooses his loadout
self.classCallback = ::bot_fireteam_loadout_class_callback;
return "callback";
}
bot_fireteam_loadout_class_callback()
{
if ( IsDefined(self.botLastLoadout) )
return self.botLastLoadout;
//FIXME: Unify this with Squad vs. Squad mode...
self.class_num = level.bots_fireteam_num_classes_loaded[self.team];
level.bots_fireteam_num_classes_loaded[self.team] += 1;
if ( self.class_num == 5 )
{
// Ugly hack - the 6th class is currently not supported because the player only has 5 loadouts unlocked by default
// Different things happen depending on how you are playing - loadouts return "none", cause script errors, etc.,
// so better to just pinch it off here and return a random loadout
// TODO: Fix this correctly
//return maps\mp\bots\_bots_loadout::bot_loadout_class_callback();
self.class_num = 0;
}
loadoutValueArray["loadoutPrimary"] = self.fireteam_commander bot_fireteam_cac_getWeapon( self.class_num, 0 );
loadoutValueArray["loadoutPrimaryAttachment"] = self.fireteam_commander bot_fireteam_cac_getWeaponAttachment( self.class_num, 0 );
loadoutValueArray["loadoutPrimaryAttachment2"] = self.fireteam_commander bot_fireteam_cac_getWeaponAttachmentTwo( self.class_num, 0 );
loadoutValueArray["loadoutPrimaryBuff"] = self.fireteam_commander bot_fireteam_cac_getWeaponBuff( self.class_num, 0 );
loadoutValueArray["loadoutPrimaryCamo"] = self.fireteam_commander bot_fireteam_cac_getWeaponCamo( self.class_num, 0 );
loadoutValueArray["loadoutPrimaryReticle"] = self.fireteam_commander bot_fireteam_cac_getWeaponReticle( self.class_num, 0 );
loadoutValueArray["loadoutSecondary"] = self.fireteam_commander bot_fireteam_cac_getWeapon( self.class_num, 1 );
loadoutValueArray["loadoutSecondaryAttachment"] = self.fireteam_commander bot_fireteam_cac_getWeaponAttachment( self.class_num, 1 );
loadoutValueArray["loadoutSecondaryAttachment2"] = self.fireteam_commander bot_fireteam_cac_getWeaponAttachmentTwo( self.class_num, 1 );
loadoutValueArray["loadoutSecondaryBuff"] = self.fireteam_commander bot_fireteam_cac_getWeaponBuff( self.class_num, 1 );
loadoutValueArray["loadoutSecondaryCamo"] = self.fireteam_commander bot_fireteam_cac_getWeaponCamo( self.class_num, 1 );
loadoutValueArray["loadoutSecondaryReticle"] = self.fireteam_commander bot_fireteam_cac_getWeaponReticle( self.class_num, 1 );
loadoutValueArray["loadoutEquipment"] = self.fireteam_commander bot_fireteam_cac_getPrimaryGrenade( self.class_num );
loadoutValueArray["loadoutOffhand"] = self.fireteam_commander bot_fireteam_cac_getSecondaryGrenade( self.class_num );
loadoutValueArray["loadoutPerk1"] = self.fireteam_commander bot_fireteam_cac_getPerk( self.class_num, 2 );
loadoutValueArray["loadoutPerk2"] = self.fireteam_commander bot_fireteam_cac_getPerk( self.class_num, 3 );
loadoutValueArray["loadoutPerk3"] = self.fireteam_commander bot_fireteam_cac_getPerk( self.class_num, 4 );
loadoutValueArray["loadoutStreakType"] = self.fireteam_commander bot_fireteam_cac_getPerk( self.class_num, 5 );
if ( loadoutValueArray["loadoutStreakType"] != "specialty_null" )
{
playerData = GetSubStr(loadoutValueArray["loadoutStreakType"],11) + "Streaks"; // "loadoutStreakType" will be streaktype_assault, etc, so remove first 11 chars
loadoutValueArray["loadoutStreak1"] = self.fireteam_commander bot_fireteam_cac_getStreak( self.class_num, playerData, 0 );
if ( loadoutValueArray["loadoutStreak1"] == "none" )
loadoutValueArray["loadoutStreak1"] = undefined;
loadoutValueArray["loadoutStreak2"] = self.fireteam_commander bot_fireteam_cac_getStreak( self.class_num, playerData, 1 );
if ( loadoutValueArray["loadoutStreak2"] == "none" )
loadoutValueArray["loadoutStreak2"] = undefined;
loadoutValueArray["loadoutStreak3"] = self.fireteam_commander bot_fireteam_cac_getStreak( self.class_num, playerData, 2 );
if ( loadoutValueArray["loadoutStreak3"] == "none" )
loadoutValueArray["loadoutStreak3"] = undefined;
/#
bot_fireteam_test_killstreaks(loadoutValueArray, self);
#/
}
self.botLastLoadout = loadoutValueArray;
return loadoutValueArray;
}
/#
bot_fireteam_test_killstreaks(loadoutValueArray, bot)
{
if ( IsDefined(loadoutValueArray["loadoutStreak1"]) )
bot_killstreak_valid_for_specific_streakType(loadoutValueArray["loadoutStreak1"], loadoutValueArray["loadoutStreakType"], true);
if ( IsDefined(loadoutValueArray["loadoutStreak2"]) )
bot_killstreak_valid_for_specific_streakType(loadoutValueArray["loadoutStreak2"], loadoutValueArray["loadoutStreakType"], true);
if ( IsDefined(loadoutValueArray["loadoutStreak3"]) )
bot_killstreak_valid_for_specific_streakType(loadoutValueArray["loadoutStreak3"], loadoutValueArray["loadoutStreakType"], true);
}
#/
bot_fireteam_cac_getWeapon( classIndex, weaponIndex )
{
return self getCaCPlayerData( classIndex, "weaponSetups", weaponIndex, "weapon" );
}
bot_fireteam_cac_getWeaponAttachment( classIndex, weaponIndex )
{
return self getCaCPlayerData( classIndex, "weaponSetups", weaponIndex, "attachment", 0 );
}
bot_fireteam_cac_getWeaponAttachmentTwo( classIndex, weaponIndex )
{
return self getCaCPlayerData( classIndex, "weaponSetups", weaponIndex, "attachment", 1 );
}
bot_fireteam_cac_getWeaponBuff( classIndex, weaponIndex )
{
return self getCaCPlayerData( classIndex, "weaponSetups", weaponIndex, "buff" );
}
bot_fireteam_cac_getWeaponCamo( classIndex, weaponIndex )
{
return self getCaCPlayerData( classIndex, "weaponSetups", weaponIndex, "camo" );
}
bot_fireteam_cac_getWeaponReticle( classIndex, weaponIndex )
{
return self getCaCPlayerData( classIndex, "weaponSetups", weaponIndex, "reticle" );
}
bot_fireteam_cac_getPrimaryGrenade( classIndex )
{
return self getCaCPlayerData( classIndex, "perks", 0 );
}
bot_fireteam_cac_getSecondaryGrenade( classIndex )
{
return self getCaCPlayerData( classIndex, "perks", 1 );
}
bot_fireteam_cac_getPerk( classIndex, perkIndex )
{
return self getCaCPlayerData( classIndex, "perks", perkIndex );
}
bot_fireteam_cac_getStreak( classIndex, playerData, streakIndex )
{
return self getCaCPlayerData( classIndex, playerData, streakIndex );
}
//========================================
//
// TACTICS
//
//========================================
//---------------------
// BUDDY SYSTEM
//---------------------
bot_fireteam_buddy_think()
{
buddy_range = 250;
buddy_range_sq = (buddy_range*buddy_range);
if ( !self bot_is_guarding_player( self.owner ) )
{
self bot_guard_player( self.owner, buddy_range );
}
if ( DistanceSquared( self.origin, self.owner.origin ) > buddy_range_sq )
{
self BotSetFlag( "force_sprint", true );
}
else if ( self.owner IsSprinting() )
{
self BotSetFlag( "force_sprint", true );
}
else
{
self BotSetFlag( "force_sprint", false );
}
}
bot_fireteam_buddy_search()
{
self endon( "buddy_cancel" );
self endon( "disconnect" );
self notify( "buddy_search_start" );
self endon( "buddy_search_start" );
while(1)
{
if ( IsAlive( self ) && !IsDefined( self.bot_fireteam_follower ) )
{//we're alive & not a leader
if ( IsDefined( self.owner ) )
{//we have a leader
if ( self.sessionstate == "playing" )
{//we're playing
if ( !self.owner.connected )
{//my owner disconnected - search for a new one
self.owner.bot_fireteam_follower = undefined;
self.owner = undefined;
}
else if ( isdefined( level.fireteam_commander[self.team] ) )
{//we have a player commander
if ( IsDefined(level.fireteam_commander[self.team].commanding_bot) && level.fireteam_commander[self.team].commanding_bot == self )
{//the commander took us over, make our leader follow the commander instead
//clear our leader's follower
self.owner.bot_fireteam_follower = undefined;
//make our leader follow the commander
self.owner.owner = level.fireteam_commander[self.team];
self.owner.personality_update_function = ::bot_fireteam_buddy_think;
//clear our leader
self.owner = undefined;
}
else if ( IsDefined(level.fireteam_commander[self.team].commanding_bot) && level.fireteam_commander[self.team].commanding_bot == self.owner )
{//the commander has taken over our owner, switch to the commander as our owner
self.owner.bot_fireteam_follower = undefined;
self.owner = level.fireteam_commander[self.team];
self.owner.bot_fireteam_follower = self;
}
else if ( self.owner == level.fireteam_commander[self.team] && !IsDefined( self.owner.commanding_bot ) )
{//the commander was our owner and is no longer controlling a bot, switch to the bot they were last controlling
self.owner.bot_fireteam_follower = undefined;
if ( IsDefined( self.owner.last_commanded_bot ) )
{//switch to the bot the commander relinquished control over
self.owner = self.owner.last_commanded_bot;
self.owner.bot_fireteam_follower = self;
}
else
{//there is no last bot they were commanding? search for a new one
self.owner = undefined;
}
}
}
}
else
{//we've been sidelined
if ( isdefined( level.fireteam_commander[self.team] ) )
{//we have a player commander
if ( IsDefined(level.fireteam_commander[self.team].commanding_bot) && level.fireteam_commander[self.team].commanding_bot == self )
{//the commander took us over, make our leader follow the commander instead
//clear our leader's follower
self.owner.bot_fireteam_follower = undefined;
//make our leader follow the commander
self.owner.owner = level.fireteam_commander[self.team];
self.owner.personality_update_function = ::bot_fireteam_buddy_think;
//clear our leader
self.owner = undefined;
}
}
}
}
if ( self.sessionstate == "playing" )
{
if ( !IsDefined( self.owner ) )
{//look for the closest other player/bot
myAvailableTeam = [];
foreach( player in level.players )
{
if ( player != self && player.team == self.team )
{//on our team and not us, ourselves
if ( IsAlive( player ) && player.sessionstate == "playing" && !IsDefined( player.bot_fireteam_follower ) && !IsDefined( player.owner ) )
{//player/bot is alive, playing and not already a leader and not a follower
myAvailableTeam[myAvailableTeam.size] = player;
}
}
}
if ( myAvailableTeam.size > 0 )
{//there's at least one available, unattached other bot/player
closestBot = getClosest( self.origin, myAvailableTeam );
if ( IsDefined( closestBot ) )
{//get the closest one and follow them
self.owner = closestBot;
self.owner.bot_fireteam_follower = self;
}
}
}
}
if ( IsDefined( self.owner ) )
{//have a leader
//Line( self.origin + (0,0,48), self.owner.origin + (0,0,48), (0,1,0), 1.0, false, 10 );
//Line( self.origin + (0,0,48), self.origin + (0,0,128), (1,1,1), 1.0, false, 10 );
//Line( self.owner.origin + (0,0,48), self.owner.origin + (0,0,128), (0,1,1), 1.0, false, 10 );
self.personality_update_function = ::bot_fireteam_buddy_think;
}
else
{//no leader
//Line( self.origin + (0,0,48), self.origin + (0,0,128), (1,0,0), 1.0, false, 10 );
self bot_assign_personality_functions();
}
}
wait(0.5);
}
}
//---------------------
// HUNTING PARTY
//---------------------
fireteam_tdm_set_hunt_leader( whichTeam )
{
//try to use the player if they're in the game
botsOnTeam = [];
foreach( player in level.players )
{
if ( player.team == whichTeam )
{
if ( player.connected && IsAlive( player ) && player.sessionstate == "playing" )
{
if ( !IsBot( player ) )
{
level.fireteam_hunt_leader[whichTeam] = player;
return true;
}
else
{
botsOnTeam[botsOnTeam.size] = player;
}
}
}
}
if ( !IsDefined( level.fireteam_hunt_leader[whichTeam] ) )
{//if no leader yet, use a bot
if ( botsOnTeam.size > 0 )
{
if ( botsOnTeam.size == 1 )
{
level.fireteam_hunt_leader[whichTeam] = botsOnTeam[0];
}
else
{
level.fireteam_hunt_leader[whichTeam] = botsOnTeam[RandomInt(botsOnTeam.size)];
}
return true;
}
}
return false;
}
fireteam_tdm_hunt_end( whichTeam )
{
level notify( "hunting_party_end_"+whichTeam );
level.fireteam_hunt_leader[whichTeam] = undefined;
level.fireteam_hunt_target_zone[whichTeam] = undefined;
level.bot_random_path_function[whichTeam] = ::bot_random_path_default;
}
fireteam_tdm_hunt_most_dangerous_zone( leaderZone, whichTeam )
{
bestZoneEnemyCount = 0;
bestZone = undefined;
bestZonePathSize = -1;
if ( level.zoneCount > 0 )
{//go through all zones, return the closest one with the highest # of predicted enemies
for( testZone = 0; testZone < level.zoneCount; testZone++ )
{
zoneEnemyCount = BotZoneGetCount( testZone, whichTeam, "enemy_predict" );
if ( zoneEnemyCount < bestZoneEnemyCount )
{
continue;
}
zonePath = undefined;
if ( zoneEnemyCount == bestZoneEnemyCount )
{//same number of enemies - use the closer zone
zonePath = GetZonePath( leaderZone, testZone );
if ( !IsDefined( zonePath ) )
{//no path?
continue;
}
if ( bestZonePathSize >= 0 && zonePath.size > bestZonePathSize )
{
continue;
}
}
bestZoneEnemyCount = zoneEnemyCount;
bestZone = testZone;
if ( IsDefined( zonePath ) )
{
bestZonePathSize = zonePath.size;
}
else
{
bestZonePathSize = -1;
}
}
}
return bestZone;
}
fireteam_tdm_find_hunt_zone( whichTeam )
{
level endon( "hunting_party_end_"+whichTeam );
self endon( "disconnect" );
level endon( "game_ended" );
if ( level.zoneCount <= 0 )
return;
level.bot_random_path_function[whichTeam] = ::bot_fireteam_hunt_zone_find_node;
while(1)
{
wait_time = 3;
if ( !IsDefined( level.fireteam_hunt_leader[whichTeam] ) || IsBot( level.fireteam_hunt_leader[whichTeam] ) || IsDefined( level.fireteam_hunt_leader[whichTeam].commanding_bot ) )
{
fireteam_tdm_set_hunt_leader( whichTeam );
}
if ( IsDefined( level.fireteam_hunt_leader[whichTeam] ) )
{
leaderZone = GetZoneNearest( level.fireteam_hunt_leader[whichTeam].origin );
if ( !IsDefined(leaderZone) )
{
wait(wait_time);
continue;
}
if ( !IsBot( level.fireteam_hunt_leader[whichTeam] ) )
{//just follow the player around
if ( IsAlive( level.fireteam_hunt_leader[whichTeam] ) && level.fireteam_hunt_leader[whichTeam].sessionstate == "playing" && (!IsDefined(level.fireteam_hunt_leader[whichTeam].deathTime) || level.fireteam_hunt_leader[whichTeam].deathTime+5000 < GetTime()) )
{//only if the player is alive and didn't die recently
level.fireteam_hunt_target_zone[whichTeam] = leaderZone;
level.fireteam_hunt_next_zone_search_time[whichTeam] = GetTime() + 1000;
wait_time = 0.5;
}
else
{//just hold this position for a while
wait_time = 1;
}
}
else
{//try to find the zone with the most enemies
first_search = false;
changeZones = false;
curZone = undefined;
if ( IsDefined( level.fireteam_hunt_target_zone[whichTeam] ) )
curZone = level.fireteam_hunt_target_zone[whichTeam];
else
{
first_search = true;
changeZones = true;
curZone = leaderZone;
}
newZone = undefined;
if ( IsDefined( curZone ) )
{
// Get nearest zone with the most predicted enemies
newZone = fireteam_tdm_hunt_most_dangerous_zone( leaderZone, whichTeam );
if ( !first_search )
{//not the first search
if ( !IsDefined( newZone ) || newZone != curZone )
{//either no known enemies at all or no known enemies in our target zone, but we found another with enemies
if ( curZone == leaderZone )
{//leader has reached the original target zone
changeZones = true;
}
else if ( GetTime() > level.fireteam_hunt_next_zone_search_time[whichTeam] )
{//enough time has passed to change nodes
changeZones = true;
}
}
}
if ( changeZones )
{
if ( !IsDefined( newZone ) )
{// If we have no idea where enemies are then pick the zone furthest from us
furthestDist = 0;
furthestZone = -1;
for ( z = 0; z < level.zoneCount; z++ )
{
dist = Distance2D( GetZoneOrigin( z ), level.fireteam_hunt_leader[whichTeam].origin );
if ( dist > furthestDist )
{
furthestDist = dist;
furthestZone = z;
}
}
newZone = furthestZone;
}
if ( IsDefined( newZone ) )
{
if ( !IsDefined( level.fireteam_hunt_target_zone[whichTeam] ) || level.fireteam_hunt_target_zone[whichTeam] != newZone )
{//tell all the bots to get new goals
foreach( player in level.players )
{
if ( IsBot( player ) && player.team == whichTeam )
{
player BotClearScriptGoal();
player.fireteam_hunt_goalpos = undefined;
player thread bot_fireteam_hunt_zone_find_node();
}
}
}
level.fireteam_hunt_target_zone[whichTeam] = newZone;
level.fireteam_hunt_next_zone_search_time[whichTeam] = GetTime() + 12000;
}
}
}
}
}
wait(wait_time);
}
}
bot_debug_script_goal()
{
self notify( "bot_debug_script_goal" );
level endon( "hunting_party_end_"+ self.team );
self endon( "bot_debug_script_goal" );
lineHeight = 48;
while(1)
{
if ( self BotHasScriptGoal() )
{
goalPos = self BotGetScriptGoal();
if ( !IsDefined( self.fireteam_hunt_goalpos ) )
{//we have a goal, but no hunt goal??
Line( self.origin + (0,0,lineHeight), goalPos + (0,0,lineHeight), (0,1,1), 1.0, false, 1 );
}
else if ( self.fireteam_hunt_goalpos != goalPos )
{//something changed our goal on us! Bastards!
Line( self.origin + (0,0,lineHeight), goalPos + (0,0,lineHeight), (1,1,1), 1.0, false, 1 );
Line( self.origin + (0,0,lineHeight), self.fireteam_hunt_goalpos + (0,0,lineHeight), (1,1,0), 1.0, false, 1 );
}
else
{//all is good
Line( self.origin + (0,0,lineHeight), self.fireteam_hunt_goalpos + (0,0,lineHeight), (0,1,0), 1.0, false, 1 );
}
}
else if ( IsDefined( self.fireteam_hunt_goalpos ) )
{//something removed our goal on us! Bastards!
Line( self.origin + (0,0,lineHeight), self.fireteam_hunt_goalpos + (0,0,lineHeight), (1,0,0), 1.0, false, 1 );
}
wait(0.05);
}
}
//=======================================================
// bot_fireteam_hunt_zone_find_node
//=======================================================
bot_fireteam_hunt_zone_find_node()
{
result = false;
node_to_guard = undefined;
if ( IsDefined( level.fireteam_hunt_target_zone[self.team] ) )
{
// get set of nodes in the region we want to camp
nodes_to_select_from = GetZoneNodes( level.fireteam_hunt_target_zone[self.team], 0 );
// Choose from only the BEST camp spots from within those nodes
if ( nodes_to_select_from.size <= 18 )
{//each zone should have at least 3 nodes per teammate - if not, expand to neighbor zones
nodes_to_select_from = GetZoneNodes( level.fireteam_hunt_target_zone[self.team], 1 );
if ( nodes_to_select_from.size <= 18 )
{//each zone should have at least 3 nodes per teammate - if not, expand to neighbor zones
nodes_to_select_from = GetZoneNodes( level.fireteam_hunt_target_zone[self.team], 2 );
if ( nodes_to_select_from.size <= 18 )
{//each zone should have at least 3 nodes per teammate - if not, expand to neighbor zones
nodes_to_select_from = GetZoneNodes( level.fireteam_hunt_target_zone[self.team], 3 );
}
}
}
if ( nodes_to_select_from.size <= 0 )
{
return bot_random_path_default();
}
node_to_guard = self BotNodePick( nodes_to_select_from, nodes_to_select_from.size, "node_hide" );
tries = 0;
while(!IsDefined( node_to_guard ) || !self BotNodeAvailable( node_to_guard ) )
{
tries++;
if ( tries >= 10 )
return bot_random_path_default();
node_to_guard = nodes_to_select_from[RandomInt(nodes_to_select_from.size)];
}
goalPos = node_to_guard.origin;
if ( IsDefined( goalPos ) )
{
node_type = "guard";
myZone = GetZoneNearest( self.origin );
if ( IsDefined(myZone) && myZone == level.fireteam_hunt_target_zone[self.team] )
{//in the zone, free to move around a bit more
//node_type = "hunt";
self BotSetFlag( "force_sprint", false );
}
else
{
self BotSetFlag( "force_sprint", true );
}
result = self BotSetScriptGoal( goalPos, 128, node_type );
self.fireteam_hunt_goalpos = goalPos;
//self thread bot_debug_script_goal();
}
}
if ( !result )
return bot_random_path_default();
return result;
}
bot_fireteam_monitor_killstreak_earned()
{
level endon( "game_ended" );
self endon( "disconnect" );
self notify( "bot_fireteam_monitor_killstreak_earned" );
self endon( "bot_fireteam_monitor_killstreak_earned" );
while(1)
{
self waittill( "bot_killstreak_earned", splashString, streakVal );
//In Fireteam mode, notify my commander if I got a killstreak
if ( bot_is_fireteam_mode() )
{
if ( IsDefined( self ) && IsBot( self ) )
{
if ( IsDefined( self.fireteam_commander ) )
{
commandersBot = undefined;
if ( IsDefined( self.fireteam_commander.commanding_bot ) )
{
commandersBot = self.fireteam_commander.commanding_bot;
}
else
{
commandersBot = self.fireteam_commander GetSpectatingPlayer();
}
if ( !IsDefined( commandersBot ) || commandersBot != self )
{
self.fireteam_commander thread maps\mp\gametypes\_hud_message::playerCardSplashNotify( splashString, self, streakVal );
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,14 @@
#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;
//=======================================================
// main
//=======================================================
main()
{
// nothing for now...
}

View File

@ -0,0 +1,302 @@
#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;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_blitz();
}
/#
empty_function_to_force_script_dev_compile() {}
#/
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_blitz_think;
}
setup_bot_blitz()
{
bot_waittill_bots_enabled( true );
level.protect_radius = 600;
thread bot_blitz_ai_director_update();
level.bot_gametype_precaching_done = true;
}
bot_blitz_think()
{
self notify( "bot_blitz_think" );
self endon( "bot_blitz_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
while ( true )
{
wait(0.05);
if ( !IsDefined(self.role) )
self initialize_blitz_role();
if ( bot_has_tactical_goal() )
continue;
if ( self.role == "attacker" )
{
target_portal = level.portalList[get_enemy_team(self.team)];
if ( target_portal.open )
{
if ( self bot_is_defending() )
self bot_defend_stop();
if ( !self BotHasScriptGoal() )
self BotSetScriptGoal( target_portal.origin, 0, "objective" );
}
else
{
if ( !bot_is_defending() )
{
self BotClearScriptGoal();
self bot_protect_point( target_portal.origin, level.protect_radius );
}
}
}
else if ( self.role == "defender" )
{
target_portal = level.portalList[self.team];
if ( !self bot_is_defending_point( target_portal.origin ) )
{
optional_params["min_goal_time"] = 20;
optional_params["max_goal_time"] = 30;
optional_params["score_flags"] = "strict_los";
self bot_protect_point( target_portal.origin, level.protect_radius, optional_params );
}
}
}
}
initialize_blitz_role()
{
attackers = get_allied_attackers_for_team(self.team);
defenders = get_allied_defenders_for_team(self.team);
attacker_limit = blitz_bot_attacker_limit_for_team(self.team);
defender_limit = blitz_bot_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 blitz_set_role("attacker");
else
self blitz_set_role("defender");
}
else
{
self blitz_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 blitz_set_role("defender");
else
self blitz_set_role("attacker");
}
else
{
self blitz_set_role("defender");
}
}
}
bot_blitz_ai_director_update()
{
level notify("bot_blitz_ai_director_update");
level endon("bot_blitz_ai_director_update");
level endon("game_ended");
teams[0] = "allies";
teams[1] = "axis";
while(1)
{
foreach( team in teams )
{
attacker_limit = blitz_bot_attacker_limit_for_team(team);
defender_limit = blitz_bot_defender_limit_for_team(team);
attackers = get_allied_attackers_for_team(team);
defenders = get_allied_defenders_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 blitz_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) blitz_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 blitz_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) blitz_set_role("attacker");
}
}
wait(1.0);
}
}
blitz_bot_attacker_limit_for_team(team)
{
team_limit = blitz_get_num_players_on_team(team);
return int( int(team_limit) / 2 ) + 1 + ( int(team_limit) % 2 );
}
blitz_bot_defender_limit_for_team(team)
{
team_limit = blitz_get_num_players_on_team(team);
return max( int( int(team_limit) / 2 ) - 1, 0 );
}
blitz_get_num_players_on_team(team)
{
num_on_team = 0;
foreach( player in level.participants )
{
if ( IsTeamParticipant(player) && IsDefined(player.team) && player.team == team )
num_on_team++;
}
return num_on_team;
}
get_allied_attackers_for_team(team)
{
attackers = get_players_by_role( "attacker", team );
foreach ( player in level.players )
{
if ( !IsAI(player) && IsDefined(player.team) && player.team == team )
{
if ( DistanceSquared(level.portalList[team].origin,player.origin) > level.protect_radius * level.protect_radius )
attackers = array_add(attackers, player);
}
}
return attackers;
}
get_allied_defenders_for_team(team)
{
defenders = get_players_by_role( "defender", team );
foreach ( player in level.players )
{
if ( !IsAI(player) && IsDefined(player.team) && player.team == team )
{
if ( DistanceSquared(level.portalList[team].origin,player.origin) <= level.protect_radius * level.protect_radius )
defenders = array_add(defenders, player);
}
}
return defenders;
}
blitz_set_role( new_role )
{
self.role = new_role;
self BotClearScriptGoal();
self bot_defend_stop();
}
get_players_by_role( role, team )
{
players = [];
foreach( player in level.participants )
{
if ( !IsDefined( player.team ) )
continue;
if ( IsAlive(player) && IsTeamParticipant(player) && player.team == team && IsDefined(player.role) && player.role == role )
players[players.size] = player;
}
return players;
}

View File

@ -0,0 +1,558 @@
#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;
//=======================================================
// main
//=======================================================
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
//=======================================================
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;
/#
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 ( GetDvar("bot_DrawDebugGametype") == "conf" )
{
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
//=======================================================
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.curorigin, 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.curorigin, 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.curorigin, 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 ( GetDvar("bot_DrawDebugGametype") == "conf" && 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(1.0);
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 )
{
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.curorigin) < 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);
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.curorigin, self.origin );
if ( dist_self_to_tag_sq < best_tag_dist_sq )
{
best_tag = tag_struct.tag;
best_tag_dist_sq = dist_self_to_tag_sq;
}
}
}
}
return best_tag;
}
bot_remove_invalid_tags( tags )
{
valid_tags = [];
foreach ( tag_struct in tags )
{
// Need to check if the tag can still be interacted with and if it is in the same place as where we originally saw it
// This is because the tags are reused in the game, so this is to check if the tag has already been picked up, or if the player whose tag it is
// died again in a different spot, moving the tag to that location
if ( tag_struct.tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) && bot_vectors_are_equal( tag_struct.tag.curorigin, tag_struct.origin ) )
{
if ( !self bot_check_tag_above_head( tag_struct.tag ) && tag_struct.tag.on_path_grid )
valid_tags = array_add( valid_tags, tag_struct );
}
}
return valid_tags;
}
get_num_allies_getting_tag( tag )
{
num = 0;
foreach( player in level.participants )
{
if ( !IsDefined( player.team ) )
continue;
if ( player.team == self.team && player != self )
{
if ( IsAI( player ) )
{
if ( IsDefined(player.tag_getting) && player.tag_getting == tag )
num++;
}
else
{
// If player is within 400 distance from a tag, consider him to be going for it
if ( DistanceSquared( player.origin, tag.curorigin ) < 400 * 400 )
num++;
}
}
}
return num;
}
bot_camp_tag( tag, goal_type, optional_endon )
{
self notify("bot_camp_tag");
self endon("bot_camp_tag");
self endon("stop_camping_tag");
if ( IsDefined(optional_endon) )
self endon(optional_endon);
self BotSetScriptGoalNode( self.node_ambushing_from, goal_type, self.ambush_yaw );
result = self bot_waittill_goal_or_fail();
if ( result == "goal" )
{
nearest_node_to_tag = tag.nearest_node;
if ( IsDefined( nearest_node_to_tag ) )
{
nodes_to_watch = FindEntrances( self.origin );
nodes_to_watch = array_add( nodes_to_watch, nearest_node_to_tag );
self childthread bot_watch_nodes( nodes_to_watch );
}
}
}

View File

@ -0,0 +1,18 @@
#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;
//=======================================================
// main
//=======================================================
main()
{
// By default, they will use TDM callback/think
// Limit personality types to just the active ones.
level.bot_personality_types_desired["active"] = 1;
level.bot_personality_types_desired["stationary"] = 0;
}

View File

@ -0,0 +1,14 @@
#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;
//=======================================================
// main
//=======================================================
main()
{
// nothing for now...
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,259 @@
#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;
MAX_MELEE_CHARGE_DIST = 500;
//=======================================================
// main
//=======================================================
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
maps\mp\bots\_bots_gametype_conf::setup_bot_conf();
}
//=======================================================
// setup_callbacks
//=======================================================
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_grind_think;
}
bot_grind_think()
{
self notify( "bot_grind_think" );
self endon( "bot_grind_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self.grind_waiting_to_bank = false;
self.goal_zone = undefined;
self.conf_camping_zone = false;
self.additional_tactical_logic_func = ::bot_grind_extra_think;
if ( self BotGetDifficultySetting( "strategyLevel" ) > 0 )
{
self childthread enemy_watcher();
}
// Do normal Kill Confirmed behavior
maps\mp\bots\_bots_gametype_conf::bot_conf_think();
}
bot_grind_extra_think()
{
if ( !IsDefined(self.tag_getting) )
{
// Also: if we have tags to drop off and there's a drop-off point nearby, drop them off.
if ( self.tagsCarried > 0 )
{
// Find the closest one within max distance (higher tolerence the more tags we're carrying) and head to it.
bestDistSq = squared(500+self.tagsCarried*250);
if ( game["teamScores"][self.team] + self.tagsCarried >= getWatchedDvar( "scorelimit" ) )
{
// My tags would win the game
// Go to the nearest one, regardless of distance
bestDistSq = squared(5000);
}
else if ( !IsDefined( self.enemy ) && !self bot_in_combat() )
{
// If I have no enemy, be braver about how far we'll go to score
bestDistSq = squared(1500+self.tagsCarried*250);
}
bestZone = undefined;
foreach( zone in level.zoneList )
{
distSq = DistanceSquared( self.origin, zone.origin );
if ( distSq < bestDistSq )
{
bestDistSq = distSq;
bestZone = zone;
}
}
if ( IsDefined( bestZone ) )
{
// Head for this zone
set_new_goal = true;
if ( self.grind_waiting_to_bank )
{
// Already heading for a zone
if ( IsDefined( self.goal_zone ) && self.goal_zone == bestZone )
{
set_new_goal = false;
}
}
if ( set_new_goal )
{
self.grind_waiting_to_bank = true;
self.goal_zone = bestZone;
self BotClearScriptGoal();
self notify("stop_going_to_zone");
self notify("stop_camping_zone");
self.conf_camping_zone = false;
self clear_camper_data();
self bot_abort_tactical_goal( "kill_tag" );
self childthread bot_goto_zone( bestZone, "tactical" );
}
}
if ( self.grind_waiting_to_bank )
{
// Heading to a score zone
if ( game["teamScores"][self.team] + self.tagsCarried >= getWatchedDvar( "scorelimit" ) )
{
// My tags would win the game
// SPRINT!
self BotSetFlag( "force_sprint", true );
}
}
}
else if ( self.grind_waiting_to_bank )
{
self.grind_waiting_to_bank = false;
self.goal_zone = undefined;
self notify("stop_going_to_zone");
self BotClearScriptGoal();
}
// Also: If a camper personality and not camping a tag, camp the drop-off points
if ( self.personality == "camper" && !self.conf_camping_tag && !self.grind_waiting_to_bank )
{
bestDistSq = undefined;
bestZone = undefined;
foreach( zone in level.zoneList )
{
distSq = DistanceSquared( self.origin, zone.origin );
if ( !IsDefined( bestDistSq ) || distSq < bestDistSq )
{
bestDistSq = distSq;
bestZone = zone;
}
}
if ( IsDefined( bestZone ) )
{
// Camp this zone
if ( self should_select_new_ambush_point() )
{
if ( find_ambush_node( bestZone.origin ) )
{
self.conf_camping_zone = true;
self notify("stop_going_to_zone");
self.grind_waiting_to_bank = false;
self BotClearScriptGoal();
self childthread bot_camp_zone( bestZone, "camp" );
}
else
{
self notify("stop_camping_zone");
self.conf_camping_zone = false;
self clear_camper_data();
}
}
}
else
{
self.conf_camping_zone = true;
}
}
}
else
{
self notify("stop_going_to_zone");
self.grind_waiting_to_bank = false;
self.goal_zone = undefined;
self notify("stop_camping_zone");
self.conf_camping_zone = false;
}
return (self.grind_waiting_to_bank || self.conf_camping_zone );
}
bot_goto_zone( zone, goal_type )
{
self endon("stop_going_to_zone");
if ( !IsDefined( zone.calculated_nearest_node ) )
{
zone.nearest_node = GetClosestNodeInSight( zone.origin );
zone.calculated_nearest_node = true;
}
nearest_node_to_zone = zone.nearest_node;
self BotSetScriptGoal( nearest_node_to_zone.origin, 32, goal_type );
result = self bot_waittill_goal_or_fail();
}
bot_camp_zone( zone, goal_type )
{
self endon("stop_camping_zone");
self BotSetScriptGoalNode( self.node_ambushing_from, goal_type, self.ambush_yaw );
result = self bot_waittill_goal_or_fail();
if ( result == "goal" )
{
if ( !IsDefined( zone.calculated_nearest_node ) )
{
zone.nearest_node = GetClosestNodeInSight( zone.origin );
zone.calculated_nearest_node = true;
}
nearest_node_to_zone = zone.nearest_node;
if ( IsDefined( nearest_node_to_zone ) )
{
nodes_to_watch = FindEntrances( self.origin );
nodes_to_watch = array_add( nodes_to_watch, nearest_node_to_zone );
self childthread bot_watch_nodes( nodes_to_watch );
}
}
}
enemy_watcher()
{
self.default_meleeChargeDist = self BotGetDifficultySetting( "meleeChargeDist" );
while(1)
{
if ( self BotGetDifficultySetting( "strategyLevel" ) < 2 )
{
wait(0.5);
}
else
{
wait(0.2);
}
if ( IsDefined( self.enemy ) && IsPlayer( self.enemy ) && IsDefined( self.enemy.tagsCarried ) && self.enemy.tagsCarried >= 3 && self BotCanSeeEntity( self.enemy ) && Distance( self.origin, self.enemy.origin ) <= MAX_MELEE_CHARGE_DIST )
{
// We want to knife this guy, not shoot him
self BotSetDifficultySetting( "meleeChargeDist", MAX_MELEE_CHARGE_DIST );
self BotSetFlag( "prefer_melee", true );
}
else
{
// Treat him like normal
self BotSetDifficultySetting( "meleeChargeDist", self.default_meleeChargeDist );
self BotSetFlag( "prefer_melee", false );
}
}
}

View File

@ -0,0 +1,65 @@
#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;
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_grnd();
}
/#
empty_function_to_force_script_dev_compile() {}
#/
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_grnd_think;
}
setup_bot_grnd()
{
bot_waittill_bots_enabled( true );
level.protect_radius = 128;
level.bot_gametype_precaching_done = true;
}
bot_grnd_think()
{
self notify( "bot_grnd_think" );
self endon( "bot_grnd_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
while ( true )
{
wait(0.05);
if ( bot_has_tactical_goal() )
continue;
if ( !self BotHasScriptGoal() )
{
self BotSetScriptGoal( level.grnd_zone.origin, 0, "objective" );
}
else
{
if ( !bot_is_defending() )
{
self BotClearScriptGoal();
self bot_protect_point( level.grnd_zone.origin, level.protect_radius );
}
}
}
}

View File

@ -0,0 +1,14 @@
#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;
//=======================================================
// main
//=======================================================
main()
{
// nothing for now...
}

View File

@ -0,0 +1,14 @@
#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;
//=======================================================
// main
//=======================================================
main()
{
// nothing for now...
}

View File

@ -0,0 +1,330 @@
#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;
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;
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 );
self BotSetFlag( "path_traverse_wait", true ); // use manners when using traversals
// If an infected bot has been without his throwing knife for some time, magically give it back to him
while ( true )
{
if ( self HasWeapon( "throwingknife_mp" ) )
{
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( "throwingknife_mp" ) ) )
{
self SetWeaponAmmoClip( "throwingknife_mp", 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 );
}
}
}

View File

@ -0,0 +1,516 @@
#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;
MAX_TAG_PILE_TIME = 7500;
MAX_MELEE_CHARGE_DIST = 500;
MAX_TAG_AWARE_DIST_SQ = (350*350);
MAX_TAG_SIGHT_DIST_SQ = (1000*1000);
//=======================================================
// main
//=======================================================
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
level.bot_tag_obj_radius = 200;
setup_callbacks();
}
/#
empty_function_to_force_script_dev_compile() {}
#/
//=======================================================
// setup_callbacks
//=======================================================
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_mugger_think;
level.bot_funcs["gametype_loadout_modify"] = ::bot_mugger_loadout_modify;
}
//=======================================================
// bot_mugger_think
//=======================================================
bot_mugger_think()
{
self notify( "bot_mugger_think" );
self endon( "bot_mugger_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
self.last_killtag_tactical_goal_pos = (0,0,0);
self.tag_getting = undefined;
self.heading_for_tag_pile = false;
self.hiding_until_bank = false;
self.default_meleeChargeDist = self BotGetDifficultySetting( "meleeChargeDist" );
// Pick up tags when you can see them
self childthread tag_watcher();
if ( self BotGetDifficultySetting( "strategyLevel" ) > 0 )
{
self childthread tag_pile_watcher();
}
if ( self BotGetDifficultySetting( "strategyLevel" ) > 0 )
{
self childthread enemy_watcher();
}
// Mugger just performs normal personality logic for now
while ( true )
{
if ( self BotGetDifficultySetting( "strategyLevel" ) > 1 )
{
if ( IsDefined( self.tags_carried ) && level.mugger_bank_limit <= self.tags_carried )
{
// Getting ready to bank - hide!
if ( !self.hiding_until_bank )
{
hide_nodes = GetNodesInRadius( self.origin, 1000, 0, 500, "node_hide" );
best_hide_node = self BotNodePick( hide_nodes, 3, "node_hide" );
if ( IsDefined( best_hide_node ) )
{
self BotSetScriptGoalNode(best_hide_node, "critical");
self.hiding_until_bank = true;
}
}
}
else if ( self.hiding_until_bank )
{
self BotClearScriptGoal();
self.hiding_until_bank = false;
}
}
if ( !self.hiding_until_bank )
{
if ( !IsDefined( self.tag_getting ) && !self.heading_for_tag_pile )
{
self [[ self.personality_update_function ]]();
}
}
wait(0.05);
}
}
enemy_watcher()
{
while(1)
{
if ( self BotGetDifficultySetting( "strategyLevel" ) < 2 )
{
wait(0.5);
}
else
{
wait(0.2);
}
if ( IsDefined( self.enemy ) && IsPlayer( self.enemy ) && IsDefined( self.enemy.tags_carried ) && self.enemy.tags_carried >= 3 && self BotCanSeeEntity( self.enemy ) && Distance( self.origin, self.enemy.origin ) <= MAX_MELEE_CHARGE_DIST )
{
// We want to knife this guy, not shoot him
self BotSetDifficultySetting( "meleeChargeDist", MAX_MELEE_CHARGE_DIST );
self BotSetFlag( "prefer_melee", true );
self BotSetFlag( "throw_knife_melee", (level.mugger_throwing_knife_mug_frac > 0) );
}
else
{
// Treat him like normal
self BotSetDifficultySetting( "meleeChargeDist", self.default_meleeChargeDist );
self BotSetFlag( "prefer_melee", false );
self BotSetFlag( "throw_knife_melee", false );
}
}
}
tag_pile_watcher()
{
while(1)
{
// See if there's a large pile somewhere
level waittill( "mugger_tag_pile", pos );
if ( self.health <= 0 )
continue;
if ( self.hiding_until_bank )
continue;
if ( !IsDefined( self.last_tag_pile_time ) || GetTime() - self.last_tag_pile_time > MAX_TAG_PILE_TIME )
{
// The last one is so old, we might as well forget about it
self.last_tag_pile_time = undefined;
self.last_tag_pile_location = undefined;
self.heading_for_tag_pile = false;
}
if ( !IsDefined( self.last_tag_pile_location ) || DistanceSquared( self.origin, self.last_tag_pile_location ) > DistanceSquared( self.origin, pos ) )
{
// New pile is closer
self.last_tag_pile_time = GetTime();
self.last_tag_pile_location = pos;
}
}
}
bot_find_closest_tag()
{
nearest_node_self = self GetNearestNode();
best_tag = undefined;
if ( IsDefined( nearest_node_self ) )
{
best_dist_sq = MAX_TAG_SIGHT_DIST_SQ;
all_tags = array_combine( level.dogtags, level.mugger_extra_tags );
foreach( tag in all_tags )
{
if ( tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) )
{
distSq = DistanceSquared( self.origin, tag.curorigin );
if ( !IsDefined( best_tag ) || distSq < best_dist_sq )
{
if ( (self BotGetDifficultySetting( "strategyLevel" ) > 0 && distSq < MAX_TAG_AWARE_DIST_SQ) || (distSq < MAX_TAG_SIGHT_DIST_SQ && self maps\mp\bots\_bots_gametype_conf::bot_is_tag_visible( tag, nearest_node_self, self BotGetFovDot() )) )//bot_tag_is_visible( tag )) )
{
best_dist_sq = distSq;
best_tag = tag;
}
}
}
}
}
return best_tag;
}
bot_find_visible_tags_mugger( nearest_node_self, fov_self )
{
//NOTE: "self" COULD actually be a player, here...
visible_tags = [];
if ( IsDefined( nearest_node_self ) )
{
all_tags = array_combine( level.dogtags, level.mugger_extra_tags );
foreach( tag in all_tags )
{
if ( tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) )
{
if ( (IsPlayer( self ) || DistanceSquared( self.origin, tag.curorigin ) < MAX_TAG_SIGHT_DIST_SQ) )
{
if ( self maps\mp\bots\_bots_gametype_conf::bot_is_tag_visible( tag, nearest_node_self, fov_self ) )//bot_tag_is_visible( tag )) )
{
new_tag_struct = SpawnStruct();
new_tag_struct.origin = tag.curorigin;
new_tag_struct.tag = tag;
visible_tags[visible_tags.size] = new_tag_struct;
}
}
}
}
}
return visible_tags;
}
tag_watcher()
{
wait(RandomFloatRange(0,0.5));
while(1)
{
if ( self BotGetDifficultySetting( "strategyLevel" ) == 0 )
{
wait(3.0);
}
else if ( self BotGetDifficultySetting( "strategyLevel" ) == 1 )
{
wait(1.5);
}
else
{
wait(0.5);
}
if ( self.health <= 0 )
continue;
if ( self.hiding_until_bank )
continue;
if ( IsDefined( self.enemy ) && IsPlayer( self.enemy ) && self BotCanSeeEntity( self.enemy ) )
continue;
closest_tag = bot_find_closest_tag();
if ( IsDefined( closest_tag ) )
{
self mugger_pick_up_tag( closest_tag );
}
else if ( !self.heading_for_tag_pile )
{
// See if there's a large pile somewhere
if ( IsDefined( self.last_tag_pile_location ) && IsDefined( self.last_tag_pile_time ) && GetTime() - self.last_tag_pile_time <= MAX_TAG_PILE_TIME )
{
// The last one is so old, we might as well forget about it
self thread mugger_go_to_tag_pile( self.last_tag_pile_location );
}
}
}
}
mugger_go_to_tag_pile( pos )
{
self endon( "disconnect" );
level endon( "game_ended" );
self.heading_for_tag_pile = true;
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_pile", pos, 25, extra_params );
result = self waittill_any_return( "death", "tag_spotted" );
self BotClearScriptGoal();
self.heading_for_tag_pile = false;
self bot_abort_tactical_goal( "kill_tag_pile" );
}
mugger_pick_up_tag( tag )
{
self endon( "disconnect" );
level endon( "game_ended" );
self.tag_getting = tag;
self notify( "tag_spotted" );
self childthread notify_when_tag_picked_up(tag, "tag_picked_up");
// Set up a tactical goal
self bot_abort_tactical_goal( "kill_tag" );
tag_origin = tag.curorigin;
if ( bot_vectors_are_equal( self.last_killtag_tactical_goal_pos, tag.curorigin ) )
{
// Trying a tactical goal to exactly the same position as last time. This is probably because the bot couldn't calculate a path to the tag
// So we need to adjust the position a bit
nearest_node_to_tag = tag.nearest_node;
if ( IsDefined(nearest_node_to_tag) )
{
dir_to_nearest_node = nearest_node_to_tag.origin - tag_origin;
tag_origin = tag_origin + (VectorNormalize(dir_to_nearest_node) * Length(dir_to_nearest_node) * 0.5);
}
}
self.last_killtag_tactical_goal_pos = tag.curorigin;
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", tag_origin, 25, extra_params );
// Watch to detect if the tactical goal is aborted for some reason
self thread notify_when_tag_aborted( "tag_aborted" );
result = self waittill_any_return( "death", "tag_picked_up" );
self notify( "tag_watch_stop" );
self.tag_getting = undefined;
self BotClearScriptGoal();
self bot_abort_tactical_goal( "kill_tag" );
}
notify_when_tag_aborted( tag_notify )
{
self endon( "disconnect" );
level endon( "game_ended" );
self endon( "tag_watch_stop" );
while( self bot_has_tactical_goal( "kill_tag" ) )
{
wait(0.05);
}
self notify(tag_notify);
}
notify_when_tag_picked_up( tag, tag_notify )
{
self endon( "disconnect" );
level endon( "game_ended" );
self endon( "tag_watch_stop" );
while( tag maps\mp\gametypes\_gameobjects::canInteractWith(self.team) )
{
wait(0.05);
}
self notify(tag_notify);
}
bot_mugger_loadout_modify( loadoutValueArray )
{
chance = 0;
difficulty = self BotGetDifficulty();
if ( difficulty == "recruit" )
{
chance = 0.1;
}
else if ( difficulty == "regular" )
{
chance = 0.25;
}
else if ( difficulty == "hardened" )
{
chance = 0.6;
}
else if ( difficulty == "veteran" )
{
chance = 0.9;
}
has_throwing_knife = (loadoutValueArray["loadoutEquipment"] == "throwingknife_mp" );
if ( !has_throwing_knife )
{
if ( chance >= RandomFloat(1) )
{
// Throwing knife for long-distance mugging
loadoutValueArray["loadoutEquipment"] = "throwingknife_mp";
has_throwing_knife = true;
}
}
if ( chance >= RandomFloat(1) )
{
// Concussion grenade for slowing others down
if ( loadoutValueArray["loadoutOffhand"] != "concussion_grenade_mp" )
{
loadoutValueArray["loadoutOffhand"] = "concussion_grenade_mp";
}
}
// NOTE: it appears none of the weapons bots randomly choose are compatible with tactical knife attachments? Maybe force the issue?
if ( chance >= RandomFloat(1) )
{
// Tactical knife for faster knifing with primary
if ( loadoutValueArray["loadoutPrimaryAttachment"] != "tactical" && loadoutValueArray["loadoutPrimaryAttachment2"] != "tactical" )
{
valid = self maps\mp\bots\_bots_loadout::bot_validate_weapon( loadoutValueArray["loadoutPrimary"], loadoutValueArray["loadoutPrimaryAttachment"], "tactical" );
if ( valid )
{
loadoutValueArray["loadoutPrimaryAttachment2"] = "tactical";
}
else
{
valid = self maps\mp\bots\_bots_loadout::bot_validate_weapon( loadoutValueArray["loadoutPrimary"], "tactical", loadoutValueArray["loadoutPrimaryAttachment2"] );
if ( valid )
{
loadoutValueArray["loadoutPrimaryAttachment"] = "tactical";
}
}
}
}
if ( chance >= RandomFloat(1) )
{
// Tactical knife for faster knifing with secondary
if ( loadoutValueArray["loadoutSecondaryAttachment"] != "tactical" && loadoutValueArray["loadoutSecondaryAttachment2"] != "tactical" )
{
valid = self maps\mp\bots\_bots_loadout::bot_validate_weapon( loadoutValueArray["loadoutSecondary"], loadoutValueArray["loadoutSecondaryAttachment"], "tactical" );
if ( valid )
{
loadoutValueArray["loadoutSecondaryAttachment2"] = "tactical";
}
else
{
valid = self maps\mp\bots\_bots_loadout::bot_validate_weapon( loadoutValueArray["loadoutSecondary"], "tactical", loadoutValueArray["loadoutSecondaryAttachment2"] );
if ( valid )
{
loadoutValueArray["loadoutSecondaryAttachment"] = "tactical";
}
}
}
}
// Speed-related perks
perks = [];
available_perk_indices = [];
empty_perk_indices = [];
desired_perks = [];
if ( has_throwing_knife )
desired_perks[desired_perks.size] = "specialty_extra_deadly"; // Extra knife
desired_perks[desired_perks.size] = "specialty_lightweight"; // Move faster
desired_perks[desired_perks.size] = "specialty_marathon"; // Unlimited sprint
desired_perks[desired_perks.size] = "specialty_fastsprintrecovery"; // Aim fast after sprint
// NOTE: blood rush appears to have been removed from the Abilities UI...
//desired_perks[desired_perks.size] = "specialty_bloodrush"; // Move faster and health regen after a kill
desired_perks[desired_perks.size] = "specialty_stun_resistance"; // Less stun reaction
for( i = 1; i < 9; i++ )
{
if ( IsDefined( loadoutValueArray["loadoutPerk"+i] ) )
{
if ( loadoutValueArray["loadoutPerk"+i] != "none" )
{
perks[perks.size] = loadoutValueArray["loadoutPerk"+i];
available_perk_indices[available_perk_indices.size] = i;
}
else
{
empty_perk_indices[empty_perk_indices.size] = i;
}
}
}
foreach( des_perk in desired_perks )
{
if ( chance >= RandomFloat(1) )
{
if ( !array_contains( perks, des_perk ) )
{
index = -1;
if ( empty_perk_indices.size )
{
index = empty_perk_indices[0];
empty_perk_indices = array_remove( empty_perk_indices, index );
}
else if ( available_perk_indices.size )
{
index = random(available_perk_indices);
available_perk_indices = array_remove( available_perk_indices, index );
}
if ( index != -1 )
{
loadoutValueArray["loadoutPerk"+index] = des_perk;
}
}
}
}
// Jugger Maniac killstreak
if ( chance >= RandomFloat(1) )
{
if ( loadoutValueArray["loadoutStreakType"] == "streaktype_assault" && loadoutValueArray["loadoutStreak1"] != "airdrop_juggernaut_maniac" && loadoutValueArray["loadoutStreak2"] != "airdrop_juggernaut_maniac" && loadoutValueArray["loadoutStreak3"] != "airdrop_juggernaut_maniac" )
{
loadoutValueArray["loadoutStreak3"] = "airdrop_juggernaut_maniac";
}
}
return loadoutValueArray;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,169 @@
#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\bots\_bots_strategy;
main()
{
setup_callbacks();
thread bot_siege_manager_think();
setup_bot_siege();
/#
thread bot_siege_debug();
#/
}
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_siege_think;
}
setup_bot_siege()
{
level.bot_gametype_precaching_done = true;
}
/#
bot_siege_debug()
{
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
while(1)
{
if ( GetDvarInt("bot_debugSiege", 0) == 1 )
{
foreach ( player in level.participants )
{
if ( IsAI(player) && IsDefined( player.goalFlag ) && isReallyAlive(player) )
{
line( player.origin, player.goalFlag.origin, (0, 255, 0), 1, false, 1 );
}
}
}
wait(0.05);
}
}
#/
bot_siege_manager_think()
{
level.siege_bot_team_need_flags = [];
gameFlagWait( "prematch_done" );
for( ;; )
{
// Check for dead team mates
level.siege_bot_team_need_flags = [];
foreach( player in level.players )
{
if( !isReallyAlive( player ) && player.hasSpawned )
{
// If there are any dead team mates, we need to cap a flag.
if( player.team != "spectator" && player.team != "neutral" )
{
level.siege_bot_team_need_flags[ player.team ] = true;
}
}
}
// Check flag status
flagCounts = [];
foreach( flag in level.flags )
{
team = flag.useObj maps\mp\gametypes\_gameobjects::getOwnerTeam();
if( team != "neutral" )
{
if( !IsDefined( flagCounts[ team ] ) )
flagCounts[ team ] = 1;
else
flagCounts[ team ]++;
}
}
foreach( team, count in flagCounts )
{
if( count >= 2 )
{
// If we have 2 flags, let the enemy team know it needs to cap flags
enemyTeam = getOtherTeam( team );
level.siege_bot_team_need_flags[ enemyTeam ] = true;
}
}
wait( 1.0 );
}
}
bot_siege_think()
{
self notify( "bot_siege_think" );
self endon( "bot_siege_think" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
while( !IsDefined(level.bot_gametype_precaching_done) )
wait(0.05);
while( !IsDefined(level.siege_bot_team_need_flags) )
wait(0.05);
self BotSetFlag("separation",0); // don't slow down when we get close to other bots
self BotSetFlag("use_obj_path_style", true);
for( ;; )
{
if( IsDefined( level.siege_bot_team_need_flags[ self.team ] ) && level.siege_bot_team_need_flags[ self.team ] )
{
self bot_choose_flag();
}
else
{
if( IsDefined( self.goalFlag ) )
{
if( self maps\mp\bots\_bots_util::bot_is_defending() )
self bot_defend_stop();
self.goalFlag = undefined;
}
}
wait( 1.0 );
}
}
bot_choose_flag()
{
goalFlag = undefined;
shortestDistSq = undefined;
// Find the nearest flag not owned by the bot's team.
foreach( flag in level.flags )
{
team = flag.useObj maps\mp\gametypes\_gameobjects::getOwnerTeam();
if( team != self.team )
{
distToFlagSq = DistanceSquared( self.origin, flag.origin );
if( !IsDefined( shortestDistSq ) || distToFlagSq < shortestDistSq )
{
shortestDistSq = distToFlagSq;
goalFlag = flag;
}
}
}
// Assign the flag as the goal
if( IsDefined( goalFlag ) )
{
if( !IsDefined( self.goalFlag ) || self.goalFlag != goalFlag )
{
self.goalFlag = goalFlag;
bot_capture_point( goalFlag.origin, 100 );
}
}
}

View File

@ -0,0 +1,227 @@
#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;
//=======================================================
// main
//=======================================================
main()
{
// nothing for now...
setup_callbacks();
setup_bot_sotf();
}
setup_callbacks()
{
level.bot_funcs["dropped_weapon_think"] = ::sotf_bot_think_seek_dropped_weapons;
level.bot_funcs["dropped_weapon_cancel"] = ::sotf_should_stop_seeking_weapon;
level.bot_funcs["crate_low_ammo_check"] = ::sotf_crate_low_ammo_check;
level.bot_funcs["crate_should_claim"] = ::sotf_crate_should_claim;
level.bot_funcs["crate_wait_use"] = ::sotf_crate_wait_use;
level.bot_funcs["crate_in_range"] = ::sotf_crate_in_range;
level.bot_funcs["crate_can_use"] = ::sotf_crate_can_use;
}
setup_bot_sotf()
{
level.bots_gametype_handles_class_choice = true;
}
sotf_should_stop_seeking_weapon( goal )
{
// goal.object is the dropped weapon
if ( self bot_get_total_gun_ammo() > 0 )
{
myWeapClass = getWeaponClass( self GetCurrentWeapon() );
if ( IsDefined( goal.object ) )
{
dropped_weapon_name = goal.object.classname;
if(string_starts_with( dropped_weapon_name, "weapon_" ) )
dropped_weapon_name = getsubstr( dropped_weapon_name, 7 );
weapClass = getWeaponClass( dropped_weapon_name );
if ( !(self bot_weapon_is_better_class( myWeapClass, weapClass )) )
{
return true;
}
}
}
if ( !IsDefined( goal.object ) )
return true;
return false;
}
sotf_bot_think_seek_dropped_weapons()
{
self notify( "bot_think_seek_dropped_weapons" );
self endon( "bot_think_seek_dropped_weapons" );
self endon( "death" );
self endon( "disconnect" );
level endon( "game_ended" );
while( true )
{
still_seeking_weapon = false;
if ( self [[level.bot_funcs["should_pickup_weapons"]]]() && !self bot_is_remote_or_linked() )
{
if ( self bot_out_of_ammo() )
{
dropped_weapons = GetEntArray("dropped_weapon","targetname");
dropped_weapons_sorted = get_array_of_closest(self.origin,dropped_weapons);
if ( dropped_weapons_sorted.size > 0 )
{
dropped_weapon = dropped_weapons_sorted[0];
self maps\mp\bots\_bots::bot_seek_dropped_weapon( dropped_weapon );
}
}
else
{
//go through all visible dropped weapons - if see one better than mine, go pick it up
dropped_weapons = GetEntArray("dropped_weapon","targetname");
dropped_weapons_sorted = get_array_of_closest(self.origin,dropped_weapons);
if ( dropped_weapons_sorted.size > 0 )
{
// First check for the closest weapon the bot can see (ignoring current FOV)
nearest_node_bot = self GetNearestNode();
if ( IsDefined(nearest_node_bot) )
{
myWeapClass = getWeaponClass( self GetCurrentWeapon() );
foreach( dropped_weapon in dropped_weapons_sorted )
{
dropped_weapon_name = dropped_weapon.classname;
if(string_starts_with( dropped_weapon_name, "weapon_" ) )
dropped_weapon_name = getsubstr( dropped_weapon_name, 7 );
weapClass = getWeaponClass( dropped_weapon_name );
if ( self bot_weapon_is_better_class( myWeapClass, weapClass ) )
{
if ( !IsDefined( dropped_weapon.calculated_nearest_node ) || !dropped_weapon.calculated_nearest_node )
{
dropped_weapon.nearest_node = GetClosestNodeInSight(dropped_weapon.origin);
dropped_weapon.calculated_nearest_node = true;
}
if ( IsDefined( dropped_weapon.nearest_node ) && NodesVisible(nearest_node_bot, dropped_weapon.nearest_node, true) )
{
self maps\mp\bots\_bots::bot_seek_dropped_weapon( dropped_weapon );
break;
}
}
}
}
}
}
}
wait( RandomFloatRange(0.25, 0.75) );
}
}
bot_rank_weapon_class( weapClass )
{
weapRank = 0;
switch( weapClass )
{
// NOTE: these come from statstable.csv, which is where getWeaponClass looks up a weapon's class - NOT the class defined in the weapon's .gdt!
case "weapon_other":
case "weapon_projectile":
case "weapon_explosive":
case "weapon_grenade":
break;
case "weapon_pistol":
weapRank = 1;
break;
case "weapon_sniper":
case "weapon_dmr":
weapRank = 2;
break;
case "weapon_shotgun":
case "weapon_smg":
case "weapon_assault":
case "weapon_lmg":
weapRank = 3;
break;
}
return weapRank;
}
bot_weapon_is_better_class( oldWeapClass, newWeapClass )
{
oldWeapRank = bot_rank_weapon_class( oldWeapClass );
newWeapRank = bot_rank_weapon_class( newWeapClass );
return (newWeapRank > oldWeapRank);
}
sotf_crate_low_ammo_check()
{
// Ammo is extremely sparse in SOTF
// Only consider ourselves low on ammo when we are running out of our last clip
myWeapon = self GetCurrentWeapon();
ammoInClip = self GetWeaponAmmoClip( myWeapon );
ammoInStock = self GetWeaponAmmoStock( myWeapon );
maxClipSize = WeaponClipSize( myWeapon );
return (ammoInClip + ammoInStock) < (maxClipSize * 0.25);
}
sotf_crate_should_claim()
{
return false;
}
sotf_crate_wait_use()
{
// clear the area before trying to use the crate
bot_waittill_out_of_combat_or_time(5000);
}
sotf_crate_in_range( crate )
{
return true;
}
sotf_crate_can_use( crate )
{
if ( maps\mp\bots\_bots::crate_can_use_always( crate ) )
{
if ( IsDefined( crate ) && IsDefined( crate.bots_used ) && array_contains( crate.bots_used, self ) )
{
// Only use a crate once unless we are out of ammo
if ( self bot_out_of_ammo() )
return true;
else
return false;
}
// If we only have a pistol or worse
myWeapClass = getWeaponClass( self GetCurrentWeapon() );
if ( bot_rank_weapon_class( myWeapClass ) <= 1 )
return true;
// If we are getting pretty low on ammo
if ( self sotf_crate_low_ammo_check() )
return true;
// Otherwise just keep using what we have
return false;
}
return false;
}

View File

@ -0,0 +1,16 @@
#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;
//=======================================================
// main
//=======================================================
main()
{
maps\mp\bots\_bots_gametype_sotf::setup_callbacks();
maps\mp\bots\_bots_gametype_sotf::setup_bot_sotf();
}

View File

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

View File

@ -0,0 +1,169 @@
#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_fireteam;
//=======================================================
// main
//=======================================================
main()
{
// This is called directly from native code on game startup after the _bots::main() is executed
setup_callbacks();
setup_bot_war();
}
//=======================================================
// setup_callbacks
//=======================================================
setup_callbacks()
{
level.bot_funcs["gametype_think"] = ::bot_war_think;
level.bot_funcs["commander_gametype_tactics"] = ::bot_tdm_apply_commander_tactics;
}
setup_bot_war()
{
if( bot_is_fireteam_mode() )
{
level.bot_team_tdm_personality = "default";
level.bot_fireteam_buddy_up = false;
}
}
//=======================================================
// bot_war_think
//=======================================================
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" );
// TDM just performs normal personality logic
while ( true )
{
self [[ self.personality_update_function ]]();
wait(0.05);
}
}
//=======================================================
// bot_tdm_apply_commander_tactics( new_tactic )
//=======================================================
bot_tdm_apply_commander_tactics( new_tactic )
{
reset_all_bots = false;
was_buddied = level.bot_fireteam_buddy_up;
hunting_party = false;
level.bot_fireteam_buddy_up = false;
switch( new_tactic )
{
case "tactic_none":
level.bot_team_tdm_personality = "revert";
reset_all_bots = true;
break;
case "tactic_war_hp"://hunting party
level.bot_team_tdm_personality = "revert";
level thread fireteam_tdm_find_hunt_zone( self.team );
hunting_party = true;
reset_all_bots = true;
break;
case "tactic_war_buddy"://buddy system - 3 two-man teams
level.bot_team_tdm_personality = "revert";
level.bot_fireteam_buddy_up = true;
reset_all_bots = true;
break;
case "tactic_war_hyg"://hold your ground
level.bot_team_tdm_personality = "camper";
reset_all_bots = true;
break;
}
if ( !hunting_party )
{
level fireteam_tdm_hunt_end(self.team);
}
if ( reset_all_bots )
{
foreach( player in level.players )
{
if ( !IsDefined( player.team ) )
continue;
if ( IsBot(player) && player.team == self.team )
{
player BotSetFlag( "force_sprint", false );
if ( level.bot_team_tdm_personality == "revert" )
{
if ( IsDefined( player.fireteam_personality_original ) )
{
player notify( "stop_camping_tag" );
player clear_camper_data();
player bot_set_personality( player.fireteam_personality_original );
player.can_camp_near_others = undefined;
player.camping_needs_fallback_camp_location = undefined;
}
}
else
{
if ( !IsDefined( player.fireteam_personality_original ) )
{
player.fireteam_personality_original = player BotGetPersonality();
}
player notify( "stop_camping_tag" );
player clear_camper_data();
player bot_set_personality( level.bot_team_tdm_personality );
if ( level.bot_team_tdm_personality == "camper" )
{
player.can_camp_near_others = true;
player.camping_needs_fallback_camp_location = true;
}
}
}
}
}
if ( level.bot_fireteam_buddy_up )
{
foreach( player in level.players )
{
if ( !IsDefined( player.team ) )
continue;
if ( player.team == self.team )
{
if ( IsBot( player ) )
player thread bot_fireteam_buddy_search();
}
}
}
else if ( was_buddied )
{
foreach( player in level.players )
{
if ( !IsDefined( player.team ) )
continue;
if ( player.team == self.team )
{
if ( IsBot( player ) )
{
player.owner = undefined;
player.bot_fireteam_follower = undefined;
player notify( "buddy_cancel" );
player bot_assign_personality_functions();
}
}
}
}
}

885
maps/mp/bots/_bots_ks.gsc Normal file
View File

@ -0,0 +1,885 @@
#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()
{
if ( !IsDefined( level.killstreak_botfunc ) )
{
// ======================================================================================
// Selectable Killstreaks ===============================================================
// Simple Use ===========================================================================
bot_register_killstreak_func( "ball_drone_backup", ::bot_killstreak_simple_use, ::bot_can_use_ball_drone );
bot_register_killstreak_func( "ball_drone_radar", ::bot_killstreak_simple_use, ::bot_can_use_ball_drone );
bot_register_killstreak_func( "guard_dog", ::bot_killstreak_simple_use );
bot_register_killstreak_func( "recon_agent", ::bot_killstreak_simple_use );
bot_register_killstreak_func( "agent", ::bot_killstreak_simple_use );
bot_register_killstreak_func( "nuke", ::bot_killstreak_simple_use );
bot_register_killstreak_func( "jammer", ::bot_killstreak_simple_use, ::bot_can_use_emp );
bot_register_killstreak_func( "air_superiority", ::bot_killstreak_simple_use, ::bot_can_use_air_superiority );
bot_register_killstreak_func( "helicopter", ::bot_killstreak_simple_use, ::aerial_vehicle_allowed );
bot_register_killstreak_func( "specialist", ::bot_killstreak_simple_use );
bot_register_killstreak_func( "all_perks_bonus", ::bot_killstreak_simple_use );
// Airdrop / Drop =======================================================================
bot_register_killstreak_func( "airdrop_juggernaut", ::bot_killstreak_drop_outside );
bot_register_killstreak_func( "airdrop_juggernaut_maniac", ::bot_killstreak_drop_outside );
bot_register_killstreak_func( "airdrop_juggernaut_recon", ::bot_killstreak_drop_outside );
bot_register_killstreak_func( "uav_3dping", ::bot_killstreak_drop_outside );
bot_register_killstreak_func( "deployable_vest", ::bot_killstreak_drop_anywhere );
bot_register_killstreak_func( "deployable_ammo", ::bot_killstreak_drop_anywhere );
// Remote Control =======================================================================
bot_register_killstreak_func( "odin_assault", ::bot_killstreak_remote_control, ::aerial_vehicle_allowed, ::bot_control_odin_assault );
bot_register_killstreak_func( "odin_support", ::bot_killstreak_remote_control, ::aerial_vehicle_allowed, ::bot_control_odin_support );
bot_register_killstreak_func( "heli_pilot", ::bot_killstreak_remote_control, ::heli_pilot_allowed, ::bot_control_heli_pilot );
bot_register_killstreak_func( "heli_sniper", ::bot_killstreak_remote_control, ::heli_sniper_allowed, ::bot_control_heli_sniper );
bot_register_killstreak_func( "drone_hive", ::bot_killstreak_remote_control, undefined, ::bot_control_switchblade_cluster );
bot_register_killstreak_func( "vanguard", ::bot_killstreak_vanguard_start, ::vanguard_allowed, ::bot_control_vanguard );
// Placeable ============================================================================
bot_register_killstreak_func( "ims", ::bot_killstreak_sentry, undefined, "trap" );
bot_register_killstreak_func( "sentry", ::bot_killstreak_sentry, undefined, "turret" );
bot_register_killstreak_func( "uplink", ::bot_killstreak_sentry, undefined, "hide_nonlethal" );
bot_register_killstreak_func( "uplink_support", ::bot_killstreak_sentry, undefined, "hide_nonlethal" );
// Weapons ==============================================================================
bot_register_killstreak_func( "aa_launcher", ::bot_killstreak_never_use, ::bot_can_use_aa_launcher );
// ======================================================================================
// Other Killstreaks (functional but not picked in botTemplateTable.csv) ================
bot_register_killstreak_func( "airdrop_assault", ::bot_killstreak_drop_outside );
if(IsDefined(level.mapCustomBotKillstreakFunc))
[[ level.mapCustomBotKillstreakFunc ]]();
// ======================================================================================
// No Longer Supported (killstreaks that have been removed from the game) ===============
/*
bot_register_killstreak_func( "stealth_airstrike", ::bot_killstreak_choose_loc_enemies );
bot_register_killstreak_func( "sam_turret", ::bot_killstreak_sentry, undefined, "turret_air" );
bot_register_killstreak_func( "deployable_juicebox", ::bot_killstreak_drop_anywhere );
bot_register_killstreak_func( "deployable_grenades", ::bot_killstreak_drop_anywhere );
bot_register_killstreak_func( "emp", ::bot_killstreak_simple_use, ::bot_can_use_emp );
bot_register_killstreak_func( "helicopter_flares", ::bot_killstreak_simple_use, ::aerial_vehicle_allowed );
bot_register_killstreak_func( "precision_airstrike", ::bot_killstreak_choose_loc_enemies );
bot_register_killstreak_func( "high_value_target", ::bot_killstreak_simple_use, ::bot_can_use_hvt );
*/
/#
if ( !is_aliens() )
{
bot_validate_killstreak_funcs();
}
#/
}
thread remote_vehicle_setup();
}
//========================================================
// 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_killstreak_valid_for_specific_streakType(streakName, streakType, assertIt)
{
if ( bot_is_fireteam_mode() )
{
// Disable asserts in fireteam for now
return true;
}
// 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 a bot with the given streakType could use it
if ( bot_killstreak_is_valid_internal(streakName, "bots", undefined, streakType) )
{
return true;
}
else if ( assertIt )
{
AssertMsg( "Bots with streakType <" + streakType + "> do not support killstreak <" + streakName + ">" );
}
return false;
}
bot_killstreak_is_valid_internal(streakName, who_to_check, optional_bot, optional_streak_type)
{
streakTypeSubStr = undefined;
if ( streakName == "specialist" )
return true;
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;
}
if ( IsDefined( optional_streak_type ) )
{
// "loadoutStreakType" will be streaktype_assault, etc, so remove first 11 chars
streakTypeSubStr = GetSubStr( optional_streak_type, 11);
switch ( streakTypeSubStr )
{
case "assault":
if ( !isAssaultKillstreak( streakName ) )
return false;
break;
case "support":
if ( !isSupportKillstreak( streakName ) )
return false;
break;
case "specialist":
if ( !isSpecialistKillstreak( streakName ) )
return false;
break;
}
}
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;
// all_perks_bonus is awarded still but does nothing and cannot be used - should be removed from game
if ( killstreak_info.streakName == "all_perks_bonus" )
continue;
// Specialist killstreaks need to be handled by one handler type
if ( isSpecialistKillstreak( killstreak_info.streakName ) )
{
if ( !killstreak_info.earned )
tableName = "specialist";
else
continue;
}
killstreak_info.weapon = getKillstreakWeapon( killstreak_info.streakName );
can_use_killstreak_function = level.killstreak_botcanuse[ tableName ];
if ( IsDefined(can_use_killstreak_function) && !self [[ can_use_killstreak_function ]]() )
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( 1.0, 2.0 ) );
}
}
bot_can_use_aa_launcher()
{
// Bots don't use the AA Launcher like any other killstreak, rather it sits in their weapon list and code can select it as a weapon
return false;
}
bot_start_aa_launcher_tracking()
{
aaLauncherName = maps\mp\killstreaks\_AALauncher::getAALauncherName();
while ( 1 )
{
self waittill( "aa_launcher_fire" );
ammo_left = self GetAmmoCount( aaLauncherName );
if ( ammo_left == 0 )
{
// For bots this forces this as the script weapon, so they'll continue aiming even while empty
self SwitchToWeapon( aaLauncherName );
// Now wait till the last missiles are destroyed or the enemy dies
result = self waittill_any_return( "LGM_player_allMissilesDestroyed", "enemy" );
// Wait a tiny bit longer so it doesn't seem robotic to immediately switch away on missile impact
wait(0.5);
self SwitchToWeapon( "none" );
}
}
}
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_can_use_air_superiority()
{
if ( !self aerial_vehicle_allowed() )
return false;
possible_targets = maps\mp\killstreaks\_air_superiority::findAllTargets( self, self.team );
cur_time = GetTime();
foreach( target in possible_targets )
{
if ( cur_time - target.birthtime > 5000 )
return true;
}
return false;
}
aerial_vehicle_allowed()
{
if ( self isAirDenied() )
return false;
if ( vehicle_would_exceed_limit() )
return false;
return true;
}
vehicle_would_exceed_limit()
{
return ( currentActiveVehicleCount() >= maxVehiclesAllowed() || level.fauxVehicleCount + 1 >= maxVehiclesAllowed() );
}
/*
bot_can_use_hvt()
{
return !(self maps\mp\killstreaks\_highValueTarget::reached_max_xp_multiplier());
}
*/
bot_can_use_emp()
{
// 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_ball_drone()
{
if ( self isUsingRemote() )
return false;
if ( maps\mp\killstreaks\_ball_drone::exceededMaxBallDrones() )
return false;
if ( vehicle_would_exceed_limit() )
return false;
if ( IsDefined( self.ballDrone ) )
return false;
return true;
}
//========================================================
// bot_killstreak_simple_use
//========================================================
bot_killstreak_simple_use( killstreak_info, killstreaks_array, canUseFunc, optional_param )
{
self endon( "commander_took_over" );
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]]() )
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 )
{
self endon( "commander_took_over" );
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]]() )
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( 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 )
{
self endon( "commander_took_over" );
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;
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);
self notify( "confirm_location", zoneCenter + randomOffset, RandomIntRange(0, 360) );
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;
level.killstreak_global_bp_exists_for["allies"] = [];
level.killstreak_global_bp_exists_for["axis"] = [];
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);
//Assert(!IsDefined(level.harriers) || level.harriers.size == 0); // Removed support for harriers, assert that they will never exist
Assert(!IsDefined(level.remote_mortar)); // Removed support for Reaper, assert that it will never exist
// AC130 has been added to mp_favela_iw6 as map specific killstreak
//Assert(!IsDefined(level.ac130InUse) || !level.ac130InUse); // Removed support for AC130, assert that it will never exist
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 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[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 );
}
}
}
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;
}
enemy_mortar_strike_exists( my_team )
{
if ( IsDefined(level.air_raid_active) && level.air_raid_active )
{
if ( my_team != level.air_raid_team_called )
return true;
}
return false;
}
enemy_switchblade_exists( my_team )
{
if ( IsDefined(level.remoteMissileInProgress) )
{
foreach ( rocket in level.rockets )
{
if ( IsDefined(rocket.type) && rocket.type == "remote" && rocket.team != my_team )
return true;
}
}
return false;
}
enemy_odin_assault_exists( my_team )
{
foreach( player in level.players )
{
if ( !level.teamBased || (IsDefined(player.team) && my_team != player.team) )
{
if ( IsDefined(player.odin) && player.odin.odinType == "odin_assault" && GetTime() - player.odin.birthtime > 3000 )
return true;
}
}
return false;
}
get_enemy_vanguard()
{
foreach( player in level.players )
{
if ( !level.teamBased || (IsDefined(player.team) && self.team != player.team) )
{
if ( IsDefined(player.remoteUAV) && player.remoteUAV.helitype == "remote_uav" )
return player.remoteUAV;
}
}
return undefined;
}
/*
=============
///ScriptDocBegin
"Name: isKillstreakBlockedForBots()"
"Summary: checks to see if bots can use this killstreak on this level. In rare cases with bugs, we may not want bots to use a killstreak. e.g. in mp_swamp, no vanguard because we don't have enough heli nodes to look good.
"Module: bots_ks"
"Example: if (isKillstreakBlockedForBots( "vanguard")"
"SPMP: Multiplayer"
///ScriptDocEnd
=============
*/
// 2014-01-16 wallace: Only vanguard supports this check right now!
isKillstreakBlockedForBots( ksName )
{
return ( IsDefined( level.botBlockedKillstreaks ) && IsDefined( level.botBlockedKillstreaks[ksName] ) && level.botBlockedKillstreaks[ksName] );
}
blockKillstreakForBots( ksName )
{
level.botBlockedKillstreaks[ ksName ] = true;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,902 @@
// 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 2 active bots (run and gun, cqb, etc) for every 1 camper-type bot
level.bot_personality_types_desired = [];
level.bot_personality_types_desired["active"] = 2;
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.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 ( bot_is_fireteam_mode() )
return;
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.players )
{
if ( IsBot(bot) && IsDefined(bot.team) && (bot.team == self.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 );
}
}
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 ( !(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_queued_process( "bot_find_ambush_entrances", ::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" );
}
}
}
}
//=======================================================
// 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 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++ )
{
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) )
{
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 )
{
// Only want to pick from the best of the best
selectCount = int( min( max( 1, nodes_to_select_from.size * 0.15 ), 5 ) );
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 ( self bot_setup_loadout_callback() )
return "callback";
else
return "class0";
}

View File

@ -0,0 +1,360 @@
#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_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.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_ims" );
self notify( "placePlaceable" );
}
bot_send_cancel_notify()
{
self SwitchToWeapon( "none" );
self enableWeapons();
self enableWeaponSwitch();
self notify( "cancel_sentry" );
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.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.carriedIMS = undefined;
self.carriedItem = undefined;
self SwitchToWeapon( "none" );
self enableWeapons();
self enableWeaponSwitch();
}

File diff suppressed because it is too large Load Diff

2600
maps/mp/bots/_bots_util.gsc Normal file

File diff suppressed because it is too large Load Diff