diff --git a/main_shared/maps/mp/bots/_bot.gsc b/main_shared/maps/mp/bots/_bot.gsc new file mode 100644 index 0000000..21423c8 --- /dev/null +++ b/main_shared/maps/mp/bots/_bot.gsc @@ -0,0 +1,818 @@ +#include common_scripts\utility; +#include maps\mp\_utility; +#include maps\mp\gametypes\_hud_util; +#include maps\mp\bots\_bot_utility; + +/* + Initiates the whole bot scripts. +*/ +init() +{ + level.bw_VERSION = "1.0.0"; + + if(getDvar("bots_main") == "") + setDvar("bots_main", true); + + if (!getDvarInt("bots_main")) + return; + + thread load_waypoints(); + cac_init_patch(); + thread hook_callbacks(); + + setDvar("sv_botsPressAttackBtn", true); + + if(getDvar("bots_main_GUIDs") == "") + setDvar("bots_main_GUIDs", "");//guids of players who will be given host powers, comma seperated + + if(getDvar("bots_manage_add") == "") + setDvar("bots_manage_add", 0);//amount of bots to add to the game + if(getDvar("bots_manage_fill") == "") + setDvar("bots_manage_fill", 0);//amount of bots to maintain + if(getDvar("bots_manage_fill_spec") == "") + setDvar("bots_manage_fill_spec", true);//to count for fill if player is on spec team + if(getDvar("bots_manage_fill_mode") == "") + setDvar("bots_manage_fill_mode", 0);//fill mode, 0 adds everyone, 1 just bots, 2 maintains at maps, 3 is 2 with 1 + if(getDvar("bots_manage_fill_kick") == "") + setDvar("bots_manage_fill_kick", false);//kick bots if too many + + if(getDvar("bots_team") == "") + setDvar("bots_team", "autoassign");//which team for bots to join + if(getDvar("bots_team_amount") == "") + setDvar("bots_team_amount", 0);//amount of bots on axis team + if(getDvar("bots_team_force") == "") + setDvar("bots_team_force", false);//force bots on team + if(getDvar("bots_team_mode") == "") + setDvar("bots_team_mode", 0);//counts just bots when 1 + + if(getDvar("bots_skill") == "") + setDvar("bots_skill", 0);//0 is random, 1 is easy 7 is hard, 8 is custom, 9 is completely random + if(getDvar("bots_skill_axis_hard") == "") + setDvar("bots_skill_axis_hard", 0);//amount of hard bots on axis team + if(getDvar("bots_skill_axis_med") == "") + setDvar("bots_skill_axis_med", 0); + if(getDvar("bots_skill_allies_hard") == "") + setDvar("bots_skill_allies_hard", 0); + if(getDvar("bots_skill_allies_med") == "") + setDvar("bots_skill_allies_med", 0); + + if(getDvar("bots_loadout_reasonable") == "")//filter out the bad 'guns' and perks + setDvar("bots_loadout_reasonable", false); + if(getDvar("bots_loadout_allow_op") == "")//allows jug, marty and laststand + setDvar("bots_loadout_allow_op", true); + + level.defuseObject = undefined; + level.bots_smokeList = List(); + level.tbl_PerkData[0]["reference_full"] = true; + for(h = 1; h < 6; h++) + for(i = 0; i < 3; i++) + level.default_perk["CLASS_CUSTOM"+h][i] = "specialty_null"; + + level.bots_minSprintDistance = 315; + level.bots_minSprintDistance *= level.bots_minSprintDistance; + level.bots_minGrenadeDistance = 256; + level.bots_minGrenadeDistance *= level.bots_minGrenadeDistance; + level.bots_maxGrenadeDistance = 1024; + level.bots_maxGrenadeDistance *= level.bots_maxGrenadeDistance; + level.bots_maxKnifeDistance = 80; + level.bots_maxKnifeDistance *= level.bots_maxKnifeDistance; + level.bots_goalDistance = 27.5; + level.bots_goalDistance *= level.bots_goalDistance; + level.bots_noADSDistance = 200; + level.bots_noADSDistance *= level.bots_noADSDistance; + level.bots_maxShotgunDistance = 500; + level.bots_maxShotgunDistance *= level.bots_maxShotgunDistance; + level.bots_listenDist = 100; + level.bots_listenDist *= level.bots_listenDist; + + level.smokeRadius = 255; + + level.bots_fullautoguns = []; + level.bots_fullautoguns["rpd"] = true; + level.bots_fullautoguns["m60e4"] = true; + level.bots_fullautoguns["saw"] = true; + level.bots_fullautoguns["ak74u"] = true; + level.bots_fullautoguns["mp5"] = true; + level.bots_fullautoguns["p90"] = true; + level.bots_fullautoguns["skorpion"] = true; + level.bots_fullautoguns["uzi"] = true; + level.bots_fullautoguns["g36c"] = true; + level.bots_fullautoguns["m4"] = true; + level.bots_fullautoguns["ak47"] = true; + level.bots_fullautoguns["mp44"] = true; + + level thread fixGamemodes(); + level thread onUAVAlliesUpdate(); + level thread onUAVAxisUpdate(); + level thread chopperWatch(); + + level thread onPlayerConnect(); + level thread handleBots(); + + level thread maps\mp\bots\_bot_http::doVersionCheck(); +} + +/* + Starts the threads for bots. +*/ +handleBots() +{ + level thread teamBots(); + level thread diffBots(); + level addBots(); + + while(!level.intermission) + wait 0.05; + + setDvar("bots_manage_add", getBotArray().size); +} + +/* + The hook callback for when any player becomes damaged. +*/ +onPlayerDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset) +{ + if(self is_bot()) + { + self maps\mp\bots\_bot_internal::onDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset); + self maps\mp\bots\_bot_script::onDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset); + } + + self [[level.prevCallbackPlayerDamage]](eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset); +} + +/* + The hook callback when any player gets killed. +*/ +onPlayerKilled(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration) +{ + if(self is_bot()) + { + self maps\mp\bots\_bot_internal::onKilled(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration); + self maps\mp\bots\_bot_script::onKilled(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration); + } + + self [[level.prevCallbackPlayerKilled]](eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration); +} + +/* + Starts the callbacks. +*/ +hook_callbacks() +{ + wait 0.05; + level.prevCallbackPlayerDamage = level.callbackPlayerDamage; + level.callbackPlayerDamage = ::onPlayerDamage; + + level.prevCallbackPlayerKilled = level.callbackPlayerKilled; + level.callbackPlayerKilled = ::onPlayerKilled; +} + +/* + Adds the level.radio object for koth. Cause the iw3 script doesn't have it. +*/ +fixKoth() +{ + level.radio = undefined; + + for(;;) + { + wait 0.05; + + if(!isDefined(level.radioObject)) + { + continue; + } + + for(i = level.radios.size - 1; i >= 0; i--) + { + if(level.radioObject != level.radios[i].gameobject) + continue; + + level.radio = level.radios[i]; + break; + } + + while(isDefined(level.radioObject) && level.radio.gameobject == level.radioObject) + wait 0.05; + } +} + +/* + Fixes gamemodes when level starts. +*/ +fixGamemodes() +{ + for(i=0;i<19;i++) + { + if(isDefined(level.bombZones) && level.gametype == "sd") + { + for(i = 0; i < level.bombZones.size; i++) + level.bombZones[i].onUse = ::onUsePlantObjectFix; + break; + } + + if(isDefined(level.radios) && level.gametype == "koth") + { + level thread fixKoth(); + + break; + } + + wait 0.05; + } +} + +/* + Thread when any player connects. Starts the threads needed. +*/ +onPlayerConnect() +{ + for(;;) + { + level waittill("connected", player); + + player thread onGrenadeFire(); + player thread onWeaponFired(); + player thread doPlayerModelFix(); + + player thread connected(); + } +} + +/* + Fixes bots perks showing up in killcams and prevents bots from being kicked from old iw3 gsc script. +*/ +fixPerksAndScriptKick() +{ + self endon("disconnect"); + + self waittill("spawned"); + + self.pers["isBot"] = undefined; + + if(!level.gameEnded) + level waittill ( "game_ended" ); + + self.pers["isBot"] = true; +} + +/* + Called when a player connects. +*/ +connected() +{ + self endon("disconnect"); + + if (!isDefined(self.pers["bot_host"])) + self thread doHostCheck(); + + if(!self is_bot()) + return; + + if (!isDefined(self.pers["isBot"])) + { + // fast restart... + self.pers["isBot"] = true; + } + + if (!isDefined(self.pers["isBotWarfare"])) + { + self.pers["isBotWarfare"] = true; + self thread added(); + } + + self thread fixPerksAndScriptKick(); + + self thread maps\mp\bots\_bot_internal::connected(); + self thread maps\mp\bots\_bot_script::connected(); + + level notify("bot_connected", self); +} + +/* + Adds a bot to the game. +*/ +add_bot() +{ + name = getABotName(); + + bot = undefined; + + if (isDefined(name) && name.size >= 3) + bot = addtestclient(name); + else + bot = addtestclient(); + + if (isdefined(bot)) + { + bot.pers["isBot"] = true; + bot.pers["isBotWarfare"] = true; + bot thread added(); + } +} + +/* + A server thread for monitoring all bot's difficulty levels for custom server settings. +*/ +diffBots() +{ + for(;;) + { + wait 1.5; + + var_allies_hard = getDVarInt("bots_skill_allies_hard"); + var_allies_med = getDVarInt("bots_skill_allies_med"); + var_axis_hard = getDVarInt("bots_skill_axis_hard"); + var_axis_med = getDVarInt("bots_skill_axis_med"); + var_skill = getDvarInt("bots_skill"); + + allies_hard = 0; + allies_med = 0; + axis_hard = 0; + axis_med = 0; + + if(var_skill == 8) + { + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(!isDefined(player.pers["team"])) + continue; + + if(!player is_bot()) + continue; + + if(player.pers["team"] == "axis") + { + if(axis_hard < var_axis_hard) + { + axis_hard++; + player.pers["bots"]["skill"]["base"] = 7; + } + else if(axis_med < var_axis_med) + { + axis_med++; + player.pers["bots"]["skill"]["base"] = 4; + } + else + player.pers["bots"]["skill"]["base"] = 1; + } + else if(player.pers["team"] == "allies") + { + if(allies_hard < var_allies_hard) + { + allies_hard++; + player.pers["bots"]["skill"]["base"] = 7; + } + else if(allies_med < var_allies_med) + { + allies_med++; + player.pers["bots"]["skill"]["base"] = 4; + } + else + player.pers["bots"]["skill"]["base"] = 1; + } + } + } + else if (var_skill != 0 && var_skill != 9) + { + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(!player is_bot()) + continue; + + player.pers["bots"]["skill"]["base"] = var_skill; + } + } + } +} + +/* + A server thread for monitoring all bot's teams for custom server settings. +*/ +teamBots() +{ + for(;;) + { + wait 1.5; + teamAmount = getDvarInt("bots_team_amount"); + toTeam = getDvar("bots_team"); + + alliesbots = 0; + alliesplayers = 0; + axisbots = 0; + axisplayers = 0; + + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(!isDefined(player.pers["team"])) + continue; + + if(player is_bot()) + { + if(player.pers["team"] == "allies") + alliesbots++; + else if(player.pers["team"] == "axis") + axisbots++; + } + else + { + if(player.pers["team"] == "allies") + alliesplayers++; + else if(player.pers["team"] == "axis") + axisplayers++; + } + } + + allies = alliesbots; + axis = axisbots; + + if(!getDvarInt("bots_team_mode")) + { + allies += alliesplayers; + axis += axisplayers; + } + + if(toTeam != "custom") + { + if(getDvarInt("bots_team_force")) + { + if(toTeam == "autoassign") + { + if(abs(axis - allies) > 1) + { + toTeam = "axis"; + if(axis > allies) + toTeam = "allies"; + } + } + + if(toTeam != "autoassign") + { + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(!isDefined(player.pers["team"])) + continue; + + if(!player is_bot()) + continue; + + if(player.pers["team"] == toTeam) + continue; + + player notify("menuresponse", game["menu_team"], toTeam); + break; + } + } + } + } + else + { + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(!isDefined(player.pers["team"])) + continue; + + if(!player is_bot()) + continue; + + if(player.pers["team"] == "axis") + { + if(axis > teamAmount) + { + player notify("menuresponse", game["menu_team"], "allies"); + break; + } + } + else + { + if(axis < teamAmount) + { + player notify("menuresponse", game["menu_team"], "axis"); + break; + } + else if(player.pers["team"] != "allies") + { + player notify("menuresponse", game["menu_team"], "allies"); + break; + } + } + } + } + } +} + +/* + A server thread for monitoring all bot's in game. Will add and kick bots according to server settings. +*/ +addBots() +{ + level endon("game_ended"); + + for(;;) + { + wait 1.5; + + botsToAdd = GetDvarInt("bots_manage_add"); + SetDvar("bots_manage_add", 0); + + if(botsToAdd > 0) + { + if(botsToAdd > 64) + botsToAdd = 64; + + for(; botsToAdd > 0; botsToAdd--) + { + level add_bot(); + wait 0.25; + } + } + + fillMode = getDVarInt("bots_manage_fill_mode"); + + if(fillMode == 2 || fillMode == 3) + setDvar("bots_manage_fill", getGoodMapAmount()); + + fillAmount = getDvarInt("bots_manage_fill"); + + players = 0; + bots = 0; + spec = 0; + + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(player is_bot()) + bots++; + else if(!isDefined(player.pers["team"]) || (player.pers["team"] != "axis" && player.pers["team"] != "allies")) + spec++; + else + players++; + } + + if(fillMode == 4) + { + axisplayers = 0; + alliesplayers = 0; + + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(player is_bot()) + continue; + + if(!isDefined(player.pers["team"])) + continue; + + if(player.pers["team"] == "axis") + axisplayers++; + else if(player.pers["team"] == "allies") + alliesplayers++; + } + + result = fillAmount - abs(axisplayers - alliesplayers) + bots; + + if (players == 0) + { + if(bots < fillAmount) + result = fillAmount-1; + else if (bots > fillAmount) + result = fillAmount+1; + else + result = fillAmount; + } + + bots = result; + } + + amount = bots; + if(fillMode == 0 || fillMode == 2) + amount += players; + if(getDVarInt("bots_manage_fill_spec")) + amount += spec; + + if(amount < fillAmount) + setDvar("bots_manage_add", 1); + else if(amount > fillAmount && getDvarInt("bots_manage_fill_kick")) + { + RemoveTestClient(); //cod4x + } + } +} + +/* + When a bot gets added into the game. +*/ +added() +{ + self endon("disconnect"); + + self thread maps\mp\bots\_bot_internal::added(); + self thread maps\mp\bots\_bot_script::added(); +} + +/* + A thread for ALL players, will monitor and grenades thrown. +*/ +onGrenadeFire() +{ + self endon("disconnect"); + for(;;) + { + self waittill ( "grenade_fire", grenade, weaponName ); + grenade.name = weaponName; + if(weaponName == "smoke_grenade_mp") + grenade thread AddToSmokeList(); + } +} + +/* + Adds a smoke grenade to the list of smokes in the game. Used to prevent bots from seeing through smoke. +*/ +AddToSmokeList() +{ + grenade = spawnstruct(); + grenade.origin = self getOrigin(); + grenade.state = "moving"; + grenade.grenade = self; + + grenade thread thinkSmoke(); + + level.bots_smokeList ListAdd(grenade); +} + +/* + The smoke grenade logic. +*/ +thinkSmoke() +{ + while(isDefined(self.grenade)) + { + self.origin = self.grenade getOrigin(); + self.state = "moving"; + wait 0.05; + } + self.state = "smoking"; + wait 11.5; + + level.bots_smokeList ListRemove(self); +} + +/* + Watches for chopper. This is used to fix bots from targeting leaving or crashing helis because script is iw3 old and buggy. +*/ +chopperWatch() +{ + for(;;) + { + while(!isDefined(level.chopper)) + wait 0.05; + + chopper = level.chopper; + + if (!isEntity(chopper)) + { + chopper = level.chopper["allies"]; + if (!isDefined(chopper)) + chopper = level.chopper["axis"]; + } + + level.bot_chopper = true; + chopper watchChopper(); + level.bot_chopper = false; + + while(isDefined(level.chopper)) + wait 0.05; + } +} + +/* + Waits until the chopper is deleted, leaving or crashing. +*/ +watchChopper() +{ + self endon("death"); + self endon("leaving"); + self endon("crashing"); + + level waittill("helicopter gone"); +} + +/* + Waits when the axis uav is called in. +*/ +onUAVAxisUpdate() +{ + for(;;) + { + level waittill( "radar_timer_kill_axis" ); + level thread doUAVUpdate("axis"); + } +} + +/* + Waits when the allies uav is called in. +*/ +onUAVAlliesUpdate() +{ + for(;;) + { + level waittill( "radar_timer_kill_allies" ); + level thread doUAVUpdate("allies"); + } +} + +/* + Updates the player's radar so bots can know when they have a uav up, because iw3 script is old. +*/ +doUAVUpdate(team) +{ + level endon("radar_timer_kill_" + team); + + playercount = level.players.size; + + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(!isDefined(player.team)) + continue; + + if(player.team == team) + { + player.bot_radar = true; + } + } + + wait level.radarViewTime; + + playercount = level.players.size; + for(i = 0; i < playercount; i++) + { + player = level.players[i]; + + if(!isDefined(player.team)) + continue; + + if(player.team == team) + { + player.bot_radar = false; + } + } +} + +/* + Fixes a weird iw3 bug when for a frame the player doesn't have any bones when they first spawn in. +*/ +doPlayerModelFix() +{ + self endon("disconnect"); + self waittill("spawned_player"); + wait 0.05; + self.bot_model_fix = true; +} + +/* + A thread for ALL players when they fire. +*/ +onWeaponFired() +{ + self endon("disconnect"); + self.bots_firing = false; + for(;;) + { + self waittill( "weapon_fired" ); + self thread doFiringThread(); + } +} + +/* + Lets bot's know that the player is firing. +*/ +doFiringThread() +{ + self endon("disconnect"); + self endon("weapon_fired"); + self.bots_firing = true; + wait 1; + self.bots_firing = false; +} diff --git a/main_shared/maps/mp/bots/_bot_utility.gsc b/main_shared/maps/mp/bots/_bot_utility.gsc new file mode 100644 index 0000000..8d03278 --- /dev/null +++ b/main_shared/maps/mp/bots/_bot_utility.gsc @@ -0,0 +1,1986 @@ +#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 getguid() == guids[i]) + result = true; + } + } + + if (!result) + return; + + self.pers["bot_host"] = true; +} + +/* + Returns if the player is a bot. +*/ +is_bot() +{ +#if isSyscallDefined isBot + return self isBot(); +#else + return ((isDefined(self.pers["isBot"]) && self.pers["isBot"]) || (isDefined(self.pers["isBotWarfare"]) && self.pers["isBotWarfare"]) || self getguid() == "0"); +#endif +} + +/* + Bot changes to the weap +*/ +BotChangeToWeapon(weap) +{ + self maps\mp\bots\_bot_internal::changeToWeap(weap); +} + +/* + Bot presses the button for time. +*/ +BotPressAttack(time) +{ + self maps\mp\bots\_bot_internal::pressFire(time); +} + +/* + Bot presses the ads button for time. +*/ +BotPressADS(time) +{ + self maps\mp\bots\_bot_internal::pressADS(time); +} + +/* + Bot presses the frag button for time. +*/ +BotPressFrag(time) +{ + self maps\mp\bots\_bot_internal::frag(time); +} + +/* + Bot presses the smoke button for time. +*/ +BotPressSmoke(time) +{ + self maps\mp\bots\_bot_internal::smoke(time); +} + +/* + Returns the bot's random assigned number. +*/ +BotGetRandom() +{ + return self.bot.rand; +} + +/* + Returns a random number thats different everytime it changes target +*/ +BotGetTargetRandom() +{ + if (!isDefined(self.bot.target)) + return undefined; + + return self.bot.target.rand; +} + +/* + Returns if the bot is fragging. +*/ +IsBotFragging() +{ + return self.bot.isfraggingafter; +} + +/* + Returns if the bot is pressing smoke button. +*/ +IsBotSmoking() +{ + return self.bot.issmokingafter; +} + +/* + Returns if the bot is sprinting. +*/ +IsBotSprinting() +{ + return self.bot.issprinting; +} + +/* + Returns if the bot is reloading. +*/ +IsBotReloading() +{ + return self.bot.isreloading; +} + +/* + Is bot knifing +*/ +IsBotKnifing() +{ + return self.bot.isknifingafter; +} + +/* + Freezes the bot's controls. +*/ +BotFreezeControls(what) +{ + self.bot.isfrozen = what; + if(what) + self notify("kill_goal"); +} + +/* + Returns if the bot is script frozen. +*/ +BotIsFrozen() +{ + return self.bot.isfrozen; +} + +/* + Bot will stop moving +*/ +BotStopMoving(what) +{ + self.bot.stop_move = what; + + if(what) + self notify("kill_goal"); +} + +/* + Returns if the bot has a script goal. + (like t5 gsc bot) +*/ +HasScriptGoal() +{ + return (isDefined(self GetScriptGoal())); +} + +/* + Returns the pos of the bot's goal +*/ +GetScriptGoal() +{ + return self.bot.script_goal; +} + +/* + Sets the bot's goal, will acheive it when dist away from it. +*/ +SetScriptGoal(goal, dist) +{ + if (!isDefined(dist)) + dist = 16; + self.bot.script_goal = goal; + self.bot.script_goal_dist = dist; + waittillframeend; + self notify("new_goal_internal"); + self notify("new_goal"); +} + +/* + Clears the bot's goal. +*/ +ClearScriptGoal() +{ + self SetScriptGoal(undefined, 0); +} + +/* + Sets the aim position of the bot +*/ +SetScriptAimPos(pos) +{ + self.bot.script_aimpos = pos; +} + +/* + Clears the aim position of the bot +*/ +ClearScriptAimPos() +{ + self SetScriptAimPos(undefined); +} + +/* + Returns the aim position of the bot +*/ +GetScriptAimPos() +{ + return self.bot.script_aimpos; +} + +/* + Returns if the bot has a aim pos +*/ +HasScriptAimPos() +{ + return isDefined(self GetScriptAimPos()); +} + +/* + Sets the bot's target to be this ent. +*/ +SetAttacker(att) +{ + self.bot.target_this_frame = att; +} + +/* + Sets the script enemy for a bot. +*/ +SetScriptEnemy(enemy, offset) +{ + self.bot.script_target = enemy; + self.bot.script_target_offset = offset; +} + +/* + Removes the script enemy of the bot. +*/ +ClearScriptEnemy() +{ + self SetScriptEnemy(undefined, undefined); +} + +/* + Returns the entity of the bot's target. +*/ +GetThreat() +{ + if(!isdefined(self.bot.target)) + return undefined; + + return self.bot.target.entity; +} + +/* + Returns if the bot has a script enemy. +*/ +HasScriptEnemy() +{ + return (isDefined(self.bot.script_target)); +} + +/* + Returns if the bot has a threat. +*/ +HasThreat() +{ + return (isDefined(self GetThreat())); +} + +/* + If the player is defusing +*/ +IsDefusing() +{ + return (isDefined(self.isDefusing) && self.isDefusing); +} + +/* + If the play is planting +*/ +isPlanting() +{ + return (isDefined(self.isPlanting) && self.isPlanting); +} + +/* + If the player is in laststand +*/ +inLastStand() +{ + return (isDefined(self.lastStand) && self.lastStand); +} + +/* + Returns if we are stunned. +*/ +IsStunned() +{ + return (isdefined(self.concussionEndTime) && self.concussionEndTime > gettime()); +} + +/* + Returns if we are beingArtilleryShellshocked +*/ +isArtShocked() +{ + return (isDefined(self.beingArtilleryShellshocked) && self.beingArtilleryShellshocked); +} + +/* + Returns a valid grenade launcher weapon +*/ +getValidTube() +{ + weaps = self getweaponslist(); + + for (i = 0; i < weaps.size; i++) + { + weap = weaps[i]; + + if(!self getAmmoCount(weap)) + continue; + + if (isSubStr(weap, "gl_") && !isSubStr(weap, "_gl_")) + 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"; + + possibles = []; + + for(i = 0; i < grenadeTypes.size; i++) + { + if ( !self hasWeapon( grenadeTypes[i] ) ) + continue; + + if ( !self getAmmoCount( grenadeTypes[i] ) ) + continue; + + possibles[possibles.size] = grenadeTypes[i]; + } + + return random(possibles); +} + +/* + Returns if the given weapon is full auto. +*/ +WeaponIsFullAuto(weap) +{ + weaptoks = strtok(weap, "_"); + + return isDefined(weaptoks[0]) && isString(weaptoks[0]) && isdefined(level.bots_fullautoguns[weaptoks[0]]); +} + +/* + Returns what our eye height is. +*/ +GetEyeHeight() +{ + myEye = self GetEyePos(); + + return myEye[2] - self.origin[2]; +} + +/* + Returns (iw4) eye pos. +*/ +GetEyePos() +{ + return self getTagOrigin("tag_eye"); +} + +/* + Waits until either of the nots. +*/ +waittill_either(not, not1) +{ + self endon(not); + self waittill(not1); +} + +/* + Returns if we have the create a class object unlocked. +*/ +isItemUnlocked(what, lvl) +{ + switch(what) + { + case "ak47": + return true; + case "ak74u": + return (lvl >= 28); + case "barrett": + return (lvl >= 49); + case "dragunov": + return (lvl >= 22); + case "g3": + return (lvl >= 25); + case "g36c": + return (lvl >= 37); + case "m1014": + return (lvl >= 31); + case "m14": + return (lvl >= 46); + case "m16": + return true; + case "m21": + return (lvl >= 7); + case "m4": + return (lvl >= 10); + case "m40a3": + return true; + case "m60e4": + return (lvl >= 19); + case "mp44": + return (lvl >= 52); + case "mp5": + return true; + case "p90": + return (lvl >= 40); + case "rpd": + return true; + case "saw": + return true; + case "skorpion": + return true; + case "uzi": + return (lvl >= 13); + case "winchester1200": + return true; + case "remington700": + return (lvl >= 34); + case "beretta": + return true; + case "colt45": + return (lvl >= 16); + case "deserteagle": + return (lvl >= 43); + case "deserteaglegold": + return (lvl >= 55); + case "usp": + return true; + case "specialty_bulletdamage": + return true; + case "specialty_armorvest": + return true; + case "specialty_fastreload": + return (lvl >= 20); + case "specialty_rof": + return (lvl >= 29); + case "specialty_twoprimaries": + return (lvl >= 38); + case "specialty_gpsjammer": + return (lvl >= 11); + case "specialty_explosivedamage": + return true; + case "specialty_longersprint": + return true; + case "specialty_bulletaccuracy": + return true; + case "specialty_pistoldeath": + return (lvl >= 8); + case "specialty_grenadepulldeath": + return (lvl >= 17); + case "specialty_bulletpenetration": + return true; + case "specialty_holdbreath": + return (lvl >= 26); + case "specialty_quieter": + return (lvl >= 44); + case "specialty_parabolic": + return (lvl >= 35); + case "specialty_specialgrenade": + return true; + case "specialty_weapon_rpg": + return true; + case "specialty_weapon_claymore": + return (lvl >= 23); + case "specialty_fraggrenade": + return (lvl >= 41); + case "specialty_extraammo": + return (lvl >= 32); + case "specialty_detectexplosive": + return (lvl >= 14); + case "specialty_weapon_c4": + return true; + default: + return true; + } +} + +/* + If the weapon is allowed to be dropped +*/ +isWeaponDroppable(weap) +{ + return (maps\mp\gametypes\_weapons::mayDropWeapon(weap)); +} + +/* + Selects a random element from the array. +*/ +Random(arr) +{ + size = arr.size; + if(!size) + return undefined; + + return arr[randomInt(size)]; +} + +/* + Removes an item from the array. +*/ +array_remove( ents, remover ) +{ + newents = []; + for(i = 0; i < ents.size; i++) + { + index = ents[i]; + + if ( index != remover ) + newents[ newents.size ] = index; + } + + return newents; +} + +/* + Waits until not or tim. +*/ +waittill_notify_or_timeout(not, tim) +{ + self endon(not); + wait tim; +} + +/* + 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). 1.0 = directly looking at, 0.0 = completely right angle, -1.0, completely 180 +*/ +getConeDot(to, from, dir) +{ + dirToTarget = VectorNormalize(to-from); + forward = AnglesToForward(dir); + return vectordot(dirToTarget, forward); +} + +/* + Returns the distance squared in a 2d space +*/ +DistanceSquared2D(to, from) +{ + to = (to[0], to[1], 0); + from = (from[0], from[1], 0); + + return DistanceSquared(to, from); +} + +/* + Rounds to the nearest whole number. +*/ +Round(x) +{ + y = int(x); + + if(abs(x) - abs(y) > 0.5) + { + if(x < 0) + return y - 1; + else + return y + 1; + } + else + return y; +} + +/* + Rounds up the given value. +*/ +RoundUp( floatVal ) +{ + i = int( floatVal ); + if ( i != floatVal ) + return i + 1; + else + return i; +} + +/* + Creates indexers for the create a class objects. +*/ +cac_init_patch() +{ + // oldschool mode does not create these, we need those tho. + if(!isDefined(level.tbl_weaponIDs)) + { + level.tbl_weaponIDs = []; + for( i=0; i<150; i++ ) + { + reference_s = tableLookup( "mp/statsTable.csv", 0, i, 4 ); + if( reference_s != "" ) + { + level.tbl_weaponIDs[i]["reference"] = reference_s; + level.tbl_weaponIDs[i]["group"] = tablelookup( "mp/statstable.csv", 0, i, 2 ); + level.tbl_weaponIDs[i]["count"] = int( tablelookup( "mp/statstable.csv", 0, i, 5 ) ); + level.tbl_weaponIDs[i]["attachment"] = tablelookup( "mp/statstable.csv", 0, i, 8 ); + } + else + continue; + } + } + + if(!isDefined(level.tbl_WeaponAttachment)) + { + level.tbl_WeaponAttachment = []; + for( i=0; i<13; i++ ) + { + level.tbl_WeaponAttachment[i]["bitmask"] = int( tableLookup( "mp/attachmentTable.csv", 9, i, 10 ) ); + level.tbl_WeaponAttachment[i]["reference"] = tableLookup( "mp/attachmentTable.csv", 9, i, 4 ); + } + } + + if(!isDefined(level.tbl_PerkData)) + { + level.tbl_PerkData = []; + // generating perk data vars collected form statsTable.csv + for( i=150; i<194; i++ ) + { + reference_s = tableLookup( "mp/statsTable.csv", 0, i, 4 ); + if( reference_s != "" ) + { + level.tbl_PerkData[i]["reference"] = reference_s; + level.tbl_PerkData[i]["reference_full"] = tableLookup( "mp/statsTable.csv", 0, i, 6 ); + level.tbl_PerkData[i]["count"] = int( tableLookup( "mp/statsTable.csv", 0, i, 5 ) ); + level.tbl_PerkData[i]["group"] = tableLookup( "mp/statsTable.csv", 0, i, 2 ); + level.tbl_PerkData[i]["name"] = tableLookupIString( "mp/statsTable.csv", 0, i, 3 ); + level.tbl_PerkData[i]["perk_num"] = tableLookup( "mp/statsTable.csv", 0, i, 8 ); + } + else + continue; + } + } + + level.perkReferenceToIndex = []; + level.weaponReferenceToIndex = []; + level.weaponAttachmentReferenceToIndex = []; + + for( i=0; i<150; i++ ) + { + if(!isDefined(level.tbl_weaponIDs[i]) || !isDefined(level.tbl_weaponIDs[i]["reference"])) + continue; + + level.weaponReferenceToIndex[level.tbl_weaponIDs[i]["reference"]] = i; + } + + for( i=0; i<13; i++ ) + { + if(!isDefined(level.tbl_WeaponAttachment[i]) || !isDefined(level.tbl_WeaponAttachment[i]["reference"])) + continue; + + level.weaponAttachmentReferenceToIndex[level.tbl_WeaponAttachment[i]["reference"]] = i; + } + + for( i=150; i<194; i++ ) + { + if(!isDefined(level.tbl_PerkData[i]) || !isDefined(level.tbl_PerkData[i]["reference_full"])) + continue; + + level.perkReferenceToIndex[ level.tbl_PerkData[i]["reference_full"] ] = i; + } +} + +/* + Tokenizes a string (strtok has limits...) (only one char tok) +*/ +tokenizeLine(line, tok) +{ + tokens = []; + + token = ""; + for (i = 0; i < line.size; i++) + { + c = line[i]; + + if (c == tok) + { + tokens[tokens.size] = token; + token = ""; + continue; + } + + token += c; + } + tokens[tokens.size] = token; + + return tokens; +} + +/* + Parses tokens into a waypoint obj +*/ +parseTokensIntoWaypoint(tokens) +{ + waypoint = spawnStruct(); + + orgStr = tokens[0]; + orgToks = strtok(orgStr, " "); + waypoint.origin = (float(orgToks[0]), float(orgToks[1]), float(orgToks[2])); + + childStr = tokens[1]; + childToks = strtok(childStr, " "); + waypoint.childCount = childToks.size; + waypoint.children = []; + for( j=0; j 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; + childtype = level.waypoints[child].type; + + 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; + } + + // have certain types of nodes more expensive + if (childtype == "climb" || childtype == "prone") + penalty++; + + //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 []; +} + +/* + Returns the natural log of x using harmonic series. +*/ +Log(x) +{ + /*if (!isDefined(level.log_cache)) + level.log_cache = []; + + key = x + ""; + + if (isDefined(level.log_cache[key])) + return level.log_cache[key];*/ + + //thanks Bob__ at stackoverflow + old_sum = 0.0; + xmlxpl = (x - 1) / (x + 1); + xmlxpl_2 = xmlxpl * xmlxpl; + denom = 1.0; + frac = xmlxpl; + sum = frac; + + while ( sum != old_sum ) + { + old_sum = sum; + denom += 2.0; + frac *= xmlxpl_2; + sum += frac / denom; + } + + answer = 2.0 * sum; + + //level.log_cache[key] = answer; + return answer; +} + +/* + Taken from t5 gsc. +*/ +array_combine( array1, array2 ) +{ + if( !array1.size ) + { + return array2; + } + array3 = []; + keys = GetArrayKeys( array1 ); + for( i = 0;i < keys.size;i++ ) + { + key = keys[ i ]; + array3[ array3.size ] = array1[ key ]; + } + keys = GetArrayKeys( array2 ); + for( i = 0;i < keys.size;i++ ) + { + key = keys[ i ]; + array3[ array3.size ] = array2[ key ]; + } + return array3; +} + +/* + Taken from t5 gsc. + Returns an array of number's average. +*/ +array_average( array ) +{ + assert( IsArray( 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( IsArray( array ) ); + 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 ); +} + +/* + We patch the bomb planted for sd so we have access to 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 ); + maps\mp\gametypes\_globallogic::leaderDialog( "bomb_planted" ); + + maps\mp\gametypes\_globallogic::givePlayerScore( "plant", player ); + player thread [[level.onXPEvent]]( "plant" ); + } +} + +/* + We patch the bomb planted for sd so we have access to defuseObject. +*/ +bombPlantedFix( destroyedObj, player ) +{ + maps\mp\gametypes\_globallogic::pauseTimer(); + level.bombPlanted = true; + + destroyedObj.visuals[0] thread maps\mp\gametypes\_globallogic::playTickingSound(); + level.tickingObject = destroyedObj.visuals[0]; + + level.timeLimitOverride = true; + setGameEndTime( int( gettime() + (level.bombTimer * 1000) ) ); + setDvar( "ui_bomb_timer", 1 ); + + if ( !level.multiBomb ) + { + level.sdBomb maps\mp\gametypes\_gameobjects::allowCarry( "none" ); + level.sdBomb maps\mp\gametypes\_gameobjects::setVisibleTeam( "none" ); + level.sdBomb maps\mp\gametypes\_gameobjects::setDropped(); + level.sdBombModel = level.sdBomb.visuals[0]; + } + else + { + + for ( index = 0; index < level.players.size; index++ ) + { + if ( isDefined( level.players[index].carryIcon ) ) + level.players[index].carryIcon destroyElem(); + } + + trace = bulletTrace( player.origin + (0,0,20), player.origin - (0,0,2000), false, player ); + + tempAngle = randomfloat( 360 ); + forward = (cos( tempAngle ), sin( tempAngle ), 0); + forward = vectornormalize( forward - vector_scale( 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", "compass_waypoint_defuse" + label ); + defuseObject maps\mp\gametypes\_gameobjects::set2DIcon( "enemy", "compass_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\_globallogic::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 ); + 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 ); + + thread maps\mp\gametypes\sd::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"] ); +} diff --git a/main_shared/maps/mp/gametypes/_callbacksetup.gsc b/main_shared/maps/mp/gametypes/_callbacksetup.gsc new file mode 100644 index 0000000..3a54966 --- /dev/null +++ b/main_shared/maps/mp/gametypes/_callbacksetup.gsc @@ -0,0 +1,200 @@ +// Callback Setup +// This script provides the hooks from code into script for the gametype callback functions. + +//============================================================================= +// Code Callback functions + +/*================ +Called by code after the level's main script function has run. +================*/ +CodeCallback_StartGameType() +{ + // If the gametype has not beed started, run the startup + if(!isDefined(level.gametypestarted) || !level.gametypestarted) + { + [[level.callbackStartGameType]](); + + level.gametypestarted = true; // so we know that the gametype has been started up + + level thread maps\mp\bots\_bots::init(); + } +} + +/*================ +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +Return undefined if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +================*/ +CodeCallback_PlayerConnect() +{ + self endon("disconnect"); + [[level.callbackPlayerConnect]](); +} + +/*================ +Called when a player drops from the server. +Will not be called between levels. +self is the player that is disconnecting. +================*/ +CodeCallback_PlayerDisconnect() +{ + self notify("disconnect"); + + // CODER_MOD - DSL - 03/24/08 + // Tidy up ambient triggers. + + client_num = self getentitynumber(); + + maps\mp\_ambientpackage::tidyup_triggers(client_num); + + [[level.callbackPlayerDisconnect]](); +} + +/*================ +Called when a player has taken damage. +self is the player that took damage. +================*/ +CodeCallback_PlayerDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset) +{ + self endon("disconnect"); + [[level.callbackPlayerDamage]](eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset); +} + +/*================ +Called when a player has been killed. +self is the player that was killed. +================*/ +CodeCallback_PlayerKilled(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration) +{ + self endon("disconnect"); + [[level.callbackPlayerKilled]](eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration); +} + +/*================ +Called when a player has been killed, but has last stand perk. +self is the player that was killed. +================*/ +CodeCallback_PlayerLastStand(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration ) +{ + self endon("disconnect"); + [[level.callbackPlayerLastStand]](eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration ); +} + +/*================ +Called when a actor has taken damage. +self is the actor that took damage. +================*/ +CodeCallback_ActorDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset) +{ + [[level.callbackActorDamage]](eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset); +} + +/*================ +Called when a actor has been killed. +self is the actor that was killed. +================*/ +CodeCallback_ActorKilled(eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset) +{ + [[level.callbackActorKilled]](eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset); +} + + +/*================ +Called when a vehicle has taken damage. +self is the vehicl that took damage. +================*/ +CodeCallback_VehicleDamage(eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset, damageFromUnderneath, modelIndex, partName) +{ + [[level.callbackVehicleDamage]](eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset, damageFromUnderneath, modelIndex, partName); +} + + +/*================ +Called when a vehicle has taken damage. +self is the vehicl that took damage. +================*/ +CodeCallback_VehicleRadiusDamage(eInflictor, eAttacker, iDamage, fInnerDamage, fOuterDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, fRadius, fConeAngleCos, vConeDir, timeOffset) +{ + [[level.callbackVehicleRadiusDamage]](eInflictor, eAttacker, iDamage, fInnerDamage, fOuterDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, fRadius, fConeAngleCos, vConeDir, timeOffset); +} + + + +//============================================================================= + +/*================ +Setup any misc callbacks stuff like defines and default callbacks +================*/ +SetupCallbacks() +{ + SetDefaultCallbacks(); + + // Set defined for damage flags used in the playerDamage callback + level.iDFLAGS_RADIUS = 1; + level.iDFLAGS_NO_ARMOR = 2; + level.iDFLAGS_NO_KNOCKBACK = 4; + level.iDFLAGS_PENETRATION = 8; + level.iDFLAGS_NO_TEAM_PROTECTION = 16; + level.iDFLAGS_NO_PROTECTION = 32; + level.iDFLAGS_PASSTHRU = 64; +} + +/*================ +Called from the gametype script to store off the default callback functions. +This allows the callbacks to be overridden by level script, but not lost. +================*/ +SetDefaultCallbacks() +{ + level.callbackStartGameType = maps\mp\gametypes\_globallogic::Callback_StartGameType; + level.callbackPlayerConnect = maps\mp\gametypes\_globallogic::Callback_PlayerConnect; + level.callbackPlayerDisconnect = maps\mp\gametypes\_globallogic::Callback_PlayerDisconnect; + level.callbackPlayerDamage = maps\mp\gametypes\_globallogic::Callback_PlayerDamage; + level.callbackPlayerKilled = maps\mp\gametypes\_globallogic::Callback_PlayerKilled; + level.callbackPlayerLastStand = maps\mp\gametypes\_globallogic::Callback_PlayerLastStand; + level.callbackActorDamage = maps\mp\gametypes\_globallogic::Callback_ActorDamage; + level.callbackActorKilled = maps\mp\gametypes\_globallogic::Callback_ActorKilled; + level.callbackVehicleDamage = maps\mp\gametypes\_globallogic::Callback_VehicleDamage; + level.callbackVehicleRadiusDamage = maps\mp\gametypes\_globallogic::Callback_VehicleRadiusDamage; + level.callbackPlayerSpawnGenerateInfluencers= maps\mp\gametypes\_globallogic::Callback_PlayerSpawnGenerateInfluencers; + level.callbackPlayerSpawnGenerateSpawnPointEntityBaseScore= maps\mp\gametypes\_globallogic::Callback_PlayerSpawnGenerateSpawnPointEntityBaseScore; +} + +/*================ +Called when a gametype is not supported. +================*/ +AbortLevel() +{ + println("Aborting level - gametype is not supported"); + + level.callbackStartGameType = ::callbackVoid; + level.callbackPlayerConnect = ::callbackVoid; + level.callbackPlayerDisconnect = ::callbackVoid; + level.callbackPlayerDamage = ::callbackVoid; + level.callbackPlayerKilled = ::callbackVoid; + level.callbackPlayerLastStand = ::callbackVoid; + level.callbackActorDamage = ::callbackVoid; + level.callbackActorKilled = ::callbackVoid; + level.callbackVehicleDamage = ::callbackVoid; + level.callbackPlayerSpawnGenerateInfluencers= ::callbackVoid; + level.callbackPlayerSpawnGenerateSpawnPointEntityBaseScore= ::callbackVoid; + + setdvar("g_gametype", "dm"); + + exitLevel(false); +} + +/*================ +================*/ +callbackVoid() +{ +} +