2023-04-13 17:30:38 +02:00

1549 lines
38 KiB
Plaintext

#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 );
}
#/