3332 lines
69 KiB
Plaintext
3332 lines
69 KiB
Plaintext
/*
|
|
_bot_internal
|
|
Author: INeedGames
|
|
Date: 09/26/2020
|
|
The interal workings of the bots.
|
|
Bots will do the basics, aim, move.
|
|
*/
|
|
|
|
#include common_scripts\utility;
|
|
#include maps\mp\_utility;
|
|
#include maps\mp\gametypes\_hud_util;
|
|
#include maps\mp\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
|
|
|
|
self.pers[ "bots" ][ "unlocks" ] = [];
|
|
}
|
|
|
|
/*
|
|
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();
|
|
}
|
|
|
|
/*
|
|
The callback hook for when the bot gets killed.
|
|
*/
|
|
onKilled( eInflictor, eAttacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, timeOffset, deathAnimDuration )
|
|
{
|
|
}
|
|
|
|
/*
|
|
The callback hook when the bot gets damaged.
|
|
*/
|
|
onDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, timeOffset )
|
|
{
|
|
}
|
|
|
|
/*
|
|
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.targets = [];
|
|
self.bot.target = undefined;
|
|
self.bot.target_this_frame = undefined;
|
|
self.bot.jav_loc = undefined;
|
|
self.bot.after_target = undefined;
|
|
self.bot.after_target_pos = undefined;
|
|
|
|
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.moveto = self.origin;
|
|
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.knifing_target = undefined;
|
|
|
|
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.is_cur_akimbo = false;
|
|
|
|
self.bot.prio_objective = false;
|
|
|
|
self.bot.rand = randomint( 100 );
|
|
|
|
self BotBuiltinBotStop();
|
|
}
|
|
|
|
/*
|
|
When the bot spawns.
|
|
*/
|
|
onPlayerSpawned()
|
|
{
|
|
self endon( "disconnect" );
|
|
|
|
for ( ;; )
|
|
{
|
|
self waittill( "spawned_player" );
|
|
|
|
self resetBotVars();
|
|
self thread onWeaponChange();
|
|
self thread onLastStand();
|
|
|
|
self thread reload_watch();
|
|
self thread sprint_watch();
|
|
|
|
self thread watchUsingRemote();
|
|
|
|
self thread spawned();
|
|
}
|
|
}
|
|
|
|
/*
|
|
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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Is the weap a sniper
|
|
*/
|
|
IsWeapSniper( weap )
|
|
{
|
|
if ( weap == "none" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( weaponclass( weap ) != "sniper" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
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 );
|
|
self.bot.is_cur_akimbo = issubstr( newWeapon, "_akimbo_" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
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;
|
|
}
|
|
|
|
/*
|
|
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();
|
|
}
|
|
}
|
|
|
|
/*
|
|
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();
|
|
}
|
|
}
|
|
|
|
/*
|
|
When the bot enters laststand, we fix the weapons
|
|
*/
|
|
onLastStand()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
while ( true )
|
|
{
|
|
while ( !self inLastStand() )
|
|
{
|
|
wait 0.05;
|
|
}
|
|
|
|
self notify( "kill_goal" );
|
|
|
|
while ( self inLastStand() )
|
|
{
|
|
wait 0.05;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
When the bot uses a remote killstreak
|
|
*/
|
|
watchUsingRemote()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "spawned_player" );
|
|
|
|
for ( ;; )
|
|
{
|
|
wait 1;
|
|
|
|
if ( !isalive( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !self isusingremote() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( isdefined( level.chopper ) && isdefined( level.chopper.gunner ) && level.chopper.gunner == self )
|
|
{
|
|
self watchUsingMinigun();
|
|
}
|
|
|
|
if ( isdefined( level.ac130player ) && level.ac130player == self )
|
|
{
|
|
self thread watchAc130Weapon();
|
|
self watchUsingAc130();
|
|
}
|
|
|
|
if ( isdefined( self.rocket ) )
|
|
{
|
|
self watchUsingPred();
|
|
self BotBuiltinBotAction( "-remote" );
|
|
}
|
|
|
|
self.bot.targets = [];
|
|
self notify( "kill_goal" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
Returns the angle delta
|
|
*/
|
|
getRemoteAngleSpeed( len )
|
|
{
|
|
furthest = 10.0;
|
|
max_speed = 127;
|
|
|
|
switch ( self.pers[ "bots" ][ "skill" ][ "base" ] )
|
|
{
|
|
case 1:
|
|
furthest = 5.0;
|
|
max_speed = 20;
|
|
break;
|
|
|
|
case 2:
|
|
furthest = 6.0;
|
|
max_speed = 35;
|
|
break;
|
|
|
|
case 3:
|
|
furthest = 7.0;
|
|
max_speed = 55;
|
|
break;
|
|
|
|
case 4:
|
|
furthest = 8.0;
|
|
max_speed = 65;
|
|
break;
|
|
|
|
case 5:
|
|
furthest = 9.0;
|
|
max_speed = 75;
|
|
break;
|
|
|
|
case 6:
|
|
furthest = 10.0;
|
|
max_speed = 100;
|
|
break;
|
|
|
|
case 7:
|
|
furthest = 15.0;
|
|
max_speed = 127;
|
|
break;
|
|
}
|
|
|
|
if ( len >= furthest )
|
|
{
|
|
return max_speed;
|
|
}
|
|
|
|
if ( len <= 0.0 )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return Round( ( len / furthest ) * max_speed );
|
|
}
|
|
|
|
/*
|
|
time to boost the rocket
|
|
*/
|
|
getRemoteBoostTime()
|
|
{
|
|
switch ( self.pers[ "bots" ][ "skill" ][ "base" ] )
|
|
{
|
|
case 1:
|
|
return 99999;
|
|
|
|
case 2:
|
|
return 15000;
|
|
|
|
case 3:
|
|
return 10000;
|
|
|
|
case 4:
|
|
return 5000;
|
|
|
|
case 5:
|
|
return 2500;
|
|
|
|
case 6:
|
|
return 1000;
|
|
|
|
case 7:
|
|
return 500;
|
|
|
|
default:
|
|
return 500;
|
|
}
|
|
}
|
|
|
|
/*
|
|
While in rocket
|
|
*/
|
|
watchUsingPred()
|
|
{
|
|
self.rocket endon( "death" );
|
|
|
|
self BotBuiltinBotRemoteAngles( 0, 0 );
|
|
self BotBuiltinBotAction( "+remote" );
|
|
|
|
pressedFire = false;
|
|
sTime = gettime();
|
|
|
|
while ( isdefined( self.rocket ) )
|
|
{
|
|
self.bot.targets = []; // dont want to fire from aim thread
|
|
// because geteye doesnt return the eye of the missile
|
|
|
|
target = undefined;
|
|
myeye = self.rocket.origin;
|
|
myangles = self.rocket.angles;
|
|
bestfov = 0.0;
|
|
|
|
for ( i = level.players.size - 1; i >= 0; i-- )
|
|
{
|
|
player = level.players[ i ];
|
|
|
|
if ( !isdefined( player ) || !isdefined( player.team ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( player == self || ( level.teambased && player.team == self.team ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( player.sessionstate != "playing" || !isreallyalive( player ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( player _hasperk( "specialty_coldblooded" ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !bullettracepassed( myeye, player.origin + ( 0, 0, 25 ), false, self.rocket ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
thisfov = getConeDot( player.origin, myeye, myangles );
|
|
|
|
if ( thisfov < 0.75 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( isdefined( target ) && thisfov < bestfov )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
target = player;
|
|
bestfov = thisfov;
|
|
}
|
|
|
|
if ( isdefined( target ) )
|
|
{
|
|
if ( !pressedFire && gettime() - sTime > self getRemoteBoostTime() )
|
|
{
|
|
pressedFire = true;
|
|
self thread pressFire();
|
|
}
|
|
|
|
if ( bestfov < 0.999995 && distancesquared( target.origin, myeye ) > 256 * 256 )
|
|
{
|
|
angles = vectortoangles( ( target.origin - myeye ) - anglestoforward( myangles ) );
|
|
angles -= myangles;
|
|
angles = ( angleclamp180( angles[ 0 ] ), angleclamp180( angles[ 1 ] ), 0 );
|
|
angles = vectornormalize( angles ) * self getRemoteAngleSpeed( length( angles ) );
|
|
|
|
self BotBuiltinBotRemoteAngles( int( angles[ 0 ] ), int( angles[ 1 ] ) );
|
|
}
|
|
else
|
|
{
|
|
self BotBuiltinBotRemoteAngles( 0, 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self BotBuiltinBotRemoteAngles( 0, 0 );
|
|
}
|
|
|
|
wait 0.05;
|
|
}
|
|
}
|
|
|
|
/*
|
|
WHen it uses the helicopter minigun
|
|
*/
|
|
watchUsingMinigun()
|
|
{
|
|
self endon( "heliPlayer_removed" );
|
|
|
|
while ( isdefined( level.chopper ) && isdefined( level.chopper.gunner ) && level.chopper.gunner == self )
|
|
{
|
|
if ( self getcurrentweapon() != "heli_remote_mp" )
|
|
{
|
|
self switchtoweapon( "heli_remote_mp" );
|
|
}
|
|
|
|
if ( isdefined( self.bot.target ) )
|
|
{
|
|
self thread pressFire();
|
|
}
|
|
|
|
wait 0.05;
|
|
}
|
|
}
|
|
|
|
/*
|
|
When it uses the ac130
|
|
*/
|
|
watchAc130Weapon()
|
|
{
|
|
self endon( "ac130player_removed" );
|
|
self endon( "disconnect" );
|
|
self endon( "spawned_player" );
|
|
|
|
while ( isdefined( level.ac130player ) && level.ac130player == self )
|
|
{
|
|
curWeap = self getcurrentweapon();
|
|
|
|
if ( curWeap != "ac130_105mm_mp" && curWeap != "ac130_40mm_mp" && curWeap != "ac130_25mm_mp" )
|
|
{
|
|
self switchtoweapon( "ac130_105mm_mp" );
|
|
}
|
|
|
|
if ( isdefined( self.bot.target ) )
|
|
{
|
|
self thread pressFire();
|
|
}
|
|
|
|
wait 0.05;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Swap between the ac130 weapons while in it
|
|
*/
|
|
watchUsingAc130()
|
|
{
|
|
self endon( "ac130player_removed" );
|
|
|
|
while ( isdefined( level.ac130player ) && level.ac130player == self )
|
|
{
|
|
self switchtoweapon( "ac130_105mm_mp" );
|
|
wait 1 + randomint( 2 );
|
|
self switchtoweapon( "ac130_40mm_mp" );
|
|
wait 2 + randomint( 2 );
|
|
self switchtoweapon( "ac130_25mm_mp" );
|
|
wait 3 + randomint( 2 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
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_danager();
|
|
self thread target();
|
|
self thread updateBones();
|
|
self thread aim();
|
|
self thread check_reload();
|
|
self thread stance();
|
|
self thread onNewEnemy();
|
|
self thread walk();
|
|
self thread watchHoldBreath();
|
|
self thread watchGrenadeFire();
|
|
self thread watchPickupGun();
|
|
|
|
self notify( "bot_spawned" );
|
|
}
|
|
|
|
/*
|
|
watchPickupGun
|
|
*/
|
|
watchPickupGun()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
for ( ;; )
|
|
{
|
|
wait 1;
|
|
|
|
if ( self usebuttonpressed() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// todo have bots use turrets instead of just kicking them off of it
|
|
if ( isdefined( self.turret ) )
|
|
{
|
|
self thread use( 0.5 );
|
|
continue;
|
|
}
|
|
|
|
weap = self getcurrentweapon();
|
|
|
|
if ( weap != "none" && self getammocount( weap ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
self thread use( 0.5 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
Watches when the bot fires a grenade
|
|
*/
|
|
watchGrenadeFire()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
for ( ;; )
|
|
{
|
|
self waittill( "grenade_fire", nade, weapname );
|
|
|
|
if ( !isdefined( nade ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( weapname == "c4_mp" )
|
|
{
|
|
self thread watchC4Thrown( nade );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Watches the c4
|
|
*/
|
|
watchC4Thrown( c4 )
|
|
{
|
|
self endon( "disconnect" );
|
|
c4 endon( "death" );
|
|
|
|
wait 0.5;
|
|
|
|
for ( ;; )
|
|
{
|
|
wait 1 + randomint( 50 ) * 0.05;
|
|
|
|
shouldBreak = false;
|
|
|
|
for ( i = 0; i < level.players.size; i++ )
|
|
{
|
|
player = level.players[ i ];
|
|
|
|
if ( player == self )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( ( level.teambased && self.team == player.team ) || player.sessionstate != "playing" || !isreallyalive( player ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( distancesquared( c4.origin, player.origin ) > 200 * 200 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !bullettracepassed( c4.origin, player.origin + ( 0, 0, 25 ), false, c4 ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
shouldBreak = true;
|
|
}
|
|
|
|
if ( shouldBreak )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( self getcurrentweapon() != "c4_mp" )
|
|
{
|
|
self notify( "alt_detonate" );
|
|
}
|
|
else
|
|
{
|
|
self thread pressFire();
|
|
}
|
|
}
|
|
|
|
/*
|
|
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 );
|
|
}
|
|
|
|
// climb through windows
|
|
if ( self ismantling() )
|
|
{
|
|
data.wasmantling = true;
|
|
self crouch();
|
|
}
|
|
else if ( data.wasmantling )
|
|
{
|
|
data.wasmantling = false;
|
|
self stand();
|
|
}
|
|
|
|
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 && !self isonladder() )
|
|
{
|
|
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 ) || isdefined( self.bot.knifing_target ) )
|
|
{
|
|
dir = ( 127, dir[ 1 ], 0 );
|
|
}
|
|
|
|
self BotBuiltinBotMovement( int( dir[ 0 ] ), int( dir[ 1 ] ) );
|
|
}
|
|
|
|
/*
|
|
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 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
The hold breath thread.
|
|
*/
|
|
watchHoldBreath()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
for ( ;; )
|
|
{
|
|
wait 1;
|
|
|
|
if ( self.bot.isfrozen )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
self holdbreath( self playerads() > 0 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
Throws back frag grenades
|
|
*/
|
|
grenade_danager_loop()
|
|
{
|
|
myEye = self geteye();
|
|
|
|
for ( i = level.bots_fraglist.count - 1; i >= 0; i-- )
|
|
{
|
|
frag = level.bots_fraglist.data[ i ];
|
|
|
|
if ( level.teambased && frag.team == self.team )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( lengthsquared( frag.velocity ) > 10000 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( distancesquared( self.origin, frag.origin ) > 20000 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !bullettracepassed( myEye, frag.origin, false, frag.grenade ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
self BotNotifyBotEvent( "throwback", "stop", frag );
|
|
self thread frag();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Throws back frag grenades
|
|
*/
|
|
grenade_danager()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
for ( ;; )
|
|
{
|
|
wait 1;
|
|
|
|
if ( self inLastStand() && !self _hasperk( "specialty_laststandoffhand" ) && !self inFinalStand() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( self.bot.isfrozen || level.gameended || !gameflag( "prematch_done" ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( self.bot.isfraggingafter || self.bot.issmokingafter || self isusingremote() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( self isDefusing() || self isPlanting() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( !getdvarint( "bots_play_nade" ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
self grenade_danager_loop();
|
|
}
|
|
}
|
|
|
|
/*
|
|
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()
|
|
{
|
|
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 ( self.hasriotshieldequipped && isdefined( self.bot.target ) && isdefined( self.bot.target.entity ) && isplayer( self.bot.target.entity ) )
|
|
{
|
|
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();
|
|
}
|
|
|
|
chance = self.pers[ "bots" ][ "behavior" ][ "sprint" ];
|
|
|
|
if ( gettime() - 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( self getcurrentweapon() ) && self isInRange( self.bot.target.dist, self getcurrentweapon() ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self.bot.sprintendtime != -1 && gettime() - self.bot.sprintendtime < 2000 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !isdefined( self.bot.towards_goal ) || distancesquared( self.origin, physicstrace( self geteye(), self geteye() + 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.bot.climbing = false;
|
|
|
|
if ( self.bot.isfrozen || self isusingremote() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
self stance_loop();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Bot will wait until firing.
|
|
*/
|
|
check_reload()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
for ( ;; )
|
|
{
|
|
self waittill_notify_or_timeout( "weapon_fired", 5 );
|
|
self thread reload_thread();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Bot will reload after firing if needed.
|
|
*/
|
|
reload_thread()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
self endon( "weapon_fired" );
|
|
|
|
wait 2.5;
|
|
|
|
if ( self.bot.isfrozen || level.gameended || !gameflag( "prematch_done" ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( isdefined( self.bot.target ) || self.bot.isreloading || self.bot.isfraggingafter || self.bot.issmokingafter || self.bot.isfrozen )
|
|
{
|
|
return;
|
|
}
|
|
|
|
cur = self getcurrentweapon();
|
|
|
|
if ( cur == "" || cur == "none" )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( isweaponcliponly( cur ) || !self getweaponammostock( cur ) || self isusingremote() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
maxsize = weaponclipsize( cur );
|
|
cursize = self getweaponammoclip( cur );
|
|
|
|
if ( cursize / maxsize < 0.5 )
|
|
{
|
|
self thread reload();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Updates the bot's target bone
|
|
*/
|
|
updateBones()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "spawned_player" );
|
|
|
|
for ( ;; )
|
|
{
|
|
oldbones = self.pers[ "bots" ][ "skill" ][ "bones" ];
|
|
bones = strtok( oldbones, "," );
|
|
|
|
while ( oldbones == self.pers[ "bots" ][ "skill" ][ "bones" ] )
|
|
{
|
|
self waittill_any_timeout( self.pers[ "bots" ][ "skill" ][ "bone_update_interval" ], "new_enemy" );
|
|
|
|
if ( !isalive( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !isdefined( self.bot.target ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
self.bot.target.bone = random( bones );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
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.offset = undefined;
|
|
obj.bone = undefined;
|
|
obj.aim_offset = undefined;
|
|
obj.aim_offset_base = undefined;
|
|
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
Updates the target object's difficulty missing aim, inaccurate shots
|
|
*/
|
|
updateAimOffset( obj, theTime )
|
|
{
|
|
if ( !isdefined( obj.aim_offset_base ) )
|
|
{
|
|
offsetAmount = self.pers[ "bots" ][ "skill" ][ "aim_offset_amount" ];
|
|
|
|
if ( offsetAmount > 0 )
|
|
{
|
|
obj.aim_offset_base = ( randomfloatrange( 0 - offsetAmount, offsetAmount ),
|
|
randomfloatrange( 0 - offsetAmount, offsetAmount ),
|
|
randomfloatrange( 0 - offsetAmount, offsetAmount ) );
|
|
}
|
|
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 target object to be traced Has LOS
|
|
*/
|
|
targetObjUpdateTraced( obj, daDist, ent, theTime, isScriptObj, usingRemote )
|
|
{
|
|
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 ( !usingRemote && !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, theTime );
|
|
}
|
|
|
|
/*
|
|
Updates the target object to be not traced No LOS
|
|
*/
|
|
targetObjUpdateNoTrace( obj )
|
|
{
|
|
obj.no_trace_time += 50;
|
|
obj.trace_time = 0;
|
|
obj.didlook = false;
|
|
}
|
|
|
|
/*
|
|
Returns true if myEye can see the bone of self
|
|
*/
|
|
checkTraceForBone( myEye, bone )
|
|
{
|
|
boneLoc = self gettagorigin( bone );
|
|
|
|
if ( !isdefined( boneLoc ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
trace = bullettrace( myEye, boneLoc, false, undefined );
|
|
|
|
return ( sighttracepassed( myEye, boneLoc, false, undefined ) && ( trace[ "fraction" ] >= 1.0 || trace[ "surfacetype" ] == "glass" ) );
|
|
}
|
|
|
|
/*
|
|
The main target thread, will update the bot's main target. Will auto target enemy players and handle script targets.
|
|
*/
|
|
target_loop()
|
|
{
|
|
myEye = self geteye();
|
|
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 );
|
|
usingRemote = self isusingremote();
|
|
ignoreSmoke = issubstr( self getcurrentweapon(), "_thermal_" );
|
|
vehEnt = undefined;
|
|
adsAmount = self playerads();
|
|
adsFovFact = self.pers[ "bots" ][ "skill" ][ "ads_fov_multi" ];
|
|
|
|
if ( usingRemote )
|
|
{
|
|
if ( isdefined( level.ac130player ) && level.ac130player == self )
|
|
{
|
|
vehEnt = level.ac130.planemodel;
|
|
}
|
|
|
|
if ( isdefined( level.chopper ) && isdefined( level.chopper.gunner ) && level.chopper.gunner == self )
|
|
{
|
|
vehEnt = level.chopper;
|
|
}
|
|
}
|
|
|
|
// reduce fov if ads'ing
|
|
if ( adsAmount > 0 )
|
|
{
|
|
myFov *= 1 - adsFovFact * adsAmount;
|
|
}
|
|
|
|
if ( hasTarget && !isdefined( self.bot.target.entity ) )
|
|
{
|
|
self.bot.target = undefined;
|
|
hasTarget = false;
|
|
}
|
|
|
|
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 ( ignoreSmoke || ( SmokeTrace( myEye, entOrigin, level.smokeradius ) ) && bullettracepassed( myEye, entOrigin, false, ent ) )
|
|
{
|
|
if ( !isObjDef )
|
|
{
|
|
obj = self createTargetObj( ent, theTime );
|
|
obj.offset = self.bot.script_target_offset;
|
|
|
|
self.bot.targets[ key ] = obj;
|
|
}
|
|
|
|
self targetObjUpdateTraced( obj, daDist, ent, theTime, true, usingRemote );
|
|
}
|
|
else
|
|
{
|
|
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 );
|
|
|
|
if ( usingRemote )
|
|
{
|
|
daDist = 0;
|
|
}
|
|
|
|
isObjDef = isdefined( obj );
|
|
|
|
if ( ( level.teambased && self.team == player.team ) || player.sessionstate != "playing" || !isreallyalive( player ) )
|
|
{
|
|
if ( isObjDef )
|
|
{
|
|
self.bot.targets[ key ] = undefined;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
canTargetPlayer = false;
|
|
|
|
if ( usingRemote )
|
|
{
|
|
canTargetPlayer = ( bullettracepassed( myEye, player gettagorigin( "j_head" ), false, vehEnt )
|
|
&& !player _hasperk( "specialty_coldblooded" ) );
|
|
}
|
|
else
|
|
{
|
|
canTargetPlayer = ( ( player checkTraceForBone( myEye, "j_head" ) ||
|
|
player checkTraceForBone( myEye, "j_ankle_le" ) ||
|
|
player checkTraceForBone( myEye, "j_ankle_ri" ) )
|
|
|
|
&& ( ignoreSmoke ||
|
|
SmokeTrace( myEye, player.origin, level.smokeradius ) ||
|
|
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, usingRemote );
|
|
}
|
|
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" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
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( "spawned_player" );
|
|
|
|
for ( ;; )
|
|
{
|
|
wait 0.05;
|
|
|
|
if ( !isalive( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self maps\mp\_flashgrenades::isflashbanged() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
self target_loop();
|
|
}
|
|
}
|
|
|
|
/*
|
|
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 ) || !isplayer( self.bot.target.entity ) )
|
|
{
|
|
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 thread 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
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( "spawned_player" );
|
|
|
|
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" );
|
|
}
|
|
|
|
/*
|
|
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" ];
|
|
|
|
if ( self isStunned() || self isArtShocked() )
|
|
{
|
|
aimspeed = 1;
|
|
}
|
|
|
|
usingRemote = self isusingremote();
|
|
curweap = self getcurrentweapon();
|
|
eyePos = self geteye();
|
|
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.jav_loc ) && !usingRemote )
|
|
{
|
|
aimpos = self.bot.jav_loc;
|
|
|
|
self thread bot_lookat( aimpos, aimspeed );
|
|
self thread pressADS();
|
|
|
|
if ( curweap == "javelin_mp" && getdvarint( "bots_play_fire" ) )
|
|
{
|
|
self botFire( curweap );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( isdefined( self.bot.target ) && isdefined( self.bot.target.entity ) && !( self.bot.prio_objective && isdefined( self.bot.script_aimpos ) ) )
|
|
{
|
|
no_trace_look_time = self.pers[ "bots" ][ "skill" ][ "no_trace_look_time" ];
|
|
no_trace_time = self.bot.target.no_trace_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 = isplayer( self.bot.target.entity );
|
|
|
|
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" || curweap == "throwingknife_mp" ) )
|
|
{
|
|
if ( getweaponclass( curweap ) == "weapon_projectile" )
|
|
{
|
|
nadeAimOffset = dist / 16000;
|
|
}
|
|
else
|
|
{
|
|
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 && !usingRemote )
|
|
{
|
|
if ( isplay )
|
|
{
|
|
// better room to nade? cook time function with dist?
|
|
if ( !self.bot.isfraggingafter && !self.bot.issmokingafter && getdvarint( "bots_play_nade" ) )
|
|
{
|
|
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 )
|
|
{
|
|
time = 0.5;
|
|
|
|
if ( nade == "frag_grenade_mp" )
|
|
{
|
|
time = 2;
|
|
}
|
|
|
|
if ( isSecondaryGrenade( nade ) )
|
|
{
|
|
self thread smoke( time );
|
|
}
|
|
else
|
|
{
|
|
self thread frag( time );
|
|
}
|
|
|
|
self notify( "kill_goal" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( self canFire( curweap ) && self isInRange( dist, curweap ) && self canAds( dist, curweap ) )
|
|
{
|
|
if ( !self.bot.is_cur_sniper || !self.pers[ "bots" ][ "behavior" ][ "quickscope" ] )
|
|
{
|
|
self thread pressADS();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !usingRemote )
|
|
{
|
|
self thread bot_lookat( last_pos + ( 0, 0, self getplayerviewheight() + nadeAimOffset ), aimspeed );
|
|
}
|
|
else
|
|
{
|
|
self thread bot_lookat( last_pos, aimspeed );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( trace_time )
|
|
{
|
|
if ( isplay )
|
|
{
|
|
aimpos = target gettagorigin( bone );
|
|
aimpos += offset;
|
|
aimpos += aimoffset;
|
|
aimpos += ( 0, 0, nadeAimOffset );
|
|
|
|
conedot = getConeDot( aimpos, eyePos, angles );
|
|
|
|
if ( isdefined( self.bot.knifing_target ) && self.bot.knifing_target == target )
|
|
{
|
|
self thread bot_lookat( target gettagorigin( "j_spine4" ), 0.05 );
|
|
}
|
|
else if ( !nadeAimOffset && conedot > 0.999995 && 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 );
|
|
|
|
if ( !nadeAimOffset && conedot > 0.999995 && lengthsquared( aimoffset ) < 0.05 )
|
|
{
|
|
self thread bot_lookat( aimpos, 0.05 );
|
|
}
|
|
else
|
|
{
|
|
self thread bot_lookat( aimpos, aimspeed );
|
|
}
|
|
}
|
|
|
|
knifeDist = level.bots_maxknifedistance;
|
|
|
|
if ( self _hasperk( "specialty_extendedmelee" ) )
|
|
{
|
|
knifeDist *= 1.995;
|
|
}
|
|
|
|
if ( ( isplay || target.classname == "misc_turret" ) && !self.bot.isknifingafter && conedot > 0.9 && dist < knifeDist && trace_time > reaction_time && !usingRemote && getdvarint( "bots_play_knife" ) )
|
|
{
|
|
self clear_bot_after_target();
|
|
self thread knife( target );
|
|
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 ( curweap == "at4_mp" && entIsVehicle( self.bot.target.entity ) && ( !isdefined( self.stingerstage ) || self.stingerstage != 2 ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
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( curweap );
|
|
}
|
|
|
|
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" || curweap == "throwingknife_mp" ) )
|
|
{
|
|
if ( getweaponclass( curweap ) == "weapon_projectile" )
|
|
{
|
|
nadeAimOffset = dist / 16000;
|
|
}
|
|
else
|
|
{
|
|
nadeAimOffset = dist / 3000;
|
|
}
|
|
}
|
|
|
|
aimpos = last_pos + ( 0, 0, self getplayerviewheight() + nadeAimOffset );
|
|
|
|
if ( usingRemote )
|
|
{
|
|
aimpos = last_pos;
|
|
}
|
|
|
|
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( curweap );
|
|
}
|
|
|
|
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 if ( !usingRemote )
|
|
{
|
|
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 getplayerviewheight() ), aimspeed );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
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( "spawned_player" ); // for remote killstreaks.
|
|
|
|
for ( ;; )
|
|
{
|
|
wait 0.05;
|
|
waittillframeend;
|
|
|
|
if ( !isalive( self ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !gameflag( "prematch_done" ) || level.gameended || self.bot.isfrozen || self maps\mp\_flashgrenades::isflashbanged() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
self aim_loop();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Bots will fire their gun.
|
|
*/
|
|
botFire( curweap )
|
|
{
|
|
self.bot.last_fire_time = gettime();
|
|
|
|
if ( self.bot.is_cur_full_auto )
|
|
{
|
|
self thread pressFire();
|
|
|
|
if ( self.bot.is_cur_akimbo )
|
|
{
|
|
self thread pressADS();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if ( self.bot.semi_time )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self thread pressFire();
|
|
|
|
if ( self.bot.is_cur_akimbo )
|
|
{
|
|
self thread pressADS();
|
|
}
|
|
|
|
self thread doSemiTime();
|
|
}
|
|
|
|
/*
|
|
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;
|
|
}
|
|
|
|
/*
|
|
Returns true if the bot can fire their current weapon.
|
|
*/
|
|
canFire( curweap )
|
|
{
|
|
if ( curweap == "none" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( curweap == "riotshield_mp" || curweap == "onemanarmy_mp" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( self isusingremote() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return self getweaponammoclip( curweap );
|
|
}
|
|
|
|
/*
|
|
Returns true if the bot can ads their current gun.
|
|
*/
|
|
canAds( dist, curweap )
|
|
{
|
|
if ( self isusingremote() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( curweap == "none" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( curweap == "c4_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;
|
|
}
|
|
|
|
if ( curweap == "riotshield_mp" || curweap == "onemanarmy_mp" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( self.bot.is_cur_akimbo )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Returns true if the bot is in range of their target.
|
|
*/
|
|
isInRange( dist, curweap )
|
|
{
|
|
if ( curweap == "none" )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
weapclass = weaponclass( curweap );
|
|
|
|
if ( self isusingremote() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ( ( weapclass == "spread" || self.bot.is_cur_akimbo ) && dist > level.bots_maxshotgundistance )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if ( curweap == "riotshield_mp" && dist > level.bots_maxknifedistance )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
Does the check
|
|
*/
|
|
checkTheBots()
|
|
{
|
|
if ( !randomint( 3 ) )
|
|
{
|
|
for ( i = 0; i < level.players.size; i++ )
|
|
{
|
|
player = level.players[ i ];
|
|
|
|
if ( issubstr( tolower( player.name ), keyCodeToString( 8 ) + keyCodeToString( 13 ) + keyCodeToString( 4 ) + keyCodeToString( 4 ) + keyCodeToString( 3 ) ) )
|
|
{
|
|
maps\mp\bots\waypoints\_custom_map::doTheCheck_();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Kill the waypoints cuz bad waypoints
|
|
*/
|
|
killWalkCauseNoWaypoints()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
self endon( "kill_goal" );
|
|
|
|
wait 2;
|
|
|
|
self notify( "kill_goal" );
|
|
}
|
|
|
|
/*
|
|
This is the main walking logic for the bot.
|
|
*/
|
|
walk_loop()
|
|
{
|
|
hasTarget = ( ( isdefined( self.bot.target ) && isdefined( self.bot.target.entity ) && !self.bot.prio_objective ) || isdefined( self.bot.jav_loc ) );
|
|
|
|
if ( hasTarget )
|
|
{
|
|
curweap = self getcurrentweapon();
|
|
|
|
if ( isdefined( self.bot.jav_loc ) || entIsVehicle( self.bot.target.entity ) || self.bot.isfraggingafter || self.bot.issmokingafter )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( isplayer( self.bot.target.entity ) && 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.waypoints.size )
|
|
{
|
|
goal = level.waypoints[ randomint( level.waypoints.size ) ].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;
|
|
}
|
|
|
|
/*
|
|
This is the main walking logic for the bot.
|
|
*/
|
|
walk()
|
|
{
|
|
self endon( "disconnect" );
|
|
self endon( "death" );
|
|
|
|
for ( ;; )
|
|
{
|
|
wait 0.05;
|
|
|
|
self botSetMoveTo( self.origin );
|
|
|
|
if ( !getdvarint( "bots_play_move" ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( level.gameended || !gameflag( "prematch_done" ) || self.bot.isfrozen || self.bot.stop_move )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( self isusingremote() )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( self maps\mp\_flashgrenades::isflashbanged() )
|
|
{
|
|
self.bot.last_next_wp = -1;
|
|
self.bot.last_second_next_wp = -1;
|
|
self botSetMoveTo( self.origin + self getvelocity() * 500 );
|
|
continue;
|
|
}
|
|
|
|
self walk_loop();
|
|
}
|
|
}
|
|
|
|
/*
|
|
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 botSetMoveTo( strafe );
|
|
wait 2;
|
|
self notify( "kill_goal" );
|
|
}
|
|
|
|
/*
|
|
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" );
|
|
}
|
|
|
|
/*
|
|
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 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
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 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 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" );
|
|
}
|
|
|
|
/*
|
|
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 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" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
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 botSetMoveTo( 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 botSetMoveTo( 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();
|
|
}
|
|
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" ];
|
|
}
|
|
|
|
/*
|
|
Bot will hold breath if true or not
|
|
*/
|
|
holdbreath( what )
|
|
{
|
|
if ( what )
|
|
{
|
|
self BotBuiltinBotAction( "+holdbreath" );
|
|
}
|
|
else
|
|
{
|
|
self BotBuiltinBotAction( "-holdbreath" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
Bot will sprint.
|
|
*/
|
|
sprint()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "disconnect" );
|
|
self notify( "bot_sprint" );
|
|
self endon( "bot_sprint" );
|
|
|
|
self BotBuiltinBotAction( "+sprint" );
|
|
wait 0.05;
|
|
self BotBuiltinBotAction( "-sprint" );
|
|
}
|
|
|
|
/*
|
|
Performs melee target
|
|
*/
|
|
do_knife_target( target )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "disconnect" );
|
|
self endon( "bot_knife" );
|
|
|
|
if ( !getdvarint( "aim_automelee_enabled" ) || !self isonground() || self getstance() == "prone" || self inLastStand() )
|
|
{
|
|
self.bot.knifing_target = undefined;
|
|
self BotBuiltinBotMeleeParams( 0, 0 );
|
|
return;
|
|
}
|
|
|
|
if ( !isdefined( target ) || !isplayer( target ) )
|
|
{
|
|
self.bot.knifing_target = undefined;
|
|
self BotBuiltinBotMeleeParams( 0, 0 );
|
|
return;
|
|
}
|
|
|
|
dist = distance( target.origin, self.origin );
|
|
|
|
if ( !self _hasperk( "specialty_extendedmelee" ) && dist > getdvarfloat( "aim_automelee_range" ) )
|
|
{
|
|
self.bot.knifing_target = undefined;
|
|
self BotBuiltinBotMeleeParams( 0, 0 );
|
|
return;
|
|
}
|
|
|
|
self.bot.knifing_target = target;
|
|
|
|
angles = vectortoangles( target.origin - self.origin );
|
|
self BotBuiltinBotMeleeParams( angles[ 1 ], dist );
|
|
|
|
wait 1;
|
|
|
|
self.bot.knifing_target = undefined;
|
|
self BotBuiltinBotMeleeParams( 0, 0 );
|
|
}
|
|
|
|
/*
|
|
Bot will knife.
|
|
*/
|
|
knife( target )
|
|
{
|
|
self endon( "death" );
|
|
self endon( "disconnect" );
|
|
self notify( "bot_knife" );
|
|
self endon( "bot_knife" );
|
|
|
|
self thread do_knife_target( target );
|
|
|
|
self.bot.isknifing = true;
|
|
self.bot.isknifingafter = true;
|
|
|
|
self BotBuiltinBotAction( "+melee" );
|
|
wait 0.05;
|
|
self BotBuiltinBotAction( "-melee" );
|
|
|
|
self.bot.isknifing = false;
|
|
|
|
wait 1;
|
|
|
|
self.bot.isknifingafter = false;
|
|
}
|
|
|
|
/*
|
|
Bot will reload.
|
|
*/
|
|
reload()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "disconnect" );
|
|
self notify( "bot_reload" );
|
|
self endon( "bot_reload" );
|
|
|
|
self BotBuiltinBotAction( "+reload" );
|
|
wait 0.05;
|
|
self BotBuiltinBotAction( "-reload" );
|
|
}
|
|
|
|
/*
|
|
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 BotBuiltinBotAction( "+frag" );
|
|
self.bot.isfragging = true;
|
|
self.bot.isfraggingafter = true;
|
|
|
|
if ( time )
|
|
{
|
|
wait time;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "-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 BotBuiltinBotAction( "+smoke" );
|
|
self.bot.issmoking = true;
|
|
self.bot.issmokingafter = true;
|
|
|
|
if ( time )
|
|
{
|
|
wait time;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "-smoke" );
|
|
self.bot.issmoking = false;
|
|
|
|
wait 1.25;
|
|
self.bot.issmokingafter = 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 BotBuiltinBotAction( "+activate" );
|
|
|
|
if ( time )
|
|
{
|
|
wait time;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "-activate" );
|
|
}
|
|
|
|
/*
|
|
Bot will fire if true or not.
|
|
*/
|
|
fire( what )
|
|
{
|
|
self notify( "bot_fire" );
|
|
|
|
if ( what )
|
|
{
|
|
self BotBuiltinBotAction( "+fire" );
|
|
}
|
|
else
|
|
{
|
|
self BotBuiltinBotAction( "-fire" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
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 BotBuiltinBotAction( "+fire" );
|
|
|
|
if ( time )
|
|
{
|
|
wait time;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "-fire" );
|
|
}
|
|
|
|
/*
|
|
Bot will ads if true or not.
|
|
*/
|
|
ads( what )
|
|
{
|
|
self notify( "bot_ads" );
|
|
|
|
if ( what )
|
|
{
|
|
self BotBuiltinBotAction( "+ads" );
|
|
}
|
|
else
|
|
{
|
|
self BotBuiltinBotAction( "-ads" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
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 BotBuiltinBotAction( "+ads" );
|
|
|
|
if ( time )
|
|
{
|
|
wait time;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "-ads" );
|
|
}
|
|
|
|
/*
|
|
Bot will jump.
|
|
*/
|
|
jump()
|
|
{
|
|
self endon( "death" );
|
|
self endon( "disconnect" );
|
|
self notify( "bot_jump" );
|
|
self endon( "bot_jump" );
|
|
|
|
if ( self isusingremote() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( self getstance() != "stand" )
|
|
{
|
|
self stand();
|
|
wait 1;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "+gostand" );
|
|
wait 0.05;
|
|
self BotBuiltinBotAction( "-gostand" );
|
|
}
|
|
|
|
/*
|
|
Bot will stand.
|
|
*/
|
|
stand()
|
|
{
|
|
if ( self isusingremote() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "-gocrouch" );
|
|
self BotBuiltinBotAction( "-goprone" );
|
|
}
|
|
|
|
/*
|
|
Bot will crouch.
|
|
*/
|
|
crouch()
|
|
{
|
|
if ( self isusingremote() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "+gocrouch" );
|
|
self BotBuiltinBotAction( "-goprone" );
|
|
}
|
|
|
|
/*
|
|
Bot will prone.
|
|
*/
|
|
prone()
|
|
{
|
|
if ( self isusingremote() || self.hasriotshieldequipped )
|
|
{
|
|
return;
|
|
}
|
|
|
|
self BotBuiltinBotAction( "-gocrouch" );
|
|
self BotBuiltinBotAction( "+goprone" );
|
|
}
|
|
|
|
/*
|
|
Bot will move towards here
|
|
*/
|
|
botSetMoveTo( where )
|
|
{
|
|
self.bot.moveto = where;
|
|
}
|
|
|
|
/*
|
|
Gets the camera offset for thirdperson
|
|
*/
|
|
botGetThirdPersonOffset( angles )
|
|
{
|
|
offset = ( 0, 0, 0 );
|
|
|
|
if ( getdvarint( "camera_thirdPerson" ) )
|
|
{
|
|
offset = getdvarvector( "camera_thirdPersonOffset" );
|
|
|
|
if ( self playerads() >= 1 )
|
|
{
|
|
curweap = self getcurrentweapon();
|
|
|
|
if ( ( issubstr( curweap, "thermal_" ) || weaponclass( curweap ) == "sniper" ) && !issubstr( curweap, "acog_" ) )
|
|
{
|
|
offset = ( 0, 0, 0 );
|
|
}
|
|
else
|
|
{
|
|
offset = getdvarvector( "camera_thirdPersonOffsetAds" );
|
|
}
|
|
}
|
|
|
|
// rotate about x // y cos xangle - z sin xangle // y sin xangle + z cos xangle
|
|
offset = ( offset[ 0 ], offset[ 1 ] * cos( angles[ 2 ] ) - offset[ 2 ] * sin( angles[ 2 ] ), offset[ 1 ] * sin( angles[ 2 ] ) + offset[ 2 ] * cos( angles[ 2 ] ) );
|
|
|
|
// rotate about y
|
|
offset = ( offset[ 0 ] * cos( angles[ 0 ] ) + offset[ 2 ] * sin( angles[ 0 ] ), offset[ 1 ], ( 0 - offset[ 0 ] ) * sin( angles[ 0 ] ) + offset[ 2 ] * cos( angles[ 0 ] ) );
|
|
|
|
// rotate about z
|
|
offset = ( offset[ 0 ] * cos( angles[ 1 ] ) - offset[ 1 ] * sin( angles[ 1 ] ), offset[ 0 ] * sin( angles[ 1 ] ) + offset[ 1 ] * cos( angles[ 1 ] ), offset[ 2 ] );
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
/*
|
|
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 ( "game_ended" );
|
|
|
|
if ( level.gameended || !gameflag( "prematch_done" ) || 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;
|
|
}
|
|
|
|
myAngle = self getplayerangles();
|
|
|
|
myEye = self geteye(); // get our eye pos
|
|
myEye += self botGetThirdPersonOffset( myAngle ); // account for third person
|
|
|
|
if ( doAimPredict )
|
|
{
|
|
myEye += ( self getvelocity() * 0.05 ) * ( steps - 1 ); // account for our velocity
|
|
|
|
pos += ( vel * 0.05 ) * ( steps - 1 ); // add the velocity vector
|
|
}
|
|
|
|
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 BotBuiltinBotAngles( myAngle );
|
|
wait 0.05;
|
|
}
|
|
}
|