iw4_bot_warfare/userraw/maps/mp/bots/_bot_utility.gsc
INeedBots d5789913e7 doc
2020-09-26 22:31:43 -06:00

2455 lines
60 KiB
Plaintext

/*
_bot_utility
Author: INeedGames
Date: 09/26/2020
The shared functions for bots
*/
#include common_scripts\utility;
#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
/*
Returns if player is the host
*/
is_host()
{
return (isDefined(self.pers["bot_host"]) && self.pers["bot_host"]);
}
/*
Setups the host variable on the player
*/
doHostCheck()
{
self.pers["bot_host"] = false;
if (self is_bot())
return;
DvarGUID = getDvar("bots_main_GUIDs");
result = false;
if (DvarGUID != "")
{
guids = strtok(DvarGUID, ",");
for (i = 0; i < guids.size; i++)
{
if(self.guid == guids[i])
result = true;
}
}
if (!self isHost() && !result)
return;
self.pers["bot_host"] = true;
}
/*
Returns if the player is a bot.
*/
is_bot()
{
assert(isDefined(self));
assert(isPlayer(self));
return ((isDefined(self.pers["isBot"]) && self.pers["isBot"]) || (isDefined(self.pers["isBotWarfare"]) && self.pers["isBotWarfare"]) || isSubStr( self.guid, "bot" ));
}
/*
Returns how much the bot is ads'ing all the way.
*/
botAdsAmount()
{
return (1 / (self.bot.ads_highest - self.bot.ads_lowest)) * self.bot.ads_tightness + (1 + (self.bot.ads_highest / (self.bot.ads_lowest - self.bot.ads_highest)));
}
/*
Bot will press the ads button for the time
*/
BotPressADS(time)
{
self maps\mp\bots\_bot_internal::pressAds(time);
}
/*
Bots will press the attack button for a time
*/
BotPressAttack(time)
{
self maps\mp\bots\_bot_internal::pressFire(time);
}
/*
Bot will stop moving
*/
BotStopMoving(what)
{
self.bot.stop_move = what;
if(what)
self notify("kill_goal");
}
/*
Returns a valid grenade launcher weapon
*/
getValidTube()
{
weaps = self getweaponslistall();
for (i = 0; i < weaps.size; i++)
{
weap = weaps[i];
if(!self getAmmoCount(weap))
continue;
if ((isSubStr(weap, "gl_") && !isSubStr(weap, "_gl_")) || weap == "m79_mp")
return weap;
}
return undefined;
}
/*
Returns a random grenade in the bot's inventory.
*/
getValidGrenade()
{
grenadeTypes = [];
grenadeTypes[grenadeTypes.size] = "frag_grenade_mp";
grenadeTypes[grenadeTypes.size] = "smoke_grenade_mp";
grenadeTypes[grenadeTypes.size] = "flash_grenade_mp";
grenadeTypes[grenadeTypes.size] = "concussion_grenade_mp";
grenadeTypes[grenadeTypes.size] = "semtex_mp";
grenadeTypes[grenadeTypes.size] = "throwingknife_mp";
possibles = [];
for(i = 0; i < grenadeTypes.size; i++)
{
if ( !self hasWeapon( grenadeTypes[i] ) )
continue;
if ( !self getAmmoCount( grenadeTypes[i] ) )
continue;
possibles[possibles.size] = grenadeTypes[i];
}
return random(possibles);
}
/*
UNUSED cause buggy
Bots change weapons, does the anims
*/
botChangeWeapon(weapon)// intrestingly, this allows the bots to use pullout and pulldown anims and etc, but bugs out when the bot is frozen while midburst of a firerate limited weapon (m16, only shot one shot, or two shots, even though its a 3 round burst) (never switches until unfrozen)
{
self endon("death");
self endon("disconnect");
if (level.gameEnded || !gameFlag( "prematch_done" ) || self.bot.isfrozen)
return;
if (self.bot.knifing || self.bot.isfraggingafter)
return;
if (self.disabledWeapon)
return;
if (self InLastStand() && !self InFinalStand())
return;
self.bot.switch_to_after_none = weapon;
self.bot.switching = true;
ret = undefined;
if (self GetCurrentWeapon() == "none")
{
self notify("weapon_change");
ret = self waittill_any_timeout(5, "weapon_change");
}
else
{
self _DisableWeapon();
self waittill_any_timeout(5, "weapon_change");
self _EnableWeapon();
ret = self waittill_any_timeout(5, "weapon_change");
}
if (ret == "timeout")
return false;
waittillframeend;
self notify("bot_weapon_change", self GetCurrentWeapon());
return true;
}
/*
Bot will throw the grenade
*/
throwBotGrenade(gname, gtime)
{
return self maps\mp\bots\_bot_internal::botThrowGrenade(gname, gtime);
}
/*
If the bot is climbing
*/
botIsClimbing()
{
return self.bot.climbing;
}
/*
Returns a random number thats different everytime it changes target
*/
BotGetTargetRandom()
{
if (!isDefined(self.bot.target))
return undefined;
return self.bot.target.rand;
}
/*
Returns the bot's random assigned number.
*/
BotGetRandom()
{
return self.bot.rand;
}
/*
Returns if the bot is pressing frag button.
*/
IsBotFragging()
{
return self.bot.isfragging;
}
/*
Returns if the bot is sprinting.
*/
IsBotSprinting()
{
return self.bot.running;
}
/*
Returns if the bot is reloading.
*/
IsBotReloading()
{
return self.bot.isreloading;
}
/*
Freezes the bot's controls.
*/
BotFreezeControls(what)
{
self.bot.isfrozen = what;
if(what)
self notify("kill_goal");
}
/*
Returns if the bot is script frozen.
*/
BotIsFrozen()
{
return self.bot.isfrozen;
}
/*
Sets the bot's target to be this ent.
*/
SetAttacker(att)
{
self.bot.target_this_frame = att;
}
/*
Returns if the bot has a script goal.
(like t5 gsc bot)
*/
HasScriptGoal()
{
return (isDefined(self GetScriptGoal()));
}
/*
Sets the bot's goal, will acheive it when dist away from it.
*/
SetScriptGoal(goal, dist)
{
if (!isDefined(dist))
dist = 16;
self.bot.script_goal = goal;
self.bot.script_goal_dist = dist;
waittillframeend;
self notify("new_goal_internal");
self notify("new_goal");
}
/*
Returns the pos of the bot's goal
*/
GetScriptGoal()
{
return self.bot.script_goal;
}
/*
Clears the bot's goal.
*/
ClearScriptGoal()
{
self SetScriptGoal(undefined, 0);
}
/*
Returns the location of the bot's javelin target
*/
HasBotJavelinLocation()
{
return isDefined(self.bot.jav_loc);
}
/*
Sets the aim position of the bot
*/
SetScriptAimPos(pos)
{
self.bot.script_aimpos = pos;
}
/*
Clears the aim position of the bot
*/
ClearScriptAimPos()
{
self SetScriptAimPos(undefined);
}
/*
Returns the aim position of the bot
*/
GetScriptAimPos()
{
return self.bot.script_aimpos;
}
/*
Returns if the bot has a aim pos
*/
HasScriptAimPos()
{
return isDefined(self GetScriptAimPos());
}
/*
Sets the bot's javelin target location
*/
SetBotJavelinLocation(loc)
{
self.bot.jav_loc = loc;
self notify("new_enemy");
}
/*
Clears the bot's javelin location
*/
ClearBotJavelinLocation()
{
self SetBotJavelinLocation(undefined);
}
/*
Sets the script enemy for a bot.
*/
SetScriptEnemy(enemy, offset)
{
self.bot.script_target = enemy;
self.bot.script_target_offset = offset;
}
/*
Removes the script enemy of the bot.
*/
ClearScriptEnemy()
{
self SetScriptEnemy(undefined, undefined);
}
/*
Returns the entity of the bot's target.
*/
GetThreat()
{
if(!isdefined(self.bot.target))
return undefined;
return self.bot.target.entity;
}
/*
Returns if the bot has a script enemy.
*/
HasScriptEnemy()
{
return (isDefined(self.bot.script_target));
}
/*
Returns if the bot has a threat.
*/
HasThreat()
{
return (isDefined(self GetThreat()));
}
/*
If the bot is doing a knife
*/
IsBotKnifing()
{
return self.bot.knifing;
}
/*
Returns the bot's velocity
*/
getBotVelocity()
{
return self.bot.velocity;
}
/*
If the weapon is not a script weapon (bomb, killstreak, etc, grenades)
*/
isWeaponPrimary(weap)
{
return (maps\mp\gametypes\_weapons::isPrimaryWeapon(weap) || maps\mp\gametypes\_weapons::isAltModeWeapon(weap));
}
/*
If the ent is a vehicle
*/
entIsVehicle(ent)
{
return (ent.classname == "script_vehicle" || ent.model == "vehicle_uav_static_mp" || ent.model == "vehicle_ac130_coop");
}
/*
Returns if the given weapon is full auto.
*/
WeaponIsFullAuto(weap)
{
weaptoks = strtok(weap, "_");
assert(isDefined(weaptoks[0]));
assert(isString(weaptoks[0]));
return !isDefined(level.bots_nonfullautoguns[weaptoks[0]]);
}
/*
If the player is defusing
*/
IsDefusing()
{
return (isDefined(self.isDefusing) && self.isDefusing);
}
/*
If the play is planting
*/
isPlanting()
{
return (isDefined(self.isPlanting) && self.isPlanting);
}
/*
If the player is in laststand
*/
inLastStand()
{
return (isDefined(self.lastStand) && self.lastStand);
}
/*
If the player is in final stand
*/
inFinalStand()
{
return (isDefined(self.inFinalStand) && self.inFinalStand);
}
/*
If the player is the flag carrier
*/
isFlagCarrier()
{
return (isDefined(self.carryFlag) && self.carryFlag);
}
/*
If the weapon is allowed to be dropped
*/
isWeaponDroppable(weap)
{
return (maps\mp\gametypes\_weapons::mayDropWeapon(weap));
}
/*
Returns if we are stunned.
*/
IsStunned()
{
return (isdefined(self.concussionEndTime) && self.concussionEndTime > gettime());
}
/*
Returns if we are beingArtilleryShellshocked
*/
isArtShocked()
{
return (isDefined(self.beingArtilleryShellshocked) && self.beingArtilleryShellshocked);
}
/*
Returns the height the viewpos is above the origin
*/
getEyeHeight()
{
myEye = self getEye();
return myEye[2] - self.origin[2];
}
/*
Does a notify after a delay
*/
notifyAfterDelay(delay, not)
{
wait delay;
self notify(not);
}
/*
Pezbot's line sphere intersection.
*/
RaySphereIntersect(start, end, spherePos, radius)
{
dp = end - start;
a = dp[0] * dp[0] + dp[1] * dp[1] + dp[2] * dp[2];
b = 2 * (dp[0] * (start[0] - spherePos[0]) + dp[1] * (start[1] - spherePos[1]) + dp[2] * (start[2] - spherePos[2]));
c = spherePos[0] * spherePos[0] + spherePos[1] * spherePos[1] + spherePos[2] * spherePos[2];
c += start[0] * start[0] + start[1] * start[1] + start[2] * start[2];
c -= 2.0 * (spherePos[0] * start[0] + spherePos[1] * start[1] + spherePos[2] * start[2]);
c -= radius * radius;
bb4ac = b * b - 4.0 * a * c;
return (bb4ac >= 0);
}
/*
Returns if a smoke grenade would intersect start to end line.
*/
SmokeTrace(start, end, rad)
{
for(i = level.bots_smokeList.count - 1; i >= 0; i--)
{
nade = level.bots_smokeList.data[i];
if(nade.state != "smoking")
continue;
if(!RaySphereIntersect(start, end, nade.origin, rad))
continue;
return false;
}
return true;
}
/*
Returns the cone dot (like fov, or distance from the center of our screen).
*/
getConeDot(to, from, dir)
{
dirToTarget = VectorNormalize(to-from);
forward = AnglesToForward(dir);
return vectordot(dirToTarget, forward);
}
/*
Returns the distance squared in a 2d space
*/
DistanceSquared2D(to, from)
{
to = (to[0], to[1], 0);
from = (from[0], from[1], 0);
return DistanceSquared(to, from);
}
/*
Rounds to the nearest whole number.
*/
Round(x)
{
y = int(x);
if(abs(x) - abs(y) > 0.5)
{
if(x < 0)
return y - 1;
else
return y + 1;
}
else
return y;
}
/*
Rounds up the given value.
*/
RoundUp( floatVal )
{
i = int( floatVal );
if ( i != floatVal )
return i + 1;
else
return i;
}
/*
converts a string into a float
*/
float(num)
{
setdvar("temp_dvar_bot_util", num);
return GetDvarFloat("temp_dvar_bot_util");
}
/*
Tokenizes a string (strtok has limits...) (only one char tok)
*/
tokenizeLine(line, tok)
{
tokens = [];
token = "";
for (i = 0; i < line.size; i++)
{
c = line[i];
if (c == tok)
{
tokens[tokens.size] = token;
token = "";
continue;
}
token += c;
}
tokens[tokens.size] = token;
return tokens;
}
/*
If the string starts with
*/
isStrStart( string1, subStr )
{
return ( getSubStr( string1, 0, subStr.size ) == subStr );
}
/*
Parses tokens into a waypoint obj
*/
parseTokensIntoWaypoint(tokens)
{
waypoint = spawnStruct();
orgStr = tokens[0];
orgToks = strtok(orgStr, " ");
waypoint.origin = (float(orgToks[0]), float(orgToks[1]), float(orgToks[2]));
childStr = tokens[1];
childToks = strtok(childStr, " ");
waypoint.childCount = childToks.size;
waypoint.children = [];
for( j=0; j<childToks.size; j++ )
waypoint.children[j] = int(childToks[j]);
type = tokens[2];
waypoint.type = type;
anglesStr = tokens[3];
if (isDefined(anglesStr) && anglesStr != "")
{
anglesToks = strtok(anglesStr, " ");
waypoint.angles = (float(anglesToks[0]), float(anglesToks[1]), float(anglesToks[2]));
}
javStr = tokens[4];
if (isDefined(javStr) && javStr != "")
{
javToks = strtok(javStr, " ");
waypoint.jav_point = (float(javToks[0]), float(javToks[1]), float(javToks[2]));
}
return waypoint;
}
/*
Loads waypoints from tables (iw4x will insert csv's into the game's filesystem, no need for them to be in a ff)
*/
// https://github.com/leiizko/cod4x_lua_plugin/blob/master/LuaScripts/Rotu-R/waypoints.gsc
wpsFromCSV(mapname)
{
fileName = "waypoints/"+ toLower(mapname) + "_wp.csv";
waypoints = [];
waypointCount = int(tableLookupByRow(fileName, 0, 0));
if (waypointCount <= 0)
return waypoints;
printLn( "Getting waypoints from csv: "+fileName );
for (i = 1; i <= waypointCount; i++)
{
tokens = [];
tokens[tokens.size] = tableLookupByRow(fileName, i, 0);
tokens[tokens.size] = tableLookupByRow(fileName, i, 1);
tokens[tokens.size] = tableLookupByRow(fileName, i, 2);
tokens[tokens.size] = tableLookupByRow(fileName, i, 3);
tokens[tokens.size] = tableLookupByRow(fileName, i, 4);
waypoint = parseTokensIntoWaypoint(tokens);
waypoints[i-1] = waypoint;
}
return waypoints;
}
/*
Loads the waypoints. Populating everything needed for the waypoints.
*/
load_waypoints()
{
level.waypointCount = 0;
level.waypoints = [];
mapname = getDvar("mapname");
wps = wpsFromCSV(mapname);
if (wps.size)
{
level.waypoints = wps;
println("Loaded " + wps.size + " waypoints from csv.");
}
else
{
switch(mapname)
{
case "mp_afghan":
level.waypoints = maps\mp\bots\waypoints\afghan::Afghan();
break;
case "mp_derail":
level.waypoints = maps\mp\bots\waypoints\derail::Derail();
break;
case "mp_estate":
case "mp_estate_trop":
case "mp_estate_tropical":
level.waypoints = maps\mp\bots\waypoints\estate::Estate();
break;
case "mp_favela":
case "mp_fav_tropical":
level.waypoints = maps\mp\bots\waypoints\favela::Favela();
break;
case "mp_highrise":
level.waypoints = maps\mp\bots\waypoints\highrise::Highrise();
break;
case "mp_invasion":
level.waypoints = maps\mp\bots\waypoints\invasion::Invasion();
break;
case "mp_checkpoint":
level.waypoints = maps\mp\bots\waypoints\karachi::Karachi();
break;
case "mp_quarry":
level.waypoints = maps\mp\bots\waypoints\quarry::Quarry();
break;
case "mp_rundown":
level.waypoints = maps\mp\bots\waypoints\rundown::Rundown();
break;
case "mp_rust":
level.waypoints = maps\mp\bots\waypoints\rust::Rust();
break;
case "mp_boneyard":
level.waypoints = maps\mp\bots\waypoints\scrapyard::Scrapyard();
break;
case "mp_nightshift":
level.waypoints = maps\mp\bots\waypoints\skidrow::Skidrow();
break;
case "mp_subbase":
level.waypoints = maps\mp\bots\waypoints\subbase::Subbase();
break;
case "mp_terminal":
level.waypoints = maps\mp\bots\waypoints\terminal::Terminal();
break;
case "mp_underpass":
level.waypoints = maps\mp\bots\waypoints\underpass::Underpass();
break;
case "mp_brecourt":
level.waypoints = maps\mp\bots\waypoints\wasteland::Wasteland();
break;
case "mp_complex":
level.waypoints = maps\mp\bots\waypoints\bailout::Bailout();
break;
case "mp_crash":
case "mp_crash_trop":
case "mp_crash_tropical":
level.waypoints = maps\mp\bots\waypoints\crash::Crash();
break;
case "mp_overgrown":
level.waypoints = maps\mp\bots\waypoints\overgrown::Overgrown();
break;
case "mp_compact":
level.waypoints = maps\mp\bots\waypoints\salvage::Salvage();
break;
case "mp_storm":
case "mp_storm_spring":
level.waypoints = maps\mp\bots\waypoints\storm::Storm();
break;
case "mp_abandon":
level.waypoints = maps\mp\bots\waypoints\carnival::Carnival();
break;
case "mp_fuel2":
level.waypoints = maps\mp\bots\waypoints\fuel::Fuel();
break;
case "mp_strike":
level.waypoints = maps\mp\bots\waypoints\strike::Strike();
break;
case "mp_trailerpark":
level.waypoints = maps\mp\bots\waypoints\trailerPark::TrailerPark();
break;
case "mp_vacant":
level.waypoints = maps\mp\bots\waypoints\vacant::Vacant();
break;
case "mp_nuked":
level.waypoints = maps\mp\bots\waypoints\nuketown::Nuketown();
break;
case "mp_cross_fire":
level.waypoints = maps\mp\bots\waypoints\crossfire::Crossfire();
break;
case "mp_bloc":
case "mp_bloc_sh":
level.waypoints = maps\mp\bots\waypoints\bloc::Bloc();
break;
case "mp_cargoship":
case "mp_cargoship_sh":
level.waypoints = maps\mp\bots\waypoints\wetwork::Wetwork();
break;
case "mp_killhouse":
level.waypoints = maps\mp\bots\waypoints\killhouse::Killhouse();
break;
case "mp_bog_sh":
level.waypoints = maps\mp\bots\waypoints\bog::Bog();
break;
case "mp_firingrange":
level.waypoints = maps\mp\bots\waypoints\firingrange::Firingrange();
break;
case "mp_shipment":
level.waypoints = maps\mp\bots\waypoints\shipment::Shipment();
break;
case "mp_shipment_long":
level.waypoints = maps\mp\bots\waypoints\shipmentlong::ShipmentLong();
break;
case "mp_rust_long":
level.waypoints = maps\mp\bots\waypoints\rustlong::Rustlong();
break;
case "invasion":
level.waypoints = maps\mp\bots\waypoints\burgertown::Burgertown();
break;
case "iw4_credits":
level.waypoints = maps\mp\bots\waypoints\testmap::Testmap();
break;
case "oilrig":
level.waypoints = maps\mp\bots\waypoints\oilrig::Oilrig();
break;
case "co_hunted":
level.waypoints = maps\mp\bots\waypoints\hunted::Hunted();
break;
case "contingency":
level.waypoints = maps\mp\bots\waypoints\contingency::Contingency();
break;
case "gulag":
level.waypoints = maps\mp\bots\waypoints\gulag::Gulag();
break;
case "so_ghillies":
level.waypoints = maps\mp\bots\waypoints\pripyat::Pripyat();
break;
case "airport":
level.waypoints = maps\mp\bots\waypoints\airport::Airport();
break;
case "ending":
level.waypoints = maps\mp\bots\waypoints\museum::Museum();
break;
case "af_chase":
level.waypoints = maps\mp\bots\waypoints\afghanchase::AfghanChase();
break;
case "trainer":
level.waypoints = maps\mp\bots\waypoints\trainer::Trainer();
break;
case "roadkill":
level.waypoints = maps\mp\bots\waypoints\roadkill::Roadkill();
break;
case "dcemp":
level.waypoints = maps\mp\bots\waypoints\dcemp::DCEMP();
break;
case "dcburning":
level.waypoints = maps\mp\bots\waypoints\dcburning::DCBurning();
break;
case "af_caves":
level.waypoints = maps\mp\bots\waypoints\afghancaves::AfghanCaves();
break;
case "arcadia":
level.waypoints = maps\mp\bots\waypoints\arcadia::Arcadia();
break;
case "boneyard":
level.waypoints = maps\mp\bots\waypoints\boneyard::Boneyard();
break;
case "cliffhanger":
level.waypoints = maps\mp\bots\waypoints\cliffhanger::Cliffhanger();
break;
case "downtown":
level.waypoints = maps\mp\bots\waypoints\downtown::Downtown();
break;
case "estate":
level.waypoints = maps\mp\bots\waypoints\estatesp::EstateSP();
break;
case "favela":
level.waypoints = maps\mp\bots\waypoints\favelasp::FavelaSP();
break;
case "favela_escape":
level.waypoints = maps\mp\bots\waypoints\favelaescape::FavelaEscape();
break;
case "so_bridge":
level.waypoints = maps\mp\bots\waypoints\bridge::Bridge();
break;
case "dc_whitehouse":
level.waypoints = maps\mp\bots\waypoints\whitehouse::Whitehouse();
break;
default:
maps\mp\bots\waypoints\_custom_map::main(mapname);
break;
}
if (level.waypoints.size)
println("Loaded " + level.waypoints.size + " waypoints from script.");
}
if (!level.waypoints.size)
{
maps\mp\bots\_bot_http::getRemoteWaypoints(mapname);
}
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;
}
level.waypointsKDTree = WaypointsToKDTree();
level.waypointsCamp = [];
level.waypointsTube = [];
level.waypointsGren = [];
level.waypointsClay = [];
level.waypointsJav = [];
for(i = 0; i < level.waypointCount; i++)
if(level.waypoints[i].type == "crouch" && level.waypoints[i].childCount == 1)
level.waypointsCamp[level.waypointsCamp.size] = level.waypoints[i];
else if(level.waypoints[i].type == "tube")
level.waypointsTube[level.waypointsTube.size] = level.waypoints[i];
else if(level.waypoints[i].type == "grenade")
level.waypointsGren[level.waypointsGren.size] = level.waypoints[i];
else if(level.waypoints[i].type == "claymore")
level.waypointsClay[level.waypointsClay.size] = level.waypoints[i];
else if(level.waypoints[i].type == "javelin")
level.waypointsJav[level.waypointsJav.size] = level.waypoints[i];
}
/*
Returns the friendly user name for a given map's codename
*/
getMapName(mapname)
{
switch(mapname)
{
case "mp_abandon":
return "Carnival";
case "mp_rundown":
return "Rundown";
case "mp_afghan":
return "Afghan";
case "mp_boneyard":
return "Scrapyard";
case "mp_brecourt":
return "Wasteland";
case "mp_cargoship":
return "Wetwork";
case "mp_checkpoint":
return "Karachi";
case "mp_compact":
return "Salvage";
case "mp_complex":
return "Bailout";
case "mp_crash":
return "Crash";
case "mp_cross_fire":
return "Crossfire";
case "mp_derail":
return "Derail";
case "mp_estate":
return "Estate";
case "mp_favela":
return "Favela";
case "mp_fuel2":
return "Fuel";
case "mp_highrise":
return "Highrise";
case "mp_invasion":
return "Invasion";
case "mp_killhouse":
return "Killhouse";
case "mp_nightshift":
return "Skidrow";
case "mp_nuked":
return "Nuketown";
case "oilrig":
return "Oilrig";
case "mp_quarry":
return "Quarry";
case "mp_rust":
return "Rust";
case "mp_storm":
return "Storm";
case "mp_strike":
return "Strike";
case "mp_subbase":
return "Subbase";
case "mp_terminal":
return "Terminal";
case "mp_trailerpark":
return "Trailer Park";
case "mp_overgrown":
return "Overgrown";
case "mp_underpass":
return "Underpass";
case "mp_vacant":
return "Vacant";
case "iw4_credits":
return "IW4 Test Map";
case "airport":
return "Airport";
case "co_hunted":
return "Hunted";
case "invasion":
return "Burgertown";
case "mp_bloc":
return "Bloc";
case "mp_bog_sh":
return "Bog";
case "contingency":
return "Contingency";
case "gulag":
return "Gulag";
case "so_ghillies":
return "Pripyat";
case "ending":
return "Museum";
case "af_chase":
return "Afghan Chase";
case "af_caves":
return "Afghan Caves";
case "arcadia":
return "Arcadia";
case "boneyard":
return "Boneyard";
case "cliffhanger":
return "Cliffhanger";
case "dcburning":
return "DCBurning";
case "dcemp":
return "DCEMP";
case "downtown":
return "Downtown";
case "estate":
return "EstateSP";
case "favela":
return "FavelaSP";
case "favela_escape":
return "Favela Escape";
case "roadkill":
return "Roadkill";
case "trainer":
return "TH3 PIT";
case "so_bridge":
return "Bridge";
case "dc_whitehouse":
return "Whitehouse";
case "mp_shipment_long":
return "ShipmentLong";
case "mp_shipment":
return "Shipment";
case "mp_firingrange":
return "Firing Range";
case "mp_rust_long":
return "RustLong";
case "mp_cargoship_sh":
return "Freighter";
case "mp_storm_spring":
return "Chemical Plant";
case "mp_crash_trop":
case "mp_crash_tropical":
return "Crash Tropical";
case "mp_fav_tropical":
return "Favela Tropical";
case "mp_estate_trop":
case "mp_estate_tropical":
return "Estate Tropical";
case "mp_bloc_sh":
return "Forgotten City";
default:
return mapname;
}
}
/*
Returns a good amount of players.
*/
getGoodMapAmount()
{
switch(getdvar("mapname"))
{
case "mp_rust":
case "iw4_credits":
case "mp_nuked":
case "oilrig":
case "mp_killhouse":
case "invasion":
case "mp_bog_sh":
case "co_hunted":
case "contingency":
case "gulag":
case "so_ghillies":
case "ending":
case "af_chase":
case "af_caves":
case "arcadia":
case "boneyard":
case "cliffhanger":
case "dcburning":
case "dcemp":
case "downtown":
case "estate":
case "favela":
case "favela_escape":
case "roadkill":
case "so_bridge":
case "trainer":
case "dc_whitehouse":
case "mp_shipment":
if(level.teambased)
return 8;
else
return 4;
case "mp_vacant":
case "mp_terminal":
case "mp_nightshift":
case "mp_favela":
case "mp_highrise":
case "mp_boneyard":
case "mp_subbase":
case "mp_firingrange":
case "mp_fav_tropical":
case "mp_shipment_long":
case "mp_rust_long":
if(level.teambased)
return 12;
else
return 8;
case "mp_afghan":
case "mp_crash":
case "mp_brecourt":
case "mp_cross_fire":
case "mp_overgrown":
case "mp_trailerpark":
case "mp_underpass":
case "mp_checkpoint":
case "mp_quarry":
case "mp_rundown":
case "mp_cargoship":
case "mp_estate":
case "mp_bloc":
case "mp_storm":
case "mp_strike":
case "mp_abandon":
case "mp_complex":
case "airport":
case "mp_storm_spring":
case "mp_crash_trop":
case "mp_cargoship_sh":
case "mp_estate_trop":
case "mp_compact":
case "mp_crash_tropical":
case "mp_estate_tropical":
case "mp_bloc_sh":
if(level.teambased)
return 14;
else
return 9;
case "mp_fuel2":
case "mp_invasion":
case "mp_derail":
if(level.teambased)
return 16;
else
return 10;
default:
return 2;
}
}
/*
Returns an array of all the bots in the game.
*/
getBotArray()
{
result = [];
playercount = level.players.size;
for(i = 0; i < playercount; i++)
{
player = level.players[i];
if(!player is_bot())
continue;
result[result.size] = player;
}
return result;
}
/*
We return a balanced KDTree from the waypoints.
*/
WaypointsToKDTree()
{
kdTree = KDTree();
kdTree _WaypointsToKDTree(level.waypoints, 0);
return kdTree;
}
/*
Recurive function. We construct a balanced KD tree by sorting the waypoints using heap sort.
*/
_WaypointsToKDTree(waypoints, dem)
{
if(!waypoints.size)
return;
callbacksort = undefined;
switch(dem)
{
case 0:
callbacksort = ::HeapSortCoordX;
break;
case 1:
callbacksort = ::HeapSortCoordY;
break;
case 2:
callbacksort = ::HeapSortCoordZ;
break;
}
heap = NewHeap(callbacksort);
for(i = 0; i < waypoints.size; i++)
{
heap HeapInsert(waypoints[i]);
}
sorted = [];
while(heap.data.size)
{
sorted[sorted.size] = heap.data[0];
heap HeapRemove();
}
median = int(sorted.size/2);//use divide and conq
left = [];
right = [];
for(i = 0; i < sorted.size; i++)
if(i < median)
right[right.size] = sorted[i];
else if(i > median)
left[left.size] = sorted[i];
self KDTreeInsert(sorted[median]);
_WaypointsToKDTree(left, (dem+1)%3);
_WaypointsToKDTree(right, (dem+1)%3);
}
/*
Returns a new list.
*/
List()
{
list = spawnStruct();
list.count = 0;
list.data = [];
return list;
}
/*
Adds a new thing to the list.
*/
ListAdd(thing)
{
self.data[self.count] = thing;
self.count++;
}
/*
Adds to the start of the list.
*/
ListAddFirst(thing)
{
for (i = self.count - 1; i >= 0; i--)
{
self.data[i + 1] = self.data[i];
}
self.data[0] = thing;
self.count++;
}
/*
Removes the thing from the list.
*/
ListRemove(thing)
{
for ( i = 0; i < self.count; i++ )
{
if ( self.data[i] == thing )
{
while ( i < self.count-1 )
{
self.data[i] = self.data[i+1];
i++;
}
self.data[i] = undefined;
self.count--;
break;
}
}
}
/*
Returns a new KDTree.
*/
KDTree()
{
kdTree = spawnStruct();
kdTree.root = undefined;
kdTree.count = 0;
return kdTree;
}
/*
Called on a KDTree. Will insert the object into the KDTree.
*/
KDTreeInsert(data)//as long as what you insert has a .origin attru, it will work.
{
self.root = self _KDTreeInsert(self.root, data, 0, -9999999999, -9999999999, -9999999999, 9999999999, 9999999999, 9999999999);
}
/*
Recurive function that insert the object into the KDTree.
*/
_KDTreeInsert(node, data, dem, x0, y0, z0, x1, y1, z1)
{
if(!isDefined(node))
{
r = spawnStruct();
r.data = data;
r.left = undefined;
r.right = undefined;
r.x0 = x0;
r.x1 = x1;
r.y0 = y0;
r.y1 = y1;
r.z0 = z0;
r.z1 = z1;
self.count++;
return r;
}
switch(dem)
{
case 0:
if(data.origin[0] < node.data.origin[0])
node.left = self _KDTreeInsert(node.left, data, 1, x0, y0, z0, node.data.origin[0], y1, z1);
else
node.right = self _KDTreeInsert(node.right, data, 1, node.data.origin[0], y0, z0, x1, y1, z1);
break;
case 1:
if(data.origin[1] < node.data.origin[1])
node.left = self _KDTreeInsert(node.left, data, 2, x0, y0, z0, x1, node.data.origin[1], z1);
else
node.right = self _KDTreeInsert(node.right, data, 2, x0, node.data.origin[1], z0, x1, y1, z1);
break;
case 2:
if(data.origin[2] < node.data.origin[2])
node.left = self _KDTreeInsert(node.left, data, 0, x0, y0, z0, x1, y1, node.data.origin[2]);
else
node.right = self _KDTreeInsert(node.right, data, 0, x0, y0, node.data.origin[2], x1, y1, z1);
break;
}
return node;
}
/*
Called on a KDTree, will return the nearest object to the given origin.
*/
KDTreeNearest(origin)
{
if(!isDefined(self.root))
return undefined;
return self _KDTreeNearest(self.root, origin, self.root.data, DistanceSquared(self.root.data.origin, origin), 0);
}
/*
Recurive function that will retrieve the closest object to the query.
*/
_KDTreeNearest(node, point, closest, closestdist, dem)
{
if(!isDefined(node))
{
return closest;
}
thisDis = DistanceSquared(node.data.origin, point);
if(thisDis < closestdist)
{
closestdist = thisDis;
closest = node.data;
}
if(node RectDistanceSquared(point) < closestdist)
{
near = node.left;
far = node.right;
if(point[dem] > node.data.origin[dem])
{
near = node.right;
far = node.left;
}
closest = self _KDTreeNearest(near, point, closest, closestdist, (dem+1)%3);
closest = self _KDTreeNearest(far, point, closest, DistanceSquared(closest.origin, point), (dem+1)%3);
}
return closest;
}
/*
Called on a rectangle, returns the distance from origin to the rectangle.
*/
RectDistanceSquared(origin)
{
dx = 0;
dy = 0;
dz = 0;
if(origin[0] < self.x0)
dx = origin[0] - self.x0;
else if(origin[0] > self.x1)
dx = origin[0] - self.x1;
if(origin[1] < self.y0)
dy = origin[1] - self.y0;
else if(origin[1] > self.y1)
dy = origin[1] - self.y1;
if(origin[2] < self.z0)
dz = origin[2] - self.z0;
else if(origin[2] > self.z1)
dz = origin[2] - self.z1;
return dx*dx + dy*dy + dz*dz;
}
/*
A heap invarient comparitor, used for objects, objects with a higher X coord will be first in the heap.
*/
HeapSortCoordX(item, item2)
{
return item.origin[0] > item2.origin[0];
}
/*
A heap invarient comparitor, used for objects, objects with a higher Y coord will be first in the heap.
*/
HeapSortCoordY(item, item2)
{
return item.origin[1] > item2.origin[1];
}
/*
A heap invarient comparitor, used for objects, objects with a higher Z coord will be first in the heap.
*/
HeapSortCoordZ(item, item2)
{
return item.origin[2] > item2.origin[2];
}
/*
A heap invarient comparitor, used for numbers, numbers with the highest number will be first in the heap.
*/
Heap(item, item2)
{
return item > item2;
}
/*
A heap invarient comparitor, used for numbers, numbers with the lowest number will be first in the heap.
*/
ReverseHeap(item, item2)
{
return item < item2;
}
/*
A heap invarient comparitor, used for traces. Wanting the trace with the largest length first in the heap.
*/
HeapTraceFraction(item, item2)
{
return item["fraction"] > item2["fraction"];
}
/*
Returns a new heap.
*/
NewHeap(compare)
{
heap_node = spawnStruct();
heap_node.data = [];
heap_node.compare = compare;
return heap_node;
}
/*
Inserts the item into the heap. Called on a heap.
*/
HeapInsert(item)
{
insert = self.data.size;
self.data[insert] = item;
current = insert+1;
while(current > 1)
{
last = current;
current = int(current/2);
if(![[self.compare]](item, self.data[current-1]))
break;
self.data[last-1] = self.data[current-1];
self.data[current-1] = item;
}
}
/*
Helper function to determine what is the next child of the bst.
*/
_HeapNextChild(node, hsize)
{
left = node * 2;
right = left + 1;
if(left > hsize)
return -1;
if(right > hsize)
return left;
if([[self.compare]](self.data[left-1], self.data[right-1]))
return left;
else
return right;
}
/*
Removes an item from the heap. Called on a heap.
*/
HeapRemove()
{
remove = self.data.size;
if(!remove)
return remove;
move = self.data[remove-1];
self.data[0] = move;
self.data[remove-1] = undefined;
remove--;
if(!remove)
return remove;
last = 1;
next = self _HeapNextChild(1, remove);
while(next != -1)
{
if([[self.compare]](move, self.data[next-1]))
break;
self.data[last-1] = self.data[next-1];
self.data[next-1] = move;
last = next;
next = self _HeapNextChild(next, remove);
}
return remove;
}
/*
A heap invarient comparitor, used for the astar's nodes, wanting the node with the lowest f to be first in the heap.
*/
ReverseHeapAStar(item, item2)
{
return item.f < item2.f;
}
/*
Will linearly search for the nearest waypoint to pos that has a direct line of sight.
*/
GetNearestWaypointWithSight(pos)
{
candidate = undefined;
dist = 9999999999;
for(i = 0; i < level.waypointCount; i++)
{
if(!bulletTracePassed(pos + (0, 0, 15), level.waypoints[i].origin + (0, 0, 15), false, undefined))
continue;
curdis = DistanceSquared(level.waypoints[i].origin, pos);
if(curdis > dist)
continue;
dist = curdis;
candidate = level.waypoints[i];
}
return candidate;
}
/*
Modified Pezbot astar search.
This makes use of sets for quick look up and a heap for a priority queue instead of simple lists which require to linearly search for elements everytime.
Also makes use of the KD tree to search for the nearest node to the goal. We only use the closest node from the KD tree if it has a direct line of sight, else we will have to linearly search for one that we have a line of sight on.
It is also modified to make paths with bots already on more expensive and will try a less congested path first. Thus spliting up the bots onto more paths instead of just one (the smallest).
*/
AStarSearch(start, goal, team, greedy_path)
{
open = NewHeap(::ReverseHeapAStar);//heap
openset = [];//set for quick lookup
closed = [];//set for quick lookup
startwp = level.waypointsKDTree KDTreeNearest(start);//balanced kdtree, for nns
if(!isDefined(startwp))
return [];
_startwp = undefined;
if(!bulletTracePassed(start + (0, 0, 15), startwp.origin + (0, 0, 15), false, undefined))
_startwp = GetNearestWaypointWithSight(start);
if(isDefined(_startwp))
startwp = _startwp;
startwp = startwp.index;
goalwp = level.waypointsKDTree KDTreeNearest(goal);
if(!isDefined(goalwp))
return [];
_goalwp = undefined;
if(!bulletTracePassed(goal + (0, 0, 15), goalwp.origin + (0, 0, 15), false, undefined))
_goalwp = GetNearestWaypointWithSight(goal);
if(isDefined(_goalwp))
goalwp = _goalwp;
goalwp = goalwp.index;
goalorg = level.waypoints[goalWp].origin;
node = spawnStruct();
node.g = 0; //path dist so far
node.h = DistanceSquared(level.waypoints[startWp].origin, goalorg); //herustic, distance to goal for path finding
//node.f = node.h + node.g; // combine path dist and heru, use reverse heap to sort the priority queue by this attru
node.f = node.h;
node.index = startwp;
node.parent = undefined; //we are start, so we have no parent
//push node onto queue
openset[node.index] = node;
open HeapInsert(node);
//while the queue is not empty
while(open.data.size)
{
//pop bestnode from queue
bestNode = open.data[0];
open HeapRemove();
openset[bestNode.index] = undefined;
//check if we made it to the goal
if(bestNode.index == goalwp)
{
path = [];
while(isDefined(bestNode))
{
if(isdefined(team))
level.waypoints[bestNode.index].bots[team]++;
//construct path
path[path.size] = bestNode.index;
bestNode = bestNode.parent;
}
return path;
}
nodeorg = level.waypoints[bestNode.index].origin;
childcount = level.waypoints[bestNode.index].childCount;
//for each child of bestnode
for(i = 0; i < childcount; i++)
{
child = level.waypoints[bestNode.index].children[i];
childorg = level.waypoints[child].origin;
penalty = 1;
if(!greedy_path && isdefined(team))
{
temppen = level.waypoints[child].bots[team];//consider how many bots are taking this path
if(temppen > 1)
penalty = temppen;
}
//calc the total path we have took
newg = bestNode.g + DistanceSquared(nodeorg, childorg)*penalty;//bots on same team's path are more expensive
//check if this child is in open or close with a g value less than newg
inopen = isDefined(openset[child]);
if(inopen && openset[child].g <= newg)
continue;
inclosed = isDefined(closed[child]);
if(inclosed && closed[child].g <= newg)
continue;
if(inopen)
node = openset[child];
else if(inclosed)
node = closed[child];
else
node = spawnStruct();
node.parent = bestNode;
node.g = newg;
node.h = DistanceSquared(childorg, goalorg);
node.f = node.g + node.h;
node.index = child;
//check if in closed, remove it
if(inclosed)
closed[child] = undefined;
//check if not in open, add it
if(!inopen)
{
open HeapInsert(node);
openset[child] = node;
}
}
//done with children, push onto closed
closed[bestNode.index] = bestNode;
}
return [];
}
/*
Taken from t5 gsc.
Returns an array of number's average.
*/
array_average( array )
{
assert( array.size > 0 );
total = 0;
for ( i = 0; i < array.size; i++ )
{
total += array[i];
}
return ( total / array.size );
}
/*
Taken from t5 gsc.
Returns an array of number's standard deviation.
*/
array_std_deviation( array, mean )
{
assert( array.size > 0 );
tmp = [];
for ( i = 0; i < array.size; i++ )
{
tmp[i] = ( array[i] - mean ) * ( array[i] - mean );
}
total = 0;
for ( i = 0; i < tmp.size; i++ )
{
total = total + tmp[i];
}
return Sqrt( total / array.size );
}
/*
Taken from t5 gsc.
Will produce a random number between lower_bound and upper_bound but with a bell curve distribution (more likely to be close to the mean).
*/
random_normal_distribution( mean, std_deviation, lower_bound, upper_bound )
{
x1 = 0;
x2 = 0;
w = 1;
y1 = 0;
while ( w >= 1 )
{
x1 = 2 * RandomFloatRange( 0, 1 ) - 1;
x2 = 2 * RandomFloatRange( 0, 1 ) - 1;
w = x1 * x1 + x2 * x2;
}
w = Sqrt( ( -2.0 * Log( w ) ) / w );
y1 = x1 * w;
number = mean + y1 * std_deviation;
if ( IsDefined( lower_bound ) && number < lower_bound )
{
number = lower_bound;
}
if ( IsDefined( upper_bound ) && number > upper_bound )
{
number = upper_bound;
}
return( number );
}
/*
Patches the plant sites so it exposes the defuseObject
*/
onUsePlantObjectFix( player )
{
// planted the bomb
if ( !self maps\mp\gametypes\_gameobjects::isFriendlyTeam( player.pers["team"] ) )
{
level thread bombPlantedFix( self, player );
//player logString( "bomb planted: " + self.label );
// disable all bomb zones except this one
for ( index = 0; index < level.bombZones.size; index++ )
{
if ( level.bombZones[index] == self )
continue;
level.bombZones[index] maps\mp\gametypes\_gameobjects::disableObject();
}
player playSound( "mp_bomb_plant" );
player notify ( "bomb_planted" );
//if ( !level.hardcoreMode )
// iPrintLn( &"MP_EXPLOSIVES_PLANTED_BY", player );
leaderDialog( "bomb_planted" );
level thread teamPlayerCardSplash( "callout_bombplanted", player );
level.bombOwner = player;
player thread maps\mp\gametypes\_hud_message::SplashNotify( "plant", maps\mp\gametypes\_rank::getScoreInfoValue( "plant" ) );
player thread maps\mp\gametypes\_rank::giveRankXP( "plant" );
player.bombPlantedTime = getTime();
maps\mp\gametypes\_gamescore::givePlayerScore( "plant", player );
player incPlayerStat( "bombsplanted", 1 );
player thread maps\mp\_matchdata::logGameEvent( "plant", player.origin );
}
}
/*
Patches the plant sites so it exposes the defuseObject
*/
bombPlantedFix( destroyedObj, player )
{
maps\mp\gametypes\_gamelogic::pauseTimer();
level.bombPlanted = true;
destroyedObj.visuals[0] thread maps\mp\gametypes\_gamelogic::playTickingSound();
level.tickingObject = destroyedObj.visuals[0];
level.timeLimitOverride = true;
setGameEndTime( int( gettime() + (level.bombTimer * 1000) ) );
setDvar( "ui_bomb_timer", 1 );
if ( !level.multiBomb )
{
level.sdBomb maps\mp\gametypes\_gameobjects::allowCarry( "none" );
level.sdBomb maps\mp\gametypes\_gameobjects::setVisibleTeam( "none" );
level.sdBomb maps\mp\gametypes\_gameobjects::setDropped();
level.sdBombModel = level.sdBomb.visuals[0];
}
else
{
for ( index = 0; index < level.players.size; index++ )
{
if ( isDefined( level.players[index].carryIcon ) )
level.players[index].carryIcon destroyElem();
}
trace = bulletTrace( player.origin + (0,0,20), player.origin - (0,0,2000), false, player );
tempAngle = randomfloat( 360 );
forward = (cos( tempAngle ), sin( tempAngle ), 0);
forward = vectornormalize( forward - common_scripts\utility::vector_multiply( trace["normal"], vectordot( forward, trace["normal"] ) ) );
dropAngles = vectortoangles( forward );
level.sdBombModel = spawn( "script_model", trace["position"] );
level.sdBombModel.angles = dropAngles;
level.sdBombModel setModel( "prop_suitcase_bomb" );
}
destroyedObj maps\mp\gametypes\_gameobjects::allowUse( "none" );
destroyedObj maps\mp\gametypes\_gameobjects::setVisibleTeam( "none" );
/*
destroyedObj maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", undefined );
destroyedObj maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", undefined );
destroyedObj maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", undefined );
destroyedObj maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", undefined );
*/
label = destroyedObj maps\mp\gametypes\_gameobjects::getLabel();
// create a new object to defuse with.
trigger = destroyedObj.bombDefuseTrig;
trigger.origin = level.sdBombModel.origin;
visuals = [];
defuseObject = maps\mp\gametypes\_gameobjects::createUseObject( game["defenders"], trigger, visuals, (0,0,32) );
defuseObject maps\mp\gametypes\_gameobjects::allowUse( "friendly" );
defuseObject maps\mp\gametypes\_gameobjects::setUseTime( level.defuseTime );
defuseObject maps\mp\gametypes\_gameobjects::setUseText( &"MP_DEFUSING_EXPLOSIVE" );
defuseObject maps\mp\gametypes\_gameobjects::setUseHintText( &"PLATFORM_HOLD_TO_DEFUSE_EXPLOSIVES" );
defuseObject maps\mp\gametypes\_gameobjects::setVisibleTeam( "any" );
defuseObject maps\mp\gametypes\_gameobjects::set2DIcon( "friendly", "waypoint_defuse" + label );
defuseObject maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "waypoint_defend" + label );
defuseObject maps\mp\gametypes\_gameobjects::set3DIcon( "friendly", "waypoint_defuse" + label );
defuseObject maps\mp\gametypes\_gameobjects::set3DIcon( "enemy", "waypoint_defend" + label );
defuseObject.label = label;
defuseObject.onBeginUse = maps\mp\gametypes\sd::onBeginUse;
defuseObject.onEndUse = maps\mp\gametypes\sd::onEndUse;
defuseObject.onUse = maps\mp\gametypes\sd::onUseDefuseObject;
defuseObject.useWeapon = "briefcase_bomb_defuse_mp";
level.defuseObject = defuseObject;
maps\mp\gametypes\sd::BombTimerWait();
setDvar( "ui_bomb_timer", 0 );
destroyedObj.visuals[0] maps\mp\gametypes\_gamelogic::stopTickingSound();
if ( level.gameEnded || level.bombDefused )
return;
level.bombExploded = true;
explosionOrigin = level.sdBombModel.origin;
level.sdBombModel hide();
if ( isdefined( player ) )
{
destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20, player );
player incPlayerStat( "targetsdestroyed", 1 );
}
else
destroyedObj.visuals[0] radiusDamage( explosionOrigin, 512, 200, 20 );
rot = randomfloat(360);
explosionEffect = spawnFx( level._effect["bombexplosion"], explosionOrigin + (0,0,50), (0,0,1), (cos(rot),sin(rot),0) );
triggerFx( explosionEffect );
PlayRumbleOnPosition( "grenade_rumble", explosionOrigin );
earthquake( 0.75, 2.0, explosionOrigin, 2000 );
thread playSoundinSpace( "exp_suitcase_bomb_main", explosionOrigin );
if ( isDefined( destroyedObj.exploderIndex ) )
exploder( destroyedObj.exploderIndex );
for ( index = 0; index < level.bombZones.size; index++ )
level.bombZones[index] maps\mp\gametypes\_gameobjects::disableObject();
defuseObject maps\mp\gametypes\_gameobjects::disableObject();
setGameEndTime( 0 );
wait 3;
maps\mp\gametypes\sd::sd_endGame( game["attackers"], game["strings"]["target_destroyed"] );
}
/*
Patches giveLoadout so that it doesn't use IsItemUnlocked
*/
botGiveLoadout( team, class, allowCopycat )
{
self takeAllWeapons();
primaryIndex = 0;
// initialize specialty array
self.specialty = [];
if ( !isDefined( allowCopycat ) )
allowCopycat = true;
primaryWeapon = undefined;
if ( isDefined( self.pers["copyCatLoadout"] ) && self.pers["copyCatLoadout"]["inUse"] && allowCopycat )
{
self maps\mp\gametypes\_class::setClass( "copycat" );
self.class_num = maps\mp\gametypes\_class::getClassIndex( "copycat" );
clonedLoadout = self.pers["copyCatLoadout"];
loadoutPrimary = clonedLoadout["loadoutPrimary"];
loadoutPrimaryAttachment = clonedLoadout["loadoutPrimaryAttachment"];
loadoutPrimaryAttachment2 = clonedLoadout["loadoutPrimaryAttachment2"] ;
loadoutPrimaryCamo = clonedLoadout["loadoutPrimaryCamo"];
loadoutSecondary = clonedLoadout["loadoutSecondary"];
loadoutSecondaryAttachment = clonedLoadout["loadoutSecondaryAttachment"];
loadoutSecondaryAttachment2 = clonedLoadout["loadoutSecondaryAttachment2"];
loadoutSecondaryCamo = clonedLoadout["loadoutSecondaryCamo"];
loadoutEquipment = clonedLoadout["loadoutEquipment"];
loadoutPerk1 = clonedLoadout["loadoutPerk1"];
loadoutPerk2 = clonedLoadout["loadoutPerk2"];
loadoutPerk3 = clonedLoadout["loadoutPerk3"];
loadoutOffhand = clonedLoadout["loadoutOffhand"];
loadoutDeathStreak = "specialty_copycat";
}
else if ( isSubstr( class, "custom" ) )
{
class_num = maps\mp\gametypes\_class::getClassIndex( class );
self.class_num = class_num;
loadoutPrimary = maps\mp\gametypes\_class::cac_getWeapon( class_num, 0 );
loadoutPrimaryAttachment = maps\mp\gametypes\_class::cac_getWeaponAttachment( class_num, 0 );
loadoutPrimaryAttachment2 = maps\mp\gametypes\_class::cac_getWeaponAttachmentTwo( class_num, 0 );
loadoutPrimaryCamo = maps\mp\gametypes\_class::cac_getWeaponCamo( class_num, 0 );
loadoutSecondaryCamo = maps\mp\gametypes\_class::cac_getWeaponCamo( class_num, 1 );
loadoutSecondary = maps\mp\gametypes\_class::cac_getWeapon( class_num, 1 );
loadoutSecondaryAttachment = maps\mp\gametypes\_class::cac_getWeaponAttachment( class_num, 1 );
loadoutSecondaryAttachment2 = maps\mp\gametypes\_class::cac_getWeaponAttachmentTwo( class_num, 1 );
loadoutSecondaryCamo = maps\mp\gametypes\_class::cac_getWeaponCamo( class_num, 1 );
loadoutEquipment = maps\mp\gametypes\_class::cac_getPerk( class_num, 0 );
loadoutPerk1 = maps\mp\gametypes\_class::cac_getPerk( class_num, 1 );
loadoutPerk2 = maps\mp\gametypes\_class::cac_getPerk( class_num, 2 );
loadoutPerk3 = maps\mp\gametypes\_class::cac_getPerk( class_num, 3 );
loadoutOffhand = maps\mp\gametypes\_class::cac_getOffhand( class_num );
loadoutDeathStreak = maps\mp\gametypes\_class::cac_getDeathstreak( class_num );
}
else
{
class_num = maps\mp\gametypes\_class::getClassIndex( class );
self.class_num = class_num;
loadoutPrimary = maps\mp\gametypes\_class::table_getWeapon( level.classTableName, class_num, 0 );
loadoutPrimaryAttachment = maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 0 , 0);
loadoutPrimaryAttachment2 = maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 0, 1 );
loadoutPrimaryCamo = maps\mp\gametypes\_class::table_getWeaponCamo( level.classTableName, class_num, 0 );
loadoutSecondaryCamo = maps\mp\gametypes\_class::table_getWeaponCamo( level.classTableName, class_num, 1 );
loadoutSecondary = maps\mp\gametypes\_class::table_getWeapon( level.classTableName, class_num, 1 );
loadoutSecondaryAttachment = maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 1 , 0);
loadoutSecondaryAttachment2 = maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 1, 1 );;
loadoutSecondaryCamo = maps\mp\gametypes\_class::table_getWeaponCamo( level.classTableName, class_num, 1 );
loadoutEquipment = maps\mp\gametypes\_class::table_getEquipment( level.classTableName, class_num, 0 );
loadoutPerk1 = maps\mp\gametypes\_class::table_getPerk( level.classTableName, class_num, 1 );
loadoutPerk2 = maps\mp\gametypes\_class::table_getPerk( level.classTableName, class_num, 2 );
loadoutPerk3 = maps\mp\gametypes\_class::table_getPerk( level.classTableName, class_num, 3 );
loadoutOffhand = maps\mp\gametypes\_class::table_getOffhand( level.classTableName, class_num );
loadoutDeathstreak = maps\mp\gametypes\_class::table_getDeathstreak( level.classTableName, class_num );
}
if ( loadoutPerk1 != "specialty_bling" )
{
loadoutPrimaryAttachment2 = "none";
loadoutSecondaryAttachment2 = "none";
}
if ( loadoutPerk1 != "specialty_onemanarmy" && loadoutSecondary == "onemanarmy" )
loadoutSecondary = maps\mp\gametypes\_class::table_getWeapon( level.classTableName, 10, 1 );
//loadoutSecondaryCamo = "none";
// stop default class op'ness
allowOp = (getDvarInt("bots_loadout_allow_op") >= 1);
if (!allowOp)
{
loadoutDeathstreak = "specialty_none";
if (loadoutPrimary == "riotshield")
loadoutPrimary = "m4";
if (loadoutSecondary == "at4")
loadoutSecondary = "usp";
if (loadoutPrimaryAttachment == "gl")
loadoutPrimaryAttachment = "none";
}
if ( level.killstreakRewards )
{
if ( getDvarInt( "scr_classic" ) == 1 )
{
loadoutKillstreak1 = "uav";
loadoutKillstreak2 = "precision_airstrike";
loadoutKillstreak3 = "helicopter";
}
else
{
loadoutKillstreak1 = self getPlayerData( "killstreaks", 0 );
loadoutKillstreak2 = self getPlayerData( "killstreaks", 1 );
loadoutKillstreak3 = self getPlayerData( "killstreaks", 2 );
}
}
else
{
loadoutKillstreak1 = "none";
loadoutKillstreak2 = "none";
loadoutKillstreak3 = "none";
}
secondaryName = maps\mp\gametypes\_class::buildWeaponName( loadoutSecondary, loadoutSecondaryAttachment, loadoutSecondaryAttachment2 );
self _giveWeapon( secondaryName, int(tableLookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 ) ) );
self.loadoutPrimaryCamo = int(tableLookup( "mp/camoTable.csv", 1, loadoutPrimaryCamo, 0 ));
self.loadoutPrimary = loadoutPrimary;
self.loadoutSecondary = loadoutSecondary;
self.loadoutSecondaryCamo = int(tableLookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 ));
self SetOffhandPrimaryClass( "other" );
// Action Slots
//self _SetActionSlot( 1, "" );
self _SetActionSlot( 1, "nightvision" );
self _SetActionSlot( 3, "altMode" );
self _SetActionSlot( 4, "" );
// Perks
self _clearPerks();
self maps\mp\gametypes\_class::_detachAll();
// these special case giving pistol death have to come before
// perk loadout to ensure player perk icons arent overwritten
if ( level.dieHardMode )
self maps\mp\perks\_perks::givePerk( "specialty_pistoldeath" );
// only give the deathstreak for the initial spawn for this life.
if ( loadoutDeathStreak != "specialty_null" && (getTime() - self.spawnTime) < 0.1 )
{
deathVal = int( tableLookup( "mp/perkTable.csv", 1, loadoutDeathStreak, 6 ) );
if ( self botGetPerkUpgrade( loadoutPerk1 ) == "specialty_rollover" || self botGetPerkUpgrade( loadoutPerk2 ) == "specialty_rollover" || self botGetPerkUpgrade( loadoutPerk3 ) == "specialty_rollover" )
deathVal -= 1;
if ( self.pers["cur_death_streak"] == deathVal )
{
self thread maps\mp\perks\_perks::givePerk( loadoutDeathStreak );
self thread maps\mp\gametypes\_hud_message::splashNotify( loadoutDeathStreak );
}
else if ( self.pers["cur_death_streak"] > deathVal )
{
self thread maps\mp\perks\_perks::givePerk( loadoutDeathStreak );
}
}
self botLoadoutAllPerks( loadoutEquipment, loadoutPerk1, loadoutPerk2, loadoutPerk3 );
self maps\mp\gametypes\_class::setKillstreaks( loadoutKillstreak1, loadoutKillstreak2, loadoutKillstreak3 );
if ( self hasPerk( "specialty_extraammo", true ) && getWeaponClass( secondaryName ) != "weapon_projectile" )
self giveMaxAmmo( secondaryName );
// Primary Weapon
primaryName = maps\mp\gametypes\_class::buildWeaponName( loadoutPrimary, loadoutPrimaryAttachment, loadoutPrimaryAttachment2 );
self _giveWeapon( primaryName, self.loadoutPrimaryCamo );
// fix changing from a riotshield class to a riotshield class during grace period not giving a shield
if ( primaryName == "riotshield_mp" && level.inGracePeriod )
self notify ( "weapon_change", "riotshield_mp" );
if ( self hasPerk( "specialty_extraammo", true ) )
self giveMaxAmmo( primaryName );
self setSpawnWeapon( primaryName );
primaryTokens = strtok( primaryName, "_" );
self.pers["primaryWeapon"] = primaryTokens[0];
// Primary Offhand was given by givePerk (it's your perk1)
// Secondary Offhand
offhandSecondaryWeapon = loadoutOffhand + "_mp";
if ( loadoutOffhand == "flash_grenade" )
self SetOffhandSecondaryClass( "flash" );
else
self SetOffhandSecondaryClass( "smoke" );
self giveWeapon( offhandSecondaryWeapon );
if( loadOutOffhand == "smoke_grenade" )
self setWeaponAmmoClip( offhandSecondaryWeapon, 1 );
else if( loadOutOffhand == "flash_grenade" )
self setWeaponAmmoClip( offhandSecondaryWeapon, 2 );
else if( loadOutOffhand == "concussion_grenade" )
self setWeaponAmmoClip( offhandSecondaryWeapon, 2 );
else
self setWeaponAmmoClip( offhandSecondaryWeapon, 1 );
primaryWeapon = primaryName;
self.primaryWeapon = primaryWeapon;
self.secondaryWeapon = secondaryName;
self botPlayerModelForWeapon( self.pers["primaryWeapon"], getBaseWeaponName( secondaryName ) );
self.isSniper = (weaponClass( self.primaryWeapon ) == "sniper");
self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" );
// cac specialties that require loop threads
self maps\mp\perks\_perks::cac_selector();
self notify ( "changed_kit" );
self notify( "bot_giveLoadout", allowCopycat );
}
/*
Patches giveLoadout so that it doesn't use IsItemUnlocked
*/
botGetPerkUpgrade( perkName )
{
perkUpgrade = tablelookup( "mp/perktable.csv", 1, perkName, 8 );
if ( perkUpgrade == "" || perkUpgrade == "specialty_null" )
return "specialty_null";
if ( !isDefined(self.pers["bots"]["unlocks"]["upgraded_"+perkName]) || !self.pers["bots"]["unlocks"]["upgraded_"+perkName] )
return "specialty_null";
return ( perkUpgrade );
}
/*
Patches giveLoadout so that it doesn't use IsItemUnlocked
*/
botLoadoutAllPerks( loadoutEquipment, loadoutPerk1, loadoutPerk2, loadoutPerk3 )
{
loadoutEquipment = maps\mp\perks\_perks::validatePerk( 1, loadoutEquipment );
loadoutPerk1 = maps\mp\perks\_perks::validatePerk( 1, loadoutPerk1 );
loadoutPerk2 = maps\mp\perks\_perks::validatePerk( 2, loadoutPerk2 );
loadoutPerk3 = maps\mp\perks\_perks::validatePerk( 3, loadoutPerk3 );
self maps\mp\perks\_perks::givePerk( loadoutEquipment );
self maps\mp\perks\_perks::givePerk( loadoutPerk1 );
self maps\mp\perks\_perks::givePerk( loadoutPerk2 );
self maps\mp\perks\_perks::givePerk( loadoutPerk3 );
perks[0] = loadoutPerk1;
perks[1] = loadoutPerk2;
perks[2] = loadoutPerk3;
perkUpgrd[0] = tablelookup( "mp/perktable.csv", 1, loadoutPerk1, 8 );
perkUpgrd[1] = tablelookup( "mp/perktable.csv", 1, loadoutPerk2, 8 );
perkUpgrd[2] = tablelookup( "mp/perktable.csv", 1, loadoutPerk3, 8 );
for (i = 0; i < perkUpgrd.size; i++)
{
upgrade = perkUpgrd[i];
perk = perks[i];
if ( upgrade == "" || upgrade == "specialty_null" )
continue;
if ( isDefined(self.pers["bots"]["unlocks"]["upgraded_"+perk]) && self.pers["bots"]["unlocks"]["upgraded_"+perk] )
self maps\mp\perks\_perks::givePerk( upgrade );
}
}
/*
Patches giveLoadout so that it doesn't use IsItemUnlocked
*/
botPlayerModelForWeapon( weapon, secondary )
{
team = self.team;
if ( isDefined( game[team + "_model"][weapon] ) )
{
[[game[team+"_model"][weapon]]]();
return;
}
weaponClass = tablelookup( "mp/statstable.csv", 4, weapon, 2 );
switch ( weaponClass )
{
case "weapon_smg":
[[game[team+"_model"]["SMG"]]]();
break;
case "weapon_assault":
weaponClass = tablelookup( "mp/statstable.csv", 4, secondary, 2 );
if ( weaponClass == "weapon_shotgun" )
[[game[team+"_model"]["SHOTGUN"]]]();
else
[[game[team+"_model"]["ASSAULT"]]]();
break;
case "weapon_sniper":
if ( level.environment != "" && isDefined(self.pers["bots"]["unlocks"]["ghillie"]) && self.pers["bots"]["unlocks"]["ghillie"] )
[[game[team+"_model"]["GHILLIE"]]]();
else
[[game[team+"_model"]["SNIPER"]]]();
break;
case "weapon_lmg":
[[game[team+"_model"]["LMG"]]]();
break;
case "weapon_riot":
[[game[team+"_model"]["RIOT"]]]();
break;
default:
[[game[team+"_model"]["ASSAULT"]]]();
break;
}
}
/*
patches so that it uses the bot's getVelocity func
*/
claymoreDetonationBotFix()
{
self endon( "death" );
self waittill( "missile_stuck" );
damagearea = spawn( "trigger_radius", self.origin + ( 0, 0, 0 - level.claymoreDetonateRadius ), 0, level.claymoreDetonateRadius, level.claymoreDetonateRadius * 2 );
self thread maps\mp\gametypes\_weapons::deleteOnDeath( damagearea );
while ( 1 )
{
damagearea waittill( "trigger", player );
if (!player is_bot())
continue;
if ( getdvarint( "scr_claymoredebug" ) != 1 )
{
if ( isdefined( self.owner ) && player == self.owner )
continue;
if ( !maps\mp\gametypes\_weapons::friendlyFireCheck( self.owner, player, 0 ) )
continue;
}
if ( lengthsquared( player getBotVelocity() ) < 10 )
continue;
if ( !player maps\mp\gametypes\_weapons::shouldAffectClaymore( self ) )
continue;
if ( player damageConeTrace( self.origin, self ) > 0 )
break;
}
self playsound ("claymore_activated");
if ( player _hasPerk( "specialty_delaymine" ) )
wait 3.0;
else
wait level.claymoreDetectionGracePeriod;
self detonate();
}