Files
Enemy-Territory/src/botai/ai_team.c

3184 lines
76 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
===========================================================================
Wolfenstein: Enemy Territory GPL Source Code
Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”).
Wolf ET Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Wolf ET Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Wolf ET Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
/*****************************************************************************
* name: ai_team.c
*
* desc: Wolf bot AI
*
*
*****************************************************************************/
#include "../game/g_local.h"
#include "../game/botlib.h"
#include "../game/be_aas.h"
#include "../game/be_ea.h"
#include "../game/be_ai_char.h"
#include "../game/be_ai_chat.h"
#include "../game/be_ai_gen.h"
#include "../game/be_ai_goal.h"
#include "../game/be_ai_move.h"
#include "../game/be_ai_weap.h"
#include "../botai/botai.h"
#include "../botai/inv.h"
//
#include "ai_main.h"
#include "ai_dmq3.h"
#include "ai_cmd.h"
#include "ai_team.h"
//
#include "ai_dmnet_mp.h"
// Key AI constants
#include "ai_distances.h"
// Array contains all the info about the team VO and AI coordination
AI_Team_t g_aiTeam[TEAM_NUM_TEAMS];
/*
==================
BotValidTeamLeader
==================
*/
int BotValidTeamLeader( bot_state_t *bs ) {
if ( !strlen( bs->teamleader ) ) {
return qfalse;
}
if ( ClientFromName( bs->teamleader ) == -1 ) {
return qfalse;
}
return qtrue;
}
/*
==================
BotNumTeamMates
==================
*/
int BotNumTeamMates( bot_state_t *bs, int *list, int maxList ) {
int i, j, numplayers;
numplayers = 0;
for ( i = 0; i < level.numConnectedClients; i++ ) {
j = level.sortedClients[i];
if ( bs->client == j ) {
continue;
}
if ( !g_entities[j].inuse ) {
continue;
}
if ( !BotSameTeam( bs, j ) ) {
continue;
}
if ( list ) {
if ( numplayers < maxList ) {
list[numplayers++] = j;
}
} else {
// calling without a list gives the full count no matter what maxList is set to
numplayers++;
}
}
return numplayers;
}
/*
==================
BotNumTeamMatesWithTarget
==================
*/
int BotNumTeamMatesWithTarget( bot_state_t *bs, int targetEntity, int *list, int maxList ) {
int i, j, numplayers;
numplayers = 0;
for ( i = 0; i < level.numConnectedClients; i++ ) {
j = level.sortedClients[i];
if ( bs->client == j ) {
continue;
}
if ( !g_entities[j].inuse ) {
continue;
}
if ( !BotSameTeam( bs, j ) ) {
continue;
}
if ( g_entities[j].health <= 0 ) {
continue;
}
if ( !botstates[j].inuse ) {
continue;
}
if ( botstates[j].target_goal.entitynum != targetEntity ) {
continue;
}
if ( list ) {
if ( numplayers < maxList ) {
list[numplayers++] = j;
}
} else {
// calling without a list gives the full count no matter what maxList is set to
numplayers++;
}
}
return numplayers;
}
/*
==================
BotNumTeamMatesWithTargetByClass
==================
*/
int BotNumTeamMatesWithTargetByClass( bot_state_t *bs, int targetEntity, int *list, int maxList, int playerType ) {
int i, j, numplayers;
numplayers = 0;
for ( i = 0; i < level.numConnectedClients; i++ ) {
j = level.sortedClients[i];
if ( bs->client == j ) {
continue;
}
if ( !g_entities[j].inuse ) {
continue;
}
if ( !BotSameTeam( bs, j ) ) {
continue;
}
if ( g_entities[j].health <= 0 ) {
continue;
}
if ( g_entities[j].client->sess.playerType != playerType ) {
continue;
}
if ( !botstates[j].inuse ) {
continue;
}
if ( botstates[j].target_goal.entitynum != targetEntity ) {
continue;
}
if ( list ) {
if ( numplayers < maxList ) {
list[numplayers++] = j;
}
} else {
// calling without a list gives the full count no matter what maxList is set to
numplayers++;
}
}
return numplayers;
}
/*
==================
BotNumTeamMatesWithTargetAndCloser
==================
*/
int BotNumTeamMatesWithTargetAndCloser( bot_state_t *bs, int targetEntity, int targetArea, int *list, int maxList, int playerType ) {
int i, j, numplayers;
int ourTime, t;
numplayers = 0;
ourTime = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, targetArea, bs->tfl );
for ( i = 0; i < level.numConnectedClients; i++ ) {
j = level.sortedClients[i];
if ( bs->client == j ) {
continue;
}
if ( !g_entities[j].inuse ) {
continue;
}
if ( !botstates[j].inuse ) {
continue;
}
if ( !BotSameTeam( bs, j ) ) {
continue;
}
if ( g_entities[j].health <= 0 ) {
continue;
}
if ( playerType >= 0 && g_entities[j].client->sess.playerType != playerType ) {
continue;
}
if ( botstates[j].target_goal.entitynum != targetEntity ) {
continue;
}
t = botstates[j].inventory[GOAL_TRAVELTIME];
// trap_AAS_AreaTravelTimeToGoalArea( botstates[j].target_goal.areanum, botstates[j].origin, targetArea, bs->tfl );
if ( !t ) {
continue;
}
if ( t > ourTime ) {
continue;
}
if ( list ) {
if ( numplayers < maxList ) {
list[numplayers++] = j;
}
} else {
// calling without a list gives the full count no matter what maxList is set to
numplayers++;
}
}
return numplayers;
}
/*
==================
BotSortPlayersByDistance
returns the distances list
==================
*/
float* BotSortPlayersByDistance( vec3_t target, int *list, int numList ) {
static float outDistances[MAX_CLIENTS];
float distances[MAX_CLIENTS], bestDist;
int i, j, outList[MAX_CLIENTS], best = 0;
for ( i = 0; i < numList; i++ ) {
distances[i] = VectorDistanceSquared( g_entities[list[i]].r.currentOrigin, target );
}
for ( j = 0; j < numList; j++ ) {
// find the closest player
bestDist = -1;
for ( i = 0; i < numList; i++ ) {
if ( bestDist < 0 || distances[i] <= bestDist ) {
best = i;
bestDist = distances[i];
}
}
outDistances[j] = distances[best];
distances[best] = -1;
outList[j] = list[best];
}
return outDistances;
}
/*
==================
BotSortPlayersByTraveltime
returns the distances list
==================
*/
float *BotSortPlayersByTraveltime( int areanum, int *list, int numList ) {
static float outDistances[MAX_CLIENTS];
float distances[MAX_CLIENTS], bestDist;
int i, j, outList[MAX_CLIENTS], best = 0;
bot_state_t *lbs;
for ( i = 0; i < numList; i++ ) {
lbs = &botstates[list[i]];
if ( lbs && lbs->inuse ) {
distances[i] = (float)trap_AAS_AreaTravelTimeToGoalArea( BotGetArea( list[i] ), BotGetOrigin( list[i] ), areanum, lbs->tfl );
} else {
distances[i] = 0;
}
}
for ( j = 0; j < numList; j++ ) {
// find the closest player
bestDist = -1;
for ( i = 0; i < numList; i++ ) {
if ( distances[i] < 0 ) {
continue;
}
if ( bestDist < 0 || distances[i] <= bestDist ) {
best = i;
bestDist = distances[i];
}
}
outDistances[j] = distances[best];
distances[best] = -1;
outList[j] = list[best];
}
return outDistances;
}
/*
==================
BotGetEnemyFlagCarrier
==================
*/
gentity_t *BotGetEnemyFlagCarrier( bot_state_t *bs ) {
int i;
char buf[MAX_INFO_STRING];
static int maxclients;
if ( !maxclients ) {
maxclients = level.maxclients;
}
for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) {
if ( !g_entities[i].inuse ) {
continue;
}
if ( g_entities[i].health <= 0 ) {
continue;
}
trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) );
//if no config string or no name
if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) {
continue;
}
//skip spectators
if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) {
continue;
}
//
if ( !BotSameTeam( bs, i ) ) {
if ( g_entities[i].client->ps.powerups[PW_BLUEFLAG] || g_entities[i].client->ps.powerups[PW_REDFLAG] ) {
return BotGetEntity( i );
}
}
}
return NULL;
}
/*
==================
BotGetTeamFlagCarrier
==================
*/
int BotGetTeamFlagCarrier( bot_state_t *bs ) {
int i;
char buf[MAX_INFO_STRING];
static int maxclients;
if ( !maxclients ) {
maxclients = trap_Cvar_VariableIntegerValue( "sv_maxclients" );
}
for ( i = 0; i < maxclients && i < MAX_CLIENTS; i++ ) {
if ( !g_entities[i].inuse ) {
continue;
}
if ( g_entities[i].health <= 0 ) {
continue;
}
trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) );
//if no config string or no name
if ( !strlen( buf ) || !strlen( Info_ValueForKey( buf, "n" ) ) ) {
continue;
}
//skip spectators
if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) {
continue;
}
//
if ( BotSameTeam( bs, i ) ) {
if ( g_entities[i].client->ps.powerups[PW_BLUEFLAG] || g_entities[i].client->ps.powerups[PW_REDFLAG] ) {
return g_entities[i].s.number;
}
}
}
return -1;
}
/*
==================
BotFlagFlagAtBase
returns -1 if there is no such flag, qfalse or qtrue otherwise.
==================
*/
int BotFlagAtBase( int team, gentity_t **returnEnt ) {
gentity_t *ent;
botStaticEntityEnum_t flags[2] = {BOTSTATICENTITY_CTF_REDFLAG, BOTSTATICENTITY_CTF_BLUEFLAG};
//
if ( team > TEAM_SPECTATOR ) {
return qfalse;
}
//
ent = NULL;
if ( returnEnt ) {
*returnEnt = NULL;
}
while ( ( ent = BotFindNextStaticEntity( ent, flags[team - 1] ) ) ) {
if ( ent->flags & FL_DROPPED_ITEM ) {
continue;
}
if ( returnEnt ) {
*returnEnt = ent;
}
// this is the enemy flag, is it at base?
if ( ent->r.svFlags & SVF_NOCLIENT ) {
// not at base
return qfalse;
} else {
// at base
return qtrue;
}
}
//
return -1;
}
/*
==================
BotClientTravelTimeToGoal
==================
*/
int BotClientTravelTimeToGoal( int client, bot_goal_t *goal ) {
playerState_t ps;
int areanum;
BotAI_GetClientState( client, &ps );
areanum = BotPointAreaNum( client, ps.origin );
if ( !areanum ) {
return 1;
}
if ( !BotTravelFlagsForClient( client ) ) {
return 1;
}
return trap_AAS_AreaTravelTimeToGoalArea( areanum, ps.origin, goal->areanum, BotTravelFlagsForClient( client ) );
}
/*
==================
BotSortTeamMatesByBaseTravelTime
==================
*/
int BotSortTeamMatesByBaseTravelTime( bot_state_t *bs, int *teammates, int maxteammates ) {
return 0;
}
/*
==================
BotSayTeamOrders
==================
*/
void BotSayTeamOrder( bot_state_t *bs, int toclient ) {
char teamchat[MAX_MESSAGE_SIZE];
char buf[MAX_MESSAGE_SIZE];
char name[MAX_NETNAME];
//if the bot is talking to itself
if ( bs->client == toclient ) {
//don't show the message just put it in the console message queue
trap_BotGetChatMessage( bs->cs, buf, sizeof( buf ) );
ClientName( bs->client, name, sizeof( name ) );
Com_sprintf( teamchat, sizeof( teamchat ), "(%s): %s", name, buf );
trap_BotQueueConsoleMessage( bs->cs, CMS_CHAT, teamchat );
} else {
trap_BotEnterChat( bs->cs, bs->client, CHAT_TEAM );
}
}
/*
==================
BotFindDroppedFlag
==================
*/
qboolean BotFindDroppedFlag( gentity_t **returnEnt ) {
gentity_t *ent;
char *flagStr[2] = {"team_CTF_redflag", "team_CTF_blueflag"};
int i, j;
//
ent = BotGetEntity( level.maxclients );
for ( j = 0; j < level.num_entities - level.maxclients; j++, ent++ ) {
if ( !ent->inuse ) {
continue;
}
if ( !( ent->flags & FL_DROPPED_ITEM ) ) {
continue;
}
if ( ent->classname[0] != 't' || ent->classname[1] != 'e' ) {
continue;
}
for ( i = 0; i < 2; i++ ) {
if ( Q_stricmp( ent->classname, flagStr[i] ) ) {
continue;
}
// this is a dropped flag
if ( returnEnt ) {
*returnEnt = ent;
}
return qtrue;
}
}
//
return qfalse;
}
/*
==================
BotFindSparseDefendArea
==================
*/
int BotFindSparseDefendArea( bot_state_t *bs, bot_goal_t *goal, qboolean force ) {
vec3_t dSpot, targetPos;
int numTeam, i, j, t, t2, teammates[64];
int numareas, numTPos, area, bestArea = 0, bestTime = 0;
vec3_t tPos[64], waypoints[64];
bot_state_t *tbs;
float dist, bestDist, closest, d, maxrange;
int avoidEnts[] = {BOTSTATICENTITY_FUNC_DOOR, BOTSTATICENTITY_FUNC_DOOR_ROTATING, -1};
vec3_t avoidPos[128];
int numAvoidPos;
gentity_t *trav, *flagEnt;
qboolean hasflag, getFurthestFromFlag;
bot_goal_t flagGoal, flag;
int flagDestTime = 0;
bot_state_t *obs;
vec3_t brushPos, center, vec;
int list[20], numList;
int oldestTime = -1, oldest = 0;
//
if ( !force && bs->last_SparseDefense > level.time - 400 - rand() % 200 ) {
return -1;
}
bs->last_SparseDefense = level.time;
//
// find our team mates that are defending the same goal
numTeam = BotNumTeamMates( bs, teammates, 64 );
numTPos = 0;
for ( i = 0; i < numTeam; i++ ) {
if ( !g_entities[teammates[i]].inuse || !( g_entities[teammates[i]].r.svFlags & SVF_BOT ) || g_entities[teammates[i]].health <= 0 ) {
continue;
}
if ( teammates[i] == bs->client ) {
continue;
}
tbs = &botstates[teammates[i]];
if ( tbs->target_goal.entitynum == goal->entitynum ) {
// same goal
VectorCopy( tbs->target_goal.origin, tPos[numTPos] );
numTPos++;
}
}
// get the location of the goal
if ( goal->entitynum >= 0 ) {
VectorAdd( g_entities[goal->entitynum].r.absmin, g_entities[goal->entitynum].r.absmax, targetPos );
VectorScale( targetPos, 0.5, targetPos );
//if (!trap_AAS_PointAreaNum( targetPos )) {
// VectorCopy( goal->origin, targetPos );
//}
} else {
VectorCopy( goal->origin, targetPos );
}
//
hasflag = qfalse;
getFurthestFromFlag = qfalse;
if ( !BotSinglePlayer() && !BotCoop() && goal->entitynum >= 0 && goal->entitynum < MAX_CLIENTS && BotCarryingFlag( goal->entitynum ) ) {
if ( !( g_entities[goal->entitynum].r.svFlags & SVF_BOT ) ) {
trav = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY );
if ( !trav ) {
trav = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_FLAGONLY_MULTIPLE );
}
if ( trav ) {
BotGoalForEntity( NULL, trav->s.number, &flagGoal, 0 );
// find the flag itself, if we are closer the that, then pick a spot further away from the flag
flagEnt = NULL;
if ( bs->sess.sessionTeam == TEAM_AXIS ) {
flagEnt = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_CTF_BLUEFLAG );
} else {
flagEnt = BotFindNextStaticEntity( NULL, BOTSTATICENTITY_CTF_REDFLAG );
}
if ( flagEnt ) {
BotGoalForEntity( NULL, flagEnt->s.number, &flag, 0 );
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, flag.areanum, bs->tfl );
if ( t ) {
t2 = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, flagGoal.areanum, bs->tfl );
if ( t < t2 ) {
// we should find a spot further away from the flag itself, rather than closer to the destination.
// this takes into account the player taking an alternate route, so that the bots lead the player
// still, rather than trailing behind while they think they are leading the player.
getFurthestFromFlag = qtrue;
BotGoalForEntity( NULL, flagEnt->s.number, &flagGoal, 0 );
hasflag = qtrue;
flagDestTime = t;
}
}
} else {
VectorAdd( trav->r.absmin, trav->r.absmax, brushPos );
VectorScale( brushPos, 0.5, brushPos );
// find the best goal area
numList = trap_AAS_BBoxAreas( trav->r.absmin, trav->r.absmax, list, 20 );
bestDist = -1;
if ( numList ) {
oldestTime = -1;
for ( i = 0; i < numList; i++ ) {
if ( !trap_AAS_AreaReachability( list[i] ) ) {
continue;
}
t = trap_AAS_AreaTravelTimeToGoalArea( goal->areanum, goal->origin, list[i], bs->tfl );
if ( t > 0 ) {
// choose the reachable area closest to the center of the info_objective brush
trap_AAS_AreaCenter( list[i], center );
VectorSubtract( brushPos, center, vec );
vec[2] = 0;
dist = VectorLength( vec );
if ( bestDist < 0 || dist < bestDist ) {
oldestTime = t;
oldest = list[i];
bestDist = dist;
}
}
}
}
if ( bestDist > -1 ) {
hasflag = qtrue;
flagDestTime = oldestTime;
flagGoal.areanum = oldest;
}
}
}
} else { // use the bot's current goal
obs = &botstates[goal->entitynum];
flagGoal = obs->target_goal;
// do they have an alt_goal
if ( obs->alt_goal.number && obs->alt_goal.entitynum == obs->target_goal.entitynum && obs->alt_goal.number == obs->target_goal.number ) {
flagGoal = obs->target_goal; // use the ultimate destination, not the altgoal
t = trap_AAS_AreaTravelTimeToGoalArea( goal->areanum, goal->origin, flagGoal.areanum, bs->tfl );
if ( t ) {
hasflag = qtrue;
flagDestTime = t;
}
}
}
}
//
if ( !BotSinglePlayer() && !BotCoop() ) {
maxrange = 700;
if ( !hasflag && numTPos < 4 ) {
maxrange = 300.0 + ( ( maxrange - 300.0 ) * numTPos / 4.0 );
} else if ( hasflag ) {
maxrange = 800;
}
} else {
// use autonomy range
maxrange = BotGetMovementAutonomyRange( bs, goal );
if ( maxrange > 700 ) {
maxrange = 700;
}
/* // if we are following a leader, we need to work out where to position ourselves from them
if (goal->entitynum >= 0 && goal->entitynum < MAX_CLIENTS) {
useoffset = qtrue;
//
trav = BotGetEntity( goal->entitynum );
// are they moving?
if (VectorLengthSquared( trav->client->ps.velocity ) > SQR(10)) {
VectorNormalize2( trav->client->ps.velocity, offsetVec );
// low aggression, stay behind
}
}
*/
}
//
numareas = trap_AAS_ListAreasInRange( targetPos, goal->areanum, maxrange, bs->tfl & ~( TFL_BARRIERJUMP | TFL_LADDER | TFL_JUMP ), (float **)waypoints, 64 );
//
if ( !numareas ) {
return 0;
}
//
// build a list of avoidEnts
numAvoidPos = 0;
for ( i = 0; avoidEnts[i] >= 0; i++ ) {
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, avoidEnts[i] ) ) ) {
VectorAdd( trav->r.absmin, trav->r.absmax, dSpot );
VectorScale( dSpot, 0.5, dSpot );
VectorCopy( dSpot, avoidPos[numAvoidPos] );
numAvoidPos++;
if ( numAvoidPos == 128 ) {
break;
}
}
if ( numAvoidPos == 128 ) {
break;
}
}
//
// find the area with the greatest distance from all other teammates, and the goal
bestDist = -999999;
closest = 99999;
for ( i = 0; i < numareas; i++ ) {
// if this point is outside movement autonomy, ignore it
if ( !BotPointWithinMovementAutonomy( bs, goal, waypoints[i] ) ) {
continue;
}
//
dist = 0.5 * VectorDistance( targetPos, waypoints[i] );
for ( j = 0; j < numTPos; j++ ) {
d = VectorDistance( waypoints[i], tPos[j] );
if ( !j || d < closest ) {
closest = d;
}
}
// add avoidEnts
for ( j = 0; j < numAvoidPos; j++ ) {
d = VectorDistance( avoidPos[j], bs->origin );
if ( d < closest ) {
closest = d;
}
}
dist += closest * 6; // weight this higher than the goal distance, since we really want to space each other out
//
// if they have the flag, select areas that are closer to their goal
if ( hasflag ) {
// get closer or further from goal depending on leader's status
area = trap_AAS_PointAreaNum( waypoints[i] );
t = trap_AAS_AreaTravelTimeToGoalArea( area, waypoints[i], flagGoal.areanum, bs->tfl );
//
if ( !t ) {
continue;
}
//
if ( BotCarryingFlag( goal->entitynum ) && !getFurthestFromFlag ) {
dist += 14 * ( flagDestTime - t ); // closer
} else {
dist -= 12 * ( flagDestTime - t ); // further
}
}
//
if ( dist > bestDist ) {
area = trap_AAS_PointAreaNum( waypoints[i] );
if ( area ) {
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, area, bs->tfl );
if ( t ) {
bestDist = dist;
VectorCopy( waypoints[i], dSpot );
bestArea = area;
bestTime = t;
}
}
}
}
//
if ( bestArea ) {
// we have our spot, defend this area now
goal->areanum = bestArea;
VectorCopy( dSpot, goal->origin );
goal->flags |= GFL_DEFEND_CLOSE;
}
//
return bestTime;
}
#define GETTARGETEXPLOSIVE_CACHE_SIZE 32
typedef struct botExplosiveCache_s {
int list[GETTARGETEXPLOSIVE_CACHE_SIZE];
int count;
int listSize;
qboolean ignore;
int time;
} botExplosiveCache_t;
botExplosiveCache_t g_botExplosiveCache[2];
botExplosiveCache_t g_botSatchelCache[2];
gentity_t* G_FindMissile( gentity_t* start, weapon_t weap ) {
int i = start ? ( start - g_entities ) + 1 : 0;
gentity_t* ent = &g_entities[i];
for ( ; i < level.num_entities; i++, ent++ ) {
if ( ent->s.eType != ET_MISSILE ) {
continue;
}
if ( ent->s.weapon != weap ) {
continue;
}
return ent;
}
return NULL;
}
gentity_t* G_FindDynamite( gentity_t* start ) {
return G_FindMissile( start, WP_DYNAMITE );
}
gentity_t* G_FindSmokeBomb( gentity_t* start ) {
return G_FindMissile( start, WP_SMOKE_BOMB );
}
gentity_t* G_FindLandmine( gentity_t* start ) {
return G_FindMissile( start, WP_LANDMINE );
}
gentity_t* G_FindSatchels( gentity_t* start ) {
return G_FindMissile( start, WP_SATCHEL );
}
// Gordon: adding some support functions
// returns qtrue if a construction is under way on this ent, even before it hits any stages
qboolean G_ConstructionBegun( gentity_t* ent ) {
if ( G_ConstructionIsPartlyBuilt( ent ) ) {
return qtrue;
}
if ( ent->s.angles2[0] ) {
return qtrue;
}
return qfalse;
}
// returns qtrue if all stage are built
qboolean G_ConstructionIsFullyBuilt( gentity_t* ent ) {
if ( ent->s.angles2[1] != 1 ) {
return qfalse;
}
return qtrue;
}
// returns qtrue if 1 stage or more is built
qboolean G_ConstructionIsPartlyBuilt( gentity_t* ent ) {
if ( G_ConstructionIsFullyBuilt( ent ) ) {
return qtrue;
}
if ( ent->count2 ) {
if ( !ent->grenadeFired ) {
return qfalse;
} else {
return qtrue;
}
}
return qfalse;
}
qboolean G_ConstructionIsDestroyable( gentity_t* ent ) {
if ( !G_ConstructionIsPartlyBuilt( ent ) ) {
return qfalse;
}
if ( ent->s.angles2[0] ) {
return qfalse;
}
return qtrue;
}
// returns the constructible for this team that is attached to this toi
gentity_t* G_ConstructionForTeam( gentity_t* toi, team_t team ) {
gentity_t* targ = toi->target_ent;
if ( !targ || targ->s.eType != ET_CONSTRUCTIBLE ) {
return NULL;
}
if ( targ->spawnflags & 4 ) {
if ( team == TEAM_ALLIES ) {
return targ->chain;
}
} else if ( targ->spawnflags & 8 ) {
if ( team == TEAM_AXIS ) {
return targ->chain;
}
}
return targ;
}
gentity_t* G_IsConstructible( team_t team, gentity_t* toi ) {
gentity_t* ent;
if ( !toi || toi->s.eType != ET_OID_TRIGGER ) {
return NULL;
}
if ( !( ent = G_ConstructionForTeam( toi, team ) ) ) {
return NULL;
}
if ( G_ConstructionIsFullyBuilt( ent ) ) {
return NULL;
}
if ( ent->chain && G_ConstructionBegun( ent->chain ) ) {
return NULL;
}
return ent;
}
gentity_t* G_FindDynamiteTargetForTeam( gentity_t* trav, team_t team ) {
gentity_t* targ;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_OBJECTIVE_INFO ) ) ) {
if ( !trav->r.linked ) {
continue;
}
if ( ( targ = trav->target_ent ) ) {
if ( targ->s.eType == ET_EXPLOSIVE ) {
if ( !( targ->spawnflags & 64 ) ) { // DY-NO-MITE
continue;
}
if ( !targ->parent ) {
continue;
}
if ( targ->aiInactive & ( 1 << team ) ) {
continue;
}
// Gordon: dont wanna dynamite our own things
if ( ( targ->parent->spawnflags & AXIS_OBJECTIVE ) && ( team == TEAM_AXIS ) ) {
continue;
} else if ( ( targ->parent->spawnflags & ALLIED_OBJECTIVE ) && ( team == TEAM_ALLIES ) ) {
continue;
}
return targ;
} else if ( targ->s.eType == ET_CONSTRUCTIBLE ) {
targ = G_ConstructionForTeam( trav, team == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS );
// no constructible for the other team attached to this
if ( !targ ) {
continue;
}
// dynamite only
if ( !( targ->spawnflags & 32 ) ) {
continue;
}
// if it isn't built yet, there's nothing to blow up
if ( !G_ConstructionIsDestroyable( targ ) ) {
continue;
}
// not active from the script
if ( targ->aiInactive & ( 1 << team ) ) {
continue;
}
return targ;
}
}
}
return NULL;
}
gentity_t* G_FindSatchelChargeTargetForTeam( gentity_t* trav, team_t team ) {
gentity_t* targ;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_OBJECTIVE_INFO ) ) ) {
if ( !trav->r.linked ) {
continue;
}
if ( ( targ = trav->target_ent ) ) {
if ( targ->s.eType == ET_EXPLOSIVE ) {
continue;
} else if ( targ->s.eType == ET_CONSTRUCTIBLE ) {
targ = G_ConstructionForTeam( trav, team == TEAM_AXIS ? TEAM_ALLIES : TEAM_AXIS );
// no constructible for the other team attached to this
if ( !targ ) {
continue;
}
// can satchel charge it
if ( !( targ->spawnflags & 256 ) ) {
continue;
}
// if it isn't built yet, there's nothing to blow up
if ( !G_ConstructionIsDestroyable( targ ) ) {
continue;
}
// not active from the script
if ( targ->aiInactive & ( 1 << team ) ) {
continue;
}
return targ;
}
}
}
return NULL;
}
/*
==================
BotGetTargetsForSatchelCharge
==================
*/
int BotGetTargetsForSatchelCharge( team_t team, int *list, int listSize, qboolean ignoreSatchelCharge ) {
int count;
gentity_t *trav, *dyn;
vec3_t pos, vec;
botExplosiveCache_t* pCache = &g_botSatchelCache[team - TEAM_AXIS];
if ( ( pCache->time == level.time ) && ( pCache->listSize == listSize ) && ( pCache->ignore == ignoreSatchelCharge ) ) {
// cache hit
if ( listSize <= GETTARGETEXPLOSIVE_CACHE_SIZE ) {
memcpy( list, pCache->list, sizeof( int ) * listSize );
return pCache->count;
}
}
count = 0;
trav = NULL;
for ( trav = G_FindSatchelChargeTargetForTeam( NULL, team ); trav; trav = G_FindSatchelChargeTargetForTeam( trav->parent, team ) ) {
if ( !ignoreSatchelCharge ) {
// is there already some dynamite planted here?
VectorAdd( trav->r.absmax, trav->r.absmin, pos );
VectorScale( pos, 0.5, pos );
// Gordon: could really do with just having some ref counting for this...
for ( dyn = G_FindSatchels( NULL ); dyn; dyn = G_FindSatchels( dyn ) ) {
G_AdjustedDamageVec( trav, dyn->r.currentOrigin, vec );
if ( ( VectorLengthSquared( vec ) <= SQR( dyn->splashRadius ) ) && CanDamage( trav, dyn->r.currentOrigin ) ) {
if ( listSize ) {
if ( list && count >= listSize ) {
continue;
}
continue; // planted satchel charge was found
}
}
}
}
if ( list ) {
list[count] = trav->s.number;
}
count++;
if ( list && count >= listSize ) {
break;
}
}
// set the cache items
if ( list && listSize <= GETTARGETEXPLOSIVE_CACHE_SIZE ) {
memcpy( pCache->list, list, sizeof( int ) * listSize );
pCache->count = count;
pCache->ignore = ignoreSatchelCharge;
pCache->listSize = listSize;
pCache->time = level.time;
}
return count;
}
/*
==================
BotGetTargetExplosives
==================
*/
int BotGetTargetExplosives( team_t team, int *list, int listSize, qboolean ignoreDynamite ) {
int count;
gentity_t *trav, *dyn;
vec3_t pos, vec;
botExplosiveCache_t* pCache = &g_botExplosiveCache[team - TEAM_AXIS];
if ( ( pCache->time == level.time ) && ( pCache->listSize == listSize ) && ( pCache->ignore == ignoreDynamite ) ) {
// cache hit
if ( listSize <= GETTARGETEXPLOSIVE_CACHE_SIZE ) {
memcpy( list, pCache->list, sizeof( int ) * listSize );
return pCache->count;
}
}
count = 0;
trav = NULL;
for ( trav = G_FindDynamiteTargetForTeam( NULL, team ); trav; trav = G_FindDynamiteTargetForTeam( trav->parent, team ) ) {
if ( !ignoreDynamite ) {
// is there already some dynamite planted here?
VectorAdd( trav->r.absmax, trav->r.absmin, pos );
VectorScale( pos, 0.5, pos );
for ( dyn = G_FindDynamite( NULL ); dyn; dyn = G_FindDynamite( dyn ) ) {
G_AdjustedDamageVec( trav, dyn->r.currentOrigin, vec );
if ( ( VectorLengthSquared( vec ) <= SQR( dyn->splashRadius ) ) && CanDamage( trav, dyn->r.currentOrigin ) ) {
// Gordon: checking damage makes no sense in this case, as these are one hit wonders
if ( listSize ) {
if ( list && count >= listSize ) {
continue;
}
continue; // planted dynamite was found
}
}
}
}
if ( list ) {
if ( trav->s.eType == ET_EXPLOSIVE ) {
list[count] = trav->parent->s.number;
} else {
list[count] = trav->s.number;
}
}
count++;
if ( list && count >= listSize ) {
break;
}
}
// set the cache items
if ( list && listSize <= GETTARGETEXPLOSIVE_CACHE_SIZE ) {
memcpy( pCache->list, list, sizeof( int ) * listSize );
pCache->count = count;
pCache->ignore = ignoreDynamite;
pCache->listSize = listSize;
pCache->time = level.time;
}
return count;
}
int GetTargetExplosives( team_t team, qboolean ignoreDynamite ) {
return BotGetTargetExplosives( team, NULL, 0, ignoreDynamite );
}
/*
==================
BotGetTargetDynamite
==================
*/
int BotGetTargetDynamite( int *list, int listSize, gentity_t* target ) {
gentity_t *dyn, *trav;
vec3_t vec;
int count = 0;
team_t team;
for ( dyn = G_FindDynamite( NULL ); dyn; dyn = G_FindDynamite( dyn ) ) {
// RF, if the dynamite is unarmed, ignore
if ( dyn->s.teamNum >= 4 ) {
continue;
}
for ( team = TEAM_AXIS; team <= TEAM_ALLIES; team++ ) {
vec3_t mins, maxs;
VectorAdd( dyn->r.currentOrigin, dyn->r.mins, mins );
VectorAdd( dyn->r.currentOrigin, dyn->r.maxs, maxs );
if ( target ) {
if ( target->s.eType == ET_EXPLOSIVE ) {
if ( !target->parent ) {
continue;
}
if ( BG_BBoxCollision( dyn->r.absmin, dyn->r.absmax, target->parent->r.absmin, target->parent->r.absmax ) ) {
if ( list ) {
list[count] = dyn->s.number;
}
count++;
break;
}
} else {
G_AdjustedDamageVec( target, dyn->r.currentOrigin, vec );
if ( ( VectorLengthSquared( vec ) <= SQR( dyn->splashRadius ) ) && CanDamage( target, dyn->r.currentOrigin ) ) {
if ( list ) {
list[count] = dyn->s.number;
}
count++;
break;
}
}
} else {
for ( trav = G_FindDynamiteTargetForTeam( NULL, team ); trav; trav = G_FindDynamiteTargetForTeam( trav->parent, team ) ) {
if ( trav->s.eType == ET_EXPLOSIVE ) {
if ( !trav->parent ) {
continue;
}
if ( BG_BBoxCollision( dyn->r.absmin, dyn->r.absmax, trav->parent->r.absmin, trav->parent->r.absmax ) ) {
if ( list ) {
list[count] = dyn->s.number;
}
count++;
break;
}
} else {
G_AdjustedDamageVec( trav, dyn->r.currentOrigin, vec );
if ( ( VectorLengthSquared( vec ) <= SQR( dyn->splashRadius ) ) && CanDamage( trav, dyn->r.currentOrigin ) ) {
if ( list ) {
list[count] = dyn->s.number;
}
count++;
break;
}
}
}
}
if ( list && count >= listSize ) {
break;
}
}
}
return count;
}
/*
==================
BotGetConstructibles
==================
*/
int BotGetConstructibles( team_t team, int *list, int listSize, qboolean ignoreBuilt ) {
gentity_t *trav, *targ;
int count = 0;
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_OBJECTIVE_INFO ) ) ) {
if ( !trav->r.linked ) {
continue;
}
targ = G_ConstructionForTeam( trav, team );
if ( !targ ) {
continue;
}
if ( ignoreBuilt && G_ConstructionIsFullyBuilt( targ ) ) {
continue;
}
if ( listSize >= 0 ) {
if ( list ) {
list[count] = targ->s.number;
}
count++;
}
if ( listSize > 0 && list && count >= listSize ) {
break;
}
}
return count;
}
/*
==================
BotNumTeamMembers
==================
*/
int BotNumTeamMembers( int team ) {
gclient_t *cl;
int i, cnt;
cl = &level.clients[0];
cnt = 0;
for ( i = 0; i < level.maxclients; i++, cl++ ) {
if ( !( cl->pers.connected == CON_CONNECTED ) ) {
continue;
}
if ( cl->sess.sessionTeam == team ) {
cnt++;
}
}
//
return cnt;
}
/*
==================
BotNumTeamClasses
==================
*/
int BotNumTeamClasses( team_t team, int mpClass, int ignore ) {
gclient_t *cl;
int i, cnt, j;
cnt = 0;
for ( i = 0; i < level.numConnectedClients; i++ ) {
j = level.sortedClients[i];
if ( j == ignore ) {
continue;
}
if ( !g_entities[j].inuse ) {
continue;
}
cl = &level.clients[j];
if ( cl->sess.sessionTeam != team ) {
continue;
}
if ( g_entities[j].r.svFlags & SVF_BOT ) {
if ( BotIsDead( &botstates[j] ) ) {
if ( botstates[j].mpClass != mpClass ) {
continue;
}
} else {
if ( cl->sess.playerType != mpClass ) {
continue;
}
}
} else {
if ( cl->ps.pm_flags & PMF_LIMBO ) {
if ( cl->sess.latchPlayerType != mpClass ) {
continue;
}
} else {
if ( cl->sess.playerType != mpClass ) {
continue;
}
}
}
cnt++;
}
return cnt;
}
/*
==================
BotCheckNeedEngineer
==================
*/
qboolean BotCheckNeedEngineer( bot_state_t *bs, team_t team ) {
static int teamLastTime[2] = { 0, 0 };
int* lastTime = &teamLastTime[ team == TEAM_AXIS ? 0 : 1 ];
// Gordon: want a couple at the start
if ( level.time - level.startTime < 20000 ) {
if ( BotNumTeamClasses( team, PC_ENGINEER, bs->client ) < 2 ) {
return qtrue;
} else {
return qfalse;
}
}
if ( *lastTime && *lastTime <= level.time && *lastTime > level.time - 10000 ) {
return qfalse; // only check every 10 seconds, to prevent everyone from changing class at once
}
*lastTime = level.time;
if ( bs->last_fire > level.time - 10000 ) {
return qfalse;
}
// flag checks
if ( BotCarryingFlag( bs->client ) || ( bs->leader > -1 && BotCarryingFlag( bs->leader ) ) ) { // dont abandon a leader that needs us
return qfalse;
}
if ( BotNumTeamClasses( team, PC_ENGINEER, bs->client ) > 0 ) {
return qfalse;
}
if ( !BotGetTargetExplosives( team, NULL, 0, qfalse ) && !BotGetConstructibles( team, NULL, 0, qtrue ) ) {
return qfalse;
}
//
// we need engineer's
return qtrue;
}
/*
==================
BotSuggestClass
==================
*/
int BotSuggestClass( bot_state_t *bs, team_t team ) {
gclient_t *cl;
int i;
int numRequired[NUM_PLAYER_CLASSES];
// int list[10], numList;
int numTeamMembers;
int classPriority[NUM_PLAYER_CLASSES] = {PC_ENGINEER, PC_SOLDIER, PC_MEDIC, PC_FIELDOPS, PC_COVERTOPS};
// char userinfo[MAX_INFO_STRING], *str;
float bestDiff, diff;
int bestClass;
qboolean needEngineers = qfalse;
int lastMg42Death = ( team == TEAM_ALLIES ? level.alliesMG42Counter : level.axisMG42Counter );
cl = &level.clients[bs->entitynum];
// if we have a specified class, then use that
/* trap_GetUserinfo( bs->client, userinfo, sizeof(userinfo) );
if ((str = Info_ValueForKey( userinfo, "pClass" )) && strlen(str)) {
i = atoi(str);
if (i > 0) {
return i-1;
}
}*/
// quick test for engineers
if ( BotCheckNeedEngineer( bs, team ) ) {
return PC_ENGINEER;
}
memset( numRequired, 0, sizeof( numRequired ) );
numTeamMembers = BotNumTeamMembers( team );
// do we need to destroy barriers?
if ( BotGetTargetExplosives( team, NULL, 0, qtrue ) || BotGetConstructibles( team, NULL, 0, qtrue ) ) {
needEngineers = qtrue;
} else {
numRequired[PC_ENGINEER] = 0;
}
if ( needEngineers ) {
if ( numTeamMembers <= 3 ) {
numRequired[PC_ENGINEER] = 1;
} else {
numRequired[PC_ENGINEER] = (int)ceil( numTeamMembers / 3.f );
}
}
// we should have at least one of each other class
numTeamMembers -= numRequired[PC_ENGINEER];
numRequired[PC_SOLDIER] = numTeamMembers / 2.f > 1 ? numTeamMembers / 2.f : 1;
numRequired[PC_COVERTOPS] = numRequired[PC_FIELDOPS] = numRequired[PC_MEDIC] = ( numTeamMembers / 6.f ) > 1 ? ( numTeamMembers / 6.f ) : 1;
//
// special cases
if ( lastMg42Death && ( ( level.time - lastMg42Death ) < ( 30 * 1000 ) ) ) {
// use panzers to clear mg42 nests
numRequired[PC_SOLDIER] = ( numTeamMembers - 3 > numRequired[PC_SOLDIER] ? numTeamMembers - 3 : numRequired[PC_SOLDIER] );
numRequired[PC_COVERTOPS] = numRequired[PC_FIELDOPS] = numRequired[PC_MEDIC] = 1;
}
// allocate classes in order of priority
bestDiff = 1.0;
bestClass = -1;
for ( i = 0; i < NUM_PLAYER_CLASSES; i++ ) {
if ( !numRequired[classPriority[i]] ) {
continue;
}
diff = BotNumTeamClasses( team, classPriority[i], bs->client ) / (float)numRequired[classPriority[i]];
if ( bestDiff > diff ) {
// we need one of these
bestDiff = diff;
bestClass = classPriority[i];
}
}
if ( bestClass >= 0 ) {
return bestClass;
}
if ( level.time < level.startTime + 20000 ) {
return PC_SOLDIER;
}
// Gordon: FIXME: balanace it? and why not engineer, they ARE useful outside of constructing stuff etc...
// not important, so just pick at random
while ( ( i = rand() % NUM_PLAYER_CLASSES ) == PC_ENGINEER ) ;
return i;
}
/*
==================
BotSuggestWeapon
==================
*/
int BotSuggestWeapon( bot_state_t *bs, team_t team ) {
int i, r;
gentity_t *trav;
qboolean noSniper = qfalse;
int lastMg42Death = ( team == TEAM_ALLIES ? level.alliesMG42Counter : level.axisMG42Counter );
// char userinfo[MAX_INFO_STRING], *str;
//
// if we have a specified weapon, then use that
/* trap_GetUserinfo( bs->client, userinfo, sizeof(userinfo) );
if ((str = Info_ValueForKey( userinfo, "pWeapon" )) && Q_stricmp(str, "ANY") && (i = atoi(str))) {
return i;
}*/
//
// special cases
if ( level.captureFlagMode ) {
// if defending team, try to get some snipers at the start
if ( ( bs->mpClass == PC_COVERTOPS ) && ( rand() % 3 ) && ( level.time < level.startTime + 120000 ) && ( bs->mpTeam != level.attackingTeam ) ) {
r = rand() % 2;
switch ( r ) {
default: return WP_FG42;
case 1:
switch ( bs->mpTeam ) {
case TEAM_AXIS:
return WP_K43;
default:
return WP_GARAND;
}
}
}
// if attacking, use panzers to clear defenses
if ( ( bs->mpClass == PC_SOLDIER ) && ( rand() % 3 ) && ( level.time < level.startTime + 30000 ) && ( bs->mpTeam == level.attackingTeam ) ) {
//return 4; // panzer
return WP_PANZERFAUST;
}
// if GT_WOLF, and checkpoint is owned by opposition, then no point in having snipers
trav = NULL;
while ( ( trav = BotFindNextStaticEntity( trav, BOTSTATICENTITY_CHECKPOINT ) ) ) {
if ( trav->count == level.attackingTeam ) {
break;
}
}
if ( trav ) {
// checkpoints are not defended
noSniper = qtrue;
}
}
if ( !noSniper && !( rand() % 2 ) && ( bs->mpClass == PC_COVERTOPS ) ) {
// are there sniper spots available?
i = BotBestSniperSpot( bs );
if ( i >= 0 ) {
// sniper spots available!
g_entities[i].missionLevel = 0; // dont avoid this spot
switch ( bs->mpTeam ) {
case TEAM_AXIS:
return WP_K43;
default:
return WP_GARAND;
}
}
}
// else choose at random
switch ( bs->mpClass ) {
case PC_ENGINEER:
r = rand() % 2;
switch ( bs->mpTeam ) {
case TEAM_AXIS:
switch ( r ) {
default: return WP_KAR98;
case 1: return WP_MP40;
}
default:
switch ( r ) {
default: return WP_CARBINE;
case 1: return WP_THOMPSON;
}
}
case PC_SOLDIER:
if ( lastMg42Death && ( level.time - lastMg42Death ) < ( 60 * 1000 ) ) {
// Gordon: much greater chance of panzerfaust
r = rand() % 12;
} else {
r = rand() % 3;
}
switch ( r ) {
default: return WP_PANZERFAUST;
case 1: return WP_FLAMETHROWER;
case 2: return WP_MOBILE_MG42;
//case 3: return WP_MORTAR; // they dont understand this yet
case 0:
switch ( bs->mpTeam ) {
case TEAM_AXIS:
return WP_MP40;
default:
return WP_THOMPSON;
}
}
break;
case PC_COVERTOPS:
r = rand() % 3;
switch ( r ) {
default: return WP_FG42;
case 1:
switch ( bs->mpTeam ) {
case TEAM_AXIS:
return WP_K43;
default:
return WP_GARAND;
}
case 2: return WP_STEN;
}
break;
}
switch ( bs->mpTeam ) {
case TEAM_AXIS:
return WP_MP40;
default:
return WP_THOMPSON;
}
}
/*
==================
BotCTFOrders
==================
*/
void BotCTFOrders_BothFlagsNotAtBase( bot_state_t *bs ) {
int numteammates, defenders, attackers, i, other;
int teammates[MAX_CLIENTS];
char name[MAX_NETNAME], carriername[MAX_NETNAME];
numteammates = BotSortTeamMatesByBaseTravelTime( bs, teammates, sizeof( teammates ) );
//different orders based on the number of team mates
switch ( bs->numteammates ) {
case 1: break;
case 2:
{
//tell the one not carrying the flag to attack the enemy base
if ( teammates[0] != bs->flagcarrier ) {
other = teammates[0];
} else { other = teammates[1];}
ClientName( other, name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, other );
break;
}
case 3:
{
//tell the one closest to the base not carrying the flag to accompany the flag carrier
if ( teammates[0] != bs->flagcarrier ) {
other = teammates[0];
} else { other = teammates[1];}
ClientName( other, name, sizeof( name ) );
ClientName( bs->flagcarrier, carriername, sizeof( carriername ) );
if ( bs->flagcarrier == bs->client ) {
BotAI_BotInitialChat( bs, "cmd_accompanyme", name, NULL );
} else {
BotAI_BotInitialChat( bs, "cmd_accompany", name, carriername, NULL );
}
BotSayTeamOrder( bs, other );
//tell the one furthest from the the base not carrying the flag to get the enemy flag
if ( teammates[2] != bs->flagcarrier ) {
other = teammates[2];
} else { other = teammates[1];}
ClientName( other, name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, other );
break;
}
default:
{
defenders = (int) ( float ) numteammates * 0.4 + 0.5;
attackers = (int) ( float ) numteammates * 0.5 + 0.5;
ClientName( bs->flagcarrier, carriername, sizeof( carriername ) );
for ( i = 0; i < defenders; i++ ) {
//
if ( teammates[i] == bs->flagcarrier ) {
continue;
}
//
ClientName( teammates[i], name, sizeof( name ) );
if ( bs->flagcarrier == bs->client ) {
BotAI_BotInitialChat( bs, "cmd_accompanyme", name, NULL );
} else {
BotAI_BotInitialChat( bs, "cmd_accompany", name, carriername, NULL );
}
BotSayTeamOrder( bs, teammates[i] );
}
for ( i = 0; i < attackers; i++ ) {
//
if ( teammates[numteammates - i - 1] == bs->flagcarrier ) {
continue;
}
//
ClientName( teammates[numteammates - i - 1], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, teammates[numteammates - i - 1] );
}
//
break;
}
}
}
/*
==================
BotCTFOrders
==================
*/
void BotCTFOrders_FlagNotAtBase( bot_state_t *bs ) {
int numteammates, defenders, attackers, i;
int teammates[MAX_CLIENTS];
char name[MAX_NETNAME];
numteammates = BotSortTeamMatesByBaseTravelTime( bs, teammates, sizeof( teammates ) );
//different orders based on the number of team mates
switch ( bs->numteammates ) {
case 1: break;
case 2:
{
//the one closest to the base will defend the base
ClientName( teammates[0], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, teammates[0] );
//the other will get the flag
ClientName( teammates[1], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, teammates[1] );
break;
}
case 3:
{
//the one closest to the base will defend the base
ClientName( teammates[0], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, teammates[0] );
//the other two get the flag
ClientName( teammates[1], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, teammates[1] );
//
ClientName( teammates[2], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, teammates[2] );
break;
}
default:
{
defenders = (int) ( float ) numteammates * 0.3 + 0.5;
attackers = (int) ( float ) numteammates * 0.5 + 0.5;
for ( i = 0; i < defenders; i++ ) {
//
ClientName( teammates[i], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, teammates[i] );
}
for ( i = 0; i < attackers; i++ ) {
//
ClientName( teammates[numteammates - i - 1], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, teammates[numteammates - i - 1] );
}
//
break;
}
}
}
/*
==================
BotCTFOrders
==================
*/
void BotCTFOrders_EnemyFlagNotAtBase( bot_state_t *bs ) {
int numteammates, defenders, attackers, i, other;
int teammates[MAX_CLIENTS];
char name[MAX_NETNAME], carriername[MAX_NETNAME];
numteammates = BotSortTeamMatesByBaseTravelTime( bs, teammates, sizeof( teammates ) );
//different orders based on the number of team mates
switch ( numteammates ) {
case 1: break;
case 2:
{
//tell the one not carrying the flag to defend the base
if ( teammates[0] == bs->flagcarrier ) {
other = teammates[1];
} else { other = teammates[0];}
ClientName( other, name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, other );
break;
}
case 3:
{
//tell the one closest to the base not carrying the flag to defend the base
if ( teammates[0] != bs->flagcarrier ) {
other = teammates[0];
} else { other = teammates[1];}
ClientName( other, name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, other );
//tell the one furthest from the base not carrying the flag to accompany the flag carrier
if ( teammates[2] != bs->flagcarrier ) {
other = teammates[2];
} else { other = teammates[1];}
ClientName( other, name, sizeof( name ) );
ClientName( bs->flagcarrier, carriername, sizeof( carriername ) );
if ( bs->flagcarrier == bs->client ) {
BotAI_BotInitialChat( bs, "cmd_accompanyme", name, NULL );
} else {
BotAI_BotInitialChat( bs, "cmd_accompany", name, carriername, NULL );
}
BotSayTeamOrder( bs, other );
break;
}
default:
{
//40% will defend the base
defenders = (int) ( float ) numteammates * 0.4 + 0.5;
//50% accompanies the flag carrier
attackers = (int) ( float ) numteammates * 0.5 + 0.5;
for ( i = 0; i < defenders; i++ ) {
//
if ( teammates[i] == bs->flagcarrier ) {
continue;
}
ClientName( teammates[i], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, teammates[i] );
}
ClientName( bs->flagcarrier, carriername, sizeof( carriername ) );
for ( i = 0; i < attackers; i++ ) {
//
if ( teammates[numteammates - i - 1] == bs->flagcarrier ) {
continue;
}
//
ClientName( teammates[numteammates - i - 1], name, sizeof( name ) );
if ( bs->flagcarrier == bs->client ) {
BotAI_BotInitialChat( bs, "cmd_accompanyme", name, NULL );
} else {
BotAI_BotInitialChat( bs, "cmd_accompany", name, carriername, NULL );
}
BotSayTeamOrder( bs, teammates[numteammates - i - 1] );
}
//
break;
}
}
}
/*
==================
BotCTFOrders
==================
*/
void BotCTFOrders_BothFlagsAtBase( bot_state_t *bs ) {
int numteammates, defenders, attackers, i;
int teammates[MAX_CLIENTS];
char name[MAX_NETNAME];
// char buf[MAX_MESSAGE_SIZE];
numteammates = BotSortTeamMatesByBaseTravelTime( bs, teammates, sizeof( teammates ) );
//different orders based on the number of team mates
switch ( numteammates ) {
case 1: break;
case 2:
{
//the one closest to the base will defend the base
ClientName( teammates[0], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, teammates[0] );
//the other will get the flag
ClientName( teammates[1], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, teammates[1] );
break;
}
case 3:
{
//the one closest to the base will defend the base
ClientName( teammates[0], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, teammates[0] );
//the second one closest to the base will defend the base
ClientName( teammates[1], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, teammates[1] );
//the other will get the flag
ClientName( teammates[2], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, teammates[2] );
break;
}
default:
{
defenders = (int) ( float ) numteammates * 0.5 + 0.5;
attackers = (int) ( float ) numteammates * 0.3 + 0.5;
for ( i = 0; i < defenders; i++ ) {
//
ClientName( teammates[i], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_defendbase", name, NULL );
BotSayTeamOrder( bs, teammates[i] );
}
for ( i = 0; i < attackers; i++ ) {
//
ClientName( teammates[numteammates - i - 1], name, sizeof( name ) );
BotAI_BotInitialChat( bs, "cmd_getflag", name, NULL );
BotSayTeamOrder( bs, teammates[numteammates - i - 1] );
}
//
break;
}
}
}
/*
==================
BotTeamOrders
==================
*/
void BotTeamOrders( bot_state_t *bs ) {
//no teamplay orders at this time
}
// Start - TAT 9/20/2002
// Covert Ops bot searches for a body to steal the uniform from
qboolean BotClass_CovertOpsCheckDisguises( bot_state_t *bs, int maxTravel, bot_goal_t *goal ) {
gentity_t *trav;
int t, area, best = -1, bestTravel, bestArea = -1; // Arnout: bestArea was not initialized
bot_goal_t target;
int list[32], numList;
vec3_t loc;
//if we are not covert ops
if ( bs->sess.playerType != PC_COVERTOPS ) {
return qfalse;
}
bestTravel = maxTravel;
trav = NULL;
// loop through all the corpses in the world
while ( ( trav = G_Find( trav, FOFS( classname ), "corpse" ) ) )
{
// if on the same team
if ( OnSameTeam( BotGetEntity( bs->client ), trav ) ) {
continue;
}
// make sure there isn't already a covertop snagging their getup
numList = BotNumTeamMatesWithTargetByClass( bs, trav->s.number, list, 32, PC_COVERTOPS );
if ( numList ) {
numList = BotReduceListByTravelTime( list, numList, BotGetOrigin( trav->s.number ), BotGetArea( trav->s.number ), BotTravelTimeToEntity( bs, trav->s.number ) );
}
if ( numList ) {
continue;
}
//
t = 0;
VectorCopy( trav->r.currentOrigin, loc );
loc[2] += 30;
// check the route to them
area = trap_AAS_PointAreaNum( loc );
if ( area ) {
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, area, bs->tfl );
}
if ( t && t < bestTravel ) {
BotClearGoal( &target );
target.entitynum = trav->s.number;
target.areanum = area;
VectorCopy( trav->r.mins, target.mins );
VectorCopy( trav->r.maxs, target.maxs );
VectorCopy( loc, target.origin );
//
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_HIGH ) ) {
best = trav->s.number;
bestTravel = t;
bestArea = area;
}
}
}
// did we find someone
if ( best >= 0 ) {
// yes, so copy the goal over
*goal = target;
// and return success
return qtrue;
}
//
return qfalse;
}
// End - TAT 9/20/2002
/*
==============
BotClass_MedicCheckRevives
==============
*/
qboolean BotClass_MedicCheckRevives( bot_state_t *bs, int maxtravel, bot_goal_t *goal, qboolean lookForBots ) {
gentity_t *trav;
int i, t, area, best = -1, bestTravel, bestArea = -1; // Arnout: bestArea was not initialized
int teammates[64], numTeammates;
bot_goal_t target;
int list[32], numList;
//if we are not medic
if ( bs->sess.playerType != PC_MEDIC ) {
return qfalse;
}
if ( !BotGotEnoughAmmoForWeapon( bs, WP_MEDIC_SYRINGE ) ) {
return qfalse;
}
bestTravel = maxtravel;
numTeammates = BotNumTeamMates( bs, teammates, 64 );
for ( i = 0; i < numTeammates; i++ ) {
trav = &g_entities[teammates[i]];
if ( trav->botIgnoreHealthTime > level.time ) {
continue;
}
if ( trav->client->ps.pm_type != PM_DEAD ) {
continue;
}
if ( trav->client->ps.pm_flags & PMF_LIMBO ) {
continue;
}
// make sure there isn't already another medic helping them
if ( ( numList = BotNumTeamMatesWithTargetByClass( bs, teammates[i], list, 32, PC_MEDIC ) ) ) {
if ( BotReduceListByTravelTime( list, numList, BotGetOrigin( teammates[i] ), BotGetArea( teammates[i] ), BotTravelTimeToEntity( bs, teammates[i] ) ) ) {
continue;
}
}
// check the route to them
area = BotGetArea( trav->s.number ); //trap_AAS_PointAreaNum(trav->r.currentOrigin);
if ( !area ) {
continue;
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, area, bs->tfl );
if ( !t ) {
continue;
}
if ( t < bestTravel ) {
BotClearGoal( &target );
target.entitynum = i;
target.areanum = area;
VectorCopy( g_entities[teammates[i]].r.mins, target.mins );
VectorCopy( g_entities[teammates[i]].r.maxs, target.maxs );
VectorCopy( g_entities[teammates[i]].r.currentOrigin, target.origin );
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_HIGH ) ) {
best = teammates[i];
bestTravel = t;
bestArea = area;
}
}
}
if ( best >= 0 ) {
// make this our goal?
BotClearGoal( &target );
target.entitynum = best;
target.areanum = bestArea;
VectorCopy( g_entities[best].r.mins, target.mins );
VectorCopy( g_entities[best].r.maxs, target.maxs );
VectorCopy( g_entities[best].r.currentOrigin, target.origin );
*goal = target;
return qtrue;
}
return qfalse;
}
/*
==============
G_RequestedHealth
==============
*/
qboolean G_RequestedHealth( bot_state_t *bs, int client, qboolean clearRequest ) {
bot_chat_t *trav;
int i;
trav = bs->vchats;
for ( i = 0; i < MAX_VCHATS; i++, trav++ ) {
if ( !trav->time ) {
continue;
}
if ( trav->time < level.time - 8000 ) {
trav->time = 0;
continue;
}
if ( trav->client != client ) {
continue;
}
if ( !OnSameTeam( BotGetEntity( trav->client ), BotGetEntity( client ) ) ) {
continue;
}
if ( trav->id != VCHAT_MEDIC ) {
continue;
}
// they want health!
if ( clearRequest ) {
trav->time = 0; // chat has been processed
}
return qtrue;
}
return qfalse;
}
/*
==============
BotClass_MedicCheckGiveHealth
==============
*/
qboolean BotClass_MedicCheckGiveHealth( bot_state_t *bs, int maxTravelTime, bot_goal_t *goal ) {
gentity_t *trav;
int i, area, best = -1, bestArea = -1; // Arnout: bestArea was not initialized
int time;
int bestTravelTime = maxTravelTime;
int teammates[64], numTeammates;
bot_goal_t target;
int list[32], numList;
//if we are not medic
if ( bs->sess.playerType != PC_MEDIC ) {
return qfalse;
}
if ( !BotWeaponCharged( bs, WP_MEDKIT ) ) {
return qfalse;
}
numTeammates = BotNumTeamMates( bs, teammates, 64 );
for ( i = 0; i < numTeammates; i++ ) {
float scale;
trav = &g_entities[teammates[i]];
scale = BotHealthScale( trav - g_entities );
if ( scale <= 0 || scale >= 1.f ) {
continue;
}
if ( trav->botIgnoreHealthTime > level.time ) {
continue;
}
if ( trav->s.number == bs->target_goal.entitynum ) {
continue;
}
if ( trav->client->sess.playerType == PC_MEDIC ) {
continue;
}
if ( trav->client->ps.pm_type != PM_NORMAL ) {
continue;
}
// make sure there isn't already another medic helping them
if ( ( numList = BotNumTeamMatesWithTargetByClass( bs, teammates[i], list, 32, PC_MEDIC ) ) ) {
if ( BotReduceListByTravelTime( list, numList, BotGetOrigin( teammates[i] ), BotGetArea( teammates[i] ), BotTravelTimeToEntity( bs, teammates[i] ) ) ) {
continue;
}
}
// check the route to them
area = BotGetArea( trav->s.number ); //trap_AAS_PointAreaNum(trav->r.currentOrigin);
if ( !area ) {
continue;
}
time = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, area, bs->tfl );
if ( !time ) {
continue;
}
if ( !G_RequestedHealth( bs, trav->s.number, qfalse ) ) {
if ( !trap_InPVS( bs->origin, trav->r.currentOrigin ) ) {
continue;
}
if ( BotHealthScale( trav->s.number ) > 0.5 ) {
time += ( BotHealthScale( trav->s.number ) - 0.5 ) * 100 * 5;
}
} else {
time -= 1500;
if ( time <= 0 ) {
time = 1;
}
}
if ( time && time < bestTravelTime ) {
BotClearGoal( &target );
target.entitynum = i;
target.areanum = area;
VectorCopy( g_entities[teammates[i]].r.mins, target.mins );
VectorCopy( g_entities[teammates[i]].r.maxs, target.maxs );
VectorCopy( g_entities[teammates[i]].r.currentOrigin, target.origin );
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
best = teammates[i];
bestTravelTime = time;
bestArea = area;
}
}
}
if ( best >= 0 ) {
G_RequestedHealth( bs, best, qtrue );
// make this our goal?
BotClearGoal( &target );
target.entitynum = best;
target.areanum = bestArea;
VectorCopy( g_entities[best].r.mins, target.mins );
VectorCopy( g_entities[best].r.maxs, target.maxs );
VectorCopy( g_entities[best].r.currentOrigin, target.origin );
*goal = target;
return qtrue;
}
return qfalse;
}
/*
==============
G_RequestedAmmo
==============
*/
qboolean G_RequestedAmmo( bot_state_t *bs, int client, qboolean clear ) {
bot_chat_t *trav;
int i;
qboolean clearRequest = qfalse;
//
if ( client < 0 ) {
clearRequest = qtrue;
client = -client - 1;
}
//
trav = bs->vchats;
for ( i = 0; i < MAX_VCHATS; i++, trav++ ) {
if ( !trav->time ) {
continue;
}
if ( trav->time < level.time - 8000 ) {
trav->time = 0;
continue;
}
if ( trav->client != client ) {
continue;
}
if ( !OnSameTeam( BotGetEntity( trav->client ), BotGetEntity( client ) ) ) {
continue;
}
if ( trav->id != VCHAT_NEEDAMMO ) {
continue;
}
// they want ammo!
if ( clearRequest ) {
trav->time = 0; // chat has been processed
}
return qtrue;
}
//
return qfalse;
}
/*
==============
BotClass_LtCheckGiveAmmo
==============
*/
qboolean BotClass_LtCheckGiveAmmo( bot_state_t *bs, int maxTravelTime, bot_goal_t *goal ) {
gentity_t *trav;
int i, area = 0, best = -1, bestArea = -1; // Arnout: bestArea was not initialized
int time;
int bestTravelTime = maxTravelTime;
int teammates[64], numTeammates;
bot_goal_t target;
int list[32], numList;
//if we are not fieldops
if ( bs->sess.playerType != PC_FIELDOPS ) {
return qfalse;
}
if ( !BotWeaponCharged( bs, WP_AMMO ) ) {
return qfalse;
}
numTeammates = BotNumTeamMates( bs, teammates, 64 );
for ( i = 0; i < numTeammates; i++ ) {
trav = &g_entities[teammates[i]];
if ( trav->health <= 0 ) {
continue;
}
if ( trav->client->sess.playerType == PC_FIELDOPS ) {
continue; // fieldops's dont need ammo from us
}
if ( trav->botIgnoreAmmoTime > level.time ) {
continue;
}
if ( trav->s.number == bs->target_goal.entitynum ) {
continue;
}
// make sure there isn't already another field op helping them
if ( ( numList = BotNumTeamMatesWithTargetByClass( bs, teammates[i], list, 32, PC_FIELDOPS ) ) ) {
if ( BotReduceListByTravelTime( list, numList, BotGetOrigin( teammates[i] ), BotGetArea( teammates[i] ), BotTravelTimeToEntity( bs, teammates[i] ) ) ) {
continue;
}
}
time = 0;
if ( trav->client->ps.pm_type == PM_NORMAL ) {
// if they requested ammo, ignore check for PVS
if ( !G_RequestedAmmo( bs, teammates[i], qfalse ) ) {
// do they need ammo?
if ( !ClientNeedsAmmo( teammates[i] ) ) {
continue;
}
if ( !trap_InPVS( bs->origin, trav->r.currentOrigin ) ) {
continue;
}
} else {
if ( !ClientNeedsAmmo( teammates[i] ) ) {
continue;
}
}
// check the route to them
if ( !( area = BotGetArea( trav->s.number ) ) ) {
continue;
}
time = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, area, bs->tfl );
if ( !time ) {
continue;
}
if ( G_RequestedAmmo( bs, teammates[i], qfalse ) ) {
time -= 1500;
if ( time < 0 ) {
time = 1;
}
}
}
if ( time && time < bestTravelTime ) {
BotClearGoal( &target );
target.entitynum = i;
target.areanum = area;
VectorCopy( g_entities[teammates[i]].r.mins, target.mins );
VectorCopy( g_entities[teammates[i]].r.maxs, target.maxs );
VectorCopy( g_entities[teammates[i]].r.currentOrigin, target.origin );
if ( BotGoalWithinMovementAutonomy( bs, &target, BGU_LOW ) ) {
best = teammates[i];
bestTravelTime = time;
bestArea = area;
}
}
}
if ( best >= 0 ) {
G_RequestedAmmo( bs, best, qtrue ); // clear the request
// make this our goal
BotClearGoal( &target );
target.entitynum = best;
target.areanum = bestArea;
VectorCopy( g_entities[best].r.mins, target.mins );
VectorCopy( g_entities[best].r.maxs, target.maxs );
VectorCopy( g_entities[best].r.currentOrigin, target.origin );
*goal = target;
return qtrue;
}
return qfalse;
}
extern void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly );
// NOTE!!! must be in synch with enum table in ai_team.h
char *vchat_idstr[] =
{
"HealMe",
"NeedAmmo",
"NeedBackup",
"DynamitePlanted",
"GreatShot",
"Hi",
"Thanks",
"FollowMe",
"DropFlag",
"AggressionLow",
"AggressionMed",
"AggressionHigh",
"AggressionCycle",
"AutonomyLow",
"AutonomyMed",
"AutonomyHigh",
"AutonomyCycle",
// Start - TAT 8/21/2002
// corresponding strings from the .menu file that trigger the vchat events below
"HoldPosition",
// End - TAT 8/21/2002
// START xkan, 9/13/2002
"Prone",
// END xkan, 9/13/2002
"Crouch",
"Stand",
"PositionCycle",
"FireteamA",
"FireteamB",
"FireteamC",
"ReviveMe",
"HealTeam",
"TeamAmmo",
// End - TAT 9/23/2002
"CycleWeapon",
"BestWeapon",
"HelpMe",
"HelpTeam",
NULL
};
/*
===============
BotRecordVoiceChat
===============
*/
void BotRecordVoiceChat( int client, int destclient, const char *id, int mode, qboolean noResponse ) {
int i,j, vchat_id;
bot_chat_t *trav, *oldest;
bot_state_t *bs;
if ( destclient == client ) {
return;
}
if ( noResponse ) {
return;
}
vchat_id = -1;
for ( j = 0; vchat_idstr[j]; j++ ) {
if ( !Q_stricmp( id, vchat_idstr[j] ) ) {
vchat_id = j;
break;
}
}
if ( vchat_id == -1 ) {
return; // not a known vchat
}
bs = &botstates[destclient];
if ( !bs->inuse ) {
return;
}
// find a free chat slot
trav = bs->vchats;
oldest = NULL;
for ( i = 0; i < MAX_VCHATS; i++, trav++ ) {
if ( trav->time ) {
if ( !oldest || trav->time < oldest->time ) {
oldest = trav;
}
continue;
}
trav->id = vchat_id;
// RF, if this is a request to drop the flag from a teammate, then drop it
if ( trav->id == VCHAT_DROPFLAG ) {
if ( bs && BotSameTeam( bs, client ) && BotCarryingFlag( destclient ) ) {
BotDropFlag( bs );
}
}
// record it
oldest = NULL;
break;
}
if ( i == MAX_VCHATS ) {
trav = NULL;
}
if ( oldest ) {
trav = oldest;
trav->id = vchat_id;
}
if ( trav ) {
trav->client = client;
trav->mode = mode;
trav->time = level.time + 1200 + rand() % 2000;
BotCheckVoiceChatResponse( bs );
}
}
/*
===============
BotCheckVoiceChatResponse
===============
*/
void BotCheckVoiceChatResponse( bot_state_t *bs ) {
int i;
bot_chat_t *trav;
gentity_t *ent, *other;
qboolean clear;
ent = &g_entities[bs->client];
trav = bs->vchats;
for ( i = 0; i < MAX_VCHATS; i++, trav++ ) {
if ( !trav->time ) {
continue;
}
if ( trav->time < level.time ) {
continue;
}
if ( trav->time > level.time + 5000 ) {
memset( trav, 0, sizeof( *trav ) );
continue;
}
other = &g_entities[trav->client];
clear = qfalse;
switch ( trav->id ) {
case VCHAT_THANKS:
clear = qtrue;
if ( !BotSameTeam( bs, trav->client ) ) {
break;
}
// did we just help them?
if ( bs->last_helped_client != trav->client ) {
break;
}
if ( bs->last_helped_time < level.time - 5000 ) {
break;
}
// your welcome!
BotVoiceChatAfterIdleTime( bs->client, "Welcome", SAY_TEAM, 1000 + rand() % 1000, BOT_SHOWTEXT, 3000, qfalse );
break;
case VCHAT_GREATSHOT:
clear = qtrue;
// did we just kill them?
if ( other->health > 0 ) {
break;
}
if ( other->client->lasthurt_client != bs->client ) {
break;
}
if ( ent->client->lastKillTime > level.time - 10000 ) {
break;
}
// thanks!
BotSendVoiceChat( bs, "Thanks", SAY_ALL, 1000 + rand() % 1000, BOT_SHOWTEXT, qfalse );
break;
case VCHAT_HI:
clear = qtrue;
if ( other->client->sess.sessionTeam && !BotSameTeam( bs, trav->client ) ) {
break;
}
// if we dont feel like talking
if ( rand() % 100 > 50 ) {
break;
}
if ( other->client->sess.sessionTeam ) {
BotVoiceChatAfterIdleTime( bs->client, "Hi", SAY_TEAM, 1000 + rand() % 6000, BOT_SHOWTEXT, 7000, qfalse );
} else {
BotVoiceChatAfterIdleTime( bs->client, "Hi", SAY_ALL, 1000 + rand() % 6000, BOT_SHOWTEXT, 7000, qfalse );
}
break;
// Make these explicit requests
case VCHAT_NEEDAMMO:
case VCHAT_MEDIC:
break;
}
if ( clear ) {
memset( trav, 0, sizeof( *trav ) );
}
}
}
/*
===============
BotDelayedVoiceChat
===============
*/
void BotDelayedVoiceChat( gentity_t *ent ) {
bot_state_t *bs;
// Should we force the voice to play even if the bot is dead?
qboolean forceIsDead = ent->spawnflags & 1;
//
if ( level.intermissiontime ) {
G_FreeEntity( ent );
return;
}
//
bs = &botstates[ent->r.ownerNum];
if ( !bs->inuse ) {
G_FreeEntity( ent );
return;
}
//
if ( !forceIsDead && BotIsDead( bs ) ) {
G_FreeEntity( ent );
return;
}
//
if ( g_entities[ent->r.ownerNum].inuse && ent->count2 == g_entities[ent->r.ownerNum].client->pers.connectTime ) {
bs->last_voice_chat = level.time;
G_Voice( BotGetEntity( ent->r.ownerNum ), NULL, ent->missionLevel, ent->spawnitem, ent->count );
}
G_FreeEntity( ent );
}
/*
===============
BotSendVoiceChat
===============
*/
void BotSendVoiceChat( bot_state_t *bs, const char *id, int mode, int delay, qboolean voiceonly, qboolean forceIfDead ) {
gentity_t *thinker;
//
if ( level.intermissiontime ) {
return;
}
//
if ( !forceIfDead && BotIsDead( bs ) ) {
return;
}
//
bs->last_voice_chat = level.time;
//
if ( delay ) {
thinker = G_Spawn();
if ( !thinker ) {
return;
}
thinker->nextthink = level.time + delay;
thinker->think = BotDelayedVoiceChat;
thinker->spawnitem = (char *)id;
thinker->r.ownerNum = bs->client;
thinker->missionLevel = mode;
thinker->count = voiceonly;
thinker->count2 = g_entities[bs->client].client->pers.connectTime;
// We need to signal if this VO can play if the bot is dead
if ( forceIfDead ) {
thinker->spawnflags |= 1;
}
return;
}
G_Voice( BotGetEntity( bs->client ), NULL, mode, id, voiceonly );
}
/*
===============
BotVoiceChatAfterIdleTime
===============
*/
void BotVoiceChatAfterIdleTime( int client, const char *id, int mode, int delay, qboolean voiceonly, int idleTime, qboolean forceIfDead ) {
bot_state_t* bs = &botstates[client];
if ( !bs->inuse ) {
return;
}
if ( !forceIfDead && BotIsDead( bs ) ) {
return;
}
if ( bs->last_voice_chat && bs->last_voice_chat > level.time - idleTime ) {
return; // ignore
}
BotSendVoiceChat( bs, id, mode, delay, voiceonly, forceIfDead );
}
//
// AI_Team_Init
//
// Description: Blank out the AI Team information
// Written: 10/31/2002
//
void AI_Team_Init
(
AI_Team_t *thisOne
) {
// Reset the last Team VO time
thisOne->last_voice_chat = 0;
}
//
// AI_Team_Init
//
//
// AI_Team_Init_All_Teams
//
// Description: Blank out the AI Team information for All teams
// Written: 10/31/2002
//
void AI_Team_Init_All_Teams
(
) {
// Local Variables ////////////////////////////////////////////////////////
int i;
///////////////////////////////////////////////////////////////////////////
// Loop through the AI teams and blank each one
for ( i = 0; i < TEAM_NUM_TEAMS; i++ )
{
// Blank the team
AI_Team_Init( &( g_aiTeam[i] ) );
} // for (i = 0; i < TEAM_NUM_TEAMS; i++)...
}
//
// AI_Team_Init_All_Teams
//
/*
===============
BotVoiceChatAfterTeamIdleTime
===============
Says the specified line if the TEAM has been idle for the specified time.
*/
void BotVoiceChatAfterTeamIdleTime( int client, const char *id, int mode, int delay, qboolean voiceonly, int idleTime, qboolean forceIfDead ) {
bot_state_t* bs = &botstates[client];
if ( !bs->inuse ) {
return;
}
//
if ( !forceIfDead && BotIsDead( bs ) ) {
return;
}
// If we had a team voice chat too recently
if ( g_aiTeam[bs->sess.sessionTeam].last_voice_chat && ( g_aiTeam[bs->sess.sessionTeam].last_voice_chat > level.time - idleTime ) ) {
// Ignore this request
return;
}
// We'll have chatted now
g_aiTeam[bs->sess.sessionTeam].last_voice_chat = level.time;
// Say the line after the specified delay
BotSendVoiceChat( bs, id, mode, delay, voiceonly, forceIfDead );
}
void G_SpawnGEntityFromSpawnVars( void );
char *G_AddSpawnVarToken( const char *string );
/*
===============
BotSpawnSpecialEntities
===============
*/
void BotSpawnSpecialEntities( void ) {
vmCvar_t cvar_mapname;
char keyname[MAX_QPATH];
char *com_token;
char string[8192], *pStr;
char filename[MAX_QPATH];
int len;
fileHandle_t f;
// HACK, spawn special entities that would usually be compiled into maps
trap_Cvar_Register( &cvar_mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
Com_sprintf( filename, sizeof( filename ), "maps/%s.botents", cvar_mapname.string );
if ( ( len = trap_FS_FOpenFile( filename, &f, FS_READ ) ) < 0 ) {
return;
}
if ( len >= sizeof( string ) ) {
G_Error( "BotSpawnSpecialEntities: (%s) file is too big", filename );
}
memset( string, 0, sizeof( string ) );
trap_FS_Read( string, len, f );
trap_FS_FCloseFile( f );
pStr = string;
G_Printf( "Enable spawning!\n" );
level.spawning = qtrue;
while ( 1 ) {
level.numSpawnVars = 0;
level.numSpawnVarChars = 0;
// parse the opening brace
com_token = COM_Parse( &pStr );
if ( !com_token || !com_token[0] ) {
// end of spawn string
return;
}
if ( com_token[0] != '{' ) {
G_Error( "BotSpawnSpecialEntities: (%s) found %s when expecting {", filename, com_token );
}
// go through all the key / value pairs
while ( 1 ) {
// parse key
com_token = COM_Parse( &pStr );
if ( !com_token || !com_token[0] ) {
G_Error( "BotSpawnSpecialEntities: (%s) EOF without closing brace", filename );
}
Q_strncpyz( keyname, com_token, sizeof( keyname ) );
if ( keyname[0] == '}' ) {
break;
}
// parse value
com_token = COM_Parse( &pStr );
if ( !com_token || !com_token[0] ) {
G_Error( "BotSpawnSpecialEntities: (%s) EOF without closing brace", filename );
}
if ( com_token[0] == '}' ) {
G_Error( "BotSpawnSpecialEntities: (%s) closing brace without data", filename );
}
if ( level.numSpawnVars == MAX_SPAWN_VARS ) {
G_Error( "BotSpawnSpecialEntities: (%s) MAX_SPAWN_VARS", filename );
}
level.spawnVars[ level.numSpawnVars ][0] = G_AddSpawnVarToken( keyname );
level.spawnVars[ level.numSpawnVars ][1] = G_AddSpawnVarToken( com_token );
level.numSpawnVars++;
}
// spawn the entity
G_SpawnGEntityFromSpawnVars();
}
G_Printf( "Disable spawning!\n" );
level.spawning = qfalse;
return;
}
/*
==============
G_RequestedFollow
==============
*/
qboolean G_RequestedFollow( bot_state_t *bs, int client ) {
bot_chat_t *trav;
int i;
qboolean clearRequest = qfalse;
//
if ( client < 0 ) {
clearRequest = qtrue;
client = -client - 1;
}
//
trav = bs->vchats;
for ( i = 0; i < MAX_VCHATS; i++, trav++ ) {
if ( !trav->time ) {
continue;
}
if ( trav->time < level.time - 30000 ) {
trav->time = 0;
continue;
}
if ( trav->client != client ) {
continue;
}
if ( !OnSameTeam( BotGetEntity( trav->client ), BotGetEntity( client ) ) ) {
continue;
}
if ( trav->id != VCHAT_FOLLOWME && trav->id != VCHAT_NEEDBACKUP ) {
continue;
}
// they want us to follow!
//if (clearRequest)
trav->time = 0; // chat has been processed
return qtrue;
}
//
return qfalse;
}
/*
===============
BotGetLeader
===============
*/
int BotGetLeader( bot_state_t *bs, qboolean onlyRequested ) {
int i, j, t, best = -1, bestTime = 99999;
gentity_t *trav;
bot_goal_t goal;
int heavyWeapons[] = {WP_PANZERFAUST, WP_MOBILE_MG42, -1};
qboolean requested, bestRequested = qfalse;
int list[MAX_CLIENTS], numList;
float ourDist, *distances;
bot_state_t *obs;
//
// if we have a scripted leader
if ( bs->leader > -1 && ( bs->script.frameFlags & BSFFL_FOLLOW_LEADER ) && ( bs->leader == bs->script.entityNum ) ) {
return bs->leader;
}
// if our current leader is carrying the flag, stay with them
if ( bs->leader > -1 && BotCarryingFlag( bs->leader ) ) {
return bs->leader;
}
// if we have a scripting enforced movement autonomy, stay put, unless we are given a new scripted leader
if ( bs->leader == -1 && ( bs->script.flags & BSFL_FORCED_MOVEMENT_AUTONOMY ) ) {
return -1;
}
best = bs->leader;
//
// find a teammate that is close, and is worth protecting (heavy weapons)
for ( i = 0; i < level.numConnectedClients; i++, trav++ ) {
trav = &g_entities[level.sortedClients[i]];
if ( i == bs->client ) {
continue;
}
if ( bs->leader == i ) {
continue;
}
if ( trav->r.svFlags & SVF_BOT ) {
obs = &botstates[i];
} else {
obs = NULL;
}
if ( trav->health <= 0 ) {
continue;
}
if ( !BotSameTeam( bs, i ) ) {
continue;
}
if ( obs && obs->leader > -1 ) {
continue;
}
requested = qfalse;
if ( G_RequestedFollow( bs, i ) ) {
requested = qtrue;
}
if ( BotCarryingFlag( i ) ) {
requested = qtrue;
}
if ( !requested && obs ) {
// let us follow engineers only
if ( trav->client->sess.playerType != PC_ENGINEER ) {
continue; // if not a bot, only follow if requested
}
}
if ( !requested && bestRequested ) {
continue;
}
if ( !requested && bs->leader > -1 ) {
continue;
}
/* if (!requested && (trav->client->sess.playerType != PC_SOLDIER)) {
// follow engineers only if they have a critical task
if (trav->client->sess.playerType == PC_ENGINEER && obs) {
if (!(obs->ainode == AINode_MP_DynamiteTarget || obs->ainode == AINode_MP_ConstructibleTarget)) {
continue;
} else {
requested = qtrue;
}
} else {
continue;
}
}*/
if ( ( ourDist = VectorDistanceSquared( trav->r.currentOrigin, bs->origin ) ) > ( MAX_BOTLEADER_DIST * MAX_BOTLEADER_DIST ) ) {
continue;
}
if ( requested && ( !trap_InPVS( trav->r.currentOrigin, bs->origin ) || ( ourDist > ( 1024 * 1024 ) ) ) ) {
continue;
}
if ( ( numList = BotNumTeamMatesWithTarget( bs, i, list, MAX_CLIENTS ) ) >= 2 + ( requested ? 2 : 0 ) ) {
// if there are too many defenders, and we are the furthest away, dont defend
distances = BotSortPlayersByDistance( trav->r.currentOrigin, list, numList );
if ( distances[numList] < ourDist ) {
// we are the furthest
continue;
}
}
if ( !requested ) {
for ( j = 0; heavyWeapons[j] != -1; j++ ) {
if ( COM_BitCheck( trav->client->ps.weapons, heavyWeapons[j] ) ) {
break;
}
}
if ( heavyWeapons[j] == -1 ) {
continue;
}
}
// check travel time
if ( !BotGoalForEntity( bs, i, &goal, BGU_MEDIUM ) && !requested ) {
continue; // NOTE: BotGoalForEntity() must be called first, so that if it fails, but we requested it, it still goes through with valid goal information
}
t = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, goal.areanum, bs->tfl );
if ( !t || t > MAX_BOTLEADER_TRAVEL ) {
continue;
}
if ( ( bestRequested == requested ) && ( t > bestTime ) ) {
continue;
}
// we have a valid leader
if ( requested ) {
j = rand() % 2;
if ( j == 0 ) {
BotVoiceChatAfterIdleTime( bs->client, "WhereTo", SAY_TEAM, 1000 + rand() % 3000, BOT_SHOWTEXT, ( requested ? 4000 : 20000 ), qfalse );
} else if ( j == 1 ) {
BotVoiceChatAfterIdleTime( bs->client, "LetsGo", SAY_TEAM, 1000 + rand() % 3000, BOT_SHOWTEXT, ( requested ? 4000 : 20000 ), qfalse );
}
bs->lead_time = trap_AAS_Time() + 99999.0;
bestRequested = qtrue;
}
best = i;
}
//
return best;
}
int EntGetNumBotFollowers( int entNum ) {
return 0;
}
/*
==================
BotSetLeaderTagEnt
==================
*/
void BotSetLeaderTagEnt( bot_state_t *bs ) {
}
int BotEngagementFunc( bot_state_t *bs ) {
return 0;
}
int BotBehaviourFunc( bot_state_t *bs ) {
return 0;
}
int BotObjectiveFunc( bot_state_t *bs ) {
return 0;
}