#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_buttons; #using scripts\shared\bots\bot_traversals; #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\_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.1 #define RESPAWN_INTERVAL 0.1 #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; /# level.botDevguiCmd = &bot_devgui_cmd; level thread system_devgui_gadget_think(); #/ } function init() { level endon( "game_ended" ); level.botSoak = is_bot_soak(); if ( ( level.rankedMatch && !level.botSoak ) || !init_bot_gametype() ) { return; } wait_for_host(); level thread populate_bots(); } // Init Utils //======================================== function is_bot_soak() { /* return GetDvarInt( "sv_botsoak", 0 ); */ 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; /# 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 ); } #/ } 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_satellite": case "killstreak_helicopter_player_gunner": 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 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" ); } 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(); /# for ( i = 0; i < enemies.size; i++ ) { if ( isplayer( enemies[i] ) && enemies[i] IsInMoveMode( "ufo", "noclip" ) ) { ArrayRemoveIndex( enemies, i ); i--; } } #/ 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 ) { } /# // 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 ); } } #/