/* _bot_utility Author: INeedGames Date: 09/26/2020 The shared functions for bots Notes for engine obv the old bot behavior should be changed to use gsc built-ins to control bots bots using objects needs to be fixed setSpawnWeapon and switchToWeapon should be modified to work for testclients */ #include common_scripts\utility; #include maps\mp\_utility; #include maps\mp\gametypes\_hud_util; /* Waits for the built-ins to be defined */ wait_for_builtins() { for ( i = 0; i < 20; i++ ) { if ( isDefined( level.bot_builtins ) ) { return true; } if ( i < 18 ) { waittillframeend; } else { wait 0.05; } } return false; } /* Prints to console without dev script on */ BotBuiltinPrintConsole( s ) { if ( isDefined( level.bot_builtins ) && isDefined( level.bot_builtins["printconsole"] ) ) { [[ level.bot_builtins["printconsole" ]]]( s ); } } /* Writes to the file, mode can be "append" or "write" */ BotBuiltinFileWrite( file, contents, mode ) { if ( isDefined( level.bot_builtins ) && isDefined( level.bot_builtins["filewrite"] ) ) { [[ level.bot_builtins["filewrite" ]]]( file, contents, mode ); } } /* Returns the whole file as a string */ BotBuiltinFileRead( file ) { if ( isDefined( level.bot_builtins ) && isDefined( level.bot_builtins["fileread"] ) ) { return [[ level.bot_builtins["fileread" ]]]( file ); } return undefined; } /* Test if a file exists */ BotBuiltinFileExists( file ) { if ( isDefined( level.bot_builtins ) && isDefined( level.bot_builtins["fileexists"] ) ) { return [[ level.bot_builtins["fileexists" ]]]( file ); } return false; } /* Bot action, does a bot action botAction() */ BotBuiltinBotAction( action ) { if ( isDefined( level.bot_builtins ) && isDefined( level.bot_builtins["botaction"] ) ) { self [[ level.bot_builtins["botaction" ]]]( action ); } } /* Clears the bot from movement and actions botStop() */ BotBuiltinBotStop() { if ( isDefined( level.bot_builtins ) && isDefined( level.bot_builtins["botstop"] ) ) { self [[ level.bot_builtins["botstop" ]]](); } } /* Sets the bot's movement botMovement(, ) */ BotBuiltinBotMovement( forward, right ) { if ( isDefined( level.bot_builtins ) && isDefined( level.bot_builtins["botmovement"] ) ) { self [[ level.bot_builtins["botmovement" ]]]( forward, right ); } } /* Sets melee params */ BotBuiltinBotMeleeParams( yaw, dist ) { if ( isDefined( level.bot_builtins ) && isDefined( level.bot_builtins["botmeleeparams"] ) ) { self [[ level.bot_builtins["botmeleeparams" ]]]( yaw, dist ); } } /* Returns if player is the host */ is_host() { return ( isDefined( self.pers["bot_host"] ) && self.pers["bot_host"] ); } /* Setups the host variable on the player */ doHostCheck() { self.pers["bot_host"] = false; if ( self is_bot() ) { return; } result = false; if ( getDvar( "bots_main_firstIsHost" ) != "0" ) { BotBuiltinPrintConsole( "WARNING: bots_main_firstIsHost is enabled" ); if ( getDvar( "bots_main_firstIsHost" ) == "1" ) { setDvar( "bots_main_firstIsHost", self getguid() ); } if ( getDvar( "bots_main_firstIsHost" ) == self getguid() + "" ) { result = true; } } DvarGUID = getDvar( "bots_main_GUIDs" ); if ( DvarGUID != "" ) { guids = strtok( DvarGUID, "," ); for ( i = 0; i < guids.size; i++ ) { if ( self getguid() + "" == guids[i] ) { result = true; } } } if ( !self isHost() && !result ) { return; } self.pers["bot_host"] = true; } /* Returns if the player is a bot. */ is_bot() { assert( isDefined( self ) ); assert( isPlayer( self ) ); return ( ( isDefined( self.pers["isBot"] ) && self.pers["isBot"] ) || ( isDefined( self.pers["isBotWarfare"] ) && self.pers["isBotWarfare"] ) || isSubStr( self getguid() + "", "bot" ) ); } /* Set the bot's stance */ BotSetStance( stance ) { switch ( stance ) { case "stand": self maps\mp\bots\_bot_internal::stand(); break; case "crouch": self maps\mp\bots\_bot_internal::crouch(); break; case "prone": self maps\mp\bots\_bot_internal::prone(); break; } } /* Bot presses the frag button for time. */ BotPressFrag( time ) { self maps\mp\bots\_bot_internal::frag( time ); } /* Bot presses the smoke button for time. */ BotPressSmoke( time ) { self maps\mp\bots\_bot_internal::smoke( time ); } /* Bot will press the ads button for the time */ BotPressADS( time ) { self maps\mp\bots\_bot_internal::pressAds( time ); } /* Bot presses the use button for time. */ BotPressUse( time ) { self maps\mp\bots\_bot_internal::use( time ); } /* Bots will press the attack button for a time */ BotPressAttack( time ) { self maps\mp\bots\_bot_internal::pressFire( time ); } /* Returns a random number thats different everytime it changes target */ BotGetTargetRandom() { if ( !isDefined( self.bot.target ) ) { return undefined; } return self.bot.target.rand; } /* Returns the bot's random assigned number. */ BotGetRandom() { return self.bot.rand; } /* Returns if the bot is pressing frag button. */ IsBotFragging() { return self.bot.isfraggingafter; } /* Returns if the bot is pressing smoke button. */ IsBotSmoking() { return self.bot.issmokingafter; } /* Returns if the bot is sprinting. */ IsBotSprinting() { return self.bot.issprinting; } /* Returns if the bot is reloading. */ IsBotReloading() { return self.bot.isreloading; } /* Is bot knifing */ IsBotKnifing() { return self.bot.isknifingafter; } /* Freezes the bot's controls. */ BotFreezeControls( what ) { self.bot.isfrozen = what; if ( what ) { self notify( "kill_goal" ); } } /* Returns if the bot is script frozen. */ BotIsFrozen() { return self.bot.isfrozen; } /* Bot will stop moving */ BotStopMoving( what ) { self.bot.stop_move = what; if ( what ) { self notify( "kill_goal" ); } } /* Notify the bot chat message */ BotNotifyBotEvent( msg, a, b, c, d, e, f, g ) { self notify( "bot_event", msg, a, b, c, d, e, f, g ); } /* Returns if the bot has a script goal. (like t5 gsc bot) */ HasScriptGoal() { return ( isDefined( self GetScriptGoal() ) ); } /* Sets the bot's goal, will acheive it when dist away from it. */ SetScriptGoal( goal, dist ) { if ( !isDefined( dist ) ) { dist = 16; } self.bot.script_goal = goal; self.bot.script_goal_dist = dist; waittillframeend; self notify( "new_goal_internal" ); self notify( "new_goal" ); } /* Returns the pos of the bot's goal */ GetScriptGoal() { return self.bot.script_goal; } /* Clears the bot's goal. */ ClearScriptGoal() { self SetScriptGoal( undefined, 0 ); } /* Returns the location of the bot's javelin target */ HasBotJavelinLocation() { return isDefined( self.bot.jav_loc ); } /* Returns whether the bot has a priority objective */ HasPriorityObjective() { return self.bot.prio_objective; } /* Sets the bot to prioritize the objective over targeting enemies */ SetPriorityObjective() { self.bot.prio_objective = true; self notify( "kill_goal" ); } /* Clears the bot's priority objective to allow the bot to target enemies automatically again */ ClearPriorityObjective() { self.bot.prio_objective = false; self notify( "kill_goal" ); } /* Sets the aim position of the bot */ SetScriptAimPos( pos ) { self.bot.script_aimpos = pos; } /* Clears the aim position of the bot */ ClearScriptAimPos() { self SetScriptAimPos( undefined ); } /* Returns the aim position of the bot */ GetScriptAimPos() { return self.bot.script_aimpos; } /* Returns if the bot has a aim pos */ HasScriptAimPos() { return isDefined( self GetScriptAimPos() ); } /* Sets the bot's javelin target location */ SetBotJavelinLocation( loc ) { self.bot.jav_loc = loc; self notify( "new_enemy" ); } /* Clears the bot's javelin location */ ClearBotJavelinLocation() { self SetBotJavelinLocation( undefined ); } /* Sets the bot's target to be this ent. */ SetAttacker( att ) { self.bot.target_this_frame = att; } /* Sets the script enemy for a bot. */ SetScriptEnemy( enemy, offset ) { self.bot.script_target = enemy; self.bot.script_target_offset = offset; } /* Removes the script enemy of the bot. */ ClearScriptEnemy() { self SetScriptEnemy( undefined, undefined ); } /* Returns the entity of the bot's target. */ GetThreat() { if ( !isdefined( self.bot.target ) ) { return undefined; } return self.bot.target.entity; } /* Returns if the bot has a script enemy. */ HasScriptEnemy() { return ( isDefined( self.bot.script_target ) ); } /* Returns if the bot has a threat. */ HasThreat() { return ( isDefined( self GetThreat() ) ); } /* If the player is defusing */ IsDefusing() { return ( isDefined( self.isDefusing ) && self.isDefusing ); } /* If the play is planting */ isPlanting() { return ( isDefined( self.isPlanting ) && self.isPlanting ); } /* If the player is carrying a bomb */ isBombCarrier() { return ( isDefined( self.isBombCarrier ) && self.isBombCarrier ); } /* If the site is in use */ isInUse() { return ( isDefined( self.inUse ) && self.inUse ); } /* If the player is in laststand */ inLastStand() { return ( isDefined( self.lastStand ) && self.lastStand ); } /* Is being revived */ isBeingRevived() { return ( isDefined( self.beingRevived ) && self.beingRevived ); } /* If the player is in final stand */ inFinalStand() { return ( isDefined( self.inFinalStand ) && self.inFinalStand ); } /* If the player is the flag carrier */ isFlagCarrier() { return ( isDefined( self.carryFlag ) && self.carryFlag ); } /* Returns if we are stunned. */ IsStunned() { return ( isdefined( self.concussionEndTime ) && self.concussionEndTime > gettime() ); } /* Returns if we are beingArtilleryShellshocked */ isArtShocked() { return ( isDefined( self.beingArtilleryShellshocked ) && self.beingArtilleryShellshocked ); } /* Returns a valid grenade launcher weapon */ getValidTube() { weaps = self getweaponslistall(); for ( i = 0; i < weaps.size; i++ ) { weap = weaps[i]; if ( !self getAmmoCount( weap ) ) { continue; } if ( ( isSubStr( weap, "gl_" ) && !isSubStr( weap, "_gl_" ) ) || weap == "m79_mp" ) { return weap; } } return undefined; } /* iw5 */ allowClassChoiceUtil() { entry = tableLookup( "mp/gametypesTable.csv", 0, level.gameType, 4 ); if ( !isDefined( entry ) || entry == "" ) { return true; } return int( entry ); } /* iw5 */ allowTeamChoiceUtil() { entry = tableLookup( "mp/gametypesTable.csv", 0, level.gameType, 5 ); if ( !isDefined( entry ) || entry == "" ) { return true; } return int( entry ); } /* helper */ waittill_either_return_( str1, str2 ) { self endon( str1 ); self waittill( str2 ); return true; } /* Returns which string gets notified first */ waittill_either_return( str1, str2 ) { if ( !isDefined( self waittill_either_return_( str1, str2 ) ) ) { return str1; } return str2; } /* Returns a random grenade in the bot's inventory. */ getValidGrenade() { grenadeTypes = []; grenadeTypes[grenadeTypes.size] = "frag_grenade_mp"; grenadeTypes[grenadeTypes.size] = "smoke_grenade_mp"; grenadeTypes[grenadeTypes.size] = "flash_grenade_mp"; grenadeTypes[grenadeTypes.size] = "concussion_grenade_mp"; grenadeTypes[grenadeTypes.size] = "semtex_mp"; grenadeTypes[grenadeTypes.size] = "throwingknife_mp"; possibles = []; for ( i = 0; i < grenadeTypes.size; i++ ) { if ( !self hasWeapon( grenadeTypes[i] ) ) { continue; } if ( !self getAmmoCount( grenadeTypes[i] ) ) { continue; } possibles[possibles.size] = grenadeTypes[i]; } return random( possibles ); } /* If the weapon is not a script weapon (bomb, killstreak, etc, grenades) */ isWeaponPrimary( weap ) { return ( maps\mp\gametypes\_weapons::isPrimaryWeapon( weap ) || maps\mp\gametypes\_weapons::isAltModeWeapon( weap ) ); } /* If the ent is a vehicle */ entIsVehicle( ent ) { return ( ent.classname == "script_vehicle" || ent.model == "vehicle_uav_static_mp" || ent.model == "vehicle_ac130_coop" ); } /* Returns if the given weapon is full auto. */ WeaponIsFullAuto( weap ) { weaptoks = strtok( weap, "_" ); assert( isDefined( weaptoks[0] ) ); assert( isString( weaptoks[0] ) ); return isDefined( level.bots_fullautoguns[weaptoks[0]] ); } /* If weap is a secondary gnade */ isSecondaryGrenade( gnade ) { return ( gnade == "concussion_grenade_mp" || gnade == "flash_grenade_mp" || gnade == "smoke_grenade_mp" ); } /* If the weapon is allowed to be dropped */ isWeaponDroppable( weap ) { return ( maps\mp\gametypes\_weapons::mayDropWeapon( weap ) ); } /* Does a notify after a delay */ notifyAfterDelay( delay, not ) { wait delay; self notify( not ); } /* Returns a bot to be kicked */ getBotToKick() { bots = getBotArray(); if ( !isDefined( bots ) || !isDefined( bots.size ) || bots.size <= 0 || !isDefined( bots[0] ) ) { return undefined; } tokick = undefined; axis = 0; allies = 0; team = getDvar( "bots_team" ); // count teams for ( i = 0; i < bots.size; i++ ) { bot = bots[i]; if ( !isDefined( bot ) || !isDefined( bot.team ) ) { continue; } if ( bot.team == "allies" ) { allies++; } else if ( bot.team == "axis" ) { axis++; } else // choose bots that are not on a team first { return bot; } } // search for a bot on the other team if ( team == "custom" || team == "axis" ) { team = "allies"; } else if ( team == "autoassign" ) { // get the team with the most bots team = "allies"; if ( axis > allies ) { team = "axis"; } } else { team = "axis"; } // get the bot on this team with lowest skill for ( i = 0; i < bots.size; i++ ) { bot = bots[i]; if ( !isDefined( bot ) || !isDefined( bot.team ) ) { continue; } if ( bot.team != team ) { continue; } if ( !isDefined( bot.pers ) || !isDefined( bot.pers["bots"] ) || !isDefined( bot.pers["bots"]["skill"] ) || !isDefined( bot.pers["bots"]["skill"]["base"] ) ) { continue; } if ( isDefined( tokick ) && bot.pers["bots"]["skill"]["base"] > tokick.pers["bots"]["skill"]["base"] ) { continue; } tokick = bot; } if ( isDefined( tokick ) ) { return tokick; } // just kick lowest skill for ( i = 0; i < bots.size; i++ ) { bot = bots[i]; if ( !isDefined( bot ) || !isDefined( bot.team ) ) { continue; } if ( !isDefined( bot.pers ) || !isDefined( bot.pers["bots"] ) || !isDefined( bot.pers["bots"]["skill"] ) || !isDefined( bot.pers["bots"]["skill"]["base"] ) ) { continue; } if ( isDefined( tokick ) && bot.pers["bots"]["skill"]["base"] > tokick.pers["bots"]["skill"]["base"] ) { continue; } tokick = bot; } return tokick; } /* Gets a player who is host */ GetHostPlayer() { for ( i = 0; i < level.players.size; i++ ) { player = level.players[i]; if ( !player is_host() ) { continue; } return player; } return undefined; } /* Waits for a host player */ bot_wait_for_host() { host = undefined; while ( !isDefined( level ) || !isDefined( level.players ) ) { wait 0.05; } for ( i = getDvarFloat( "bots_main_waitForHostTime" ); i > 0; i -= 0.05 ) { host = GetHostPlayer(); if ( isDefined( host ) ) { break; } wait 0.05; } if ( !isDefined( host ) ) { return; } for ( i = getDvarFloat( "bots_main_waitForHostTime" ); i > 0; i -= 0.05 ) { if ( IsDefined( host.pers[ "team" ] ) ) { break; } wait 0.05; } if ( !IsDefined( host.pers[ "team" ] ) ) { return; } for ( i = getDvarFloat( "bots_main_waitForHostTime" ); i > 0; i -= 0.05 ) { if ( host.pers[ "team" ] == "allies" || host.pers[ "team" ] == "axis" ) { break; } wait 0.05; } } /* Pezbot's line sphere intersection. http://paulbourke.net/geometry/circlesphere/raysphere.c */ RaySphereIntersect( start, end, spherePos, radius ) { // check if the start or end points are in the sphere r2 = radius * radius; if ( DistanceSquared( start, spherePos ) < r2 ) { return true; } if ( DistanceSquared( end, spherePos ) < r2 ) { return true; } // check if the line made by start and end intersect the sphere dp = end - start; a = dp[0] * dp[0] + dp[1] * dp[1] + dp[2] * dp[2]; b = 2 * ( dp[0] * ( start[0] - spherePos[0] ) + dp[1] * ( start[1] - spherePos[1] ) + dp[2] * ( start[2] - spherePos[2] ) ); c = spherePos[0] * spherePos[0] + spherePos[1] * spherePos[1] + spherePos[2] * spherePos[2]; c += start[0] * start[0] + start[1] * start[1] + start[2] * start[2]; c -= 2.0 * ( spherePos[0] * start[0] + spherePos[1] * start[1] + spherePos[2] * start[2] ); c -= radius * radius; bb4ac = b * b - 4.0 * a * c; if ( abs( a ) < 0.0001 || bb4ac < 0 ) { return false; } mu1 = ( 0 - b + sqrt( bb4ac ) ) / ( 2 * a ); //mu2 = (0-b - sqrt(bb4ac)) / (2 * a); // intersection points of the sphere ip1 = start + mu1 * dp; //ip2 = start + mu2 * dp; myDist = DistanceSquared( start, end ); // check if both intersection points far if ( DistanceSquared( start, ip1 ) > myDist/* && DistanceSquared(start, ip2) > myDist*/ ) { return false; } dpAngles = VectorToAngles( dp ); // check if the point is behind us if ( getConeDot( ip1, start, dpAngles ) < 0/* || getConeDot(ip2, start, dpAngles) < 0*/ ) { return false; } return true; } /* Returns if a smoke grenade would intersect start to end line. */ SmokeTrace( start, end, rad ) { for ( i = level.bots_smokeList.count - 1; i >= 0; i-- ) { nade = level.bots_smokeList.data[i]; if ( nade.state != "smoking" ) { continue; } if ( !RaySphereIntersect( start, end, nade.origin, rad ) ) { continue; } return false; } return true; } /* Returns the cone dot (like fov, or distance from the center of our screen). */ getConeDot( to, from, dir ) { dirToTarget = VectorNormalize( to - from ); forward = AnglesToForward( dir ); return vectordot( dirToTarget, forward ); } /* Returns the distance squared in a 2d space */ DistanceSquared2D( to, from ) { to = ( to[0], to[1], 0 ); from = ( from[0], from[1], 0 ); return DistanceSquared( to, from ); } /* Rounds to the nearest whole number. */ Round( x ) { y = int( x ); if ( abs( x ) - abs( y ) > 0.5 ) { if ( x < 0 ) { return y - 1; } else { return y + 1; } } else { return y; } } /* converts a string into a float */ float_old( num ) { setdvar( "temp_dvar_bot_util", num ); return GetDvarFloat( "temp_dvar_bot_util" ); } /* If the string starts with */ isStrStart( string1, subStr ) { return ( getSubStr( string1, 0, subStr.size ) == subStr ); } /* Parses tokens into a waypoint obj */ parseTokensIntoWaypoint( tokens ) { waypoint = spawnStruct(); orgStr = tokens[0]; orgToks = strtok( orgStr, " " ); waypoint.origin = ( float_old( orgToks[0] ), float_old( orgToks[1] ), float_old( orgToks[2] ) ); childStr = tokens[1]; childToks = strtok( childStr, " " ); waypoint.children = []; for ( j = 0; j < childToks.size; j++ ) { waypoint.children[j] = int( childToks[j] ); } type = tokens[2]; waypoint.type = type; anglesStr = tokens[3]; if ( isDefined( anglesStr ) && anglesStr != "" ) { anglesToks = strtok( anglesStr, " " ); if ( anglesToks.size >= 3 ) { waypoint.angles = ( float_old( anglesToks[0] ), float_old( anglesToks[1] ), float_old( anglesToks[2] ) ); } } javStr = tokens[4]; if ( isDefined( javStr ) && javStr != "" ) { javToks = strtok( javStr, " " ); if ( javToks.size >= 3 ) { waypoint.jav_point = ( float_old( javToks[0] ), float_old( javToks[1] ), float_old( javToks[2] ) ); } } return waypoint; } /* Function to extract lines from a file specified by 'filename' and store them in a result structure. */ getWaypointLinesFromFile( filename ) { // Create a structure to store the result, including an array to hold individual lines. result = spawnStruct(); result.lines = []; // Read the entire content of the file into the 'waypointStr' variable. // Note: max string length in GSC is 65535. waypointStr = BotBuiltinFileRead( filename ); // If the file is empty or not defined, return the empty result structure. if ( !isDefined( waypointStr ) ) { return result; } // Variables to track the current line's character count and starting position. linecount = 0; linestart = 0; // Iterate through each character in the 'waypointStr'. for ( i = 0; i < waypointStr.size; i++ ) { // Check for newline characters '\n' or '\r'. if ( waypointStr[i] == "\n" || waypointStr[i] == "\r" ) { // Extract the current line using 'getSubStr' and store it in the result array. result.lines[result.lines.size] = getSubStr( waypointStr, linestart, linestart + linecount ); // If the newline is '\r\n', skip the next character. if ( waypointStr[i] == "\r" && i < waypointStr.size - 1 && waypointStr[i + 1] == "\n" ) { i++; } // Reset linecount and update linestart for the next line. linecount = 0; linestart = i + 1; continue; } // Increment linecount for the current line. linecount++; } // Store the last line (or the only line if there are no newline characters) in the result array. result.lines[result.lines.size] = getSubStr( waypointStr, linestart, linestart + linecount ); // Return the result structure containing the array of extracted lines. return result; } /* Loads waypoints from file */ readWpsFromFile( mapname ) { waypoints = []; filename = "waypoints/" + mapname + "_wp.csv"; if ( !BotBuiltinFileExists( filename ) ) { return waypoints; } res = getWaypointLinesFromFile( filename ); if ( !res.lines.size ) { return waypoints; } BotBuiltinPrintConsole( "Attempting to read waypoints from " + filename ); waypointCount = int( res.lines[0] ); for ( i = 1; i <= waypointCount; i++ ) { tokens = strtok( res.lines[i], "," ); waypoint = parseTokensIntoWaypoint( tokens ); waypoints[i - 1] = waypoint; } return waypoints; } /* Loads the waypoints. Populating everything needed for the waypoints. */ load_waypoints() { level.waypointCount = 0; level.waypointUsage = []; level.waypointUsage["allies"] = []; level.waypointUsage["axis"] = []; if ( !isDefined( level.waypoints ) ) { level.waypoints = []; } mapname = getDvar( "mapname" ); wps = readWpsFromFile( mapname ); if ( wps.size ) { level.waypoints = wps; BotBuiltinPrintConsole( "Loaded " + wps.size + " waypoints from csv" ); } else { switch ( mapname ) { default: maps\mp\bots\waypoints\_custom_map::main( mapname ); break; } if ( level.waypoints.size ) { BotBuiltinPrintConsole( "Loaded " + level.waypoints.size + " waypoints from script" ); } } if ( !level.waypoints.size ) { BotBuiltinPrintConsole( "No waypoints loaded!" ); } level.waypointCount = level.waypoints.size; for ( i = 0; i < level.waypointCount; i++ ) { if ( !isDefined( level.waypoints[i].children ) || !isDefined( level.waypoints[i].children.size ) ) { level.waypoints[i].children = []; } if ( !isDefined( level.waypoints[i].origin ) ) { level.waypoints[i].origin = ( 0, 0, 0 ); } if ( !isDefined( level.waypoints[i].type ) ) { level.waypoints[i].type = "crouch"; } level.waypoints[i].childCount = undefined; } } /* Is bot near any of the given waypoints */ nearAnyOfWaypoints( dist, waypoints ) { dist *= dist; for ( i = 0; i < waypoints.size; i++ ) { waypoint = level.waypoints[waypoints[i]]; if ( DistanceSquared( waypoint.origin, self.origin ) > dist ) { continue; } return true; } return false; } /* Returns the waypoints that are near */ waypointsNear( waypoints, dist ) { dist *= dist; answer = []; for ( i = 0; i < waypoints.size; i++ ) { wp = level.waypoints[waypoints[i]]; if ( DistanceSquared( wp.origin, self.origin ) > dist ) { continue; } answer[answer.size] = waypoints[i]; } return answer; } /* Returns nearest waypoint of waypoints */ getNearestWaypointOfWaypoints( waypoints ) { answer = undefined; closestDist = 2147483647; for ( i = 0; i < waypoints.size; i++ ) { waypoint = level.waypoints[waypoints[i]]; thisDist = DistanceSquared( self.origin, waypoint.origin ); if ( isDefined( answer ) && thisDist > closestDist ) { continue; } answer = waypoints[i]; closestDist = thisDist; } return answer; } /* Returns all waypoints of type */ getWaypointsOfType( type ) { answer = []; for ( i = 0; i < level.waypointCount; i++ ) { wp = level.waypoints[i]; if ( type == "camp" ) { if ( wp.type != "crouch" ) { continue; } if ( wp.children.size != 1 ) { continue; } } else if ( type != wp.type ) { continue; } answer[answer.size] = i; } return answer; } /* Returns the waypoint for index */ getWaypointForIndex( i ) { if ( !isDefined( i ) ) { return undefined; } return level.waypoints[i]; } /* Returns the friendly user name for a given map's codename */ getMapName( mapname ) { switch ( mapname ) { case "mp_abandon": return "Carnival"; case "mp_rundown": return "Rundown"; case "mp_afghan": return "Afghan"; case "mp_boneyard": return "Scrapyard"; case "mp_brecourt": return "Wasteland"; case "mp_cargoship": return "Wetwork"; case "mp_checkpoint": return "Karachi"; case "mp_compact": return "Salvage"; case "mp_complex": return "Bailout"; case "mp_crash": return "Crash"; case "mp_cross_fire": return "Crossfire"; case "mp_derail": return "Derail"; case "mp_estate": return "Estate"; case "mp_favela": return "Favela"; case "mp_fuel2": return "Fuel"; case "mp_highrise": return "Highrise"; case "mp_invasion": return "Invasion"; case "mp_killhouse": return "Killhouse"; case "mp_nightshift": return "Skidrow"; case "mp_nuked": return "Nuketown"; case "oilrig": return "Oilrig"; case "mp_quarry": return "Quarry"; case "mp_rust": return "Rust"; case "mp_storm": return "Storm"; case "mp_strike": return "Strike"; case "mp_subbase": return "Subbase"; case "mp_terminal": return "Terminal"; case "mp_trailerpark": return "Trailer Park"; case "mp_overgrown": return "Overgrown"; case "mp_underpass": return "Underpass"; case "mp_vacant": return "Vacant"; case "iw4_credits": return "IW4 Test Map"; case "airport": return "Airport"; case "co_hunted": return "Hunted"; case "invasion": return "Burgertown"; case "mp_bloc": return "Bloc"; case "mp_bog_sh": return "Bog"; case "contingency": return "Contingency"; case "gulag": return "Gulag"; case "so_ghillies": return "Pripyat"; case "ending": return "Museum"; case "af_chase": return "Afghan Chase"; case "af_caves": return "Afghan Caves"; case "arcadia": return "Arcadia"; case "boneyard": return "Boneyard"; case "cliffhanger": return "Cliffhanger"; case "dcburning": return "DCBurning"; case "dcemp": return "DCEMP"; case "downtown": return "Downtown"; case "estate": return "EstateSP"; case "favela": return "FavelaSP"; case "favela_escape": return "Favela Escape"; case "roadkill": return "Roadkill"; case "trainer": return "TH3 PIT"; case "so_bridge": return "Bridge"; case "dc_whitehouse": return "Whitehouse"; case "mp_shipment_long": return "ShipmentLong"; case "mp_shipment": return "Shipment"; case "mp_firingrange": return "Firing Range"; case "mp_rust_long": return "RustLong"; case "mp_cargoship_sh": return "Freighter"; case "mp_storm_spring": return "Chemical Plant"; case "mp_crash_trop": case "mp_crash_tropical": return "Crash Tropical"; case "mp_fav_tropical": return "Favela Tropical"; case "mp_estate_trop": case "mp_estate_tropical": return "Estate Tropical"; case "mp_bloc_sh": return "Forgotten City"; default: return mapname; } } /* Returns a good amount of players. */ getGoodMapAmount() { switch ( getdvar( "mapname" ) ) { case "mp_rust": case "iw4_credits": case "mp_nuked": case "oilrig": case "mp_killhouse": case "invasion": case "mp_bog_sh": case "co_hunted": case "contingency": case "gulag": case "so_ghillies": case "ending": case "af_chase": case "af_caves": case "arcadia": case "boneyard": case "cliffhanger": case "dcburning": case "dcemp": case "downtown": case "estate": case "favela": case "favela_escape": case "roadkill": case "so_bridge": case "trainer": case "dc_whitehouse": case "mp_shipment": if ( level.teambased ) { return 8; } else { return 4; } case "mp_vacant": case "mp_terminal": case "mp_nightshift": case "mp_favela": case "mp_highrise": case "mp_boneyard": case "mp_subbase": case "mp_firingrange": case "mp_fav_tropical": case "mp_shipment_long": case "mp_rust_long": if ( level.teambased ) { return 12; } else { return 8; } case "mp_afghan": case "mp_crash": case "mp_brecourt": case "mp_cross_fire": case "mp_overgrown": case "mp_trailerpark": case "mp_underpass": case "mp_checkpoint": case "mp_quarry": case "mp_rundown": case "mp_cargoship": case "mp_estate": case "mp_bloc": case "mp_storm": case "mp_strike": case "mp_abandon": case "mp_complex": case "airport": case "mp_storm_spring": case "mp_crash_trop": case "mp_cargoship_sh": case "mp_estate_trop": case "mp_compact": case "mp_crash_tropical": case "mp_estate_tropical": case "mp_bloc_sh": if ( level.teambased ) { return 14; } else { return 9; } case "mp_fuel2": case "mp_invasion": case "mp_derail": if ( level.teambased ) { return 16; } else { return 10; } default: return 2; } } /* Matches a num to a char */ keyCodeToString( a ) { b = ""; switch ( a ) { case 0: b = "a"; break; case 1: b = "b"; break; case 2: b = "c"; break; case 3: b = "d"; break; case 4: b = "e"; break; case 5: b = "f"; break; case 6: b = "g"; break; case 7: b = "h"; break; case 8: b = "i"; break; case 9: b = "j"; break; case 10: b = "k"; break; case 11: b = "l"; break; case 12: b = "m"; break; case 13: b = "n"; break; case 14: b = "o"; break; case 15: b = "p"; break; case 16: b = "q"; break; case 17: b = "r"; break; case 18: b = "s"; break; case 19: b = "t"; break; case 20: b = "u"; break; case 21: b = "v"; break; case 22: b = "w"; break; case 23: b = "x"; break; case 24: b = "y"; break; case 25: b = "z"; break; case 26: b = "."; break; case 27: b = " "; break; } return b; } /* Returns an array of all the bots in the game. */ getBotArray() { result = []; playercount = level.players.size; for ( i = 0; i < playercount; i++ ) { player = level.players[i]; if ( !player is_bot() ) { continue; } result[result.size] = player; } return result; } /* We return a balanced KDTree from the waypoints. */ WaypointsToKDTree() { kdTree = KDTree(); kdTree _WaypointsToKDTree( level.waypoints, 0 ); return kdTree; } /* Recurive function. We construct a balanced KD tree by sorting the waypoints using heap sort. */ _WaypointsToKDTree( waypoints, dem ) { if ( !waypoints.size ) { return; } callbacksort = undefined; switch ( dem ) { case 0: callbacksort = ::HeapSortCoordX; break; case 1: callbacksort = ::HeapSortCoordY; break; case 2: callbacksort = ::HeapSortCoordZ; break; } heap = NewHeap( callbacksort ); for ( i = 0; i < waypoints.size; i++ ) { heap HeapInsert( waypoints[i] ); } sorted = []; while ( heap.data.size ) { sorted[sorted.size] = heap.data[0]; heap HeapRemove(); } median = int( sorted.size / 2 ); //use divide and conq left = []; right = []; for ( i = 0; i < sorted.size; i++ ) { if ( i < median ) { right[right.size] = sorted[i]; } else if ( i > median ) { left[left.size] = sorted[i]; } } self KDTreeInsert( sorted[median] ); _WaypointsToKDTree( left, ( dem + 1 ) % 3 ); _WaypointsToKDTree( right, ( dem + 1 ) % 3 ); } /* Returns a new list. */ List() { list = spawnStruct(); list.count = 0; list.data = []; return list; } /* Adds a new thing to the list. */ ListAdd( thing ) { self.data[self.count] = thing; self.count++; } /* Adds to the start of the list. */ ListAddFirst( thing ) { for ( i = self.count - 1; i >= 0; i-- ) { self.data[i + 1] = self.data[i]; } self.data[0] = thing; self.count++; } /* Removes the thing from the list. */ ListRemove( thing ) { for ( i = 0; i < self.count; i++ ) { if ( self.data[i] == thing ) { while ( i < self.count - 1 ) { self.data[i] = self.data[i + 1]; i++; } self.data[i] = undefined; self.count--; break; } } } /* Returns a new KDTree. */ KDTree() { kdTree = spawnStruct(); kdTree.root = undefined; kdTree.count = 0; return kdTree; } /* Called on a KDTree. Will insert the object into the KDTree. */ KDTreeInsert( data ) //as long as what you insert has a .origin attru, it will work. { self.root = self _KDTreeInsert( self.root, data, 0, -2147483647, -2147483647, -2147483647, 2147483647, 2147483647, 2147483647 ); } /* Recurive function that insert the object into the KDTree. */ _KDTreeInsert( node, data, dem, x0, y0, z0, x1, y1, z1 ) { if ( !isDefined( node ) ) { r = spawnStruct(); r.data = data; r.left = undefined; r.right = undefined; r.x0 = x0; r.x1 = x1; r.y0 = y0; r.y1 = y1; r.z0 = z0; r.z1 = z1; self.count++; return r; } switch ( dem ) { case 0: if ( data.origin[0] < node.data.origin[0] ) { node.left = self _KDTreeInsert( node.left, data, 1, x0, y0, z0, node.data.origin[0], y1, z1 ); } else { node.right = self _KDTreeInsert( node.right, data, 1, node.data.origin[0], y0, z0, x1, y1, z1 ); } break; case 1: if ( data.origin[1] < node.data.origin[1] ) { node.left = self _KDTreeInsert( node.left, data, 2, x0, y0, z0, x1, node.data.origin[1], z1 ); } else { node.right = self _KDTreeInsert( node.right, data, 2, x0, node.data.origin[1], z0, x1, y1, z1 ); } break; case 2: if ( data.origin[2] < node.data.origin[2] ) { node.left = self _KDTreeInsert( node.left, data, 0, x0, y0, z0, x1, y1, node.data.origin[2] ); } else { node.right = self _KDTreeInsert( node.right, data, 0, x0, y0, node.data.origin[2], x1, y1, z1 ); } break; } return node; } /* Called on a KDTree, will return the nearest object to the given origin. */ KDTreeNearest( origin ) { if ( !isDefined( self.root ) ) { return undefined; } return self _KDTreeNearest( self.root, origin, self.root.data, DistanceSquared( self.root.data.origin, origin ), 0 ); } /* Recurive function that will retrieve the closest object to the query. */ _KDTreeNearest( node, point, closest, closestdist, dem ) { if ( !isDefined( node ) ) { return closest; } thisDis = DistanceSquared( node.data.origin, point ); if ( thisDis < closestdist ) { closestdist = thisDis; closest = node.data; } if ( node RectDistanceSquared( point ) < closestdist ) { near = node.left; far = node.right; if ( point[dem] > node.data.origin[dem] ) { near = node.right; far = node.left; } closest = self _KDTreeNearest( near, point, closest, closestdist, ( dem + 1 ) % 3 ); closest = self _KDTreeNearest( far, point, closest, DistanceSquared( closest.origin, point ), ( dem + 1 ) % 3 ); } return closest; } /* Called on a rectangle, returns the distance from origin to the rectangle. */ RectDistanceSquared( origin ) { dx = 0; dy = 0; dz = 0; if ( origin[0] < self.x0 ) { dx = origin[0] - self.x0; } else if ( origin[0] > self.x1 ) { dx = origin[0] - self.x1; } if ( origin[1] < self.y0 ) { dy = origin[1] - self.y0; } else if ( origin[1] > self.y1 ) { dy = origin[1] - self.y1; } if ( origin[2] < self.z0 ) { dz = origin[2] - self.z0; } else if ( origin[2] > self.z1 ) { dz = origin[2] - self.z1; } return dx * dx + dy * dy + dz * dz; } /* Does the extra check when adding bots */ doExtraCheck() { maps\mp\bots\_bot_internal::checkTheBots(); } /* A heap invarient comparitor, used for objects, objects with a higher X coord will be first in the heap. */ HeapSortCoordX( item, item2 ) { return item.origin[0] > item2.origin[0]; } /* A heap invarient comparitor, used for objects, objects with a higher Y coord will be first in the heap. */ HeapSortCoordY( item, item2 ) { return item.origin[1] > item2.origin[1]; } /* A heap invarient comparitor, used for objects, objects with a higher Z coord will be first in the heap. */ HeapSortCoordZ( item, item2 ) { return item.origin[2] > item2.origin[2]; } /* A heap invarient comparitor, used for numbers, numbers with the highest number will be first in the heap. */ Heap( item, item2 ) { return item > item2; } /* A heap invarient comparitor, used for numbers, numbers with the lowest number will be first in the heap. */ ReverseHeap( item, item2 ) { return item < item2; } /* A heap invarient comparitor, used for traces. Wanting the trace with the largest length first in the heap. */ HeapTraceFraction( item, item2 ) { return item["fraction"] > item2["fraction"]; } /* Returns a new heap. */ NewHeap( compare ) { heap_node = spawnStruct(); heap_node.data = []; heap_node.compare = compare; return heap_node; } /* Inserts the item into the heap. Called on a heap. */ HeapInsert( item ) { insert = self.data.size; self.data[insert] = item; current = insert + 1; while ( current > 1 ) { last = current; current = int( current / 2 ); if ( ![[self.compare]]( item, self.data[current - 1] ) ) { break; } self.data[last - 1] = self.data[current - 1]; self.data[current - 1] = item; } } /* Helper function to determine what is the next child of the bst. */ _HeapNextChild( node, hsize ) { left = node * 2; right = left + 1; if ( left > hsize ) { return -1; } if ( right > hsize ) { return left; } if ( [[self.compare]]( self.data[left - 1], self.data[right - 1] ) ) { return left; } else { return right; } } /* Removes an item from the heap. Called on a heap. */ HeapRemove() { remove = self.data.size; if ( !remove ) { return remove; } move = self.data[remove - 1]; self.data[0] = move; self.data[remove - 1] = undefined; remove--; if ( !remove ) { return remove; } last = 1; next = self _HeapNextChild( 1, remove ); while ( next != -1 ) { if ( [[self.compare]]( move, self.data[next - 1] ) ) { break; } self.data[last - 1] = self.data[next - 1]; self.data[next - 1] = move; last = next; next = self _HeapNextChild( next, remove ); } return remove; } /* A heap invarient comparitor, used for the astar's nodes, wanting the node with the lowest f to be first in the heap. */ ReverseHeapAStar( item, item2 ) { return item.f < item2.f; } /* Removes the waypoint usage */ RemoveWaypointUsage( wp, team ) { if ( !isDefined( level.waypointUsage ) ) { return; } if ( !isDefined( level.waypointUsage[team][wp + ""] ) ) { return; } level.waypointUsage[team][wp + ""]--; if ( level.waypointUsage[team][wp + ""] <= 0 ) { level.waypointUsage[team][wp + ""] = undefined; } } /* Will linearly search for the nearest waypoint to pos that has a direct line of sight. */ GetNearestWaypointWithSight( pos ) { candidate = undefined; dist = 2147483647; for ( i = 0; i < level.waypointCount; i++ ) { if ( !bulletTracePassed( pos + ( 0, 0, 15 ), level.waypoints[i].origin + ( 0, 0, 15 ), false, undefined ) ) { continue; } curdis = DistanceSquared( level.waypoints[i].origin, pos ); if ( curdis > dist ) { continue; } dist = curdis; candidate = i; } return candidate; } /* Will linearly search for the nearest waypoint */ GetNearestWaypoint( pos ) { candidate = undefined; dist = 2147483647; for ( i = 0; i < level.waypointCount; i++ ) { curdis = DistanceSquared( level.waypoints[i].origin, pos ); if ( curdis > dist ) { continue; } dist = curdis; candidate = i; } return candidate; } /* Modified Pezbot astar search. This makes use of sets for quick look up and a heap for a priority queue instead of simple lists which require to linearly search for elements everytime. It is also modified to make paths with bots already on more expensive and will try a less congested path first. Thus spliting up the bots onto more paths instead of just one (the smallest). */ AStarSearch( start, goal, team, greedy_path ) { open = NewHeap( ::ReverseHeapAStar ); //heap openset = [];//set for quick lookup closed = [];//set for quick lookup startWp = getNearestWaypoint( start ); if ( !isDefined( startWp ) ) { return []; } _startwp = undefined; if ( !bulletTracePassed( start + ( 0, 0, 15 ), level.waypoints[startWp].origin + ( 0, 0, 15 ), false, undefined ) ) { _startwp = GetNearestWaypointWithSight( start ); } if ( isDefined( _startwp ) ) { startWp = _startwp; } goalWp = getNearestWaypoint( goal ); if ( !isDefined( goalWp ) ) { return []; } _goalWp = undefined; if ( !bulletTracePassed( goal + ( 0, 0, 15 ), level.waypoints[goalWp].origin + ( 0, 0, 15 ), false, undefined ) ) { _goalwp = GetNearestWaypointWithSight( goal ); } if ( isDefined( _goalwp ) ) { goalWp = _goalwp; } node = spawnStruct(); node.g = 0; //path dist so far node.h = DistanceSquared( level.waypoints[startWp].origin, level.waypoints[goalWp].origin ); //herustic, distance to goal for path finding node.f = node.h + node.g; // combine path dist and heru, use reverse heap to sort the priority queue by this attru node.index = startWp; node.parent = undefined; //we are start, so we have no parent //push node onto queue openset[node.index + ""] = node; open HeapInsert( node ); //while the queue is not empty while ( open.data.size ) { //pop bestnode from queue bestNode = open.data[0]; open HeapRemove(); openset[bestNode.index + ""] = undefined; wp = level.waypoints[bestNode.index]; //check if we made it to the goal if ( bestNode.index == goalWp ) { path = []; while ( isDefined( bestNode ) ) { if ( isdefined( team ) && isDefined( level.waypointUsage ) ) { if ( !isDefined( level.waypointUsage[team][bestNode.index + ""] ) ) { level.waypointUsage[team][bestNode.index + ""] = 0; } level.waypointUsage[team][bestNode.index + ""]++; } //construct path path[path.size] = bestNode.index; bestNode = bestNode.parent; } return path; } //for each child of bestnode for ( i = wp.children.size - 1; i >= 0; i-- ) { child = wp.children[i]; childWp = level.waypoints[child]; penalty = 1; if ( !greedy_path && isdefined( team ) && isDefined( level.waypointUsage ) ) { temppen = 1; if ( isDefined( level.waypointUsage[team][child + ""] ) ) { temppen = level.waypointUsage[team][child + ""]; //consider how many bots are taking this path } if ( temppen > 1 ) { penalty = temppen; } } // have certain types of nodes more expensive if ( childWp.type == "climb" || childWp.type == "prone" ) { penalty += 4; } //calc the total path we have took newg = bestNode.g + DistanceSquared( wp.origin, childWp.origin ) * penalty; //bots on same team's path are more expensive //check if this child is in open or close with a g value less than newg inopen = isDefined( openset[child + ""] ); if ( inopen && openset[child + ""].g <= newg ) { continue; } inclosed = isDefined( closed[child + ""] ); if ( inclosed && closed[child + ""].g <= newg ) { continue; } node = undefined; if ( inopen ) { node = openset[child + ""]; } else if ( inclosed ) { node = closed[child + ""]; } else { node = spawnStruct(); } node.parent = bestNode; node.g = newg; node.h = DistanceSquared( childWp.origin, level.waypoints[goalWp].origin ); node.f = node.g + node.h; node.index = child; //check if in closed, remove it if ( inclosed ) { closed[child + ""] = undefined; } //check if not in open, add it if ( !inopen ) { open HeapInsert( node ); openset[child + ""] = node; } } //done with children, push onto closed closed[bestNode.index + ""] = bestNode; } return []; } /* 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 endon( "death" ); self takeAllWeapons(); primaryIndex = 0; // initialize specialty array self.specialty = []; if ( !isDefined( allowCopycat ) ) { allowCopycat = true; } primaryWeapon = undefined; if ( isDefined( self.pers["copyCatLoadout"] ) && self.pers["copyCatLoadout"]["inUse"] && allowCopycat ) { self maps\mp\gametypes\_class::setClass( "copycat" ); self.class_num = maps\mp\gametypes\_class::getClassIndex( "copycat" ); clonedLoadout = self.pers["copyCatLoadout"]; loadoutPrimary = clonedLoadout["loadoutPrimary"]; loadoutPrimaryAttachment = clonedLoadout["loadoutPrimaryAttachment"]; loadoutPrimaryAttachment2 = clonedLoadout["loadoutPrimaryAttachment2"] ; loadoutPrimaryCamo = clonedLoadout["loadoutPrimaryCamo"]; loadoutSecondary = clonedLoadout["loadoutSecondary"]; loadoutSecondaryAttachment = clonedLoadout["loadoutSecondaryAttachment"]; loadoutSecondaryAttachment2 = clonedLoadout["loadoutSecondaryAttachment2"]; loadoutSecondaryCamo = clonedLoadout["loadoutSecondaryCamo"]; loadoutEquipment = clonedLoadout["loadoutEquipment"]; loadoutPerk1 = clonedLoadout["loadoutPerk1"]; loadoutPerk2 = clonedLoadout["loadoutPerk2"]; loadoutPerk3 = clonedLoadout["loadoutPerk3"]; loadoutOffhand = clonedLoadout["loadoutOffhand"]; loadoutDeathStreak = "specialty_copycat"; } else if ( isSubstr( class, "custom" ) ) { class_num = maps\mp\gametypes\_class::getClassIndex( class ); self.class_num = class_num; loadoutPrimary = maps\mp\gametypes\_class::cac_getWeapon( class_num, 0 ); loadoutPrimaryAttachment = maps\mp\gametypes\_class::cac_getWeaponAttachment( class_num, 0 ); loadoutPrimaryAttachment2 = maps\mp\gametypes\_class::cac_getWeaponAttachmentTwo( class_num, 0 ); loadoutPrimaryCamo = maps\mp\gametypes\_class::cac_getWeaponCamo( class_num, 0 ); loadoutSecondaryCamo = maps\mp\gametypes\_class::cac_getWeaponCamo( class_num, 1 ); loadoutSecondary = maps\mp\gametypes\_class::cac_getWeapon( class_num, 1 ); loadoutSecondaryAttachment = maps\mp\gametypes\_class::cac_getWeaponAttachment( class_num, 1 ); loadoutSecondaryAttachment2 = maps\mp\gametypes\_class::cac_getWeaponAttachmentTwo( class_num, 1 ); loadoutSecondaryCamo = maps\mp\gametypes\_class::cac_getWeaponCamo( class_num, 1 ); loadoutEquipment = maps\mp\gametypes\_class::cac_getPerk( class_num, 0 ); loadoutPerk1 = maps\mp\gametypes\_class::cac_getPerk( class_num, 1 ); loadoutPerk2 = maps\mp\gametypes\_class::cac_getPerk( class_num, 2 ); loadoutPerk3 = maps\mp\gametypes\_class::cac_getPerk( class_num, 3 ); loadoutOffhand = maps\mp\gametypes\_class::cac_getOffhand( class_num ); loadoutDeathStreak = maps\mp\gametypes\_class::cac_getDeathstreak( class_num ); } else { class_num = maps\mp\gametypes\_class::getClassIndex( class ); self.class_num = class_num; loadoutPrimary = maps\mp\gametypes\_class::table_getWeapon( level.classTableName, class_num, 0 ); loadoutPrimaryAttachment = maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 0, 0 ); loadoutPrimaryAttachment2 = maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 0, 1 ); loadoutPrimaryCamo = maps\mp\gametypes\_class::table_getWeaponCamo( level.classTableName, class_num, 0 ); loadoutSecondaryCamo = maps\mp\gametypes\_class::table_getWeaponCamo( level.classTableName, class_num, 1 ); loadoutSecondary = maps\mp\gametypes\_class::table_getWeapon( level.classTableName, class_num, 1 ); loadoutSecondaryAttachment = maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 1, 0 ); loadoutSecondaryAttachment2 = maps\mp\gametypes\_class::table_getWeaponAttachment( level.classTableName, class_num, 1, 1 );; loadoutSecondaryCamo = maps\mp\gametypes\_class::table_getWeaponCamo( level.classTableName, class_num, 1 ); loadoutEquipment = maps\mp\gametypes\_class::table_getEquipment( level.classTableName, class_num, 0 ); loadoutPerk1 = maps\mp\gametypes\_class::table_getPerk( level.classTableName, class_num, 1 ); loadoutPerk2 = maps\mp\gametypes\_class::table_getPerk( level.classTableName, class_num, 2 ); loadoutPerk3 = maps\mp\gametypes\_class::table_getPerk( level.classTableName, class_num, 3 ); loadoutOffhand = maps\mp\gametypes\_class::table_getOffhand( level.classTableName, class_num ); loadoutDeathstreak = maps\mp\gametypes\_class::table_getDeathstreak( level.classTableName, class_num ); } if ( loadoutPerk1 != "specialty_bling" ) { loadoutPrimaryAttachment2 = "none"; loadoutSecondaryAttachment2 = "none"; } if ( loadoutPerk1 != "specialty_onemanarmy" && loadoutSecondary == "onemanarmy" ) { loadoutSecondary = maps\mp\gametypes\_class::table_getWeapon( level.classTableName, 10, 1 ); } //loadoutSecondaryCamo = "none"; // stop default class op'ness allowOp = ( getDvarInt( "bots_loadout_allow_op" ) >= 1 ); if ( !allowOp ) { loadoutDeathstreak = "specialty_null"; if ( loadoutPrimary == "riotshield" ) { loadoutPrimary = "m4"; } if ( loadoutSecondary == "at4" ) { loadoutSecondary = "usp"; } if ( loadoutPrimaryAttachment == "gl" ) { loadoutPrimaryAttachment = "none"; } if ( loadoutPerk2 == "specialty_coldblooded" ) { loadoutPerk2 = "specialty_null"; } if ( loadoutPerk3 == "specialty_localjammer" ) { loadoutPerk3 = "specialty_null"; } } if ( level.killstreakRewards ) { if ( getDvarInt( "scr_classic" ) == 1 ) { loadoutKillstreak1 = "uav"; loadoutKillstreak2 = "precision_airstrike"; loadoutKillstreak3 = "helicopter"; } else { loadoutKillstreak1 = self getPlayerData( "killstreaks", 0 ); loadoutKillstreak2 = self getPlayerData( "killstreaks", 1 ); loadoutKillstreak3 = self getPlayerData( "killstreaks", 2 ); } } else { loadoutKillstreak1 = "none"; loadoutKillstreak2 = "none"; loadoutKillstreak3 = "none"; } secondaryName = maps\mp\gametypes\_class::buildWeaponName( loadoutSecondary, loadoutSecondaryAttachment, loadoutSecondaryAttachment2 ); self _giveWeapon( secondaryName, int( tableLookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 ) ) ); self.loadoutPrimaryCamo = int( tableLookup( "mp/camoTable.csv", 1, loadoutPrimaryCamo, 0 ) ); self.loadoutPrimary = loadoutPrimary; self.loadoutSecondary = loadoutSecondary; self.loadoutSecondaryCamo = int( tableLookup( "mp/camoTable.csv", 1, loadoutSecondaryCamo, 0 ) ); self SetOffhandPrimaryClass( "other" ); // Action Slots //self _SetActionSlot( 1, "" ); self _SetActionSlot( 1, "nightvision" ); self _SetActionSlot( 3, "altMode" ); self _SetActionSlot( 4, "" ); // Perks self _clearPerks(); self maps\mp\gametypes\_class::_detachAll(); // these special case giving pistol death have to come before // perk loadout to ensure player perk icons arent overwritten if ( level.dieHardMode ) { self maps\mp\perks\_perks::givePerk( "specialty_pistoldeath" ); } // only give the deathstreak for the initial spawn for this life. if ( loadoutDeathStreak != "specialty_null" && ( getTime() - self.spawnTime ) < 0.1 ) { deathVal = int( tableLookup( "mp/perkTable.csv", 1, loadoutDeathStreak, 6 ) ); if ( self botGetPerkUpgrade( loadoutPerk1 ) == "specialty_rollover" || self botGetPerkUpgrade( loadoutPerk2 ) == "specialty_rollover" || self botGetPerkUpgrade( loadoutPerk3 ) == "specialty_rollover" ) { deathVal -= 1; } if ( self.pers["cur_death_streak"] == deathVal ) { self thread maps\mp\perks\_perks::givePerk( loadoutDeathStreak ); self thread maps\mp\gametypes\_hud_message::splashNotify( loadoutDeathStreak ); } else if ( self.pers["cur_death_streak"] > deathVal ) { self thread maps\mp\perks\_perks::givePerk( loadoutDeathStreak ); } } self botLoadoutAllPerks( loadoutEquipment, loadoutPerk1, loadoutPerk2, loadoutPerk3 ); self maps\mp\gametypes\_class::setKillstreaks( loadoutKillstreak1, loadoutKillstreak2, loadoutKillstreak3 ); if ( self hasPerk( "specialty_extraammo", true ) && getWeaponClass( secondaryName ) != "weapon_projectile" ) { self giveMaxAmmo( secondaryName ); } // Primary Weapon primaryName = maps\mp\gametypes\_class::buildWeaponName( loadoutPrimary, loadoutPrimaryAttachment, loadoutPrimaryAttachment2 ); self _giveWeapon( primaryName, self.loadoutPrimaryCamo ); // fix changing from a riotshield class to a riotshield class during grace period not giving a shield if ( primaryName == "riotshield_mp" && level.inGracePeriod ) { self notify ( "weapon_change", "riotshield_mp" ); } if ( self hasPerk( "specialty_extraammo", true ) ) { self giveMaxAmmo( primaryName ); } self setSpawnWeapon( primaryName ); primaryTokens = strtok( primaryName, "_" ); self.pers["primaryWeapon"] = primaryTokens[0]; // Primary Offhand was given by givePerk (it's your perk1) // Secondary Offhand offhandSecondaryWeapon = loadoutOffhand + "_mp"; if ( loadoutOffhand == "flash_grenade" ) { self SetOffhandSecondaryClass( "flash" ); } else { self SetOffhandSecondaryClass( "smoke" ); } self giveWeapon( offhandSecondaryWeapon ); if ( loadOutOffhand == "smoke_grenade" ) { self setWeaponAmmoClip( offhandSecondaryWeapon, 1 ); } else if ( loadOutOffhand == "flash_grenade" ) { self setWeaponAmmoClip( offhandSecondaryWeapon, 2 ); } else if ( loadOutOffhand == "concussion_grenade" ) { self setWeaponAmmoClip( offhandSecondaryWeapon, 2 ); } else { self setWeaponAmmoClip( offhandSecondaryWeapon, 1 ); } primaryWeapon = primaryName; self.primaryWeapon = primaryWeapon; self.secondaryWeapon = secondaryName; self botPlayerModelForWeapon( self.pers["primaryWeapon"], getBaseWeaponName( secondaryName ) ); self.isSniper = ( weaponClass( self.primaryWeapon ) == "sniper" ); self maps\mp\gametypes\_weapons::updateMoveSpeedScale( "primary" ); // cac specialties that require loop threads self maps\mp\perks\_perks::cac_selector(); self notify ( "changed_kit" ); self notify( "bot_giveLoadout", allowCopycat ); } /* Patches giveLoadout so that it doesn't use IsItemUnlocked */ botGetPerkUpgrade( perkName ) { perkUpgrade = tablelookup( "mp/perktable.csv", 1, perkName, 8 ); if ( perkUpgrade == "" || perkUpgrade == "specialty_null" ) { return "specialty_null"; } if ( !isDefined( self.pers["bots"]["unlocks"]["upgraded_" + perkName] ) || !self.pers["bots"]["unlocks"]["upgraded_" + perkName] ) { return "specialty_null"; } return ( perkUpgrade ); } /* Patches giveLoadout so that it doesn't use IsItemUnlocked */ botLoadoutAllPerks( loadoutEquipment, loadoutPerk1, loadoutPerk2, loadoutPerk3 ) { loadoutEquipment = maps\mp\perks\_perks::validatePerk( 1, loadoutEquipment ); loadoutPerk1 = maps\mp\perks\_perks::validatePerk( 1, loadoutPerk1 ); loadoutPerk2 = maps\mp\perks\_perks::validatePerk( 2, loadoutPerk2 ); loadoutPerk3 = maps\mp\perks\_perks::validatePerk( 3, loadoutPerk3 ); self maps\mp\perks\_perks::givePerk( loadoutEquipment ); self maps\mp\perks\_perks::givePerk( loadoutPerk1 ); self maps\mp\perks\_perks::givePerk( loadoutPerk2 ); self maps\mp\perks\_perks::givePerk( loadoutPerk3 ); perks[0] = loadoutPerk1; perks[1] = loadoutPerk2; perks[2] = loadoutPerk3; perkUpgrd[0] = tablelookup( "mp/perktable.csv", 1, loadoutPerk1, 8 ); perkUpgrd[1] = tablelookup( "mp/perktable.csv", 1, loadoutPerk2, 8 ); perkUpgrd[2] = tablelookup( "mp/perktable.csv", 1, loadoutPerk3, 8 ); for ( i = 0; i < perkUpgrd.size; i++ ) { upgrade = perkUpgrd[i]; perk = perks[i]; if ( upgrade == "" || upgrade == "specialty_null" ) { continue; } if ( isDefined( self.pers["bots"]["unlocks"]["upgraded_" + perk] ) && self.pers["bots"]["unlocks"]["upgraded_" + perk] ) { self maps\mp\perks\_perks::givePerk( upgrade ); } } } /* Patches giveLoadout so that it doesn't use IsItemUnlocked */ botPlayerModelForWeapon( weapon, secondary ) { team = self.team; if ( isDefined( game[team + "_model"][weapon] ) ) { [[game[team + "_model"][weapon]]](); return; } weaponClass = tablelookup( "mp/statstable.csv", 4, weapon, 2 ); switch ( weaponClass ) { case "weapon_smg": [[game[team + "_model"]["SMG"]]](); break; case "weapon_assault": weaponClass = tablelookup( "mp/statstable.csv", 4, secondary, 2 ); if ( weaponClass == "weapon_shotgun" ) { [[game[team + "_model"]["SHOTGUN"]]](); } else { [[game[team + "_model"]["ASSAULT"]]](); } break; case "weapon_sniper": if ( level.environment != "" && isDefined( self.pers["bots"]["unlocks"]["ghillie"] ) && self.pers["bots"]["unlocks"]["ghillie"] ) { [[game[team + "_model"]["GHILLIE"]]](); } else { [[game[team + "_model"]["SNIPER"]]](); } break; case "weapon_lmg": [[game[team + "_model"]["LMG"]]](); break; case "weapon_riot": [[game[team + "_model"]["RIOT"]]](); break; default: [[game[team + "_model"]["ASSAULT"]]](); break; } }