codemp/game/ai_main.c

Go to the documentation of this file.
00001 // Copyright (C) 1999-2000 Id Software, Inc.
00002 //
00003 
00004 /*****************************************************************************
00005  * name:                ai_main.c
00006  *
00007  * desc:                Quake3 bot AI
00008  *
00009  * $Archive: /MissionPack/code/game/ai_main.c $
00010  * $Author: osman $ 
00011  * $Revision: 1.5 $
00012  * $Modtime: 6/06/01 1:11p $
00013  * $Date: 2003/03/15 23:43:59 $
00014  *
00015  *****************************************************************************/
00016 
00017 
00018 #include "g_local.h"
00019 #include "q_shared.h"
00020 #include "botlib.h"             //bot lib interface
00021 #include "be_aas.h"
00022 #include "be_ea.h"
00023 #include "be_ai_char.h"
00024 #include "be_ai_chat.h"
00025 #include "be_ai_gen.h"
00026 #include "be_ai_goal.h"
00027 #include "be_ai_move.h"
00028 #include "be_ai_weap.h"
00029 //
00030 #include "ai_main.h"
00031 #include "w_saber.h"
00032 //
00033 #include "chars.h"
00034 #include "inv.h"
00035 #include "syn.h"
00036 
00037 /*
00038 #define BOT_CTF_DEBUG   1
00039 */
00040 
00041 #define MAX_PATH                144
00042 
00043 #define BOT_THINK_TIME  0
00044 
00045 //bot states
00046 bot_state_t     *botstates[MAX_CLIENTS];
00047 //number of bots
00048 int numbots;
00049 //floating point time
00050 float floattime;
00051 //time to do a regular update
00052 float regularupdate_time;
00053 //
00054 
00055 //for siege:
00056 extern int rebel_attackers;
00057 extern int imperial_attackers;
00058 
00059 boteventtracker_t gBotEventTracker[MAX_CLIENTS];
00060 
00061 //rww - new bot cvars..
00062 vmCvar_t bot_forcepowers;
00063 vmCvar_t bot_forgimmick;
00064 vmCvar_t bot_honorableduelacceptance;
00065 vmCvar_t bot_pvstype;
00066 vmCvar_t bot_normgpath;
00067 #ifndef FINAL_BUILD
00068 vmCvar_t bot_getinthecarrr;
00069 #endif
00070 
00071 #ifdef _DEBUG
00072 vmCvar_t bot_nogoals;
00073 vmCvar_t bot_debugmessages;
00074 #endif
00075 
00076 vmCvar_t bot_attachments;
00077 vmCvar_t bot_camp;
00078 
00079 vmCvar_t bot_wp_info;
00080 vmCvar_t bot_wp_edit;
00081 vmCvar_t bot_wp_clearweight;
00082 vmCvar_t bot_wp_distconnect;
00083 vmCvar_t bot_wp_visconnect;
00084 //end rww
00085 
00086 wpobject_t *flagRed;
00087 wpobject_t *oFlagRed;
00088 wpobject_t *flagBlue;
00089 wpobject_t *oFlagBlue;
00090 
00091 gentity_t *eFlagRed;
00092 gentity_t *droppedRedFlag;
00093 gentity_t *eFlagBlue;
00094 gentity_t *droppedBlueFlag;
00095 
00096 char *ctfStateNames[] = {
00097         "CTFSTATE_NONE",
00098         "CTFSTATE_ATTACKER",
00099         "CTFSTATE_DEFENDER",
00100         "CTFSTATE_RETRIEVAL",
00101         "CTFSTATE_GUARDCARRIER",
00102         "CTFSTATE_GETFLAGHOME",
00103         "CTFSTATE_MAXCTFSTATES"
00104 };
00105 
00106 char *ctfStateDescriptions[] = {
00107         "I'm not occupied",
00108         "I'm attacking the enemy's base",
00109         "I'm defending our base",
00110         "I'm getting our flag back",
00111         "I'm escorting our flag carrier",
00112         "I've got the enemy's flag"
00113 };
00114 
00115 char *siegeStateDescriptions[] = {
00116         "I'm not occupied",
00117         "I'm attemtping to complete the current objective",
00118         "I'm preventing the enemy from completing their objective"
00119 };
00120 
00121 char *teamplayStateDescriptions[] = {
00122         "I'm not occupied",
00123         "I'm following my squad commander",
00124         "I'm assisting my commanding",
00125         "I'm attempting to regroup and form a new squad"
00126 };
00127 
00128 void BotStraightTPOrderCheck(gentity_t *ent, int ordernum, bot_state_t *bs)
00129 {
00130         switch (ordernum)
00131         {
00132         case 0:
00133                 if (bs->squadLeader == ent)
00134                 {
00135                         bs->teamplayState = 0;
00136                         bs->squadLeader = NULL;
00137                 }
00138                 break;
00139         case TEAMPLAYSTATE_FOLLOWING:
00140                 bs->teamplayState = ordernum;
00141                 bs->isSquadLeader = 0;
00142                 bs->squadLeader = ent;
00143                 bs->wpDestSwitchTime = 0;
00144                 break;
00145         case TEAMPLAYSTATE_ASSISTING:
00146                 bs->teamplayState = ordernum;
00147                 bs->isSquadLeader = 0;
00148                 bs->squadLeader = ent;
00149                 bs->wpDestSwitchTime = 0;
00150                 break;
00151         default:
00152                 bs->teamplayState = ordernum;
00153                 break;
00154         }
00155 }
00156 
00157 void BotSelectWeapon(int client, int weapon)
00158 {
00159         if (weapon <= WP_NONE)
00160         {
00161 //              assert(0);
00162                 return;
00163         }
00164         trap_EA_SelectWeapon(client, weapon);
00165 }
00166 
00167 void BotReportStatus(bot_state_t *bs)
00168 {
00169         if (g_gametype.integer == GT_TEAM)
00170         {
00171                 trap_EA_SayTeam(bs->client, teamplayStateDescriptions[bs->teamplayState]);
00172         }
00173         else if (g_gametype.integer == GT_SIEGE)
00174         {
00175                 trap_EA_SayTeam(bs->client, siegeStateDescriptions[bs->siegeState]);
00176         }
00177         else if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY)
00178         {
00179                 trap_EA_SayTeam(bs->client, ctfStateDescriptions[bs->ctfState]);
00180         }
00181 }
00182 
00183 //accept a team order from a player
00184 void BotOrder(gentity_t *ent, int clientnum, int ordernum)
00185 {
00186         int stateMin = 0;
00187         int stateMax = 0;
00188         int i = 0;
00189 
00190         if (!ent || !ent->client || !ent->client->sess.teamLeader)
00191         {
00192                 return;
00193         }
00194 
00195         if (clientnum != -1 && !botstates[clientnum])
00196         {
00197                 return;
00198         }
00199 
00200         if (clientnum != -1 && !OnSameTeam(ent, &g_entities[clientnum]))
00201         {
00202                 return;
00203         }
00204 
00205         if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY && g_gametype.integer != GT_SIEGE &&
00206                 g_gametype.integer != GT_TEAM)
00207         {
00208                 return;
00209         }
00210 
00211         if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY)
00212         {
00213                 stateMin = CTFSTATE_NONE;
00214                 stateMax = CTFSTATE_MAXCTFSTATES;
00215         }
00216         else if (g_gametype.integer == GT_SIEGE)
00217         {
00218                 stateMin = SIEGESTATE_NONE;
00219                 stateMax = SIEGESTATE_MAXSIEGESTATES;
00220         }
00221         else if (g_gametype.integer == GT_TEAM)
00222         {
00223                 stateMin = TEAMPLAYSTATE_NONE;
00224                 stateMax = TEAMPLAYSTATE_MAXTPSTATES;
00225         }
00226 
00227         if ((ordernum < stateMin && ordernum != -1) || ordernum >= stateMax)
00228         {
00229                 return;
00230         }
00231 
00232         if (clientnum != -1)
00233         {
00234                 if (ordernum == -1)
00235                 {
00236                         BotReportStatus(botstates[clientnum]);
00237                 }
00238                 else
00239                 {
00240                         BotStraightTPOrderCheck(ent, ordernum, botstates[clientnum]);
00241                         botstates[clientnum]->state_Forced = ordernum;
00242                         botstates[clientnum]->chatObject = ent;
00243                         botstates[clientnum]->chatAltObject = NULL;
00244                         if (BotDoChat(botstates[clientnum], "OrderAccepted", 1))
00245                         {
00246                                 botstates[clientnum]->chatTeam = 1;
00247                         }
00248                 }
00249         }
00250         else
00251         {
00252                 while (i < MAX_CLIENTS)
00253                 {
00254                         if (botstates[i] && OnSameTeam(ent, &g_entities[i]))
00255                         {
00256                                 if (ordernum == -1)
00257                                 {
00258                                         BotReportStatus(botstates[i]);
00259                                 }
00260                                 else
00261                                 {
00262                                         BotStraightTPOrderCheck(ent, ordernum, botstates[i]);
00263                                         botstates[i]->state_Forced = ordernum;
00264                                         botstates[i]->chatObject = ent;
00265                                         botstates[i]->chatAltObject = NULL;
00266                                         if (BotDoChat(botstates[i], "OrderAccepted", 0))
00267                                         {
00268                                                 botstates[i]->chatTeam = 1;
00269                                         }
00270                                 }
00271                         }
00272 
00273                         i++;
00274                 }
00275         }
00276 }
00277 
00278 //See if bot is mindtricked by the client in question
00279 int BotMindTricked(int botClient, int enemyClient)
00280 {
00281         forcedata_t *fd;
00282 
00283         if (!g_entities[enemyClient].client)
00284         {
00285                 return 0;
00286         }
00287         
00288         fd = &g_entities[enemyClient].client->ps.fd;
00289 
00290         if (!fd)
00291         {
00292                 return 0;
00293         }
00294 
00295         if (botClient > 47)
00296         {
00297                 if (fd->forceMindtrickTargetIndex4 & (1 << (botClient-48)))
00298                 {
00299                         return 1;
00300                 }
00301         }
00302         else if (botClient > 31)
00303         {
00304                 if (fd->forceMindtrickTargetIndex3 & (1 << (botClient-32)))
00305                 {
00306                         return 1;
00307                 }
00308         }
00309         else if (botClient > 15)
00310         {
00311                 if (fd->forceMindtrickTargetIndex2 & (1 << (botClient-16)))
00312                 {
00313                         return 1;
00314                 }
00315         }
00316         else
00317         {
00318                 if (fd->forceMindtrickTargetIndex & (1 << botClient))
00319                 {
00320                         return 1;
00321                 }
00322         }
00323 
00324         return 0;
00325 }
00326 
00327 int BotGetWeaponRange(bot_state_t *bs);
00328 int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent);
00329 
00330 void ExitLevel( void );
00331 
00332 void QDECL BotAI_Print(int type, char *fmt, ...) { return; }
00333 
00334 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower );
00335 
00336 int IsTeamplay(void)
00337 {
00338         if ( g_gametype.integer < GT_TEAM )
00339         {
00340                 return 0;
00341         }
00342 
00343         return 1;
00344 }
00345 
00346 /*
00347 ==================
00348 BotAI_GetClientState
00349 ==================
00350 */
00351 int BotAI_GetClientState( int clientNum, playerState_t *state ) {
00352         gentity_t       *ent;
00353 
00354         ent = &g_entities[clientNum];
00355         if ( !ent->inuse ) {
00356                 return qfalse;
00357         }
00358         if ( !ent->client ) {
00359                 return qfalse;
00360         }
00361 
00362         memcpy( state, &ent->client->ps, sizeof(playerState_t) );
00363         return qtrue;
00364 }
00365 
00366 /*
00367 ==================
00368 BotAI_GetEntityState
00369 ==================
00370 */
00371 int BotAI_GetEntityState( int entityNum, entityState_t *state ) {
00372         gentity_t       *ent;
00373 
00374         ent = &g_entities[entityNum];
00375         memset( state, 0, sizeof(entityState_t) );
00376         if (!ent->inuse) return qfalse;
00377         if (!ent->r.linked) return qfalse;
00378         if (ent->r.svFlags & SVF_NOCLIENT) return qfalse;
00379         memcpy( state, &ent->s, sizeof(entityState_t) );
00380         return qtrue;
00381 }
00382 
00383 /*
00384 ==================
00385 BotAI_GetSnapshotEntity
00386 ==================
00387 */
00388 int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) {
00389         int             entNum;
00390 
00391         entNum = trap_BotGetSnapshotEntity( clientNum, sequence );
00392         if ( entNum == -1 ) {
00393                 memset(state, 0, sizeof(entityState_t));
00394                 return -1;
00395         }
00396 
00397         BotAI_GetEntityState( entNum, state );
00398 
00399         return sequence + 1;
00400 }
00401 
00402 /*
00403 ==============
00404 BotEntityInfo
00405 ==============
00406 */
00407 void BotEntityInfo(int entnum, aas_entityinfo_t *info) {
00408         trap_AAS_EntityInfo(entnum, info);
00409 }
00410 
00411 /*
00412 ==============
00413 NumBots
00414 ==============
00415 */
00416 int NumBots(void) {
00417         return numbots;
00418 }
00419 
00420 /*
00421 ==============
00422 AngleDifference
00423 ==============
00424 */
00425 float AngleDifference(float ang1, float ang2) {
00426         float diff;
00427 
00428         diff = ang1 - ang2;
00429         if (ang1 > ang2) {
00430                 if (diff > 180.0) diff -= 360.0;
00431         }
00432         else {
00433                 if (diff < -180.0) diff += 360.0;
00434         }
00435         return diff;
00436 }
00437 
00438 /*
00439 ==============
00440 BotChangeViewAngle
00441 ==============
00442 */
00443 float BotChangeViewAngle(float angle, float ideal_angle, float speed) {
00444         float move;
00445 
00446         angle = AngleMod(angle);
00447         ideal_angle = AngleMod(ideal_angle);
00448         if (angle == ideal_angle) return angle;
00449         move = ideal_angle - angle;
00450         if (ideal_angle > angle) {
00451                 if (move > 180.0) move -= 360.0;
00452         }
00453         else {
00454                 if (move < -180.0) move += 360.0;
00455         }
00456         if (move > 0) {
00457                 if (move > speed) move = speed;
00458         }
00459         else {
00460                 if (move < -speed) move = -speed;
00461         }
00462         return AngleMod(angle + move);
00463 }
00464 
00465 /*
00466 ==============
00467 BotChangeViewAngles
00468 ==============
00469 */
00470 void BotChangeViewAngles(bot_state_t *bs, float thinktime) {
00471         float diff, factor, maxchange, anglespeed, disired_speed;
00472         int i;
00473 
00474         if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
00475         
00476         if (bs->currentEnemy && bs->frame_Enemy_Vis)
00477         {
00478                 if (bs->settings.skill <= 1)
00479                 {
00480                         factor = (bs->skills.turnspeed_combat*0.4f)*bs->settings.skill;
00481                 }
00482                 else if (bs->settings.skill <= 2)
00483                 {
00484                         factor = (bs->skills.turnspeed_combat*0.6f)*bs->settings.skill;
00485                 }
00486                 else if (bs->settings.skill <= 3)
00487                 {
00488                         factor = (bs->skills.turnspeed_combat*0.8f)*bs->settings.skill;
00489                 }
00490                 else
00491                 {
00492                         factor = bs->skills.turnspeed_combat*bs->settings.skill;
00493                 }
00494         }
00495         else
00496         {
00497                 factor = bs->skills.turnspeed;
00498         }
00499 
00500         if (factor > 1)
00501         {
00502                 factor = 1;
00503         }
00504         if (factor < 0.001)
00505         {
00506                 factor = 0.001f;
00507         }
00508 
00509         maxchange = bs->skills.maxturn;
00510 
00511         //if (maxchange < 240) maxchange = 240;
00512         maxchange *= thinktime;
00513         for (i = 0; i < 2; i++) {
00514                 bs->viewangles[i] = AngleMod(bs->viewangles[i]);
00515                 bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]);
00516                 diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]);
00517                 disired_speed = diff * factor;
00518                 bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed);
00519                 if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange;
00520                 if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange;
00521                 anglespeed = bs->viewanglespeed[i];
00522                 if (anglespeed > maxchange) anglespeed = maxchange;
00523                 if (anglespeed < -maxchange) anglespeed = -maxchange;
00524                 bs->viewangles[i] += anglespeed;
00525                 bs->viewangles[i] = AngleMod(bs->viewangles[i]);
00526                 bs->viewanglespeed[i] *= 0.45 * (1 - factor);
00527         }
00528         if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360;
00529         trap_EA_View(bs->client, bs->viewangles);
00530 }
00531 
00532 /*
00533 ==============
00534 BotInputToUserCommand
00535 ==============
00536 */
00537 void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time, int useTime) {
00538         vec3_t angles, forward, right;
00539         short temp;
00540         int j;
00541 
00542         //clear the whole structure
00543         memset(ucmd, 0, sizeof(usercmd_t));
00544         //
00545         //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed);
00546         //the duration for the user command in milli seconds
00547         ucmd->serverTime = time;
00548         //
00549         if (bi->actionflags & ACTION_DELAYEDJUMP) {
00550                 bi->actionflags |= ACTION_JUMP;
00551                 bi->actionflags &= ~ACTION_DELAYEDJUMP;
00552         }
00553         //set the buttons
00554         if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK;
00555         if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK;
00556         if (bi->actionflags & ACTION_ALT_ATTACK) ucmd->buttons |= BUTTON_ALT_ATTACK;
00557 //      if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
00558         if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
00559         if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE;
00560         if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING;
00561 
00562         if (bi->actionflags & ACTION_FORCEPOWER) ucmd->buttons |= BUTTON_FORCEPOWER;
00563 
00564         if (useTime < level.time && Q_irand(1, 10) < 5)
00565         { //for now just hit use randomly in case there's something useable around
00566                 ucmd->buttons |= BUTTON_USE;
00567         }
00568 #if 0
00569 // Here's an interesting bit.  The bots in TA used buttons to do additional gestures.
00570 // I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2.
00571 // We can always add some back in if we want though.
00572         if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE;
00573         if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE;
00574         if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG;
00575         if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE;
00576         if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL;
00577         if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME;
00578 #endif //0
00579 
00580         if (bi->weapon == WP_NONE)
00581         {
00582 #ifdef _DEBUG
00583 //              Com_Printf("WARNING: Bot tried to use WP_NONE!\n");
00584 #endif
00585                 bi->weapon = WP_BRYAR_PISTOL;
00586         }
00587 
00588         //
00589         ucmd->weapon = bi->weapon;
00590         //set the view angles
00591         //NOTE: the ucmd->angles are the angles WITHOUT the delta angles
00592         ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]);
00593         ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]);
00594         ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]);
00595         //subtract the delta angles
00596         for (j = 0; j < 3; j++) {
00597                 temp = ucmd->angles[j] - delta_angles[j];
00598                 ucmd->angles[j] = temp;
00599         }
00600         //NOTE: movement is relative to the REAL view angles
00601         //get the horizontal forward and right vector
00602         //get the pitch in the range [-180, 180]
00603         if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH];
00604         else angles[PITCH] = 0;
00605         angles[YAW] = bi->viewangles[YAW];
00606         angles[ROLL] = 0;
00607         AngleVectors(angles, forward, right, NULL);
00608         //bot input speed is in the range [0, 400]
00609         bi->speed = bi->speed * 127 / 400;
00610         //set the view independent movement
00611         ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed;
00612         ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed;
00613         ucmd->upmove = abs((int)(forward[2])) * bi->dir[2] * bi->speed;
00614         //normal keyboard movement
00615         if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127;
00616         if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127;
00617         if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127;
00618         if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127;
00619         //jump/moveup
00620         if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127;
00621         //crouch/movedown
00622         if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127;
00623         //
00624         //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove);
00625         //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime);
00626 }
00627 
00628 /*
00629 ==============
00630 BotUpdateInput
00631 ==============
00632 */
00633 void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) {
00634         bot_input_t bi;
00635         int j;
00636 
00637         //add the delta angles to the bot's current view angles
00638         for (j = 0; j < 3; j++) {
00639                 bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00640         }
00641         //change the bot view angles
00642         BotChangeViewAngles(bs, (float) elapsed_time / 1000);
00643         //retrieve the bot input
00644         trap_EA_GetInput(bs->client, (float) time / 1000, &bi);
00645         //respawn hack
00646         if (bi.actionflags & ACTION_RESPAWN) {
00647                 if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK);
00648         }
00649         //convert the bot input to a usercmd
00650         BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time, bs->noUseTime);
00651         //subtract the delta angles
00652         for (j = 0; j < 3; j++) {
00653                 bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00654         }
00655 }
00656 
00657 /*
00658 ==============
00659 BotAIRegularUpdate
00660 ==============
00661 */
00662 void BotAIRegularUpdate(void) {
00663         if (regularupdate_time < FloatTime()) {
00664                 trap_BotUpdateEntityItems();
00665                 regularupdate_time = FloatTime() + 0.3;
00666         }
00667 }
00668 
00669 /*
00670 ==============
00671 RemoveColorEscapeSequences
00672 ==============
00673 */
00674 void RemoveColorEscapeSequences( char *text ) {
00675         int i, l;
00676 
00677         l = 0;
00678         for ( i = 0; text[i]; i++ ) {
00679                 if (Q_IsColorString(&text[i])) {
00680                         i++;
00681                         continue;
00682                 }
00683                 if (text[i] > 0x7E)
00684                         continue;
00685                 text[l++] = text[i];
00686         }
00687         text[l] = '\0';
00688 }
00689 
00690 
00691 /*
00692 ==============
00693 BotAI
00694 ==============
00695 */
00696 int BotAI(int client, float thinktime) {
00697         bot_state_t *bs;
00698         char buf[1024], *args;
00699         int j;
00700 #ifdef _DEBUG
00701         int start = 0;
00702         int end = 0;
00703 #endif
00704 
00705         trap_EA_ResetInput(client);
00706         //
00707         bs = botstates[client];
00708         if (!bs || !bs->inuse) {
00709                 BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client);
00710                 return qfalse;
00711         }
00712 
00713         //retrieve the current client state
00714         BotAI_GetClientState( client, &bs->cur_ps );
00715 
00716         //retrieve any waiting server commands
00717         while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) {
00718                 //have buf point to the command and args to the command arguments
00719                 args = strchr( buf, ' ');
00720                 if (!args) continue;
00721                 *args++ = '\0';
00722 
00723                 //remove color espace sequences from the arguments
00724                 RemoveColorEscapeSequences( args );
00725 
00726                 if (!Q_stricmp(buf, "cp "))
00727                         { /*CenterPrintf*/ }
00728                 else if (!Q_stricmp(buf, "cs"))
00729                         { /*ConfigStringModified*/ }
00730                 else if (!Q_stricmp(buf, "scores"))
00731                         { /*FIXME: parse scores?*/ }
00732                 else if (!Q_stricmp(buf, "clientLevelShot"))
00733                         { /*ignore*/ }
00734         }
00735         //add the delta angles to the bot's current view angles
00736         for (j = 0; j < 3; j++) {
00737                 bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00738         }
00739         //increase the local time of the bot
00740         bs->ltime += thinktime;
00741         //
00742         bs->thinktime = thinktime;
00743         //origin of the bot
00744         VectorCopy(bs->cur_ps.origin, bs->origin);
00745         //eye coordinates of the bot
00746         VectorCopy(bs->cur_ps.origin, bs->eye);
00747         bs->eye[2] += bs->cur_ps.viewheight;
00748         //get the area the bot is in
00749 
00750 #ifdef _DEBUG
00751         start = trap_Milliseconds();
00752 #endif
00753         StandardBotAI(bs, thinktime);
00754 #ifdef _DEBUG
00755         end = trap_Milliseconds();
00756 
00757         trap_Cvar_Update(&bot_debugmessages);
00758 
00759         if (bot_debugmessages.integer)
00760         {
00761                 Com_Printf("Single AI frametime: %i\n", (end - start));
00762         }
00763 #endif
00764 
00765         //subtract the delta angles
00766         for (j = 0; j < 3; j++) {
00767                 bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00768         }
00769         //everything was ok
00770         return qtrue;
00771 }
00772 
00773 /*
00774 ==================
00775 BotScheduleBotThink
00776 ==================
00777 */
00778 void BotScheduleBotThink(void) {
00779         int i, botnum;
00780 
00781         botnum = 0;
00782 
00783         for( i = 0; i < MAX_CLIENTS; i++ ) {
00784                 if( !botstates[i] || !botstates[i]->inuse ) {
00785                         continue;
00786                 }
00787                 //initialize the bot think residual time
00788                 botstates[i]->botthink_residual = BOT_THINK_TIME * botnum / numbots;
00789                 botnum++;
00790         }
00791 }
00792 
00793 int PlayersInGame(void)
00794 {
00795         int i = 0;
00796         gentity_t *ent;
00797         int pl = 0;
00798 
00799         while (i < MAX_CLIENTS)
00800         {
00801                 ent = &g_entities[i];
00802 
00803                 if (ent && ent->client && ent->client->pers.connected == CON_CONNECTED)
00804                 {
00805                         pl++;
00806                 }
00807 
00808                 i++;
00809         }
00810 
00811         return pl;
00812 }
00813 
00814 /*
00815 ==============
00816 BotAISetupClient
00817 ==============
00818 */
00819 int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) {
00820         bot_state_t *bs;
00821 
00822         if (!botstates[client]) botstates[client] = (bot_state_t *) B_Alloc(sizeof(bot_state_t)); //G_Alloc(sizeof(bot_state_t));
00823                                                                                                                                                           //rww - G_Alloc bad! B_Alloc good.
00824 
00825         memset(botstates[client], 0, sizeof(bot_state_t));
00826 
00827         bs = botstates[client];
00828 
00829         if (bs && bs->inuse) {
00830                 BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client);
00831                 return qfalse;
00832         }
00833 
00834         memcpy(&bs->settings, settings, sizeof(bot_settings_t));
00835 
00836         bs->client = client; //need to know the client number before doing personality stuff
00837 
00838         //initialize weapon weight defaults..
00839         bs->botWeaponWeights[WP_NONE] = 0;
00840         bs->botWeaponWeights[WP_STUN_BATON] = 1;
00841         bs->botWeaponWeights[WP_SABER] = 10;
00842         bs->botWeaponWeights[WP_BRYAR_PISTOL] = 11;
00843         bs->botWeaponWeights[WP_BLASTER] = 12;
00844         bs->botWeaponWeights[WP_DISRUPTOR] = 13;
00845         bs->botWeaponWeights[WP_BOWCASTER] = 14;
00846         bs->botWeaponWeights[WP_REPEATER] = 15;
00847         bs->botWeaponWeights[WP_DEMP2] = 16;
00848         bs->botWeaponWeights[WP_FLECHETTE] = 17;
00849         bs->botWeaponWeights[WP_ROCKET_LAUNCHER] = 18;
00850         bs->botWeaponWeights[WP_THERMAL] = 14;
00851         bs->botWeaponWeights[WP_TRIP_MINE] = 0;
00852         bs->botWeaponWeights[WP_DET_PACK] = 0;
00853         bs->botWeaponWeights[WP_MELEE] = 1;
00854 
00855         BotUtilizePersonality(bs);
00856 
00857         if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
00858         {
00859                 bs->botWeaponWeights[WP_SABER] = 13;
00860         }
00861 
00862         //allocate a goal state
00863         bs->gs = trap_BotAllocGoalState(client);
00864 
00865         //allocate a weapon state
00866         bs->ws = trap_BotAllocWeaponState();
00867 
00868         bs->inuse = qtrue;
00869         bs->entitynum = client;
00870         bs->setupcount = 4;
00871         bs->entergame_time = FloatTime();
00872         bs->ms = trap_BotAllocMoveState();
00873         numbots++;
00874 
00875         //NOTE: reschedule the bot thinking
00876         BotScheduleBotThink();
00877 
00878         if (PlayersInGame())
00879         { //don't talk to yourself
00880                 BotDoChat(bs, "GeneralGreetings", 0);
00881         }
00882 
00883         return qtrue;
00884 }
00885 
00886 /*
00887 ==============
00888 BotAIShutdownClient
00889 ==============
00890 */
00891 int BotAIShutdownClient(int client, qboolean restart) {
00892         bot_state_t *bs;
00893 
00894         bs = botstates[client];
00895         if (!bs || !bs->inuse) {
00896                 //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client);
00897                 return qfalse;
00898         }
00899 
00900         trap_BotFreeMoveState(bs->ms);
00901         //free the goal state`                  
00902         trap_BotFreeGoalState(bs->gs);
00903         //free the weapon weights
00904         trap_BotFreeWeaponState(bs->ws);
00905         //
00906         //clear the bot state
00907         memset(bs, 0, sizeof(bot_state_t));
00908         //set the inuse flag to qfalse
00909         bs->inuse = qfalse;
00910         //there's one bot less
00911         numbots--;
00912         //everything went ok
00913         return qtrue;
00914 }
00915 
00916 /*
00917 ==============
00918 BotResetState
00919 
00920 called when a bot enters the intermission or observer mode and
00921 when the level is changed
00922 ==============
00923 */
00924 void BotResetState(bot_state_t *bs) {
00925         int client, entitynum, inuse;
00926         int movestate, goalstate, weaponstate;
00927         bot_settings_t settings;
00928         playerState_t ps;                                                       //current player state
00929         float entergame_time;
00930 
00931         //save some things that should not be reset here
00932         memcpy(&settings, &bs->settings, sizeof(bot_settings_t));
00933         memcpy(&ps, &bs->cur_ps, sizeof(playerState_t));
00934         inuse = bs->inuse;
00935         client = bs->client;
00936         entitynum = bs->entitynum;
00937         movestate = bs->ms;
00938         goalstate = bs->gs;
00939         weaponstate = bs->ws;
00940         entergame_time = bs->entergame_time;
00941         //reset the whole state
00942         memset(bs, 0, sizeof(bot_state_t));
00943         //copy back some state stuff that should not be reset
00944         bs->ms = movestate;
00945         bs->gs = goalstate;
00946         bs->ws = weaponstate;
00947         memcpy(&bs->cur_ps, &ps, sizeof(playerState_t));
00948         memcpy(&bs->settings, &settings, sizeof(bot_settings_t));
00949         bs->inuse = inuse;
00950         bs->client = client;
00951         bs->entitynum = entitynum;
00952         bs->entergame_time = entergame_time;
00953         //reset several states
00954         if (bs->ms) trap_BotResetMoveState(bs->ms);
00955         if (bs->gs) trap_BotResetGoalState(bs->gs);
00956         if (bs->ws) trap_BotResetWeaponState(bs->ws);
00957         if (bs->gs) trap_BotResetAvoidGoals(bs->gs);
00958         if (bs->ms) trap_BotResetAvoidReach(bs->ms);
00959 }
00960 
00961 /*
00962 ==============
00963 BotAILoadMap
00964 ==============
00965 */
00966 int BotAILoadMap( int restart ) {
00967         int                     i;
00968 
00969         for (i = 0; i < MAX_CLIENTS; i++) {
00970                 if (botstates[i] && botstates[i]->inuse) {
00971                         BotResetState( botstates[i] );
00972                         botstates[i]->setupcount = 4;
00973                 }
00974         }
00975 
00976         return qtrue;
00977 }
00978 
00979 //rww - bot ai
00980 
00981 //standard visibility check
00982 int OrgVisible(vec3_t org1, vec3_t org2, int ignore)
00983 {
00984         trace_t tr;
00985 
00986         trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID);
00987 
00988         if (tr.fraction == 1)
00989         {
00990                 return 1;
00991         }
00992 
00993         return 0;
00994 }
00995 
00996 //special waypoint visibility check
00997 int WPOrgVisible(gentity_t *bot, vec3_t org1, vec3_t org2, int ignore)
00998 {
00999         trace_t tr;
01000         gentity_t *ownent;
01001 
01002         trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID);
01003 
01004         if (tr.fraction == 1)
01005         {
01006                 trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_PLAYERSOLID);
01007 
01008                 if (tr.fraction != 1 && tr.entityNum != ENTITYNUM_NONE && g_entities[tr.entityNum].s.eType == ET_SPECIAL)
01009                 {
01010                         if (g_entities[tr.entityNum].parent && g_entities[tr.entityNum].parent->client)
01011                         {
01012                                 ownent = g_entities[tr.entityNum].parent;
01013 
01014                                 if (OnSameTeam(bot, ownent) || bot->s.number == ownent->s.number)
01015                                 {
01016                                         return 1;
01017                                 }
01018                         }
01019                         return 2;
01020                 }
01021 
01022                 return 1;
01023         }
01024 
01025         return 0;
01026 }
01027 
01028 //visibility check with hull trace
01029 int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore)
01030 {
01031         trace_t tr;
01032 
01033         if (g_RMG.integer)
01034         {
01035                 trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID);
01036         }
01037         else
01038         {
01039                 trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID);
01040         }
01041 
01042         if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
01043         {
01044                 return 1;
01045         }
01046 
01047         return 0;
01048 }
01049 
01050 //see if there's a func_* ent under the given pos.
01051 //kind of badly done, but this shouldn't happen
01052 //often.
01053 int CheckForFunc(vec3_t org, int ignore)
01054 {
01055         gentity_t *fent;
01056         vec3_t under;
01057         trace_t tr;
01058 
01059         VectorCopy(org, under);
01060 
01061         under[2] -= 64;
01062 
01063         trap_Trace(&tr, org, NULL, NULL, under, ignore, MASK_SOLID);
01064 
01065         if (tr.fraction == 1)
01066         {
01067                 return 0;
01068         }
01069 
01070         fent = &g_entities[tr.entityNum];
01071 
01072         if (!fent)
01073         {
01074                 return 0;
01075         }
01076 
01077         if (strstr(fent->classname, "func_"))
01078         {
01079                 return 1; //there's a func brush here
01080         }
01081 
01082         return 0;
01083 }
01084 
01085 //perform pvs check based on rmg or not
01086 qboolean BotPVSCheck( const vec3_t p1, const vec3_t p2 )
01087 {
01088         if (g_RMG.integer && bot_pvstype.integer)
01089         {
01090                 vec3_t subPoint;
01091                 VectorSubtract(p1, p2, subPoint);
01092 
01093                 if (VectorLength(subPoint) > 5000)
01094                 {
01095                         return qfalse;
01096                 }
01097                 return qtrue;
01098         }
01099 
01100         return trap_InPVS(p1, p2);
01101 }
01102 
01103 //get the index to the nearest visible waypoint in the global trail
01104 int GetNearestVisibleWP(vec3_t org, int ignore)
01105 {
01106         int i;
01107         float bestdist;
01108         float flLen;
01109         int bestindex;
01110         vec3_t a, mins, maxs;
01111 
01112         i = 0;
01113         if (g_RMG.integer)
01114         {
01115                 bestdist = 300;
01116         }
01117         else
01118         {
01119                 bestdist = 800;//99999;
01120                                    //don't trace over 800 units away to avoid GIANT HORRIBLE SPEED HITS ^_^
01121         }
01122         bestindex = -1;
01123 
01124         mins[0] = -15;
01125         mins[1] = -15;
01126         mins[2] = -1;
01127         maxs[0] = 15;
01128         maxs[1] = 15;
01129         maxs[2] = 1;
01130 
01131         while (i < gWPNum)
01132         {
01133                 if (gWPArray[i] && gWPArray[i]->inuse)
01134                 {
01135                         VectorSubtract(org, gWPArray[i]->origin, a);
01136                         flLen = VectorLength(a);
01137 
01138                         if (flLen < bestdist && (g_RMG.integer || BotPVSCheck(org, gWPArray[i]->origin)) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore))
01139                         {
01140                                 bestdist = flLen;
01141                                 bestindex = i;
01142                         }
01143                 }
01144 
01145                 i++;
01146         }
01147 
01148         return bestindex;
01149 }
01150 
01151 //wpDirection
01152 //0 == FORWARD
01153 //1 == BACKWARD
01154 
01155 //see if this is a valid waypoint to pick up in our
01156 //current state (whatever that may be)
01157 int PassWayCheck(bot_state_t *bs, int windex)
01158 {
01159         if (!gWPArray[windex] || !gWPArray[windex]->inuse)
01160         { //bad point index
01161                 return 0;
01162         }
01163 
01164         if (g_RMG.integer)
01165         {
01166                 if ((gWPArray[windex]->flags & WPFLAG_RED_FLAG) ||
01167                         (gWPArray[windex]->flags & WPFLAG_BLUE_FLAG))
01168                 { //red or blue flag, we'd like to get here
01169                         return 1;
01170                 }
01171         }
01172 
01173         if (bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_FWD))
01174         { //we're not travelling in a direction on the trail that will allow us to pass this point
01175                 return 0;
01176         }
01177         else if (!bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_BACK))
01178         { //we're not travelling in a direction on the trail that will allow us to pass this point
01179                 return 0;
01180         }
01181 
01182         if (bs->wpCurrent && gWPArray[windex]->forceJumpTo &&
01183                 gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) &&
01184                 bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo)
01185         { //waypoint requires force jump level greater than our current one to pass
01186                 return 0;
01187         }
01188 
01189         return 1;
01190 }
01191 
01192 //tally up the distance between two waypoints
01193 float TotalTrailDistance(int start, int end, bot_state_t *bs)
01194 {
01195         int beginat;
01196         int endat;
01197         float distancetotal;
01198 
01199         distancetotal = 0;
01200 
01201         if (start > end)
01202         {
01203                 beginat = end;
01204                 endat = start;
01205         }
01206         else
01207         {
01208                 beginat = start;
01209                 endat = end;
01210         }
01211 
01212         while (beginat < endat)
01213         {
01214                 if (beginat >= gWPNum || !gWPArray[beginat] || !gWPArray[beginat]->inuse)
01215                 { //invalid waypoint index
01216                         return -1;
01217                 }
01218 
01219                 if (!g_RMG.integer)
01220                 {
01221                         if ((end > start && gWPArray[beginat]->flags & WPFLAG_ONEWAY_BACK) ||
01222                                 (start > end && gWPArray[beginat]->flags & WPFLAG_ONEWAY_FWD))
01223                         { //a one-way point, this means this path cannot be travelled to the final point
01224                                 return -1;
01225                         }
01226                 }
01227         
01228 #if 0 //disabled force jump checks for now
01229                 if (gWPArray[beginat]->forceJumpTo)
01230                 {
01231                         if (gWPArray[beginat-1] && gWPArray[beginat-1]->origin[2]+64 < gWPArray[beginat]->origin[2])
01232                         {
01233                                 gdif = gWPArray[beginat]->origin[2] - gWPArray[beginat-1]->origin[2];
01234                         }
01235 
01236                         if (gdif)
01237                         {
01238                                 if (bs && bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[beginat]->forceJumpTo)
01239                                 {
01240                                         return -1;
01241                                 }
01242                         }
01243                 }
01244                 
01245                 if (bs->wpCurrent && gWPArray[windex]->forceJumpTo &&
01246                         gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) &&
01247                         bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo)
01248                 {
01249                         return -1;
01250                 }
01251 #endif
01252 
01253                 distancetotal += gWPArray[beginat]->disttonext;
01254 
01255                 beginat++;
01256         }
01257 
01258         return distancetotal;
01259 }
01260 
01261 //see if there's a route shorter than our current one to get
01262 //to the final destination we currently desire
01263 void CheckForShorterRoutes(bot_state_t *bs, int newwpindex)
01264 {
01265         float bestlen;
01266         float checklen;
01267         int bestindex;
01268         int i;
01269         int fj;
01270 
01271         i = 0;
01272         fj = 0;
01273 
01274         if (!bs->wpDestination)
01275         {
01276                 return;
01277         }
01278 
01279         //set our traversal direction based on the index of the point
01280         if (newwpindex < bs->wpDestination->index)
01281         {
01282                 bs->wpDirection = 0;
01283         }
01284         else if (newwpindex > bs->wpDestination->index)
01285         {
01286                 bs->wpDirection = 1;
01287         }
01288 
01289         //can't switch again yet
01290         if (bs->wpSwitchTime > level.time)
01291         {
01292                 return;
01293         }
01294 
01295         //no neighboring points to check off of
01296         if (!gWPArray[newwpindex]->neighbornum)
01297         {
01298                 return;
01299         }
01300 
01301         //get the trail distance for our wp
01302         bestindex = newwpindex;
01303         bestlen = TotalTrailDistance(newwpindex, bs->wpDestination->index, bs);
01304 
01305         while (i < gWPArray[newwpindex]->neighbornum)
01306         { //now go through the neighbors and check the distance to the desired point from each neighbor
01307                 checklen = TotalTrailDistance(gWPArray[newwpindex]->neighbors[i].num, bs->wpDestination->index, bs);
01308 
01309                 if (checklen < bestlen-64 || bestlen == -1)
01310                 { //this path covers less distance, let's take it instead
01311                         if (bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] >= gWPArray[newwpindex]->neighbors[i].forceJumpTo)
01312                         {
01313                                 bestlen = checklen;
01314                                 bestindex = gWPArray[newwpindex]->neighbors[i].num;
01315 
01316                                 if (gWPArray[newwpindex]->neighbors[i].forceJumpTo)
01317                                 {
01318                                         fj = gWPArray[newwpindex]->neighbors[i].forceJumpTo;
01319                                 }
01320                                 else
01321                                 {
01322                                         fj = 0;
01323                                 }
01324                         }
01325                 }
01326 
01327                 i++;
01328         }
01329 
01330         if (bestindex != newwpindex && bestindex != -1)
01331         { //we found a path we want to switch to, let's do it
01332                 bs->wpCurrent = gWPArray[bestindex];
01333                 bs->wpSwitchTime = level.time + 3000;
01334 
01335                 if (fj)
01336                 { //do we have to force jump to get to this neighbor?
01337 #ifndef FORCEJUMP_INSTANTMETHOD
01338                         bs->forceJumpChargeTime = level.time + 1000;
01339                         bs->beStill = level.time + 1000;
01340                         bs->forceJumping = bs->forceJumpChargeTime;
01341 #else
01342                         bs->beStill = level.time + 500;
01343                         bs->jumpTime = level.time + fj*1200;
01344                         bs->jDelay = level.time + 200;
01345                         bs->forceJumping = bs->jumpTime;
01346