/* =========================================================================== 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 . 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; }