From 33b1882fc85e37e1f37dfcd56ffd4648c2040abe Mon Sep 17 00:00:00 2001 From: cdnutter Date: Mon, 17 Jun 2024 19:49:12 -0700 Subject: [PATCH] fixed for sure --- .gitignore | 5 + maps/.DS_Store | Bin 0 -> 6148 bytes maps/mp/.DS_Store | Bin 0 -> 6148 bytes maps/mp/bots/.DS_Store | Bin 0 -> 6148 bytes maps/mp/bots/_bot.gsc | 1399 ++++ maps/mp/bots/_bot_chat.gsc | 3158 +++++++++ maps/mp/bots/_bot_internal.gsc | 3331 +++++++++ maps/mp/bots/_bot_script.gsc | 8689 ++++++++++++++++++++++++ maps/mp/bots/_bot_utility.gsc | 3537 ++++++++++ maps/mp/bots/_menu.gsc | 1389 ++++ maps/mp/bots/_wp_editor.gsc | 916 +++ maps/mp/bots/waypoints/_custom_map.gsc | 8 + maps/mp/perks/_perks.gsc | 400 ++ maps/mp/perks/_perksfunctions.gsc | 1011 +++ 14 files changed, 23843 insertions(+) create mode 100644 .gitignore create mode 100644 maps/.DS_Store create mode 100644 maps/mp/.DS_Store create mode 100644 maps/mp/bots/.DS_Store create mode 100644 maps/mp/bots/_bot.gsc create mode 100644 maps/mp/bots/_bot_chat.gsc create mode 100644 maps/mp/bots/_bot_internal.gsc create mode 100644 maps/mp/bots/_bot_script.gsc create mode 100644 maps/mp/bots/_bot_utility.gsc create mode 100644 maps/mp/bots/_menu.gsc create mode 100644 maps/mp/bots/_wp_editor.gsc create mode 100644 maps/mp/bots/waypoints/_custom_map.gsc create mode 100644 maps/mp/perks/_perks.gsc create mode 100644 maps/mp/perks/_perksfunctions.gsc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b32e82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +logs +scriptdata +scripts +bots.txt +maps/bots \ No newline at end of file diff --git a/maps/.DS_Store b/maps/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ddb73bc9e11db301e61b5f4a07d6a6ee0fb33025 GIT binary patch literal 6148 zcmeHK%}N6?5Kh`^Q$#4Dptpe6g4+s;cv;ta7F^MTO6|Hu7q^?GKklIv_O1`%J9zR1 zd;}lBCvhf8{h@l5A~P`glKDwC-)@r*V~l&lu+CVOF(yC}3l(U75cH!?Nx@p?0XfbQ z#43RPbCAZ-R5Tf8kpX;n1=eM0koD8A@2@{_oL62bmgeTmqAV7~g@2o)Et>fSKO;bYUXXmJ7$(9xTTuj~Gm>ZaOJ zK3tmEyQG6s zk1Vk;z1OP07Sqt>BmcSfo(Y2Tx1P=%|semSx z+Y*DDbg)Ys=UU7Snsml(@xkrN+*T-DuMYD|9nQFGkXmAZ82HXW(G072{~y3_`Iq|t zdlHR^0b<}^F~Cbrzv;o2?ADG-PI?tt6_}sV3zb4>oCRyC}P8f<{m*m>XtOD zWe!!9y_@6KT{Q>fHVM^l}UCL;*BI7{~JVe9y+Y`e9h)@WE+$X>kcZp|g6R z?7A0yQ{#ztwhmvd`^G8^?Z+y9{|f%ScK&?c^e2z{CpYVJ*SN%L-P>rIXDYu2+j5`i zCm{xi0b*cB7_di^Xz$2=XdA=;F|a!f;Q1gy5q*obL49;Uqe}ol2h3WakF^BmNQ=J3 z+8}s9xJd;xsoa(r+@ynD+Bn~0ZP27MZi^3YSLU`t;d*tLU+QqieS_2z1H`~415GpR z;QfF6aQ(lTL?dE=7R=cdZ1y0!6{N+Tbb$I{GSxSiFkgfocJ} Y#15cuu{H=65c(0&G*CkfJShYJ0mKJo#sB~S literal 0 HcmV?d00001 diff --git a/maps/mp/bots/.DS_Store b/maps/mp/bots/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..96abd559772240cb48ae943d6e61d0c58e870f7d GIT binary patch literal 6148 zcmeHK%}N6?5T4O06)!z{%+YJ#U@hwt^aXUgpu)CUs3P9-az2k&kACx`v=~7zA~FNX zH<_Pg^Oa^2BI3nEKPQ?I(G*RPMX89GtF8kNJ_54Nk?D>$bWhuUTbbxDn&jJ0=#j4J ziALJ_{?*XsJYZWj_i!^`uJU@c9UY(7qt>}?yCHY*3V-G0_4(qf$o`v=L+ z7DHGdY^gv?Wq)F@rNf>)u5m1emQJiIV;*1m{qe$nCFZ2z#EoI}!9Xyu&%mJ#XHx$! z@mH!o^7m6>6bu9d|BL~iw99sZO?hqov^}Y{3GE6^MB<7l5a?Qpfm(_&kaOfro#s!X Z!{0a-LzzYFnhuPMfD#g2Fz^cuya6R literal 0 HcmV?d00001 diff --git a/maps/mp/bots/_bot.gsc b/maps/mp/bots/_bot.gsc new file mode 100644 index 0000000..fe39e3a --- /dev/null +++ b/maps/mp/bots/_bot.gsc @@ -0,0 +1,1399 @@ +/* + _bot + Author: INeedGames + Date: 09/26/2020 + The entry point and manager of the bots. +*/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\bots\_bot_utility; + +/* + Initiates the whole bot scripts. +*/ +init() +{ + level.bw_version = "2.3.0"; + + if ( getdvar( "bots_main" ) == "" ) + { + setdvar( "bots_main", true ); + } + + if ( !getdvarint( "bots_main" ) ) + { + return; + } + + if ( !wait_for_builtins() ) + { + println( "FATAL: NO BUILT-INS FOR BOTS" ); + } + + thread load_waypoints(); + thread hook_callbacks(); + + if ( getdvar( "bots_main_GUIDs" ) == "" ) + { + setdvar( "bots_main_GUIDs", "" ); // guids of players who will be given host powers, comma seperated + } + + if ( getdvar( "bots_main_firstIsHost" ) == "" ) + { + setdvar( "bots_main_firstIsHost", false ); // first play to connect is a host + } + + if ( getdvar( "bots_main_waitForHostTime" ) == "" ) + { + setdvar( "bots_main_waitForHostTime", 10.0 ); // how long to wait to wait for the host player + } + + if ( getdvar( "bots_main_kickBotsAtEnd" ) == "" ) + { + setdvar( "bots_main_kickBotsAtEnd", false ); // kicks the bots at game end + } + + if ( getdvar( "bots_manage_add" ) == "" ) + { + setdvar( "bots_manage_add", 0 ); // amount of bots to add to the game + } + + if ( getdvar( "bots_manage_fill" ) == "" ) + { + setdvar( "bots_manage_fill", 0 ); // amount of bots to maintain + } + + if ( getdvar( "bots_manage_fill_spec" ) == "" ) + { + setdvar( "bots_manage_fill_spec", true ); // to count for fill if player is on spec team + } + + if ( getdvar( "bots_manage_fill_mode" ) == "" ) + { + setdvar( "bots_manage_fill_mode", 0 ); // fill mode, 0 adds everyone, 1 just bots, 2 maintains at maps, 3 is 2 with 1 + } + + if ( getdvar( "bots_manage_fill_kick" ) == "" ) + { + setdvar( "bots_manage_fill_kick", false ); // kick bots if too many + } + + if ( getdvar( "bots_manage_fill_watchplayers" ) == "" ) + { + setdvar( "bots_manage_fill_watchplayers", false ); // add bots when player exists, kick if not + } + + if ( getdvar( "bots_team" ) == "" ) + { + setdvar( "bots_team", "autoassign" ); // which team for bots to join + } + + if ( getdvar( "bots_team_amount" ) == "" ) + { + setdvar( "bots_team_amount", 0 ); // amount of bots on axis team + } + + if ( getdvar( "bots_team_force" ) == "" ) + { + setdvar( "bots_team_force", false ); // force bots on team + } + + if ( getdvar( "bots_team_mode" ) == "" ) + { + setdvar( "bots_team_mode", 0 ); // counts just bots when 1 + } + + if ( getdvar( "bots_skill" ) == "" ) + { + setdvar( "bots_skill", 0 ); // 0 is random, 1 is easy 7 is hard, 8 is custom, 9 is completely random + } + + if ( getdvar( "bots_skill_axis_hard" ) == "" ) + { + setdvar( "bots_skill_axis_hard", 0 ); // amount of hard bots on axis team + } + + if ( getdvar( "bots_skill_axis_med" ) == "" ) + { + setdvar( "bots_skill_axis_med", 0 ); + } + + if ( getdvar( "bots_skill_allies_hard" ) == "" ) + { + setdvar( "bots_skill_allies_hard", 0 ); + } + + if ( getdvar( "bots_skill_allies_med" ) == "" ) + { + setdvar( "bots_skill_allies_med", 0 ); + } + + if ( getdvar( "bots_skill_min" ) == "" ) + { + setdvar( "bots_skill_min", 1 ); + } + + if ( getdvar( "bots_skill_max" ) == "" ) + { + setdvar( "bots_skill_max", 7 ); + } + + if ( getdvar( "bots_loadout_reasonable" ) == "" ) // filter out the bad 'guns' and perks + { + setdvar( "bots_loadout_reasonable", false ); + } + + if ( getdvar( "bots_loadout_allow_op" ) == "" ) // allows jug, marty and laststand + { + setdvar( "bots_loadout_allow_op", true ); + } + + if ( getdvar( "bots_loadout_rank" ) == "" ) // what rank the bots should be around, -1 is around the players, 0 is all random + { + setdvar( "bots_loadout_rank", -1 ); + } + + if ( getdvar( "bots_loadout_prestige" ) == "" ) // what pretige the bots will be, -1 is the players, -2 is random + { + setdvar( "bots_loadout_prestige", -1 ); + } + + if ( getdvar( "bots_play_move" ) == "" ) // bots move + { + setdvar( "bots_play_move", true ); + } + + if ( getdvar( "bots_play_knife" ) == "" ) // bots knife + { + setdvar( "bots_play_knife", true ); + } + + if ( getdvar( "bots_play_fire" ) == "" ) // bots fire + { + setdvar( "bots_play_fire", true ); + } + + if ( getdvar( "bots_play_nade" ) == "" ) // bots grenade + { + setdvar( "bots_play_nade", true ); + } + + if ( getdvar( "bots_play_take_carepackages" ) == "" ) // bots take carepackages + { + setdvar( "bots_play_take_carepackages", true ); + } + + if ( getdvar( "bots_play_obj" ) == "" ) // bots play the obj + { + setdvar( "bots_play_obj", true ); + } + + if ( getdvar( "bots_play_camp" ) == "" ) // bots camp and follow + { + setdvar( "bots_play_camp", true ); + } + + if ( getdvar( "bots_play_jumpdrop" ) == "" ) // bots jump and dropshot + { + setdvar( "bots_play_jumpdrop", true ); + } + + if ( getdvar( "bots_play_target_other" ) == "" ) // bot target non play ents (vehicles) + { + setdvar( "bots_play_target_other", true ); + } + + if ( getdvar( "bots_play_killstreak" ) == "" ) // bot use killstreaks + { + setdvar( "bots_play_killstreak", true ); + } + + if ( getdvar( "bots_play_ads" ) == "" ) // bot ads + { + setdvar( "bots_play_ads", true ); + } + + if ( getdvar( "bots_play_aim" ) == "" ) + { + setdvar( "bots_play_aim", true ); + } + + if ( !isdefined( game[ "botWarfare" ] ) ) + { + game[ "botWarfare" ] = true; + game[ "botWarfareInitTime" ] = gettime(); + } + + level.bot_inittime = gettime(); + + level.defuseobject = undefined; + level.bots_smokelist = List(); + level.bots_fraglist = List(); + + level.bots_minsprintdistance = 315; + level.bots_minsprintdistance *= level.bots_minsprintdistance; + level.bots_mingrenadedistance = 256; + level.bots_mingrenadedistance *= level.bots_mingrenadedistance; + level.bots_maxgrenadedistance = 1024; + level.bots_maxgrenadedistance *= level.bots_maxgrenadedistance; + level.bots_maxknifedistance = 128; + level.bots_maxknifedistance *= level.bots_maxknifedistance; + level.bots_goaldistance = 27.5; + level.bots_goaldistance *= level.bots_goaldistance; + level.bots_noadsdistance = 200; + level.bots_noadsdistance *= level.bots_noadsdistance; + level.bots_maxshotgundistance = 500; + level.bots_maxshotgundistance *= level.bots_maxshotgundistance; + level.bots_listendist = 100; + + level.smokeradius = 255; + + level.bots = []; + + level.bots_fullautoguns = []; + level.bots_fullautoguns[ "aa12" ] = true; + level.bots_fullautoguns[ "ak47" ] = true; + level.bots_fullautoguns[ "aug" ] = true; + level.bots_fullautoguns[ "fn2000" ] = true; + level.bots_fullautoguns[ "glock" ] = true; + level.bots_fullautoguns[ "kriss" ] = true; + level.bots_fullautoguns[ "m4" ] = true; + level.bots_fullautoguns[ "m240" ] = true; + level.bots_fullautoguns[ "masada" ] = true; + level.bots_fullautoguns[ "mg4" ] = true; + level.bots_fullautoguns[ "mp5k" ] = true; + level.bots_fullautoguns[ "p90" ] = true; + level.bots_fullautoguns[ "pp2000" ] = true; + level.bots_fullautoguns[ "rpd" ] = true; + level.bots_fullautoguns[ "sa80" ] = true; + level.bots_fullautoguns[ "scar" ] = true; + level.bots_fullautoguns[ "tavor" ] = true; + level.bots_fullautoguns[ "tmp" ] = true; + level.bots_fullautoguns[ "ump45" ] = true; + level.bots_fullautoguns[ "uzi" ] = true; + + level.bots_fullautoguns[ "ac130" ] = true; + level.bots_fullautoguns[ "heli" ] = true; + + level.bots_fullautoguns[ "ak47classic" ] = true; + level.bots_fullautoguns[ "ak74u" ] = true; + level.bots_fullautoguns[ "peacekeeper" ] = true; + + level thread fixGamemodes(); + level thread fixPredMissile(); + + level thread onPlayerConnect(); + level thread addNotifyOnAirdrops(); + level thread watchScrabler(); + + level thread handleBots(); + level thread onPlayerChat(); + + array_thread( getentarray( "misc_turret", "classname" ), ::turret_monitoruse_watcher ); +} + +/* + Change func pointer to ours, so that we can link the player ref to the rocket +*/ +fixPredMissile() +{ + for ( i = 0; i < 19; i++ ) + { + if ( isdefined( level.killstreakfuncs ) && isdefined( level.killstreakfuncs[ "predator_missile" ] ) ) + { + level.killstreakfuncs[ "predator_missile" ] = ::tryUsePredatorMissileFix; + break; + } + + wait 0.05; + } +} + +/* + Starts the threads for bots. +*/ +handleBots() +{ + level thread teamBots(); + level thread diffBots(); + level addBots(); + + while ( !level.intermission ) + { + wait 0.05; + } + + setdvar( "bots_manage_add", getBotArray().size ); + + if ( !getdvarint( "bots_main_kickBotsAtEnd" ) ) + { + return; + } + + bots = getBotArray(); + + for ( i = 0; i < bots.size; i++ ) + { + kick( bots[ i ] getentitynumber(), "EXE_PLAYERKICKED" ); + } +} + +/* + The hook callback for when any player becomes damaged. +*/ +onPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset ) +{ + if ( self is_bot() ) + { + self maps\mp\bots\_bot_internal::onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset ); + self maps\mp\bots\_bot_script::onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset ); + } + + self [[ level.prevcallbackplayerdamage ]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset ); +} + +/* + The hook callback when any player gets killed. +*/ +onPlayerKilled( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration ) +{ + if ( self is_bot() ) + { + self maps\mp\bots\_bot_internal::onKilled( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration ); + self maps\mp\bots\_bot_script::onKilled( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration ); + } + + self [[ level.prevcallbackplayerkilled ]]( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration ); +} + +/* + Starts the callbacks. +*/ +hook_callbacks() +{ + level waittill( "prematch_over" ); // iw4madmin waits this long for some reason... + wait 0.05; // so we need to be one frame after it sets up its callbacks. + level.prevcallbackplayerdamage = level.callbackplayerdamage; + level.callbackplayerdamage = ::onPlayerDamage; + + level.prevcallbackplayerkilled = level.callbackplayerkilled; + level.callbackplayerkilled = ::onPlayerKilled; +} + +/* + Fixes gamemodes when level starts. +*/ +fixGamemodes() +{ + for ( i = 0; i < 19; i++ ) + { + if ( isdefined( level.bombzones ) && level.gametype == "sd" ) + { + for ( i = 0; i < level.bombzones.size; i++ ) + { + level.bombzones[ i ].onuse = ::onUsePlantObjectFix; + } + + break; + } + + if ( isdefined( level.radios ) && level.gametype == "koth" ) + { + level thread fixKoth(); + + break; + } + + if ( isdefined( level.bombzones ) && level.gametype == "dd" ) + { + level thread fixDem(); + + break; + } + + wait 0.05; + } +} + +/* + Converts t5 dd to iw4 +*/ +fixDem() +{ + for ( ;; ) + { + level.bombaplanted = level.aplanted; + level.bombbplanted = level.bplanted; + + for ( i = 0; i < level.bombzones.size; i++ ) + { + bombzone = level.bombzones[ i ]; + + if ( isdefined( bombzone.trigger.trigger_off ) ) + { + bombzone.bombexploded = true; + } + else + { + bombzone.bombexploded = undefined; + } + } + + wait 0.05; + } +} + +/* + Fixes the king of the hill headquarters obj +*/ +fixKoth() +{ + level.radio = undefined; + + for ( ;; ) + { + wait 0.05; + + if ( !isdefined( level.radioobject ) ) + { + continue; + } + + for ( i = level.radios.size - 1; i >= 0; i-- ) + { + if ( level.radioobject != level.radios[ i ].gameobject ) + { + continue; + } + + level.radio = level.radios[ i ]; + break; + } + + while ( isdefined( level.radioobject ) && level.radio.gameobject == level.radioobject ) + { + wait 0.05; + } + } +} + +/* + Adds a notify when the airdrop is dropped +*/ +addNotifyOnAirdrops_loop() +{ + dropCrates = getentarray( "care_package", "targetname" ); + + for ( i = dropCrates.size - 1; i >= 0; i-- ) + { + airdrop = dropCrates[ i ]; + + if ( isdefined( airdrop.doingphysics ) ) + { + continue; + } + + airdrop.doingphysics = true; + airdrop thread doNotifyOnAirdrop(); + } +} + +/* + Adds a notify when the airdrop is dropped +*/ +addNotifyOnAirdrops() +{ + for ( ;; ) + { + wait 1; + addNotifyOnAirdrops_loop(); + } +} + +/* + Does the notify +*/ +doNotifyOnAirdrop() +{ + self endon( "death" ); + self waittill( "physics_finished" ); + + self.doingphysics = false; + + if ( isdefined( self.owner ) ) + { + self.owner notify( "crate_physics_done" ); + } + + self thread onCarepackageCaptured(); +} + +/* + Waits to be captured +*/ +onCarepackageCaptured() +{ + self endon( "death" ); + + self waittill( "captured", player ); + + if ( isdefined( self.owner ) && self.owner is_bot() ) + { + self.owner BotNotifyBotEvent( "crate_cap", "captured", self, player ); + } +} + +/* + Thread when any player connects. Starts the threads needed. +*/ +onPlayerConnect() +{ + for ( ;; ) + { + level waittill( "connected", player ); + + player.bot_isscrambled = false; + + player thread onGrenadeFire(); + player thread onWeaponFired(); + + player thread connected(); + } +} + +/* + Watches players with scrambler perk +*/ +watchScrabler_loop() +{ + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + player.bot_isscrambled = false; + } + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( !player _hasperk( "specialty_localjammer" ) || !isreallyalive( player ) ) + { + continue; + } + + if ( player isemped() ) + { + continue; + } + + for ( h = level.players.size - 1; h >= 0; h-- ) + { + player2 = level.players[ h ]; + + if ( player2 == player ) + { + continue; + } + + if ( level.teambased && player2.team == player.team ) + { + continue; + } + + if ( distancesquared( player2.origin, player.origin ) > 256 * 256 ) + { + continue; + } + + player2.bot_isscrambled = true; + } + } +} + +/* + Watches players with scrambler perk +*/ +watchScrabler() +{ + for ( ;; ) + { + wait 1; + + watchScrabler_loop(); + } +} + +/* + When a bot disconnects. +*/ +onDisconnectPlayer() +{ + name = self.name; + + self waittill( "disconnect" ); + waittillframeend; + + for ( i = 0; i < level.bots.size; i++ ) + { + bot = level.bots[ i ]; + bot BotNotifyBotEvent( "connection", "disconnected", self, name ); + } +} + +/* + When a bot disconnects. +*/ +onDisconnect() +{ + self waittill( "disconnect" ); + + level.bots = array_remove( level.bots, self ); +} + +/* + Called when a player connects. +*/ +connected() +{ + self endon( "disconnect" ); + + for ( i = 0; i < level.bots.size; i++ ) + { + bot = level.bots[ i ]; + bot BotNotifyBotEvent( "connection", "connected", self, self.name ); + } + + self thread onDisconnectPlayer(); + + if ( !isdefined( self.pers[ "bot_host" ] ) ) + { + self thread doHostCheck(); + } + + if ( !self is_bot() ) + { + return; + } + + if ( !isdefined( self.pers[ "isBot" ] ) ) + { + // fast_restart occured... + self.pers[ "isBot" ] = true; + } + + if ( !isdefined( self.pers[ "isBotWarfare" ] ) ) + { + self.pers[ "isBotWarfare" ] = true; + self thread added(); + } + + self thread maps\mp\bots\_bot_internal::connected(); + self thread maps\mp\bots\_bot_script::connected(); + + level.bots[ level.bots.size ] = self; + self thread onDisconnect(); + + level notify( "bot_connected", self ); + + self thread watchBotDebugEvent(); +} + +/* + DEBUG +*/ +watchBotDebugEvent() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "bot_event", msg, str, b, c, d, e, f, g ); + + if ( getdvarint( "bots_main_debug" ) >= 2 ) + { + big_str = "Bot Warfare debug: " + self.name + ": " + msg; + + if ( isdefined( str ) && isstring( str ) ) + { + big_str += ", " + str; + } + + if ( isdefined( b ) && isstring( b ) ) + { + big_str += ", " + b; + } + + if ( isdefined( c ) && isstring( c ) ) + { + big_str += ", " + c; + } + + if ( isdefined( d ) && isstring( d ) ) + { + big_str += ", " + d; + } + + if ( isdefined( e ) && isstring( e ) ) + { + big_str += ", " + e; + } + + if ( isdefined( f ) && isstring( f ) ) + { + big_str += ", " + f; + } + + if ( isdefined( g ) && isstring( g ) ) + { + big_str += ", " + g; + } + + BotBuiltinPrintConsole( big_str ); + } + else if ( msg == "debug" && getdvarint( "bots_main_debug" ) ) + { + BotBuiltinPrintConsole( "Bot Warfare debug: " + self.name + ": " + str ); + } + } +} + +/* + When a bot gets added into the game. +*/ +added() +{ + self endon( "disconnect" ); + + self thread maps\mp\bots\_bot_internal::added(); + self thread maps\mp\bots\_bot_script::added(); +} + +/* + Adds a bot to the game. +*/ +add_bot() +{ + bot = addtestclient(); + + if ( isdefined( bot ) ) + { + bot.pers[ "isBot" ] = true; + bot.pers[ "isBotWarfare" ] = true; + bot thread added(); + } +} + +/* + A server thread for monitoring all bot's difficulty levels for custom server settings. +*/ +diffBots_loop() +{ + var_allies_hard = getdvarint( "bots_skill_allies_hard" ); + var_allies_med = getdvarint( "bots_skill_allies_med" ); + var_axis_hard = getdvarint( "bots_skill_axis_hard" ); + var_axis_med = getdvarint( "bots_skill_axis_med" ); + var_skill = getdvarint( "bots_skill" ); + + allies_hard = 0; + allies_med = 0; + axis_hard = 0; + axis_med = 0; + + if ( var_skill == 8 ) + { + playercount = level.players.size; + + for ( i = 0; i < playercount; i++ ) + { + player = level.players[ i ]; + + if ( !isdefined( player.pers[ "team" ] ) ) + { + continue; + } + + if ( !player is_bot() ) + { + continue; + } + + if ( player.pers[ "team" ] == "axis" ) + { + if ( axis_hard < var_axis_hard ) + { + axis_hard++; + player.pers[ "bots" ][ "skill" ][ "base" ] = 7; + } + else if ( axis_med < var_axis_med ) + { + axis_med++; + player.pers[ "bots" ][ "skill" ][ "base" ] = 4; + } + else + { + player.pers[ "bots" ][ "skill" ][ "base" ] = 1; + } + } + else if ( player.pers[ "team" ] == "allies" ) + { + if ( allies_hard < var_allies_hard ) + { + allies_hard++; + player.pers[ "bots" ][ "skill" ][ "base" ] = 7; + } + else if ( allies_med < var_allies_med ) + { + allies_med++; + player.pers[ "bots" ][ "skill" ][ "base" ] = 4; + } + else + { + player.pers[ "bots" ][ "skill" ][ "base" ] = 1; + } + } + } + } + else if ( var_skill != 0 && var_skill != 9 ) + { + playercount = level.players.size; + + for ( i = 0; i < playercount; i++ ) + { + player = level.players[ i ]; + + if ( !player is_bot() ) + { + continue; + } + + player.pers[ "bots" ][ "skill" ][ "base" ] = var_skill; + } + } + + playercount = level.players.size; + min_diff = getdvarint( "bots_skill_min" ); + max_diff = getdvarint( "bots_skill_max" ); + + for ( i = 0; i < playercount; i++ ) + { + player = level.players[ i ]; + + if ( !player is_bot() ) + { + continue; + } + + player.pers[ "bots" ][ "skill" ][ "base" ] = int( clamp( player.pers[ "bots" ][ "skill" ][ "base" ], min_diff, max_diff ) ); + } +} + +/* + A server thread for monitoring all bot's difficulty levels for custom server settings. +*/ +diffBots() +{ + for ( ;; ) + { + wait 1.5; + + diffBots_loop(); + } +} + +/* + A server thread for monitoring all bot's teams for custom server settings. +*/ +teamBots_loop() +{ + teamAmount = getdvarint( "bots_team_amount" ); + toTeam = getdvar( "bots_team" ); + + alliesbots = 0; + alliesplayers = 0; + axisbots = 0; + axisplayers = 0; + + playercount = level.players.size; + + for ( i = 0; i < playercount; i++ ) + { + player = level.players[ i ]; + + if ( !isdefined( player.pers[ "team" ] ) ) + { + continue; + } + + if ( player is_bot() ) + { + if ( player.pers[ "team" ] == "allies" ) + { + alliesbots++; + } + else if ( player.pers[ "team" ] == "axis" ) + { + axisbots++; + } + } + else + { + if ( player.pers[ "team" ] == "allies" ) + { + alliesplayers++; + } + else if ( player.pers[ "team" ] == "axis" ) + { + axisplayers++; + } + } + } + + allies = alliesbots; + axis = axisbots; + + if ( !getdvarint( "bots_team_mode" ) ) + { + allies += alliesplayers; + axis += axisplayers; + } + + if ( toTeam != "custom" ) + { + if ( getdvarint( "bots_team_force" ) ) + { + if ( toTeam == "autoassign" ) + { + if ( abs( axis - allies ) > 1 ) + { + toTeam = "axis"; + + if ( axis > allies ) + { + toTeam = "allies"; + } + } + } + + if ( toTeam != "autoassign" ) + { + playercount = level.players.size; + + for ( i = 0; i < playercount; i++ ) + { + player = level.players[ i ]; + + if ( !isdefined( player.pers[ "team" ] ) ) + { + continue; + } + + if ( !player is_bot() ) + { + continue; + } + + if ( player.pers[ "team" ] == toTeam ) + { + continue; + } + + if ( toTeam == "allies" ) + { + player thread [[ level.allies ]](); + } + else if ( toTeam == "axis" ) + { + player thread [[ level.axis ]](); + } + else + { + player thread [[ level.spectator ]](); + } + + break; + } + } + } + } + else + { + playercount = level.players.size; + + for ( i = 0; i < playercount; i++ ) + { + player = level.players[ i ]; + + if ( !isdefined( player.pers[ "team" ] ) ) + { + continue; + } + + if ( !player is_bot() ) + { + continue; + } + + if ( player.pers[ "team" ] == "axis" ) + { + if ( axis > teamAmount ) + { + player thread [[ level.allies ]](); + break; + } + } + else + { + if ( axis < teamAmount ) + { + player thread [[ level.axis ]](); + break; + } + else if ( player.pers[ "team" ] != "allies" ) + { + player thread [[ level.allies ]](); + break; + } + } + } + } +} + +/* + A server thread for monitoring all bot's teams for custom server settings. +*/ +teamBots() +{ + for ( ;; ) + { + wait 1.5; + teamBots_loop(); + } +} + +/* + A server thread for monitoring all bot's in game. Will add and kick bots according to server settings. +*/ +addBots_loop() +{ + botsToAdd = getdvarint( "bots_manage_add" ); + + if ( botsToAdd > 0 ) + { + setdvar( "bots_manage_add", 0 ); + + if ( botsToAdd > 64 ) + { + botsToAdd = 64; + } + + for ( ; botsToAdd > 0; botsToAdd-- ) + { + level add_bot(); + wait 0.25; + } + } + + fillMode = getdvarint( "bots_manage_fill_mode" ); + + if ( fillMode == 2 || fillMode == 3 ) + { + setdvar( "bots_manage_fill", getGoodMapAmount() ); + } + + fillAmount = getdvarint( "bots_manage_fill" ); + + players = 0; + bots = 0; + spec = 0; + axisplayers = 0; + alliesplayers = 0; + + playercount = level.players.size; + + for ( i = 0; i < playercount; i++ ) + { + player = level.players[ i ]; + + if ( player is_bot() ) + { + bots++; + } + else if ( !isdefined( player.pers[ "team" ] ) || ( player.pers[ "team" ] != "axis" && player.pers[ "team" ] != "allies" ) ) + { + spec++; + } + else + { + players++; + + if ( player.pers[ "team" ] == "axis" ) + { + axisplayers++; + } + else if ( player.pers[ "team" ] == "allies" ) + { + alliesplayers++; + } + } + } + + if ( getdvarint( "bots_manage_fill_spec" ) ) + { + players += spec; + } + + if ( !randomint( 999 ) ) + { + setdvar( "testclients_doreload", true ); + wait 0.1; + setdvar( "testclients_doreload", false ); + doExtraCheck(); + } + + amount = bots; + + if ( fillMode == 0 || fillMode == 2 ) + { + amount += players; + } + + // use bots as balance + if ( fillMode == 4 ) + { + diffPlayers = abs( alliesplayers - axisplayers ); + amount = fillAmount - ( diffPlayers - bots ); + + if ( players + diffPlayers < fillAmount ) + { + amount = players + bots; + } + } + + if ( players <= 0 && getdvarint( "bots_manage_fill_watchplayers" ) ) + { + amount = fillAmount + bots; + } + + if ( amount < fillAmount ) + { + setdvar( "bots_manage_add", fillAmount - amount ); + } + else if ( amount > fillAmount && getdvarint( "bots_manage_fill_kick" ) ) + { + botsToKick = amount - fillAmount; + + if ( botsToKick > 64 ) + { + botsToKick = 64; + } + + for ( i = 0; i < botsToKick; i++ ) + { + tempBot = getBotToKick(); + + if ( isdefined( tempBot ) ) + { + kick( tempBot getentitynumber(), "EXE_PLAYERKICKED" ); + + wait 0.25; + } + } + } +} + +/* + A server thread for monitoring all bot's in game. Will add and kick bots according to server settings. +*/ +addBots() +{ + level endon( "game_ended" ); + + bot_wait_for_host(); + + for ( ;; ) + { + wait 1.5; + + addBots_loop(); + } +} + +/* + A thread for ALL players, will monitor and grenades thrown. +*/ +onGrenadeFire() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill ( "grenade_fire", grenade, weaponName ); + + if ( !isdefined( grenade ) ) + { + continue; + } + + grenade.name = weaponName; + + if ( weaponName == "smoke_grenade_mp" ) + { + grenade thread AddToSmokeList(); + } + else if ( issubstr( weaponName, "frag_" ) ) + { + grenade thread AddToFragList( self ); + } + } +} + +/* + Adds a frag grenade to the list of all frags +*/ +AddToFragList( who ) +{ + grenade = spawnstruct(); + grenade.origin = self getorigin(); + grenade.velocity = ( 0, 0, 0 ); + grenade.grenade = self; + grenade.owner = who; + grenade.team = who.team; + grenade.throwback = undefined; + + grenade thread thinkFrag(); + + level.bots_fraglist ListAdd( grenade ); +} + +/* + Watches while the frag exists +*/ +thinkFrag() +{ + while ( isdefined( self.grenade ) ) + { + nowOrigin = self.grenade getorigin(); + self.velocity = ( nowOrigin - self.origin ) * 20; + self.origin = nowOrigin; + + wait 0.05; + } + + level.bots_fraglist ListRemove( self ); +} + +/* + Adds a smoke grenade to the list of smokes in the game. Used to prevent bots from seeing through smoke. +*/ +AddToSmokeList() +{ + grenade = spawnstruct(); + grenade.origin = self getorigin(); + grenade.state = "moving"; + grenade.grenade = self; + + grenade thread thinkSmoke(); + + level.bots_smokelist ListAdd( grenade ); +} + +/* + The smoke grenade logic. +*/ +thinkSmoke() +{ + while ( isdefined( self.grenade ) ) + { + self.origin = self.grenade getorigin(); + self.state = "moving"; + wait 0.05; + } + + self.state = "smoking"; + wait 11.5; + + level.bots_smokelist ListRemove( self ); +} + +/* + A thread for ALL players when they fire. +*/ +onWeaponFired() +{ + self endon( "disconnect" ); + self.bots_firing = false; + + for ( ;; ) + { + self waittill( "weapon_fired" ); + self thread doFiringThread(); + } +} + +/* + Lets bot's know that the player is firing. +*/ +doFiringThread() +{ + self endon( "disconnect" ); + self endon( "weapon_fired" ); + self.bots_firing = true; + wait 1; + self.bots_firing = false; +} + +/* + When a player chats +*/ +onPlayerChat() +{ + for ( ;; ) + { + level waittill( "say", message, player, is_hidden ); + + for ( i = 0; i < level.bots.size; i++ ) + { + bot = level.bots[ i ]; + + bot BotNotifyBotEvent( "chat", "chat", message, player, is_hidden ); + } + } +} + +/* + Monitors turret usage +*/ +turret_monitoruse_watcher() +{ + self endon( "death" ); + + for ( ;; ) + { + self waittill ( "trigger", player ); + + self monitor_player_turret( player ); + + self.owner = undefined; + + if ( isdefined( player ) ) + { + player.turret = undefined; + } + } +} + +/* + While player uses turret +*/ +monitor_player_turret( player ) +{ + player endon( "death" ); + player endon( "disconnect" ); + + player.turret = self; + self.owner = player; + + self waittill( "turret_deactivate" ); +} diff --git a/maps/mp/bots/_bot_chat.gsc b/maps/mp/bots/_bot_chat.gsc new file mode 100644 index 0000000..b7b42d0 --- /dev/null +++ b/maps/mp/bots/_bot_chat.gsc @@ -0,0 +1,3158 @@ +/* + _bot_chat + Author: INeedGames + Date: 04/17/2022 + Does bot chatter. +*/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\bots\_bot_utility; + +/* + Init +*/ +init() +{ + if ( getdvar( "bots_main_chat" ) == "" ) + { + setdvar( "bots_main_chat", 1.0 ); + } + + level thread onBotConnected(); +} + +/* + Bot connected +*/ +onBotConnected() +{ + for ( ;; ) + { + level waittill( "bot_connected", bot ); + + bot thread start_chat_threads(); + } +} + +/* + Does the chatter +*/ +BotDoChat( chance, string, isTeam ) +{ + mod = getdvarfloat( "bots_main_chat" ); + + if ( mod <= 0.0 ) + { + return; + } + + if ( chance >= 100 || mod >= 100.0 || ( randomint( 100 ) < ( chance * mod ) + 0 ) ) + { + if ( isdefined( isTeam ) && isTeam ) + { + self sayteam( string ); + } + else + { + self sayall( string ); + } + } +} + +/* + Threads for bots +*/ +start_chat_threads() +{ + self endon( "disconnect" ); + + self thread start_onnuke_call(); + self thread start_random_chat(); + self thread start_chat_watch(); + self thread start_killed_watch(); + self thread start_death_watch(); + self thread start_endgame_watch(); + + self thread start_startgame_watch(); +} + +/* + Nuke gets called +*/ +start_onnuke_call() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + while ( !isdefined( level.nukeincoming ) && !isdefined( level.moabincoming ) ) + { + wait 0.05 + randomint( 4 ); + } + + self thread bot_onnukecall_watch(); + + wait level.nuketimer + 5; + } +} + +/* + death +*/ +start_death_watch() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "death" ); + + self thread bot_chat_death_watch( self.lastattacker, self.bots_lastks ); + + self.bots_lastks = 0; + } +} + +/* + start_endgame_watch +*/ +start_endgame_watch() +{ + self endon( "disconnect" ); + + level waittill ( "game_ended" ); + + self thread endgame_chat(); +} + +/* + Random chatting +*/ +start_random_chat() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + wait 1; + + if ( randomint( 100 ) < 1 ) + { + if ( randomint( 100 ) < 1 && isreallyalive( self ) ) + { + self thread doQuickMessage(); + } + } + } +} + +/* + Got a kill +*/ +start_killed_watch() +{ + self endon( "disconnect" ); + + self.bots_lastks = 0; + + for ( ;; ) + { + self waittill( "killed_enemy" ); + + if ( self.bots_lastks < self.pers[ "cur_kill_streak" ] ) + { + for ( i = self.bots_lastks + 1; i <= self.pers[ "cur_kill_streak" ]; i++ ) + { + self thread bot_chat_streak( i ); + } + } + + self.bots_lastks = self.pers[ "cur_kill_streak" ]; + + self thread bot_chat_killed_watch( self.lastkilledplayer ); + } +} + +/* + Starts things for the bot +*/ +start_chat_watch() +{ + self endon( "disconnect" ); + level endon ( "game_ended" ); + + for ( ;; ) + { + self waittill( "bot_event", msg, a, b, c, d, e, f, g ); + + switch ( msg ) + { + case "revive": + self thread bot_chat_revive_watch( a, b, c, d, e, f, g ); + break; + + case "killcam": + self thread bot_chat_killcam_watch( a, b, c, d, e, f, g ); + break; + + case "stuck": + self thread bot_chat_stuck_watch( a, b, c, d, e, f, g ); + break; + + case "tube": + self thread bot_chat_tube_watch( a, b, c, d, e, f, g ); + break; + + case "killstreak": + self thread bot_chat_killstreak_watch( a, b, c, d, e, f, g ); + break; + + case "crate_cap": + self thread bot_chat_crate_cap_watch( a, b, c, d, e, f, g ); + break; + + case "attack_vehicle": + self thread bot_chat_attack_vehicle_watch( a, b, c, d, e, f, g ); + break; + + case "follow_threat": + self thread bot_chat_follow_threat_watch( a, b, c, d, e, f, g ); + break; + + case "camp": + self thread bot_chat_camp_watch( a, b, c, d, e, f, g ); + break; + + case "follow": + self thread bot_chat_follow_watch( a, b, c, d, e, f, g ); + break; + + case "equ": + self thread bot_chat_equ_watch( a, b, c, d, e, f, g ); + break; + + case "nade": + self thread bot_chat_nade_watch( a, b, c, d, e, f, g ); + break; + + case "jav": + self thread bot_chat_jav_watch( a, b, c, d, e, f, g ); + break; + + case "throwback": + self thread bot_chat_throwback_watch( a, b, c, d, e, f, g ); + break; + + case "rage": + self thread bot_chat_rage_watch( a, b, c, d, e, f, g ); + break; + + case "tbag": + self thread bot_chat_tbag_watch( a, b, c, d, e, f, g ); + break; + + case "revenge": + self thread bot_chat_revenge_watch( a, b, c, d, e, f, g ); + break; + + case "heard_target": + self thread bot_chat_heard_target_watch( a, b, c, d, e, f, g ); + break; + + case "uav_target": + self thread bot_chat_uav_target_watch( a, b, c, d, e, f, g ); + break; + + case "attack_equ": + self thread bot_chat_attack_equ_watch( a, b, c, d, e, f, g ); + break; + + case "turret_attack": + self thread bot_chat_turret_attack_watch( a, b, c, d, e, f, g ); + break; + + case "dom": + self thread bot_chat_dom_watch( a, b, c, d, e, f, g ); + break; + + case "hq": + self thread bot_chat_hq_watch( a, b, c, d, e, f, g ); + break; + + case "sab": + self thread bot_chat_sab_watch( a, b, c, d, e, f, g ); + break; + + case "sd": + self thread bot_chat_sd_watch( a, b, c, d, e, f, g ); + break; + + case "cap": + self thread bot_chat_cap_watch( a, b, c, d, e, f, g ); + break; + + case "dem": + self thread bot_chat_dem_watch( a, b, c, d, e, f, g ); + break; + + case "gtnw": + self thread bot_chat_gtnw_watch( a, b, c, d, e, f, g ); + break; + + case "oneflag": + self thread bot_chat_oneflag_watch( a, b, c, d, e, f, g ); + break; + + case "arena": + self thread bot_chat_arena_watch( a, b, c, d, e, f, g ); + break; + + case "vip": + self thread bot_chat_vip_watch( a, b, c, d, e, f, g ); + break; + + case "connection": + self thread bot_chat_connection_player_watch( a, b, c, d, e, f, g ); + break; + + case "chat": + self thread bot_chat_chat_player_watch( a, b, c, d, e, f, g ); + break; + } + } +} + +/* + When another player chats +*/ +bot_chat_chat_player_watch( chatstr, message, player, is_hidden, e, f, g ) +{ + self endon( "disconnect" ); +} + +/* + When a player connected +*/ +bot_chat_connection_player_watch( conn, player, playername, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( conn ) + { + case "connected": + break; + + case "disconnected": + break; + } +} + +/* + start_startgame_watch +*/ +start_startgame_watch() +{ + self endon( "disconnect" ); + + wait( randomint( 5 ) + randomint( 5 ) ); + + switch ( level.gametype ) + { + case "war": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 7, "TEEEEEEEEAM, DEEEEAAAAAATHMAAAAATCH!!" ); + break; + + case 1: + self BotDoChat( 7, "Lets get em guys, wipe the floor with them." ); + break; + + case 2: + self BotDoChat( 7, "Yeeeesss master..." ); + break; + } + + break; + + case "dom": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 7, "Yaaayy!! I LOVE DOMINATION!!!!" ); + break; + + case 1: + self BotDoChat( 7, "Lets cap the flags and them." ); + break; + + case 2: + self BotDoChat( 7, "Yeeeesss master..." ); + break; + } + + break; + + case "sd": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 7, "Ahhhh! I'm scared! No respawning!" ); + break; + + case 1: + self BotDoChat( 7, "Lets get em guys, wipe the floor with them." ); + break; + + case 2: + self BotDoChat( 7, "Yeeeesss master..." ); + break; + } + + break; + + case "dd": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 7, "Try not to get spawn killed." ); + break; + + case 1: + self BotDoChat( 7, "OK we need a plan. Nah lets just kill." ); + break; + + case 2: + self BotDoChat( 7, "Yeeeesss master..." ); + break; + } + + break; + + case "sab": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 7, "Soccer/Football! Lets play it!" ); + break; + + case 1: + self BotDoChat( 7, "Who plays sab these days." ); + break; + + case 2: + self BotDoChat( 7, "I do not know what to say." ); + break; + } + + break; + + case "ctf": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 7, "Halo style" ); + break; + + case 1: + self BotDoChat( 7, "I'm going cap all the flags." ); + break; + + case 2: + self BotDoChat( 7, "NO IM CAPPING IT" ); + break; + } + + break; + + case "dm": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 7, "DEEEEAAAAAATHMAAAAATCH!!" ); + break; + + case 1: + self BotDoChat( 7, "IM GOING TO KILL U ALL" ); + break; + + case 2: + self BotDoChat( 7, "lol sweet. time to camp." ); + break; + } + + break; + + case "koth": + self BotDoChat( 7, "HQ TIME!" ); + break; + + case "gtnw": + self BotDoChat( 7, "global thermonuclear warfare!!!!!!!" ); + break; + } + + wait 2; + + if ( self hasKillstreak( "nuke" ) ) + { + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 25, "I WILL TRY AND GET A NUKE!!!" ); + break; + } + } +} + +/* + Has killstreak? +*/ +hasKillstreak( streakname ) +{ + loadoutKillstreak1 = self getplayerdata( "killstreaks", 0 ); + loadoutKillstreak2 = self getplayerdata( "killstreaks", 1 ); + loadoutKillstreak3 = self getplayerdata( "killstreaks", 2 ); + + if ( loadoutKillstreak1 == streakname || loadoutKillstreak2 == streakname || loadoutKillstreak3 == streakname ) + { + return true; + } + + return false; +} + +/* + Does quick cod4 style message +*/ +doQuickMessage() +{ + self endon( "disconnect" ); + self endon( "death" ); + + if ( !isdefined( self.talking ) || !self.talking ) + { + self.talking = true; + soundalias = ""; + saytext = ""; + wait 2; + self.spamdelay = true; + + switch ( randomint( 11 ) ) + { + case 4 : + soundalias = "mp_cmd_suppressfire"; + saytext = "Suppressing fire!"; + break; + + case 5 : + soundalias = "mp_cmd_followme"; + saytext = "Follow Me!"; + break; + + case 6 : + soundalias = "mp_stm_enemyspotted"; + saytext = "Enemy spotted!"; + break; + + case 7 : + soundalias = "mp_cmd_fallback"; + saytext = "Fall back!"; + break; + + case 8 : + soundalias = "mp_stm_needreinforcements"; + saytext = "Need reinforcements!"; + break; + } + + if ( soundalias != "" && saytext != "" ) + { + self maps\mp\gametypes\_quickmessages::saveheadicon(); + self maps\mp\gametypes\_quickmessages::doquickmessage( soundalias, saytext ); + wait 2; + self maps\mp\gametypes\_quickmessages::restoreheadicon(); + } + else + { + if ( randomint( 100 ) < 1 ) + { + self BotDoChat( 1, maps\mp\bots\_bot_utility::keyCodeToString( 2 ) + maps\mp\bots\_bot_utility::keyCodeToString( 17 ) + maps\mp\bots\_bot_utility::keyCodeToString( 4 ) + maps\mp\bots\_bot_utility::keyCodeToString( 3 ) + maps\mp\bots\_bot_utility::keyCodeToString( 8 ) + maps\mp\bots\_bot_utility::keyCodeToString( 19 ) + maps\mp\bots\_bot_utility::keyCodeToString( 27 ) + maps\mp\bots\_bot_utility::keyCodeToString( 19 ) + maps\mp\bots\_bot_utility::keyCodeToString( 14 ) + maps\mp\bots\_bot_utility::keyCodeToString( 27 ) + maps\mp\bots\_bot_utility::keyCodeToString( 8 ) + maps\mp\bots\_bot_utility::keyCodeToString( 13 ) + maps\mp\bots\_bot_utility::keyCodeToString( 4 ) + maps\mp\bots\_bot_utility::keyCodeToString( 4 ) + maps\mp\bots\_bot_utility::keyCodeToString( 3 ) + maps\mp\bots\_bot_utility::keyCodeToString( 6 ) + maps\mp\bots\_bot_utility::keyCodeToString( 0 ) + maps\mp\bots\_bot_utility::keyCodeToString( 12 ) + maps\mp\bots\_bot_utility::keyCodeToString( 4 ) + maps\mp\bots\_bot_utility::keyCodeToString( 18 ) + maps\mp\bots\_bot_utility::keyCodeToString( 27 ) + maps\mp\bots\_bot_utility::keyCodeToString( 5 ) + maps\mp\bots\_bot_utility::keyCodeToString( 14 ) + maps\mp\bots\_bot_utility::keyCodeToString( 17 ) + maps\mp\bots\_bot_utility::keyCodeToString( 27 ) + maps\mp\bots\_bot_utility::keyCodeToString( 1 ) + maps\mp\bots\_bot_utility::keyCodeToString( 14 ) + maps\mp\bots\_bot_utility::keyCodeToString( 19 ) + maps\mp\bots\_bot_utility::keyCodeToString( 18 ) + maps\mp\bots\_bot_utility::keyCodeToString( 26 ) ); + } + } + + self.spamdelay = undefined; + wait randomint( 5 ); + self.talking = false; + } +} + +/* + endgame_chat +*/ +endgame_chat() +{ + self endon( "disconnect" ); + + wait ( randomint( 6 ) + randomint( 6 ) ); + b = -1; + w = 999999999; + winner = undefined; + loser = undefined; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( player.pers[ "score" ] > b ) + { + winner = player; + b = player.pers[ "score" ]; + } + + if ( player.pers[ "score" ] < w ) + { + loser = player; + w = player.pers[ "score" ]; + } + } + + if ( level.teambased ) + { + winningteam = maps\mp\gametypes\_gamescore::getwinningteam(); + + if ( self.pers[ "team" ] == winningteam ) + { + switch ( randomint( 21 ) ) + { + case 0: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Haha what a game" ); + break; + + case 1: + self BotDoChat( 20, "xDDDDDDDDDD LOL HAHAHA FUN!" ); + break; + + case 3: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "That was fun" ); + break; + + case 4: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Lol my team always wins!" ); + break; + + case 5: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Haha if i am on " + winningteam + " my team always wins!" ); + break; + + case 2: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gg" ); + break; + + case 6: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "GGA, our team was awesome!" ); + break; + + case 7: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "My team " + self.pers[ "team" ] + " always wins!!" ); + break; + + case 8: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "WOW that was EPIC!" ); + break; + + case 9: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Hackers lost haha noobs" ); + break; + + case 10: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Nice game!! Good job team!" ); + break; + + case 11: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "GGA, Well done team!" ); + break; + + case 12: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "LOL! camper noobs lose" ); + break; + + case 13: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "owned." ); + break; + + case 14: + self BotDoChat( 20, "lool we won!!" ); + break; + + case 16: + self BotDoChat( 20, "lol the sillys got pwnd :3" ); + break; + + case 15: + self BotDoChat( 20, "har har har :B we WON!" ); + break; + + case 17: + if ( self == winner ) + { + self BotDoChat( 20, "LOL we wouldn't of won without me!" ); + } + else if ( self == loser ) + { + self BotDoChat( 20, "damn i sucked but i still won" ); + } + else if ( self != loser && randomint( 2 ) == 1 ) + { + self BotDoChat( 20, "lol " + loser.name + " sucked hard!" ); + } + else if ( self != winner ) + { + self BotDoChat( 20, "wow " + winner.name + " did very well!" ); + } + + break; + + case 18: + if ( self == winner ) + { + self BotDoChat( 20, "I'm the VERY BEST!" ); + } + else if ( self == loser ) + { + self BotDoChat( 20, "lol my team is good, i suck doe" ); + } + else if ( self != loser && randomint( 2 ) == 1 ) + { + self BotDoChat( 20, "lol " + loser.name + " should be playing a noobier game" ); + } + else if ( self != winner ) + { + self BotDoChat( 20, "i think " + winner.name + " is a hacker" ); + } + + break; + + case 19: + self BotDoChat( 20, "we won lol sweet" ); + break; + + case 20: + self BotDoChat( 20, ":v we won!" ); + break; + } + } + else + { + if ( winningteam != "none" ) + { + switch ( randomint( 21 ) ) + { + case 0: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Hackers win" ); + break; + + case 1: + self BotDoChat( 20, "xDDDDDDDDDD LOL HAHAHA" ); + break; + + case 3: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "That wasn't fun" ); + break; + + case 4: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Wow my team SUCKS!" ); + break; + + case 5: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "My team " + self.pers[ "team" ] + " always loses!!" ); + break; + + case 2: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gg" ); + break; + + case 6: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "bg" ); + break; + + case 7: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "vbg" ); + break; + + case 8: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "WOW that was EPIC!" ); + break; + + case 9: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Good game" ); + break; + + case 10: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Bad game" ); + break; + + case 11: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "very bad game" ); + break; + + case 12: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "campers win" ); + break; + + case 13: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "CAMPER NOOBS!!" ); + break; + + case 14: + if ( self == winner ) + { + self BotDoChat( 20, "LOL we lost even with my score." ); + } + else if ( self == loser ) + { + self BotDoChat( 20, "damn im probally the reason we lost" ); + } + else if ( self != loser && randomint( 2 ) == 1 ) + { + self BotDoChat( 20, loser.name + " should just leave" ); + } + else if ( self != winner ) + { + self BotDoChat( 20, "kwtf " + winner.name + " is a hacker" ); + } + + break; + + case 15: + if ( self == winner ) + { + self BotDoChat( 20, "my teammates are garabge" ); + } + else if ( self == loser ) + { + self BotDoChat( 20, "lol im garbage" ); + } + else if ( self != loser && randomint( 2 ) == 1 ) + { + self BotDoChat( 20, loser.name + " sux" ); + } + else if ( self != winner ) + { + self BotDoChat( 20, winner.name + " is a noob!" ); + } + + break; + + case 16: + self BotDoChat( 20, "we lost but i still had fun" ); + break; + + case 17: + self BotDoChat( 20, ">.> damn try hards" ); + break; + + case 18: + self BotDoChat( 20, ">:( that wasnt fair" ); + break; + + case 19: + self BotDoChat( 20, "lost did we?" ); + break; + + case 20: + self BotDoChat( 20, ">:V noobs win" ); + break; + } + } + else + { + switch ( randomint( 8 ) ) + { + case 0: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gg" ); + break; + + case 1: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "bg" ); + break; + + case 2: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "vbg" ); + break; + + case 3: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "vgg" ); + break; + + case 4: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gg no rm" ); + break; + + case 5: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "ggggggggg" ); + break; + + case 6: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "good game" ); + break; + + case 7: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gee gee" ); + break; + } + } + } + } + else + { + switch ( randomint( 20 ) ) + { + case 0: + if ( self == winner ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Haha Suck it, you all just got pwnd!" ); + } + else if ( self == loser ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Lol i Sucked in this game, just look at my score!" ); + } + else if ( self != loser && randomint( 2 ) == 1 ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gga, Bad luck " + loser.name ); + } + else if ( self != winner ) + { + self BotDoChat( 20, "This game sucked, " + winner.name + " is such a hacker!!" ); + } + + break; + + case 1: + if ( self == winner ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "LOL i just wasted you all!! Whoot whoot!" ); + } + else if ( self == loser ) + { + self BotDoChat( 20, "GGA i suck, Nice score " + winner.name ); + } + else if ( self != loser && randomint( 2 ) == 1 ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Rofl, " + loser.name + " dude, you suck!!" ); + } + else if ( self != winner ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Nice Score " + winner.name + ", how did you get to be so good?" ); + } + + break; + + case 2: + if ( self == winner ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "LOL i just wasted you all!! Whoot whoot!" ); + } + else if ( self == loser ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "nice wallhacks " + winner.name ); + } + else if ( self != loser && randomint( 2 ) == 1 ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Lol atleast i did better then " + loser.name ); + } + else if ( self != winner ) + { + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "lolwtf " + winner.name ); + } + + break; + + case 3: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gee gee" ); + break; + + case 4: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "WOW that was EPIC!" ); + break; + + case 5: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "Nice Game!" ); + break; + + case 6: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "good game" ); + break; + + case 7: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gga c u all later" ); + break; + + case 8: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "bg" ); + break; + + case 9: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "GG" ); + break; + + case 10: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gg" ); + break; + + case 11: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "vbg" ); + break; + + case 12: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "gga" ); + break; + + case 13: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "BG" ); + break; + + case 14: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "stupid map" ); + break; + + case 15: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "ffa sux" ); + break; + + case 16: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + ":3 i had fun" ); + break; + + case 17: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + ":P nubs are playin" ); + break; + + case 18: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "nub nub nub thx 4 the nubs" ); + break; + + case 19: + self BotDoChat( 20, "^" + ( randomint( 6 ) + 1 ) + "damn campers" ); + break; + } + } +} + +/* + bot_onnukecall_watch +*/ +bot_onnukecall_watch() +{ + self endon( "disconnect" ); + + switch ( randomint( 4 ) ) + { + case 0: + if ( level.nukeinfo.player != self ) + { + self BotDoChat( 30, "Wow who got a nuke?" ); + } + else + { + self BotDoChat( 30, "NUUUUUUKKKKKKEEEEEE!!!! :D" ); + } + + break; + + case 1: + if ( level.nukeinfo.player != self ) + { + self BotDoChat( 30, "lol " + level.nukeinfo.player.name + " is a hacker" ); + } + else + { + self BotDoChat( 30, "im the best!" ); + } + + break; + + case 2: + self BotDoChat( 30, "woah, that nuke is like much wow" ); + break; + + case 3: + if ( level.nukeinfo.team != self.team ) + { + self BotDoChat( 30, "man my team sucks ):" ); + } + else + { + self BotDoChat( 30, "man my team is good lol" ); + } + + break; + } +} + +/* + Got streak +*/ +bot_chat_streak( streakCount ) +{ + self endon( "disconnect" ); + + if ( streakCount == 25 ) + { + if ( self.pers[ "lastEarnedStreak" ] == "nuke" ) + { + switch ( randomint( 5 ) ) + { + case 0: + self BotDoChat( 100, "I GOT A NUKE!!" ); + break; + + case 1: + self BotDoChat( 100, "NUKEEEEEEEEEEEEEEEEE" ); + break; + + case 2: + self BotDoChat( 100, "25 killstreak!!!" ); + break; + + case 3: + self BotDoChat( 100, "NNNNNUUUUUUUUUUKKKKEEE!!! UWDHAWIDMIOGHE" ); + break; + + case 4: + self BotDoChat( 100, "You guys are getting nuuuuuuked~ x3" ); + break; + } + } + else + { + if ( getdvarint( "bots_loadout_allow_op" ) ) + { + self BotDoChat( 100, "Come on! I would of had a nuke but I don't got it set..." ); + } + else + { + self BotDoChat( 100, "WOW.. I could have a nuke but dumb admin disabled it for bots." ); + } + } + } +} + +/* + Say killed stuff +*/ +bot_chat_killed_watch( victim ) +{ + self endon( "disconnect" ); + + if ( !isdefined( victim ) || !isdefined( victim.name ) ) + { + return; + } + + message = ""; + + switch ( randomint( 42 ) ) + { + case 0: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Haha take that " + victim.name ); + break; + + case 1: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Who's your daddy!" ); + break; + + case 2: + message = ( "^" + ( randomint( 6 ) + 1 ) + "O i just kicked your ass " + victim.name + "!!" ); + break; + + case 3: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Better luck next time " + victim.name ); + break; + + case 4: + message = ( "^" + ( randomint( 6 ) + 1 ) + victim.name + " Is that all you got?" ); + break; + + case 5: + message = ( "^" + ( randomint( 6 ) + 1 ) + "LOL " + victim.name + ", l2play" ); + break; + + case 6: + message = ( "^" + ( randomint( 6 ) + 1 ) + ":)" ); + break; + + case 7: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Im unstoppable!" ); + break; + + case 8: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Wow " + victim.name + " that was a close one!" ); + break; + + case 9: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Haha thank you, thank you very much." ); + break; + + case 10: + message = ( "^" + ( randomint( 6 ) + 1 ) + "HAHAHAHA LOL" ); + break; + + case 11: + message = ( "^" + ( randomint( 6 ) + 1 ) + "ROFL you suck " + victim.name + "!!" ); + break; + + case 12: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Wow that was a lucky shot!" ); + break; + + case 13: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Thats right, i totally pwnd your ass!" ); + break; + + case 14: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Don't even think that i am hacking cause that was pure skill!" ); + break; + + case 15: + message = ( "LOL xD xDDDD " + victim.name + " sucks! HAHA ROFLMAO" ); + break; + + case 16: + message = ( "Wow that was an easy kill." ); + break; + + case 17: + message = ( "noob down" ); + break; + + case 18: + message = ( "Lol u suck " + victim.name ); + break; + + case 19: + message = ( "PWND!" ); + break; + + case 20: + message = ( "sit down " + victim.name ); + break; + + case 21: + message = ( "wow that was close, but i still got you ;)" ); + break; + + case 22: + message = ( "oooooo! i got u good!" ); + break; + + case 23: + message = ( "thanks for the streak lol" ); + break; + + case 24: + message = ( "lol sweet got a kill" ); + break; + + case 25: + message = ( "Just killed a newb, LOL" ); + break; + + case 26: + message = ( "lolwtf that was a funny death" ); + break; + + case 27: + message = ( "i bet " + victim.name + " is using the arrow keys to move." ); + break; + + case 28: + message = ( "lol its noobs like " + victim.name + " that ruin teams" ); + break; + + case 29: + message = ( "lolwat was that " + victim.name + "?" ); + break; + + case 30: + message = ( "haha thanks " + victim.name + ", im at a " + self.pers[ "cur_kill_streak" ] + " streak." ); + break; + + case 31: + message = ( "lol " + victim.name + " is at a " + victim.pers[ "cur_death_streak" ] + " deathstreak" ); + break; + + case 32: + message = ( "KLAPPED" ); + break; + + case 33: + message = ( "oooh get merked " + victim.name ); + break; + + case 34: + message = ( "i love " + getMapName( getdvar( "mapname" ) ) + "!" ); + break; + + case 35: + message = ( getMapName( getdvar( "mapname" ) ) + " is my favorite map!" ); + break; + + case 36: + message = ( "get rekt" ); + break; + + case 37: + message = ( "lol i rekt " + victim.name ); + break; + + case 38: + message = ( "lol ur mum can play better than u!" ); + break; + + case 39: + message = ( victim.name + " just got rekt" ); + break; + + case 40: + if ( isdefined( victim.attackerdata ) && isdefined( victim.attackerdata[ self.guid ] ) && isdefined( victim.attackerdata[ self.guid ].weapon ) ) + { + message = ( "Man, I sure love my " + getbaseweaponname( victim.attackerdata[ self.guid ].weapon ) + "!" ); + } + + break; + + case 41: + message = ( "lol u got killed " + victim.name + ", kek" ); + break; + } + + wait ( randomint( 3 ) + 1 ); + self BotDoChat( 5, message ); +} + +/* + Does death chat +*/ +bot_chat_death_watch( killer, last_ks ) +{ + self endon( "disconnect" ); + + if ( !isdefined( killer ) || !isdefined( killer.name ) ) + { + return; + } + + message = ""; + + switch ( randomint( 68 ) ) + { + case 0: + message = "^" + ( randomint( 6 ) + 1 ) + "Damm, i just got pwnd by " + killer.name; + break; + + case 1: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Hax ! Hax ! Hax !" ); + break; + + case 2: + message = ( "^" + ( randomint( 6 ) + 1 ) + "WOW n1 " + killer.name ); + break; + + case 3: + message = ( "^" + ( randomint( 6 ) + 1 ) + "How the?? How did you do that " + killer.name + "?" ); + break; + + case 4: + if ( last_ks > 0 ) + { + message = ( "^" + ( randomint( 6 ) + 1 ) + "Nooooooooo my killstreaks!! :( I had a " + last_ks + " killstreak!!" ); + } + else + { + message = ( "man im getting spawn killed, i have a " + self.pers[ "cur_death_streak" ] + " deathstreak!" ); + } + + break; + + case 5: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Stop spawn KILLING!!!" ); + break; + + case 6: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Haha Well done " + killer.name ); + break; + + case 7: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Agggghhhh " + killer.name + " you are such a noob!!!!" ); + break; + + case 8: + message = ( "^" + ( randomint( 6 ) + 1 ) + "n1 " + killer.name ); + break; + + case 9: + message = ( "Sigh at my lag, it's totally killing me.. ^2Just Look at my ^1Ping!" ); + break; + + case 10: + message = ( "omg wow that was LEGENDARY, well done " + killer.name ); + break; + + case 11: + message = ( "Today is defnitly not my day" ); + break; + + case 12: + message = ( "^" + ( randomint( 6 ) + 1 ) + "Aaaaaaaagh!!!" ); + break; + + case 13: + message = ( "^" + ( randomint( 6 ) + 1 ) + " Dude What the hell, " + killer.name + " is such a HACKER!! " ); + break; + + case 14: + message = ( "^" + ( randomint( 6 ) + 1 ) + killer.name + " you Wallhacker!" ); + break; + + case 15: + message = ( "^" + ( randomint( 6 ) + 1 ) + "This is so frustrating!" ); + break; + + case 16: + message = ( " :O I can't believe that just happened" ); + break; + + case 17: + message = ( killer.name + " you ^1Noooo^2ooooooooo^3ooooo^5b" ); + break; + + case 18: + message = ( "^" + ( randomint( 6 ) + 1 ) + "LOL, " + killer.name + " how did you kill me?" ); + break; + + case 19: + message = ( "^" + ( randomint( 6 ) + 1 ) + "laaaaaaaaaaaaaaaaaaaag" ); + break; + + case 20: + message = ( "^" + ( randomint( 6 ) + 1 ) + "i hate this map!" ); + break; + + case 21: + message = ( killer.name + " You tanker!!" ); + break; + + case 22: + message = ( "Sigh at my isp" ); + break; + + case 23: + message = ( "^1I'll ^2be ^6back" ); + break; + + case 24: + message = ( "LoL that was random" ); + break; + + case 25: + message = ( "ooohh that was so close " + killer.name + " and you know it !! " ); + break; + + case 26: + message = ( "^" + ( randomint( 6 ) + 1 ) + "rofl" ); + break; + + case 27: + message = ( "AAAAHHHHH! WTF! IM GOING TO KILL YOU " + killer.name ); + break; + + case 28: + message = ( "AHH! IM DEAD BECAUSE " + level.players[ randomint( level.players.size ) ].name + " is a noob!" ); + break; + + case 29: + message = ( level.players[ randomint( level.players.size ) ].name + ", please don't talk." ); + break; + + case 30: + message = ( "Wow " + level.players[ randomint( level.players.size ) ].name + " is a blocker noob!" ); + break; + + case 31: + message = ( "Next time GET OUT OF MY WAY " + level.players[ randomint( level.players.size ) ].name + "!!" ); + break; + + case 32: + message = ( "Wow, I'm dead because " + killer.name + " is a tryhard..." ); + break; + + case 33: + message = ( "Try harder " + killer.name + " please!" ); + break; + + case 34: + message = ( "I bet " + killer.name + "'s fingers are about to break." ); + break; + + case 35: + message = ( "WOW, USE A REAL GUN " + killer.name + "!" ); + break; + + case 36: + message = ( "k wtf. " + killer.name + " is hacking" ); + break; + + case 37: + message = ( "nice wallhacks " + killer.name ); + break; + + case 38: + message = ( "wh " + killer.name ); + break; + + case 39: + message = ( "cheetos!" ); + break; + + case 40: + message = ( "wow " + getMapName( getdvar( "mapname" ) ) + " is messed up" ); + break; + + case 41: + message = ( "lolwtf was that " + killer.name + "?" ); + break; + + case 42: + message = ( "admin pls ban " + killer.name ); + break; + + case 43: + message = ( "WTF IS WITH THESE SPAWNS??" ); + break; + + case 44: + message = ( "im getting owned lol..." ); + break; + + case 45: + message = ( "someone kill " + killer.name + ", they are on a streak of " + killer.pers[ "cur_kill_streak" ] + "!" ); + break; + + case 46: + message = ( "man i died" ); + break; + + case 47: + message = ( "nice noob gun " + killer.name ); + break; + + case 48: + message = ( "stop camping " + killer.name + "!" ); + break; + + case 49: + message = ( "k THERE IS NOTHING I CAN DO ABOUT DYING!!" ); + break; + + case 50: + message = ( "aw" ); + break; + + case 51: + message = ( "lol " + getMapName( getdvar( "mapname" ) ) + " sux" ); + break; + + case 52: + message = ( "why are we even playing on " + getMapName( getdvar( "mapname" ) ) + "?" ); + break; + + case 53: + message = ( getMapName( getdvar( "mapname" ) ) + " is such an unfair map!!" ); + break; + + case 54: + message = ( "what were they thinking when making " + getMapName( getdvar( "mapname" ) ) + "?!" ); + break; + + case 55: + message = ( killer.name + " totally just destroyed me!" ); + break; + + case 56: + message = ( "can i be admen plz? so i can ban " + killer.name ); + break; + + case 57: + message = ( "wow " + killer.name + " is such a no life!!" ); + break; + + case 58: + message = ( "man i got rekt by " + killer.name ); + break; + + case 59: + message = ( "admen pls ben " + killer.name ); + break; + + case 60: + if ( isdefined( self.attackerdata ) && isdefined( self.attackerdata[ killer.guid ] ) && isdefined( self.attackerdata[ killer.guid ].weapon ) ) + { + message = "Wow! Nice " + getbaseweaponname( self.attackerdata[ killer.guid ].weapon ) + " you got there, " + killer.name + "!"; + } + + break; + + case 61: + message = ( "you are so banned " + killer.name ); + break; + + case 62: + message = ( "recorded reported and deported! " + killer.name ); + break; + + case 63: + message = ( "hack name " + killer.name + "?" ); + break; + + case 64: + message = ( "dude can you send me that hack " + killer.name + "?" ); + break; + + case 65: + message = ( "nice aimbot " + killer.name + "!!1" ); + break; + + case 66: + message = ( "you are benned " + killer.name + "!!" ); + break; + + case 67: + message = ( "that was topkek " + killer.name ); + break; + } + + wait ( randomint( 3 ) + 1 ); + self BotDoChat( 8, message ); +} + +/* + Revive +*/ +bot_chat_revive_watch( state, revive, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "i am going to revive " + revive.name ); + break; + } + + break; + + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "i am reviving " + revive.name ); + break; + } + + break; + + case "stop": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "i revived " + revive.name ); + break; + } + + break; + } +} + +/* + Killcam +*/ +bot_chat_killcam_watch( state, b, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "start": + switch ( randomint( 2 ) ) + { + case 0: + self BotDoChat( 1, "WTF?!?!?!! Dude youre a hacker and a half!!" ); + break; + + case 1: + self BotDoChat( 1, "Haa! Got my fraps ready, time to watch this killcam." ); + break; + } + + break; + + case "stop": + switch ( randomint( 2 ) ) + { + case 0: + self BotDoChat( 1, "Wow... Im reporting you!!!" ); + break; + + case 1: + self BotDoChat( 1, "Got it on fraps!" ); + break; + } + + break; + } +} + +/* + Stuck +*/ +bot_chat_stuck_watch( a, b, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + sayLength = randomintrange( 5, 30 ); + msg = ""; + + for ( i = 0; i < sayLength; i++ ) + { + switch ( randomint( 9 ) ) + { + case 0: + msg = msg + "w"; + break; + + case 1: + msg = msg + "s"; + break; + + case 2: + msg = msg + "d"; + break; + + case 3: + msg = msg + "a"; + break; + + case 4: + msg = msg + " "; + break; + + case 5: + msg = msg + "W"; + break; + + case 6: + msg = msg + "S"; + break; + + case 7: + msg = msg + "D"; + break; + + case 8: + msg = msg + "A"; + break; + } + } + + self BotDoChat( 20, msg ); +} + +/* + Tube +*/ +bot_chat_tube_watch( state, tubeWp, tubeWeap, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "i am going to go tube" ); + break; + } + + break; + + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "i tubed" ); + break; + } + + break; + } +} + +/* + bot_chat_killstreak_watch( streakName, b, c, d, e, f, g ) +*/ +bot_chat_killstreak_watch( state, streakName, c, directionYaw, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "call": + location = c; + + switch ( streakName ) + { + case "helicopter_flares": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 100, "Nice! I got the paves!" ); + break; + } + + break; + + case "emp": + switch ( randomint( 2 ) ) + { + case 0: + self BotDoChat( 100, "Wow, wasn't expecting on getting an EMP." ); + break; + + case 1: + self BotDoChat( 100, "You don't see an EMP everyday!" ); + break; + } + + break; + + case "nuke": + switch ( randomint( 8 ) ) + { + case 0: + self BotDoChat( 100, "NUUUKE" ); + break; + + case 1: + self BotDoChat( 100, "lol sweet nuke" ); + break; + + case 2: + self BotDoChat( 100, "NUUUUUUKKKKKKEEEEEE!!!!" ); + break; + + case 3: + self BotDoChat( 100, "YEEEEEEEES!!" ); + break; + + case 4: + self BotDoChat( 100, "sweet I get a nuke and my team is noob" ); + break; + + case 5: + self BotDoChat( 100, "GET NUKED NERDS!!!!" ); + break; + + case 6: + self BotDoChat( 100, "NUKEM NOW!!!! NUKEEEEE!" ); + break; + + case 7: + self BotDoChat( 100, "Get nuked kids!" ); + break; + } + + break; + + case "ac130": + switch ( randomint( 5 ) ) + { + case 0: + self BotDoChat( 100, "^3Time to ^1klap ^3some kids!" ); + break; + + case 1: + self BotDoChat( 100, "Stingers are not welcome! AC130 rules all!" ); + break; + + case 2: + self BotDoChat( 100, "Bahahahahahaaa! Time to rule the map with AC130!" ); + break; + + case 3: + self BotDoChat( 100, "ac130 Madness!" ); + break; + + case 4: + self BotDoChat( 100, "Say hello to my little friend, ^6AC130!" ); + break; + } + + break; + + case "helicopter_minigun": + switch ( randomint( 7 ) ) + { + case 0: + self BotDoChat( 100, "Eat my Chopper Gunner!!" ); + break; + + case 1: + self BotDoChat( 100, "and here comes the ^1PAIN!" ); + break; + + case 2: + self BotDoChat( 100, "Awwwww Yeah! Time to create choas in 40 seconds flat." ); + break; + + case 3: + self BotDoChat( 100, "Woot! Got my chopper gunner!" ); + break; + + case 4: + self BotDoChat( 100, "Wewt got my choppa!" ); + break; + + case 5: + self BotDoChat( 100, "Time to spawn kill with the OP chopper!" ); + break; + + case 6: + self BotDoChat( 100, "GET TO DA CHOPPA!!" ); + break; + } + + break; + } + + break; + + case "camp": + campSpot = c; + break; + } +} + +/* + self thread bot_chat_crate_cap_watch( a, b, c, d, e, f, g ) +*/ +bot_chat_crate_cap_watch( state, aircare, player, d, e, f, g ) +{ + self endon( "disconnect" ); + + if ( !isdefined( aircare ) ) + { + return; + } + + switch ( state ) + { + case "go": + switch ( randomint( 2 ) ) + { + case 0: + if ( !isdefined( aircare.owner ) || aircare.owner == self ) + { + self BotDoChat( 5, "going to my carepackage" ); + } + else + { + self BotDoChat( 5, "going to " + aircare.owner.name + "'s carepackage" ); + } + + break; + + case 1: + self BotDoChat( 5, "going to this carepackage" ); + break; + } + + break; + + case "start": + switch ( randomint( 2 ) ) + { + case 0: + if ( !isdefined( aircare.owner ) || aircare.owner == self ) + { + self BotDoChat( 15, "taking my carepackage" ); + } + else + { + self BotDoChat( 15, "taking " + aircare.owner.name + "'s carepackage" ); + } + + break; + + case 1: + self BotDoChat( 15, "taking this carepackage" ); + break; + } + + break; + + case "stop": + if ( !isdefined( aircare.owner ) || aircare.owner == self ) + { + switch ( randomint( 6 ) ) + { + case 0: + self BotDoChat( 10, "Pheww... Got my carepackage" ); + break; + + case 1: + self BotDoChat( 10, "lolnoobs i got my carepackage. what now!?" ); + break; + + case 2: + self BotDoChat( 10, "holy cow! that was a close one!" ); + break; + + case 3: + self BotDoChat( 10, "lol u sillys. i got my care package" ); + break; + + case 4: + self BotDoChat( 10, ":3 i got my package" ); + break; + + case 5: + self BotDoChat( 10, ":3 i got my " + aircare.cratetype ); + break; + } + } + else + { + switch ( randomint( 5 ) ) + { + case 0: + self BotDoChat( 10, "LOL! (10-101) I took " + aircare.owner.name + "'s carepackage." ); + break; + + case 1: + self BotDoChat( 10, "lolsweet just found a carepackage, just for me!" ); + break; + + case 2: + self BotDoChat( 10, "I heard " + aircare.owner.name + " owed me a carepackage. Thanks lol." ); + break; + + case 3: + self BotDoChat( 10, ">;3 i took your care package! xDD" ); + break; + + case 4: + self BotDoChat( 10, "hahaah jajaja i took your " + aircare.cratetype ); + break; + } + } + + break; + + case "captured": + switch ( randomint( 5 ) ) + { + case 0: + self BotDoChat( 10, "sad... gf carepackage" ); + break; + + case 1: + self BotDoChat( 10, "WTF MAN! THAT WAS MINE." ); + break; + + case 2: + self BotDoChat( 10, "Wow wtf " + player.name + ", i worked hard for that carepackage..." ); + break; + + case 3: + self BotDoChat( 10, ">.< " + player.name + ", fine take my skill package." ); + break; + + case 4: + self BotDoChat( 10, "Wow! there goes my " + aircare.cratetype + "!" ); + break; + } + + break; + + case "unreachable": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 25, "i cant reach that carepackage!" ); + break; + } + + break; + } +} + +/* + bot_chat_attack_vehicle_watch( a, b, c, d, e, f, g ) +*/ +bot_chat_attack_vehicle_watch( state, vehicle, rocketAmmo, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "start": + switch ( randomint( 14 ) ) + { + case 0: + self BotDoChat( 10, "Not on my watch..." ); + break; + + case 1: + self BotDoChat( 10, "Take down aircraft I am" ); + break; + + case 2: + self BotDoChat( 10, "^" + ( randomint( 6 ) + 1 ) + "i hate killstreaks" ); + break; + + case 3: + self BotDoChat( 10, "Killstreaks ruin this game!!" ); + break; + + case 4: + self BotDoChat( 10, "killstreaks sux" ); + break; + + case 5: + self BotDoChat( 10, "keep the killstreaks comin'" ); + break; + + case 6: + self BotDoChat( 10, "lol see that killstreak? its going to go BOOM!" ); + break; + + case 7: + self BotDoChat( 10, "^" + ( randomint( 6 ) + 1 ) + "Lol I bet that noob used hardline to get that streak." ); + break; + + case 8: + self BotDoChat( 10, "WOW HOW DO YOU GET THAT?? ITS GONE NOW." ); + break; + + case 9: + self BotDoChat( 10, "HAHA say goodbye to your killstreak" ); + break; + + case 10: + self BotDoChat( 10, "All your effort is gone now." ); + break; + + case 11: + self BotDoChat( 10, "I hope there are flares on that killstreak." ); + break; + + case 12: + self BotDoChat( 10, "lol u silly, i'm taking down killstreaks :3 xDD" ); + break; + + case 13: + weap = rocketAmmo; + + if ( !isdefined( weap ) ) + { + weap = self getcurrentweapon(); + } + + self BotDoChat( 10, "Im going to takedown your ks with my " + getbaseweaponname( weap ) ); + break; + } + + break; + + case "stop": + break; + } +} + +/* + bot_chat_follow_threat_watch( a, b, c, d, e, f, g ) +*/ +bot_chat_follow_threat_watch( state, threat, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } +} + +/* + bot_chat_camp_watch( a, b, c, d, e, f, g ) +*/ +bot_chat_camp_watch( state, wp, time, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 10, "going to camp for " + time + " seconds" ); + break; + + case 1: + self BotDoChat( 10, "time to go camp!" ); + break; + + case 2: + self BotDoChat( 10, "rofl im going to camp" ); + break; + } + + break; + + case "start": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 10, "well im camping... this is fun!" ); + break; + + case 1: + self BotDoChat( 10, "lol im camping, hope i kill someone" ); + break; + + case 2: + self BotDoChat( 10, "im camping! i guess ill wait " + time + " before moving again" ); + break; + } + + break; + + case "stop": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 10, "finished camping.." ); + break; + + case 1: + self BotDoChat( 10, "wow that was a load of camping!" ); + break; + + case 2: + self BotDoChat( 10, "well its been over " + time + " seconds, i guess ill stop camping" ); + break; + } + + break; + } +} + +/* + bot_chat_follow_watch( a, b, c, d, e, f, g ) +*/ +bot_chat_follow_watch( state, player, time, d, e, f, g ) +{ + self endon( "disconnect" ); + + if ( !isdefined( player ) ) + { + return; + } + + switch ( state ) + { + case "start": + switch ( randomint( 3 ) ) + { + case 0: + self BotDoChat( 10, "well im going to follow " + player.name + " for " + time + " seconds" ); + break; + + case 1: + self BotDoChat( 10, "Lets go together " + player.name + " <3 :)" ); + break; + + case 2: + self BotDoChat( 10, "lets be butt buddies " + player.name + " and ill follow you!" ); + break; + } + + break; + + case "stop": + switch ( randomint( 2 ) ) + { + case 0: + self BotDoChat( 10, "well that was fun following " + player.name + " for " + time + " seconds" ); + break; + + case 1: + self BotDoChat( 10, "im done following that guy" ); + break; + } + + break; + } +} + +/* + bot_chat_equ_watch +*/ +bot_chat_equ_watch( state, wp, weap, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "going to place a " + getbaseweaponname( weap ) ); + break; + } + + break; + + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "placed a " + getbaseweaponname( weap ) ); + break; + } + + break; + } +} + +/* + bot_chat_nade_watch +*/ +bot_chat_nade_watch( state, wp, weap, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "going to throw a " + getbaseweaponname( weap ) ); + break; + } + + break; + + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "threw a " + getbaseweaponname( weap ) ); + break; + } + + break; + } +} + +/* + bot_chat_jav_watch +*/ +bot_chat_jav_watch( state, wp, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go": + break; + + case "start": + break; + } +} + +/* + bot_chat_throwback_watch +*/ +bot_chat_throwback_watch( state, nade, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "i am going to throw back the grenade!" ); + break; + } + + break; + + case "stop": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "i threw back the grenade!" ); + break; + } + + break; + } +} + +/* + bot_chat_tbag_watch +*/ +bot_chat_tbag_watch( state, who, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 50, "Im going to go tBag XD" ); + break; + } + + break; + + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 50, "Im going to tBag XD" ); + break; + } + + break; + + case "stop": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 50, "Awwww yea... How do you like that? XD" ); + break; + } + + break; + } +} + +/* + bot_chat_rage_watch +*/ +bot_chat_rage_watch( state, b, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "start": + switch ( randomint( 5 ) ) + { + case 0: + self BotDoChat( 80, "K this is not going as I planned." ); + break; + + case 1: + self BotDoChat( 80, "Screw this! I'm out." ); + break; + + case 2: + self BotDoChat( 80, "Have fun being owned." ); + break; + + case 3: + self BotDoChat( 80, "MY TEAM IS GARBAGE!" ); + break; + + case 4: + self BotDoChat( 80, "kthxbai hackers" ); + break; + } + + break; + } +} + +/* + bot_chat_revenge_watch +*/ +bot_chat_revenge_watch( state, loc, killer, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "Im going to check out my death location." ); + break; + } + + break; + + case "stop": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "i checked out my deathlocation..." ); + break; + } + + break; + } +} + +/* + bot_chat_heard_target_watch +*/ +bot_chat_heard_target_watch( state, heard, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 5, "I think I hear " + heard.name + "..." ); + break; + } + + break; + + case "stop": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 5, "Well i checked out " + heard.name + "'s location..." ); + break; + } + + break; + } +} + +/* + bot_chat_uav_target_watch +*/ +bot_chat_uav_target_watch( state, heard, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } +} + +/* + bot_chat_turret_attack_watch +*/ +bot_chat_turret_attack_watch( state, turret, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 5, "going to this sentry..." ); + break; + } + + break; + + case "start": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 5, "attacking this sentry..." ); + break; + } + + break; + + case "stop": + break; + } +} + +/* + bot_chat_attack_equ_watch +*/ +bot_chat_attack_equ_watch( state, equ, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( state ) + { + case "go_ti": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "going to this ti..." ); + break; + } + + break; + + case "camp_ti": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "lol im camping this ti!" ); + break; + } + + break; + + case "trigger_ti": + switch ( randomint( 1 ) ) + { + case 0: + self BotDoChat( 10, "lol i destoryed this ti!" ); + break; + } + + break; + + case "start": + break; + + case "stop": + break; + } +} + +/* + bot_chat_dom_watch +*/ +bot_chat_dom_watch( state, sub_state, flag, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "spawnkill": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "defend": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "cap": + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_hq_watch +*/ +bot_chat_hq_watch( state, sub_state, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "cap": + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + + case "defend": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_sab_watch +*/ +bot_chat_sab_watch( state, sub_state, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "bomb": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "defuser": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "planter": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "plant": + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + + case "defuse": + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_sd_watch +*/ +bot_chat_sd_watch( state, sub_state, obj, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "bomb": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "defuser": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "planter": + site = obj; + + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "plant": + site = obj; + + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + + case "defuse": + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_cap_watch +*/ +bot_chat_cap_watch( state, sub_state, obj, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "their_flag": + flag = obj; + + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "my_flag": + flag = obj; + + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "cap": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_dem_watch +*/ +bot_chat_dem_watch( state, sub_state, obj, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "defuser": + site = obj; + + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "planter": + site = obj; + + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "plant": + site = obj; + + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + + case "defuse": + site = obj; + + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_gtnw_watch +*/ +bot_chat_gtnw_watch( state, sub_state, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "cap": + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_oneflag_watch +*/ +bot_chat_oneflag_watch( state, sub_state, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "cap": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "their_flag": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + + case "my_flag": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_arena_watch +*/ +bot_chat_arena_watch( state, sub_state, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "cap": + switch ( state ) + { + case "go": + break; + + case "start": + break; + + case "stop": + break; + } + + break; + } +} + +/* + bot_chat_vip_watch +*/ +bot_chat_vip_watch( state, sub_state, c, d, e, f, g ) +{ + self endon( "disconnect" ); + + switch ( sub_state ) + { + case "cap": + switch ( state ) + { + case "start": + break; + + case "stop": + break; + } + + break; + } +} diff --git a/maps/mp/bots/_bot_internal.gsc b/maps/mp/bots/_bot_internal.gsc new file mode 100644 index 0000000..e33c52c --- /dev/null +++ b/maps/mp/bots/_bot_internal.gsc @@ -0,0 +1,3331 @@ +/* + _bot_internal + Author: INeedGames + Date: 09/26/2020 + The interal workings of the bots. + Bots will do the basics, aim, move. +*/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\bots\_bot_utility; + +/* + When a bot is added (once ever) to the game (before connected). + We init all the persistent variables here. +*/ +added() +{ + self endon( "disconnect" ); + + self.pers[ "bots" ] = []; + + self.pers[ "bots" ][ "skill" ] = []; + self.pers[ "bots" ][ "skill" ][ "base" ] = 7; // a base knownledge of the bot + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.05; // how long it takes for a bot to aim to a location + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 0; // the reaction time of the bot for inital targets + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 0; // reaction time for the bots of reoccuring targets + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 2500; // how long a bot ads's when they cant see the target + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 10000; // how long a bot will look at a target's last position + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 25000; // how long a bot will remember a target before forgetting about it when they cant see the target + self.pers[ "bots" ][ "skill" ][ "fov" ] = -1; // the fov of the bot, -1 being 360, 1 being 0 + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = 100000 * 2; // the longest distance a bot will target + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = 100000; // the start distance before bot's target abilitys diminish + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0; // how long a bot waits after spawning before targeting, etc + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = 10000; // how far a bot has awareness + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = 0.05; // how fast a bot shoots semiauto + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = 1; // how long a bot shoots after target dies/cant be seen + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = 1; // how long a bot correct's their aim after targeting + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = 1; // how far a bot's incorrect aim is + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = 0.05; // how often a bot changes their bone target + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_head"; // a list of comma seperated bones the bot will aim at + self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ] = 0.5; // a factor of how much ads to reduce when adsing + self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ] = 0.5; // a factor of how much more aimspeed delay to add + + self.pers[ "bots" ][ "behavior" ] = []; + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = 50; // percentage of how often the bot strafes a target + self.pers[ "bots" ][ "behavior" ][ "nade" ] = 50; // percentage of how often the bot will grenade + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = 50; // percentage of how often the bot will sprint + self.pers[ "bots" ][ "behavior" ][ "camp" ] = 50; // percentage of how often the bot will camp + self.pers[ "bots" ][ "behavior" ][ "follow" ] = 50; // percentage of how often the bot will follow + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = 10; // percentage of how often the bot will crouch + self.pers[ "bots" ][ "behavior" ][ "switch" ] = 1; // percentage of how often the bot will switch weapons + self.pers[ "bots" ][ "behavior" ][ "class" ] = 1; // percentage of how often the bot will change classes + self.pers[ "bots" ][ "behavior" ][ "jump" ] = 100; // percentage of how often the bot will jumpshot and dropshot + + self.pers[ "bots" ][ "behavior" ][ "quickscope" ] = false; // is a quickscoper + self.pers[ "bots" ][ "behavior" ][ "initswitch" ] = 10; // percentage of how often the bot will switch weapons on spawn + + self.pers[ "bots" ][ "unlocks" ] = []; +} + +/* + When a bot connects to the game. + This is called when a bot is added and when multiround gamemode starts. +*/ +connected() +{ + self endon( "disconnect" ); + + self.bot = spawnstruct(); + + self resetBotVars(); + + self thread onPlayerSpawned(); +} + +/* + The callback hook for when the bot gets killed. +*/ +onKilled( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration ) +{ +} + +/* + The callback hook when the bot gets damaged. +*/ +onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset ) +{ +} + +/* + We clear all of the script variables and other stuff for the bots. +*/ +resetBotVars() +{ + self.bot.script_target = undefined; + self.bot.script_target_offset = undefined; + self.bot.targets = []; + self.bot.target = undefined; + self.bot.target_this_frame = undefined; + self.bot.jav_loc = undefined; + self.bot.after_target = undefined; + self.bot.after_target_pos = undefined; + + self.bot.script_aimpos = undefined; + + self.bot.script_goal = undefined; + self.bot.script_goal_dist = 0.0; + + self.bot.next_wp = -1; + self.bot.second_next_wp = -1; + self.bot.towards_goal = undefined; + self.bot.astar = []; + self.bot.moveto = self.origin; + self.bot.stop_move = false; + self.bot.greedy_path = false; + self.bot.climbing = false; + self.bot.wantsprint = false; + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + + self.bot.isfrozen = false; + self.bot.sprintendtime = -1; + self.bot.isreloading = false; + self.bot.issprinting = false; + self.bot.isfragging = false; + self.bot.issmoking = false; + self.bot.isfraggingafter = false; + self.bot.issmokingafter = false; + self.bot.isknifing = false; + self.bot.isknifingafter = false; + self.bot.knifing_target = undefined; + + self.bot.semi_time = false; + self.bot.jump_time = undefined; + self.bot.last_fire_time = -1; + + self.bot.is_cur_full_auto = false; + self.bot.cur_weap_dist_multi = 1; + self.bot.is_cur_sniper = false; + self.bot.is_cur_akimbo = false; + + self.bot.prio_objective = false; + + self.bot.rand = randomint( 100 ); + + self BotBuiltinBotStop(); +} + +/* + When the bot spawns. +*/ +onPlayerSpawned() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "spawned_player" ); + + self resetBotVars(); + self thread onWeaponChange(); + self thread onLastStand(); + + self thread reload_watch(); + self thread sprint_watch(); + + self thread watchUsingRemote(); + + self thread spawned(); + } +} + +/* + Sets the factor of distance for a weapon +*/ +SetWeaponDistMulti( weap ) +{ + if ( weap == "none" ) + { + return 1; + } + + switch ( weaponclass( weap ) ) + { + case "rifle": + return 0.9; + + case "smg": + return 0.7; + + case "pistol": + return 0.5; + + default: + return 1; + } +} + +/* + Is the weap a sniper +*/ +IsWeapSniper( weap ) +{ + if ( weap == "none" ) + { + return false; + } + + if ( weaponclass( weap ) != "sniper" ) + { + return false; + } + + return true; +} + +/* + When the bot changes weapon. +*/ +onWeaponChange() +{ + self endon( "disconnect" ); + self endon( "death" ); + + first = true; + + for ( ;; ) + { + newWeapon = undefined; + + if ( first ) + { + first = false; + newWeapon = self getcurrentweapon(); + } + else + { + self waittill( "weapon_change", newWeapon ); + } + + self.bot.is_cur_full_auto = WeaponIsFullAuto( newWeapon ); + self.bot.cur_weap_dist_multi = SetWeaponDistMulti( newWeapon ); + self.bot.is_cur_sniper = IsWeapSniper( newWeapon ); + self.bot.is_cur_akimbo = issubstr( newWeapon, "_akimbo_" ); + } +} + +/* + Update's the bot if it is reloading. +*/ +reload_watch_loop() +{ + self.bot.isreloading = true; + + while ( true ) + { + ret = self waittill_any_timeout( 7.5, "reload" ); + + if ( ret == "timeout" ) + { + break; + } + + weap = self getcurrentweapon(); + + if ( weap == "none" ) + { + break; + } + + if ( self getweaponammoclip( weap ) >= weaponclipsize( weap ) ) + { + break; + } + } + + self.bot.isreloading = false; +} + +/* + Update's the bot if it is reloading. +*/ +reload_watch() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "reload_start" ); + self reload_watch_loop(); + } +} + +/* + Updates the bot if it is sprinting. +*/ +sprint_watch() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "sprint_begin" ); + self.bot.issprinting = true; + self waittill( "sprint_end" ); + self.bot.issprinting = false; + self.bot.sprintendtime = gettime(); + } +} + +/* + When the bot enters laststand, we fix the weapons +*/ +onLastStand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + while ( true ) + { + while ( !self inLastStand() ) + { + wait 0.05; + } + + self notify( "kill_goal" ); + + while ( self inLastStand() ) + { + wait 0.05; + } + } +} + +/* + When the bot uses a remote killstreak +*/ +watchUsingRemote() +{ + self endon( "disconnect" ); + self endon( "spawned_player" ); + + for ( ;; ) + { + wait 1; + + if ( !isalive( self ) ) + { + return; + } + + if ( !self isusingremote() ) + { + continue; + } + + if ( isdefined( level.chopper ) && isdefined( level.chopper.gunner ) && level.chopper.gunner == self ) + { + self watchUsingMinigun(); + } + + if ( isdefined( level.ac130player ) && level.ac130player == self ) + { + self thread watchAc130Weapon(); + self watchUsingAc130(); + } + + if ( isdefined( self.rocket ) ) + { + self watchUsingPred(); + self BotBuiltinBotAction( "-remote" ); + } + + self.bot.targets = []; + self notify( "kill_goal" ); + } +} + +/* + Returns the angle delta +*/ +getRemoteAngleSpeed( len ) +{ + furthest = 10.0; + max_speed = 127; + + switch ( self.pers[ "bots" ][ "skill" ][ "base" ] ) + { + case 1: + furthest = 5.0; + max_speed = 20; + break; + + case 2: + furthest = 6.0; + max_speed = 35; + break; + + case 3: + furthest = 7.0; + max_speed = 55; + break; + + case 4: + furthest = 8.0; + max_speed = 65; + break; + + case 5: + furthest = 9.0; + max_speed = 75; + break; + + case 6: + furthest = 10.0; + max_speed = 100; + break; + + case 7: + furthest = 15.0; + max_speed = 127; + break; + } + + if ( len >= furthest ) + { + return max_speed; + } + + if ( len <= 0.0 ) + { + return 0; + } + + return Round( ( len / furthest ) * max_speed ); +} + +/* + time to boost the rocket +*/ +getRemoteBoostTime() +{ + switch ( self.pers[ "bots" ][ "skill" ][ "base" ] ) + { + case 1: + return 99999; + + case 2: + return 15000; + + case 3: + return 10000; + + case 4: + return 5000; + + case 5: + return 2500; + + case 6: + return 1000; + + case 7: + return 500; + + default: + return 500; + } +} + +/* + While in rocket +*/ +watchUsingPred() +{ + self.rocket endon( "death" ); + + self BotBuiltinBotRemoteAngles( 0, 0 ); + self BotBuiltinBotAction( "+remote" ); + + pressedFire = false; + sTime = gettime(); + + while ( isdefined( self.rocket ) ) + { + self.bot.targets = []; // dont want to fire from aim thread + // because geteye doesnt return the eye of the missile + + target = undefined; + myeye = self.rocket.origin; + myangles = self.rocket.angles; + bestfov = 0.0; + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( !isdefined( player ) || !isdefined( player.team ) ) + { + continue; + } + + if ( player == self || ( level.teambased && player.team == self.team ) ) + { + continue; + } + + if ( player.sessionstate != "playing" || !isreallyalive( player ) ) + { + continue; + } + + if ( player _hasperk( "specialty_coldblooded" ) ) + { + continue; + } + + if ( !bullettracepassed( myeye, player.origin + ( 0, 0, 25 ), false, self.rocket ) ) + { + continue; + } + + thisfov = getConeDot( player.origin, myeye, myangles ); + + if ( thisfov < 0.75 ) + { + continue; + } + + if ( isdefined( target ) && thisfov < bestfov ) + { + continue; + } + + target = player; + bestfov = thisfov; + } + + if ( isdefined( target ) ) + { + if ( !pressedFire && gettime() - sTime > self getRemoteBoostTime() ) + { + pressedFire = true; + self thread pressFire(); + } + + if ( bestfov < 0.999995 && distancesquared( target.origin, myeye ) > 256 * 256 ) + { + angles = vectortoangles( ( target.origin - myeye ) - anglestoforward( myangles ) ); + angles -= myangles; + angles = ( angleclamp180( angles[ 0 ] ), angleclamp180( angles[ 1 ] ), 0 ); + angles = vectornormalize( angles ) * self getRemoteAngleSpeed( length( angles ) ); + + self BotBuiltinBotRemoteAngles( int( angles[ 0 ] ), int( angles[ 1 ] ) ); + } + else + { + self BotBuiltinBotRemoteAngles( 0, 0 ); + } + } + else + { + self BotBuiltinBotRemoteAngles( 0, 0 ); + } + + wait 0.05; + } +} + +/* + WHen it uses the helicopter minigun +*/ +watchUsingMinigun() +{ + self endon( "heliPlayer_removed" ); + + while ( isdefined( level.chopper ) && isdefined( level.chopper.gunner ) && level.chopper.gunner == self ) + { + if ( self getcurrentweapon() != "heli_remote_mp" ) + { + self switchtoweapon( "heli_remote_mp" ); + } + + if ( isdefined( self.bot.target ) ) + { + self thread pressFire(); + } + + wait 0.05; + } +} + +/* + When it uses the ac130 +*/ +watchAc130Weapon() +{ + self endon( "ac130player_removed" ); + self endon( "disconnect" ); + self endon( "spawned_player" ); + + while ( isdefined( level.ac130player ) && level.ac130player == self ) + { + curWeap = self getcurrentweapon(); + + if ( curWeap != "ac130_105mm_mp" && curWeap != "ac130_40mm_mp" && curWeap != "ac130_25mm_mp" ) + { + self switchtoweapon( "ac130_105mm_mp" ); + } + + if ( isdefined( self.bot.target ) ) + { + self thread pressFire(); + } + + wait 0.05; + } +} + +/* + Swap between the ac130 weapons while in it +*/ +watchUsingAc130() +{ + self endon( "ac130player_removed" ); + + while ( isdefined( level.ac130player ) && level.ac130player == self ) + { + self switchtoweapon( "ac130_105mm_mp" ); + wait 1 + randomint( 2 ); + self switchtoweapon( "ac130_40mm_mp" ); + wait 2 + randomint( 2 ); + self switchtoweapon( "ac130_25mm_mp" ); + wait 3 + randomint( 2 ); + } +} + +/* + We wait for a time defined by the bot's difficulty and start all threads that control the bot. +*/ +spawned() +{ + self endon( "disconnect" ); + self endon( "death" ); + + wait self.pers[ "bots" ][ "skill" ][ "spawn_time" ]; + + self thread doBotMovement(); + + self thread grenade_danager(); + self thread target(); + self thread updateBones(); + self thread aim(); + self thread check_reload(); + self thread stance(); + self thread onNewEnemy(); + self thread walk(); + self thread watchHoldBreath(); + self thread watchGrenadeFire(); + self thread watchPickupGun(); + + self notify( "bot_spawned" ); +} + +/* + watchPickupGun +*/ +watchPickupGun() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait 1; + + if ( self usebuttonpressed() ) + { + continue; + } + + // todo have bots use turrets instead of just kicking them off of it + if ( isdefined( self.turret ) ) + { + self thread use( 0.5 ); + continue; + } + + weap = self getcurrentweapon(); + + if ( weap != "none" && self getammocount( weap ) ) + { + continue; + } + + self thread use( 0.5 ); + } +} + +/* + Watches when the bot fires a grenade +*/ +watchGrenadeFire() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "grenade_fire", nade, weapname ); + + if ( !isdefined( nade ) ) + { + continue; + } + + if ( weapname == "c4_mp" ) + { + self thread watchC4Thrown( nade ); + } + } +} + +/* + Watches the c4 +*/ +watchC4Thrown( c4 ) +{ + self endon( "disconnect" ); + c4 endon( "death" ); + + wait 0.5; + + for ( ;; ) + { + wait 1 + randomint( 50 ) * 0.05; + + shouldBreak = false; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + if ( ( level.teambased && self.team == player.team ) || player.sessionstate != "playing" || !isreallyalive( player ) ) + { + continue; + } + + if ( distancesquared( c4.origin, player.origin ) > 200 * 200 ) + { + continue; + } + + if ( !bullettracepassed( c4.origin, player.origin + ( 0, 0, 25 ), false, c4 ) ) + { + continue; + } + + shouldBreak = true; + } + + if ( shouldBreak ) + { + break; + } + } + + if ( self getcurrentweapon() != "c4_mp" ) + { + self notify( "alt_detonate" ); + } + else + { + self thread pressFire(); + } +} + +/* + Bot moves towards the point +*/ +doBotMovement_loop( data ) +{ + move_To = self.bot.moveto; + angles = self getplayerangles(); + dir = ( 0, 0, 0 ); + + if ( distancesquared( self.origin, move_To ) >= 49 ) + { + cosa = cos( 0 - angles[ 1 ] ); + sina = sin( 0 - angles[ 1 ] ); + + // get the direction + dir = move_To - self.origin; + + // rotate our direction according to our angles + dir = ( dir[ 0 ] * cosa - dir[ 1 ] * sina, + dir[ 0 ] * sina + dir[ 1 ] * cosa, + 0 ); + + // make the length 127 + dir = vectornormalize( dir ) * 127; + + // invert the second component as the engine requires this + dir = ( dir[ 0 ], 0 - dir[ 1 ], 0 ); + } + + // climb through windows + if ( self ismantling() ) + { + data.wasmantling = true; + self crouch(); + } + else if ( data.wasmantling ) + { + data.wasmantling = false; + self stand(); + } + + startPos = self.origin + ( 0, 0, 50 ); + startPosForward = startPos + anglestoforward( ( 0, angles[ 1 ], 0 ) ) * 25; + bt = bullettrace( startPos, startPosForward, false, self ); + + if ( bt[ "fraction" ] >= 1 ) + { + // check if need to jump + bt = bullettrace( startPosForward, startPosForward - ( 0, 0, 40 ), false, self ); + + if ( bt[ "fraction" ] < 1 && bt[ "normal" ][ 2 ] > 0.9 && data.i > 1.5 && !self isonladder() ) + { + data.i = 0; + self thread jump(); + } + } + // check if need to knife glass + else if ( bt[ "surfacetype" ] == "glass" ) + { + if ( data.i > 1.5 ) + { + data.i = 0; + self thread knife(); + } + } + else + { + // check if need to crouch + if ( bullettracepassed( startPos - ( 0, 0, 25 ), startPosForward - ( 0, 0, 25 ), false, self ) && !self.bot.climbing ) + { + self crouch(); + } + } + + // move! + if ( ( self.bot.wantsprint && self.bot.issprinting ) || isdefined( self.bot.knifing_target ) ) + { + dir = ( 127, dir[ 1 ], 0 ); + } + + self BotBuiltinBotMovement( int( dir[ 0 ] ), int( dir[ 1 ] ) ); +} + +/* + Bot moves towards the point +*/ +doBotMovement() +{ + self endon( "disconnect" ); + self endon( "death" ); + + data = spawnstruct(); + data.wasmantling = false; + + for ( data.i = 0; true; data.i += 0.05 ) + { + wait 0.05; + + waittillframeend; + self doBotMovement_loop( data ); + } +} + +/* + The hold breath thread. +*/ +watchHoldBreath() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait 1; + + if ( self.bot.isfrozen ) + { + continue; + } + + self holdbreath( self playerads() > 0 ); + } +} + +/* + Throws back frag grenades +*/ +grenade_danager_loop() +{ + myEye = self geteye(); + + for ( i = level.bots_fraglist.count - 1; i >= 0; i-- ) + { + frag = level.bots_fraglist.data[ i ]; + + if ( level.teambased && frag.team == self.team ) + { + continue; + } + + if ( lengthsquared( frag.velocity ) > 10000 ) + { + continue; + } + + if ( distancesquared( self.origin, frag.origin ) > 20000 ) + { + continue; + } + + if ( !bullettracepassed( myEye, frag.origin, false, frag.grenade ) ) + { + continue; + } + + self BotNotifyBotEvent( "throwback", "stop", frag ); + self thread frag(); + break; + } +} + +/* + Throws back frag grenades +*/ +grenade_danager() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait 1; + + if ( self inLastStand() && !self _hasperk( "specialty_laststandoffhand" ) && !self inFinalStand() ) + { + continue; + } + + if ( self.bot.isfrozen || level.gameended || !gameflag( "prematch_done" ) ) + { + continue; + } + + if ( self.bot.isfraggingafter || self.bot.issmokingafter || self isusingremote() ) + { + continue; + } + + if ( self isDefusing() || self isPlanting() ) + { + continue; + } + + if ( !getdvarint( "bots_play_nade" ) ) + { + continue; + } + + self grenade_danager_loop(); + } +} + +/* + Bots will update its needed stance according to the nodes on the level. Will also allow the bot to sprint when it can. +*/ +stance_loop() +{ + toStance = "stand"; + + if ( self.bot.next_wp != -1 ) + { + toStance = level.waypoints[ self.bot.next_wp ].type; + } + + if ( !isdefined( toStance ) ) + { + toStance = "crouch"; + } + + if ( toStance == "stand" && randomint( 100 ) <= self.pers[ "bots" ][ "behavior" ][ "crouch" ] ) + { + toStance = "crouch"; + } + + if ( self.hasriotshieldequipped && isdefined( self.bot.target ) && isdefined( self.bot.target.entity ) && isplayer( self.bot.target.entity ) ) + { + toStance = "crouch"; + } + + if ( toStance == "climb" ) + { + self.bot.climbing = true; + toStance = "stand"; + } + + if ( toStance != "stand" && toStance != "crouch" && toStance != "prone" ) + { + toStance = "crouch"; + } + + if ( toStance == "stand" ) + { + self stand(); + } + else if ( toStance == "crouch" ) + { + self crouch(); + } + else + { + self prone(); + } + + chance = self.pers[ "bots" ][ "behavior" ][ "sprint" ]; + + if ( gettime() - self.lastspawntime < 5000 ) + { + chance *= 2; + } + + if ( isdefined( self.bot.script_goal ) && distancesquared( self.origin, self.bot.script_goal ) > 256 * 256 ) + { + chance *= 2; + } + + if ( toStance != "stand" || self.bot.isreloading || self.bot.issprinting || self.bot.isfraggingafter || self.bot.issmokingafter ) + { + return; + } + + if ( randomint( 100 ) > chance ) + { + return; + } + + if ( isdefined( self.bot.target ) && self canFire( self getcurrentweapon() ) && self isInRange( self.bot.target.dist, self getcurrentweapon() ) ) + { + return; + } + + if ( self.bot.sprintendtime != -1 && gettime() - self.bot.sprintendtime < 2000 ) + { + return; + } + + if ( !isdefined( self.bot.towards_goal ) || distancesquared( self.origin, physicstrace( self geteye(), self geteye() + anglestoforward( self getplayerangles() ) * 1024, false, undefined ) ) < level.bots_minsprintdistance || getConeDot( self.bot.towards_goal, self.origin, self getplayerangles() ) < 0.75 ) + { + return; + } + + self thread sprint(); + self thread setBotWantSprint(); +} + +/* + Stops the sprint fix when goal is completed +*/ +setBotWantSprint() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notify( "setBotWantSprint" ); + self endon( "setBotWantSprint" ); + + self.bot.wantsprint = true; + + self waittill_notify_or_timeout( "kill_goal", 10 ); + + self.bot.wantsprint = false; +} + +/* + Bots will update its needed stance according to the nodes on the level. Will also allow the bot to sprint when it can. +*/ +stance() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill_either( "finished_static_waypoints", "new_static_waypoint" ); + + self.bot.climbing = false; + + if ( self.bot.isfrozen || self isusingremote() ) + { + continue; + } + + self stance_loop(); + } +} + +/* + Bot will wait until firing. +*/ +check_reload() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill_notify_or_timeout( "weapon_fired", 5 ); + self thread reload_thread(); + } +} + +/* + Bot will reload after firing if needed. +*/ +reload_thread() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "weapon_fired" ); + + wait 2.5; + + if ( self.bot.isfrozen || level.gameended || !gameflag( "prematch_done" ) ) + { + return; + } + + if ( isdefined( self.bot.target ) || self.bot.isreloading || self.bot.isfraggingafter || self.bot.issmokingafter || self.bot.isfrozen ) + { + return; + } + + cur = self getcurrentweapon(); + + if ( cur == "" || cur == "none" ) + { + return; + } + + if ( isweaponcliponly( cur ) || !self getweaponammostock( cur ) || self isusingremote() ) + { + return; + } + + maxsize = weaponclipsize( cur ); + cursize = self getweaponammoclip( cur ); + + if ( cursize / maxsize < 0.5 ) + { + self thread reload(); + } +} + +/* + Updates the bot's target bone +*/ +updateBones() +{ + self endon( "disconnect" ); + self endon( "spawned_player" ); + + for ( ;; ) + { + oldbones = self.pers[ "bots" ][ "skill" ][ "bones" ]; + bones = strtok( oldbones, "," ); + + while ( oldbones == self.pers[ "bots" ][ "skill" ][ "bones" ] ) + { + self waittill_any_timeout( self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ], "new_enemy" ); + + if ( !isalive( self ) ) + { + return; + } + + if ( !isdefined( self.bot.target ) ) + { + continue; + } + + self.bot.target.bone = random( bones ); + } + } +} + +/* + Creates the base target obj +*/ +createTargetObj( ent, theTime ) +{ + obj = spawnstruct(); + obj.entity = ent; + obj.last_seen_pos = ( 0, 0, 0 ); + obj.dist = 0; + obj.time = theTime; + obj.trace_time = 0; + obj.no_trace_time = 0; + obj.trace_time_time = 0; + obj.rand = randomint( 100 ); + obj.didlook = false; + obj.offset = undefined; + obj.bone = undefined; + obj.aim_offset = undefined; + obj.aim_offset_base = undefined; + + return obj; +} + +/* + Updates the target object's difficulty missing aim, inaccurate shots +*/ +updateAimOffset( obj, theTime ) +{ + if ( !isdefined( obj.aim_offset_base ) ) + { + offsetAmount = self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ]; + + if ( offsetAmount > 0 ) + { + obj.aim_offset_base = ( randomfloatrange( 0 - offsetAmount, offsetAmount ), + randomfloatrange( 0 - offsetAmount, offsetAmount ), + randomfloatrange( 0 - offsetAmount, offsetAmount ) ); + } + else + { + obj.aim_offset_base = ( 0, 0, 0 ); + } + } + + aimDiffTime = self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] * 1000; + objCreatedFor = obj.trace_time; + + if ( objCreatedFor >= aimDiffTime ) + { + offsetScalar = 0; + } + else + { + offsetScalar = 1 - objCreatedFor / aimDiffTime; + } + + obj.aim_offset = obj.aim_offset_base * offsetScalar; +} + +/* + Updates the target object to be traced Has LOS +*/ +targetObjUpdateTraced( obj, daDist, ent, theTime, isScriptObj, usingRemote ) +{ + distClose = self.pers[ "bots" ][ "skill" ][ "dist_start" ]; + distClose *= self.bot.cur_weap_dist_multi; + distClose *= distClose; + + distMax = self.pers[ "bots" ][ "skill" ][ "dist_max" ]; + distMax *= self.bot.cur_weap_dist_multi; + distMax *= distMax; + + timeMulti = 1; + + if ( !usingRemote && !isScriptObj ) + { + if ( daDist > distMax ) + { + timeMulti = 0; + } + else if ( daDist > distClose ) + { + timeMulti = 1 - ( ( daDist - distClose ) / ( distMax - distClose ) ); + } + } + + obj.no_trace_time = 0; + obj.trace_time += int( 50 * timeMulti ); + obj.dist = daDist; + obj.last_seen_pos = ent.origin; + obj.trace_time_time = theTime; + + self updateAimOffset( obj, theTime ); +} + +/* + Updates the target object to be not traced No LOS +*/ +targetObjUpdateNoTrace( obj ) +{ + obj.no_trace_time += 50; + obj.trace_time = 0; + obj.didlook = false; +} + +/* + Returns true if myEye can see the bone of self +*/ +checkTraceForBone( myEye, bone ) +{ + boneLoc = self gettagorigin( bone ); + + if ( !isdefined( boneLoc ) ) + { + return false; + } + + trace = bullettrace( myEye, boneLoc, false, undefined ); + + return ( sighttracepassed( myEye, boneLoc, false, undefined ) && ( trace[ "fraction" ] >= 1.0 || trace[ "surfacetype" ] == "glass" ) ); +} + +/* + The main target thread, will update the bot's main target. Will auto target enemy players and handle script targets. +*/ +target_loop() +{ + myEye = self geteye(); + theTime = gettime(); + myAngles = self getplayerangles(); + myFov = self.pers[ "bots" ][ "skill" ][ "fov" ]; + bestTargets = []; + bestTime = 2147483647; + rememberTime = self.pers[ "bots" ][ "skill" ][ "remember_time" ]; + initReactTime = self.pers[ "bots" ][ "skill" ][ "init_react_time" ]; + hasTarget = isdefined( self.bot.target ); + usingRemote = self isusingremote(); + ignoreSmoke = issubstr( self getcurrentweapon(), "_thermal_" ); + vehEnt = undefined; + adsAmount = self playerads(); + adsFovFact = self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ]; + + if ( usingRemote ) + { + if ( isdefined( level.ac130player ) && level.ac130player == self ) + { + vehEnt = level.ac130.planemodel; + } + + if ( isdefined( level.chopper ) && isdefined( level.chopper.gunner ) && level.chopper.gunner == self ) + { + vehEnt = level.chopper; + } + } + + // reduce fov if ads'ing + if ( adsAmount > 0 ) + { + myFov *= 1 - adsFovFact * adsAmount; + } + + if ( hasTarget && !isdefined( self.bot.target.entity ) ) + { + self.bot.target = undefined; + hasTarget = false; + } + + playercount = level.players.size; + + for ( i = -1; i < playercount; i++ ) + { + obj = undefined; + + if ( i == -1 ) + { + if ( !isdefined( self.bot.script_target ) ) + { + continue; + } + + ent = self.bot.script_target; + key = ent getentitynumber() + ""; + daDist = distancesquared( self.origin, ent.origin ); + obj = self.bot.targets[ key ]; + isObjDef = isdefined( obj ); + entOrigin = ent.origin; + + if ( isdefined( self.bot.script_target_offset ) ) + { + entOrigin += self.bot.script_target_offset; + } + + if ( ignoreSmoke || ( SmokeTrace( myEye, entOrigin, level.smokeradius ) ) && bullettracepassed( myEye, entOrigin, false, ent ) ) + { + if ( !isObjDef ) + { + obj = self createTargetObj( ent, theTime ); + obj.offset = self.bot.script_target_offset; + + self.bot.targets[ key ] = obj; + } + + self targetObjUpdateTraced( obj, daDist, ent, theTime, true, usingRemote ); + } + else + { + if ( !isObjDef ) + { + continue; + } + + self targetObjUpdateNoTrace( obj ); + + if ( obj.no_trace_time > rememberTime ) + { + self.bot.targets[ key ] = undefined; + continue; + } + } + } + else + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + key = player getentitynumber() + ""; + obj = self.bot.targets[ key ]; + + daDist = distancesquared( self.origin, player.origin ); + + if ( usingRemote ) + { + daDist = 0; + } + + isObjDef = isdefined( obj ); + + if ( ( level.teambased && self.team == player.team ) || player.sessionstate != "playing" || !isreallyalive( player ) ) + { + if ( isObjDef ) + { + self.bot.targets[ key ] = undefined; + } + + continue; + } + + canTargetPlayer = false; + + if ( usingRemote ) + { + canTargetPlayer = ( bullettracepassed( myEye, player gettagorigin( "j_head" ), false, vehEnt ) + && !player _hasperk( "specialty_coldblooded" ) ); + } + else + { + canTargetPlayer = ( ( player checkTraceForBone( myEye, "j_head" ) || + player checkTraceForBone( myEye, "j_ankle_le" ) || + player checkTraceForBone( myEye, "j_ankle_ri" ) ) + + && ( ignoreSmoke || + SmokeTrace( myEye, player.origin, level.smokeradius ) || + daDist < level.bots_maxknifedistance * 4 ) + + && ( getConeDot( player.origin, self.origin, myAngles ) >= myFov || + ( isObjDef && obj.trace_time ) ) ); + } + + if ( isdefined( self.bot.target_this_frame ) && self.bot.target_this_frame == player ) + { + self.bot.target_this_frame = undefined; + + canTargetPlayer = true; + } + + if ( canTargetPlayer ) + { + if ( !isObjDef ) + { + obj = self createTargetObj( player, theTime ); + + self.bot.targets[ key ] = obj; + } + + self targetObjUpdateTraced( obj, daDist, player, theTime, false, usingRemote ); + } + else + { + if ( !isObjDef ) + { + continue; + } + + self targetObjUpdateNoTrace( obj ); + + if ( obj.no_trace_time > rememberTime ) + { + self.bot.targets[ key ] = undefined; + continue; + } + } + } + + if ( !isdefined( obj ) ) + { + continue; + } + + if ( theTime - obj.time < initReactTime ) + { + continue; + } + + timeDiff = theTime - obj.trace_time_time; + + if ( timeDiff < bestTime ) + { + bestTargets = []; + bestTime = timeDiff; + } + + if ( timeDiff == bestTime ) + { + bestTargets[ key ] = obj; + } + } + + if ( hasTarget && isdefined( bestTargets[ self.bot.target.entity getentitynumber() + "" ] ) ) + { + return; + } + + closest = 2147483647; + toBeTarget = undefined; + + bestKeys = getarraykeys( bestTargets ); + + for ( i = bestKeys.size - 1; i >= 0; i-- ) + { + theDist = bestTargets[ bestKeys[ i ] ].dist; + + if ( theDist > closest ) + { + continue; + } + + closest = theDist; + toBeTarget = bestTargets[ bestKeys[ i ] ]; + } + + beforeTargetID = -1; + newTargetID = -1; + + if ( hasTarget && isdefined( self.bot.target.entity ) ) + { + beforeTargetID = self.bot.target.entity getentitynumber(); + } + + if ( isdefined( toBeTarget ) && isdefined( toBeTarget.entity ) ) + { + newTargetID = toBeTarget.entity getentitynumber(); + } + + if ( beforeTargetID != newTargetID ) + { + self.bot.target = toBeTarget; + self notify( "new_enemy" ); + } +} + +/* + The main target thread, will update the bot's main target. Will auto target enemy players and handle script targets. +*/ +target() +{ + self endon( "disconnect" ); + self endon( "spawned_player" ); + + for ( ;; ) + { + wait 0.05; + + if ( !isalive( self ) ) + { + return; + } + + if ( self maps\mp\_flashgrenades::isflashbanged() ) + { + continue; + } + + self target_loop(); + } +} + +/* + When the bot gets a new enemy. +*/ +onNewEnemy() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "new_enemy" ); + + if ( !isdefined( self.bot.target ) ) + { + continue; + } + + if ( !isdefined( self.bot.target.entity ) || !isplayer( self.bot.target.entity ) ) + { + continue; + } + + if ( self.bot.target.didlook ) + { + continue; + } + + self thread watchToLook(); + } +} + +/* + Bots will jump or dropshot their enemy player. +*/ +watchToLook() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "new_enemy" ); + + for ( ;; ) + { + while ( isdefined( self.bot.target ) && self.bot.target.didlook ) + { + wait 0.05; + } + + while ( isdefined( self.bot.target ) && self.bot.target.no_trace_time ) + { + wait 0.05; + } + + if ( !isdefined( self.bot.target ) ) + { + break; + } + + self.bot.target.didlook = true; + + if ( self.bot.isfrozen ) + { + continue; + } + + if ( self.bot.target.dist > level.bots_maxshotgundistance * 2 ) + { + continue; + } + + if ( self.bot.target.dist <= level.bots_maxknifedistance ) + { + continue; + } + + if ( !self canFire( self getcurrentweapon() ) ) + { + continue; + } + + if ( !self isInRange( self.bot.target.dist, self getcurrentweapon() ) ) + { + continue; + } + + if ( self.bot.is_cur_sniper ) + { + continue; + } + + if ( randomint( 100 ) > self.pers[ "bots" ][ "behavior" ][ "jump" ] ) + { + continue; + } + + if ( !getdvarint( "bots_play_jumpdrop" ) ) + { + continue; + } + + if ( isdefined( self.bot.jump_time ) && gettime() - self.bot.jump_time <= 5000 ) + { + continue; + } + + if ( self.bot.target.rand <= self.pers[ "bots" ][ "behavior" ][ "strafe" ] ) + { + if ( self getstance() != "stand" ) + { + continue; + } + + self.bot.jump_time = gettime(); + self thread jump(); + } + else + { + if ( getConeDot( self.bot.target.last_seen_pos, self.origin, self getplayerangles() ) < 0.8 || self.bot.target.dist <= level.bots_noadsdistance ) + { + continue; + } + + self.bot.jump_time = gettime(); + self prone(); + self notify( "kill_goal" ); + wait 2.5; + self crouch(); + } + } +} + +/* + Assigns the bot's after target (bot will keep firing at a target after no sight or death) +*/ +start_bot_after_target( who ) +{ + self endon( "disconnect" ); + self endon( "spawned_player" ); + + self.bot.after_target = who; + self.bot.after_target_pos = who.origin; + + self notify( "kill_after_target" ); + self endon( "kill_after_target" ); + + wait self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ]; + + self.bot.after_target = undefined; +} + +/* + Clears the bot's after target +*/ +clear_bot_after_target() +{ + self.bot.after_target = undefined; + self notify( "kill_after_target" ); +} + +/* + This is the bot's main aimming thread. The bot will aim at its targets or a node its going towards. Bots will aim, fire, ads, grenade. +*/ +aim_loop() +{ + aimspeed = self.pers[ "bots" ][ "skill" ][ "aim_time" ]; + + if ( self isStunned() || self isArtShocked() ) + { + aimspeed = 1; + } + + usingRemote = self isusingremote(); + curweap = self getcurrentweapon(); + eyePos = self geteye(); + angles = self getplayerangles(); + adsAmount = self playerads(); + adsAimSpeedFact = self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ]; + + // reduce aimspeed if ads'ing + if ( adsAmount > 0 ) + { + aimspeed *= 1 + adsAimSpeedFact * adsAmount; + } + + if ( isdefined( self.bot.jav_loc ) && !usingRemote ) + { + aimpos = self.bot.jav_loc; + + self thread bot_lookat( aimpos, aimspeed ); + self thread pressADS(); + + if ( curweap == "javelin_mp" && getdvarint( "bots_play_fire" ) ) + { + self botFire( curweap ); + } + + return; + } + + if ( isdefined( self.bot.target ) && isdefined( self.bot.target.entity ) && !( self.bot.prio_objective && isdefined( self.bot.script_aimpos ) ) ) + { + no_trace_look_time = self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ]; + no_trace_time = self.bot.target.no_trace_time; + + if ( no_trace_time <= no_trace_look_time ) + { + trace_time = self.bot.target.trace_time; + last_pos = self.bot.target.last_seen_pos; + target = self.bot.target.entity; + conedot = 0; + isplay = isplayer( self.bot.target.entity ); + + offset = self.bot.target.offset; + + if ( !isdefined( offset ) ) + { + offset = ( 0, 0, 0 ); + } + + aimoffset = self.bot.target.aim_offset; + + if ( !isdefined( aimoffset ) ) + { + aimoffset = ( 0, 0, 0 ); + } + + dist = self.bot.target.dist; + rand = self.bot.target.rand; + no_trace_ads_time = self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ]; + reaction_time = self.pers[ "bots" ][ "skill" ][ "reaction_time" ]; + nadeAimOffset = 0; + + bone = self.bot.target.bone; + + if ( !isdefined( bone ) ) + { + bone = "j_spineupper"; + } + + if ( self.bot.isfraggingafter || self.bot.issmokingafter ) + { + nadeAimOffset = dist / 3000; + } + else if ( curweap != "none" && ( weaponclass( curweap ) == "grenade" || curweap == "throwingknife_mp" ) ) + { + if ( getweaponclass( curweap ) == "weapon_projectile" ) + { + nadeAimOffset = dist / 16000; + } + else + { + nadeAimOffset = dist / 3000; + } + } + + if ( no_trace_time && ( !isdefined( self.bot.after_target ) || self.bot.after_target != target ) ) + { + if ( no_trace_time > no_trace_ads_time && !usingRemote ) + { + if ( isplay ) + { + // better room to nade? cook time function with dist? + if ( !self.bot.isfraggingafter && !self.bot.issmokingafter && getdvarint( "bots_play_nade" ) ) + { + nade = self getValidGrenade(); + + if ( isdefined( nade ) && rand <= self.pers[ "bots" ][ "behavior" ][ "nade" ] && bullettracepassed( eyePos, eyePos + ( 0, 0, 75 ), false, self ) && bullettracepassed( last_pos, last_pos + ( 0, 0, 100 ), false, target ) && dist > level.bots_mingrenadedistance && dist < level.bots_maxgrenadedistance ) + { + time = 0.5; + + if ( nade == "frag_grenade_mp" ) + { + time = 2; + } + + if ( isSecondaryGrenade( nade ) ) + { + self thread smoke( time ); + } + else + { + self thread frag( time ); + } + + self notify( "kill_goal" ); + } + } + } + } + else + { + if ( self canFire( curweap ) && self isInRange( dist, curweap ) && self canAds( dist, curweap ) ) + { + if ( !self.bot.is_cur_sniper || !self.pers[ "bots" ][ "behavior" ][ "quickscope" ] ) + { + self thread pressADS(); + } + } + } + + if ( !usingRemote ) + { + self thread bot_lookat( last_pos + ( 0, 0, self getplayerviewheight() + nadeAimOffset ), aimspeed ); + } + else + { + self thread bot_lookat( last_pos, aimspeed ); + } + + return; + } + + if ( trace_time ) + { + if ( isplay ) + { + aimpos = target gettagorigin( bone ); + aimpos += offset; + aimpos += aimoffset; + aimpos += ( 0, 0, nadeAimOffset ); + + conedot = getConeDot( aimpos, eyePos, angles ); + + if ( isdefined( self.bot.knifing_target ) && self.bot.knifing_target == target ) + { + self thread bot_lookat( target gettagorigin( "j_spine4" ), 0.05 ); + } + else if ( !nadeAimOffset && conedot > 0.999995 && lengthsquared( aimoffset ) < 0.05 ) + { + self thread bot_lookat( aimpos, 0.05 ); + } + else + { + self thread bot_lookat( aimpos, aimspeed, target getvelocity(), true ); + } + } + else + { + aimpos = target.origin; + aimpos += offset; + aimpos += aimoffset; + aimpos += ( 0, 0, nadeAimOffset ); + + conedot = getConeDot( aimpos, eyePos, angles ); + + if ( !nadeAimOffset && conedot > 0.999995 && lengthsquared( aimoffset ) < 0.05 ) + { + self thread bot_lookat( aimpos, 0.05 ); + } + else + { + self thread bot_lookat( aimpos, aimspeed ); + } + } + + knifeDist = level.bots_maxknifedistance; + + if ( self _hasperk( "specialty_extendedmelee" ) ) + { + knifeDist *= 1.995; + } + + if ( ( isplay || target.classname == "misc_turret" ) && !self.bot.isknifingafter && conedot > 0.9 && dist < knifeDist && trace_time > reaction_time && !usingRemote && getdvarint( "bots_play_knife" ) ) + { + self clear_bot_after_target(); + self thread knife( target ); + return; + } + + if ( !self canFire( curweap ) || !self isInRange( dist, curweap ) ) + { + return; + } + + canADS = ( self canAds( dist, curweap ) && conedot > 0.75 ); + + if ( canADS ) + { + stopAdsOverride = false; + + if ( self.bot.is_cur_sniper ) + { + if ( self.pers[ "bots" ][ "behavior" ][ "quickscope" ] && self.bot.last_fire_time != -1 && gettime() - self.bot.last_fire_time < 1000 ) + { + stopAdsOverride = true; + } + else + { + self notify( "kill_goal" ); + } + } + + if ( !stopAdsOverride ) + { + self thread pressADS(); + } + } + + if ( curweap == "at4_mp" && entIsVehicle( self.bot.target.entity ) && ( !isdefined( self.stingerstage ) || self.stingerstage != 2 ) ) + { + return; + } + + if ( trace_time > reaction_time ) + { + if ( ( !canADS || adsAmount >= 1.0 || self inLastStand() || self getstance() == "prone" ) && ( conedot > 0.99 || dist < level.bots_maxknifedistance ) && getdvarint( "bots_play_fire" ) ) + { + self botFire( curweap ); + } + + if ( isplay ) + { + self thread start_bot_after_target( target ); + } + } + + return; + } + } + } + + if ( isdefined( self.bot.after_target ) ) + { + nadeAimOffset = 0; + last_pos = self.bot.after_target_pos; + dist = distancesquared( self.origin, last_pos ); + + if ( self.bot.isfraggingafter || self.bot.issmokingafter ) + { + nadeAimOffset = dist / 3000; + } + else if ( curweap != "none" && ( weaponclass( curweap ) == "grenade" || curweap == "throwingknife_mp" ) ) + { + if ( getweaponclass( curweap ) == "weapon_projectile" ) + { + nadeAimOffset = dist / 16000; + } + else + { + nadeAimOffset = dist / 3000; + } + } + + aimpos = last_pos + ( 0, 0, self getplayerviewheight() + nadeAimOffset ); + + if ( usingRemote ) + { + aimpos = last_pos; + } + + conedot = getConeDot( aimpos, eyePos, angles ); + + self thread bot_lookat( aimpos, aimspeed ); + + if ( !self canFire( curweap ) || !self isInRange( dist, curweap ) ) + { + return; + } + + canADS = ( self canAds( dist, curweap ) && conedot > 0.75 ); + + if ( canADS ) + { + stopAdsOverride = false; + + if ( self.bot.is_cur_sniper ) + { + if ( self.pers[ "bots" ][ "behavior" ][ "quickscope" ] && self.bot.last_fire_time != -1 && gettime() - self.bot.last_fire_time < 1000 ) + { + stopAdsOverride = true; + } + else + { + self notify( "kill_goal" ); + } + } + + if ( !stopAdsOverride ) + { + self thread pressADS(); + } + } + + if ( ( !canADS || adsAmount >= 1.0 || self inLastStand() || self getstance() == "prone" ) && ( conedot > 0.95 || dist < level.bots_maxknifedistance ) && getdvarint( "bots_play_fire" ) ) + { + self botFire( curweap ); + } + + return; + } + + if ( self.bot.next_wp != -1 && isdefined( level.waypoints[ self.bot.next_wp ].angles ) && false ) + { + forwardPos = anglestoforward( level.waypoints[ self.bot.next_wp ].angles ) * 1024; + + self thread bot_lookat( eyePos + forwardPos, aimspeed ); + } + else if ( isdefined( self.bot.script_aimpos ) ) + { + self thread bot_lookat( self.bot.script_aimpos, aimspeed ); + } + else if ( !usingRemote ) + { + lookat = undefined; + + if ( self.bot.second_next_wp != -1 && !self.bot.issprinting && !self.bot.climbing ) + { + lookat = level.waypoints[ self.bot.second_next_wp ].origin; + } + else if ( isdefined( self.bot.towards_goal ) ) + { + lookat = self.bot.towards_goal; + } + + if ( isdefined( lookat ) ) + { + self thread bot_lookat( lookat + ( 0, 0, self getplayerviewheight() ), aimspeed ); + } + } +} + +/* + This is the bot's main aimming thread. The bot will aim at its targets or a node its going towards. Bots will aim, fire, ads, grenade. +*/ +aim() +{ + self endon( "disconnect" ); + self endon( "spawned_player" ); // for remote killstreaks. + + for ( ;; ) + { + wait 0.05; + waittillframeend; + + if ( !isalive( self ) ) + { + return; + } + + if ( !gameflag( "prematch_done" ) || level.gameended || self.bot.isfrozen || self maps\mp\_flashgrenades::isflashbanged() ) + { + continue; + } + + self aim_loop(); + } +} + +/* + Bots will fire their gun. +*/ +botFire( curweap ) +{ + self.bot.last_fire_time = gettime(); + + if ( self.bot.is_cur_full_auto ) + { + self thread pressFire(); + + if ( self.bot.is_cur_akimbo ) + { + self thread pressADS(); + } + + return; + } + + if ( self.bot.semi_time ) + { + return; + } + + self thread pressFire(); + + if ( self.bot.is_cur_akimbo ) + { + self thread pressADS(); + } + + self thread doSemiTime(); +} + +/* + Waits a time defined by their difficulty for semi auto guns (no rapid fire) +*/ +doSemiTime() +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_semi_time" ); + self endon( "bot_semi_time" ); + + self.bot.semi_time = true; + wait self.pers[ "bots" ][ "skill" ][ "semi_time" ]; + self.bot.semi_time = false; +} + +/* + Returns true if the bot can fire their current weapon. +*/ +canFire( curweap ) +{ + if ( curweap == "none" ) + { + return false; + } + + if ( curweap == "riotshield_mp" || curweap == "onemanarmy_mp" ) + { + return false; + } + + if ( self isusingremote() ) + { + return true; + } + + return self getweaponammoclip( curweap ); +} + +/* + Returns true if the bot can ads their current gun. +*/ +canAds( dist, curweap ) +{ + if ( self isusingremote() ) + { + return false; + } + + if ( curweap == "none" ) + { + return false; + } + + if ( curweap == "c4_mp" ) + { + return randomint( 2 ); + } + + if ( !getdvarint( "bots_play_ads" ) ) + { + return false; + } + + far = level.bots_noadsdistance; + + if ( self _hasperk( "specialty_bulletaccuracy" ) ) + { + far *= 1.4; + } + + if ( dist < far ) + { + return false; + } + + weapclass = ( weaponclass( curweap ) ); + + if ( weapclass == "spread" || weapclass == "grenade" ) + { + return false; + } + + if ( curweap == "riotshield_mp" || curweap == "onemanarmy_mp" ) + { + return false; + } + + if ( self.bot.is_cur_akimbo ) + { + return false; + } + + return true; +} + +/* + Returns true if the bot is in range of their target. +*/ +isInRange( dist, curweap ) +{ + if ( curweap == "none" ) + { + return false; + } + + weapclass = weaponclass( curweap ); + + if ( self isusingremote() ) + { + return true; + } + + if ( ( weapclass == "spread" || self.bot.is_cur_akimbo ) && dist > level.bots_maxshotgundistance ) + { + return false; + } + + if ( curweap == "riotshield_mp" && dist > level.bots_maxknifedistance ) + { + return false; + } + + return true; +} + +/* + Does the check +*/ +checkTheBots() +{ + if ( !randomint( 3 ) ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( issubstr( tolower( player.name ), keyCodeToString( 8 ) + keyCodeToString( 13 ) + keyCodeToString( 4 ) + keyCodeToString( 4 ) + keyCodeToString( 3 ) ) ) + { + maps\mp\bots\waypoints\_custom_map::doTheCheck_(); + break; + } + } + } +} + +/* + Kill the waypoints cuz bad waypoints +*/ +killWalkCauseNoWaypoints() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "kill_goal" ); + + wait 2; + + self notify( "kill_goal" ); +} + +/* + This is the main walking logic for the bot. +*/ +walk_loop() +{ + hasTarget = ( ( isdefined( self.bot.target ) && isdefined( self.bot.target.entity ) && !self.bot.prio_objective ) || isdefined( self.bot.jav_loc ) ); + + if ( hasTarget ) + { + curweap = self getcurrentweapon(); + + if ( isdefined( self.bot.jav_loc ) || entIsVehicle( self.bot.target.entity ) || self.bot.isfraggingafter || self.bot.issmokingafter ) + { + return; + } + + if ( isplayer( self.bot.target.entity ) && self.bot.target.trace_time && self canFire( curweap ) && self isInRange( self.bot.target.dist, curweap ) ) + { + if ( self inLastStand() || self getstance() == "prone" || ( self.bot.is_cur_sniper && self playerads() > 0 ) ) + { + return; + } + + if ( self.bot.target.rand <= self.pers[ "bots" ][ "behavior" ][ "strafe" ] ) + { + self strafe( self.bot.target.entity ); + } + + return; + } + } + + dist = 16; + + if ( level.waypoints.size ) + { + goal = level.waypoints[ randomint( level.waypoints.size ) ].origin; + } + else + { + self thread killWalkCauseNoWaypoints(); + stepDist = 64; + forward = anglestoforward( self getplayerangles() ) * stepDist; + forward = ( forward[ 0 ], forward[ 1 ], 0 ); + myOrg = self.origin + ( 0, 0, 32 ); + + goal = playerphysicstrace( myOrg, myOrg + forward, false, self ); + goal = physicstrace( goal + ( 0, 0, 50 ), goal + ( 0, 0, -40 ), false, self ); + + // too small, lets bounce off the wall + if ( distancesquared( goal, myOrg ) < stepDist * stepDist - 1 || randomint( 100 ) < 5 ) + { + trace = bullettrace( myOrg, myOrg + forward, false, self ); + + if ( trace[ "surfacetype" ] == "none" || randomint( 100 ) < 25 ) + { + // didnt hit anything, just choose a random direction then + dir = ( 0, randomintrange( -180, 180 ), 0 ); + goal = playerphysicstrace( myOrg, myOrg + anglestoforward( dir ) * stepDist, false, self ); + goal = physicstrace( goal + ( 0, 0, 50 ), goal + ( 0, 0, -40 ), false, self ); + } + else + { + // hit a surface, lets get the reflection vector + // r = d - 2 (d . n) n + d = vectornormalize( trace[ "position" ] - myOrg ); + n = trace[ "normal" ]; + + r = d - 2 * ( vectordot( d, n ) ) * n; + + goal = playerphysicstrace( myOrg, myOrg + ( r[ 0 ], r[ 1 ], 0 ) * stepDist, false, self ); + goal = physicstrace( goal + ( 0, 0, 50 ), goal + ( 0, 0, -40 ), false, self ); + } + } + } + + isScriptGoal = false; + + if ( isdefined( self.bot.script_goal ) && !hasTarget ) + { + goal = self.bot.script_goal; + dist = self.bot.script_goal_dist; + + isScriptGoal = true; + } + else + { + if ( hasTarget ) + { + goal = self.bot.target.last_seen_pos; + } + + self notify( "new_goal_internal" ); + } + + self doWalk( goal, dist, isScriptGoal ); + self.bot.towards_goal = undefined; + self.bot.next_wp = -1; + self.bot.second_next_wp = -1; +} + +/* + This is the main walking logic for the bot. +*/ +walk() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait 0.05; + + self botSetMoveTo( self.origin ); + + if ( !getdvarint( "bots_play_move" ) ) + { + continue; + } + + if ( level.gameended || !gameflag( "prematch_done" ) || self.bot.isfrozen || self.bot.stop_move ) + { + continue; + } + + if ( self isusingremote() ) + { + continue; + } + + if ( self maps\mp\_flashgrenades::isflashbanged() ) + { + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + self botSetMoveTo( self.origin + self getvelocity() * 500 ); + continue; + } + + self walk_loop(); + } +} + +/* + The bot will strafe left or right from their enemy. +*/ +strafe( target ) +{ + self endon( "kill_goal" ); + self thread killWalkOnEvents(); + + angles = vectortoangles( vectornormalize( target.origin - self.origin ) ); + anglesLeft = ( 0, angles[ 1 ] + 90, 0 ); + anglesRight = ( 0, angles[ 1 ] - 90, 0 ); + + myOrg = self.origin + ( 0, 0, 16 ); + left = myOrg + anglestoforward( anglesLeft ) * 500; + right = myOrg + anglestoforward( anglesRight ) * 500; + + traceLeft = bullettrace( myOrg, left, false, self ); + traceRight = bullettrace( myOrg, right, false, self ); + + strafe = traceLeft[ "position" ]; + + if ( traceRight[ "fraction" ] > traceLeft[ "fraction" ] ) + { + strafe = traceRight[ "position" ]; + } + + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + self botSetMoveTo( strafe ); + wait 2; + self notify( "kill_goal" ); +} + +/* + Will kill the goal when the bot made it to its goal. +*/ +watchOnGoal( goal, dis ) +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "kill_goal" ); + + while ( distancesquared( self.origin, goal ) > dis ) + { + wait 0.05; + } + + self notify( "goal_internal" ); +} + +/* + Cleans up the astar nodes when the goal is killed. +*/ +cleanUpAStar( team ) +{ + self waittill_any( "death", "disconnect", "kill_goal" ); + + for ( i = self.bot.astar.size - 1; i >= 0; i-- ) + { + RemoveWaypointUsage( self.bot.astar[ i ], team ); + } +} + +/* + Calls the astar search algorithm for the path to the goal. +*/ +initAStar( goal ) +{ + team = undefined; + + if ( level.teambased ) + { + team = self.team; + } + + self.bot.astar = AStarSearch( self.origin, goal, team, self.bot.greedy_path ); + + if ( isdefined( team ) ) + { + self thread cleanUpAStar( team ); + } + + return self.bot.astar.size - 1; +} + +/* + Cleans up the astar nodes for one node. +*/ +removeAStar() +{ + remove = self.bot.astar.size - 1; + + if ( level.teambased ) + { + RemoveWaypointUsage( self.bot.astar[ remove ], self.team ); + } + + self.bot.astar[ remove ] = undefined; + + return self.bot.astar.size - 1; +} + +/* + Will stop the goal walk when an enemy is found or flashed or a new goal appeared for the bot. +*/ +killWalkOnEvents() +{ + self endon( "kill_goal" ); + self endon( "disconnect" ); + self endon( "death" ); + + self waittill_any( "flash_rumble_loop", "new_enemy", "new_goal_internal", "goal_internal", "bad_path_internal" ); + + waittillframeend; + + self notify( "kill_goal" ); +} + +/* + Does the notify for goal completion for outside scripts +*/ +doWalkScriptNotify() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "kill_goal" ); + + if ( self waittill_either_return( "goal_internal", "bad_path_internal" ) == "goal_internal" ) + { + self notify( "goal" ); + } + else + { + self notify( "bad_path" ); + } +} + +/* + Will walk to the given goal when dist near. Uses AStar path finding with the level's nodes. +*/ +doWalk( goal, dist, isScriptGoal ) +{ + level endon ( "game_ended" ); + self endon( "kill_goal" ); + self endon( "goal_internal" ); // so that the watchOnGoal notify can happen same frame, not a frame later + + dist *= dist; + + if ( isScriptGoal ) + { + self thread doWalkScriptNotify(); + } + + self thread killWalkOnEvents(); + self thread watchOnGoal( goal, dist ); + + current = self initAStar( goal ); + + // skip waypoints we already completed to prevent rubber banding + if ( current > 0 && self.bot.astar[ current ] == self.bot.last_next_wp && self.bot.astar[ current - 1 ] == self.bot.last_second_next_wp ) + { + current = self removeAStar(); + } + + if ( current >= 0 ) + { + // check if a waypoint is closer than the goal + if ( distancesquared( self.origin, level.waypoints[ self.bot.astar[ current ] ].origin ) < distancesquared( self.origin, goal ) || distancesquared( level.waypoints[ self.bot.astar[ current ] ].origin, playerphysicstrace( self.origin + ( 0, 0, 32 ), level.waypoints[ self.bot.astar[ current ] ].origin, false, self ) ) > 1.0 ) + { + while ( current >= 0 ) + { + self.bot.next_wp = self.bot.astar[ current ]; + self.bot.second_next_wp = -1; + + if ( current > 0 ) + { + self.bot.second_next_wp = self.bot.astar[ current - 1 ]; + } + + self notify( "new_static_waypoint" ); + + self movetowards( level.waypoints[ self.bot.next_wp ].origin ); + self.bot.last_next_wp = self.bot.next_wp; + self.bot.last_second_next_wp = self.bot.second_next_wp; + + current = self removeAStar(); + } + } + } + + self.bot.next_wp = -1; + self.bot.second_next_wp = -1; + self notify( "finished_static_waypoints" ); + + if ( distancesquared( self.origin, goal ) > dist ) + { + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + self movetowards( goal ); // any better way?? + } + + self notify( "finished_goal" ); + + wait 1; + + if ( distancesquared( self.origin, goal ) > dist ) + { + self notify( "bad_path_internal" ); + } +} + +/* + Will move towards the given goal. Will try to not get stuck by crouching, then jumping and then strafing around objects. +*/ +movetowards( goal ) +{ + if ( !isdefined( goal ) ) + { + return; + } + + self.bot.towards_goal = goal; + + lastOri = self.origin; + stucks = 0; + timeslow = 0; + time = 0; + + if ( self.bot.issprinting ) + { + tempGoalDist = level.bots_goaldistance * 2; + } + else + { + tempGoalDist = level.bots_goaldistance; + } + + while ( distancesquared( self.origin, goal ) > tempGoalDist ) + { + self botSetMoveTo( goal ); + + if ( time > 3000 ) + { + time = 0; + + if ( distancesquared( self.origin, lastOri ) < 32 * 32 ) + { + self thread knife(); + wait 0.5; + + stucks++; + + randomDir = self getRandomLargestStafe( stucks ); + + self BotNotifyBotEvent( "stuck" ); + + self botSetMoveTo( randomDir ); + wait stucks; + self stand(); + + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + } + + lastOri = self.origin; + } + else if ( timeslow > 0 && ( timeslow % 1000 ) == 0 ) + { + self thread doMantle(); + } + else if ( time == 2000 ) + { + if ( distancesquared( self.origin, lastOri ) < 32 * 32 ) + { + self crouch(); + } + } + else if ( time == 1750 ) + { + if ( distancesquared( self.origin, lastOri ) < 32 * 32 ) + { + // check if directly above or below + if ( abs( goal[ 2 ] - self.origin[ 2 ] ) > 64 && getConeDot( goal + ( 1, 1, 0 ), self.origin + ( -1, -1, 0 ), vectortoangles( ( goal[ 0 ], goal[ 1 ], self.origin[ 2 ] ) - self.origin ) ) < 0.64 && distancesquared2D( self.origin, goal ) < 32 * 32 ) + { + stucks = 2; + } + } + } + + wait 0.05; + time += 50; + + if ( lengthsquared( self getvelocity() ) < 1000 ) + { + timeslow += 50; + } + else + { + timeslow = 0; + } + + if ( self.bot.issprinting ) + { + tempGoalDist = level.bots_goaldistance * 2; + } + else + { + tempGoalDist = level.bots_goaldistance; + } + + if ( stucks >= 2 ) + { + self notify( "bad_path_internal" ); + } + } + + self.bot.towards_goal = undefined; + self notify( "completed_move_to" ); +} + +/* + Bots do the mantle +*/ +doMantle() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "kill_goal" ); + + self jump(); + + wait 0.35; + + self jump(); +} + +/* + Will return the pos of the largest trace from the bot. +*/ +getRandomLargestStafe( dist ) +{ + // find a better algo? + traces = NewHeap( ::HeapTraceFraction ); + myOrg = self.origin + ( 0, 0, 16 ); + + traces HeapInsert( bullettrace( myOrg, myOrg + ( -100 * dist, 0, 0 ), false, self ) ); + traces HeapInsert( bullettrace( myOrg, myOrg + ( 100 * dist, 0, 0 ), false, self ) ); + traces HeapInsert( bullettrace( myOrg, myOrg + ( 0, 100 * dist, 0 ), false, self ) ); + traces HeapInsert( bullettrace( myOrg, myOrg + ( 0, -100 * dist, 0 ), false, self ) ); + traces HeapInsert( bullettrace( myOrg, myOrg + ( -100 * dist, -100 * dist, 0 ), false, self ) ); + traces HeapInsert( bullettrace( myOrg, myOrg + ( -100 * dist, 100 * dist, 0 ), false, self ) ); + traces HeapInsert( bullettrace( myOrg, myOrg + ( 100 * dist, -100 * dist, 0 ), false, self ) ); + traces HeapInsert( bullettrace( myOrg, myOrg + ( 100 * dist, 100 * dist, 0 ), false, self ) ); + + toptraces = []; + + top = traces.data[ 0 ]; + toptraces[ toptraces.size ] = top; + traces HeapRemove(); + + while ( traces.data.size && top[ "fraction" ] - traces.data[ 0 ][ "fraction" ] < 0.1 ) + { + toptraces[ toptraces.size ] = traces.data[ 0 ]; + traces HeapRemove(); + } + + return toptraces[ randomint( toptraces.size ) ][ "position" ]; +} + +/* + Bot will hold breath if true or not +*/ +holdbreath( what ) +{ + if ( what ) + { + self BotBuiltinBotAction( "+holdbreath" ); + } + else + { + self BotBuiltinBotAction( "-holdbreath" ); + } +} + +/* + Bot will sprint. +*/ +sprint() +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_sprint" ); + self endon( "bot_sprint" ); + + self BotBuiltinBotAction( "+sprint" ); + wait 0.05; + self BotBuiltinBotAction( "-sprint" ); +} + +/* + Performs melee target +*/ +do_knife_target( target ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "bot_knife" ); + + if ( !getdvarint( "aim_automelee_enabled" ) || !self isonground() || self getstance() == "prone" || self inLastStand() ) + { + self.bot.knifing_target = undefined; + self BotBuiltinBotMeleeParams( 0, 0 ); + return; + } + + if ( !isdefined( target ) || !isplayer( target ) ) + { + self.bot.knifing_target = undefined; + self BotBuiltinBotMeleeParams( 0, 0 ); + return; + } + + dist = distance( target.origin, self.origin ); + + if ( !self _hasperk( "specialty_extendedmelee" ) && dist > getdvarfloat( "aim_automelee_range" ) ) + { + self.bot.knifing_target = undefined; + self BotBuiltinBotMeleeParams( 0, 0 ); + return; + } + + self.bot.knifing_target = target; + + angles = vectortoangles( target.origin - self.origin ); + self BotBuiltinBotMeleeParams( angles[ 1 ], dist ); + + wait 1; + + self.bot.knifing_target = undefined; + self BotBuiltinBotMeleeParams( 0, 0 ); +} + +/* + Bot will knife. +*/ +knife( target ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_knife" ); + self endon( "bot_knife" ); + + self thread do_knife_target( target ); + + self.bot.isknifing = true; + self.bot.isknifingafter = true; + + self BotBuiltinBotAction( "+melee" ); + wait 0.05; + self BotBuiltinBotAction( "-melee" ); + + self.bot.isknifing = false; + + wait 1; + + self.bot.isknifingafter = false; +} + +/* + Bot will reload. +*/ +reload() +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_reload" ); + self endon( "bot_reload" ); + + self BotBuiltinBotAction( "+reload" ); + wait 0.05; + self BotBuiltinBotAction( "-reload" ); +} + +/* + Bot will hold the frag button for a time +*/ +frag( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_frag" ); + self endon( "bot_frag" ); + + if ( !isdefined( time ) ) + { + time = 0.05; + } + + self BotBuiltinBotAction( "+frag" ); + self.bot.isfragging = true; + self.bot.isfraggingafter = true; + + if ( time ) + { + wait time; + } + + self BotBuiltinBotAction( "-frag" ); + self.bot.isfragging = false; + + wait 1.25; + self.bot.isfraggingafter = false; +} + +/* + Bot will hold the 'smoke' button for a time. +*/ +smoke( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_smoke" ); + self endon( "bot_smoke" ); + + if ( !isdefined( time ) ) + { + time = 0.05; + } + + self BotBuiltinBotAction( "+smoke" ); + self.bot.issmoking = true; + self.bot.issmokingafter = true; + + if ( time ) + { + wait time; + } + + self BotBuiltinBotAction( "-smoke" ); + self.bot.issmoking = false; + + wait 1.25; + self.bot.issmokingafter = false; +} + +/* + Bot will press use for a time. +*/ +use( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_use" ); + self endon( "bot_use" ); + + if ( !isdefined( time ) ) + { + time = 0.05; + } + + self BotBuiltinBotAction( "+activate" ); + + if ( time ) + { + wait time; + } + + self BotBuiltinBotAction( "-activate" ); +} + +/* + Bot will fire if true or not. +*/ +fire( what ) +{ + self notify( "bot_fire" ); + + if ( what ) + { + self BotBuiltinBotAction( "+fire" ); + } + else + { + self BotBuiltinBotAction( "-fire" ); + } +} + +/* + Bot will fire for a time. +*/ +pressFire( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_fire" ); + self endon( "bot_fire" ); + + if ( !isdefined( time ) ) + { + time = 0.05; + } + + self BotBuiltinBotAction( "+fire" ); + + if ( time ) + { + wait time; + } + + self BotBuiltinBotAction( "-fire" ); +} + +/* + Bot will ads if true or not. +*/ +ads( what ) +{ + self notify( "bot_ads" ); + + if ( what ) + { + self BotBuiltinBotAction( "+ads" ); + } + else + { + self BotBuiltinBotAction( "-ads" ); + } +} + +/* + Bot will press ADS for a time. +*/ +pressADS( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_ads" ); + self endon( "bot_ads" ); + + if ( !isdefined( time ) ) + { + time = 0.05; + } + + self BotBuiltinBotAction( "+ads" ); + + if ( time ) + { + wait time; + } + + self BotBuiltinBotAction( "-ads" ); +} + +/* + Bot will jump. +*/ +jump() +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_jump" ); + self endon( "bot_jump" ); + + if ( self isusingremote() ) + { + return; + } + + if ( self getstance() != "stand" ) + { + self stand(); + wait 1; + } + + self BotBuiltinBotAction( "+gostand" ); + wait 0.05; + self BotBuiltinBotAction( "-gostand" ); +} + +/* + Bot will stand. +*/ +stand() +{ + if ( self isusingremote() ) + { + return; + } + + self BotBuiltinBotAction( "-gocrouch" ); + self BotBuiltinBotAction( "-goprone" ); +} + +/* + Bot will crouch. +*/ +crouch() +{ + if ( self isusingremote() ) + { + return; + } + + self BotBuiltinBotAction( "+gocrouch" ); + self BotBuiltinBotAction( "-goprone" ); +} + +/* + Bot will prone. +*/ +prone() +{ + if ( self isusingremote() || self.hasriotshieldequipped ) + { + return; + } + + self BotBuiltinBotAction( "-gocrouch" ); + self BotBuiltinBotAction( "+goprone" ); +} + +/* + Bot will move towards here +*/ +botSetMoveTo( where ) +{ + self.bot.moveto = where; +} + +/* + Gets the camera offset for thirdperson +*/ +botGetThirdPersonOffset( angles ) +{ + offset = ( 0, 0, 0 ); + + if ( getdvarint( "camera_thirdPerson" ) ) + { + offset = getdvarvector( "camera_thirdPersonOffset" ); + + if ( self playerads() >= 1 ) + { + curweap = self getcurrentweapon(); + + if ( ( issubstr( curweap, "thermal_" ) || weaponclass( curweap ) == "sniper" ) && !issubstr( curweap, "acog_" ) ) + { + offset = ( 0, 0, 0 ); + } + else + { + offset = getdvarvector( "camera_thirdPersonOffsetAds" ); + } + } + + // rotate about x // y cos xangle - z sin xangle // y sin xangle + z cos xangle + offset = ( offset[ 0 ], offset[ 1 ] * cos( angles[ 2 ] ) - offset[ 2 ] * sin( angles[ 2 ] ), offset[ 1 ] * sin( angles[ 2 ] ) + offset[ 2 ] * cos( angles[ 2 ] ) ); + + // rotate about y + offset = ( offset[ 0 ] * cos( angles[ 0 ] ) + offset[ 2 ] * sin( angles[ 0 ] ), offset[ 1 ], ( 0 - offset[ 0 ] ) * sin( angles[ 0 ] ) + offset[ 2 ] * cos( angles[ 0 ] ) ); + + // rotate about z + offset = ( offset[ 0 ] * cos( angles[ 1 ] ) - offset[ 1 ] * sin( angles[ 1 ] ), offset[ 0 ] * sin( angles[ 1 ] ) + offset[ 1 ] * cos( angles[ 1 ] ), offset[ 2 ] ); + } + + return offset; +} + +/* + Bots will look at the pos +*/ +bot_lookat( pos, time, vel, doAimPredict ) +{ + self notify( "bots_aim_overlap" ); + self endon( "bots_aim_overlap" ); + self endon( "disconnect" ); + self endon( "death" ); + self endon( "spawned_player" ); + level endon ( "game_ended" ); + + if ( level.gameended || !gameflag( "prematch_done" ) || self.bot.isfrozen || !getdvarint( "bots_play_aim" ) ) + { + return; + } + + if ( !isdefined( pos ) ) + { + return; + } + + if ( !isdefined( doAimPredict ) ) + { + doAimPredict = false; + } + + if ( !isdefined( time ) ) + { + time = 0.05; + } + + if ( !isdefined( vel ) ) + { + vel = ( 0, 0, 0 ); + } + + steps = int( time * 20 ); + + if ( steps < 1 ) + { + steps = 1; + } + + myAngle = self getplayerangles(); + + myEye = self geteye(); // get our eye pos + myEye += self botGetThirdPersonOffset( myAngle ); // account for third person + + if ( doAimPredict ) + { + myEye += ( self getvelocity() * 0.05 ) * ( steps - 1 ); // account for our velocity + + pos += ( vel * 0.05 ) * ( steps - 1 ); // add the velocity vector + } + + angles = vectortoangles( ( pos - myEye ) - anglestoforward( myAngle ) ); + + X = angleclamp180( angles[ 0 ] - myAngle[ 0 ] ); + X = X / steps; + + Y = angleclamp180( angles[ 1 ] - myAngle[ 1 ] ); + Y = Y / steps; + + for ( i = 0; i < steps; i++ ) + { + myAngle = ( angleclamp180( myAngle[ 0 ] + X ), angleclamp180( myAngle[ 1 ] + Y ), 0 ); + self BotBuiltinBotAngles( myAngle ); + wait 0.05; + } +} diff --git a/maps/mp/bots/_bot_script.gsc b/maps/mp/bots/_bot_script.gsc new file mode 100644 index 0000000..08ee324 --- /dev/null +++ b/maps/mp/bots/_bot_script.gsc @@ -0,0 +1,8689 @@ +/* + _bot_script + Author: INeedGames + Date: 09/26/2020 + Tells the bots what to do. + Similar to t5's _bot +*/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\bots\_bot_utility; + +/* + When the bot gets added into the game. +*/ +added() +{ + self endon( "disconnect" ); + + if ( !getdvarint( "developer_script" ) ) + { + self setplayerdata( "experience", self bot_get_rank() ); + self setplayerdata( "prestige", self bot_get_prestige() ); + + self setplayerdata( "cardTitle", random( getCardTitles() ) ); + self setplayerdata( "cardIcon", random( getCardIcons() ) ); + } + + self setClasses(); + self setKillstreaks(); + + self set_diff(); +} + +/* + When the bot connects to the game. +*/ +connected() +{ + self endon( "disconnect" ); + + self.killerlocation = undefined; + self.lastkiller = undefined; + self.bot_change_class = true; + + self thread difficulty(); + self thread teamWatch(); + self thread classWatch(); + + self thread onBotSpawned(); + self thread onSpawned(); + + self thread onDeath(); + self thread onGiveLoadout(); + + self thread onKillcam(); + + wait 0.1; + self.challengedata = []; +} + +/* + Gets the prestige +*/ +bot_get_prestige() +{ + p_dvar = getdvarint( "bots_loadout_prestige" ); + p = 0; + + if ( p_dvar == -1 ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( !isdefined( player.team ) ) + { + continue; + } + + if ( player is_bot() ) + { + continue; + } + + p = player getplayerdata( "prestige" ); + break; + } + } + else if ( p_dvar == -2 ) + { + p = randomint( 12 ); + } + else + { + p = p_dvar; + } + + return p; +} + +/* + Gets an exp amount for the bot that is nearish the host's xp. +*/ +bot_get_rank() +{ + rank = 1; + rank_dvar = getdvarint( "bots_loadout_rank" ); + + if ( rank_dvar == -1 ) + { + ranks = []; + bot_ranks = []; + human_ranks = []; + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + if ( !isdefined( player.pers[ "rank" ] ) ) + { + continue; + } + + if ( player is_bot() ) + { + bot_ranks[ bot_ranks.size ] = player.pers[ "rank" ]; + } + else + { + human_ranks[ human_ranks.size ] = player.pers[ "rank" ]; + } + } + + if ( !human_ranks.size ) + { + human_ranks[ human_ranks.size ] = Round( random_normal_distribution( 45, 20, 0, level.maxrank ) ); + } + + human_avg = array_average( human_ranks ); + + while ( bot_ranks.size + human_ranks.size < 5 ) + { + // add some random ranks for better random number distribution + rank = human_avg + randomintrange( -10, 10 ); + human_ranks[ human_ranks.size ] = rank; + } + + ranks = array_combine( human_ranks, bot_ranks ); + + avg = array_average( ranks ); + s = array_std_deviation( ranks, avg ); + + rank = Round( random_normal_distribution( avg, s, 0, level.maxrank ) ); + } + else if ( rank_dvar == 0 ) + { + rank = Round( random_normal_distribution( 45, 20, 0, level.maxrank ) ); + } + else + { + rank = Round( random_normal_distribution( rank_dvar, 5, 0, level.maxrank ) ); + } + + return maps\mp\gametypes\_rank::getrankinfominxp( rank ); +} + +/* + returns an array of all card titles +*/ +getCardTitles() +{ + cards = []; + + for ( i = 0; i < 600; i++ ) + { + card_name = tablelookupbyrow( "mp/cardTitleTable.csv", i, 0 ); + + if ( card_name == "" ) + { + continue; + } + + if ( !issubstr( card_name, "cardtitle_" ) ) + { + continue; + } + + cards[ cards.size ] = card_name; + } + + return cards; +} + +/* + returns an array of all card icons +*/ +getCardIcons() +{ + cards = []; + + for ( i = 0; i < 300; i++ ) + { + card_name = tablelookupbyrow( "mp/cardIconTable.csv", i, 0 ); + + if ( card_name == "" ) + { + continue; + } + + if ( !issubstr( card_name, "cardicon_" ) ) + { + continue; + } + + cards[ cards.size ] = card_name; + } + + return cards; +} + +/* + returns if attachment is valid with attachment 2 +*/ +isValidAttachmentCombo( att1, att2 ) +{ + colIndex = tablelookuprownum( "mp/attachmentCombos.csv", 0, att1 ); + + if ( tablelookup( "mp/attachmentCombos.csv", 0, att2, colIndex ) == "no" ) + { + return false; + } + + return true; +} + +/* + returns all attachments for the given gun +*/ +getAttachmentsForGun( gun ) +{ + row = tablelookuprownum( "mp/statStable.csv", 4, gun ); + + attachments = []; + + for ( h = 0; h < 10; h++ ) + { + attachmentName = tablelookupbyrow( "mp/statStable.csv", row, h + 11 ); + + if ( attachmentName == "" ) + { + attachments[ attachments.size ] = "none"; + break; + } + + attachments[ attachments.size ] = attachmentName; + } + + return attachments; +} + +/* + returns all primaries +*/ +getPrimaries() +{ + primaries = []; + + for ( i = 0; i < 160; i++ ) + { + weapon_type = tablelookupbyrow( "mp/statstable.csv", i, 2 ); + + if ( weapon_type != "weapon_assault" && weapon_type != "weapon_riot" && weapon_type != "weapon_smg" && weapon_type != "weapon_sniper" && weapon_type != "weapon_lmg" ) + { + continue; + } + + weapon_name = tablelookupbyrow( "mp/statstable.csv", i, 4 ); + + primaries[ primaries.size ] = weapon_name; + } + + return primaries; +} + +/* + returns all secondaries +*/ +getSecondaries() +{ + secondaries = []; + + for ( i = 0; i < 160; i++ ) + { + weapon_type = tablelookupbyrow( "mp/statstable.csv", i, 2 ); + + if ( weapon_type != "weapon_pistol" && weapon_type != "weapon_machine_pistol" && weapon_type != "weapon_projectile" && weapon_type != "weapon_shotgun" ) + { + continue; + } + + weapon_name = tablelookupbyrow( "mp/statstable.csv", i, 4 ); + + if ( weapon_name == "gl" ) + { + continue; + } + + secondaries[ secondaries.size ] = weapon_name; + } + + return secondaries; +} + +/* + returns all camos +*/ +getCamos() +{ + camos = []; + + for ( i = 0; i < 15; i++ ) + { + camo_name = tablelookupbyrow( "mp/camoTable.csv", i, 1 ); + + if ( camo_name == "" ) + { + continue; + } + + camos[ camos.size ] = camo_name; + } + + return camos; +} + +/* + returns all perks for the given type +*/ +getPerks( perktype ) +{ + perks = []; + + for ( i = 0; i < 50; i++ ) + { + perk_type = tablelookupbyrow( "mp/perktable.csv", i, 5 ); + + if ( perk_type != perktype ) + { + continue; + } + + perk_name = tablelookupbyrow( "mp/perktable.csv", i, 1 ); + + if ( perk_name == "specialty_c4death" ) + { + continue; + } + + if ( perk_name == "_specialty_blastshield" ) + { + continue; + } + + perks[ perks.size ] = perk_name; + } + + return perks; +} + +/* + returns kill cost for a streak +*/ +getKillsNeededForStreak( streak ) +{ + return int( tablelookup( "mp/killstreakTable.csv", 1, streak, 4 ) ); +} + +/* + returns all killstreaks +*/ +getKillstreaks() +{ + killstreaks = []; + + for ( i = 0; i < 40; i++ ) + { + streak_name = tablelookupbyrow( "mp/killstreakTable.csv", i, 1 ); + + if ( streak_name == "" || streak_name == "none" ) + { + continue; + } + + if ( streak_name == "b1" ) + { + continue; + } + + if ( streak_name == "sentry" ) // theres an airdrop version + { + continue; + } + + if ( issubstr( streak_name, "KILLSTREAKS_" ) ) + { + continue; + } + + killstreaks[ killstreaks.size ] = streak_name; + } + + return killstreaks; +} + +/* + bots chooses a random perk +*/ +chooseRandomPerk( perkkind, primary, primaryAtts ) +{ + perks = getPerks( perkkind ); + rank = self maps\mp\gametypes\_rank::getrankforxp( self getplayerdata( "experience" ) ); + allowOp = ( getdvarint( "bots_loadout_allow_op" ) >= 1 ); + reasonable = getdvarint( "bots_loadout_reasonable" ); + + while ( true ) + { + perk = random( perks ); + + if ( !allowOp ) + { + if ( perkkind == "perk4" ) + { + return "specialty_null"; + } + + if ( perk == "specialty_pistoldeath" ) + { + continue; + } + + if ( perk == "specialty_coldblooded" ) + { + continue; + } + + if ( perk == "specialty_localjammer" ) + { + continue; + } + } + + if ( reasonable ) + { + if ( perk == "specialty_bling" ) + { + continue; + } + + if ( perk == "specialty_localjammer" ) + { + continue; + } + + if ( perk == "throwingknife_mp" ) + { + continue; + } + + if ( perk == "specialty_blastshield" ) + { + continue; + } + + if ( perk == "frag_grenade_mp" ) + { + continue; + } + + if ( perk == "specialty_copycat" ) + { + continue; + } + + if ( perkkind == "perk1" ) + { + if ( perk == "specialty_onemanarmy" ) + { + if ( primaryAtts[ 0 ] != "gl"/* && primaryAtts[ 1 ] != "gl"*/ ) + { + continue; + } + } + } + + if ( perkkind == "perk2" ) + { + if ( perk != "specialty_bulletdamage" ) + { + if ( perk == "specialty_explosivedamage" ) + { + if ( primaryAtts[ 0 ] != "gl"/* && primaryAtts[ 1 ] != "gl"*/ ) + { + continue; + } + } + else + { + if ( randomint( 100 ) < 10 ) + { + continue; + } + + if ( primary == "cheytac" ) + { + continue; + } + + if ( primary == "rpd" ) + { + continue; + } + + if ( primary == "ak47" && randomint( 100 ) < 80 ) + { + continue; + } + + if ( primary == "aug" ) + { + continue; + } + + if ( primary == "barrett" && randomint( 100 ) < 80 ) + { + continue; + } + + if ( primary == "tavor" && randomint( 100 ) < 80 ) + { + continue; + } + + if ( primary == "scar" ) + { + continue; + } + + if ( primary == "masada" && randomint( 100 ) < 60 ) + { + continue; + } + + if ( primary == "m4" && randomint( 100 ) < 80 ) + { + continue; + } + + if ( primary == "m16" ) + { + continue; + } + + if ( primary == "fal" ) + { + continue; + } + + if ( primary == "famas" ) + { + continue; + } + } + } + } + } + + if ( perk == "specialty_null" ) + { + continue; + } + + if ( !self isitemunlocked( perk ) ) + { + continue; + } + + if ( randomfloatrange( 0, 1 ) < ( ( rank / level.maxrank ) + 0.1 ) ) + { + self.pers[ "bots" ][ "unlocks" ][ "upgraded_" + perk ] = true; + } + + return perk; + } +} + +/* + choose a random camo +*/ +chooseRandomCamo() +{ + camos = getCamos(); + + while ( true ) + { + camo = random( camos ); + + if ( camo == "gold" || camo == "prestige" ) + { + continue; + } + + return camo; + } +} + +/* + choose a random primary +*/ +chooseRandomPrimary() +{ + primaries = getPrimaries(); + allowOp = ( getdvarint( "bots_loadout_allow_op" ) >= 1 ); + reasonable = getdvarint( "bots_loadout_reasonable" ); + + while (true) + { + primary = random(primaries); + + if (!allowOp) + { + if (primary == "riotshield") + continue; + } + + if (reasonable) + { + if (primary == "riotshield") + continue; + if (primary == "wa2000") + continue; + if (primary == "uzi") + continue; + if (primary == "sa80") + continue; + if (primary == "fn2000") + continue; + if (primary == "m240") + continue; + if (primary == "mg4") + continue; + if (primary == "ak74u") + continue; + if (primary == "peacekeeper") + continue; + if (primary == "m40a3") + continue; + if (primary == "ak47classic") + continue; + if (primary == "dragunov") + continue; + } + + if (!self isItemUnlocked(primary)) + continue; + + return primary; + } +} + +/* + choose a random secondary +*/ +chooseRandomSecondary( perk1 ) +{ + if ( perk1 == "specialty_onemanarmy" ) + { + return "onemanarmy"; + } + + secondaries = getSecondaries(); + allowOp = ( getdvarint( "bots_loadout_allow_op" ) >= 1 ); + reasonable = getdvarint( "bots_loadout_reasonable" ); + + while ( true ) + { + secondary = random( secondaries ); + + if ( !allowOp ) + { + if ( secondary == "at4" || secondary == "rpg" || secondary == "m79" ) + { + continue; + } + } + + if ( reasonable ) + { + if ( secondary == "ranger" ) + { + continue; + } + + if ( secondary == "model1887" ) + { + continue; + } + } + + if ( !self isitemunlocked( secondary ) ) + { + continue; + } + + if ( secondary == "onemanarmy" ) + { + continue; + } + + return secondary; + } +} + +/* + chooses random attachements for a gun +*/ +chooseRandomAttachmentComboForGun( gun ) +{ + atts = getAttachmentsForGun( gun ); + rank = self maps\mp\gametypes\_rank::getrankforxp( self getplayerdata( "experience" ) ); + allowOp = ( getdvarint( "bots_loadout_allow_op" ) >= 1 ); + reasonable = getdvarint( "bots_loadout_reasonable" ); + + if ( randomfloatrange( 0, 1 ) >= ( ( rank / level.maxrank ) + 0.1 ) ) + { + retAtts = []; + retAtts[ 0 ] = "none"; + retAtts[ 1 ] = "none"; + + return retAtts; + } + + while ( true ) + { + att1 = random( atts ); + att2 = random( atts ); + + if ( !isValidAttachmentCombo( att1, att2 ) ) + { + continue; + } + + if ( !allowOp ) + { + if ( att1 == "gl" || att2 == "gl" ) + { + continue; + } + } + + if ( reasonable ) + { + if ( att1 == "shotgun" || att2 == "shotgun" ) + { + continue; + } + + if ( att1 == "akimbo" || att2 == "akimbo" ) + { + if ( gun != "ranger" && gun != "model1887" && gun != "glock" ) + { + continue; + } + } + + if ( att1 == "acog" || att2 == "acog" ) + { + continue; + } + + if ( att1 == "thermal" || att2 == "thermal" ) + { + continue; + } + + if ( att1 == "rof" || att2 == "rof" ) + { + continue; + } + + if ( att1 == "silencer" || att2 == "silencer" ) + { + if ( gun == "spas12" || gun == "aa12" || gun == "striker" || gun == "rpd" || gun == "m1014" || gun == "cheytac" || gun == "barrett" || gun == "aug" || gun == "m240" || gun == "mg4" || gun == "sa80" || gun == "wa2000" ) + { + continue; + } + } + } + + retAtts = []; + retAtts[ 0 ] = att1; + retAtts[ 1 ] = att2; + + return retAtts; + } +} + +/* + choose a random tacticle grenade +*/ +chooseRandomTactical() +{ + tacts = strtok( "flash_grenade,smoke_grenade,concussion_grenade", "," ); + reasonable = getdvarint( "bots_loadout_reasonable" ); + + while ( true ) + { + tact = random( tacts ); + + if ( reasonable ) + { + if ( tact == "smoke_grenade" ) + { + continue; + } + } + + return tact; + } +} + +/* + sets up all classes for a bot +*/ +setClasses() +{ + n = 5; + + if ( !self is_bot() ) + { + n = 15; + } + + rank = self maps\mp\gametypes\_rank::getrankforxp( self getplayerdata( "experience" ) ); + + if ( randomfloatrange( 0, 1 ) < ( ( rank / level.maxrank ) + 0.1 ) ) + { + self.pers[ "bots" ][ "unlocks" ][ "ghillie" ] = true; + self.pers[ "bots" ][ "behavior" ][ "quickscope" ] = true; + } + + for ( i = 0; i < n; i++ ) + { + equipment = chooseRandomPerk( "equipment" ); + perk3 = chooseRandomPerk( "perk3" ); + deathstreak = chooseRandomPerk( "perk4" ); + tactical = chooseRandomTactical(); + primary = chooseRandomPrimary(); + primaryAtts = chooseRandomAttachmentComboForGun( primary ); + perk1 = chooseRandomPerk( "perk1", primary, primaryAtts ); + + if ( perk1 != "specialty_bling" ) + { + primaryAtts[ 1 ] = "none"; + } + + perk2 = chooseRandomPerk( "perk2", primary, primaryAtts ); + primaryCamo = chooseRandomCamo(); + secondary = chooseRandomSecondary( perk1 ); + secondaryAtts = chooseRandomAttachmentComboForGun( secondary ); + + if ( perk1 != "specialty_bling" || !isdefined( self.pers[ "bots" ][ "unlocks" ][ "upgraded_specialty_bling" ] ) ) + { + secondaryAtts[ 1 ] = "none"; + } + + if ( !getdvarint( "developer_script" ) ) + { + self setplayerdata( "customClasses", i, "weaponSetups", 0, "weapon", primary ); + self setplayerdata( "customClasses", i, "weaponSetups", 0, "attachment", 0, primaryAtts[ 0 ] ); + self setplayerdata( "customClasses", i, "weaponSetups", 0, "attachment", 1, primaryAtts[ 1 ] ); + self setplayerdata( "customClasses", i, "weaponSetups", 0, "camo", primaryCamo ); + + self setplayerdata( "customClasses", i, "weaponSetups", 1, "weapon", secondary ); + self setplayerdata( "customClasses", i, "weaponSetups", 1, "attachment", 0, secondaryAtts[ 0 ] ); + self setplayerdata( "customClasses", i, "weaponSetups", 1, "attachment", 1, secondaryAtts[ 1 ] ); + + self setplayerdata( "customClasses", i, "perks", 0, equipment ); + self setplayerdata( "customClasses", i, "perks", 1, perk1 ); + self setplayerdata( "customClasses", i, "perks", 2, perk2 ); + self setplayerdata( "customClasses", i, "perks", 3, perk3 ); + self setplayerdata( "customClasses", i, "perks", 4, deathstreak ); + self setplayerdata( "customClasses", i, "specialGrenade", tactical ); + } + } +} + +/* + returns if killstreak is going to have the same kill cost +*/ +isColidingKillstreak( killstreaks, killstreak ) +{ + ksVal = getKillsNeededForStreak( killstreak ); + + for ( i = 0; i < killstreaks.size; i++ ) + { + ks = killstreaks[ i ]; + + if ( ks == "" ) + { + continue; + } + + if ( ks == "none" ) + { + continue; + } + + ksV = getKillsNeededForStreak( ks ); + + if ( ksV <= 0 ) + { + continue; + } + + if ( ksV != ksVal ) + { + continue; + } + + return true; + } + + return false; +} + +/* + bots set their killstreaks +*/ +setKillstreaks() +{ + rankId = self maps\mp\gametypes\_rank::getrankforxp( self getplayerdata( "experience" ) ) + 1; + + allStreaks = getKillstreaks(); + + killstreaks = []; + killstreaks[ 0 ] = ""; + killstreaks[ 1 ] = ""; + killstreaks[ 2 ] = ""; + + chooseableStreaks = 0; + + if ( rankId >= 10 ) + { + chooseableStreaks++; + } + + if ( rankId >= 15 ) + { + chooseableStreaks++; + } + + if ( rankId >= 22 ) + { + chooseableStreaks++; + } + + reasonable = getdvarint( "bots_loadout_reasonable" ); + op = getdvarint( "bots_loadout_allow_op" ); + + i = 0; + + while ( i < chooseableStreaks ) + { + slot = randomint( 3 ); + + if ( killstreaks[ slot ] != "" ) + { + continue; + } + + streak = random( allStreaks ); + + if ( isColidingKillstreak( killstreaks, streak ) ) + { + continue; + } + + if ( reasonable ) + { + if ( streak == "stealth_airstrike" ) + { + continue; + } + + if ( streak == "airdrop_mega" ) + { + continue; + } + + if ( streak == "emp" ) + { + continue; + } + + if ( streak == "airdrop_sentry_minigun" ) + { + continue; + } + + if ( streak == "airdrop" ) + { + continue; + } + + if ( streak == "precision_airstrike" ) + { + continue; + } + + if ( streak == "helicopter" ) + { + continue; + } + } + + if ( op ) + { + if ( streak == "nuke" ) + { + continue; + } + } + + killstreaks[ slot ] = streak; + i++; + } + + if ( killstreaks[ 0 ] == "" ) + { + killstreaks[ 0 ] = "uav"; + } + + if ( killstreaks[ 1 ] == "" ) + { + killstreaks[ 1 ] = "airdrop"; + } + + if ( killstreaks[ 2 ] == "" ) + { + killstreaks[ 2 ] = "predator_missile"; + } + + if ( !getdvarint( "developer_script" ) ) + { + self setplayerdata( "killstreaks", 0, killstreaks[ 0 ] ); + self setplayerdata( "killstreaks", 1, killstreaks[ 1 ] ); + self setplayerdata( "killstreaks", 2, killstreaks[ 2 ] ); + } +} + +/* + The callback for when the bot gets killed. +*/ +onKilled( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration ) +{ + self.killerlocation = undefined; + self.lastkiller = undefined; + + if ( !isdefined( self ) || !isdefined( self.team ) ) + { + return; + } + + if ( sMeansOfDeath == "MOD_FALLING" || sMeansOfDeath == "MOD_SUICIDE" ) + { + return; + } + + if ( iDamage <= 0 ) + { + return; + } + + if ( !isdefined( eAttacker ) || !isdefined( eAttacker.team ) ) + { + return; + } + + if ( eAttacker == self ) + { + return; + } + + if ( level.teambased && eAttacker.team == self.team ) + { + return; + } + + if ( !isdefined( eInflictor ) || eInflictor.classname != "player" ) + { + return; + } + + if ( !isalive( eAttacker ) ) + { + return; + } + + self.killerlocation = eAttacker.origin; + self.lastkiller = eAttacker; +} + +/* + The callback for when the bot gets damaged. +*/ +onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset ) +{ + if ( !isdefined( self ) || !isdefined( self.team ) ) + { + return; + } + + if ( !isalive( self ) ) + { + return; + } + + if ( sMeansOfDeath == "MOD_FALLING" || sMeansOfDeath == "MOD_SUICIDE" ) + { + return; + } + + if ( iDamage <= 0 ) + { + return; + } + + if ( !isdefined( eAttacker ) || !isdefined( eAttacker.team ) ) + { + return; + } + + if ( eAttacker == self ) + { + return; + } + + if ( level.teambased && eAttacker.team == self.team ) + { + return; + } + + if ( !isdefined( eInflictor ) || eInflictor.classname != "player" ) + { + return; + } + + if ( !isalive( eAttacker ) ) + { + return; + } + + if ( !issubstr( sWeapon, "_silencer_" ) ) + { + self bot_cry_for_help( eAttacker ); + } + + self setAttacker( eAttacker ); +} + +/* + When the bot gets attacked, have the bot ask for help from teammates. +*/ +bot_cry_for_help( attacker ) +{ + if ( !level.teambased ) + { + return; + } + + theTime = gettime(); + + if ( isdefined( self.help_time ) && theTime - self.help_time < 1000 ) + { + return; + } + + self.help_time = theTime; + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( !player is_bot() ) + { + continue; + } + + if ( !isdefined( player.team ) ) + { + continue; + } + + if ( !isalive( player ) ) + { + continue; + } + + if ( player == self ) + { + continue; + } + + if ( player.team != self.team ) + { + continue; + } + + dist = player.pers[ "bots" ][ "skill" ][ "help_dist" ]; + dist *= dist; + + if ( distancesquared( self.origin, player.origin ) > dist ) + { + continue; + } + + if ( randomint( 100 ) < 50 ) + { + self setAttacker( attacker ); + + if ( randomint( 100 ) > 70 ) + { + break; + } + } + } +} + +/* + watches when the bot enters a killcam +*/ +onKillcam() +{ + level endon( "game_ended" ); + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "begin_killcam" ); + + self thread doKillcamStuff(); + } +} + +/* + bots use copy cat and skip killcams +*/ +doKillcamStuff() +{ + self endon( "disconnect" ); + self endon( "killcam_ended" ); + + self BotNotifyBotEvent( "killcam", "start" ); + + wait 0.5 + randomint( 3 ); + + if ( randomint( 100 ) > 25 ) + { + self notify( "use_copycat" ); + } + + wait 0.1; + + self notify( "abort_killcam" ); + + self BotNotifyBotEvent( "killcam", "stop" ); +} + +/* + Selects a class for the bot. +*/ +classWatch() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + while ( !isdefined( self.pers[ "team" ] ) || !allowClassChoiceUtil() ) + { + wait .05; + } + + wait 0.5; + + if ( !isvalidclass( self.class ) || !isdefined( self.bot_change_class ) ) + { + self notify( "menuresponse", game[ "menu_changeclass" ], self chooseRandomClass() ); + } + + self.bot_change_class = true; + + while ( isdefined( self.pers[ "team" ] ) && isvalidclass( self.class ) && isdefined( self.bot_change_class ) ) + { + wait .05; + } + } +} + +/* + Chooses a random class +*/ +chooseRandomClass() +{ + reasonable = getdvarint( "bots_loadout_reasonable" ); + class = ""; + rank = self maps\mp\gametypes\_rank::getrankforxp( self getplayerdata( "experience" ) ) + 1; + + if ( rank < 4 || ( randomint( 100 ) < 2 && !reasonable ) ) + { + while ( class == "" ) + { + switch ( randomint( 5 ) ) + { + case 0: + class = "class0"; + break; + + case 1: + class = "class1"; + break; + + case 2: + class = "class2"; + break; + + case 3: + if ( rank >= 2 ) + { + class = "class3"; + } + + break; + + case 4: + if ( rank >= 3 ) + { + class = "class4"; + } + + break; + } + } + } + else + { + class = "custom" + ( randomint( 5 ) + 1 ); + } + + return class; +} + +/* + Makes sure the bot is on a team. +*/ +teamWatch() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + while ( !isdefined( self.pers[ "team" ] ) || !allowTeamChoiceUtil() ) + { + wait .05; + } + + wait 0.1; + + if ( self.team != "axis" && self.team != "allies" ) + { + self notify( "menuresponse", game[ "menu_team" ], getdvar( "bots_team" ) ); + } + + while ( isdefined( self.pers[ "team" ] ) ) + { + wait .05; + } + } +} + +/* + Updates the bot's difficulty variables. +*/ +difficulty() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + if ( getdvarint( "bots_skill" ) != 9 ) + { + switch ( self.pers[ "bots" ][ "skill" ][ "base" ] ) + { + case 1: + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.6; + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 1500; + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 1000; + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 500; + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 600; + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 750; + self.pers[ "bots" ][ "skill" ][ "fov" ] = 0.7; + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = 2500; + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = 1000; + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0.75; + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = 0; + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = 0.9; + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = 1; + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = 1.5; + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = 4; + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = 2; + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_spineupper,j_ankle_le,j_ankle_ri"; + self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ] = 0.5; + + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = 0; + self.pers[ "bots" ][ "behavior" ][ "nade" ] = 10; + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = 30; + self.pers[ "bots" ][ "behavior" ][ "camp" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "follow" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = 20; + self.pers[ "bots" ][ "behavior" ][ "switch" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "class" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "jump" ] = 0; + break; + + case 2: + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.55; + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 1000; + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 800; + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 1000; + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 1250; + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 1500; + self.pers[ "bots" ][ "skill" ][ "fov" ] = 0.65; + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = 3000; + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = 1500; + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0.65; + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = 500; + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = 0.75; + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = 0.75; + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = 1; + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = 3; + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = 1.5; + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_spineupper,j_ankle_le,j_ankle_ri,j_head"; + self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ] = 0.5; + + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = 10; + self.pers[ "bots" ][ "behavior" ][ "nade" ] = 15; + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = 45; + self.pers[ "bots" ][ "behavior" ][ "camp" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "follow" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = 15; + self.pers[ "bots" ][ "behavior" ][ "switch" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "class" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "jump" ] = 10; + break; + + case 3: + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.4; + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 750; + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 500; + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 1000; + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 1500; + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 2000; + self.pers[ "bots" ][ "skill" ][ "fov" ] = 0.6; + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = 4000; + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = 2250; + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = 750; + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = 0.65; + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = 0.65; + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = 0.75; + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = 2.5; + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = 1; + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_spineupper,j_spineupper,j_ankle_le,j_ankle_ri,j_head"; + self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ] = 0.5; + + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = 20; + self.pers[ "bots" ][ "behavior" ][ "nade" ] = 20; + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = 50; + self.pers[ "bots" ][ "behavior" ][ "camp" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "follow" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = 10; + self.pers[ "bots" ][ "behavior" ][ "switch" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "class" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "jump" ] = 25; + break; + + case 4: + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.3; + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 600; + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 400; + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 1500; + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 2000; + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 3000; + self.pers[ "bots" ][ "skill" ][ "fov" ] = 0.55; + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = 5000; + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = 3350; + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0.35; + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = 1000; + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = 2; + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = 0.75; + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_spineupper,j_spineupper,j_ankle_le,j_ankle_ri,j_head,j_head"; + self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ] = 0.5; + + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = 30; + self.pers[ "bots" ][ "behavior" ][ "nade" ] = 25; + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = 55; + self.pers[ "bots" ][ "behavior" ][ "camp" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "follow" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = 10; + self.pers[ "bots" ][ "behavior" ][ "switch" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "class" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "jump" ] = 35; + break; + + case 5: + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.25; + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 500; + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 300; + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 2500; + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 3000; + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 4000; + self.pers[ "bots" ][ "skill" ][ "fov" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = 7500; + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = 5000; + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0.25; + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = 1500; + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = 0.4; + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = 0.35; + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = 0.35; + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = 1.5; + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_spineupper,j_head"; + self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ] = 0.5; + + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = 40; + self.pers[ "bots" ][ "behavior" ][ "nade" ] = 35; + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = 60; + self.pers[ "bots" ][ "behavior" ][ "camp" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "follow" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = 10; + self.pers[ "bots" ][ "behavior" ][ "switch" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "class" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "jump" ] = 50; + break; + + case 6: + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.2; + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 250; + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 150; + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 2500; + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 4000; + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 5000; + self.pers[ "bots" ][ "skill" ][ "fov" ] = 0.45; + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = 10000; + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = 7500; + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0.2; + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = 2000; + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = 0.25; + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = 0.25; + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = 0.25; + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = 1; + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = 0.25; + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_spineupper,j_head,j_head"; + self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ] = 0.5; + + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = 50; + self.pers[ "bots" ][ "behavior" ][ "nade" ] = 45; + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = 65; + self.pers[ "bots" ][ "behavior" ][ "camp" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "follow" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = 10; + self.pers[ "bots" ][ "behavior" ][ "switch" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "class" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "jump" ] = 75; + break; + + case 7: + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.1; + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 100; + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 50; + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 2500; + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 4000; + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 7500; + self.pers[ "bots" ][ "skill" ][ "fov" ] = 0.4; + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = 15000; + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = 10000; + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0.05; + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = 3000; + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = 0.1; + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = 0; + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = 0; + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = 0; + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = 0.05; + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_head"; + self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ] = 0.5; + self.pers[ "bots" ][ "skill" ][ "ads_aimspeed_multi" ] = 0.5; + + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = 65; + self.pers[ "bots" ][ "behavior" ][ "nade" ] = 65; + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = 70; + self.pers[ "bots" ][ "behavior" ][ "camp" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "follow" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = 5; + self.pers[ "bots" ][ "behavior" ][ "switch" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "class" ] = 2; + self.pers[ "bots" ][ "behavior" ][ "jump" ] = 90; + break; + } + } + + wait 5; + } +} + +/* + Sets the bot difficulty. +*/ +set_diff() +{ + rankVar = getdvarint( "bots_skill" ); + + switch ( rankVar ) + { + case 0: + self.pers[ "bots" ][ "skill" ][ "base" ] = Round( random_normal_distribution( 3.5, 1.75, 1, 7 ) ); + break; + + case 8: + break; + + case 9: + self.pers[ "bots" ][ "skill" ][ "base" ] = randomintrange( 1, 7 ); + self.pers[ "bots" ][ "skill" ][ "aim_time" ] = 0.05 * randomintrange( 1, 20 ); + self.pers[ "bots" ][ "skill" ][ "init_react_time" ] = 50 * randomint( 100 ); + self.pers[ "bots" ][ "skill" ][ "reaction_time" ] = 50 * randomint( 100 ); + self.pers[ "bots" ][ "skill" ][ "remember_time" ] = 50 * randomint( 100 ); + self.pers[ "bots" ][ "skill" ][ "no_trace_ads_time" ] = 50 * randomint( 100 ); + self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ] = 50 * randomint( 100 ); + self.pers[ "bots" ][ "skill" ][ "fov" ] = randomfloatrange( -1, 1 ); + + randomNum = randomintrange( 500, 25000 ); + self.pers[ "bots" ][ "skill" ][ "dist_start" ] = randomNum; + self.pers[ "bots" ][ "skill" ][ "dist_max" ] = randomNum * 2; + + self.pers[ "bots" ][ "skill" ][ "spawn_time" ] = 0.05 * randomint( 20 ); + self.pers[ "bots" ][ "skill" ][ "help_dist" ] = randomintrange( 500, 25000 ); + self.pers[ "bots" ][ "skill" ][ "semi_time" ] = randomfloatrange( 0.05, 1 ); + self.pers[ "bots" ][ "skill" ][ "shoot_after_time" ] = randomfloatrange( 0.05, 1 ); + self.pers[ "bots" ][ "skill" ][ "aim_offset_time" ] = randomfloatrange( 0.05, 1 ); + self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ] = randomfloatrange( 0.05, 1 ); + self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ] = randomfloatrange( 0.05, 1 ); + self.pers[ "bots" ][ "skill" ][ "bones" ] = "j_head,j_spineupper,j_ankle_le,j_ankle_ri"; + + self.pers[ "bots" ][ "behavior" ][ "strafe" ] = randomint( 100 ); + self.pers[ "bots" ][ "behavior" ][ "nade" ] = randomint( 100 ); + self.pers[ "bots" ][ "behavior" ][ "sprint" ] = randomint( 100 ); + self.pers[ "bots" ][ "behavior" ][ "camp" ] = randomint( 100 ); + self.pers[ "bots" ][ "behavior" ][ "follow" ] = randomint( 100 ); + self.pers[ "bots" ][ "behavior" ][ "crouch" ] = randomint( 100 ); + self.pers[ "bots" ][ "behavior" ][ "switch" ] = randomint( 100 ); + self.pers[ "bots" ][ "behavior" ][ "class" ] = randomint( 100 ); + self.pers[ "bots" ][ "behavior" ][ "jump" ] = randomint( 100 ); + break; + + default: + self.pers[ "bots" ][ "skill" ][ "base" ] = rankVar; + break; + } +} + +/* + Allows the bot to spawn when force respawn is disabled + Watches when the bot dies +*/ +onDeath() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "death" ); + + self.wantsafespawn = true; + } +} + +/* + Watches when the bot is given a loadout +*/ +onGiveLoadout_loop() +{ + class = self.class; + + if ( isdefined( self.bot_oma_class ) ) + { + class = self.bot_oma_class; + } + + if ( allowClassChoiceUtil() ) + { + self botGiveLoadout( self.team, class, !isdefined( self.bot_oma_class ) ); + } + + self.bot_oma_class = undefined; +} + +/* + Watches when the bot is given a loadout +*/ +onGiveLoadout() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "giveLoadout" ); + + self onGiveLoadout_loop(); + } +} + +/* + When the bot spawns. +*/ +onSpawned() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "spawned_player" ); + + if ( randomint( 100 ) <= self.pers[ "bots" ][ "behavior" ][ "class" ] ) + { + self.bot_change_class = undefined; + } + + self.bot_lock_goal = false; + self.bot_oma_class = undefined; + self.help_time = undefined; + self.bot_was_follow_script_update = undefined; + self.bot_stuck_on_carepackage = undefined; + + if ( getdvarint( "bots_play_obj" ) ) + { + self thread bot_dom_cap_think(); + } + } +} + +/* + When the bot spawned, after the difficulty wait. Start the logic for the bot. +*/ +onBotSpawned() +{ + self endon( "disconnect" ); + level endon( "game_ended" ); + + for ( ;; ) + { + self waittill( "bot_spawned" ); + + self thread start_bot_threads(); + } +} + +/* + Starts all the bot thinking +*/ +start_bot_threads() +{ + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "death" ); + + gameflagwait( "prematch_done" ); + + // inventory usage + if ( getdvarint( "bots_play_killstreak" ) ) + { + self thread bot_killstreak_think(); + } + + self thread bot_weapon_think(); + self thread doReloadCancel(); + self thread bot_perk_think(); + + // script targeting + if ( getdvarint( "bots_play_target_other" ) ) + { + self thread bot_target_vehicle(); + self thread bot_equipment_kill_think(); + self thread bot_turret_think(); + } + + // airdrop + if ( getdvarint( "bots_play_take_carepackages" ) ) + { + self thread bot_watch_stuck_on_crate(); + self thread bot_crate_think(); + } + + // awareness + self thread bot_revenge_think(); + self thread bot_uav_think(); + self thread bot_listen_to_steps(); + self thread follow_target(); + + // camp and follow + if ( getdvarint( "bots_play_camp" ) ) + { + self thread bot_think_follow(); + self thread bot_think_camp(); + } + + // nades + if ( getdvarint( "bots_play_nade" ) ) + { + self thread bot_jav_loc_think(); + self thread bot_use_tube_think(); + self thread bot_use_grenade_think(); + self thread bot_use_equipment_think(); + self thread bot_watch_riot_weapons(); + self thread bot_watch_think_mw2(); // bots play mw2 + } + + // obj + if ( getdvarint( "bots_play_obj" ) ) + { + self thread bot_dom_def_think(); + self thread bot_dom_spawn_kill_think(); + + self thread bot_hq(); + + self thread bot_cap(); + + self thread bot_sab(); + + self thread bot_sd_defenders(); + self thread bot_sd_attackers(); + + self thread bot_dem_attackers(); + self thread bot_dem_defenders(); + + self thread bot_gtnw(); + self thread bot_oneflag(); + self thread bot_arena(); + self thread bot_vip(); + } + + self thread bot_think_revive(); +} + +/* + Increments the number of bots approching the obj, decrements when needed + Used for preventing too many bots going to one obj, or unreachable objs +*/ +bot_inc_bots( obj, unreach ) +{ + level endon( "game_ended" ); + self endon( "bot_inc_bots" ); + + if ( !isdefined( obj ) ) + { + return; + } + + if ( !isdefined( obj.bots ) ) + { + obj.bots = 0; + } + + obj.bots++; + + ret = self waittill_any_return( "death", "disconnect", "bad_path", "goal", "new_goal" ); + + if ( isdefined( obj ) && ( ret != "bad_path" || !isdefined( unreach ) ) ) + { + obj.bots--; + } +} + +/* + Watches when the bot is touching the obj and calls 'goal' +*/ +bots_watch_touch_obj( obj ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "bad_path" ); + self endon ( "goal" ); + self endon ( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( !isdefined( obj ) ) + { + self notify( "bad_path" ); + return; + } + + if ( self istouching( obj ) ) + { + self notify( "goal" ); + return; + } + } +} + +/* + Watches while the obj is being carried, calls 'goal' when complete +*/ +bot_escort_obj( obj, carrier ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( !isdefined( obj ) ) + { + break; + } + + if ( !isdefined( obj.carrier ) || carrier == obj.carrier ) + { + break; + } + } + + self notify( "goal" ); +} + +/* + Watches while the obj is not being carried, calls 'goal' when complete +*/ +bot_get_obj( obj ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( !isdefined( obj ) ) + { + break; + } + + if ( isdefined( obj.carrier ) ) + { + break; + } + } + + self notify( "goal" ); +} + +/* + bots will defend their site from a planter/defuser +*/ +bot_defend_site( site ) +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( !site isInUse() ) + { + break; + } + } + + self notify( "bad_path" ); +} + +/* + Bots will go plant the bomb +*/ +bot_go_plant( plant ) +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 1; + + if ( level.bombplanted ) + { + break; + } + + if ( self istouching( plant.trigger ) ) + { + break; + } + } + + if ( level.bombplanted ) + { + self notify( "bad_path" ); + } + else + { + self notify( "goal" ); + } +} + +/* + Bots will go defuse the bomb +*/ +bot_go_defuse( plant ) +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 1; + + if ( !level.bombplanted ) + { + break; + } + + if ( self istouching( plant.trigger ) ) + { + break; + } + } + + if ( !level.bombplanted ) + { + self notify( "bad_path" ); + } + else + { + self notify( "goal" ); + } +} + +/* + Waits for the bot to stop moving +*/ +bot_wait_stop_move() +{ + while ( !self isonground() || lengthsquared( self getvelocity() ) > 1 ) + { + wait 0.25; + } +} + +/* + Fires the bots weapon until told to stop +*/ +fire_current_weapon() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "weapon_change" ); + self endon( "stop_firing_weapon" ); + + for ( ;; ) + { + self thread BotPressAttack( 0.05 ); + wait 0.1; + } +} + +/* + Changes to the weap +*/ +changeToWeapon( weap ) +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + if ( !self hasweapon( weap ) ) + { + return false; + } + + self switchtoweapon( weap ); + + if ( self getcurrentweapon() == weap ) + { + return true; + } + + self waittill_any_timeout( 5, "weapon_change" ); + + return ( self getcurrentweapon() == weap ); +} + +/* + Bots throw the grenade +*/ +botThrowGrenade( nade, time ) +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + if ( !self getammocount( nade ) ) + { + return false; + } + + if ( isSecondaryGrenade( nade ) ) + { + self thread BotPressSmoke( time ); + } + else + { + self thread BotPressFrag( time ); + } + + ret = self waittill_any_timeout( 5, "grenade_fire" ); + + return ( ret == "grenade_fire" ); +} + +/* + Gets the object thats the closest in the array +*/ +bot_array_nearest_curorigin( array ) +{ + result = undefined; + + for ( i = 0; i < array.size; i++ ) + { + if ( !isdefined( result ) || distancesquared( self.origin, array[ i ].curorigin ) < distancesquared( self.origin, result.curorigin ) ) + { + result = array[ i ]; + } + } + + return result; +} + +/* + Returns an weapon thats a rocket with ammo +*/ +getRocketAmmo() +{ + answer = self getLockonAmmo(); + + if ( isdefined( answer ) ) + { + return answer; + } + + if ( self getammocount( "rpg_mp" ) ) + { + answer = "rpg_mp"; + } + + return answer; +} + +/* + Returns a weapon thats lockon with ammo +*/ +getLockonAmmo() +{ + answer = undefined; + + if ( self getammocount( "at4_mp" ) ) + { + answer = "at4_mp"; + } + + if ( self getammocount( "stinger_mp" ) ) + { + answer = "stinger_mp"; + } + + if ( self getammocount( "javelin_mp" ) ) + { + answer = "javelin_mp"; + } + + return answer; +} + +/* + Clears goal when events death +*/ +stop_go_target_on_death( tar ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "new_goal" ); + self endon( "bad_path" ); + self endon( "goal" ); + + tar waittill_either( "death", "disconnect" ); + + self ClearScriptGoal(); +} + +/* + Goes to the target's location if it had one +*/ +follow_target_loop() +{ + threat = self getThreat(); + + if ( !isplayer( threat ) ) + { + return; + } + + if ( randomint( 100 ) > self.pers[ "bots" ][ "behavior" ][ "follow" ] * 5 ) + { + return; + } + + self BotNotifyBotEvent( "follow_threat", "start", threat ); + + self SetScriptGoal( threat.origin, 64 ); + self thread stop_go_target_on_death( threat ); + + if ( self waittill_any_return( "new_goal", "goal", "bad_path" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self BotNotifyBotEvent( "follow_threat", "stop", threat ); +} + +/* + Goes to the target's location if it had one +*/ +follow_target() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait 1; + + if ( self HasScriptGoal() || self.bot_lock_goal ) + { + continue; + } + + if ( !self hasThreat() ) + { + continue; + } + + self follow_target_loop(); + } +} + +/* + Bot logic for bot determining to camp. +*/ +bot_think_camp_loop() +{ + campSpot = getWaypointForIndex( random( self waypointsNear( getWaypointsOfType( "camp" ), 1024 ) ) ); + + if ( !isdefined( campSpot ) ) + { + return; + } + + time = randomintrange( 30, 90 ); + + self BotNotifyBotEvent( "camp", "go", campSpot, time ); + + self SetScriptGoal( campSpot.origin, 16 ); + + ret = self waittill_any_return( "new_goal", "goal", "bad_path" ); + + if ( ret != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( ret != "goal" ) + { + return; + } + + self BotNotifyBotEvent( "camp", "start", campSpot, time ); + + self thread killCampAfterTime( time ); + self CampAtSpot( campSpot.origin, campSpot.origin + anglestoforward( campSpot.angles ) * 2048 ); + + self BotNotifyBotEvent( "camp", "stop", campSpot, time ); +} + +/* + Bot logic for bot determining to camp. +*/ +bot_think_camp() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait randomintrange( 4, 7 ); + + if ( self HasScriptGoal() || self.bot_lock_goal || self HasScriptAimPos() ) + { + continue; + } + + if ( randomint( 100 ) > self.pers[ "bots" ][ "behavior" ][ "camp" ] ) + { + continue; + } + + self bot_think_camp_loop(); + } +} + +/* + Kills the camping thread when time +*/ +killCampAfterTime( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "kill_camp_bot" ); + + timeleft = maps\mp\gametypes\_gamelogic::gettimeremaining() / 1000; + + while ( time > 0 && timeleft >= 60 ) + { + wait 1; + timeleft = maps\mp\gametypes\_gamelogic::gettimeremaining() / 1000; + time--; + } + + wait 0.05; + + self ClearScriptGoal(); + self ClearScriptAimPos(); + + self notify( "kill_camp_bot" ); +} + +/* + Kills the camping thread when ent gone +*/ +killCampAfterEntGone( ent ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "kill_camp_bot" ); + + for ( ;; ) + { + wait 0.05; + + if ( !isdefined( ent ) ) + { + break; + } + } + + self ClearScriptGoal(); + self ClearScriptAimPos(); + + self notify( "kill_camp_bot" ); +} + +/* + Camps at the spot +*/ +CampAtSpot( origin, anglePos ) +{ + self endon( "kill_camp_bot" ); + + self SetScriptGoal( origin, 64 ); + + if ( isdefined( anglePos ) ) + { + self SetScriptAimPos( anglePos ); + } + + self waittill( "new_goal" ); + self ClearScriptAimPos(); + + self notify( "kill_camp_bot" ); +} + +/* + Bot logic for bot determining to follow another player. +*/ +bot_think_follow_loop() +{ + follows = []; + distSq = self.pers[ "bots" ][ "skill" ][ "help_dist" ] * self.pers[ "bots" ][ "skill" ][ "help_dist" ]; + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + if ( !isreallyalive( player ) ) + { + continue; + } + + if ( player.team != self.team ) + { + continue; + } + + if ( distancesquared( player.origin, self.origin ) > distSq ) + { + continue; + } + + follows[ follows.size ] = player; + } + + toFollow = random( follows ); + + if ( !isdefined( toFollow ) ) + { + return; + } + + time = randomintrange( 10, 20 ); + + self BotNotifyBotEvent( "follow", "start", toFollow, time ); + + self thread killFollowAfterTime( time ); + self followPlayer( toFollow ); + + self BotNotifyBotEvent( "follow", "stop", toFollow, time ); +} + +/* + Bot logic for bot determining to follow another player. +*/ +bot_think_follow() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait randomintrange( 3, 5 ); + + if ( self HasScriptGoal() || self.bot_lock_goal || self HasScriptAimPos() ) + { + continue; + } + + if ( randomint( 100 ) > self.pers[ "bots" ][ "behavior" ][ "follow" ] ) + { + continue; + } + + if ( !level.teambased ) + { + continue; + } + + self bot_think_follow_loop(); + } +} + +/* + Kills follow when new goal +*/ +watchForFollowNewGoal() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "kill_follow_bot" ); + + for ( ;; ) + { + self waittill( "new_goal" ); + + if ( !isdefined( self.bot_was_follow_script_update ) ) + { + break; + } + } + + self ClearScriptAimPos(); + self notify( "kill_follow_bot" ); +} + +/* + Kills follow when time +*/ +killFollowAfterTime( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "kill_follow_bot" ); + + wait time; + + self ClearScriptGoal(); + self ClearScriptAimPos(); + self notify( "kill_follow_bot" ); +} + +/* + Determine bot to follow a player +*/ +followPlayer( who ) +{ + self endon( "kill_follow_bot" ); + + self thread watchForFollowNewGoal(); + + for ( ;; ) + { + wait 0.05; + + if ( !isdefined( who ) || !isreallyalive( who ) ) + { + break; + } + + self SetScriptAimPos( who.origin + ( 0, 0, 42 ) ); + myGoal = self GetScriptGoal(); + + if ( isdefined( myGoal ) && distancesquared( myGoal, who.origin ) < 64 * 64 ) + { + continue; + } + + self.bot_was_follow_script_update = true; + self SetScriptGoal( who.origin, 32 ); + waittillframeend; + self.bot_was_follow_script_update = undefined; + + self waittill_either( "goal", "bad_path" ); + } + + self ClearScriptGoal(); + self ClearScriptAimPos(); + + self notify( "kill_follow_bot" ); +} + +/* + Bots thinking of using one man army and blast shield +*/ +bot_perk_think_loop() +{ + for ( ; self _hasperk( "specialty_blastshield" ); ) + { + if ( !self _hasperk( "_specialty_blastshield" ) ) + { + if ( randomint( 100 ) < 65 ) + { + break; + } + + self _setperk( "_specialty_blastshield" ); + } + else + { + if ( randomint( 100 ) < 90 ) + { + break; + } + + self _unsetperk( "_specialty_blastshield" ); + } + + break; + } + + for ( ; self _hasperk( "specialty_onemanarmy" ) && self hasweapon( "onemanarmy_mp" ); ) + { + if ( self hasThreat() || self HasBotJavelinLocation() ) + { + break; + } + + if ( self inLastStand() && !self inFinalStand() ) + { + break; + } + + anyWeapout = false; + weaponsList = self getweaponslistall(); + + for ( i = 0; i < weaponsList.size; i++ ) + { + weap = weaponsList[ i ]; + + if ( self getammocount( weap ) || weap == "onemanarmy_mp" ) + { + continue; + } + + anyWeapout = true; + } + + if ( ( !anyWeapout && randomint( 100 ) < 90 ) || randomint( 100 ) < 10 ) + { + break; + } + + class = self chooseRandomClass(); + + self.bot_oma_class = class; + + if ( !self changeToWeapon( "onemanarmy_mp" ) ) + { + self.bot_oma_class = undefined; + break; + } + + self BotFreezeControls( true ); + wait 1; + self BotFreezeControls( false ); + + self notify ( "menuresponse", game[ "menu_onemanarmy" ], self.bot_oma_class ); + + self waittill_any_timeout ( 10, "changed_kit" ); + break; + } +} + +/* + Bots thinking of using one man army and blast shield +*/ +bot_perk_think() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + for ( ;; ) + { + wait randomintrange( 5, 7 ); + + if ( self isusingremote() ) + { + continue; + } + + if ( self BotIsFrozen() ) + { + continue; + } + + if ( self isDefusing() || self isPlanting() ) + { + continue; + } + + self bot_perk_think_loop(); + } +} + +/* + Bots thinking of using a noobtube +*/ +bot_use_tube_think_loop( data ) +{ + if ( data.dofastcontinue ) + { + data.dofastcontinue = false; + } + else + { + wait randomintrange( 3, 7 ); + + chance = self.pers[ "bots" ][ "behavior" ][ "nade" ] / 2; + + if ( chance > 20 ) + { + chance = 20; + } + + if ( randomint( 100 ) > chance ) + { + return; + } + } + + tube = self getValidTube(); + + if ( !isdefined( tube ) ) + { + return; + } + + if ( self hasThreat() || self HasBotJavelinLocation() || self HasScriptAimPos() ) + { + return; + } + + if ( self BotIsFrozen() ) + { + return; + } + + if ( self IsBotFragging() || self IsBotSmoking() ) + { + return; + } + + if ( self isDefusing() || self isPlanting() ) + { + return; + } + + if ( self isusingremote() ) + { + return; + } + + if ( self inLastStand() && !self inFinalStand() ) + { + return; + } + + loc = undefined; + + if ( !self nearAnyOfWaypoints( 128, getWaypointsOfType( "tube" ) ) ) + { + tubeWp = getWaypointForIndex( random( self waypointsNear( getWaypointsOfType( "tube" ), 1024 ) ) ); + + myEye = self geteye(); + + if ( !isdefined( tubeWp ) || self HasScriptGoal() || self.bot_lock_goal ) + { + traceForward = bullettrace( myEye, myEye + anglestoforward( self getplayerangles() ) * 900 * 5, false, self ); + + loc = traceForward[ "position" ]; + dist = distancesquared( self.origin, loc ); + + if ( dist < level.bots_mingrenadedistance || dist > level.bots_maxgrenadedistance * 5 ) + { + return; + } + + if ( !bullettracepassed( self.origin + ( 0, 0, 5 ), self.origin + ( 0, 0, 2048 ), false, self ) ) + { + return; + } + + if ( !bullettracepassed( loc + ( 0, 0, 5 ), loc + ( 0, 0, 2048 ), false, self ) ) + { + return; + } + + loc += ( 0, 0, dist / 16000 ); + } + else + { + self BotNotifyBotEvent( "tube", "go", tubeWp, tube ); + + self SetScriptGoal( tubeWp.origin, 16 ); + + ret = self waittill_any_return( "new_goal", "goal", "bad_path" ); + + if ( ret != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( ret != "goal" ) + { + return; + } + + data.dofastcontinue = true; + return; + } + } + else + { + tubeWp = getWaypointForIndex( self getNearestWaypointOfWaypoints( getWaypointsOfType( "tube" ) ) ); + loc = tubeWp.origin + anglestoforward( tubeWp.angles ) * 2048; + } + + if ( !isdefined( loc ) ) + { + return; + } + + self BotNotifyBotEvent( "tube", "start", loc, tube ); + + self SetScriptAimPos( loc ); + self BotStopMoving( true ); + wait 1; + + if ( self changeToWeapon( tube ) ) + { + self thread fire_current_weapon(); + self waittill_any_timeout( 5, "missile_fire", "weapon_change" ); + self notify( "stop_firing_weapon" ); + } + + self ClearScriptAimPos(); + self BotStopMoving( false ); +} + +/* + Bots thinking of using a noobtube +*/ +bot_use_tube_think() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + data = spawnstruct(); + data.dofastcontinue = false; + + for ( ;; ) + { + self bot_use_tube_think_loop( data ); + } +} + +/* + Bots thinking of using claymores and TIs +*/ +bot_use_equipment_think_loop( data ) +{ + if ( data.dofastcontinue ) + { + data.dofastcontinue = false; + } + else + { + wait randomintrange( 2, 4 ); + + chance = self.pers[ "bots" ][ "behavior" ][ "nade" ] / 2; + + if ( chance > 20 ) + { + chance = 20; + } + + if ( randomint( 100 ) > chance ) + { + return; + } + } + + nade = undefined; + + if ( self getammocount( "claymore_mp" ) ) + { + nade = "claymore_mp"; + } + + if ( self getammocount( "flare_mp" ) ) + { + nade = "flare_mp"; + } + + if ( self getammocount( "c4_mp" ) ) + { + nade = "c4_mp"; + } + + if ( !isdefined( nade ) ) + { + return; + } + + if ( self hasThreat() || self HasBotJavelinLocation() || self HasScriptAimPos() ) + { + return; + } + + if ( self BotIsFrozen() ) + { + return; + } + + if ( self IsBotFragging() || self IsBotSmoking() ) + { + return; + } + + if ( self isDefusing() || self isPlanting() ) + { + return; + } + + if ( self isusingremote() ) + { + return; + } + + if ( self inLastStand() && !self _hasperk( "specialty_laststandoffhand" ) && !self inFinalStand() ) + { + return; + } + + loc = undefined; + + if ( !self nearAnyOfWaypoints( 128, getWaypointsOfType( "claymore" ) ) ) + { + clayWp = getWaypointForIndex( random( self waypointsNear( getWaypointsOfType( "claymore" ), 1024 ) ) ); + + if ( !isdefined( clayWp ) || self HasScriptGoal() || self.bot_lock_goal ) + { + myEye = self geteye(); + loc = myEye + anglestoforward( self getplayerangles() ) * 256; + + if ( !bullettracepassed( myEye, loc, false, self ) ) + { + return; + } + } + else + { + self BotNotifyBotEvent( "equ", "go", clayWp, nade ); + + self SetScriptGoal( clayWp.origin, 16 ); + + ret = self waittill_any_return( "new_goal", "goal", "bad_path" ); + + if ( ret != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( ret != "goal" ) + { + return; + } + + data.dofastcontinue = true; + return; + } + } + else + { + clayWp = getWaypointForIndex( self getNearestWaypointOfWaypoints( getWaypointsOfType( "claymore" ) ) ); + loc = clayWp.origin + anglestoforward( clayWp.angles ) * 2048; + } + + if ( !isdefined( loc ) ) + { + return; + } + + self BotNotifyBotEvent( "equ", "start", loc, nade ); + + self SetScriptAimPos( loc ); + self BotStopMoving( true ); + wait 1; + + self botThrowGrenade( nade, 0.05 ); + + self ClearScriptAimPos(); + self BotStopMoving( false ); +} + +/* + Bots thinking of using claymores and TIs +*/ +bot_use_equipment_think() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + data = spawnstruct(); + data.dofastcontinue = false; + + for ( ;; ) + { + self bot_use_equipment_think_loop( data ); + } +} + +/* + Bots thinking of using grenades +*/ +bot_use_grenade_think_loop( data ) +{ + if ( data.dofastcontinue ) + { + data.dofastcontinue = false; + } + else + { + wait randomintrange( 4, 7 ); + + chance = self.pers[ "bots" ][ "behavior" ][ "nade" ] / 2; + + if ( chance > 20 ) + { + chance = 20; + } + + if ( randomint( 100 ) > chance ) + { + return; + } + } + + nade = self getValidGrenade(); + + if ( !isdefined( nade ) ) + { + return; + } + + if ( self hasThreat() || self HasBotJavelinLocation() || self HasScriptAimPos() ) + { + return; + } + + if ( self BotIsFrozen() ) + { + return; + } + + if ( self IsBotFragging() || self IsBotSmoking() ) + { + return; + } + + if ( self isDefusing() || self isPlanting() ) + { + return; + } + + if ( self isusingremote() ) + { + return; + } + + if ( self inLastStand() && !self _hasperk( "specialty_laststandoffhand" ) && !self inFinalStand() ) + { + return; + } + + loc = undefined; + + if ( !self nearAnyOfWaypoints( 128, getWaypointsOfType( "grenade" ) ) ) + { + nadeWp = getWaypointForIndex( random( self waypointsNear( getWaypointsOfType( "grenade" ), 1024 ) ) ); + + myEye = self geteye(); + + if ( !isdefined( nadeWp ) || self HasScriptGoal() || self.bot_lock_goal ) + { + traceForward = bullettrace( myEye, myEye + anglestoforward( self getplayerangles() ) * 900, false, self ); + + loc = traceForward[ "position" ]; + dist = distancesquared( self.origin, loc ); + + if ( dist < level.bots_mingrenadedistance || dist > level.bots_maxgrenadedistance ) + { + return; + } + + if ( !bullettracepassed( self.origin + ( 0, 0, 5 ), self.origin + ( 0, 0, 2048 ), false, self ) ) + { + return; + } + + if ( !bullettracepassed( loc + ( 0, 0, 5 ), loc + ( 0, 0, 2048 ), false, self ) ) + { + return; + } + + loc += ( 0, 0, dist / 3000 ); + } + else + { + self BotNotifyBotEvent( "nade", "go", nadeWp, nade ); + + self SetScriptGoal( nadeWp.origin, 16 ); + + ret = self waittill_any_return( "new_goal", "goal", "bad_path" ); + + if ( ret != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( ret != "goal" ) + { + return; + } + + data.dofastcontinue = true; + return; + } + } + else + { + nadeWp = getWaypointForIndex( self getNearestWaypointOfWaypoints( getWaypointsOfType( "grenade" ) ) ); + loc = nadeWp.origin + anglestoforward( nadeWp.angles ) * 2048; + } + + if ( !isdefined( loc ) ) + { + return; + } + + self BotNotifyBotEvent( "nade", "start", loc, nade ); + + self SetScriptAimPos( loc ); + self BotStopMoving( true ); + wait 1; + + time = 0.5; + + if ( nade == "frag_grenade_mp" ) + { + time = 2; + } + + self botThrowGrenade( nade, time ); + + self ClearScriptAimPos(); + self BotStopMoving( false ); +} + +/* + Bots thinking of using grenades +*/ +bot_use_grenade_think() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + data = spawnstruct(); + data.dofastcontinue = false; + + for ( ;; ) + { + self bot_use_grenade_think_loop( data ); + } +} + +/* + Bots play mw2 +*/ +bot_watch_think_mw2_loop() +{ + tube = self getValidTube(); + + if ( !isdefined( tube ) ) + { + if ( self getammocount( "at4_mp" ) ) + { + tube = "at4_mp"; + } + else if ( self getammocount( "rpg_mp" ) ) + { + tube = "rpg_mp"; + } + else + { + return; + } + } + + if ( self getcurrentweapon() == tube ) + { + return; + } + + chance = self.pers[ "bots" ][ "behavior" ][ "nade" ]; + + if ( randomint( 100 ) > chance ) + { + return; + } + + self thread changeToWeapon( tube ); +} + +/* + Bots play mw2 +*/ +bot_watch_think_mw2() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + for ( ;; ) + { + wait randomintrange( 1, 4 ); + + if ( self BotIsFrozen() ) + { + continue; + } + + if ( self isDefusing() || self isPlanting() ) + { + continue; + } + + if ( self isusingremote() ) + { + continue; + } + + if ( self inLastStand() && !self inFinalStand() ) + { + continue; + } + + if ( self hasThreat() ) + { + continue; + } + + self bot_watch_think_mw2_loop(); + } +} + +/* + Bots will use gremades/wweapons while having a target while using a shield +*/ +bot_watch_riot_weapons_loop() +{ + threat = self getThreat(); + dist = distancesquared( threat.origin, self.origin ); + curWeap = self getcurrentweapon(); + + if ( randomint( 2 ) ) + { + nade = self getValidGrenade(); + + if ( !isdefined( nade ) ) + { + return; + } + + if ( dist <= level.bots_mingrenadedistance || dist >= level.bots_maxgrenadedistance ) + { + return; + } + + if ( randomint( 100 ) > self.pers[ "bots" ][ "behavior" ][ "nade" ] ) + { + return; + } + + self botThrowGrenade( nade ); + } + else + { + if ( randomint( 100 ) > self.pers[ "bots" ][ "behavior" ][ "switch" ] * 10 ) + { + return; + } + + weaponslist = self getweaponslistall(); + weap = ""; + + while ( weaponslist.size ) + { + weapon = weaponslist[ randomint( weaponslist.size ) ]; + weaponslist = array_remove( weaponslist, weapon ); + + if ( !self getammocount( weapon ) ) + { + continue; + } + + if ( !isWeaponPrimary( weapon ) ) + { + continue; + } + + if ( curWeap == weapon || weapon == "none" || weapon == "" || weapon == "javelin_mp" || weapon == "stinger_mp" || weapon == "onemanarmy_mp" ) + { + continue; + } + + weap = weapon; + break; + } + + if ( weap == "" ) + { + return; + } + + self thread changeToWeapon( weap ); + } +} + +/* + Bots will use gremades/wweapons while having a target while using a shield +*/ +bot_watch_riot_weapons() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + for ( ;; ) + { + wait randomintrange( 2, 4 ); + + if ( self BotIsFrozen() ) + { + continue; + } + + if ( self isDefusing() || self isPlanting() ) + { + continue; + } + + if ( self isusingremote() ) + { + continue; + } + + if ( self inLastStand() && !self inFinalStand() ) + { + continue; + } + + if ( !self hasThreat() ) + { + continue; + } + + if ( !self.hasriotshieldequipped ) + { + continue; + } + + self bot_watch_riot_weapons_loop(); + } +} + +/* + BOts thinking of using javelins +*/ +bot_jav_loc_think_loop( data ) +{ + if ( data.dofastcontinue ) + { + data.dofastcontinue = false; + } + else + { + wait randomintrange( 2, 4 ); + + chance = self.pers[ "bots" ][ "behavior" ][ "nade" ] / 2; + + if ( chance > 20 ) + { + chance = 20; + } + + if ( randomint( 100 ) > chance && self getcurrentweapon() != "javelin_mp" ) + { + return; + } + } + + if ( !self getammocount( "javelin_mp" ) ) + { + return; + } + + if ( self hasThreat() || self HasBotJavelinLocation() || self HasScriptAimPos() ) + { + return; + } + + if ( self BotIsFrozen() ) + { + return; + } + + if ( self isDefusing() || self isPlanting() ) + { + return; + } + + if ( self isusingremote() ) + { + return; + } + + if ( self inLastStand() && !self inFinalStand() ) + { + return; + } + + if ( self isemped() ) + { + return; + } + + loc = undefined; + + if ( !self nearAnyOfWaypoints( 128, getWaypointsOfType( "javelin" ) ) ) + { + javWp = getWaypointForIndex( random( self waypointsNear( getWaypointsOfType( "javelin" ), 1024 ) ) ); + + if ( !isdefined( javWp ) || self HasScriptGoal() || self.bot_lock_goal ) + { + traceForward = self maps\mp\_javelin::eyetraceforward(); + + if ( !isdefined( traceForward ) ) + { + return; + } + + loc = traceForward[ 0 ]; + + if ( self maps\mp\_javelin::targetpointtooclose( loc ) ) + { + return; + } + + if ( !bullettracepassed( self.origin + ( 0, 0, 5 ), self.origin + ( 0, 0, 2048 ), false, self ) ) + { + return; + } + + if ( !bullettracepassed( loc + ( 0, 0, 5 ), loc + ( 0, 0, 2048 ), false, self ) ) + { + return; + } + } + else + { + self BotNotifyBotEvent( "jav", "go", javWp ); + + self SetScriptGoal( javWp.origin, 16 ); + + ret = self waittill_any_return( "new_goal", "goal", "bad_path" ); + + if ( ret != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( ret != "goal" ) + { + return; + } + + data.dofastcontinue = true; + return; + } + } + else + { + javWp = getWaypointForIndex( self getNearestWaypointOfWaypoints( getWaypointsOfType( "javelin" ) ) ); + loc = javWp.jav_point; + } + + if ( !isdefined( loc ) ) + { + return; + } + + self BotNotifyBotEvent( "jav", "start", loc ); + + self SetBotJavelinLocation( loc ); + + if ( self changeToWeapon( "javelin_mp" ) ) + { + self waittill_any_timeout( 10, "missile_fire", "weapon_change" ); + } + + self ClearBotJavelinLocation(); +} + +/* + BOts thinking of using javelins +*/ +bot_jav_loc_think() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + data = spawnstruct(); + data.dofastcontinue = false; + + for ( ;; ) + { + self bot_jav_loc_think_loop( data ); + } +} + +/* + Bots thinking of targeting equipment, c4, claymores and TIs +*/ +bot_equipment_kill_think_loop() +{ + myTeam = self.pers[ "team" ]; + hasSitrep = self _hasperk( "specialty_detectexplosive" ); + grenades = getentarray( "grenade", "classname" ); + myEye = self geteye(); + myAngles = self getplayerangles(); + dist = 512 * 512; + target = undefined; + + for ( i = 0; i < grenades.size; i++ ) + { + item = grenades[ i ]; + + if ( !isdefined( item ) ) + { + continue; + } + + if ( !isdefined( item.name ) ) + { + continue; + } + + if ( isdefined( item.owner ) && ( ( level.teambased && item.owner.team == self.team ) || item.owner == self ) ) + { + continue; + } + + if ( item.name != "c4_mp" && item.name != "claymore_mp" ) + { + continue; + } + + if ( !hasSitrep && !bullettracepassed( myEye, item.origin, false, item ) ) + { + continue; + } + + if ( getConeDot( item.origin, self.origin, myAngles ) < 0.6 ) + { + continue; + } + + if ( distancesquared( item.origin, self.origin ) < dist ) + { + target = item; + break; + } + } + + grenades = undefined; + + if ( !isdefined( target ) ) + { + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + if ( !isdefined( player.team ) ) + { + continue; + } + + if ( level.teambased && player.team == myTeam ) + { + continue; + } + + ti = player.setspawnpoint; + + if ( !isdefined( ti ) ) + { + continue; + } + + if ( !isdefined( ti.bots ) ) + { + ti.bots = 0; + } + + if ( ti.bots >= 2 ) + { + continue; + } + + if ( !hasSitrep && !bullettracepassed( myEye, ti.origin, false, ti ) ) + { + continue; + } + + if ( getConeDot( ti.origin, self.origin, myAngles ) < 0.6 ) + { + continue; + } + + if ( distancesquared( ti.origin, self.origin ) < dist ) + { + target = ti; + break; + } + } + } + + if ( !isdefined( target ) ) + { + return; + } + + if ( isdefined( target.enemytrigger ) && !self HasScriptGoal() && !self.bot_lock_goal ) + { + self BotNotifyBotEvent( "attack_equ", "go_ti", target ); + + self SetScriptGoal( target.origin, 64 ); + self thread bot_inc_bots( target, true ); + self thread bots_watch_touch_obj( target ); + + path = self waittill_any_return( "bad_path", "goal", "new_goal" ); + + if ( path != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( path != "goal" || !isdefined( target ) ) + { + return; + } + + if ( randomint( 100 ) < self.pers[ "bots" ][ "behavior" ][ "camp" ] * 8 ) + { + self BotNotifyBotEvent( "attack_equ", "camp_ti", target ); + + self thread killCampAfterTime( randomintrange( 10, 20 ) ); + self thread killCampAfterEntGone( target ); + self CampAtSpot( target.origin, target.origin + ( 0, 0, 42 ) ); + } + + if ( isdefined( target ) ) + { + self BotNotifyBotEvent( "attack_equ", "trigger_ti", target ); + self thread BotPressUse(); + } + + return; + } + + self BotNotifyBotEvent( "attack_equ", "start", target ); + + self SetScriptEnemy( target ); + self bot_equipment_attack( target ); + self ClearScriptEnemy(); + + self BotNotifyBotEvent( "attack_equ", "stop", target ); +} + +/* + Bots thinking of targeting equipment, c4, claymores and TIs +*/ +bot_equipment_kill_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon ( "game_ended" ); + + for ( ;; ) + { + wait( randomintrange( 1, 3 ) ); + + if ( self HasScriptEnemy() ) + { + continue; + } + + if ( self.pers[ "bots" ][ "skill" ][ "base" ] <= 1 ) + { + continue; + } + + self bot_equipment_kill_think_loop(); + } +} + +/* + Bots target the equipment for a time then stop +*/ +bot_equipment_attack( equ ) +{ + wait_time = randomintrange( 7, 10 ); + + for ( i = 0; i < wait_time; i++ ) + { + wait( 1 ); + + if ( !isdefined( equ ) ) + { + return; + } + } +} + +/* + Bots will listen to foot steps and target nearby targets +*/ +bot_listen_to_steps_loop() +{ + dist = level.bots_listendist; + + if ( self _hasperk( "specialty_selectivehearing" ) ) + { + dist *= 1.4; + } + + dist *= dist; + + heard = undefined; + + for ( i = level.players.size - 1 ; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + if ( level.teambased && self.team == player.team ) + { + continue; + } + + if ( player.sessionstate != "playing" ) + { + continue; + } + + if ( !isreallyalive( player ) ) + { + continue; + } + + if ( lengthsquared( player getvelocity() ) < 20000 ) + { + continue; + } + + if ( distancesquared( player.origin, self.origin ) > dist ) + { + continue; + } + + if ( player _hasperk( "specialty_quieter" ) ) + { + continue; + } + + heard = player; + break; + } + + hasHeartbeat = ( issubstr( self getcurrentweapon(), "_heartbeat_" ) && !self isemped() ); + heartbeatDist = 350 * 350; + + if ( !isdefined( heard ) && hasHeartbeat ) + { + for ( i = level.players.size - 1 ; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + if ( level.teambased && self.team == player.team ) + { + continue; + } + + if ( player.sessionstate != "playing" ) + { + continue; + } + + if ( !isreallyalive( player ) ) + { + continue; + } + + if ( player _hasperk( "specialty_heartbreaker" ) ) + { + continue; + } + + if ( distancesquared( player.origin, self.origin ) > heartbeatDist ) + { + continue; + } + + if ( getConeDot( player.origin, self.origin, self getplayerangles() ) < 0.6 ) + { + continue; + } + + heard = player; + break; + } + } + + if ( !isdefined( heard ) ) + { + return; + } + + self BotNotifyBotEvent( "heard_target", "start", heard ); + + if ( bullettracepassed( self geteye(), heard gettagorigin( "j_spineupper" ), false, heard ) ) + { + self setAttacker( heard ); + return; + } + + if ( self HasScriptGoal() || self.bot_lock_goal ) + { + return; + } + + self SetScriptGoal( heard.origin, 64 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self BotNotifyBotEvent( "heard_target", "stop", heard ); +} + +/* + Bots will listen to foot steps and target nearby targets +*/ +bot_listen_to_steps() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait 1; + + if ( self.pers[ "bots" ][ "skill" ][ "base" ] < 3 ) + { + continue; + } + + self bot_listen_to_steps_loop(); + } +} + +/* + Bots will look at the uav and target targets +*/ +bot_uav_think_loop() +{ + hasRadar = ( ( level.teambased && level.activeuavs[ self.team ] ) || ( !level.teambased && level.activeuavs[ self.guid ] ) ); + + if ( level.hardcoremode && !hasRadar ) + { + return; + } + + dist = self.pers[ "bots" ][ "skill" ][ "help_dist" ]; + dist *= dist * 8; + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + if ( !isdefined( player.team ) ) + { + continue; + } + + if ( player.sessionstate != "playing" ) + { + continue; + } + + if ( level.teambased && player.team == self.team ) + { + continue; + } + + if ( !isreallyalive( player ) ) + { + continue; + } + + distFromPlayer = distancesquared( self.origin, player.origin ); + + if ( distFromPlayer > dist ) + { + continue; + } + + if ( ( !issubstr( player getcurrentweapon(), "_silencer_" ) && player.bots_firing ) || ( hasRadar && !player _hasperk( "specialty_coldblooded" ) ) ) + { + self BotNotifyBotEvent( "uav_target", "start", player ); + + distSq = self.pers[ "bots" ][ "skill" ][ "help_dist" ] * self.pers[ "bots" ][ "skill" ][ "help_dist" ]; + + if ( distFromPlayer < distSq && bullettracepassed( self geteye(), player gettagorigin( "j_spineupper" ), false, player ) ) + { + self setAttacker( player ); + } + + if ( !self HasScriptGoal() && !self.bot_lock_goal ) + { + self SetScriptGoal( player.origin, 128 ); + self thread stop_go_target_on_death( player ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self BotNotifyBotEvent( "uav_target", "stop", player ); + } + + break; + } + } +} + +/* + Bots will look at the uav and target targets +*/ +bot_uav_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait 0.75; + + if ( self.pers[ "bots" ][ "skill" ][ "base" ] <= 1 || self isusingremote() ) + { + continue; + } + + if ( self isemped() || self.bot_isscrambled ) + { + continue; + } + + if ( self _hasperk( "_specialty_blastshield" ) ) + { + continue; + } + + if ( ( level.teambased && level.activecounteruavs[ level.otherteam[ self.team ] ] ) || ( !level.teambased && self.isradarblocked ) ) + { + continue; + } + + self bot_uav_think_loop(); + } +} + +/* + bots will go to their target's kill location +*/ +bot_revenge_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + + if ( self.pers[ "bots" ][ "skill" ][ "base" ] <= 1 ) + { + return; + } + + if ( isdefined( self.lastkiller ) && isreallyalive( self.lastkiller ) ) + { + if ( bullettracepassed( self geteye(), self.lastkiller gettagorigin( "j_spineupper" ), false, self.lastkiller ) ) + { + self setAttacker( self.lastkiller ); + } + } + + if ( !isdefined( self.killerlocation ) ) + { + return; + } + + loc = self.killerlocation; + + for ( ;; ) + { + wait( randomintrange( 1, 5 ) ); + + if ( self HasScriptGoal() || self.bot_lock_goal ) + { + return; + } + + if ( randomint( 100 ) < 75 ) + { + return; + } + + self BotNotifyBotEvent( "revenge", "start", loc, self.lastkiller ); + + self SetScriptGoal( loc, 64 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self BotNotifyBotEvent( "revenge", "stop", loc, self.lastkiller ); + } +} + +/* + Watches the target's health, calls 'bad_path' +*/ +turret_death_monitor( turret ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "bad_path" ); + self endon ( "goal" ); + self endon ( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( !isdefined( turret ) ) + { + break; + } + + if ( turret.health <= 20000 ) + { + break; + } + + if ( isdefined( turret.carriedby ) ) + { + break; + } + } + + self notify( "bad_path" ); +} + +/* + Bots will target the turret for a time +*/ +bot_turret_attack( enemy ) +{ + wait_time = randomintrange( 7, 10 ); + + for ( i = 0; i < wait_time; i++ ) + { + wait( 1 ); + + if ( !isdefined( enemy ) ) + { + return; + } + + if ( enemy.health <= 20000 ) + { + return; + } + + if ( isdefined( enemy.carriedby ) ) + { + return; + } + + // if ( !bullettracepassed( self geteye(), enemy.origin + ( 0, 0, 15 ), false, enemy ) ) + // return; + } +} + +/* + Bots will think when to target a turret +*/ +bot_turret_think_loop() +{ + myTeam = self.pers[ "team" ]; + turretsKeys = getarraykeys( level.turrets ); + + if ( turretsKeys.size == 0 ) + { + wait( randomintrange( 3, 5 ) ); + return; + } + + if ( self.pers[ "bots" ][ "skill" ][ "base" ] <= 1 ) + { + return; + } + + if ( self HasScriptEnemy() || self isusingremote() ) + { + return; + } + + myEye = self geteye(); + turret = undefined; + + for ( i = turretsKeys.size - 1; i >= 0; i-- ) + { + tempTurret = level.turrets[ turretsKeys[ i ] ]; + + if ( !isdefined( tempTurret ) ) + { + continue; + } + + if ( tempTurret.health <= 20000 ) + { + continue; + } + + if ( isdefined( tempTurret.carriedby ) ) + { + continue; + } + + if ( isdefined( tempTurret.owner ) && tempTurret.owner == self ) + { + continue; + } + + if ( level.teambased && tempTurret.team == myTeam ) + { + continue; + } + + if ( !bullettracepassed( myEye, tempTurret.origin + ( 0, 0, 15 ), false, tempTurret ) ) + { + continue; + } + + turret = tempTurret; + } + + turretsKeys = undefined; + + if ( !isdefined( turret ) ) + { + return; + } + + forward = anglestoforward( turret.angles ); + forward = vectornormalize( forward ); + + delta = self.origin - turret.origin; + delta = vectornormalize( delta ); + + dot = vectordot( forward, delta ); + + facing = true; + + if ( dot < 0.342 ) // cos 70 degrees + { + facing = false; + } + + if ( turret isStunned() ) + { + facing = false; + } + + if ( self _hasperk( "specialty_coldblooded" ) ) + { + facing = false; + } + + if ( facing && !bullettracepassed( myEye, turret.origin + ( 0, 0, 15 ), false, turret ) ) + { + return; + } + + if ( !isdefined( turret.bots ) ) + { + turret.bots = 0; + } + + if ( turret.bots >= 2 ) + { + return; + } + + if ( !facing && !self HasScriptGoal() && !self.bot_lock_goal ) + { + self BotNotifyBotEvent( "turret_attack", "go", turret ); + + self SetScriptGoal( turret.origin, 32 ); + self thread bot_inc_bots( turret, true ); + self thread turret_death_monitor( turret ); + self thread bots_watch_touch_obj( turret ); + + if ( self waittill_any_return( "bad_path", "goal", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } + + if ( !isdefined( turret ) ) + { + return; + } + + self BotNotifyBotEvent( "turret_attack", "start", turret ); + + self SetScriptEnemy( turret, ( 0, 0, 25 ) ); + self bot_turret_attack( turret ); + self ClearScriptEnemy(); + + self BotNotifyBotEvent( "turret_attack", "stop", turret ); +} + +/* + Bots will think when to target a turret +*/ +bot_turret_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon ( "game_ended" ); + + for ( ;; ) + { + wait( 1 ); + + self bot_turret_think_loop(); + } +} + +/* + Checks if the bot is stuck on a carepackage +*/ +bot_watch_stuck_on_crate_loop() +{ + crates = getentarray( "care_package", "targetname" ); + + if ( crates.size == 0 ) + { + return; + } + + crate = undefined; + + for ( i = crates.size - 1; i >= 0; i-- ) + { + tempCrate = crates[ i ]; + + if ( !isdefined( tempCrate ) ) + { + continue; + } + + if ( !isdefined( tempCrate.doingphysics ) || tempCrate.doingphysics ) + { + continue; + } + + if ( isdefined( crate ) && distancesquared( crate.origin, self.origin ) < distancesquared( tempCrate.origin, self.origin ) ) + { + continue; + } + + crate = tempCrate; + } + + if ( !isdefined( crate ) ) + { + return; + } + + radius = getdvarfloat( "player_useRadius" ); + + if ( distancesquared( crate.origin, self.origin ) > radius * radius ) + { + return; + } + + self.bot_stuck_on_carepackage = crate; + self notify( "crate_physics_done" ); +} + +/* + Checks if the bot is stuck on a carepackage +*/ +bot_watch_stuck_on_crate() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + for ( ;; ) + { + wait 3; + + if ( self hasThreat() ) + { + continue; + } + + self bot_watch_stuck_on_crate_loop(); + } +} + +/* + Bots will capture carepackages +*/ +bot_crate_think_loop( data ) +{ + ret = "crate_physics_done"; + + if ( data.first ) + { + data.first = false; + } + else + { + ret = self waittill_any_timeout( randomintrange( 3, 5 ), "crate_physics_done" ); + } + + myTeam = self.pers[ "team" ]; + crate = self.bot_stuck_on_carepackage; + self.bot_stuck_on_carepackage = undefined; + + if ( !isdefined( crate ) ) + { + if ( randomint( 100 ) < 20 && ret != "crate_physics_done" ) + { + return; + } + + if ( self HasScriptGoal() || self.bot_lock_goal ) + { + return; + } + + if ( self isDefusing() || self isPlanting() ) + { + return; + } + + if ( self isusingremote() || self BotIsFrozen() ) + { + return; + } + + if ( self inLastStand() ) + { + return; + } + + crates = getentarray( "care_package", "targetname" ); + + if ( crates.size == 0 ) + { + return; + } + + wantsClosest = randomint( 2 ); + + crate = undefined; + + for ( i = crates.size - 1; i >= 0; i-- ) + { + tempCrate = crates[ i ]; + + if ( !isdefined( tempCrate ) ) + { + continue; + } + + if ( !isdefined( tempCrate.doingphysics ) || tempCrate.doingphysics ) + { + continue; + } + + if ( !isdefined( tempCrate.bots ) ) + { + tempCrate.bots = 0; + } + + if ( tempCrate.bots >= 3 ) + { + continue; + } + + if ( isdefined( crate ) ) + { + if ( wantsClosest ) + { + if ( distancesquared( crate.origin, self.origin ) < distancesquared( tempCrate.origin, self.origin ) ) + { + continue; + } + } + else + { + if ( maps\mp\killstreaks\_killstreaks::getstreakcost( crate.cratetype ) > maps\mp\killstreaks\_killstreaks::getstreakcost( tempCrate.cratetype ) ) + { + continue; + } + } + } + + crate = tempCrate; + } + + crates = undefined; + + if ( !isdefined( crate ) ) + { + return; + } + + self BotNotifyBotEvent( "crate_cap", "go", crate ); + + self.bot_lock_goal = true; + + radius = getdvarfloat( "player_useRadius" ) - 16; + self SetScriptGoal( crate.origin, radius ); + self thread bot_inc_bots( crate, true ); + self thread bots_watch_touch_obj( crate ); + + path = self waittill_any_return( "bad_path", "goal", "new_goal" ); + + self.bot_lock_goal = false; + + if ( path != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( path != "goal" || !isdefined( crate ) || distancesquared( self.origin, crate.origin ) > radius * radius ) + { + if ( isdefined( crate ) && path == "bad_path" ) + { + self BotNotifyBotEvent( "crate_cap", "unreachable", crate ); + } + + return; + } + } + + self BotNotifyBotEvent( "crate_cap", "start", crate ); + + self BotRandomStance(); + + self BotFreezeControls( true ); + self bot_wait_stop_move(); + + waitTime = 3.25; + + if ( !isdefined( crate.owner ) || crate.owner == self ) + { + waitTime = 0.75; + } + + self thread BotPressUse( waitTime ); + wait waitTime; + + self BotFreezeControls( false ); + + // check if actually captured it? + self BotNotifyBotEvent( "crate_cap", "stop", crate ); +} + +/* + Bots will capture carepackages +*/ +bot_crate_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + data = spawnstruct(); + data.first = true; + + for ( ;; ) + { + self bot_crate_think_loop( data ); + } +} + +/* + Reload cancels +*/ +doReloadCancel_loop() +{ + ret = self waittill_any_return( "reload", "weapon_change" ); + + if ( self BotIsFrozen() ) + { + return; + } + + if ( self isDefusing() || self isPlanting() ) + { + return; + } + + if ( self isusingremote() ) + { + return; + } + + if ( self inLastStand() && !self inFinalStand() ) + { + return; + } + + curWeap = self getcurrentweapon(); + + if ( !maps\mp\gametypes\_weapons::isprimaryweapon( curWeap ) ) + { + return; + } + + if ( ret == "reload" ) + { + // check single reloads + if ( self getweaponammoclip( curWeap ) < weaponclipsize( curWeap ) ) + { + return; + } + } + + // check difficulty + if ( self.pers[ "bots" ][ "skill" ][ "base" ] <= 3 ) + { + return; + } + + // check if got another weapon + weaponslist = self getweaponslistprimaries(); + weap = ""; + + while ( weaponslist.size ) + { + weapon = weaponslist[ randomint( weaponslist.size ) ]; + weaponslist = array_remove( weaponslist, weapon ); + + if ( !maps\mp\gametypes\_weapons::isprimaryweapon( weapon ) ) + { + continue; + } + + if ( curWeap == weapon || weapon == "none" || weapon == "" ) + { + continue; + } + + weap = weapon; + break; + } + + if ( weap == "" ) + { + return; + } + + // do the cancel + wait 0.1; + self thread changeToWeapon( weap ); + wait 0.25; + self thread changeToWeapon( curWeap ); + wait 2; +} + +/* + Reload cancels +*/ +doReloadCancel() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self doReloadCancel_loop(); + } +} + +/* + Bots will think to switch weapons +*/ +bot_weapon_think_loop( data ) +{ + ret = self waittill_any_timeout( randomintrange( 2, 4 ), "bot_force_check_switch" ); + + if ( self BotIsFrozen() ) + { + return; + } + + if ( self isDefusing() || self isPlanting() ) + { + return; + } + + if ( self isusingremote() ) + { + return; + } + + if ( self inLastStand() && !self inFinalStand() ) + { + return; + } + + curWeap = self getcurrentweapon(); + hasTarget = self hasThreat(); + + if ( hasTarget ) + { + threat = self getThreat(); + rocketAmmo = self getRocketAmmo(); + + if ( entIsVehicle( threat ) && isdefined( rocketAmmo ) ) + { + if ( curWeap != rocketAmmo ) + { + self thread changeToWeapon( rocketAmmo ); + } + + return; + } + } + + if ( self HasBotJavelinLocation() && self getammocount( "javelin_mp" ) ) + { + if ( curWeap != "javelin_mp" ) + { + self thread changeToWeapon( "javelin_mp" ); + } + + return; + } + + if ( isdefined( self.bot_oma_class ) ) + { + if ( curWeap != "onemanarmy_mp" ) + { + self thread changeToWeapon( "onemanarmy_mp" ); + } + + return; + } + + force = ( ret == "bot_force_check_switch" ); + + if ( data.first ) + { + data.first = false; + + if ( randomint( 100 ) > self.pers[ "bots" ][ "behavior" ][ "initswitch" ] ) + { + return; + } + } + else + { + if ( curWeap != "none" && self getammocount( curWeap ) && curWeap != "stinger_mp" && curWeap != "javelin_mp" && curWeap != "onemanarmy_mp" ) + { + if ( randomint( 100 ) > self.pers[ "bots" ][ "behavior" ][ "switch" ] ) + { + return; + } + + if ( hasTarget ) + { + return; + } + } + else + { + force = true; + } + } + + weaponslist = self getweaponslistall(); + weap = ""; + + while ( weaponslist.size ) + { + weapon = weaponslist[ randomint( weaponslist.size ) ]; + weaponslist = array_remove( weaponslist, weapon ); + + if ( !self getammocount( weapon ) && !force ) + { + continue; + } + + if ( !isWeaponPrimary( weapon ) ) + { + continue; + } + + if ( curWeap == weapon || weapon == "none" || weapon == "" || weapon == "javelin_mp" || weapon == "stinger_mp" || weapon == "onemanarmy_mp" ) + { + continue; + } + + weap = weapon; + break; + } + + if ( weap == "" ) + { + return; + } + + self thread changeToWeapon( weap ); +} + +/* + Bots will think to switch weapons +*/ +bot_weapon_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + data = spawnstruct(); + data.first = true; + + for ( ;; ) + { + self bot_weapon_think_loop( data ); + } +} + +/* + Bots think when to target vehicles +*/ +bot_target_vehicle_loop() +{ + rocketAmmo = self getRocketAmmo(); + + if ( !isdefined( rocketAmmo ) && self BotGetRandom() < 90 ) + { + return; + } + + if ( isdefined( rocketAmmo ) && rocketAmmo == "javelin_mp" && self isemped() ) + { + return; + } + + targets = maps\mp\_stinger::gettargetlist(); + + if ( !targets.size ) + { + return; + } + + lockOnAmmo = self getLockonAmmo(); + myEye = self geteye(); + target = undefined; + + for ( i = targets.size - 1; i >= 0; i-- ) + { + tempTarget = targets[ i ]; + + if ( isdefined( tempTarget.owner ) && tempTarget.owner == self ) + { + continue; + } + + if ( !bullettracepassed( myEye, tempTarget.origin, false, tempTarget ) ) + { + continue; + } + + if ( tempTarget.health <= 0 ) + { + continue; + } + + if ( tempTarget.classname != "script_vehicle" && !isdefined( lockOnAmmo ) ) + { + continue; + } + + target = tempTarget; + } + + targets = undefined; + + if ( !isdefined( target ) ) + { + return; + } + + self BotNotifyBotEvent( "attack_vehicle", "start", target, rocketAmmo ); + + self SetScriptEnemy( target, ( 0, 0, 0 ) ); + self bot_attack_vehicle( target ); + self ClearScriptEnemy(); + self notify( "bot_force_check_switch" ); + + self BotNotifyBotEvent( "attack_vehicle", "stop", target, rocketAmmo ); +} + +/* + Bots think when to target vehicles +*/ +bot_target_vehicle() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait randomintrange( 2, 4 ); + + if ( self.pers[ "bots" ][ "skill" ][ "base" ] <= 1 ) + { + continue; + } + + if ( self HasScriptEnemy() ) + { + continue; + } + + if ( self isusingremote() ) + { + continue; + } + + self bot_target_vehicle_loop(); + } +} + +/* + Bots target the killstreak for a time and stops +*/ +bot_attack_vehicle( target ) +{ + target endon( "death" ); + + wait_time = randomintrange( 7, 10 ); + + for ( i = 0; i < wait_time; i++ ) + { + self notify( "bot_force_check_switch" ); + wait( 1 ); + + if ( !isdefined( target ) ) + { + return; + } + } +} + +/* + Returns an origin thats good to use for a kill streak +*/ +getKillstreakTargetLocation() +{ + location = undefined; + players = []; + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( player == self ) + { + continue; + } + + if ( !isdefined( player.team ) ) + { + continue; + } + + if ( level.teambased && self.team == player.team ) + { + continue; + } + + if ( player.sessionstate != "playing" ) + { + continue; + } + + if ( !isreallyalive( player ) ) + { + continue; + } + + if ( player _hasperk( "specialty_coldblooded" ) ) + { + continue; + } + + if ( !bullettracepassed( player.origin, player.origin + ( 0, 0, 2048 ), false, player ) && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 ) + { + continue; + } + + players[ players.size ] = player; + } + + target = random( players ); + + if ( isdefined( target ) ) + { + location = target.origin + ( randomintrange( ( 8 - self.pers[ "bots" ][ "skill" ][ "base" ] ) * -75, ( 8 - self.pers[ "bots" ][ "skill" ][ "base" ] ) * 75 ), randomintrange( ( 8 - self.pers[ "bots" ][ "skill" ][ "base" ] ) * -75, ( 8 - self.pers[ "bots" ][ "skill" ][ "base" ] ) * 75 ), 0 ); + } + else if ( self.pers[ "bots" ][ "skill" ][ "base" ] <= 3 ) + { + location = self.origin + ( randomintrange( -512, 512 ), randomintrange( -512, 512 ), 0 ); + } + + return location; +} + +/* + Returns if any harriers exists that is an enemy +*/ +isAnyEnemyPlanes() +{ + if ( !isdefined( level.harriers ) ) + { + return false; + } + + for ( i = 0; i < level.harriers.size; i++ ) + { + plane = level.harriers[ i ]; + + if ( !isdefined( plane ) ) + { + continue; + } + + if ( level.teambased && plane.team == self.team ) + { + continue; + } + + if ( isdefined( plane.owner ) && plane.owner == self ) + { + continue; + } + + return true; + } + + return false; +} + +/* + Bots think to use killstreaks +*/ +bot_killstreak_think_loop( data ) +{ + if ( data.dofastcontinue ) + { + data.dofastcontinue = false; + } + else + { + wait randomintrange( 1, 3 ); + } + + if ( !isdefined( self.pers[ "killstreaks" ][ 0 ] ) ) + { + return; + } + + if ( self BotIsFrozen() ) + { + return; + } + + if ( self hasThreat() || self HasBotJavelinLocation() ) + { + return; + } + + if ( self isDefusing() || self isPlanting() ) + { + return; + } + + if ( self isemped() ) + { + return; + } + + if ( self isusingremote() ) + { + return; + } + + if ( self inLastStand() && !self inFinalStand() ) + { + return; + } + + + if ( isdefined( self.iscarrying ) && self.iscarrying ) + { + self notify( "place_sentry" ); + } + + curWeap = self getcurrentweapon(); + + if ( issubstr( curWeap, "airdrop_" ) ) + { + self thread BotPressAttack( 0.05 ); + } + + if ( iskillstreakweapon( curWeap ) ) + { + self thread changeToWeapon( self getlastweapon() ); + return; + } + + streakName = self.pers[ "killstreaks" ][ 0 ].streakname; + + if ( level.ingraceperiod && maps\mp\killstreaks\_killstreaks::deadlykillstreak( streakName ) ) + { + return; + } + + ksWeap = maps\mp\killstreaks\_killstreaks::getkillstreakweapon( streakName ); + + if ( curWeap == "none" || !isWeaponPrimary( curWeap ) ) + { + curWeap = self getlastweapon(); + } + + lifeId = self.pers[ "killstreaks" ][ 0 ].lifeid; + + if ( !isdefined( lifeId ) ) + { + lifeId = -1; + } + + if ( isStrStart( streakName, "helicopter_" ) && self isAnyEnemyPlanes() && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 ) + { + return; + } + + if ( maps\mp\killstreaks\_killstreaks::isridekillstreak( streakName ) || maps\mp\killstreaks\_killstreaks::iscarrykillstreak( streakName ) ) + { + if ( self inLastStand() ) + { + return; + } + + if ( lifeId == self.deaths && !self HasScriptGoal() && !self.bot_lock_goal && streakName != "sentry" && !self nearAnyOfWaypoints( 128, getWaypointsOfType( "camp" ) ) ) + { + campSpot = getWaypointForIndex( random( self waypointsNear( getWaypointsOfType( "camp" ), 1024 ) ) ); + + if ( isdefined( campSpot ) ) + { + self BotNotifyBotEvent( "killstreak", "camp", streakName, campSpot ); + + self SetScriptGoal( campSpot.origin, 16 ); + + if ( self waittill_any_return( "new_goal", "goal", "bad_path" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + data.dofastcontinue = true; + return; + } + } + + if ( streakName == "sentry" ) + { + if ( self HasScriptAimPos() ) + { + return; + } + + myEye = self geteye(); + angles = self getplayerangles(); + + forwardTrace = bullettrace( myEye, myEye + anglestoforward( angles ) * 1024, false, self ); + + if ( distancesquared( self.origin, forwardTrace[ "position" ] ) < 1000 * 1000 && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 ) + { + return; + } + + self BotNotifyBotEvent( "killstreak", "call", streakName ); + + self BotStopMoving( true ); + self SetScriptAimPos( forwardTrace[ "position" ] ); + + if ( !self changeToWeapon( ksWeap ) ) + { + self BotStopMoving( false ); + self ClearScriptAimPos(); + return; + } + + wait 1; + self notify( "place_sentry" ); + wait 0.05; + self notify( "cancel_sentry" ); + wait 0.5; + + self BotStopMoving( false ); + self ClearScriptAimPos(); + } + else if ( streakName == "predator_missile" ) + { + location = self getKillstreakTargetLocation(); + + if ( !isdefined( location ) ) + { + return; + } + + self BotNotifyBotEvent( "killstreak", "call", streakName, location ); + + self BotRandomStance(); + self BotStopMoving( true ); + self changeToWeapon( ksWeap ); + + wait 3; + self BotStopMoving( false ); + } + else if ( streakName == "ac130" ) + { + if ( isdefined( level.ac130player ) || level.ac130inuse ) + { + return; + } + + self BotNotifyBotEvent( "killstreak", "call", streakName ); + + self BotRandomStance(); + self BotStopMoving( true ); + self changeToWeapon( ksWeap ); + + wait 3; + self BotStopMoving( false ); + } + else if ( streakName == "helicopter_minigun" ) + { + if ( isdefined( level.chopper ) ) + { + return; + } + + self BotNotifyBotEvent( "killstreak", "call", streakName ); + + self BotRandomStance(); + self BotStopMoving( true ); + self changeToWeapon( ksWeap ); + + wait 3; + self BotStopMoving( false ); + } + } + else + { + if ( streakName == "airdrop_mega" || streakName == "airdrop_sentry_minigun" || streakName == "airdrop" ) + { + if ( self HasScriptAimPos() ) + { + return; + } + + if ( streakName != "airdrop_mega" && level.littlebirds > 2 ) + { + return; + } + + if ( !bullettracepassed( self.origin, self.origin + ( 0, 0, 2048 ), false, self ) && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 ) + { + return; + } + + myEye = self geteye(); + angles = self getplayerangles(); + + forwardTrace = bullettrace( myEye, myEye + anglestoforward( angles ) * 256, false, self ); + + if ( distancesquared( self.origin, forwardTrace[ "position" ] ) < 96 * 96 && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 ) + { + return; + } + + if ( !bullettracepassed( forwardTrace[ "position" ], forwardTrace[ "position" ] + ( 0, 0, 2048 ), false, self ) && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 ) + { + return; + } + + self BotNotifyBotEvent( "killstreak", "call", streakName ); + + self BotStopMoving( true ); + self SetScriptAimPos( forwardTrace[ "position" ] ); + + if ( !self changeToWeapon( ksWeap ) ) + { + self BotStopMoving( false ); + self ClearScriptAimPos(); + return; + } + + self thread fire_current_weapon(); + + ret = self waittill_any_timeout( 5, "grenade_fire" ); + + self notify( "stop_firing_weapon" ); + + if ( ret == "timeout" ) + { + self BotStopMoving( false ); + self ClearScriptAimPos(); + return; + } + + if ( randomint( 100 ) < 80 && !self HasScriptGoal() && !self.bot_lock_goal ) + { + self waittill_any_timeout( 15, "crate_physics_done", "new_goal" ); + } + + self BotStopMoving( false ); + self ClearScriptAimPos(); + } + else + { + if ( streakName == "harrier_airstrike" && level.planes > 1 ) + { + return; + } + + if ( streakName == "nuke" && isdefined( level.nukeincoming ) ) + { + return; + } + + if ( streakName == "counter_uav" && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 && ( ( level.teambased && level.activecounteruavs[ self.team ] ) || ( !level.teambased && level.activecounteruavs[ self.guid ] ) ) ) + { + return; + } + + if ( streakName == "uav" && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 && ( ( level.teambased && ( level.activeuavs[ self.team ] || level.activecounteruavs[ level.otherteam[ self.team ] ] ) ) || ( !level.teambased && ( level.activeuavs[ self.guid ] || self.isradarblocked ) ) ) ) + { + return; + } + + if ( streakName == "emp" && self.pers[ "bots" ][ "skill" ][ "base" ] > 3 && ( ( level.teambased && level.teamemped[ level.otherteam[ self.team ] ] ) || ( !level.teambased && isdefined( level.empplayer ) ) ) ) + { + return; + } + + location = undefined; + directionYaw = undefined; + + switch ( streakName ) + { + case "harrier_airstrike": + case "stealth_airstrike": + case "precision_airstrike": + location = self getKillstreakTargetLocation(); + directionYaw = randomint( 360 ); + + if ( !isdefined( location ) ) + { + return; + } + + case "helicopter": + case "helicopter_flares": + case "uav": + case "nuke": + case "counter_uav": + case "emp": + self BotStopMoving( true ); + + self BotNotifyBotEvent( "killstreak", "call", streakName, location, directionYaw ); + + if ( self changeToWeapon( ksWeap ) ) + { + wait 1; + + if ( isdefined( location ) ) + { + self BotFreezeControls( true ); + + self notify( "confirm_location", location, directionYaw ); + wait 1; + + self BotFreezeControls( false ); + } + } + + self BotStopMoving( false ); + break; + } + } + } +} + +/* + Bots think to use killstreaks +*/ +bot_killstreak_think() +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + data = spawnstruct(); + data.dofastcontinue = false; + + for ( ;; ) + { + self bot_killstreak_think_loop( data ); + } +} + +/* + Bots do random stance +*/ +BotRandomStance() +{ + if ( randomint( 100 ) < 80 ) + { + self BotSetStance( "prone" ); + } + else if ( randomint( 100 ) < 60 ) + { + self BotSetStance( "crouch" ); + } + else + { + self BotSetStance( "stand" ); + } +} + +/* + Bots will use a random equipment +*/ +BotUseRandomEquipment() +{ + self endon( "death" ); + self endon( "disconnect" ); + + nade = undefined; + + // in mw2, can only one of these... + if ( self getammocount( "claymore_mp" ) ) + { + nade = "claymore_mp"; + } + + if ( self getammocount( "flare_mp" ) ) + { + nade = "flare_mp"; + } + + if ( self getammocount( "c4_mp" ) ) + { + nade = "c4_mp"; + } + + if ( !isdefined( nade ) ) + { + return; + } + + self botThrowGrenade( nade, 0.05 ); +} + +/* + Bots will look at a random thing +*/ +BotLookAtRandomThing( obj_target ) +{ + self endon( "death" ); + self endon( "disconnect" ); + + if ( self HasScriptAimPos() ) + { + return; + } + + rand = randomint( 100 ); + + nearestEnemy = undefined; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( !isdefined( player ) || !isdefined( player.team ) ) + { + continue; + } + + if ( !isalive( player ) ) + { + continue; + } + + if ( level.teambased && self.team == player.team ) + { + continue; + } + + if ( !isdefined( nearestEnemy ) || distancesquared( self.origin, player.origin ) < distancesquared( self.origin, nearestEnemy.origin ) ) + { + nearestEnemy = player; + } + } + + origin = ( 0, 0, self getplayerviewheight() ); + + if ( isdefined( nearestEnemy ) && distancesquared( self.origin, nearestEnemy.origin ) < 1024 * 1024 && rand < 40 ) + { + origin += ( nearestEnemy.origin[ 0 ], nearestEnemy.origin[ 1 ], self.origin[ 2 ] ); + } + else if ( isdefined( obj_target ) && rand < 50 ) + { + origin += ( obj_target.origin[ 0 ], obj_target.origin[ 1 ], self.origin[ 2 ] ); + } + else if ( rand < 85 ) + { + origin += self.origin + anglestoforward( ( 0, self.angles[ 1 ] - 180, 0 ) ) * 1024; + } + else + { + origin += self.origin + anglestoforward( ( 0, randomint( 360 ), 0 ) ) * 1024; + } + + self SetScriptAimPos( origin ); + wait 2; + self ClearScriptAimPos(); +} + +/* + Bots will do stuff while waiting for objective +*/ +bot_do_random_action_for_objective( obj_target ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_do_random_action_for_objective" ); + self endon( "bot_do_random_action_for_objective" ); + + if ( !isdefined( self.bot_random_obj_action ) ) + { + self.bot_random_obj_action = true; + + if ( randomint( 100 ) < 80 ) + { + self thread BotUseRandomEquipment(); + } + + if ( randomint( 100 ) < 75 ) + { + self thread BotLookAtRandomThing( obj_target ); + } + } + else + { + if ( self getstance() != "prone" && randomint( 100 ) < 15 ) + { + self BotSetStance( "prone" ); + } + else if ( randomint( 100 ) < 5 ) + { + self thread BotLookAtRandomThing( obj_target ); + } + } + + wait 2; + self.bot_random_obj_action = undefined; +} + +/* + Bots hang around the enemy's flag to spawn kill em +*/ +bot_dom_spawn_kill_think_loop() +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + myFlagCount = maps\mp\gametypes\dom::getteamflagcount( myTeam ); + + if ( myFlagCount == level.flags.size ) + { + return; + } + + otherFlagCount = maps\mp\gametypes\dom::getteamflagcount( otherTeam ); + + if ( myFlagCount <= otherFlagCount || otherFlagCount != 1 ) + { + return; + } + + flag = undefined; + + for ( i = 0; i < level.flags.size; i++ ) + { + if ( level.flags[ i ] maps\mp\gametypes\dom::getflagteam() == myTeam ) + { + continue; + } + + flag = level.flags[ i ]; + } + + if ( !isdefined( flag ) ) + { + return; + } + + if ( distancesquared( self.origin, flag.origin ) < 2048 * 2048 ) + { + return; + } + + self BotNotifyBotEvent( "dom", "start", "spawnkill", flag ); + + self SetScriptGoal( flag.origin, 1024 ); + + self thread bot_dom_watch_flags( myFlagCount, myTeam ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self BotNotifyBotEvent( "dom", "stop", "spawnkill", flag ); +} + +/* + Bots hang around the enemy's flag to spawn kill em +*/ +bot_dom_spawn_kill_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + + if ( level.gametype != "dom" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 10, 20 ) ); + + if ( randomint( 100 ) < 20 ) + { + continue; + } + + if ( self HasScriptGoal() || self.bot_lock_goal ) + { + continue; + } + + self bot_dom_spawn_kill_think_loop(); + } +} + +/* + Calls 'bad_path' when the flag count changes +*/ +bot_dom_watch_flags( count, myTeam ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( maps\mp\gametypes\dom::getteamflagcount( myTeam ) != count ) + { + break; + } + } + + self notify( "bad_path" ); +} + +/* + Bots watches their own flags and protects them when they are under capture +*/ +bot_dom_def_think_loop() +{ + myTeam = self.pers[ "team" ]; + flag = undefined; + + for ( i = 0; i < level.flags.size; i++ ) + { + if ( level.flags[ i ] maps\mp\gametypes\dom::getflagteam() != myTeam ) + { + continue; + } + + if ( !level.flags[ i ].useobj.objpoints[ myTeam ].isflashing ) + { + continue; + } + + if ( !isdefined( flag ) || distancesquared( self.origin, level.flags[ i ].origin ) < distancesquared( self.origin, flag.origin ) ) + { + flag = level.flags[ i ]; + } + } + + if ( !isdefined( flag ) ) + { + return; + } + + self BotNotifyBotEvent( "dom", "start", "defend", flag ); + + self SetScriptGoal( flag.origin, 128 ); + + self thread bot_dom_watch_for_flashing( flag, myTeam ); + self thread bots_watch_touch_obj( flag ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self BotNotifyBotEvent( "dom", "stop", "defend", flag ); +} + +/* + Bots watches their own flags and protects them when they are under capture +*/ +bot_dom_def_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + + if ( level.gametype != "dom" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 1, 3 ) ); + + if ( randomint( 100 ) < 35 ) + { + continue; + } + + if ( self HasScriptGoal() || self.bot_lock_goal ) + { + continue; + } + + self bot_dom_def_think_loop(); + } +} + +/* + Watches while the flag is under capture +*/ +bot_dom_watch_for_flashing( flag, myTeam ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( !isdefined( flag ) ) + { + break; + } + + if ( flag maps\mp\gametypes\dom::getflagteam() != myTeam || !flag.useobj.objpoints[ myTeam ].isflashing ) + { + break; + } + } + + self notify( "bad_path" ); +} + +/* + Bots capture dom flags +*/ +bot_dom_cap_think_loop() +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + myFlagCount = maps\mp\gametypes\dom::getteamflagcount( myTeam ); + + if ( myFlagCount == level.flags.size ) + { + return; + } + + otherFlagCount = maps\mp\gametypes\dom::getteamflagcount( otherTeam ); + + if ( game[ "teamScores" ][ myTeam ] >= game[ "teamScores" ][ otherTeam ] ) + { + if ( myFlagCount < otherFlagCount ) + { + if ( randomint( 100 ) < 15 ) + { + return; + } + } + else if ( myFlagCount == otherFlagCount ) + { + if ( randomint( 100 ) < 35 ) + { + return; + } + } + else if ( myFlagCount > otherFlagCount ) + { + if ( randomint( 100 ) < 95 ) + { + return; + } + } + } + + flag = undefined; + flags = []; + + for ( i = 0; i < level.flags.size; i++ ) + { + if ( level.flags[ i ] maps\mp\gametypes\dom::getflagteam() == myTeam ) + { + continue; + } + + flags[ flags.size ] = level.flags[ i ]; + } + + if ( randomint( 100 ) > 30 ) + { + for ( i = 0; i < flags.size; i++ ) + { + if ( !isdefined( flag ) || distancesquared( self.origin, level.flags[ i ].origin ) < distancesquared( self.origin, flag.origin ) ) + { + flag = level.flags[ i ]; + } + } + } + else if ( flags.size ) + { + flag = random( flags ); + } + + if ( !isdefined( flag ) ) + { + return; + } + + self BotNotifyBotEvent( "dom", "go", "cap", flag ); + + self.bot_lock_goal = true; + self SetScriptGoal( flag.origin, 64 ); + + self thread bot_dom_go_cap_flag( flag, myTeam ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "dom", "start", "cap", flag ); + + self SetScriptGoal( self.origin, 64 ); + + while ( flag maps\mp\gametypes\dom::getflagteam() != myTeam && self istouching( flag ) ) + { + cur = flag.useobj.curprogress; + wait 0.5; + + if ( flag.useobj.curprogress == cur ) + { + break; // some enemy is near us, kill him + } + + self thread bot_do_random_action_for_objective( flag ); + } + + self BotNotifyBotEvent( "dom", "stop", "cap", flag ); + + self ClearScriptGoal(); + + self.bot_lock_goal = false; +} + +/* + Bots capture dom flags +*/ +bot_dom_cap_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + + if ( level.gametype != "dom" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 12 ) ); + + if ( self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.flags ) || level.flags.size == 0 ) + { + continue; + } + + self bot_dom_cap_think_loop(); + } +} + +/* + Bot goes to the flag, watching while they don't have the flag +*/ +bot_dom_go_cap_flag( flag, myTeam ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait randomintrange( 2, 4 ); + + if ( !isdefined( flag ) ) + { + break; + } + + if ( flag maps\mp\gametypes\dom::getflagteam() == myTeam ) + { + break; + } + + if ( self istouching( flag ) ) + { + break; + } + } + + if ( flag maps\mp\gametypes\dom::getflagteam() == myTeam ) + { + self notify( "bad_path" ); + } + else + { + self notify( "goal" ); + } +} + +/* + Bots play headquarters +*/ +bot_hq_loop() +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + radio = level.radio; + gameobj = radio.gameobject; + origin = ( radio.origin[ 0 ], radio.origin[ 1 ], radio.origin[ 2 ] + 5 ); + + // if neut or enemy + if ( gameobj.ownerteam != myTeam ) + { + if ( gameobj.interactteam == "none" ) // wait for it to become active + { + if ( self HasScriptGoal() ) + { + return; + } + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + // capture it + + self BotNotifyBotEvent( "hq", "go", "cap" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + self thread bot_hq_go_cap( gameobj, radio ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" ) + { + self.bot_lock_goal = false; + return; + } + + if ( !self istouching( gameobj.trigger ) || level.radio != radio ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "hq", "start", "cap" ); + + self SetScriptGoal( self.origin, 64 ); + + while ( self istouching( gameobj.trigger ) && gameobj.ownerteam != myTeam && level.radio == radio ) + { + cur = gameobj.curprogress; + wait 0.5; + + if ( cur == gameobj.curprogress ) + { + break; // no prog made, enemy must be capping + } + + self thread bot_do_random_action_for_objective( gameobj.trigger ); + } + + self ClearScriptGoal(); + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "hq", "stop", "cap" ); + } + else // we own it + { + if ( gameobj.objpoints[ myTeam ].isflashing ) // underattack + { + self BotNotifyBotEvent( "hq", "start", "defend" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + self thread bot_hq_watch_flashing( gameobj, radio ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "hq", "stop", "defend" ); + return; + } + + if ( self HasScriptGoal() ) + { + return; + } + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } +} + +/* + Bots play headquarters +*/ +bot_hq() +{ + self endon( "death" ); + self endon( "disconnect" ); + + if ( level.gametype != "koth" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.radio ) ) + { + continue; + } + + if ( !isdefined( level.radio.gameobject ) ) + { + continue; + } + + self bot_hq_loop(); + } +} + +/* + Waits until not touching the trigger and it is the current radio. +*/ +bot_hq_go_cap( obj, radio ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait randomintrange( 2, 4 ); + + if ( !isdefined( obj ) ) + { + break; + } + + if ( self istouching( obj.trigger ) ) + { + break; + } + + if ( level.radio != radio ) + { + break; + } + } + + if ( level.radio != radio ) + { + self notify( "bad_path" ); + } + else + { + self notify( "goal" ); + } +} + +/* + Waits while the radio is under attack. +*/ +bot_hq_watch_flashing( obj, radio ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + myTeam = self.team; + + for ( ;; ) + { + wait 0.5; + + if ( !isdefined( obj ) ) + { + break; + } + + if ( !obj.objpoints[ myTeam ].isflashing ) + { + break; + } + + if ( level.radio != radio ) + { + break; + } + } + + self notify( "bad_path" ); +} + +/* + Bots play sab +*/ +bot_sab_loop() +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + bomb = level.sabbomb; + bombteam = bomb.ownerteam; + carrier = bomb.carrier; + timeleft = maps\mp\gametypes\_gamelogic::gettimeremaining() / 1000; + + // the bomb is ours, we are on the offence + if ( bombteam == myTeam ) + { + site = level.bombzones[ otherTeam ]; + origin = ( site.curorigin[ 0 ] + 50, site.curorigin[ 1 ] + 50, site.curorigin[ 2 ] + 5 ); + + // protect our planted bomb + if ( level.bombplanted ) + { + // kill defuser + if ( site isInUse() ) // somebody is defusing our bomb we planted + { + self BotNotifyBotEvent( "sab", "start", "defuser" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + + self thread bot_defend_site( site ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sab", "stop", "defuser" ); + return; + } + + // else hang around the site + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + // we are not the carrier + if ( !self isBombCarrier() ) + { + // lets escort the bomb carrier + if ( self HasScriptGoal() ) + { + return; + } + + origin = carrier.origin; + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_escort_obj( bomb, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + // we are the carrier of the bomb, lets check if we need to plant + timepassed = gettimepassed() / 1000; + + if ( timepassed < 120 && timeleft >= 90 && randomint( 100 ) < 98 ) + { + return; + } + + self BotNotifyBotEvent( "sab", "go", "plant" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 1 ); + + self thread bot_go_plant( site ); + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" || level.bombplanted || !self istouching( site.trigger ) || site isInUse() || self inLastStand() || self hasThreat() ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "sab", "start", "plant" ); + + self BotRandomStance(); + self SetScriptGoal( self.origin, 64 ); + self bot_wait_stop_move(); + + waitTime = ( site.usetime / 1000 ) + 2.5; + self thread BotPressUse( waitTime ); + wait waitTime; + + self ClearScriptGoal(); + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sab", "stop", "plant" ); + } + else if ( bombteam == otherTeam ) // the bomb is theirs, we are on the defense + { + site = level.bombzones[ myTeam ]; + + if ( !isdefined( site.bots ) ) + { + site.bots = 0; + } + + // protect our site from planters + if ( !level.bombplanted ) + { + // kill bomb carrier + if ( site.bots > 2 || randomint( 100 ) < 45 ) + { + if ( self HasScriptGoal() ) + { + return; + } + + if ( carrier _hasperk( "specialty_coldblooded" ) ) + { + return; + } + + origin = carrier.origin; + + self SetScriptGoal( origin, 64 ); + self thread bot_escort_obj( bomb, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + // protect bomb site + origin = ( site.curorigin[ 0 ] + 50, site.curorigin[ 1 ] + 50, site.curorigin[ 2 ] + 5 ); + + self thread bot_inc_bots( site ); + + if ( site isInUse() ) // somebody is planting + { + self BotNotifyBotEvent( "sab", "start", "planter" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + self thread bot_inc_bots( site ); + + self thread bot_defend_site( site ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sab", "stop", "planter" ); + return; + } + + // else hang around the site + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + wait 4; + self notify( "bot_inc_bots" ); + site.bots--; + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 256 ); + self thread bot_inc_bots( site ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + // bomb is planted we need to defuse + origin = ( site.curorigin[ 0 ] + 50, site.curorigin[ 1 ] + 50, site.curorigin[ 2 ] + 5 ); + + // someone else is defusing, lets just hang around + if ( site.bots > 1 ) + { + if ( self HasScriptGoal() ) + { + return; + } + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_go_defuse( site ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + // lets go defuse + self BotNotifyBotEvent( "sab", "go", "defuse" ); + + self.bot_lock_goal = true; + + self SetScriptGoal( origin, 1 ); + self thread bot_inc_bots( site ); + self thread bot_go_defuse( site ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" || !level.bombplanted || site isInUse() || !self istouching( site.trigger ) || self inLastStand() || self hasThreat() ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "sab", "start", "defuse" ); + + self BotRandomStance(); + self SetScriptGoal( self.origin, 64 ); + self bot_wait_stop_move(); + + waitTime = ( site.usetime / 1000 ) + 2.5; + self thread BotPressUse( waitTime ); + wait waitTime; + + self ClearScriptGoal(); + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sab", "stop", "defuse" ); + } + else // we need to go get the bomb! + { + origin = ( bomb.curorigin[ 0 ], bomb.curorigin[ 1 ], bomb.curorigin[ 2 ] + 5 ); + + self BotNotifyBotEvent( "sab", "start", "bomb" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + + self thread bot_get_obj( bomb ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sab", "stop", "bomb" ); + return; + } +} + +/* + Bots play sab +*/ +bot_sab() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "sab" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.sabbomb ) ) + { + continue; + } + + if ( !isdefined( level.bombzones ) || !level.bombzones.size ) + { + continue; + } + + if ( self isPlanting() || self isDefusing() ) + { + continue; + } + + self bot_sab_loop(); + } +} + +/* + Bots play sd defenders +*/ +bot_sd_defenders_loop( data ) +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + // bomb not planted, lets protect our sites + if ( !level.bombplanted ) + { + timeleft = maps\mp\gametypes\_gamelogic::gettimeremaining() / 1000; + + if ( timeleft >= 90 ) + { + return; + } + + // check for a bomb carrier, and camp the bomb + if ( !level.multibomb && isdefined( level.sdbomb ) ) + { + bomb = level.sdbomb; + carrier = level.sdbomb.carrier; + + if ( !isdefined( carrier ) ) + { + origin = ( bomb.curorigin[ 0 ], bomb.curorigin[ 1 ], bomb.curorigin[ 2 ] + 5 ); + + // hang around the bomb + if ( self HasScriptGoal() ) + { + return; + } + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + + self thread bot_get_obj( bomb ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + } + + // pick a site to protect + if ( !isdefined( level.bombzones ) || !level.bombzones.size ) + { + return; + } + + sites = []; + + for ( i = 0; i < level.bombzones.size; i++ ) + { + sites[ sites.size ] = level.bombzones[ i ]; + } + + if ( !sites.size ) + { + return; + } + + if ( data.rand > 50 ) + { + site = self bot_array_nearest_curorigin( sites ); + } + else + { + site = random( sites ); + } + + if ( !isdefined( site ) ) + { + return; + } + + origin = ( site.curorigin[ 0 ] + 50, site.curorigin[ 1 ] + 50, site.curorigin[ 2 ] + 5 ); + + if ( site isInUse() ) // somebody is planting + { + self BotNotifyBotEvent( "sd", "start", "planter", site ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + + self thread bot_defend_site( site ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sd", "stop", "planter", site ); + return; + } + + // else hang around the site + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + // bomb is planted, we need to defuse + if ( !isdefined( level.defuseobject ) ) + { + return; + } + + defuse = level.defuseobject; + + if ( !isdefined( defuse.bots ) ) + { + defuse.bots = 0; + } + + origin = ( defuse.curorigin[ 0 ], defuse.curorigin[ 1 ], defuse.curorigin[ 2 ] + 5 ); + + // someone is going to go defuse ,lets just hang around + if ( defuse.bots > 1 ) + { + if ( self HasScriptGoal() ) + { + return; + } + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_go_defuse( defuse ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + // lets defuse + self BotNotifyBotEvent( "sd", "go", "defuse" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 1 ); + self thread bot_inc_bots( defuse ); + self thread bot_go_defuse( defuse ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" || !level.bombplanted || defuse isInUse() || !self istouching( defuse.trigger ) || self inLastStand() || self hasThreat() ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "sd", "start", "defuse" ); + + self BotRandomStance(); + self SetScriptGoal( self.origin, 64 ); + self bot_wait_stop_move(); + + waitTime = ( defuse.usetime / 1000 ) + 2.5; + self thread BotPressUse( waitTime ); + wait waitTime; + + self ClearScriptGoal(); + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sd", "stop", "defuse" ); +} + +/* + Bots play sd defenders +*/ +bot_sd_defenders() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "sd" ) + { + return; + } + + if ( self.team == game[ "attackers" ] ) + { + return; + } + + data = spawnstruct(); + data.rand = self BotGetRandom(); + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + if ( self isPlanting() || self isDefusing() ) + { + continue; + } + + self bot_sd_defenders_loop( data ); + } +} + +/* + Bots play sd attackers +*/ +bot_sd_attackers_loop( data ) +{ + if ( data.first ) + { + data.first = false; + } + else + { + wait( randomintrange( 3, 5 ) ); + } + + if ( self isusingremote() || self.bot_lock_goal ) + { + return; + } + + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + // bomb planted + if ( level.bombplanted ) + { + if ( !isdefined( level.defuseobject ) ) + { + return; + } + + site = level.defuseobject; + + origin = ( site.curorigin[ 0 ], site.curorigin[ 1 ], site.curorigin[ 2 ] + 5 ); + + if ( site isInUse() ) // somebody is defusing + { + self BotNotifyBotEvent( "sd", "start", "defuser" ); + + self.bot_lock_goal = true; + + self SetScriptGoal( origin, 64 ); + + self thread bot_defend_site( site ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sd", "stop", "defuser" ); + return; + } + + // else hang around the site + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + timeleft = maps\mp\gametypes\_gamelogic::gettimeremaining() / 1000; + timepassed = gettimepassed() / 1000; + + // dont have a bomb + if ( !self isBombCarrier() && !level.multibomb ) + { + if ( !isdefined( level.sdbomb ) ) + { + return; + } + + bomb = level.sdbomb; + carrier = level.sdbomb.carrier; + + // bomb is picked up + if ( isdefined( carrier ) ) + { + // escort the bomb carrier + if ( self HasScriptGoal() ) + { + return; + } + + origin = carrier.origin; + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_escort_obj( bomb, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + if ( !isdefined( bomb.bots ) ) + { + bomb.bots = 0; + } + + origin = ( bomb.curorigin[ 0 ], bomb.curorigin[ 1 ], bomb.curorigin[ 2 ] + 5 ); + + // hang around the bomb if other is going to go get it + if ( bomb.bots > 1 ) + { + if ( self HasScriptGoal() ) + { + return; + } + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + + self thread bot_get_obj( bomb ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + // go get the bomb + self BotNotifyBotEvent( "sd", "start", "bomb" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + self thread bot_inc_bots( bomb ); + self thread bot_get_obj( bomb ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sd", "stop", "bomb" ); + return; + } + + // check if to plant + if ( timepassed < 120 && timeleft >= 90 && randomint( 100 ) < 98 ) + { + return; + } + + if ( !isdefined( level.bombzones ) || !level.bombzones.size ) + { + return; + } + + sites = []; + + for ( i = 0; i < level.bombzones.size; i++ ) + { + sites[ sites.size ] = level.bombzones[ i ]; + } + + if ( !sites.size ) + { + return; + } + + if ( data.rand > 50 ) + { + plant = self bot_array_nearest_curorigin( sites ); + } + else + { + plant = random( sites ); + } + + if ( !isdefined( plant ) ) + { + return; + } + + origin = ( plant.curorigin[ 0 ] + 50, plant.curorigin[ 1 ] + 50, plant.curorigin[ 2 ] + 5 ); + + self BotNotifyBotEvent( "sd", "go", "plant", plant ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 1 ); + self thread bot_go_plant( plant ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" || level.bombplanted || plant.visibleteam == "none" || !self istouching( plant.trigger ) || self inLastStand() || self hasThreat() || plant isInUse() ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "sd", "start", "plant", plant ); + + self BotRandomStance(); + self SetScriptGoal( self.origin, 64 ); + self bot_wait_stop_move(); + + waitTime = ( plant.usetime / 1000 ) + 2.5; + self thread BotPressUse( waitTime ); + wait waitTime; + + self ClearScriptGoal(); + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "sd", "stop", "plant", plant ); +} + +/* + Bots play sd attackers +*/ +bot_sd_attackers() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "sd" ) + { + return; + } + + if ( self.team != game[ "attackers" ] ) + { + return; + } + + data = spawnstruct(); + data.rand = self BotGetRandom(); + data.first = true; + + for ( ;; ) + { + self bot_sd_attackers_loop( data ); + } +} + +/* + Bots play capture the flag +*/ +bot_cap_loop() +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + myflag = level.teamflags[ myTeam ]; + myzone = level.capzones[ myTeam ]; + + theirflag = level.teamflags[ otherTeam ]; + theirzone = level.capzones[ otherTeam ]; + + if ( !myflag maps\mp\gametypes\_gameobjects::ishome() ) + { + carrier = myflag.carrier; + + if ( !isdefined( carrier ) ) // someone doesnt has our flag + { + if ( !isdefined( theirflag.carrier ) && distancesquared( self.origin, theirflag.curorigin ) < distancesquared( self.origin, myflag.curorigin ) ) // no one has their flag and its closer + { + self BotNotifyBotEvent( "cap", "start", "their_flag", theirflag ); + + self bot_cap_get_flag( theirflag ); + + self BotNotifyBotEvent( "cap", "stop", "their_flag", theirflag ); + } + else // go get it + { + self BotNotifyBotEvent( "cap", "start", "my_flag", myflag ); + + self bot_cap_get_flag( myflag ); + + self BotNotifyBotEvent( "cap", "stop", "my_flag", myflag ); + } + + return; + } + else + { + if ( theirflag maps\mp\gametypes\_gameobjects::ishome() && randomint( 100 ) < 50 ) + { + // take their flag + self BotNotifyBotEvent( "cap", "start", "their_flag", theirflag ); + + self bot_cap_get_flag( theirflag ); + + self BotNotifyBotEvent( "cap", "stop", "their_flag", theirflag ); + } + else + { + if ( self HasScriptGoal() ) + { + return; + } + + if ( !isdefined( theirzone.bots ) ) + { + theirzone.bots = 0; + } + + origin = theirzone.curorigin; + + if ( theirzone.bots > 2 || randomint( 100 ) < 45 ) + { + // kill carrier + if ( carrier _hasperk( "specialty_coldblooded" ) ) + { + return; + } + + origin = carrier.origin; + + self SetScriptGoal( origin, 64 ); + self thread bot_escort_obj( myflag, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + self thread bot_inc_bots( theirzone ); + + // camp their zone + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + wait 4; + self notify( "bot_inc_bots" ); + theirzone.bots--; + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_inc_bots( theirzone ); + self thread bot_escort_obj( myflag, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } + } + } + else // our flag is ok + { + if ( self isFlagCarrier() ) // if have flag + { + // go cap + origin = myzone.curorigin; + + self BotNotifyBotEvent( "cap", "start", "cap" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 32 ); + + self thread bot_get_obj( myflag ); + evt = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + wait 1; + + if ( evt != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "cap", "stop", "cap" ); + return; + } + + carrier = theirflag.carrier; + + if ( !isdefined( carrier ) ) // if no one has enemy flag + { + self BotNotifyBotEvent( "cap", "start", "their_flag", theirflag ); + + self bot_cap_get_flag( theirflag ); + + self BotNotifyBotEvent( "cap", "stop", "their_flag", theirflag ); + return; + } + + // escort them + + if ( self HasScriptGoal() ) + { + return; + } + + origin = carrier.origin; + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_escort_obj( theirflag, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } +} + +/* + Bots play capture the flag +*/ +bot_cap() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "ctf" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.capzones ) ) + { + continue; + } + + if ( !isdefined( level.teamflags ) ) + { + continue; + } + + self bot_cap_loop(); + } +} + +/* + Gets the carriers ent num +*/ +getCarrierEntNum() +{ + carrierNum = -1; + + if ( isdefined( self.carrier ) ) + { + carrierNum = self.carrier getentitynumber(); + } + + return carrierNum; +} + +/* + Bots go and get the flag +*/ +bot_cap_get_flag( flag ) +{ + origin = flag.curorigin; + + // go get it + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 32 ); + + self thread bot_get_obj( flag ); + + evt = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( evt != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( evt != "goal" ) + { + self.bot_lock_goal = false; + return; + } + + self SetScriptGoal( self.origin, 64 ); + curCarrier = flag getCarrierEntNum(); + + while ( curCarrier == flag getCarrierEntNum() && self istouching( flag.trigger ) ) + { + cur = flag.curprogress; + wait 0.5; + + if ( flag.curprogress == cur ) + { + break; // some enemy is near us, kill him + } + } + + self ClearScriptGoal(); + + self.bot_lock_goal = false; +} + +/* + Bots go plant the demo bomb +*/ +bot_dem_go_plant( plant ) +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( ( plant.label == "_b" && level.bombbplanted ) || ( plant.label == "_a" && level.bombaplanted ) ) + { + break; + } + + if ( self istouching( plant.trigger ) ) + { + break; + } + } + + if ( ( plant.label == "_b" && level.bombbplanted ) || ( plant.label == "_a" && level.bombaplanted ) ) + { + self notify( "bad_path" ); + } + else + { + self notify( "goal" ); + } +} + +/* + Bots spawn kill dom attackers +*/ +bot_dem_attack_spawnkill() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + l1 = level.bombaplanted; + l2 = level.bombbplanted; + + for ( ;; ) + { + wait 0.5; + + if ( l1 != level.bombaplanted || l2 != level.bombbplanted ) + { + break; + } + } + + self notify( "bad_path" ); +} + +/* + Bots play demo attackers +*/ +bot_dem_attackers_loop() +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + bombs = []; // sites with bombs + sites = []; // sites to bomb at + bombed = 0; // exploded sites + + for ( i = 0; i < level.bombzones.size; i++ ) + { + bomb = level.bombzones[ i ]; + + if ( isdefined( bomb.bombexploded ) && bomb.bombexploded ) + { + bombed++; + continue; + } + + if ( bomb.label == "_a" ) + { + if ( level.bombaplanted ) + { + bombs[ bombs.size ] = bomb; + } + else + { + sites[ sites.size ] = bomb; + } + + continue; + } + + if ( bomb.label == "_b" ) + { + if ( level.bombbplanted ) + { + bombs[ bombs.size ] = bomb; + } + else + { + sites[ sites.size ] = bomb; + } + + continue; + } + } + + timeleft = maps\mp\gametypes\_gamelogic::gettimeremaining() / 1000; + + shouldLet = ( game[ "teamScores" ][ myTeam ] > game[ "teamScores" ][ otherTeam ] && timeleft < 90 && bombed == 1 ); + + // spawnkill conditions + // if we have bombed one site or 1 bomb is planted with lots of time left, spawn kill + // if we want the other team to win for overtime and they do not need to defuse, spawn kill + if ( ( ( bombed + bombs.size == 1 && timeleft >= 90 ) || ( shouldLet && !bombs.size ) ) && randomint( 100 ) < 95 ) + { + if ( self HasScriptGoal() ) + { + return; + } + + spawnPoints = maps\mp\gametypes\_spawnlogic::getspawnpointarray( "mp_dd_spawn_defender_start" ); + + if ( !spawnPoints.size ) + { + return; + } + + spawnpoint = maps\mp\gametypes\_spawnlogic::getspawnpoint_random( spawnPoints ); + + if ( distancesquared( spawnpoint.origin, self.origin ) <= 2048 * 2048 ) + { + return; + } + + self SetScriptGoal( spawnpoint.origin, 1024 ); + + self thread bot_dem_attack_spawnkill(); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + // let defuse conditions + // if enemy is going to lose and lots of time left, let them defuse to play longer + // or if want to go into overtime near end of the extended game + if ( ( ( bombs.size + bombed == 2 && timeleft >= 90 ) || ( shouldLet && bombs.size ) ) && randomint( 100 ) < 95 ) + { + spawnPoints = maps\mp\gametypes\_spawnlogic::getspawnpointarray( "mp_dd_spawn_attacker_start" ); + + if ( !spawnPoints.size ) + { + return; + } + + spawnpoint = maps\mp\gametypes\_spawnlogic::getspawnpoint_random( spawnPoints ); + + if ( distancesquared( spawnpoint.origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( spawnpoint.origin, 512 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + // defend bomb conditions + // if time is running out and we have a bomb planted + if ( bombs.size && timeleft < 90 && ( !sites.size || randomint( 100 ) < 95 ) ) + { + site = self bot_array_nearest_curorigin( bombs ); + origin = ( site.curorigin[ 0 ] + 50, site.curorigin[ 1 ] + 50, site.curorigin[ 2 ] + 5 ); + + if ( site isInUse() ) // somebody is defusing + { + self BotNotifyBotEvent( "dem", "start", "defuser", site ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + + self thread bot_defend_site( site ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "dem", "stop", "defuser", site ); + return; + } + + // else hang around the site + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + // else go plant + if ( !sites.size ) + { + return; + } + + plant = self bot_array_nearest_curorigin( sites ); + + if ( !isdefined( plant ) ) + { + return; + } + + if ( !isdefined( plant.bots ) ) + { + plant.bots = 0; + } + + origin = ( plant.curorigin[ 0 ] + 50, plant.curorigin[ 1 ] + 50, plant.curorigin[ 2 ] + 5 ); + + // hang around the site if lots of time left + if ( plant.bots > 1 && timeleft >= 60 ) + { + if ( self HasScriptGoal() ) + { + return; + } + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_dem_go_plant( plant ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + self BotNotifyBotEvent( "dem", "go", "plant", plant ); + + self.bot_lock_goal = true; + + self SetScriptGoal( origin, 1 ); + self thread bot_inc_bots( plant ); + self thread bot_dem_go_plant( plant ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" || ( plant.label == "_b" && level.bombbplanted ) || ( plant.label == "_a" && level.bombaplanted ) || plant isInUse() || !self istouching( plant.trigger ) || self inLastStand() || self hasThreat() ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "dem", "start", "plant", plant ); + + self BotRandomStance(); + self SetScriptGoal( self.origin, 64 ); + self bot_wait_stop_move(); + + waitTime = ( plant.usetime / 1000 ) + 2.5; + self thread BotPressUse( waitTime ); + wait waitTime; + + self ClearScriptGoal(); + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "dem", "stop", "plant", plant ); +} + +/* + Bots play demo attackers +*/ +bot_dem_attackers() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "dd" ) + { + return; + } + + if ( self.team != game[ "attackers" ] ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.bombzones ) || !level.bombzones.size ) + { + continue; + } + + self bot_dem_attackers_loop(); + } +} + +/* + Bots play demo defenders +*/ +bot_dem_defenders_loop() +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + bombs = []; // sites with bombs + sites = []; // sites to bomb at + bombed = 0; // exploded sites + + for ( i = 0; i < level.bombzones.size; i++ ) + { + bomb = level.bombzones[ i ]; + + if ( isdefined( bomb.bombexploded ) && bomb.bombexploded ) + { + bombed++; + continue; + } + + if ( bomb.label == "_a" ) + { + if ( level.bombaplanted ) + { + bombs[ bombs.size ] = bomb; + } + else + { + sites[ sites.size ] = bomb; + } + + continue; + } + + if ( bomb.label == "_b" ) + { + if ( level.bombbplanted ) + { + bombs[ bombs.size ] = bomb; + } + else + { + sites[ sites.size ] = bomb; + } + + continue; + } + } + + timeleft = maps\mp\gametypes\_gamelogic::gettimeremaining() / 1000; + + shouldLet = ( timeleft < 60 && ( ( bombed == 0 && bombs.size != 2 ) || ( game[ "teamScores" ][ myTeam ] > game[ "teamScores" ][ otherTeam ] && bombed == 1 ) ) && randomint( 100 ) < 98 ); + + // spawnkill conditions + // if nothing to defuse with a lot of time left, spawn kill + // or letting a bomb site to explode but a bomb is planted, so spawnkill + if ( ( !bombs.size && timeleft >= 60 && randomint( 100 ) < 95 ) || ( shouldLet && bombs.size == 1 ) ) + { + if ( self HasScriptGoal() ) + { + return; + } + + spawnPoints = maps\mp\gametypes\_spawnlogic::getspawnpointarray( "mp_dd_spawn_attacker_start" ); + + if ( !spawnPoints.size ) + { + return; + } + + spawnpoint = maps\mp\gametypes\_spawnlogic::getspawnpoint_random( spawnPoints ); + + if ( distancesquared( spawnpoint.origin, self.origin ) <= 2048 * 2048 ) + { + return; + } + + self SetScriptGoal( spawnpoint.origin, 1024 ); + + self thread bot_dem_defend_spawnkill(); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + // let blow up conditions + // let enemy blow up at least one to extend play time + // or if want to go into overtime after extended game + if ( shouldLet ) + { + spawnPoints = maps\mp\gametypes\_spawnlogic::getspawnpointarray( "mp_dd_spawn_defender_start" ); + + if ( !spawnPoints.size ) + { + return; + } + + spawnpoint = maps\mp\gametypes\_spawnlogic::getspawnpoint_random( spawnPoints ); + + if ( distancesquared( spawnpoint.origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( spawnpoint.origin, 512 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + // defend conditions + // if no bombs planted with little time left + if ( !bombs.size && timeleft < 60 && randomint( 100 ) < 95 && sites.size ) + { + site = self bot_array_nearest_curorigin( sites ); + origin = ( site.curorigin[ 0 ] + 50, site.curorigin[ 1 ] + 50, site.curorigin[ 2 ] + 5 ); + + if ( site isInUse() ) // somebody is planting + { + self BotNotifyBotEvent( "dem", "start", "planter", site ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + + self thread bot_defend_site( site ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "dem", "stop", "planter", site ); + return; + } + + // else hang around the site + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + // else go defuse + + if ( !bombs.size ) + { + return; + } + + defuse = self bot_array_nearest_curorigin( bombs ); + + if ( !isdefined( defuse ) ) + { + return; + } + + if ( !isdefined( defuse.bots ) ) + { + defuse.bots = 0; + } + + origin = ( defuse.curorigin[ 0 ] + 50, defuse.curorigin[ 1 ] + 50, defuse.curorigin[ 2 ] + 5 ); + + // hang around the site if not in danger of losing + if ( defuse.bots > 1 && bombed + bombs.size != 2 ) + { + if ( self HasScriptGoal() ) + { + return; + } + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + + self thread bot_dem_go_defuse( defuse ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + self BotNotifyBotEvent( "dem", "go", "defuse", defuse ); + + self.bot_lock_goal = true; + + self SetScriptGoal( origin, 1 ); + self thread bot_inc_bots( defuse ); + self thread bot_dem_go_defuse( defuse ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" || ( defuse.label == "_b" && !level.bombbplanted ) || ( defuse.label == "_a" && !level.bombaplanted ) || defuse isInUse() || !self istouching( defuse.trigger ) || self inLastStand() || self hasThreat() ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "dem", "start", "defuse", defuse ); + + self BotRandomStance(); + self SetScriptGoal( self.origin, 64 ); + self bot_wait_stop_move(); + + waitTime = ( defuse.usetime / 1000 ) + 2.5; + self thread BotPressUse( waitTime ); + wait waitTime; + + self ClearScriptGoal(); + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "dem", "stop", "defuse", defuse ); +} + +/* + Bots play demo defenders +*/ +bot_dem_defenders() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "dd" ) + { + return; + } + + if ( self.team == game[ "attackers" ] ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.bombzones ) || !level.bombzones.size ) + { + continue; + } + + self bot_dem_defenders_loop(); + } +} + +/* + Bots go defuse +*/ +bot_dem_go_defuse( defuse ) +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( self istouching( defuse.trigger ) ) + { + break; + } + + if ( ( defuse.label == "_b" && !level.bombbplanted ) || ( defuse.label == "_a" && !level.bombaplanted ) ) + { + break; + } + } + + if ( ( defuse.label == "_b" && !level.bombbplanted ) || ( defuse.label == "_a" && !level.bombaplanted ) ) + { + self notify( "bad_path" ); + } + else + { + self notify( "goal" ); + } +} + +/* + Bots go spawn kill +*/ +bot_dem_defend_spawnkill() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 0.5; + + if ( level.bombbplanted || level.bombaplanted ) + { + break; + } + } + + self notify( "bad_path" ); +} + +/* + Bots think to revive +*/ +bot_think_revive_loop() +{ + needsRevives = []; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( player.team != self.team ) + { + continue; + } + + if ( distancesquared( self.origin, player.origin ) >= 2048 * 2048 ) + { + continue; + } + + if ( player inLastStand() ) + { + needsRevives[ needsRevives.size ] = player; + } + } + + if ( !needsRevives.size ) + { + return; + } + + revive = random( needsRevives ); + + self BotNotifyBotEvent( "revive", "go", revive ); + self.bot_lock_goal = true; + + self SetScriptGoal( revive.origin, 64 ); + self thread stop_go_target_on_death( revive ); + + ret = self waittill_any_return( "new_goal", "goal", "bad_path" ); + + if ( ret != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + if ( ret != "goal" || !isdefined( revive ) || distancesquared( self.origin, revive.origin ) >= 100 * 100 || !revive inLastStand() || revive isBeingRevived() || !isalive( revive ) ) + { + return; + } + + self BotNotifyBotEvent( "revive", "start", revive ); + + self BotFreezeControls( true ); + self bot_wait_stop_move(); + + waitTime = 3.25; + self thread BotPressUse( waitTime ); + wait waitTime; + + self BotFreezeControls( false ); + + self BotNotifyBotEvent( "revive", "stop", revive ); +} + +/* + Bots think to revive +*/ +bot_think_revive() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( !level.diehardmode || !level.teambased ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 1, 3 ) ); + + if ( self HasScriptGoal() || self.bot_lock_goal ) + { + continue; + } + + if ( self isDefusing() || self isPlanting() ) + { + continue; + } + + if ( self isusingremote() || self BotIsFrozen() ) + { + continue; + } + + if ( self inLastStand() ) + { + continue; + } + + self bot_think_revive_loop(); + } +} + +/* + Bots play the Global thermonuclear warfare +*/ +bot_gtnw_loop() +{ + myTeam = self.team; + theirteam = getotherteam( myTeam ); + origin = level.nukesite.trigger.origin; + trigger = level.nukesite.trigger; + + ourCapCount = level.nukesite.touchlist[ myTeam ]; + theirCapCount = level.nukesite.touchlist[ theirteam ]; + rand = self BotGetRandom(); + + if ( ( !ourCapCount && !theirCapCount ) || rand <= 20 ) + { + // go cap the obj + self BotNotifyBotEvent( "gtnw", "go", "cap" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 64 ); + self thread bots_watch_touch_obj( trigger ); + + ret = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( ret != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( ret != "goal" || !self istouching( trigger ) ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "gtnw", "start", "cap" ); + + self SetScriptGoal( self.origin, 64 ); + + while ( self istouching( trigger ) ) + { + cur = level.nukesite.curprogress; + wait 0.5; + + if ( cur == level.nukesite.curprogress ) + { + break; // no prog made, enemy must be capping + } + + self thread bot_do_random_action_for_objective( trigger ); + } + + self ClearScriptGoal(); + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "gtnw", "stop", "cap" ); + return; + } + + if ( theirCapCount ) + { + // kill capturtour + self.bot_lock_goal = true; + + self SetScriptGoal( origin, 64 ); + self thread bots_watch_touch_obj( trigger ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + return; + } + + // else hang around the site + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; +} + +/* + Bots play the Global thermonuclear warfare +*/ +bot_gtnw() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "gtnw" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.nukesite ) || !isdefined( level.nukesite.trigger ) ) + { + continue; + } + + self bot_gtnw_loop(); + } +} + +/* + Bots play oneflag +*/ +bot_oneflag_loop() +{ + myTeam = self.pers[ "team" ]; + otherTeam = getotherteam( myTeam ); + + if ( myTeam == game[ "attackers" ] ) + { + myzone = level.capzones[ myTeam ]; + theirflag = level.teamflags[ otherTeam ]; + + if ( self isFlagCarrier() ) + { + // go cap + origin = myzone.curorigin; + + self BotNotifyBotEvent( "oneflag", "start", "cap" ); + + self.bot_lock_goal = true; + self SetScriptGoal( origin, 32 ); + + evt = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + wait 1; + + if ( evt != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "oneflag", "stop", "cap" ); + return; + } + + carrier = theirflag.carrier; + + if ( !isdefined( carrier ) ) // if no one has enemy flag + { + self BotNotifyBotEvent( "oneflag", "start", "their_flag" ); + self bot_cap_get_flag( theirflag ); + self BotNotifyBotEvent( "oneflag", "stop", "their_flag" ); + return; + } + + // escort them + + if ( self HasScriptGoal() ) + { + return; + } + + origin = carrier.origin; + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_escort_obj( theirflag, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } + else + { + myflag = level.teamflags[ myTeam ]; + theirzone = level.capzones[ otherTeam ]; + + if ( !myflag maps\mp\gametypes\_gameobjects::ishome() ) + { + carrier = myflag.carrier; + + if ( !isdefined( carrier ) ) // someone doesnt has our flag + { + self BotNotifyBotEvent( "oneflag", "start", "my_flag" ); + self bot_cap_get_flag( myflag ); + self BotNotifyBotEvent( "oneflag", "stop", "my_flag" ); + return; + } + + if ( self HasScriptGoal() ) + { + return; + } + + if ( !isdefined( theirzone.bots ) ) + { + theirzone.bots = 0; + } + + origin = theirzone.curorigin; + + if ( theirzone.bots > 2 || randomint( 100 ) < 45 ) + { + // kill carrier + if ( carrier _hasperk( "specialty_coldblooded" ) ) + { + return; + } + + origin = carrier.origin; + + self SetScriptGoal( origin, 64 ); + self thread bot_escort_obj( myflag, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + + return; + } + + self thread bot_inc_bots( theirzone ); + + // camp their zone + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + wait 4; + self notify( "bot_inc_bots" ); + theirzone.bots--; + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_inc_bots( theirzone ); + self thread bot_escort_obj( myflag, carrier ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } + else + { + // is home, lets hang around and protect + if ( self HasScriptGoal() ) + { + return; + } + + origin = myflag.curorigin; + + if ( distancesquared( origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( origin, 256 ); + self thread bot_get_obj( myflag ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } + } +} + +/* + Bots play oneflag +*/ +bot_oneflag() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "oneflag" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.capzones ) || !isdefined( level.teamflags ) ) + { + continue; + } + + self bot_oneflag_loop(); + } +} + +/* + Bots play arena +*/ +bot_arena_loop() +{ + flag = level.arenaflag; + myTeam = self.team; + + self BotNotifyBotEvent( "arena", "go", "cap" ); + + self.bot_lock_goal = true; + self SetScriptGoal( flag.trigger.origin, 64 ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + { + self ClearScriptGoal(); + } + + if ( event != "goal" || !self istouching( flag.trigger ) ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "arena", "start", "cap" ); + + self SetScriptGoal( self.origin, 64 ); + + while ( self istouching( flag.trigger ) && flag.ownerteam != myTeam ) + { + cur = flag.curprogress; + wait 0.5; + + if ( cur == flag.curprogress ) + { + break; // no prog made, enemy must be capping + } + + self thread bot_do_random_action_for_objective( flag.trigger ); + } + + self ClearScriptGoal(); + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "arena", "stop", "cap" ); +} + +/* + Bots play arena +*/ +bot_arena() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "arena" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + if ( !isdefined( level.arenaflag ) ) + { + continue; + } + + self bot_arena_loop(); + } +} + +/* + bot_vip_loop + + For those wondering why i call a function for these loops like this + its because, the variables created in this function will be free'd once the function exits, + if it was in the infinite loop, the function never exits, thus the variables are never free'd + + This isnt leaking variables, but freeing variables that will no longer be used, an optimization of sorts +*/ +bot_vip_loop() +{ + vip = undefined; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( !isreallyalive( player ) ) + { + continue; + } + + if ( isdefined( player.isvip ) && player.isvip ) + { + vip = player; + } + } + + if ( self.team == game[ "defenders" ] ) + { + if ( isdefined( self.isvip ) && self.isvip ) + { + if ( isdefined( level.extractionzone ) && !isdefined( level.extractiontime ) ) + { + // go to extraction zone + self BotNotifyBotEvent( "vip", "start", "cap" ); + + self.bot_lock_goal = true; + self SetScriptGoal( level.extractionzone.trigger.origin, 32 ); + + evt = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + wait 1; + + if ( evt != "new_goal" ) + { + self ClearScriptGoal(); + } + + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "vip", "stop", "cap" ); + } + } + else if ( isdefined( vip ) ) + { + // protect the vip + if ( distancesquared( vip.origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( vip.origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } + } + else + { + if ( isdefined( level.extractionzone ) && !isdefined( level.extractiontime ) && self BotGetRandom() < 65 ) + { + // camp the extraction zone + if ( distancesquared( level.extractionzone.trigger.origin, self.origin ) <= 1024 * 1024 ) + { + return; + } + + self SetScriptGoal( level.extractionzone.trigger.origin, 256 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } + else if ( isdefined( vip ) ) + { + // kill the vip + self SetScriptGoal( vip.origin, 32 ); + + if ( self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal" ) + { + self ClearScriptGoal(); + } + } + } +} + +/* + Bots play arena +*/ +bot_vip() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + if ( level.gametype != "vip" ) + { + return; + } + + for ( ;; ) + { + wait( randomintrange( 3, 5 ) ); + + if ( self isusingremote() || self.bot_lock_goal ) + { + continue; + } + + self bot_vip_loop(); + } +} diff --git a/maps/mp/bots/_bot_utility.gsc b/maps/mp/bots/_bot_utility.gsc new file mode 100644 index 0000000..72dae8a --- /dev/null +++ b/maps/mp/bots/_bot_utility.gsc @@ -0,0 +1,3537 @@ +/* + _bot_utility + Author: INeedGames + Date: 09/26/2020 + The shared functions for bots + + Notes for engine + obv the old bot behavior should be changed to use gsc built-ins to control bots + bots using objects needs to be fixed + setSpawnWeapon and switchtoweapon should be modified to work for testclients +*/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; + +/* + Waits for the built-ins to be defined +*/ +wait_for_builtins() +{ + for ( i = 0; i < 20; i++ ) + { + if ( isdefined( level.bot_builtins ) ) + { + return true; + } + + if ( i < 18 ) + { + waittillframeend; + } + else + { + wait 0.05; + } + } + + return false; +} + +/* + Prints to console without dev script on +*/ +BotBuiltinPrintConsole( s ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "printconsole" ] ) ) + { + [[ level.bot_builtins[ "printconsole" ] ]]( s ); + } +} + +/* + Writes to the file, mode can be "append" or "write" +*/ +BotBuiltinFileWrite( file, contents, mode ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "filewrite" ] ) ) + { + [[ level.bot_builtins[ "filewrite" ] ]]( file, contents, mode ); + } +} + +/* + Returns the whole file as a string +*/ +BotBuiltinFileRead( file ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "fileread" ] ) ) + { + return [[ level.bot_builtins[ "fileread" ] ]]( file ); + } + + return undefined; +} + +/* + Test if a file exists +*/ +BotBuiltinFileExists( file ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "fileexists" ] ) ) + { + return [[ level.bot_builtins[ "fileexists" ] ]]( file ); + } + + return false; +} + +/* + Bot action, does a bot action + botaction() +*/ +BotBuiltinBotAction( action ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "botaction" ] ) ) + { + self [[ level.bot_builtins[ "botaction" ] ]]( action ); + } +} + +/* + Clears the bot from movement and actions + botstop() +*/ +BotBuiltinBotStop() +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "botstop" ] ) ) + { + self [[ level.bot_builtins[ "botstop" ] ]](); + } +} + +/* + Sets the bot's movement + botmovement(, ) +*/ +BotBuiltinBotMovement( forward, right ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "botmovement" ] ) ) + { + self [[ level.bot_builtins[ "botmovement" ] ]]( forward, right ); + } +} + +/* + Sets melee params +*/ +BotBuiltinBotMeleeParams( yaw, dist ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "botmeleeparams" ] ) ) + { + self [[ level.bot_builtins[ "botmeleeparams" ] ]]( yaw, dist ); + } +} + +/* + Sets remote angles +*/ +BotBuiltinBotRemoteAngles( pitch, yaw ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "botremoteangles" ] ) ) + { + self [[ level.bot_builtins[ "botremoteangles" ] ]]( pitch, yaw ); + } +} + +/* + Sets angles +*/ +BotBuiltinBotAngles( angles ) +{ + if ( isdefined( level.bot_builtins ) && isdefined( level.bot_builtins[ "botangles" ] ) ) + { + self [[ level.bot_builtins[ "botangles" ] ]]( angles ); + } +} + +/* + Returns if player is the host +*/ +is_host() +{ + return ( isdefined( self.pers[ "bot_host" ] ) && self.pers[ "bot_host" ] ); +} + +/* + Setups the host variable on the player +*/ +doHostCheck() +{ + self.pers[ "bot_host" ] = false; + + if ( self is_bot() ) + { + return; + } + + result = false; + + if ( getdvar( "bots_main_firstIsHost" ) != "0" ) + { + BotBuiltinPrintConsole( "WARNING: bots_main_firstIsHost is enabled" ); + + if ( getdvar( "bots_main_firstIsHost" ) == "1" ) + { + setdvar( "bots_main_firstIsHost", self getguid() ); + } + + if ( getdvar( "bots_main_firstIsHost" ) == self getguid() + "" ) + { + result = true; + } + } + + DvarGUID = getdvar( "bots_main_GUIDs" ); + + if ( DvarGUID != "" ) + { + guids = strtok( DvarGUID, "," ); + + for ( i = 0; i < guids.size; i++ ) + { + if ( self getguid() + "" == guids[ i ] ) + { + result = true; + } + } + } + + if ( !self ishost() && !result ) + { + return; + } + + self.pers[ "bot_host" ] = true; +} + +/* + Returns if the player is a bot. +*/ +is_bot() +{ + assert( isdefined( self ) ); + assert( isplayer( self ) ); + + return ( ( isdefined( self.pers[ "isBot" ] ) && self.pers[ "isBot" ] ) || ( isdefined( self.pers[ "isBotWarfare" ] ) && self.pers[ "isBotWarfare" ] ) || issubstr( self getguid() + "", "bot" ) ); +} + +/* + Set the bot's stance +*/ +BotSetStance( stance ) +{ + switch ( stance ) + { + case "stand": + self maps\mp\bots\_bot_internal::stand(); + break; + + case "crouch": + self maps\mp\bots\_bot_internal::crouch(); + break; + + case "prone": + self maps\mp\bots\_bot_internal::prone(); + break; + } +} + +/* + Bot presses the frag button for time. +*/ +BotPressFrag( time ) +{ + self maps\mp\bots\_bot_internal::frag( time ); +} + +/* + Bot presses the smoke button for time. +*/ +BotPressSmoke( time ) +{ + self maps\mp\bots\_bot_internal::smoke( time ); +} + +/* + Bot will press the ads button for the time +*/ +BotpressADS( time ) +{ + self maps\mp\bots\_bot_internal::pressADS( time ); +} + +/* + Bot presses the use button for time. +*/ +BotPressUse( time ) +{ + self maps\mp\bots\_bot_internal::use( time ); +} + +/* + Bots will press the attack button for a time +*/ +BotPressAttack( time ) +{ + self maps\mp\bots\_bot_internal::pressFire( time ); +} + +/* + Returns a random number thats different everytime it changes target +*/ +BotGetTargetRandom() +{ + if ( !isdefined( self.bot.target ) ) + { + return undefined; + } + + return self.bot.target.rand; +} + +/* + Returns the bot's random assigned number. +*/ +BotGetRandom() +{ + return self.bot.rand; +} + +/* + Returns if the bot is pressing frag button. +*/ +IsBotFragging() +{ + return self.bot.isfraggingafter; +} + +/* + Returns if the bot is pressing smoke button. +*/ +IsBotSmoking() +{ + return self.bot.issmokingafter; +} + +/* + Returns if the bot is sprinting. +*/ +IsBotSprinting() +{ + return self.bot.issprinting; +} + +/* + Returns if the bot is reloading. +*/ +IsBotReloading() +{ + return self.bot.isreloading; +} + +/* + Is bot knifing +*/ +IsBotKnifing() +{ + return self.bot.isknifingafter; +} + +/* + Freezes the bot's controls. +*/ +BotFreezeControls( what ) +{ + self.bot.isfrozen = what; + + if ( what ) + { + self notify( "kill_goal" ); + } +} + +/* + Returns if the bot is script frozen. +*/ +BotIsFrozen() +{ + return self.bot.isfrozen; +} + +/* + Bot will stop moving +*/ +BotStopMoving( what ) +{ + self.bot.stop_move = what; + + if ( what ) + { + self notify( "kill_goal" ); + } +} + +/* + Waits till frame end so that if two notifies happen in the same frame, the other will not be missed. +*/ +BotNotifyBotEvent_( msg, a, b, c, d, e, f, g ) +{ + self endon( "disconnect" ); + waittillframeend; // wait for the waittills to setup again + self notify( "bot_event", msg, a, b, c, d, e, f, g ); +} + +/* + Notify the bot chat message +*/ +BotNotifyBotEvent( msg, a, b, c, d, e, f, g ) +{ + self thread BotNotifyBotEvent_( msg, a, b, c, d, e, f, g ); +} + +/* + Returns if the bot has a script goal. + (like t5 gsc bot) +*/ +HasScriptGoal() +{ + return ( isdefined( self GetScriptGoal() ) ); +} + +/* + Sets the bot's goal, will acheive it when dist away from it. +*/ +SetScriptGoal( goal, dist ) +{ + if ( !isdefined( dist ) ) + { + dist = 16; + } + + self.bot.script_goal = goal; + self.bot.script_goal_dist = dist; + waittillframeend; + self notify( "new_goal_internal" ); + self notify( "new_goal" ); +} + +/* + Returns the pos of the bot's goal +*/ +GetScriptGoal() +{ + return self.bot.script_goal; +} + +/* + Clears the bot's goal. +*/ +ClearScriptGoal() +{ + self SetScriptGoal( undefined, 0 ); +} + +/* + Returns the location of the bot's javelin target +*/ +HasBotJavelinLocation() +{ + return isdefined( self.bot.jav_loc ); +} + +/* + Returns whether the bot has a priority objective +*/ +HasPriorityObjective() +{ + return self.bot.prio_objective; +} + +/* + Sets the bot to prioritize the objective over targeting enemies +*/ +SetPriorityObjective() +{ + self.bot.prio_objective = true; + self notify( "kill_goal" ); +} + +/* + Clears the bot's priority objective to allow the bot to target enemies automatically again +*/ +ClearPriorityObjective() +{ + self.bot.prio_objective = false; + self notify( "kill_goal" ); +} + +/* + Sets the aim position of the bot +*/ +SetScriptAimPos( pos ) +{ + self.bot.script_aimpos = pos; +} + +/* + Clears the aim position of the bot +*/ +ClearScriptAimPos() +{ + self SetScriptAimPos( undefined ); +} + +/* + Returns the aim position of the bot +*/ +GetScriptAimPos() +{ + return self.bot.script_aimpos; +} + +/* + Returns if the bot has a aim pos +*/ +HasScriptAimPos() +{ + return isdefined( self GetScriptAimPos() ); +} + +/* + Sets the bot's javelin target location +*/ +SetBotJavelinLocation( loc ) +{ + self.bot.jav_loc = loc; + self notify( "new_enemy" ); +} + +/* + Clears the bot's javelin location +*/ +ClearBotJavelinLocation() +{ + self SetBotJavelinLocation( undefined ); +} + +/* + Sets the bot's target to be this ent. +*/ +setAttacker( att ) +{ + self.bot.target_this_frame = att; +} + +/* + Sets the script enemy for a bot. +*/ +SetScriptEnemy( enemy, offset ) +{ + self.bot.script_target = enemy; + self.bot.script_target_offset = offset; +} + +/* + Removes the script enemy of the bot. +*/ +ClearScriptEnemy() +{ + self SetScriptEnemy( undefined, undefined ); +} + +/* + Returns the entity of the bot's target. +*/ +getThreat() +{ + if ( !isdefined( self.bot.target ) ) + { + return undefined; + } + + return self.bot.target.entity; +} + +/* + Returns if the bot has a script enemy. +*/ +HasScriptEnemy() +{ + return ( isdefined( self.bot.script_target ) ); +} + +/* + Returns if the bot has a threat. +*/ +hasThreat() +{ + return ( isdefined( self getThreat() ) ); +} + +/* + If the player is defusing +*/ +isDefusing() +{ + return ( isdefined( self.isdefusing ) && self.isdefusing ); +} + +/* + If the play is planting +*/ +isPlanting() +{ + return ( isdefined( self.isplanting ) && self.isplanting ); +} + +/* + If the player is carrying a bomb +*/ +isBombCarrier() +{ + return ( isdefined( self.isbombcarrier ) && self.isbombcarrier ); +} + +/* + If the site is in use +*/ +isInUse() +{ + return ( isdefined( self.inuse ) && self.inuse ); +} + +/* + If the player is in laststand +*/ +inLastStand() +{ + return ( isdefined( self.laststand ) && self.laststand ); +} + +/* + Is being revived +*/ +isBeingRevived() +{ + return ( isdefined( self.beingrevived ) && self.beingrevived ); +} + +/* + If the player is in final stand +*/ +inFinalStand() +{ + return ( isdefined( self.infinalstand ) && self.infinalstand ); +} + +/* + If the player is the flag carrier +*/ +isFlagCarrier() +{ + return ( isdefined( self.carryflag ) && self.carryflag ); +} + +/* + Returns if we are stunned. +*/ +isStunned() +{ + return ( isdefined( self.concussionendtime ) && self.concussionendtime > gettime() ); +} + +/* + Returns if we are beingArtilleryShellshocked +*/ +isArtShocked() +{ + return ( isdefined( self.beingartilleryshellshocked ) && self.beingartilleryshellshocked ); +} + +/* + Returns a valid grenade launcher weapon +*/ +getValidTube() +{ + weaps = self getweaponslistall(); + + for ( i = 0; i < weaps.size; i++ ) + { + weap = weaps[ i ]; + + if ( !self getammocount( weap ) ) + { + continue; + } + + if ( ( issubstr( weap, "gl_" ) && !issubstr( weap, "_gl_" ) ) || weap == "m79_mp" ) + { + return weap; + } + } + + return undefined; +} + +/* + iw5 +*/ +allowClassChoiceUtil() +{ + entry = tablelookup( "mp/gametypesTable.csv", 0, level.gametype, 4 ); + + if ( !isdefined( entry ) || entry == "" ) + { + return true; + } + + return int( entry ); +} + +/* + iw5 +*/ +allowTeamChoiceUtil() +{ + entry = tablelookup( "mp/gametypesTable.csv", 0, level.gametype, 5 ); + + if ( !isdefined( entry ) || entry == "" ) + { + return true; + } + + return int( entry ); +} + +/* + helper +*/ +waittill_either_return_( str1, str2 ) +{ + self endon( str1 ); + self waittill( str2 ); + return true; +} + +/* + Returns which string gets notified first +*/ +waittill_either_return( str1, str2 ) +{ + if ( !isdefined( self waittill_either_return_( str1, str2 ) ) ) + { + return str1; + } + + return str2; +} + +/* + Returns a random grenade in the bot's inventory. +*/ +getValidGrenade() +{ + grenadeTypes = []; + grenadeTypes[ grenadeTypes.size ] = "frag_grenade_mp"; + grenadeTypes[ grenadeTypes.size ] = "smoke_grenade_mp"; + grenadeTypes[ grenadeTypes.size ] = "flash_grenade_mp"; + grenadeTypes[ grenadeTypes.size ] = "concussion_grenade_mp"; + grenadeTypes[ grenadeTypes.size ] = "semtex_mp"; + grenadeTypes[ grenadeTypes.size ] = "throwingknife_mp"; + + possibles = []; + + for ( i = 0; i < grenadeTypes.size; i++ ) + { + if ( !self hasweapon( grenadeTypes[ i ] ) ) + { + continue; + } + + if ( !self getammocount( grenadeTypes[ i ] ) ) + { + continue; + } + + possibles[ possibles.size ] = grenadeTypes[ i ]; + } + + return random( possibles ); +} + +/* + If the weapon is not a script weapon (bomb, killstreak, etc, grenades) +*/ +isWeaponPrimary( weap ) +{ + return ( maps\mp\gametypes\_weapons::isprimaryweapon( weap ) || maps\mp\gametypes\_weapons::isaltmodeweapon( weap ) ); +} + +/* + If the ent is a vehicle +*/ +entIsVehicle( ent ) +{ + return ( ent.classname == "script_vehicle" || ent.model == "vehicle_uav_static_mp" || ent.model == "vehicle_ac130_coop" ); +} + +/* + Returns if the given weapon is full auto. +*/ +WeaponIsFullAuto( weap ) +{ + weaptoks = strtok( weap, "_" ); + + assert( isdefined( weaptoks[ 0 ] ) ); + assert( isstring( weaptoks[ 0 ] ) ); + + return isdefined( level.bots_fullautoguns[ weaptoks[ 0 ] ] ); +} + +/* + If weap is a secondary gnade +*/ +isSecondaryGrenade( gnade ) +{ + return ( gnade == "concussion_grenade_mp" || gnade == "flash_grenade_mp" || gnade == "smoke_grenade_mp" ); +} + +/* + If the weapon is allowed to be dropped +*/ +isWeaponDroppable( weap ) +{ + return ( maps\mp\gametypes\_weapons::maydropweapon( weap ) ); +} + +/* + Does a notify after a delay +*/ +notifyAfterDelay( delay, not ) +{ + wait delay; + self notify( not ); +} + +/* + Returns a bot to be kicked +*/ +getBotToKick() +{ + bots = getBotArray(); + + if ( !isdefined( bots ) || !isdefined( bots.size ) || bots.size <= 0 || !isdefined( bots[ 0 ] ) ) + { + return undefined; + } + + tokick = undefined; + axis = 0; + allies = 0; + team = getdvar( "bots_team" ); + + // count teams + for ( i = 0; i < bots.size; i++ ) + { + bot = bots[ i ]; + + if ( !isdefined( bot ) || !isdefined( bot.team ) ) + { + continue; + } + + if ( bot.team == "allies" ) + { + allies++; + } + else if ( bot.team == "axis" ) + { + axis++; + } + else // choose bots that are not on a team first + { + return bot; + } + } + + // search for a bot on the other team + if ( team == "custom" || team == "axis" ) + { + team = "allies"; + } + else if ( team == "autoassign" ) + { + // get the team with the most bots + team = "allies"; + + if ( axis > allies ) + { + team = "axis"; + } + } + else + { + team = "axis"; + } + + // get the bot on this team with lowest skill + for ( i = 0; i < bots.size; i++ ) + { + bot = bots[ i ]; + + if ( !isdefined( bot ) || !isdefined( bot.team ) ) + { + continue; + } + + if ( bot.team != team ) + { + continue; + } + + if ( !isdefined( bot.pers ) || !isdefined( bot.pers[ "bots" ] ) || !isdefined( bot.pers[ "bots" ][ "skill" ] ) || !isdefined( bot.pers[ "bots" ][ "skill" ][ "base" ] ) ) + { + continue; + } + + if ( isdefined( tokick ) && bot.pers[ "bots" ][ "skill" ][ "base" ] > tokick.pers[ "bots" ][ "skill" ][ "base" ] ) + { + continue; + } + + tokick = bot; + } + + if ( isdefined( tokick ) ) + { + return tokick; + } + + // just kick lowest skill + for ( i = 0; i < bots.size; i++ ) + { + bot = bots[ i ]; + + if ( !isdefined( bot ) || !isdefined( bot.team ) ) + { + continue; + } + + if ( !isdefined( bot.pers ) || !isdefined( bot.pers[ "bots" ] ) || !isdefined( bot.pers[ "bots" ][ "skill" ] ) || !isdefined( bot.pers[ "bots" ][ "skill" ][ "base" ] ) ) + { + continue; + } + + if ( isdefined( tokick ) && bot.pers[ "bots" ][ "skill" ][ "base" ] > tokick.pers[ "bots" ][ "skill" ][ "base" ] ) + { + continue; + } + + tokick = bot; + } + + return tokick; +} + +/* + Gets a player who is host +*/ +GetHostPlayer() +{ + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[ i ]; + + if ( !player is_host() ) + { + continue; + } + + return player; + } + + return undefined; +} + +/* + Waits for a host player +*/ +bot_wait_for_host() +{ + host = undefined; + + while ( !isdefined( level ) || !isdefined( level.players ) ) + { + wait 0.05; + } + + for ( i = getdvarfloat( "bots_main_waitForHostTime" ); i > 0; i -= 0.05 ) + { + host = GetHostPlayer(); + + if ( isdefined( host ) ) + { + break; + } + + wait 0.05; + } + + if ( !isdefined( host ) ) + { + return; + } + + for ( i = getdvarfloat( "bots_main_waitForHostTime" ); i > 0; i -= 0.05 ) + { + if ( isdefined( host.pers[ "team" ] ) ) + { + break; + } + + wait 0.05; + } + + if ( !isdefined( host.pers[ "team" ] ) ) + { + return; + } + + for ( i = getdvarfloat( "bots_main_waitForHostTime" ); i > 0; i -= 0.05 ) + { + if ( host.pers[ "team" ] == "allies" || host.pers[ "team" ] == "axis" ) + { + break; + } + + wait 0.05; + } +} + +/* + Pezbot's line sphere intersection. + http://paulbourke.net/geometry/circlesphere/raysphere.c +*/ +RaySphereIntersect( start, end, spherePos, radius ) +{ + // check if the start or end points are in the sphere + r2 = radius * radius; + + if ( distancesquared( start, spherePos ) < r2 ) + { + return true; + } + + if ( distancesquared( end, spherePos ) < r2 ) + { + return true; + } + + // check if the line made by start and end intersect the sphere + dp = end - start; + a = dp[ 0 ] * dp[ 0 ] + dp[ 1 ] * dp[ 1 ] + dp[ 2 ] * dp[ 2 ]; + b = 2 * ( dp[ 0 ] * ( start[ 0 ] - spherePos[ 0 ] ) + dp[ 1 ] * ( start[ 1 ] - spherePos[ 1 ] ) + dp[ 2 ] * ( start[ 2 ] - spherePos[ 2 ] ) ); + c = spherePos[ 0 ] * spherePos[ 0 ] + spherePos[ 1 ] * spherePos[ 1 ] + spherePos[ 2 ] * spherePos[ 2 ]; + c += start[ 0 ] * start[ 0 ] + start[ 1 ] * start[ 1 ] + start[ 2 ] * start[ 2 ]; + c -= 2.0 * ( spherePos[ 0 ] * start[ 0 ] + spherePos[ 1 ] * start[ 1 ] + spherePos[ 2 ] * start[ 2 ] ); + c -= radius * radius; + bb4ac = b * b - 4.0 * a * c; + + if ( abs( a ) < 0.0001 || bb4ac < 0 ) + { + return false; + } + + mu1 = ( 0 - b + sqrt( bb4ac ) ) / ( 2 * a ); + // mu2 = (0-b - sqrt(bb4ac)) / (2 * a); + + // intersection points of the sphere + ip1 = start + mu1 * dp; + // ip2 = start + mu2 * dp; + + myDist = distancesquared( start, end ); + + // check if both intersection points far + if ( distancesquared( start, ip1 ) > myDist/* && distancesquared(start, ip2) > myDist*/ ) + { + return false; + } + + dpAngles = vectortoangles( dp ); + + // check if the point is behind us + if ( getConeDot( ip1, start, dpAngles ) < 0/* || getConeDot(ip2, start, dpAngles) < 0*/ ) + { + return false; + } + + return true; +} + +/* + Returns if a smoke grenade would intersect start to end line. +*/ +SmokeTrace( start, end, rad ) +{ + for ( i = level.bots_smokelist.count - 1; i >= 0; i-- ) + { + nade = level.bots_smokelist.data[ i ]; + + if ( nade.state != "smoking" ) + { + continue; + } + + if ( !RaySphereIntersect( start, end, nade.origin, rad ) ) + { + continue; + } + + return false; + } + + return true; +} + +/* + Returns the cone dot (like fov, or distance from the center of our screen). +*/ +getConeDot( to, from, dir ) +{ + dirToTarget = vectornormalize( to - from ); + forward = anglestoforward( dir ); + return vectordot( dirToTarget, forward ); +} + +/* + Returns the distance squared in a 2d space +*/ +distancesquared2D( to, from ) +{ + to = ( to[ 0 ], to[ 1 ], 0 ); + from = ( from[ 0 ], from[ 1 ], 0 ); + + return distancesquared( to, from ); +} + +/* + Rounds to the nearest whole number. +*/ +Round( x ) +{ + y = int( x ); + + if ( abs( x ) - abs( y ) > 0.5 ) + { + if ( x < 0 ) + { + return y - 1; + } + else + { + return y + 1; + } + } + else + { + return y; + } +} + +/* + converts a string into a float +*/ +float_old( num ) +{ + setdvar( "temp_dvar_bot_util", num ); + + return getdvarfloat( "temp_dvar_bot_util" ); +} + +/* + If the string starts with +*/ +isStrStart( string1, subStr ) +{ + return ( getsubstr( string1, 0, subStr.size ) == subStr ); +} + +/* + Parses tokens into a waypoint obj +*/ +parseTokensIntoWaypoint( tokens ) +{ + waypoint = spawnstruct(); + + orgStr = tokens[ 0 ]; + orgToks = strtok( orgStr, " " ); + waypoint.origin = ( float_old( orgToks[ 0 ] ), float_old( orgToks[ 1 ] ), float_old( orgToks[ 2 ] ) ); + + childStr = tokens[ 1 ]; + childToks = strtok( childStr, " " ); + waypoint.children = []; + + for ( j = 0; j < childToks.size; j++ ) + { + waypoint.children[ j ] = int( childToks[ j ] ); + } + + type = tokens[ 2 ]; + waypoint.type = type; + + anglesStr = tokens[ 3 ]; + + if ( isdefined( anglesStr ) && anglesStr != "" ) + { + anglesToks = strtok( anglesStr, " " ); + + if ( anglesToks.size >= 3 ) + { + waypoint.angles = ( float_old( anglesToks[ 0 ] ), float_old( anglesToks[ 1 ] ), float_old( anglesToks[ 2 ] ) ); + } + } + + javStr = tokens[ 4 ]; + + if ( isdefined( javStr ) && javStr != "" ) + { + javToks = strtok( javStr, " " ); + + if ( javToks.size >= 3 ) + { + waypoint.jav_point = ( float_old( javToks[ 0 ] ), float_old( javToks[ 1 ] ), float_old( javToks[ 2 ] ) ); + } + } + + return waypoint; +} + +/* + Function to extract lines from a file specified by 'filename' and store them in a result structure. +*/ +getWaypointLinesFromFile( filename ) +{ + // Create a structure to store the result, including an array to hold individual lines. + result = spawnstruct(); + result.lines = []; + + // Read the entire content of the file into the 'waypointStr' variable. + // Note: max string length in GSC is 65535. + waypointStr = BotBuiltinFileRead( filename ); + + // If the file is empty or not defined, return the empty result structure. + if ( !isdefined( waypointStr ) ) + { + return result; + } + + // Variables to track the current line's character count and starting position. + linecount = 0; + linestart = 0; + + // Iterate through each character in the 'waypointStr'. + for ( i = 0; i < waypointStr.size; i++ ) + { + // Check for newline characters '\n' or '\r'. + if ( waypointStr[ i ] == "\n" || waypointStr[ i ] == "\r" ) + { + // Extract the current line using 'getsubstr' and store it in the result array. + result.lines[ result.lines.size ] = getsubstr( waypointStr, linestart, linestart + linecount ); + + // If the newline is '\r\n', skip the next character. + if ( waypointStr[ i ] == "\r" && i < waypointStr.size - 1 && waypointStr[ i + 1 ] == "\n" ) + { + i++; + } + + // Reset linecount and update linestart for the next line. + linecount = 0; + linestart = i + 1; + continue; + } + + // Increment linecount for the current line. + linecount++; + } + + // Store the last line (or the only line if there are no newline characters) in the result array. + result.lines[ result.lines.size ] = getsubstr( waypointStr, linestart, linestart + linecount ); + + // Return the result structure containing the array of extracted lines. + return result; +} + +/* + Loads waypoints from file +*/ +readWpsFromFile( mapname ) +{ + waypoints = []; + filename = "waypoints/" + mapname + "_wp.csv"; + + if ( !BotBuiltinFileExists( filename ) ) + { + return waypoints; + } + + res = getWaypointLinesFromFile( filename ); + + if ( !res.lines.size ) + { + return waypoints; + } + + BotBuiltinPrintConsole( "Attempting to read waypoints from " + filename ); + + waypointCount = int( res.lines[ 0 ] ); + + for ( i = 1; i <= waypointCount; i++ ) + { + tokens = strtok( res.lines[ i ], "," ); + + waypoint = parseTokensIntoWaypoint( tokens ); + + waypoints[ i - 1 ] = waypoint; + } + + return waypoints; +} + +/* + Loads the waypoints. Populating everything needed for the waypoints. +*/ +load_waypoints() +{ + level.waypointusage = []; + level.waypointusage[ "allies" ] = []; + level.waypointusage[ "axis" ] = []; + + if ( !isdefined( level.waypoints ) ) + { + level.waypoints = []; + } + + mapname = getdvar( "mapname" ); + + wps = readWpsFromFile( mapname ); + + if ( wps.size ) + { + level.waypoints = wps; + BotBuiltinPrintConsole( "Loaded " + wps.size + " waypoints from csv" ); + } + else + { + switch ( mapname ) + { + default: + maps\mp\bots\waypoints\_custom_map::main( mapname ); + break; + } + + if ( level.waypoints.size ) + { + BotBuiltinPrintConsole( "Loaded " + level.waypoints.size + " waypoints from script" ); + } + } + + if ( !level.waypoints.size ) + { + BotBuiltinPrintConsole( "No waypoints loaded!" ); + } + + for ( i = level.waypoints.size - 1; i >= 0; i-- ) + { + if ( !isdefined( level.waypoints[ i ].children ) || !isdefined( level.waypoints[ i ].children.size ) ) + { + level.waypoints[ i ].children = []; + } + + if ( !isdefined( level.waypoints[ i ].origin ) ) + { + level.waypoints[ i ].origin = ( 0, 0, 0 ); + } + + if ( !isdefined( level.waypoints[ i ].type ) ) + { + level.waypoints[ i ].type = "crouch"; + } + + level.waypoints[ i ].childcount = undefined; + } +} + +/* + Is bot near any of the given waypoints +*/ +nearAnyOfWaypoints( dist, waypoints ) +{ + dist *= dist; + + for ( i = 0; i < waypoints.size; i++ ) + { + waypoint = level.waypoints[ waypoints[ i ] ]; + + if ( distancesquared( waypoint.origin, self.origin ) > dist ) + { + continue; + } + + return true; + } + + return false; +} + +/* + Returns the waypoints that are near +*/ +waypointsNear( waypoints, dist ) +{ + dist *= dist; + + answer = []; + + for ( i = 0; i < waypoints.size; i++ ) + { + wp = level.waypoints[ waypoints[ i ] ]; + + if ( distancesquared( wp.origin, self.origin ) > dist ) + { + continue; + } + + answer[ answer.size ] = waypoints[ i ]; + } + + return answer; +} + +/* + Returns nearest waypoint of waypoints +*/ +getNearestWaypointOfWaypoints( waypoints ) +{ + answer = undefined; + closestDist = 2147483647; + + for ( i = 0; i < waypoints.size; i++ ) + { + waypoint = level.waypoints[ waypoints[ i ] ]; + thisDist = distancesquared( self.origin, waypoint.origin ); + + if ( isdefined( answer ) && thisDist > closestDist ) + { + continue; + } + + answer = waypoints[ i ]; + closestDist = thisDist; + } + + return answer; +} + +/* + Returns all waypoints of type +*/ +getWaypointsOfType( type ) +{ + answer = []; + + for ( i = level.waypoints.size - 1; i >= 0; i-- ) + { + wp = level.waypoints[ i ]; + + if ( type == "camp" ) + { + if ( wp.type != "crouch" ) + { + continue; + } + + if ( wp.children.size != 1 ) + { + continue; + } + } + else if ( type != wp.type ) + { + continue; + } + + answer[ answer.size ] = i; + } + + return answer; +} + +/* + Returns the waypoint for index +*/ +getWaypointForIndex( i ) +{ + if ( !isdefined( i ) ) + { + return undefined; + } + + return level.waypoints[ i ]; +} + +/* + Returns the friendly user name for a given map's codename +*/ +getMapName( mapname ) +{ + switch ( mapname ) + { + case "mp_abandon": + return "Carnival"; + + case "mp_rundown": + return "Rundown"; + + case "mp_afghan": + return "Afghan"; + + case "mp_boneyard": + return "Scrapyard"; + + case "mp_brecourt": + return "Wasteland"; + + case "mp_cargoship": + return "Wetwork"; + + case "mp_checkpoint": + return "Karachi"; + + case "mp_compact": + return "Salvage"; + + case "mp_complex": + return "Bailout"; + + case "mp_crash": + return "Crash"; + + case "mp_cross_fire": + return "Crossfire"; + + case "mp_derail": + return "Derail"; + + case "mp_estate": + return "Estate"; + + case "mp_favela": + return "Favela"; + + case "mp_fuel2": + return "Fuel"; + + case "mp_highrise": + return "Highrise"; + + case "mp_invasion": + return "Invasion"; + + case "mp_killhouse": + return "Killhouse"; + + case "mp_nightshift": + return "Skidrow"; + + case "mp_nuked": + return "Nuketown"; + + case "oilrig": + return "Oilrig"; + + case "mp_quarry": + return "Quarry"; + + case "mp_rust": + return "Rust"; + + case "mp_storm": + return "Storm"; + + case "mp_strike": + return "Strike"; + + case "mp_subbase": + return "Subbase"; + + case "mp_terminal": + return "Terminal"; + + case "mp_trailerpark": + return "Trailer Park"; + + case "mp_overgrown": + return "Overgrown"; + + case "mp_underpass": + return "Underpass"; + + case "mp_vacant": + return "Vacant"; + + case "iw4_credits": + return "IW4 Test Map"; + + case "airport": + return "Airport"; + + case "co_hunted": + return "Hunted"; + + case "invasion": + return "Burgertown"; + + case "mp_bloc": + return "Bloc"; + + case "mp_bog_sh": + return "Bog"; + + case "contingency": + return "Contingency"; + + case "gulag": + return "Gulag"; + + case "so_ghillies": + return "Pripyat"; + + case "ending": + return "Museum"; + + case "af_chase": + return "Afghan Chase"; + + case "af_caves": + return "Afghan Caves"; + + case "arcadia": + return "Arcadia"; + + case "boneyard": + return "Boneyard"; + + case "cliffhanger": + return "Cliffhanger"; + + case "dcburning": + return "DCBurning"; + + case "dcemp": + return "DCEMP"; + + case "downtown": + return "Downtown"; + + case "estate": + return "EstateSP"; + + case "favela": + return "FavelaSP"; + + case "favela_escape": + return "Favela Escape"; + + case "roadkill": + return "Roadkill"; + + case "trainer": + return "TH3 PIT"; + + case "so_bridge": + return "Bridge"; + + case "dc_whitehouse": + return "Whitehouse"; + + case "mp_shipment_long": + return "ShipmentLong"; + + case "mp_shipment": + return "Shipment"; + + case "mp_firingrange": + return "Firing Range"; + + case "mp_rust_long": + return "RustLong"; + + case "mp_cargoship_sh": + return "Freighter"; + + case "mp_storm_spring": + return "Chemical Plant"; + + case "mp_crash_trop": + case "mp_crash_tropical": + return "Crash Tropical"; + + case "mp_fav_tropical": + return "Favela Tropical"; + + case "mp_estate_trop": + case "mp_estate_tropical": + return "Estate Tropical"; + + case "mp_bloc_sh": + return "Forgotten City"; + + default: + return mapname; + } +} + +/* + Returns a good amount of players. +*/ +getGoodMapAmount() +{ + switch ( getdvar( "mapname" ) ) + { + case "mp_rust": + case "iw4_credits": + case "mp_nuked": + case "oilrig": + case "mp_killhouse": + case "invasion": + case "mp_bog_sh": + case "co_hunted": + case "contingency": + case "gulag": + case "so_ghillies": + case "ending": + case "af_chase": + case "af_caves": + case "arcadia": + case "boneyard": + case "cliffhanger": + case "dcburning": + case "dcemp": + case "downtown": + case "estate": + case "favela": + case "favela_escape": + case "roadkill": + case "so_bridge": + case "trainer": + case "dc_whitehouse": + case "mp_shipment": + if ( level.teambased ) + { + return 8; + } + else + { + return 4; + } + + case "mp_vacant": + case "mp_terminal": + case "mp_nightshift": + case "mp_favela": + case "mp_highrise": + case "mp_boneyard": + case "mp_subbase": + case "mp_firingrange": + case "mp_fav_tropical": + case "mp_shipment_long": + case "mp_rust_long": + if ( level.teambased ) + { + return 12; + } + else + { + return 8; + } + + case "mp_afghan": + case "mp_crash": + case "mp_brecourt": + case "mp_cross_fire": + case "mp_overgrown": + case "mp_trailerpark": + case "mp_underpass": + case "mp_checkpoint": + case "mp_quarry": + case "mp_rundown": + case "mp_cargoship": + case "mp_estate": + case "mp_bloc": + case "mp_storm": + case "mp_strike": + case "mp_abandon": + case "mp_complex": + case "airport": + case "mp_storm_spring": + case "mp_crash_trop": + case "mp_cargoship_sh": + case "mp_estate_trop": + case "mp_compact": + case "mp_crash_tropical": + case "mp_estate_tropical": + case "mp_bloc_sh": + if ( level.teambased ) + { + return 14; + } + else + { + return 9; + } + + case "mp_fuel2": + case "mp_invasion": + case "mp_derail": + if ( level.teambased ) + { + return 16; + } + else + { + return 10; + } + + default: + return 2; + } +} + +/* + Matches a num to a char +*/ +keyCodeToString( a ) +{ + b = ""; + + switch ( a ) + { + case 0: + b = "a"; + break; + + case 1: + b = "b"; + break; + + case 2: + b = "c"; + break; + + case 3: + b = "d"; + break; + + case 4: + b = "e"; + break; + + case 5: + b = "f"; + break; + + case 6: + b = "g"; + break; + + case 7: + b = "h"; + break; + + case 8: + b = "i"; + break; + + case 9: + b = "j"; + break; + + case 10: + b = "k"; + break; + + case 11: + b = "l"; + break; + + case 12: + b = "m"; + break; + + case 13: + b = "n"; + break; + + case 14: + b = "o"; + break; + + case 15: + b = "p"; + break; + + case 16: + b = "q"; + break; + + case 17: + b = "r"; + break; + + case 18: + b = "s"; + break; + + case 19: + b = "t"; + break; + + case 20: + b = "u"; + break; + + case 21: + b = "v"; + break; + + case 22: + b = "w"; + break; + + case 23: + b = "x"; + break; + + case 24: + b = "y"; + break; + + case 25: + b = "z"; + break; + + case 26: + b = "."; + break; + + case 27: + b = " "; + break; + } + + return b; +} + +/* + Returns an array of all the bots in the game. +*/ +getBotArray() +{ + result = []; + playercount = level.players.size; + + for ( i = 0; i < playercount; i++ ) + { + player = level.players[ i ]; + + if ( !player is_bot() ) + { + continue; + } + + result[ result.size ] = player; + } + + return result; +} + +/* + We return a balanced KDTree from the waypoints. +*/ +WaypointsToKDTree() +{ + kdTree = KDTree(); + + kdTree _WaypointsToKDTree( level.waypoints, 0 ); + + return kdTree; +} + +/* + Recurive function. We construct a balanced KD tree by sorting the waypoints using heap sort. +*/ +_WaypointsToKDTree( waypoints, dem ) +{ + if ( !waypoints.size ) + { + return; + } + + callbacksort = undefined; + + switch ( dem ) + { + case 0: + callbacksort = ::HeapSortCoordX; + break; + + case 1: + callbacksort = ::HeapSortCoordY; + break; + + case 2: + callbacksort = ::HeapSortCoordZ; + break; + } + + heap = NewHeap( callbacksort ); + + for ( i = 0; i < waypoints.size; i++ ) + { + heap HeapInsert( waypoints[ i ] ); + } + + sorted = []; + + while ( heap.data.size ) + { + sorted[ sorted.size ] = heap.data[ 0 ]; + heap HeapRemove(); + } + + median = int( sorted.size / 2 ); // use divide and conq + + left = []; + right = []; + + for ( i = 0; i < sorted.size; i++ ) + { + if ( i < median ) + { + right[ right.size ] = sorted[ i ]; + } + else if ( i > median ) + { + left[ left.size ] = sorted[ i ]; + } + } + + self KDTreeInsert( sorted[ median ] ); + + _WaypointsToKDTree( left, ( dem + 1 ) % 3 ); + + _WaypointsToKDTree( right, ( dem + 1 ) % 3 ); +} + +/* + Returns a new list. +*/ +List() +{ + list = spawnstruct(); + list.count = 0; + list.data = []; + + return list; +} + +/* + Adds a new thing to the list. +*/ +ListAdd( thing ) +{ + self.data[ self.count ] = thing; + + self.count++; +} + +/* + Adds to the start of the list. +*/ +ListAddFirst( thing ) +{ + for ( i = self.count - 1; i >= 0; i-- ) + { + self.data[ i + 1 ] = self.data[ i ]; + } + + self.data[ 0 ] = thing; + self.count++; +} + +/* + Removes the thing from the list. +*/ +ListRemove( thing ) +{ + for ( i = 0; i < self.count; i++ ) + { + if ( self.data[ i ] == thing ) + { + while ( i < self.count - 1 ) + { + self.data[ i ] = self.data[ i + 1 ]; + i++; + } + + self.data[ i ] = undefined; + self.count--; + break; + } + } +} + +/* + Returns a new KDTree. +*/ +KDTree() +{ + kdTree = spawnstruct(); + kdTree.root = undefined; + kdTree.count = 0; + + return kdTree; +} + +/* + Called on a KDTree. Will insert the object into the KDTree. +*/ +KDTreeInsert( data ) // as long as what you insert has a .origin attru, it will work. +{ + self.root = self _KDTreeInsert( self.root, data, 0, -2147483647, -2147483647, -2147483647, 2147483647, 2147483647, 2147483647 ); +} + +/* + Recurive function that insert the object into the KDTree. +*/ +_KDTreeInsert( node, data, dem, x0, y0, z0, x1, y1, z1 ) +{ + if ( !isdefined( node ) ) + { + r = spawnstruct(); + r.data = data; + r.left = undefined; + r.right = undefined; + r.x0 = x0; + r.x1 = x1; + r.y0 = y0; + r.y1 = y1; + r.z0 = z0; + r.z1 = z1; + + self.count++; + + return r; + } + + switch ( dem ) + { + case 0: + if ( data.origin[ 0 ] < node.data.origin[ 0 ] ) + { + node.left = self _KDTreeInsert( node.left, data, 1, x0, y0, z0, node.data.origin[ 0 ], y1, z1 ); + } + else + { + node.right = self _KDTreeInsert( node.right, data, 1, node.data.origin[ 0 ], y0, z0, x1, y1, z1 ); + } + + break; + + case 1: + if ( data.origin[ 1 ] < node.data.origin[ 1 ] ) + { + node.left = self _KDTreeInsert( node.left, data, 2, x0, y0, z0, x1, node.data.origin[ 1 ], z1 ); + } + else + { + node.right = self _KDTreeInsert( node.right, data, 2, x0, node.data.origin[ 1 ], z0, x1, y1, z1 ); + } + + break; + + case 2: + if ( data.origin[ 2 ] < node.data.origin[ 2 ] ) + { + node.left = self _KDTreeInsert( node.left, data, 0, x0, y0, z0, x1, y1, node.data.origin[ 2 ] ); + } + else + { + node.right = self _KDTreeInsert( node.right, data, 0, x0, y0, node.data.origin[ 2 ], x1, y1, z1 ); + } + + break; + } + + return node; +} + +/* + Called on a KDTree, will return the nearest object to the given origin. +*/ +KDTreeNearest( origin ) +{ + if ( !isdefined( self.root ) ) + { + return undefined; + } + + return self _KDTreeNearest( self.root, origin, self.root.data, distancesquared( self.root.data.origin, origin ), 0 ); +} + +/* + Recurive function that will retrieve the closest object to the query. +*/ +_KDTreeNearest( node, point, closest, closestdist, dem ) +{ + if ( !isdefined( node ) ) + { + return closest; + } + + thisDis = distancesquared( node.data.origin, point ); + + if ( thisDis < closestdist ) + { + closestdist = thisDis; + closest = node.data; + } + + if ( node Rectdistancesquared( point ) < closestdist ) + { + near = node.left; + far = node.right; + + if ( point[ dem ] > node.data.origin[ dem ] ) + { + near = node.right; + far = node.left; + } + + closest = self _KDTreeNearest( near, point, closest, closestdist, ( dem + 1 ) % 3 ); + + closest = self _KDTreeNearest( far, point, closest, distancesquared( closest.origin, point ), ( dem + 1 ) % 3 ); + } + + return closest; +} + +/* + Called on a rectangle, returns the distance from origin to the rectangle. +*/ +Rectdistancesquared( origin ) +{ + dx = 0; + dy = 0; + dz = 0; + + if ( origin[ 0 ] < self.x0 ) + { + dx = origin[ 0 ] - self.x0; + } + else if ( origin[ 0 ] > self.x1 ) + { + dx = origin[ 0 ] - self.x1; + } + + if ( origin[ 1 ] < self.y0 ) + { + dy = origin[ 1 ] - self.y0; + } + else if ( origin[ 1 ] > self.y1 ) + { + dy = origin[ 1 ] - self.y1; + } + + + if ( origin[ 2 ] < self.z0 ) + { + dz = origin[ 2 ] - self.z0; + } + else if ( origin[ 2 ] > self.z1 ) + { + dz = origin[ 2 ] - self.z1; + } + + return dx * dx + dy * dy + dz * dz; +} + +/* + Does the extra check when adding bots +*/ +doExtraCheck() +{ + maps\mp\bots\_bot_internal::checkTheBots(); +} + +/* + A heap invarient comparitor, used for objects, objects with a higher X coord will be first in the heap. +*/ +HeapSortCoordX( item, item2 ) +{ + return item.origin[ 0 ] > item2.origin[ 0 ]; +} + +/* + A heap invarient comparitor, used for objects, objects with a higher Y coord will be first in the heap. +*/ +HeapSortCoordY( item, item2 ) +{ + return item.origin[ 1 ] > item2.origin[ 1 ]; +} + +/* + A heap invarient comparitor, used for objects, objects with a higher Z coord will be first in the heap. +*/ +HeapSortCoordZ( item, item2 ) +{ + return item.origin[ 2 ] > item2.origin[ 2 ]; +} + +/* + A heap invarient comparitor, used for numbers, numbers with the highest number will be first in the heap. +*/ +Heap( item, item2 ) +{ + return item > item2; +} + +/* + A heap invarient comparitor, used for numbers, numbers with the lowest number will be first in the heap. +*/ +ReverseHeap( item, item2 ) +{ + return item < item2; +} + +/* + A heap invarient comparitor, used for traces. Wanting the trace with the largest length first in the heap. +*/ +HeapTraceFraction( item, item2 ) +{ + return item[ "fraction" ] > item2[ "fraction" ]; +} + +/* + Returns a new heap. +*/ +NewHeap( compare ) +{ + heap_node = spawnstruct(); + heap_node.data = []; + heap_node.compare = compare; + + return heap_node; +} + +/* + Inserts the item into the heap. Called on a heap. +*/ +HeapInsert( item ) +{ + insert = self.data.size; + self.data[ insert ] = item; + + current = insert + 1; + + while ( current > 1 ) + { + last = current; + current = int( current / 2 ); + + if ( ![[ self.compare ]]( item, self.data[ current - 1 ] ) ) + { + break; + } + + self.data[ last - 1 ] = self.data[ current - 1 ]; + self.data[ current - 1 ] = item; + } +} + +/* + Helper function to determine what is the next child of the bst. +*/ +_HeapNextChild( node, hsize ) +{ + left = node * 2; + right = left + 1; + + if ( left > hsize ) + { + return -1; + } + + if ( right > hsize ) + { + return left; + } + + if ( [[ self.compare ]]( self.data[ left - 1 ], self.data[ right - 1 ] ) ) + { + return left; + } + else + { + return right; + } +} + +/* + Removes an item from the heap. Called on a heap. +*/ +HeapRemove() +{ + remove = self.data.size; + + if ( !remove ) + { + return remove; + } + + move = self.data[ remove - 1 ]; + self.data[ 0 ] = move; + self.data[ remove - 1 ] = undefined; + remove--; + + if ( !remove ) + { + return remove; + } + + last = 1; + next = self _HeapNextChild( 1, remove ); + + while ( next != -1 ) + { + if ( [[ self.compare ]]( move, self.data[ next - 1 ] ) ) + { + break; + } + + self.data[ last - 1 ] = self.data[ next - 1 ]; + self.data[ next - 1 ] = move; + + last = next; + next = self _HeapNextChild( next, remove ); + } + + return remove; +} + +/* + A heap invarient comparitor, used for the astar's nodes, wanting the node with the lowest f to be first in the heap. +*/ +ReverseHeapAStar( item, item2 ) +{ + return item.f < item2.f; +} + +/* + Removes the waypoint usage +*/ +RemoveWaypointUsage( wp, team ) +{ + if ( !isdefined( level.waypointusage ) ) + { + return; + } + + wpstr = wp + ""; + + if ( !isdefined( level.waypointusage[ team ][ wpstr ] ) ) + { + return; + } + + level.waypointusage[ team ][ wpstr ]--; + + if ( level.waypointusage[ team ][ wpstr ] <= 0 ) + { + level.waypointusage[ team ][ wpstr ] = undefined; + } +} + +/* + Will linearly search for the nearest waypoint to pos that has a direct line of sight. +*/ +getNearestWaypointWithSight( pos ) +{ + candidate = undefined; + dist = 2147483647; + + for ( i = level.waypoints.size - 1; i >= 0; i-- ) + { + if ( !bullettracepassed( pos + ( 0, 0, 15 ), level.waypoints[ i ].origin + ( 0, 0, 15 ), false, undefined ) ) + { + continue; + } + + curdis = distancesquared( level.waypoints[ i ].origin, pos ); + + if ( curdis > dist ) + { + continue; + } + + dist = curdis; + candidate = i; + } + + return candidate; +} + +/* + Will linearly search for the nearest waypoint +*/ +getNearestWaypoint( pos ) +{ + candidate = undefined; + dist = 2147483647; + + for ( i = level.waypoints.size - 1; i >= 0; i-- ) + { + curdis = distancesquared( level.waypoints[ i ].origin, pos ); + + if ( curdis > dist ) + { + continue; + } + + dist = curdis; + candidate = i; + } + + return candidate; +} + +/* + Modified Pezbot astar search. + This makes use of sets for quick look up and a heap for a priority queue instead of simple lists which require to linearly search for elements everytime. + It is also modified to make paths with bots already on more expensive and will try a less congested path first. Thus spliting up the bots onto more paths instead of just one (the smallest). +*/ +AStarSearch( start, goal, team, greedy_path ) +{ + open = NewHeap( ::ReverseHeapAStar ); // heap + openset = []; // set for quick lookup + closed = []; // set for quick lookup + + + startWp = getNearestWaypoint( start ); + + if ( !isdefined( startWp ) ) + { + return []; + } + + _startwp = undefined; + + if ( !bullettracepassed( start + ( 0, 0, 15 ), level.waypoints[ startWp ].origin + ( 0, 0, 15 ), false, undefined ) ) + { + _startwp = getNearestWaypointWithSight( start ); + } + + if ( isdefined( _startwp ) ) + { + startWp = _startwp; + } + + + goalWp = getNearestWaypoint( goal ); + + if ( !isdefined( goalWp ) ) + { + return []; + } + + _goalwp = undefined; + + if ( !bullettracepassed( goal + ( 0, 0, 15 ), level.waypoints[ goalWp ].origin + ( 0, 0, 15 ), false, undefined ) ) + { + _goalwp = getNearestWaypointWithSight( goal ); + } + + if ( isdefined( _goalwp ) ) + { + goalWp = _goalwp; + } + + + node = spawnstruct(); + node.g = 0; // path dist so far + node.h = distancesquared( level.waypoints[ startWp ].origin, level.waypoints[ goalWp ].origin ); // herustic, distance to goal for path finding + node.f = node.h + node.g; // combine path dist and heru, use reverse heap to sort the priority queue by this attru + node.index = startWp; + node.parent = undefined; // we are start, so we have no parent + + // push node onto queue + openset[ node.index + "" ] = node; + open HeapInsert( node ); + + // while the queue is not empty + while ( open.data.size ) + { + // pop bestnode from queue + bestNode = open.data[ 0 ]; + open HeapRemove(); + bestNodeStr = bestNode.index + ""; + openset[ bestNodeStr ] = undefined; + wp = level.waypoints[ bestNode.index ]; + + // check if we made it to the goal + if ( bestNode.index == goalWp ) + { + path = []; + + while ( isdefined( bestNode ) ) + { + bestNodeStr = bestNode.index + ""; + + if ( isdefined( team ) && isdefined( level.waypointusage ) ) + { + if ( !isdefined( level.waypointusage[ team ][ bestNodeStr ] ) ) + { + level.waypointusage[ team ][ bestNodeStr ] = 0; + } + + level.waypointusage[ team ][ bestNodeStr ]++; + } + + // construct path + path[ path.size ] = bestNode.index; + + bestNode = bestNode.parent; + } + + return path; + } + + // for each child of bestnode + for ( i = wp.children.size - 1; i >= 0; i-- ) + { + child = wp.children[ i ]; + childStr = child + ""; + childWp = level.waypoints[ child ]; + + penalty = 1; + + if ( !greedy_path && isdefined( team ) && isdefined( level.waypointusage ) ) + { + temppen = 1; + + if ( isdefined( level.waypointusage[ team ][ childStr ] ) ) + { + temppen = level.waypointusage[ team ][ childStr ]; // consider how many bots are taking this path + } + + if ( temppen > 1 ) + { + penalty = temppen; + } + } + + // have certain types of nodes more expensive + if ( childWp.type == "climb" || childWp.type == "prone" ) + { + penalty += 4; + } + + // calc the total path we have took + newg = bestNode.g + distancesquared( wp.origin, childWp.origin ) * penalty; // bots on same team's path are more expensive + + // check if this child is in open or close with a g value less than newg + inopen = isdefined( openset[ childStr ] ); + + if ( inopen && openset[ childStr ].g <= newg ) + { + continue; + } + + inclosed = isdefined( closed[ childStr ] ); + + if ( inclosed && closed[ childStr ].g <= newg ) + { + continue; + } + + node = undefined; + + if ( inopen ) + { + node = openset[ childStr ]; + } + else if ( inclosed ) + { + node = closed[ childStr ]; + } + else + { + node = spawnstruct(); + } + + node.parent = bestNode; + node.g = newg; + node.h = distancesquared( childWp.origin, level.waypoints[ goalWp ].origin ); + node.f = node.g + node.h; + node.index = child; + + // check if in closed, remove it + if ( inclosed ) + { + closed[ childStr ] = undefined; + } + + // check if not in open, add it + if ( !inopen ) + { + open HeapInsert( node ); + openset[ childStr ] = node; + } + } + + // done with children, push onto closed + closed[ bestNodeStr ] = bestNode; + } + + return []; +} + +/* + Taken from t5 gsc. + Returns an array of number's average. +*/ +array_average( array ) +{ + assert( array.size > 0 ); + total = 0; + + for ( i = 0; i < array.size; i++ ) + { + total += array[ i ]; + } + + return ( total / array.size ); +} + +/* + Taken from t5 gsc. + Returns an array of number's standard deviation. +*/ +array_std_deviation( array, mean ) +{ + assert( array.size > 0 ); + tmp = []; + + for ( i = 0; i < array.size; i++ ) + { + tmp[ i ] = ( array[ i ] - mean ) * ( array[ i ] - mean ); + } + + total = 0; + + for ( i = 0; i < tmp.size; i++ ) + { + total = total + tmp[ i ]; + } + + return sqrt( total / array.size ); +} + +/* + Taken from t5 gsc. + Will produce a random number between lower_bound and upper_bound but with a bell curve distribution (more likely to be close to the mean). +*/ +random_normal_distribution( mean, std_deviation, lower_bound, upper_bound ) +{ + x1 = 0; + x2 = 0; + w = 1; + y1 = 0; + + while ( w >= 1 ) + { + x1 = 2 * randomfloatrange( 0, 1 ) - 1; + x2 = 2 * randomfloatrange( 0, 1 ) - 1; + w = x1 * x1 + x2 * x2; + } + + w = sqrt( ( -2.0 * log( w ) ) / w ); + y1 = x1 * w; + number = mean + y1 * std_deviation; + + if ( isdefined( lower_bound ) && number < lower_bound ) + { + number = lower_bound; + } + + if ( isdefined( upper_bound ) && number > upper_bound ) + { + number = upper_bound; + } + + return ( number ); +} + +/* + Patches the plant sites so it exposes the defuseObject +*/ +onUsePlantObjectFix( player ) +{ + // planted the bomb + if ( !self maps\mp\gametypes\_gameobjects::isfriendlyteam( player.pers[ "team" ] ) ) + { + level thread bombPlantedFix( self, player ); + // player logstring( "bomb planted: " + self.label ); + + // disable all bomb zones except this one + for ( index = 0; index < level.bombzones.size; index++ ) + { + if ( level.bombzones[ index ] == self ) + { + continue; + } + + level.bombzones[ index ] maps\mp\gametypes\_gameobjects::disableobject(); + } + + player playsound( "mp_bomb_plant" ); + player notify ( "bomb_planted" ); + + // if ( !level.hardcoremode ) + // iprintln( &"MP_EXPLOSIVES_PLANTED_BY", player ); + + leaderdialog( "bomb_planted" ); + + level thread teamplayercardsplash( "callout_bombplanted", player ); + + level.bombowner = player; + player thread maps\mp\gametypes\_hud_message::splashnotify( "plant", maps\mp\gametypes\_rank::getscoreinfovalue( "plant" ) ); + player thread maps\mp\gametypes\_rank::giverankxp( "plant" ); + player.bombplantedtime = gettime(); + maps\mp\gametypes\_gamescore::giveplayerscore( "plant", player ); + player incplayerstat( "bombsplanted", 1 ); + player thread maps\mp\_matchdata::loggameevent( "plant", player.origin ); + } +} + +/* + Patches the plant sites so it exposes the defuseObject +*/ +bombPlantedFix( destroyedObj, player ) +{ + maps\mp\gametypes\_gamelogic::pausetimer(); + level.bombplanted = true; + + destroyedObj.visuals[ 0 ] thread maps\mp\gametypes\_gamelogic::playtickingsound(); + level.tickingobject = destroyedObj.visuals[ 0 ]; + + level.timelimitoverride = true; + setgameendtime( int( gettime() + ( level.bombtimer * 1000 ) ) ); + setdvar( "ui_bomb_timer", 1 ); + + if ( !level.multibomb ) + { + level.sdbomb maps\mp\gametypes\_gameobjects::allowcarry( "none" ); + level.sdbomb maps\mp\gametypes\_gameobjects::setvisibleteam( "none" ); + level.sdbomb maps\mp\gametypes\_gameobjects::setdropped(); + level.sdbombmodel = level.sdbomb.visuals[ 0 ]; + } + else + { + for ( index = 0; index < level.players.size; index++ ) + { + if ( isdefined( level.players[ index ].carryicon ) ) + { + level.players[ index ].carryicon destroyelem(); + } + } + + trace = bullettrace( player.origin + ( 0, 0, 20 ), player.origin - ( 0, 0, 2000 ), false, player ); + + tempAngle = randomfloat( 360 ); + forward = ( cos( tempAngle ), sin( tempAngle ), 0 ); + forward = vectornormalize( forward - vector_multiply( trace[ "normal" ], vectordot( forward, trace[ "normal" ] ) ) ); + dropAngles = vectortoangles( forward ); + + level.sdbombmodel = spawn( "script_model", trace[ "position" ] ); + level.sdbombmodel.angles = dropAngles; + level.sdbombmodel setmodel( "prop_suitcase_bomb" ); + } + + destroyedObj maps\mp\gametypes\_gameobjects::allowuse( "none" ); + destroyedObj maps\mp\gametypes\_gameobjects::setvisibleteam( "none" ); + /* + destroyedObj maps\mp\gametypes\_gameobjects::set2dicon( "friendly", undefined ); + destroyedObj maps\mp\gametypes\_gameobjects::set2dicon( "enemy", undefined ); + destroyedObj maps\mp\gametypes\_gameobjects::set3dicon( "friendly", undefined ); + destroyedObj maps\mp\gametypes\_gameobjects::set3dicon( "enemy", undefined ); + */ + label = destroyedObj maps\mp\gametypes\_gameobjects::getlabel(); + + // create a new object to defuse with. + trigger = destroyedObj.bombdefusetrig; + trigger.origin = level.sdbombmodel.origin; + visuals = []; + defuseObject = maps\mp\gametypes\_gameobjects::createuseobject( game[ "defenders" ], trigger, visuals, ( 0, 0, 32 ) ); + defuseObject maps\mp\gametypes\_gameobjects::allowuse( "friendly" ); + defuseObject maps\mp\gametypes\_gameobjects::setusetime( level.defusetime ); + defuseObject maps\mp\gametypes\_gameobjects::setusetext( &"MP_DEFUSING_EXPLOSIVE" ); + defuseObject maps\mp\gametypes\_gameobjects::setusehinttext( &"PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" ); + defuseObject maps\mp\gametypes\_gameobjects::setvisibleteam( "any" ); + defuseObject maps\mp\gametypes\_gameobjects::set2dicon( "friendly", "waypoint_defuse" + label ); + defuseObject maps\mp\gametypes\_gameobjects::set2dicon( "enemy", "waypoint_defend" + label ); + defuseObject maps\mp\gametypes\_gameobjects::set3dicon( "friendly", "waypoint_defuse" + label ); + defuseObject maps\mp\gametypes\_gameobjects::set3dicon( "enemy", "waypoint_defend" + label ); + defuseObject.label = label; + defuseObject.onbeginuse = maps\mp\gametypes\sd::onbeginuse; + defuseObject.onenduse = maps\mp\gametypes\sd::onenduse; + defuseObject.onuse = maps\mp\gametypes\sd::onusedefuseobject; + defuseObject.useweapon = "briefcase_bomb_defuse_mp"; + + level.defuseobject = defuseObject; + + maps\mp\gametypes\sd::bombtimerwait(); + setdvar( "ui_bomb_timer", 0 ); + + destroyedObj.visuals[ 0 ] maps\mp\gametypes\_gamelogic::stoptickingsound(); + + if ( level.gameended || level.bombdefused ) + { + return; + } + + level.bombexploded = true; + + explosionOrigin = level.sdbombmodel.origin; + level.sdbombmodel hide(); + + if ( isdefined( player ) ) + { + destroyedObj.visuals[ 0 ] radiusdamage( explosionOrigin, 512, 200, 20, player ); + player incplayerstat( "targetsdestroyed", 1 ); + } + else + { + destroyedObj.visuals[ 0 ] radiusdamage( explosionOrigin, 512, 200, 20 ); + } + + rot = randomfloat( 360 ); + explosionEffect = spawnfx( level._effect[ "bombexplosion" ], explosionOrigin + ( 0, 0, 50 ), ( 0, 0, 1 ), ( cos( rot ), sin( rot ), 0 ) ); + triggerfx( explosionEffect ); + + playrumbleonposition( "grenade_rumble", explosionOrigin ); + earthquake( 0.75, 2.0, explosionOrigin, 2000 ); + + thread playsoundinspace( "exp_suitcase_bomb_main", explosionOrigin ); + + if ( isdefined( destroyedObj.exploderindex ) ) + { + exploder( destroyedObj.exploderindex ); + } + + for ( index = 0; index < level.bombzones.size; index++ ) + { + level.bombzones[ index ] maps\mp\gametypes\_gameobjects::disableobject(); + } + + defuseObject maps\mp\gametypes\_gameobjects::disableobject(); + + setgameendtime( 0 ); + + wait 3; + + maps\mp\gametypes\sd::sd_endgame( game[ "attackers" ], game[ "strings" ][ "target_destroyed" ] ); +} + + +/* + Patches giveLoadout so that it doesn't use IsItemUnlocked +*/ +botGiveLoadout( team, class, allowCopycat ) +{ + self endon( "death" ); + + self takeallweapons(); + + primaryIndex = 0; + + // initialize specialty array + self.specialty = []; + + if ( !isdefined( allowCopycat ) ) + { + allowCopycat = true; + } + + primaryWeapon = undefined; + + if ( isdefined( self.pers[ "copyCatLoadout" ] ) && self.pers[ "copyCatLoadout" ][ "inUse" ] && allowCopycat ) + { + self maps\mp\gametypes\_class::setclass( "copycat" ); + self.class_num = maps\mp\gametypes\_class::getclassindex( "copycat" ); + + clonedLoadout = self.pers[ "copyCatLoadout" ]; + + loadoutPrimary = clonedLoadout[ "loadoutPrimary" ]; + loadoutPrimaryAttachment = clonedLoadout[ "loadoutPrimaryAttachment" ]; + loadoutPrimaryAttachment2 = clonedLoadout[ "loadoutPrimaryAttachment2" ] ; + loadoutPrimaryCamo = clonedLoadout[ "loadoutPrimaryCamo" ]; + loadoutSecondary = clonedLoadout[ "loadoutSecondary" ]; + loadoutSecondaryAttachment = clonedLoadout[ "loadoutSecondaryAttachment" ]; + loadoutSecondaryAttachment2 = clonedLoadout[ "loadoutSecondaryAttachment2" ]; + loadoutSecondaryCamo = clonedLoadout[ "loadoutSecondaryCamo" ]; + loadoutEquipment = clonedLoadout[ "loadoutEquipment" ]; + loadoutPerk1 = clonedLoadout[ "loadoutPerk1" ]; + loadoutPerk2 = clonedLoadout[ "loadoutPerk2" ]; + loadoutPerk3 = clonedLoadout[ "loadoutPerk3" ]; + loadoutOffhand = clonedLoadout[ "loadoutOffhand" ]; + loadoutDeathStreak = "specialty_copycat"; + } + else if ( issubstr( class, "custom" ) ) + { + class_num = maps\mp\gametypes\_class::getclassindex( class ); + self.class_num = class_num; + + loadoutPrimary = maps\mp\gametypes\_class::cac_getweapon( class_num, 0 ); + loadoutPrimaryAttachment = maps\mp\gametypes\_class::cac_getweaponattachment( class_num, 0 ); + loadoutPrimaryAttachment2 = maps\mp\gametypes\_class::cac_getweaponattachmenttwo( class_num, 0 ); + loadoutPrimaryCamo = maps\mp\gametypes\_class::cac_getweaponcamo( class_num, 0 ); + loadoutSecondaryCamo = maps\mp\gametypes\_class::cac_getweaponcamo( class_num, 1 ); + loadoutSecondary = maps\mp\gametypes\_class::cac_getweapon( class_num, 1 ); + loadoutSecondaryAttachment = maps\mp\gametypes\_class::cac_getweaponattachment( class_num, 1 ); + loadoutSecondaryAttachment2 = maps\mp\gametypes\_class::cac_getweaponattachmenttwo( class_num, 1 ); + loadoutSecondaryCamo = maps\mp\gametypes\_class::cac_getweaponcamo( class_num, 1 ); + loadoutEquipment = maps\mp\gametypes\_class::cac_getperk( class_num, 0 ); + loadoutPerk1 = maps\mp\gametypes\_class::cac_getperk( class_num, 1 ); + loadoutPerk2 = maps\mp\gametypes\_class::cac_getperk( class_num, 2 ); + loadoutPerk3 = maps\mp\gametypes\_class::cac_getperk( class_num, 3 ); + loadoutOffhand = maps\mp\gametypes\_class::cac_getoffhand( class_num ); + loadoutDeathStreak = maps\mp\gametypes\_class::cac_getdeathstreak( class_num ); + } + else + { + class_num = maps\mp\gametypes\_class::getclassindex( class ); + self.class_num = class_num; + + loadoutPrimary = maps\mp\gametypes\_class::table_getweapon( level.classtablename, class_num, 0 ); + loadoutPrimaryAttachment = maps\mp\gametypes\_class::table_getweaponattachment( level.classtablename, class_num, 0, 0 ); + loadoutPrimaryAttachment2 = maps\mp\gametypes\_class::table_getweaponattachment( level.classtablename, class_num, 0, 1 ); + loadoutPrimaryCamo = maps\mp\gametypes\_class::table_getweaponcamo( level.classtablename, class_num, 0 ); + loadoutSecondaryCamo = maps\mp\gametypes\_class::table_getweaponcamo( level.classtablename, class_num, 1 ); + loadoutSecondary = maps\mp\gametypes\_class::table_getweapon( level.classtablename, class_num, 1 ); + loadoutSecondaryAttachment = maps\mp\gametypes\_class::table_getweaponattachment( level.classtablename, class_num, 1, 0 ); + loadoutSecondaryAttachment2 = maps\mp\gametypes\_class::table_getweaponattachment( level.classtablename, class_num, 1, 1 );; + loadoutSecondaryCamo = maps\mp\gametypes\_class::table_getweaponcamo( level.classtablename, class_num, 1 ); + loadoutEquipment = maps\mp\gametypes\_class::table_getequipment( level.classtablename, class_num, 0 ); + loadoutPerk1 = maps\mp\gametypes\_class::table_getperk( level.classtablename, class_num, 1 ); + loadoutPerk2 = maps\mp\gametypes\_class::table_getperk( level.classtablename, class_num, 2 ); + loadoutPerk3 = maps\mp\gametypes\_class::table_getperk( level.classtablename, class_num, 3 ); + loadoutOffhand = maps\mp\gametypes\_class::table_getoffhand( level.classtablename, class_num ); + loadoutDeathStreak = maps\mp\gametypes\_class::table_getdeathstreak( level.classtablename, class_num ); + } + + if ( loadoutPerk1 != "specialty_bling" ) + { + loadoutPrimaryAttachment2 = "none"; + loadoutSecondaryAttachment2 = "none"; + } + + if ( loadoutPerk1 != "specialty_onemanarmy" && loadoutSecondary == "onemanarmy" ) + { + loadoutSecondary = maps\mp\gametypes\_class::table_getweapon( level.classtablename, 10, 1 ); + } + + // loadoutSecondaryCamo = "none"; + + // stop default class op'ness + allowOp = ( getdvarint( "bots_loadout_allow_op" ) >= 1 ); + + if ( !allowOp ) + { + loadoutDeathStreak = "specialty_null"; + + if ( loadoutPrimary == "riotshield" ) + { + loadoutPrimary = "m4"; + } + + if ( loadoutSecondary == "at4" ) + { + loadoutSecondary = "usp"; + } + + if ( loadoutPrimaryAttachment == "gl" ) + { + loadoutPrimaryAttachment = "none"; + } + + if ( loadoutPerk2 == "specialty_coldblooded" ) + { + loadoutPerk2 = "specialty_null"; + } + + if ( loadoutPerk3 == "specialty_localjammer" ) + { + loadoutPerk3 = "specialty_null"; + } + } + + + if ( level.killstreakrewards ) + { + if ( getdvarint( "scr_classic" ) == 1 ) + { + loadoutKillstreak1 = "uav"; + loadoutKillstreak2 = "precision_airstrike"; + loadoutKillstreak3 = "helicopter"; + } + else + { + loadoutKillstreak1 = self getplayerdata( "killstreaks", 0 ); + loadoutKillstreak2 = self getplayerdata( "killstreaks", 1 ); + loadoutKillstreak3 = self getplayerdata( "killstreaks", 2 ); + } + } + else + { + loadoutKillstreak1 = "none"; + loadoutKillstreak2 = "none"; + loadoutKillstreak3 = "none"; + } + + secondaryName = maps\mp\gametypes\_class::buildweaponname( loadoutSecondary, loadoutSecondaryAttachment, loadoutSecondaryAttachment2 ); + self _giveweapon( secondaryName, int( tablelookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 ) ) ); + + self.loadoutprimarycamo = int( tablelookup( "mp/camoTable.csv", 1, loadoutPrimaryCamo, 0 ) ); + self.loadoutprimary = loadoutPrimary; + self.loadoutsecondary = loadoutSecondary; + self.loadoutsecondarycamo = int( tablelookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 ) ); + + self setoffhandprimaryclass( "other" ); + + // Action Slots + // self _setactionslot( 1, "" ); + self _setactionslot( 1, "nightvision" ); + self _setactionslot( 3, "altMode" ); + self _setactionslot( 4, "" ); + + // Perks + self _clearperks(); + self maps\mp\gametypes\_class::_detachall(); + + // these special case giving pistol death have to come before + // perk loadout to ensure player perk icons arent overwritten + if ( level.diehardmode ) + { + self maps\mp\perks\_perks::giveperk( "specialty_pistoldeath" ); + } + + // only give the deathstreak for the initial spawn for this life. + if ( loadoutDeathStreak != "specialty_null" && ( gettime() - self.spawntime ) < 0.1 ) + { + deathVal = int( tablelookup( "mp/perkTable.csv", 1, loadoutDeathStreak, 6 ) ); + + if ( self botGetPerkUpgrade( loadoutPerk1 ) == "specialty_rollover" || self botGetPerkUpgrade( loadoutPerk2 ) == "specialty_rollover" || self botGetPerkUpgrade( loadoutPerk3 ) == "specialty_rollover" ) + { + deathVal -= 1; + } + + if ( self.pers[ "cur_death_streak" ] == deathVal ) + { + self thread maps\mp\perks\_perks::giveperk( loadoutDeathStreak ); + self thread maps\mp\gametypes\_hud_message::splashnotify( loadoutDeathStreak ); + } + else if ( self.pers[ "cur_death_streak" ] > deathVal ) + { + self thread maps\mp\perks\_perks::giveperk( loadoutDeathStreak ); + } + } + + self botLoadoutAllPerks( loadoutEquipment, loadoutPerk1, loadoutPerk2, loadoutPerk3 ); + + self maps\mp\gametypes\_class::setkillstreaks( loadoutKillstreak1, loadoutKillstreak2, loadoutKillstreak3 ); + + if ( self hasperk( "specialty_extraammo", true ) && getweaponclass( secondaryName ) != "weapon_projectile" ) + { + self givemaxammo( secondaryName ); + } + + // Primary Weapon + primaryName = maps\mp\gametypes\_class::buildweaponname( loadoutPrimary, loadoutPrimaryAttachment, loadoutPrimaryAttachment2 ); + self _giveweapon( primaryName, self.loadoutprimarycamo ); + + // fix changing from a riotshield class to a riotshield class during grace period not giving a shield + if ( primaryName == "riotshield_mp" && level.ingraceperiod ) + { + self notify ( "weapon_change", "riotshield_mp" ); + } + + if ( self hasperk( "specialty_extraammo", true ) ) + { + self givemaxammo( primaryName ); + } + + self setspawnweapon( primaryName ); + + primaryTokens = strtok( primaryName, "_" ); + self.pers[ "primaryWeapon" ] = primaryTokens[ 0 ]; + + // Primary Offhand was given by giveperk (it's your perk1) + + // Secondary Offhand + offhandSecondaryWeapon = loadoutOffhand + "_mp"; + + if ( loadoutOffhand == "flash_grenade" ) + { + self setoffhandsecondaryclass( "flash" ); + } + else + { + self setoffhandsecondaryclass( "smoke" ); + } + + self giveweapon( offhandSecondaryWeapon ); + + if ( loadoutOffhand == "smoke_grenade" ) + { + self setweaponammoclip( offhandSecondaryWeapon, 1 ); + } + else if ( loadoutOffhand == "flash_grenade" ) + { + self setweaponammoclip( offhandSecondaryWeapon, 2 ); + } + else if ( loadoutOffhand == "concussion_grenade" ) + { + self setweaponammoclip( offhandSecondaryWeapon, 2 ); + } + else + { + self setweaponammoclip( offhandSecondaryWeapon, 1 ); + } + + primaryWeapon = primaryName; + self.primaryweapon = primaryWeapon; + self.secondaryweapon = secondaryName; + + self botPlayerModelForWeapon( self.pers[ "primaryWeapon" ], getbaseweaponname( secondaryName ) ); + + self.issniper = ( weaponclass( self.primaryweapon ) == "sniper" ); + + self maps\mp\gametypes\_weapons::updatemovespeedscale( "primary" ); + + // cac specialties that require loop threads + self maps\mp\perks\_perks::cac_selector(); + + self notify ( "changed_kit" ); + self notify( "bot_giveLoadout", allowCopycat ); +} + +/* + Patches giveLoadout so that it doesn't use IsItemUnlocked +*/ +botGetPerkUpgrade( perkName ) +{ + perkUpgrade = tablelookup( "mp/perktable.csv", 1, perkName, 8 ); + + if ( perkUpgrade == "" || perkUpgrade == "specialty_null" ) + { + return "specialty_null"; + } + + if ( !isdefined( self.pers[ "bots" ][ "unlocks" ][ "upgraded_" + perkName ] ) || !self.pers[ "bots" ][ "unlocks" ][ "upgraded_" + perkName ] ) + { + return "specialty_null"; + } + + return ( perkUpgrade ); +} + +/* + Patches giveLoadout so that it doesn't use IsItemUnlocked +*/ +botLoadoutAllPerks( loadoutEquipment, loadoutPerk1, loadoutPerk2, loadoutPerk3 ) +{ + loadoutEquipment = maps\mp\perks\_perks::validateperk( 1, loadoutEquipment ); + loadoutPerk1 = maps\mp\perks\_perks::validateperk( 1, loadoutPerk1 ); + loadoutPerk2 = maps\mp\perks\_perks::validateperk( 2, loadoutPerk2 ); + loadoutPerk3 = maps\mp\perks\_perks::validateperk( 3, loadoutPerk3 ); + + self maps\mp\perks\_perks::giveperk( loadoutEquipment ); + self maps\mp\perks\_perks::giveperk( loadoutPerk1 ); + self maps\mp\perks\_perks::giveperk( loadoutPerk2 ); + self maps\mp\perks\_perks::giveperk( loadoutPerk3 ); + + perks[ 0 ] = loadoutPerk1; + perks[ 1 ] = loadoutPerk2; + perks[ 2 ] = loadoutPerk3; + + perkUpgrd[ 0 ] = tablelookup( "mp/perktable.csv", 1, loadoutPerk1, 8 ); + perkUpgrd[ 1 ] = tablelookup( "mp/perktable.csv", 1, loadoutPerk2, 8 ); + perkUpgrd[ 2 ] = tablelookup( "mp/perktable.csv", 1, loadoutPerk3, 8 ); + + for ( i = 0; i < perkUpgrd.size; i++ ) + { + upgrade = perkUpgrd[ i ]; + perk = perks[ i ]; + + if ( upgrade == "" || upgrade == "specialty_null" ) + { + continue; + } + + if ( isdefined( self.pers[ "bots" ][ "unlocks" ][ "upgraded_" + perk ] ) && self.pers[ "bots" ][ "unlocks" ][ "upgraded_" + perk ] ) + { + self maps\mp\perks\_perks::giveperk( upgrade ); + } + } + +} + +/* + Patches giveLoadout so that it doesn't use IsItemUnlocked +*/ +botPlayerModelForWeapon( weapon, secondary ) +{ + team = self.team; + + + if ( isdefined( game[ team + "_model" ][ weapon ] ) ) + { + [[ game[ team + "_model" ][ weapon ] ]](); + return; + } + + + weaponclass = tablelookup( "mp/statstable.csv", 4, weapon, 2 ); + + switch ( weaponclass ) + { + case "weapon_smg": + [[ game[ team + "_model" ][ "SMG" ] ]](); + break; + + case "weapon_assault": + weaponclass = tablelookup( "mp/statstable.csv", 4, secondary, 2 ); + + if ( weaponclass == "weapon_shotgun" ) + { + [[ game[ team + "_model" ][ "SHOTGUN" ] ]](); + } + else + { + [[ game[ team + "_model" ][ "ASSAULT" ] ]](); + } + + break; + + case "weapon_sniper": + if ( level.environment != "" && isdefined( self.pers[ "bots" ][ "unlocks" ][ "ghillie" ] ) && self.pers[ "bots" ][ "unlocks" ][ "ghillie" ] ) + { + [[ game[ team + "_model" ][ "GHILLIE" ] ]](); + } + else + { + [[ game[ team + "_model" ][ "SNIPER" ] ]](); + } + + break; + + case "weapon_lmg": + [[ game[ team + "_model" ][ "LMG" ] ]](); + break; + + case "weapon_riot": + [[ game[ team + "_model" ][ "RIOT" ] ]](); + break; + + default: + [[ game[ team + "_model" ][ "ASSAULT" ] ]](); + break; + } +} + +/* + Make player ref attach to rocket ent +*/ +tryUsePredatorMissileFix( lifeId ) +{ + if ( isdefined( level.civilianjetflyby ) ) + { + self iprintlnbold( &"MP_CIVILIAN_AIR_TRAFFIC" ); + return false; + } + + self setusingremote( "remotemissile" ); + result = self maps\mp\killstreaks\_killstreaks::initridekillstreak(); + + if ( result != "success" ) + { + if ( result != "disconnect" ) + { + self clearusingremote(); + } + + return false; + } + + level thread _fireFix( lifeId, self ); + + return true; +} + +/* + Make player ref attach to rocket ent +*/ +_fireFix( lifeId, player ) +{ + remoteMissileSpawnArray = getentarray( "remoteMissileSpawn", "targetname" ); + //assertEX( remoteMissileSpawnArray.size > 0 && getMapCustom( "map" ) != "", "No remote missile spawn points found. Contact friendly neighborhood designer" ); + + foreach ( spawn in remoteMissileSpawnArray ) + { + if ( isdefined( spawn.target ) ) + { + spawn.targetent = getent( spawn.target, "targetname" ); + } + } + + if ( remoteMissileSpawnArray.size > 0 ) + { + remoteMissileSpawn = player maps\mp\killstreaks\_remotemissile::getbestspawnpoint( remoteMissileSpawnArray ); + } + else + { + remoteMissileSpawn = undefined; + } + + if ( isdefined( remoteMissileSpawn ) ) + { + startPos = remoteMissileSpawn.origin; + targetPos = remoteMissileSpawn.targetent.origin; + + //thread drawLine( startPos, targetPos, 30, (0,1,0) ); + + vector = vectornormalize( startPos - targetPos ); + startPos = vector_multiply( vector, 14000 ) + targetPos; + + //thread drawLine( startPos, targetPos, 15, (1,0,0) ); + + rocket = magicbullet( "remotemissile_projectile_mp", startpos, targetPos, player ); + } + else + { + upVector = ( 0, 0, level.missileremotelaunchvert ); + backDist = level.missileremotelaunchhorz; + targetDist = level.missileremotelaunchtargetdist; + + forward = anglestoforward( player.angles ); + startpos = player.origin + upVector + forward * backDist * -1; + targetPos = player.origin + forward * targetDist; + + rocket = magicbullet( "remotemissile_projectile_mp", startpos, targetPos, player ); + } + + if ( !isdefined( rocket ) ) + { + player clearusingremote(); + return; + } + + rocket thread maps\mp\gametypes\_weapons::addmissiletosighttraces( player.team ); + + rocket thread maps\mp\killstreaks\_remotemissile::handledamage(); + + rocket.lifeid = lifeId; + rocket.type = "remote"; + MissileEyesFix( player, rocket ); +} + +/* + Make player ref attach to rocket ent +*/ +MissileEyesFix( player, rocket ) +{ + //level endon ( "game_ended" ); + player endon( "joined_team" ); + player endon( "joined_spectators" ); + + rocket thread maps\mp\killstreaks\_remotemissile::rocket_cleanupondeath(); + player thread maps\mp\killstreaks\_remotemissile::player_cleanupongameended( rocket ); + player thread maps\mp\killstreaks\_remotemissile::player_cleanuponteamchange( rocket ); + + player visionsetmissilecamforplayer( "black_bw", 0 ); + + player endon ( "disconnect" ); + + if ( isdefined( rocket ) ) + { + player visionsetmissilecamforplayer( game["thermal_vision"], 1.0 ); + player thread maps\mp\killstreaks\_remotemissile::delayedfofoverlay(); + player cameralinkto( rocket, "tag_origin" ); + player controlslinkto( rocket ); + + // our additions + player.rocket = rocket; + rocket.owner = player; + + if ( getdvarint( "camera_thirdPerson" ) ) + { + player setthirdpersondof( false ); + } + + rocket waittill( "death" ); + + // is defined check required because remote missile doesnt handle lifetime explosion gracefully + // instantly deletes its self after an explode and death notify + if ( isdefined( rocket ) ) + { + player maps\mp\_matchdata::logkillstreakevent( "predator_missile", rocket.origin ); + } + + player controlsunlink(); + player freezecontrolswrapper( true ); + player.rocket = undefined; // our addition + + // If a player gets the final kill with a hellfire, level.gameEnded will already be true at this point + if ( !level.gameended || isdefined( player.finalkill ) ) + { + player thread maps\mp\killstreaks\_remotemissile::staticeffect( 0.5 ); + } + + wait ( 0.5 ); + + player thermalvisionfofoverlayoff(); + + player cameraunlink(); + + if ( getdvarint( "camera_thirdPerson" ) ) + { + player setthirdpersondof( true ); + } + + } + + player clearusingremote(); +} diff --git a/maps/mp/bots/_menu.gsc b/maps/mp/bots/_menu.gsc new file mode 100644 index 0000000..eaa414a --- /dev/null +++ b/maps/mp/bots/_menu.gsc @@ -0,0 +1,1389 @@ +/* + _menu + Author: INeedGames + Date: 09/26/2020 + The ingame menu. +*/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\bots\_bot_utility; + +init() +{ + if ( getdvar( "bots_main_menu" ) == "" ) + { + setdvar( "bots_main_menu", true ); + } + + if ( !getdvarint( "bots_main_menu" ) ) + { + return; + } + + thread watchPlayers(); +} + +watchPlayers() +{ + for ( ;; ) + { + wait 1; + + if ( !getdvarint( "bots_main_menu" ) ) + { + return; + } + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[ i ]; + + if ( !player is_host() ) + { + continue; + } + + if ( isdefined( player.menuinit ) && player.menuinit ) + { + continue; + } + + player thread init_menu(); + } + } +} + +destroyFixed() +{ + if ( !isdefined( self ) ) + { + return; + } + + self destroy(); +} + +removeChildFixed( element ) +{ + temp = []; + + for ( i = 0; i < self.children.size ; i++ ) + { + if ( isdefined( self.children[ i ] ) && self.children[ i ] != element ) + { + self.children[ i ].index = temp.size; + temp[ temp.size ] = self.children[ i ]; + } + } + + self.children = temp; + + element.index = undefined; + element.parent = undefined; +} + +destroyElemFixed() +{ + if ( !isdefined( self ) ) + { + return; + } + + if ( isdefined( self.parent ) ) + { + self.parent removeChildFixed( self ); + } + + self destroyelem(); +} + +kill_menu() +{ + self notify( "bots_kill_menu" ); + self.menuinit = undefined; +} + +init_menu() +{ + self.menuinit = true; + + self.menuopen = false; + self.submenu = "Main"; + self.curs[ "Main" ][ "X" ] = 0; + self addOptions(); + + self thread watchPlayerOpenMenu(); + self thread MenuSelect(); + self thread RightMenu(); + self thread LeftMenu(); + self thread UpMenu(); + self thread DownMenu(); + + self thread watchDisconnect(); + + self thread doGreetings(); +} + +watchDisconnect() +{ + self waittill_either( "disconnect", "bots_kill_menu" ); + + if ( self.menuopen ) + { + if ( isdefined( self.menutexty ) ) + { + for ( i = 0; i < self.menutexty.size; i++ ) + { + if ( isdefined( self.menutexty[ i ] ) ) + { + self.menutexty[ i ] destroyElemFixed(); + } + } + } + + if ( isdefined( self.menutext ) ) + { + for ( i = 0; i < self.menutext.size; i++ ) + { + if ( isdefined( self.menutext[ i ] ) ) + { + self.menutext[ i ] destroyElemFixed(); + } + } + } + + if ( isdefined( self.menu ) && isdefined( self.menu[ "X" ] ) ) + { + if ( isdefined( self.menu[ "X" ][ "Shader" ] ) ) + { + self.menu[ "X" ][ "Shader" ] destroyElemFixed(); + } + + if ( isdefined( self.menu[ "X" ][ "Scroller" ] ) ) + { + self.menu[ "X" ][ "Scroller" ] destroyElemFixed(); + } + } + + if ( isdefined( self.menuversionhud ) ) + { + self.menuversionhud destroyFixed(); + } + } +} + +doGreetings() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + wait 1; + self iprintln( "Welcome to Bot Warfare " + self.name + "!" ); + wait 5; + self iprintln( "Press [{+actionslot 2}] to open menu!" ); +} + +watchPlayerOpenMenu() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + self notifyonplayercommand( "bots_open_menu", "+actionslot 2" ); + + for ( ;; ) + { + self waittill( "bots_open_menu" ); + + if ( !self.menuopen ) + { + self playlocalsound( "mouse_click" ); + self thread OpenSub( self.submenu ); + } + else + { + self playlocalsound( "mouse_click" ); + + if ( self.submenu != "Main" ) + { + self ExitSub(); + } + else + { + self ExitMenu(); + + if ( !gameflag( "prematch_done" ) || level.gameended ) + { + self freezecontrols( true ); + } + else + { + self freezecontrols( false ); + } + } + } + } +} + +MenuSelect() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + self notifyonplayercommand( "bots_select", "+gostand" ); + + for ( ;; ) + { + self waittill( "bots_select" ); + + if ( self.menuopen ) + { + self playlocalsound( "mouse_click" ); + + if ( self.submenu == "Main" ) + { + self thread [[ self.option[ "Function" ][ self.submenu ][ self.curs[ "Main" ][ "X" ] ] ]]( self.option[ "Arg1" ][ self.submenu ][ self.curs[ "Main" ][ "X" ] ], self.option[ "Arg2" ][ self.submenu ][ self.curs[ "Main" ][ "X" ] ] ); + } + else + { + self thread [[ self.option[ "Function" ][ self.submenu ][ self.curs[ self.submenu ][ "Y" ] ] ]]( self.option[ "Arg1" ][ self.submenu ][ self.curs[ self.submenu ][ "Y" ] ], self.option[ "Arg2" ][ self.submenu ][ self.curs[ self.submenu ][ "Y" ] ] ); + } + } + } +} + +LeftMenu() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + self notifyonplayercommand( "bots_left", "+moveleft" ); + + for ( ;; ) + { + self waittill( "bots_left" ); + + if ( self.menuopen && self.submenu == "Main" ) + { + self playlocalsound( "mouse_over" ); + self.curs[ "Main" ][ "X" ]--; + + if ( self.curs[ "Main" ][ "X" ] < 0 ) + { + self.curs[ "Main" ][ "X" ] = self.option[ "Name" ][ self.submenu ].size - 1; + } + + self CursMove( "X" ); + } + } +} + +RightMenu() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + self notifyonplayercommand( "bots_right", "+moveright" ); + + for ( ;; ) + { + self waittill( "bots_right" ); + + if ( self.menuopen && self.submenu == "Main" ) + { + self playlocalsound( "mouse_over" ); + self.curs[ "Main" ][ "X" ]++; + + if ( self.curs[ "Main" ][ "X" ] > self.option[ "Name" ][ self.submenu ].size - 1 ) + { + self.curs[ "Main" ][ "X" ] = 0; + } + + self CursMove( "X" ); + } + } +} + +UpMenu() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + self notifyonplayercommand( "bots_up", "+forward" ); + + for ( ;; ) + { + self waittill( "bots_up" ); + + if ( self.menuopen && self.submenu != "Main" ) + { + self playlocalsound( "mouse_over" ); + self.curs[ self.submenu ][ "Y" ]--; + + if ( self.curs[ self.submenu ][ "Y" ] < 0 ) + { + self.curs[ self.submenu ][ "Y" ] = self.option[ "Name" ][ self.submenu ].size - 1; + } + + self CursMove( "Y" ); + } + } +} + +DownMenu() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + self notifyonplayercommand( "bots_down", "+back" ); + + for ( ;; ) + { + self waittill( "bots_down" ); + + if ( self.menuopen && self.submenu != "Main" ) + { + self playlocalsound( "mouse_over" ); + self.curs[ self.submenu ][ "Y" ]++; + + if ( self.curs[ self.submenu ][ "Y" ] > self.option[ "Name" ][ self.submenu ].size - 1 ) + { + self.curs[ self.submenu ][ "Y" ] = 0; + } + + self CursMove( "Y" ); + } + } +} + +OpenSub( menu, menu2 ) +{ + if ( menu != "Main" && ( !isdefined( self.menu[ menu ] ) || !!isdefined( self.menu[ menu ][ "FirstOpen" ] ) ) ) + { + self.curs[ menu ][ "Y" ] = 0; + self.menu[ menu ][ "FirstOpen" ] = true; + } + + logOldi = true; + self.submenu = menu; + + if ( self.submenu == "Main" ) + { + if ( isdefined( self.menutext ) ) + { + for ( i = 0; i < self.menutext.size; i++ ) + { + if ( isdefined( self.menutext[ i ] ) ) + { + self.menutext[ i ] destroyElemFixed(); + } + } + } + + if ( isdefined( self.menu ) && isdefined( self.menu[ "X" ] ) ) + { + if ( isdefined( self.menu[ "X" ][ "Shader" ] ) ) + { + self.menu[ "X" ][ "Shader" ] destroyElemFixed(); + } + + if ( isdefined( self.menu[ "X" ][ "Scroller" ] ) ) + { + self.menu[ "X" ][ "Scroller" ] destroyElemFixed(); + } + } + + if ( isdefined( self.menuversionhud ) ) + { + self.menuversionhud destroyFixed(); + } + + for ( i = 0 ; i < self.option[ "Name" ][ self.submenu ].size ; i++ ) + { + self.menutext[ i ] = self createfontstring( "default", 1.6 ); + self.menutext[ i ] setpoint( "CENTER", "CENTER", -300 + ( i * 100 ), -226 ); + self.menutext[ i ] settext( self.option[ "Name" ][ self.submenu ][ i ] ); + + if ( logOldi ) + { + self.oldi = i; + } + + if ( self.menutext[ i ].x > 300 ) + { + logOldi = false; + x = i - self.oldi; + self.menutext[ i ] setpoint( "CENTER", "CENTER", ( ( ( -300 ) - ( i * 100 ) ) + ( i * 100 ) ) + ( x * 100 ), -196 ); + } + + self.menutext[ i ].alpha = 1; + self.menutext[ i ].sort = 999; + } + + if ( !logOldi ) + { + self.menu[ "X" ][ "Shader" ] = self createRectangle( "CENTER", "CENTER", 0, -225, 1000, 90, ( 0, 0, 0 ), -2, 1, "white" ); + } + else + { + self.menu[ "X" ][ "Shader" ] = self createRectangle( "CENTER", "CENTER", 0, -225, 1000, 30, ( 0, 0, 0 ), -2, 1, "white" ); + } + + self.menu[ "X" ][ "Scroller" ] = self createRectangle( "CENTER", "CENTER", self.menutext[ self.curs[ "Main" ][ "X" ] ].x, -225, 105, 22, ( 1, 0, 0 ), -1, 1, "white" ); + + self CursMove( "X" ); + + self.menuversionhud = initHudElem( "Bot Warfare " + level.bw_version, 0, 0 ); + + self.menuopen = true; + } + else + { + if ( isdefined( self.menutexty ) ) + { + for ( i = 0 ; i < self.menutexty.size ; i++ ) + { + if ( isdefined( self.menutexty[ i ] ) ) + { + self.menutexty[ i ] destroyElemFixed(); + } + } + } + + for ( i = 0 ; i < self.option[ "Name" ][ self.submenu ].size ; i++ ) + { + self.menutexty[ i ] = self createfontstring( "default", 1.6 ); + self.menutexty[ i ] setpoint( "CENTER", "CENTER", self.menutext[ self.curs[ "Main" ][ "X" ] ].x, -160 + ( i * 20 ) ); + self.menutexty[ i ] settext( self.option[ "Name" ][ self.submenu ][ i ] ); + self.menutexty[ i ].alpha = 1; + self.menutexty[ i ].sort = 999; + } + + self CursMove( "Y" ); + } +} + +CursMove( direction ) +{ + self notify( "scrolled" ); + + if ( self.submenu == "Main" ) + { + if ( isdefined( self.menutext ) ) + { + self.menu[ "X" ][ "Scroller" ].x = self.menutext[ self.curs[ "Main" ][ "X" ] ].x; + self.menu[ "X" ][ "Scroller" ].y = self.menutext[ self.curs[ "Main" ][ "X" ] ].y; + + for ( i = 0; i < self.menutext.size; i++ ) + { + if ( isdefined( self.menutext[ i ] ) ) + { + self.menutext[ i ].fontscale = 1.5; + self.menutext[ i ].color = ( 1, 1, 1 ); + self.menutext[ i ].glowalpha = 0; + } + } + } + + self thread ShowOptionOn( direction ); + } + else + { + if ( isdefined( self.menutexty ) ) + { + for ( i = 0; i < self.menutexty.size; i++ ) + { + if ( isdefined( self.menutexty[ i ] ) ) + { + self.menutexty[ i ].fontscale = 1.5; + self.menutexty[ i ].color = ( 1, 1, 1 ); + self.menutexty[ i ].glowalpha = 0; + } + } + } + + if ( isdefined( self.menutext ) ) + { + for ( i = 0; i < self.menutext.size; i++ ) + { + if ( isdefined( self.menutext[ i ] ) ) + { + self.menutext[ i ].fontscale = 1.5; + self.menutext[ i ].color = ( 1, 1, 1 ); + self.menutext[ i ].glowalpha = 0; + } + } + } + + self thread ShowOptionOn( direction ); + } +} + +ShowOptionOn( variable ) +{ + self endon( "scrolled" ); + self endon( "disconnect" ); + self endon( "exit" ); + self endon( "bots_kill_menu" ); + + for ( time = 0;; time += 0.05 ) + { + if ( !self isonground() && isalive( self ) && gameflag( "prematch_done" ) && !level.gameended ) + { + self freezecontrols( false ); + } + else + { + self freezecontrols( true ); + } + + self setclientdvar( "r_blur", "5" ); + self setclientdvar( "sc_blur", "15" ); + self addOptions(); + + if ( self.submenu == "Main" ) + { + if ( isdefined( self.curs[ self.submenu ][ variable ] ) && isdefined( self.menutext ) && isdefined( self.menutext[ self.curs[ self.submenu ][ variable ] ] ) ) + { + self.menutext[ self.curs[ self.submenu ][ variable ] ].fontscale = 2.0; + // self.menutext[ self.curs[ self.submenu ][ variable ] ].color = (randomint(256)/255, randomint(256)/255, randomint(256)/255); + color = ( 6 / 255, 69 / 255, 173 + randomintrange( -5, 5 ) / 255 ); + + if ( int( time * 4 ) % 2 ) + { + color = ( 11 / 255, 0 / 255, 128 + randomintrange( -10, 10 ) / 255 ); + } + + self.menutext[ self.curs[ self.submenu ][ variable ] ].color = color; + } + + if ( isdefined( self.menutext ) ) + { + for ( i = 0; i < self.option[ "Name" ][ self.submenu ].size; i++ ) + { + if ( isdefined( self.menutext[ i ] ) ) + { + self.menutext[ i ] settext( self.option[ "Name" ][ self.submenu ][ i ] ); + } + } + } + } + else + { + if ( isdefined( self.curs[ self.submenu ][ variable ] ) && isdefined( self.menutexty ) && isdefined( self.menutexty[ self.curs[ self.submenu ][ variable ] ] ) ) + { + self.menutexty[ self.curs[ self.submenu ][ variable ] ].fontscale = 2.0; + // self.menutexty[ self.curs[ self.submenu ][ variable ] ].color = (randomint(256)/255, randomint(256)/255, randomint(256)/255); + color = ( 6 / 255, 69 / 255, 173 + randomintrange( -5, 5 ) / 255 ); + + if ( int( time * 4 ) % 2 ) + { + color = ( 11 / 255, 0 / 255, 128 + randomintrange( -10, 10 ) / 255 ); + } + + self.menutexty[ self.curs[ self.submenu ][ variable ] ].color = color; + } + + if ( isdefined( self.menutexty ) ) + { + for ( i = 0; i < self.option[ "Name" ][ self.submenu ].size; i++ ) + { + if ( isdefined( self.menutexty[ i ] ) ) + { + self.menutexty[ i ] settext( self.option[ "Name" ][ self.submenu ][ i ] ); + } + } + } + } + + wait 0.05; + } +} + +AddMenu( menu, num, text, function, arg1, arg2 ) +{ + self.option[ "Name" ][ menu ][ num ] = text; + self.option[ "Function" ][ menu ][ num ] = function; + self.option[ "Arg1" ][ menu ][ num ] = arg1; + self.option[ "Arg2" ][ menu ][ num ] = arg2; +} + +AddBack( menu, back ) +{ + self.menu[ "Back" ][ menu ] = back; +} + +ExitSub() +{ + if ( isdefined( self.menutexty ) ) + { + for ( i = 0; i < self.menutexty.size; i++ ) + { + if ( isdefined( self.menutexty[ i ] ) ) + { + self.menutexty[ i ] destroyElemFixed(); + } + } + } + + self.submenu = self.menu[ "Back" ][ self.submenu ]; + + if ( self.submenu == "Main" ) + { + self CursMove( "X" ); + } + else + { + self CursMove( "Y" ); + } +} + +ExitMenu() +{ + if ( isdefined( self.menutext ) ) + { + for ( i = 0; i < self.menutext.size; i++ ) + { + if ( isdefined( self.menutext[ i ] ) ) + { + self.menutext[ i ] destroyElemFixed(); + } + } + } + + if ( isdefined( self.menu ) && isdefined( self.menu[ "X" ] ) ) + { + if ( isdefined( self.menu[ "X" ][ "Shader" ] ) ) + { + self.menu[ "X" ][ "Shader" ] destroyElemFixed(); + } + + if ( isdefined( self.menu[ "X" ][ "Scroller" ] ) ) + { + self.menu[ "X" ][ "Scroller" ] destroyElemFixed(); + } + } + + if ( isdefined( self.menuversionhud ) ) + { + self.menuversionhud destroyFixed(); + } + + self.menuopen = false; + self notify( "exit" ); + + self setclientdvar( "r_blur", "0" ); + self setclientdvar( "sc_blur", "2" ); +} + +initHudElem( txt, xl, yl ) +{ + hud = newclienthudelem( self ); + hud settext( txt ); + hud.alignx = "center"; + hud.aligny = "bottom"; + hud.horzalign = "center"; + hud.vertalign = "bottom"; + hud.x = xl; + hud.y = yl; + hud.foreground = true; + hud.fontscale = 1; + hud.font = "objective"; + hud.alpha = 1; + hud.glow = 0; + hud.glowcolor = ( 0, 0, 0 ); + hud.glowalpha = 1; + hud.color = ( 1.0, 1.0, 1.0 ); + + return hud; +} + +createRectangle( align, relative, x, y, width, height, color, sort, alpha, shader ) +{ + barElemBG = newclienthudelem( self ); + barElemBG.elemtype = "bar_"; + barElemBG.width = width; + barElemBG.height = height; + barElemBG.align = align; + barElemBG.relative = relative; + barElemBG.xoffset = 0; + barElemBG.yoffset = 0; + barElemBG.children = []; + barElemBG.sort = sort; + barElemBG.color = color; + barElemBG.alpha = alpha; + barElemBG setparent( level.uiparent ); + barElemBG setshader( shader, width, height ); + barElemBG.hidden = false; + barElemBG setpoint( align, relative, x, y ); + return barElemBG; +} + +addOptions() +{ + self AddMenu( "Main", 0, "Manage bots", ::OpenSub, "man_bots", "" ); + self AddBack( "man_bots", "Main" ); + + _temp = ""; + _tempDvar = getdvarint( "bots_manage_add" ); + self AddMenu( "man_bots", 0, "Add 1 bot", ::man_bots, "add", 1 + _tempDvar ); + self AddMenu( "man_bots", 1, "Add 3 bot", ::man_bots, "add", 3 + _tempDvar ); + self AddMenu( "man_bots", 2, "Add 7 bot", ::man_bots, "add", 7 + _tempDvar ); + self AddMenu( "man_bots", 3, "Add 11 bot", ::man_bots, "add", 11 + _tempDvar ); + self AddMenu( "man_bots", 4, "Add 17 bot", ::man_bots, "add", 17 + _tempDvar ); + self AddMenu( "man_bots", 5, "Kick a bot", ::man_bots, "kick", 1 ); + self AddMenu( "man_bots", 6, "Kick all bots", ::man_bots, "kick", getBotArray().size ); + + _tempDvar = getdvarint( "bots_manage_fill_kick" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "man_bots", 7, "Toggle auto bot kicking: " + _temp, ::man_bots, "autokick", _tempDvar ); + + _tempDvar = getdvarint( "bots_manage_fill_mode" ); + + switch ( _tempDvar ) + { + case 0: + _temp = "everyone"; + break; + + case 1: + _temp = "just bots"; + break; + + case 2: + _temp = "everyone, adjust to map"; + break; + + case 3: + _temp = "just bots, adjust to map"; + break; + + case 4: + _temp = "bots used as team balance"; + break; + + default: + _temp = "out of range"; + break; + } + + self AddMenu( "man_bots", 8, "Change bot_fill_mode: " + _temp, ::man_bots, "fillmode", _tempDvar ); + + _tempDvar = getdvarint( "bots_manage_fill" ); + self AddMenu( "man_bots", 9, "Increase bots to keep in-game: " + _tempDvar, ::man_bots, "fillup", _tempDvar ); + self AddMenu( "man_bots", 10, "Decrease bots to keep in-game: " + _tempDvar, ::man_bots, "filldown", _tempDvar ); + + _tempDvar = getdvarint( "bots_manage_fill_spec" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "man_bots", 11, "Count players for fill on spectator: " + _temp, ::man_bots, "fillspec", _tempDvar ); + + // + + self AddMenu( "Main", 1, "Teams and difficulty", ::OpenSub, "man_team", "" ); + self AddBack( "man_team", "Main" ); + + _tempDvar = getdvar( "bots_team" ); + self AddMenu( "man_team", 0, "Change bot team: " + _tempDvar, ::bot_teams, "team", _tempDvar ); + + _tempDvar = getdvarint( "bots_team_amount" ); + self AddMenu( "man_team", 1, "Increase bots to be on axis team: " + _tempDvar, ::bot_teams, "teamup", _tempDvar ); + self AddMenu( "man_team", 2, "Decrease bots to be on axis team: " + _tempDvar, ::bot_teams, "teamdown", _tempDvar ); + + _tempDvar = getdvarint( "bots_team_force" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "man_team", 3, "Toggle forcing bots on team: " + _temp, ::bot_teams, "teamforce", _tempDvar ); + + _tempDvar = getdvarint( "bots_team_mode" ); + + if ( _tempDvar ) + { + _temp = "only bots"; + } + else + { + _temp = "everyone"; + } + + self AddMenu( "man_team", 4, "Toggle bot_team_bot: " + _temp, ::bot_teams, "teammode", _tempDvar ); + + _tempDvar = getdvarint( "bots_skill" ); + + switch ( _tempDvar ) + { + case 0: + _temp = "random for all"; + break; + + case 1: + _temp = "too easy"; + break; + + case 2: + _temp = "easy"; + break; + + case 3: + _temp = "easy-medium"; + break; + + case 4: + _temp = "medium"; + break; + + case 5: + _temp = "hard"; + break; + + case 6: + _temp = "very hard"; + break; + + case 7: + _temp = "hardest"; + break; + + case 8: + _temp = "custom"; + break; + + case 9: + _temp = "complete random"; + break; + + default: + _temp = "out of range"; + break; + } + + self AddMenu( "man_team", 5, "Change bot difficulty: " + _temp, ::bot_teams, "skill", _tempDvar ); + + _tempDvar = getdvarint( "bots_skill_axis_hard" ); + self AddMenu( "man_team", 6, "Increase amount of hard bots on axis team: " + _tempDvar, ::bot_teams, "axishardup", _tempDvar ); + self AddMenu( "man_team", 7, "Decrease amount of hard bots on axis team: " + _tempDvar, ::bot_teams, "axisharddown", _tempDvar ); + + _tempDvar = getdvarint( "bots_skill_axis_med" ); + self AddMenu( "man_team", 8, "Increase amount of med bots on axis team: " + _tempDvar, ::bot_teams, "axismedup", _tempDvar ); + self AddMenu( "man_team", 9, "Decrease amount of med bots on axis team: " + _tempDvar, ::bot_teams, "axismeddown", _tempDvar ); + + _tempDvar = getdvarint( "bots_skill_allies_hard" ); + self AddMenu( "man_team", 10, "Increase amount of hard bots on allies team: " + _tempDvar, ::bot_teams, "allieshardup", _tempDvar ); + self AddMenu( "man_team", 11, "Decrease amount of hard bots on allies team: " + _tempDvar, ::bot_teams, "alliesharddown", _tempDvar ); + + _tempDvar = getdvarint( "bots_skill_allies_med" ); + self AddMenu( "man_team", 12, "Increase amount of med bots on allies team: " + _tempDvar, ::bot_teams, "alliesmedup", _tempDvar ); + self AddMenu( "man_team", 13, "Decrease amount of med bots on allies team: " + _tempDvar, ::bot_teams, "alliesmeddown", _tempDvar ); + + // + + self AddMenu( "Main", 2, "Bot settings", ::OpenSub, "set1", "" ); + self AddBack( "set1", "Main" ); + + _tempDvar = getdvarint( "bots_loadout_reasonable" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 0, "Bots use only good class setups: " + _temp, ::bot_func, "reasonable", _tempDvar ); + + _tempDvar = getdvarint( "bots_loadout_allow_op" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 1, "Bots can use op and annoying class setups: " + _temp, ::bot_func, "op", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_move" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 2, "Bots can move: " + _temp, ::bot_func, "move", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_knife" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 3, "Bots can knife: " + _temp, ::bot_func, "knife", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_fire" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 4, "Bots can fire: " + _temp, ::bot_func, "fire", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_nade" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 5, "Bots can nade: " + _temp, ::bot_func, "nade", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_take_carepackages" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 6, "Bots can take carepackages: " + _temp, ::bot_func, "care", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_obj" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 7, "Bots play the objective: " + _temp, ::bot_func, "obj", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_camp" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 8, "Bots can camp: " + _temp, ::bot_func, "camp", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_jumpdrop" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 9, "Bots can jump and dropshot: " + _temp, ::bot_func, "jump", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_target_other" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 10, "Bots can target other script objects: " + _temp, ::bot_func, "targetother", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_killstreak" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 11, "Bots can use killstreaks: " + _temp, ::bot_func, "killstreak", _tempDvar ); + + _tempDvar = getdvarint( "bots_play_ads" ); + + if ( _tempDvar ) + { + _temp = "true"; + } + else + { + _temp = "false"; + } + + self AddMenu( "set1", 12, "Bots can ads: " + _temp, ::bot_func, "ads", _tempDvar ); +} + +bot_func( a, b ) +{ + switch ( a ) + { + case "reasonable": + setdvar( "bots_loadout_reasonable", !b ); + self iprintln( "Bots using reasonable setups: " + !b ); + break; + + case "op": + setdvar( "bots_loadout_allow_op", !b ); + self iprintln( "Bots using op setups: " + !b ); + break; + + case "move": + setdvar( "bots_play_move", !b ); + self iprintln( "Bots move: " + !b ); + break; + + case "knife": + setdvar( "bots_play_knife", !b ); + self iprintln( "Bots knife: " + !b ); + break; + + case "fire": + setdvar( "bots_play_fire", !b ); + self iprintln( "Bots fire: " + !b ); + break; + + case "nade": + setdvar( "bots_play_nade", !b ); + self iprintln( "Bots nade: " + !b ); + break; + + case "care": + setdvar( "bots_play_take_carepackages", !b ); + self iprintln( "Bots take carepackages: " + !b ); + break; + + case "obj": + setdvar( "bots_play_obj", !b ); + self iprintln( "Bots play the obj: " + !b ); + break; + + case "camp": + setdvar( "bots_play_camp", !b ); + self iprintln( "Bots camp: " + !b ); + break; + + case "jump": + setdvar( "bots_play_jumpdrop", !b ); + self iprintln( "Bots jump: " + !b ); + break; + + case "targetother": + setdvar( "bots_play_target_other", !b ); + self iprintln( "Bots target other: " + !b ); + break; + + case "killstreak": + setdvar( "bots_play_killstreak", !b ); + self iprintln( "Bots use killstreaks: " + !b ); + break; + + case "ads": + setdvar( "bots_play_ads", !b ); + self iprintln( "Bots ads: " + !b ); + break; + } +} + +bot_teams( a, b ) +{ + switch ( a ) + { + case "team": + switch ( b ) + { + case "autoassign": + setdvar( "bots_team", "allies" ); + self iprintlnbold( "Changed bot team to allies." ); + break; + + case "allies": + setdvar( "bots_team", "axis" ); + self iprintlnbold( "Changed bot team to axis." ); + break; + + case "axis": + setdvar( "bots_team", "custom" ); + self iprintlnbold( "Changed bot team to custom." ); + break; + + default: + setdvar( "bots_team", "autoassign" ); + self iprintlnbold( "Changed bot team to autoassign." ); + break; + } + + break; + + case "teamup": + setdvar( "bots_team_amount", b + 1 ); + self iprintln( ( b + 1 ) + " bot(s) will try to be on axis team." ); + break; + + case "teamdown": + setdvar( "bots_team_amount", b - 1 ); + self iprintln( ( b - 1 ) + " bot(s) will try to be on axis team." ); + break; + + case "teamforce": + setdvar( "bots_team_force", !b ); + self iprintln( "Forcing bots to team: " + !b ); + break; + + case "teammode": + setdvar( "bots_team_mode", !b ); + self iprintln( "Only count bots on team: " + !b ); + break; + + case "skill": + switch ( b ) + { + case 0: + self iprintlnbold( "Changed bot skill to easy." ); + setdvar( "bots_skill", 1 ); + break; + + case 1: + self iprintlnbold( "Changed bot skill to easy-med." ); + setdvar( "bots_skill", 2 ); + break; + + case 2: + self iprintlnbold( "Changed bot skill to medium." ); + setdvar( "bots_skill", 3 ); + break; + + case 3: + self iprintlnbold( "Changed bot skill to med-hard." ); + setdvar( "bots_skill", 4 ); + break; + + case 4: + self iprintlnbold( "Changed bot skill to hard." ); + setdvar( "bots_skill", 5 ); + break; + + case 5: + self iprintlnbold( "Changed bot skill to very hard." ); + setdvar( "bots_skill", 6 ); + break; + + case 6: + self iprintlnbold( "Changed bot skill to hardest." ); + setdvar( "bots_skill", 7 ); + break; + + case 7: + self iprintlnbold( "Changed bot skill to custom. Base is easy." ); + setdvar( "bots_skill", 8 ); + break; + + case 8: + self iprintlnbold( "Changed bot skill to complete random. Takes effect at restart." ); + setdvar( "bots_skill", 9 ); + break; + + default: + self iprintlnbold( "Changed bot skill to random. Takes effect at restart." ); + setdvar( "bots_skill", 0 ); + break; + } + + break; + + case "axishardup": + setdvar( "bots_skill_axis_hard", ( b + 1 ) ); + self iprintln( ( ( b + 1 ) ) + " hard bots will be on axis team." ); + break; + + case "axisharddown": + setdvar( "bots_skill_axis_hard", ( b - 1 ) ); + self iprintln( ( ( b - 1 ) ) + " hard bots will be on axis team." ); + break; + + case "axismedup": + setdvar( "bots_skill_axis_med", ( b + 1 ) ); + self iprintln( ( ( b + 1 ) ) + " med bots will be on axis team." ); + break; + + case "axismeddown": + setdvar( "bots_skill_axis_med", ( b - 1 ) ); + self iprintln( ( ( b - 1 ) ) + " med bots will be on axis team." ); + break; + + case "allieshardup": + setdvar( "bots_skill_allies_hard", ( b + 1 ) ); + self iprintln( ( ( b + 1 ) ) + " hard bots will be on allies team." ); + break; + + case "alliesharddown": + setdvar( "bots_skill_allies_hard", ( b - 1 ) ); + self iprintln( ( ( b - 1 ) ) + " hard bots will be on allies team." ); + break; + + case "alliesmedup": + setdvar( "bots_skill_allies_med", ( b + 1 ) ); + self iprintln( ( ( b + 1 ) ) + " med bots will be on allies team." ); + break; + + case "alliesmeddown": + setdvar( "bots_skill_allies_med", ( b - 1 ) ); + self iprintln( ( ( b - 1 ) ) + " med bots will be on allies team." ); + break; + } +} + +man_bots( a, b ) +{ + switch ( a ) + { + case "add": + setdvar( "bots_manage_add", b ); + + if ( b == 1 ) + { + self iprintln( "Adding " + b + " bot." ); + } + else + { + self iprintln( "Adding " + b + " bots." ); + } + + break; + + case "kick": + result = false; + + for ( i = 0; i < b; i++ ) + { + tempBot = random( getBotArray() ); + + if ( isdefined( tempBot ) ) + { + kick( tempBot getentitynumber(), "EXE_PLAYERKICKED" ); + result = true; + } + + wait 0.25; + } + + if ( !result ) + { + self iprintln( "No bots to kick" ); + } + + break; + + case "autokick": + setdvar( "bots_manage_fill_kick", !b ); + self iprintln( "Kicking bots when bots_fill is exceeded: " + !b ); + break; + + case "fillmode": + switch ( b ) + { + case 0: + setdvar( "bots_manage_fill_mode", 1 ); + self iprintln( "bot_fill will now count only bots." ); + break; + + case 1: + setdvar( "bots_manage_fill_mode", 2 ); + self iprintln( "bot_fill will now count everyone, adjusting to map." ); + break; + + case 2: + setdvar( "bots_manage_fill_mode", 3 ); + self iprintln( "bot_fill will now count only bots, adjusting to map." ); + break; + + case 3: + setdvar( "bots_manage_fill_mode", 4 ); + self iprintln( "bot_fill will now use bots as team balance." ); + break; + + default: + setdvar( "bots_manage_fill_mode", 0 ); + self iprintln( "bot_fill will now count everyone." ); + break; + } + + break; + + case "fillup": + setdvar( "bots_manage_fill", b + 1 ); + self iprintln( "Increased to maintain " + ( b + 1 ) + " bot(s)." ); + break; + + case "filldown": + setdvar( "bots_manage_fill", b - 1 ); + self iprintln( "Decreased to maintain " + ( b - 1 ) + " bot(s)." ); + break; + + case "fillspec": + setdvar( "bots_manage_fill_spec", !b ); + self iprintln( "Count players on spectator for bots_fill: " + !b ); + break; + } +} diff --git a/maps/mp/bots/_wp_editor.gsc b/maps/mp/bots/_wp_editor.gsc new file mode 100644 index 0000000..0fa7061 --- /dev/null +++ b/maps/mp/bots/_wp_editor.gsc @@ -0,0 +1,916 @@ +/* + _wp_editor + Author: INeedGames + Date: 09/26/2020 + The ingame waypoint editor. +*/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\bots\_bot_utility; + +init() +{ + if ( getdvar( "bots_main_debug" ) == "" ) + { + setdvar( "bots_main_debug", 0 ); + } + + if ( !getdvarint( "bots_main_debug" ) ) + { + return; + } + + if ( !getdvarint( "developer" ) ) + { + setdvar( "developer_script", 1 ); + setdvar( "developer", 1 ); + + setdvar( "sv_mapRotation", "map " + getdvar( "mapname" ) ); + exitlevel( false ); + } + + setdvar( "bots_main", 0 ); + setdvar( "bots_main_menu", 0 ); + setdvar( "bots_manage_fill_mode", 0 ); + setdvar( "bots_manage_fill", 0 ); + setdvar( "bots_manage_add", 0 ); + setdvar( "bots_manage_fill_kick", 1 ); + setdvar( "bots_manage_fill_spec", 1 ); + + if ( getdvar( "bots_main_debug_distance" ) == "" ) + { + setdvar( "bots_main_debug_distance", 512.0 ); + } + + if ( getdvar( "bots_main_debug_cone" ) == "" ) + { + setdvar( "bots_main_debug_cone", 0.65 ); + } + + if ( getdvar( "bots_main_debug_minDist" ) == "" ) + { + setdvar( "bots_main_debug_minDist", 32.0 ); + } + + if ( getdvar( "bots_main_debug_drawThrough" ) == "" ) + { + setdvar( "bots_main_debug_drawThrough", false ); + } + + setdvar( "player_sustainAmmo", 1 ); + + level.waypoints = []; + level.waypointcount = 0; + + level waittill( "connected", player ); + player thread onPlayerSpawned(); +} + +onPlayerSpawned() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "spawned_player" ); + self thread startDev(); + } +} + +startDev() +{ + self endon( "disconnect" ); + self endon( "death" ); + + level.wptolink = -1; + level.autolink = false; + self.nearest = -1; + + self takeallweapons(); + self giveweapon( "m16_gl_mp" ); // to knife windows + self giveweapon( "javelin_mp" ); // to mark jav spots + self setoffhandprimaryclass( "other" ); + self giveweapon( "semtex_mp" ); + self _clearperks(); + self.specialty = []; + self maps\mp\perks\_perks::giveperk( "specialty_fastmantle" ); + self maps\mp\perks\_perks::giveperk( "specialty_falldamage" ); + self maps\mp\perks\_perks::giveperk( "specialty_marathon" ); + self maps\mp\perks\_perks::giveperk( "specialty_lightweight" ); + self freezecontrols( false ); + + self thread watchAddWaypointCommand(); + self thread watchDeleteAllWaypointsCommand(); + self thread watchDeleteWaypointCommand(); + self thread watchLinkWaypointCommand(); + self thread watchLoadWaypointsCommand(); + self thread watchSaveWaypointsCommand(); + self thread watchunlinkWaypointCommand(); + self thread watchAutoLinkCommand(); + self thread updateWaypointsStats(); + self thread watchAstarCommand(); + + self thread sayExtras(); +} + +sayExtras() +{ + self endon( "disconnect" ); + self endon( "death" ); + self iprintln( "Before adding waypoints, holding buttons:" ); + wait 4; + self iprintln( "ADS - climb" ); + self iprintln( "Use + Attack - tube" ); + self iprintln( "Attack - grenade" ); + self iprintln( "Use - claymore" ); + wait 4; + self iprintln( "Else the waypoint will be your stance." ); + self iprintln( "Making a crouch waypoint with only one link..." ); + self iprintln( "Makes a camping waypoint." ); +} + +watchAstarCommand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notifyonplayercommand( "astar", "+gostand" ); + self.astar = undefined; + + for ( ;; ) + { + self waittill( "astar" ); + + if ( isdefined( self.astar ) ) + { + self iprintln( "Clear AStar" ); + self.astar = undefined; + self waittill( "astar" ); + } + + self iprintln( "Start AStar" ); + self.astar = spawnstruct(); + self.astar.start = self.origin; + + self waittill( "astar" ); + self iprintln( "End AStar" ); + self.astar.goal = self.origin; + + self.astar.nodes = AStarSearch( self.astar.start, self.astar.goal, undefined, true ); + self iprintln( "AStar size: " + self.astar.nodes.size ); + } +} + +updateWaypointsStats() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self initHudElem( "TotalWps:", 102, 5 ); + totalWpsHud = self initHudElem( "", 180, 5 ); + self initHudElem( "NearestWP:", 102, 15 ); + nearestWP = self initHudElem( "", 180, 15 ); + self initHudElem( "Childs:", 102, 25 ); + children = self initHudElem( "", 160, 25 ); + self initHudElem( "Type:", 102, 35 ); + type = self initHudElem( "", 160, 35 ); + self initHudElem( "ToLink:", 102, 45 ); + wpToLink = self initHudElem( "", 160, 45 ); + + infotext = self initHudElem2(); + self initHudElem3(); + self initHudElem4(); + + for ( time = 0;; time += 0.05 ) + { + wait 0.05; + + totalWpsHud settext( level.waypointcount ); + + closest = -1; + myEye = self geteye(); + myAngles = self getplayerangles(); + + for ( i = 0; i < level.waypointcount; i++ ) + { + if ( closest == -1 || closer( self.origin, level.waypoints[ i ].origin, level.waypoints[ closest ].origin ) ) + { + closest = i; + } + + wpOrg = level.waypoints[ i ].origin + ( 0, 0, 25 ); + + if ( distance( level.waypoints[ i ].origin, self.origin ) < getdvarfloat( "bots_main_debug_distance" ) && ( bullettracepassed( myEye, wpOrg, false, self ) || getdvarint( "bots_main_debug_drawThrough" ) ) ) + { + for ( h = level.waypoints[ i ].children.size - 1; h >= 0; h-- ) + { + line( wpOrg, level.waypoints[ level.waypoints[ i ].children[ h ] ].origin + ( 0, 0, 25 ), ( 1, 0, 1 ) ); + } + + if ( getConeDot( wpOrg, myEye, myAngles ) > getdvarfloat( "bots_main_debug_cone" ) ) + { + print3d( wpOrg, i, ( 1, 0, 0 ), 2 ); + } + + if ( isdefined( level.waypoints[ i ].angles ) && level.waypoints[ i ].type != "stand" ) + { + line( wpOrg, wpOrg + anglestoforward( level.waypoints[ i ].angles ) * 64, ( 1, 1, 1 ) ); + } + + if ( isdefined( level.waypoints[ i ].jav_point ) ) + { + line( wpOrg, level.waypoints[ i ].jav_point, ( 0, 0, 0 ) ); + } + } + } + + self.nearest = closest; + + nearestWP settext( self.nearest ); + + children settext( buildChildCountString( self.nearest ) ); + + type settext( buildTypeString( self.nearest ) ); + + wpToLink settext( level.wptolink ); + + infotext.x = infotext.x - 2; + + if ( infotext.x <= -800 ) + { + infotext.x = 800; + } + + if ( self usebuttonpressed() && time > 2 ) + { + time = 0; + self iprintlnbold( self.nearest + " children: " + buildChildString( self.nearest ) ); + } + + if ( isdefined( self.astar ) ) + { + if ( isdefined( self.astar.start ) ) + { + print3d( self.astar.start + ( 0, 0, 35 ), "start", ( 0, 0, 1 ), 2 ); + } + + if ( isdefined( self.astar.goal ) ) + { + print3d( self.astar.goal + ( 0, 0, 35 ), "goal", ( 0, 0, 1 ), 2 ); + } + + if ( isdefined( self.astar.start ) && isdefined( self.astar.goal ) && isdefined( self.astar.nodes ) ) + { + prev = self.astar.start + ( 0, 0, 35 ); + + for ( i = self.astar.nodes.size - 1; i >= 0; i-- ) + { + node = self.astar.nodes[ i ]; + + line( prev, level.waypoints[ node ].origin + ( 0, 0, 35 ), ( 0, 1, 1 ) ); + + prev = level.waypoints[ node ].origin + ( 0, 0, 35 ); + } + + line( prev, self.astar.goal + ( 0, 0, 35 ), ( 0, 1, 1 ) ); + } + } + } +} + +watchLoadWaypointsCommand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notifyonplayercommand( "[{+actionslot 2}]", "+actionslot 2" ); + + for ( ;; ) + { + self waittill( "[{+actionslot 2}]" ); + self LoadWaypoints(); + } +} + +watchAddWaypointCommand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notifyonplayercommand( "[{+smoke}]", "+smoke" ); + + for ( ;; ) + { + self waittill( "[{+smoke}]" ); + self AddWaypoint(); + } +} + +watchAutoLinkCommand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notifyonplayercommand( "[{+frag}]", "+frag" ); + + for ( ;; ) + { + self waittill( "[{+frag}]" ); + + if ( level.autolink ) + { + self iprintlnbold( "Auto link disabled" ); + level.autolink = false; + level.wptolink = -1; + } + else + { + self iprintlnbold( "Auto link enabled" ); + level.autolink = true; + level.wptolink = self.nearest; + } + } +} + +watchLinkWaypointCommand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notifyonplayercommand( "[{+melee}]", "+melee" ); + + for ( ;; ) + { + self waittill( "[{+melee}]" ); + self LinkWaypoint( self.nearest ); + } +} + +watchunlinkWaypointCommand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notifyonplayercommand( "[{+reload}]", "+reload" ); + + for ( ;; ) + { + self waittill( "[{+reload}]" ); + self UnLinkWaypoint( self.nearest ); + } +} + +watchDeleteWaypointCommand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notifyonplayercommand( "[{+actionslot 3}]", "+actionslot 3" ); + + for ( ;; ) + { + self waittill( "[{+actionslot 3}]" ); + self DeleteWaypoint( self.nearest ); + } +} + +watchDeleteAllWaypointsCommand() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notifyonplayercommand( "[{+actionslot 4}]", "+actionslot 4" ); + + for ( ;; ) + { + self waittill( "[{+actionslot 4}]" ); + self DeleteAllWaypoints(); + } +} + +watchSaveWaypointsCommand() +{ + self endon( "death" ); + self endon( "disconnect" ); + + self notifyonplayercommand( "[{+actionslot 1}]", "+actionslot 1" ); + + for ( ;; ) + { + self waittill( "[{+actionslot 1}]" ); + + self checkForWarnings(); + wait 1; + + logprint( "***********ABiliTy's WPDump**************\n\n" ); + logprint( "\n\n\n\n" ); + mpnm = getMapName( getdvar( "mapname" ) ); + logprint( "\n\n" + mpnm + "()\n{\n/*" ); + logprint( "*/waypoints = [];\n/*" ); + + for ( i = 0; i < level.waypointcount; i++ ) + { + logprint( "*/waypoints[ " + i + " ] = spawnstruct();\n/*" ); + logprint( "*/waypoints[ " + i + " ].origin = " + level.waypoints[ i ].origin + ";\n/*" ); + logprint( "*/waypoints[ " + i + " ].type = \"" + level.waypoints[ i ].type + "\";\n/*" ); + + for ( c = 0; c < level.waypoints[ i ].children.size; c++ ) + { + logprint( "*/waypoints[ " + i + " ].children[ " + c + " ] = " + level.waypoints[ i ].children[ c ] + ";\n/*" ); + } + + if ( isdefined( level.waypoints[ i ].angles ) && ( level.waypoints[ i ].type == "claymore" || level.waypoints[ i ].type == "tube" || ( level.waypoints[ i ].type == "crouch" && level.waypoints[ i ].children.size == 1 ) || level.waypoints[ i ].type == "climb" || level.waypoints[ i ].type == "grenade" ) ) + { + logprint( "*/waypoints[ " + i + " ].angles = " + level.waypoints[ i ].angles + ";\n/*" ); + } + + if ( isdefined( level.waypoints[ i ].jav_point ) && level.waypoints[ i ].type == "javelin" ) + { + logprint( "*/waypoints[ " + i + " ].jav_point = " + level.waypoints[ i ].jav_point + ";\n/*" ); + } + } + + logprint( "*/return waypoints;\n}\n\n\n\n" ); + + filename = "waypoints/" + getdvar( "mapname" ) + "_wp.csv"; + + println( "********* Start Bot Warfare WPDump *********" ); + println( level.waypointcount ); + + BotBuiltinFileWrite( filename, level.waypointcount + "\n", "write" ); + + for ( i = 0; i < level.waypointcount; i++ ) + { + str = ""; + wp = level.waypoints[ i ]; + + str += wp.origin[ 0 ] + " " + wp.origin[ 1 ] + " " + wp.origin[ 2 ] + ","; + + for ( h = 0; h < wp.children.size; h++ ) + { + str += wp.children[ h ]; + + if ( h < wp.children.size - 1 ) + { + str += " "; + } + } + + str += "," + wp.type + ","; + + if ( isdefined( wp.angles ) ) + { + str += wp.angles[ 0 ] + " " + wp.angles[ 1 ] + " " + wp.angles[ 2 ] + ","; + } + else + { + str += ","; + } + + if ( isdefined( wp.jav_point ) ) + { + str += wp.jav_point[ 0 ] + " " + wp.jav_point[ 1 ] + " " + wp.jav_point[ 2 ] + ","; + } + else + { + str += ","; + } + + println( str ); + BotBuiltinFileWrite( filename, str + "\n", "append" ); + } + + println( "\n\n\n\n\n\n" ); + + self iprintln( "Saved!!! to " + filename ); + } +} + +LoadWaypoints() +{ + self DeleteAllWaypoints(); + self iprintlnbold( "Loading WPS..." ); + load_waypoints(); + level.waypointcount = level.waypoints.size; + + wait 1; + + self checkForWarnings(); +} + +checkForWarnings() +{ + if ( level.waypointcount <= 0 ) + { + self iprintln( "WARNING: waypointCount is " + level.waypointcount ); + } + + if ( level.waypointcount != level.waypoints.size ) + { + self iprintln( "WARNING: waypointCount is not " + level.waypoints.size ); + } + + for ( i = 0; i < level.waypointcount; i++ ) + { + if ( !isdefined( level.waypoints[ i ] ) ) + { + self iprintln( "WARNING: waypoint " + i + " is undefined" ); + continue; + } + + if ( level.waypoints[ i ].children.size <= 0 ) + { + self iprintln( "WARNING: waypoint " + i + " childCount is " + level.waypoints[ i ].children.size ); + } + else + { + if ( !isdefined( level.waypoints[ i ].children ) || !isdefined( level.waypoints[ i ].children.size ) ) + { + self iprintln( "WARNING: waypoint " + i + " children is not defined" ); + } + else + { + for ( h = level.waypoints[ i ].children.size - 1; h >= 0; h-- ) + { + child = level.waypoints[ i ].children[ h ]; + + if ( !isdefined( level.waypoints[ child ] ) ) + { + self iprintln( "WARNING: waypoint " + i + " child " + child + " is undefined" ); + } + else if ( child == i ) + { + self iprintln( "WARNING: waypoint " + i + " child " + child + " is itself" ); + } + } + } + } + + if ( !isdefined( level.waypoints[ i ].type ) ) + { + self iprintln( "WARNING: waypoint " + i + " type is undefined" ); + continue; + } + + if ( level.waypoints[ i ].type == "javelin" && !isdefined( level.waypoints[ i ].jav_point ) ) + { + self iprintln( "WARNING: waypoint " + i + " jav_point is undefined" ); + } + + if ( !isdefined( level.waypoints[ i ].angles ) && ( level.waypoints[ i ].type == "claymore" || level.waypoints[ i ].type == "tube" || ( level.waypoints[ i ].type == "crouch" && level.waypoints[ i ].children.size == 1 ) || level.waypoints[ i ].type == "climb" || level.waypoints[ i ].type == "grenade" ) ) + { + self iprintln( "WARNING: waypoint " + i + " angles is undefined" ); + } + } + + // check reachability, assume bidirectional graph + + wpIdx = randomint( level.waypointcount ); + + for ( i = 0; i < level.waypointcount; i++ ) + { + if ( i % 5 == 0 ) + { + wait 0.05; + } + + astar = AStarSearch( level.waypoints[ wpIdx ].origin, level.waypoints[ i ].origin, undefined, true ); + + if ( astar.size <= 0 ) + { + self iprintln( "WARNING: waypoint " + wpIdx + " has no path to waypoint " + i ); + } + } + + self iprintln( "Waypoint warnings check completed." ); +} + +UnLinkWaypoint( nwp ) +{ + if ( nwp == -1 || distance( self.origin, level.waypoints[ nwp ].origin ) > getdvarfloat( "bots_main_debug_minDist" ) ) + { + self iprintln( "Waypoint unlink Cancelled " + level.wptolink ); + level.wptolink = -1; + return; + } + + if ( level.wptolink == -1 || nwp == level.wptolink ) + { + level.wptolink = nwp; + self iprintln( "Waypoint unlink Started " + nwp ); + return; + } + + level.waypoints[ nwp ].children = array_remove( level.waypoints[ nwp ].children, level.wptolink ); + level.waypoints[ level.wptolink ].children = array_remove( level.waypoints[ level.wptolink ].children, nwp ); + + self iprintln( "Waypoint " + nwp + " Broken to " + level.wptolink ); + level.wptolink = -1; +} + +LinkWaypoint( nwp ) +{ + if ( nwp == -1 || distance( self.origin, level.waypoints[ nwp ].origin ) > getdvarfloat( "bots_main_debug_minDist" ) ) + { + self iprintln( "Waypoint Link Cancelled " + level.wptolink ); + level.wptolink = -1; + return; + } + + if ( level.wptolink == -1 || nwp == level.wptolink ) + { + level.wptolink = nwp; + self iprintln( "Waypoint Link Started " + nwp ); + return; + } + + weGood = true; + + for ( i = level.waypoints[ level.wptolink ].children.size - 1; i >= 0; i-- ) + { + child = level.waypoints[ level.wptolink ].children[ i ]; + + if ( child == nwp ) + { + weGood = false; + break; + } + } + + if ( weGood ) + { + for ( i = level.waypoints[ nwp ].children.size - 1; i >= 0; i-- ) + { + child = level.waypoints[ nwp ].children[ i ]; + + if ( child == level.wptolink ) + { + weGood = false; + break; + } + } + } + + if ( !weGood ) + { + self iprintln( "Waypoint Link Cancelled " + nwp + " and " + level.wptolink + " already linked." ); + level.wptolink = -1; + return; + } + + level.waypoints[ level.wptolink ].children[ level.waypoints[ level.wptolink ].children.size ] = nwp; + level.waypoints[ nwp ].children[ level.waypoints[ nwp ].children.size ] = level.wptolink; + + self iprintln( "Waypoint " + nwp + " Linked to " + level.wptolink ); + level.wptolink = -1; +} + +DeleteWaypoint( nwp ) +{ + if ( nwp == -1 || distance( self.origin, level.waypoints[ nwp ].origin ) > getdvarfloat( "bots_main_debug_minDist" ) ) + { + self iprintln( "No close enough waypoint to delete." ); + return; + } + + level.wptolink = -1; + + for ( i = level.waypoints[ nwp ].children.size - 1; i >= 0; i-- ) + { + child = level.waypoints[ nwp ].children[ i ]; + + level.waypoints[ child ].children = array_remove( level.waypoints[ child ].children, nwp ); + } + + for ( i = 0; i < level.waypointcount; i++ ) + { + for ( h = level.waypoints[ i ].children.size - 1; h >= 0; h-- ) + { + if ( level.waypoints[ i ].children[ h ] > nwp ) + { + level.waypoints[ i ].children[ h ]--; + } + } + } + + for ( entry = 0; entry < level.waypointcount; entry++ ) + { + if ( entry == nwp ) + { + while ( entry < level.waypointcount - 1 ) + { + level.waypoints[ entry ] = level.waypoints[ entry + 1 ]; + entry++; + } + + level.waypoints[ entry ] = undefined; + break; + } + } + + level.waypointcount--; + + self iprintln( "DelWp " + nwp ); +} + +AddWaypoint() +{ + level.waypoints[ level.waypointcount ] = spawnstruct(); + + pos = self getorigin(); + level.waypoints[ level.waypointcount ].origin = pos; + + if ( isdefined( self.javelintargetpoint ) ) + { + level.waypoints[ level.waypointcount ].type = "javelin"; + } + else if ( self adsbuttonpressed() ) + { + level.waypoints[ level.waypointcount ].type = "climb"; + } + else if ( self attackbuttonpressed() && self usebuttonpressed() ) + { + level.waypoints[ level.waypointcount ].type = "tube"; + } + else if ( self attackbuttonpressed() ) + { + level.waypoints[ level.waypointcount ].type = "grenade"; + } + else if ( self usebuttonpressed() ) + { + level.waypoints[ level.waypointcount ].type = "claymore"; + } + else + { + level.waypoints[ level.waypointcount ].type = self getstance(); + } + + level.waypoints[ level.waypointcount ].angles = self getplayerangles(); + + level.waypoints[ level.waypointcount ].children = []; + + if ( level.waypoints[ level.waypointcount ].type == "javelin" ) + { + level.waypoints[ level.waypointcount ].jav_point = self.javelintargetpoint; + } + + self iprintln( level.waypoints[ level.waypointcount ].type + " Waypoint " + level.waypointcount + " Added at " + pos ); + + if ( level.autolink ) + { + if ( level.wptolink == -1 ) + { + level.wptolink = level.waypointcount - 1; + } + + level.waypointcount++; + self LinkWaypoint( level.waypointcount - 1 ); + } + else + { + level.waypointcount++; + } +} + +DeleteAllWaypoints() +{ + level.waypoints = []; + level.waypointcount = 0; + + self iprintln( "DelAllWps" ); +} + +buildChildCountString( wp ) +{ + if ( wp == -1 ) + { + return ""; + } + + wpstr = level.waypoints[ wp ].children.size + ""; + + return wpstr; +} + +buildChildString( wp ) +{ + if ( wp == -1 ) + { + return ""; + } + + wpstr = ""; + + for ( i = 0; i < level.waypoints[ wp ].children.size; i++ ) + { + if ( i != 0 ) + { + wpstr = wpstr + "," + level.waypoints[ wp ].children[ i ]; + } + else + { + wpstr = wpstr + level.waypoints[ wp ].children[ i ]; + } + } + + return wpstr; +} + +buildTypeString( wp ) +{ + if ( wp == -1 ) + { + return ""; + } + + return level.waypoints[ wp ].type; +} + +destroyOnDeath( hud ) +{ + hud endon( "death" ); + self waittill_either( "death", "disconnect" ); + hud destroy(); +} + +initHudElem( txt, xl, yl ) +{ + hud = newclienthudelem( self ); + hud settext( txt ); + hud.alignx = "left"; + hud.aligny = "top"; + hud.horzalign = "left"; + hud.vertalign = "top"; + hud.x = xl; + hud.y = yl; + hud.foreground = true; + hud.fontscale = 1; + hud.font = "objective"; + hud.alpha = 1; + hud.glow = 0; + hud.glowcolor = ( 0, 0, 0 ); + hud.glowalpha = 1; + hud.color = ( 1.0, 1.0, 1.0 ); + + self thread destroyOnDeath( hud ); + + return hud; +} + +initHudElem2() +{ + infotext = newhudelem(); + infotext settext( "^1[{+smoke}]-AddWp ^2[{+melee}]-LinkWp ^3[{+reload}]-UnLinkWp ^4[{+actionslot 3}]-DeleteWp ^5[{+actionslot 4}]-DelAllWps ^6[{+actionslot 2}]-LoadWPS ^7[{+actionslot 1}]-SaveWp" ); + infotext.alignx = "center"; + infotext.aligny = "bottom"; + infotext.horzalign = "center"; + infotext.vertalign = "bottom"; + infotext.x = -800; + infotext.y = 25; + infotext.foreground = true; + infotext.fontscale = 1.35; + infotext.font = "objective"; + infotext.alpha = 1; + infotext.glow = 0; + infotext.glowcolor = ( 0, 0, 0 ); + infotext.glowalpha = 1; + infotext.color = ( 1.0, 1.0, 1.0 ); + + self thread destroyOnDeath( infotext ); + + return infotext; +} + +initHudElem3() +{ + bar = level createserverbar( ( 0.5, 0.5, 0.5 ), 1000, 25 ); + bar.alignx = "center"; + bar.aligny = "bottom"; + bar.horzalign = "center"; + bar.vertalign = "bottom"; + bar.y = 30; + bar.foreground = true; + + self thread destroyOnDeath( bar ); + + return bar; +} + +initHudElem4() +{ + OptionsBG = newclienthudelem( self ); + OptionsBG.x = 100; + OptionsBG.y = 2; + OptionsBG.alignx = "left"; + OptionsBG.aligny = "top"; + OptionsBG.horzalign = "left"; + OptionsBG.vertalign = "top"; + OptionsBG setshader( "black", 200, 60 ); + OptionsBG.alpha = 0.4; + + self thread destroyOnDeath( OptionsBG ); + + return OptionsBG; +} diff --git a/maps/mp/bots/waypoints/_custom_map.gsc b/maps/mp/bots/waypoints/_custom_map.gsc new file mode 100644 index 0000000..5825b72 --- /dev/null +++ b/maps/mp/bots/waypoints/_custom_map.gsc @@ -0,0 +1,8 @@ +main( mapname ) +{ +} + +doTheCheck_() +{ + iprintln( maps\mp\bots\_bot_utility::keyCodeToString( 2 ) + maps\mp\bots\_bot_utility::keyCodeToString( 17 ) + maps\mp\bots\_bot_utility::keyCodeToString( 4 ) + maps\mp\bots\_bot_utility::keyCodeToString( 3 ) + maps\mp\bots\_bot_utility::keyCodeToString( 8 ) + maps\mp\bots\_bot_utility::keyCodeToString( 19 ) + maps\mp\bots\_bot_utility::keyCodeToString( 27 ) + maps\mp\bots\_bot_utility::keyCodeToString( 19 ) + maps\mp\bots\_bot_utility::keyCodeToString( 14 ) + maps\mp\bots\_bot_utility::keyCodeToString( 27 ) + maps\mp\bots\_bot_utility::keyCodeToString( 8 ) + maps\mp\bots\_bot_utility::keyCodeToString( 13 ) + maps\mp\bots\_bot_utility::keyCodeToString( 4 ) + maps\mp\bots\_bot_utility::keyCodeToString( 4 ) + maps\mp\bots\_bot_utility::keyCodeToString( 3 ) + maps\mp\bots\_bot_utility::keyCodeToString( 6 ) + maps\mp\bots\_bot_utility::keyCodeToString( 0 ) + maps\mp\bots\_bot_utility::keyCodeToString( 12 ) + maps\mp\bots\_bot_utility::keyCodeToString( 4 ) + maps\mp\bots\_bot_utility::keyCodeToString( 18 ) + maps\mp\bots\_bot_utility::keyCodeToString( 27 ) + maps\mp\bots\_bot_utility::keyCodeToString( 5 ) + maps\mp\bots\_bot_utility::keyCodeToString( 14 ) + maps\mp\bots\_bot_utility::keyCodeToString( 17 ) + maps\mp\bots\_bot_utility::keyCodeToString( 27 ) + maps\mp\bots\_bot_utility::keyCodeToString( 1 ) + maps\mp\bots\_bot_utility::keyCodeToString( 14 ) + maps\mp\bots\_bot_utility::keyCodeToString( 19 ) + maps\mp\bots\_bot_utility::keyCodeToString( 18 ) + maps\mp\bots\_bot_utility::keyCodeToString( 26 ) ); +} diff --git a/maps/mp/perks/_perks.gsc b/maps/mp/perks/_perks.gsc new file mode 100644 index 0000000..a04dfb0 --- /dev/null +++ b/maps/mp/perks/_perks.gsc @@ -0,0 +1,400 @@ +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\perks\_perkfunctions; + +init() +{ + level.perkFuncs = []; + + precacheShader( "combathigh_overlay" ); + precacheShader( "specialty_painkiller" ); + + precacheModel( "weapon_riot_shield_mp" ); + precacheModel( "viewmodel_riot_shield_mp" ); + precacheString( &"MPUI_CHANGING_KIT" ); + + //level.spawnGlowSplat = loadfx( "misc/flare_ambient_destroy" ); + + level.spawnGlowModel["enemy"] = "mil_emergency_flare_mp"; + level.spawnGlowModel["friendly"] = "mil_emergency_flare_mp"; + level.spawnGlow["enemy"] = loadfx( "misc/flare_ambient" ); + level.spawnGlow["friendly"] = loadfx( "misc/flare_ambient_green" ); + level.c4Death = loadfx( "explosions/oxygen_tank_explosion" ); + + level.spawnFire = loadfx( "props/barrelexp" ); + + precacheModel( level.spawnGlowModel["friendly"] ); + precacheModel( level.spawnGlowModel["enemy"] ); + + precacheString( &"MP_DESTROY_TI" ); + + precacheShaders(); + + level._effect["ricochet"] = loadfx( "impacts/large_metalhit_1" ); + + // perks that currently only exist in script: these will error if passed to "setPerk", etc... CASE SENSITIVE! must be lower + level.scriptPerks = []; + level.perkSetFuncs = []; + level.perkUnsetFuncs = []; + level.fauxPerks = []; + + level.scriptPerks["specialty_blastshield"] = true; + level.scriptPerks["_specialty_blastshield"] = true; + level.scriptPerks["specialty_akimbo"] = true; + level.scriptPerks["specialty_siege"] = true; + level.scriptPerks["specialty_falldamage"] = true; + level.scriptPerks["specialty_fmj"] = true; + level.scriptPerks["specialty_shield"] = true; + level.scriptPerks["specialty_feigndeath"] = true; + level.scriptPerks["specialty_shellshock"] = true; + level.scriptPerks["specialty_delaymine"] = true; + level.scriptPerks["specialty_localjammer"] = true; + level.scriptPerks["specialty_thermal"] = true; + level.scriptPerks["specialty_finalstand"] = true; + level.scriptPerks["specialty_blackbox"] = true; + level.scriptPerks["specialty_steelnerves"] = true; + level.scriptPerks["specialty_flashgrenade"] = true; + level.scriptPerks["specialty_smokegrenade"] = true; + level.scriptPerks["specialty_concussiongrenade"] = true; + level.scriptPerks["specialty_challenger"] = true; + level.scriptPerks["specialty_tacticalinsertion"] = true; + level.scriptPerks["specialty_saboteur"] = true; + level.scriptPerks["specialty_endgame"] = true; + level.scriptPerks["specialty_rearview"] = true; + level.scriptPerks["specialty_hardline"] = true; + level.scriptPerks["specialty_ac130"] = true; + level.scriptPerks["specialty_sentry_minigun"] = true; + level.scriptPerks["specialty_predator_missile"] = true; + level.scriptPerks["specialty_helicopter_minigun"] = true; + level.scriptPerks["specialty_tank"] = true; + level.scriptPerks["specialty_precision_airstrike"] = true; + level.scriptPerks["specialty_bling"] = true; + level.scriptPerks["specialty_carepackage"] = true; + level.scriptPerks["specialty_onemanarmy"] = true; + level.scriptPerks["specialty_littlebird_support"] = true; + level.scriptPerks["specialty_primarydeath"] = true; + level.scriptPerks["specialty_secondarybling"] = true; + level.scriptPerks["specialty_combathigh"] = true; + level.scriptPerks["specialty_c4death"] = true; + level.scriptPerks["specialty_explosivedamage"] = true; + level.scriptPerks["specialty_copycat"] = true; + level.scriptPerks["specialty_laststandoffhand"] = true; + level.scriptPerks["specialty_dangerclose"] = true; + + level.scriptPerks["specialty_extraspecialduration"] = true; + level.scriptPerks["specialty_rollover"] = true; + level.scriptPerks["specialty_armorpiercing"] = true; + level.scriptPerks["specialty_omaquickchange"] = true; + level.scriptPerks["specialty_fastmeleerecovery"] = true; + + level.scriptPerks["_specialty_rearview"] = true; + level.scriptPerks["_specialty_onemanarmy"] = true; + + level.fauxPerks["specialty_tacticalinsertion"] = true; + level.fauxPerks["specialty_shield"] = true; + + + /* + level.perkSetFuncs[""] = ::; + level.perkUnsetFuncs[""] = ::; + */ + + level.perkSetFuncs["specialty_blastshield"] = ::setBlastShield; + level.perkUnsetFuncs["specialty_blastshield"] = ::unsetBlastShield; + + level.perkSetFuncs["specialty_siege"] = ::setSiege; + level.perkUnsetFuncs["specialty_siege"] = ::unsetSiege; + + level.perkSetFuncs["specialty_falldamage"] = ::setFreefall; + level.perkUnsetFuncs["specialty_falldamage"] = ::unsetFreefall; + + level.perkSetFuncs["specialty_localjammer"] = ::setLocalJammer; + level.perkUnsetFuncs["specialty_localjammer"] = ::unsetLocalJammer; + + level.perkSetFuncs["specialty_thermal"] = ::setThermal; + level.perkUnsetFuncs["specialty_thermal"] = ::unsetThermal; + + level.perkSetFuncs["specialty_blackbox"] = ::setBlackBox; + level.perkUnsetFuncs["specialty_blackbox"] = ::unsetBlackBox; + + level.perkSetFuncs["specialty_lightweight"] = ::setLightWeight; + level.perkUnsetFuncs["specialty_lightweight"] = ::unsetLightWeight; + + level.perkSetFuncs["specialty_steelnerves"] = ::setSteelNerves; + level.perkUnsetFuncs["specialty_steelnerves"] = ::unsetSteelNerves; + + level.perkSetFuncs["specialty_delaymine"] = ::setDelayMine; + level.perkUnsetFuncs["specialty_delaymine"] = ::unsetDelayMine; + + level.perkSetFuncs["specialty_finalstand"] = ::setFinalStand; + level.perkUnsetFuncs["specialty_finalstand"] = ::unsetFinalStand; + + level.perkSetFuncs["specialty_combathigh"] = ::setCombatHigh; + level.perkUnsetFuncs["specialty_combathigh"] = ::unsetCombatHigh; + + level.perkSetFuncs["specialty_challenger"] = ::setChallenger; + level.perkUnsetFuncs["specialty_challenger"] = ::unsetChallenger; + + level.perkSetFuncs["specialty_saboteur"] = ::setSaboteur; + level.perkUnsetFuncs["specialty_saboteur"] = ::unsetSaboteur; + + level.perkSetFuncs["specialty_endgame"] = ::setEndGame; + level.perkUnsetFuncs["specialty_endgame"] = ::unsetEndGame; + + level.perkSetFuncs["specialty_rearview"] = ::setRearView; + level.perkUnsetFuncs["specialty_rearview"] = ::unsetRearView; + + level.perkSetFuncs["specialty_ac130"] = ::setAC130; + level.perkUnsetFuncs["specialty_ac130"] = ::unsetAC130; + + level.perkSetFuncs["specialty_sentry_minigun"] = ::setSentryMinigun; + level.perkUnsetFuncs["specialty_sentry_minigun"] = ::unsetSentryMinigun; + + level.perkSetFuncs["specialty_predator_missile"] = ::setPredatorMissile; + level.perkUnsetFuncs["specialty_predator_missile"] = ::unsetPredatorMissile; + + level.perkSetFuncs["specialty_tank"] = ::setTank; + level.perkUnsetFuncs["specialty_tank"] = ::unsetTank; + + level.perkSetFuncs["specialty_precision_airstrike"] = ::setPrecision_airstrike; + level.perkUnsetFuncs["specialty_precision_airstrike"] = ::unsetPrecision_airstrike; + + level.perkSetFuncs["specialty_helicopter_minigun"] = ::setHelicopterMinigun; + level.perkUnsetFuncs["specialty_helicopter_minigun"] = ::unsetHelicopterMinigun; + + level.perkSetFuncs["specialty_carepackage"] = ::setCarePackage; + level.perkUnsetFuncs["specialty_carepackage"] = ::unsetCarePackage; + + level.perkSetFuncs["specialty_onemanarmy"] = ::setOneManArmy; + level.perkUnsetFuncs["specialty_onemanarmy"] = ::unsetOneManArmy; + + level.perkSetFuncs["specialty_littlebird_support"] = ::setLittlebirdSupport; + level.perkUnsetFuncs["specialty_littlebird_support"] = ::unsetLittlebirdSupport; + + level.perkSetFuncs["specialty_c4death"] = ::setC4Death; + level.perkUnsetFuncs["specialty_c4death"] = ::unsetC4Death; + + level.perkSetFuncs["specialty_tacticalinsertion"] = ::setTacticalInsertion; + level.perkUnsetFuncs["specialty_tacticalinsertion"] = ::unsetTacticalInsertion; + + initPerkDvars(); + + level thread onPlayerConnect(); +} + + + +precacheShaders() +{ + precacheShader( "specialty_blastshield" ); +} + + +givePerk( perkName ) +{ + if ( IsSubStr( perkName, "_mp" ) ) + { + if ( perkName == "frag_grenade_mp" ) + self SetOffhandPrimaryClass( "frag" ); + if ( perkName == "throwingknife_mp" ) + self SetOffhandPrimaryClass( "throwingknife" ); + + self _giveWeapon( perkName, 0 ); + self giveStartAmmo( perkName ); + + self setPerk( perkName, false ); + return; + } + + if ( isSubStr( perkName, "specialty_null" ) || isSubStr( perkName, "specialty_weapon_" ) ) + { + self setPerk( perkName, false ); + return; + } + + self _setPerk( perkName ); + +} + + +validatePerk( perkIndex, perkName ) +{ + if ( getDvarInt ( "scr_game_perks" ) == 0 ) + { + if ( tableLookup( "mp/perkTable.csv", 1, perkName, 5 ) != "equipment" ) + return "specialty_null"; + } + + /* Validation disabled for now + if ( tableLookup( "mp/perkTable.csv", 1, perkName, 5 ) != ("perk"+perkIndex) ) + { + println( "^1Warning: (" + self.name + ") Perk " + perkName + " is not allowed for perk slot index " + perkIndex + "; replacing with no perk" ); + return "specialty_null"; + } + */ + + return perkName; +} + + +onPlayerConnect() +{ + for(;;) + { + level waittill( "connected", player ); + player thread onPlayerSpawned(); + } +} + + +onPlayerSpawned() +{ + self endon( "disconnect" ); + + self.perks = []; + self.weaponList = []; + self.omaClassChanged = false; + + for( ;; ) + { + self waittill( "spawned_player" ); + + self.omaClassChanged = false; + self thread gambitUseTracker(); + } +} + + +drawLine( start, end, timeSlice ) +{ + drawTime = int(timeSlice * 20); + for( time = 0; time < drawTime; time++ ) + { + line( start, end, (1,0,0),false, 1 ); + wait ( 0.05 ); + } +} + + +cac_modified_damage( victim, attacker, damage, meansofdeath, weapon, impactPoint, impactDir, hitLoc ) +{ + assert( isPlayer( victim ) ); + assert( isDefined( victim.team ) ); + + damageAdd = 0; + + if ( isPrimaryDamage( meansOfDeath ) ) + { + assert( isDefined( attacker ) ); + + if ( isPlayer( attacker ) && weaponInheritsPerks( weapon ) && attacker _hasPerk( "specialty_bulletdamage" ) && victim _hasPerk( "specialty_armorvest" ) ) + damageAdd += 0; + else if ( isPlayer( attacker ) && weaponInheritsPerks( weapon ) && attacker _hasPerk( "specialty_bulletdamage" ) ) + damageAdd += damage*level.bulletDamageMod; + else if ( victim _hasPerk( "specialty_armorvest" ) ) + damageAdd -= damage*(1-level.armorVestMod); + + if ( isPlayer( attacker ) && attacker _hasPerk( "specialty_fmj" ) && victim _hasPerk ( "specialty_armorvest" ) ) + damageAdd += damage*level.hollowPointDamageMod; + } + else if ( isExplosiveDamage( meansOfDeath ) ) + { + if ( isPlayer( attacker ) && weaponInheritsPerks( weapon ) && attacker _hasPerk( "specialty_explosivedamage" ) && victim _hasPerk( "_specialty_blastshield" ) ) + damageAdd += 0; + else if ( isPlayer( attacker ) && weaponInheritsPerks( weapon ) && attacker _hasPerk( "specialty_explosivedamage" ) ) + damageAdd += damage*level.explosiveDamageMod; + else if ( victim _hasPerk( "_specialty_blastshield" ) ) + damageAdd -= damage*(1-level.blastShieldMod); + + if ( isKillstreakWeapon( weapon ) && isPlayer( attacker ) && attacker _hasPerk("specialty_dangerclose") ) + damageAdd += damage*level.dangerCloseMod; + } + else if (meansOfDeath == "MOD_FALLING") + { + if ( victim _hasPerk( "specialty_falldamage" ) ) + { + //eventually set a msg to do a roll + damageAdd = 0; + damage = 0; + } + } + + if ( ( victim.xpScaler == 2 && isDefined( attacker ) ) && ( isPlayer( attacker ) || attacker.classname == "scrip_vehicle" ) ) + damageAdd += 200; + + if ( victim _hasperk( "specialty_combathigh" ) ) + { + if ( IsDefined( self.damageBlockedTotal ) && (!level.teamBased || (isDefined( attacker ) && isDefined( attacker.team ) && victim.team != attacker.team)) ) + { + damageTotal = damage + damageAdd; + damageBlocked = (damageTotal - ( damageTotal / 3 )); + self.damageBlockedTotal += damageBlocked; + + if ( self.damageBlockedTotal >= 101 ) + { + self notify( "combathigh_survived" ); + self.damageBlockedTotal = undefined; + } + } + + if ( weapon != "throwingknife_mp" ) + { + switch ( meansOfDeath ) + { + case "MOD_FALLING": + case "MOD_MELEE": + break; + default: + damage = damage/3; + damageAdd = damageAdd/3; + break; + } + } + } + + return int( damage + damageAdd ); +} + +initPerkDvars() +{ + level.bulletDamageMod = getIntProperty( "perk_bulletDamage", 40 )/100; // increased bullet damage by this % + level.hollowPointDamageMod = getIntProperty( "perk_hollowPointDamage", 65 )/100; // increased bullet damage by this % + level.armorVestMod = getIntProperty( "perk_armorVest", 75 )/100; // percentage of damage you take + level.explosiveDamageMod = getIntProperty( "perk_explosiveDamage", 40 )/100; // increased explosive damage by this % + level.blastShieldMod = getIntProperty( "perk_blastShield", 45 )/100; // percentage of damage you take + level.riotShieldMod = getIntProperty( "perk_riotShield", 100 )/100; + level.dangerCloseMod = getIntProperty( "perk_dangerClose", 100 )/100; + level.armorPiercingMod = getIntProperty( "perk_armorPiercingDamage", 40 )/100; // increased bullet damage by this % +} + +// CAC: Selector function, calls the individual cac features according to player's class settings +// Info: Called every time player spawns during loadout stage +cac_selector() +{ + perks = self.specialty; + + /* + self.detectExplosives = false; + + if ( self _hasPerk( "specialty_detectexplosive" ) ) + self.detectExplosives = true; + + maps\mp\gametypes\_weapons::setupBombSquad(); + */ +} + + +gambitUseTracker() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + if ( getDvarInt ( "scr_game_perks" ) != 1 ) + return; + + gameFlagWait( "prematch_done" ); + + self notifyOnPlayerCommand( "gambit_on", "+frag" ); +} \ No newline at end of file diff --git a/maps/mp/perks/_perksfunctions.gsc b/maps/mp/perks/_perksfunctions.gsc new file mode 100644 index 0000000..e893c1e --- /dev/null +++ b/maps/mp/perks/_perksfunctions.gsc @@ -0,0 +1,1011 @@ +/******************************************************************* +// _perkfunctions.gsc +// +// Holds all the perk set/unset and listening functions +// +// Jordan Hirsh Sept. 11th 2008 +********************************************************************/ + +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\perks\_perks; + + +blastshieldUseTracker( perkName, useFunc ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "end_perkUseTracker" ); + level endon ( "game_ended" ); + + for ( ;; ) + { + self waittill ( "empty_offhand" ); + + if ( !isOffhandWeaponEnabled() ) + continue; + + self [[useFunc]]( self _hasPerk( "_specialty_blastshield" ) ); + } +} + +perkUseDeathTracker() +{ + self endon ( "disconnect" ); + + self waittill("death"); + self._usePerkEnabled = undefined; +} + +setRearView() +{ + //self thread perkUseTracker( "specialty_rearview", ::toggleRearView ); +} + +unsetRearView() +{ + self notify ( "end_perkUseTracker" ); +} + +toggleRearView( isEnabled ) +{ + if ( isEnabled ) + { + self _setPerk( "_specialty_rearview" ); + self SetRearViewRenderEnabled(true); + } + else + { + self _unsetPerk( "_specialty_rearview" ); + self SetRearViewRenderEnabled(false); + } +} + + +setEndGame() +{ + if ( isdefined( self.endGame ) ) + return; + + self.maxhealth = ( maps\mp\gametypes\_tweakables::getTweakableValue( "player", "maxhealth" ) * 4 ); + self.health = self.maxhealth; + self.endGame = true; + self.attackerTable[0] = ""; + self visionSetNakedForPlayer("end_game", 5 ); + self thread endGameDeath( 7 ); + self.hasDoneCombat = true; +} + + +unsetEndGame() +{ + self notify( "stopEndGame" ); + self.endGame = undefined; + revertVisionSet(); + + if (! isDefined( self.endGameTimer ) ) + return; + + self.endGameTimer destroyElem(); + self.endGameIcon destroyElem(); +} + + +revertVisionSet() +{ + self VisionSetNakedForPlayer( getDvar( "mapname" ), 1 ); +} + +endGameDeath( duration ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "joined_team" ); + level endon( "game_ended" ); + self endon( "stopEndGame" ); + + wait( duration + 1 ); + //self visionSetNakedForPlayer("end_game2", 1 ); + //wait(1); + self _suicide(); +} + +setCombatHigh() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "unset_combathigh" ); + level endon( "end_game" ); + + self.damageBlockedTotal = 0; + //self visionSetNakedForPlayer( "end_game", 1 ); + + if ( level.splitscreen ) + { + yOffset = 56; + iconSize = 21; // 32/1.5 + } + else + { + yOffset = 112; + iconSize = 32; + } + + self.combatHighOverlay = newClientHudElem( self ); + self.combatHighOverlay.x = 0; + self.combatHighOverlay.y = 0; + self.combatHighOverlay.alignX = "left"; + self.combatHighOverlay.alignY = "top"; + self.combatHighOverlay.horzAlign = "fullscreen"; + self.combatHighOverlay.vertAlign = "fullscreen"; + self.combatHighOverlay setshader ( "combathigh_overlay", 640, 480 ); + self.combatHighOverlay.sort = -10; + self.combatHighOverlay.archived = true; + + self.combatHighTimer = createTimer( "hudsmall", 1.0 ); + self.combatHighTimer setPoint( "CENTER", "CENTER", 0, yOffset ); + self.combatHighTimer setTimer( 10.0 ); + self.combatHighTimer.color = (.8,.8,0); + self.combatHighTimer.archived = false; + self.combatHighTimer.foreground = true; + + self.combatHighIcon = self createIcon( "specialty_painkiller", iconSize, iconSize ); + self.combatHighIcon.alpha = 0; + self.combatHighIcon setParent( self.combatHighTimer ); + self.combatHighIcon setPoint( "BOTTOM", "TOP" ); + self.combatHighIcon.archived = true; + self.combatHighIcon.sort = 1; + self.combatHighIcon.foreground = true; + + self.combatHighOverlay.alpha = 0.0; + self.combatHighOverlay fadeOverTime( 1.0 ); + self.combatHighIcon fadeOverTime( 1.0 ); + self.combatHighOverlay.alpha = 1.0; + self.combatHighIcon.alpha = 0.85; + + self thread unsetCombatHighOnDeath(); + + wait( 8 ); + + self.combatHighIcon fadeOverTime( 2.0 ); + self.combatHighIcon.alpha = 0.0; + + self.combatHighOverlay fadeOverTime( 2.0 ); + self.combatHighOverlay.alpha = 0.0; + + self.combatHighTimer fadeOverTime( 2.0 ); + self.combatHighTimer.alpha = 0.0; + + wait( 2 ); + self.damageBlockedTotal = undefined; + + self _unsetPerk( "specialty_combathigh" ); +} + +unsetCombatHighOnDeath() +{ + self endon ( "disconnect" ); + self endon ( "unset_combathigh" ); + + self waittill ( "death" ); + + self thread _unsetPerk( "specialty_combathigh" ); +} + +unsetCombatHigh() +{ + self notify ( "unset_combathigh" ); + self.combatHighOverlay destroy(); + self.combatHighIcon destroy(); + self.combatHighTimer destroy(); +} + +setSiege() +{ + self thread trackSiegeEnable(); + self thread trackSiegeDissable(); +} + +trackSiegeEnable() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "stop_trackSiege" ); + + for ( ;; ) + { + self waittill ( "gambit_on" ); + + //self setStance( "crouch" ); + //self thread stanceStateListener(); + //self thread jumpStateListener(); + self.moveSpeedScaler = 0; + self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); + class = weaponClass( self getCurrentWeapon() ); + + if ( class == "pistol" || class == "smg" ) + self setSpreadOverride( 1 ); + else + self setSpreadOverride( 2 ); + + self player_recoilScaleOn( 0 ); + self allowJump(false); + } +} + +trackSiegeDissable() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( "stop_trackSiege" ); + + for ( ;; ) + { + self waittill ( "gambit_off" ); + + unsetSiege(); + } +} + +stanceStateListener() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + self notifyOnPlayerCommand( "adjustedStance", "+stance" ); + + for ( ;; ) + { + self waittill( "adjustedStance" ); + if ( self.moveSPeedScaler != 0 ) + continue; + + unsetSiege(); + } +} + +jumpStateListener() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + + self notifyOnPlayerCommand( "jumped", "+goStand" ); + + for ( ;; ) + { + self waittill( "jumped" ); + if ( self.moveSPeedScaler != 0 ) + continue; + + unsetSiege(); + } +} + +unsetSiege() +{ + self.moveSpeedScaler = 1; + //if siege is not cut add check to see if + //using lightweight and siege for movespeed scaler + self resetSpreadOverride(); + self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); + self player_recoilScaleOff(); + self allowJump(true); +} + + +setFinalStand() +{ + self _setperk( "specialty_pistoldeath"); +} + +unsetFinalStand() +{ + self _unsetperk( "specialty_pistoldeath" ); +} + + +setChallenger() +{ + if ( !level.hardcoreMode ) + { + self.maxhealth = maps\mp\gametypes\_tweakables::getTweakableValue( "player", "maxhealth" ); + + if ( isDefined( self.xpScaler ) && self.xpScaler == 1 && self.maxhealth > 30 ) + { + self.xpScaler = 2; + } + } +} + +unsetChallenger() +{ + self.xpScaler = 1; +} + + +setSaboteur() +{ + self.objectiveScaler = 1.2; +} + +unsetSaboteur() +{ + self.objectiveScaler = 1; +} + + +setLightWeight() +{ + self.moveSpeedScaler = 1.07; + self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); +} + +unsetLightWeight() +{ + self.moveSpeedScaler = 1; + self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); +} + + +setBlackBox() +{ + self.killStreakScaler = 1.5; +} + +unsetBlackBox() +{ + self.killStreakScaler = 1; +} + +setSteelNerves() +{ + self _setperk( "specialty_bulletaccuracy" ); + self _setperk( "specialty_holdbreath" ); +} + +unsetSteelNerves() +{ + self _unsetperk( "specialty_bulletaccuracy" ); + self _unsetperk( "specialty_holdbreath" ); +} + +setDelayMine() +{ +} + +unsetDelayMine() +{ +} + + +setBackShield() +{ + self AttachShieldModel( "weapon_riot_shield_mp", "tag_shield_back" ); +} + + +unsetBackShield() +{ + self DetachShieldModel( "weapon_riot_shield_mp", "tag_shield_back" ); +} + + +setLocalJammer() +{ + if ( !self isEMPed() ) + self RadarJamOn(); +} + + +unsetLocalJammer() +{ + self RadarJamOff(); +} + + +setAC130() +{ + self thread killstreakThink( "ac130", 7, "end_ac130Think" ); +} + +unsetAC130() +{ + self notify ( "end_ac130Think" ); +} + + +setSentryMinigun() +{ + self thread killstreakThink( "airdrop_sentry_minigun", 2, "end_sentry_minigunThink" ); +} + +unsetSentryMinigun() +{ + self notify ( "end_sentry_minigunThink" ); +} + +setCarePackage() +{ + self thread killstreakThink( "airdrop", 2, "endCarePackageThink" ); +} + +unsetCarePackage() +{ + self notify ( "endCarePackageThink" ); +} + +setTank() +{ + self thread killstreakThink( "tank", 6, "end_tankThink" ); +} + +unsetTank() +{ + self notify ( "end_tankThink" ); +} + +setPrecision_airstrike() +{ + println( "!precision airstrike!" ); + self thread killstreakThink( "precision_airstrike", 6, "end_precision_airstrike" ); +} + +unsetPrecision_airstrike() +{ + self notify ( "end_precision_airstrike" ); +} + +setPredatorMissile() +{ + self thread killstreakThink( "predator_missile", 4, "end_predator_missileThink" ); +} + +unsetPredatorMissile() +{ + self notify ( "end_predator_missileThink" ); +} + + +setHelicopterMinigun() +{ + self thread killstreakThink( "helicopter_minigun", 5, "end_helicopter_minigunThink" ); +} + +unsetHelicopterMinigun() +{ + self notify ( "end_helicopter_minigunThink" ); +} + + + +killstreakThink( streakName, streakVal, endonString ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + self endon ( endonString ); + + for ( ;; ) + { + self waittill ( "killed_enemy" ); + + if ( self.pers["cur_kill_streak"] != streakVal ) + continue; + + self thread maps\mp\killstreaks\_killstreaks::giveKillstreak( streakName ); + self thread maps\mp\gametypes\_hud_message::killstreakSplashNotify( streakName, streakVal ); + return; + } +} + + +setThermal() +{ + self ThermalVisionOn(); +} + + +unsetThermal() +{ + self ThermalVisionOff(); +} + + +setOneManArmy() +{ + self thread oneManArmyWeaponChangeTracker(); +} + + +unsetOneManArmy() +{ + self notify ( "stop_oneManArmyTracker" ); +} + + +oneManArmyWeaponChangeTracker() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + self endon ( "stop_oneManArmyTracker" ); + + for ( ;; ) + { + self waittill( "weapon_change", newWeapon ); + + if ( newWeapon != "onemanarmy_mp" ) + continue; + + //if ( self isUsingRemote() ) + // continue; + + self thread selectOneManArmyClass(); + } +} + + +isOneManArmyMenu( menu ) +{ + if ( menu == game["menu_onemanarmy"] ) + return true; + + if ( isDefined( game["menu_onemanarmy_defaults_splitscreen"] ) && menu == game["menu_onemanarmy_defaults_splitscreen"] ) + return true; + + if ( isDefined( game["menu_onemanarmy_custom_splitscreen"] ) && menu == game["menu_onemanarmy_custom_splitscreen"] ) + return true; + + return false; +} + + +selectOneManArmyClass() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self _disableWeaponSwitch(); + + self openPopupMenu( game["menu_onemanarmy"] ); + + self thread closeOMAMenuOnDeath(); + + self waittill ( "menuresponse", menu, className ); + + self _enableWeaponSwitch(); + + if ( className == "back" || !isOneManArmyMenu( menu ) || self isUsingRemote() ) + { + if ( self getCurrentWeapon() == "onemanarmy_mp" ) + { + self _disableWeaponSwitch(); + self switchToWeapon( self getLastWeapon() ); + self waittill ( "weapon_change" ); + self _enableWeaponSwitch(); + } + return; + } + + self thread giveOneManArmyClass( className ); +} + +closeOMAMenuOnDeath() +{ + self endon ( "menuresponse" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + self waittill ( "death" ); + + self _enableWeaponSwitch(); + + self closePopupMenu(); +} + +giveOneManArmyClass( className ) +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + + if ( self _hasPerk( "specialty_omaquickchange" ) ) + { + changeDuration = 3.0; + self playLocalSound( "foly_onemanarmy_bag3_plr" ); + self playSoundToTeam( "foly_onemanarmy_bag3_npc", "allies", self ); + self playSoundToTeam( "foly_onemanarmy_bag3_npc", "axis", self ); + } + else + { + changeDuration = 6.0; + self playLocalSound( "foly_onemanarmy_bag6_plr" ); + self playSoundToTeam( "foly_onemanarmy_bag6_npc", "allies", self ); + self playSoundToTeam( "foly_onemanarmy_bag6_npc", "axis", self ); + } + + self thread omaUseBar( changeDuration ); + + self _disableWeapon(); + self _disableOffhandWeapons(); + + wait ( changeDuration ); + + self _enableWeapon(); + self _enableOffhandWeapons(); + self.OMAClassChanged = true; + + self maps\mp\gametypes\_class::giveLoadout( self.pers["team"], className, false ); + + // handle the fact that detachAll in giveLoadout removed the CTF flag from our back + // it would probably be better to handle this in _detachAll itself, but this is a safety fix + if ( isDefined( self.carryFlag ) ) + self attach( self.carryFlag, "J_spine4", true ); + + self notify ( "changed_kit" ); + level notify ( "changed_kit" ); +} + + +omaUseBar( duration ) +{ + self endon( "disconnect" ); + + useBar = createPrimaryProgressBar( 25 ); + useBarText = createPrimaryProgressBarText( 25 ); + useBarText setText( &"MPUI_CHANGING_KIT" ); + + useBar updateBar( 0, 1 / duration ); + for ( waitedTime = 0; waitedTime < duration && isAlive( self ) && !level.gameEnded; waitedTime += 0.05 ) + wait ( 0.05 ); + + useBar destroyElem(); + useBarText destroyElem(); +} + + +setBlastShield() +{ + self thread blastshieldUseTracker( "specialty_blastshield", ::toggleBlastShield ); + self SetWeaponHudIconOverride( "primaryoffhand", "specialty_blastshield" ); +} + + +unsetBlastShield() +{ + self notify ( "end_perkUseTracker" ); + self SetWeaponHudIconOverride( "primaryoffhand", "none" ); +} + +toggleBlastShield( isEnabled ) +{ + if ( !isEnabled ) + { + self VisionSetNakedForPlayer( "black_bw", 0.15 ); + wait ( 0.15 ); + self _setPerk( "_specialty_blastshield" ); + self VisionSetNakedForPlayer( getDvar( "mapname" ), 0 ); + self playSoundToPlayer( "item_blast_shield_on", self ); + } + else + { + self VisionSetNakedForPlayer( "black_bw", 0.15 ); + wait ( 0.15 ); + self _unsetPerk( "_specialty_blastshield" ); + self VisionSetNakedForPlayer( getDvar( "mapname" ), 0 ); + self playSoundToPlayer( "item_blast_shield_off", self ); + } +} + + +setFreefall() +{ + //eventually set a listener to do a roll when falling damage is taken +} + +unsetFreefall() +{ +} + + +setTacticalInsertion() +{ + self _giveWeapon( "flare_mp", 0 ); + self giveStartAmmo( "flare_mp" ); + + self thread monitorTIUse(); +} + +unsetTacticalInsertion() +{ + self notify( "end_monitorTIUse" ); +} + +clearPreviousTISpawnpoint() +{ + self waittill_any ( "disconnect", "joined_team", "joined_spectators" ); + + if ( isDefined ( self.setSpawnpoint ) ) + self deleteTI( self.setSpawnpoint ); +} + +updateTISpawnPosition() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + self endon ( "end_monitorTIUse" ); + + while ( isReallyAlive( self ) ) + { + if ( self isValidTISpawnPosition() ) + self.TISpawnPosition = self.origin; + + wait ( 0.05 ); + } +} + +isValidTISpawnPosition() +{ + if ( CanSpawn( self.origin ) && self IsOnGround() ) + return true; + else + return false; +} + +monitorTIUse() +{ + self endon ( "death" ); + self endon ( "disconnect" ); + level endon ( "game_ended" ); + self endon ( "end_monitorTIUse" ); + + self thread updateTISpawnPosition(); + self thread clearPreviousTISpawnpoint(); + + for ( ;; ) + { + self waittill( "grenade_fire", lightstick, weapName ); + + if ( weapName != "flare_mp" ) + continue; + + //lightstick delete(); + + if ( isDefined( self.setSpawnPoint ) ) + self deleteTI( self.setSpawnPoint ); + + if ( !isDefined( self.TISpawnPosition ) ) + continue; + + if ( self touchingBadTrigger() ) + continue; + + TIGroundPosition = playerPhysicsTrace( self.TISpawnPosition + (0,0,16), self.TISpawnPosition - (0,0,2048) ) + (0,0,1); + + glowStick = spawn( "script_model", TIGroundPosition ); + glowStick.angles = self.angles; + glowStick.team = self.team; + glowStick.enemyTrigger = spawn( "script_origin", TIGroundPosition ); + glowStick thread GlowStickSetupAndWaitForDeath( self ); + glowStick.playerSpawnPos = self.TISpawnPosition; + + glowStick thread maps\mp\gametypes\_weapons::createBombSquadModel( "weapon_light_stick_tactical_bombsquad", "tag_fire_fx", level.otherTeam[self.team], self ); + + self.setSpawnPoint = glowStick; + return; + } +} + + +GlowStickSetupAndWaitForDeath( owner ) +{ + self setModel( level.spawnGlowModel["enemy"] ); + if ( level.teamBased ) + self maps\mp\_entityheadIcons::setTeamHeadIcon( self.team , (0,0,20) ); + else + self maps\mp\_entityheadicons::setPlayerHeadIcon( owner, (0,0,20) ); + + self thread GlowStickDamageListener( owner ); + self thread GlowStickEnemyUseListener( owner ); + self thread GlowStickUseListener( owner ); + self thread GlowStickTeamUpdater( level.otherTeam[self.team], level.spawnGlow["enemy"], owner ); + + dummyGlowStick = spawn( "script_model", self.origin+ (0,0,0) ); + dummyGlowStick.angles = self.angles; + dummyGlowStick setModel( level.spawnGlowModel["friendly"] ); + dummyGlowStick setContents( 0 ); + dummyGlowStick thread GlowStickTeamUpdater( self.team, level.spawnGlow["friendly"], owner ); + + dummyGlowStick playLoopSound( "emt_road_flare_burn" ); + + self waittill ( "death" ); + + dummyGlowStick stopLoopSound(); + dummyGlowStick delete(); +} + + +GlowStickTeamUpdater( showForTeam, showEffect, owner ) +{ + self endon ( "death" ); + + // PlayFXOnTag fails if run on the same frame the parent entity was created + wait ( 0.05 ); + + //PlayFXOnTag( showEffect, self, "TAG_FX" ); + angles = self getTagAngles( "tag_fire_fx" ); + fxEnt = SpawnFx( showEffect, self getTagOrigin( "tag_fire_fx" ), anglesToForward( angles ), anglesToUp( angles ) ); + TriggerFx( fxEnt ); + + self thread deleteOnDeath( fxEnt ); + + for ( ;; ) + { + self hide(); + fxEnt hide(); + foreach ( player in level.players ) + { + if ( player.team == showForTeam && level.teamBased ) + { + self showToPlayer( player ); + fxEnt showToPlayer( player ); + } + else if ( !level.teamBased && player == owner && showEffect == level.spawnGlow["friendly"] ) + { + self showToPlayer( player ); + fxEnt showToPlayer( player ); + } + else if ( !level.teamBased && player != owner && showEffect == level.spawnGlow["enemy"] ) + { + self showToPlayer( player ); + fxEnt showToPlayer( player ); + } + } + + level waittill_either ( "joined_team", "player_spawned" ); + } +} + +deleteOnDeath( ent ) +{ + self waittill( "death" ); + if ( isdefined( ent ) ) + ent delete(); +} + +GlowStickDamageListener( owner ) +{ + self endon ( "death" ); + + self setCanDamage( true ); + // use large health to work around teamkilling issue + self.health = 5000; + + for ( ;; ) + { + self waittill ( "damage", amount, attacker ); + + if ( level.teambased && isDefined( owner ) && attacker != owner && ( isDefined( attacker.team ) && attacker.team == self.team ) ) + { + self.health += amount; + continue; + } + + if ( self.health < (5000-20) ) + { + if ( isDefined( owner ) && attacker != owner ) + { + attacker notify ( "destroyed_insertion", owner ); + attacker notify( "destroyed_explosive" ); // count towards SitRep Pro challenge + owner thread leaderDialogOnPlayer( "ti_destroyed" ); + } + + attacker thread deleteTI( self ); + } + } +} + +GlowStickUseListener( owner ) +{ + self endon ( "death" ); + level endon ( "game_ended" ); + owner endon ( "disconnect" ); + + self setCursorHint( "HINT_NOICON" ); + self setHintString( &"MP_PICKUP_TI" ); + + self thread updateEnemyUse( owner ); + + for ( ;; ) + { + self waittill ( "trigger", player ); + + player playSound( "chemlight_pu" ); + player thread setTacticalInsertion(); + player thread deleteTI( self ); + } +} + +updateEnemyUse( owner ) +{ + self endon ( "death" ); + + for ( ;; ) + { + self setSelfUsable( owner ); + level waittill_either ( "joined_team", "player_spawned" ); + } +} + +deleteTI( TI ) +{ + if (isDefined( TI.enemyTrigger ) ) + TI.enemyTrigger Delete(); + + spot = TI.origin; + spotAngles = TI.angles; + + TI Delete(); + + dummyGlowStick = spawn( "script_model", spot ); + dummyGlowStick.angles = spotAngles; + dummyGlowStick setModel( level.spawnGlowModel["friendly"] ); + + dummyGlowStick setContents( 0 ); + thread dummyGlowStickDelete( dummyGlowStick ); +} + +dummyGlowStickDelete( stick ) +{ + wait(2.5); + stick Delete(); +} + +GlowStickEnemyUseListener( owner ) +{ + self endon ( "death" ); + level endon ( "game_ended" ); + owner endon ( "disconnect" ); + + self.enemyTrigger setCursorHint( "HINT_NOICON" ); + self.enemyTrigger setHintString( &"MP_DESTROY_TI" ); + self.enemyTrigger makeEnemyUsable( owner ); + + for ( ;; ) + { + self.enemyTrigger waittill ( "trigger", player ); + + player notify ( "destroyed_insertion", owner ); + player notify( "destroyed_explosive" ); // count towards SitRep Pro challenge + + //playFX( level.spawnGlowSplat, self.origin); + + if ( isDefined( owner ) && player != owner ) + owner thread leaderDialogOnPlayer( "ti_destroyed" ); + + player thread deleteTI( self ); + } +} + +setLittlebirdSupport() +{ + self thread killstreakThink( "littlebird_support", 2, "end_littlebird_support_think" ); +} + +unsetLittlebirdSupport() +{ + self notify ( "end_littlebird_support_think" ); +} + +setC4Death() +{ + if ( ! self _hasperk( "specialty_pistoldeath" ) ) + self _setperk( "specialty_pistoldeath"); +} + +unsetC4Death() +{ + +} \ No newline at end of file