#using scripts\codescripts\struct; #using scripts\shared\array_shared; #using scripts\shared\callbacks_shared; #using scripts\shared\laststand_shared; #using scripts\shared\math_shared; #using scripts\shared\system_shared; #using scripts\shared\util_shared; #using scripts\shared\bots\_bot_combat; #using scripts\shared\bots\bot_buttons; #using scripts\shared\bots\bot_traversals; #namespace bot; function autoexec __init__sytem__() { system::register("bot",&__init__,undefined,undefined); } function __init__() { callback::on_start_gametype( &init ); callback::on_connect( &on_player_connect ); callback::on_spawned( &on_player_spawned); callback::on_player_killed( &on_player_killed ); // Setup Methods if(!isdefined(level.getBotSettings))level.getBotSettings=&get_bot_default_settings; // Lifecycle events if(!isdefined(level.onBotRemove))level.onBotRemove=&bot_void; if(!isdefined(level.onBotConnect))level.onBotConnect=&bot_void; if(!isdefined(level.onBotSpawned))level.onBotSpawned=&bot_void; if(!isdefined(level.onBotKilled))level.onBotKilled=&bot_void; // Outside events if(!isdefined(level.onBotDamage))level.onBotDamage=&bot_void; // Think Events if(!isdefined(level.botUpdate))level.botUpdate=&bot_update; if(!isdefined(level.botPreCombat))level.botPreCombat=&bot_void; if(!isdefined(level.botCombat))level.botCombat=&bot_combat::combat_think; if(!isdefined(level.botPostCombat))level.botPostCombat=&bot_void; if(!isdefined(level.botIdle))level.botIdle=&bot_void; // Combat Events if(!isdefined(level.botThreatDead))level.botThreatDead=&bot_combat::clear_threat; if(!isdefined(level.botThreatEngage))level.botThreatEngage=&bot_combat::engage_threat; if(!isdefined(level.botUpdateThreatGoal))level.botUpdateThreatGoal=&bot_combat::update_threat_goal; if(!isdefined(level.botThreatLost))level.botThreatLost=&bot_combat::clear_threat; // Combat Queries //level.botThreatIsAlive if(!isdefined(level.botGetThreats))level.botGetThreats=&bot_combat::get_bot_threats; if(!isdefined(level.botIgnoreThreat))level.botIgnoreThreat=&bot_combat::ignore_non_sentient; SetDvar( "bot_maxMantleHeight", 200 ); //SetDvar( "bot_enableWallrun", true ); /# level thread bot_devgui_think(); #/ } function init() { init_bot_settings(); } function is_bot_ranked_match() { return false; } function bot_void() { } function bot_unhandled() { return false; } // Add Bots //======================================== function add_bots( count, team ) { for ( i = 0; i < count; i++ ) { add_bot( team ); } } function add_bot( team ) { botEnt = AddTestClient(); if ( !isdefined( botEnt ) ) { return undefined; } botEnt BotSetRandomCharacterCustomization(); if ( ( isdefined( level.disableClassSelection ) && level.disableClassSelection ) ) { botEnt.pers["class"] = level.defaultClass; botEnt.curClass = level.defaultClass; } if ( level.teamBased && team !== "autoassign" ) { botEnt.pers[ "team" ] = team; } return botEnt; } // Remove Bots //======================================== function remove_bots( count, team ) { players = GetPlayers(); foreach( player in players ) { if ( !player IsTestClient() ) { continue; } if ( isdefined( team ) && player.team != team ) { continue; } remove_bot( player ); if ( isdefined( count ) ) { count--; if ( count <= 0 ) { break; } } } } function remove_bot( bot ) { if ( !bot IsTestClient() ) { return; } bot [[level.onBotRemove]](); bot BotDropClient(); } // Utils //======================================== function filter_bots( players ) { bots = []; foreach( player in players ) { if ( player util::is_bot() ) { bots[bots.size] = player; } } return bots; } // Events //======================================== function on_player_connect() { if ( !self IsTestClient() ) { return; } self endon ( "disconnect" ); // Do the bot initialization on connect so it gets called after skiptos self.bot = SpawnStruct(); self.bot.threat = SpawnStruct(); self.bot.damage = SpawnStruct(); self.pers["isBot"] = true; if ( level.teambased ) { self notify( "menuresponse", game["menu_team"], self.team ); wait 0.5; } self notify( "joined_team" ); callback::callback( #"on_joined_team" ); self thread [[level.onBotConnect]](); } function on_player_spawned() { if ( !self util::is_bot() ) { return; } self clear_stuck(); self bot_combat::clear_threat(); self.bot.prevWeapon = undefined; self BotLookForward(); self thread [[level.onBotSpawned]](); self thread bot_combat::wait_damage_loop(); self thread wait_bot_path_failed_loop(); self thread wait_bot_goal_reached_loop(); self thread bot_think_loop(); } function on_player_killed() { if ( !self util::is_bot() ) { return; } self thread [[level.onBotKilled]](); self BotReleaseManualControl(); } // Think //======================================== function bot_think_loop() { self endon( "death" ); level endon( "game_ended" ); while(1) { self bot_think(); wait level.botSettings.thinkInterval; } } function bot_think() { self BotReleaseButtons(); if ( level.inprematchperiod || level.gameEnded || !IsAlive( self ) ) { return; } self check_stuck(); self sprint_think(); self update_swim(); self thread [[level.botUpdate]](); self thread [[level.botPreCombat]](); self thread [[level.botCombat]](); self thread [[level.botPostCombat]](); // No threat and no goal means the bot is idle if ( !self bot_combat::has_threat() && !self BotGoalSet() ) { self thread [[level.botIdle]](); } } function bot_update() { // TODO: Cache things that get checked frequently } function update_swim() { if ( !self IsPlayerSwimming() ) { self.bot.resurfaceTime = undefined; return; } if ( self IsPlayerUnderwater() ) { if ( !isdefined( self.bot.resurfaceTime ) ) { self.bot.resurfaceTime = GetTime() + level.botSettings.swimTime; } } else { self.bot.resurfaceTime = undefined; } if ( self BotUnderManualControl() ) { return; } goalPosition = self BotGetGoalPosition(); // Swim down to a navmesh goal under the water if ( Distance2DSquared( goalPosition, self.origin ) <= ( 128 * 128 ) && GetWaterHeight( goalPosition ) > 0 ) { self bot::press_swim_down(); return; } if ( isdefined( self.bot.resurfaceTime ) && self.bot.resurfaceTime <= GetTime() ) { { bot::press_swim_up(); return; } } bottomTrace = GroundTrace( self.origin, self.origin + ( 0, 0, -1000 ), false, self, true ); swimHeight = self.origin[2] - bottomTrace[ "position" ][2]; if ( swimHeight < 25 ) { self bot::press_swim_up(); vertDist = 25 - swimHeight; } else if ( swimHeight > 45 ) { self bot::press_swim_down(); vertDist = swimHeight - 45; } if ( isdefined( vertDist ) ) { intervalDist = level.botSettings.swimVerticalSpeed * level.botSettings.thinkInterval; if ( intervalDist > vertDist ) { self wait_release_swim_buttons( level.botSettings.thinkInterval * vertDist / intervalDist ); } } } function wait_release_swim_buttons( waitTime ) { self endon( "death" ); level endon( "game_ended" ); wait waitTime; self bot::release_swim_up(); self bot::release_swim_down(); } // Settings //======================================== function init_bot_settings() { level.botSettings = [[level.getBotSettings]](); // Dvar Settings SetDvar( "bot_AllowMelee", (isdefined(level.botSettings.allowMelee)?level.botSettings.allowMelee:0) ); SetDvar( "bot_AllowGrenades", (isdefined(level.botSettings.allowGrenades)?level.botSettings.allowGrenades:0) ); SetDvar( "bot_AllowKillstreaks", (isdefined(level.botSettings.allowKillstreaks)?level.botSettings.allowKillstreaks:0) ); SetDvar( "bot_AllowHeroGadgets", (isdefined(level.botSettings.allowHeroGadgets)?level.botSettings.allowHeroGadgets:0) ); SetDvar( "bot_Fov", (isdefined(level.botSettings.fov)?level.botSettings.fov:0) ); SetDvar( "bot_FovAds", (isdefined(level.botSettings.fovAds)?level.botSettings.fovAds:0) ); SetDvar( "bot_PitchSensitivity", level.botSettings.pitchSensitivity ); SetDvar( "bot_YawSensitivity", level.botSettings.yawSensitivity ); SetDvar( "bot_PitchSpeed", (isdefined(level.botSettings.pitchSpeed)?level.botSettings.pitchSpeed:0) ); SetDvar( "bot_PitchSpeedAds", (isdefined(level.botSettings.pitchSpeedAds)?level.botSettings.pitchSpeedAds:0) ); SetDvar( "bot_YawSpeed", (isdefined(level.botSettings.yawSpeed)?level.botSettings.yawSpeed:0) ); SetDvar( "bot_YawSpeedAds", (isdefined(level.botSettings.yawSpeedAds)?level.botSettings.yawSpeedAds:0) ); SetDvar( "pitchAccelerationTime", (isdefined(level.botSettings.pitchAccelerationTime)?level.botSettings.pitchAccelerationTime:0) ); SetDvar( "yawAccelerationTime", (isdefined(level.botSettings.yawAccelerationTime)?level.botSettings.yawAccelerationTime:0) ); SetDvar( "pitchDecelerationThreshold", (isdefined(level.botSettings.pitchDecelerationThreshold)?level.botSettings.pitchDecelerationThreshold:0) ); SetDvar( "yawDecelerationThreshold", (isdefined(level.botSettings.yawDecelerationThreshold)?level.botSettings.yawDecelerationThreshold:0) ); // Cached Settings meleeRange = GetDvarInt( "player_meleeRangeDefault" ) * (isdefined(level.botSettings.meleeRangeMultiplier)?level.botSettings.meleeRangeMultiplier:0); level.botSettings.meleeRange = Int( meleeRange ); level.botSettings.meleeRangeSq = meleeRange * meleeRange; level.botSettings.threatRadiusMinSq = level.botsettings.threatRadiusMin * level.botsettings.threatRadiusMin; level.botSettings.threatRadiusMaxSq = level.botSettings.threatRadiusMax * level.botSettings.threatRadiusMax; lethalDistanceMin = (isdefined(level.botSettings.lethalDistanceMin)?level.botSettings.lethalDistanceMin:0); level.botSettings.lethalDistanceMinSq = lethalDistanceMin * lethalDistanceMin; lethalDistanceMax = (isdefined(level.botSettings.lethalDistanceMax)?level.botSettings.lethalDistanceMax:1024); level.botSettings.lethalDistanceMaxSq = lethalDistanceMax * lethalDistanceMax; tacticalDistanceMin = (isdefined(level.botSettings.tacticalDistanceMin)?level.botSettings.tacticalDistanceMin:0); level.botSettings.tacticalDistanceMinSq = tacticalDistanceMin * tacticalDistanceMin; tacticalDistanceMax = (isdefined(level.botSettings.tacticalDistanceMax)?level.botSettings.tacticalDistanceMax:1024); level.botSettings.tacticalDistanceMaxSq = tacticalDistanceMax * tacticalDistanceMax; level.botSettings.swimVerticalSpeed = GetDvarFloat( "player_swimVerticalSpeedMax" ); level.botSettings.swimTime = GetDvarFloat( "player_swimTime", 5 ) * 1000; } function get_bot_default_settings( ) { return struct::get_script_bundle( "botsettings", "bot_default" ); } // Movement //======================================== function sprint_to_goal() { self.bot.sprintToGoal = true; } function end_sprint_to_goal() { self.bot.sprintToGoal = false; } function sprint_think() { if ( ( isdefined( self.bot.sprintToGoal ) && self.bot.sprintToGoal ) ) { if ( self BotGoalReached() ) { self end_sprint_to_goal(); return; } self press_sprint_button(); return; } } function goal_in_trigger( trigger ) { radius = self get_trigger_radius( trigger ); return distanceSquared( trigger.origin, self BotGetGoalPosition() ) <= radius * radius; } function point_in_goal( point ) { deltaSq = Distance2DSquared( self BotGetGoalPosition(), point ); goalRadius = self BotGetGoalRadius(); return deltaSq <= goalRadius * goalRadius; } function path_to_trigger( trigger, radius ) { // These usually have something inside them, possibly cutting the navmesh if ( trigger.className == "trigger_use" || trigger.className == "trigger_use_touch" ) { if(!isdefined(radius))radius=get_trigger_radius( trigger ); randomAngle = ( 0, RandomInt( 360 ), 0 ); randomVec = AnglesToForward( randomAngle ); point = trigger.origin + randomVec * radius; self BotSetGoal( point ); } if(!isdefined(radius))radius=0; self BotSetGoal( trigger.origin, Int( radius ) ); } function path_to_point_in_trigger( trigger ) { mins = trigger GetMins(); maxs = trigger GetMaxs(); radius = Min( maxs[0], maxs[1] ); height = maxs[2] - mins[2]; minOrigin = trigger.origin + ( 0, 0, mins[2] ); queryHeight = height / 4; queryOrigin = minOrigin + ( 0, 0, queryHeight ); /# if ( GetDvarInt( "bot_drawtriggerquery", 0 ) ) { drawS = 10; Circle( queryOrigin, radius, (0,1,0), false, true, 20*drawS ); Circle( queryOrigin + (0,0,queryHeight), radius, (0,1,0), false, true, 20*drawS ); Circle( queryOrigin - (0,0,queryHeight), radius, (0,1,0), false, true, 20*drawS ); } #/ queryResult = PositionQuery_Source_Navigation( queryOrigin, 0, radius, queryHeight, 17, self ); best_point = undefined; foreach ( point in queryResult.data ) { point.score = randomFloatRange( 0, 100 ); if ( !isdefined( best_point ) || point.score > best_point.score ) { best_point = point; } } if ( isdefined( best_point ) ) { self BotSetGoal( best_point.origin, 24 ); return; } self bot::path_to_trigger( trigger, radius ); } function get_trigger_radius( trigger ) { maxs = trigger GetMaxs(); if ( trigger.classname == "trigger_radius" ) { return maxs[0]; } return Min( maxs[0], maxs[1] ); } function get_trigger_height( trigger ) { maxs = trigger GetMaxs(); if ( trigger.classname == "trigger_radius" ) { return maxs[2]; } return maxs[2] * 2; } // Path Failure //======================================== function check_stuck() { /# if ( !GetDvarInt( "bot_AllowMovement" ) ) { return; } #/ if ( self BotUnderManualControl() || self BotGoalReached() || self util::isstunned() || self IsMeleeing() || self MeleeButtonPressed() || // Target is within 128 units, probably meleeing ( self bot_combat::has_threat() && self.bot.threat.lastDistanceSq < 16384 ) ) { return; } velocity = self GetVelocity(); if ( velocity[0] == 0 && velocity[1] == 0 && ( velocity[2] == 0 || self IsPlayerSwimming() ) ) { if(!isdefined(self.bot.stuckCycles))self.bot.stuckCycles=0; self.bot.stuckCycles++; if ( self.bot.stuckCycles >= 3 ) { /# if ( GetDvarInt( "bot_debugStuck" , 0 ) ) { Sphere( self.origin, 16, ( 1, 0, 0 ), 0.25, false, 16, 1200 ); iprintln( "Bot " + self.name + " not moving at: "+ self.origin ); } #/ self thread stuck_resolution(); } } else { self.bot.stuckCycles = 0; } // Only do this check if we're moving and don't have a visible // Bots could be adsing or meleeing or all kinds of things if ( !self bot_combat::threat_visible() ) { self check_stuck_position(); } } function check_stuck_position() { if ( GetTime() < self.bot.checkPositionTime ) return; self.bot.checkPositionTime = GetTime() + 500; self.bot.positionHistory[self.bot.positionHistoryIndex] = self.origin; self.bot.positionHistoryIndex = ( self.bot.positionHistoryIndex + 1 ) % 5; if ( self.bot.positionHistory.size < 5 ) return; maxDistSq = undefined; for( i = 0; i < self.bot.positionHistory.size; i++ ) { /# if ( GetDvarInt( "bot_debugStuck" , 0 ) ) { Line( self.bot.positionHistory[i], self.bot.positionHistory[i] + ( 0, 0, 72 ), ( 0, 1, 0 ), 1, false, 10 ); } #/ for ( j = i + 1; j < self.bot.positionHistory.size; j++ ) { distSq = DistanceSquared( self.bot.positionHistory[i], self.bot.positionHistory[j] ); // Early out if we find evidence of enough movement if ( distSq > 128 * 128 ) { return; } } } /# if ( GetDvarInt( "bot_debugStuck" , 0 ) ) { Sphere( self.origin, 128, ( 1, 0, 0 ), 0.25, false, 16, 1200 ); iprintln( "Bot " + self.name + " hanging out at: "+ self.origin ); } #/ self thread stuck_resolution(); } function stuck_resolution() { self endon( "death" ); level endon( "game_ended" ); self clear_stuck(); self BotTakeManualControl(); escapeAngle = self GetAngles()[1] + 180 + RandomIntRange( -60, 60 );; escapeDir = AnglesToForward( ( 0, escapeAngle, 0 ) ); self BotSetMoveAngle( escapeDir ); self BotSetMoveMagnitude( 1 ); wait( 1.5 ); self BotReleaseManualControl(); } function clear_stuck() { self.bot.stuckCycles = 0; self.bot.positionHistory = []; self.bot.positionHistoryIndex = 0; self.bot.checkPositionTime = 0; } function camp() { self BotSetGoal( self.origin ); self bot::press_crouch_button(); } // 0 - Unknown // 1 - Invalid Start ( Bot off navmesh ) // 2 - Invalid End ( Can't get desination point on navmesh ) // 3 - Unreachable ( Can't get path ) function wait_bot_path_failed_loop() { self endon( "death" ); level endon( "game_ended" ); while( 1 ) { self waittill( "bot_path_failed", reason ); /# if ( GetDvarInt( "bot_debugStuck" , 0 ) ) { goalPosition = self BotGetGoalPosition(); Box( self.origin, ( -15, -15, 0 ), ( 15, 15, 72 ), 0, ( 0, 1, 0 ), 0.25, false, 1200 ); Box( goalPosition, ( -15, -15, 0 ), ( 15, 15, 72 ), 0, ( 1, 0, 0 ), 0.25, false, 1200 ); Line( self.origin, goalPosition, ( 1, 1, 1 ), 1, false, 1200 ); iprintln( "Bot " + self.name + " path failed from: " + self.origin + " to: " + goalPosition ); } #/ self thread stuck_resolution(); } } function wait_bot_goal_reached_loop() { self endon( "death" ); level endon( "game_ended" ); while( 1 ) { self waittill( "bot_goal_reached", reason ); self clear_stuck(); } } // Hero Stuff //======================================== function stow_gun_gadget() { currentWeapon = self GetCurrentWeapon(); if ( self GetWeaponAmmoClip( currentWeapon ) || !currentWeapon.isheroweapon) { return; } if ( isdefined( self.lastDroppableWeapon ) && self hasWeapon(self.lastDroppableWeapon) ) { self SwitchToWeapon( self.lastDroppableWeapon ); } } function get_ready_gadget( ) { weapons = self GetWeaponsList(); foreach( weapon in weapons ) { slot = self GadgetGetSlot( weapon ); if ( slot < 0 || !self GadgetIsReady( slot ) || self GadgetIsActive( slot ) ) { continue; } return weapon; } return level.weaponNone; } function get_ready_gun_gadget() { weapons = self GetWeaponsList(); foreach( weapon in weapons ) { if ( !is_gun_gadget( weapon ) ) { continue; } slot = self GadgetGetSlot( weapon ); if ( slot < 0 || !self GadgetIsReady( slot ) || self GadgetIsActive( slot ) ) { continue; } return weapon; } return level.weaponNone; } function is_gun_gadget( weapon ) { if ( !isdefined( weapon ) || weapon == level.weaponNone || !weapon.isHeroWeapon ) { return false; } // TODO: May need to add more of these return weapon.isBulletWeapon || weapon.isProjectileWeapon || weapon.isLauncher || weapon.isGasWeapon; } function activate_hero_gadget( weapon ) { if ( !isdefined( weapon ) || weapon == level.weaponNone || !weapon.isgadget ) { return; } if ( is_gun_gadget( weapon ) ) { self SwitchToWeapon( weapon ); } else if ( weapon.isHeroWeapon ) { self bot::tap_offhand_special_button(); } else { self BotPressButtonForGadget( weapon ); } } // Coop Methods //======================================== function coop_pre_combat() { self bot_combat::bot_pre_combat(); if ( self bot_combat::has_threat() ) { return; } if ( self IsReloading() || self IsSwitchingWeapons() || self IsThrowingGrenade() || self FragButtonPressed() || self SecondaryOffhandButtonPressed() || self IsMeleeing() || self IsRemoteControlling() || self IsInVehicle() || self IsWeaponViewOnlyLinked() ) { return; } if ( self bot_combat::switch_weapon() ) { return; } if ( self bot_combat::reload_weapon() ) { return; } } function coop_post_combat() { if ( self revive_players() ) { if ( self bot_combat::has_threat() ) { self bot_combat::clear_threat(); self BotSetGoal( self.origin ); } return; } self bot_combat::bot_post_combat(); } // Following //======================================== function follow_coop_players() { // Favor the host host = bot::get_host_player(); if ( !IsAlive( host ) ) { players = ArraySort( level.players, self.origin ); foreach( player in players ) { if ( !player util::is_bot() && player.team == self.team && IsAlive( player ) ) { break; } } } else { player = host; } if ( isdefined( player ) ) { //self thread follow_entity( player, COOP_FOLLOW_RADIUS_MIN, COOP_FOLLOW_RADIUS_MAX ); fwd = AnglesToForward( player.angles ); botDir = self.origin - player.origin; if ( VectorDot( botDir, fwd ) < 0 ) self thread lead_player( player, 150 ); } } function lead_player( player, followMin ) { radiusMin = followMin - 32; radiusMax = followMin; dotMin = 0.85; dotMax = 0.92; queryResult = PositionQuery_Source_Navigation( player.origin, radiusMin, radiusMax, 150, 32, self ); fwd = AnglesToForward( player.angles ); point = player.origin + fwd * 72; self BotSetGoal( point, 42 ); self sprint_to_goal(); } function follow_entity( entity, radiusMin, radiusMax ) { if(!isdefined(radiusMin))radiusMin=24; if(!isdefined(radiusMax))radiusMax=radiusMin + 1; if ( !point_in_goal( entity.origin ) ) { radius = RandomIntRange( radiusMin, radiusMax ); self BotSetGoal( entity.origin, radius ); self sprint_to_goal(); } } // Navmesh Wander //======================================== function navmesh_wander( fwd, radiusMin, radiusMax, spacing, fwdDot ) { if(!isdefined(radiusMin))radiusMin=(isdefined(level.botSettings.wanderMin)?level.botSettings.wanderMin:0); if(!isdefined(radiusMax))radiusMax=(isdefined(level.botSettings.wanderMax)?level.botSettings.wanderMax:0); if(!isdefined(spacing))spacing=(isdefined(level.botSettings.wanderSpacing)?level.botSettings.wanderSpacing:0); if(!isdefined(fwdDot))fwdDot=(isdefined(level.botSettings.wanderFwdDot)?level.botSettings.wanderFwdDot:0); if(!isdefined(fwd))fwd=AnglesToForward( self.angles ); // Don't factor in pitch or elevation fwd = VectorNormalize( ( fwd[0], fwd[1], 0 ) ); /# //Circle( self.origin, radiusMin, ( 1, 1, 1 ), false, true, 3 * 20 ); //Circle( self.origin, radiusMax, ( 1, 1, 1 ), false, true, 3 * 20 ); //Line( self.origin, self.origin + ( fwd * radiusMax ), ( 1, 1, 1 ), 1, false, 3 * 20 ); #/ queryResult = PositionQuery_Source_Navigation( self.origin, radiusMin, radiusMax, 150, spacing, self ); best_point = undefined; origin = ( self.origin[0], self.origin[1], 0 ); foreach ( point in queryResult.data ) { movePoint = ( point.origin[0], point.origin[1], 0 ); moveDir = VectorNormalize( movePoint - origin ); dot = VectorDot( moveDir, fwd ); point.score = MapFloat( radiusMin, radiusMax, 0, 50, point.distToOrigin2D ); if ( dot > fwdDot ) { point.score += randomFloatRange( 30, 50 ); } else if ( dot > 0 ) { point.score += randomFloatRange( 10, 35 ); } else { point.score += randomFloatRange( 0, 15 ); } /# //Line( point.origin, point.origin + ( 0, 0, point.score ), ( 0, 0, 1 ), 1, false, 3 * 20 ); #/ if ( !isdefined( best_point ) || point.score > best_point.score ) { best_point = point; } } if( isdefined( best_point ) ) { /# //Circle( best_point.origin, 16, ( 0, 1, 0 ), false, true, 3 * 20 ); #/ self BotSetGoal( best_point.origin, radiusMin ); } else { /# if ( GetDvarInt( "bot_debugStuck" , 0 ) ) { Circle( self.origin, radiusMin, ( 1, 0, 0 ), false, true, 1200 ); Circle( self.origin, radiusMax, ( 1, 0, 0 ), false, true, 1200 ); Sphere( self.origin, 16, ( 0, 1, 0 ), 0.25, false, 16, 1200 ); iprintln( "Bot " + self.name + " can't find wander point at: "+ self.origin ); } #/ self thread stuck_resolution(); } } // Goal Approach Pathing //======================================== function approach_goal_trigger( trigger, radiusMax, spacing ) { if(!isdefined(radiusMax))radiusMax=1500; if(!isdefined(spacing))spacing=128; distSq = DistanceSquared( self.origin, trigger.origin ); if ( distSq < radiusMax * radiusMax ) { self path_to_point_in_trigger( trigger ); return; } radiusMin = self get_trigger_radius( trigger ); self approach_point( trigger.origin, radiusMin, radiusMax, spacing ); } function approach_point( point, radiusMin, radiusMax, spacing ) { if(!isdefined(radiusMin))radiusMin=0; if(!isdefined(radiusMax))radiusMax=1500; if(!isdefined(spacing))spacing=128; distSq = DistanceSquared( self.origin, point ); if ( distSq < radiusMax * radiusMax ) { self BotSetGoal( point, 24 ); return; } queryResult = PositionQuery_Source_Navigation( point, radiusMin, radiusMax, 150, spacing, self ); fwd = AnglesToForward( self.angles ); // Don't factor in pitch or elevation fwd = ( fwd[0], fwd[1], 0 ); origin = ( self.origin[0], self.origin[1], 0 ); best_point = undefined; foreach ( point in queryResult.data ) { movePoint = ( point.origin[0], point.origin[1], 0 ); moveDir = VectorNormalize( movePoint - origin ); dot = VectorDot( moveDir, fwd ); point.score = randomFloatRange( 0, 50 ); if ( dot < .5 ) // Favor points in the 240 degree arc towards the bot { point.score += randomFloatRange( 30, 50 ); } else { point.score += randomFloatRange( 0, 15 ); } if ( !isdefined( best_point ) || point.score > best_point.score ) { best_point = point; } } if ( isdefined( best_point ) ) { self BotSetGoal( best_point.origin, 24 ); } } // Revive //======================================== function revive_players() { players = self get_team_players_in_laststand(); if ( players.size > 0 ) { revive_player( players[0] ); return true; } return false; } function get_team_players_in_laststand() { players = []; foreach( player in level.players ) { if ( player != self && player laststand::player_is_in_laststand() && player.team == self.team ) { players[players.size] = player; } } players = ArraySort( players, self.origin ); return players; } function revive_player( player ) { if ( !point_in_goal( player.origin ) ) { self BotSetGoal( player.origin, 64 ); self sprint_to_goal(); return; } if ( self BotGoalReached() ) { self BotSetLookAnglesFromPoint( player GetCentroid() ); self tap_use_button(); } } // Cornering //======================================== function watch_bot_corner( startCornerDist, cornerDist ) { self endon( "death" ); self endon( "bot_combat_target" ); level endon( "game_ended" ); if(!isdefined(startCornerDist))startCornerDist=64; if(!isdefined(cornerDist))cornerDist=128; startCornerDistSq = cornerDist * cornerDist; cornerDistSq = cornerDist * cornerDist; while ( 1 ) { self waittill( "bot_corner", centerPoint, enterPoint, leavePoint, angle, nextEnterPoint ); if ( self bot_combat::has_threat() ) { continue; } if( Distance2DSquared( self.origin, enterPoint ) < startCornerDistSq || Distance2DSquared( leavePoint, nextEnterPoint ) < cornerDistSq ) { continue; } self thread wait_corner_radius( startCornerDistSq, centerPoint, enterPoint, leavePoint, angle, nextEnterPoint ); } } function wait_corner_radius( startCornerDistSq, centerPoint, enterPoint, leavePoint, angle, nextEnterPoint ) { self endon( "death" ); self endon( "bot_corner" ); self endon( "bot_goal_reached" ); self endon( "bot_combat_target" ); level endon( "game_ended" ); while( Distance2DSquared( self.origin, enterPoint ) > startCornerDistSq ) { if ( self bot_combat::has_threat() ) { return; } {wait(.05);}; } // + standing viewheight self BotLookAtPoint( ( nextEnterPoint[0], nextEnterPoint[1], nextEnterPoint[1] + 60 ) ); self thread finish_corner(); } function finish_corner() { self endon( "death" ); self endon( "combat_target" ); level endon( "game_ended" ); self util::waittill_any( "bot_corner", "bot_goal_reached" ); self BotLookForward(); } // Utilities //======================================== function get_host_player() { players = GetPlayers(); foreach( player in players ) { if ( player IsHost() ) { return player; } } return undefined; } function fwd_dot( point ) { angles = self GetPlayerAngles(); fwd = AnglesToForward( angles ); delta = point - self GetEye(); delta = VectorNormalize( delta ); dot = VectorDot( fwd, delta ); return dot; } // TODO: fwd_dot2d ? function has_launcher() { weapons = self GetWeaponsList(); foreach( weapon in weapons ) { if ( weapon.isRocketLauncher ) { return true; } } return false; } function kill_bot() { self DoDamage( self.health, self.origin ); } /# // Debugging //======================================== function kill_bots() { foreach( player in level.players ) { if ( player util::is_bot() ) { player kill_bot(); } } } function add_bot_at_eye_trace( team ) { host = util::getHostPlayer(); trace = host eye_trace(); direction_vec = host.origin - trace["position"]; direction = VectorToAngles( direction_vec ); yaw = direction[1]; bot = add_bot( team ); if ( isdefined( bot ) ) { bot waittill( "spawned_player" ); bot SetOrigin( trace[ "position" ] ); bot SetPlayerAngles( ( bot.angles[0], yaw, bot.angles[2] ) ); } return bot; } function eye_trace() { direction = self GetPlayerAngles(); direction_vec = AnglesToForward( direction ); eye = self GetEye(); scale = 8000; direction_vec = ( direction_vec[0] * scale, direction_vec[1] * scale, direction_vec[2] * scale ); return bullettrace( eye, eye + direction_vec, 0, undefined ); } // Route Debugging //======================================== function devgui_debug_route() { iprintln( "Debug Patrol:" ); points = self get_nav_points(); if ( !isdefined( points ) || points.size == 0 ) { iprintln( "Route Debug Cancelled" ); return; } iprintln( "Sending bots to chosen points" ); players = GetPlayers(); foreach( player in players ) { if ( !player util::is_bot() ) { continue; } player thread debug_patrol( points ); } } function get_nav_points() { iprintln( "Square (X) - Add Point" ); iprintln( "Cross (A) - Done" ); iprintln( "Circle (B) - Cancel" ); points = []; while ( 1 ) { {wait(.05);}; point = self eye_trace()["position"]; if ( isdefined( point ) ) { point = GetClosestPointOnNavMesh( point, 128 ); if ( isdefined( point ) ) { Sphere( point, 16, ( 0, 0, 1 ), 0.25, false, 16, 1 ); } } if ( self ButtonPressed( "BUTTON_X" ) ) { if ( isdefined( point ) && ( points.size == 0 || Distance2D( point, points[points.size-1] ) > 16 ) ) { points[points.size] = point; } } else if ( self ButtonPressed( "BUTTON_A" ) ) { return points; } else if ( self ButtonPressed( "BUTTON_B" ) ) { return undefined; } for ( i = 0; i < points.size; i++ ) { Sphere( points[i], 16, ( 0, 1, 0 ), 0.25, false, 16, 1 ); } } } function debug_patrol( points ) { self notify( "debug_patrol" ); self endon( "death" ); self endon( "debug_patrol" ); i = 0; //self end_sprint_to_goal(); while( 1 ) { self BotSetGoal( points[i], 24 ); self bot::sprint_to_goal(); self waittill( "bot_goal_reached" ); i = ( i + 1 ) % points.size; } } // Devgui //======================================== function bot_devgui_think() { while( 1 ) { wait( 0.25 ); cmd = GetDvarString( "devgui_bot", "" ); if ( !isdefined( level.botDevguiCmd ) || ![[level.botDevguiCmd]](cmd) ) { host = util::getHostPlayer(); switch( cmd ) { case "remove_all": remove_bots(); break; case "laststand": kill_bots(); break; case "routes": host devgui_debug_route(); break; default: break; } } SetDvar( "devgui_bot", "" ); } } function coop_bot_devgui_cmd( cmd ) { host = get_host_player(); switch( cmd ) { case "add": add_bot( host.team ); return true; case "add_3": add_bots( 3, host.team ); return true; case "add_crosshair": add_bot_at_eye_trace(); return true; case "remove": remove_bots( 1 ); return true; break; } return false; } // Debug Drawing //======================================== function debug_star( origin, seconds, color ) { if ( !isdefined( seconds ) ) { seconds = 1; } if ( !isdefined( color ) ) { color = ( 1, 0, 0 ); } frames = Int( 20 * seconds ); DebugStar( origin, frames, color ); } #/