init
This commit is contained in:
3765
maps/mp/bots/_bots.gsc
Normal file
3765
maps/mp/bots/_bots.gsc
Normal file
File diff suppressed because it is too large
Load Diff
729
maps/mp/bots/_bots_fireteam.gsc
Normal file
729
maps/mp/bots/_bots_fireteam.gsc
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1439
maps/mp/bots/_bots_fireteam_commander.gsc
Normal file
1439
maps/mp/bots/_bots_fireteam_commander.gsc
Normal file
File diff suppressed because it is too large
Load Diff
14
maps/mp/bots/_bots_gametype_aliens.gsc
Normal file
14
maps/mp/bots/_bots_gametype_aliens.gsc
Normal 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...
|
||||
}
|
302
maps/mp/bots/_bots_gametype_blitz.gsc
Normal file
302
maps/mp/bots/_bots_gametype_blitz.gsc
Normal 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;
|
||||
}
|
558
maps/mp/bots/_bots_gametype_conf.gsc
Normal file
558
maps/mp/bots/_bots_gametype_conf.gsc
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
18
maps/mp/bots/_bots_gametype_cranked.gsc
Normal file
18
maps/mp/bots/_bots_gametype_cranked.gsc
Normal 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;
|
||||
}
|
14
maps/mp/bots/_bots_gametype_dm.gsc
Normal file
14
maps/mp/bots/_bots_gametype_dm.gsc
Normal 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...
|
||||
}
|
1415
maps/mp/bots/_bots_gametype_dom.gsc
Normal file
1415
maps/mp/bots/_bots_gametype_dom.gsc
Normal file
File diff suppressed because it is too large
Load Diff
259
maps/mp/bots/_bots_gametype_grind.gsc
Normal file
259
maps/mp/bots/_bots_gametype_grind.gsc
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
65
maps/mp/bots/_bots_gametype_grnd.gsc
Normal file
65
maps/mp/bots/_bots_gametype_grnd.gsc
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
maps/mp/bots/_bots_gametype_gun.gsc
Normal file
14
maps/mp/bots/_bots_gametype_gun.gsc
Normal 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...
|
||||
}
|
14
maps/mp/bots/_bots_gametype_horde.gsc
Normal file
14
maps/mp/bots/_bots_gametype_horde.gsc
Normal 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...
|
||||
}
|
330
maps/mp/bots/_bots_gametype_infect.gsc
Normal file
330
maps/mp/bots/_bots_gametype_infect.gsc
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
516
maps/mp/bots/_bots_gametype_mugger.gsc
Normal file
516
maps/mp/bots/_bots_gametype_mugger.gsc
Normal 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;
|
||||
}
|
1505
maps/mp/bots/_bots_gametype_sd.gsc
Normal file
1505
maps/mp/bots/_bots_gametype_sd.gsc
Normal file
File diff suppressed because it is too large
Load Diff
169
maps/mp/bots/_bots_gametype_siege.gsc
Normal file
169
maps/mp/bots/_bots_gametype_siege.gsc
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
227
maps/mp/bots/_bots_gametype_sotf.gsc
Normal file
227
maps/mp/bots/_bots_gametype_sotf.gsc
Normal 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;
|
||||
}
|
||||
|
16
maps/mp/bots/_bots_gametype_sotf_ffa.gsc
Normal file
16
maps/mp/bots/_bots_gametype_sotf_ffa.gsc
Normal 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();
|
||||
}
|
||||
|
318
maps/mp/bots/_bots_gametype_sr.gsc
Normal file
318
maps/mp/bots/_bots_gametype_sr.gsc
Normal 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;
|
||||
}
|
169
maps/mp/bots/_bots_gametype_war.gsc
Normal file
169
maps/mp/bots/_bots_gametype_war.gsc
Normal 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
885
maps/mp/bots/_bots_ks.gsc
Normal 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;
|
||||
}
|
2432
maps/mp/bots/_bots_ks_remote_vehicle.gsc
Normal file
2432
maps/mp/bots/_bots_ks_remote_vehicle.gsc
Normal file
File diff suppressed because it is too large
Load Diff
1563
maps/mp/bots/_bots_loadout.gsc
Normal file
1563
maps/mp/bots/_bots_loadout.gsc
Normal file
File diff suppressed because it is too large
Load Diff
902
maps/mp/bots/_bots_personality.gsc
Normal file
902
maps/mp/bots/_bots_personality.gsc
Normal 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";
|
||||
}
|
360
maps/mp/bots/_bots_sentry.gsc
Normal file
360
maps/mp/bots/_bots_sentry.gsc
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
|
2233
maps/mp/bots/_bots_strategy.gsc
Normal file
2233
maps/mp/bots/_bots_strategy.gsc
Normal file
File diff suppressed because it is too large
Load Diff
2600
maps/mp/bots/_bots_util.gsc
Normal file
2600
maps/mp/bots/_bots_util.gsc
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user