diff --git a/scripts/sp/T4ZM_zbots_main.gsc b/scripts/sp/T4ZM_zbots_main.gsc index 2891e7e..f86c926 100644 --- a/scripts/sp/T4ZM_zbots_main.gsc +++ b/scripts/sp/T4ZM_zbots_main.gsc @@ -15,7 +15,7 @@ init() if ( !getDvarInt( "bots_main" ) ) return; - thread load_waypoints(); + //thread load_waypoints(); //Don't call for now thread hook_callbacks(); if ( getDvar( "bots_main_GUIDs" ) == "" ) @@ -144,8 +144,8 @@ onPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, { if ( self is_bot() ) { - //self maps\mp\bots\_bot_internal::onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, iModelIndex, timeOffset ); - //self maps\mp\bots\_bot_script::onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, iModelIndex, timeOffset ); + //self scripts\sp\bots\_bot_internal::onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, iModelIndex, timeOffset ); + self scripts\sp\bots\_bot_script::onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, iModelIndex, timeOffset ); } self [[level.prevCallbackPlayerDamage]]( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, iModelIndex, timeOffset ); @@ -222,8 +222,8 @@ connected() self thread added(); } - //self thread maps\mp\bots\_bot_internal::connected(); - //self thread maps\mp\bots\_bot_script::connected(); + self thread scripts\sp\bots\_bot_internal::connected(); + self thread scripts\sp\bots\_bot_script::connected(); level.bots[level.bots.size] = self; self thread onDisconnect(); @@ -258,8 +258,8 @@ added() { self endon( "disconnect" ); - //self thread maps\mp\bots\_bot_internal::added(); - //self thread maps\mp\bots\_bot_script::added(); + self thread scripts\sp\bots\_bot_internal::added(); + //self thread scripts\sp\bots\_bot_script::added(); } /* diff --git a/scripts/sp/bots/_bot_internal.gsc b/scripts/sp/bots/_bot_internal.gsc new file mode 100644 index 0000000..308404f --- /dev/null +++ b/scripts/sp/bots/_bot_internal.gsc @@ -0,0 +1,2001 @@ +#include common_scripts\utility; +#include maps\_utility; +#include scripts\sp\bots\_bot_utility; + +/* + When a bot is added (once ever) to the game (before connected). + We init all the persistent variables here. +*/ +added() +{ + self endon( "disconnect" ); + + self.pers["bots"] = []; + + self.pers["bots"]["skill"] = []; + self.pers["bots"]["skill"]["base"] = 7; // a base knownledge of the bot + self.pers["bots"]["skill"]["aim_time"] = 0.05; // how long it takes for a bot to aim to a location + self.pers["bots"]["skill"]["init_react_time"] = 0; // the reaction time of the bot for inital targets + self.pers["bots"]["skill"]["reaction_time"] = 0; // reaction time for the bots of reoccuring targets + self.pers["bots"]["skill"]["no_trace_ads_time"] = 2500; // how long a bot ads's when they cant see the target + self.pers["bots"]["skill"]["no_trace_look_time"] = 10000; // how long a bot will look at a target's last position + self.pers["bots"]["skill"]["remember_time"] = 25000; // how long a bot will remember a target before forgetting about it when they cant see the target + self.pers["bots"]["skill"]["fov"] = -1; // the fov of the bot, -1 being 360, 1 being 0 + self.pers["bots"]["skill"]["dist_max"] = 100000 * 2; // the longest distance a bot will target + self.pers["bots"]["skill"]["dist_start"] = 100000; // the start distance before bot's target abilitys diminish + self.pers["bots"]["skill"]["spawn_time"] = 0; // how long a bot waits after spawning before targeting, etc + self.pers["bots"]["skill"]["help_dist"] = 10000; // how far a bot has awareness + self.pers["bots"]["skill"]["semi_time"] = 0.05; // how fast a bot shoots semiauto + self.pers["bots"]["skill"]["shoot_after_time"] = 1; // how long a bot shoots after target dies/cant be seen + self.pers["bots"]["skill"]["aim_offset_time"] = 1; // how long a bot correct's their aim after targeting + self.pers["bots"]["skill"]["aim_offset_amount"] = 1; // how far a bot's incorrect aim is + self.pers["bots"]["skill"]["bone_update_interval"] = 0.05; // how often a bot changes their bone target + self.pers["bots"]["skill"]["bones"] = "j_head"; // a list of comma seperated bones the bot will aim at + self.pers["bots"]["skill"]["ads_fov_multi"] = 0.5; // a factor of how much ads to reduce when adsing + self.pers["bots"]["skill"]["ads_aimspeed_multi"] = 0.5; // a factor of how much more aimspeed delay to add + + self.pers["bots"]["behavior"] = []; + self.pers["bots"]["behavior"]["strafe"] = 50; // percentage of how often the bot strafes a target + self.pers["bots"]["behavior"]["nade"] = 50; // percentage of how often the bot will grenade + self.pers["bots"]["behavior"]["sprint"] = 50; // percentage of how often the bot will sprint + self.pers["bots"]["behavior"]["camp"] = 50; // percentage of how often the bot will camp + self.pers["bots"]["behavior"]["follow"] = 50; // percentage of how often the bot will follow + self.pers["bots"]["behavior"]["crouch"] = 10; // percentage of how often the bot will crouch + self.pers["bots"]["behavior"]["switch"] = 1; // percentage of how often the bot will switch weapons + self.pers["bots"]["behavior"]["class"] = 1; // percentage of how often the bot will change classes + self.pers["bots"]["behavior"]["jump"] = 100; // percentage of how often the bot will jumpshot and dropshot + + self.pers["bots"]["behavior"]["quickscope"] = false; // is a quickscoper + self.pers["bots"]["behavior"]["initswitch"] = 10; // percentage of how often the bot will switch weapons on spawn +} + +/* + We clear all of the script variables and other stuff for the bots. +*/ +resetBotVars() +{ + self.bot.script_target = undefined; + self.bot.script_target_offset = undefined; + self.bot.target = undefined; + self.bot.targets = []; + self.bot.target_this_frame = undefined; + self.bot.after_target = undefined; + self.bot.after_target_pos = undefined; + self.bot.moveTo = self.origin; + + self.bot.script_aimpos = undefined; + + self.bot.script_goal = undefined; + self.bot.script_goal_dist = 0.0; + + self.bot.next_wp = -1; + self.bot.second_next_wp = -1; + self.bot.towards_goal = undefined; + self.bot.astar = []; + self.bot.stop_move = false; + self.bot.greedy_path = false; + self.bot.climbing = false; + self.bot.wantsprint = false; + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + + self.bot.isfrozen = false; + self.bot.sprintendtime = -1; + self.bot.isreloading = false; + self.bot.issprinting = false; + self.bot.isfragging = false; + self.bot.issmoking = false; + self.bot.isfraggingafter = false; + self.bot.issmokingafter = false; + self.bot.isknifing = false; + self.bot.isknifingafter = false; + + self.bot.semi_time = false; + self.bot.jump_time = undefined; + self.bot.last_fire_time = -1; + + self.bot.is_cur_full_auto = false; + self.bot.cur_weap_dist_multi = 1; + self.bot.is_cur_sniper = false; + + self.bot.rand = randomInt( 100 ); + + self botStop(); +} + + +/* + When a bot connects to the game. + This is called when a bot is added and when multiround gamemode starts. +*/ +connected() +{ + self endon( "disconnect" ); + + self.bot = spawnStruct(); + self resetBotVars(); + + self thread onPlayerSpawned(); +} + +/* + When the bot spawns. +*/ +onPlayerSpawned() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "spawned_player" ); + + self resetBotVars(); + self thread onWeaponChange(); + + self thread reload_watch(); + self thread sprint_watch(); + + self thread spawned(); + } +} + +/* + When the bot changes weapon. +*/ +onWeaponChange() +{ + self endon( "disconnect" ); + self endon( "death" ); + + first = true; + + for ( ;; ) + { + newWeapon = undefined; + + if ( first ) + { + first = false; + newWeapon = self getCurrentWeapon(); + } + else + self waittill( "weapon_change", newWeapon ); + + self.bot.is_cur_full_auto = WeaponIsFullAuto( newWeapon ); + self.bot.cur_weap_dist_multi = SetWeaponDistMulti( newWeapon ); + self.bot.is_cur_sniper = /* IsWeapSniper( newWeapon ) */ false; + } +} + +/* + Sets the factor of distance for a weapon +*/ +SetWeaponDistMulti( weap ) +{ + if ( weap == "none" ) + return 1; + + switch ( weaponClass( weap ) ) + { + case "rifle": + return 0.9; + + case "smg": + return 0.7; + + case "pistol": + return 0.5; + + default: + return 1; + } +} + +/* + Update's the bot if it is reloading. +*/ +reload_watch() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "reload_start" ); + + self reload_watch_loop(); + } +} + +/* + Update's the bot if it is reloading. +*/ +reload_watch_loop() +{ + self.bot.isreloading = true; + + while ( true ) + { + ret = self waittill_any_timeout( 7.5, "reload" ); + + if ( ret == "timeout" ) + break; + + weap = self GetCurrentWeapon(); + + if ( weap == "none" ) + break; + + if ( self GetWeaponAmmoClip( weap ) >= WeaponClipSize( weap ) ) + break; + } + + self.bot.isreloading = false; +} + +/* + Updates the bot if it is sprinting. +*/ +sprint_watch() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "sprint_begin" ); + self.bot.issprinting = true; + self waittill( "sprint_end" ); + self.bot.issprinting = false; + self.bot.sprintendtime = getTime(); + } +} + +/* + We wait for a time defined by the bot's difficulty and start all threads that control the bot. +*/ +spawned() +{ + self endon( "disconnect" ); + self endon( "death" ); + + wait self.pers["bots"]["skill"]["spawn_time"]; + + self thread doBotMovement(); + // self thread grenade_danger(); + // self thread check_reload(); + self thread stance(); + self thread walk(); + self thread target(); + self thread updateBones(); + self thread aim(); + // self thread watchHoldBreath(); + self thread onNewEnemy(); + // self thread watchGrenadeFire(); + // self thread watchPickupGun(); + + self notify( "bot_spawned" ); +} + +/* + Bot moves towards the point +*/ +doBotMovement() +{ + self endon( "disconnect" ); + self endon( "death" ); + + data = spawnStruct(); + data.wasMantling = false; + + for ( data.i = 0; true; data.i += 0.05 ) + { + wait 0.05; + + waittillframeend; + self doBotMovement_loop( data ); + } +} + +/* + Bot moves towards the point +*/ +doBotMovement_loop( data ) +{ + move_To = self.bot.moveTo; + angles = self GetPlayerAngles(); + dir = ( 0, 0, 0 ); + + if ( DistanceSquared( self.origin, move_To ) >= 49 ) + { + cosa = cos( 0 - angles[1] ); + sina = sin( 0 - angles[1] ); + + // get the direction + dir = move_To - self.origin; + + // rotate our direction according to our angles + dir = ( dir[0] * cosa - dir[1] * sina, + dir[0] * sina + dir[1] * cosa, + 0 ); + + // make the length 127 + dir = VectorNormalize( dir ) * 127; + + // invert the second component as the engine requires this + dir = ( dir[0], 0 - dir[1], 0 ); + } + + startPos = self.origin + ( 0, 0, 50 ); + startPosForward = startPos + anglesToForward( ( 0, angles[1], 0 ) ) * 25; + bt = bulletTrace( startPos, startPosForward, false, self ); + + if ( bt["fraction"] >= 1 ) + { + // check if need to jump + bt = bulletTrace( startPosForward, startPosForward - ( 0, 0, 40 ), false, self ); + + if ( bt["fraction"] < 1 && bt["normal"][2] > 0.9 && data.i > 1.5 ) + { + data.i = 0; + self thread jump(); + } + } + // check if need to knife glass + else if ( bt["surfacetype"] == "glass" ) + { + if ( data.i > 1.5 ) + { + data.i = 0; + self thread knife(); + } + } + else + { + // check if need to crouch + if ( bulletTracePassed( startPos - ( 0, 0, 25 ), startPosForward - ( 0, 0, 25 ), false, self ) && !self.bot.climbing ) + self crouch(); + } + + // move! + if ( self.bot.wantsprint && self.bot.issprinting ) + dir = ( 127, dir[1], 0 ); + + self botMovement( int( dir[0] ), int( dir[1] ) ); +} + +/* + Bots will update its needed stance according to the nodes on the level. Will also allow the bot to sprint when it can. +*/ +stance_loop() +{ + self.bot.climbing = false; + + if ( self.bot.isfrozen ) + return; + + toStance = "stand"; + + if ( self.bot.next_wp != -1 ) + toStance = level.waypoints[self.bot.next_wp].type; + + if ( !isDefined( toStance ) ) + toStance = "crouch"; + + if ( toStance == "stand" && randomInt( 100 ) <= self.pers["bots"]["behavior"]["crouch"] ) + toStance = "crouch"; + + if ( toStance == "climb" ) + { + self.bot.climbing = true; + toStance = "stand"; + } + + if ( toStance != "stand" && toStance != "crouch" && toStance != "prone" ) + toStance = "crouch"; + + if ( toStance == "stand" ) + self stand(); + else if ( toStance == "crouch" ) + self crouch(); + else + self prone(); + + curweap = self getCurrentWeapon(); + time = getTime(); + chance = self.pers["bots"]["behavior"]["sprint"]; + + if ( time - self.lastSpawnTime < 5000 ) + chance *= 2; + + if ( isDefined( self.bot.script_goal ) && DistanceSquared( self.origin, self.bot.script_goal ) > 256 * 256 ) + chance *= 2; + + if ( toStance != "stand" || self.bot.isreloading || self.bot.issprinting || self.bot.isfraggingafter || self.bot.issmokingafter ) + return; + + if ( randomInt( 100 ) > chance ) + return; + + if ( isDefined( self.bot.target ) && self canFire( curweap ) && self isInRange( self.bot.target.dist, curweap ) ) + return; + + if ( self.bot.sprintendtime != -1 && time - self.bot.sprintendtime < 2000 ) + return; + + if ( !isDefined( self.bot.towards_goal ) || DistanceSquared( self.origin, physicsTrace( self getEyePos(), self getEyePos() + anglesToForward( self getPlayerAngles() ) * 1024, false, undefined ) ) < level.bots_minSprintDistance || getConeDot( self.bot.towards_goal, self.origin, self GetPlayerAngles() ) < 0.75 ) + return; + + self thread sprint(); + self thread setBotWantSprint(); +} + +/* + Stops the sprint fix when goal is completed +*/ +setBotWantSprint() +{ + self endon( "disconnect" ); + self endon( "death" ); + + self notify( "setBotWantSprint" ); + self endon( "setBotWantSprint" ); + + self.bot.wantsprint = true; + + self waittill_notify_or_timeout( "kill_goal", 10 ); + + self.bot.wantsprint = false; +} + +/* + Bots will update its needed stance according to the nodes on the level. Will also allow the bot to sprint when it can. +*/ +stance() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill_either( "finished_static_waypoints", "new_static_waypoint" ); + + self stance_loop(); + } +} + +/* + This is the main walking logic for the bot. +*/ +walk() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait 0.05; + + self botMoveTo( self.origin ); + + if ( !getDvarInt( "bots_play_move" ) ) + continue; + + if ( self.bot.isfrozen || self.bot.stop_move ) + continue; + + self walk_loop(); + } +} + +/* + Bot will move towards here +*/ +botMoveTo( where ) +{ + self.bot.moveTo = where; +} + +/* + This is the main walking logic for the bot. +*/ +walk_loop() +{ + hasTarget = isDefined( self.bot.target ) && isDefined( self.bot.target.entity ); + + if ( hasTarget ) + { + curweap = self getCurrentWeapon(); + + if ( self.bot.isfraggingafter || self.bot.issmokingafter ) + { + return; + } + + if ( self.bot.target.isplay && self.bot.target.trace_time && self canFire( curweap ) && self isInRange( self.bot.target.dist, curweap ) ) + { + if ( self InLastStand() || self GetStance() == "prone" || ( self.bot.is_cur_sniper && self PlayerADS() > 0 ) ) + return; + + if ( self.bot.target.rand <= self.pers["bots"]["behavior"]["strafe"] ) + self strafe( self.bot.target.entity ); + + return; + } + } + + dist = 16; + + if ( level.waypointCount ) + goal = level.waypoints[randomInt( level.waypointCount )].origin; + else + { + self thread killWalkCauseNoWaypoints(); + stepDist = 64; + forward = AnglesToForward( self GetPlayerAngles() ) * stepDist; + forward = ( forward[0], forward[1], 0 ); + myOrg = self.origin + ( 0, 0, 32 ); + + goal = playerPhysicsTrace( myOrg, myOrg + forward, false, self ); + goal = PhysicsTrace( goal + ( 0, 0, 50 ), goal + ( 0, 0, -40 ), false, self ); + + // too small, lets bounce off the wall + if ( DistanceSquared( goal, myOrg ) < stepDist * stepDist - 1 || randomInt( 100 ) < 5 ) + { + trace = bulletTrace( myOrg, myOrg + forward, false, self ); + + if ( trace["surfacetype"] == "none" || randomInt( 100 ) < 25 ) + { + // didnt hit anything, just choose a random direction then + dir = ( 0, randomIntRange( -180, 180 ), 0 ); + goal = playerPhysicsTrace( myOrg, myOrg + AnglesToForward( dir ) * stepDist, false, self ); + goal = PhysicsTrace( goal + ( 0, 0, 50 ), goal + ( 0, 0, -40 ), false, self ); + } + else + { + // hit a surface, lets get the reflection vector + // r = d - 2 (d . n) n + d = VectorNormalize( trace["position"] - myOrg ); + n = trace["normal"]; + + r = d - 2 * ( VectorDot( d, n ) ) * n; + + goal = playerPhysicsTrace( myOrg, myOrg + ( r[0], r[1], 0 ) * stepDist, false, self ); + goal = PhysicsTrace( goal + ( 0, 0, 50 ), goal + ( 0, 0, -40 ), false, self ); + } + } + } + + isScriptGoal = false; + + if ( isDefined( self.bot.script_goal ) && !hasTarget ) + { + goal = self.bot.script_goal; + dist = self.bot.script_goal_dist; + + isScriptGoal = true; + } + else + { + if ( hasTarget ) + goal = self.bot.target.last_seen_pos; + + self notify( "new_goal_internal" ); + } + + self doWalk( goal, dist, isScriptGoal ); + self.bot.towards_goal = undefined; + self.bot.next_wp = -1; + self.bot.second_next_wp = -1; +} + +/* + Will walk to the given goal when dist near. Uses AStar path finding with the level's nodes. +*/ +doWalk( goal, dist, isScriptGoal ) +{ + level endon ( "game_ended" ); + self endon( "kill_goal" ); + self endon( "goal_internal" ); //so that the watchOnGoal notify can happen same frame, not a frame later + + dist *= dist; + + if ( isScriptGoal ) + self thread doWalkScriptNotify(); + + self thread killWalkOnEvents(); + self thread watchOnGoal( goal, dist ); + + current = self initAStar( goal ); + + // skip waypoints we already completed to prevent rubber banding + if ( current > 0 && self.bot.astar[current] == self.bot.last_next_wp && self.bot.astar[current - 1] == self.bot.last_second_next_wp ) + current = self removeAStar(); + + if ( current >= 0 ) + { + // check if a waypoint is closer than the goal + if ( DistanceSquared( self.origin, level.waypoints[self.bot.astar[current]].origin ) < DistanceSquared( self.origin, goal ) || DistanceSquared( level.waypoints[self.bot.astar[current]].origin, PlayerPhysicsTrace( self.origin + ( 0, 0, 32 ), level.waypoints[self.bot.astar[current]].origin, false, self ) ) > 1.0 ) + { + while ( current >= 0 ) + { + self.bot.next_wp = self.bot.astar[current]; + self.bot.second_next_wp = -1; + + if ( current > 0 ) + self.bot.second_next_wp = self.bot.astar[current - 1]; + + self notify( "new_static_waypoint" ); + + self movetowards( level.waypoints[self.bot.next_wp].origin ); + self.bot.last_next_wp = self.bot.next_wp; + self.bot.last_second_next_wp = self.bot.second_next_wp; + + current = self removeAStar(); + } + } + } + + self.bot.next_wp = -1; + self.bot.second_next_wp = -1; + self notify( "finished_static_waypoints" ); + + if ( DistanceSquared( self.origin, goal ) > dist ) + { + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + self movetowards( goal ); // any better way?? + } + + self notify( "finished_goal" ); + + wait 1; + + if ( DistanceSquared( self.origin, goal ) > dist ) + self notify( "bad_path_internal" ); +} + +/* + Does the notify for goal completion for outside scripts +*/ +doWalkScriptNotify() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "kill_goal" ); + + if ( self waittill_either_return( "goal_internal", "bad_path_internal" ) == "goal_internal" ) + self notify( "goal" ); + else + self notify( "bad_path" ); +} + +/* + Will kill the goal when the bot made it to its goal. +*/ +watchOnGoal( goal, dis ) +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "kill_goal" ); + + while ( DistanceSquared( self.origin, goal ) > dis ) + wait 0.05; + + self notify( "goal_internal" ); +} + +/* + Calls the astar search algorithm for the path to the goal. +*/ +initAStar( goal ) +{ + team = undefined; + + if ( level.teamBased ) + team = self.team; + + self.bot.astar = AStarSearch( self.origin, goal, team, self.bot.greedy_path ); + + if ( isDefined( team ) ) + self thread cleanUpAStar( team ); + + return self.bot.astar.size - 1; +} + +/* + Cleans up the astar nodes when the goal is killed. +*/ +cleanUpAStar( team ) +{ + self waittill_any( "death", "disconnect", "kill_goal" ); + /* + for ( i = self.bot.astar.size - 1; i >= 0; i-- ) + RemoveWaypointUsage( self.bot.astar[i], team ); + */ +} + +/* + Cleans up the astar nodes for one node. +*/ +removeAStar() +{ + remove = self.bot.astar.size - 1; + + /* + if ( level.teamBased ) + RemoveWaypointUsage( self.bot.astar[remove], self.team ); + */ + + self.bot.astar[remove] = undefined; + + return self.bot.astar.size - 1; +} + +/* + Will move towards the given goal. Will try to not get stuck by crouching, then jumping and then strafing around objects. +*/ +movetowards( goal ) +{ + if ( !isDefined( goal ) ) + return; + + self.bot.towards_goal = goal; + + lastOri = self.origin; + stucks = 0; + timeslow = 0; + time = 0; + + if ( self.bot.issprinting ) + tempGoalDist = level.bots_goalDistance * 2; + else + tempGoalDist = level.bots_goalDistance; + + while ( distanceSquared( self.origin, goal ) > tempGoalDist ) + { + self botMoveTo( goal ); + + if ( time > 3000 ) + { + time = 0; + + if ( distanceSquared( self.origin, lastOri ) < 32 * 32 ) + { + self thread knife(); + wait 0.5; + + stucks++; + + randomDir = self getRandomLargestStafe( stucks ); + + self BotNotifyBotEvent( "stuck" ); + + self botMoveTo( randomDir ); + wait stucks; + self stand(); + + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + } + + lastOri = self.origin; + } + else if ( timeslow > 0 && ( timeslow % 1000 ) == 0 ) + { + self thread doMantle(); + + // door open hack + if ( getDvar( "mapname" ) == "mp_lapatrouille" ) + { + self thread use( 0.5 ); + } + } + else if ( time == 2000 ) + { + if ( distanceSquared( self.origin, lastOri ) < 32 * 32 ) + self crouch(); + } + else if ( time == 1750 ) + { + if ( distanceSquared( self.origin, lastOri ) < 32 * 32 ) + { + // check if directly above or below + if ( abs( goal[2] - self.origin[2] ) > 64 && getConeDot( goal + ( 1, 1, 0 ), self.origin + ( -1, -1, 0 ), VectorToAngles( ( goal[0], goal[1], self.origin[2] ) - self.origin ) ) < 0.64 && DistanceSquared2D( self.origin, goal ) < 32 * 32 ) + { + stucks = 2; + } + } + } + + wait 0.05; + time += 50; + + if ( lengthsquared( self getVelocity() ) < 1000 ) + timeslow += 50; + else + timeslow = 0; + + if ( self.bot.issprinting ) + tempGoalDist = level.bots_goalDistance * 2; + else + tempGoalDist = level.bots_goalDistance; + + if ( stucks >= 2 ) + self notify( "bad_path_internal" ); + } + + self.bot.towards_goal = undefined; + self notify( "completed_move_to" ); +} + +/* + Bots do the mantle +*/ +doMantle() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "kill_goal" ); + + self jump(); + + wait 0.35; + + self jump(); +} + +/* + Will return the pos of the largest trace from the bot. +*/ +getRandomLargestStafe( dist ) +{ + //find a better algo? + traces = NewHeap( ::HeapTraceFraction ); + myOrg = self.origin + ( 0, 0, 16 ); + + traces HeapInsert( bulletTrace( myOrg, myOrg + ( -100 * dist, 0, 0 ), false, self ) ); + traces HeapInsert( bulletTrace( myOrg, myOrg + ( 100 * dist, 0, 0 ), false, self ) ); + traces HeapInsert( bulletTrace( myOrg, myOrg + ( 0, 100 * dist, 0 ), false, self ) ); + traces HeapInsert( bulletTrace( myOrg, myOrg + ( 0, -100 * dist, 0 ), false, self ) ); + traces HeapInsert( bulletTrace( myOrg, myOrg + ( -100 * dist, -100 * dist, 0 ), false, self ) ); + traces HeapInsert( bulletTrace( myOrg, myOrg + ( -100 * dist, 100 * dist, 0 ), false, self ) ); + traces HeapInsert( bulletTrace( myOrg, myOrg + ( 100 * dist, -100 * dist, 0 ), false, self ) ); + traces HeapInsert( bulletTrace( myOrg, myOrg + ( 100 * dist, 100 * dist, 0 ), false, self ) ); + + toptraces = []; + + top = traces.data[0]; + toptraces[toptraces.size] = top; + traces HeapRemove(); + + while ( traces.data.size && top["fraction"] - traces.data[0]["fraction"] < 0.1 ) + { + toptraces[toptraces.size] = traces.data[0]; + traces HeapRemove(); + } + + return toptraces[randomInt( toptraces.size )]["position"]; +} + +killWalkCauseNoWaypoints() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "kill_goal" ); + + wait 2; + + self notify( "kill_goal" ); +} + +/* + The bot will strafe left or right from their enemy. +*/ +strafe( target ) +{ + self endon( "kill_goal" ); + self thread killWalkOnEvents(); + + angles = VectorToAngles( vectorNormalize( target.origin - self.origin ) ); + anglesLeft = ( 0, angles[1] + 90, 0 ); + anglesRight = ( 0, angles[1] - 90, 0 ); + + myOrg = self.origin + ( 0, 0, 16 ); + left = myOrg + anglestoforward( anglesLeft ) * 500; + right = myOrg + anglestoforward( anglesRight ) * 500; + + traceLeft = BulletTrace( myOrg, left, false, self ); + traceRight = BulletTrace( myOrg, right, false, self ); + + strafe = traceLeft["position"]; + + if ( traceRight["fraction"] > traceLeft["fraction"] ) + strafe = traceRight["position"]; + + self.bot.last_next_wp = -1; + self.bot.last_second_next_wp = -1; + self botMoveTo( strafe ); + wait 2; + self notify( "kill_goal" ); +} + +/* + Will stop the goal walk when an enemy is found or flashed or a new goal appeared for the bot. +*/ +killWalkOnEvents() +{ + self endon( "kill_goal" ); + self endon( "disconnect" ); + self endon( "death" ); + + self waittill_any( "flash_rumble_loop", "new_enemy", "new_goal_internal", "goal_internal", "bad_path_internal" ); + + waittillframeend; + + self notify( "kill_goal" ); +} + +/* + The main target thread, will update the bot's main target. Will auto target enemy players and handle script targets. +*/ +target() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait 0.05; + + self target_loop(); + } +} + +/* + The main target thread, will update the bot's main target. Will auto target enemy players and handle script targets. +*/ +target_loop() +{ + myEye = self GetEyePos(); + theTime = getTime(); + myAngles = self GetPlayerAngles(); + myFov = self.pers["bots"]["skill"]["fov"]; + bestTargets = []; + bestTime = 2147483647; + rememberTime = self.pers["bots"]["skill"]["remember_time"]; + initReactTime = self.pers["bots"]["skill"]["init_react_time"]; + hasTarget = isDefined( self.bot.target ); + adsAmount = self PlayerADS(); + adsFovFact = self.pers["bots"]["skill"]["ads_fov_multi"]; + + if ( hasTarget && !isDefined( self.bot.target.entity ) ) + { + self.bot.target = undefined; + hasTarget = false; + } + + // reduce fov if ads'ing + if ( adsAmount > 0 ) + { + myFov *= 1 - adsFovFact * adsAmount; + } + + playercount = level.players.size; + + for ( i = -1; i < playercount; i++ ) + { + obj = undefined; + + if ( i == -1 ) + { + if ( !isDefined( self.bot.script_target ) ) + continue; + + ent = self.bot.script_target; + key = ent getEntityNumber() + ""; + daDist = distanceSquared( self.origin, ent.origin ); + obj = self.bot.targets[key]; + isObjDef = isDefined( obj ); + entOrigin = ent.origin; + + if ( isDefined( self.bot.script_target_offset ) ) + entOrigin += self.bot.script_target_offset; + + if ( !isObjDef ) + continue; + + self targetObjUpdateNoTrace( obj ); + + if ( obj.no_trace_time > rememberTime ) + { + self.bot.targets[key] = undefined; + continue; + } + } + else + { + player = level.players[i]; + + if ( player == self ) + continue; + + key = player getEntityNumber() + ""; + obj = self.bot.targets[key]; + daDist = distanceSquared( self.origin, player.origin ); + isObjDef = isDefined( obj ); + + if ( ( level.teamBased && self.team == player.team ) || player.sessionstate != "playing" || !isAlive( player ) ) + { + if ( isObjDef ) + self.bot.targets[key] = undefined; + + continue; + } + + targetHead = player getTagOrigin( "j_head" ); + targetAnkleLeft = player getTagOrigin( "j_ankle_le" ); + targetAnkleRight = player getTagOrigin( "j_ankle_ri" ); + + traceHead = bulletTrace( myEye, targetHead, false, undefined ); + traceAnkleLeft = bulletTrace( myEye, targetAnkleLeft, false, undefined ); + traceAnkleRight = bulletTrace( myEye, targetAnkleRight, false, undefined ); + + canTargetPlayer = ( ( sightTracePassed( myEye, targetHead, false, undefined ) || + sightTracePassed( myEye, targetAnkleLeft, false, undefined ) || + sightTracePassed( myEye, targetAnkleRight, false, undefined ) ) + + && ( ( traceHead["fraction"] >= 1.0 || traceHead["surfacetype"] == "glass" ) || + ( traceAnkleLeft["fraction"] >= 1.0 || traceAnkleLeft["surfacetype"] == "glass" ) || + ( traceAnkleRight["fraction"] >= 1.0 || traceAnkleRight["surfacetype"] == "glass" ) ) + + && ( daDist < level.bots_maxKnifeDistance * 4 ) + + && ( getConeDot( player.origin, self.origin, myAngles ) >= myFov || + ( isObjDef && obj.trace_time ) ) ); + + if ( isDefined( self.bot.target_this_frame ) && self.bot.target_this_frame == player ) + { + self.bot.target_this_frame = undefined; + + canTargetPlayer = true; + } + + if ( canTargetPlayer ) + { + if ( !isObjDef ) + { + obj = self createTargetObj( player, theTime ); + + self.bot.targets[key] = obj; + } + + self targetObjUpdateTraced( obj, daDist, player, theTime, false ); + } + else + { + if ( !isObjDef ) + continue; + + self targetObjUpdateNoTrace( obj ); + + if ( obj.no_trace_time > rememberTime ) + { + self.bot.targets[key] = undefined; + continue; + } + } + } + + if ( !isdefined( obj ) ) + continue; + + if ( theTime - obj.time < initReactTime ) + continue; + + timeDiff = theTime - obj.trace_time_time; + + if ( timeDiff < bestTime ) + { + bestTargets = []; + bestTime = timeDiff; + } + + if ( timeDiff == bestTime ) + bestTargets[key] = obj; + } + + if ( hasTarget && isDefined( bestTargets[self.bot.target.entity getEntityNumber() + ""] ) ) + return; + + closest = 2147483647; + toBeTarget = undefined; + + bestKeys = getArrayKeys( bestTargets ); + + for ( i = bestKeys.size - 1; i >= 0; i-- ) + { + theDist = bestTargets[bestKeys[i]].dist; + + if ( theDist > closest ) + continue; + + closest = theDist; + toBeTarget = bestTargets[bestKeys[i]]; + } + + beforeTargetID = -1; + newTargetID = -1; + + if ( hasTarget && isDefined( self.bot.target.entity ) ) + beforeTargetID = self.bot.target.entity getEntityNumber(); + + if ( isDefined( toBeTarget ) && isDefined( toBeTarget.entity ) ) + newTargetID = toBeTarget.entity getEntityNumber(); + + if ( beforeTargetID != newTargetID ) + { + self.bot.target = toBeTarget; + self notify( "new_enemy" ); + } +} + +/* + Updates the target object to be not traced No LOS +*/ +targetObjUpdateNoTrace( obj ) +{ + obj.no_trace_time += 50; + obj.trace_time = 0; + obj.didlook = false; +} + +/* + Creates the base target obj +*/ +createTargetObj( ent, theTime ) +{ + obj = spawnStruct(); + obj.entity = ent; + obj.last_seen_pos = ( 0, 0, 0 ); + obj.dist = 0; + obj.time = theTime; + obj.trace_time = 0; + obj.no_trace_time = 0; + obj.trace_time_time = 0; + obj.rand = randomInt( 100 ); + obj.didlook = false; + obj.isplay = isPlayer( ent ); + obj.offset = undefined; + obj.bone = undefined; + obj.aim_offset = undefined; + obj.aim_offset_base = undefined; + + return obj; +} + +/* + Updates the target object to be traced Has LOS +*/ +targetObjUpdateTraced( obj, daDist, ent, theTime, isScriptObj ) +{ + distClose = self.pers["bots"]["skill"]["dist_start"]; + distClose *= self.bot.cur_weap_dist_multi; + distClose *= distClose; + + distMax = self.pers["bots"]["skill"]["dist_max"]; + distMax *= self.bot.cur_weap_dist_multi; + distMax *= distMax; + + timeMulti = 1; + + if ( !isScriptObj ) + { + if ( daDist > distMax ) + timeMulti = 0; + else if ( daDist > distClose ) + timeMulti = 1 - ( ( daDist - distClose ) / ( distMax - distClose ) ); + } + + obj.no_trace_time = 0; + obj.trace_time += int( 50 * timeMulti ); + obj.dist = daDist; + obj.last_seen_pos = ent.origin; + obj.trace_time_time = theTime; + + self updateAimOffset( obj ); +} + +/* + Updates the target object's difficulty missing aim, inaccurate shots +*/ +updateAimOffset( obj ) +{ + if ( !isDefined( obj.aim_offset_base ) ) + { + diffAimAmount = self.pers["bots"]["skill"]["aim_offset_amount"]; + + if ( diffAimAmount > 0 ) + obj.aim_offset_base = ( randomFloatRange( 0 - diffAimAmount, diffAimAmount ), + randomFloatRange( 0 - diffAimAmount, diffAimAmount ), + randomFloatRange( 0 - diffAimAmount, diffAimAmount ) ); + else + obj.aim_offset_base = ( 0, 0, 0 ); + } + + aimDiffTime = self.pers["bots"]["skill"]["aim_offset_time"] * 1000; + objCreatedFor = obj.trace_time; + + if ( objCreatedFor >= aimDiffTime ) + offsetScalar = 0; + else + offsetScalar = 1 - objCreatedFor / aimDiffTime; + + obj.aim_offset = obj.aim_offset_base * offsetScalar; +} + +/* + Updates the bot's target bone +*/ +updateBones() +{ + self endon( "disconnect" ); + self endon( "death" ); + + bones = strtok( self.pers["bots"]["skill"]["bones"], "," ); + waittime = self.pers["bots"]["skill"]["bone_update_interval"]; + + for ( ;; ) + { + self waittill_notify_or_timeout( "new_enemy", waittime ); + + if ( !isDefined( self.bot.target ) ) + continue; + + self.bot.target.bone = PickRandom( bones ); + } +} + +/* + This is the bot's main aimming thread. The bot will aim at its targets or a node its going towards. Bots will aim, fire, ads, grenade. +*/ +aim() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + wait 0.05; + + if ( self.bot.isfrozen ) + continue; + + self aim_loop(); + } +} + +/* + Bots will look at the pos +*/ +bot_lookat( pos, time, vel, doAimPredict ) +{ + self notify( "bots_aim_overlap" ); + self endon( "bots_aim_overlap" ); + self endon( "disconnect" ); + self endon( "death" ); + self endon( "spawned_player" ); + level endon ( "intermission" ); + + if ( level.intermission || self.bot.isfrozen || !getDvarInt( "bots_play_aim" ) ) + return; + + if ( !isDefined( pos ) ) + return; + + if ( !isDefined( doAimPredict ) ) + doAimPredict = false; + + if ( !isDefined( time ) ) + time = 0.05; + + if ( !isDefined( vel ) ) + vel = ( 0, 0, 0 ); + + steps = int( time * 20 ); + + if ( steps < 1 ) + steps = 1; + + myEye = self GetEyePos(); // get our eye pos + + if ( doAimPredict ) + { + myEye += ( self getVelocity() * 0.05 ) * ( steps - 1 ); // account for our velocity + + pos += ( vel * 0.05 ) * ( steps - 1 ); // add the velocity vector + } + + myAngle = self getPlayerAngles(); + angles = VectorToAngles( ( pos - myEye ) - anglesToForward( myAngle ) ); + + X = AngleClamp180( angles[0] - myAngle[0] ); + X = X / steps; + + Y = AngleClamp180( angles[1] - myAngle[1] ); + Y = Y / steps; + + for ( i = 0; i < steps; i++ ) + { + myAngle = ( AngleClamp180(myAngle[0] + X), AngleClamp180(myAngle[1] + Y), 0 ); + self setPlayerAngles( myAngle ); + wait 0.05; + } +} + +/* + This is the bot's main aimming thread. The bot will aim at its targets or a node its going towards. Bots will aim, fire, ads, grenade. +*/ +aim_loop() +{ + aimspeed = self.pers["bots"]["skill"]["aim_time"]; + + eyePos = self getEyePos(); + curweap = self getCurrentWeapon(); + angles = self GetPlayerAngles(); + adsAmount = self PlayerADS(); + adsAimSpeedFact = self.pers["bots"]["skill"]["ads_aimspeed_multi"]; + + // reduce aimspeed if ads'ing + if ( adsAmount > 0 ) + { + aimspeed *= 1 + adsAimSpeedFact * adsAmount; + } + + if ( isDefined( self.bot.target ) && isDefined( self.bot.target.entity ) ) + { + no_trace_time = self.bot.target.no_trace_time; + no_trace_look_time = self.pers["bots"]["skill"]["no_trace_look_time"]; + + if ( no_trace_time <= no_trace_look_time ) + { + trace_time = self.bot.target.trace_time; + last_pos = self.bot.target.last_seen_pos; + target = self.bot.target.entity; + conedot = 0; + isplay = self.bot.target.isplay; + + offset = self.bot.target.offset; + + if ( !isDefined( offset ) ) + offset = ( 0, 0, 0 ); + + aimoffset = self.bot.target.aim_offset; + + if ( !isDefined( aimoffset ) ) + aimoffset = ( 0, 0, 0 ); + + dist = self.bot.target.dist; + rand = self.bot.target.rand; + no_trace_ads_time = self.pers["bots"]["skill"]["no_trace_ads_time"]; + reaction_time = self.pers["bots"]["skill"]["reaction_time"]; + nadeAimOffset = 0; + + bone = self.bot.target.bone; + + if ( !isDefined( bone ) ) + bone = "j_spineupper"; + + if ( self.bot.isfraggingafter || self.bot.issmokingafter ) + nadeAimOffset = dist / 3000; + else if ( curweap != "none" && weaponClass( curweap ) == "grenade" ) + { + nadeAimOffset = dist / 3000; + } + + if ( no_trace_time && ( !isDefined( self.bot.after_target ) || self.bot.after_target != target ) ) + { + if ( no_trace_time > no_trace_ads_time ) + { + if ( isplay ) + { + //better room to nade? cook time function with dist? + if ( !self.bot.isfraggingafter && !self.bot.issmokingafter ) + { + nade = self getValidGrenade(); + + if ( isDefined( nade ) && rand <= self.pers["bots"]["behavior"]["nade"] && bulletTracePassed( eyePos, eyePos + ( 0, 0, 75 ), false, self ) && bulletTracePassed( last_pos, last_pos + ( 0, 0, 100 ), false, target ) && dist > level.bots_minGrenadeDistance && dist < level.bots_maxGrenadeDistance && getDvarInt( "bots_play_nade" ) ) + { + time = 0.5; + + if ( nade == "frag_grenade_mp" ) + time = 2; + + if ( !isSecondaryGrenade( nade ) ) + self thread frag( time ); + else + self thread smoke( time ); + + self notify( "kill_goal" ); + } + } + } + } + else + { + if ( self canAds( dist, curweap ) ) + { + if ( !self.bot.is_cur_sniper || !self.pers["bots"]["behavior"]["quickscope"] ) + self thread pressAds(); + } + } + + self thread bot_lookat( last_pos + ( 0, 0, self getEyeHeight() + nadeAimOffset ), aimspeed ); + return; + } + + if ( trace_time ) + { + if ( isplay ) + { + aimpos = target getTagOrigin( bone ); + + if ( !isDefined( aimpos ) ) + return; + + aimpos += offset; + aimpos += aimoffset; + aimpos += ( 0, 0, nadeAimOffset ); + + conedot = getConeDot( aimpos, eyePos, angles ); + + if ( !nadeAimOffset && conedot > 0.999 && lengthsquared( aimoffset ) < 0.05 ) + self thread bot_lookat( aimpos, 0.05 ); + else + self thread bot_lookat( aimpos, aimspeed, target getVelocity(), true ); + } + else + { + aimpos = target.origin; + aimpos += offset; + aimpos += aimoffset; + aimpos += ( 0, 0, nadeAimOffset ); + + conedot = getConeDot( aimpos, eyePos, angles ); + + self thread bot_lookat( aimpos, aimspeed ); + } + + if ( isplay && !self.bot.isknifingafter && conedot > 0.9 && dist < level.bots_maxKnifeDistance && trace_time > reaction_time && getDvarInt( "bots_play_knife" ) ) + { + self clear_bot_after_target(); + self thread knife(); + return; + } + + if ( !self canFire( curweap ) || !self isInRange( dist, curweap ) ) + return; + + canADS = ( self canAds( dist, curweap ) && conedot > 0.75 ); + + if ( canADS ) + { + stopAdsOverride = false; + + if ( self.bot.is_cur_sniper ) + { + if ( self.pers["bots"]["behavior"]["quickscope"] && self.bot.last_fire_time != -1 && getTime() - self.bot.last_fire_time < 1000 ) + stopAdsOverride = true; + else + self notify( "kill_goal" ); + } + + if ( !stopAdsOverride ) + self thread pressAds(); + } + + if ( trace_time > reaction_time ) + { + if ( ( !canADS || adsAmount >= 1.0 || self InLastStand() || self GetStance() == "prone" ) && ( conedot > 0.99 || dist < level.bots_maxKnifeDistance ) && getDvarInt( "bots_play_fire" ) ) + self botFire(); + + if ( isplay ) + self thread start_bot_after_target( target ); + } + + return; + } + } + } + + if ( isDefined( self.bot.after_target ) ) + { + nadeAimOffset = 0; + last_pos = self.bot.after_target_pos; + dist = DistanceSquared( self.origin, last_pos ); + + if ( self.bot.isfraggingafter || self.bot.issmokingafter ) + nadeAimOffset = dist / 3000; + else if ( curweap != "none" && weaponClass( curweap ) == "grenade" ) + { + nadeAimOffset = dist / 3000; + } + + aimpos = last_pos + ( 0, 0, self getEyeHeight() + nadeAimOffset ); + conedot = getConeDot( aimpos, eyePos, angles ); + + self thread bot_lookat( aimpos, aimspeed ); + + if ( !self canFire( curweap ) || !self isInRange( dist, curweap ) ) + return; + + canADS = ( self canAds( dist, curweap ) && conedot > 0.75 ); + + if ( canADS ) + { + stopAdsOverride = false; + + if ( self.bot.is_cur_sniper ) + { + if ( self.pers["bots"]["behavior"]["quickscope"] && self.bot.last_fire_time != -1 && getTime() - self.bot.last_fire_time < 1000 ) + stopAdsOverride = true; + else + self notify( "kill_goal" ); + } + + if ( !stopAdsOverride ) + self thread pressAds(); + } + + if ( ( !canADS || adsAmount >= 1.0 || self InLastStand() || self GetStance() == "prone" ) && ( conedot > 0.95 || dist < level.bots_maxKnifeDistance ) && getDvarInt( "bots_play_fire" ) ) + self botFire(); + + return; + } + + if ( self.bot.next_wp != -1 && isDefined( level.waypoints[self.bot.next_wp].angles ) && false ) + { + forwardPos = anglesToForward( level.waypoints[self.bot.next_wp].angles ) * 1024; + + self thread bot_lookat( eyePos + forwardPos, aimspeed ); + } + else if ( isDefined( self.bot.script_aimpos ) ) + { + self thread bot_lookat( self.bot.script_aimpos, aimspeed ); + } + else + { + lookat = undefined; + + if ( self.bot.second_next_wp != -1 && !self.bot.issprinting && !self.bot.climbing ) + lookat = level.waypoints[self.bot.second_next_wp].origin; + else if ( isDefined( self.bot.towards_goal ) ) + lookat = self.bot.towards_goal; + + if ( isDefined( lookat ) ) + self thread bot_lookat( lookat + ( 0, 0, self getEyeHeight() ), aimspeed ); + } +} + +/* + When the bot gets a new enemy. +*/ +onNewEnemy() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self waittill( "new_enemy" ); + + if ( !isDefined( self.bot.target ) ) + continue; + + if ( !isDefined( self.bot.target.entity ) || !self.bot.target.isplay ) + continue; + + if ( self.bot.target.didlook ) + continue; + + self thread watchToLook(); + } +} + +/* + Bots will jump or dropshot their enemy player. +*/ +watchToLook() +{ + self endon( "disconnect" ); + self endon( "death" ); + self endon( "new_enemy" ); + + for ( ;; ) + { + while ( isDefined( self.bot.target ) && self.bot.target.didlook ) + wait 0.05; + + while ( isDefined( self.bot.target ) && self.bot.target.no_trace_time ) + wait 0.05; + + if ( !isDefined( self.bot.target ) ) + break; + + self.bot.target.didlook = true; + + if ( self.bot.isfrozen ) + continue; + + if ( self.bot.target.dist > level.bots_maxShotgunDistance * 2 ) + continue; + + if ( self.bot.target.dist <= level.bots_maxKnifeDistance ) + continue; + + if ( !self canFire( self getCurrentWEapon() ) ) + continue; + + if ( !self isInRange( self.bot.target.dist, self getCurrentWEapon() ) ) + continue; + + if ( self.bot.is_cur_sniper ) + continue; + + if ( randomInt( 100 ) > self.pers["bots"]["behavior"]["jump"] ) + continue; + + if ( !getDvarInt( "bots_play_jumpdrop" ) ) + continue; + + if ( isDefined( self.bot.jump_time ) && getTime() - self.bot.jump_time <= 5000 ) + continue; + + if ( self.bot.target.rand <= self.pers["bots"]["behavior"]["strafe"] ) + { + if ( self getStance() != "stand" ) + continue; + + self.bot.jump_time = getTime(); + self jump(); + } + else + { + if ( getConeDot( self.bot.target.last_seen_pos, self.origin, self getPlayerAngles() ) < 0.8 || self.bot.target.dist <= level.bots_noADSDistance ) + continue; + + self.bot.jump_time = getTime(); + self prone(); + self notify( "kill_goal" ); + wait 2.5; + self crouch(); + } + } +} + +/* + Returns true if the bot can fire their current weapon. +*/ +canFire( curweap ) +{ + if ( curweap == "none" ) + return false; + + return self GetWeaponammoclip( curweap ); +} + +/* + Returns true if the bot is in range of their target. +*/ +isInRange( dist, curweap ) +{ + if ( curweap == "none" ) + return false; + + weapclass = weaponClass( curweap ); + + if ( weapclass == "spread" && dist > level.bots_maxShotgunDistance ) + return false; + + if ( curweap == "m2_flamethrower_mp" && dist > level.bots_maxShotgunDistance ) + return false; + + return true; +} + +/* + Assigns the bot's after target (bot will keep firing at a target after no sight or death) +*/ +start_bot_after_target( who ) +{ + self endon( "disconnect" ); + self endon( "death" ); + + self.bot.after_target = who; + self.bot.after_target_pos = who.origin; + + self notify( "kill_after_target" ); + self endon( "kill_after_target" ); + + wait self.pers["bots"]["skill"]["shoot_after_time"]; + + self.bot.after_target = undefined; +} + +/* + Clears the bot's after target +*/ +clear_bot_after_target() +{ + self.bot.after_target = undefined; + self notify( "kill_after_target" ); +} + +/* + Returns true if the bot can ads their current gun. +*/ +canAds( dist, curweap ) +{ + if ( curweap == "none" ) + return false; + + if ( curweap == "satchel_charge_mp" ) + return RandomInt( 2 ); + + if ( !getDvarInt( "bots_play_ads" ) ) + return false; + + far = level.bots_noADSDistance; + + if ( self hasPerk( "specialty_bulletaccuracy" ) ) + far *= 1.4; + + if ( dist < far ) + return false; + + weapclass = ( weaponClass( curweap ) ); + + if ( weapclass == "spread" || weapclass == "grenade" ) + return false; + + return true; +} + +/* + Bot will press ADS for a time. +*/ +pressADS( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_ads" ); + self endon( "bot_ads" ); + + if ( !isDefined( time ) ) + time = 0.05; + + self botAction( "+ads" ); + + if ( time ) + wait time; + + self botAction( "-ads" ); +} + +/* + Bot will hold the frag button for a time +*/ +frag( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_frag" ); + self endon( "bot_frag" ); + + if ( !isDefined( time ) ) + time = 0.05; + + self botAction( "+frag" ); + self.bot.isfragging = true; + self.bot.isfraggingafter = true; + + if ( time ) + wait time; + + self botAction( "-frag" ); + self.bot.isfragging = false; + + wait 1.25; + self.bot.isfraggingafter = false; +} + +/* + Bot will hold the 'smoke' button for a time. +*/ +smoke( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_smoke" ); + self endon( "bot_smoke" ); + + if ( !isDefined( time ) ) + time = 0.05; + + self botAction( "+smoke" ); + self.bot.issmoking = true; + self.bot.issmokingafter = true; + + if ( time ) + wait time; + + self botAction( "-smoke" ); + self.bot.issmoking = false; + + wait 1.25; + self.bot.issmokingafter = false; +} + +/* + Bots will fire their gun. +*/ +botFire() +{ + self.bot.last_fire_time = getTime(); + + if ( self.bot.is_cur_full_auto ) + { + self thread pressFire(); + return; + } + + if ( self.bot.semi_time ) + return; + + self thread pressFire(); + self thread doSemiTime(); +} + +/* + Bot will fire for a time. +*/ +pressFire( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_fire" ); + self endon( "bot_fire" ); + + if ( !isDefined( time ) ) + time = 0.05; + + self botAction( "+fire" ); + + if ( time ) + wait time; + + self botAction( "-fire" ); +} + +/* + Waits a time defined by their difficulty for semi auto guns (no rapid fire) +*/ +doSemiTime() +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_semi_time" ); + self endon( "bot_semi_time" ); + + self.bot.semi_time = true; + wait self.pers["bots"]["skill"]["semi_time"]; + self.bot.semi_time = false; +} + +/* + Bot will knife. +*/ +knife() +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_knife" ); + self endon( "bot_knife" ); + + self.bot.isknifing = true; + self.bot.isknifingafter = true; + + self botAction( "+melee" ); + wait 0.05; + self botAction( "-melee" ); + + self.bot.isknifing = false; + + wait 1; + + self.bot.isknifingafter = false; +} + +/* + Bot will press use for a time. +*/ +use( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_use" ); + self endon( "bot_use" ); + + if ( !isDefined( time ) ) + time = 0.05; + + self botAction( "+activate" ); + + if ( time ) + wait time; + + self botAction( "-activate" ); +} + +/* + Bot will jump. +*/ +jump() +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_jump" ); + self endon( "bot_jump" ); + + if ( self getStance() != "stand" ) + { + self stand(); + wait 1; + } + + self botAction( "+gostand" ); + wait 0.05; + self botAction( "-gostand" ); +} + +/* + Bot will stand. +*/ +stand() +{ + self botAction( "-gocrouch" ); + self botAction( "-goprone" ); +} + +/* + Bot will crouch. +*/ +crouch() +{ + self botAction( "+gocrouch" ); + self botAction( "-goprone" ); +} + +/* + Bot will prone. +*/ +prone() +{ + self botAction( "-gocrouch" ); + self botAction( "+goprone" ); +} + +/* + Bot will sprint. +*/ +sprint() +{ + self endon( "death" ); + self endon( "disconnect" ); + self notify( "bot_sprint" ); + self endon( "bot_sprint" ); + + self botAction( "+sprint" ); + wait 0.05; + self botAction( "-sprint" ); +} \ No newline at end of file diff --git a/scripts/sp/bots/_bot_script.gsc b/scripts/sp/bots/_bot_script.gsc new file mode 100644 index 0000000..21ad781 --- /dev/null +++ b/scripts/sp/bots/_bot_script.gsc @@ -0,0 +1,1074 @@ +#include common_scripts\utility; +#include maps\_utility; +#include scripts\sp\bots\_bot_utility; + +/* + When the bot connects to the game. +*/ +connected() +{ + self endon( "disconnect" ); + + self.killerLocation = undefined; + self.lastKiller = undefined; + self.bot_change_class = true; + + self thread difficulty(); + self thread onBotSpawned(); + self thread onSpawned(); + + wait 0.1; + self.challengeData = []; +} + +/* + When the bot spawned, after the difficulty wait. Start the logic for the bot. +*/ +onBotSpawned() +{ + self endon( "disconnect" ); + level endon( "game_ended" ); + + for ( ;; ) + { + self waittill( "bot_spawned" ); + + self thread start_bot_threads(); + } +} + +/* + Starts all the bot thinking +*/ +start_bot_threads() +{ + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "death" ); + + // inventory usage + if ( getDvarInt( "bots_play_killstreak" ) ) + //self thread bot_killstreak_think(); + + self thread bot_weapon_think(); + self thread doReloadCancel(); + + // script targeting + if ( getDvarInt( "bots_play_target_other" ) ) + { + //self thread bot_target_vehicle(); + //self thread bot_kill_dog_think(); + //self thread bot_equipment_kill_think(); + } + + // awareness + //self thread bot_revenge_think(); + //self thread bot_uav_think(); + //self thread bot_listen_to_steps(); + //self thread follow_target(); + + // camp and follow + if ( getDvarInt( "bots_play_camp" ) ) + { + self thread bot_think_follow(); + self thread bot_think_camp(); + } + + // nades + if ( getDvarInt( "bots_play_nade" ) ) + { + //self thread bot_use_tube_think(); + //self thread bot_use_grenade_think(); + //self thread bot_use_equipment_think(); + //self thread bot_watch_think_mw2(); + } + + // obj + if ( getDvarInt( "bots_play_obj" ) ) + { + //self thread bot_dom_def_think(); + //self thread bot_dom_spawn_kill_think(); + + //self thread bot_hq(); + + //self thread bot_sab(); + + //self thread bot_sd_defenders(); + //self thread bot_sd_attackers(); + + //self thread bot_cap(); + + //self thread bot_war(); + } + + self thread bot_revive_think(); +} + +/* + Bot logic for switching weapons. +*/ +bot_weapon_think_loop( data ) +{ + self waittill_any_timeout( randomIntRange( 2, 4 ), "bot_force_check_switch" ); + + if ( self BotIsFrozen() ) + return; + + if ( self InLastStand() ) + return; + + hasTarget = self hasThreat(); + curWeap = self GetCurrentWeapon(); + + if ( data.first ) + { + data.first = false; + + if ( randomInt( 100 ) > self.pers["bots"]["behavior"]["initswitch"] ) + return; + } + else + { + if ( curWeap != "none" && self getAmmoCount( curWeap ) && curWeap != "squadcommand_mp" ) + { + if ( randomInt( 100 ) > self.pers["bots"]["behavior"]["switch"] ) + return; + + if ( hasTarget ) + return; + } + } + + weaponslist = self getweaponslist(); + weap = ""; + + while ( weaponslist.size ) + { + weapon = weaponslist[randomInt( weaponslist.size )]; + weaponslist = array_remove( weaponslist, weapon ); + + if ( !self getAmmoCount( weapon ) ) + continue; + /* + if ( maps\mp\gametypes\_weapons::isHackWeapon( weapon ) ) + continue; + + if ( maps\mp\gametypes\_weapons::isGrenade( weapon ) ) + continue; + */ + if ( curWeap == weapon || weapon == "satchel_charge_mp" || weapon == "none" || weapon == "mine_bouncing_betty_mp" || weapon == "" || weapon == "squadcommand_mp" ) + continue; + + weap = weapon; + break; + } + + if ( weap == "" ) + return; + + self thread ChangeToWeapon( weap ); +} + +/* + Bot logic for switching weapons. +*/ +bot_weapon_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + + data = spawnStruct(); + data.first = true; + + for ( ;; ) + { + self bot_weapon_think_loop( data ); + } +} + +/* + Reload cancels +*/ +doReloadCancel() +{ + self endon( "disconnect" ); + self endon( "death" ); + + for ( ;; ) + { + self doReloadCancel_loop(); + } +} + +/* + Reload cancels +*/ +doReloadCancel_loop() +{ + ret = self waittill_any_return( "reload", "weapon_change" ); + + if ( self BotIsFrozen() ) + return; + + if ( self InLastStand() ) + return; + + curWeap = self GetCurrentWeapon(); + /* + if ( !maps\mp\gametypes\_weapons::isSideArm( curWeap ) && !maps\mp\gametypes\_weapons::isPrimaryWeapon( curWeap ) ) + return; + */ + if ( ret == "reload" ) + { + // check single reloads + if ( self GetWeaponAmmoClip( curWeap ) < WeaponClipSize( curWeap ) ) + return; + } + + // check difficulty + if ( self.pers["bots"]["skill"]["base"] <= 3 ) + return; + + // check if got another weapon + weaponslist = self GetWeaponsListPrimaries(); + weap = ""; + + while ( weaponslist.size ) + { + weapon = weaponslist[randomInt( weaponslist.size )]; + weaponslist = array_remove( weaponslist, weapon ); + + /* + if ( !maps\mp\gametypes\_weapons::isSideArm( weapon ) && !maps\mp\gametypes\_weapons::isPrimaryWeapon( weapon ) ) + continue; + */ + if ( curWeap == weapon || weapon == "none" || weapon == "" ) + continue; + + weap = weapon; + break; + } + + if ( weap == "" ) + return; + + // do the cancel + wait 0.1; + self thread ChangeToWeapon( weap ); + wait 0.25; + self thread ChangeToWeapon( curWeap ); + wait 2; +} + +/* + Updates the bot's difficulty variables. +*/ +difficulty() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + if ( GetDvarInt( "bots_skill" ) != 9 ) + { + switch ( self.pers["bots"]["skill"]["base"] ) + { + case 1: + self.pers["bots"]["skill"]["aim_time"] = 0.6; + self.pers["bots"]["skill"]["init_react_time"] = 1500; + self.pers["bots"]["skill"]["reaction_time"] = 1000; + self.pers["bots"]["skill"]["no_trace_ads_time"] = 500; + self.pers["bots"]["skill"]["no_trace_look_time"] = 600; + self.pers["bots"]["skill"]["remember_time"] = 750; + self.pers["bots"]["skill"]["fov"] = 0.7; + self.pers["bots"]["skill"]["dist_max"] = 2500; + self.pers["bots"]["skill"]["dist_start"] = 1000; + self.pers["bots"]["skill"]["spawn_time"] = 0.75; + self.pers["bots"]["skill"]["help_dist"] = 0; + self.pers["bots"]["skill"]["semi_time"] = 0.9; + self.pers["bots"]["skill"]["shoot_after_time"] = 1; + self.pers["bots"]["skill"]["aim_offset_time"] = 1.5; + self.pers["bots"]["skill"]["aim_offset_amount"] = 4; + self.pers["bots"]["skill"]["bone_update_interval"] = 2; + self.pers["bots"]["skill"]["bones"] = "j_spineupper,j_ankle_le,j_ankle_ri"; + self.pers["bots"]["skill"]["ads_fov_multi"] = 0.5; + self.pers["bots"]["skill"]["ads_aimspeed_multi"] = 0.5; + + self.pers["bots"]["behavior"]["strafe"] = 0; + self.pers["bots"]["behavior"]["nade"] = 10; + self.pers["bots"]["behavior"]["sprint"] = 30; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 20; + self.pers["bots"]["behavior"]["switch"] = 2; + self.pers["bots"]["behavior"]["class"] = 2; + self.pers["bots"]["behavior"]["jump"] = 0; + break; + + case 2: + self.pers["bots"]["skill"]["aim_time"] = 0.55; + self.pers["bots"]["skill"]["init_react_time"] = 1000; + self.pers["bots"]["skill"]["reaction_time"] = 800; + self.pers["bots"]["skill"]["no_trace_ads_time"] = 1000; + self.pers["bots"]["skill"]["no_trace_look_time"] = 1250; + self.pers["bots"]["skill"]["remember_time"] = 1500; + self.pers["bots"]["skill"]["fov"] = 0.65; + self.pers["bots"]["skill"]["dist_max"] = 3000; + self.pers["bots"]["skill"]["dist_start"] = 1500; + self.pers["bots"]["skill"]["spawn_time"] = 0.65; + self.pers["bots"]["skill"]["help_dist"] = 500; + self.pers["bots"]["skill"]["semi_time"] = 0.75; + self.pers["bots"]["skill"]["shoot_after_time"] = 0.75; + self.pers["bots"]["skill"]["aim_offset_time"] = 1; + self.pers["bots"]["skill"]["aim_offset_amount"] = 3; + self.pers["bots"]["skill"]["bone_update_interval"] = 1.5; + self.pers["bots"]["skill"]["bones"] = "j_spineupper,j_ankle_le,j_ankle_ri,j_head"; + self.pers["bots"]["skill"]["ads_fov_multi"] = 0.5; + self.pers["bots"]["skill"]["ads_aimspeed_multi"] = 0.5; + + self.pers["bots"]["behavior"]["strafe"] = 10; + self.pers["bots"]["behavior"]["nade"] = 15; + self.pers["bots"]["behavior"]["sprint"] = 45; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 15; + self.pers["bots"]["behavior"]["switch"] = 2; + self.pers["bots"]["behavior"]["class"] = 2; + self.pers["bots"]["behavior"]["jump"] = 10; + break; + + case 3: + self.pers["bots"]["skill"]["aim_time"] = 0.4; + self.pers["bots"]["skill"]["init_react_time"] = 750; + self.pers["bots"]["skill"]["reaction_time"] = 500; + self.pers["bots"]["skill"]["no_trace_ads_time"] = 1000; + self.pers["bots"]["skill"]["no_trace_look_time"] = 1500; + self.pers["bots"]["skill"]["remember_time"] = 2000; + self.pers["bots"]["skill"]["fov"] = 0.6; + self.pers["bots"]["skill"]["dist_max"] = 4000; + self.pers["bots"]["skill"]["dist_start"] = 2250; + self.pers["bots"]["skill"]["spawn_time"] = 0.5; + self.pers["bots"]["skill"]["help_dist"] = 750; + self.pers["bots"]["skill"]["semi_time"] = 0.65; + self.pers["bots"]["skill"]["shoot_after_time"] = 0.65; + self.pers["bots"]["skill"]["aim_offset_time"] = 0.75; + self.pers["bots"]["skill"]["aim_offset_amount"] = 2.5; + self.pers["bots"]["skill"]["bone_update_interval"] = 1; + self.pers["bots"]["skill"]["bones"] = "j_spineupper,j_spineupper,j_ankle_le,j_ankle_ri,j_head"; + self.pers["bots"]["skill"]["ads_fov_multi"] = 0.5; + self.pers["bots"]["skill"]["ads_aimspeed_multi"] = 0.5; + + self.pers["bots"]["behavior"]["strafe"] = 20; + self.pers["bots"]["behavior"]["nade"] = 20; + self.pers["bots"]["behavior"]["sprint"] = 50; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 10; + self.pers["bots"]["behavior"]["switch"] = 2; + self.pers["bots"]["behavior"]["class"] = 2; + self.pers["bots"]["behavior"]["jump"] = 25; + break; + + case 4: + self.pers["bots"]["skill"]["aim_time"] = 0.3; + self.pers["bots"]["skill"]["init_react_time"] = 600; + self.pers["bots"]["skill"]["reaction_time"] = 400; + self.pers["bots"]["skill"]["no_trace_ads_time"] = 1000; + self.pers["bots"]["skill"]["no_trace_look_time"] = 1500; + self.pers["bots"]["skill"]["remember_time"] = 3000; + self.pers["bots"]["skill"]["fov"] = 0.55; + self.pers["bots"]["skill"]["dist_max"] = 5000; + self.pers["bots"]["skill"]["dist_start"] = 3350; + self.pers["bots"]["skill"]["spawn_time"] = 0.35; + self.pers["bots"]["skill"]["help_dist"] = 1000; + self.pers["bots"]["skill"]["semi_time"] = 0.5; + self.pers["bots"]["skill"]["shoot_after_time"] = 0.5; + self.pers["bots"]["skill"]["aim_offset_time"] = 0.5; + self.pers["bots"]["skill"]["aim_offset_amount"] = 2; + self.pers["bots"]["skill"]["bone_update_interval"] = 0.75; + self.pers["bots"]["skill"]["bones"] = "j_spineupper,j_spineupper,j_ankle_le,j_ankle_ri,j_head,j_head"; + self.pers["bots"]["skill"]["ads_fov_multi"] = 0.5; + self.pers["bots"]["skill"]["ads_aimspeed_multi"] = 0.5; + + self.pers["bots"]["behavior"]["strafe"] = 30; + self.pers["bots"]["behavior"]["nade"] = 25; + self.pers["bots"]["behavior"]["sprint"] = 55; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 10; + self.pers["bots"]["behavior"]["switch"] = 2; + self.pers["bots"]["behavior"]["class"] = 2; + self.pers["bots"]["behavior"]["jump"] = 35; + break; + + case 5: + self.pers["bots"]["skill"]["aim_time"] = 0.25; + self.pers["bots"]["skill"]["init_react_time"] = 500; + self.pers["bots"]["skill"]["reaction_time"] = 300; + self.pers["bots"]["skill"]["no_trace_ads_time"] = 1500; + self.pers["bots"]["skill"]["no_trace_look_time"] = 2000; + self.pers["bots"]["skill"]["remember_time"] = 4000; + self.pers["bots"]["skill"]["fov"] = 0.5; + self.pers["bots"]["skill"]["dist_max"] = 7500; + self.pers["bots"]["skill"]["dist_start"] = 5000; + self.pers["bots"]["skill"]["spawn_time"] = 0.25; + self.pers["bots"]["skill"]["help_dist"] = 1500; + self.pers["bots"]["skill"]["semi_time"] = 0.4; + self.pers["bots"]["skill"]["shoot_after_time"] = 0.35; + self.pers["bots"]["skill"]["aim_offset_time"] = 0.35; + self.pers["bots"]["skill"]["aim_offset_amount"] = 1.5; + self.pers["bots"]["skill"]["bone_update_interval"] = 0.5; + self.pers["bots"]["skill"]["bones"] = "j_spineupper,j_head"; + self.pers["bots"]["skill"]["ads_fov_multi"] = 0.5; + self.pers["bots"]["skill"]["ads_aimspeed_multi"] = 0.5; + + self.pers["bots"]["behavior"]["strafe"] = 40; + self.pers["bots"]["behavior"]["nade"] = 35; + self.pers["bots"]["behavior"]["sprint"] = 60; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 10; + self.pers["bots"]["behavior"]["switch"] = 2; + self.pers["bots"]["behavior"]["class"] = 2; + self.pers["bots"]["behavior"]["jump"] = 50; + break; + + case 6: + self.pers["bots"]["skill"]["aim_time"] = 0.2; + self.pers["bots"]["skill"]["init_react_time"] = 250; + self.pers["bots"]["skill"]["reaction_time"] = 150; + self.pers["bots"]["skill"]["no_trace_ads_time"] = 2000; + self.pers["bots"]["skill"]["no_trace_look_time"] = 3000; + self.pers["bots"]["skill"]["remember_time"] = 5000; + self.pers["bots"]["skill"]["fov"] = 0.45; + self.pers["bots"]["skill"]["dist_max"] = 10000; + self.pers["bots"]["skill"]["dist_start"] = 7500; + self.pers["bots"]["skill"]["spawn_time"] = 0.2; + self.pers["bots"]["skill"]["help_dist"] = 2000; + self.pers["bots"]["skill"]["semi_time"] = 0.25; + self.pers["bots"]["skill"]["shoot_after_time"] = 0.25; + self.pers["bots"]["skill"]["aim_offset_time"] = 0.25; + self.pers["bots"]["skill"]["aim_offset_amount"] = 1; + self.pers["bots"]["skill"]["bone_update_interval"] = 0.25; + self.pers["bots"]["skill"]["bones"] = "j_spineupper,j_head,j_head"; + self.pers["bots"]["skill"]["ads_fov_multi"] = 0.5; + self.pers["bots"]["skill"]["ads_aimspeed_multi"] = 0.5; + + self.pers["bots"]["behavior"]["strafe"] = 50; + self.pers["bots"]["behavior"]["nade"] = 45; + self.pers["bots"]["behavior"]["sprint"] = 65; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 10; + self.pers["bots"]["behavior"]["switch"] = 2; + self.pers["bots"]["behavior"]["class"] = 2; + self.pers["bots"]["behavior"]["jump"] = 75; + break; + + case 7: + self.pers["bots"]["skill"]["aim_time"] = 0.1; + self.pers["bots"]["skill"]["init_react_time"] = 100; + self.pers["bots"]["skill"]["reaction_time"] = 50; + self.pers["bots"]["skill"]["no_trace_ads_time"] = 2500; + self.pers["bots"]["skill"]["no_trace_look_time"] = 4000; + self.pers["bots"]["skill"]["remember_time"] = 7500; + self.pers["bots"]["skill"]["fov"] = 0.4; + self.pers["bots"]["skill"]["dist_max"] = 15000; + self.pers["bots"]["skill"]["dist_start"] = 10000; + self.pers["bots"]["skill"]["spawn_time"] = 0.05; + self.pers["bots"]["skill"]["help_dist"] = 3000; + self.pers["bots"]["skill"]["semi_time"] = 0.1; + self.pers["bots"]["skill"]["shoot_after_time"] = 0; + self.pers["bots"]["skill"]["aim_offset_time"] = 0; + self.pers["bots"]["skill"]["aim_offset_amount"] = 0; + self.pers["bots"]["skill"]["bone_update_interval"] = 0.05; + self.pers["bots"]["skill"]["bones"] = "j_head"; + self.pers["bots"]["skill"]["ads_fov_multi"] = 0.5; + self.pers["bots"]["skill"]["ads_aimspeed_multi"] = 0.5; + + self.pers["bots"]["behavior"]["strafe"] = 65; + self.pers["bots"]["behavior"]["nade"] = 65; + self.pers["bots"]["behavior"]["sprint"] = 70; + self.pers["bots"]["behavior"]["camp"] = 5; + self.pers["bots"]["behavior"]["follow"] = 5; + self.pers["bots"]["behavior"]["crouch"] = 5; + self.pers["bots"]["behavior"]["switch"] = 2; + self.pers["bots"]["behavior"]["class"] = 2; + self.pers["bots"]["behavior"]["jump"] = 90; + break; + } + } + + wait 5; + } +} + +/* + The callback for when the bot gets damaged. +*/ +onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset ) +{ + if ( !IsDefined( self ) || !isDefined( self.team ) ) + return; + + if ( !isAlive( self ) ) + return; + + if ( sMeansOfDeath == "MOD_FALLING" || sMeansOfDeath == "MOD_SUICIDE" ) + return; + + if ( iDamage <= 0 ) + return; + + if ( !IsDefined( eAttacker ) || !isDefined( eAttacker.team ) ) + return; + + if ( eAttacker == self ) + return; + + if ( level.teamBased && eAttacker.team == self.team ) + return; + + if ( !IsDefined( eInflictor ) || eInflictor.classname != "player" ) + return; + + if ( !isAlive( eAttacker ) ) + return; + + if ( !isSubStr( sWeapon, "silenced_" ) && !isSubStr( sWeapon, "flash_" ) ) + //self bot_cry_for_help( eAttacker ); + + self SetAttacker( eAttacker ); +} + +/* + When the bot spawns. +*/ +onSpawned() +{ + self endon( "disconnect" ); + + for ( ;; ) + { + self waittill( "spawned_player" ); + + if ( randomInt( 100 ) <= self.pers["bots"]["behavior"]["class"] ) + self.bot_change_class = undefined; + + self.bot_lock_goal = false; + self.help_time = undefined; + self.bot_was_follow_script_update = undefined; + } +} + +/* + Wait for the revive to complete +*/ +bot_revive_wait( revive ) +{ + level endon( "game_ended" ); + self endon( "death" ); + self endon( "disconnect" ); + self endon( "bot_try_use_fail" ); + self endon( "bot_try_use_success" ); + + timer = 0; + + for ( reviveTime = GetDvarInt( "revive_time_taken" ); timer < reviveTime; timer += 0.05 ) + { + wait 0.05; + + if ( !isDefined( revive ) || !isDefined( revive.revivetrigger ) ) + { + self notify( "bot_try_use_fail" ); + return; + } + } + + self notify( "bot_try_use_success" ); +} + +/* + Bots revive +*/ +bots_use_revive( revive ) +{ + level endon( "game_ended" ); + + self.revivingTeammate = true; + revive.currentlyBeingRevived = true; + self BotFreezeControls( true ); + + self.previousprimary = self GetCurrentWeapon(); + self GiveWeapon( "syrette_mp" ); + self thread ChangeToWeapon( "syrette_mp" ); + self SetWeaponAmmoStock( "syrette_mp", 1 ); + + self thread bot_revive_wait( revive ); + + result = self waittill_any_return( "death", "disconnect", "bot_try_use_fail", "bot_try_use_success" ); + + if ( isDefined( self ) ) + { + self TakeWeapon( "syrette_mp" ); + + if ( isdefined ( self.previousPrimary ) && self.previousPrimary != "none" ) + self thread changeToWeapon( self.previousPrimary ); + + self.previousprimary = undefined; + self notify( "completedRevive" ); + self.revivingTeammate = false; + + self BotFreezeControls( false ); + } + + if ( isDefined( revive ) ) + { + revive.currentlyBeingRevived = false; + } + + if ( result == "bot_try_use_success" ) + { + revive.thisPlayerIsInLastStand = false; + + if ( isdefined ( revive.previousPrimary ) && revive.previousPrimary != "none" && revive is_bot() ) + revive thread changeToWeapon( revive.previousPrimary ); + } +} + +/* + Changes to the weap +*/ +changeToWeapon( weap ) +{ + self endon( "disconnect" ); + self endon( "death" ); + level endon( "game_ended" ); + + if ( !self HasWeapon( weap ) ) + return false; + + self BotChangeToWeapon( weap ); + + if ( self GetCurrentWeapon() == weap ) + return true; + + self waittill_any_timeout( 5, "weapon_change" ); + + return ( self GetCurrentWeapon() == weap ); +} + +/* + Bots revive the player +*/ +bot_use_revive_thread( revivePlayer ) +{ + self thread bots_use_revive( revivePlayer ); + self waittill_any( "bot_try_use_fail", "bot_try_use_success" ); +} + +/* + Bots think to go revive +*/ +bot_revive_think_loop() +{ + revivePlayer = undefined; + + for ( i = 0; i < level.players.size; i++ ) + { + player = level.players[i]; + + if ( !isDefined( player.pers["team"] ) ) + continue; + + if ( player == self ) + continue; + + if ( self.pers["team"] != player.pers["team"] ) + continue; + + if ( !isDefined( player.revivetrigger ) ) + continue; + + if ( isDefined( player.currentlyBeingRevived ) && player.currentlyBeingRevived ) + continue; + + if ( !isDefined( player.revivetrigger.bots ) ) + player.revivetrigger.bots = 0; + + if ( player.revivetrigger.bots > 2 ) + continue; + + revivePlayer = player; + } + + if ( !isDefined( revivePlayer ) ) + return; + + self BotNotifyBotEvent( "revive", "go", revivePlayer ); + + self.bot_lock_goal = true; + + self SetScriptGoal( revivePlayer.origin, 1 ); + self thread bot_inc_bots( revivePlayer.revivetrigger, true ); + self thread bot_go_revive( revivePlayer ); + + event = self waittill_any_return( "goal", "bad_path", "new_goal" ); + + if ( event != "new_goal" ) + self ClearScriptGoal(); + + if ( event != "goal" || !isDefined( revivePlayer ) || ( isDefined( revivePlayer.currentlyBeingRevived ) && revivePlayer.currentlyBeingRevived ) || !self isTouching( revivePlayer.revivetrigger ) || self InLastStand() ) + { + self.bot_lock_goal = false; + return; + } + + self BotNotifyBotEvent( "revive", "start", revivePlayer ); + + self SetScriptGoal( self.origin, 64 ); + + self bot_use_revive_thread( revivePlayer ); + wait 1; + self ClearScriptGoal(); + self.bot_lock_goal = false; + + self BotNotifyBotEvent( "revive", "stop", revivePlayer ); +} + +/* + Increments the number of bots approching the obj, decrements when needed + Used for preventing too many bots going to one obj, or unreachable objs +*/ +bot_inc_bots( obj, unreach ) +{ + level endon( "game_ended" ); + self endon( "bot_inc_bots" ); + + if ( !isDefined( obj ) ) + return; + + if ( !isDefined( obj.bots ) ) + obj.bots = 0; + + obj.bots++; + + ret = self waittill_any_return( "death", "disconnect", "bad_path", "goal", "new_goal" ); + + if ( isDefined( obj ) && ( ret != "bad_path" || !isDefined( unreach ) ) ) + obj.bots--; +} + +/* + Bots think to go revive +*/ +bot_revive_think() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait randomintrange( 2, 5 ); + + if ( !level.teamBased ) + continue; + + if ( !self.canreviveothers ) + continue; + + if ( self HasScriptGoal() || self.bot_lock_goal ) + continue; + + if ( self inLastStand() ) + continue; + + self bot_revive_think_loop(); + } +} + +/* + Bots go to the revive +*/ +bot_go_revive( revive ) +{ + self endon( "death" ); + self endon( "disconnect" ); + level endon( "game_ended" ); + self endon( "goal" ); + self endon( "bad_path" ); + self endon( "new_goal" ); + + for ( ;; ) + { + wait 1; + + if ( !isDefined( revive ) ) + break; + + if ( !isDefined( revive.revivetrigger ) ) + break; + + if ( self isTouching( revive.revivetrigger ) ) + break; + } + + if ( !isDefined( revive ) || !isDefined( revive.revivetrigger ) ) + self notify( "bad_path" ); + else + self notify( "goal" ); +} + +/* + Bot logic for bot determining to camp. +*/ +bot_think_camp_loop() +{ + campSpot = getWaypointForIndex( PickRandom( self waypointsNear( getWaypointsOfType( "camp" ), 1024 ) ) ); + + if ( !isDefined( campSpot ) ) + return; + + self SetScriptGoal( campSpot.origin, 16 ); + + time = randomIntRange( 10, 20 ); + + self BotNotifyBotEvent( "camp", "go", campSpot, time ); + + ret = self waittill_any_return( "new_goal", "goal", "bad_path" ); + + if ( ret != "new_goal" ) + self ClearScriptGoal(); + + if ( ret != "goal" ) + return; + + self BotNotifyBotEvent( "camp", "start", campSpot, time ); + + self thread killCampAfterTime( time ); + self CampAtSpot( campSpot.origin, campSpot.origin + AnglesToForward( campSpot.angles ) * 2048 ); + + self BotNotifyBotEvent( "camp", "stop", campSpot, time ); +} + +/* + Bot logic for bot determining to camp. +*/ +bot_think_camp() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait randomintrange( 4, 7 ); + + if ( self HasScriptGoal() || self.bot_lock_goal || self HasScriptAimPos() ) + continue; + + if ( randomInt( 100 ) > self.pers["bots"]["behavior"]["camp"] ) + continue; + + self bot_think_camp_loop(); + } +} + +/* + Kills the camping thread when time +*/ +killCampAfterTime( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "kill_camp_bot" ); + + wait time + 0.05; + self ClearScriptGoal(); + self ClearScriptAimPos(); + + self notify( "kill_camp_bot" ); +} + +/* + Kills the camping thread when ent gone +*/ +killCampAfterEntGone( ent ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "kill_camp_bot" ); + + for ( ;; ) + { + wait 0.05; + + if ( !isDefined( ent ) ) + break; + } + + self ClearScriptGoal(); + self ClearScriptAimPos(); + + self notify( "kill_camp_bot" ); +} + +/* + Camps at the spot +*/ +CampAtSpot( origin, anglePos ) +{ + self endon( "kill_camp_bot" ); + + self SetScriptGoal( origin, 64 ); + + if ( isDefined( anglePos ) ) + { + self SetScriptAimPos( anglePos ); + } + + self waittill( "new_goal" ); + self ClearScriptAimPos(); + + self notify( "kill_camp_bot" ); +} + +/* + Bot logic for bot determining to follow another player. +*/ +bot_think_follow_loop() +{ + follows = []; + distSq = self.pers["bots"]["skill"]["help_dist"] * self.pers["bots"]["skill"]["help_dist"]; + + for ( i = level.players.size - 1; i >= 0; i-- ) + { + player = level.players[i]; + + if ( player == self ) + continue; + + if ( !isAlive( player ) ) + continue; + + if ( player.team != self.team ) + continue; + + if ( DistanceSquared( player.origin, self.origin ) > distSq ) + continue; + + follows[follows.size] = player; + } + + toFollow = PickRandom( follows ); + + if ( !isDefined( toFollow ) ) + return; + + time = randomIntRange( 10, 20 ); + + self BotNotifyBotEvent( "follow", "start", toFollow, time ); + + self thread killFollowAfterTime( time ); + self followPlayer( toFollow ); + + self BotNotifyBotEvent( "follow", "stop", toFollow, time ); +} + +/* + Bot logic for bot determining to follow another player. +*/ +bot_think_follow() +{ + self endon( "death" ); + self endon( "disconnect" ); + + for ( ;; ) + { + wait randomIntRange( 3, 5 ); + + if ( self HasScriptGoal() || self.bot_lock_goal || self HasScriptAimPos() ) + continue; + + if ( randomInt( 100 ) > self.pers["bots"]["behavior"]["follow"] ) + continue; + + if ( !level.teamBased ) + continue; + + self bot_think_follow_loop(); + } +} + +/* + Kills follow when new goal +*/ +watchForFollowNewGoal() +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "kill_follow_bot" ); + + for ( ;; ) + { + self waittill( "new_goal" ); + + if ( !isDefined( self.bot_was_follow_script_update ) ) + break; + } + + self ClearScriptAimPos(); + self notify( "kill_follow_bot" ); +} + +/* + Kills follow when time +*/ +killFollowAfterTime( time ) +{ + self endon( "death" ); + self endon( "disconnect" ); + self endon( "kill_follow_bot" ); + + wait time; + + self ClearScriptGoal(); + self ClearScriptAimPos(); + self notify( "kill_follow_bot" ); +} + +/* + Determine bot to follow a player +*/ +followPlayer( who ) +{ + self endon( "kill_follow_bot" ); + + self thread watchForFollowNewGoal(); + + for ( ;; ) + { + wait 0.05; + + if ( !isDefined( who ) || !isAlive( who ) ) + break; + + self SetScriptAimPos( who.origin + ( 0, 0, 42 ) ); + myGoal = self GetScriptGoal(); + + if ( isDefined( myGoal ) && DistanceSquared( myGoal, who.origin ) < 64 * 64 ) + continue; + + self.bot_was_follow_script_update = true; + self SetScriptGoal( who.origin, 32 ); + waittillframeend; + self.bot_was_follow_script_update = undefined; + + self waittill_either( "goal", "bad_path" ); + } + + self ClearScriptGoal(); + self ClearScriptAimPos(); + + self notify( "kill_follow_bot" ); +} \ No newline at end of file diff --git a/scripts/sp/bots/_bot_utility.gsc b/scripts/sp/bots/_bot_utility.gsc index 9238d30..8c4abd8 100644 --- a/scripts/sp/bots/_bot_utility.gsc +++ b/scripts/sp/bots/_bot_utility.gsc @@ -69,15 +69,15 @@ BotSetStance( stance ) switch ( stance ) { case "stand": - //self maps\mp\bots\_bot_internal::stand(); + //self scripts\sp\bots\_bot_internal::stand(); break; case "crouch": - //self maps\mp\bots\_bot_internal::crouch(); + //self scripts\sp\bots\_bot_internal::crouch(); break; case "prone": - //self maps\mp\bots\_bot_internal::prone(); + //self scripts\sp\bots\_bot_internal::prone(); break; } } @@ -87,7 +87,7 @@ BotSetStance( stance ) */ BotChangeToWeapon( weap ) { - //self maps\mp\bots\_bot_internal::changeToWeap( weap ); + self botWeapon( weap ); } /* @@ -95,7 +95,7 @@ BotChangeToWeapon( weap ) */ BotPressAttack( time ) { - //self maps\mp\bots\_bot_internal::pressFire( time ); + self scripts\sp\bots\_bot_internal::pressFire( time ); } /* @@ -103,7 +103,7 @@ BotPressAttack( time ) */ BotPressADS( time ) { - //self maps\mp\bots\_bot_internal::pressADS( time ); + self scripts\sp\bots\_bot_internal::pressADS( time ); } /* @@ -111,7 +111,7 @@ BotPressADS( time ) */ BotPressUse( time ) { - //self maps\mp\bots\_bot_internal::use( time ); + self scripts\sp\bots\_bot_internal::use( time ); } /* @@ -119,7 +119,7 @@ BotPressUse( time ) */ BotPressFrag( time ) { - //self maps\mp\bots\_bot_internal::frag( time ); + self scripts\sp\bots\_bot_internal::frag( time ); } /* @@ -127,7 +127,7 @@ BotPressFrag( time ) */ BotPressSmoke( time ) { - //self maps\mp\bots\_bot_internal::smoke( time ); + self scripts\sp\bots\_bot_internal::smoke( time ); } /* @@ -798,13 +798,6 @@ load_waypoints() } else { - switch ( mapname ) - { - default: - maps\mp\bots\waypoints\_custom_map::main( mapname ); - break; - } - if ( level.waypoints.size ) PrintConsole( "Loaded " + level.waypoints.size + " waypoints from script.\n" ); } @@ -1383,3 +1376,11 @@ random_normal_distribution( mean, std_deviation, lower_bound, upper_bound ) return ( number ); } + +/* + If the player is in laststand +*/ +inLastStand() +{ + return ( isDefined( self.lastStand ) && self.lastStand ); +} \ No newline at end of file