2002-05-24 00:00:00 +00:00
// Copyright (C) 2001-2002 Raven Software.
//
/*****************************************************************************
* name : ai_main . c
*
* desc : Quake3 bot AI
*
* $ Archive : / MissionPack / code / game / ai_main . c $
* $ Author : Mrelusive $
* $ Revision : 35 $
* $ Modtime : 6 / 06 / 01 1 : 11 p $
* $ Date : 6 / 06 / 01 12 : 06 p $
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "g_local.h"
# include "q_shared.h"
# include "botlib.h" //bot lib interface
# include "be_aas.h"
# include "be_ea.h"
# include "be_ai_char.h"
# include "be_ai_chat.h"
# include "be_ai_gen.h"
# include "be_ai_goal.h"
# include "be_ai_move.h"
# include "be_ai_weap.h"
//
# include "ai_main.h"
//
# include "chars.h"
# include "inv.h"
# include "syn.h"
/*
# define BOT_CTF_DEBUG 1
*/
# define MAX_PATH 144
# define BOT_THINK_TIME 0
//bot states
bot_state_t * botstates [ MAX_CLIENTS ] ;
//number of bots
int numbots ;
//floating point time
float floattime ;
//time to do a regular update
float regularupdate_time ;
//
boteventtracker_t gBotEventTracker [ MAX_CLIENTS ] ;
//rww - new bot cvars..
# ifdef _DEBUG
vmCvar_t bot_debugmessages ;
# endif
vmCvar_t bot_attachments ;
vmCvar_t bot_camp ;
vmCvar_t bot_pause ;
vmCvar_t bot_wp_info ;
vmCvar_t bot_wp_edit ;
vmCvar_t bot_wp_clearweight ;
vmCvar_t bot_wp_distconnect ;
vmCvar_t bot_wp_visconnect ;
//end rww
wpobject_t * flagRed ;
wpobject_t * oFlagRed ;
wpobject_t * flagBlue ;
wpobject_t * oFlagBlue ;
gentity_t * eFlagRed ;
gentity_t * droppedRedFlag ;
gentity_t * eFlagBlue ;
gentity_t * droppedBlueFlag ;
char * ctfStateNames [ ] = {
" CTFSTATE_NONE " ,
" CTFSTATE_ATTACKER " ,
" CTFSTATE_DEFENDER " ,
" CTFSTATE_RETRIEVAL " ,
" CTFSTATE_GUARDCARRIER " ,
" CTFSTATE_GETFLAGHOME " ,
" CTFSTATE_MAXCTFSTATES "
} ;
char * ctfStateDescriptions [ ] = {
" I'm not occupied " ,
" I'm attacking the enemy's base " ,
" I'm defending our base " ,
" I'm getting our flag back " ,
" I'm escorting our flag carrier " ,
" I've got the enemy's flag "
} ;
char * teamplayStateDescriptions [ ] = {
" I'm not occupied " ,
" I'm following my squad commander " ,
" I'm assisting my commanding " ,
" I'm attempting to regroup and form a new squad "
} ;
void BotStraightTPOrderCheck ( gentity_t * ent , int ordernum , bot_state_t * bs )
{
switch ( ordernum )
{
case 0 :
if ( bs - > squadLeader = = ent )
{
bs - > teamplayState = 0 ;
bs - > squadLeader = NULL ;
}
break ;
case TEAMPLAYSTATE_FOLLOWING :
bs - > teamplayState = ordernum ;
bs - > isSquadLeader = 0 ;
bs - > squadLeader = ent ;
bs - > wpDestSwitchTime = 0 ;
break ;
case TEAMPLAYSTATE_ASSISTING :
bs - > teamplayState = ordernum ;
bs - > isSquadLeader = 0 ;
bs - > squadLeader = ent ;
bs - > wpDestSwitchTime = 0 ;
break ;
default :
bs - > teamplayState = ordernum ;
break ;
}
}
void BotReportStatus ( bot_state_t * bs )
{
if ( level . gametypeData - > teams )
{
trap_EA_SayTeam ( bs - > client , teamplayStateDescriptions [ bs - > teamplayState ] ) ;
}
}
void BotOrder ( gentity_t * ent , int clientnum , int ordernum )
{
int stateMin = 0 ;
int stateMax = 0 ;
int i = 0 ;
if ( ! ent | | ! ent - > client )
{
return ;
}
if ( clientnum ! = - 1 & & ! botstates [ clientnum ] )
{
return ;
}
if ( clientnum ! = - 1 & & ! OnSameTeam ( ent , & g_entities [ clientnum ] ) )
{
return ;
}
if ( ! level . gametypeData - > teams )
{
return ;
}
/*
if ( level . gametype = = GT_CTF )
{
stateMin = CTFSTATE_NONE ;
stateMax = CTFSTATE_MAXCTFSTATES ;
}
else if ( level . gametype = = GT_TDM )
{
stateMin = TEAMPLAYSTATE_NONE ;
stateMax = TEAMPLAYSTATE_MAXTPSTATES ;
}
*/
if ( ( ordernum < stateMin & & ordernum ! = - 1 ) | | ordernum > = stateMax )
{
return ;
}
if ( clientnum ! = - 1 )
{
if ( ordernum = = - 1 )
{
BotReportStatus ( botstates [ clientnum ] ) ;
}
else
{
BotStraightTPOrderCheck ( ent , ordernum , botstates [ clientnum ] ) ;
botstates [ clientnum ] - > state_Forced = ordernum ;
botstates [ clientnum ] - > chatObject = ent ;
botstates [ clientnum ] - > chatAltObject = NULL ;
if ( BotDoChat ( botstates [ clientnum ] , " OrderAccepted " , 1 ) )
{
botstates [ clientnum ] - > chatTeam = 1 ;
}
}
}
else
{
while ( i < MAX_CLIENTS )
{
if ( botstates [ i ] & & OnSameTeam ( ent , & g_entities [ i ] ) )
{
if ( ordernum = = - 1 )
{
BotReportStatus ( botstates [ i ] ) ;
}
else
{
BotStraightTPOrderCheck ( ent , ordernum , botstates [ i ] ) ;
botstates [ i ] - > state_Forced = ordernum ;
botstates [ i ] - > chatObject = ent ;
botstates [ i ] - > chatAltObject = NULL ;
if ( BotDoChat ( botstates [ i ] , " OrderAccepted " , 0 ) )
{
botstates [ i ] - > chatTeam = 1 ;
}
}
}
i + + ;
}
}
}
int BotGetWeaponRange ( bot_state_t * bs ) ;
int PassLovedOneCheck ( bot_state_t * bs , gentity_t * ent ) ;
void ExitLevel ( void ) ;
void QDECL BotAI_Print ( int type , char * fmt , . . . ) { return ; }
int IsTeamplay ( void )
{
return level . gametypeData - > teams ;
}
/*
= = = = = = = = = = = = = = = = = =
BotAI_GetClientState
= = = = = = = = = = = = = = = = = =
*/
int BotAI_GetClientState ( int clientNum , playerState_t * state ) {
gentity_t * ent ;
ent = & g_entities [ clientNum ] ;
if ( ! ent - > inuse ) {
return qfalse ;
}
if ( ! ent - > client ) {
return qfalse ;
}
memcpy ( state , & ent - > client - > ps , sizeof ( playerState_t ) ) ;
return qtrue ;
}
/*
= = = = = = = = = = = = = = = = = =
BotAI_GetEntityState
= = = = = = = = = = = = = = = = = =
*/
int BotAI_GetEntityState ( int entityNum , entityState_t * state ) {
gentity_t * ent ;
ent = & g_entities [ entityNum ] ;
memset ( state , 0 , sizeof ( entityState_t ) ) ;
if ( ! ent - > inuse ) return qfalse ;
if ( ! ent - > r . linked ) return qfalse ;
if ( ent - > r . svFlags & SVF_NOCLIENT ) return qfalse ;
memcpy ( state , & ent - > s , sizeof ( entityState_t ) ) ;
return qtrue ;
}
/*
= = = = = = = = = = = = = = = = = =
BotAI_GetSnapshotEntity
= = = = = = = = = = = = = = = = = =
*/
int BotAI_GetSnapshotEntity ( int clientNum , int sequence , entityState_t * state ) {
int entNum ;
entNum = trap_BotGetSnapshotEntity ( clientNum , sequence ) ;
if ( entNum = = - 1 ) {
memset ( state , 0 , sizeof ( entityState_t ) ) ;
return - 1 ;
}
BotAI_GetEntityState ( entNum , state ) ;
return sequence + 1 ;
}
/*
= = = = = = = = = = = = = =
BotEntityInfo
= = = = = = = = = = = = = =
*/
void BotEntityInfo ( int entnum , aas_entityinfo_t * info ) {
trap_AAS_EntityInfo ( entnum , info ) ;
}
/*
= = = = = = = = = = = = = =
NumBots
= = = = = = = = = = = = = =
*/
int NumBots ( void ) {
return numbots ;
}
/*
= = = = = = = = = = = = = =
AngleDifference
= = = = = = = = = = = = = =
*/
float AngleDifference ( float ang1 , float ang2 ) {
float diff ;
diff = ang1 - ang2 ;
if ( ang1 > ang2 ) {
if ( diff > 180.0 ) diff - = 360.0 ;
}
else {
if ( diff < - 180.0 ) diff + = 360.0 ;
}
return diff ;
}
/*
= = = = = = = = = = = = = =
BotChangeViewAngle
= = = = = = = = = = = = = =
*/
float BotChangeViewAngle ( float angle , float ideal_angle , float speed ) {
float move ;
angle = AngleMod ( angle ) ;
ideal_angle = AngleMod ( ideal_angle ) ;
if ( angle = = ideal_angle ) return angle ;
move = ideal_angle - angle ;
if ( ideal_angle > angle ) {
if ( move > 180.0 ) move - = 360.0 ;
}
else {
if ( move < - 180.0 ) move + = 360.0 ;
}
if ( move > 0 ) {
if ( move > speed ) move = speed ;
}
else {
if ( move < - speed ) move = - speed ;
}
return AngleMod ( angle + move ) ;
}
/*
= = = = = = = = = = = = = =
BotChangeViewAngles
= = = = = = = = = = = = = =
*/
void BotChangeViewAngles ( bot_state_t * bs , float thinktime ) {
float diff , factor , maxchange , anglespeed , disired_speed ;
int i ;
if ( bs - > ideal_viewangles [ PITCH ] > 180 ) bs - > ideal_viewangles [ PITCH ] - = 360 ;
if ( bs - > currentEnemy & & bs - > frame_Enemy_Vis )
{
factor = bs - > skills . turnspeed_combat * bs - > settings . skill ;
}
else
{
factor = bs - > skills . turnspeed ;
}
if ( factor > 1 )
{
factor = 1 ;
}
if ( factor < 0.001 )
{
factor = 0.001f ;
}
maxchange = bs - > skills . maxturn ;
//if (maxchange < 240) maxchange = 240;
maxchange * = thinktime ;
for ( i = 0 ; i < 2 ; i + + ) {
bs - > viewangles [ i ] = AngleMod ( bs - > viewangles [ i ] ) ;
bs - > ideal_viewangles [ i ] = AngleMod ( bs - > ideal_viewangles [ i ] ) ;
diff = AngleDifference ( bs - > viewangles [ i ] , bs - > ideal_viewangles [ i ] ) ;
disired_speed = diff * factor ;
bs - > viewanglespeed [ i ] + = ( bs - > viewanglespeed [ i ] - disired_speed ) ;
if ( bs - > viewanglespeed [ i ] > 180 ) bs - > viewanglespeed [ i ] = maxchange ;
if ( bs - > viewanglespeed [ i ] < - 180 ) bs - > viewanglespeed [ i ] = - maxchange ;
anglespeed = bs - > viewanglespeed [ i ] ;
if ( anglespeed > maxchange ) anglespeed = maxchange ;
if ( anglespeed < - maxchange ) anglespeed = - maxchange ;
bs - > viewangles [ i ] + = anglespeed ;
bs - > viewangles [ i ] = AngleMod ( bs - > viewangles [ i ] ) ;
bs - > viewanglespeed [ i ] * = 0.45 * ( 1 - factor ) ;
}
if ( bs - > viewangles [ PITCH ] > 180 ) bs - > viewangles [ PITCH ] - = 360 ;
trap_EA_View ( bs - > client , bs - > viewangles ) ;
}
/*
= = = = = = = = = = = = = =
BotInputToUserCommand
= = = = = = = = = = = = = =
*/
void BotInputToUserCommand ( bot_input_t * bi , usercmd_t * ucmd , int delta_angles [ 3 ] , int time , int useTime ) {
vec3_t angles , forward , right ;
short temp ;
int j ;
//clear the whole structure
memset ( ucmd , 0 , sizeof ( usercmd_t ) ) ;
//
//Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
//the duration for the user command in milli seconds
ucmd - > serverTime = time ;
//
if ( bi - > actionflags & ACTION_DELAYEDJUMP ) {
bi - > actionflags | = ACTION_JUMP ;
bi - > actionflags & = ~ ACTION_DELAYEDJUMP ;
}
//set the buttons
if ( bi - > actionflags & ACTION_RESPAWN )
{
ucmd - > buttons = BUTTON_ATTACK ;
}
if ( bi - > actionflags & ACTION_ATTACK ) ucmd - > buttons | = BUTTON_ATTACK ;
if ( bi - > actionflags & ACTION_ALT_ATTACK ) ucmd - > buttons | = BUTTON_ALT_ATTACK ;
// if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
// if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
# ifdef BOT_USE_HOLDABLE
if ( bi - > actionflags & ACTION_USE ) ucmd - > buttons | = BUTTON_USE_HOLDABLE ;
# endif
if ( bi - > actionflags & ACTION_WALK ) ucmd - > buttons | = BUTTON_WALKING ;
if ( useTime < level . time & & Q_irand ( 1 , 10 ) < 5 )
{ //for now just hit use randomly in case there's something useable around
ucmd - > buttons | = BUTTON_USE ;
}
#if 0
// Here's an interesting bit. The bots in TA used buttons to do additional gestures.
// I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2.
// We can always add some back in if we want though.
if ( bi - > actionflags & ACTION_AFFIRMATIVE ) ucmd - > buttons | = BUTTON_AFFIRMATIVE ;
if ( bi - > actionflags & ACTION_NEGATIVE ) ucmd - > buttons | = BUTTON_NEGATIVE ;
if ( bi - > actionflags & ACTION_GETFLAG ) ucmd - > buttons | = BUTTON_GETFLAG ;
if ( bi - > actionflags & ACTION_GUARDBASE ) ucmd - > buttons | = BUTTON_GUARDBASE ;
if ( bi - > actionflags & ACTION_PATROL ) ucmd - > buttons | = BUTTON_PATROL ;
if ( bi - > actionflags & ACTION_FOLLOWME ) ucmd - > buttons | = BUTTON_FOLLOWME ;
# endif //0
//
ucmd - > weapon = bi - > weapon ;
//set the view angles
//NOTE: the ucmd->angles are the angles WITHOUT the delta angles
ucmd - > angles [ PITCH ] = ANGLE2SHORT ( bi - > viewangles [ PITCH ] ) ;
ucmd - > angles [ YAW ] = ANGLE2SHORT ( bi - > viewangles [ YAW ] ) ;
ucmd - > angles [ ROLL ] = ANGLE2SHORT ( bi - > viewangles [ ROLL ] ) ;
//subtract the delta angles
for ( j = 0 ; j < 3 ; j + + ) {
temp = ucmd - > angles [ j ] - delta_angles [ j ] ;
ucmd - > angles [ j ] = temp ;
}
//NOTE: movement is relative to the REAL view angles
//get the horizontal forward and right vector
//get the pitch in the range [-180, 180]
if ( bi - > dir [ 2 ] ) angles [ PITCH ] = bi - > viewangles [ PITCH ] ;
else angles [ PITCH ] = 0 ;
angles [ YAW ] = bi - > viewangles [ YAW ] ;
angles [ ROLL ] = 0 ;
AngleVectors ( angles , forward , right , NULL ) ;
//bot input speed is in the range [0, 400]
bi - > speed = bi - > speed * 127 / 400 ;
//set the view independent movement
ucmd - > forwardmove = DotProduct ( forward , bi - > dir ) * bi - > speed ;
ucmd - > rightmove = DotProduct ( right , bi - > dir ) * bi - > speed ;
ucmd - > upmove = abs ( forward [ 2 ] ) * bi - > dir [ 2 ] * bi - > speed ;
//normal keyboard movement
if ( bi - > actionflags & ACTION_MOVEFORWARD ) ucmd - > forwardmove + = 127 ;
if ( bi - > actionflags & ACTION_MOVEBACK ) ucmd - > forwardmove - = 127 ;
if ( bi - > actionflags & ACTION_MOVELEFT ) ucmd - > rightmove - = 127 ;
if ( bi - > actionflags & ACTION_MOVERIGHT ) ucmd - > rightmove + = 127 ;
//jump/moveup
if ( bi - > actionflags & ACTION_JUMP ) ucmd - > upmove + = 127 ;
//crouch/movedown
if ( bi - > actionflags & ACTION_CROUCH ) ucmd - > upmove - = 127 ;
//
//Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
//Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime);
}
/*
= = = = = = = = = = = = = =
BotUpdateInput
= = = = = = = = = = = = = =
*/
void BotUpdateInput ( bot_state_t * bs , int time , int elapsed_time ) {
bot_input_t bi ;
int j ;
//add the delta angles to the bot's current view angles
for ( j = 0 ; j < 3 ; j + + ) {
bs - > viewangles [ j ] = AngleMod ( bs - > viewangles [ j ] + SHORT2ANGLE ( bs - > cur_ps . delta_angles [ j ] ) ) ;
}
//change the bot view angles
BotChangeViewAngles ( bs , ( float ) elapsed_time / 1000 ) ;
//retrieve the bot input
trap_EA_GetInput ( bs - > client , ( float ) time / 1000 , & bi ) ;
//respawn hack
if ( bi . actionflags & ACTION_RESPAWN )
{
// IF already trying to respawn or a ghost then cancel the respawn
if ( ( bs - > lastucmd . buttons & BUTTON_ATTACK ) | | ( bs - > cur_ps . pm_flags & PMF_GHOST ) )
{
bi . actionflags & = ~ ( ACTION_RESPAWN | ACTION_ATTACK ) ;
}
}
//convert the bot input to a usercmd
BotInputToUserCommand ( & bi , & bs - > lastucmd , bs - > cur_ps . delta_angles , time , bs - > noUseTime ) ;
//subtract the delta angles
for ( j = 0 ; j < 3 ; j + + ) {
bs - > viewangles [ j ] = AngleMod ( bs - > viewangles [ j ] - SHORT2ANGLE ( bs - > cur_ps . delta_angles [ j ] ) ) ;
}
}
/*
= = = = = = = = = = = = = =
BotAIRegularUpdate
= = = = = = = = = = = = = =
*/
void BotAIRegularUpdate ( void ) {
if ( regularupdate_time < FloatTime ( ) ) {
trap_BotUpdateEntityItems ( ) ;
regularupdate_time = FloatTime ( ) + 0.3 ;
}
}
/*
= = = = = = = = = = = = = =
RemoveColorEscapeSequences
= = = = = = = = = = = = = =
*/
void RemoveColorEscapeSequences ( char * text ) {
int i , l ;
l = 0 ;
for ( i = 0 ; text [ i ] ; i + + ) {
if ( Q_IsColorString ( & text [ i ] ) ) {
i + + ;
continue ;
}
if ( text [ i ] > 0x7E )
continue ;
text [ l + + ] = text [ i ] ;
}
text [ l ] = ' \0 ' ;
}
/*
= = = = = = = = = = = = = =
BotAI
= = = = = = = = = = = = = =
*/
int BotAI ( int client , float thinktime ) {
bot_state_t * bs ;
char buf [ 1024 ] , * args ;
int j ;
# ifdef _DEBUG
int start = 0 ;
int end = 0 ;
# endif
trap_EA_ResetInput ( client ) ;
//
bs = botstates [ client ] ;
if ( ! bs | | ! bs - > inuse ) {
BotAI_Print ( PRT_FATAL , " BotAI: client %d is not setup \n " , client ) ;
return qfalse ;
}
//retrieve the current client state
BotAI_GetClientState ( client , & bs - > cur_ps ) ;
//retrieve any waiting server commands
while ( trap_BotGetServerCommand ( client , buf , sizeof ( buf ) ) ) {
//have buf point to the command and args to the command arguments
args = strchr ( buf , ' ' ) ;
if ( ! args ) continue ;
* args + + = ' \0 ' ;
//remove color espace sequences from the arguments
RemoveColorEscapeSequences ( args ) ;
if ( ! Q_stricmp ( buf , " cp " ) )
{ /*CenterPrintf*/ }
else if ( ! Q_stricmp ( buf , " cs " ) )
{ /*ConfigStringModified*/ }
else if ( ! Q_stricmp ( buf , " scores " ) )
{ /*FIXME: parse scores?*/ }
else if ( ! Q_stricmp ( buf , " clientLevelShot " ) )
{ /*ignore*/ }
}
//add the delta angles to the bot's current view angles
for ( j = 0 ; j < 3 ; j + + ) {
bs - > viewangles [ j ] = AngleMod ( bs - > viewangles [ j ] + SHORT2ANGLE ( bs - > cur_ps . delta_angles [ j ] ) ) ;
}
//increase the local time of the bot
bs - > ltime + = thinktime ;
//
bs - > thinktime = thinktime ;
//origin of the bot
VectorCopy ( bs - > cur_ps . origin , bs - > origin ) ;
//eye coordinates of the bot
VectorCopy ( bs - > cur_ps . origin , bs - > eye ) ;
bs - > eye [ 2 ] + = bs - > cur_ps . viewheight ;
//get the area the bot is in
# ifdef _DEBUG
start = trap_Milliseconds ( ) ;
# endif
StandardBotAI ( bs , thinktime ) ;
# ifdef _DEBUG
end = trap_Milliseconds ( ) ;
trap_Cvar_Update ( & bot_debugmessages ) ;
if ( bot_debugmessages . integer )
{
Com_Printf ( " Single AI frametime: %i \n " , ( end - start ) ) ;
}
# endif
//subtract the delta angles
for ( j = 0 ; j < 3 ; j + + ) {
bs - > viewangles [ j ] = AngleMod ( bs - > viewangles [ j ] - SHORT2ANGLE ( bs - > cur_ps . delta_angles [ j ] ) ) ;
}
//everything was ok
return qtrue ;
}
/*
= = = = = = = = = = = = = = = = = =
BotScheduleBotThink
= = = = = = = = = = = = = = = = = =
*/
void BotScheduleBotThink ( void ) {
int i , botnum ;
botnum = 0 ;
for ( i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( ! botstates [ i ] | | ! botstates [ i ] - > inuse ) {
continue ;
}
//initialize the bot think residual time
botstates [ i ] - > botthink_residual = BOT_THINK_TIME * botnum / numbots ;
botnum + + ;
}
}
int PlayersInGame ( void )
{
int i = 0 ;
gentity_t * ent ;
int pl = 0 ;
while ( i < MAX_CLIENTS )
{
ent = & g_entities [ i ] ;
if ( ent & & ent - > client & & ent - > client - > pers . connected = = CON_CONNECTED )
{
pl + + ;
}
i + + ;
}
return pl ;
}
/*
= = = = = = = = = = = = = =
BotAISetupClient
= = = = = = = = = = = = = =
*/
int BotAISetupClient ( int client , struct bot_settings_s * settings , qboolean restart ) {
bot_state_t * bs ;
if ( ! botstates [ client ] ) botstates [ client ] = B_Alloc ( sizeof ( bot_state_t ) ) ; //G_Alloc(sizeof(bot_state_t));
//rww - G_Alloc bad! B_Alloc good.
memset ( botstates [ client ] , 0 , sizeof ( bot_state_t ) ) ;
bs = botstates [ client ] ;
if ( bs & & bs - > inuse ) {
BotAI_Print ( PRT_FATAL , " BotAISetupClient: client %d already setup \n " , client ) ;
return qfalse ;
}
memcpy ( & bs - > settings , settings , sizeof ( bot_settings_t ) ) ;
bs - > client = client ; //need to know the client number before doing personality stuff
//initialize weapon weight defaults..
bs - > botWeaponWeights [ WP_NONE ] = 0 ;
bs - > botWeaponWeights [ WP_KNIFE ] = 1 ;
bs - > botWeaponWeights [ WP_M1911A1_PISTOL ] = 3 ;
2002-09-24 00:00:00 +00:00
bs - > botWeaponWeights [ WP_SILVER_TALON ] = 4 ;
2002-05-24 00:00:00 +00:00
bs - > botWeaponWeights [ WP_USSOCOM_PISTOL ] = 2 ;
bs - > botWeaponWeights [ WP_M4_ASSAULT_RIFLE ] = 10 ;
bs - > botWeaponWeights [ WP_AK74_ASSAULT_RIFLE ] = 9 ;
bs - > botWeaponWeights [ WP_M60_MACHINEGUN ] = 11 ;
bs - > botWeaponWeights [ WP_MICRO_UZI_SUBMACHINEGUN ] = 8 ;
bs - > botWeaponWeights [ WP_M3A1_SUBMACHINEGUN ] = 7 ;
bs - > botWeaponWeights [ WP_MSG90A1 ] = 11 ;
bs - > botWeaponWeights [ WP_USAS_12_SHOTGUN ] = 12 ;
bs - > botWeaponWeights [ WP_M590_SHOTGUN ] = 13 ;
bs - > botWeaponWeights [ WP_MM1_GRENADE_LAUNCHER ] = 8 ;
bs - > botWeaponWeights [ WP_RPG7_LAUNCHER ] = 16 ;
bs - > botWeaponWeights [ WP_M84_GRENADE ] = 6 ;
bs - > botWeaponWeights [ WP_SMOHG92_GRENADE ] = 2 ;
bs - > botWeaponWeights [ WP_ANM14_GRENADE ] = 2 ;
bs - > botWeaponWeights [ WP_M15_GRENADE ] = 2 ;
2002-07-15 00:00:00 +00:00
bs - > botWeaponWeights [ WP_MP5 ] = 7 ;
2002-09-24 00:00:00 +00:00
bs - > botWeaponWeights [ WP_SIG551 ] = 7 ;
2002-05-24 00:00:00 +00:00
BotUtilizePersonality ( bs ) ;
//allocate a goal state
bs - > gs = trap_BotAllocGoalState ( client ) ;
//allocate a weapon state
bs - > ws = trap_BotAllocWeaponState ( ) ;
bs - > inuse = qtrue ;
bs - > entitynum = client ;
bs - > setupcount = 4 ;
bs - > entergame_time = FloatTime ( ) ;
bs - > ms = trap_BotAllocMoveState ( ) ;
numbots + + ;
//NOTE: reschedule the bot thinking
BotScheduleBotThink ( ) ;
if ( PlayersInGame ( ) )
{ //don't talk to yourself
BotDoChat ( bs , " GeneralGreetings " , 0 ) ;
}
return qtrue ;
}
/*
= = = = = = = = = = = = = =
BotAIShutdownClient
= = = = = = = = = = = = = =
*/
int BotAIShutdownClient ( int client , qboolean restart ) {
bot_state_t * bs ;
bs = botstates [ client ] ;
if ( ! bs | | ! bs - > inuse ) {
//BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client);
return qfalse ;
}
trap_BotFreeMoveState ( bs - > ms ) ;
//free the goal state`
trap_BotFreeGoalState ( bs - > gs ) ;
//free the weapon weights
trap_BotFreeWeaponState ( bs - > ws ) ;
//
//clear the bot state
memset ( bs , 0 , sizeof ( bot_state_t ) ) ;
//set the inuse flag to qfalse
bs - > inuse = qfalse ;
//there's one bot less
numbots - - ;
//everything went ok
return qtrue ;
}
/*
= = = = = = = = = = = = = =
BotResetState
called when a bot enters the intermission or observer mode and
when the level is changed
= = = = = = = = = = = = = =
*/
void BotResetState ( bot_state_t * bs ) {
int client , entitynum , inuse ;
int movestate , goalstate , weaponstate ;
bot_settings_t settings ;
playerState_t ps ; //current player state
float entergame_time ;
//save some things that should not be reset here
memcpy ( & settings , & bs - > settings , sizeof ( bot_settings_t ) ) ;
memcpy ( & ps , & bs - > cur_ps , sizeof ( playerState_t ) ) ;
inuse = bs - > inuse ;
client = bs - > client ;
entitynum = bs - > entitynum ;
movestate = bs - > ms ;
goalstate = bs - > gs ;
weaponstate = bs - > ws ;
entergame_time = bs - > entergame_time ;
//reset the whole state
memset ( bs , 0 , sizeof ( bot_state_t ) ) ;
//copy back some state stuff that should not be reset
bs - > ms = movestate ;
bs - > gs = goalstate ;
bs - > ws = weaponstate ;
memcpy ( & bs - > cur_ps , & ps , sizeof ( playerState_t ) ) ;
memcpy ( & bs - > settings , & settings , sizeof ( bot_settings_t ) ) ;
bs - > inuse = inuse ;
bs - > client = client ;
bs - > entitynum = entitynum ;
bs - > entergame_time = entergame_time ;
//reset several states
if ( bs - > ms ) trap_BotResetMoveState ( bs - > ms ) ;
if ( bs - > gs ) trap_BotResetGoalState ( bs - > gs ) ;
if ( bs - > ws ) trap_BotResetWeaponState ( bs - > ws ) ;
if ( bs - > gs ) trap_BotResetAvoidGoals ( bs - > gs ) ;
if ( bs - > ms ) trap_BotResetAvoidReach ( bs - > ms ) ;
}
/*
= = = = = = = = = = = = = =
BotAILoadMap
= = = = = = = = = = = = = =
*/
int BotAILoadMap ( int restart ) {
int i ;
for ( i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( botstates [ i ] & & botstates [ i ] - > inuse ) {
BotResetState ( botstates [ i ] ) ;
botstates [ i ] - > setupcount = 4 ;
}
}
return qtrue ;
}
//rww - bot ai
int OrgVisible ( vec3_t org1 , vec3_t org2 , int ignore )
{
trace_t tr ;
trap_Trace ( & tr , org1 , NULL , NULL , org2 , ignore , MASK_SOLID ) ;
if ( tr . fraction = = 1 )
{
return 1 ;
}
return 0 ;
}
int WPOrgVisible ( gentity_t * bot , vec3_t org1 , vec3_t org2 , int ignore )
{
trace_t tr ;
trap_Trace ( & tr , org1 , NULL , NULL , org2 , ignore , MASK_SOLID ) ;
if ( tr . fraction = = 1 )
{
return 1 ;
}
return 0 ;
}
int OrgVisibleBox ( vec3_t org1 , vec3_t mins , vec3_t maxs , vec3_t org2 , int ignore )
{
trace_t tr ;
trap_Trace ( & tr , org1 , mins , maxs , org2 , ignore , MASK_SOLID ) ;
if ( tr . fraction = = 1 & & ! tr . startsolid & & ! tr . allsolid )
{
return 1 ;
}
return 0 ;
}
int CheckForFunc ( vec3_t org , int ignore )
{
gentity_t * fent ;
vec3_t under ;
trace_t tr ;
VectorCopy ( org , under ) ;
under [ 2 ] - = 64 ;
trap_Trace ( & tr , org , NULL , NULL , under , ignore , MASK_SOLID ) ;
if ( tr . fraction = = 1 )
{
return 0 ;
}
fent = & g_entities [ tr . entityNum ] ;
if ( ! fent )
{
return 0 ;
}
if ( strstr ( fent - > classname , " func_ " ) )
{
return 1 ; //there's a func brush here
}
return 0 ;
}
int GetNearestVisibleWP ( vec3_t org , int ignore )
{
int i ;
float bestdist ;
float flLen ;
int bestindex ;
vec3_t a , mins , maxs ;
i = 0 ;
bestdist = 800 ; //99999;
//don't trace over 800 units away to avoid GIANT HORRIBLE SPEED HITS ^_^
bestindex = - 1 ;
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = - 1 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 1 ;
while ( i < gWPNum )
{
if ( gWPArray [ i ] & & gWPArray [ i ] - > inuse )
{
VectorSubtract ( org , gWPArray [ i ] - > origin , a ) ;
flLen = VectorLength ( a ) ;
if ( flLen < bestdist & & trap_InPVS ( org , gWPArray [ i ] - > origin ) & & OrgVisibleBox ( org , mins , maxs , gWPArray [ i ] - > origin , ignore ) )
{
bestdist = flLen ;
bestindex = i ;
}
}
i + + ;
}
return bestindex ;
}
//wpDirection
//0 == FORWARD
//1 == BACKWARD
int PassWayCheck ( bot_state_t * bs , int windex )
{
if ( ! gWPArray [ windex ] | | ! gWPArray [ windex ] - > inuse )
{
return 0 ;
}
if ( bs - > wpDirection & & ( gWPArray [ windex ] - > flags & WPFLAG_ONEWAY_FWD ) )
{
return 0 ;
}
else if ( ! bs - > wpDirection & & ( gWPArray [ windex ] - > flags & WPFLAG_ONEWAY_BACK ) )
{
return 0 ;
}
return 1 ;
}
float TotalTrailDistance ( int start , int end , bot_state_t * bs )
{
int beginat ;
int endat ;
float distancetotal ;
float gdif = 0 ;
distancetotal = 0 ;
if ( start > end )
{
beginat = end ;
endat = start ;
}
else
{
beginat = start ;
endat = end ;
}
while ( beginat < endat )
{
if ( beginat > = gWPNum | | ! gWPArray [ beginat ] | | ! gWPArray [ beginat ] - > inuse )
{
return - 1 ; //error
}
if ( ( end > start & & gWPArray [ beginat ] - > flags & WPFLAG_ONEWAY_BACK ) | |
( start > end & & gWPArray [ beginat ] - > flags & WPFLAG_ONEWAY_FWD ) )
{
return - 1 ;
}
if ( gWPArray [ beginat ] - > forceJumpTo )
{
if ( gWPArray [ beginat - 1 ] & & gWPArray [ beginat - 1 ] - > origin [ 2 ] + 64 < gWPArray [ beginat ] - > origin [ 2 ] )
{
gdif = gWPArray [ beginat ] - > origin [ 2 ] - gWPArray [ beginat - 1 ] - > origin [ 2 ] ;
}
if ( gdif )
{
// if (bs && bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[beginat]->forceJumpTo)
// {
// return -1;
// }
}
}
/* if (bs->wpCurrent && gWPArray[windex]->forceJumpTo &&
gWPArray [ windex ] - > origin [ 2 ] > ( bs - > wpCurrent - > origin [ 2 ] + 64 ) & &
bs - > cur_ps . fd . forcePowerLevel [ FP_LEVITATION ] < gWPArray [ windex ] - > forceJumpTo )
{
return - 1 ;
} */
distancetotal + = gWPArray [ beginat ] - > disttonext ;
beginat + + ;
}
return distancetotal ;
}
void CheckForShorterRoutes ( bot_state_t * bs , int newwpindex )
{
float bestlen ;
float checklen ;
int bestindex ;
int i ;
i = 0 ;
if ( ! bs - > wpDestination )
{
return ;
}
if ( newwpindex < bs - > wpDestination - > index )
{
bs - > wpDirection = 0 ;
}
else if ( newwpindex > bs - > wpDestination - > index )
{
bs - > wpDirection = 1 ;
}
if ( bs - > wpSwitchTime > level . time )
{
return ;
}
if ( ! gWPArray [ newwpindex ] - > neighbornum )
{
return ;
}
bestindex = newwpindex ;
bestlen = TotalTrailDistance ( newwpindex , bs - > wpDestination - > index , bs ) ;
while ( i < gWPArray [ newwpindex ] - > neighbornum )
{
checklen = TotalTrailDistance ( gWPArray [ newwpindex ] - > neighbors [ i ] . num , bs - > wpDestination - > index , bs ) ;
if ( checklen < bestlen - 64 | | bestlen = = - 1 )
{
if ( ! gWPArray [ newwpindex ] - > neighbors [ i ] . forceJumpTo )
{
bestlen = checklen ;
bestindex = gWPArray [ newwpindex ] - > neighbors [ i ] . num ;
}
}
i + + ;
}
if ( bestindex ! = newwpindex & & bestindex ! = - 1 )
{
bs - > wpCurrent = gWPArray [ bestindex ] ;
bs - > wpSwitchTime = level . time + 3000 ;
}
}
void WPConstantRoutine ( bot_state_t * bs )
{
if ( ! bs - > wpCurrent )
{
return ;
}
if ( bs - > wpCurrent - > flags & WPFLAG_DUCK )
{
bs - > duckTime = level . time + 100 ;
}
}
qboolean BotCTFGuardDuty ( bot_state_t * bs )
{
/*
if ( level . gametype ! = GT_CTF )
{
return qfalse ;
}
if ( bs - > ctfState = = CTFSTATE_DEFENDER )
{
return qtrue ;
}
*/
return qfalse ;
}
void WPTouchRoutine ( bot_state_t * bs )
{
int lastNum ;
if ( ! bs - > wpCurrent )
{
return ;
}
bs - > wpTravelTime = level . time + 10000 ;
if ( bs - > wpCurrent - > flags & WPFLAG_NOMOVEFUNC )
{
bs - > noUseTime = level . time + 4000 ;
}
# ifdef FORCEJUMP_INSTANTMETHOD
if ( ( bs - > wpCurrent - > flags & WPFLAG_JUMP ) & & bs - > wpCurrent - > forceJumpTo )
{ //jump if we're flagged to but not if this indicates a force jump point. Force jumping is
//handled elsewhere.
bs - > jumpTime = level . time + 100 ;
}
# else
if ( ( bs - > wpCurrent - > flags & WPFLAG_JUMP ) & & ! bs - > wpCurrent - > forceJumpTo )
{ //jump if we're flagged to but not if this indicates a force jump point. Force jumping is
//handled elsewhere.
bs - > jumpTime = level . time + 100 ;
}
# endif
trap_Cvar_Update ( & bot_camp ) ;
if ( bs - > isCamper & & bot_camp . integer & & ( BotIsAChickenWuss ( bs ) | | BotCTFGuardDuty ( bs ) | | bs - > isCamper = = 2 ) & & ( ( bs - > wpCurrent - > flags & WPFLAG_SNIPEORCAMP ) | | ( bs - > wpCurrent - > flags & WPFLAG_SNIPEORCAMPSTAND ) ) & &
bs - > cur_ps . weapon ! = WP_KNIFE )
{ //if we're a camper and a chicken then camp
if ( bs - > wpDirection )
{
lastNum = bs - > wpCurrent - > index + 1 ;
}
else
{
lastNum = bs - > wpCurrent - > index - 1 ;
}
if ( gWPArray [ lastNum ] & & gWPArray [ lastNum ] - > inuse & & gWPArray [ lastNum ] - > index & & bs - > isCamping < level . time )
{
bs - > isCamping = level . time + rand ( ) % 15000 + 30000 ;
bs - > wpCamping = bs - > wpCurrent ;
bs - > wpCampingTo = gWPArray [ lastNum ] ;
if ( bs - > wpCurrent - > flags & WPFLAG_SNIPEORCAMPSTAND )
{
bs - > campStanding = qtrue ;
}
else
{
bs - > campStanding = qfalse ;
}
}
}
else if ( ( bs - > cur_ps . weapon = = WP_KNIFE ) & &
bs - > isCamping > level . time )
{
bs - > isCamping = 0 ;
bs - > wpCampingTo = NULL ;
bs - > wpCamping = NULL ;
}
if ( bs - > wpDestination )
{
if ( bs - > wpCurrent - > index = = bs - > wpDestination - > index )
{
bs - > wpDestination = NULL ;
if ( bs - > runningLikeASissy )
{ //this obviously means we're scared and running, so we'll want to keep our navigational priorities less delayed
bs - > destinationGrabTime = level . time + 500 ;
}
else
{
bs - > destinationGrabTime = level . time + 3500 ;
}
}
else
{
CheckForShorterRoutes ( bs , bs - > wpCurrent - > index ) ;
}
}
}
void MoveTowardIdealAngles ( bot_state_t * bs )
{
VectorCopy ( bs - > goalAngles , bs - > ideal_viewangles ) ;
}
# define BOT_STRAFE_AVOIDANCE
# ifdef BOT_STRAFE_AVOIDANCE
# define STRAFEAROUND_RIGHT 1
# define STRAFEAROUND_LEFT 2
int BotTrace_Strafe ( bot_state_t * bs , vec3_t traceto )
{
vec3_t playerMins = { - 15 , - 15 , /*-24*/ - 8 } ;
vec3_t playerMaxs = { 15 , 15 , 32 } ;
vec3_t from , to ;
vec3_t dirAng , dirDif ;
vec3_t forward , right ;
trace_t tr ;
if ( bs - > cur_ps . groundEntityNum = = ENTITYNUM_NONE )
{ //don't do this in the air, it can be.. dangerous.
return 0 ;
}
VectorSubtract ( traceto , bs - > origin , dirAng ) ;
VectorNormalize ( dirAng ) ;
vectoangles ( dirAng , dirAng ) ;
if ( AngleDifference ( bs - > viewangles [ YAW ] , dirAng [ YAW ] ) > 60 | |
AngleDifference ( bs - > viewangles [ YAW ] , dirAng [ YAW ] ) < - 60 )
{ //If we aren't facing the direction we're going here, then we've got enough excuse to be too stupid to strafe around anyway
return 0 ;
}
VectorCopy ( bs - > origin , from ) ;
VectorCopy ( traceto , to ) ;
VectorSubtract ( to , from , dirDif ) ;
VectorNormalize ( dirDif ) ;
vectoangles ( dirDif , dirDif ) ;
AngleVectors ( dirDif , forward , 0 , 0 ) ;
to [ 0 ] = from [ 0 ] + forward [ 0 ] * 32 ;
to [ 1 ] = from [ 1 ] + forward [ 1 ] * 32 ;
to [ 2 ] = from [ 2 ] + forward [ 2 ] * 32 ;
trap_Trace ( & tr , from , playerMins , playerMaxs , to , bs - > client , MASK_PLAYERSOLID ) ;
if ( tr . fraction = = 1 )
{
return 0 ;
}
AngleVectors ( dirAng , 0 , right , 0 ) ;
from [ 0 ] + = right [ 0 ] * 32 ;
from [ 1 ] + = right [ 1 ] * 32 ;
from [ 2 ] + = right [ 2 ] * 16 ;
to [ 0 ] + = right [ 0 ] * 32 ;
to [ 1 ] + = right [ 1 ] * 32 ;
to [ 2 ] + = right [ 2 ] * 32 ;
trap_Trace ( & tr , from , playerMins , playerMaxs , to , bs - > client , MASK_PLAYERSOLID ) ;
if ( tr . fraction = = 1 )
{
return STRAFEAROUND_RIGHT ;
}
from [ 0 ] - = right [ 0 ] * 64 ;
from [ 1 ] - = right [ 1 ] * 64 ;
from [ 2 ] - = right [ 2 ] * 64 ;
to [ 0 ] - = right [ 0 ] * 64 ;
to [ 1 ] - = right [ 1 ] * 64 ;
to [ 2 ] - = right [ 2 ] * 64 ;
trap_Trace ( & tr , from , playerMins , playerMaxs , to , bs - > client , MASK_PLAYERSOLID ) ;
if ( tr . fraction = = 1 )
{
return STRAFEAROUND_LEFT ;
}
return 0 ;
}
# endif
int BotTrace_Jump ( bot_state_t * bs , vec3_t traceto )
{
vec3_t mins , maxs , a , fwd , traceto_mod , tracefrom_mod ;
trace_t tr ;
int orTr ;
VectorSubtract ( traceto , bs - > origin , a ) ;
vectoangles ( a , a ) ;
AngleVectors ( a , fwd , NULL , NULL ) ;
traceto_mod [ 0 ] = bs - > origin [ 0 ] + fwd [ 0 ] * 4 ;
traceto_mod [ 1 ] = bs - > origin [ 1 ] + fwd [ 1 ] * 4 ;
traceto_mod [ 2 ] = bs - > origin [ 2 ] + fwd [ 2 ] * 4 ;
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = - 15 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 32 ;
trap_Trace ( & tr , bs - > origin , mins , maxs , traceto_mod , bs - > client , MASK_PLAYERSOLID ) ;
if ( tr . fraction = = 1 )
{
return 0 ;
}
orTr = tr . entityNum ;
VectorCopy ( bs - > origin , tracefrom_mod ) ;
tracefrom_mod [ 2 ] + = 41 ;
traceto_mod [ 2 ] + = 41 ;
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = 0 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 8 ;
trap_Trace ( & tr , tracefrom_mod , mins , maxs , traceto_mod , bs - > client , MASK_PLAYERSOLID ) ;
if ( tr . fraction = = 1 )
{
if ( orTr > = 0 & & orTr < MAX_CLIENTS & & botstates [ orTr ] & & botstates [ orTr ] - > jumpTime > level . time )
{
return 0 ; //so bots don't try to jump over each other at the same time
}
if ( bs - > currentEnemy & & bs - > currentEnemy - > s . number = = orTr & & ( BotGetWeaponRange ( bs ) = = BWEAPONRANGE_SABER | | BotGetWeaponRange ( bs ) = = BWEAPONRANGE_MELEE ) )
{
return 0 ;
}
return 1 ;
}
return 0 ;
}
int BotTrace_Duck ( bot_state_t * bs , vec3_t traceto )
{
vec3_t mins , maxs , a , fwd , traceto_mod , tracefrom_mod ;
trace_t tr ;
VectorSubtract ( traceto , bs - > origin , a ) ;
vectoangles ( a , a ) ;
AngleVectors ( a , fwd , NULL , NULL ) ;
traceto_mod [ 0 ] = bs - > origin [ 0 ] + fwd [ 0 ] * 4 ;
traceto_mod [ 1 ] = bs - > origin [ 1 ] + fwd [ 1 ] * 4 ;
traceto_mod [ 2 ] = bs - > origin [ 2 ] + fwd [ 2 ] * 4 ;
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = - 23 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 8 ;
trap_Trace ( & tr , bs - > origin , mins , maxs , traceto_mod , bs - > client , MASK_PLAYERSOLID ) ;
if ( tr . fraction ! = 1 )
{
return 0 ;
}
VectorCopy ( bs - > origin , tracefrom_mod ) ;
tracefrom_mod [ 2 ] + = 31 ; //33;
traceto_mod [ 2 ] + = 31 ; //33;
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = 0 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 32 ;
trap_Trace ( & tr , tracefrom_mod , mins , maxs , traceto_mod , bs - > client , MASK_PLAYERSOLID ) ;
if ( tr . fraction ! = 1 )
{
return 1 ;
}
return 0 ;
}
int PassStandardEnemyChecks ( bot_state_t * bs , gentity_t * en )
{
if ( ! bs | | ! en )
{
return 0 ;
}
if ( ! en - > client )
{
return 0 ;
}
if ( en - > health < 1 )
{
return 0 ;
}
if ( ! en - > takedamage )
{
return 0 ;
}
if ( en - > client )
{
if ( en - > client - > ps . pm_type ! = PM_NORMAL )
{
return 0 ;
}
if ( G_IsClientSpectating ( en - > client ) )
{
return 0 ;
}
}
if ( ! en - > s . solid )
{
return 0 ;
}
if ( bs - > client = = en - > s . number )
{
return 0 ;
}
if ( OnSameTeam ( & g_entities [ bs - > client ] , en ) )
{
return 0 ;
}
/*
if ( en - > client & & en - > client - > pers . connected ! = CON_CONNECTED )
{
return 0 ;
}
*/
return 1 ;
}
void BotDamageNotification ( gclient_t * bot , gentity_t * attacker )
{
bot_state_t * bs ;
bot_state_t * bs_a ;
int i ;
if ( ! bot | | ! attacker | | ! attacker - > client )
{
return ;
}
bs_a = botstates [ attacker - > s . number ] ;
if ( bs_a )
{
bs_a - > lastAttacked = & g_entities [ bot - > ps . clientNum ] ;
i = 0 ;
while ( i < MAX_CLIENTS )
{
if ( botstates [ i ] & &
i ! = bs_a - > client & &
botstates [ i ] - > lastAttacked = = & g_entities [ bot - > ps . clientNum ] )
{
botstates [ i ] - > lastAttacked = NULL ;
}
i + + ;
}
}
else //got attacked by a real client, so no one gets rights to lastAttacked
{
i = 0 ;
while ( i < MAX_CLIENTS )
{
if ( botstates [ i ] & &
botstates [ i ] - > lastAttacked = = & g_entities [ bot - > ps . clientNum ] )
{
botstates [ i ] - > lastAttacked = NULL ;
}
i + + ;
}
}
bs = botstates [ bot - > ps . clientNum ] ;
if ( ! bs )
{
return ;
}
bs - > lastHurt = attacker ;
if ( bs - > currentEnemy )
{
return ;
}
if ( ! PassStandardEnemyChecks ( bs , attacker ) )
{
return ;
}
if ( PassLovedOneCheck ( bs , attacker ) )
{
bs - > currentEnemy = attacker ;
bs - > enemySeenTime = level . time + ENEMY_FORGET_MS ;
}
}
int BotCanHear ( bot_state_t * bs , gentity_t * en , float endist )
{
float minlen ;
if ( ! en | | ! en - > client )
{
return 0 ;
}
/*
if ( en & & en - > client & & en - > client - > ps . otherSoundTime > level . time )
{
minlen = en - > client - > ps . otherSoundLen ;
goto checkStep ;
}
*/
/*
if ( en & & en - > client & & en - > client - > ps . footstepTime > level . time )
{
minlen = 256 ;
goto checkStep ;
}
*/
if ( gBotEventTracker [ en - > s . number ] . eventTime < level . time )
{
return 0 ;
}
switch ( gBotEventTracker [ en - > s . number ] . events [ gBotEventTracker [ en - > s . number ] . eventSequence & ( MAX_PS_EVENTS - 1 ) ] )
{
case EV_GLOBAL_SOUND :
minlen = 256 ;
break ;
case EV_FIRE_WEAPON :
case EV_ALT_FIRE :
minlen = 512 ;
break ;
case EV_STEP_4 :
case EV_STEP_8 :
case EV_STEP_12 :
case EV_STEP_16 :
case EV_FOOTSTEP :
case EV_FOOTWADE :
minlen = 256 ;
break ;
case EV_JUMP :
minlen = 256 ;
break ;
default :
minlen = 999999 ;
break ;
}
if ( endist < = minlen )
{
return 1 ;
}
return 0 ;
}
void UpdateEventTracker ( void )
{
int i ;
i = 0 ;
while ( i < MAX_CLIENTS )
{
if ( gBotEventTracker [ i ] . eventSequence ! = level . clients [ i ] . ps . eventSequence )
{ //updated event
gBotEventTracker [ i ] . eventSequence = level . clients [ i ] . ps . eventSequence ;
gBotEventTracker [ i ] . events [ 0 ] = level . clients [ i ] . ps . events [ 0 ] ;
gBotEventTracker [ i ] . events [ 1 ] = level . clients [ i ] . ps . events [ 1 ] ;
gBotEventTracker [ i ] . eventTime = level . time + 0.5 ;
}
i + + ;
}
}
int InFieldOfVision ( vec3_t viewangles , float fov , vec3_t angles )
{
int i ;
float diff , angle ;
for ( i = 0 ; i < 2 ; i + + )
{
angle = AngleMod ( viewangles [ i ] ) ;
angles [ i ] = AngleMod ( angles [ i ] ) ;
diff = angles [ i ] - angle ;
if ( angles [ i ] > angle )
{
if ( diff > 180.0 )
{
diff - = 360.0 ;
}
}
else
{
if ( diff < - 180.0 )
{
diff + = 360.0 ;
}
}
if ( diff > 0 )
{
if ( diff > fov * 0.5 )
{
return 0 ;
}
}
else
{
if ( diff < - fov * 0.5 )
{
return 0 ;
}
}
}
return 1 ;
}
int PassLovedOneCheck ( bot_state_t * bs , gentity_t * ent )
{
int i ;
bot_state_t * loved ;
if ( ! bs - > lovednum )
{
return 1 ;
}
i = 0 ;
if ( ! botstates [ ent - > s . number ] )
{ //not a bot
return 1 ;
}
trap_Cvar_Update ( & bot_attachments ) ;
if ( ! bot_attachments . integer )
{
return 1 ;
}
loved = botstates [ ent - > s . number ] ;
while ( i < bs - > lovednum )
{
if ( strcmp ( level . clients [ loved - > client ] . pers . netname , bs - > loved [ i ] . name ) = = 0 )
{
if ( ! IsTeamplay ( ) & & bs - > loved [ i ] . level < 2 )
{ //if FFA and level of love is not greater than 1, just don't care
return 1 ;
}
else if ( IsTeamplay ( ) & & ! OnSameTeam ( & g_entities [ bs - > client ] , & g_entities [ loved - > client ] ) & & bs - > loved [ i ] . level < 2 )
{ //is teamplay, but not on same team and level < 2
return 1 ;
}
else
{
return 0 ;
}
}
i + + ;
}
return 1 ;
}
int ScanForEnemies ( bot_state_t * bs )
{
vec3_t a ;
float distcheck ;
float closest ;
int bestindex ;
int i ;
float hasEnemyDist = 0 ;
closest = 999999 ;
i = 0 ;
bestindex = - 1 ;
if ( bs - > currentEnemy )
{
hasEnemyDist = bs - > frame_Enemy_Len ;
}
while ( i < = MAX_CLIENTS )
{
if ( i ! = bs - > client & & g_entities [ i ] . client & & ! OnSameTeam ( & g_entities [ bs - > client ] , & g_entities [ i ] ) & & PassStandardEnemyChecks ( bs , & g_entities [ i ] ) & & trap_InPVS ( g_entities [ i ] . client - > ps . origin , bs - > eye ) & & PassLovedOneCheck ( bs , & g_entities [ i ] ) )
{
VectorSubtract ( g_entities [ i ] . client - > ps . origin , bs - > eye , a ) ;
distcheck = VectorLength ( a ) ;
vectoangles ( a , a ) ;
if ( distcheck < closest & & ( ( InFieldOfVision ( bs - > viewangles , 90 , a ) /*&& !BotMindTricked(bs->client, i)*/ ) | | BotCanHear ( bs , & g_entities [ i ] , distcheck ) ) & & OrgVisible ( bs - > eye , g_entities [ i ] . client - > ps . origin , - 1 ) )
{
if ( ! hasEnemyDist | | distcheck < ( hasEnemyDist - 128 ) )
{ //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out
closest = distcheck ;
bestindex = i ;
}
}
}
i + + ;
}
return bestindex ;
}
int WaitingForNow ( bot_state_t * bs , vec3_t goalpos )
{ //checks if the bot is doing something along the lines of waiting for an elevator to raise up
vec3_t xybot , xywp , a ;
if ( ! bs - > wpCurrent )
{
return 0 ;
}
if ( ( int ) goalpos [ 0 ] ! = ( int ) bs - > wpCurrent - > origin [ 0 ] | |
( int ) goalpos [ 1 ] ! = ( int ) bs - > wpCurrent - > origin [ 1 ] | |
( int ) goalpos [ 2 ] ! = ( int ) bs - > wpCurrent - > origin [ 2 ] )
{
return 0 ;
}
VectorCopy ( bs - > origin , xybot ) ;
VectorCopy ( bs - > wpCurrent - > origin , xywp ) ;
xybot [ 2 ] = 0 ;
xywp [ 2 ] = 0 ;
VectorSubtract ( xybot , xywp , a ) ;
if ( VectorLength ( a ) < 16 & & bs - > frame_Waypoint_Len > 100 )
{
if ( CheckForFunc ( bs - > origin , bs - > client ) )
{
return 1 ; //we're probably standing on an elevator and riding up/down. Or at least we hope so.
}
}
else if ( VectorLength ( a ) < 64 & & bs - > frame_Waypoint_Len > 64 & &
CheckForFunc ( bs - > origin , bs - > client ) )
{
bs - > noUseTime = level . time + 2000 ;
}
return 0 ;
}
int BotGetWeaponRange ( bot_state_t * bs )
{
switch ( weaponData [ bs - > cur_ps . weapon ] . category )
{
case CAT_KNIFE :
return BWEAPONRANGE_MELEE ;
case CAT_PISTOL :
return BWEAPONRANGE_MID ; //short
case CAT_SHOTGUN :
return BWEAPONRANGE_MID ; //short
case CAT_SUB :
return BWEAPONRANGE_MID ;
case CAT_ASSAULT :
return BWEAPONRANGE_MID ;
case CAT_SNIPER :
return BWEAPONRANGE_LONG ;
case CAT_HEAVY :
return BWEAPONRANGE_LONG ;
case CAT_GRENADE :
return BWEAPONRANGE_MID ; //short
default :
return BWEAPONRANGE_MID ;
}
}
int BotIsAChickenWuss ( bot_state_t * bs )
{
int bWRange ;
if ( bs - > chickenWussCalculationTime > level . time )
{
return 2 ; //don't want to keep going between two points...
}
bs - > chickenWussCalculationTime = level . time + MAX_CHICKENWUSS_TIME ;
if ( g_entities [ bs - > client ] . health < BOT_RUN_HEALTH )
{
return 1 ;
}
bWRange = BotGetWeaponRange ( bs ) ;
if ( bWRange = = BWEAPONRANGE_MELEE )
{
if ( ! bs - > meleeSpecialist )
{
return 1 ;
}
}
if ( bs - > cur_ps . weapon < WP_USAS_12_SHOTGUN )
{ //the bryar is a weak weapon, so just try to find a new one if it's what you're having to use
return 1 ;
}
if ( bs - > currentEnemy & & bs - > currentEnemy - > client & &
bs - > currentEnemy - > client - > ps . weapon = = WP_KNIFE & &
bs - > frame_Enemy_Len < 512 & & bs - > cur_ps . weapon ! = WP_KNIFE )
{ //if close to an enemy with a knife and not using a knife, then try to back off
return 1 ;
}
//didn't run, reset the timer
bs - > chickenWussCalculationTime = 0 ;
return 0 ;
}
gentity_t * GetNearestBadThing ( bot_state_t * bs )
{
int i = 0 ;
float glen ;
vec3_t hold ;
int bestindex = 0 ;
float bestdist = 800 ; //if not within a radius of 800, it's no threat anyway
int foundindex = 0 ;
float factor = 0 ;
gentity_t * ent ;
trace_t tr ;
while ( i < MAX_GENTITIES )
{
ent = & g_entities [ i ] ;
if ( ( ent & &
! ent - > client & &
ent - > inuse & &
ent - > damage & &
/*(ent->s.weapon == WP_THERMAL || ent->s.weapon == WP_FLECHETTE)*/
ent - > s . weapon & &
ent - > splashDamage ) )
{ //try to escape from anything with a non-0 s.weapon and non-0 damage. This hopefully only means dangerous projectiles.
//Or a sentry gun if bolt_Head == 1000. This is a terrible hack, yes.
VectorSubtract ( bs - > origin , ent - > r . currentOrigin , hold ) ;
glen = VectorLength ( hold ) ;
//if (ent->s.weapon != WP_THERMAL && ent->s.weapon != WP_FLECHETTE &&
// ent->s.weapon != WP_DET_PACK && ent->s.weapon != WP_TRIP_MINE)
if ( weaponData [ ent - > s . weapon ] . category ! = CAT_GRENADE )
{
factor = 0.5 ;
}
else
{
factor = 1 ;
}
if ( ent - > s . weapon = = WP_RPG7_LAUNCHER & &
( ent - > r . ownerNum = = bs - > client | |
( ent - > r . ownerNum > 0 & & ent - > r . ownerNum < MAX_CLIENTS & &
g_entities [ ent - > r . ownerNum ] . client & & OnSameTeam ( & g_entities [ bs - > client ] , & g_entities [ ent - > r . ownerNum ] ) ) ) )
{ //don't be afraid of your own rockets or your teammates' rockets
factor = 0 ;
}
if ( glen < bestdist * factor & & trap_InPVS ( bs - > origin , ent - > s . pos . trBase ) )
{
trap_Trace ( & tr , bs - > origin , NULL , NULL , ent - > s . pos . trBase , bs - > client , MASK_SOLID ) ;
if ( tr . fraction = = 1 | | tr . entityNum = = ent - > s . number )
{
bestindex = i ;
bestdist = glen ;
foundindex = 1 ;
}
}
}
i + + ;
}
if ( foundindex )
{
bs - > dontGoBack = level . time + 1500 ;
return & g_entities [ bestindex ] ;
}
else
{
return NULL ;
}
}
int BotDefendFlag ( bot_state_t * bs )
{
wpobject_t * flagPoint ;
vec3_t a ;
if ( level . clients [ bs - > client ] . sess . team = = TEAM_RED )
{
flagPoint = flagRed ;
}
else if ( level . clients [ bs - > client ] . sess . team = = TEAM_BLUE )
{
flagPoint = flagBlue ;
}
else
{
return 0 ;
}
if ( ! flagPoint )
{
return 0 ;
}
VectorSubtract ( bs - > origin , flagPoint - > origin , a ) ;
if ( VectorLength ( a ) > BASE_GUARD_DISTANCE )
{
bs - > wpDestination = flagPoint ;
}
return 1 ;
}
int BotGetEnemyFlag ( bot_state_t * bs )
{
wpobject_t * flagPoint ;
vec3_t a ;
if ( level . clients [ bs - > client ] . sess . team = = TEAM_RED )
{
flagPoint = flagBlue ;
}
else if ( level . clients [ bs - > client ] . sess . team = = TEAM_BLUE )
{
flagPoint = flagRed ;
}
else
{
return 0 ;
}
if ( ! flagPoint )
{
return 0 ;
}
VectorSubtract ( bs - > origin , flagPoint - > origin , a ) ;
if ( VectorLength ( a ) > BASE_GETENEMYFLAG_DISTANCE )
{
bs - > wpDestination = flagPoint ;
}
return 1 ;
}
int BotGetFlagBack ( bot_state_t * bs )
{
# ifdef BOT_KNOW_CTF
int i = 0 ;
int myFlag = 0 ;
int foundCarrier = 0 ;
int tempInt = 0 ;
gentity_t * ent = NULL ;
vec3_t usethisvec ;
if ( level . clients [ bs - > client ] . sess . sessionTeam = = TEAM_RED )
{
myFlag = PW_REDFLAG ;
}
else
{
myFlag = PW_BLUEFLAG ;
}
while ( i < MAX_CLIENTS )
{
ent = & g_entities [ i ] ;
if ( ent & & ent - > client & & ent - > client - > ps . powerups [ myFlag ] & & ! OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
foundCarrier = 1 ;
break ;
}
i + + ;
}
if ( ! foundCarrier )
{
return 0 ;
}
if ( ! ent )
{
return 0 ;
}
if ( bs - > wpDestSwitchTime < level . time )
{
if ( ent - > client )
{
VectorCopy ( ent - > client - > ps . origin , usethisvec ) ;
}
else
{
VectorCopy ( ent - > s . origin , usethisvec ) ;
}
tempInt = GetNearestVisibleWP ( usethisvec , 0 ) ;
if ( tempInt ! = - 1 & & TotalTrailDistance ( bs - > wpCurrent - > index , tempInt , bs ) ! = - 1 )
{
bs - > wpDestination = gWPArray [ tempInt ] ;
bs - > wpDestSwitchTime = level . time + Q_irand ( 1000 , 5000 ) ;
}
}
return 1 ;
# else
return 0 ;
# endif
}
int BotGuardFlagCarrier ( bot_state_t * bs )
{
# ifdef BOT_KNOW_CTF
int i = 0 ;
int enemyFlag = 0 ;
int foundCarrier = 0 ;
int tempInt = 0 ;
gentity_t * ent = NULL ;
vec3_t usethisvec ;
if ( level . clients [ bs - > client ] . sess . sessionTeam = = TEAM_RED )
{
enemyFlag = PW_BLUEFLAG ;
}
else
{
enemyFlag = PW_REDFLAG ;
}
while ( i < MAX_CLIENTS )
{
ent = & g_entities [ i ] ;
if ( ent & & ent - > client & & ent - > client - > ps . powerups [ enemyFlag ] & & OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
foundCarrier = 1 ;
break ;
}
i + + ;
}
if ( ! foundCarrier )
{
return 0 ;
}
if ( ! ent )
{
return 0 ;
}
if ( bs - > wpDestSwitchTime < level . time )
{
if ( ent - > client )
{
VectorCopy ( ent - > client - > ps . origin , usethisvec ) ;
}
else
{
VectorCopy ( ent - > s . origin , usethisvec ) ;
}
tempInt = GetNearestVisibleWP ( usethisvec , 0 ) ;
if ( tempInt ! = - 1 & & TotalTrailDistance ( bs - > wpCurrent - > index , tempInt , bs ) ! = - 1 )
{
bs - > wpDestination = gWPArray [ tempInt ] ;
bs - > wpDestSwitchTime = level . time + Q_irand ( 1000 , 5000 ) ;
}
}
return 1 ;
# else
return 0 ;
# endif
}
int BotGetFlagHome ( bot_state_t * bs )
{
# ifdef BOT_KNOW_CTF
wpobject_t * flagPoint ;
vec3_t a ;
if ( level . clients [ bs - > client ] . sess . sessionTeam = = TEAM_RED )
{
flagPoint = flagRed ;
}
else if ( level . clients [ bs - > client ] . sess . sessionTeam = = TEAM_BLUE )
{
flagPoint = flagBlue ;
}
else
{
return 0 ;
}
if ( ! flagPoint )
{
return 0 ;
}
VectorSubtract ( bs - > origin , flagPoint - > origin , a ) ;
if ( VectorLength ( a ) > BASE_FLAGWAIT_DISTANCE )
{
bs - > wpDestination = flagPoint ;
}
return 1 ;
# else
return 0 ;
# endif
}
void GetNewFlagPoint ( wpobject_t * wp , gentity_t * flagEnt , int team )
{ //get the nearest possible waypoint to the flag since it's not in its original position
# ifdef BOT_KNOW_CTF
int i = 0 ;
vec3_t a , mins , maxs ;
float bestdist ;
float testdist ;
int bestindex = 0 ;
int foundindex = 0 ;
trace_t tr ;
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = - 5 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 5 ;
VectorSubtract ( wp - > origin , flagEnt - > s . pos . trBase , a ) ;
bestdist = VectorLength ( a ) ;
if ( bestdist < = WP_KEEP_FLAG_DIST )
{
trap_Trace ( & tr , wp - > origin , mins , maxs , flagEnt - > s . pos . trBase , flagEnt - > s . number , MASK_SOLID ) ;
if ( tr . fraction = = 1 )
{ //this point is good
return ;
}
}
while ( i < gWPNum )
{
VectorSubtract ( gWPArray [ i ] - > origin , flagEnt - > s . pos . trBase , a ) ;
testdist = VectorLength ( a ) ;
if ( testdist < bestdist )
{
trap_Trace ( & tr , gWPArray [ i ] - > origin , mins , maxs , flagEnt - > s . pos . trBase , flagEnt - > s . number , MASK_SOLID ) ;
if ( tr . fraction = = 1 )
{
foundindex = 1 ;
bestindex = i ;
bestdist = testdist ;
}
}
i + + ;
}
if ( foundindex )
{
if ( team = = TEAM_RED )
{
flagRed = gWPArray [ bestindex ] ;
}
else
{
flagBlue = gWPArray [ bestindex ] ;
}
}
# endif
}
int CTFTakesPriority ( bot_state_t * bs )
{
/*
# ifdef BOT_KNOW_CTF
gentity_t * ent = NULL ;
int enemyFlag = 0 ;
int myFlag = 0 ;
int enemyHasOurFlag = 0 ;
int weHaveEnemyFlag = 0 ;
int numOnMyTeam = 0 ;
int numOnEnemyTeam = 0 ;
int numAttackers = 0 ;
int numDefenders = 0 ;
int i = 0 ;
int idleWP ;
int dosw = 0 ;
wpobject_t * dest_sw = NULL ;
# ifdef BOT_CTF_DEBUG
vec3_t t ;
G_Printf ( " CTFSTATE: %s \n " , ctfStateNames [ bs - > ctfState ] ) ;
# endif
if ( level . gametype ! = GT_CTF )
{
return 0 ;
}
if ( bs - > cur_ps . weapon = = WP_BRYAR_PISTOL & &
( level . time - bs - > lastDeadTime ) < BOT_MAX_WEAPON_GATHER_TIME )
{ //get the nearest weapon laying around base before heading off for battle
idleWP = GetBestIdleGoal ( bs ) ;
if ( idleWP ! = - 1 & & gWPArray [ idleWP ] & & gWPArray [ idleWP ] - > inuse )
{
if ( bs - > wpDestSwitchTime < level . time )
{
bs - > wpDestination = gWPArray [ idleWP ] ;
}
return 1 ;
}
}
else if ( bs - > cur_ps . weapon = = WP_BRYAR_PISTOL & &
( level . time - bs - > lastDeadTime ) < BOT_MAX_WEAPON_CHASE_CTF & &
bs - > wpDestination & & bs - > wpDestination - > weight )
{
dest_sw = bs - > wpDestination ;
dosw = 1 ;
}
if ( level . clients [ bs - > client ] . sess . sessionTeam = = TEAM_RED )
{
myFlag = PW_REDFLAG ;
}
else
{
myFlag = PW_BLUEFLAG ;
}
if ( level . clients [ bs - > client ] . sess . sessionTeam = = TEAM_RED )
{
enemyFlag = PW_BLUEFLAG ;
}
else
{
enemyFlag = PW_REDFLAG ;
}
if ( ! flagRed | | ! flagBlue | |
! flagRed - > inuse | | ! flagBlue - > inuse | |
! eFlagRed | | ! eFlagBlue )
{
return 0 ;
}
# ifdef BOT_CTF_DEBUG
VectorCopy ( flagRed - > origin , t ) ;
t [ 2 ] + = 128 ;
G_TestLine ( flagRed - > origin , t , 0x0000ff , 500 ) ;
VectorCopy ( flagBlue - > origin , t ) ;
t [ 2 ] + = 128 ;
G_TestLine ( flagBlue - > origin , t , 0x0000ff , 500 ) ;
# endif
if ( droppedRedFlag & & ( droppedRedFlag - > flags & FL_DROPPED_ITEM ) )
{
GetNewFlagPoint ( flagRed , droppedRedFlag , TEAM_RED ) ;
}
else
{
flagRed = oFlagRed ;
}
if ( droppedBlueFlag & & ( droppedBlueFlag - > flags & FL_DROPPED_ITEM ) )
{
GetNewFlagPoint ( flagBlue , droppedBlueFlag , TEAM_BLUE ) ;
}
else
{
flagBlue = oFlagBlue ;
}
if ( ! bs - > ctfState )
{
return 0 ;
}
i = 0 ;
while ( i < MAX_CLIENTS )
{
ent = & g_entities [ i ] ;
if ( ent & & ent - > client )
{
if ( ent - > client - > ps . powerups [ enemyFlag ] & & OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
weHaveEnemyFlag = 1 ;
}
else if ( ent - > client - > ps . powerups [ myFlag ] & & ! OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
enemyHasOurFlag = 1 ;
}
if ( OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
numOnMyTeam + + ;
}
else
{
numOnEnemyTeam + + ;
}
if ( botstates [ ent - > s . number ] )
{
if ( botstates [ ent - > s . number ] - > ctfState = = CTFSTATE_ATTACKER | |
botstates [ ent - > s . number ] - > ctfState = = CTFSTATE_RETRIEVAL )
{
numAttackers + + ;
}
else
{
numDefenders + + ;
}
}
else
{ //assume real players to be attackers in our logic
numAttackers + + ;
}
}
i + + ;
}
if ( bs - > cur_ps . powerups [ enemyFlag ] )
{
if ( ( numOnMyTeam < 2 | | ! numAttackers ) & & enemyHasOurFlag )
{
bs - > ctfState = CTFSTATE_RETRIEVAL ;
}
else
{
bs - > ctfState = CTFSTATE_GETFLAGHOME ;
}
}
else if ( bs - > ctfState = = CTFSTATE_GETFLAGHOME )
{
bs - > ctfState = 0 ;
}
if ( bs - > state_Forced )
{
bs - > ctfState = bs - > state_Forced ;
}
if ( bs - > ctfState = = CTFSTATE_DEFENDER )
{
if ( BotDefendFlag ( bs ) )
{
goto success ;
}
}
if ( bs - > ctfState = = CTFSTATE_ATTACKER )
{
if ( BotGetEnemyFlag ( bs ) )
{
goto success ;
}
}
if ( bs - > ctfState = = CTFSTATE_RETRIEVAL )
{
if ( BotGetFlagBack ( bs ) )
{
goto success ;
}
else
{ //can't find anyone on another team being a carrier, so ignore this priority
bs - > ctfState = 0 ;
}
}
if ( bs - > ctfState = = CTFSTATE_GUARDCARRIER )
{
if ( BotGuardFlagCarrier ( bs ) )
{
goto success ;
}
else
{ //can't find anyone on our team being a carrier, so ignore this priority
bs - > ctfState = 0 ;
}
}
if ( bs - > ctfState = = CTFSTATE_GETFLAGHOME )
{
if ( BotGetFlagHome ( bs ) )
{
goto success ;
}
}
return 0 ;
success :
if ( dosw )
{ //allow ctf code to run, but if after a particular item then keep going after it
bs - > wpDestination = dest_sw ;
}
return 1 ;
# else
return 0 ;
# endif
*/
return 0 ;
}
int EntityVisibleBox ( vec3_t org1 , vec3_t mins , vec3_t maxs , vec3_t org2 , int ignore , int ignore2 )
{
trace_t tr ;
trap_Trace ( & tr , org1 , mins , maxs , org2 , ignore , MASK_SOLID ) ;
if ( tr . fraction = = 1 & & ! tr . startsolid & & ! tr . allsolid )
{
return 1 ;
}
else if ( tr . entityNum ! = ENTITYNUM_NONE & & tr . entityNum = = ignore2 )
{
return 1 ;
}
return 0 ;
}
int BotHasAssociated ( bot_state_t * bs , wpobject_t * wp )
{
gentity_t * as ;
if ( wp - > associated_entity = = ENTITYNUM_NONE )
{ //make it think this is an item we have so we don't go after nothing
return 1 ;
}
as = & g_entities [ wp - > associated_entity ] ;
if ( ! as | | ! as - > item )
{
return 0 ;
}
if ( as - > item - > giType = = IT_WEAPON )
{
if ( bs - > cur_ps . stats [ STAT_WEAPONS ] & ( 1 < < as - > item - > giTag ) )
{
return 1 ;
}
return 0 ;
}
# ifdef BOT_USE_HOLDABLE
else if ( as - > item - > giType = = IT_HOLDABLE )
{
if ( bs - > cur_ps . stats [ STAT_HOLDABLE_ITEMS ] & ( 1 < < as - > item - > giTag ) )
{
return 1 ;
}
return 0 ;
}
# endif
else if ( as - > item - > giType = = IT_AMMO )
{
if ( bs - > cur_ps . ammo [ as - > item - > giTag ] > 10 ) //hack
{
return 1 ;
}
return 0 ;
}
return 0 ;
}
int GetBestIdleGoal ( bot_state_t * bs )
{
int i = 0 ;
int highestweight = 0 ;
int desiredindex = - 1 ;
int dist_to_weight = 0 ;
int traildist ;
if ( ! bs - > wpCurrent )
{
return - 1 ;
}
if ( bs - > isCamper ! = 2 )
{
if ( bs - > randomNavTime < level . time )
{
if ( Q_irand ( 1 , 10 ) < 5 )
{
bs - > randomNav = 1 ;
}
else
{
bs - > randomNav = 0 ;
}
bs - > randomNavTime = level . time + Q_irand ( 5000 , 15000 ) ;
}
}
if ( bs - > randomNav )
{ //stop looking for items and/or camping on them
return - 1 ;
}
while ( i < gWPNum )
{
if ( gWPArray [ i ] & &
gWPArray [ i ] - > inuse & &
( gWPArray [ i ] - > flags & WPFLAG_GOALPOINT ) & &
gWPArray [ i ] - > weight > highestweight & &
! BotHasAssociated ( bs , gWPArray [ i ] ) )
{
traildist = TotalTrailDistance ( bs - > wpCurrent - > index , i , bs ) ;
if ( traildist ! = - 1 )
{
dist_to_weight = ( int ) traildist / 10000 ;
dist_to_weight = ( gWPArray [ i ] - > weight ) - dist_to_weight ;
if ( dist_to_weight > highestweight )
{
highestweight = dist_to_weight ;
desiredindex = i ;
}
}
}
i + + ;
}
return desiredindex ;
}
void GetIdealDestination ( bot_state_t * bs )
{
int tempInt , cWPIndex , bChicken , idleWP ;
float distChange , plusLen , minusLen ;
vec3_t usethisvec , a ;
gentity_t * badthing ;
if ( ! bs - > wpCurrent )
{
return ;
}
if ( ( level . time - bs - > escapeDirTime ) > 4000 )
{
badthing = GetNearestBadThing ( bs ) ;
}
else
{
badthing = NULL ;
}
if ( badthing & & badthing - > inuse & &
badthing - > health > 0 & & badthing - > takedamage )
{
bs - > dangerousObject = badthing ;
}
else
{
bs - > dangerousObject = NULL ;
}
if ( ! badthing & & bs - > wpDestIgnoreTime > level . time )
{
return ;
}
if ( ! badthing & & bs - > dontGoBack > level . time )
{
if ( bs - > wpDestination )
{
bs - > wpStoreDest = bs - > wpDestination ;
}
bs - > wpDestination = NULL ;
return ;
}
else if ( ! badthing & & bs - > wpStoreDest )
{ //after we finish running away, switch back to our original destination
bs - > wpDestination = bs - > wpStoreDest ;
bs - > wpStoreDest = NULL ;
}
if ( badthing & & bs - > wpCamping )
{
bs - > wpCamping = NULL ;
}
if ( bs - > wpCamping )
{
bs - > wpDestination = bs - > wpCamping ;
return ;
}
if ( ! badthing & & CTFTakesPriority ( bs ) )
{
if ( bs - > ctfState )
{
bs - > runningToEscapeThreat = 1 ;
}
return ;
}
if ( badthing )
{
bs - > runningLikeASissy = level . time + 100 ;
if ( bs - > wpDestination )
{
bs - > wpStoreDest = bs - > wpDestination ;
}
bs - > wpDestination = NULL ;
if ( bs - > wpDirection )
{
tempInt = bs - > wpCurrent - > index + 1 ;
}
else
{
tempInt = bs - > wpCurrent - > index - 1 ;
}
if ( gWPArray [ tempInt ] & & gWPArray [ tempInt ] - > inuse & & bs - > escapeDirTime < level . time )
{
VectorSubtract ( badthing - > s . pos . trBase , bs - > wpCurrent - > origin , a ) ;
plusLen = VectorLength ( a ) ;
VectorSubtract ( badthing - > s . pos . trBase , gWPArray [ tempInt ] - > origin , a ) ;
minusLen = VectorLength ( a ) ;
if ( plusLen < minusLen )
{
if ( bs - > wpDirection )
{
bs - > wpDirection = 0 ;
}
else
{
bs - > wpDirection = 1 ;
}
bs - > wpCurrent = gWPArray [ tempInt ] ;
bs - > escapeDirTime = level . time + Q_irand ( 500 , 1000 ) ; //Q_irand(1000, 1400);
//G_Printf("Escaping from scary bad thing [%s]\n", badthing->classname);
}
}
//G_Printf("Run away run away run away!\n");
return ;
}
distChange = 0 ; //keep the compiler from complaining
tempInt = BotGetWeaponRange ( bs ) ;
if ( tempInt = = BWEAPONRANGE_MELEE )
{
distChange = 1 ;
}
else if ( tempInt = = BWEAPONRANGE_SABER )
{
distChange = 1 ;
}
else if ( tempInt = = BWEAPONRANGE_MID )
{
distChange = 128 ;
}
else if ( tempInt = = BWEAPONRANGE_LONG )
{
distChange = 300 ;
}
if ( bs - > revengeEnemy & & bs - > revengeEnemy - > health > 0 & &
bs - > revengeEnemy - > client & & ( bs - > revengeEnemy - > client - > pers . connected = = CA_ACTIVE | | bs - > revengeEnemy - > client - > pers . connected = = CA_AUTHORIZING ) )
{ //if we hate someone, always try to get to them
if ( bs - > wpDestSwitchTime < level . time )
{
if ( bs - > revengeEnemy - > client )
{
VectorCopy ( bs - > revengeEnemy - > client - > ps . origin , usethisvec ) ;
}
else
{
VectorCopy ( bs - > revengeEnemy - > s . origin , usethisvec ) ;
}
tempInt = GetNearestVisibleWP ( usethisvec , 0 ) ;
if ( tempInt ! = - 1 & & TotalTrailDistance ( bs - > wpCurrent - > index , tempInt , bs ) ! = - 1 )
{
bs - > wpDestination = gWPArray [ tempInt ] ;
bs - > wpDestSwitchTime = level . time + Q_irand ( 5000 , 10000 ) ;
}
}
}
else if ( bs - > squadLeader & & bs - > squadLeader - > health > 0 & &
bs - > squadLeader - > client & & ( bs - > squadLeader - > client - > pers . connected = = CA_ACTIVE | | bs - > squadLeader - > client - > pers . connected = = CA_AUTHORIZING ) )
{
if ( bs - > wpDestSwitchTime < level . time )
{
if ( bs - > squadLeader - > client )
{
VectorCopy ( bs - > squadLeader - > client - > ps . origin , usethisvec ) ;
}
else
{
VectorCopy ( bs - > squadLeader - > s . origin , usethisvec ) ;
}
tempInt = GetNearestVisibleWP ( usethisvec , 0 ) ;
if ( tempInt ! = - 1 & & TotalTrailDistance ( bs - > wpCurrent - > index , tempInt , bs ) ! = - 1 )
{
bs - > wpDestination = gWPArray [ tempInt ] ;
bs - > wpDestSwitchTime = level . time + Q_irand ( 5000 , 10000 ) ;
}
}
}
else if ( bs - > currentEnemy )
{
if ( bs - > currentEnemy - > client )
{
VectorCopy ( bs - > currentEnemy - > client - > ps . origin , usethisvec ) ;
}
else
{
VectorCopy ( bs - > currentEnemy - > s . origin , usethisvec ) ;
}
bChicken = BotIsAChickenWuss ( bs ) ;
bs - > runningToEscapeThreat = bChicken ;
if ( bs - > frame_Enemy_Len < distChange | | ( bChicken & & bChicken ! = 2 ) )
{
cWPIndex = bs - > wpCurrent - > index ;
if ( bs - > frame_Enemy_Len > 400 )
{ //good distance away, start running toward a good place for an item or powerup or whatever
idleWP = GetBestIdleGoal ( bs ) ;
if ( idleWP ! = - 1 & & gWPArray [ idleWP ] & & gWPArray [ idleWP ] - > inuse )
{
bs - > wpDestination = gWPArray [ idleWP ] ;
}
}
else if ( gWPArray [ cWPIndex - 1 ] & & gWPArray [ cWPIndex - 1 ] - > inuse & &
gWPArray [ cWPIndex + 1 ] & & gWPArray [ cWPIndex + 1 ] - > inuse )
{
VectorSubtract ( gWPArray [ cWPIndex + 1 ] - > origin , usethisvec , a ) ;
plusLen = VectorLength ( a ) ;
VectorSubtract ( gWPArray [ cWPIndex - 1 ] - > origin , usethisvec , a ) ;
minusLen = VectorLength ( a ) ;
if ( minusLen > plusLen )
{
bs - > wpDestination = gWPArray [ cWPIndex - 1 ] ;
}
else
{
bs - > wpDestination = gWPArray [ cWPIndex + 1 ] ;
}
}
}
else if ( bChicken ! = 2 & & bs - > wpDestSwitchTime < level . time )
{
tempInt = GetNearestVisibleWP ( usethisvec , 0 ) ;
if ( tempInt ! = - 1 & & TotalTrailDistance ( bs - > wpCurrent - > index , tempInt , bs ) ! = - 1 )
{
bs - > wpDestination = gWPArray [ tempInt ] ;
bs - > wpDestSwitchTime = level . time + Q_irand ( 1000 , 5000 ) ;
}
}
}
if ( ! bs - > wpDestination & & bs - > wpDestSwitchTime < level . time )
{
//G_Printf("I need something to do\n");
idleWP = GetBestIdleGoal ( bs ) ;
if ( idleWP ! = - 1 & & gWPArray [ idleWP ] & & gWPArray [ idleWP ] - > inuse )
{
bs - > wpDestination = gWPArray [ idleWP ] ;
}
}
}
void CommanderBotCTFAI ( bot_state_t * bs )
{
# ifdef BOT_KNOW_CTF
int i = 0 ;
gentity_t * ent ;
int squadmates = 0 ;
gentity_t * squad [ MAX_CLIENTS ] ;
int defendAttackPriority = 0 ; //0 == attack, 1 == defend
int guardDefendPriority = 0 ; //0 == defend, 1 == guard
int attackRetrievePriority = 0 ; //0 == retrieve, 1 == attack
int myFlag = 0 ;
int enemyFlag = 0 ;
int enemyHasOurFlag = 0 ;
int weHaveEnemyFlag = 0 ;
int numOnMyTeam = 0 ;
int numOnEnemyTeam = 0 ;
int numAttackers = 0 ;
int numDefenders = 0 ;
if ( level . clients [ bs - > client ] . sess . sessionTeam = = TEAM_RED )
{
myFlag = PW_REDFLAG ;
}
else
{
myFlag = PW_BLUEFLAG ;
}
if ( level . clients [ bs - > client ] . sess . sessionTeam = = TEAM_RED )
{
enemyFlag = PW_BLUEFLAG ;
}
else
{
enemyFlag = PW_REDFLAG ;
}
while ( i < MAX_CLIENTS )
{
ent = & g_entities [ i ] ;
if ( ent & & ent - > client )
{
if ( ent - > client - > ps . powerups [ enemyFlag ] & & OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
weHaveEnemyFlag = 1 ;
}
else if ( ent - > client - > ps . powerups [ myFlag ] & & ! OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
enemyHasOurFlag = 1 ;
}
if ( OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
numOnMyTeam + + ;
}
else
{
numOnEnemyTeam + + ;
}
if ( botstates [ ent - > s . number ] )
{
if ( botstates [ ent - > s . number ] - > ctfState = = CTFSTATE_ATTACKER | |
botstates [ ent - > s . number ] - > ctfState = = CTFSTATE_RETRIEVAL )
{
numAttackers + + ;
}
else
{
numDefenders + + ;
}
}
else
{ //assume real players to be attackers in our logic
numAttackers + + ;
}
}
i + + ;
}
i = 0 ;
while ( i < MAX_CLIENTS )
{
ent = & g_entities [ i ] ;
if ( ent & & ent - > client & & botstates [ i ] & & botstates [ i ] - > squadLeader & & botstates [ i ] - > squadLeader - > s . number = = bs - > client & & i ! = bs - > client )
{
squad [ squadmates ] = ent ;
squadmates + + ;
}
i + + ;
}
squad [ squadmates ] = & g_entities [ bs - > client ] ;
squadmates + + ;
i = 0 ;
if ( enemyHasOurFlag & & ! weHaveEnemyFlag )
{ //start off with an attacker instead of a retriever if we don't have the enemy flag yet so that they can't capture it first.
//after that we focus on getting our flag back.
attackRetrievePriority = 1 ;
}
while ( i < squadmates )
{
if ( squad [ i ] & & squad [ i ] - > client & & botstates [ squad [ i ] - > s . number ] )
{
if ( botstates [ squad [ i ] - > s . number ] - > ctfState ! = CTFSTATE_GETFLAGHOME )
{ //never tell a bot to stop trying to bring the flag to the base
if ( defendAttackPriority )
{
if ( weHaveEnemyFlag )
{
if ( guardDefendPriority )
{
botstates [ squad [ i ] - > s . number ] - > ctfState = CTFSTATE_GUARDCARRIER ;
guardDefendPriority = 0 ;
}
else
{
botstates [ squad [ i ] - > s . number ] - > ctfState = CTFSTATE_DEFENDER ;
guardDefendPriority = 1 ;
}
}
else
{
botstates [ squad [ i ] - > s . number ] - > ctfState = CTFSTATE_DEFENDER ;
}
defendAttackPriority = 0 ;
}
else
{
if ( enemyHasOurFlag )
{
if ( attackRetrievePriority )
{
botstates [ squad [ i ] - > s . number ] - > ctfState = CTFSTATE_ATTACKER ;
attackRetrievePriority = 0 ;
}
else
{
botstates [ squad [ i ] - > s . number ] - > ctfState = CTFSTATE_RETRIEVAL ;
attackRetrievePriority = 1 ;
}
}
else
{
botstates [ squad [ i ] - > s . number ] - > ctfState = CTFSTATE_ATTACKER ;
}
defendAttackPriority = 1 ;
}
}
else if ( ( numOnMyTeam < 2 | | ! numAttackers ) & & enemyHasOurFlag )
{ //I'm the only one on my team who will attack and the enemy has my flag, I have to go after him
botstates [ squad [ i ] - > s . number ] - > ctfState = CTFSTATE_RETRIEVAL ;
}
}
i + + ;
}
# endif
}
void BotDoTeamplayAI ( bot_state_t * bs )
{
if ( bs - > state_Forced )
{
bs - > teamplayState = bs - > state_Forced ;
}
if ( bs - > teamplayState = = TEAMPLAYSTATE_REGROUP )
{ //force to find a new leader
bs - > squadLeader = NULL ;
bs - > isSquadLeader = 0 ;
}
}
void CommanderBotTeamplayAI ( bot_state_t * bs )
{
int i = 0 ;
int squadmates = 0 ;
int teammates = 0 ;
int teammate_indanger = - 1 ;
int teammate_helped = 0 ;
int foundsquadleader = 0 ;
int worsthealth = 50 ;
gentity_t * squad [ MAX_CLIENTS ] ;
gentity_t * ent ;
bot_state_t * bst ;
while ( i < MAX_CLIENTS )
{
ent = & g_entities [ i ] ;
if ( ent & & ent - > client & & OnSameTeam ( & g_entities [ bs - > client ] , ent ) & & botstates [ ent - > s . number ] )
{
bst = botstates [ ent - > s . number ] ;
if ( foundsquadleader & & bst & & bst - > isSquadLeader )
{ //never more than one squad leader
bst - > isSquadLeader = 0 ;
}
if ( bst & & ! bst - > isSquadLeader )
{
squad [ squadmates ] = ent ;
squadmates + + ;
}
else if ( bst )
{
foundsquadleader = 1 ;
}
}
if ( ent & & ent - > client & & OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
teammates + + ;
if ( ent - > health < worsthealth )
{
teammate_indanger = ent - > s . number ;
worsthealth = ent - > health ;
}
}
i + + ;
}
if ( ! squadmates )
{
return ;
}
i = 0 ;
while ( i < squadmates & & squad [ i ] )
{
bst = botstates [ squad [ i ] - > s . number ] ;
if ( bst & & ! bst - > state_Forced )
{ //only order if this guy is not being ordered directly by the real player team leader
if ( teammate_indanger > = 0 & & ! teammate_helped )
{ //send someone out to help whoever needs help most at the moment
bst - > teamplayState = TEAMPLAYSTATE_ASSISTING ;
bst - > squadLeader = & g_entities [ teammate_indanger ] ;
teammate_helped = 1 ;
}
else if ( ( teammate_indanger = = - 1 | | teammate_helped ) & & bst - > teamplayState = = TEAMPLAYSTATE_ASSISTING )
{ //no teammates need help badly, but this guy is trying to help them anyway, so stop
bst - > teamplayState = TEAMPLAYSTATE_FOLLOWING ;
bst - > squadLeader = & g_entities [ bs - > client ] ;
}
if ( bs - > squadRegroupInterval < level . time & & Q_irand ( 1 , 10 ) < 5 )
{ //every so often tell the squad to regroup for the sake of variation
if ( bst - > teamplayState = = TEAMPLAYSTATE_FOLLOWING )
{
bst - > teamplayState = TEAMPLAYSTATE_REGROUP ;
}
bs - > isSquadLeader = 0 ;
bs - > squadCannotLead = level . time + 500 ;
bs - > squadRegroupInterval = level . time + Q_irand ( 45000 , 65000 ) ;
}
}
i + + ;
}
}
void CommanderBotAI ( bot_state_t * bs )
{
/*
if ( level . gametype = = GT_CTF )
{
CommanderBotCTFAI ( bs ) ;
}
else if ( level . gametype = = GT_TDM )
{
CommanderBotTeamplayAI ( bs ) ;
}
*/
}
void MeleeCombatHandling ( bot_state_t * bs )
{
vec3_t usethisvec ;
vec3_t downvec ;
vec3_t midorg ;
vec3_t a ;
vec3_t fwd ;
vec3_t mins , maxs ;
trace_t tr ;
int en_down ;
int me_down ;
int mid_down ;
if ( ! bs - > currentEnemy )
{
return ;
}
if ( bs - > currentEnemy - > client )
{
VectorCopy ( bs - > currentEnemy - > client - > ps . origin , usethisvec ) ;
}
else
{
VectorCopy ( bs - > currentEnemy - > s . origin , usethisvec ) ;
}
if ( bs - > meleeStrafeTime < level . time )
{
if ( bs - > meleeStrafeDir )
{
bs - > meleeStrafeDir = 0 ;
}
else
{
bs - > meleeStrafeDir = 1 ;
}
bs - > meleeStrafeTime = level . time + Q_irand ( 500 , 1800 ) ;
}
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = - 24 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 32 ;
VectorCopy ( usethisvec , downvec ) ;
downvec [ 2 ] - = 4096 ;
trap_Trace ( & tr , usethisvec , mins , maxs , downvec , - 1 , MASK_SOLID ) ;
en_down = ( int ) tr . endpos [ 2 ] ;
VectorCopy ( bs - > origin , downvec ) ;
downvec [ 2 ] - = 4096 ;
trap_Trace ( & tr , bs - > origin , mins , maxs , downvec , - 1 , MASK_SOLID ) ;
me_down = ( int ) tr . endpos [ 2 ] ;
VectorSubtract ( usethisvec , bs - > origin , a ) ;
vectoangles ( a , a ) ;
AngleVectors ( a , fwd , NULL , NULL ) ;
midorg [ 0 ] = bs - > origin [ 0 ] + fwd [ 0 ] * bs - > frame_Enemy_Len / 2 ;
midorg [ 1 ] = bs - > origin [ 1 ] + fwd [ 1 ] * bs - > frame_Enemy_Len / 2 ;
midorg [ 2 ] = bs - > origin [ 2 ] + fwd [ 2 ] * bs - > frame_Enemy_Len / 2 ;
VectorCopy ( midorg , downvec ) ;
downvec [ 2 ] - = 4096 ;
trap_Trace ( & tr , midorg , mins , maxs , downvec , - 1 , MASK_SOLID ) ;
mid_down = ( int ) tr . endpos [ 2 ] ;
if ( me_down = = en_down & &
en_down = = mid_down )
{
VectorCopy ( usethisvec , bs - > goalPosition ) ;
}
}
#if 0
void SaberCombatHandling ( bot_state_t * bs )
{
vec3_t usethisvec ;
vec3_t downvec ;
vec3_t midorg ;
vec3_t a ;
vec3_t fwd ;
vec3_t mins , maxs ;
trace_t tr ;
int en_down ;
int me_down ;
int mid_down ;
if ( ! bs - > currentEnemy )
{
return ;
}
if ( bs - > currentEnemy - > client )
{
VectorCopy ( bs - > currentEnemy - > client - > ps . origin , usethisvec ) ;
}
else
{
VectorCopy ( bs - > currentEnemy - > s . origin , usethisvec ) ;
}
if ( bs - > meleeStrafeTime < level . time )
{
if ( bs - > meleeStrafeDir )
{
bs - > meleeStrafeDir = 0 ;
}
else
{
bs - > meleeStrafeDir = 1 ;
}
bs - > meleeStrafeTime = level . time + Q_irand ( 500 , 1800 ) ;
}
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = - 24 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 32 ;
VectorCopy ( usethisvec , downvec ) ;
downvec [ 2 ] - = 4096 ;
trap_Trace ( & tr , usethisvec , mins , maxs , downvec , - 1 , MASK_SOLID ) ;
en_down = ( int ) tr . endpos [ 2 ] ;
if ( tr . startsolid | | tr . allsolid )
{
en_down = 1 ;
me_down = 2 ;
}
else
{
VectorCopy ( bs - > origin , downvec ) ;
downvec [ 2 ] - = 4096 ;
trap_Trace ( & tr , bs - > origin , mins , maxs , downvec , - 1 , MASK_SOLID ) ;
me_down = ( int ) tr . endpos [ 2 ] ;
if ( tr . startsolid | | tr . allsolid )
{
en_down = 1 ;
me_down = 2 ;
}
}
VectorSubtract ( usethisvec , bs - > origin , a ) ;
vectoangles ( a , a ) ;
AngleVectors ( a , fwd , NULL , NULL ) ;
midorg [ 0 ] = bs - > origin [ 0 ] + fwd [ 0 ] * bs - > frame_Enemy_Len / 2 ;
midorg [ 1 ] = bs - > origin [ 1 ] + fwd [ 1 ] * bs - > frame_Enemy_Len / 2 ;
midorg [ 2 ] = bs - > origin [ 2 ] + fwd [ 2 ] * bs - > frame_Enemy_Len / 2 ;
VectorCopy ( midorg , downvec ) ;
downvec [ 2 ] - = 4096 ;
trap_Trace ( & tr , midorg , mins , maxs , downvec , - 1 , MASK_SOLID ) ;
mid_down = ( int ) tr . endpos [ 2 ] ;
if ( me_down = = en_down & &
en_down = = mid_down )
{
if ( usethisvec [ 2 ] > ( bs - > origin [ 2 ] + 32 ) & &
bs - > currentEnemy - > client & &
bs - > currentEnemy - > client - > ps . groundEntityNum = = ENTITYNUM_NONE )
{
bs - > jumpTime = level . time + 100 ;
}
if ( bs - > frame_Enemy_Len > 128 )
{ //be ready to attack
bs - > saberDefending = 0 ;
bs - > saberDefendDecideTime = level . time + Q_irand ( 1000 , 2000 ) ;
}
else
{
if ( bs - > saberDefendDecideTime < level . time )
{
if ( bs - > saberDefending )
{
bs - > saberDefending = 0 ;
}
else
{
bs - > saberDefending = 1 ;
}
bs - > saberDefendDecideTime = level . time + Q_irand ( 500 , 2000 ) ;
}
}
if ( bs - > frame_Enemy_Len < 54 )
{
VectorCopy ( bs - > origin , bs - > goalPosition ) ;
bs - > saberBFTime = 0 ;
}
else
{
VectorCopy ( usethisvec , bs - > goalPosition ) ;
}
if ( bs - > frame_Enemy_Len > 90 & & bs - > saberBFTime > level . time & & bs - > saberBTime > level . time & & bs - > beStill < level . time & & bs - > saberSTime < level . time )
{
bs - > beStill = level . time + Q_irand ( 500 , 1000 ) ;
bs - > saberSTime = level . time + Q_irand ( 1200 , 1800 ) ;
}
else if ( bs - > currentEnemy - > client - > ps . weapon = = WP_SABER & & bs - > frame_Enemy_Len < 80 & & ( Q_irand ( 1 , 10 ) < 8 & & bs - > saberBFTime < level . time ) | | bs - > saberBTime > level . time )
{
vec3_t vs ;
vec3_t groundcheck ;
VectorSubtract ( bs - > origin , usethisvec , vs ) ;
VectorNormalize ( vs ) ;
bs - > goalPosition [ 0 ] = bs - > origin [ 0 ] + vs [ 0 ] * 64 ;
bs - > goalPosition [ 1 ] = bs - > origin [ 1 ] + vs [ 1 ] * 64 ;
bs - > goalPosition [ 2 ] = bs - > origin [ 2 ] + vs [ 2 ] * 64 ;
if ( bs - > saberBTime < level . time )
{
bs - > saberBFTime = level . time + Q_irand ( 900 , 1300 ) ;
bs - > saberBTime = level . time + Q_irand ( 300 , 700 ) ;
}
VectorCopy ( bs - > goalPosition , groundcheck ) ;
groundcheck [ 2 ] - = 64 ;
trap_Trace ( & tr , bs - > goalPosition , NULL , NULL , groundcheck , bs - > client , MASK_SOLID ) ;
if ( tr . fraction = = 1.0 )
{ //don't back off of a ledge
VectorCopy ( usethisvec , bs - > goalPosition ) ;
}
}
else if ( bs - > currentEnemy - > client - > ps . weapon = = WP_SABER & & bs - > frame_Enemy_Len > = 75 )
{
bs - > saberBFTime = level . time + Q_irand ( 700 , 1300 ) ;
bs - > saberBTime = 0 ;
}
/*AngleVectors(bs->viewangles, NULL, fwd, NULL);
if ( bs - > meleeStrafeDir )
{
bs - > goalPosition [ 0 ] + = fwd [ 0 ] * 16 ;
bs - > goalPosition [ 1 ] + = fwd [ 1 ] * 16 ;
bs - > goalPosition [ 2 ] + = fwd [ 2 ] * 16 ;
}
else
{
bs - > goalPosition [ 0 ] - = fwd [ 0 ] * 16 ;
bs - > goalPosition [ 1 ] - = fwd [ 1 ] * 16 ;
bs - > goalPosition [ 2 ] - = fwd [ 2 ] * 16 ;
} */
}
else if ( bs - > frame_Enemy_Len < = 56 )
{
bs - > doAttack = 1 ;
bs - > saberDefending = 0 ;
}
}
# endif
float BotWeaponCanLead ( bot_state_t * bs )
{
switch ( bs - > cur_ps . weapon )
{
default :
return 0 ;
case WP_KNIFE :
return 0.5 ;
// no leading needed for any bullet weapons
case WP_M1911A1_PISTOL :
2002-09-24 00:00:00 +00:00
case WP_SILVER_TALON :
2002-05-24 00:00:00 +00:00
case WP_USSOCOM_PISTOL :
case WP_M4_ASSAULT_RIFLE :
case WP_AK74_ASSAULT_RIFLE :
case WP_M60_MACHINEGUN :
case WP_MICRO_UZI_SUBMACHINEGUN :
case WP_M3A1_SUBMACHINEGUN :
2002-07-15 00:00:00 +00:00
case WP_MP5 :
2002-09-24 00:00:00 +00:00
case WP_SIG551 :
2002-05-24 00:00:00 +00:00
case WP_MSG90A1 :
case WP_USAS_12_SHOTGUN :
case WP_M590_SHOTGUN :
return 0 ;
// projectile weapons lead
case WP_MM1_GRENADE_LAUNCHER :
return 0.5 ;
case WP_RPG7_LAUNCHER :
return 0.5 ;
case WP_M84_GRENADE :
case WP_SMOHG92_GRENADE :
case WP_ANM14_GRENADE :
case WP_M15_GRENADE :
return 0.7 ;
}
}
// Calculate proper angle for ballistic weapon
static float CalcWeaponAngle ( float vel , float gravity , float targetRange )
{
float angle = 0 ;
float val = ( gravity * targetRange ) / ( vel * vel ) ;
if ( val > = - 1 & & val < = 1 )
angle = RAD2DEG ( asin ( val ) / 2 ) ;
else
angle = 90 ;
return angle ;
}
void BotAimLeading ( bot_state_t * bs , vec3_t headlevel , float leadAmount )
{
int x ;
vec3_t predictedSpot ;
vec3_t movementVector ;
vec3_t a , ang ;
float vtotal ;
if ( ! bs - > currentEnemy | |
! bs - > currentEnemy - > client )
{
return ;
}
if ( ! bs - > frame_Enemy_Len )
{
return ;
}
vtotal = 0 ;
if ( bs - > currentEnemy - > client - > ps . velocity [ 0 ] < 0 )
{
vtotal + = - bs - > currentEnemy - > client - > ps . velocity [ 0 ] ;
}
else
{
vtotal + = bs - > currentEnemy - > client - > ps . velocity [ 0 ] ;
}
if ( bs - > currentEnemy - > client - > ps . velocity [ 1 ] < 0 )
{
vtotal + = - bs - > currentEnemy - > client - > ps . velocity [ 1 ] ;
}
else
{
vtotal + = bs - > currentEnemy - > client - > ps . velocity [ 1 ] ;
}
if ( bs - > currentEnemy - > client - > ps . velocity [ 2 ] < 0 )
{
vtotal + = - bs - > currentEnemy - > client - > ps . velocity [ 2 ] ;
}
else
{
vtotal + = bs - > currentEnemy - > client - > ps . velocity [ 2 ] ;
}
//G_Printf("Leadin target with a velocity total of %f\n", vtotal);
VectorCopy ( bs - > currentEnemy - > client - > ps . velocity , movementVector ) ;
VectorNormalize ( movementVector ) ;
x = bs - > frame_Enemy_Len * leadAmount ; //hardly calculated with an exact science, but it works
if ( vtotal > 400 )
{
vtotal = 400 ;
}
if ( vtotal )
{
x = ( bs - > frame_Enemy_Len * 0.9 ) * leadAmount * ( vtotal * 0.0012 ) ; //hardly calculated with an exact science, but it works
}
else
{
x = ( bs - > frame_Enemy_Len * 0.9 ) * leadAmount ; //hardly calculated with an exact science, but it works
}
predictedSpot [ 0 ] = headlevel [ 0 ] + ( movementVector [ 0 ] * x ) ;
predictedSpot [ 1 ] = headlevel [ 1 ] + ( movementVector [ 1 ] * x ) ;
predictedSpot [ 2 ] = headlevel [ 2 ] + ( movementVector [ 2 ] * x ) ;
VectorSubtract ( predictedSpot , bs - > eye , a ) ;
vectoangles ( a , ang ) ;
VectorCopy ( ang , bs - > goalAngles ) ;
}
void BotAimOffsetGoalAngles ( bot_state_t * bs )
{
int i ;
float accVal ;
i = 0 ;
if ( bs - > skills . perfectaim )
{
return ;
}
if ( bs - > aimOffsetTime > level . time )
{
if ( bs - > aimOffsetAmtYaw )
{
bs - > goalAngles [ YAW ] + = bs - > aimOffsetAmtYaw ;
}
if ( bs - > aimOffsetAmtPitch )
{
bs - > goalAngles [ PITCH ] + = bs - > aimOffsetAmtPitch ;
}
while ( i < = 2 )
{
if ( bs - > goalAngles [ i ] > 360 )
{
bs - > goalAngles [ i ] - = 360 ;
}
if ( bs - > goalAngles [ i ] < 0 )
{
bs - > goalAngles [ i ] + = 360 ;
}
i + + ;
}
return ;
}
accVal = bs - > skills . accuracy / bs - > settings . skill ;
if ( bs - > revengeEnemy & & bs - > revengeHateLevel & &
bs - > currentEnemy = = bs - > revengeEnemy )
{ //bot becomes more skilled as anger level raises
accVal = accVal / bs - > revengeHateLevel ;
}
if ( bs - > currentEnemy & & bs - > frame_Enemy_Vis )
{ //assume our goal is aiming at the enemy, seeing as he's visible and all
if ( ! bs - > currentEnemy - > s . pos . trDelta [ 0 ] & &
! bs - > currentEnemy - > s . pos . trDelta [ 1 ] & &
! bs - > currentEnemy - > s . pos . trDelta [ 2 ] )
{
accVal = 0 ; //he's not even moving, so he shouldn't really be hard to hit.
}
else
{
accVal + = accVal * 0.25 ; //if he's moving he's this much harder to hit
}
if ( g_entities [ bs - > client ] . s . pos . trDelta [ 0 ] | |
g_entities [ bs - > client ] . s . pos . trDelta [ 1 ] | |
g_entities [ bs - > client ] . s . pos . trDelta [ 2 ] )
{
accVal + = accVal * 0.15 ; //make it somewhat harder to aim if we're moving also
}
}
if ( accVal > 90 )
{
accVal = 90 ;
}
if ( accVal < 1 )
{
accVal = 0 ;
}
if ( ! accVal )
{
bs - > aimOffsetAmtYaw = 0 ;
bs - > aimOffsetAmtPitch = 0 ;
return ;
}
if ( rand ( ) % 10 < = 5 )
{
bs - > aimOffsetAmtYaw = rand ( ) % ( int ) accVal ;
}
else
{
bs - > aimOffsetAmtYaw = - ( rand ( ) % ( int ) accVal ) ;
}
if ( rand ( ) % 10 < = 5 )
{
bs - > aimOffsetAmtPitch = rand ( ) % ( int ) accVal ;
}
else
{
bs - > aimOffsetAmtPitch = - ( rand ( ) % ( int ) accVal ) ;
}
bs - > aimOffsetTime = level . time + rand ( ) % 500 + 200 ;
}
int ShouldSecondaryFire ( bot_state_t * bs , vec3_t eorg , vec3_t dir )
{
weaponData_t * weapon = & weaponData [ bs - > cur_ps . weapon ] ;
attackData_t * attack = & weapon - > attack [ ATTACK_ALTERNATE ] ;
if ( ( bs - > cur_ps . ammo [ attack - > ammoIndex ] < 1 ) | | 0 = = attack - > damage )
{ // no ammo for alt fire
return 0 ;
}
# ifdef BOT_LAUNCH_ANGLES
if ( ( attack - > weaponFlags & PROJECTILE_FIRE ) & & bs - > frame_Enemy_Len > MAX_PROJECTILE_DISTANCE ) //don't forget to make sure the bot doesn't shoot it off in his own face!
{
// if alt-fire is projectile
if ( bs - > frame_Enemy_Len < attack - > rV . velocity * attack - > projectileLifetime * 0.001 )
{
// in range
// 2D range
float range2D = max ( 0 , SQRTFAST ( dir [ 0 ] * dir [ 0 ] + dir [ 1 ] * dir [ 1 ] ) - attack - > splashRadius ) ;
if ( attack - > weaponFlags & PROJECTILE_TIMED )
{ // if timed projectile, let bounce for 1 sec.
range2D = max ( 0 , range2D - attack - > rV . velocity ) ;
}
// calculate projectile launch angle
bs - > launchAngle = CalcWeaponAngle ( attack - > rV . velocity , DEFAULT_GRAVITY , range2D ) ;
if ( bs - > launchAngle < 90 )
{
if ( ! OrgVisible ( bs - > eye , eorg , bs - > client ) )
bs - > launchAngle = 90 - bs - > launchAngle ;
return 1 ;
}
}
}
else if ( bs - > frame_Enemy_Len < attack - > rV . range )
{ // alt-fire is bullet
return 1 ;
}
# else
if ( ( attack - > weaponFlags & PROJECTILE_FIRE ) & & bs - > frame_Enemy_Len > MAX_PROJECTILE_DISTANCE ) //don't forget to make sure the bot doesn't shoot it off in his own face!
{
return 1 ;
}
else if ( bs - > frame_Enemy_Len < attack - > rV . range )
{ // alt-fire is bullet
return 1 ;
}
# endif
return 0 ;
}
int CombatBotAI ( bot_state_t * bs , float thinktime )
{
vec3_t eorg , a , dir ;
int secFire ;
float fovcheck ;
if ( ! bs - > currentEnemy )
{
return 0 ;
}
if ( bs - > currentEnemy - > client )
{
VectorCopy ( bs - > currentEnemy - > client - > ps . origin , eorg ) ;
}
else
{
VectorCopy ( bs - > currentEnemy - > s . origin , eorg ) ;
}
VectorSubtract ( eorg , bs - > eye , dir ) ;
vectoangles ( dir , a ) ;
if ( BotGetWeaponRange ( bs ) = = BWEAPONRANGE_SABER )
{
if ( bs - > frame_Enemy_Len < = SABER_ATTACK_RANGE )
{
bs - > doAttack = 1 ;
}
}
else if ( BotGetWeaponRange ( bs ) = = BWEAPONRANGE_MELEE )
{
if ( bs - > frame_Enemy_Len < = MELEE_ATTACK_RANGE )
{
bs - > doAttack = 1 ;
}
}
else
{
if ( bs - > cur_ps . weapon = = WP_MSG90A1 | | bs - > cur_ps . weapon = = WP_RPG7_LAUNCHER )
{ //be careful with the hurty weapons
fovcheck = 10 ;
}
else
{
fovcheck = 60 ;
}
if ( bs - > frame_Enemy_Len < 128 )
{
fovcheck * = 2 ;
}
if ( InFieldOfVision ( bs - > viewangles , fovcheck , a ) )
{
# ifdef BOT_LAUNCH_ANGLES
weaponData_t * weapon = & weaponData [ bs - > cur_ps . weapon ] ;
if ( CAT_GRENADE = = weapon - > category | | WP_MM1_GRENADE_LAUNCHER = = bs - > cur_ps . weapon )
{
attackData_t * attack ;
float range2D ;
// are we using a grenade type weapon?
if ( bs - > frame_Enemy_Len < weapon - > attack [ ATTACK_NORMAL ] . rV . velocity * weapon - > attack [ ATTACK_NORMAL ] . projectileLifetime * 0.001 )
{
attack = & weapon - > attack [ ATTACK_NORMAL ] ;
}
else
{
attack = & weapon - > attack [ ATTACK_ALTERNATE ] ;
}
// only 2D range since Z is for the ballistic path only
range2D = max ( 0 , SQRTFAST ( dir [ 0 ] * dir [ 0 ] + dir [ 1 ] * dir [ 1 ] ) - attack - > splashRadius ) ;
if ( attack - > weaponFlags & PROJECTILE_TIMED )
{
// if timed projectile, let bounce for 1 sec.
range2D = max ( 0 , range2D - attack - > rV . velocity ) ;
}
bs - > launchAngle = CalcWeaponAngle ( attack - > rV . velocity , DEFAULT_GRAVITY , range2D ) ;
if ( bs - > launchAngle < 90 )
{
if ( ! OrgVisible ( bs - > eye , eorg , bs - > client ) )
{
bs - > launchAngle = 90 - bs - > launchAngle ;
}
bs - > doAttack = 1 ;
}
}
else
# endif
{
secFire = ShouldSecondaryFire ( bs , eorg , dir ) ;
if ( bs - > cur_ps . weaponstate ! = WEAPON_CHARGING_ALT )
{
bs - > altChargeTime = Q_irand ( 500 , 1000 ) ;
}
if ( secFire = = 1 )
{
bs - > doAltAttack = 1 ;
}
else if ( ! secFire )
{
bs - > doAttack = 1 ;
}
if ( secFire = = 2 )
{ //released a charge
return 1 ;
}
}
}
}
return 0 ;
}
int BotFallbackNavigation ( bot_state_t * bs )
{
vec3_t b_angle , fwd , trto , mins , maxs ;
trace_t tr ;
if ( bs - > currentEnemy & & bs - > frame_Enemy_Vis )
{
return 2 ; //we're busy
}
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = 0 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 32 ;
bs - > goalAngles [ PITCH ] = 0 ;
bs - > goalAngles [ ROLL ] = 0 ;
VectorCopy ( bs - > goalAngles , b_angle ) ;
AngleVectors ( b_angle , fwd , NULL , NULL ) ;
trto [ 0 ] = bs - > origin [ 0 ] + fwd [ 0 ] * 16 ;
trto [ 1 ] = bs - > origin [ 1 ] + fwd [ 1 ] * 16 ;
trto [ 2 ] = bs - > origin [ 2 ] + fwd [ 2 ] * 16 ;
trap_Trace ( & tr , bs - > origin , mins , maxs , trto , - 1 , MASK_SOLID ) ;
if ( tr . fraction = = 1 )
{
VectorCopy ( trto , bs - > goalPosition ) ;
return 1 ; //success!
}
else
{
bs - > goalAngles [ YAW ] = rand ( ) % 360 ;
}
return 0 ;
}
int BotTryAnotherWeapon ( bot_state_t * bs )
{ //out of ammo, resort to the first weapon we come across that has ammo
int i ;
i = 0 ;
while ( i < WP_NUM_WEAPONS )
{
if ( ( bs - > cur_ps . ammo [ weaponData [ i ] . attack [ ATTACK_NORMAL ] . ammoIndex ] > 1 | | bs - > cur_ps . clip [ ATTACK_NORMAL ] [ i ] > 1 ) & &
( bs - > cur_ps . stats [ STAT_WEAPONS ] & ( 1 < < i ) ) )
{
bs - > virtualWeapon = i ;
trap_EA_SelectWeapon ( bs - > client , i ) ;
//bs->cur_ps.weapon = i;
//level.clients[bs->client].ps.weapon = i;
return 1 ;
}
i + + ;
}
if ( bs - > cur_ps . weapon ! = 1 & & bs - > virtualWeapon ! = 1 )
{ //should always have this.. shouldn't we?
bs - > virtualWeapon = 1 ;
trap_EA_SelectWeapon ( bs - > client , 1 ) ;
//bs->cur_ps.weapon = 1;
//level.clients[bs->client].ps.weapon = 1;
return 1 ;
}
return 0 ;
}
int BotSelectIdealWeapon ( bot_state_t * bs )
{
int i ;
int bestweight = - 1 ;
int bestweapon = 0 ;
i = 0 ;
while ( i < WP_NUM_WEAPONS )
{
if ( ( bs - > cur_ps . ammo [ weaponData [ i ] . attack [ ATTACK_NORMAL ] . ammoIndex ] > 1 | | bs - > cur_ps . clip [ ATTACK_NORMAL ] [ i ] > 1 ) & &
( bs - > botWeaponWeights [ i ] > bestweight ) & &
( bs - > cur_ps . stats [ STAT_WEAPONS ] & ( 1 < < i ) ) )
{
bestweight = bs - > botWeaponWeights [ i ] ;
bestweapon = i ;
}
i + + ;
}
if ( bestweight ! = - 1 & & bs - > cur_ps . weapon ! = bestweapon & & bs - > virtualWeapon ! = bestweapon )
{
bs - > virtualWeapon = bestweapon ;
trap_EA_SelectWeapon ( bs - > client , bestweapon ) ;
//bs->cur_ps.weapon = bestweapon;
//level.clients[bs->client].ps.weapon = bestweapon;
return 1 ;
}
return 0 ;
}
int BotSelectChoiceWeapon ( bot_state_t * bs , int weapon , int doselection )
{ //if !doselection then bot will only check if he has the specified weapon and return 1 (yes) or 0 (no)
int i ;
int hasit = 0 ;
i = 0 ;
while ( i < WP_NUM_WEAPONS )
{
if ( ( bs - > cur_ps . ammo [ weaponData [ i ] . attack [ ATTACK_NORMAL ] . ammoIndex ] > 1 | | bs - > cur_ps . clip [ ATTACK_NORMAL ] [ i ] > 1 ) & &
i = = weapon & &
( bs - > cur_ps . stats [ STAT_WEAPONS ] & ( 1 < < i ) ) )
{
hasit = 1 ;
break ;
}
i + + ;
}
if ( hasit & & bs - > cur_ps . weapon ! = weapon & & doselection & & bs - > virtualWeapon ! = weapon )
{
bs - > virtualWeapon = weapon ;
trap_EA_SelectWeapon ( bs - > client , weapon ) ;
//bs->cur_ps.weapon = weapon;
//level.clients[bs->client].ps.weapon = weapon;
return 2 ;
}
if ( hasit )
{
return 1 ;
}
return 0 ;
}
int BotSelectMelee ( bot_state_t * bs )
{
if ( bs - > cur_ps . weapon ! = 1 & & bs - > virtualWeapon ! = 1 )
{
bs - > virtualWeapon = 1 ;
trap_EA_SelectWeapon ( bs - > client , 1 ) ;
//bs->cur_ps.weapon = 1;
//level.clients[bs->client].ps.weapon = 1;
return 1 ;
}
return 0 ;
}
int GetLoveLevel ( bot_state_t * bs , bot_state_t * love )
{
int i = 0 ;
const char * lname = NULL ;
if ( ! bs | | ! love | | ! g_entities [ love - > client ] . client )
{
return 0 ;
}
if ( ! bs - > lovednum )
{
return 0 ;
}
trap_Cvar_Update ( & bot_attachments ) ;
if ( ! bot_attachments . integer )
{
return 0 ;
}
lname = g_entities [ love - > client ] . client - > pers . netname ;
if ( ! lname )
{
return 0 ;
}
while ( i < bs - > lovednum )
{
if ( strcmp ( bs - > loved [ i ] . name , lname ) = = 0 )
{
return bs - > loved [ i ] . level ;
}
i + + ;
}
return 0 ;
}
void BotLovedOneDied ( bot_state_t * bs , bot_state_t * loved , int lovelevel )
{
if ( ! loved - > lastHurt | | ! loved - > lastHurt - > client | |
loved - > lastHurt - > s . number = = loved - > client )
{
return ;
}
if ( ! IsTeamplay ( ) )
{
if ( lovelevel < 2 )
{
return ;
}
}
else if ( OnSameTeam ( & g_entities [ bs - > client ] , loved - > lastHurt ) )
{ //don't hate teammates no matter what
return ;
}
if ( loved - > client = = loved - > lastHurt - > s . number )
{
return ;
}
if ( bs - > client = = loved - > lastHurt - > s . number )
{ //oops!
return ;
}
trap_Cvar_Update ( & bot_attachments ) ;
if ( ! bot_attachments . integer )
{
return ;
}
if ( ! PassLovedOneCheck ( bs , loved - > lastHurt ) )
{ //a loved one killed a loved one.. you cannot hate them
bs - > chatObject = loved - > lastHurt ;
bs - > chatAltObject = & g_entities [ loved - > client ] ;
BotDoChat ( bs , " LovedOneKilledLovedOne " , 0 ) ;
return ;
}
if ( bs - > revengeEnemy = = loved - > lastHurt )
{
if ( bs - > revengeHateLevel < bs - > loved_death_thresh )
{
bs - > revengeHateLevel + + ;
if ( bs - > revengeHateLevel = = bs - > loved_death_thresh )
{
//broke into the highest anger level
//CHAT: Hatred section
bs - > chatObject = loved - > lastHurt ;
bs - > chatAltObject = NULL ;
BotDoChat ( bs , " Hatred " , 1 ) ;
}
}
}
else if ( bs - > revengeHateLevel < bs - > loved_death_thresh - 1 )
{ //only switch hatred if we don't hate the existing revenge-enemy too much
//CHAT: BelovedKilled section
bs - > chatObject = & g_entities [ loved - > client ] ;
bs - > chatAltObject = loved - > lastHurt ;
BotDoChat ( bs , " BelovedKilled " , 0 ) ;
bs - > revengeHateLevel = 0 ;
bs - > revengeEnemy = loved - > lastHurt ;
}
}
void BotDeathNotify ( bot_state_t * bs )
{ //in case someone has an emotional attachment to us, we'll notify them
int i = 0 ;
int ltest = 0 ;
while ( i < MAX_CLIENTS )
{
if ( botstates [ i ] & & botstates [ i ] - > lovednum )
{
ltest = 0 ;
while ( ltest < botstates [ i ] - > lovednum )
{
if ( strcmp ( level . clients [ bs - > client ] . pers . netname , botstates [ i ] - > loved [ ltest ] . name ) = = 0 )
{
BotLovedOneDied ( botstates [ i ] , bs , botstates [ i ] - > loved [ ltest ] . level ) ;
break ;
}
ltest + + ;
}
}
i + + ;
}
}
void StrafeTracing ( bot_state_t * bs )
{
vec3_t mins , maxs ;
vec3_t right , rorg , drorg ;
trace_t tr ;
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
//mins[2] = -24;
mins [ 2 ] = - 22 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 32 ;
AngleVectors ( bs - > viewangles , NULL , right , NULL ) ;
if ( bs - > meleeStrafeDir )
{
rorg [ 0 ] = bs - > origin [ 0 ] - right [ 0 ] * 32 ;
rorg [ 1 ] = bs - > origin [ 1 ] - right [ 1 ] * 32 ;
rorg [ 2 ] = bs - > origin [ 2 ] - right [ 2 ] * 32 ;
}
else
{
rorg [ 0 ] = bs - > origin [ 0 ] + right [ 0 ] * 32 ;
rorg [ 1 ] = bs - > origin [ 1 ] + right [ 1 ] * 32 ;
rorg [ 2 ] = bs - > origin [ 2 ] + right [ 2 ] * 32 ;
}
trap_Trace ( & tr , bs - > origin , mins , maxs , rorg , bs - > client , MASK_SOLID ) ;
if ( tr . fraction ! = 1 )
{
bs - > meleeStrafeDisable = level . time + Q_irand ( 500 , 1500 ) ;
}
VectorCopy ( rorg , drorg ) ;
drorg [ 2 ] - = 32 ;
trap_Trace ( & tr , rorg , NULL , NULL , drorg , bs - > client , MASK_SOLID ) ;
if ( tr . fraction = = 1 )
{ //this may be a dangerous ledge, so don't strafe over it just in case
bs - > meleeStrafeDisable = level . time + Q_irand ( 500 , 1500 ) ;
}
}
int PrimFiring ( bot_state_t * bs )
{
if ( bs - > cur_ps . weaponstate ! = WEAPON_CHARGING & &
bs - > doAttack )
{
return 1 ;
}
if ( bs - > cur_ps . weaponstate = = WEAPON_CHARGING & &
! bs - > doAttack )
{
return 1 ;
}
return 0 ;
}
int KeepPrimFromFiring ( bot_state_t * bs )
{
if ( bs - > cur_ps . weaponstate ! = WEAPON_CHARGING & &
bs - > doAttack )
{
bs - > doAttack = 0 ;
}
if ( bs - > cur_ps . weaponstate = = WEAPON_CHARGING & &
! bs - > doAttack )
{
bs - > doAttack = 1 ;
}
return 0 ;
}
int AltFiring ( bot_state_t * bs )
{
if ( bs - > cur_ps . weaponstate ! = WEAPON_CHARGING_ALT & &
bs - > doAltAttack )
{
return 1 ;
}
if ( bs - > cur_ps . weaponstate = = WEAPON_CHARGING_ALT & &
! bs - > doAltAttack )
{
return 1 ;
}
return 0 ;
}
int KeepAltFromFiring ( bot_state_t * bs )
{
if ( bs - > cur_ps . weaponstate ! = WEAPON_CHARGING_ALT & &
bs - > doAltAttack )
{
bs - > doAltAttack = 0 ;
}
if ( bs - > cur_ps . weaponstate = = WEAPON_CHARGING_ALT & &
! bs - > doAltAttack )
{
bs - > doAltAttack = 1 ;
}
return 0 ;
}
gentity_t * CheckForFriendInLOF ( bot_state_t * bs )
{
vec3_t fwd ;
vec3_t trfrom , trto ;
vec3_t mins , maxs ;
gentity_t * trent ;
trace_t tr ;
mins [ 0 ] = - 3 ;
mins [ 1 ] = - 3 ;
mins [ 2 ] = - 3 ;
maxs [ 0 ] = 3 ;
maxs [ 1 ] = 3 ;
maxs [ 2 ] = 3 ;
AngleVectors ( bs - > viewangles , fwd , NULL , NULL ) ;
VectorCopy ( bs - > eye , trfrom ) ;
trto [ 0 ] = trfrom [ 0 ] + fwd [ 0 ] * 2048 ;
trto [ 1 ] = trfrom [ 1 ] + fwd [ 1 ] * 2048 ;
trto [ 2 ] = trfrom [ 2 ] + fwd [ 2 ] * 2048 ;
trap_Trace ( & tr , trfrom , mins , maxs , trto , bs - > client , MASK_PLAYERSOLID ) ;
if ( tr . fraction ! = 1 & & tr . entityNum < = MAX_CLIENTS )
{
trent = & g_entities [ tr . entityNum ] ;
if ( trent & & trent - > client )
{
if ( IsTeamplay ( ) & & OnSameTeam ( & g_entities [ bs - > client ] , trent ) )
{
return trent ;
}
if ( botstates [ trent - > s . number ] & & GetLoveLevel ( bs , botstates [ trent - > s . number ] ) > 1 )
{
return trent ;
}
}
}
return NULL ;
}
void BotScanForLeader ( bot_state_t * bs )
{ //bots will only automatically obtain a leader if it's another bot using this method.
int i = 0 ;
gentity_t * ent ;
if ( bs - > isSquadLeader )
{
return ;
}
while ( i < MAX_CLIENTS )
{
ent = & g_entities [ i ] ;
if ( ent & & ent - > client & & botstates [ i ] & & botstates [ i ] - > isSquadLeader & & bs - > client ! = i )
{
if ( OnSameTeam ( & g_entities [ bs - > client ] , ent ) )
{
bs - > squadLeader = ent ;
break ;
}
if ( GetLoveLevel ( bs , botstates [ i ] ) > 1 & & ! IsTeamplay ( ) )
{ //ignore love status regarding squad leaders if we're in teamplay
bs - > squadLeader = ent ;
break ;
}
}
i + + ;
}
}
void BotReplyGreetings ( bot_state_t * bs )
{
int i = 0 ;
int numhello = 0 ;
while ( i < MAX_CLIENTS )
{
if ( botstates [ i ] & &
botstates [ i ] - > canChat & &
i ! = bs - > client )
{
botstates [ i ] - > chatObject = & g_entities [ bs - > client ] ;
botstates [ i ] - > chatAltObject = NULL ;
if ( BotDoChat ( botstates [ i ] , " ResponseGreetings " , 0 ) )
{
numhello + + ;
}
}
if ( numhello > 3 )
{ //don't let more than 4 bots say hello at once
return ;
}
i + + ;
}
}
void CTFFlagMovement ( bot_state_t * bs )
{
int diddrop = 0 ;
gentity_t * desiredDrop = NULL ;
vec3_t a , mins , maxs ;
trace_t tr ;
mins [ 0 ] = - 15 ;
mins [ 1 ] = - 15 ;
mins [ 2 ] = - 7 ;
maxs [ 0 ] = 15 ;
maxs [ 1 ] = 15 ;
maxs [ 2 ] = 7 ;
if ( bs - > wantFlag & & ( bs - > wantFlag - > flags & FL_DROPPED_ITEM ) )
{
if ( bs - > staticFlagSpot [ 0 ] = = bs - > wantFlag - > s . pos . trBase [ 0 ] & &
bs - > staticFlagSpot [ 1 ] = = bs - > wantFlag - > s . pos . trBase [ 1 ] & &
bs - > staticFlagSpot [ 2 ] = = bs - > wantFlag - > s . pos . trBase [ 2 ] )
{
VectorSubtract ( bs - > origin , bs - > wantFlag - > s . pos . trBase , a ) ;
if ( VectorLength ( a ) < = BOT_FLAG_GET_DISTANCE )
{
VectorCopy ( bs - > wantFlag - > s . pos . trBase , bs - > goalPosition ) ;
return ;
}
else
{
bs - > wantFlag = NULL ;
}
}
else
{
bs - > wantFlag = NULL ;
}
}
else if ( bs - > wantFlag )
{
bs - > wantFlag = NULL ;
}
if ( flagRed & & flagBlue )
{
if ( bs - > wpDestination = = flagRed | |
bs - > wpDestination = = flagBlue )
{
if ( bs - > wpDestination = = flagRed & & droppedRedFlag & & ( droppedRedFlag - > flags & FL_DROPPED_ITEM ) & & droppedRedFlag - > classname & & strcmp ( droppedRedFlag - > classname , " freed " ) ! = 0 )
{
desiredDrop = droppedRedFlag ;
diddrop = 1 ;
}
if ( bs - > wpDestination = = flagBlue & & droppedBlueFlag & & ( droppedBlueFlag - > flags & FL_DROPPED_ITEM ) & & droppedBlueFlag - > classname & & strcmp ( droppedBlueFlag - > classname , " freed " ) ! = 0 )
{
desiredDrop = droppedBlueFlag ;
diddrop = 1 ;
}
if ( diddrop & & desiredDrop )
{
VectorSubtract ( bs - > origin , desiredDrop - > s . pos . trBase , a ) ;
if ( VectorLength ( a ) < = BOT_FLAG_GET_DISTANCE )
{
trap_Trace ( & tr , bs - > origin , mins , maxs , desiredDrop - > s . pos . trBase , bs - > client , MASK_SOLID ) ;
if ( tr . fraction = = 1 | | tr . entityNum = = desiredDrop - > s . number )
{
VectorCopy ( desiredDrop - > s . pos . trBase , bs - > goalPosition ) ;
VectorCopy ( desiredDrop - > s . pos . trBase , bs - > staticFlagSpot ) ;
return ;
}
}
}
}
}
}
int BotUseInventoryItem ( bot_state_t * bs )
{
# ifdef BOT_USE_HOLDABLE
if ( bs - > cur_ps . stats [ STAT_HOLDABLE_ITEMS ] & ( 1 < < HI_MEDPAC ) )
{
if ( g_entities [ bs - > client ] . health < = 50 )
{
bs - > cur_ps . stats [ STAT_HOLDABLE_ITEM ] = BG_GetItemIndexByTag ( HI_MEDPAC , IT_HOLDABLE ) ;
goto wantuseitem ;
}
}
if ( bs - > cur_ps . stats [ STAT_HOLDABLE_ITEMS ] & ( 1 < < HI_SEEKER ) )
{
if ( bs - > currentEnemy & & bs - > frame_Enemy_Vis )
{
bs - > cur_ps . stats [ STAT_HOLDABLE_ITEM ] = BG_GetItemIndexByTag ( HI_SEEKER , IT_HOLDABLE ) ;
goto wantuseitem ;
}
}
if ( bs - > cur_ps . stats [ STAT_HOLDABLE_ITEMS ] & ( 1 < < HI_SENTRY_GUN ) )
{
if ( bs - > currentEnemy & & bs - > frame_Enemy_Vis )
{
bs - > cur_ps . stats [ STAT_HOLDABLE_ITEM ] = BG_GetItemIndexByTag ( HI_SENTRY_GUN , IT_HOLDABLE ) ;
goto wantuseitem ;
}
}
if ( bs - > cur_ps . stats [ STAT_HOLDABLE_ITEMS ] & ( 1 < < HI_SHIELD ) )
{
if ( bs - > currentEnemy & & bs - > frame_Enemy_Vis & & bs - > runningToEscapeThreat )
{ //this will (hopefully) result in the bot placing the shield down while facing
//the enemy and running away
bs - > cur_ps . stats [ STAT_HOLDABLE_ITEM ] = BG_GetItemIndexByTag ( HI_SHIELD , IT_HOLDABLE ) ;
goto wantuseitem ;
}
}
return 0 ;
wantuseitem :
level . clients [ bs - > client ] . ps . stats [ STAT_HOLDABLE_ITEM ] = bs - > cur_ps . stats [ STAT_HOLDABLE_ITEM ] ;
return 1 ;
# else
return 0 ;
# endif
}
int BotSurfaceNear ( bot_state_t * bs )
{
trace_t tr ;
vec3_t fwd ;
AngleVectors ( bs - > viewangles , fwd , NULL , NULL ) ;
fwd [ 0 ] = bs - > origin [ 0 ] + ( fwd [ 0 ] * 64 ) ;
fwd [ 1 ] = bs - > origin [ 1 ] + ( fwd [ 1 ] * 64 ) ;
fwd [ 2 ] = bs - > origin [ 2 ] + ( fwd [ 2 ] * 64 ) ;
trap_Trace ( & tr , bs - > origin , NULL , NULL , fwd , bs - > client , MASK_SOLID ) ;
if ( tr . fraction ! = 1 )
{
return 1 ;
}
return 0 ;
}
wpobject_t * GetLastWP ( bot_state_t * bs , int wpIndex )
{
int goalWPIndex = 0 ;
//reverse because we want the last wp to the current, not the next
if ( bs - > wpDirection )
{
goalWPIndex = bs - > wpCurrent - > index + 1 ;
}
else
{
goalWPIndex = bs - > wpCurrent - > index - 1 ;
}
if ( goalWPIndex < 0 )
{
return NULL ;
}
if ( goalWPIndex > = gWPNum )
{
return NULL ;
}
return gWPArray [ goalWPIndex ] ;
}
void StandardBotAI ( bot_state_t * bs , float thinktime )
{
int wp , enemy ;
int desiredIndex ;
int goalWPIndex ;
int doingFallback ;
vec3_t a , ang , headlevel , eorg , dif ;
float reaction ;
float bLeadAmount ;
int meleestrafe = 0 ;
int cBAI = 0 ;
gentity_t * friendInLOF = 0 ;
int visResult = 0 ;
int selResult = 0 ;
trap_Cvar_Update ( & bot_pause ) ;
if ( gDeactivated | | bot_pause . integer )
{
bs - > wpCurrent = NULL ;
bs - > currentEnemy = NULL ;
bs - > wpDestination = NULL ;
bs - > wpDirection = 0 ;
return ;
}
if ( g_entities [ bs - > client ] . inuse & &
g_entities [ bs - > client ] . client & &
G_IsClientSpectating ( g_entities [ bs - > client ] . client ) )
{
bs - > wpCurrent = NULL ;
bs - > currentEnemy = NULL ;
bs - > wpDestination = NULL ;
bs - > wpDirection = 0 ;
return ;
}
if ( ! bs - > lastDeadTime )
{ //just spawned in?
bs - > lastDeadTime = level . time ;
}
if ( g_entities [ bs - > client ] . health < 1 )
{
bs - > lastDeadTime = level . time ;
if ( ! bs - > deathActivitiesDone & & bs - > lastHurt & & bs - > lastHurt - > client & & bs - > lastHurt - > s . number ! = bs - > client )
{
BotDeathNotify ( bs ) ;
if ( PassLovedOneCheck ( bs , bs - > lastHurt ) )
{
//CHAT: Died
bs - > chatObject = bs - > lastHurt ;
bs - > chatAltObject = NULL ;
BotDoChat ( bs , " Died " , 0 ) ;
}
else if ( ! PassLovedOneCheck ( bs , bs - > lastHurt ) & &
botstates [ bs - > lastHurt - > s . number ] & &
PassLovedOneCheck ( botstates [ bs - > lastHurt - > s . number ] , & g_entities [ bs - > client ] ) )
{ //killed by a bot that I love, but that does not love me
bs - > chatObject = bs - > lastHurt ;
bs - > chatAltObject = NULL ;
BotDoChat ( bs , " KilledOnPurposeByLove " , 0 ) ;
}
bs - > deathActivitiesDone = 1 ;
}
bs - > wpCurrent = NULL ;
bs - > currentEnemy = NULL ;
bs - > wpDestination = NULL ;
bs - > wpCamping = NULL ;
bs - > wpCampingTo = NULL ;
bs - > wpStoreDest = NULL ;
bs - > wpDestIgnoreTime = 0 ;
bs - > wpDestSwitchTime = 0 ;
bs - > wpSeenTime = 0 ;
bs - > wpDirection = 0 ;
if ( rand ( ) % 10 < 5 & &
( ! bs - > doChat | | bs - > chatTime < level . time ) )
{
trap_EA_Attack ( bs - > client ) ;
}
return ;
}
bs - > doAttack = 0 ;
bs - > doAltAttack = 0 ;
//reset the attack states
if ( bs - > isSquadLeader )
{
CommanderBotAI ( bs ) ;
}
else
{
BotDoTeamplayAI ( bs ) ;
}
if ( ! bs - > currentEnemy )
{
bs - > frame_Enemy_Vis = 0 ;
}
if ( bs - > revengeEnemy & & bs - > revengeEnemy - > client & &
bs - > revengeEnemy - > client - > pers . connected ! = CA_ACTIVE & & bs - > revengeEnemy - > client - > pers . connected ! = CA_AUTHORIZING )
{
bs - > revengeEnemy = NULL ;
bs - > revengeHateLevel = 0 ;
}
if ( bs - > currentEnemy & & bs - > currentEnemy - > client & &
bs - > currentEnemy - > client - > pers . connected ! = CA_ACTIVE & & bs - > currentEnemy - > client - > pers . connected ! = CA_AUTHORIZING )
{
bs - > currentEnemy = NULL ;
}
doingFallback = 0 ;
bs - > deathActivitiesDone = 0 ;
if ( BotUseInventoryItem ( bs ) )
{
if ( rand ( ) % 10 < 5 )
{
trap_EA_Use ( bs - > client ) ;
}
}
if ( ( bs - > cur_ps . ammo [ weaponData [ bs - > cur_ps . weapon ] . attack [ ATTACK_NORMAL ] . ammoIndex ] < 1 & & bs - > cur_ps . clip [ ATTACK_NORMAL ] [ bs - > cur_ps . weapon ] < 1 ) )
{
if ( BotTryAnotherWeapon ( bs ) )
{
return ;
}
}
else
{
if ( bs - > currentEnemy & & bs - > lastVisibleEnemyIndex = = bs - > currentEnemy - > s . number & &
bs - > frame_Enemy_Vis & & bs - > forceWeaponSelect /*&& bs->plantContinue < level.time*/ )
{
bs - > forceWeaponSelect = 0 ;
}
if ( bs - > forceWeaponSelect )
{
selResult = BotSelectChoiceWeapon ( bs , bs - > forceWeaponSelect , 1 ) ;
}
if ( selResult )
{
if ( selResult = = 2 )
{ //newly selected
return ;
}
}
else if ( BotSelectIdealWeapon ( bs ) )
{
return ;
}
}
/*if (BotSelectMelee(bs))
{
return ;
} */
reaction = bs - > skills . reflex / bs - > settings . skill ;
if ( reaction < 0 )
{
reaction = 0 ;
}
if ( reaction > 2000 )
{
reaction = 2000 ;
}
if ( ! bs - > currentEnemy )
{
bs - > timeToReact = level . time + reaction ;
}
if ( bs - > wpCamping )
{
if ( bs - > isCamping < level . time )
{
bs - > wpCamping = NULL ;
bs - > isCamping = 0 ;
}
if ( bs - > currentEnemy & & bs - > frame_Enemy_Vis )
{
bs - > wpCamping = NULL ;
bs - > isCamping = 0 ;
}
}
if ( bs - > wpCurrent & &
( bs - > wpSeenTime < level . time | | bs - > wpTravelTime < level . time ) )
{
bs - > wpCurrent = NULL ;
}
if ( bs - > currentEnemy )
{
if ( bs - > enemySeenTime < level . time | |
! PassStandardEnemyChecks ( bs , bs - > currentEnemy ) )
{
if ( bs - > revengeEnemy = = bs - > currentEnemy & &
bs - > currentEnemy - > health < 1 & &
bs - > lastAttacked & & bs - > lastAttacked = = bs - > currentEnemy )
{
//CHAT: Destroyed hated one [KilledHatedOne section]
bs - > chatObject = bs - > revengeEnemy ;
bs - > chatAltObject = NULL ;
BotDoChat ( bs , " KilledHatedOne " , 1 ) ;
bs - > revengeEnemy = NULL ;
bs - > revengeHateLevel = 0 ;
}
else if ( bs - > currentEnemy - > health < 1 & & PassLovedOneCheck ( bs , bs - > currentEnemy ) & &
bs - > lastAttacked & & bs - > lastAttacked = = bs - > currentEnemy )
{
//CHAT: Killed
bs - > chatObject = bs - > currentEnemy ;
bs - > chatAltObject = NULL ;
BotDoChat ( bs , " Killed " , 0 ) ;
}
bs - > currentEnemy = NULL ;
}
}
if ( ! bs - > wpCurrent )
{
wp = GetNearestVisibleWP ( bs - > origin , bs - > client ) ;
if ( wp ! = - 1 )
{
bs - > wpCurrent = gWPArray [ wp ] ;
bs - > wpSeenTime = level . time + 1500 ;
bs - > wpTravelTime = level . time + 10000 ; //never take more than 10 seconds to travel to a waypoint
}
}
if ( bs - > enemySeenTime < level . time | | ! bs - > frame_Enemy_Vis | | ! bs - > currentEnemy | |
( bs - > currentEnemy /*&& bs->cur_ps.weapon == WP_SABER && bs->frame_Enemy_Len > 300*/ ) )
{
enemy = ScanForEnemies ( bs ) ;
if ( enemy ! = - 1 )
{
bs - > currentEnemy = & g_entities [ enemy ] ;
bs - > enemySeenTime = level . time + ENEMY_FORGET_MS ;
}
}
if ( ! bs - > squadLeader & & ! bs - > isSquadLeader )
{
BotScanForLeader ( bs ) ;
}
if ( ! bs - > squadLeader & & bs - > squadCannotLead < level . time )
{ //if still no leader after scanning, then become a squad leader
bs - > isSquadLeader = 1 ;
}
if ( bs - > isSquadLeader & & bs - > squadLeader )
{ //we don't follow anyone if we are a leader
bs - > squadLeader = NULL ;
}
//ESTABLISH VISIBILITIES AND DISTANCES FOR THE WHOLE FRAME HERE
if ( bs - > wpCurrent )
{
VectorSubtract ( bs - > wpCurrent - > origin , bs - > origin , a ) ;
bs - > frame_Waypoint_Len = VectorLength ( a ) ;
visResult = WPOrgVisible ( & g_entities [ bs - > client ] , bs - > origin , bs - > wpCurrent - > origin , bs - > client ) ;
if ( visResult = = 2 )
{
bs - > frame_Waypoint_Vis = 0 ;
bs - > wpSeenTime = 0 ;
bs - > wpDestination = NULL ;
bs - > wpDestIgnoreTime = level . time + 5000 ;
if ( bs - > wpDirection )
{
bs - > wpDirection = 0 ;
}
else
{
bs - > wpDirection = 1 ;
}
}
else if ( visResult )
{
bs - > frame_Waypoint_Vis = 1 ;
}
else
{
bs - > frame_Waypoint_Vis = 0 ;
}
}
if ( bs - > currentEnemy )
{
if ( bs - > currentEnemy - > client )
{
VectorCopy ( bs - > currentEnemy - > client - > ps . origin , eorg ) ;
eorg [ 2 ] + = bs - > currentEnemy - > client - > ps . viewheight ;
}
else
{
VectorCopy ( bs - > currentEnemy - > s . origin , eorg ) ;
}
VectorSubtract ( eorg , bs - > eye , a ) ;
bs - > frame_Enemy_Len = VectorLength ( a ) ;
if ( OrgVisible ( bs - > eye , eorg , bs - > client ) )
{
bs - > frame_Enemy_Vis = 1 ;
VectorCopy ( eorg , bs - > lastEnemySpotted ) ;
VectorCopy ( bs - > origin , bs - > hereWhenSpotted ) ;
bs - > lastVisibleEnemyIndex = bs - > currentEnemy - > s . number ;
//VectorCopy(bs->eye, bs->lastEnemySpotted);
bs - > hitSpotted = 0 ;
}
else
{
bs - > frame_Enemy_Vis = 0 ;
}
}
else
{
bs - > lastVisibleEnemyIndex = ENTITYNUM_NONE ;
}
//END
if ( bs - > frame_Enemy_Vis )
{
bs - > enemySeenTime = level . time + ENEMY_FORGET_MS ;
}
if ( bs - > wpCurrent )
{
WPConstantRoutine ( bs ) ;
if ( ! bs - > wpCurrent )
{ //WPConstantRoutine has the ability to nullify the waypoint if it fails certain checks, so..
return ;
}
if ( bs - > wpCurrent - > flags & WPFLAG_WAITFORFUNC )
{
if ( ! CheckForFunc ( bs - > wpCurrent - > origin , - 1 ) )
{
bs - > beStill = level . time + 500 ; //no func brush under.. wait
}
}
if ( bs - > wpCurrent - > flags & WPFLAG_NOMOVEFUNC )
{
if ( CheckForFunc ( bs - > wpCurrent - > origin , - 1 ) )
{
bs - > beStill = level . time + 500 ; //func brush under.. wait
}
}
if ( bs - > frame_Waypoint_Vis | | ( bs - > wpCurrent - > flags & WPFLAG_NOVIS ) )
{
bs - > wpSeenTime = level . time + 1500 ; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it
}
VectorCopy ( bs - > wpCurrent - > origin , bs - > goalPosition ) ;
if ( bs - > wpDirection )
{
goalWPIndex = bs - > wpCurrent - > index - 1 ;
}
else
{
goalWPIndex = bs - > wpCurrent - > index + 1 ;
}
if ( bs - > wpCamping )
{
VectorSubtract ( bs - > wpCampingTo - > origin , bs - > origin , a ) ;
vectoangles ( a , ang ) ;
VectorCopy ( ang , bs - > goalAngles ) ;
VectorSubtract ( bs - > origin , bs - > wpCamping - > origin , a ) ;
if ( VectorLength ( a ) < 64 )
{
VectorCopy ( bs - > wpCamping - > origin , bs - > goalPosition ) ;
bs - > beStill = level . time + 1000 ;
if ( ! bs - > campStanding )
{
bs - > duckTime = level . time + 1000 ;
}
}
}
/*
else if ( gWPArray [ goalWPIndex ] & & gWPArray [ goalWPIndex ] - > inuse & &
! ( bs - > cur_ps . pm_flags & PMF_LADDER ) )
{ //don't look at one ahead if on a ladder..
if ( bs - > cur_ps . pm_flags & PMF_LADDER )
{
vec3_t ladderMod ;
wpobject_t * prevWP ;
prevWP = GetLastWP ( bs , gWPArray [ goalWPIndex ] - > index ) ;
VectorCopy ( gWPArray [ goalWPIndex ] - > origin , ladderMod ) ;
if ( prevWP & & prevWP - > origin [ 2 ] < gWPArray [ goalWPIndex ] - > origin [ 2 ] )
{ //We're going up a ladder, as opposed to down
ladderMod [ 2 ] + = 400 ;
}
VectorSubtract ( ladderMod , bs - > origin , a ) ;
vectoangles ( a , ang ) ;
VectorCopy ( ang , bs - > goalAngles ) ;
//bs->jumpTime = level.time + 100;
}
else
{
VectorSubtract ( gWPArray [ goalWPIndex ] - > origin , bs - > origin , a ) ;
vectoangles ( a , ang ) ;
VectorCopy ( ang , bs - > goalAngles ) ;
}
}
*/
//This code usually causes horrible issues in SoF2 because the levels are more cramped and tight.
//So I guess for now they'll just have snappier viewangles.
else
{
if ( bs - > cur_ps . pm_flags & PMF_LADDER )
{
vec3_t ladderMod ;
wpobject_t * prevWP ;
prevWP = GetLastWP ( bs , bs - > wpCurrent - > index ) ;
VectorCopy ( bs - > wpCurrent - > origin , ladderMod ) ;
if ( prevWP & & prevWP - > origin [ 2 ] < bs - > wpCurrent - > origin [ 2 ] )
{ //We're going up a ladder, as opposed to down
ladderMod [ 2 ] + = 400 ;
}
VectorSubtract ( ladderMod , bs - > origin , a ) ;
vectoangles ( a , ang ) ;
VectorCopy ( ang , bs - > goalAngles ) ;
//bs->jumpTime = level.time + 100;
}
else
{
VectorSubtract ( bs - > wpCurrent - > origin , bs - > origin , a ) ;
vectoangles ( a , ang ) ;
VectorCopy ( ang , bs - > goalAngles ) ;
}
}
if ( bs - > destinationGrabTime < level . time /*&& (!bs->wpDestination || (bs->currentEnemy && bs->frame_Enemy_Vis))*/ )
{
GetIdealDestination ( bs ) ;
}
if ( bs - > wpCurrent & & bs - > wpDestination )
{
if ( TotalTrailDistance ( bs - > wpCurrent - > index , bs - > wpDestination - > index , bs ) = = - 1 )
{
bs - > wpDestination = NULL ;
bs - > destinationGrabTime = level . time + 10000 ;
}
}
if ( bs - > frame_Waypoint_Len < BOT_WPTOUCH_DISTANCE )
{
WPTouchRoutine ( bs ) ;
if ( ! bs - > wpDirection )
{
desiredIndex = bs - > wpCurrent - > index + 1 ;
}
else
{
desiredIndex = bs - > wpCurrent - > index - 1 ;
}
if ( gWPArray [ desiredIndex ] & &
gWPArray [ desiredIndex ] - > inuse & &
desiredIndex < gWPNum & &
desiredIndex > = 0 & &
PassWayCheck ( bs , desiredIndex ) )
{
bs - > wpCurrent = gWPArray [ desiredIndex ] ;
}
else
{
if ( bs - > wpDestination )
{
bs - > wpDestination = NULL ;
bs - > destinationGrabTime = level . time + 10000 ;
}
if ( bs - > wpDirection )
{
bs - > wpDirection = 0 ;
}
else
{
bs - > wpDirection = 1 ;
}
}
}
}
else //We can't find a waypoint, going to need a fallback routine.
{
doingFallback = BotFallbackNavigation ( bs ) ;
}
if ( bs - > timeToReact < level . time & & bs - > currentEnemy & & bs - > enemySeenTime > level . time + ( ENEMY_FORGET_MS - ( ENEMY_FORGET_MS * 0.2 ) ) )
{
if ( bs - > frame_Enemy_Vis )
{
cBAI = CombatBotAI ( bs , thinktime ) ;
}
else if ( bs - > cur_ps . weaponstate = = WEAPON_CHARGING_ALT )
{ //keep charging in case we see him again before we lose track of him
bs - > doAltAttack = 1 ;
}
if ( bs - > destinationGrabTime > level . time + 100 )
{
bs - > destinationGrabTime = level . time + 100 ; //assures that we will continue staying within a general area of where we want to be in a combat situation
}
if ( bs - > cur_ps . pm_flags & PMF_LADDER )
{ //keep navigating the ladder but if anyone happens in front of us on it, shoot them up good.
goto skipviewchecks ;
}
if ( bs - > currentEnemy - > client )
{
VectorCopy ( bs - > currentEnemy - > client - > ps . origin , headlevel ) ;
headlevel [ 2 ] + = bs - > currentEnemy - > client - > ps . viewheight ;
}
else
{
VectorCopy ( bs - > currentEnemy - > client - > ps . origin , headlevel ) ;
}
bLeadAmount = BotWeaponCanLead ( bs ) ;
if ( ( bs - > skills . accuracy / bs - > settings . skill ) < = 8 & &
bLeadAmount )
{
BotAimLeading ( bs , headlevel , bLeadAmount ) ;
}
else
{
VectorSubtract ( headlevel , bs - > eye , a ) ;
vectoangles ( a , ang ) ;
VectorCopy ( ang , bs - > goalAngles ) ;
}
# ifdef BOT_LAUNCH_ANGLES
if ( ( CAT_GRENADE = = weapon - > category | |
WP_MM1_GRENADE_LAUNCHER = = bs - > cur_ps . weapon ) & & bs - > launchAngle < 90 )
{
bs - > goalAngles [ PITCH ] = bs - > goalAngles [ PITCH ] - bs - > launchAngle ;
}
# endif
BotAimOffsetGoalAngles ( bs ) ;
}
skipviewchecks :
if ( bs - > currentEnemy )
{
if ( BotGetWeaponRange ( bs ) = = BWEAPONRANGE_MELEE )
{
if ( bs - > frame_Enemy_Len < = MELEE_ATTACK_RANGE )
{
MeleeCombatHandling ( bs ) ;
meleestrafe = 1 ;
}
}
}
if ( doingFallback & & bs - > currentEnemy ) //just stand and fire if we have no idea where we are
{
VectorCopy ( bs - > origin , bs - > goalPosition ) ;
}
if ( bs - > doChat & & bs - > chatTime > level . time & & ( ! bs - > currentEnemy | | ! bs - > frame_Enemy_Vis ) )
{
return ;
}
else if ( bs - > doChat & & bs - > currentEnemy & & bs - > frame_Enemy_Vis )
{
//bs->chatTime = level.time + bs->chatTime_stored;
bs - > doChat = 0 ; //do we want to keep the bot waiting to chat until after the enemy is gone?
bs - > chatTeam = 0 ;
}
else if ( bs - > doChat & & bs - > chatTime < = level . time )
{
if ( bs - > chatTeam )
{
trap_EA_SayTeam ( bs - > client , bs - > currentChat ) ;
bs - > chatTeam = 0 ;
}
else
{
trap_EA_Say ( bs - > client , bs - > currentChat ) ;
}
if ( bs - > doChat = = 2 )
{
BotReplyGreetings ( bs ) ;
}
bs - > doChat = 0 ;
}
CTFFlagMovement ( bs ) ;
if ( /*bs->wpDestination &&*/ bs - > shootGoal & &
/*bs->wpDestination->associated_entity == bs->shootGoal->s.number &&*/
bs - > shootGoal - > health > 0 & & bs - > shootGoal - > takedamage )
{
dif [ 0 ] = ( bs - > shootGoal - > r . absmax [ 0 ] + bs - > shootGoal - > r . absmin [ 0 ] ) / 2 ;
dif [ 1 ] = ( bs - > shootGoal - > r . absmax [ 1 ] + bs - > shootGoal - > r . absmin [ 1 ] ) / 2 ;
dif [ 2 ] = ( bs - > shootGoal - > r . absmax [ 2 ] + bs - > shootGoal - > r . absmin [ 2 ] ) / 2 ;
if ( ! bs - > currentEnemy | | bs - > frame_Enemy_Len > 256 )
{ //if someone is close then don't stop shooting them for this
VectorSubtract ( dif , bs - > eye , a ) ;
vectoangles ( a , a ) ;
VectorCopy ( a , bs - > goalAngles ) ;
if ( InFieldOfVision ( bs - > viewangles , 30 , a ) & &
EntityVisibleBox ( bs - > origin , NULL , NULL , dif , bs - > client , bs - > shootGoal - > s . number ) )
{
bs - > doAttack = 1 ;
}
}
}
if ( bs - > beStill < level . time & & ! WaitingForNow ( bs , bs - > goalPosition ) )
{
VectorSubtract ( bs - > goalPosition , bs - > origin , bs - > goalMovedir ) ;
VectorNormalize ( bs - > goalMovedir ) ;
if ( bs - > jumpTime > level . time & & bs - > jDelay < level . time & &
level . clients [ bs - > client ] . pers . cmd . upmove > 0 )
{
// trap_EA_Move(bs->client, bs->origin, 5000);
bs - > beStill = level . time + 200 ;
}
else
{
trap_EA_Move ( bs - > client , bs - > goalMovedir , 5000 ) ;
}
if ( meleestrafe )
{
StrafeTracing ( bs ) ;
}
if ( bs - > meleeStrafeDir & & meleestrafe & & bs - > meleeStrafeDisable < level . time )
{
trap_EA_MoveRight ( bs - > client ) ;
}
else if ( meleestrafe & & bs - > meleeStrafeDisable < level . time )
{
trap_EA_MoveLeft ( bs - > client ) ;
}
if ( BotTrace_Jump ( bs , bs - > goalPosition ) )
{
bs - > jumpTime = level . time + 100 ;
}
else if ( BotTrace_Duck ( bs , bs - > goalPosition ) )
{
bs - > duckTime = level . time + 100 ;
}
# ifdef BOT_STRAFE_AVOIDANCE
else
{
int strafeAround = BotTrace_Strafe ( bs , bs - > goalPosition ) ;
if ( strafeAround = = STRAFEAROUND_RIGHT )
{
trap_EA_MoveRight ( bs - > client ) ;
}
else if ( strafeAround = = STRAFEAROUND_LEFT )
{
trap_EA_MoveLeft ( bs - > client ) ;
}
}
# endif
}
if ( bs - > jumpTime > level . time & & bs - > jDelay < level . time )
{
if ( ! ( bs - > cur_ps . pm_debounce & PMD_JUMP ) )
{
trap_EA_Jump ( bs - > client ) ;
}
}
if ( bs - > duckTime > level . time )
{
trap_EA_Crouch ( bs - > client ) ;
}
if ( bs - > dangerousObject & & bs - > dangerousObject - > inuse & & bs - > dangerousObject - > health > 0 & &
bs - > dangerousObject - > takedamage & & ( ! bs - > frame_Enemy_Vis | | ! bs - > currentEnemy ) & &
( BotGetWeaponRange ( bs ) = = BWEAPONRANGE_MID | | BotGetWeaponRange ( bs ) = = BWEAPONRANGE_LONG ) & &
// bs->cur_ps.weapon != WP_DET_PACK && bs->cur_ps.weapon != WP_TRIP_MINE &&
! bs - > shootGoal )
{
float danLen ;
VectorSubtract ( bs - > dangerousObject - > r . currentOrigin , bs - > eye , a ) ;
danLen = VectorLength ( a ) ;
if ( danLen > 256 )
{
vectoangles ( a , a ) ;
VectorCopy ( a , bs - > goalAngles ) ;
if ( Q_irand ( 1 , 10 ) < 5 )
{
bs - > goalAngles [ YAW ] + = Q_irand ( 0 , 3 ) ;
bs - > goalAngles [ PITCH ] + = Q_irand ( 0 , 3 ) ;
}
else
{
bs - > goalAngles [ YAW ] - = Q_irand ( 0 , 3 ) ;
bs - > goalAngles [ PITCH ] - = Q_irand ( 0 , 3 ) ;
}
if ( InFieldOfVision ( bs - > viewangles , 30 , a ) & &
EntityVisibleBox ( bs - > origin , NULL , NULL , bs - > dangerousObject - > r . currentOrigin , bs - > client , bs - > dangerousObject - > s . number ) )
{
bs - > doAttack = 1 ;
}
}
}
if ( PrimFiring ( bs ) | |
AltFiring ( bs ) )
{
friendInLOF = CheckForFriendInLOF ( bs ) ;
if ( friendInLOF )
{
if ( PrimFiring ( bs ) )
{
KeepPrimFromFiring ( bs ) ;
}
if ( AltFiring ( bs ) )
{
KeepAltFromFiring ( bs ) ;
}
}
}
if ( bs - > doAttack )
{
trap_EA_Attack ( bs - > client ) ;
}
else if ( bs - > doAltAttack )
{
trap_EA_Alt_Attack ( bs - > client ) ;
}
MoveTowardIdealAngles ( bs ) ;
}
//end rww
/*
= = = = = = = = = = = = = = = = = =
BotAIStartFrame
= = = = = = = = = = = = = = = = = =
*/
int BotAIStartFrame ( int time )
{
# ifdef _SOF2_BOTS
int i ;
int elapsed_time , thinktime ;
static int local_time ;
static int botlib_residual ;
static int lastbotthink_time ;
G_CheckBotSpawn ( ) ;
//rww - addl bot frame functions
if ( gBotEdit )
{
trap_Cvar_Update ( & bot_wp_info ) ;
BotWaypointRender ( ) ;
}
UpdateEventTracker ( ) ;
//end rww
//cap the bot think time
//if the bot think time changed we should reschedule the bots
if ( BOT_THINK_TIME ! = lastbotthink_time ) {
lastbotthink_time = BOT_THINK_TIME ;
BotScheduleBotThink ( ) ;
}
elapsed_time = time - local_time ;
local_time = time ;
if ( elapsed_time > BOT_THINK_TIME ) thinktime = elapsed_time ;
else thinktime = BOT_THINK_TIME ;
// execute scheduled bot AI
for ( i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( ! botstates [ i ] | | ! botstates [ i ] - > inuse ) {
continue ;
}
//
botstates [ i ] - > botthink_residual + = elapsed_time ;
//
if ( botstates [ i ] - > botthink_residual > = thinktime ) {
botstates [ i ] - > botthink_residual - = thinktime ;
if ( g_entities [ i ] . client - > pers . connected = = CON_CONNECTED ) {
BotAI ( i , ( float ) thinktime / 1000 ) ;
}
}
}
// execute bot user commands every frame
for ( i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( ! botstates [ i ] | | ! botstates [ i ] - > inuse ) {
continue ;
}
if ( g_entities [ i ] . client - > pers . connected ! = CON_CONNECTED ) {
continue ;
}
BotUpdateInput ( botstates [ i ] , time , elapsed_time ) ;
trap_BotUserCommand ( botstates [ i ] - > client , & botstates [ i ] - > lastucmd ) ;
}
return qtrue ;
# else
return qfalse ;
# endif
}
/*
= = = = = = = = = = = = = =
BotAISetup
= = = = = = = = = = = = = =
*/
int BotAISetup ( int restart ) {
//rww - new bot cvars..
# ifdef _DEBUG
trap_Cvar_Register ( & bot_debugmessages , " bot_debugmessages " , " 0 " , CVAR_CHEAT , 0.0 , 0.0 ) ;
# endif
trap_Cvar_Register ( & bot_attachments , " bot_attachments " , " 1 " , 0 , 0.0 , 0.0 ) ;
trap_Cvar_Register ( & bot_camp , " bot_camp " , " 1 " , 0 , 0.0 , 0.0 ) ;
trap_Cvar_Register ( & bot_pause , " bot_pause " , " 0 " , 0 , 0.0 , 0.0 ) ;
trap_Cvar_Register ( & bot_wp_info , " bot_wp_info " , " 1 " , 0 , 0.0 , 0.0 ) ;
trap_Cvar_Register ( & bot_wp_edit , " bot_wp_edit " , " 0 " , CVAR_CHEAT , 0.0 , 0.0 ) ;
trap_Cvar_Register ( & bot_wp_clearweight , " bot_wp_clearweight " , " 1 " , 0 , 0.0 , 0.0 ) ;
trap_Cvar_Register ( & bot_wp_distconnect , " bot_wp_distconnect " , " 1 " , 0 , 0.0 , 0.0 ) ;
trap_Cvar_Register ( & bot_wp_visconnect , " bot_wp_visconnect " , " 1 " , 0 , 0.0 , 0.0 ) ;
//end rww
//if the game is restarted for a tournament
if ( restart ) {
return qtrue ;
}
//initialize the bot states
memset ( botstates , 0 , sizeof ( botstates ) ) ;
if ( ! trap_BotLibSetup ( ) )
{
return qfalse ; //wts?!
}
return qtrue ;
}
/*
= = = = = = = = = = = = = =
BotAIShutdown
= = = = = = = = = = = = = =
*/
int BotAIShutdown ( int restart ) {
int i ;
//if the game is restarted for a tournament
if ( restart ) {
//shutdown all the bots in the botlib
for ( i = 0 ; i < MAX_CLIENTS ; i + + ) {
if ( botstates [ i ] & & botstates [ i ] - > inuse ) {
BotAIShutdownClient ( botstates [ i ] - > client , restart ) ;
}
}
//don't shutdown the bot library
}
else {
trap_BotLibShutdown ( ) ;
}
return qtrue ;
}