From d9787cda5acb30ad2952b85978e467407d015788 Mon Sep 17 00:00:00 2001 From: joseupp Date: Mon, 13 Mar 2023 09:39:59 +0100 Subject: [PATCH 1/2] Enable bots in ranked matches --- data/scripts/mp/bots/_bot.gsc | Bin 0 -> 14640 bytes data/scripts/mp/bots/_bot.gsc_raw | 906 ++++++++++++++++++++++ data/scripts/mp/bots/_bot_loadout.gsc | Bin 0 -> 15500 bytes data/scripts/mp/bots/_bot_loadout.gsc_raw | 854 ++++++++++++++++++++ 4 files changed, 1760 insertions(+) create mode 100644 data/scripts/mp/bots/_bot.gsc create mode 100644 data/scripts/mp/bots/_bot.gsc_raw create mode 100644 data/scripts/mp/bots/_bot_loadout.gsc create mode 100644 data/scripts/mp/bots/_bot_loadout.gsc_raw diff --git a/data/scripts/mp/bots/_bot.gsc b/data/scripts/mp/bots/_bot.gsc new file mode 100644 index 0000000000000000000000000000000000000000..a13be6c09bb219cef5652d9160a0701be4bbbf4b GIT binary patch literal 14640 zcma)C34Bw<)}NcEr7aLDi|`QPX5aU%O>PpF(n9G%f!4MZ(llvlA#GxlmTuHO7DX0W zL_kEUD4X~Ik;Mg3T<|G8sECS)%2PxIMHC+oc?jP*Nv@QB_I+P|o!#c6@*Mt6M}h6{~AF^Ey(GKggi!~zf{5@ zA#YuEFdWi)1HKA29}3sid&7UQLRFr+3ZK?fSLbPRM?rU<*W>q>d%UwkJ@98$`~9G` z!qcNfjVD~yL*Y74?W`VHp{7u{qNWFCeYo1+18a7LCm5*hskJe#)*Z`q?`?Si2Fwlr zdt|u1ftqs9pYx%dJ6z{!sHh8h{GmTpfg>iXPQh)C7CvRr)=%+z~B3 zid0qjtG$6B^x^*@;+|1oTl-%mZ+8t=Nv-ey_Y&Q{if~02tJZypyC|&!OIBSwqsNxk zRD0_Jvtb3h!}WD_6?aU*+iGA61^u4diW+$Po}RmXf8Bo{xjIj<=T}$fEr&(jV?*np z@^HofGGpDl9u=#p4uz`yJZlUPXVg_x5DyZ36&@cMQxyn=D#rLdp^$rg zZKkKDg4`YqH+G~8^alP|;9aP(_s71OE}YvhqYLr&BEy;2R8hw+q%JZsi-pTI*tG$X zY1Tz9#JR{{t7wc$_% zHU>##i;HYaUf6+fv+=O(&8go4;m8$V&$FBIG{_gnS9< z4MJxH4&JqW1=QVt|7 zq=AtB4c_1xNQWSGKzaa@7t%OLdPtF9Uz$gs(WR!HVa#roshEH9)I&>`G%u!WjT;w6WLsOu8C9Bu5 z9n4T!M%DcV>H7sjr2k^etDL@h!O%sL9j4qusprU%-*zJ1t)8H~sOR0)nYu)y_eipX zzZY*sda2z~WJ=gp8811*F!f_wF7)nfKzhBV)l!+hAhRU(0bS)3f$=G0{u-qJViQ*- z^lsT5FWGN4`17j2(fV_d-k~W8WBwIk3fN!u^49`+$UO~|9=DiT~%{`wfi8&y2|1;y7i zDJ#mYl+_f{2X=2kx?h(iVLM6Os;8}~a zTmkLC&I!oBi|_N~TQajTkH0t6RPc$9_Pjnl@uY^C^eX2TTU)&a`tyqY zM<$W1a*Tw0NYr5_MGhk+4i=(JNY;`ctp&$r6F(%&G~UMXsXOP`d>8Cy)lyAkW%M0w zch1x|OP3zoI4@-XR*^tHcVK>_EFH%1qVZWhVXA(3XC>-*+Qg%kuD2v4+i6w?tMu(J z_5B;t@0G$jOAFA%&}j5aW$w8zSM!mc>P(@7MxQkk+9}a^9R&~nCw-qQPQF3apNum3 zbCAAHG5S8Fr#L3+8lc8AX&*_6QdVXcw57B4_SW<0IV!iD1mtoen zucwJ|MrCfWVk+Anq#G1FC1jSmH6uq}&bH?nL$aD;Hi@y`yIo!%);q>k39UCX&`)o` zVBXH^O|T?eNWNf7Q=?QXobQON*Nv(ay16k>jxzY`OHVhK+Qiq+NXF6=hhKRrg|=o$ zre!iwF7gMeaJ-eSt@`pJ?AMGEzgF6I>N9b4K3X&lmX~Oixj%59-$#m? zYUcO%Tq1K+1&i0__aVBBs%M0gza;k;|O|B@3lb5`*P`f+UUa(>^bgKdi#UfMPtZ_+yF za(`-pc5(|=Wnqj$~0P)s8j-CC+

5i+o(n5ak&1`$)1(8dqAD)*n~5 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 literal 0 HcmV?d00001 diff --git a/data/scripts/mp/bots/_bot.gsc_raw b/data/scripts/mp/bots/_bot.gsc_raw new file mode 100644 index 00000000..82b1e956 --- /dev/null +++ b/data/scripts/mp/bots/_bot.gsc_raw @@ -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 ) +{ + +} + diff --git a/data/scripts/mp/bots/_bot_loadout.gsc b/data/scripts/mp/bots/_bot_loadout.gsc new file mode 100644 index 0000000000000000000000000000000000000000..1dce334526baf75b8b04aa93e58387a34dae01fe GIT binary patch literal 15500 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 BP&@zt literal 0 HcmV?d00001 diff --git a/data/scripts/mp/bots/_bot_loadout.gsc_raw b/data/scripts/mp/bots/_bot_loadout.gsc_raw new file mode 100644 index 00000000..3b48b7f1 --- /dev/null +++ b/data/scripts/mp/bots/_bot_loadout.gsc_raw @@ -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 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; +} \ No newline at end of file From b2981d4de23b47fcccd5f9a4b119c68e936538c8 Mon Sep 17 00:00:00 2001 From: joseupp Date: Tue, 14 Mar 2023 13:47:06 +0100 Subject: [PATCH 2/2] Disable infected mode init. --- data/scripts/mp/bots/_bot.gsc | Bin 14640 -> 14624 bytes data/scripts/mp/bots/_bot.gsc_raw | 11 +++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/data/scripts/mp/bots/_bot.gsc b/data/scripts/mp/bots/_bot.gsc index a13be6c09bb219cef5652d9160a0701be4bbbf4b..ae8a168e147f46df0acd22ee701ded9d4cc78e0b 100644 GIT binary patch literal 14624 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 literal 14640 zcma)C34Bw<)}NcEr7aLDi|`QPX5aU%O>PpF(n9G%f!4MZ(llvlA#GxlmTuHO7DX0W zL_kEUD4X~Ik;Mg3T<|G8sECS)%2PxIMHC+oc?jP*Nv@QB_I+P|o!#c6@*Mt6M}h6{~AF^Ey(GKggi!~zf{5@ zA#YuEFdWi)1HKA29}3sid&7UQLRFr+3ZK?fSLbPRM?rU<*W>q>d%UwkJ@98$`~9G` z!qcNfjVD~yL*Y74?W`VHp{7u{qNWFCeYo1+18a7LCm5*hskJe#)*Z`q?`?Si2Fwlr zdt|u1ftqs9pYx%dJ6z{!sHh8h{GmTpfg>iXPQh)C7CvRr)=%+z~B3 zid0qjtG$6B^x^*@;+|1oTl-%mZ+8t=Nv-ey_Y&Q{if~02tJZypyC|&!OIBSwqsNxk zRD0_Jvtb3h!}WD_6?aU*+iGA61^u4diW+$Po}RmXf8Bo{xjIj<=T}$fEr&(jV?*np z@^HofGGpDl9u=#p4uz`yJZlUPXVg_x5DyZ36&@cMQxyn=D#rLdp^$rg zZKkKDg4`YqH+G~8^alP|;9aP(_s71OE}YvhqYLr&BEy;2R8hw+q%JZsi-pTI*tG$X zY1Tz9#JR{{t7wc$_% zHU>##i;HYaUf6+fv+=O(&8go4;m8$V&$FBIG{_gnS9< z4MJxH4&JqW1=QVt|7 zq=AtB4c_1xNQWSGKzaa@7t%OLdPtF9Uz$gs(WR!HVa#roshEH9)I&>`G%u!WjT;w6WLsOu8C9Bu5 z9n4T!M%DcV>H7sjr2k^etDL@h!O%sL9j4qusprU%-*zJ1t)8H~sOR0)nYu)y_eipX zzZY*sda2z~WJ=gp8811*F!f_wF7)nfKzhBV)l!+hAhRU(0bS)3f$=G0{u-qJViQ*- z^lsT5FWGN4`17j2(fV_d-k~W8WBwIk3fN!u^49`+$UO~|9=DiT~%{`wfi8&y2|1;y7i zDJ#mYl+_f{2X=2kx?h(iVLM6Os;8}~a zTmkLC&I!oBi|_N~TQajTkH0t6RPc$9_Pjnl@uY^C^eX2TTU)&a`tyqY zM<$W1a*Tw0NYr5_MGhk+4i=(JNY;`ctp&$r6F(%&G~UMXsXOP`d>8Cy)lyAkW%M0w zch1x|OP3zoI4@-XR*^tHcVK>_EFH%1qVZWhVXA(3XC>-*+Qg%kuD2v4+i6w?tMu(J z_5B;t@0G$jOAFA%&}j5aW$w8zSM!mc>P(@7MxQkk+9}a^9R&~nCw-qQPQF3apNum3 zbCAAHG5S8Fr#L3+8lc8AX&*_6QdVXcw57B4_SW<0IV!iD1mtoen zucwJ|MrCfWVk+Anq#G1FC1jSmH6uq}&bH?nL$aD;Hi@y`yIo!%);q>k39UCX&`)o` zVBXH^O|T?eNWNf7Q=?QXobQON*Nv(ay16k>jxzY`OHVhK+Qiq+NXF6=hhKRrg|=o$ zre!iwF7gMeaJ-eSt@`pJ?AMGEzgF6I>N9b4K3X&lmX~Oixj%59-$#m? zYUcO%Tq1K+1&i0__aVBBs%M0gza;k;|O|B@3lb5`*P`f+UUa(>^bgKdi#UfMPtZ_+yF za(`-pc5(|=Wnqj$~0P)s8j-CC+

5i+o(n5ak&1`$)1(8dqAD)*n~5 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 diff --git a/data/scripts/mp/bots/_bot.gsc_raw b/data/scripts/mp/bots/_bot.gsc_raw index 82b1e956..609e7a22 100644 --- a/data/scripts/mp/bots/_bot.gsc_raw +++ b/data/scripts/mp/bots/_bot.gsc_raw @@ -101,8 +101,11 @@ function init() level endon( "game_ended" ); level.botSoak = is_bot_soak(); - - init_bot_gametype(); + + if( level.rankedmatch && level.botsoak || !init_bot_gametype() ) + { + return; + } wait_for_host(); @@ -819,11 +822,11 @@ function init_bot_gametype() bot_escort::init(); return true; } - case "infect": +/* case "infect": { return true; } - case "gun": +*/ case "gun": { return true; }