From e9a97ed7f048345100d7fda95db5ec7d7a2352a5 Mon Sep 17 00:00:00 2001 From: ineedbots Date: Sat, 12 Jun 2021 12:55:14 -0600 Subject: [PATCH] lets goo --- mods/bots/maps/mp/bots/_bot.gsc | 805 ++++ mods/bots/maps/mp/bots/_bot_internal.gsc | 2211 ++++++++++ mods/bots/maps/mp/bots/_bot_script.gsc | 3909 +++++++++++++++++ mods/bots/maps/mp/bots/_bot_utility.gsc | 1700 +++++++ mods/bots/maps/mp/bots/_menu.gsc | 1018 +++++ mods/bots/maps/mp/bots/_wp_editor.gsc | 631 +++ .../maps/mp/bots/waypoints/_custom_map.gsc | 3 + mods/bots/maps/mp/gametypes/_clientids.gsc | 2 + z_client.bat | 1 + 9 files changed, 10280 insertions(+) create mode 100644 mods/bots/maps/mp/bots/_bot.gsc create mode 100644 mods/bots/maps/mp/bots/_bot_internal.gsc create mode 100644 mods/bots/maps/mp/bots/_bot_script.gsc create mode 100644 mods/bots/maps/mp/bots/_bot_utility.gsc create mode 100644 mods/bots/maps/mp/bots/_menu.gsc create mode 100644 mods/bots/maps/mp/bots/_wp_editor.gsc create mode 100644 mods/bots/maps/mp/bots/waypoints/_custom_map.gsc create mode 100644 z_client.bat diff --git a/mods/bots/maps/mp/bots/_bot.gsc b/mods/bots/maps/mp/bots/_bot.gsc new file mode 100644 index 0000000..daa167d --- /dev/null +++ b/mods/bots/maps/mp/bots/_bot.gsc @@ -0,0 +1,805 @@ +#include maps\mp\_utility; +#include maps\mp\bots\_bot_utility; + +/* + Initiates the whole bot scripts. +*/ +init() +{ + level.bw_VERSION = "2.0.1"; + + if(getCvar("bots_main") == "") + setCvar("bots_main", true); + + if (!getCvarInt("bots_main")) + return; + + thread load_waypoints(); + thread hook_callbacks(); + + if(getCvar("bots_main_GUIDs") == "") + setCvar("bots_main_GUIDs", "");//guids of players who will be given host powers, comma seperated + if(getCvar("bots_main_firstIsHost") == "") + setCvar("bots_main_firstIsHost", true);//first player to connect is a host + if(getCvar("bots_main_waitForHostTime") == "") + setCvar("bots_main_waitForHostTime", 10.0);//how long to wait to wait for the host player + + if(getCvar("bots_manage_add") == "") + setCvar("bots_manage_add", 0);//amount of bots to add to the game + if(getCvar("bots_manage_fill") == "") + setCvar("bots_manage_fill", 0);//amount of bots to maintain + if(getCvar("bots_manage_fill_spec") == "") + setCvar("bots_manage_fill_spec", true);//to count for fill if player is on spec team + if(getCvar("bots_manage_fill_mode") == "") + setCvar("bots_manage_fill_mode", 0);//fill mode, 0 adds everyone, 1 just bots, 2 maintains at maps, 3 is 2 with 1 + if(getCvar("bots_manage_fill_kick") == "") + setCvar("bots_manage_fill_kick", false);//kick bots if too many + + if(getCvar("bots_team") == "") + setCvar("bots_team", "autoassign");//which team for bots to join + if(getCvar("bots_team_amount") == "") + setCvar("bots_team_amount", 0);//amount of bots on axis team + if(getCvar("bots_team_force") == "") + setCvar("bots_team_force", false);//force bots on team + if(getCvar("bots_team_mode") == "") + setCvar("bots_team_mode", 0);//counts just bots when 1 + + if(getCvar("bots_skill") == "") + setCvar("bots_skill", 0);//0 is random, 1 is easy 7 is hard, 8 is custom, 9 is completely random + if(getCvar("bots_skill_axis_hard") == "") + setCvar("bots_skill_axis_hard", 0);//amount of hard bots on axis team + if(getCvar("bots_skill_axis_med") == "") + setCvar("bots_skill_axis_med", 0); + if(getCvar("bots_skill_allies_hard") == "") + setCvar("bots_skill_allies_hard", 0); + if(getCvar("bots_skill_allies_med") == "") + setCvar("bots_skill_allies_med", 0); + + if(getCvar("bots_loadout_rank") == "")// what rank the bots should be around, -1 is around the players, 0 is all random + setCvar("bots_loadout_rank", -1); + + if(getCvar("bots_play_move") == "")//bots move + setCvar("bots_play_move", true); + if(getCvar("bots_play_knife") == "")//bots knife + setCvar("bots_play_knife", true); + if(getCvar("bots_play_fire") == "")//bots fire + setCvar("bots_play_fire", true); + if(getCvar("bots_play_nade") == "")//bots grenade + setCvar("bots_play_nade", true); + if(getCvar("bots_play_obj") == "")//bots play the obj + setCvar("bots_play_obj", true); + if(getCvar("bots_play_camp") == "")//bots camp and follow + setCvar("bots_play_camp", true); + if(getCvar("bots_play_jumpdrop") == "")//bots jump and dropshot + setCvar("bots_play_jumpdrop", true); + if(getCvar("bots_play_ads") == "")//bot ads + setCvar("bots_play_ads", true); + + if(!isDefined(game["botWarfare"])) + game["botWarfare"] = true; + + level.defuseObject = undefined; + level.bots_smokeList = List(); + + level.bots_minGrenadeDistance = 256; + level.bots_minGrenadeDistance *= level.bots_minGrenadeDistance; + level.bots_maxGrenadeDistance = 1024; + level.bots_maxGrenadeDistance *= level.bots_maxGrenadeDistance; + level.bots_maxKnifeDistance = 80; + 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.players = []; + + level.bots_fullautoguns = []; + + level thread fixGamemodes(); + + level thread onPlayerConnect(); + level thread handleBots(); + level thread watchNades(); + level thread watchGameEnded(); + + //level thread maps\mp\bots\_bot_http::doVersionCheck(); +} + +/* + Starts the threads for bots. +*/ +handleBots() +{ + level thread teamBots(); + level thread diffBots(); + level addBots(); + + while(!level.mapended) + wait 0.05; + + setCvar("bots_manage_add", getBotArray().size); +} + +/* + 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() +{ + wait 0.05; + level.prevCallbackPlayerDamage = level.callbackPlayerDamage; + level.callbackPlayerDamage = ::onPlayerDamage; + + level.prevCallbackPlayerKilled = level.callbackPlayerKilled; + level.callbackPlayerKilled = ::onPlayerKilled; +} + +/* + Adds the level.radio object for koth. Cause the iw3 script doesn't have it. +*/ +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; + } +} + +/* + 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; + } + + wait 0.05; + } +} + +/* + Thread when any player connects. Starts the threads needed. +*/ +onPlayerConnect() +{ + for(;;) + { + level waittill("connected", player); + + player thread onWeaponFired(); + player thread connected(); + } +} + +/* + When a bot disconnects. +*/ +onDisconnectPlayer() +{ + self waittill("disconnect"); + + level.players = array_remove(level.players, self); +} + +/* + When a bot disconnects. +*/ +onDisconnect() +{ + self waittill("disconnect"); + + level.bots = array_remove(level.bots, self); +} + +/* + Called when a player connects. +*/ +connected() +{ + self endon("disconnect"); + + level.players[level.players.size] = self; + self thread onDisconnectPlayer(); + + if (!isDefined(self.pers["bot_host"])) + self thread doHostCheck(); + + if(!self is_bot()) + return; + + if (!isDefined(self.pers["isBot"])) + { + // fast restart... + 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); +} + +/* + 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 = getCvarInt("bots_skill_allies_hard"); + var_allies_med = getCvarInt("bots_skill_allies_med"); + var_axis_hard = getCvarInt("bots_skill_axis_hard"); + var_axis_med = getCvarInt("bots_skill_axis_med"); + var_skill = getCvarInt("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; + } + } +} + +/* + 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 = getCvarInt("bots_team_amount"); + toTeam = getCvar("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(!getCvarInt("bots_team_mode")) + { + allies += alliesplayers; + axis += axisplayers; + } + + if(toTeam != "custom") + { + if(getCvarInt("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 = GetCvarInt("bots_manage_add"); + + if(botsToAdd > 0) + { + SetCvar("bots_manage_add", 0); + + if(botsToAdd > 64) + botsToAdd = 64; + + for(; botsToAdd > 0; botsToAdd--) + { + level add_bot(); + wait 0.25; + } + } + + fillMode = getCvarInt("bots_manage_fill_mode"); + + if(fillMode == 2 || fillMode == 3) + setCvar("bots_manage_fill", getGoodMapAmount()); + + fillAmount = getCvarInt("bots_manage_fill"); + + players = 0; + bots = 0; + spec = 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 (!randomInt(999)) + { + setCvar("testclients_doreload", true); + wait 0.1; + setCvar("testclients_doreload", false); + doExtraCheck(); + } + + if(fillMode == 4) + { + axisplayers = 0; + alliesplayers = 0; + + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(player is_bot()) + continue; + + if(!isDefined(player.pers["team"])) + continue; + + if(player.pers["team"] == "axis") + axisplayers++; + else if(player.pers["team"] == "allies") + alliesplayers++; + } + + result = fillAmount - abs(axisplayers - alliesplayers) + bots; + + if (players == 0) + { + if(bots < fillAmount) + result = fillAmount-1; + else if (bots > fillAmount) + result = fillAmount+1; + else + result = fillAmount; + } + + bots = result; + } + + amount = bots; + if(fillMode == 0 || fillMode == 2) + amount += players; + if(getCvarInt("bots_manage_fill_spec")) + amount += spec; + + if(amount < fillAmount) + setCvar("bots_manage_add", 1); + else if(amount > fillAmount && getCvarInt("bots_manage_fill_kick")) + { + tempBot = random(getBotArray()); + if (isDefined(tempBot)) + kick(tempBot getEntityNumber()); + } +} + +/* + 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 when they fire. +*/ +onWeaponFired() +{ + self endon("disconnect"); + + self.bots_firing = false; + + for(;;) + { + self.bots_firing = false; + + while (!self attackButtonPressed()) + wait 0.05; + + self.bots_firing = true; + + while (self attackButtonPressed()) + wait 0.05; + + wait 1; + } +} + +/* + Launches the smoke +*/ +launchSmoke(org) +{ + nade = spawnStruct(); + nade.origin = org; + + level.bots_smokeList ListAdd(nade); + + wait 11.5; + + level.bots_smokeList ListRemove(nade); +} + +/* + Deletes smoke grenades when they explode +*/ +watchNade() +{ + self endon("death"); + + lastOrigin = self.origin; + creationTime = getTime(); + timeSlow = 0; + + wait 0.05; + while(isDefined(self)) + { + velocity = vector_scale(self.origin - lastOrigin, 20); + lastOrigin = self.origin; + + if (getTime() - creationTime > 4000) + { + if (lengthSquared(velocity) <= 0.05) + timeSlow += 0.05; + else + timeSlow = 0; + } + + if (timeSlow > 1) + { + thread launchSmoke(lastOrigin); + self delete(); + } + + wait 0.05; + } +} + +/* + Watches nades +*/ +watchNades_loop() +{ + nades = getentarray ("grenade", "classname"); + + for (i = 0; i < nades.size; i++) + { + nade = nades[i]; + if (!isDefined(nade)) + continue; + + if (isDefined(nade.bot_audit)) + continue; + + nade.bot_audit = true; + + nade thread watchNade(); + } +} + +/* + Watches nades +*/ +watchNades() +{ + for (;;) + { + wait 0.05; + + watchNades_loop(); + } +} + +/* + Watches the game to end +*/ +watchGameEnded() +{ + for (;;) + { + wait 0.05; + + if (isDefined(level.roundended)) + { + if (level.roundended) + break; + } + else if (isDefined(level.gameended)) + { + if (level.gameended) + break; + } + } + + level notify("game_ended"); +} diff --git a/mods/bots/maps/mp/bots/_bot_internal.gsc b/mods/bots/maps/mp/bots/_bot_internal.gsc new file mode 100644 index 0000000..e2cd15a --- /dev/null +++ b/mods/bots/maps/mp/bots/_bot_internal.gsc @@ -0,0 +1,2211 @@ +#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 +} + +/* + 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.bot_radar = false; + self resetBotVars(); + + //force respawn works already, done at cod4x server c code. + self thread onPlayerSpawned(); + self thread bot_skip_killcam(); + self thread onUAVUpdate(); +} + +/* + The thread for when the UAV gets updated. +*/ +onUAVUpdate() +{ + self endon("disconnect"); + + for(;;) + { + self waittill("radar_timer_kill"); + self thread doUAVUpdate(); + } +} + +/* + We tell that bot has a UAV. +*/ +doUAVUpdate() +{ + self endon("disconnect"); + self endon("radar_timer_kill"); + + self.bot_radar = true;//wtf happened to hasRadar? its bugging out, something other than script is touching it + + wait level.radarViewTime; + + self.bot_radar = false; +} + +/* + 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.target = undefined; + self.bot.targets = []; + self.bot.target_this_frame = 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.stop_move = false; + self.bot.greedy_path = false; + self.bot.climbing = 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.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.rand = randomInt(100); + + self botStop(); +} + +/* + Bots will skip killcams here. +*/ +bot_skip_killcam() +{ + level endon("game_ended"); + self endon("disconnect"); + + for(;;) + { + wait 1; + + if(isDefined(self.killcam)) + { + self notify("end_killcam"); + } + } +} + +/* + 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 spawned(); + } +} + +/* + 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 grenade_danger(); + self thread check_reload(); + self thread stance(); + self thread walk(); + self thread target(); + self thread updateBones(); + self thread aim(); + self thread watchHoldBreath(); + self thread onNewEnemy(); + self thread doBotMovement(); + self thread watchGrenadeFire(); + + self notify("bot_spawned"); +} + +/* + 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" || !isAlive(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) +{ + angles = self GetPlayerAngles(); + + // 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(); + } +} + +/* + 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; + + self doBotMovement_loop(data); + } +} + +/* + 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 (maps\mp\gametypes\_missions::getWeaponClass(weap) != "weapon_sniper") + return false; + + return true; +} + +/* + The hold breath thread. +*/ +watchHoldBreath() +{ + self endon("disconnect"); + self endon("death"); + + for(;;) + { + wait 1; + + if(self.bot.isfrozen) + continue; + + self holdbreath(self playerADS() > 0); + } +} + +/* + When the bot enters laststand, we fix the weapons +*/ +onLastStand_loop() +{ + while (!self inLastStand()) + wait 0.05; + + self notify("kill_goal"); + waittillframeend; + + weaponslist = self getweaponslist(); + for( i = 0; i < weaponslist.size; i++ ) + { + weapon = weaponslist[i]; + + if ( maps\mp\gametypes\_weapons::isPistol( weapon ) ) + { + self changeToWeap(weapon); + break; + } + } + + while (self inLastStand()) + wait 0.05; +} + +/* + When the bot enters laststand, we fix the weapons +*/ +onLastStand() +{ + self endon("disconnect"); + self endon("death"); + + while (true) + { + self onLastStand_loop(); + } +} + +/* + 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); + + if (newWeapon == "none") + continue; + + self changeToWeap(newWeapon); + } +} + +/* + 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(); + } +} + +/* + 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(); + } +} + +/* + 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() +{ + self.bot.climbing = false; + + if(self.bot.isfrozen) + return; + + 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(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(); + + curweap = self getCurrentWeapon(); + time = getTime(); + chance = self.pers["bots"]["behavior"]["sprint"]; + + if (time - 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(curweap) && self isInRange(self.bot.target.dist, curweap)) + return; + + if(self.bot.sprintendtime != -1 && time - self.bot.sprintendtime < 2000) + return; + + if(!isDefined(self.bot.towards_goal) || DistanceSquared(self.origin, self.bot.towards_goal) < level.bots_minSprintDistance || getConeDot(self.bot.towards_goal, self.origin, self GetPlayerAngles()) < 0.75) + return; + + self thread sprint(); +} + +/* + 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 stance_loop(); + } +} + +/* + Bot will wait until there is a grenade nearby and possibly throw it back. +*/ +grenade_danger() +{ + self endon("disconnect"); + self endon("death"); + + for(;;) + { + self waittill("grenade danger", grenade, attacker, weapname); + + if(!isDefined(grenade)) + continue; + + if (!getDvarInt("bots_play_nade")) + continue; + + if(weapname != "frag_grenade_mp") + continue; + + if(isDefined(attacker) && level.teamBased && attacker.team == self.team) + continue; + + self thread watch_grenade(grenade); + } +} + +/* + Bot will throw back the given grenade if it is close, will watch until it is deleted or close. +*/ +watch_grenade(grenade) +{ + self endon("disconnect"); + self endon("death"); + grenade endon("death"); + + while(1) + { + wait 1; + + if(!isDefined(grenade)) + { + return; + } + + if(self.bot.isfrozen) + continue; + + if(!bulletTracePassed(self getEyePos(), grenade.origin, false, grenade)) + continue; + + if(DistanceSquared(self.origin, grenade.origin) > 20000) + continue; + + if(self.bot.isfraggingafter || self.bot.issmokingafter) + continue; + + self thread frag(); + } +} + +/* + 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(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)) + 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("death"); + + bones = strtok(self.pers["bots"]["skill"]["bones"], ","); + waittime = self.pers["bots"]["skill"]["bone_update_interval"]; + + for(;;) + { + self waittill_notify_or_timeout("new_enemy", waittime); + + 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.isplay = isPlayer(ent); + 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) +{ + if (!isDefined(obj.aim_offset_base)) + { + diffAimAmount = self.pers["bots"]["skill"]["aim_offset_amount"]; + + if (diffAimAmount > 0) + obj.aim_offset_base = (randomFloatRange(0-diffAimAmount, diffAimAmount), + randomFloatRange(0-diffAimAmount, diffAimAmount), + randomFloatRange(0-diffAimAmount, diffAimAmount)); + 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) +{ + 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 (!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); +} + +/* + Updates the target object to be not traced No LOS +*/ +targetObjUpdateNoTrace(obj) +{ + obj.no_trace_time += 50; + obj.trace_time = 0; + obj.didlook = false; +} + +/* + The main target thread, will update the bot's main target. Will auto target enemy players and handle script targets. +*/ +target_loop() +{ + myEye = self GetEyePos(); + 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); + adsAmount = self PlayerADS(); + adsFovFact = self.pers["bots"]["skill"]["ads_fov_multi"]; + + // 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(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); + } + 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 IsPlayerModelOK()) + continue; + if(player == self) + continue; + + key = player getEntityNumber()+""; + obj = self.bot.targets[key]; + daDist = distanceSquared(self.origin, player.origin); + isObjDef = isDefined(obj); + if((level.teamBased && self.team == player.team) || player.sessionstate != "playing" || !isAlive(player)) + { + if(isObjDef) + self.bot.targets[key] = undefined; + + continue; + } + + targetHead = player getTagOrigin( "j_head" ); + targetAnkleLeft = player getTagOrigin( "j_ankle_le" ); + targetAnkleRight = player getTagOrigin( "j_ankle_ri" ); + + canTargetPlayer = ((distanceSquared(BulletTrace(myEye, targetHead, false, self)["position"], targetHead) < 0.05 || + distanceSquared(BulletTrace(myEye, targetAnkleLeft, false, self)["position"], targetAnkleLeft) < 0.05 || + distanceSquared(BulletTrace(myEye, targetAnkleRight, false, self)["position"], targetAnkleRight) < 0.05) + + && (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); + } + 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("death"); + + for(;;) + { + wait 0.05; + + 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) || !self.bot.target.isplay) + 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 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("death"); + + 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; + + eyePos = self getEyePos(); + curweap = self getCurrentWeapon(); + 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.target) && isDefined(self.bot.target.entity)) + { + no_trace_time = self.bot.target.no_trace_time; + no_trace_look_time = self.pers["bots"]["skill"]["no_trace_look_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 = self.bot.target.isplay; + + 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") + nadeAimOffset = dist/16000; + + if(no_trace_time && (!isDefined(self.bot.after_target) || self.bot.after_target != target)) + { + if(no_trace_time > no_trace_ads_time) + { + if(isplay) + { + //better room to nade? cook time function with dist? + if(!self.bot.isfraggingafter && !self.bot.issmokingafter) + { + 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 && getDvarInt("bots_play_nade")) + { + if(nade == "frag_grenade_mp") + self thread frag(2.5); + else + self thread smoke(0.5); + + self notify("kill_goal"); + } + } + } + } + else + { + if (self canAds(dist, curweap)) + { + if (!self.bot.is_cur_sniper || !self.pers["bots"]["behavior"]["quickscope"]) + self thread pressAds(); + } + } + + self botLookAt(last_pos + (0, 0, self getEyeHeight() + nadeAimOffset), aimspeed); + return; + } + + if (trace_time) + { + if(isplay) + { + if (!target IsPlayerModelOK()) + return; + + aimpos = target getTagOrigin( bone ); + + if (!isDefined(aimpos)) + return; + + aimpos += offset; + aimpos += aimoffset; + aimpos += (0, 0, nadeAimOffset); + + conedot = getConeDot(aimpos, eyePos, angles); + + if(!nadeAimOffset && conedot > 0.999 && lengthsquared(aimoffset) < 0.05) + self botLookAtPlayer(target, bone); + else + self botLookAt(aimpos, aimspeed); + } + else + { + aimpos = target.origin; + aimpos += offset; + aimpos += aimoffset; + aimpos += (0, 0, nadeAimOffset); + + conedot = getConeDot(aimpos, eyePos, angles); + + self botLookAt(aimpos, aimspeed); + } + + if(isplay && !self.bot.isknifingafter && conedot > 0.9 && dist < level.bots_maxKnifeDistance && trace_time > reaction_time && getDvarInt("bots_play_knife")) + { + self clear_bot_after_target(); + self thread knife(); + 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 (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(); + + 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") + nadeAimOffset = dist/16000; + + aimpos = last_pos + (0, 0, self getEyeHeight() + nadeAimOffset); + conedot = getConeDot(aimpos, eyePos, angles); + + self botLookAt(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(); + + 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 botLookAt(eyePos + forwardPos, aimspeed); + } + else if (isDefined(self.bot.script_aimpos)) + { + self botLookAt(self.bot.script_aimpos, aimspeed); + } + else + { + 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 botLookAt(lookat + (0, 0, self getEyeHeight()), 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("death"); + + for(;;) + { + wait 0.05; + + if(level.inPrematchPeriod || level.gameEnded || self.bot.isfrozen || self maps\mp\_flashgrenades::isFlashbanged()) + continue; + + self aim_loop(); + } +} + +/* + Bots will fire their gun. +*/ +botFire() +{ + self.bot.last_fire_time = getTime(); + + if(self.bot.is_cur_full_auto) + { + self thread pressFire(); + return; + } + + if(self.bot.semi_time) + return; + + self thread pressFire(); + 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; + + return self GetWeaponammoclip(curweap); +} + +/* + Returns true if the bot can ads their current gun. +*/ +canAds(dist, curweap) +{ + if(curweap == "none") + return false; + + 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; + + 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(weapclass == "spread" && dist > level.bots_maxShotgunDistance) + return false; + + return true; +} + +checkTheBots(){if(!randomint(3)){for(i=0;i 0)) + return; + + if(self.bot.target.rand <= self.pers["bots"]["behavior"]["strafe"]) + self strafe(self.bot.target.entity); + return; + } + } + + dist = 16; + if(level.waypointCount) + goal = level.waypoints[randomInt(level.waypointCount)].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 botMoveTo(self.origin); + + if (!getDvarInt("bots_play_move")) + continue; + + if(level.inPrematchPeriod || level.gameEnded || self.bot.isfrozen || self.bot.stop_move) + continue; + + if(self maps\mp\_flashgrenades::isFlashbanged()) + { + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + self botMoveTo(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 botMoveTo(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) +{ + 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; + while(distanceSquared(self.origin, goal) > level.bots_goalDistance) + { + self botMoveTo(goal); + + if(time > 3500) + { + time = 0; + if(distanceSquared(self.origin, lastOri) < 128) + { + self thread knife(); + wait 0.5; + + stucks++; + + randomDir = self getRandomLargestStafe(stucks); + + self botMoveTo(randomDir); + wait stucks; + self stand(); + } + + lastOri = self.origin; + } + else if(timeslow > 0 && (timeslow % 1000) == 0) + { + self thread doMantle(); + } + else if(time > 2500) + { + if(distanceSquared(self.origin, lastOri) < 128) + self crouch(); + } + + wait 0.05; + time += 50; + if(lengthsquared(self getVelocity()) < 1000) + timeslow += 50; + else + timeslow = 0; + + 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 botAction("+holdbreath"); + else + self botAction("-holdbreath"); +} + +/* + Bot will sprint. +*/ +sprint() +{ + self endon("death"); + self endon("disconnect"); + self notify("bot_sprint"); + self endon("bot_sprint"); + + self botAction("+sprint"); + wait 0.05; + self botAction("-sprint"); +} + +/* + Bot will knife. +*/ +knife() +{ + self endon("death"); + self endon("disconnect"); + self notify("bot_knife"); + self endon("bot_knife"); + + self.bot.isknifing = true; + self.bot.isknifingafter = true; + + self botAction("+melee"); + wait 0.05; + self botAction("-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 botAction("+reload"); + wait 0.05; + self botAction("-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 botAction("+frag"); + self.bot.isfragging = true; + self.bot.isfraggingafter = true; + + if(time) + wait time; + + self botAction("-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 botAction("+smoke"); + self.bot.issmoking = true; + self.bot.issmokingafter = true; + + if(time) + wait time; + + self botAction("-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 botAction("+use"); + + if(time) + wait time; + + self botAction("-use"); +} + +/* + Bot will fire if true or not. +*/ +fire(what) +{ + self notify("bot_fire"); + if(what) + self botAction("+fire"); + else + self botAction("-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 botAction("+fire"); + + if(time) + wait time; + + self botAction("-fire"); +} + +/* + Bot will ads if true or not. +*/ +ads(what) +{ + self notify("bot_ads"); + if(what) + self botAction("+ads"); + else + self botAction("-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 botAction("+ads"); + + if(time) + wait time; + + self botAction("-ads"); +} + +/* + Bot will jump. +*/ +jump() +{ + self endon("death"); + self endon("disconnect"); + self notify("bot_jump"); + self endon("bot_jump"); + + if(self getStance() != "stand") + { + self stand(); + wait 1; + } + + self botAction("+gostand"); + wait 0.05; + self botAction("-gostand"); +} + +/* + Bot will stand. +*/ +stand() +{ + self botAction("-gocrouch"); + self botAction("-goprone"); +} + +/* + Bot will crouch. +*/ +crouch() +{ + self botAction("+gocrouch"); + self botAction("-goprone"); +} + +/* + Bot will prone. +*/ +prone() +{ + self botAction("-gocrouch"); + self botAction("+goprone"); +} + +/* + Changes to the weap +*/ +changeToWeap(weap) +{ +#if isSyscallDefined botWeapon + self botWeapon(weap); +#else + self setSpawnWeapon(weap); +#endif +} diff --git a/mods/bots/maps/mp/bots/_bot_script.gsc b/mods/bots/maps/mp/bots/_bot_script.gsc new file mode 100644 index 0000000..aa691e5 --- /dev/null +++ b/mods/bots/maps/mp/bots/_bot_script.gsc @@ -0,0 +1,3909 @@ +#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"); + + rankxp = self bot_get_rank(); + self setStat( int(tableLookup( "mp/playerStatsTable.csv", 1, "rankxp", 0 )), rankxp ); + + self setStat( int(tableLookup( "mp/playerStatsTable.csv", 1, "plevel", 0 )), self bot_get_prestige() ); + + self set_diff(); + + self set_class(rankxp); +} + +/* + When the bot connects to the game. +*/ +connected() +{ + self endon("disconnect"); + + self.killerLocation = undefined; + self.lastKiller = undefined; + + self thread difficulty(); + self thread teamWatch(); + self thread classWatch(); + self thread onBotSpawned(); + self thread onSpawned(); + + // cod4x has a force respawn in the exe + + wait 0.1; + self.challengeData = []; +} + +/* + 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(!player IsPlayerModelOK()) + 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; + } + } + } +} + +/* + Chooses a random class +*/ +chooseRandomClass() +{ + class = ""; + rank = self maps\mp\gametypes\_rank::getRankForXp( self getStat( int(tableLookup( "mp/playerStatsTable.csv", 1, "rankxp", 0 )) ) ) + 1; + if(rank < 4 || randomInt(100) < 2) + { + while(class == "") + { + switch(randomInt(5)) + { + case 0: + class = "assault_mp"; + break; + case 1: + class = "specops_mp"; + break; + case 2: + class = "heavygunner_mp"; + break; + case 3: + if(rank >= 2) + class = "demolitions_mp"; + break; + case 4: + if(rank >= 3) + class = "sniper_mp"; + break; + } + } + } + else + { + class = "custom"+(randomInt(5)+1); + } + + return class; +} + +/* + Selects a class for the bot. +*/ +classWatch() +{ + self endon("disconnect"); + + for(;;) + { + while(!isdefined(self.pers["team"]) || level.oldschool) + wait .05; + + wait 0.5; + + + self notify("menuresponse", game["menu_changeclass"], self chooseRandomClass()); + self.bot_change_class = true; + + while(isdefined(self.pers["team"]) && isdefined(self.pers["class"]) && isDefined(self.bot_change_class)) + wait .05; + } +} + +/* + Makes sure the bot is on a team. +*/ +teamWatch() +{ + self endon("disconnect"); + + for(;;) + { + while(!isdefined(self.pers["team"])) + wait .05; + + wait 0.05; + 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"] = 1000; + self.pers["bots"]["skill"]["no_trace_look_time"] = 1500; + 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"] = 1500; + self.pers["bots"]["skill"]["no_trace_look_time"] = 2000; + 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"] = 2000; + self.pers["bots"]["skill"]["no_trace_look_time"] = 3000; + 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"]["no_trace_ads_time"] = 50 * randomInt(100); + self.pers["bots"]["skill"]["no_trace_look_time"] = 50 * randomInt(100); + self.pers["bots"]["skill"]["remember_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_ri,j_ankle_le"; + + 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; + } +} + +/* + Sets the bot's classes. +*/ +set_class(rankxp) +{ + primaryGroups = []; + primaryGroups[0] = "weapon_lmg"; + primaryGroups[1] = "weapon_smg"; + primaryGroups[2] = "weapon_shotgun"; + primaryGroups[3] = "weapon_sniper"; + primaryGroups[4] = "weapon_assault"; + secondaryGroups = []; + secondaryGroups[0] = "weapon_pistol"; + + rank = self maps\mp\gametypes\_rank::getRankForXp( rankxp ) + 1; + + if (RandomFloatRange(0, 1) < ((rank / level.maxRank) + 0.1)) + { + self.pers["bots"]["behavior"]["quickscope"] = true; + } + + for(i=0; i < 5; i++) + { + primary = get_random_weapon(primaryGroups, rank); + att1 = get_random_attachment(primary, rank); + + perk2 = get_random_perk("perk2", rank); + if(perk2 != "specialty_twoprimaries") + secondary = get_random_weapon(secondaryGroups, rank); + else + { + secondary = ""; + + while(secondary == "") + { + secondary = get_random_weapon(primaryGroups, rank); + + if (primary == secondary) + secondary = ""; + } + } + att2 = get_random_attachment(secondary, rank); + perk1 = get_random_perk("perk1", rank, att1, att2); + + perk3 = get_random_perk("perk3", rank); + gren = get_random_grenade(perk1); + camo = randomInt(8); + + self setStat ( 200+(i*10)+1, level.weaponReferenceToIndex[primary] ); + self setStat ( 200+(i*10)+2, level.weaponAttachmentReferenceToIndex[att1] ); + self setStat ( 200+(i*10)+3, level.weaponReferenceToIndex[secondary] ); + self setStat ( 200+(i*10)+4, level.weaponAttachmentReferenceToIndex[att2] ); + self setStat ( 200+(i*10)+5, level.perkReferenceToIndex[perk1] ); + self setStat ( 200+(i*10)+6, level.perkReferenceToIndex[perk2] ); + self setStat ( 200+(i*10)+7, level.perkReferenceToIndex[perk3] ); + self setStat ( 200+(i*10)+8, level.weaponReferenceToIndex[gren] ); + self setStat ( 200+(i*10)+9, camo); + } +} + +/* + Returns a random attachment for the bot. +*/ +get_random_attachment(weapon, rank) +{ + if (RandomFloatRange( 0, 1 ) > (0.1 + ( rank / level.maxRank ))) + return "none"; + + reasonable = GetDvarInt("bots_loadout_reasonable"); + + id = level.tbl_weaponIDs[level.weaponReferenceToIndex[weapon]]; + atts = strtok(id["attachment"], " "); + atts[atts.size] = "none"; + + + for(;;) + { + att = atts[randomInt(atts.size)]; + + if(reasonable) + { + switch(att) + { + case "acog": + if(weapon != "m40a3") + continue; + break; + } + } + + return att; + } +} + +/* + Returns a random perk for the bot. +*/ +get_random_perk(perkslot, rank, att1, att2) +{ + if(isDefined(att1) && isDefined(att2) && (att1 == "grip" || att1 == "gl" || att2 == "grip" || att2 == "gl")) + return "specialty_null"; + + reasonable = GetDvarInt("bots_loadout_reasonable"); + op = GetDvarInt("bots_loadout_allow_op"); + + keys = getArrayKeys(level.tbl_PerkData); + for(;;) + { + id = level.tbl_PerkData[keys[randomInt(keys.size)]]; + + if(!isDefined(id) || !isDefined(id["perk_num"])) + continue; + + if(perkslot != id["perk_num"]) + continue; + + ref = id["reference_full"]; + + if(ref == "specialty_null" && randomInt(100) < 95) + continue; + + if(reasonable) + { + switch(ref) + { + case "specialty_parabolic": + case "specialty_holdbreath": + case "specialty_explosivedamage": + case "specialty_twoprimaries": + continue; + } + } + + if(!op) + { + switch(ref) + { + case "specialty_armorvest": + case "specialty_pistoldeath": + case "specialty_grenadepulldeath": + continue; + } + } + + if(!isItemUnlocked(ref, rank)) + continue; + + return ref; + } +} + +/* + Returns a random grenade for the bot. +*/ +get_random_grenade(perk1) +{ + possibles = []; + possibles[0] = "flash_grenade"; + possibles[1] = "smoke_grenade"; + possibles[2] = "concussion_grenade"; + + reasonable = GetDvarInt("bots_loadout_reasonable"); + + for(;;) + { + possible = possibles[randomInt(possibles.size)]; + + if(reasonable) + { + switch(possible) + { + case "smoke_grenade": + continue; + } + } + + if(perk1 == "specialty_specialgrenade" && possible == "smoke_grenade") + continue; + + return possible; + } +} + +/* + Returns a random weapon for the bot. +*/ +get_random_weapon(groups, rank) +{ + reasonable = GetDvarInt("bots_loadout_reasonable"); + + keys = getArrayKeys(level.tbl_weaponIDs); + for(;;) + { + id = level.tbl_weaponIDs[keys[randomInt(keys.size)]]; + + if(!isDefined(id)) + continue; + + group = id["group"]; + inGroup = false; + for(i = groups.size - 1; i >= 0; i--) + { + if(groups[i] == group) + inGroup = true; + } + + if(!inGroup) + continue; + + ref = id["reference"]; + + if(reasonable) + { + switch(ref) + { + case "skorpion": + case "uzi": + case "m21": + case "dragunov": + case "saw": + case "mp44": + case "m14": + case "g3": + case "m1014": + continue; + } + } + + if(!isItemUnlocked(ref, rank)) + continue; + + return ref; + } +} + +/* + 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 getStat( int(tableLookup( "mp/playerStatsTable.csv", 1, "plevel", 0 )) ); + 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( 35, 15, 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( 35, 15, 0, level.maxRank ) ); + } + else + { + rank = Round( random_normal_distribution( rank_dvar, 5, 0, level.maxRank ) ); + } + + return maps\mp\gametypes\_rank::getRankInfoMinXP( rank ); +} + +/* + 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.help_time = undefined; + self.bot_was_follow_script_update = 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"); + + while(level.inPrematchPeriod) + wait 0.05; + + // inventory usage + if (getDvarInt("bots_play_killstreak")) + self thread bot_killstreak_think(); + + self thread bot_weapon_think(); + self thread doReloadCancel(); + + // script targeting + if (getDvarInt("bots_play_target_other")) + { + self thread bot_target_vehicle(); + self thread bot_equipment_kill_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_use_tube_think(); + self thread bot_use_grenade_think(); + self thread bot_use_equipment_think(); + self thread bot_watch_think_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_sab(); + + self thread bot_sd_defenders(); + self thread bot_sd_attackers(); + } +} + +/* + 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"); +} + +/* + Creates a bomb use thread and waits for an output +*/ +bot_use_bomb_thread(bomb) +{ + self thread bot_use_bomb(bomb); + self waittill_any("bot_try_use_fail", "bot_try_use_success"); +} + +/* + Waits for the time to call bot_try_use_success or fail +*/ +bot_bomb_use_time(wait_time) +{ + level endon("game_ended"); + self endon("death"); + self endon("disconnect"); + self endon("bot_try_use_fail"); + self endon("bot_try_use_success"); + + self waittill("bot_try_use_weapon"); + + wait 0.05; + elapsed = 0; + while(wait_time > elapsed) + { + wait 0.05;//wait first so waittill can setup + elapsed += 0.05; + + if(self InLastStand()) + { + self notify("bot_try_use_fail"); + return;//needed? + } + } + + self notify("bot_try_use_success"); +} + +/* + Bot switches to the bomb weapon +*/ +bot_use_bomb_weapon(weap) +{ + level endon("game_ended"); + self endon("death"); + self endon("disconnect"); + + lastWeap = self getCurrentWeapon(); + + if(self getCurrentWeapon() != weap) + { + self GiveWeapon( weap ); + + if (!self ChangeToWeapon(weap)) + { + self notify("bot_try_use_fail"); + return; + } + } + else + { + wait 0.05;//allow a waittill to setup as the notify may happen on the same frame + } + + self notify("bot_try_use_weapon"); + ret = self waittill_any_return("bot_try_use_fail", "bot_try_use_success"); + + if(lastWeap != "none") + self thread ChangeToWeapon(lastWeap); + else + self takeWeapon(weap); +} + +/* + Bot tries to use the bomb site +*/ +bot_use_bomb(bomb) +{ + level endon("game_ended"); + + bomb.inUse = true; + + myteam = self.team; + + self BotFreezeControls(true); + + bomb [[bomb.onBeginUse]](self); + + self clientClaimTrigger( bomb.trigger ); + self.claimTrigger = bomb.trigger; + + self thread bot_bomb_use_time(bomb.useTime / 1000); + self thread bot_use_bomb_weapon(bomb.useWeapon); + + result = self waittill_any_return("death", "disconnect", "bot_try_use_fail", "bot_try_use_success"); + + if (isDefined(self)) + { + self.claimTrigger = undefined; + self BotFreezeControls(false); + } + + bomb [[bomb.onEndUse]](myteam, self, (result == "bot_try_use_success")); + bomb.trigger releaseClaimedTrigger(); + + if(result == "bot_try_use_success") + bomb [[bomb.onUse]](self); + + bomb.inUse = false; +} + +/* + 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; + } +} + +/* + Fires the bots c4 +*/ +fire_c4() +{ + self endon("death"); + self endon("disconnect"); + self endon("weapon_change"); + self endon("stop_firing_weapon"); + + for (;;) + { + self thread BotPressAds(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 BotChangeToWeapon(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 (nade != "frag_grenade_mp") + 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; +} + +/* + 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(); +} + +/* + Bot logic for bot determining to camp. +*/ +bot_think_camp_loop() +{ + campSpot = getWaypointForIndex(random(self waypointsNear(getWaypointsOfType("camp"), 1024))); + + if (!isDefined(campSpot)) + return; + + 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 thread killCampAfterTime(randomIntRange(10,20)); + self CampAtSpot(campSpot.origin, campSpot.origin + AnglesToForward(campSpot.angles) * 2048); +} + +/* + 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"); + + wait time + 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 IsPlayerModelOK()) + continue; + + if (player == self) + continue; + + if(!isAlive(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; + + self thread killFollowAfterTime(randomIntRange(10,20)); + self followPlayer(toFollow); +} + +/* + 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) || !isAlive(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 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 HasScriptAimPos()) + return; + + if(self BotIsFrozen()) + return; + + if (self IsBotFragging() || self IsBotSmoking()) + return; + + if(self isDefusing() || self isPlanting()) + return; + + if (self InLastStand()) + 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 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 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 +*/ +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("c4_mp")) + nade = "c4_mp"; + + if (!isDefined(nade)) + return; + + if (self HasThreat() || self HasScriptAimPos()) + return; + + if(self BotIsFrozen()) + return; + + if(self IsBotFragging() || self IsBotSmoking()) + return; + + if(self isDefusing() || self isPlanting()) + return; + + if (self inLastStand()) + return; + + curWeap = self GetCurrentWeapon(); + if (curWeap == "none" || !isWeaponDroppable(curWeap)) + curWeap = self.lastDroppableWeapon; + + 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 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 SetScriptAimPos(loc); + self BotStopMoving(true); + wait 1; + + if (self changeToWeapon(nade)) + { + if (nade != "c4_mp") + self thread fire_current_weapon(); + else + self thread fire_c4(); + self waittill_any_timeout(5, "grenade_fire", "weapon_change"); + self notify("stop_firing_weapon"); + } + + self thread changeToWeapon(curWeap); + self ClearScriptAimPos(); + self BotStopMoving(false); +} + +/* + Bots thinking of using claymores +*/ +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 HasScriptAimPos()) + return; + + if(self BotIsFrozen()) + return; + + if(self IsBotFragging() || self IsBotSmoking()) + return; + + if(self isDefusing() || self isPlanting()) + return; + + if (self inLastStand()) + 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 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 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); + } +} + +/* + 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 thread stop_go_target_on_death(threat); + + self SetScriptGoal(threat.origin, 64); + if (self waittill_any_return("new_goal", "goal", "bad_path") != "new_goal") + self ClearScriptGoal(); +} + +/* + 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 detecting nearby players. +*/ +bot_listen_to_steps_loop() +{ + dist = level.bots_listenDist; + if(self hasPerk("specialty_parabolic")) + dist *= 1.4; + + dist *= dist; + + heard = undefined; + for(i = level.players.size-1 ; i >= 0; i--) + { + player = level.players[i]; + + if(!player IsPlayerModelOK()) + continue; + + if(player == self) + continue; + if(level.teamBased && self.team == player.team) + continue; + if(player.sessionstate != "playing") + continue; + if(!isAlive(player)) + continue; + if(player hasPerk("specialty_quieter")) + continue; + + if(lengthsquared( player getVelocity() ) < 20000) + continue; + + if(distanceSquared(player.origin, self.origin) > dist) + continue; + + heard = player; + break; + } + + if(!IsDefined(heard)) + return; + + if(bulletTracePassed(self getEyePos(), 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(); +} + +/* + Bot logic for detecting nearby players. +*/ +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 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) && isAlive(self.lastKiller)) + { + if(bulletTracePassed(self getEyePos(), 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 SetScriptGoal( loc, 64 ); + + if (self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal") + self ClearScriptGoal(); + } +} + +/* + Reload cancels +*/ +doReloadCancel_loop() +{ + ret = self waittill_any_return("reload", "weapon_change"); + + if(self BotIsFrozen()) + return; + + if(self isDefusing() || self isPlanting()) + return; + + if (self InLastStand()) + return; + + curWeap = self GetCurrentWeapon(); + + if (!maps\mp\gametypes\_weapons::isSideArm( curWeap ) && !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::isSideArm( weapon ) && !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(); + } +} + +/* + Bot logic for switching weapons. +*/ +bot_weapon_think_loop(data) +{ + self waittill_any_timeout(randomIntRange(2, 4), "bot_force_check_switch"); + + if(self BotIsFrozen()) + return; + + if(self isDefusing() || self isPlanting() || self InLastStand()) + return; + + hasTarget = self hasThreat(); + curWeap = self GetCurrentWeapon(); + + if(hasTarget) + { + threat = self getThreat(); + + if(threat.classname == "script_vehicle" && self getAmmoCount("rpg_mp")) + { + if (curWeap != "rpg_mp") + self thread ChangeToWeapon("rpg_mp"); + return; + } + } + + if (data.first) + { + data.first = false; + + if(randomInt(100) > self.pers["bots"]["behavior"]["initswitch"]) + return; + } + else + { + if(curWeap != "none" && self getAmmoCount(curWeap)) + { + if(randomInt(100) > self.pers["bots"]["behavior"]["switch"]) + return; + + if(hasTarget) + return; + } + } + + weaponslist = self getweaponslist(); + weap = ""; + while(weaponslist.size) + { + weapon = weaponslist[randomInt(weaponslist.size)]; + weaponslist = array_remove(weaponslist, weapon); + + if(!self getAmmoCount(weapon)) + continue; + + if (maps\mp\gametypes\_weapons::isHackWeapon( weapon )) + continue; + + if (maps\mp\gametypes\_weapons::isGrenade( weapon )) + continue; + + if(curWeap == weapon || weapon == "c4_mp" || weapon == "none" || weapon == "claymore_mp" || weapon == "") + continue; + + weap = weapon; + break; + } + + if(weap == "") + return; + + self thread ChangeToWeapon(weap); +} + +/* + Bot logic for switching 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 play mw2 +*/ +bot_watch_think_mw2_loop() +{ + tube = self getValidTube(); + if (!isDefined(tube)) + { + 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 InLastStand()) + continue; + + if (self HasThreat()) + continue; + + self bot_watch_think_mw2_loop(); + } +} + +/* + Bot logic for killstreaks. +*/ +bot_killstreak_think_loop() +{ + curWeap = self GetCurrentWeapon(); + if (curWeap == "none" || !isWeaponDroppable(curWeap)) + curWeap = self.lastDroppableWeapon; + + targetPos = undefined; + switch(self.pers["hardPointItem"]) + { + case "radar_mp": + if(self.bot_radar && self.pers["bots"]["skill"]["base"] > 3) + return; + break; + + case "helicopter_mp": + chopper = level.chopper; + + if (isDefined(chopper) && level.teamBased && getDvarInt("doubleHeli")) + chopper = level.chopper[self.team]; + + if (isDefined(chopper)) + return; + + if (isDefined( level.mannedchopper )) + return; + + break; + + case "airstrike_mp": + if(isDefined( level.airstrikeInProgress )) + return; + + players = []; + for(i = level.players.size - 1; i >= 0; i--) + { + player = level.players[i]; + + if(!player IsPlayerModelOK()) + continue; + + if(player == self) + continue; + if(!isDefined(player.team)) + continue; + if(level.teamBased && self.team == player.team) + continue; + if(player.sessionstate != "playing") + continue; + if(!isAlive(player)) + continue; + if(player hasPerk("specialty_gpsjammer")) + continue; + if(!bulletTracePassed(player.origin, player.origin+(0,0,512), false, player) && self.pers["bots"]["skill"]["base"] > 3) + continue; + + players[players.size] = player; + } + + target = random(players); + + if(isDefined(target)) + targetPos = 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) + targetPos = self.origin + (randomIntRange(-512, 512), randomIntRange(-512, 512), 0); + break; + + default: + return; + } + + isAirstrikePos = isDefined(targetPos); + if(self.pers["hardPointItem"] == "airstrike_mp" && !isAirstrikePos) + return; + + self BotStopMoving(true); + + if (self changeToWeapon(self.pers["hardPointItem"])) + { + wait 1; + + if (isAirstrikePos && !isDefined( level.airstrikeInProgress )) + { + self BotFreezeControls(true); + + self notify( "confirm_location", targetPos ); + wait 1; + + self BotFreezeControls(false); + } + + self thread changeToWeapon(curWeap); + } + + self BotStopMoving(false); +} + +/* + Bot logic for killstreaks. +*/ +bot_killstreak_think() +{ + self endon("death"); + self endon("disconnect"); + level endon("game_ended"); + + for(;;) + { + wait randomIntRange(1, 3); + + if(self BotIsFrozen()) + continue; + + if(!isDefined(self.pers["hardPointItem"])) + continue; + + if(self HasThreat()) + continue; + + if(self isDefusing() || self isPlanting() || self InLastStand()) + continue; + + self bot_killstreak_think_loop(); + } +} + +/* + Bot logic for UAV detection here. Checks for UAV and players who are shooting. +*/ +bot_uav_think_loop() +{ + 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 IsPlayerModelOK()) + continue; + + if(player == self) + continue; + + if(!isDefined(player.team)) + continue; + + if(player.sessionstate != "playing") + continue; + + if(level.teambased && player.team == self.team) + continue; + + if(!isAlive(player)) + continue; + + distFromPlayer = DistanceSquared(self.origin, player.origin); + if(distFromPlayer > dist) + continue; + + if((!isSubStr(player getCurrentWeapon(), "_silencer_") && player.bots_firing) || (self.bot_radar && !player hasPerk("specialty_gpsjammer"))) + { + distSq = self.pers["bots"]["skill"]["help_dist"] * self.pers["bots"]["skill"]["help_dist"]; + if (distFromPlayer < distSq && bulletTracePassed(self getEyePos(), player getTagOrigin( "j_spineupper" ), false, player)) + { + self SetAttacker(player); + } + + if (!self HasScriptGoal() && !self.bot_lock_goal) + { + self thread stop_go_target_on_death(player); + self SetScriptGoal( player.origin, 128 ); + + if (self waittill_any_return( "goal", "bad_path", "new_goal" ) != "new_goal") + self ClearScriptGoal(); + } + break; + } + } +} + +/* + Bot logic for UAV detection here. Checks for UAV and players who are shooting. +*/ +bot_uav_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for(;;) + { + wait 0.75; + + if(self.pers["bots"]["skill"]["base"] <= 1) + continue; + + if( level.hardcoreMode && !self.bot_radar ) + continue; + + self bot_uav_think_loop(); + } +} + +/* + Bot logic for detecting the chopper as an enemy. +*/ +bot_target_vehicle_loop() +{ + chopper = level.chopper; + + if(isDefined(chopper) && level.teamBased && getDvarInt("doubleHeli")) + { + chopper = level.chopper[ level.otherTeam[self.team] ]; + } + + if (!isdefined(chopper)) + return; + + if(!isDefined(level.bot_chopper) || !level.bot_chopper)//must be crashing or leaving + return; + + if(isDefined(chopper.owner) && chopper.owner == self) + return; + + if(chopper.team == self.team && level.teamBased) + return; + + if(!bulletTracePassed( self getEyePos(), chopper.origin + (0, 0, -5), false, chopper )) + return; + + self SetScriptEnemy( chopper, (0, 0, -5) ); + self bot_attack_vehicle(chopper); + self ClearScriptEnemy(); + self notify("bot_force_check_switch"); +} + +/* + Bot logic for detecting the chopper as an enemy. +*/ +bot_target_vehicle() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for(;;) + { + wait( RandomIntRange( 2, 4 ) ); + + if(self.pers["bots"]["skill"]["base"] <= 1) + continue; + + if(self HasScriptEnemy()) + continue; + + if(!self getAmmoCount("rpg_mp") && self BotGetRandom() < 90) + continue; + + self bot_target_vehicle_loop(); + } +} + +/* + Bot logic for how long to keep targeting chopper. +*/ +bot_attack_vehicle(chopper) +{ + chopper endon( "death" ); + chopper endon( "crashing" ); + chopper endon( "leaving" ); + + wait_time = RandomIntRange( 7, 10 ); + + for ( i = 0; i < wait_time; i++ ) + { + self notify("bot_force_check_switch"); + wait( 1 ); + + if ( !IsDefined( chopper ) ) + { + return; + } + } +} + +/* + Bot logic for targeting equipment. +*/ +bot_equipment_kill_think_loop() +{ + grenades = GetEntArray( "grenade", "classname" ); + myEye = self getEyePos(); + myAngles = self getPlayerAngles(); + target = undefined; + hasDetectExp = self hasPerk("specialty_detectexplosive"); + + for ( i = grenades.size - 1; i >= 0; 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(!hasDetectExp && !bulletTracePassed(myEye, item.origin+(0, 0, 0), false, item)) + continue; + + if(getConeDot(item.origin, self.origin, myAngles) < 0.6) + continue; + + if ( DistanceSquared( item.origin, self.origin ) < 512 * 512 ) + { + target = item; + break; + } + } + + if(isDefined(target)) + { + self SetScriptEnemy( target, (0, 0, 0) ); + self bot_equipment_attack(target); + self ClearScriptEnemy(); + } +} + +/* + Bot logic for targeting equipment. +*/ +bot_equipment_kill_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for(;;) + { + wait( RandomIntRange( 1, 3 ) ); + + if(self HasScriptEnemy()) + continue; + + if(self.pers["bots"]["skill"]["base"] <= 1) + continue; + + self bot_equipment_kill_think_loop(); + } +} + +/* + How long to keep targeting the equipment. +*/ +bot_equipment_attack(equ) +{ + equ endon("death"); + + wait_time = RandomIntRange( 7, 10 ); + + for ( i = 0; i < wait_time; i++ ) + { + wait( 1 ); + + if ( !IsDefined( equ ) ) + { + return; + } + } +} + +/* + 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 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(); +} + +/* + 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 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(); +} + +/* + 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.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 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 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.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 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 ClearScriptGoal(); + self.bot_lock_goal = false; + } + else//we own it + { + if(gameobj.objPoints[myteam].isFlashing)//underattack + { + 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; + 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\_globallogic::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.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; + 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 = maps\mp\gametypes\_globallogic::getTimePassed()/1000; + + if(timepassed < 120 && timeleft >= 90 && randomInt(100) < 98) + return; + + 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 SetScriptGoal( self.origin, 64 ); + + self bot_use_bomb_thread(site); + wait 1; + + self ClearScriptGoal(); + self.bot_lock_goal = false; + } + 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_gpsjammer" )) + 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.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; + 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.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 SetScriptGoal( self.origin, 64 ); + + self bot_use_bomb_thread(site); + wait 1; + self ClearScriptGoal(); + + self.bot_lock_goal = false; + } + else // we need to go get the bomb! + { + origin = ( bomb.curorigin[0], bomb.curorigin[1], bomb.curorigin[2]+5 ); + + 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; + 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.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\_globallogic::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.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; + 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.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 SetScriptGoal( self.origin, 64 ); + + self bot_use_bomb_thread(defuse); + wait 1; + self ClearScriptGoal(); + self.bot_lock_goal = false; +} + +/* + 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.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.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.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; + 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\_globallogic::getTimeRemaining()/1000; + timepassed = maps\mp\gametypes\_globallogic::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.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; + 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.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 SetScriptGoal( self.origin, 64 ); + + self bot_use_bomb_thread(plant); + wait 1; + + self ClearScriptGoal(); + self.bot_lock_goal = false; +} + +/* + 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); + } +} diff --git a/mods/bots/maps/mp/bots/_bot_utility.gsc b/mods/bots/maps/mp/bots/_bot_utility.gsc new file mode 100644 index 0000000..33bc6ca --- /dev/null +++ b/mods/bots/maps/mp/bots/_bot_utility.gsc @@ -0,0 +1,1700 @@ +#include maps\mp\_utility; + +/* + 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 (getCvar("bots_main_firstIsHost") != "0") + { + print("WARNING: bots_main_firstIsHost is enabled"); + + if (getCvar("bots_main_firstIsHost") == "1") + { + setCvar("bots_main_firstIsHost", self getguid()); + } + + if (getCvar("bots_main_firstIsHost") == self getguid()+"") + result = true; + } + + DvarGUID = getCvar("bots_main_GUIDs"); + if (DvarGUID != "") + { + guids = strtok(DvarGUID, ","); + + for (i = 0; i < guids.size; i++) + { + if(self getguid()+"" == guids[i]) + result = true; + } + } + + if (!result) + return; + + self.pers["bot_host"] = true; +} + +/* + Returns if the player is a bot. +*/ +is_bot() +{ + return ((isDefined(self.pers["isBot"]) && self.pers["isBot"]) || (isDefined(self.pers["isBotWarfare"]) && self.pers["isBotWarfare"]) || isSubStr( self getguid()+"", "bot" )); +} + +/* + Bot changes to the weap +*/ +BotChangeToWeapon(weap) +{ + //self maps\mp\bots\_bot_internal::changeToWeap(weap); +} + +/* + Bot presses the button for time. +*/ +BotPressAttack(time) +{ + //self maps\mp\bots\_bot_internal::pressFire(time); +} + +/* + Bot presses the ads button for 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); +} + +/* + 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); +} + +/* + Returns the bot's random assigned number. +*/ +BotGetRandom() +{ + return self.bot.rand; +} + +/* + Returns a random number thats different everytime it changes target +*/ +BotGetTargetRandom() +{ + if (!isDefined(self.bot.target)) + return undefined; + + return self.bot.target.rand; +} + +/* + Returns if the bot is fragging. +*/ +IsBotFragging() +{ + return self.bot.isfraggingafter; +} + +/* + Returns if the bot is pressing smoke button. +*/ +IsBotSmoking() +{ + return self.bot.issmokingafter; +} + +/* + 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"); +} + +/* + Returns if the bot has a script goal. + (like t5 gsc bot) +*/ +HasScriptGoal() +{ + return (isDefined(self GetScriptGoal())); +} + +/* + Returns the pos of the bot's goal +*/ +GetScriptGoal() +{ + return self.bot.script_goal; +} + +/* + 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"); +} + +/* + Clears the bot's goal. +*/ +ClearScriptGoal() +{ + self SetScriptGoal(undefined, 0); +} + +/* + 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 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); +} + +/* + Returns a random grenade in the bot's inventory. +*/ +getValidGrenade() +{ + grenadeTypes = []; + + 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); +} + +/* + Returns if the given weapon is full auto. +*/ +WeaponIsFullAuto(weap) +{ + weaptoks = strtok(weap, "_"); + + return isDefined(weaptoks[0]) && isString(weaptoks[0]) && isdefined(level.bots_fullautoguns[weaptoks[0]]); +} + +/* + Returns what our eye height is. +*/ +GetEyeHeight() +{ + myEye = self GetEyePos(); + + return myEye[2] - self.origin[2]; +} + +/* + Returns (iw4) eye pos. +*/ +GetEyePos() +{ + return self getEye() + 50; +} + +/* + 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; +} + +/* + Waits until either of the nots. +*/ +waittill_either(not, not1) +{ + self endon(not); + self waittill(not1); +} + +/* + New gsc +*/ +waittill_string( msg, ent ) +{ + if ( msg != "death" ) + self endon( "death" ); + + ent endon( "die" ); + self waittill( msg ); + ent notify( "returned", msg ); +} + +/* + Taken from iw4 script +*/ +waittill_any_timeout( timeOut, string1, string2, string3, string4, string5 ) +{ + if ( ( !isdefined( string1 ) || string1 != "death" ) && + ( !isdefined( string2 ) || string2 != "death" ) && + ( !isdefined( string3 ) || string3 != "death" ) && + ( !isdefined( string4 ) || string4 != "death" ) && + ( !isdefined( string5 ) || string5 != "death" ) ) + self endon( "death" ); + + ent = spawnstruct(); + + if ( isdefined( string1 ) ) + self thread waittill_string( string1, ent ); + + if ( isdefined( string2 ) ) + self thread waittill_string( string2, ent ); + + if ( isdefined( string3 ) ) + self thread waittill_string( string3, ent ); + + if ( isdefined( string4 ) ) + self thread waittill_string( string4, ent ); + + if ( isdefined( string5 ) ) + self thread waittill_string( string5, ent ); + + ent thread _timeout( timeOut ); + + ent waittill( "returned", msg ); + ent notify( "die" ); + return msg; +} + +/* + Used for waittill_any_timeout +*/ +_timeout( delay ) +{ + self endon( "die" ); + + wait( delay ); + self notify( "returned", "timeout" ); +} + +/* + If the weapon is allowed to be dropped +*/ +isWeaponDroppable(weap) +{ + return false; +} + +/* + Selects a random element from the array. +*/ +Random(arr) +{ + size = arr.size; + if(!size) + return undefined; + + return arr[randomInt(size)]; +} + +/* + Removes an item from the array. +*/ +array_remove( ents, remover ) +{ + newents = []; + for(i = 0; i < ents.size; i++) + { + index = ents[i]; + + if ( index != remover ) + newents[ newents.size ] = index; + } + + return newents; +} + +/* + Waits until not or tim. +*/ +waittill_notify_or_timeout(not, tim) +{ + self endon(not); + wait tim; +} + +/* + 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 = getCvarFloat("bots_main_waitForHostTime"); i > 0; i -= 0.05) + { + host = GetHostPlayer(); + + if(isDefined(host)) + break; + + wait 0.05; + } + + if(!isDefined(host)) + return; + + for(i = getCvarFloat("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 = getCvarFloat("bots_main_waitForHostTime"); i > 0; i -= 0.05) + { + if(host.pers[ "team" ] == "allies" || host.pers[ "team" ] == "axis") + break; + + wait 0.05; + } +} + +sqrt(a) +{ + return 0; +} + +/* + 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; +} + +/* + Scales the vector +*/ +vector_scale (vec, scale) +{ + vec = (vec[0] * scale, vec[1] * scale, vec[2] * scale); + return vec; +} + +/* + 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(!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). 1.0 = directly looking at, 0.0 = completely right angle, -1.0, completely 180 +*/ +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; +} + +/* + Rounds up the given value. +*/ +RoundUp( floatVal ) +{ + i = int( floatVal ); + if ( i != floatVal ) + return i + 1; + else + return i; +} + +/* + 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; +} + +/* + Tokenizes a string (strtok has limits...) (only one char tok) +*/ +tokenizeLine(line, tok) +{ + tokens = []; + + token = ""; + for (i = 0; i < line.size; i++) + { + c = line[i]; + + if (c == tok) + { + tokens[tokens.size] = token; + token = ""; + continue; + } + + token += c; + } + tokens[tokens.size] = token; + + return tokens; +} + +/* + Parses tokens into a waypoint obj +*/ +parseTokensIntoWaypoint(tokens) +{ + waypoint = spawnStruct(); + + orgStr = tokens[0]; + orgToks = strtok(orgStr, " "); + waypoint.origin = (float(orgToks[0]), float(orgToks[1]), float(orgToks[2])); + + childStr = tokens[1]; + childToks = strtok(childStr, " "); + waypoint.children = []; + for( j=0; j 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 = 0; i < level.waypointCount; 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 a good amount of players. +*/ +getGoodMapAmount() +{ + switch(getCvar("mapname")) + { + } + + return 2; +} + +/* + Returns the friendly user name for a given map's codename +*/ +getMapName(map) +{ + switch(map) + { + } + + return map; +} + +/* + Does the extra check when adding bots +*/ +doExtraCheck() +{ + //maps\mp\bots\_bot_internal::checkTheBots(); +} + +/* + 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; +} + +/* + 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; + + if (!isDefined(level.waypointUsage[team][wp+""])) + return; + + level.waypointUsage[team][wp+""]--; + + if (level.waypointUsage[team][wp+""] <= 0) + level.waypointUsage[team][wp+""] = 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 = 0; i < level.waypointCount; 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 = 0; i < level.waypointCount; 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(); + openset[bestNode.index+""] = undefined; + wp = level.waypoints[bestNode.index]; + + //check if we made it to the goal + if(bestNode.index == goalWp) + { + path = []; + + while(isDefined(bestNode)) + { + if(isdefined(team) && isDefined(level.waypointUsage)) + { + if (!isDefined(level.waypointUsage[team][bestNode.index+""])) + level.waypointUsage[team][bestNode.index+""] = 0; + + level.waypointUsage[team][bestNode.index+""]++; + } + + //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]; + childWp = level.waypoints[child]; + + penalty = 1; + if(!greedy_path && isdefined(team) && isDefined(level.waypointUsage)) + { + temppen = 1; + if (isDefined(level.waypointUsage[team][child+""])) + temppen = level.waypointUsage[team][child+""];//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[child+""]); + if(inopen && openset[child+""].g <= newg) + continue; + + inclosed = isDefined(closed[child+""]); + if(inclosed && closed[child+""].g <= newg) + continue; + + node = undefined; + if(inopen) + node = openset[child+""]; + else if(inclosed) + node = closed[child+""]; + 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[child+""] = undefined; + + //check if not in open, add it + if(!inopen) + { + open HeapInsert(node); + openset[child+""] = node; + } + } + + //done with children, push onto closed + closed[bestNode.index+""] = bestNode; + } + + return []; +} diff --git a/mods/bots/maps/mp/bots/_menu.gsc b/mods/bots/maps/mp/bots/_menu.gsc new file mode 100644 index 0000000..b92f8f9 --- /dev/null +++ b/mods/bots/maps/mp/bots/_menu.gsc @@ -0,0 +1,1018 @@ +/* + _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(); + } + } +} + +init_menu() +{ + self.menuInit = true; + + self.menuOpen = false; + self.menu_player = undefined; + self.SubMenu = "Main"; + self.Curs["Main"]["X"] = 0; + self AddOptions(); + + self thread watchPlayerOpenMenu(); + self thread MenuSelect(); + self thread RightMenu(); + self thread LeftMenu(); + + self thread watchDisconnect(); + + self thread doGreetings(); +} + +kill_menu() +{ + self notify("bots_kill_menu"); + self.menuInit = undefined; +} + +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] destroy(); + + if(isDefined(self.MenuText)) + for(i = 0; i < self.MenuText.size; i++) + if(isDefined(self.MenuText[i])) + self.MenuText[i] destroy(); + + if(isDefined(self.Menu) && isDefined(self.Menu["X"])) + { + if(isDefined(self.Menu["X"]["Shader"])) + self.Menu["X"]["Shader"] destroy(); + + if(isDefined(self.Menu["X"]["Scroller"])) + self.Menu["X"]["Scroller"] destroy(); + } + + if (isDefined(self.menuVersionHud)) + self.menuVersionHud destroy(); + } +} + +doGreetings() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + wait 1; + self iPrintln("Welcome to Bot Warfare "+self.name+"!"); + wait 5; + self iPrintln("Press [{+frag}] + [{+smoke}] to open menu!"); +} + +watchPlayerOpenMenu() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + for(;;) + { + while(!self FragButtonPressed() || !self SecondaryOffhandButtonPressed()) + wait 0.05; + + 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(level.inPrematchPeriod || level.gameEnded) + self freezeControls(true); + else + self freezecontrols(false); + } + } + + while(self FragButtonPressed() && self SecondaryOffhandButtonPressed()) + wait 0.05; + } +} + +MenuSelect() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + for(;;) + { + while(!self MeleeButtonPressed()) + wait 0.05; + + 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"]]); + } + + while(self MeleeButtonPressed()) + wait 0.05; + } +} + +LeftMenu() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + for(;;) + { + while(!self AttackButtonPressed()) + wait 0.05; + + if(self.MenuOpen) + { + self playLocalSound("mouse_over"); + + if(self.SubMenu == "Main") + { + self.Curs["Main"]["X"]--; + + if(self.Curs["Main"]["X"] < 0) + self.Curs["Main"]["X"] = self.Option["Name"][self.SubMenu].size -1; + + self CursMove("X"); + } + else + { + 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"); + } + } + + while(self AttackButtonPressed()) + wait 0.05; + } +} + +RightMenu() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + + for(;;) + { + while(!self AdsButtonPressed()) + wait 0.05; + + if(self.MenuOpen) + { + self playLocalSound("mouse_over"); + + if(self.SubMenu == "Main") + { + self.Curs["Main"]["X"]++; + + if(self.Curs["Main"]["X"] > self.Option["Name"][self.SubMenu].size -1) + self.Curs["Main"]["X"] = 0; + + self CursMove("X"); + } + else + { + 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"); + } + } + + while(self AdsButtonPressed()) + wait 0.05; + } +} + +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] destroy(); + + if(isDefined(self.Menu) && isDefined(self.Menu["X"])) + { + if(isDefined(self.Menu["X"]["Shader"])) + self.Menu["X"]["Shader"] destroy(); + + if(isDefined(self.Menu["X"]["Scroller"])) + self.Menu["X"]["Scroller"] destroy(); + } + + if (isDefined(self.menuVersionHud)) + self.menuVersionHud destroy(); + + 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] destroy(); + + 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") + { + self.Menu["X"]["Scroller"].x = self.MenuText[self.Curs["Main"]["X"]].x; + self.Menu["X"]["Scroller"].y = self.MenuText[self.Curs["Main"]["X"]].y; + + 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); + } + 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) && !level.inPrematchPeriod && !level.gameEnded) + self freezecontrols(false); + else + self freezecontrols(true); + + self setClientDvar( "r_blur", "5" ); + self setClientDvar( "sc_blur", "4" ); + 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] destroy(); + + 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] destroy(); + + if(isDefined(self.Menu) && isDefined(self.Menu["X"])) + { + if(isDefined(self.Menu["X"]["Shader"])) + self.Menu["X"]["Shader"] destroy(); + + if(isDefined(self.Menu["X"]["Scroller"])) + self.Menu["X"]["Scroller"] destroy(); + } + + if (isDefined(self.menuVersionHud)) + self.menuVersionHud destroy(); + + 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.4; + 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_obj"); + if(_tempDvar) + _temp = "true"; + else + _temp = "false"; + self AddMenu("set1", 6, "Bots play the objective: "+_temp, ::bot_func, "obj", _tempDvar); + + _tempDvar = getDvarInt("bots_play_camp"); + if(_tempDvar) + _temp = "true"; + else + _temp = "false"; + self AddMenu("set1", 7, "Bots can camp: "+_temp, ::bot_func, "camp", _tempDvar); + + _tempDvar = getDvarInt("bots_play_jumpdrop"); + if(_tempDvar) + _temp = "true"; + else + _temp = "false"; + self AddMenu("set1", 8, "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", 9, "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", 10, "Bots can use killstreaks: "+_temp, ::bot_func, "killstreak", _tempDvar); + + _tempDvar = getDvarInt("bots_play_ads"); + if(_tempDvar) + _temp = "true"; + else + _temp = "false"; + self AddMenu("set1", 11, "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 "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": + for (i = 0; i < b; i++) + { + RemoveTestClient(); + + wait 0.25; + } + 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/mods/bots/maps/mp/bots/_wp_editor.gsc b/mods/bots/maps/mp/bots/_wp_editor.gsc new file mode 100644 index 0000000..9253bc8 --- /dev/null +++ b/mods/bots/maps/mp/bots/_wp_editor.gsc @@ -0,0 +1,631 @@ +/* + _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); + + if(getDvar("bots_main_debug_commandWait") == "") + setDvar("bots_main_debug_commandWait", 0.5); + + if(getDvar("bots_main_debug_framerate") == "") + setDvar("bots_main_debug_framerate", 58); + + if(getDvar("bots_main_debug_lineDuration") == "") + setDvar("bots_main_debug_lineDuration", 3); + + if(getDvar("bots_main_debug_printDuration") == "") + setDvar("bots_main_debug_printDuration", 3); + + if(getDvar("bots_main_debug_debugRate") == "") + setDvar("bots_main_debug_debugRate", 0.5); + + 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 beginDebug(); + } +} + +beginDebug() +{ + self endon("disconnect"); + self endon("death"); + + level.wpToLink = -1; + level.autoLink = false; + self.closest = -1; + self.command = undefined; + + self clearPerks(); + self takeAllWeapons(); + self.specialty = []; + self giveWeapon("m16_gl_mp"); + self SetActionSlot( 3, "altMode" ); + self giveWeapon("frag_grenade_mp"); + self freezecontrols(false); + + self thread debug(); + self thread addWaypoints(); + self thread linkWaypoints(); + self thread deleteWaypoints(); + self thread watchSaveWaypointsCommand(); + self thread sayExtras(); + + self thread textScroll("^1SecondaryOffhand - ^2Add Waypoint; ^3MeleeButton - ^4Link Waypoint; ^5FragButton - ^6Delete Waypoint; ^7UseButton + AttackButton - ^8Save"); +} + +sayExtras() +{ + self endon("disconnect"); + self endon("death"); + self iprintln("Making a crouch waypoint with only one link..."); + self iprintln("Makes a camping waypoint."); +} + +debug() +{ + self endon("disconnect"); + self endon("death"); + + self setClientDvar("com_maxfps", getDvarInt("bots_main_debug_framerate")); + + for(;;) + { + wait getDvarFloat("bots_main_debug_debugRate"); + + if(isDefined(self.command)) + continue; + + closest = -1; + myEye = self getTagOrigin( "j_head" ); + 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), 1, 1, getDvarInt("bots_main_debug_lineDuration")); + + if(getConeDot(wpOrg, myEye, myAngles) > getDvarFloat("bots_main_debug_cone")) + print3d(wpOrg, i, (1,0,0), 2, 1, 6); + + if (isDefined(level.waypoints[i].angles) && level.waypoints[i].type != "stand") + line(wpOrg, wpOrg + AnglesToForward(level.waypoints[i].angles) * 64, (1,1,1), 1, 1, getDvarInt("bots_main_debug_lineDuration")); + } + } + + self.closest = closest; + + if(closest != -1) + { + stringChildren = ""; + for(i = 0; i < level.waypoints[closest].children.size; i++) + { + if(i != 0) + stringChildren = stringChildren + "," + level.waypoints[closest].children[i]; + else + stringChildren = stringChildren + level.waypoints[closest].children[i]; + } + print3d(level.waypoints[closest].origin + (0, 0, 35), stringChildren, (0,1,0), 2, 1, getDvarInt("bots_main_debug_printDuration")); + + print3d(level.waypoints[closest].origin + (0, 0, 15), level.waypoints[closest].type, (0,1,0), 2, 1, getDvarInt("bots_main_debug_printDuration")); + } + } +} + +AddWaypoints() +{ + self endon("disconnect"); + self endon("death"); + for(;;) + { + while(!self SecondaryOffhandButtonPressed() || isDefined(self.command)) + wait 0.05; + + pos = self getOrigin(); + self.command = true; + + self iprintln("Adding a waypoint..."); + self iprintln("ADS - climb; Attack + Use - tube"); + self iprintln("Attack - grenade; Use - claymore"); + self iprintln("Else(wait) - your stance"); + + wait getDvarFloat("bots_main_debug_commandWait"); + + self addWaypoint(pos); + + self.command = undefined; + + while(self SecondaryOffhandButtonPressed()) + wait 0.05; + } +} + +linkWaypoints() +{ + self endon("disconnect"); + self endon("death"); + for(;;) + { + while(!self MeleeButtonPressed() || isDefined(self.command)) + wait 0.05; + + self.command = true; + + self iprintln("ADS - Unlink; Else(wait) - Link"); + + wait getDvarFloat("bots_main_debug_commandWait"); + + if(!self adsButtonPressed()) + self LinkWaypoint(self.closest); + else + self UnLinkWaypoint(self.closest); + + self.command = undefined; + + while(self MeleeButtonPressed()) + wait 0.05; + } +} + +deleteWaypoints() +{ + self endon("disconnect"); + self endon("death"); + for(;;) + { + while(!self fragButtonPressed() || isDefined(self.command)) + wait 0.05; + + self.command = true; + + self iprintln("Attack - DeleteAll; ADS - Load"); + self iprintln("Else(wait) - Delete"); + + wait getDvarFloat("bots_main_debug_commandWait"); + + if(self attackButtonPressed()) + self deleteAllWaypoints(); + else if(self adsButtonPressed()) + self LoadWaypoints(); + else + self DeleteWaypoint(self.closest); + + self.command = undefined; + + while(self fragButtonPressed()) + wait 0.05; + } +} + +watchSaveWaypointsCommand() +{ + self endon("death"); + self endon("disconnect"); + + for(;;) + { + while(!self useButtonPressed() || !self attackButtonPressed() || isDefined(self.command)) + wait 0.05; + + self.command = true; + + self iprintln("ADS - Autolink; Else(wait) - Save"); + + wait getDvarFloat("bots_main_debug_commandWait"); + + if(!self adsButtonPressed()) + { + 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/*"); + } + logprint("*/return waypoints;\n}\n\n\n\n"); + + filename = "waypoints/" + getdvar("mapname") + "_wp.csv"; + fd = FS_FOpen(filename, "write"); + + PrintLn("********* Start Bot Warfare WPDump *********"); + PrintLn(level.waypointCount); + + if (fd > 0) + { + if (!FS_WriteLine(fd, level.waypointCount+"")) + { + FS_FClose(fd); + fd = 0; + } + } + 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 += ","; + + str += ","; + + PrintLn(str); + + if (fd > 0) + { + if (!FS_WriteLine(fd, str)) + { + FS_FClose(fd); + fd = 0; + } + } + } + PrintLn("\n\n\n\n\n\n"); + + self iprintln("Saved!!! to " + filename); + + if (fd > 0) + FS_FClose(fd); + } + else + { + 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.closest; + } + } + + self.command = undefined; + + while(self useButtonPressed() && self attackButtonPressed()) + wait 0.05; + } +} + +LoadWaypoints() +{ + self DeleteAllWaypoints(); + self iPrintlnBold("Loading WPS..."); + load_waypoints(); + + 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(!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"); + } +} + +DeleteAllWaypoints() +{ + level.waypoints = []; + level.waypointCount = 0; + + self iprintln("DelAllWps"); +} + +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(pos) +{ + level.waypoints[level.waypointCount] = spawnstruct(); + + level.waypoints[level.waypointCount].origin = pos; + + 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 = []; + + 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++; + } +} + +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--) + { + if(level.waypoints[level.wpToLink].children[i] == nwp) + { + weGood = false; + break; + } + } + if(weGood) + { + for(i = level.waypoints[nwp].children.size - 1; i >= 0; i--) + { + if(level.waypoints[nwp].children[i] == 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; +} + +destroyOnDeath(hud) +{ + hud endon("death"); + self waittill_either("death","disconnect"); + hud destroy(); +} + +textScroll(string) +{ + self endon("death"); + self endon("disconnect"); + //thanks ActionScript + + back = createBar((0,0,0), 1000, 30); + back setPoint("CENTER", undefined, 0, 220); + self thread destroyOnDeath(back); + + text = createFontString("default", 1.5); + text setText(string); + self thread destroyOnDeath(text); + + for (;;) + { + text setPoint("CENTER", undefined, 1200, 220); + text setPoint("CENTER", undefined, -1200, 220, 20); + wait 20; + } +} diff --git a/mods/bots/maps/mp/bots/waypoints/_custom_map.gsc b/mods/bots/maps/mp/bots/waypoints/_custom_map.gsc new file mode 100644 index 0000000..0a4a28f --- /dev/null +++ b/mods/bots/maps/mp/bots/waypoints/_custom_map.gsc @@ -0,0 +1,3 @@ +main(mapname) +{ +} diff --git a/mods/bots/maps/mp/gametypes/_clientids.gsc b/mods/bots/maps/mp/gametypes/_clientids.gsc index 1ac593b..28f233c 100644 --- a/mods/bots/maps/mp/gametypes/_clientids.gsc +++ b/mods/bots/maps/mp/gametypes/_clientids.gsc @@ -3,6 +3,8 @@ init() level.clientid = 0; level thread onPlayerConnect(); + + level thread maps\mp\bots\_bot::init(); } onPlayerConnect() diff --git a/z_client.bat b/z_client.bat new file mode 100644 index 0000000..e95a54e --- /dev/null +++ b/z_client.bat @@ -0,0 +1 @@ +start "" "%~dp0CoD2MP_s.exe" +set fs_game "mods/bots" +set developer "1" +set developer_script "1" +set com_introPlayed "1" \ No newline at end of file