Enable bots in ranked matches

This commit is contained in:
joseupp 2023-03-13 09:39:59 +01:00
parent 5090723010
commit d9787cda5a
4 changed files with 1760 additions and 0 deletions

Binary file not shown.

View File

@ -0,0 +1,906 @@
#using scripts\codescripts\struct;
#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;
#using scripts\shared\weapons\_weapons;
#using scripts\shared\bots\_bot;
#using scripts\shared\bots\_bot_combat;
#using scripts\shared\bots\bot_traversals;
#using scripts\shared\bots\bot_buttons;
#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;
#using scripts\mp\killstreaks\_killstreakrules;
#using scripts\mp\killstreaks\_killstreaks;
#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;
#using scripts\mp\killstreaks\_killstreakrules;
#using scripts\mp\killstreaks\_killstreaks;
#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;
#using scripts\mp\killstreaks\_satellite;
#using scripts\mp\killstreaks\_sentinel;
#using scripts\mp\killstreaks\_supplydrop;
#using scripts\mp\killstreaks\_turret;
#using scripts\mp\killstreaks\_uav;
#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
#define RESPAWN_DELAY 0
#define RESPAWN_INTERVAL 0.2
#namespace bot;
REGISTER_SYSTEM( "bot_mp", &__init__, undefined )
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;
}
function init()
{
level endon( "game_ended" );
level.botSoak = is_bot_soak();
init_bot_gametype();
wait_for_host();
level thread populate_bots();
}
// Init Utils
//========================================
function is_bot_soak()
{
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;
}
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;
}
// 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_remote_missile":
{
self switchtoweapon( weapon );
self waittill( "weapon_change_complete" );
wait 1.5;
self bot::press_attack_button();
}
return;
case "killstreak_satellite":
case "killstreak_helicopter_player_gunner":
case "killstreak_ai_tank_drop":
self use_supply_drop( weapon );
break;
case "killstreak_raps":
case "killstreak_sentinel":
{
self switchtoweapon(useweapon);
break;
}
}
}
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 weapon_switch_failsafe()
{
self endon( "death" );
self endon( "disconnect" );
self endon( "weapon_change_complete" );
wait 10;
self notify( "weapon_change_complete" );
}
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();
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()
{
switch(level.gametype)
{
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;
}
case "infect":
{
return true;
}
case "gun":
{
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 )
{
}

Binary file not shown.

View File

@ -0,0 +1,854 @@
#using scripts\shared\array_shared;
#using scripts\shared\rank_shared;
#insert scripts\shared\shared.gsh;
#insert scripts\shared\statstable_shared.gsh;
#using scripts\mp\killstreaks\_killstreaks;
#using scripts\mp\bots\_bot;
#define BOT_ALLOCATION_MAX 100
#define BOT_ALLOCATION_UNLOCK_MAX 3
#define BOT_RANK_ALL_OPTIONS_AVAILABLE 20
#define BOT_RANK_OPTIONS_MULTIPLIER 4
#namespace bot_loadout;
// Item Whitelist
//========================================
function in_whitelist( itemName )
{
if ( !isdefined( itemName ) )
return false;
switch( itemName )
{
// Secondaries
case "WEAPON_KNIFE_LOADOUT":
case "WEAPON_PISTOL_STANDARD":
case "WEAPON_PISTOL_BURST":
case "WEAPON_PISTOL_FULLAUTO":
case "WEAPON_LAUNCHER_STANDARD":
case "WEAPON_LAUNCHER_LOCKONLY":
// Primaries
case "WEAPON_SMG_STANDARD":
case "WEAPON_SMG_BURST":
case "WEAPON_SMG_FASTFIRE":
case "WEAPON_SMG_LONGRANGE":
case "WEAPON_SMG_VERSATILE":
case "WEAPON_SMG_CAPACITY":
case "WEAPON_AR_STANDARD":
case "WEAPON_AR_ACCURATE":
case "WEAPON_AR_CQB":
case "WEAPON_AR_DAMAGE":
case "WEAPON_AR_FASTBURST":
case "WEAPON_AR_LONGBURST":
case "WEAPON_AR_MARKSMAN":
case "WEAPON_LMG_CQB":
case "WEAPON_LMG_HEAVY":
case "WEAPON_LMG_LIGHT":
case "WEAPON_LMG_SLOWFIRE":
case "WEAPON_SNIPER_FASTBOLT":
case "WEAPON_SNIPER_FASTSEMI":
case "WEAPON_SNIPER_POWERBOLT":
case "WEAPON_SNIPER_CHARGESHOT":
case "WEAPON_SHOTGUN_FULLAUTO":
case "WEAPON_SHOTGUN_PRECISION":
case "WEAPON_SHOTGUN_PUMP":
case "WEAPON_SHOTGUN_SEMIAUTO":
// Lethals
case "WEAPON_FRAGGRENADE":
case "WEAPON_HATCHET":
case "WEAPON_STICKY_GRENADE":
case "WEAPON_SATCHEL_CHARGE":
case "WEAPON_BOUNCINGBETTY":
case "WEAPON_INCENDIARY_GRENADE":
// Tacticals
case "WEAPON_WILLY_PETE":
case "WEAPON_STUN_GRENADE":
case "WEAPON_EMPGRENADE":
case "WEAPON_FLASHBANG":
case "WEAPON_PROXIMITY_GRENADE":
case "WEAPON_PDA_HACK":
case "WEAPON_TROPHY_SYSTEM":
// Killstreaks
//case "KILLSTREAK_RCBOMB":
case "KILLSTREAK_RECON":
case "KILLSTREAK_COUNTER_UAV":
//case "KILLSTREAK_SUPPLY_DROP":
//case "KILLSTREAK_MICROWAVE_TURRET":
case "KILLSTREAK_REMOTE_MISSILE":
//case "KILLSTREAK_PLANEMORTAR":
//case "KILLSTREAK_AUTO_TURRET":
case "KILLSTREAK_AI_TANK_DROP":
//case "KILLSTREAK_HELICOPTER_COMLINK":
case "KILLSTREAK_SATELLITE":
//case "KILLSTREAK_EMP":
//case "KILLSTREAK_HELICOPTER_GUNNER":
case "KILLSTREAK_RAPS":
//case "KILLSTREAK_DRONE_STRIKE":
//case "KILLSTREAK_DART":
case "KILLSTREAK_SENTINEL":
// TU Something Weapons
case "WEAPON_MELEE_KNUCKLES":
case "WEAPON_MELEE_BUTTERFLY":
case "WEAPON_MELEE_WRENCH":
// TU 6 Weapons
case "WEAPON_PISTOL_SHOTGUN":
case "WEAPON_AR_GARAND":
case "WEAPON_SPECIAL_CROSSBOW":
case "WEAPON_MELEE_CROWBAR":
case "WEAPON_MELEE_SWORD":
case "WEAPON_MELEE_BOXING":
case "WEAPON_SMG_AK74U":
case "WEAPON_SMG_MP40":
case "WEAPON_SMG_RECHAMBER":
case "WEAPON_SMG_NAILGUN":
case "WEAPON_AR_AN94":
case "WEAPON_AR_FAMAS":
case "WEAPON_SMG_MSMC":
case "WEAPON_LMG_INFINITE":
case "WEAPON_AR_PULSE":
case "WEAPON_AR_M16":
case "WEAPON_SMG_PPSH":
case "WEAPON_LAUNCHER_EX41":
case "WEAPON_SHOTGUN_OLYMPIA":
case "WEAPON_SNIPER_QUICKSCOPE":
case "WEAPON_SNIPER_DOUBLE":
case "WEAPON_SMG_STEN":
case "WEAPON_AR_GALIL":
case "WEAPON_LMG_RPK":
case "WEAPON_AR_M14":
case "WEAPON_SHOTGUN_ENERGY":
case "WEAPON_SPECIAL_CROSSBOW_DW":
case "WEAPON_AR_PEACEKEEPER":
case "WEAPON_MELEE_CHAINSAW":
case "WEAPON_SPECIAL_KNIFE_BALLISTIC":
case "WEAPON_MELEE_CRESCENT":
case "WEAPON_SPECIAL_DISCGUN":
return true;
}
return false;
}
// Classes
//========================================
function build_classes()
{
primaryWeapons = self get_available_items( undefined, "primary" );
secondaryWeapons = self get_available_items( undefined, "secondary" );
lethals = self get_available_items( undefined, "primarygadget" );
tacticals = self get_available_items( undefined, "secondarygadget" );
if ( IS_TRUE( level.perksEnabled ) )
{
specialties1 = self get_available_items( undefined, "specialty1" );
specialties2 = self get_available_items( undefined, "specialty2" );
specialties3 = self get_available_items( undefined, "specialty3" );
}
foreach( className, classValue in level.classMap )
{
if ( !isSubstr( className, "custom" ) )
{
continue;
}
classIndex = int( className[className.size-1] );
pickedItems = [];
pick_item( pickedItems, primaryWeapons );
if ( RandomInt( 100 ) < 95 ) // 5% chance to be a boxer for Scronce
{
pick_item( pickedItems, secondaryWeapons );
}
// Shuffle these selections around a bit so the classes don't all look the same when the allocation is low
otherItems = Array ( lethals, tacticals, specialties1, specialties2, specialties3 );
otherItems = array::randomize( otherItems );
for ( i = 0; i < otherItems.size; i ++ )
{
pick_item( pickedItems, otherItems[i] );
}
// Add items up to the max allocation
for ( i = 0; i < pickedItems.size && i < level.maxAllocation; i++ )
{
self BotClassAddItem( classIndex, pickedItems[i] );
}
// TODO: Pick primary/secondary attachments, extra perks, extra lethal, extra tactical, overkill
/*
primaryWeapon = self GetLoadoutWeapon( classIndex, "primary" );
if ( primaryWeapon != level.weaponNone && primaryWeapon.supportedAttachments.size )
{
attachment = array::random( primaryWeapon.supportedAttachments );
self BotClassAddAttachment( classIndex, primaryWeapon, attachment, "primary" );
}
secondaryWeapon = self GetLoadoutWeapon( classIndex, "secondary" );
if ( secondaryWeapon != level.weaponNone && secondaryWeapon.supportedAttachments.size )
{
attachment = array::random( secondaryWeapon.supportedAttachments );
self BotClassAddAttachment( classIndex, secondaryWeapon, attachment, "secondary" );
}
*/
}
}
function pick_item( &pickedItems, items )
{
if ( !isdefined( items ) || items.size <= 0 )
{
return;
}
pickedItems[pickedItems.size] = array::random( items );
}
function pick_classes()
{
self.loadoutClasses = [];
self.launcherClassCount = 0;
foreach( className, classValue in level.classMap )
{
if ( isSubstr( className, "custom" ) )
{
if ( level.disableCAC )
{
continue;
}
classIndex = int( className[className.size-1] );
}
else
{
// Things bots could use better in the default classes:
// C4, Trophy System, Lock on only launcher
classIndex = level.classToClassNum[ classValue ];
}
primary = self GetLoadoutWeapon( classIndex, "primary" );
secondary = self GetLoadoutWeapon( classIndex, "secondary" );
botClass = SpawnStruct();
botClass.name = className;
botClass.index = classIndex;
botClass.value = classValue;
botClass.primary = primary;
botClass.secondary = secondary;
if ( botClass.secondary.isRocketLauncher )
{
self.launcherClassCount++;
}
self.loadoutClasses[ self.loadoutClasses.size ] = botClass;
}
}
function get_current_class()
{
currValue = self.pers["class"];
if ( !isdefined( currValue ) )
{
return undefined;
}
foreach( botClass in self.loadoutClasses )
{
if ( botClass.value == currValue )
{
return botClass;
}
}
return undefined;
}
// Specialists
//========================================
function pick_hero_gadget()
{
if ( RandomInt( 2 ) < 1 || !self pick_hero_ability() )
{
self pick_hero_weapon();
}
}
function pick_hero_weapon()
{
heroWeaponRef = self GetHeroWeaponName();
if ( IsItemRestricted( heroWeaponRef ) )
{
return false;
}
heroWeaponName = self get_item_name( heroWeaponRef );
self BotClassAddItem( 0, heroWeaponName );
return true;
}
function pick_hero_ability()
{
heroAbilityRef = self GetHeroAbilityName();
if ( IsItemRestricted( heroAbilityRef ) )
{
return false;
}
heroAbilityName = self get_item_name( heroAbilityRef );
self BotClassAddItem( 0, heroAbilityName );
return true;
}
// Killstreaks
//========================================
function pick_killstreaks()
{
killstreaks = array::randomize( self get_available_items( "killstreak" ) );
for( i = 0; i < 3 && i < killstreaks.size; i++ )
{
self BotClassAddItem( 0, killstreaks[i] );
}
}
// Get Items
//========================================
function get_available_items( filterGroup, filterSlot )
{
// Get unlocked and unrestricted items
items = [];
for( i = 0; i < STATS_TABLE_MAX_ITEMS; i++ )
{
row = tableLookupRowNum( level.statsTableID, STATS_TABLE_COL_NUMBERING, i );
if ( row < 0 )
{
continue;
}
name = tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_NAME );
if ( name == "" || !in_whitelist( name ) )
{
continue;
}
allocation = Int( tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_ALLOCATION ) );
if ( allocation < 0 )
{
continue;
}
ref = tableLookupColumnForRow( level.statsTableId, row, STATS_TABLE_COL_REFERENCE );
if ( IsItemRestricted( ref ) )
{
continue;
}
number = Int( tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_NUMBERING ) );
/*
if ( SessionModeIsPrivate() && self IsItemLocked( number ) )
{
continue;
}
*/
if ( isdefined( filterGroup ) )
{
group = tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_GROUP );
if ( group != filterGroup )
{
continue;
}
}
if ( isdefined( filterSlot ) )
{
slot = tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_SLOT );
if ( slot != filterSlot )
{
continue;
}
}
items[items.size] = name;
}
return items;
}
function get_item_name( itemReference )
{
for( i = 0; i < STATS_TABLE_MAX_ITEMS; i++ )
{
row = tableLookupRowNum( level.statsTableID, STATS_TABLE_COL_NUMBERING, i );
if ( row < 0 )
{
continue;
}
reference = tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_REFERENCE );
if ( reference != itemReference )
{
continue;
}
name = tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_NAME );
return name;
}
return undefined;
}
// Not in use
function init()
{
level endon( "game_ended" );
level.bot_banned_killstreaks = Array ( "KILLSTREAK_RCBOMB",
"KILLSTREAK_QRDRONE",
/* "KILLSTREAK_REMOTE_MISSILE",*/
"KILLSTREAK_REMOTE_MORTAR",
"KILLSTREAK_HELICOPTER_GUNNER" );
for ( ;; )
{
level waittill( "connected", player );
if ( !player IsTestClient() )
{
continue;
}
player thread on_bot_connect();
}
}
function on_bot_connect()
{
self endon( "disconnect" );
if ( isdefined( self.pers[ "bot_loadout" ] ) )
{
return;
}
wait( 0.10 );
if ( self GetEntityNumber() % 2 == 0 )
{
WAIT_SERVER_FRAME;
}
self bot::set_rank();
self BotSetRandomCharacterCustomization();
max_allocation = BOT_ALLOCATION_MAX;
/*
if ( SessionModeIsPrivate() )
{
for ( i = 1; i <= BOT_ALLOCATION_UNLOCK_MAX; i++ )
{
if ( self IsItemLocked( rank::GetItemIndex( "feature_allocation_slot_" + i ) ) )
{
max_allocation--;
}
}
}
*/
self construct_loadout( max_allocation );
self.pers[ "bot_loadout" ] = true;
}
function construct_loadout( allocation_max )
{
/* if ( SessionModeIsPrivate() && self IsItemLocked( rank::GetItemIndex( "feature_cac" ) ) )
{
// cac still locked
return;
}
*/
pixbeginevent( "bot_construct_loadout" );
item_list = build_item_list();
// item_list["primary"] = [];
// item_list["primary"][0] = "WEAPON_RIOTSHIELD";
construct_class( 0, item_list, allocation_max );
construct_class( 1, item_list, allocation_max );
construct_class( 2, item_list, allocation_max );
construct_class( 3, item_list, allocation_max );
construct_class( 4, item_list, allocation_max );
killstreaks = item_list["killstreak1"];
if ( isdefined( item_list["killstreak2"] ) )
{
killstreaks = ArrayCombine( killstreaks, item_list["killstreak2"], true, false );
}
if ( isdefined( item_list["killstreak3"] ) )
{
killstreaks = ArrayCombine( killstreaks, item_list["killstreak3"], true, false );
}
if ( isdefined( killstreaks ) && killstreaks.size )
{
choose_weapon( 0, killstreaks );
choose_weapon( 0, killstreaks );
choose_weapon( 0, killstreaks );
}
self.claimed_items = undefined;
pixendevent();
}
function construct_class( constructclass, items, allocation_max )
{
allocation = 0;
claimed_count = build_claimed_list( items );
self.claimed_items = [];
// primary
weapon = choose_weapon( constructclass, items["primary"] );
claimed_count["primary"]++;
allocation++;
// secondary
weapon = choose_weapon( constructclass, items["secondary"] );
choose_weapon_option( constructclass, "camo", 1 );
}
function make_choice( chance, claimed, max_claim )
{
return ( claimed < max_claim && RandomInt( 100 ) < chance );
}
function chose_action( action1, chance1, action2, chance2, action3, chance3, action4, chance4 )
{
chance1 = Int( chance1 / 10 );
chance2 = Int( chance2 / 10 );
chance3 = Int( chance3 / 10 );
chance4 = Int( chance4 / 10 );
actions = [];
for( i = 0; i < chance1; i++ )
{
actions[ actions.size ] = action1;
}
for( i = 0; i < chance2; i++ )
{
actions[ actions.size ] = action2;
}
for( i = 0; i < chance3; i++ )
{
actions[ actions.size ] = action3;
}
for( i = 0; i < chance4; i++ )
{
actions[ actions.size ] = action4;
}
return array::random( actions );
}
function item_is_claimed( item )
{
foreach( claim in self.claimed_items )
{
if ( claim == item )
{
return true;
}
}
return false;
}
function choose_weapon( weaponclass, items )
{
if ( !isdefined( items ) || !items.size )
{
return undefined;
}
start = RandomInt( items.size );
for( i = 0; i < items.size; i++ )
{
weapon = items[ start ];
if ( !item_is_claimed( weapon ) )
{
break;
}
start = ( start + 1 ) % items.size;
}
self.claimed_items[ self.claimed_items.size ] = weapon;
self BotClassAddItem( weaponclass, weapon );
return weapon;
}
function build_weapon_options_list( optionType )
{
level.botWeaponOptionsId[optionType] = [];
level.botWeaponOptionsProb[optionType] = [];
csv_filename = "gamedata/weapons/common/attachmentTable.csv";
prob = 0;
for ( row = 0 ; row < 255 ; row++ )
{
if ( tableLookupColumnForRow( csv_filename, row, ATTACHMENT_TABLE_COL_TYPE ) == optionType )
{
index = level.botWeaponOptionsId[optionType].size;
level.botWeaponOptionsId[optionType][index] = Int( tableLookupColumnForRow( csv_filename, row, ATTACHMENT_TABLE_COL_NUMBERING ) );
prob += Int( tableLookupColumnForRow( csv_filename, row, ATTACHMENT_TABLE_COL_BOT_PROB ) );
level.botWeaponOptionsProb[optionType][index] = prob;
}
}
}
function choose_weapon_option( weaponclass, optionType, primary )
{
if ( !isdefined( level.botWeaponOptionsId ) )
{
level.botWeaponOptionsId = [];
level.botWeaponOptionsProb = [];
build_weapon_options_list( "camo" );
build_weapon_options_list( "reticle" );
}
// weapon options cannot be set in local matches
if ( !level.onlineGame && !level.systemLink )
return;
// Increase the range of the probability to reduce the chances of picking the option when the bot's level is less than BOT_RANK_ALL_OPTIONS_AVAILABLE
// (in system link all options are available)
numOptions = level.botWeaponOptionsProb[optionType].size;
maxProb = level.botWeaponOptionsProb[optionType][numOptions-1];
if ( !level.systemLink && self.pers[ "rank" ] < BOT_RANK_ALL_OPTIONS_AVAILABLE )
maxProb += BOT_RANK_OPTIONS_MULTIPLIER * maxProb * ( ( BOT_RANK_ALL_OPTIONS_AVAILABLE - self.pers[ "rank" ] ) / BOT_RANK_ALL_OPTIONS_AVAILABLE );
rnd = RandomInt( Int( maxProb ) );
for (i=0 ; i<numOptions ; i++)
{
if ( level.botWeaponOptionsProb[optionType][i] > rnd )
{
self BotClassSetWeaponOption( weaponclass, primary, optionType, level.botWeaponOptionsId[optionType][i] );
break;
}
}
}
function choose_primary_attachments( weaponclass, weapon, allocation, allocation_max )
{
attachments = weapon.supportedAttachments;
remaining = allocation_max - allocation;
if ( !attachments.size || !remaining )
{
return 0;
}
attachment_action = chose_action( "3_attachments", 25, "2_attachments", 65, "1_attachments", 10, "none", 5 );
if ( remaining >= 4 && attachment_action == "3_attachments" )
{
a1 = array::random( attachments );
self BotClassAddAttachment( weaponclass, weapon, a1, "primaryattachment1" );
count = 1;
attachments = GetWeaponAttachments( weapon, a1 );
if ( attachments.size )
{
a2 = array::random( attachments );
self BotClassAddAttachment( weaponclass, weapon, a2, "primaryattachment2" );
count++;
attachments = GetWeaponAttachments( weapon, a1, a2 );
if ( attachments.size )
{
a3 = array::random( attachments );
self BotClassAddItem( weaponclass, "BONUSCARD_PRIMARY_GUNFIGHTER" );
self BotClassAddAttachment( weaponclass, weapon, a3, "primaryattachment3" );
return 4;
}
}
return count;
}
else if ( remaining >= 2 && attachment_action == "2_attachments" )
{
a1 = array::random( attachments );
self BotClassAddAttachment( weaponclass, weapon, a1, "primaryattachment1" );
attachments = GetWeaponAttachments( weapon, a1 );
if ( attachments.size )
{
a2 = array::random( attachments );
self BotClassAddAttachment( weaponclass, weapon, a2, "primaryattachment2" );
return 2;
}
return 1;
}
else if ( remaining >= 1 && attachment_action == "1_attachments" )
{
a = array::random( attachments );
self BotClassAddAttachment( weaponclass, weapon, a, "primaryattachment1" );
return 1;
}
return 0;
}
function choose_secondary_attachments( weaponclass, weapon, allocation, allocation_max )
{
attachments = weapon.supportedAttachments ;
remaining = allocation_max - allocation;
if ( !attachments.size || !remaining )
{
return 0;
}
attachment_action = chose_action( "2_attachments", 10, "1_attachments", 40, "none", 50, "none", 0 );
if ( remaining >= 3 && attachment_action == "2_attachments" )
{
a1 = array::random( attachments );
self BotClassAddAttachment( weaponclass, weapon, a1, "secondaryattachment1" );
attachments = GetWeaponAttachments( weapon, a1 );
if ( attachments.size )
{
a2 = array::random( attachments );
self BotClassAddItem( weaponclass, "BONUSCARD_SECONDARY_GUNFIGHTER" );
self BotClassAddAttachment( weaponclass, weapon, a2, "secondaryattachment2" );
return 3;
}
return 1;
}
else if ( remaining >= 1 && attachment_action == "1_attachments" )
{
a = array::random( attachments );
self BotClassAddAttachment( weaponclass, weapon, a, "secondaryattachment1" );
return 1;
}
return 0;
}
function build_item_list()
{
items = [];
for( i = 0; i < STATS_TABLE_MAX_ITEMS; i++ )
{
row = tableLookupRowNum( level.statsTableID, STATS_TABLE_COL_NUMBERING, i );
if ( row > -1 )
{
slot = tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_SLOT );
if ( slot == "" )
{
continue;
}
number = Int( tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_NUMBERING ) );
/*
if ( SessionModeIsPrivate() && self IsItemLocked( number ) )
{
continue;
}
*/
allocation = Int( tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_ALLOCATION ) );
if ( allocation < 0 )
{
continue;
}
name = tableLookupColumnForRow( level.statsTableID, row, STATS_TABLE_COL_NAME );
/*
if ( item_is_banned( slot, name ) )
{
continue;
}
*/
if ( !isdefined( items[slot] ) )
{
items[slot] = [];
}
items[ slot ][ items[slot].size ] = name;
}
}
return items;
}
function build_claimed_list( items )
{
claimed = [];
keys = GetArrayKeys( items );
foreach( key in keys )
{
claimed[ key ] = 0;
}
return claimed;
}