2023-03-13 04:39:59 -04:00
|
|
|
#using scripts\codescripts\struct;
|
2023-04-20 20:51:05 -04:00
|
|
|
|
2023-03-13 04:39:59 -04:00
|
|
|
#using scripts\shared\array_shared;
|
|
|
|
#using scripts\shared\callbacks_shared;
|
|
|
|
#using scripts\shared\killstreaks_shared;
|
|
|
|
#using scripts\shared\math_shared;
|
|
|
|
#using scripts\shared\rank_shared;
|
|
|
|
#using scripts\shared\system_shared;
|
|
|
|
#using scripts\shared\util_shared;
|
|
|
|
#using scripts\shared\weapons_shared;
|
2023-04-20 20:51:05 -04:00
|
|
|
|
|
|
|
#using scripts\shared\weapons\_weapons;
|
2023-03-13 04:39:59 -04:00
|
|
|
|
|
|
|
#using scripts\shared\bots\_bot;
|
|
|
|
#using scripts\shared\bots\_bot_combat;
|
|
|
|
#using scripts\shared\bots\bot_buttons;
|
2023-04-20 20:51:05 -04:00
|
|
|
#using scripts\shared\bots\bot_traversals;
|
|
|
|
|
2023-03-13 04:39:59 -04:00
|
|
|
#using scripts\mp\bots\_bot_ball;
|
|
|
|
#using scripts\mp\bots\_bot_clean;
|
|
|
|
#using scripts\mp\bots\_bot_combat;
|
|
|
|
#using scripts\mp\bots\_bot_conf;
|
|
|
|
#using scripts\mp\bots\_bot_ctf;
|
|
|
|
#using scripts\mp\bots\_bot_dem;
|
|
|
|
#using scripts\mp\bots\_bot_dom;
|
|
|
|
#using scripts\mp\bots\_bot_escort;
|
|
|
|
#using scripts\mp\bots\_bot_hq;
|
|
|
|
#using scripts\mp\bots\_bot_koth;
|
|
|
|
#using scripts\mp\bots\_bot_loadout;
|
|
|
|
#using scripts\mp\bots\_bot_sd;
|
2023-04-20 20:48:03 -04:00
|
|
|
|
2023-04-20 20:51:05 -04:00
|
|
|
#using scripts\mp\killstreaks\_ai_tank;
|
|
|
|
#using scripts\mp\killstreaks\_airsupport;
|
|
|
|
#using scripts\mp\killstreaks\_combat_robot;
|
|
|
|
#using scripts\mp\killstreaks\_counteruav;
|
|
|
|
#using scripts\mp\killstreaks\_dart;
|
|
|
|
#using scripts\mp\killstreaks\_dogs;
|
|
|
|
#using scripts\mp\killstreaks\_drone_strike;
|
|
|
|
#using scripts\mp\killstreaks\_emp;
|
|
|
|
#using scripts\mp\killstreaks\_flak_drone;
|
|
|
|
#using scripts\mp\killstreaks\_helicopter;
|
|
|
|
#using scripts\mp\killstreaks\_helicopter_gunner;
|
|
|
|
#using scripts\mp\killstreaks\_killstreak_bundles;
|
|
|
|
#using scripts\mp\killstreaks\_killstreak_detect;
|
|
|
|
#using scripts\mp\killstreaks\_killstreak_hacking;
|
2023-03-13 04:39:59 -04:00
|
|
|
#using scripts\mp\killstreaks\_killstreakrules;
|
|
|
|
#using scripts\mp\killstreaks\_killstreaks;
|
2023-04-20 20:51:05 -04:00
|
|
|
#using scripts\mp\killstreaks\_microwave_turret;
|
|
|
|
#using scripts\mp\killstreaks\_planemortar;
|
|
|
|
#using scripts\mp\killstreaks\_qrdrone;
|
|
|
|
#using scripts\mp\killstreaks\_raps;
|
|
|
|
#using scripts\mp\killstreaks\_rcbomb;
|
|
|
|
#using scripts\mp\killstreaks\_remote_weapons;
|
|
|
|
#using scripts\mp\killstreaks\_remotemissile;
|
2023-04-20 20:48:03 -04:00
|
|
|
#using scripts\mp\killstreaks\_satellite;
|
2023-04-20 20:51:05 -04:00
|
|
|
#using scripts\mp\killstreaks\_sentinel;
|
|
|
|
#using scripts\mp\killstreaks\_supplydrop;
|
|
|
|
#using scripts\mp\killstreaks\_turret;
|
|
|
|
#using scripts\mp\killstreaks\_uav;
|
|
|
|
|
2023-03-13 04:39:59 -04:00
|
|
|
#using scripts\mp\teams\_teams;
|
|
|
|
#using scripts\mp\_util;
|
|
|
|
|
|
|
|
#insert scripts\shared\shared.gsh;
|
|
|
|
#insert scripts\mp\bots\_bot.gsh;
|
|
|
|
|
|
|
|
#define MAX_LOCAL_PLAYERS 10
|
|
|
|
#define MAX_ONLINE_PLAYERS 18
|
|
|
|
#define MAX_ONLINE_PLAYERS_PER_TEAM 6
|
|
|
|
|
2023-04-20 20:48:03 -04:00
|
|
|
#define RESPAWN_DELAY 0.1
|
|
|
|
#define RESPAWN_INTERVAL 0.1
|
|
|
|
|
2023-03-13 04:39:59 -04:00
|
|
|
#namespace bot;
|
|
|
|
|
|
|
|
REGISTER_SYSTEM( "bot_mp", &__init__, undefined )
|
2023-04-20 20:48:03 -04:00
|
|
|
|
2023-03-13 04:39:59 -04:00
|
|
|
function __init__()
|
|
|
|
{
|
|
|
|
callback::on_start_gametype( &init );
|
|
|
|
|
|
|
|
level.getBotSettings = &get_bot_settings;
|
|
|
|
|
|
|
|
level.onBotConnect = &on_bot_connect;
|
|
|
|
level.onBotSpawned = &on_bot_spawned;
|
|
|
|
level.onBotKilled = &on_bot_killed;
|
|
|
|
|
|
|
|
level.botIdle = &bot_idle;
|
|
|
|
|
|
|
|
level.botThreatLost = &bot_combat::chase_threat;
|
|
|
|
|
|
|
|
level.botPreCombat = &bot_combat::mp_pre_combat;
|
|
|
|
level.botCombat = &bot_combat::combat_think;
|
|
|
|
level.botPostCombat = &bot_combat::mp_post_combat;
|
|
|
|
|
|
|
|
level.botIgnoreThreat = &bot_combat::bot_ignore_threat;
|
|
|
|
|
|
|
|
level.enemyEmpActive = &emp::EnemyEmpActive;
|
2023-04-20 20:48:03 -04:00
|
|
|
|
|
|
|
/#
|
|
|
|
level.botDevguiCmd = &bot_devgui_cmd;
|
|
|
|
level thread system_devgui_gadget_think();
|
|
|
|
#/
|
2023-03-13 04:39:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function init()
|
|
|
|
{
|
|
|
|
level endon( "game_ended" );
|
|
|
|
|
|
|
|
level.botSoak = is_bot_soak();
|
2023-04-20 20:48:03 -04:00
|
|
|
|
|
|
|
if ( ( level.rankedMatch && !level.botSoak ) || !init_bot_gametype() )
|
2023-03-14 08:47:06 -04:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2023-04-20 20:48:03 -04:00
|
|
|
|
2023-03-13 04:39:59 -04:00
|
|
|
wait_for_host();
|
|
|
|
|
|
|
|
level thread populate_bots();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init Utils
|
|
|
|
//========================================
|
|
|
|
|
|
|
|
function is_bot_soak()
|
|
|
|
{
|
2023-04-20 20:48:03 -04:00
|
|
|
/*
|
|
|
|
return GetDvarInt( "sv_botsoak", 0 );
|
|
|
|
*/
|
2023-03-13 04:39:59 -04:00
|
|
|
return IsDedicated() && GetDvarInt( "sv_botsoak", 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
function wait_for_host()
|
|
|
|
{
|
|
|
|
level endon( "game_ended" );
|
|
|
|
|
|
|
|
if ( level.botSoak )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
host = util::getHostPlayerForBots();
|
|
|
|
|
|
|
|
while ( !isdefined( host ) )
|
|
|
|
{
|
|
|
|
wait( 0.25 );
|
|
|
|
host = util::getHostPlayerForBots();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_host_team()
|
|
|
|
{
|
|
|
|
host = util::getHostPlayerForBots();
|
|
|
|
|
|
|
|
if ( !isdefined( host ) || host.team == "spectator" )
|
|
|
|
{
|
|
|
|
return "allies";
|
|
|
|
}
|
|
|
|
|
|
|
|
return host.team;
|
|
|
|
}
|
|
|
|
|
|
|
|
function is_bot_comp_stomp()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Bot Events
|
|
|
|
//========================================
|
|
|
|
|
|
|
|
function on_bot_connect()
|
|
|
|
{
|
|
|
|
self endon( "disconnect" );
|
|
|
|
level endon( "game_ended" );
|
|
|
|
|
|
|
|
if ( IS_TRUE( level.disableClassSelection ) )
|
|
|
|
{
|
|
|
|
self set_rank();
|
|
|
|
|
|
|
|
// Doesn't work if we don't do it in this order
|
|
|
|
self bot_loadout::pick_hero_gadget();
|
|
|
|
self bot_loadout::pick_killstreaks();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !IS_TRUE( self.pers["bot_loadout"] ) )
|
|
|
|
{
|
|
|
|
self set_rank();
|
|
|
|
|
|
|
|
// Doesn't work if we don't do it in this order
|
|
|
|
self bot_loadout::build_classes();
|
|
|
|
self bot_loadout::pick_hero_gadget();
|
|
|
|
self bot_loadout::pick_killstreaks();
|
|
|
|
|
|
|
|
self.pers["bot_loadout"] = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
self bot_loadout::pick_classes();
|
|
|
|
self choose_class();
|
|
|
|
}
|
|
|
|
|
|
|
|
function on_bot_spawned()
|
|
|
|
{
|
|
|
|
self.bot.goalTag = undefined;
|
2023-04-20 20:48:03 -04:00
|
|
|
/#
|
|
|
|
weapon = undefined;
|
|
|
|
|
|
|
|
if ( GetDvarInt( "scr_botsHasPlayerWeapon" ) != 0 )
|
|
|
|
{
|
|
|
|
player = util::getHostPlayer();
|
|
|
|
weapon = player GetCurrentWeapon();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( GetDvarString( "devgui_bot_weapon", "" ) != "" )
|
|
|
|
{
|
|
|
|
weapon = GetWeapon( GetDvarString( "devgui_bot_weapon" ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( isdefined( weapon ) && level.weaponNone != weapon )
|
|
|
|
{
|
|
|
|
self weapons::detach_all_weapons();
|
|
|
|
self TakeAllWeapons();
|
|
|
|
self GiveWeapon( weapon );
|
|
|
|
self SwitchToWeapon( weapon );
|
|
|
|
self SetSpawnWeapon( weapon );
|
|
|
|
|
|
|
|
self teams::set_player_model( self.team, weapon );
|
|
|
|
}
|
|
|
|
#/
|
2023-03-13 04:39:59 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
function on_bot_killed()
|
|
|
|
{
|
|
|
|
self endon("disconnect");
|
|
|
|
level endon( "game_ended" );
|
|
|
|
self endon( "spawned" );
|
|
|
|
self waittill ( "death_delay_finished" );
|
|
|
|
|
|
|
|
wait RESPAWN_DELAY;
|
|
|
|
|
|
|
|
if ( self choose_class() && level.playerForceRespawn )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
self thread respawn();
|
|
|
|
}
|
|
|
|
|
|
|
|
function respawn()
|
|
|
|
{
|
|
|
|
self endon( "spawned" );
|
|
|
|
self endon( "disconnect" );
|
|
|
|
level endon( "game_ended" );
|
|
|
|
|
|
|
|
while( 1 )
|
|
|
|
{
|
|
|
|
self bot::tap_use_button();
|
|
|
|
|
|
|
|
wait RESPAWN_INTERVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function bot_idle()
|
|
|
|
{
|
|
|
|
if ( self do_supplydrop() )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2023-04-20 20:48:03 -04:00
|
|
|
|
2023-03-13 04:39:59 -04:00
|
|
|
// TODO: Look for an enemy radar blip
|
|
|
|
// TODO: Get points on navmesh and feed into the spawn system to see if an enemy is likely to spawn there
|
|
|
|
self bot::navmesh_wander();
|
|
|
|
self bot::sprint_to_goal();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Crate maxs: 23.1482
|
|
|
|
#define CRATE_GOAL_RADIUS 39
|
|
|
|
#define CRATE_USE_RADIUS 62 // Wild guess on usable radius
|
|
|
|
|
|
|
|
function do_supplydrop( maxRange = 1400 ) // A little under minimap width
|
|
|
|
{
|
|
|
|
crates = GetEntArray( "care_package", "script_noteworthy" );
|
|
|
|
|
|
|
|
maxRangeSq = maxRange * maxRange;
|
|
|
|
|
|
|
|
useRadiusSq = CRATE_USE_RADIUS * CRATE_USE_RADIUS;
|
|
|
|
|
|
|
|
closestCrate = undefined;
|
|
|
|
closestCrateDistSq = undefined;
|
|
|
|
|
|
|
|
foreach( crate in crates )
|
|
|
|
{
|
|
|
|
if ( !crate IsOnGround() )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
crateDistSq = Distance2DSquared( self.origin, crate.origin );
|
|
|
|
|
|
|
|
if ( crateDistSq > maxRangeSq )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
inUse = isdefined( crate.useEnt ) && IS_TRUE( crate.useEnt.inUse );
|
|
|
|
|
|
|
|
if ( crateDistSq <= useRadiusSq )
|
|
|
|
{
|
|
|
|
if ( inUse && !self useButtonPressed() )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
self bot::press_use_button();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !self has_minimap() && !self BotSightTracePassed( crate ) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !isdefined( closestCrate ) || crateDistSq < closestCrateDistSq )
|
|
|
|
{
|
|
|
|
closestCrate = crate;
|
|
|
|
closestCrateDistSq = crateDistSq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( isdefined( closestCrate ) )
|
|
|
|
{
|
|
|
|
randomAngle = ( 0, RandomInt( 360 ), 0 );
|
|
|
|
randomVec = AnglesToForward( randomAngle );
|
|
|
|
|
|
|
|
point = closestCrate.origin + randomVec * CRATE_GOAL_RADIUS;
|
|
|
|
|
|
|
|
if ( self BotSetGoal( point ) )
|
|
|
|
{
|
|
|
|
self thread watch_crate( closestCrate );
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function watch_crate( crate )
|
|
|
|
{
|
|
|
|
self endon( "death" );
|
|
|
|
self endon( "bot_goal_reached" );
|
|
|
|
level endon( "game_ended" );
|
|
|
|
|
|
|
|
while ( isdefined( crate ) && !self bot_combat::has_threat() )
|
|
|
|
{
|
|
|
|
wait level.botSettings.thinkInterval;
|
|
|
|
}
|
|
|
|
|
|
|
|
self BotSetGoal( self.origin );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bot Team Population
|
|
|
|
//========================================
|
|
|
|
|
|
|
|
function populate_bots()
|
|
|
|
{
|
|
|
|
level endon( "game_ended" );
|
|
|
|
|
|
|
|
if ( level.teambased )
|
|
|
|
{
|
|
|
|
maxAllies = GetDvarInt( "bot_maxAllies", 0 );
|
|
|
|
maxAxis = GetDvarInt( "bot_maxAxis", 0 );
|
|
|
|
|
|
|
|
level thread monitor_bot_team_population( maxAllies, maxAxis );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
maxFree = GetDvarInt( "bot_maxFree", 0 );
|
|
|
|
|
|
|
|
level thread monitor_bot_population( maxFree );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function monitor_bot_team_population( maxAllies, maxAxis )
|
|
|
|
{
|
|
|
|
level endon( "game_ended" );
|
|
|
|
|
|
|
|
if ( !maxAllies && !maxAxis )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fill_balanced_teams( maxAllies, maxAxis );
|
|
|
|
|
|
|
|
while ( 1 )
|
|
|
|
{
|
|
|
|
wait 3;
|
|
|
|
|
|
|
|
// TODO: Get a player count that includes 'CON_CONNECTING' players
|
|
|
|
allies = GetPlayers( "allies" );
|
|
|
|
axis = GetPlayers( "axis" );
|
|
|
|
|
|
|
|
if ( allies.size > maxAllies &&
|
|
|
|
remove_best_bot( allies ) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( axis.size > maxAxis &&
|
|
|
|
remove_best_bot( axis ) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( allies.size < maxAllies || axis.size < maxAxis )
|
|
|
|
{
|
|
|
|
add_balanced_bot( allies, maxAllies, axis, maxAxis );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function fill_balanced_teams( maxAllies, maxAxis )
|
|
|
|
{
|
|
|
|
allies = GetPlayers( "allies" );
|
|
|
|
axis = GetPlayers( "axis" );
|
|
|
|
|
|
|
|
while ( ( allies.size < maxAllies || axis.size < maxAxis ) &&
|
|
|
|
add_balanced_bot( allies, maxAllies, axis, maxAxis ) )
|
|
|
|
{
|
|
|
|
WAIT_SERVER_FRAME;
|
|
|
|
|
|
|
|
allies = GetPlayers( "allies" );
|
|
|
|
axis = GetPlayers( "axis" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function add_balanced_bot( allies, maxAllies, axis, maxAxis )
|
|
|
|
{
|
|
|
|
bot = undefined;
|
|
|
|
|
|
|
|
if ( allies.size < maxAllies &&
|
|
|
|
( allies.size <= axis.size || axis.size >= maxAxis ) )
|
|
|
|
{
|
|
|
|
bot = add_bot( "allies" );
|
|
|
|
}
|
|
|
|
else if ( axis.size < maxAxis )
|
|
|
|
{
|
|
|
|
bot = add_bot( "axis" );
|
|
|
|
}
|
|
|
|
|
|
|
|
return isdefined( bot );
|
|
|
|
}
|
|
|
|
|
|
|
|
function monitor_bot_population( maxFree )
|
|
|
|
{
|
|
|
|
level endon( "game_ended" );
|
|
|
|
|
|
|
|
if ( !maxFree )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initial Fill
|
|
|
|
players = GetPlayers( );
|
|
|
|
while ( players.size < maxFree )
|
|
|
|
{
|
|
|
|
add_bot();
|
|
|
|
WAIT_SERVER_FRAME;
|
|
|
|
players = GetPlayers( );
|
|
|
|
}
|
|
|
|
|
|
|
|
while ( 1 )
|
|
|
|
{
|
|
|
|
wait 3;
|
|
|
|
|
|
|
|
// TODO: Get a player count that includes 'CON_CONNECTING' players
|
|
|
|
players = GetPlayers( );
|
|
|
|
|
|
|
|
if ( players.size < maxFree )
|
|
|
|
{
|
|
|
|
add_bot();
|
|
|
|
}
|
|
|
|
else if ( players.size > maxFree )
|
|
|
|
{
|
|
|
|
remove_best_bot( players );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function remove_best_bot( players )
|
|
|
|
{
|
|
|
|
bots = filter_bots( players );
|
|
|
|
|
|
|
|
if ( !bots.size )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prefer non-combat bots
|
|
|
|
bestBots = [];
|
|
|
|
|
|
|
|
foreach( bot in bots )
|
|
|
|
{
|
|
|
|
// Don't kick bots in the process of connecting
|
|
|
|
if ( bot.sessionstate == "spectator" )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( bot.sessionstate == "dead" || !bot bot_combat::has_threat() )
|
|
|
|
{
|
|
|
|
bestBots[bestBots.size] = bot;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( bestBots.size )
|
|
|
|
{
|
|
|
|
remove_bot( bestBots[RandomInt( bestBots.size )] );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
remove_bot( bots[RandomInt( bots.size )] );
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bot Loadouts
|
|
|
|
//========================================
|
|
|
|
|
|
|
|
function choose_class()
|
|
|
|
{
|
|
|
|
if ( IS_TRUE( level.disableClassSelection ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
currClass = self bot_loadout::get_current_class();
|
|
|
|
|
|
|
|
if ( !isdefined( currClass ) || RandomInt( 100 ) < VAL( level.botSettings.changeClassWeight, 0 ) )
|
|
|
|
{
|
|
|
|
classIndex = RandomInt( self.loadoutClasses.size );
|
|
|
|
className = self.loadoutClasses[classIndex].name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !isdefined(className) || className === currClass )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
self notify( "menuresponse", MENU_CHANGE_CLASS, className );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Killstreaks
|
|
|
|
//========================================
|
|
|
|
|
|
|
|
function use_killstreak()
|
|
|
|
{
|
|
|
|
if ( !level.loadoutKillstreaksEnabled ||
|
|
|
|
self emp::EnemyEMPActive() )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
weapons = self GetWeaponsList();
|
|
|
|
inventoryWeapon = self GetInventoryWeapon();
|
|
|
|
|
|
|
|
foreach( weapon in weapons )
|
|
|
|
{
|
|
|
|
killstreak = killstreaks::get_killstreak_for_weapon( weapon );
|
|
|
|
|
|
|
|
if ( !isdefined( killstreak ) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( weapon != inventoryWeapon && !self GetWeaponAmmoClip( weapon ) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( self killstreakrules::isKillstreakAllowed( killstreak, self.team ) )
|
|
|
|
{
|
|
|
|
useWeapon = weapon;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !isdefined( useWeapon ) )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
killstreak_ref = killstreaks::get_menu_name( killstreak );
|
|
|
|
|
|
|
|
switch( killstreak_ref )
|
|
|
|
{
|
|
|
|
case "killstreak_uav":
|
|
|
|
case "killstreak_counteruav":
|
|
|
|
case "killstreak_satellite":
|
|
|
|
case "killstreak_helicopter_player_gunner":
|
|
|
|
case "killstreak_raps":
|
|
|
|
case "killstreak_sentinel":
|
2023-04-20 20:48:03 -04:00
|
|
|
self SwitchToWeapon( useWeapon );
|
|
|
|
break;
|
2023-03-13 04:39:59 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-20 20:58:03 -04:00
|
|
|
function get_closest_enemy( origin, on_radar )
|
|
|
|
{
|
|
|
|
enemies = self get_enemies( on_radar );
|
|
|
|
enemies = arraysort( enemies, origin );
|
|
|
|
|
|
|
|
if ( enemies.size )
|
|
|
|
return enemies[0];
|
|
|
|
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
function use_supply_drop( weapon )
|
|
|
|
{
|
|
|
|
if ( weapon == "inventory_supplydrop_mp" || weapon == "supplydrop_mp" )
|
|
|
|
{
|
|
|
|
if ( gettime() - self.spawntime > 5000 )
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
yaw = ( 0, self.angles[1], 0 );
|
|
|
|
dir = anglestoforward( yaw );
|
|
|
|
dir = vectornormalize( dir );
|
|
|
|
drop_point = self.origin + vectorscale( dir, 384 );
|
|
|
|
end = drop_point + vectorscale( ( 0, 0, 1 ), 2048.0 );
|
|
|
|
|
|
|
|
if ( !sighttracepassed( drop_point, end, 0, undefined ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( !sighttracepassed( self.origin, end, 0, undefined ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
end = drop_point - vectorscale( ( 0, 0, 1 ), 32.0 );
|
|
|
|
|
|
|
|
if ( bullettracepassed( drop_point, end, 0, undefined ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
self addgoal( self.origin, 24, 4, "killstreak" );
|
|
|
|
|
|
|
|
if ( weapon == "missile_drone_mp" || weapon == "inventory_missile_drone_mp" )
|
|
|
|
self lookat( drop_point + vectorscale( ( 0, 0, 1 ), 384.0 ) );
|
|
|
|
else
|
|
|
|
self lookat( drop_point );
|
|
|
|
|
|
|
|
wait 0.5;
|
|
|
|
|
|
|
|
if ( self getcurrentweapon() != weapon )
|
|
|
|
{
|
|
|
|
self thread weapon_switch_failsafe();
|
|
|
|
self switchtoweapon( weapon );
|
|
|
|
|
|
|
|
self waittill( "weapon_change_complete" );
|
|
|
|
}
|
|
|
|
|
|
|
|
use_item( weapon );
|
|
|
|
self switchtoweapon( self.lastnonkillstreakweapon );
|
|
|
|
self clearlookat();
|
|
|
|
self cancelgoal( "killstreak" );
|
|
|
|
}
|
|
|
|
|
|
|
|
function use_item( weapon )
|
|
|
|
{
|
|
|
|
self bot::press_attack_button();
|
|
|
|
wait 0.5;
|
|
|
|
|
|
|
|
for ( i = 0; i < 10; i++ )
|
|
|
|
{
|
|
|
|
if ( self getcurrentweapon() == weapon || self getcurrentweapon() == "none" )
|
|
|
|
self bot::press_attack_button();
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
|
|
|
|
wait 0.5;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function killstreak_location( num, weapon )
|
|
|
|
{
|
|
|
|
enemies = get_enemies();
|
|
|
|
|
|
|
|
if ( !enemies.size )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if ( !self switchtoweapon( weapon ) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
self waittill( "weapon_change" );
|
|
|
|
|
|
|
|
self util::freeze_player_controls( true );
|
|
|
|
wait_time = 1;
|
|
|
|
|
|
|
|
while ( !isdefined( self.selectinglocation ) || self.selectinglocation == 0 )
|
|
|
|
{
|
|
|
|
wait 0.05;
|
|
|
|
wait_time -= 0.05;
|
|
|
|
|
|
|
|
if ( wait_time <= 0 )
|
|
|
|
{
|
|
|
|
self util::freeze_player_controls( false );
|
|
|
|
self switchtoweapon( self.lastnonkillstreakweapon );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wait 2;
|
|
|
|
|
|
|
|
for ( i = 0; i < num; i++ )
|
|
|
|
{
|
|
|
|
enemies = get_enemies();
|
|
|
|
|
|
|
|
if ( enemies.size )
|
|
|
|
{
|
|
|
|
enemy = randomInt( enemies );
|
|
|
|
self notify( "confirm_location", enemy.origin, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
wait 0.25;
|
|
|
|
}
|
|
|
|
|
|
|
|
self util::freeze_player_controls( false );
|
|
|
|
}
|
|
|
|
|
|
|
|
function weapon_switch_failsafe()
|
|
|
|
{
|
|
|
|
self endon( "death" );
|
|
|
|
self endon( "disconnect" );
|
|
|
|
self endon( "weapon_change_complete" );
|
|
|
|
wait 10;
|
|
|
|
self notify( "weapon_change_complete" );
|
|
|
|
}
|
2023-03-13 04:39:59 -04:00
|
|
|
|
|
|
|
function has_radar()
|
|
|
|
{
|
|
|
|
if ( level.teambased )
|
|
|
|
{
|
|
|
|
return ( uav::HasUAV( self.team ) || satellite::HasSatellite( self.team ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
return ( uav::HasUAV( self.entnum ) || satellite::HasSatellite( self.entnum ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
function has_minimap()
|
|
|
|
{
|
|
|
|
if ( self IsEmpJammed() )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( IS_TRUE( level.hardcoreMode ) )
|
|
|
|
{
|
|
|
|
return self has_radar();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_enemies( on_radar )
|
|
|
|
{
|
|
|
|
if ( !isdefined( on_radar ) )
|
|
|
|
{
|
|
|
|
on_radar = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
enemies = self GetEnemies();
|
|
|
|
|
2023-04-20 20:48:03 -04:00
|
|
|
/#
|
|
|
|
for ( i = 0; i < enemies.size; i++ )
|
|
|
|
{
|
|
|
|
if ( isplayer( enemies[i] ) && enemies[i] IsInMoveMode( "ufo", "noclip" ) )
|
|
|
|
{
|
|
|
|
ArrayRemoveIndex( enemies, i );
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#/
|
|
|
|
|
2023-03-13 04:39:59 -04:00
|
|
|
if ( on_radar && !self has_radar() )
|
|
|
|
{
|
|
|
|
for ( i = 0; i < enemies.size; i++ )
|
|
|
|
{
|
|
|
|
if ( !isdefined( enemies[i].lastFireTime ) )
|
|
|
|
{
|
|
|
|
ArrayRemoveIndex( enemies, i );
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
else if ( GetTime() - enemies[i].lastFireTime > 2000 )
|
|
|
|
{
|
|
|
|
ArrayRemoveIndex( enemies, i );
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return enemies;
|
|
|
|
}
|
|
|
|
|
|
|
|
function set_rank()
|
|
|
|
{
|
|
|
|
players = GetPlayers();
|
|
|
|
|
|
|
|
ranks = [];
|
|
|
|
bot_ranks = [];
|
|
|
|
human_ranks = [];
|
|
|
|
|
|
|
|
for ( i = 0; i < players.size; i++ )
|
|
|
|
{
|
|
|
|
if ( players[i] == self )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ( isdefined( players[i].pers[ "rank" ] ) )
|
|
|
|
{
|
|
|
|
if ( players[i] util::is_bot() )
|
|
|
|
{
|
|
|
|
bot_ranks[ bot_ranks.size ] = players[i].pers[ "rank" ];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
human_ranks[ human_ranks.size ] = players[i].pers[ "rank" ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !human_ranks.size )
|
|
|
|
human_ranks[ human_ranks.size ] = 10;
|
|
|
|
|
|
|
|
human_avg = math::array_average( human_ranks );
|
|
|
|
|
|
|
|
while ( bot_ranks.size + human_ranks.size < 5 )
|
|
|
|
{
|
|
|
|
// add some random ranks for better random number distribution
|
|
|
|
r = human_avg + RandomIntRange( -5, 5 );
|
|
|
|
rank = math::clamp( r, 0, level.maxRank );
|
|
|
|
human_ranks[ human_ranks.size ] = rank;
|
|
|
|
}
|
|
|
|
|
|
|
|
ranks = ArrayCombine( human_ranks, bot_ranks, true, false );
|
|
|
|
|
|
|
|
avg = math::array_average( ranks );
|
|
|
|
s = math::array_std_deviation( ranks, avg );
|
|
|
|
|
|
|
|
rank = Int( math::random_normal_distribution( avg, s, 0, level.maxRank ) );
|
|
|
|
|
|
|
|
while ( !isdefined( self.pers["codpoints"] ) )
|
|
|
|
{
|
|
|
|
wait 0.1;
|
|
|
|
}
|
|
|
|
|
|
|
|
self.pers[ "rank" ] = rank;
|
|
|
|
self.pers[ "rankxp" ] = rank::getRankInfoMinXP( rank );
|
|
|
|
|
|
|
|
self setRank( rank );
|
|
|
|
self rank::syncXPStat();
|
|
|
|
}
|
|
|
|
|
|
|
|
function init_bot_gametype()
|
|
|
|
{
|
2023-04-20 20:48:03 -04:00
|
|
|
switch( level.gameType )
|
2023-03-13 04:39:59 -04:00
|
|
|
{
|
|
|
|
case "ball":
|
|
|
|
bot_ball::init();
|
|
|
|
return true;
|
|
|
|
case "conf":
|
|
|
|
bot_conf::init();
|
|
|
|
return true;
|
|
|
|
case "ctf":
|
|
|
|
bot_ctf::init();
|
|
|
|
return true;
|
|
|
|
case "dem":
|
|
|
|
bot_dem::init();
|
|
|
|
return true;
|
|
|
|
case "dm":
|
|
|
|
return true;
|
|
|
|
case "dom":
|
|
|
|
bot_dom::init();
|
|
|
|
return true;
|
|
|
|
case "escort":
|
|
|
|
bot_escort::init();
|
|
|
|
return true;
|
2023-04-20 20:48:03 -04:00
|
|
|
case "infect":
|
2023-03-13 04:39:59 -04:00
|
|
|
return true;
|
2023-04-20 20:48:03 -04:00
|
|
|
case "gun":
|
2023-03-13 04:39:59 -04:00
|
|
|
return true;
|
|
|
|
case "koth":
|
|
|
|
bot_koth::init();
|
|
|
|
return true;
|
|
|
|
case "sd":
|
|
|
|
bot_sd::init();
|
|
|
|
return true;
|
|
|
|
case "clean":
|
|
|
|
bot_clean::init();
|
|
|
|
return true;
|
|
|
|
case "tdm":
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_bot_settings()
|
|
|
|
{
|
|
|
|
switch ( GetDvarInt( "bot_difficulty", 1 ) )
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
bundleName = "bot_mp_easy";
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
bundleName = "bot_mp_normal";
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
bundleName = "bot_mp_hard";
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
default:
|
|
|
|
bundleName = "bot_mp_veteran";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return struct::get_script_bundle( "botsettings", bundleName );
|
|
|
|
}
|
|
|
|
|
|
|
|
function friend_goal_in_radius( goal_name, origin, radius )
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
function friend_in_radius( goal_name, origin, radius )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_friends()
|
|
|
|
{
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
function bot_vehicle_weapon_ammo( weaponName )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function navmesh_points_visible( origin, point )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function dive_to_prone( exit_stance )
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2023-04-20 20:48:03 -04:00
|
|
|
/#
|
|
|
|
|
|
|
|
// Devgui
|
|
|
|
//========================================
|
|
|
|
|
|
|
|
function bot_devgui_cmd( cmd )
|
|
|
|
{
|
|
|
|
cmdTokens = strtok( cmd," ");
|
|
|
|
|
|
|
|
if ( cmdTokens.size == 0 )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
host = util::getHostPlayerForBots();
|
|
|
|
team = get_host_team();
|
|
|
|
|
|
|
|
switch( cmdTokens[0] )
|
|
|
|
{
|
|
|
|
case "spawn_enemy":
|
|
|
|
team = util::getotherteam(team);
|
|
|
|
case "spawn_friendly":
|
|
|
|
count = 1;
|
|
|
|
if ( cmdTokens.size > 1 )
|
|
|
|
{
|
|
|
|
count = int( cmdTokens[1] );
|
|
|
|
}
|
|
|
|
for( i = 0; i < count; i++ )
|
|
|
|
{
|
|
|
|
add_bot( team );
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
case "remove_enemy":
|
|
|
|
team = util::getotherteam(team);
|
|
|
|
case "remove_friendly":
|
|
|
|
remove_bots( undefined, team );
|
|
|
|
return true;
|
|
|
|
case "fixed_spawn_enemy":
|
|
|
|
team = util::getotherteam(team);
|
|
|
|
case "fixed_spawn_friendly":
|
|
|
|
bot = add_bot_at_eye_trace( team );
|
|
|
|
if ( isdefined( bot ) )
|
|
|
|
{
|
|
|
|
bot thread fixed_spawn_override();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case "player_weapon":
|
|
|
|
players = GetPlayers();
|
|
|
|
foreach( player in players )
|
|
|
|
{
|
|
|
|
if ( !player util::is_bot() )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
weapon = host GetCurrentWeapon();
|
|
|
|
|
|
|
|
player weapons::detach_all_weapons();
|
|
|
|
player TakeAllWeapons();
|
|
|
|
player GiveWeapon( weapon );
|
|
|
|
player SwitchToWeapon( weapon );
|
|
|
|
player SetSpawnWeapon( weapon );
|
|
|
|
|
|
|
|
player teams::set_player_model( player.team, weapon );
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function system_devgui_gadget_think()
|
|
|
|
{
|
|
|
|
SetDvar( "devgui_bot_gadget", "" );
|
|
|
|
|
|
|
|
for ( ;; )
|
|
|
|
{
|
|
|
|
wait( 1 );
|
|
|
|
|
|
|
|
gadget = GetDvarString( "devgui_bot_gadget" );
|
|
|
|
|
|
|
|
if ( gadget != "" )
|
|
|
|
{
|
|
|
|
bot_turn_on_gadget( GetWeapon(gadget) );
|
|
|
|
SetDvar( "devgui_bot_gadget", "" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function bot_turn_on_gadget( gadget )
|
|
|
|
{
|
|
|
|
players = GetPlayers();
|
|
|
|
|
|
|
|
foreach( player in players )
|
|
|
|
{
|
|
|
|
if ( !player util::is_bot() )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
host = util::getHostPlayer();
|
|
|
|
weapon = host GetCurrentWeapon();
|
|
|
|
|
|
|
|
if ( !isdefined( weapon ) || weapon == level.weaponNone || weapon == level.weaponNull )
|
|
|
|
{
|
|
|
|
weapon = GetWeapon( "smg_standard" );
|
|
|
|
}
|
|
|
|
|
|
|
|
player weapons::detach_all_weapons();
|
|
|
|
player TakeAllWeapons();
|
|
|
|
player GiveWeapon( weapon );
|
|
|
|
player SwitchToWeapon( weapon );
|
|
|
|
player SetSpawnWeapon( weapon );
|
|
|
|
|
|
|
|
player teams::set_player_model( player.team, weapon );
|
|
|
|
|
|
|
|
player GiveWeapon( gadget );
|
|
|
|
slot = player GadgetGetSlot( gadget );
|
|
|
|
player GadgetPowerSet( slot, 100.0 );
|
|
|
|
player BotPressButtonForGadget( gadget );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function fixed_spawn_override()
|
|
|
|
{
|
|
|
|
self endon( "disconnect" );
|
|
|
|
|
|
|
|
spawnOrigin = self.origin;
|
|
|
|
spawnAngles = self.angles;
|
|
|
|
|
|
|
|
while( 1 )
|
|
|
|
{
|
|
|
|
self waittill( "spawned_player" );
|
|
|
|
|
|
|
|
self SetOrigin( spawnOrigin );
|
|
|
|
self SetPlayerAngles( spawnAngles );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#/
|