From 465c917248a7df35dfba1aec394ccd967d8820ed Mon Sep 17 00:00:00 2001 From: INeedBots Date: Fri, 20 Nov 2020 03:28:26 -0600 Subject: [PATCH] Working --- main_shared/maps/mp/bots/_bot.gsc | 8 +- main_shared/maps/mp/bots/_bot_internal.gsc | 1822 ++++++++++++++++++++ main_shared/maps/mp/bots/_bot_script.gsc | 829 +++++++++ main_shared/maps/mp/bots/_menu.gsc | 515 ++++++ main_shared/maps/mp/bots/_wp_editor.gsc | 755 ++++++++ 5 files changed, 3925 insertions(+), 4 deletions(-) create mode 100644 main_shared/maps/mp/bots/_bot_internal.gsc create mode 100644 main_shared/maps/mp/bots/_bot_script.gsc create mode 100644 main_shared/maps/mp/bots/_menu.gsc create mode 100644 main_shared/maps/mp/bots/_wp_editor.gsc diff --git a/main_shared/maps/mp/bots/_bot.gsc b/main_shared/maps/mp/bots/_bot.gsc index 9858862..7a73c0f 100644 --- a/main_shared/maps/mp/bots/_bot.gsc +++ b/main_shared/maps/mp/bots/_bot.gsc @@ -120,7 +120,7 @@ onPlayerDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, 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 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); @@ -134,7 +134,7 @@ onPlayerKilled(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHi 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 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); @@ -280,7 +280,7 @@ connected() self thread fixPerksAndScriptKick(); //self thread maps\mp\bots\_bot_internal::connected(); - //self thread maps\mp\bots\_bot_script::connected(); + self thread maps\mp\bots\_bot_script::connected(); level.bots[level.bots.size] = self; self thread onDisconnect(); @@ -296,7 +296,7 @@ added() self endon("disconnect"); //self thread maps\mp\bots\_bot_internal::added(); - //self thread maps\mp\bots\_bot_script::added(); + self thread maps\mp\bots\_bot_script::added(); } /* diff --git a/main_shared/maps/mp/bots/_bot_internal.gsc b/main_shared/maps/mp/bots/_bot_internal.gsc new file mode 100644 index 0000000..2152743 --- /dev/null +++ b/main_shared/maps/mp/bots/_bot_internal.gsc @@ -0,0 +1,1822 @@ +#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; + self.pers["bots"]["skill"]["aim_time"] = 0.05; + self.pers["bots"]["skill"]["init_react_time"] = 0; + self.pers["bots"]["skill"]["reaction_time"] = 0; + self.pers["bots"]["skill"]["no_trace_ads_time"] = 2500; + self.pers["bots"]["skill"]["no_trace_look_time"] = 10000; + self.pers["bots"]["skill"]["remember_time"] = 25000; + self.pers["bots"]["skill"]["fov"] = -1; + self.pers["bots"]["skill"]["dist"] = 100000; + self.pers["bots"]["skill"]["spawn_time"] = 0; + self.pers["bots"]["skill"]["help_dist"] = 10000; + self.pers["bots"]["skill"]["semi_time"] = 0.05; + self.pers["bots"]["skill"]["shoot_after_time"] = 1; + self.pers["bots"]["skill"]["aim_offset_time"] = 1; + self.pers["bots"]["skill"]["aim_offset_amount"] = 1; + self.pers["bots"]["skill"]["bone_update_interval"] = 0.05; + self.pers["bots"]["skill"]["bones"] = "j_head"; + + self.pers["bots"]["behavior"] = []; + self.pers["bots"]["behavior"]["strafe"] = 50; + self.pers["bots"]["behavior"]["nade"] = 50; + self.pers["bots"]["behavior"]["sprint"] = 50; + self.pers["bots"]["behavior"]["camp"] = 50; + self.pers["bots"]["behavior"]["follow"] = 50; + self.pers["bots"]["behavior"]["crouch"] = 10; + self.pers["bots"]["behavior"]["switch"] = 1; + self.pers["bots"]["behavior"]["class"] = 1; + self.pers["bots"]["behavior"]["jump"] = 100; +} + +/* + 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.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.is_cur_full_auto = 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 notify("bot_spawned"); +} + +/* + The hold breath thread. +*/ +watchHoldBreath() +{ + self endon("disconnect"); + self endon("death"); + + for(;;) + { + wait 1; + + if(self.bot.isfrozen) + continue; + + self holdbreath((self playerADS() && weaponClass(self getCurrentWEapon()) == "rifle")); + } +} + +/* + When the bot enters laststand, we fix the weapons +*/ +onLastStand() +{ + self endon("disconnect"); + self endon("death"); + + while (true) + { + while (!self inLastStand()) + wait 0.05; + + self notify("kill_goal"); + 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 changes weapon. +*/ +onWeaponChange() +{ + self endon("disconnect"); + self endon("death"); + + weap = self GetCurrentWeapon(); + self.bot.is_cur_full_auto = WeaponIsFullAuto(weap); + if (weap != "none") + self changeToWeap(weap); + + for(;;) + { + self waittill( "weapon_change", newWeapon ); + + self.bot.is_cur_full_auto = WeaponIsFullAuto(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() +{ + self endon("disconnect"); + self endon("death"); + + for(;;) + { + self waittill("reload_start"); + self.bot.isreloading = true; + self waittill_notify_or_timeout("reload", 7.5); + self.bot.isreloading = false; + } +} + +/* + Bots will update its needed stance according to the nodes on the level. Will also allow the bot to sprint when it can. +*/ +stance() +{ + self endon("disconnect"); + self endon("death"); + + for(;;) + { + self waittill_either("finished_static_waypoints", "new_static_waypoint"); + + if(self.bot.isfrozen) + continue; + + toStance = "stand"; + if(self.bot.next_wp != -1) + toStance = level.waypoints[self.bot.next_wp].type; + + if(toStance == "climb") + toStance = "stand"; + + if(toStance != "stand" && toStance != "crouch" && toStance != "prone") + toStance = "crouch"; + + if(toStance == "stand" && randomInt(100) <= self.pers["bots"]["behavior"]["crouch"]) + toStance = "crouch"; + + if(toStance == "stand") + self stand(); + else if(toStance == "crouch") + self crouch(); + else + self prone(); + + curweap = self getCurrentWeapon(); + + if(toStance != "stand" || self.bot.isreloading || self.bot.issprinting || self.bot.isfraggingafter || self.bot.issmokingafter) + continue; + + if(randomInt(100) > self.pers["bots"]["behavior"]["sprint"]) + continue; + + if(isDefined(self.bot.target) && self canFire(curweap) && self isInRange(self.bot.target.dist, curweap)) + continue; + + if(self.bot.sprintendtime != -1 && getTime() - self.bot.sprintendtime < 2000) + continue; + + 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) + continue; + + self thread sprint(); + } +} + +/* + 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(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; + + if (self.disabledWeapon) + 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(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) +{ + obj.no_trace_time = 0; + obj.trace_time += 50; + 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() +{ + self endon("disconnect"); + self endon("death"); + + for(;;) + { + wait 0.05; + + if(self maps\mp\_flashgrenades::isFlashbanged()) + continue; + + myEye = self GetEyePos(); + theTime = getTime(); + myAngles = self GetPlayerAngles(); + distsq = self.pers["bots"]["skill"]["dist"]; + distsq *= distsq; + myFov = self.pers["bots"]["skill"]["fov"]; + bestTargets = []; + bestTime = 9999999999; + rememberTime = self.pers["bots"]["skill"]["remember_time"]; + initReactTime = self.pers["bots"]["skill"]["init_react_time"]; + hasTarget = isDefined(self.bot.target); + + 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); + } + 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(!isDefined(player.bot_model_fix)) + 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) || daDist > distsq) + { + 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); + } + 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()+""])) + continue; + + closest = 9999999999; + 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"); + } + } +} + +/* + 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; + + curweap = self getCurrentWEapon(); + if(!self canFire(curweap)) + continue; + + if(!self isInRange(self.bot.target.dist, curweap)) + continue; + + if(randomInt(100) > self.pers["bots"]["behavior"]["jump"]) + continue; + + thetime = getTime(); + if(isDefined(self.bot.jump_time) && thetime - 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 = thetime; + 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 = thetime; + 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() +{ + self endon("disconnect"); + self endon("death"); + + for(;;) + { + wait 0.05; + + if(level.inPrematchPeriod || level.gameEnded || self.bot.isfrozen || self maps\mp\_flashgrenades::isFlashbanged())//because cod4x aim is hacky setPlayerAngles, we gotta check if inPrematchPeriod etc + continue; + + aimspeed = self.pers["bots"]["skill"]["aim_time"]; + + if(self IsStunned() || self isArtShocked()) + aimspeed = 1; + + eyePos = self getEyePos(); + curweap = self getCurrentWeapon(); + angles = self GetPlayerAngles(); + + 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(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) + { + 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)) + self thread pressADS(); + } + + self botLookAt(last_pos + (0, 0, self getEyeHeight() + nadeAimOffset), aimspeed); + continue; + } + + if (trace_time) + { + if(isplay) + { + aimpos = target getTagOrigin( bone ); + 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) + { + self clear_bot_after_target(); + self thread knife(); + continue; + } + + if(!self canFire(curweap) || !self isInRange(dist, curweap)) + continue; + + //c4 logic here, but doesnt work anyway + + canADS = self canAds(dist, curweap); + if (canADS) + self thread pressADS(); + + if (trace_time > reaction_time) + { + if((!canADS || self playerads() == 1.0 || self InLastStand() || self GetStance() == "prone") && (conedot > 0.95 || dist < level.bots_maxKnifeDistance)) + self botFire(); + + if (isplay) + self thread start_bot_after_target(target); + } + + continue; + } + } + } + + 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(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)) + continue; + + canADS = self canAds(dist, curweap); + if (canADS) + self thread pressADS(); + + if((!canADS || self playerads() == 1.0 || self InLastStand() || self GetStance() == "prone") && (conedot > 0.95 || dist < level.bots_maxKnifeDistance)) + self botFire(); + + continue; + } + + 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) + 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); + } + } +} + +/* + Bots will fire their gun. +*/ +botFire() +{ + 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) +{ + 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) +{ + weapclass = weaponClass(curweap); + + if(weapclass == "spread" && dist > level.bots_maxShotgunDistance) + return false; + + return true; +} + +/* + Will kill the walk threads and do it again after a time +*/ +killWalkCauseNoWaypoints() +{ + self endon("disconnect"); + self endon("death"); + self endon("kill_goal"); + + wait 2; + + self notify("kill_goal"); +} + +/* + This is the main walking logic for the bot. +*/ +walk() +{ + self endon("disconnect"); + self endon("death"); + + for(;;) + { + wait 0.05; + + self botMoveTo(self.origin); + + if(level.inPrematchPeriod || level.gameEnded || self.bot.isfrozen || self.bot.stop_move) + continue; + + if(self maps\mp\_flashgrenades::isFlashbanged()) + { + self botMoveTo(self.origin + self GetVelocity()*500); + continue; + } + + hasTarget = isDefined(self.bot.target) && isDefined(self.bot.target.entity); + if(hasTarget) + { + curweap = self getCurrentWeapon(); + + if(self.bot.target.entity.classname == "script_vehicle" || self.bot.isfraggingafter || self.bot.issmokingafter) + { + continue; + } + + if(self.bot.target.isplay && self.bot.target.trace_time && self canFire(curweap) && self isInRange(self.bot.target.dist, curweap)) + { + if (self InLastStand() || self GetStance() == "prone") + continue; + + if(self.bot.target.rand <= self.pers["bots"]["behavior"]["strafe"]) + self strafe(self.bot.target.entity); + continue; + } + } + + 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; + } +} + +/* + 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 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--) + level.waypoints[self.bot.astar[i]].bots[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) + level.waypoints[self.bot.astar[remove]].bots[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"); + + ret = self waittill_any_return("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"); + + ret = self waittill_any_return("kill_goal", "goal_internal", "bad_path_internal"); + + if (ret == "goal_internal") + self notify("goal"); + else if (ret == "bad_path_internal") + 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 + + distsq = dist*dist; + if (isScriptGoal) + self thread doWalkScriptNotify(); + + self thread killWalkOnEvents(); + self thread watchOnGoal(goal, distsq); + + current = self initAStar(goal); + // if a waypoint is closer than the goal + //if (current >= 0 && DistanceSquared(self.origin, level.waypoints[self.bot.astar[current]].origin) < DistanceSquared(self.origin, goal)) + //{ + while(current >= 0) + { + // skip down the line of waypoints and go to the waypoint we have a direct path too + /*for (;;) + { + if (current <= 0) + break; + + ppt = PlayerPhysicsTrace(self.origin + (0,0,32), level.waypoints[self.bot.astar[current-1]].origin, false, self); + if (DistanceSquared(level.waypoints[self.bot.astar[current-1]].origin, ppt) > 1.0) + break; + + if (level.waypoints[self.bot.astar[current-1]].type == "climb" || level.waypoints[self.bot.astar[current]].type == "climb") + break; + + current = self removeAStar(); + }*/ + + 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); + + current = self removeAStar(); + } + //} + + self.bot.next_wp = -1; + self.bot.second_next_wp = -1; + self notify("finished_static_waypoints"); + + if(DistanceSquared(self.origin, goal) > distsq) + { + self movetowards(goal); // any better way?? + } + + self notify("finished_goal"); + + wait 1; + if(DistanceSquared(self.origin, goal) > distsq) + 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)) + self.bot.towards_goal = goal; + + lastOri = self.origin; + stucks = 0; + timeslow = 0; + time = 0; + while(distanceSquared(self.origin, self.bot.towards_goal) > level.bots_goalDistance) + { + self botMoveTo(self.bot.towards_goal); + + if(time > 2.5) + { + time = 0; + if(distanceSquared(self.origin, lastOri) < 128) + { + stucks++; + + randomDir = self getRandomLargestStafe(stucks); + + self botMoveTo(randomDir); + wait stucks; + } + + lastOri = self.origin; + } + else if(timeslow > 1.5) + { + self thread jump(); + } + else if(timeslow > 0.75) + { + self crouch(); + } + + wait 0.05; + time += 0.05; + if(lengthsquared(self getVelocity()) < 1000) + timeslow += 0.05; + else + timeslow = 0; + + if(stucks == 2) + self notify("bad_path_internal"); + } + + self.bot.towards_goal = undefined; + self notify("completed_move_to"); +} + +/* + 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 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/main_shared/maps/mp/bots/_bot_script.gsc b/main_shared/maps/mp/bots/_bot_script.gsc new file mode 100644 index 0000000..866ecaf --- /dev/null +++ b/main_shared/maps/mp/bots/_bot_script.gsc @@ -0,0 +1,829 @@ +#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 set_diff(); + + //self set_class(rankxp); +} + +/* + When the bot connects to the game. +*/ +connected() +{ + self endon("disconnect"); + + self.killerLocation = undefined; + + self thread difficulty(); + self thread teamWatch(); + self thread classWatch(); + //self thread onBotSpawned(); + //self thread onSpawned(); +} + +/* + The callback for when the bot gets killed. +*/ +onKilled(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration) +{ + self.killerLocation = 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; +} + +/* + 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, "silenced_") && !isSubStr(sWeapon, "flash_")) + self bot_cry_for_help( eAttacker ); + + self SetAttacker( eAttacker ); +} + +/* + When the bot gets attacked, have the bot ask for help from teammates. +*/ +bot_cry_for_help( attacker ) +{ + if ( !level.teamBased ) + { + return; + } + + theTime = GetTime(); + if ( IsDefined( self.help_time ) && theTime - self.help_time < 1000 ) + { + return; + } + + self.help_time = theTime; + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[i]; + + if ( !player is_bot() ) + { + continue; + } + + if(!isDefined(player.team)) + continue; + + if ( !IsAlive( player ) ) + { + continue; + } + + if ( player == self ) + { + continue; + } + + if ( player.team != self.team ) + { + continue; + } + + dist = player.pers["bots"]["skill"]["help_dist"]; + dist *= dist; + if ( DistanceSquared( self.origin, player.origin ) > dist ) + { + continue; + } + + if ( RandomInt( 100 ) < 50 ) + { + self SetAttacker( attacker ); + + if ( RandomInt( 100 ) > 70 ) + { + break; + } + } + } +} + +/* + Selects a class for the bot. +*/ +classWatch() +{ + self endon("disconnect"); + + for(;;) + { + while(!isdefined(self.pers["team"]) || level.oldschool) + wait .05; + + wait 0.5; + 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); + } + + self notify("menuresponse", game["menu_changeclass"], class); + 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(;;) + { + wait 1; + + rankVar = GetDvarInt("bots_skill"); + + if(rankVar == 9) + continue; + + 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"] = 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"]["behavior"]["strafe"] = 0; + self.pers["bots"]["behavior"]["nade"] = 10; + self.pers["bots"]["behavior"]["sprint"] = 10; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 70; + 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"] = 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"]["behavior"]["strafe"] = 10; + self.pers["bots"]["behavior"]["nade"] = 15; + self.pers["bots"]["behavior"]["sprint"] = 15; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 60; + 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"] = 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"]["behavior"]["strafe"] = 20; + self.pers["bots"]["behavior"]["nade"] = 20; + self.pers["bots"]["behavior"]["sprint"] = 20; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 50; + 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"] = 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"]["behavior"]["strafe"] = 30; + self.pers["bots"]["behavior"]["nade"] = 25; + self.pers["bots"]["behavior"]["sprint"] = 30; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 40; + 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"] = 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"]["behavior"]["strafe"] = 40; + self.pers["bots"]["behavior"]["nade"] = 35; + self.pers["bots"]["behavior"]["sprint"] = 40; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 30; + 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"] = 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"]["behavior"]["strafe"] = 50; + self.pers["bots"]["behavior"]["nade"] = 45; + self.pers["bots"]["behavior"]["sprint"] = 50; + 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"] = 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"] = 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"]["behavior"]["strafe"] = 65; + self.pers["bots"]["behavior"]["nade"] = 65; + self.pers["bots"]["behavior"]["sprint"] = 65; + 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; + } + } +} + +/* + 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); + self.pers["bots"]["skill"]["dist"] = randomIntRange(500, 25000); + 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; + + 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 = get_random_weapon(primaryGroups, rank); + 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_weapon_c4": + 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 an exp amount for the bot that is nearish the host's xp. +*/ +bot_get_rank() +{ + 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 ) ); + + 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; + } +} + +/* + 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(); + } +} + +start_bot_threads() +{ + self endon("disconnect"); + level endon("game_ended"); + self endon("death"); + + while(level.inPrematchPeriod) + wait 0.05; +} diff --git a/main_shared/maps/mp/bots/_menu.gsc b/main_shared/maps/mp/bots/_menu.gsc new file mode 100644 index 0000000..2ddbba3 --- /dev/null +++ b/main_shared/maps/mp/bots/_menu.gsc @@ -0,0 +1,515 @@ +#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_fun") == "") + setDvar("bots_main_fun", false); + + if (getDvar("bots_main_menu") == "") + setDvar("bots_main_menu", true); + + thread watchPlayers(); +} + +watchPlayers() +{ + for (;;) + { + wait 1; + + for (i = level.players.size - 1; i >= 0; i--) + { + player = level.players[i]; + + if (!getDvarInt("bots_main_menu")) + continue; + + 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(); + } + } +} + +doGreetings() +{ + self endon ( "disconnect" ); + self endon ( "bots_kill_menu" ); + wait 1; + self iPrintln("Welcome to Bot Warfare "+self.name+"!"); + wait 5; + if(getDvarInt("bots_main_menu")) + 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) + { + if(getdvarint("bots_main_menu")) + { + 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) && !getDvarInt("bots_main_fun")) + 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 && getdvarint("bots_main_menu")) + { + 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(); + } + + 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.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(;;) + { + if(!getDvarInt("bots_main_fun") && !self isOnGround() && !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); + } + + 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); + } + + 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(); + } + + self.MenuOpen = false; + self notify("exit"); + + self setClientDvar( "r_blur", "0" ); + self setClientDvar( "sc_blur", "2" ); +} + +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, "test", ::OpenSub, "test", ""); + self AddBack("test", "Main"); + + self AddMenu("test", 0, "test", ::test, "test", "test"); +} + +test(a, b) +{ + self iprintln(a + b); +} diff --git a/main_shared/maps/mp/bots/_wp_editor.gsc b/main_shared/maps/mp/bots/_wp_editor.gsc new file mode 100644 index 0000000..730a076 --- /dev/null +++ b/main_shared/maps/mp/bots/_wp_editor.gsc @@ -0,0 +1,755 @@ +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; + +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); + + 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"); + + for(;;) + { + wait 0.05; + + 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 = 0; h < level.waypoints[i].childCount; h++) + line(wpOrg, level.waypoints[level.waypoints[i].children[h]].origin + (0, 0, 25), (1,0,1)); + + if(getConeDot(wpOrg, myEye, myAngles) > getDvarFloat("bots_main_debug_cone")) + print3d(wpOrg, i, (1,0,0), 2); + + if (isDefined(level.waypoints[i].angles) && level.waypoints[i].type != "stand") + line(wpOrg, wpOrg + AnglesToForward(level.waypoints[i].angles) * 64, (1,1,1)); + } + } + + self.closest = closest; + + if(closest != -1) + { + stringChildren = ""; + for(i = 0; i < level.waypoints[closest].childCount; 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); + + print3d(level.waypoints[closest].origin + (0, 0, 15), level.waypoints[closest].type, (0,1,0), 2); + } + } +} + +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(); + 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/*"); + logprint("*/waypoints["+i+"].childCount = "+level.waypoints[i].childCount+";\n/*"); + for(c = 0; c < level.waypoints[i].childCount; 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].childCount == 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"); + + PrintLn(level.waypointCount); + 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.childCount; h++) + { + str += wp.children[h]; + + if (h < wp.childCount - 1) + str += " "; + } + str += "," + wp.type + ","; + + if (isDefined(wp.angles)) + str += wp.angles[0] + " " + wp.angles[1] + " " + wp.angles[2] + ","; + else + str += ","; + + str += ","; + + PrintLn(str); + } + + self iprintln("Saved!!!"); + } + 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.nearest; + } + } + + self.command = undefined; + + while(self useButtonPressed() && self attackButtonPressed()) + wait 0.05; + } +} + +LoadWaypoints() +{ + self DeleteAllWaypoints(); + self iPrintlnBold("Loading WPS..."); + load_waypoints(); + + 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].childCount <= 0) + self iprintln("WARNING: waypoint "+i+" childCount is "+level.waypoints[i].childCount); + + if(level.waypoints[i].childCount != level.waypoints[i].children.size) + self iprintln("WARNING: waypoint "+i+" childCount is not "+level.waypoints[i].children.size); + + for(h = 0; h < level.waypoints[i].children.size; 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].childCount == 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 = 0; i < level.waypoints[nwp].childCount; i++) + { + child = level.waypoints[nwp].children[i]; + + level.waypoints[child].children = array_remove(level.waypoints[child].children, nwp); + + level.waypoints[child].childCount = level.waypoints[child].children.size; + } + + for(i = 0; i < level.waypointCount; i++) + { + for(h = 0; h < level.waypoints[i].childCount; 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 = []; + level.waypoints[level.waypointCount].childCount = 0; + + 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); + + level.waypoints[nwp].childCount = level.waypoints[nwp].children.size; + level.waypoints[level.wpToLink].childCount = level.waypoints[level.wpToLink].children.size; + + 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 = 0; i < level.waypoints[level.wpToLink].childCount; i++) + { + if(level.waypoints[level.wpToLink].children[i] == nwp) + { + weGood = false; + break; + } + } + if(weGood) + { + for(i = 0; i < level.waypoints[nwp].childCount; 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].childcount] = nwp; + level.waypoints[level.wpToLink].childcount++; + level.waypoints[nwp].children[level.waypoints[nwp].childcount] = level.wpToLink; + level.waypoints[nwp].childcount++; + + self iprintln("Waypoint " + nwp + " Linked to " + level.wpToLink); + level.wpToLink = -1; +} + +destroyOnDeath(hud) +{ + hud endon("death"); + self waittill_either("death","disconnect"); + hud notify("death"); + hud destroy(); + hud = undefined; +} + +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; + } +} + +waittill_either(not, not1) +{ + self endon(not); + self waittill(not1); +} + +array_remove( ents, remover ) +{ + newents = []; + for(i = 0; i < ents.size; i++) + { + index = ents[i]; + + if ( index != remover ) + newents[ newents.size ] = index; + } + + return newents; +} + +getConeDot(to, from, dir) +{ + dirToTarget = VectorNormalize(to-from); + forward = AnglesToForward(dir); + return vectordot(dirToTarget, forward); +} + +getMapName(map) +{ + switch(map) + { + case "mp_convoy": + return "Ambush"; + case "mp_backlot": + return "Backlot"; + case "mp_bloc": + return "Bloc"; + case "mp_bog": + return "Bog"; + case "mp_countdown": + return "Countdown"; + case "mp_crash": + return "Crash"; + case "mp_crash_snow": + return "Winter Crash"; + case "mp_crossfire": + return "Crossfire"; + case "mp_citystreets": + return "District"; + case "mp_farm": + return "Downpour"; + case "mp_overgrown": + return "Overgrown"; + case "mp_pipeline": + return "Pipeline"; + case "mp_shipment": + return "Shipment"; + case "mp_showdown": + return "Showdown"; + case "mp_strike": + return "Strike"; + case "mp_vacant": + return "Vacant"; + case "mp_cargoship": + return "Wetwork"; + case "mp_broadcast": + return "Broadcast"; + case "mp_creek": + return "Creek"; + case "mp_carentan": + return "Chinatown"; + case "mp_killhouse": + return "Killhouse"; + } + + return map; +} + +load_waypoints() +{ + mapname = getDvar("mapname"); + + level.waypointCount = 0; + level.waypoints = []; + + switch(mapname) + { + case "mp_convoy": + level.waypoints = maps\mp\bots\waypoints\ambush::Ambush(); + break; + case "mp_backlot": + level.waypoints = maps\mp\bots\waypoints\backlot::Backlot(); + break; + case "mp_bloc": + level.waypoints = maps\mp\bots\waypoints\bloc::Bloc(); + break; + case "mp_bog": + level.waypoints = maps\mp\bots\waypoints\bog::Bog(); + break; + case "mp_countdown": + level.waypoints = maps\mp\bots\waypoints\countdown::Countdown(); + break; + case "mp_crash": + case "mp_crash_snow": + level.waypoints = maps\mp\bots\waypoints\crash::Crash(); + break; + case "mp_crossfire": + level.waypoints = maps\mp\bots\waypoints\crossfire::Crossfire(); + break; + case "mp_citystreets": + level.waypoints = maps\mp\bots\waypoints\district::District(); + break; + case "mp_farm": + level.waypoints = maps\mp\bots\waypoints\downpour::Downpour(); + break; + case "mp_overgrown": + level.waypoints = maps\mp\bots\waypoints\overgrown::Overgrown(); + break; + case "mp_pipeline": + level.waypoints = maps\mp\bots\waypoints\pipeline::Pipeline(); + break; + case "mp_shipment": + level.waypoints = maps\mp\bots\waypoints\shipment::Shipment(); + break; + case "mp_showdown": + level.waypoints = maps\mp\bots\waypoints\showdown::Showdown(); + break; + case "mp_strike": + level.waypoints = maps\mp\bots\waypoints\strike::Strike(); + break; + case "mp_vacant": + level.waypoints = maps\mp\bots\waypoints\vacant::Vacant(); + break; + case "mp_cargoship": + level.waypoints = maps\mp\bots\waypoints\wetwork::Wetwork(); + break; + + case "mp_broadcast": + level.waypoints = maps\mp\bots\waypoints\broadcast::Broadcast(); + break; + case "mp_creek": + level.waypoints = maps\mp\bots\waypoints\creek::Creek(); + break; + case "mp_carentan": + level.waypoints = maps\mp\bots\waypoints\chinatown::Chinatown(); + break; + case "mp_killhouse": + level.waypoints = maps\mp\bots\waypoints\killhouse::Killhouse(); + break; + + default: + maps\mp\bots\waypoints\_custom_map::main(mapname); + break; + } + + if (level.waypoints.size) + println("Loaded " + level.waypoints.size + " waypoints from script."); + + level.waypointCount = level.waypoints.size; + + for(i = 0; i < level.waypointCount; i++) + { + level.waypoints[i].index = i; + level.waypoints[i].bots = []; + level.waypoints[i].bots["allies"] = 1; + level.waypoints[i].bots["axis"] = 1; + + level.waypoints[i].childCount = level.waypoints[i].children.size; + } +}