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 #endif
01347                 }
01348         }
01349 }
01350 
01351 //check for flags on the waypoint we're currently travelling to
01352 //and perform the desired behavior based on the flag
01353 void WPConstantRoutine(bot_state_t *bs)
01354 {
01355         if (!bs->wpCurrent)
01356         {
01357                 return;
01358         }
01359 
01360         if (bs->wpCurrent->flags & WPFLAG_DUCK)
01361         { //duck while travelling to this point
01362                 bs->duckTime = level.time + 100;
01363         }
01364 
01365 #ifndef FORCEJUMP_INSTANTMETHOD
01366         if (bs->wpCurrent->flags & WPFLAG_JUMP)
01367         { //jump while travelling to this point
01368                 float heightDif = (bs->wpCurrent->origin[2] - bs->origin[2]+16);
01369 
01370                 if (bs->origin[2]+16 >= bs->wpCurrent->origin[2])
01371                 { //don't need to jump, we're already higher than this point
01372                         heightDif = 0;
01373                 }
01374 
01375                 if (heightDif > 40 && (bs->cur_ps.fd.forcePowersKnown & (1 << FP_LEVITATION)) && (bs->cur_ps.fd.forceJumpCharge < (forceJumpStrength[bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION]]-100) || bs->cur_ps.groundEntityNum == ENTITYNUM_NONE))
01376                 { //alright, let's jump
01377                         bs->forceJumpChargeTime = level.time + 1000;
01378                         if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE && bs->jumpPrep < (level.time-300))
01379                         {
01380                                 bs->jumpPrep = level.time + 700;
01381                         }
01382                         bs->beStill = level.time + 300;
01383                         bs->jumpTime = 0;
01384 
01385                         if (bs->wpSeenTime < (level.time + 600))
01386                         {
01387                                 bs->wpSeenTime = level.time + 600;
01388                         }
01389                 }
01390                 else if (heightDif > 64 && !(bs->cur_ps.fd.forcePowersKnown & (1 << FP_LEVITATION)))
01391                 { //this point needs force jump to reach and we don't have it
01392                         //Kill the current point and turn around
01393                         bs->wpCurrent = NULL;
01394                         if (bs->wpDirection)
01395                         {
01396                                 bs->wpDirection = 0;
01397                         }
01398                         else
01399                         {
01400                                 bs->wpDirection = 1;
01401                         }
01402 
01403                         return;
01404                 }
01405         }
01406 #endif
01407 
01408         if (bs->wpCurrent->forceJumpTo)
01409         {
01410 #ifdef FORCEJUMP_INSTANTMETHOD
01411                 if (bs->origin[2]+16 < bs->wpCurrent->origin[2])
01412                 {
01413                         bs->jumpTime = level.time + 100;
01414                 }
01415 #else
01416                 float heightDif = (bs->wpCurrent->origin[2] - bs->origin[2]+16);
01417 
01418                 if (bs->origin[2]+16 >= bs->wpCurrent->origin[2])
01419                 { //then why exactly would we be force jumping?
01420                         heightDif = 0;
01421                 }
01422 
01423                 if (bs->cur_ps.fd.forceJumpCharge < (forceJumpStrength[bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION]]-100))
01424                 {
01425                         bs->forceJumpChargeTime = level.time + 200;
01426                 }
01427 #endif
01428         }
01429 }
01430 
01431 //check if our ctf state is to guard the base
01432 qboolean BotCTFGuardDuty(bot_state_t *bs)
01433 {
01434         if (g_gametype.integer != GT_CTF &&
01435                 g_gametype.integer != GT_CTY)
01436         {
01437                 return qfalse;
01438         }
01439 
01440         if (bs->ctfState == CTFSTATE_DEFENDER)
01441         {
01442                 return qtrue;
01443         }
01444 
01445         return qfalse;
01446 }
01447 
01448 //when we reach the waypoint we are travelling to,
01449 //this function will be called. We will perform any
01450 //checks for flags on the current wp and activate
01451 //any "touch" events based on that.
01452 void WPTouchRoutine(bot_state_t *bs)
01453 {
01454         int lastNum;
01455 
01456         if (!bs->wpCurrent)
01457         {
01458                 return;
01459         }
01460 
01461         bs->wpTravelTime = level.time + 10000;
01462 
01463         if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC)
01464         { //don't try to use any nearby map objects for a little while
01465                 bs->noUseTime = level.time + 4000;
01466         }
01467 
01468 #ifdef FORCEJUMP_INSTANTMETHOD
01469         if ((bs->wpCurrent->flags & WPFLAG_JUMP) && bs->wpCurrent->forceJumpTo)
01470         { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is
01471           //handled elsewhere.
01472                 bs->jumpTime = level.time + 100;
01473         }
01474 #else
01475         if ((bs->wpCurrent->flags & WPFLAG_JUMP) && !bs->wpCurrent->forceJumpTo)
01476         { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is
01477           //handled elsewhere.
01478                 bs->jumpTime = level.time + 100;
01479         }
01480 #endif
01481 
01482         if (bs->isCamper && bot_camp.integer && (BotIsAChickenWuss(bs) || BotCTFGuardDuty(bs) || bs->isCamper == 2) && ((bs->wpCurrent->flags & WPFLAG_SNIPEORCAMP) || (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)) &&
01483                 bs->cur_ps.weapon != WP_SABER && bs->cur_ps.weapon != WP_MELEE && bs->cur_ps.weapon != WP_STUN_BATON)
01484         { //if we're a camper and a chicken then camp
01485                 if (bs->wpDirection)
01486                 {
01487                         lastNum = bs->wpCurrent->index+1;
01488                 }
01489                 else
01490                 {
01491                         lastNum = bs->wpCurrent->index-1;
01492                 }
01493 
01494                 if (gWPArray[lastNum] && gWPArray[lastNum]->inuse && gWPArray[lastNum]->index && bs->isCamping < level.time)
01495                 {
01496                         bs->isCamping = level.time + rand()%15000 + 30000;
01497                         bs->wpCamping = bs->wpCurrent;
01498                         bs->wpCampingTo = gWPArray[lastNum];
01499 
01500                         if (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)
01501                         {
01502                                 bs->campStanding = qtrue;
01503                         }
01504                         else
01505                         {
01506                                 bs->campStanding = qfalse;
01507                         }
01508                 }
01509 
01510         }
01511         else if ((bs->cur_ps.weapon == WP_SABER || bs->cur_ps.weapon == WP_STUN_BATON || bs->cur_ps.weapon == WP_MELEE) &&
01512                 bs->isCamping > level.time)
01513         { //don't snipe/camp with a melee weapon, that would be silly
01514                 bs->isCamping = 0;
01515                 bs->wpCampingTo = NULL;
01516                 bs->wpCamping = NULL;
01517         }
01518 
01519         if (bs->wpDestination)
01520         {
01521                 if (bs->wpCurrent->index == bs->wpDestination->index)
01522                 {
01523                         bs->wpDestination = NULL;
01524 
01525                         if (bs->runningLikeASissy)
01526                         { //this obviously means we're scared and running, so we'll want to keep our navigational priorities less delayed
01527                                 bs->destinationGrabTime = level.time + 500;
01528                         }
01529                         else
01530                         {
01531                                 bs->destinationGrabTime = level.time + 3500;
01532                         }
01533                 }
01534                 else
01535                 {
01536                         CheckForShorterRoutes(bs, bs->wpCurrent->index);
01537                 }
01538         }
01539 }
01540 
01541 //could also slowly lerp toward, but for now
01542 //just copying straight over.
01543 void MoveTowardIdealAngles(bot_state_t *bs)
01544 {
01545         VectorCopy(bs->goalAngles, bs->ideal_viewangles);
01546 }
01547 
01548 #define BOT_STRAFE_AVOIDANCE
01549 
01550 #ifdef BOT_STRAFE_AVOIDANCE
01551 #define STRAFEAROUND_RIGHT                      1
01552 #define STRAFEAROUND_LEFT                       2
01553 
01554 //do some trace checks for strafing to get an idea of where we
01555 //are and if we should move to avoid obstacles.
01556 int BotTrace_Strafe(bot_state_t *bs, vec3_t traceto)
01557 {
01558         vec3_t playerMins = {-15, -15, /*DEFAULT_MINS_2*/-8};
01559         vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2};
01560         vec3_t from, to;
01561         vec3_t dirAng, dirDif;
01562         vec3_t forward, right;
01563         trace_t tr;
01564 
01565         if (bs->cur_ps.groundEntityNum == ENTITYNUM_NONE)
01566         { //don't do this in the air, it can be.. dangerous.
01567                 return 0;
01568         }
01569 
01570         VectorSubtract(traceto, bs->origin, dirAng);
01571         VectorNormalize(dirAng);
01572         vectoangles(dirAng, dirAng);
01573 
01574         if (AngleDifference(bs->viewangles[YAW], dirAng[YAW]) > 60 ||
01575                 AngleDifference(bs->viewangles[YAW], dirAng[YAW]) < -60)
01576         { //If we aren't facing the direction we're going here, then we've got enough excuse to be too stupid to strafe around anyway
01577                 return 0;
01578         }
01579 
01580         VectorCopy(bs->origin, from);
01581         VectorCopy(traceto, to);
01582 
01583         VectorSubtract(to, from, dirDif);
01584         VectorNormalize(dirDif);
01585         vectoangles(dirDif, dirDif);
01586 
01587         AngleVectors(dirDif, forward, 0, 0);
01588 
01589         to[0] = from[0] + forward[0]*32;
01590         to[1] = from[1] + forward[1]*32;
01591         to[2] = from[2] + forward[2]*32;
01592 
01593         trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID);
01594 
01595         if (tr.fraction == 1)
01596         {
01597                 return 0;
01598         }
01599 
01600         AngleVectors(dirAng, 0, right, 0);
01601 
01602         from[0] += right[0]*32;
01603         from[1] += right[1]*32;
01604         from[2] += right[2]*16;
01605 
01606         to[0] += right[0]*32;
01607         to[1] += right[1]*32;
01608         to[2] += right[2]*32;
01609 
01610         trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID);
01611 
01612         if (tr.fraction == 1)
01613         {
01614                 return STRAFEAROUND_RIGHT;
01615         }
01616 
01617         from[0] -= right[0]*64;
01618         from[1] -= right[1]*64;
01619         from[2] -= right[2]*64;
01620 
01621         to[0] -= right[0]*64;
01622         to[1] -= right[1]*64;
01623         to[2] -= right[2]*64;
01624 
01625         trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID);
01626 
01627         if (tr.fraction == 1)
01628         {
01629                 return STRAFEAROUND_LEFT;
01630         }
01631 
01632         return 0;
01633 }
01634 #endif
01635 
01636 //Similar to the trace check, but we want to trace to see
01637 //if there's anything we can jump over.
01638 int BotTrace_Jump(bot_state_t *bs, vec3_t traceto)
01639 {
01640         vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod;
01641         trace_t tr;
01642         int orTr;
01643 
01644         VectorSubtract(traceto, bs->origin, a);
01645         vectoangles(a, a);
01646 
01647         AngleVectors(a, fwd, NULL, NULL);
01648 
01649         traceto_mod[0] = bs->origin[0] + fwd[0]*4;
01650         traceto_mod[1] = bs->origin[1] + fwd[1]*4;
01651         traceto_mod[2] = bs->origin[2] + fwd[2]*4;
01652 
01653         mins[0] = -15;
01654         mins[1] = -15;
01655         mins[2] = -18;
01656         maxs[0] = 15;
01657         maxs[1] = 15;
01658         maxs[2] = 32;
01659 
01660         trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID);
01661 
01662         if (tr.fraction == 1)
01663         {
01664                 return 0;
01665         }
01666 
01667         orTr = tr.entityNum;
01668 
01669         VectorCopy(bs->origin, tracefrom_mod);
01670 
01671         tracefrom_mod[2] += 41;
01672         traceto_mod[2] += 41;
01673 
01674         mins[0] = -15;
01675         mins[1] = -15;
01676         mins[2] = 0;
01677         maxs[0] = 15;
01678         maxs[1] = 15;
01679         maxs[2] = 8;
01680 
01681         trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID);
01682 
01683         if (tr.fraction == 1)
01684         {
01685                 if (orTr >= 0 && orTr < MAX_CLIENTS && botstates[orTr] && botstates[orTr]->jumpTime > level.time)
01686                 {
01687                         return 0; //so bots don't try to jump over each other at the same time
01688                 }
01689 
01690                 if (bs->currentEnemy && bs->currentEnemy->s.number == orTr && (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER || BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE))
01691                 {
01692                         return 0;
01693                 }
01694 
01695                 return 1;
01696         }
01697 
01698         return 0;
01699 }
01700 
01701 //And yet another check to duck under any obstacles.
01702 int BotTrace_Duck(bot_state_t *bs, vec3_t traceto)
01703 {
01704         vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod;
01705         trace_t tr;
01706 
01707         VectorSubtract(traceto, bs->origin, a);
01708         vectoangles(a, a);
01709 
01710         AngleVectors(a, fwd, NULL, NULL);
01711 
01712         traceto_mod[0] = bs->origin[0] + fwd[0]*4;
01713         traceto_mod[1] = bs->origin[1] + fwd[1]*4;
01714         traceto_mod[2] = bs->origin[2] + fwd[2]*4;
01715 
01716         mins[0] = -15;
01717         mins[1] = -15;
01718         mins[2] = -23;
01719         maxs[0] = 15;
01720         maxs[1] = 15;
01721         maxs[2] = 8;
01722 
01723         trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID);
01724 
01725         if (tr.fraction != 1)
01726         {
01727                 return 0;
01728         }
01729 
01730         VectorCopy(bs->origin, tracefrom_mod);
01731 
01732         tracefrom_mod[2] += 31;//33;
01733         traceto_mod[2] += 31;//33;
01734 
01735         mins[0] = -15;
01736         mins[1] = -15;
01737         mins[2] = 0;
01738         maxs[0] = 15;
01739         maxs[1] = 15;
01740         maxs[2] = 32;
01741 
01742         trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID);
01743 
01744         if (tr.fraction != 1)
01745         {
01746                 return 1;
01747         }
01748 
01749         return 0;
01750 }
01751 
01752 //check of the potential enemy is a valid one
01753 int PassStandardEnemyChecks(bot_state_t *bs, gentity_t *en)
01754 {
01755         if (!bs || !en)
01756         { //shouldn't happen
01757                 return 0;
01758         }
01759 
01760         if (!en->client)
01761         { //not a client, don't care about him
01762                 return 0;
01763         }
01764 
01765         if (en->health < 1)
01766         { //he's already dead
01767                 return 0;
01768         }
01769 
01770         if (!en->takedamage)
01771         { //a client that can't take damage?
01772                 return 0;
01773         }
01774 
01775         if (bs->doingFallback &&
01776                 (gLevelFlags & LEVELFLAG_IGNOREINFALLBACK))
01777         { //we screwed up in our nav routines somewhere and we've reverted to a fallback state to
01778                 //try to get back on the trail. If the level specifies to ignore enemies in this state,
01779                 //then ignore them.
01780                 return 0;
01781         }
01782 
01783         if (en->client->ps.pm_type == PM_INTERMISSION ||
01784                 en->client->ps.pm_type == PM_SPECTATOR ||
01785                 en->client->sess.sessionTeam == TEAM_SPECTATOR)
01786         { //don't attack spectators
01787                 return 0;
01788         }
01789 
01790         if (!en->client->pers.connected)
01791         { //a "zombie" client?
01792                 return 0;
01793         }
01794 
01795         if (!en->s.solid)
01796         { //shouldn't happen
01797                 return 0;
01798         }
01799 
01800         if (bs->client == en->s.number)
01801         { //don't attack yourself
01802                 return 0;
01803         }
01804 
01805         if (OnSameTeam(&g_entities[bs->client], en))
01806         { //don't attack teammates
01807                 return 0;
01808         }
01809 
01810         if (BotMindTricked(bs->client, en->s.number))
01811         {
01812                 if (bs->currentEnemy && bs->currentEnemy->s.number == en->s.number)
01813                 { //if mindtricked by this enemy, then be less "aware" of them, even though
01814                         //we know they're there.
01815                         vec3_t vs;
01816                         float vLen = 0;
01817 
01818                         VectorSubtract(bs->origin, en->client->ps.origin, vs);
01819                         vLen = VectorLength(vs);
01820 
01821                         if (vLen > 64 /*&& (level.time - en->client->dangerTime) > 150*/)
01822                         {
01823                                 return 0;
01824                         }
01825                 }
01826         }
01827 
01828         if (en->client->ps.duelInProgress && en->client->ps.duelIndex != bs->client)
01829         { //don't attack duelists unless you're dueling them
01830                 return 0;
01831         }
01832 
01833         if (bs->cur_ps.duelInProgress && en->s.number != bs->cur_ps.duelIndex)
01834         { //ditto, the other way around
01835                 return 0;
01836         }
01837 
01838         if (g_gametype.integer == GT_JEDIMASTER && !en->client->ps.isJediMaster && !bs->cur_ps.isJediMaster)
01839         { //rules for attacking non-JM in JM mode
01840                 vec3_t vs;
01841                 float vLen = 0;
01842 
01843                 if (!g_friendlyFire.integer)
01844                 { //can't harm non-JM in JM mode if FF is off
01845                         return 0;
01846                 }
01847 
01848                 VectorSubtract(bs->origin, en->client->ps.origin, vs);
01849                 vLen = VectorLength(vs);
01850 
01851                 if (vLen > 350)
01852                 {
01853                         return 0;
01854                 }
01855         }
01856 
01857         return 1;
01858 }
01859 
01860 //Notifies the bot that he has taken damage from "attacker".
01861 void BotDamageNotification(gclient_t *bot, gentity_t *attacker)
01862 {
01863         bot_state_t *bs;
01864         bot_state_t *bs_a;
01865         int i;
01866 
01867         if (!bot || !attacker || !attacker->client)
01868         {
01869                 return;
01870         }
01871 
01872         if (bot->ps.clientNum >= MAX_CLIENTS)
01873         { //an NPC.. do nothing for them.
01874                 return;
01875         }
01876 
01877         if (attacker->s.number >= MAX_CLIENTS)
01878         { //if attacker is an npc also don't care I suppose.
01879                 return;
01880         }
01881 
01882         bs_a = botstates[attacker->s.number];
01883 
01884         if (bs_a)
01885         { //if the client attacking us is a bot as well
01886                 bs_a->lastAttacked = &g_entities[bot->ps.clientNum];
01887                 i = 0;
01888 
01889                 while (i < MAX_CLIENTS)
01890                 {
01891                         if (botstates[i] &&
01892                                 i != bs_a->client &&
01893                                 botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum])
01894                         {
01895                                 botstates[i]->lastAttacked = NULL;
01896                         }
01897 
01898                         i++;
01899                 }
01900         }
01901         else //got attacked by a real client, so no one gets rights to lastAttacked
01902         {
01903                 i = 0;
01904 
01905                 while (i < MAX_CLIENTS)
01906                 {
01907                         if (botstates[i] &&
01908                                 botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum])
01909                         {
01910                                 botstates[i]->lastAttacked = NULL;
01911                         }
01912 
01913                         i++;
01914                 }
01915         }
01916 
01917         bs = botstates[bot->ps.clientNum];
01918 
01919         if (!bs)
01920         {
01921                 return;
01922         }
01923 
01924         bs->lastHurt = attacker;
01925 
01926         if (bs->currentEnemy)
01927         { //we don't care about the guy attacking us if we have an enemy already
01928                 return;
01929         }
01930 
01931         if (!PassStandardEnemyChecks(bs, attacker))
01932         { //the person that hurt us is not a valid enemy
01933                 return;
01934         }
01935 
01936         if (PassLovedOneCheck(bs, attacker))
01937         { //the person that hurt us is the one we love!
01938                 bs->currentEnemy = attacker;
01939                 bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
01940         }
01941 }
01942 
01943 //perform cheap "hearing" checks based on the event catching
01944 //system
01945 int BotCanHear(bot_state_t *bs, gentity_t *en, float endist)
01946 {
01947         float minlen;
01948 
01949         if (!en || !en->client)
01950         {
01951                 return 0;
01952         }
01953 
01954         if (en && en->client && en->client->ps.otherSoundTime > level.time)
01955         { //they made a noise in recent time
01956                 minlen = en->client->ps.otherSoundLen;
01957                 goto checkStep;
01958         }
01959 
01960         if (en && en->client && en->client->ps.footstepTime > level.time)
01961         { //they made a footstep
01962                 minlen = 256;
01963                 goto checkStep;
01964         }
01965 
01966         if (gBotEventTracker[en->s.number].eventTime < level.time)
01967         { //no recent events to check
01968                 return 0;
01969         }
01970 
01971         switch(gBotEventTracker[en->s.number].events[gBotEventTracker[en->s.number].eventSequence & (MAX_PS_EVENTS-1)])
01972         { //did the last event contain a sound?
01973         case EV_GLOBAL_SOUND:
01974                 minlen = 256;
01975                 break;
01976         case EV_FIRE_WEAPON:
01977         case EV_ALT_FIRE:
01978         case EV_SABER_ATTACK:
01979                 minlen = 512;
01980                 break;
01981         case EV_STEP_4:
01982         case EV_STEP_8:
01983         case EV_STEP_12:
01984         case EV_STEP_16:
01985         case EV_FOOTSTEP:
01986         case EV_FOOTSTEP_METAL:
01987         case EV_FOOTWADE:
01988                 minlen = 256;
01989                 break;
01990         case EV_JUMP:
01991         case EV_ROLL:
01992                 minlen = 256;
01993                 break;
01994         default:
01995                 minlen = 999999;
01996                 break;
01997         }
01998 checkStep:
01999         if (BotMindTricked(bs->client, en->s.number))
02000         { //if mindtricked by this person, cut down on the minlen so they can't "hear" as well
02001                 minlen /= 4;
02002         }
02003 
02004         if (endist <= minlen)
02005         { //we heard it
02006                 return 1;
02007         }
02008 
02009         return 0;
02010 }
02011 
02012 //check for new events
02013 void UpdateEventTracker(void)
02014 {
02015         int i;
02016 
02017         i = 0;
02018 
02019         while (i < MAX_CLIENTS)
02020         {
02021                 if (gBotEventTracker[i].eventSequence != level.clients[i].ps.eventSequence)
02022                 { //updated event
02023                         gBotEventTracker[i].eventSequence = level.clients[i].ps.eventSequence;
02024                         gBotEventTracker[i].events[0] = level.clients[i].ps.events[0];
02025                         gBotEventTracker[i].events[1] = level.clients[i].ps.events[1];
02026                         gBotEventTracker[i].eventTime = level.time + 0.5;
02027                 }
02028 
02029                 i++;
02030         }
02031 }
02032 
02033 //check if said angles are within our fov
02034 int InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
02035 {
02036         int i;
02037         float diff, angle;
02038 
02039         for (i = 0; i < 2; i++)
02040         {
02041                 angle = AngleMod(viewangles[i]);
02042                 angles[i] = AngleMod(angles[i]);
02043                 diff = angles[i] - angle;
02044                 if (angles[i] > angle)
02045                 {
02046                         if (diff > 180.0)
02047                         {
02048                                 diff -= 360.0;
02049                         }
02050                 }
02051                 else
02052                 {
02053                         if (diff < -180.0)
02054                         {
02055                                 diff += 360.0;
02056                         }
02057                 }
02058                 if (diff > 0)
02059                 {
02060                         if (diff > fov * 0.5)
02061                         {
02062                                 return 0;
02063                         }
02064                 }
02065                 else
02066                 {
02067                         if (diff < -fov * 0.5)
02068                         {
02069                                 return 0;
02070                         }
02071                 }
02072         }
02073         return 1;
02074 }
02075 
02076 //We cannot hurt the ones we love. Unless of course this
02077 //function says we can.
02078 int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent)
02079 {
02080         int i;
02081         bot_state_t *loved;
02082 
02083         if (!bs->lovednum)
02084         {
02085                 return 1;
02086         }
02087 
02088         if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
02089         { //There is no love in 1-on-1
02090                 return 1;
02091         }
02092 
02093         i = 0;
02094 
02095         if (!botstates[ent->s.number])
02096         { //not a bot
02097                 return 1;
02098         }
02099 
02100         if (!bot_attachments.integer)
02101         {
02102                 return 1;
02103         }
02104 
02105         loved = botstates[ent->s.number];
02106 
02107         while (i < bs->lovednum)
02108         {
02109                 if (strcmp(level.clients[loved->client].pers.netname, bs->loved[i].name) == 0)
02110                 {
02111                         if (!IsTeamplay() && bs->loved[i].level < 2)
02112                         { //if FFA and level of love is not greater than 1, just don't care
02113                                 return 1;
02114                         }
02115                         else if (IsTeamplay() && !OnSameTeam(&g_entities[bs->client], &g_entities[loved->client]) && bs->loved[i].level < 2)
02116                         { //is teamplay, but not on same team and level < 2
02117                                 return 1;
02118                         }
02119                         else
02120                         {
02121                                 return 0;
02122                         }
02123                 }
02124 
02125                 i++;
02126         }
02127 
02128         return 1;
02129 }
02130 
02131 qboolean G_ThereIsAMaster(void);
02132 
02133 //standard check to find a new enemy.
02134 int ScanForEnemies(bot_state_t *bs)
02135 {
02136         vec3_t a;
02137         float distcheck;
02138         float closest;
02139         int bestindex;
02140         int i;
02141         float hasEnemyDist = 0;
02142         qboolean noAttackNonJM = qfalse;
02143 
02144         closest = 999999;
02145         i = 0;
02146         bestindex = -1;
02147 
02148         if (bs->currentEnemy)
02149         { //only switch to a new enemy if he's significantly closer
02150                 hasEnemyDist = bs->frame_Enemy_Len;
02151         }
02152 
02153         if (bs->currentEnemy && bs->currentEnemy->client &&
02154                 bs->currentEnemy->client->ps.isJediMaster)
02155         { //The Jedi Master must die.
02156                 return -1;
02157         }
02158 
02159         if (g_gametype.integer == GT_JEDIMASTER)
02160         {
02161                 if (G_ThereIsAMaster() && !bs->cur_ps.isJediMaster)
02162                 { //if friendly fire is on in jedi master we can attack people that bug us
02163                         if (!g_friendlyFire.integer)
02164                         {
02165                                 noAttackNonJM = qtrue;
02166                         }
02167                         else
02168                         {
02169                                 closest = 128; //only get mad at people if they get close enough to you to anger you, or hurt you
02170                         }
02171                 }
02172         }
02173 
02174         while (i <= MAX_CLIENTS)
02175         {
02176                 if (i != bs->client && g_entities[i].client && !OnSameTeam(&g_entities[bs->client], &g_entities[i]) && PassStandardEnemyChecks(bs, &g_entities[i]) && BotPVSCheck(g_entities[i].client->ps.origin, bs->eye) && PassLovedOneCheck(bs, &g_entities[i]))
02177                 {
02178                         VectorSubtract(g_entities[i].client->ps.origin, bs->eye, a);
02179                         distcheck = VectorLength(a);
02180                         vectoangles(a, a);
02181 
02182                         if (g_entities[i].client->ps.isJediMaster)
02183                         { //make us think the Jedi Master is close so we'll attack him above all
02184                                 distcheck = 1;
02185                         }
02186 
02187                         if (distcheck < closest && ((InFieldOfVision(bs->viewangles, 90, a) && !BotMindTricked(bs->client, i)) || BotCanHear(bs, &g_entities[i], distcheck)) && OrgVisible(bs->eye, g_entities[i].client->ps.origin, -1))
02188                         {
02189                                 if (BotMindTricked(bs->client, i))
02190                                 {
02191                                         if (distcheck < 256 || (level.time - g_entities[i].client->dangerTime) < 100)
02192                                         {
02193                                                 if (!hasEnemyDist || distcheck < (hasEnemyDist - 128))
02194                                                 { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out
02195                                                         if (!noAttackNonJM || g_entities[i].client->ps.isJediMaster)
02196                                                         {
02197                                                                 closest = distcheck;
02198                                                                 bestindex = i;
02199                                                         }
02200                                                 }
02201                                         }
02202                                 }
02203                                 else
02204                                 {
02205                                         if (!hasEnemyDist || distcheck < (hasEnemyDist - 128))
02206                                         { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out
02207                                                 if (!noAttackNonJM || g_entities[i].client->ps.isJediMaster)
02208                                                 {
02209                                                         closest = distcheck;
02210                                                         bestindex = i;
02211                                                 }
02212                                         }
02213                                 }
02214                         }
02215                 }
02216                 i++;
02217         }
02218         
02219         return bestindex;
02220 }
02221 
02222 int WaitingForNow(bot_state_t *bs, vec3_t goalpos)
02223 { //checks if the bot is doing something along the lines of waiting for an elevator to raise up
02224         vec3_t xybot, xywp, a;
02225 
02226         if (!bs->wpCurrent)
02227         {
02228                 return 0;
02229         }
02230 
02231         if ((int)goalpos[0] != (int)bs->wpCurrent->origin[0] ||
02232                 (int)goalpos[1] != (int)bs->wpCurrent->origin[1] ||
02233                 (int)goalpos[2] != (int)bs->wpCurrent->origin[2])
02234         {
02235                 return 0;
02236         }
02237 
02238         VectorCopy(bs->origin, xybot);
02239         VectorCopy(bs->wpCurrent->origin, xywp);
02240 
02241         xybot[2] = 0;
02242         xywp[2] = 0;
02243 
02244         VectorSubtract(xybot, xywp, a);
02245 
02246         if (VectorLength(a) < 16 && bs->frame_Waypoint_Len > 100)
02247         {
02248                 if (CheckForFunc(bs->origin, bs->client))
02249                 {
02250                         return 1; //we're probably standing on an elevator and riding up/down. Or at least we hope so.
02251                 }
02252         }
02253         else if (VectorLength(a) < 64 && bs->frame_Waypoint_Len > 64 &&
02254                 CheckForFunc(bs->origin, bs->client))
02255         {
02256                 bs->noUseTime = level.time + 2000;
02257         }
02258 
02259         return 0;
02260 }
02261 
02262 //get an ideal distance for us to be at in relation to our opponent
02263 //based on our weapon.
02264 int BotGetWeaponRange(bot_state_t *bs)
02265 {
02266         switch (bs->cur_ps.weapon)
02267         {
02268         case WP_STUN_BATON:
02269         case WP_MELEE:
02270                 return BWEAPONRANGE_MELEE;
02271         case WP_SABER:
02272                 return BWEAPONRANGE_SABER;
02273         case WP_BRYAR_PISTOL:
02274                 return BWEAPONRANGE_MID;
02275         case WP_BLASTER:
02276                 return BWEAPONRANGE_MID;
02277         case WP_DISRUPTOR:
02278                 return BWEAPONRANGE_MID;
02279         case WP_BOWCASTER:
02280                 return BWEAPONRANGE_LONG;
02281         case WP_REPEATER:
02282                 return BWEAPONRANGE_MID;
02283         case WP_DEMP2:
02284                 return BWEAPONRANGE_LONG;
02285         case WP_FLECHETTE:
02286                 return BWEAPONRANGE_LONG;
02287         case WP_ROCKET_LAUNCHER:
02288                 return BWEAPONRANGE_LONG;
02289         case WP_THERMAL:
02290                 return BWEAPONRANGE_LONG;
02291         case WP_TRIP_MINE:
02292                 return BWEAPONRANGE_LONG;
02293         case WP_DET_PACK:
02294                 return BWEAPONRANGE_LONG;
02295         default:
02296                 return BWEAPONRANGE_MID;
02297         }
02298 }
02299 
02300 //see if we want to run away from the opponent for whatever reason
02301 int BotIsAChickenWuss(bot_state_t *bs)
02302 {
02303         int bWRange;
02304 
02305         if (gLevelFlags & LEVELFLAG_IMUSTNTRUNAWAY)
02306         { //The level says we mustn't run away!
02307                 return 0;
02308         }
02309 
02310         if (g_gametype.integer == GT_SINGLE_PLAYER)
02311         { //"coop" (not really)
02312                 return 0;
02313         }
02314 
02315         if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster)
02316         { //Then you may know no fear.
02317                 //Well, unless he's strong.
02318                 if (bs->currentEnemy && bs->currentEnemy->client &&
02319                         bs->currentEnemy->client->ps.isJediMaster &&
02320                         bs->currentEnemy->health > 40 &&
02321                         bs->cur_ps.weapon < WP_ROCKET_LAUNCHER)
02322                 { //explosive weapons are most effective against the Jedi Master
02323                         goto jmPass;
02324                 }
02325                 return 0;
02326         }
02327 
02328         if (g_gametype.integer == GT_CTF && bs->currentEnemy && bs->currentEnemy->client)
02329         {
02330                 if (bs->currentEnemy->client->ps.powerups[PW_REDFLAG] ||
02331                         bs->currentEnemy->client->ps.powerups[PW_BLUEFLAG])
02332                 { //don't be afraid of flag carriers, they must die!
02333                         return 0;
02334                 }
02335         }
02336 
02337 jmPass:
02338         if (bs->chickenWussCalculationTime > level.time)
02339         {
02340                 return 2; //don't want to keep going between two points...
02341         }
02342 
02343         if (bs->cur_ps.fd.forcePowersActive & (1 << FP_RAGE))
02344         { //don't run while raging
02345                 return 0;
02346         }
02347 
02348         if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster)
02349         { //be frightened of the jedi master? I guess in this case.
02350                 return 1;
02351         }
02352 
02353         bs->chickenWussCalculationTime = level.time + MAX_CHICKENWUSS_TIME;
02354 
02355         if (g_entities[bs->client].health < BOT_RUN_HEALTH)
02356         { //we're low on health, let's get away
02357                 return 1;
02358         }
02359 
02360         bWRange = BotGetWeaponRange(bs);
02361 
02362         if (bWRange == BWEAPONRANGE_MELEE || bWRange == BWEAPONRANGE_SABER)
02363         {
02364                 if (bWRange != BWEAPONRANGE_SABER || !bs->saberSpecialist)
02365                 { //run away if we're using melee, or if we're using a saber and not a "saber specialist"
02366                         return 1;
02367                 }
02368         }
02369 
02370         if (bs->cur_ps.weapon == WP_BRYAR_PISTOL)
02371         { //the bryar is a weak weapon, so just try to find a new one if it's what you're having to use
02372                 return 1;
02373         }
02374 
02375         if (bs->currentEnemy && bs->currentEnemy->client &&
02376                 bs->currentEnemy->client->ps.weapon == WP_SABER &&
02377                 bs->frame_Enemy_Len < 512 && bs->cur_ps.weapon != WP_SABER)
02378         { //if close to an enemy with a saber and not using a saber, then try to back off
02379                 return 1;
02380         }
02381 
02382         if ((level.time-bs->cur_ps.electrifyTime) < 16000)
02383         { //lightning is dangerous.
02384                 return 1;
02385         }
02386 
02387         //didn't run, reset the timer
02388         bs->chickenWussCalculationTime = 0;
02389 
02390         return 0;
02391 }
02392 
02393 //look for "bad things". bad things include detpacks, thermal detonators,
02394 //and other dangerous explodey items.
02395 gentity_t *GetNearestBadThing(bot_state_t *bs)
02396 {
02397         int i = 0;
02398         float glen;
02399         vec3_t hold;
02400         int bestindex = 0;
02401         float bestdist = 800; //if not within a radius of 800, it's no threat anyway
02402         int foundindex = 0;
02403         float factor = 0;
02404         gentity_t *ent;
02405         trace_t tr;
02406 
02407         while (i < level.num_entities)
02408         {
02409                 ent = &g_entities[i];
02410 
02411                 if ( (ent &&
02412                         !ent->client &&
02413                         ent->inuse &&
02414                         ent->damage &&
02415                         /*(ent->s.weapon == WP_THERMAL || ent->s.weapon == WP_FLECHETTE)*/
02416                         ent->s.weapon &&
02417                         ent->splashDamage) ||
02418                         (ent &&
02419                         ent->genericValue5 == 1000 &&
02420                         ent->inuse &&
02421                         ent->health > 0 &&
02422                         ent->genericValue3 != bs->client &&
02423                         g_entities[ent->genericValue3].client && !OnSameTeam(&g_entities[bs->client], &g_entities[ent->genericValue3])) )
02424                 { //try to escape from anything with a non-0 s.weapon and non-0 damage. This hopefully only means dangerous projectiles.
02425                   //Or a sentry gun if bolt_Head == 1000. This is a terrible hack, yes.
02426                         VectorSubtract(bs->origin, ent->r.currentOrigin, hold);
02427                         glen = VectorLength(hold);
02428 
02429                         if (ent->s.weapon != WP_THERMAL && ent->s.weapon != WP_FLECHETTE &&
02430                                 ent->s.weapon != WP_DET_PACK && ent->s.weapon != WP_TRIP_MINE)
02431                         {
02432                                 factor = 0.5;
02433 
02434                                 if (ent->s.weapon && glen <= 256 && bs->settings.skill > 2)
02435                                 { //it's a projectile so push it away
02436                                         bs->doForcePush = level.time + 700;
02437                                         //G_Printf("PUSH PROJECTILE\n");
02438                                 }
02439                         }
02440                         else
02441                         {
02442                                 factor = 1;
02443                         }
02444 
02445                         if (ent->s.weapon == WP_ROCKET_LAUNCHER &&
02446                                 (ent->r.ownerNum == bs->client ||
02447                                 (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS &&
02448                                 g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) )
02449                         { //don't be afraid of your own rockets or your teammates' rockets
02450                                 factor = 0;
02451                         }
02452 
02453                         if (glen < bestdist*factor && BotPVSCheck(bs->origin, ent->s.pos.trBase))
02454                         {
02455                                 trap_Trace(&tr, bs->origin, NULL, NULL, ent->s.pos.trBase, bs->client, MASK_SOLID);
02456 
02457                                 if (tr.fraction == 1 || tr.entityNum == ent->s.number)
02458                                 {
02459                                         bestindex = i;
02460                                         bestdist = glen;
02461                                         foundindex = 1;
02462                                 }
02463                         }
02464                 }
02465 
02466                 if (ent && !ent->client && ent->inuse && ent->damage && ent->s.weapon && ent->r.ownerNum < MAX_CLIENTS && ent->r.ownerNum >= 0)
02467                 { //if we're in danger of a projectile belonging to someone and don't have an enemy, set the enemy to them
02468                         gentity_t *projOwner = &g_entities[ent->r.ownerNum];
02469 
02470                         if (projOwner && projOwner->inuse && projOwner->client)
02471                         {
02472                                 if (!bs->currentEnemy)
02473                                 {
02474                                         if (PassStandardEnemyChecks(bs, projOwner))
02475                                         {
02476                                                 if (PassLovedOneCheck(bs, projOwner))
02477                                                 {
02478                                                         VectorSubtract(bs->origin, ent->r.currentOrigin, hold);
02479                                                         glen = VectorLength(hold);
02480 
02481                                                         if (glen < 512)
02482                                                         {
02483                                                                 bs->currentEnemy = projOwner;
02484                                                                 bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
02485                                                         }
02486                                                 }
02487                                         }
02488                                 }
02489                         }
02490                 }
02491 
02492                 i++;
02493         }
02494 
02495         if (foundindex)
02496         {
02497                 bs->dontGoBack = level.time + 1500;
02498                 return &g_entities[bestindex];
02499         }
02500         else
02501         {
02502                 return NULL;
02503         }
02504 }
02505 
02506 //Keep our CTF priorities on defending our team's flag
02507 int BotDefendFlag(bot_state_t *bs)
02508 {
02509         wpobject_t *flagPoint;
02510         vec3_t a;
02511 
02512         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
02513         {
02514                 flagPoint = flagRed;
02515         }
02516         else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE)
02517         {
02518                 flagPoint = flagBlue;
02519         }
02520         else
02521         {
02522                 return 0;
02523         }
02524 
02525         if (!flagPoint)
02526         {
02527                 return 0;
02528         }
02529 
02530         VectorSubtract(bs->origin, flagPoint->origin, a);
02531 
02532         if (VectorLength(a) > BASE_GUARD_DISTANCE)
02533         {
02534                 bs->wpDestination = flagPoint;
02535         }
02536 
02537         return 1;
02538 }
02539 
02540 //Keep our CTF priorities on getting the other team's flag
02541 int BotGetEnemyFlag(bot_state_t *bs)
02542 {
02543         wpobject_t *flagPoint;
02544         vec3_t a;
02545 
02546         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
02547         {
02548                 flagPoint = flagBlue;
02549         }
02550         else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE)
02551         {
02552                 flagPoint = flagRed;
02553         }
02554         else
02555         {
02556                 return 0;
02557         }
02558 
02559         if (!flagPoint)
02560         {
02561                 return 0;
02562         }
02563 
02564         VectorSubtract(bs->origin, flagPoint->origin, a);
02565 
02566         if (VectorLength(a) > BASE_GETENEMYFLAG_DISTANCE)
02567         {
02568                 bs->wpDestination = flagPoint;
02569         }
02570 
02571         return 1;
02572 }
02573 
02574 //Our team's flag is gone, so try to get it back
02575 int BotGetFlagBack(bot_state_t *bs)
02576 {
02577         int i = 0;
02578         int myFlag = 0;
02579         int foundCarrier = 0;
02580         int tempInt = 0;
02581         gentity_t *ent = NULL;
02582         vec3_t usethisvec;
02583 
02584         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
02585         {
02586                 myFlag = PW_REDFLAG;
02587         }
02588         else
02589         {
02590                 myFlag = PW_BLUEFLAG;
02591         }
02592 
02593         while (i < MAX_CLIENTS)
02594         {
02595                 ent = &g_entities[i];
02596 
02597                 if (ent && ent->client && ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
02598                 {
02599                         foundCarrier = 1;
02600                         break;
02601                 }
02602 
02603                 i++;
02604         }
02605 
02606         if (!foundCarrier)
02607         {
02608                 return 0;
02609         }
02610 
02611         if (!ent)
02612         {
02613                 return 0;
02614         }
02615 
02616         if (bs->wpDestSwitchTime < level.time)
02617         {
02618                 if (ent->client)
02619                 {
02620                         VectorCopy(ent->client->ps.origin, usethisvec);
02621                 }
02622                 else
02623                 {
02624                         VectorCopy(ent->s.origin, usethisvec);
02625                 }
02626 
02627                 tempInt = GetNearestVisibleWP(usethisvec, 0);
02628 
02629                 if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
02630                 {
02631                         bs->wpDestination = gWPArray[tempInt];
02632                         bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
02633                 }
02634         }
02635 
02636         return 1;
02637 }
02638 
02639 //Someone else on our team has the enemy flag, so try to get
02640 //to their assistance
02641 int BotGuardFlagCarrier(bot_state_t *bs)
02642 {
02643         int i = 0;
02644         int enemyFlag = 0;
02645         int foundCarrier = 0;
02646         int tempInt = 0;
02647         gentity_t *ent = NULL;
02648         vec3_t usethisvec;
02649 
02650         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
02651         {
02652                 enemyFlag = PW_BLUEFLAG;
02653         }
02654         else
02655         {
02656                 enemyFlag = PW_REDFLAG;
02657         }
02658 
02659         while (i < MAX_CLIENTS)
02660         {
02661                 ent = &g_entities[i];
02662 
02663                 if (ent && ent->client && ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
02664                 {
02665                         foundCarrier = 1;
02666                         break;
02667                 }
02668 
02669                 i++;
02670         }
02671 
02672         if (!foundCarrier)
02673         {
02674                 return 0;
02675         }
02676 
02677         if (!ent)
02678         {
02679                 return 0;
02680         }
02681 
02682         if (bs->wpDestSwitchTime < level.time)
02683         {
02684                 if (ent->client)
02685                 {
02686                         VectorCopy(ent->client->ps.origin, usethisvec);
02687                 }
02688                 else
02689                 {
02690                         VectorCopy(ent->s.origin, usethisvec);
02691                 }
02692 
02693                 tempInt = GetNearestVisibleWP(usethisvec, 0);
02694 
02695                 if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
02696                 {
02697                         bs->wpDestination = gWPArray[tempInt];
02698                         bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
02699                 }
02700         }
02701 
02702         return 1;
02703 }
02704 
02705 //We have the flag, let's get it home.
02706 int BotGetFlagHome(bot_state_t *bs)
02707 {
02708         wpobject_t *flagPoint;
02709         vec3_t a;
02710 
02711         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
02712         {
02713                 flagPoint = flagRed;
02714         }
02715         else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE)
02716         {
02717                 flagPoint = flagBlue;
02718         }
02719         else
02720         {
02721                 return 0;
02722         }
02723 
02724         if (!flagPoint)
02725         {
02726                 return 0;
02727         }
02728 
02729         VectorSubtract(bs->origin, flagPoint->origin, a);
02730 
02731         if (VectorLength(a) > BASE_FLAGWAIT_DISTANCE)
02732         {
02733                 bs->wpDestination = flagPoint;
02734         }
02735 
02736         return 1;
02737 }
02738 
02739 void GetNewFlagPoint(wpobject_t *wp, gentity_t *flagEnt, int team)
02740 { //get the nearest possible waypoint to the flag since it's not in its original position
02741         int i = 0;
02742         vec3_t a, mins, maxs;
02743         float bestdist;
02744         float testdist;
02745         int bestindex = 0;
02746         int foundindex = 0;
02747         trace_t tr;
02748 
02749         mins[0] = -15;
02750         mins[1] = -15;
02751         mins[2] = -5;
02752         maxs[0] = 15;
02753         maxs[1] = 15;
02754         maxs[2] = 5;
02755 
02756         VectorSubtract(wp->origin, flagEnt->s.pos.trBase, a);
02757 
02758         bestdist = VectorLength(a);
02759 
02760         if (bestdist <= WP_KEEP_FLAG_DIST)
02761         {
02762                 trap_Trace(&tr, wp->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID);
02763 
02764                 if (tr.fraction == 1)
02765                 { //this point is good
02766                         return;
02767                 }
02768         }
02769 
02770         while (i < gWPNum)
02771         {
02772                 VectorSubtract(gWPArray[i]->origin, flagEnt->s.pos.trBase, a);
02773                 testdist = VectorLength(a);
02774 
02775                 if (testdist < bestdist)
02776                 {
02777                         trap_Trace(&tr, gWPArray[i]->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID);
02778 
02779                         if (tr.fraction == 1)
02780                         {
02781                                 foundindex = 1;
02782                                 bestindex = i;
02783                                 bestdist = testdist;
02784                         }
02785                 }
02786 
02787                 i++;
02788         }
02789 
02790         if (foundindex)
02791         {
02792                 if (team == TEAM_RED)
02793                 {
02794                         flagRed = gWPArray[bestindex];
02795                 }
02796                 else
02797                 {
02798                         flagBlue = gWPArray[bestindex];
02799                 }
02800         }
02801 }
02802 
02803 //See if our CTF state should take priority in our nav routines
02804 int CTFTakesPriority(bot_state_t *bs)
02805 {
02806         gentity_t *ent = NULL;
02807         int enemyFlag = 0;
02808         int myFlag = 0;
02809         int enemyHasOurFlag = 0;
02810         int weHaveEnemyFlag = 0;
02811         int numOnMyTeam = 0;
02812         int numOnEnemyTeam = 0;
02813         int numAttackers = 0;
02814         int numDefenders = 0;
02815         int i = 0;
02816         int idleWP;
02817         int dosw = 0;
02818         wpobject_t *dest_sw = NULL;
02819 #ifdef BOT_CTF_DEBUG
02820         vec3_t t;
02821 
02822         G_Printf("CTFSTATE: %s\n", ctfStateNames[bs->ctfState]);
02823 #endif
02824 
02825         if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY)
02826         {
02827                 return 0;
02828         }
02829 
02830         if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
02831                 (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME)
02832         { //get the nearest weapon laying around base before heading off for battle
02833                 idleWP = GetBestIdleGoal(bs);
02834 
02835                 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
02836                 {
02837                         if (bs->wpDestSwitchTime < level.time)
02838                         {
02839                                 bs->wpDestination = gWPArray[idleWP];
02840                         }
02841                         return 1;
02842                 }
02843         }
02844         else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
02845                 (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_CTF &&
02846                 bs->wpDestination && bs->wpDestination->weight)
02847         {
02848                 dest_sw = bs->wpDestination;
02849                 dosw = 1;
02850         }
02851 
02852         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
02853         {
02854                 myFlag = PW_REDFLAG;
02855         }
02856         else
02857         {
02858                 myFlag = PW_BLUEFLAG;
02859         }
02860 
02861         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
02862         {
02863                 enemyFlag = PW_BLUEFLAG;
02864         }
02865         else
02866         {
02867                 enemyFlag = PW_REDFLAG;
02868         }
02869 
02870         if (!flagRed || !flagBlue ||
02871                 !flagRed->inuse || !flagBlue->inuse ||
02872                 !eFlagRed || !eFlagBlue)
02873         {
02874                 return 0;
02875         }
02876 
02877 #ifdef BOT_CTF_DEBUG
02878         VectorCopy(flagRed->origin, t);
02879         t[2] += 128;
02880         G_TestLine(flagRed->origin, t, 0x0000ff, 500);
02881 
02882         VectorCopy(flagBlue->origin, t);
02883         t[2] += 128;
02884         G_TestLine(flagBlue->origin, t, 0x0000ff, 500);
02885 #endif
02886 
02887         if (droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM))
02888         {
02889                 GetNewFlagPoint(flagRed, droppedRedFlag, TEAM_RED);
02890         }
02891         else
02892         {
02893                 flagRed = oFlagRed;
02894         }
02895 
02896         if (droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM))
02897         {
02898                 GetNewFlagPoint(flagBlue, droppedBlueFlag, TEAM_BLUE);
02899         }
02900         else
02901         {
02902                 flagBlue = oFlagBlue;
02903         }
02904 
02905         if (!bs->ctfState)
02906         {
02907                 return 0;
02908         }
02909 
02910         i = 0;
02911 
02912         while (i < MAX_CLIENTS)
02913         {
02914                 ent = &g_entities[i];
02915 
02916                 if (ent && ent->client)
02917                 {
02918                         if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
02919                         {
02920                                 weHaveEnemyFlag = 1;
02921                         }
02922                         else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
02923                         {
02924                                 enemyHasOurFlag = 1;
02925                         }
02926 
02927                         if (OnSameTeam(&g_entities[bs->client], ent))
02928                         {
02929                                 numOnMyTeam++;
02930                         }
02931                         else
02932                         {
02933                                 numOnEnemyTeam++;
02934                         }
02935 
02936                         if (botstates[ent->s.number])
02937                         {
02938                                 if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER ||
02939                                         botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL)
02940                                 {
02941                                         numAttackers++;
02942                                 }
02943                                 else
02944                                 {
02945                                         numDefenders++;
02946                                 }
02947                         }
02948                         else
02949                         { //assume real players to be attackers in our logic
02950                                 numAttackers++;
02951                         }
02952                 }
02953                 i++;
02954         }
02955 
02956         if (bs->cur_ps.powerups[enemyFlag])
02957         {
02958                 if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag)
02959                 {
02960                         bs->ctfState = CTFSTATE_RETRIEVAL;
02961                 }
02962                 else
02963                 {
02964                         bs->ctfState = CTFSTATE_GETFLAGHOME;
02965                 }
02966         }
02967         else if (bs->ctfState == CTFSTATE_GETFLAGHOME)
02968         {
02969                 bs->ctfState = 0;
02970         }
02971 
02972         if (bs->state_Forced)
02973         {
02974                 bs->ctfState = bs->state_Forced;
02975         }
02976 
02977         if (bs->ctfState == CTFSTATE_DEFENDER)
02978         {
02979                 if (BotDefendFlag(bs))
02980                 {
02981                         goto success;
02982                 }
02983         }
02984 
02985         if (bs->ctfState == CTFSTATE_ATTACKER)
02986         {
02987                 if (BotGetEnemyFlag(bs))
02988                 {
02989                         goto success;
02990                 }
02991         }
02992 
02993         if (bs->ctfState == CTFSTATE_RETRIEVAL)
02994         {
02995                 if (BotGetFlagBack(bs))
02996                 {
02997                         goto success;
02998                 }
02999                 else
03000                 { //can't find anyone on another team being a carrier, so ignore this priority
03001                         bs->ctfState = 0;
03002                 }
03003         }
03004 
03005         if (bs->ctfState == CTFSTATE_GUARDCARRIER)
03006         {
03007                 if (BotGuardFlagCarrier(bs))
03008                 {
03009                         goto success;
03010                 }
03011                 else
03012                 { //can't find anyone on our team being a carrier, so ignore this priority
03013                         bs->ctfState = 0;
03014                 }
03015         }
03016 
03017         if (bs->ctfState == CTFSTATE_GETFLAGHOME)
03018         {
03019                 if (BotGetFlagHome(bs))
03020                 {
03021                         goto success;
03022                 }
03023         }
03024 
03025         return 0;
03026 
03027 success:
03028         if (dosw)
03029         { //allow ctf code to run, but if after a particular item then keep going after it
03030                 bs->wpDestination = dest_sw;
03031         }
03032 
03033         return 1;
03034 }
03035 
03036 int EntityVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore, int ignore2)
03037 {
03038         trace_t tr;
03039 
03040         trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID);
03041 
03042         if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
03043         {
03044                 return 1;
03045         }
03046         else if (tr.entityNum != ENTITYNUM_NONE && tr.entityNum == ignore2)
03047         {
03048                 return 1;
03049         }
03050 
03051         return 0;
03052 }
03053 
03054 //Get the closest objective for siege and go after it
03055 int Siege_TargetClosestObjective(bot_state_t *bs, int flag)
03056 {
03057         int i = 0;
03058         int bestindex = -1;
03059         float testdistance = 0;
03060         float bestdistance = 999999999;
03061         gentity_t *goalent;
03062         vec3_t a, dif;
03063         vec3_t mins, maxs;
03064 
03065         mins[0] = -1;
03066         mins[1] = -1;
03067         mins[2] = -1;
03068 
03069         maxs[0] = 1;
03070         maxs[1] = 1;
03071         maxs[2] = 1;
03072 
03073         if ( bs->wpDestination && (bs->wpDestination->flags & flag) && bs->wpDestination->associated_entity != ENTITYNUM_NONE &&
03074                  &g_entities[bs->wpDestination->associated_entity] && g_entities[bs->wpDestination->associated_entity].use )
03075         {
03076                 goto hasPoint;
03077         }
03078 
03079         while (i < gWPNum)
03080         {
03081                 if ( gWPArray[i] && gWPArray[i]->inuse && (gWPArray[i]->flags & flag) && gWPArray[i]->associated_entity != ENTITYNUM_NONE &&
03082                          &g_entities[gWPArray[i]->associated_entity] && g_entities[gWPArray[i]->associated_entity].use )
03083                 {
03084                         VectorSubtract(gWPArray[i]->origin, bs->origin, a);
03085                         testdistance = VectorLength(a);
03086 
03087                         if (testdistance < bestdistance)
03088                         {
03089                                 bestdistance = testdistance;
03090                                 bestindex = i;
03091                         }
03092                 }
03093 
03094                 i++;
03095         }
03096 
03097         if (bestindex != -1)
03098         {
03099                 bs->wpDestination = gWPArray[bestindex];
03100         }
03101         else
03102         {
03103                 return 0;
03104         }
03105 hasPoint:
03106         goalent = &g_entities[bs->wpDestination->associated_entity];
03107 
03108         if (!goalent)
03109         {
03110                 return 0;
03111         }
03112 
03113         VectorSubtract(bs->origin, bs->wpDestination->origin, a);
03114 
03115         testdistance = VectorLength(a);
03116 
03117         dif[0] = (goalent->r.absmax[0]+goalent->r.absmin[0])/2;
03118         dif[1] = (goalent->r.absmax[1]+goalent->r.absmin[1])/2;
03119         dif[2] = (goalent->r.absmax[2]+goalent->r.absmin[2])/2;
03120         //brush models can have tricky origins, so this is our hacky method of getting the center point
03121 
03122         if (goalent->takedamage && testdistance < BOT_MIN_SIEGE_GOAL_SHOOT &&
03123                 EntityVisibleBox(bs->origin, mins, maxs, dif, bs->client, goalent->s.number))
03124         {
03125                 bs->shootGoal = goalent;
03126                 bs->touchGoal = NULL;
03127         }
03128         else if (goalent->use && testdistance < BOT_MIN_SIEGE_GOAL_TRAVEL)
03129         {
03130                 bs->shootGoal = NULL;
03131                 bs->touchGoal = goalent;
03132         }
03133         else
03134         { //don't know how to handle this goal object!
03135                 bs->shootGoal = NULL;
03136                 bs->touchGoal = NULL;
03137         }
03138 
03139         if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE ||
03140                 BotGetWeaponRange(bs) == BWEAPONRANGE_SABER)
03141         {
03142                 bs->shootGoal = NULL; //too risky
03143         }
03144 
03145         if (bs->touchGoal)
03146         {
03147                 //G_Printf("Please, master, let me touch it!\n");
03148                 VectorCopy(dif, bs->goalPosition);
03149         }
03150 
03151         return 1;
03152 }
03153 
03154 void Siege_DefendFromAttackers(bot_state_t *bs)
03155 { //this may be a little cheap, but the best way to find our defending point is probably
03156   //to just find the nearest person on the opposing team since they'll most likely
03157   //be on offense in this situation
03158         int wpClose = -1;
03159         int i = 0;
03160         float testdist = 999999;
03161         int bestindex = -1;
03162         float bestdist = 999999;
03163         gentity_t *ent;
03164         vec3_t a;
03165 
03166         while (i < MAX_CLIENTS)
03167         {
03168                 ent = &g_entities[i];
03169 
03170                 if (ent && ent->client && ent->client->sess.sessionTeam != g_entities[bs->client].client->sess.sessionTeam &&
03171                         ent->health > 0 && ent->client->sess.sessionTeam != TEAM_SPECTATOR)
03172                 {
03173                         VectorSubtract(ent->client->ps.origin, bs->origin, a);
03174 
03175                         testdist = VectorLength(a);
03176 
03177                         if (testdist < bestdist)
03178                         {
03179                                 bestindex = i;
03180                                 bestdist = testdist;
03181                         }
03182                 }
03183 
03184                 i++;
03185         }
03186 
03187         if (bestindex == -1)
03188         {
03189                 return;
03190         }
03191 
03192         wpClose = GetNearestVisibleWP(g_entities[bestindex].client->ps.origin, -1);     
03193 
03194         if (wpClose != -1 && gWPArray[wpClose] && gWPArray[wpClose]->inuse)
03195         {
03196                 bs->wpDestination = gWPArray[wpClose];
03197                 bs->destinationGrabTime = level.time + 10000;
03198         }
03199 }
03200 
03201 //how many defenders on our team?
03202 int Siege_CountDefenders(bot_state_t *bs)
03203 {
03204         int i = 0;
03205         int num = 0;
03206         gentity_t *ent;
03207         bot_state_t *bot;
03208 
03209         while (i < MAX_CLIENTS)
03210         {
03211                 ent = &g_entities[i];
03212                 bot = botstates[i];
03213 
03214                 if (ent && ent->client && bot)
03215                 {
03216                         if (bot->siegeState == SIEGESTATE_DEFENDER &&
03217                                 ent->client->sess.sessionTeam == g_entities[bs->client].client->sess.sessionTeam)
03218                         {
03219                                 num++;
03220                         }
03221                 }
03222 
03223                 i++;
03224         }
03225 
03226         return num;
03227 }
03228 
03229 //how many other players on our team?
03230 int Siege_CountTeammates(bot_state_t *bs)
03231 {
03232         int i = 0;
03233         int num = 0;
03234         gentity_t *ent;
03235 
03236         while (i < MAX_CLIENTS)
03237         {
03238                 ent = &g_entities[i];
03239 
03240                 if (ent && ent->client)
03241                 {
03242                         if (ent->client->sess.sessionTeam == g_entities[bs->client].client->sess.sessionTeam)
03243                         {
03244                                 num++;
03245                         }
03246                 }
03247 
03248                 i++;
03249         }
03250 
03251         return num;
03252 }
03253 
03254 //see if siege objective completion should take priority in our
03255 //nav routines.
03256 int SiegeTakesPriority(bot_state_t *bs)
03257 {
03258         int attacker;
03259         int flagForDefendableObjective;
03260         int flagForAttackableObjective;
03261         int defenders, teammates;
03262         int idleWP;
03263         wpobject_t *dest_sw = NULL;
03264         int dosw = 0;
03265         gclient_t *bcl;
03266         vec3_t dif;
03267         trace_t tr;
03268 
03269         if (g_gametype.integer != GT_SIEGE)
03270         {
03271                 return 0;
03272         }
03273 
03274         bcl = g_entities[bs->client].client;
03275 
03276         if (!bcl)
03277         {
03278                 return 0;
03279         }
03280 
03281         if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
03282                 (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME)
03283         { //get the nearest weapon laying around base before heading off for battle
03284                 idleWP = GetBestIdleGoal(bs);
03285 
03286                 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
03287                 {
03288                         if (bs->wpDestSwitchTime < level.time)
03289                         {
03290                                 bs->wpDestination = gWPArray[idleWP];
03291                         }
03292                         return 1;
03293                 }
03294         }
03295         else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
03296                 (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_TIME &&
03297                 bs->wpDestination && bs->wpDestination->weight)
03298         {
03299                 dest_sw = bs->wpDestination;
03300                 dosw = 1;
03301         }
03302 
03303         if (bcl->sess.sessionTeam == SIEGETEAM_TEAM1)
03304         {
03305                 attacker = imperial_attackers;
03306                 flagForDefendableObjective = WPFLAG_SIEGE_REBELOBJ;
03307                 flagForAttackableObjective = WPFLAG_SIEGE_IMPERIALOBJ;
03308         }
03309         else
03310         {
03311                 attacker = rebel_attackers;
03312                 flagForDefendableObjective = WPFLAG_SIEGE_IMPERIALOBJ;
03313                 flagForAttackableObjective = WPFLAG_SIEGE_REBELOBJ;
03314         }
03315 
03316         if (attacker)
03317         {
03318                 bs->siegeState = SIEGESTATE_ATTACKER;
03319         }
03320         else
03321         {
03322                 bs->siegeState = SIEGESTATE_DEFENDER;
03323                 defenders = Siege_CountDefenders(bs);
03324                 teammates = Siege_CountTeammates(bs);
03325 
03326                 if (defenders > teammates/3 && teammates > 1)
03327                 { //devote around 1/4 of our team to completing our own side goals even if we're a defender.
03328                   //If we have no side goals we will realize that later on and join the defenders
03329                         bs->siegeState = SIEGESTATE_ATTACKER;
03330                 }
03331         }
03332 
03333         if (bs->state_Forced)
03334         {
03335                 bs->siegeState = bs->state_Forced;
03336         }
03337 
03338         if (bs->siegeState == SIEGESTATE_ATTACKER)
03339         {
03340                 if (!Siege_TargetClosestObjective(bs, flagForAttackableObjective))
03341                 { //looks like we have no goals other than to keep the other team from completing objectives
03342                         Siege_DefendFromAttackers(bs);
03343                         if (bs->shootGoal)
03344                         {
03345                                 dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
03346                                 dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
03347                                 dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
03348                                 
03349                                 if (!BotPVSCheck(bs->origin, dif))
03350                                 {
03351                                         bs->shootGoal = NULL;
03352                                 }
03353                                 else
03354                                 {
03355                                         trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID);
03356 
03357                                         if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number)
03358                                         {
03359                                                 bs->shootGoal = NULL;
03360                                         }
03361                                 }
03362                         }
03363                 }
03364         }
03365         else if (bs->siegeState == SIEGESTATE_DEFENDER)
03366         {
03367                 Siege_DefendFromAttackers(bs);
03368                 if (bs->shootGoal)
03369                 {
03370                         dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
03371                         dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
03372                         dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
03373                                 
03374                         if (!BotPVSCheck(bs->origin, dif))
03375                         {
03376                                 bs->shootGoal = NULL;
03377                         }
03378                         else
03379                         {
03380                                 trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID);
03381 
03382                                 if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number)
03383                                 {
03384                                         bs->shootGoal = NULL;
03385                                 }
03386                         }
03387                 }
03388         }
03389         else
03390         { //get busy!
03391                 Siege_TargetClosestObjective(bs, flagForAttackableObjective);
03392                 if (bs->shootGoal)
03393                 {
03394                         dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
03395                         dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
03396                         dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
03397                                 
03398                         if (!BotPVSCheck(bs->origin, dif))
03399                         {
03400                                 bs->shootGoal = NULL;
03401                         }
03402                         else
03403                         {
03404                                 trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID);
03405 
03406                                 if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number)
03407                                 {
03408                                         bs->shootGoal = NULL;
03409                                 }
03410                         }
03411                 }
03412         }
03413 
03414         if (dosw)
03415         { //allow siege objective code to run, but if after a particular item then keep going after it
03416                 bs->wpDestination = dest_sw;
03417         }
03418 
03419         return 1;
03420 }
03421 
03422 //see if jedi master priorities should take priority in our nav
03423 //routines.
03424 int JMTakesPriority(bot_state_t *bs)
03425 {
03426         int i = 0;
03427         int wpClose = -1;
03428         gentity_t *theImportantEntity = NULL;
03429 
03430         if (g_gametype.integer != GT_JEDIMASTER)
03431         {
03432                 return 0;
03433         }
03434 
03435         if (bs->cur_ps.isJediMaster)
03436         {
03437                 return 0;
03438         }
03439 
03440         //jmState becomes the index for the one who carries the saber. If jmState is -1 then the saber is currently
03441         //without an owner
03442         bs->jmState = -1;
03443 
03444         while (i < MAX_CLIENTS)
03445         {
03446                 if (g_entities[i].client && g_entities[i].inuse &&
03447                         g_entities[i].client->ps.isJediMaster)
03448                 {
03449                         bs->jmState = i;
03450                         break;
03451                 }
03452 
03453                 i++;
03454         }
03455 
03456         if (bs->jmState != -1)
03457         {
03458                 theImportantEntity = &g_entities[bs->jmState];
03459         }
03460         else
03461         {
03462                 theImportantEntity = gJMSaberEnt;
03463         }
03464 
03465         if (theImportantEntity && theImportantEntity->inuse && bs->destinationGrabTime < level.time)
03466         {
03467                 if (theImportantEntity->client)
03468                 {
03469                         wpClose = GetNearestVisibleWP(theImportantEntity->client->ps.origin, theImportantEntity->s.number);     
03470                 }
03471                 else
03472                 {
03473                         wpClose = GetNearestVisibleWP(theImportantEntity->r.currentOrigin, theImportantEntity->s.number);       
03474                 }
03475 
03476                 if (wpClose != -1 && gWPArray[wpClose] && gWPArray[wpClose]->inuse)
03477                 {
03478                         /*
03479                         Com_Printf("BOT GRABBED IDEAL JM LOCATION\n");
03480                         if (bs->wpDestination != gWPArray[wpClose])
03481                         {
03482                                 Com_Printf("IDEAL WAS NOT ALREADY IDEAL\n");
03483 
03484                                 if (!bs->wpDestination)
03485                                 {
03486                                         Com_Printf("IDEAL WAS NULL\n");
03487                                 }
03488                         }
03489                         */
03490                         bs->wpDestination = gWPArray[wpClose];
03491                         bs->destinationGrabTime = level.time + 4000;
03492                 }
03493         }
03494 
03495         return 1;
03496 }
03497 
03498 //see if we already have an item/powerup/etc. that is associated
03499 //with this waypoint.
03500 int BotHasAssociated(bot_state_t *bs, wpobject_t *wp)
03501 {
03502         gentity_t *as;
03503 
03504         if (wp->associated_entity == ENTITYNUM_NONE)
03505         { //make it think this is an item we have so we don't go after nothing
03506                 return 1;
03507         }
03508 
03509         as = &g_entities[wp->associated_entity];
03510 
03511         if (!as || !as->item)
03512         {
03513                 return 0;
03514         }
03515 
03516         if (as->item->giType == IT_WEAPON)
03517         {
03518                 if (bs->cur_ps.stats[STAT_WEAPONS] & (1 << as->item->giTag))
03519                 {
03520                         return 1;
03521                 }
03522 
03523                 return 0;
03524         }
03525         else if (as->item->giType == IT_HOLDABLE)
03526         {
03527                 if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << as->item->giTag))
03528                 {
03529                         return 1;
03530                 }
03531 
03532                 return 0;
03533         }
03534         else if (as->item->giType == IT_POWERUP)
03535         {
03536                 if (bs->cur_ps.powerups[as->item->giTag])
03537                 {
03538                         return 1;
03539                 }
03540 
03541                 return 0;
03542         }
03543         else if (as->item->giType == IT_AMMO)
03544         {
03545                 if (bs->cur_ps.ammo[as->item->giTag] > 10) //hack
03546                 {
03547                         return 1;
03548                 }
03549 
03550                 return 0;
03551         }
03552 
03553         return 0;
03554 }
03555 
03556 //we don't really have anything we want to do right now,
03557 //let's just find the best thing to do given the current
03558 //situation.
03559 int GetBestIdleGoal(bot_state_t *bs)
03560 {
03561         int i = 0;
03562         int highestweight = 0;
03563         int desiredindex = -1;
03564         int dist_to_weight = 0;
03565         int traildist;
03566 
03567         if (!bs->wpCurrent)
03568         {
03569                 return -1;
03570         }
03571 
03572         if (bs->isCamper != 2)
03573         {
03574                 if (bs->randomNavTime < level.time)
03575                 {
03576                         if (Q_irand(1, 10) < 5)
03577                         {
03578                                 bs->randomNav = 1;
03579                         }
03580                         else
03581                         {
03582                                 bs->randomNav = 0;
03583                         }
03584                         
03585                         bs->randomNavTime = level.time + Q_irand(5000, 15000);
03586                 }
03587         }
03588 
03589         if (bs->randomNav)
03590         { //stop looking for items and/or camping on them
03591                 return -1;
03592         }
03593 
03594         while (i < gWPNum)
03595         {
03596                 if (gWPArray[i] &&
03597                         gWPArray[i]->inuse &&
03598                         (gWPArray[i]->flags & WPFLAG_GOALPOINT) &&
03599                         gWPArray[i]->weight > highestweight &&
03600                         !BotHasAssociated(bs, gWPArray[i]))
03601                 {
03602                         traildist = TotalTrailDistance(bs->wpCurrent->index, i, bs);
03603 
03604                         if (traildist != -1)
03605                         {
03606                                 dist_to_weight = (int)traildist/10000;
03607                                 dist_to_weight = (gWPArray[i]->weight)-dist_to_weight;
03608 
03609                                 if (dist_to_weight > highestweight)
03610                                 {
03611                                         highestweight = dist_to_weight;
03612                                         desiredindex = i;
03613                                 }
03614                         }
03615                 }
03616 
03617                 i++;
03618         }
03619 
03620         return desiredindex;
03621 }
03622 
03623 //go through the list of possible priorities for navigating
03624 //and work out the best destination point.
03625 void GetIdealDestination(bot_state_t *bs)
03626 {
03627         int tempInt, cWPIndex, bChicken, idleWP;
03628         float distChange, plusLen, minusLen;
03629         vec3_t usethisvec, a;
03630         gentity_t *badthing;
03631 
03632 #ifdef _DEBUG
03633         trap_Cvar_Update(&bot_nogoals);
03634 
03635         if (bot_nogoals.integer)
03636         {
03637                 return;
03638         }
03639 #endif
03640 
03641         if (!bs->wpCurrent)
03642         {
03643                 return;
03644         }
03645 
03646         if ((level.time - bs->escapeDirTime) > 4000)
03647         {
03648                 badthing = GetNearestBadThing(bs);
03649         }
03650         else
03651         {
03652                 badthing = NULL;
03653         }
03654 
03655         if (badthing && badthing->inuse &&
03656                 badthing->health > 0 && badthing->takedamage)
03657         {
03658                 bs->dangerousObject = badthing;
03659         }
03660         else
03661         {
03662                 bs->dangerousObject = NULL;
03663         }
03664 
03665         if (!badthing && bs->wpDestIgnoreTime > level.time)
03666         {
03667                 return;
03668         }
03669 
03670         if (!badthing && bs->dontGoBack > level.time)
03671         {
03672                 if (bs->wpDestination)
03673                 {
03674                         bs->wpStoreDest = bs->wpDestination;
03675                 }
03676                 bs->wpDestination = NULL;
03677                 return;
03678         }
03679         else if (!badthing && bs->wpStoreDest)
03680         { //after we finish running away, switch back to our original destination
03681                 bs->wpDestination = bs->wpStoreDest;
03682                 bs->wpStoreDest = NULL;
03683         }
03684 
03685         if (badthing && bs->wpCamping)
03686         {
03687                 bs->wpCamping = NULL;
03688         }
03689 
03690         if (bs->wpCamping)
03691         {
03692                 bs->wpDestination = bs->wpCamping;
03693                 return;
03694         }
03695 
03696         if (!badthing && CTFTakesPriority(bs))
03697         {
03698                 if (bs->ctfState)
03699                 {
03700                         bs->runningToEscapeThreat = 1;
03701                 }
03702                 return;
03703         }
03704         else if (!badthing && SiegeTakesPriority(bs))
03705         {
03706                 if (bs->siegeState)
03707                 {
03708                         bs->runningToEscapeThreat = 1;
03709                 }
03710                 return;
03711         }
03712         else if (!badthing && JMTakesPriority(bs))
03713         {
03714                 bs->runningToEscapeThreat = 1;
03715         }
03716 
03717         if (badthing)
03718         {
03719                 bs->runningLikeASissy = level.time + 100;
03720 
03721                 if (bs->wpDestination)
03722                 {
03723                         bs->wpStoreDest = bs->wpDestination;
03724                 }
03725                 bs->wpDestination = NULL;
03726 
03727                 if (bs->wpDirection)
03728                 {
03729                         tempInt = bs->wpCurrent->index+1;
03730                 }
03731                 else
03732                 {
03733                         tempInt = bs->wpCurrent->index-1;
03734                 }
03735 
03736                 if (gWPArray[tempInt] && gWPArray[tempInt]->inuse && bs->escapeDirTime < level.time)
03737                 {
03738                         VectorSubtract(badthing->s.pos.trBase, bs->wpCurrent->origin, a);
03739                         plusLen = VectorLength(a);
03740                         VectorSubtract(badthing->s.pos.trBase, gWPArray[tempInt]->origin, a);
03741                         minusLen = VectorLength(a);
03742 
03743                         if (plusLen < minusLen)
03744                         {
03745                                 if (bs->wpDirection)
03746                                 {
03747                                         bs->wpDirection = 0;
03748                                 }
03749                                 else
03750                                 {
03751                                         bs->wpDirection = 1;
03752                                 }
03753 
03754                                 bs->wpCurrent = gWPArray[tempInt];
03755 
03756                                 bs->escapeDirTime = level.time + Q_irand(500, 1000);//Q_irand(1000, 1400);
03757 
03758                                 //G_Printf("Escaping from scary bad thing [%s]\n", badthing->classname);
03759                         }
03760                 }
03761                 //G_Printf("Run away run away run away!\n");
03762                 return;
03763         }
03764 
03765         distChange = 0; //keep the compiler from complaining
03766 
03767         tempInt = BotGetWeaponRange(bs);
03768 
03769         if (tempInt == BWEAPONRANGE_MELEE)
03770         {
03771                 distChange = 1;
03772         }
03773         else if (tempInt == BWEAPONRANGE_SABER)
03774         {
03775                 distChange = 1;
03776         }
03777         else if (tempInt == BWEAPONRANGE_MID)
03778         {
03779                 distChange = 128;
03780         }
03781         else if (tempInt == BWEAPONRANGE_LONG)
03782         {
03783                 distChange = 300;
03784         }
03785 
03786         if (bs->revengeEnemy && bs->revengeEnemy->health > 0 &&
03787                 bs->revengeEnemy->client && (bs->revengeEnemy->client->pers.connected == CA_ACTIVE || bs->revengeEnemy->client->pers.connected == CA_AUTHORIZING))
03788         { //if we hate someone, always try to get to them
03789                 if (bs->wpDestSwitchTime < level.time)
03790                 {
03791                         if (bs->revengeEnemy->client)
03792                         {
03793                                 VectorCopy(bs->revengeEnemy->client->ps.origin, usethisvec);
03794                         }
03795                         else
03796                         {
03797                                 VectorCopy(bs->revengeEnemy->s.origin, usethisvec);
03798                         }
03799 
03800                         tempInt = GetNearestVisibleWP(usethisvec, 0);
03801 
03802                         if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
03803                         {
03804                                 bs->wpDestination = gWPArray[tempInt];
03805                                 bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000);
03806                         }
03807                 }
03808         }
03809         else if (bs->squadLeader && bs->squadLeader->health > 0 &&
03810                 bs->squadLeader->client && (bs->squadLeader->client->pers.connected == CA_ACTIVE || bs->squadLeader->client->pers.connected == CA_AUTHORIZING))
03811         {
03812                 if (bs->wpDestSwitchTime < level.time)
03813                 {
03814                         if (bs->squadLeader->client)
03815                         {
03816                                 VectorCopy(bs->squadLeader->client->ps.origin, usethisvec);
03817                         }
03818                         else
03819                         {
03820                                 VectorCopy(bs->squadLeader->s.origin, usethisvec);
03821                         }
03822 
03823                         tempInt = GetNearestVisibleWP(usethisvec, 0);
03824 
03825                         if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
03826                         {
03827                                 bs->wpDestination = gWPArray[tempInt];
03828                                 bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000);
03829                         }
03830                 }
03831         }
03832         else if (bs->currentEnemy)
03833         {
03834                 if (bs->currentEnemy->client)
03835                 {
03836                         VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
03837                 }
03838                 else
03839                 {
03840                         VectorCopy(bs->currentEnemy->s.origin, usethisvec);
03841                 }
03842 
03843                 bChicken = BotIsAChickenWuss(bs);
03844                 bs->runningToEscapeThreat = bChicken;
03845 
03846                 if (bs->frame_Enemy_Len < distChange || (bChicken && bChicken != 2))
03847                 {
03848                         cWPIndex = bs->wpCurrent->index;
03849 
03850                         if (bs->frame_Enemy_Len > 400)
03851                         { //good distance away, start running toward a good place for an item or powerup or whatever
03852                                 idleWP = GetBestIdleGoal(bs);
03853 
03854                                 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
03855                                 {
03856                                         bs->wpDestination = gWPArray[idleWP];
03857                                 }
03858                         }
03859                         else if (gWPArray[cWPIndex-1] && gWPArray[cWPIndex-1]->inuse &&
03860                                 gWPArray[cWPIndex+1] && gWPArray[cWPIndex+1]->inuse)
03861                         {
03862                                 VectorSubtract(gWPArray[cWPIndex+1]->origin, usethisvec, a);
03863                                 plusLen = VectorLength(a);
03864                                 VectorSubtract(gWPArray[cWPIndex-1]->origin, usethisvec, a);
03865                                 minusLen = VectorLength(a);
03866 
03867                                 if (minusLen > plusLen)
03868                                 {
03869                                         bs->wpDestination = gWPArray[cWPIndex-1];
03870                                 }
03871                                 else
03872                                 {
03873                                         bs->wpDestination = gWPArray[cWPIndex+1];
03874                                 }
03875                         }
03876                 }
03877                 else if (bChicken != 2 && bs->wpDestSwitchTime < level.time)
03878                 {
03879                         tempInt = GetNearestVisibleWP(usethisvec, 0);
03880 
03881                         if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
03882                         {
03883                                 bs->wpDestination = gWPArray[tempInt];
03884 
03885                                 if (g_gametype.integer == GT_SINGLE_PLAYER)
03886                                 { //be more aggressive
03887                                         bs->wpDestSwitchTime = level.time + Q_irand(300, 1000);
03888                                 }
03889                                 else
03890                                 {
03891                                         bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
03892                                 }
03893                         }
03894                 }
03895         }
03896 
03897         if (!bs->wpDestination && bs->wpDestSwitchTime < level.time)
03898         {
03899                 //G_Printf("I need something to do\n");
03900                 idleWP = GetBestIdleGoal(bs);
03901 
03902                 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
03903                 {
03904                         bs->wpDestination = gWPArray[idleWP];
03905                 }
03906         }
03907 }
03908 
03909 //commander CTF AI - tell other bots in the so-called
03910 //"squad" what to do.
03911 void CommanderBotCTFAI(bot_state_t *bs)
03912 {
03913         int i = 0;
03914         gentity_t *ent;
03915         int squadmates = 0;
03916         gentity_t *squad[MAX_CLIENTS];
03917         int defendAttackPriority = 0; //0 == attack, 1 == defend
03918         int guardDefendPriority = 0; //0 == defend, 1 == guard
03919         int attackRetrievePriority = 0; //0 == retrieve, 1 == attack
03920         int myFlag = 0;
03921         int enemyFlag = 0;
03922         int enemyHasOurFlag = 0;
03923         int weHaveEnemyFlag = 0;
03924         int numOnMyTeam = 0;
03925         int numOnEnemyTeam = 0;
03926         int numAttackers = 0;
03927         int numDefenders = 0;
03928 
03929         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
03930         {
03931                 myFlag = PW_REDFLAG;
03932         }
03933         else
03934         {
03935                 myFlag = PW_BLUEFLAG;
03936         }
03937 
03938         if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
03939         {
03940                 enemyFlag = PW_BLUEFLAG;
03941         }
03942         else
03943         {
03944                 enemyFlag = PW_REDFLAG;
03945         }
03946 
03947         while (i < MAX_CLIENTS)
03948         {
03949                 ent = &g_entities[i];
03950 
03951                 if (ent && ent->client)
03952                 {
03953                         if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
03954                         {
03955                                 weHaveEnemyFlag = 1;
03956                         }
03957                         else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
03958                         {
03959                                 enemyHasOurFlag = 1;
03960                         }
03961 
03962                         if (OnSameTeam(&g_entities[bs->client], ent))
03963                         {
03964                                 numOnMyTeam++;
03965                         }
03966                         else
03967                         {
03968                                 numOnEnemyTeam++;
03969                         }
03970 
03971                         if (botstates[ent->s.number])
03972                         {
03973                                 if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER ||
03974                                         botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL)
03975                                 {
03976                                         numAttackers++;
03977                                 }
03978                                 else
03979                                 {
03980                                         numDefenders++;
03981                                 }
03982                         }
03983                         else
03984                         { //assume real players to be attackers in our logic
03985                                 numAttackers++;
03986                         }
03987                 }
03988                 i++;
03989         }
03990 
03991         i = 0;
03992 
03993         while (i < MAX_CLIENTS)
03994         {
03995                 ent = &g_entities[i];
03996 
03997                 if (ent && ent->client && botstates[i] && botstates[i]->squadLeader && botstates[i]->squadLeader->s.number == bs->client && i != bs->client)
03998                 {
03999                         squad[squadmates] = ent;
04000                         squadmates++;
04001                 }
04002 
04003                 i++;
04004         }
04005 
04006         squad[squadmates] = &g_entities[bs->client];
04007         squadmates++;
04008 
04009         i = 0;
04010 
04011         if (enemyHasOurFlag && !weHaveEnemyFlag)
04012         { //start off with an attacker instead of a retriever if we don't have the enemy flag yet so that they can't capture it first.
04013           //after that we focus on getting our flag back.
04014                 attackRetrievePriority = 1;
04015         }
04016 
04017         while (i < squadmates)
04018         {
04019                 if (squad[i] && squad[i]->client && botstates[squad[i]->s.number])
04020                 {
04021                         if (botstates[squad[i]->s.number]->ctfState != CTFSTATE_GETFLAGHOME)
04022                         { //never tell a bot to stop trying to bring the flag to the base
04023                                 if (defendAttackPriority)
04024                                 {
04025                                         if (weHaveEnemyFlag)
04026                                         {
04027                                                 if (guardDefendPriority)
04028                                                 {
04029                                                         botstates[squad[i]->s.number]->ctfState = CTFSTATE_GUARDCARRIER;
04030                                                         guardDefendPriority = 0;
04031                                                 }
04032                                                 else
04033                                                 {
04034                                                         botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER;
04035                                                         guardDefendPriority = 1;
04036                                                 }
04037                                         }
04038                                         else
04039                                         {
04040                                                 botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER;
04041                                         }
04042                                         defendAttackPriority = 0;
04043                                 }
04044                                 else
04045                                 {
04046                                         if (enemyHasOurFlag)
04047                                         {
04048                                                 if (attackRetrievePriority)
04049                                                 {
04050                                                         botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER;
04051                                                         attackRetrievePriority = 0;
04052                                                 }
04053                                                 else
04054                                                 {
04055                                                         botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL;
04056                                                         attackRetrievePriority = 1;
04057                                                 }
04058                                         }
04059                                         else
04060                                         {
04061                                                 botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER;
04062                                         }
04063                                         defendAttackPriority = 1;
04064                                 }
04065                         }
04066                         else if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag)
04067                         { //I'm the only one on my team who will attack and the enemy has my flag, I have to go after him
04068                                 botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL;
04069                         }
04070                 }
04071 
04072                 i++;
04073         }
04074 }
04075 
04076 //similar to ctf ai, for siege
04077 void CommanderBotSiegeAI(bot_state_t *bs)
04078 {
04079         int i = 0;
04080         int squadmates = 0;
04081         int commanded = 0;
04082         int teammates = 0;
04083         gentity_t *squad[MAX_CLIENTS];
04084         gentity_t *ent;
04085         bot_state_t *bst;
04086 
04087         while (i < MAX_CLIENTS)
04088         {
04089                 ent = &g_entities[i];
04090 
04091                 if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number])
04092                 {
04093                         bst = botstates[ent->s.number];
04094 
04095                         if (bst && !bst->isSquadLeader && !bst->state_Forced)
04096                         {
04097                                 squad[squadmates] = ent;
04098                                 squadmates++;
04099                         }
04100                         else if (bst && !bst->isSquadLeader && bst->state_Forced)
04101                         { //count them as commanded
04102                                 commanded++;
04103                         }
04104                 }
04105 
04106                 if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent))
04107                 {
04108                         teammates++;
04109                 }
04110 
04111                 i++;
04112         }
04113         
04114         if (!squadmates)
04115         {
04116                 return;
04117         }
04118 
04119         //tell squad mates to do what I'm doing, up to half of team, let the other half make their own decisions
04120         i = 0;
04121 
04122         while (i < squadmates && squad[i])
04123         {
04124                 bst = botstates[squad[i]->s.number];
04125 
04126                 if (commanded > teammates/2)
04127                 {
04128                         break;
04129                 }
04130 
04131                 if (bst)
04132                 {
04133                         bst->state_Forced = bs->siegeState;
04134                         bst->siegeState = bs->siegeState;
04135                         commanded++;
04136                 }
04137 
04138                 i++;
04139         }
04140 }
04141 
04142 //teamplay ffa squad ai
04143 void BotDoTeamplayAI(bot_state_t *bs)
04144 {
04145         if (bs->state_Forced)
04146         {
04147                 bs->teamplayState = bs->state_Forced;
04148         }
04149 
04150         if (bs->teamplayState == TEAMPLAYSTATE_REGROUP)
04151         { //force to find a new leader
04152                 bs->squadLeader = NULL;
04153                 bs->isSquadLeader = 0;
04154         }
04155 }
04156 
04157 //like ctf and siege commander ai, instruct the squad
04158 void CommanderBotTeamplayAI(bot_state_t *bs)
04159 {
04160         int i = 0;
04161         int squadmates = 0;
04162         int teammates = 0;
04163         int teammate_indanger = -1;
04164         int teammate_helped = 0;
04165         int foundsquadleader = 0;
04166         int worsthealth = 50;
04167         gentity_t *squad[MAX_CLIENTS];
04168         gentity_t *ent;
04169         bot_state_t *bst;
04170 
04171         while (i < MAX_CLIENTS)
04172         {
04173                 ent = &g_entities[i];
04174 
04175                 if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number])
04176                 {
04177                         bst = botstates[ent->s.number];
04178 
04179                         if (foundsquadleader && bst && bst->isSquadLeader)
04180                         { //never more than one squad leader
04181                                 bst->isSquadLeader = 0;
04182                         }
04183 
04184                         if (bst && !bst->isSquadLeader)
04185                         {
04186                                 squad[squadmates] = ent;
04187                                 squadmates++;
04188                         }
04189                         else if (bst)
04190                         {
04191                                 foundsquadleader = 1;
04192                         }
04193                 }
04194 
04195                 if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent))
04196                 {
04197                         teammates++;
04198 
04199                         if (ent->health < worsthealth)
04200                         {
04201                                 teammate_indanger = ent->s.number;
04202                                 worsthealth = ent->health;
04203                         }
04204                 }
04205 
04206                 i++;
04207         }
04208         
04209         if (!squadmates)
04210         {
04211                 return;
04212         }
04213 
04214         i = 0;
04215 
04216         while (i < squadmates && squad[i])
04217         {
04218                 bst = botstates[squad[i]->s.number];
04219 
04220                 if (bst && !bst->state_Forced)
04221                 { //only order if this guy is not being ordered directly by the real player team leader
04222                         if (teammate_indanger >= 0 && !teammate_helped)
04223                         { //send someone out to help whoever needs help most at the moment
04224                                 bst->teamplayState = TEAMPLAYSTATE_ASSISTING;
04225                                 bst->squadLeader = &g_entities[teammate_indanger];
04226                                 teammate_helped = 1;
04227                         }
04228                         else if ((teammate_indanger == -1 || teammate_helped) && bst->teamplayState == TEAMPLAYSTATE_ASSISTING)
04229                         { //no teammates need help badly, but this guy is trying to help them anyway, so stop
04230                                 bst->teamplayState = TEAMPLAYSTATE_FOLLOWING;
04231                                 bst->squadLeader = &g_entities[bs->client];
04232                         }
04233 
04234                         if (bs->squadRegroupInterval < level.time && Q_irand(1, 10) < 5)
04235                         { //every so often tell the squad to regroup for the sake of variation
04236                                 if (bst->teamplayState == TEAMPLAYSTATE_FOLLOWING)
04237                                 {
04238                                         bst->teamplayState = TEAMPLAYSTATE_REGROUP;
04239                                 }
04240 
04241                                 bs->isSquadLeader = 0;
04242                                 bs->squadCannotLead = level.time + 500;
04243                                 bs->squadRegroupInterval = level.time + Q_irand(45000, 65000);
04244                         }
04245                 }
04246 
04247                 i++;
04248         }       
04249 }
04250 
04251 //pick which commander ai to use based on gametype
04252 void CommanderBotAI(bot_state_t *bs)
04253 {
04254         if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY)
04255         {
04256                 CommanderBotCTFAI(bs);
04257         }
04258         else if (g_gametype.integer == GT_SIEGE)
04259         {
04260                 CommanderBotSiegeAI(bs);
04261         }
04262         else if (g_gametype.integer == GT_TEAM)
04263         {
04264                 CommanderBotTeamplayAI(bs);
04265         }
04266 }
04267 
04268 //close range combat routines
04269 void MeleeCombatHandling(bot_state_t *bs)
04270 {
04271         vec3_t usethisvec;
04272         vec3_t downvec;
04273         vec3_t midorg;
04274         vec3_t a;
04275         vec3_t fwd;
04276         vec3_t mins, maxs;
04277         trace_t tr;
04278         int en_down;
04279         int me_down;
04280         int mid_down;
04281 
04282         if (!bs->currentEnemy)
04283         {
04284                 return;
04285         }
04286 
04287         if (bs->currentEnemy->client)
04288         {
04289                 VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
04290         }
04291         else
04292         {
04293                 VectorCopy(bs->currentEnemy->s.origin, usethisvec);
04294         }
04295 
04296         if (bs->meleeStrafeTime < level.time)
04297         {
04298                 if (bs->meleeStrafeDir)
04299                 {
04300                         bs->meleeStrafeDir = 0;
04301                 }
04302                 else
04303                 {
04304                         bs->meleeStrafeDir = 1;
04305                 }
04306 
04307                 bs->meleeStrafeTime = level.time + Q_irand(500, 1800);
04308         }
04309 
04310         mins[0] = -15;
04311         mins[1] = -15;
04312         mins[2] = -24;
04313         maxs[0] = 15;
04314         maxs[1] = 15;
04315         maxs[2] = 32;
04316 
04317         VectorCopy(usethisvec, downvec);
04318         downvec[2] -= 4096;
04319 
04320         trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID);
04321 
04322         en_down = (int)tr.endpos[2];
04323 
04324         VectorCopy(bs->origin, downvec);
04325         downvec[2] -= 4096;
04326 
04327         trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID);
04328 
04329         me_down = (int)tr.endpos[2];
04330 
04331         VectorSubtract(usethisvec, bs->origin, a);
04332         vectoangles(a, a);
04333         AngleVectors(a, fwd, NULL, NULL);
04334 
04335         midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2;
04336         midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2;
04337         midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2;
04338 
04339         VectorCopy(midorg, downvec);
04340         downvec[2] -= 4096;
04341 
04342         trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID);
04343 
04344         mid_down = (int)tr.endpos[2];
04345 
04346         if (me_down == en_down &&
04347                 en_down == mid_down)
04348         {
04349                 VectorCopy(usethisvec, bs->goalPosition);
04350         }
04351 }
04352 
04353 //saber combat routines (it's simple, but it works)
04354 void SaberCombatHandling(bot_state_t *bs)
04355 {
04356         vec3_t usethisvec;
04357         vec3_t downvec;
04358         vec3_t midorg;
04359         vec3_t a;
04360         vec3_t fwd;
04361         vec3_t mins, maxs;
04362         trace_t tr;
04363         int en_down;
04364         int me_down;
04365         int mid_down;
04366 
04367         if (!bs->currentEnemy)
04368         {
04369                 return;
04370         }
04371 
04372         if (bs->currentEnemy->client)
04373         {
04374                 VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
04375         }
04376         else
04377         {
04378                 VectorCopy(bs->currentEnemy->s.origin, usethisvec);
04379         }
04380 
04381         if (bs->meleeStrafeTime < level.time)
04382         {
04383                 if (bs->meleeStrafeDir)
04384                 {
04385                         bs->meleeStrafeDir = 0;
04386                 }
04387                 else
04388                 {
04389                         bs->meleeStrafeDir = 1;
04390                 }
04391 
04392                 bs->meleeStrafeTime = level.time + Q_irand(500, 1800);
04393         }
04394 
04395         mins[0] = -15;
04396         mins[1] = -15;
04397         mins[2] = -24;
04398         maxs[0] = 15;
04399         maxs[1] = 15;
04400         maxs[2] = 32;
04401 
04402         VectorCopy(usethisvec, downvec);
04403         downvec[2] -= 4096;
04404 
04405         trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID);
04406 
04407         en_down = (int)tr.endpos[2];
04408 
04409         if (tr.startsolid || tr.allsolid)
04410         {
04411                 en_down = 1;
04412                 me_down = 2;
04413         }
04414         else
04415         {
04416                 VectorCopy(bs->origin, downvec);
04417                 downvec[2] -= 4096;
04418 
04419                 trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID);
04420 
04421                 me_down = (int)tr.endpos[2];
04422 
04423                 if (tr.startsolid || tr.allsolid)
04424                 {
04425                         en_down = 1;
04426                         me_down = 2;
04427                 }
04428         }
04429 
04430         VectorSubtract(usethisvec, bs->origin, a);
04431         vectoangles(a, a);
04432         AngleVectors(a, fwd, NULL, NULL);
04433 
04434         midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2;
04435         midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2;
04436         midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2;
04437 
04438         VectorCopy(midorg, downvec);
04439         downvec[2] -= 4096;
04440 
04441         trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID);
04442 
04443         mid_down = (int)tr.endpos[2];
04444 
04445         if (me_down == en_down &&
04446                 en_down == mid_down)
04447         {
04448                 if (usethisvec[2] > (bs->origin[2]+32) &&
04449                         bs->currentEnemy->client &&
04450                         bs->currentEnemy->client->ps.groundEntityNum == ENTITYNUM_NONE)
04451                 {
04452                         bs->jumpTime = level.time + 100;
04453                 }
04454 
04455                 if (bs->frame_Enemy_Len > 128)
04456                 { //be ready to attack
04457                         bs->saberDefending = 0;
04458                         bs->saberDefendDecideTime = level.time + Q_irand(1000, 2000);
04459                 }
04460                 else
04461                 {
04462                         if (bs->saberDefendDecideTime < level.time)
04463                         {
04464                                 if (bs->saberDefending)
04465                                 {
04466                                         bs->saberDefending = 0;
04467                                 }
04468                                 else
04469                                 {
04470                                         bs->saberDefending = 1;
04471                                 }
04472 
04473                                 bs->saberDefendDecideTime = level.time + Q_irand(500, 2000);
04474                         }
04475                 }
04476 
04477                 if (bs->frame_Enemy_Len < 54)
04478                 {
04479                         VectorCopy(bs->origin, bs->goalPosition);
04480                         bs->saberBFTime = 0;
04481                 }
04482                 else
04483                 {
04484                         VectorCopy(usethisvec, bs->goalPosition);
04485                 }
04486 
04487                 if (bs->currentEnemy && bs->currentEnemy->client)
04488                 {
04489                         if (!BG_SaberInSpecial(bs->currentEnemy->client->ps.saberMove) && bs->frame_Enemy_Len > 90 && bs->saberBFTime > level.time && bs->saberBTime > level.time && bs->beStill < level.time && bs->saberSTime < level.time)
04490                         {
04491                                 bs->beStill = level.time + Q_irand(500, 1000);
04492                                 bs->saberSTime = level.time + Q_irand(1200, 1800);
04493                         }
04494                         else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len < 80 && (Q_irand(1, 10) < 8 && bs->saberBFTime < level.time) || bs->saberBTime > level.time || BG_SaberInKata(bs->currentEnemy->client->ps.saberMove) || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK_DUAL)
04495                         {
04496                                 vec3_t vs;
04497                                 vec3_t groundcheck;
04498                                 int idealDist;
04499                                 int checkIncr = 0;
04500 
04501                                 VectorSubtract(bs->origin, usethisvec, vs);
04502                                 VectorNormalize(vs);
04503 
04504                                 if (BG_SaberInKata(bs->currentEnemy->client->ps.saberMove) || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK_DUAL)
04505                                 {
04506                                         idealDist = 256;
04507                                 }
04508                                 else
04509                                 {
04510                                         idealDist = 64;
04511                                 }
04512 
04513                                 while (checkIncr < idealDist)
04514                                 {
04515                                         bs->goalPosition[0] = bs->origin[0] + vs[0]*checkIncr;
04516                                         bs->goalPosition[1] = bs->origin[1] + vs[1]*checkIncr;
04517                                         bs->goalPosition[2] = bs->origin[2] + vs[2]*checkIncr;
04518 
04519                                         if (bs->saberBTime < level.time)
04520                                         {
04521                                                 bs->saberBFTime = level.time + Q_irand(900, 1300);
04522                                                 bs->saberBTime = level.time + Q_irand(300, 700);
04523                                         }
04524 
04525                                         VectorCopy(bs->goalPosition, groundcheck);
04526 
04527                                         groundcheck[2] -= 64;
04528 
04529                                         trap_Trace(&tr, bs->goalPosition, NULL, NULL, groundcheck, bs->client, MASK_SOLID);
04530                                         
04531                                         if (tr.fraction == 1.0f)
04532                                         { //don't back off of a ledge
04533                                                 VectorCopy(usethisvec, bs->goalPosition);
04534                                                 break;
04535                                         }
04536                                         checkIncr += 64;
04537                                 }
04538                         }
04539                         else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len >= 75)
04540                         {
04541                                 bs->saberBFTime = level.time + Q_irand(700, 1300);
04542                                 bs->saberBTime = 0;
04543                         }
04544                 }
04545 
04546                 /*AngleVectors(bs->viewangles, NULL, fwd, NULL);
04547 
04548                 if (bs->meleeStrafeDir)
04549                 {
04550                         bs->goalPosition[0] += fwd[0]*16;
04551                         bs->goalPosition[1] += fwd[1]*16;
04552                         bs->goalPosition[2] += fwd[2]*16;
04553                 }
04554                 else
04555                 {
04556                         bs->goalPosition[0] -= fwd[0]*16;
04557                         bs->goalPosition[1] -= fwd[1]*16;
04558                         bs->goalPosition[2] -= fwd[2]*16;
04559                 }*/
04560         }
04561         else if (bs->frame_Enemy_Len <= 56)
04562         {
04563                 bs->doAttack = 1;
04564                 bs->saberDefending = 0;
04565         }
04566 }
04567 
04568 //should we be "leading" our aim with this weapon? And if
04569 //so, by how much?
04570 float BotWeaponCanLead(bot_state_t *bs)
04571 {
04572         int weap = bs->cur_ps.weapon;
04573 
04574         if (weap == WP_BRYAR_PISTOL)
04575         {
04576                 return 0.5;
04577         }
04578         if (weap == WP_BLASTER)
04579         {
04580                 return 0.35;
04581         }
04582         if (weap == WP_BOWCASTER)
04583         {
04584                 return 0.5;
04585         }
04586         if (weap == WP_REPEATER)
04587         {
04588                 return 0.45;
04589         }
04590         if (weap == WP_THERMAL)
04591         {
04592                 return 0.5;
04593         }
04594         if (weap == WP_DEMP2)
04595         {
04596                 return 0.35;
04597         }
04598         if (weap == WP_ROCKET_LAUNCHER)
04599         {
04600                 return 0.7;
04601         }
04602         
04603         return 0;
04604 }
04605 
04606 //offset the desired view angles with aim leading in mind
04607 void BotAimLeading(bot_state_t *bs, vec3_t headlevel, float leadAmount)
04608 {
04609         int x;
04610         vec3_t predictedSpot;
04611         vec3_t movementVector;
04612         vec3_t a, ang;
04613         float vtotal;
04614 
04615         if (!bs->currentEnemy ||
04616                 !bs->currentEnemy->client)
04617         {
04618                 return;
04619         }
04620 
04621         if (!bs->frame_Enemy_Len)
04622         {
04623                 return;
04624         }
04625 
04626         vtotal = 0;
04627 
04628         if (bs->currentEnemy->client->ps.velocity[0] < 0)
04629         {
04630                 vtotal += -bs->currentEnemy->client->ps.velocity[0];
04631         }
04632         else
04633         {
04634                 vtotal += bs->currentEnemy->client->ps.velocity[0];
04635         }
04636 
04637         if (bs->currentEnemy->client->ps.velocity[1] < 0)
04638         {
04639                 vtotal += -bs->currentEnemy->client->ps.velocity[1];
04640         }
04641         else
04642         {
04643                 vtotal += bs->currentEnemy->client->ps.velocity[1];
04644         }
04645 
04646         if (bs->currentEnemy->client->ps.velocity[2] < 0)
04647         {
04648                 vtotal += -bs->currentEnemy->client->ps.velocity[2];
04649         }
04650         else
04651         {
04652                 vtotal += bs->currentEnemy->client->ps.velocity[2];
04653         }
04654 
04655         //G_Printf("Leadin target with a velocity total of %f\n", vtotal);
04656 
04657         VectorCopy(bs->currentEnemy->client->ps.velocity, movementVector);
04658 
04659         VectorNormalize(movementVector);
04660 
04661         x = bs->frame_Enemy_Len*leadAmount; //hardly calculated with an exact science, but it works
04662 
04663         if (vtotal > 400)
04664         {
04665                 vtotal = 400;
04666         }
04667 
04668         if (vtotal)
04669         {
04670                 x = (bs->frame_Enemy_Len*0.9)*leadAmount*(vtotal*0.0012); //hardly calculated with an exact science, but it works
04671         }
04672         else
04673         {
04674                 x = (bs->frame_Enemy_Len*0.9)*leadAmount; //hardly calculated with an exact science, but it works
04675         }
04676 
04677         predictedSpot[0] = headlevel[0] + (movementVector[0]*x);
04678         predictedSpot[1] = headlevel[1] + (movementVector[1]*x);
04679         predictedSpot[2] = headlevel[2] + (movementVector[2]*x);
04680 
04681         VectorSubtract(predictedSpot, bs->eye, a);
04682         vectoangles(a, ang);
04683         VectorCopy(ang, bs->goalAngles);
04684 }
04685 
04686 //wobble our aim around based on our sk1llz
04687 void BotAimOffsetGoalAngles(bot_state_t *bs)
04688 {
04689         int i;
04690         float accVal;
04691         i = 0;
04692 
04693         if (bs->skills.perfectaim)
04694         {
04695                 return;
04696         }
04697 
04698         if (bs->aimOffsetTime > level.time)
04699         {
04700                 if (bs->aimOffsetAmtYaw)
04701                 {
04702                         bs->goalAngles[YAW] += bs->aimOffsetAmtYaw;
04703                 }
04704 
04705                 if (bs->aimOffsetAmtPitch)
04706                 {
04707                         bs->goalAngles[PITCH] += bs->aimOffsetAmtPitch;
04708                 }
04709                 
04710                 while (i <= 2)
04711                 {
04712                         if (bs->goalAngles[i] > 360)
04713                         {
04714                                 bs->goalAngles[i] -= 360;
04715                         }
04716 
04717                         if (bs->goalAngles[i] < 0)
04718                         {
04719                                 bs->goalAngles[i] += 360;
04720                         }
04721 
04722                         i++;
04723                 }
04724                 return;
04725         }
04726 
04727         accVal = bs->skills.accuracy/bs->settings.skill;
04728 
04729         if (bs->currentEnemy && BotMindTricked(bs->client, bs->currentEnemy->s.number))
04730         { //having to judge where they are by hearing them, so we should be quite inaccurate here
04731                 accVal *= 7;
04732 
04733                 if (accVal < 30)
04734                 {
04735                         accVal = 30;
04736                 }
04737         }
04738 
04739         if (bs->revengeEnemy && bs->revengeHateLevel &&
04740                 bs->currentEnemy == bs->revengeEnemy)
04741         { //bot becomes more skilled as anger level raises
04742                 accVal = accVal/bs->revengeHateLevel;
04743         }
04744 
04745         if (bs->currentEnemy && bs->frame_Enemy_Vis)
04746         { //assume our goal is aiming at the enemy, seeing as he's visible and all
04747                 if (!bs->currentEnemy->s.pos.trDelta[0] &&
04748                         !bs->currentEnemy->s.pos.trDelta[1] &&
04749                         !bs->currentEnemy->s.pos.trDelta[2])
04750                 {
04751                         accVal = 0; //he's not even moving, so he shouldn't really be hard to hit.
04752                 }
04753                 else
04754                 {
04755                         accVal += accVal*0.25; //if he's moving he's this much harder to hit
04756                 }
04757 
04758                 if (g_entities[bs->client].s.pos.trDelta[0] ||
04759                         g_entities[bs->client].s.pos.trDelta[1] ||
04760                         g_entities[bs->client].s.pos.trDelta[2])
04761                 {
04762                         accVal += accVal*0.15; //make it somewhat harder to aim if we're moving also
04763                 }
04764         }
04765 
04766         if (accVal > 90)
04767         {
04768                 accVal = 90;
04769         }
04770         if (accVal < 1)
04771         {
04772                 accVal = 0;
04773         }
04774 
04775         if (!accVal)
04776         {
04777                 bs->aimOffsetAmtYaw = 0;
04778                 bs->aimOffsetAmtPitch = 0;
04779                 return;
04780         }
04781 
04782         if (rand()%10 <= 5)
04783         {
04784                 bs->aimOffsetAmtYaw = rand()%(int)accVal;
04785         }
04786         else
04787         {
04788                 bs->aimOffsetAmtYaw = -(rand()%(int)accVal);
04789         }
04790 
04791         if (rand()%10 <= 5)
04792         {
04793                 bs->aimOffsetAmtPitch = rand()%(int)accVal;
04794         }
04795         else
04796         {
04797                 bs->aimOffsetAmtPitch = -(rand()%(int)accVal);
04798         }
04799 
04800         bs->aimOffsetTime = level.time + rand()%500 + 200;
04801 }
04802 
04803 //do we want to alt fire with this weapon?
04804 int ShouldSecondaryFire(bot_state_t *bs)
04805 {
04806         int weap;
04807         int dif;
04808         float rTime;
04809 
04810         weap = bs->cur_ps.weapon;
04811 
04812         if (bs->cur_ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].altEnergyPerShot)
04813         {
04814                 return 0;
04815         }
04816 
04817         if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && bs->cur_ps.weapon == WP_ROCKET_LAUNCHER)
04818         {
04819                 float heldTime = (level.time - bs->cur_ps.weaponChargeTime);
04820 
04821                 rTime = bs->cur_ps.rocketLockTime;
04822 
04823                 if (rTime < 1)
04824                 {
04825                         rTime = bs->cur_ps.rocketLastValidTime;
04826                 }
04827 
04828                 if (heldTime > 5000)
04829                 { //just give up and release it if we can't manage a lock in 5 seconds
04830                         return 2;
04831                 }
04832 
04833                 if (rTime > 0)
04834                 {
04835                         dif = ( level.time - rTime ) / ( 1200.0f / 16.0f );
04836                         
04837                         if (dif >= 10)
04838                         {
04839                                 return 2;
04840                         }
04841                         else if (bs->frame_Enemy_Len > 250)
04842                         {
04843                                 return 1;
04844                         }
04845                 }
04846                 else if (bs->frame_Enemy_Len > 250)
04847                 {
04848                         return 1;
04849                 }
04850         }
04851         else if ((bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) && (level.time - bs->cur_ps.weaponChargeTime) > bs->altChargeTime)
04852         {
04853                 return 2;
04854         }
04855         else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
04856         {
04857                 return 1;
04858         }
04859 
04860         if (weap == WP_BRYAR_PISTOL && bs->frame_Enemy_Len < 300)
04861         {
04862                 return 1;
04863         }
04864         else if (weap == WP_BOWCASTER && bs->frame_Enemy_Len > 300)
04865         {
04866                 return 1;
04867         }
04868         else if (weap == WP_REPEATER && bs->frame_Enemy_Len < 600 && bs->frame_Enemy_Len > 250)
04869         {
04870                 return 1;
04871         }
04872         else if (weap == WP_BLASTER && bs->frame_Enemy_Len < 300)
04873         {
04874                 return 1;
04875         }
04876         else if (weap == WP_ROCKET_LAUNCHER && bs->frame_Enemy_Len > 250)
04877         {
04878                 return 1;
04879         }
04880 
04881         return 0;
04882 }
04883 
04884 //standard weapon combat routines
04885 int CombatBotAI(bot_state_t *bs, float thinktime)
04886 {
04887         vec3_t eorg, a;
04888         int secFire;
04889         float fovcheck;
04890 
04891         if (!bs->currentEnemy)
04892         {
04893                 return 0;
04894         }
04895 
04896         if (bs->currentEnemy->client)
04897         {
04898                 VectorCopy(bs->currentEnemy->client->ps.origin, eorg);
04899         }
04900         else
04901         {
04902                 VectorCopy(bs->currentEnemy->s.origin, eorg);
04903         }
04904 
04905         VectorSubtract(eorg, bs->eye, a);
04906         vectoangles(a, a);
04907 
04908         if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER)
04909         {
04910                 if (bs->frame_Enemy_Len <= SABER_ATTACK_RANGE)
04911                 {
04912                         bs->doAttack = 1;
04913                 }
04914         }
04915         else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)
04916         {
04917                 if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE)
04918                 {
04919                         bs->doAttack = 1;
04920                 }
04921         }
04922         else
04923         {
04924                 if (bs->cur_ps.weapon == WP_THERMAL || bs->cur_ps.weapon == WP_ROCKET_LAUNCHER)
04925                 { //be careful with the hurty weapons
04926                         fovcheck = 40;
04927 
04928                         if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
04929                                 bs->cur_ps.weapon == WP_ROCKET_LAUNCHER)
04930                         { //if we're charging the weapon up then we can hold fire down within a normal fov
04931                                 fovcheck = 60;
04932                         }
04933                 }
04934                 else
04935                 {
04936                         fovcheck = 60;
04937                 }
04938 
04939                 if (bs->cur_ps.weaponstate == WEAPON_CHARGING ||
04940                         bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
04941                 {
04942                         fovcheck = 160;
04943                 }
04944 
04945                 if (bs->frame_Enemy_Len < 128)
04946                 {
04947                         fovcheck *= 2;
04948                 }
04949 
04950                 if (InFieldOfVision(bs->viewangles, fovcheck, a))
04951                 {
04952                         if (bs->cur_ps.weapon == WP_THERMAL)
04953                         {
04954                                 if (((level.time - bs->cur_ps.weaponChargeTime) < (bs->frame_Enemy_Len*2) &&
04955                                         (level.time - bs->cur_ps.weaponChargeTime) < 4000 &&
04956                                         bs->frame_Enemy_Len > 64) ||
04957                                         (bs->cur_ps.weaponstate != WEAPON_CHARGING &&
04958                                         bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT))
04959                                 {
04960                                         if (bs->cur_ps.weaponstate != WEAPON_CHARGING && bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT)
04961                                         {
04962                                                 if (bs->frame_Enemy_Len > 512 && bs->frame_Enemy_Len < 800)
04963                                                 {
04964                                                         bs->doAltAttack = 1;
04965                                                         //bs->doAttack = 1;
04966                                                 }
04967                                                 else
04968                                                 {
04969                                                         bs->doAttack = 1;
04970                                                         //bs->doAltAttack = 1;
04971                                                 }
04972                                         }
04973 
04974                                         if (bs->cur_ps.weaponstate == WEAPON_CHARGING)
04975                                         {
04976                                                 bs->doAttack = 1;
04977                                         }
04978                                         else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
04979                                         {
04980                                                 bs->doAltAttack = 1;
04981                                         }
04982                                 }
04983                         }
04984                         else
04985                         {
04986                                 secFire = ShouldSecondaryFire(bs);
04987 
04988                                 if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT &&
04989                                         bs->cur_ps.weaponstate != WEAPON_CHARGING)
04990                                 {
04991                                         bs->altChargeTime = Q_irand(500, 1000);
04992                                 }
04993 
04994                                 if (secFire == 1)
04995                                 {
04996                                         bs->doAltAttack = 1;
04997                                 }
04998                                 else if (!secFire)
04999                                 {
05000                                         if (bs->cur_ps.weapon != WP_THERMAL)
05001                                         {
05002                                                 if (bs->cur_ps.weaponstate != WEAPON_CHARGING ||
05003                                                         bs->altChargeTime > (level.time - bs->cur_ps.weaponChargeTime))
05004                                                 {
05005                                                         bs->doAttack = 1;
05006                                                 }
05007                                         }
05008                                         else
05009                                         {
05010                                                 bs->doAttack = 1;
05011                                         }
05012                                 }
05013 
05014                                 if (secFire == 2)
05015                                 { //released a charge
05016                                         return 1;
05017                                 }
05018                         }
05019                 }
05020         }
05021 
05022         return 0;
05023 }
05024 
05025 //we messed up and got off the normal path, let's fall
05026 //back to jumping around and turning in random
05027 //directions off walls to see if we can get back to a
05028 //good place.
05029 int BotFallbackNavigation(bot_state_t *bs)
05030 {
05031         vec3_t b_angle, fwd, trto, mins, maxs;
05032         trace_t tr;
05033 
05034         if (bs->currentEnemy && bs->frame_Enemy_Vis)
05035         {
05036                 return 2; //we're busy
05037         }
05038 
05039         mins[0] = -15;
05040         mins[1] = -15;
05041         mins[2] = 0;
05042         maxs[0] = 15;
05043         maxs[1] = 15;
05044         maxs[2] = 32;
05045 
05046         bs->goalAngles[PITCH] = 0;
05047         bs->goalAngles[ROLL] = 0;
05048 
05049         VectorCopy(bs->goalAngles, b_angle);
05050 
05051         AngleVectors(b_angle, fwd, NULL, NULL);
05052 
05053         trto[0] = bs->origin[0] + fwd[0]*16;
05054         trto[1] = bs->origin[1] + fwd[1]*16;
05055         trto[2] = bs->origin[2] + fwd[2]*16;
05056 
05057         trap_Trace(&tr, bs->origin, mins, maxs, trto, ENTITYNUM_NONE, MASK_SOLID);
05058 
05059         if (tr.fraction == 1)
05060         {
05061                 VectorCopy(trto, bs->goalPosition);
05062                 return 1; //success!
05063         }
05064         else
05065         {
05066                 bs->goalAngles[YAW] = rand()%360;
05067         }
05068 
05069         return 0;
05070 }
05071 
05072 int BotTryAnotherWeapon(bot_state_t *bs)
05073 { //out of ammo, resort to the first weapon we come across that has ammo
05074         int i;
05075 
05076         i = 1;
05077 
05078         while (i < WP_NUM_WEAPONS)
05079         {
05080                 if (bs->cur_ps.ammo[weaponData[i].ammoIndex] >= weaponData[i].energyPerShot &&
05081                         (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
05082                 {
05083                         bs->virtualWeapon = i;
05084                         BotSelectWeapon(bs->client, i);
05085                         //bs->cur_ps.weapon = i;
05086                         //level.clients[bs->client].ps.weapon = i;
05087                         return 1;
05088                 }
05089 
05090                 i++;
05091         }
05092 
05093         if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1)
05094         { //should always have this.. shouldn't we?
05095                 bs->virtualWeapon = 1;
05096                 BotSelectWeapon(bs->client, 1);
05097                 //bs->cur_ps.weapon = 1;
05098                 //level.clients[bs->client].ps.weapon = 1;
05099                 return 1;
05100         }
05101 
05102         return 0;
05103 }
05104 
05105 //is this weapon available to us?
05106 qboolean BotWeaponSelectable(bot_state_t *bs, int weapon)
05107 {
05108         if (weapon == WP_NONE)
05109         {
05110                 return qfalse;
05111         }
05112 
05113         if (bs->cur_ps.ammo[weaponData[weapon].ammoIndex] >= weaponData[weapon].energyPerShot &&
05114                 (bs->cur_ps.stats[STAT_WEAPONS] & (1 << weapon)))
05115         {
05116                 return qtrue;
05117         }
05118         
05119         return qfalse;
05120 }
05121 
05122 //select the best weapon we can
05123 int BotSelectIdealWeapon(bot_state_t *bs)
05124 {
05125         int i;
05126         int bestweight = -1;
05127         int bestweapon = 0;
05128 
05129         i = 0;
05130 
05131         while (i < WP_NUM_WEAPONS)
05132         {
05133                 if (bs->cur_ps.ammo[weaponData[i].ammoIndex] >= weaponData[i].energyPerShot &&
05134                         bs->botWeaponWeights[i] > bestweight &&
05135                         (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
05136                 {
05137                         if (i == WP_THERMAL)
05138                         { //special case..
05139                                 if (bs->currentEnemy && bs->frame_Enemy_Len < 700)
05140                                 {
05141                                         bestweight = bs->botWeaponWeights[i];
05142                                         bestweapon = i;
05143                                 }
05144                         }
05145                         else
05146                         {
05147                                 bestweight = bs->botWeaponWeights[i];
05148                                 bestweapon = i;
05149                         }
05150                 }
05151 
05152                 i++;
05153         }
05154 
05155         if ( bs->currentEnemy && bs->frame_Enemy_Len < 300 &&
05156                 (bestweapon == WP_BRYAR_PISTOL || bestweapon == WP_BLASTER || bestweapon == WP_BOWCASTER) &&
05157                 (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) )
05158         {
05159                 bestweapon = WP_SABER;
05160                 bestweight = 1;
05161         }
05162 
05163         if ( bs->currentEnemy && bs->frame_Enemy_Len > 300 &&
05164                 bs->currentEnemy->client && bs->currentEnemy->client->ps.weapon != WP_SABER &&
05165                 (bestweapon == WP_SABER) )
05166         { //if the enemy is far away, and we have our saber selected, see if we have any good distance weapons instead
05167                 if (BotWeaponSelectable(bs, WP_DISRUPTOR))
05168                 {
05169                         bestweapon = WP_DISRUPTOR;
05170                         bestweight = 1;
05171                 }
05172                 else if (BotWeaponSelectable(bs, WP_ROCKET_LAUNCHER))
05173                 {
05174                         bestweapon = WP_ROCKET_LAUNCHER;
05175                         bestweight = 1;
05176                 }
05177                 else if (BotWeaponSelectable(bs, WP_BOWCASTER))
05178                 {
05179                         bestweapon = WP_BOWCASTER;
05180                         bestweight = 1;
05181                 }
05182                 else if (BotWeaponSelectable(bs, WP_BLASTER))
05183                 {
05184                         bestweapon = WP_BLASTER;
05185                         bestweight = 1;
05186                 }
05187                 else if (BotWeaponSelectable(bs, WP_REPEATER))
05188                 {
05189                         bestweapon = WP_REPEATER;
05190                         bestweight = 1;
05191                 }
05192                 else if (BotWeaponSelectable(bs, WP_DEMP2))
05193                 {
05194                         bestweapon = WP_DEMP2;
05195                         bestweight = 1;
05196                 }
05197         }
05198 
05199         //assert(bs->cur_ps.weapon > 0 && bestweapon > 0);
05200 
05201         if (bestweight != -1 && bs->cur_ps.weapon != bestweapon && bs->virtualWeapon != bestweapon)
05202         {
05203                 bs->virtualWeapon = bestweapon;
05204                 BotSelectWeapon(bs->client, bestweapon);
05205                 //bs->cur_ps.weapon = bestweapon;
05206                 //level.clients[bs->client].ps.weapon = bestweapon;
05207                 return 1;
05208         }
05209 
05210         //assert(bs->cur_ps.weapon > 0);
05211 
05212         return 0;
05213 }
05214 
05215 //check/select the chosen weapon
05216 int BotSelectChoiceWeapon(bot_state_t *bs, int weapon, int doselection)
05217 { //if !doselection then bot will only check if he has the specified weapon and return 1 (yes) or 0 (no)
05218         int i;
05219         int hasit = 0;
05220 
05221         i = 0;
05222 
05223         while (i < WP_NUM_WEAPONS)
05224         {
05225                 if (bs->cur_ps.ammo[weaponData[i].ammoIndex] > weaponData[i].energyPerShot &&
05226                         i == weapon &&
05227                         (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
05228                 {
05229                         hasit = 1;
05230                         break;
05231                 }
05232 
05233                 i++;
05234         }
05235 
05236         if (hasit && bs->cur_ps.weapon != weapon && doselection && bs->virtualWeapon != weapon)
05237         {
05238                 bs->virtualWeapon = weapon;
05239                 BotSelectWeapon(bs->client, weapon);
05240                 //bs->cur_ps.weapon = weapon;
05241                 //level.clients[bs->client].ps.weapon = weapon;
05242                 return 2;
05243         }
05244 
05245         if (hasit)
05246         {
05247                 return 1;
05248         }
05249 
05250         return 0;
05251 }
05252 
05253 //override our standard weapon choice with a melee weapon
05254 int BotSelectMelee(bot_state_t *bs)
05255 {
05256         if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1)
05257         {
05258                 bs->virtualWeapon = 1;
05259                 BotSelectWeapon(bs->client, 1);
05260                 //bs->cur_ps.weapon = 1;
05261                 //level.clients[bs->client].ps.weapon = 1;
05262                 return 1;
05263         }
05264 
05265         return 0;
05266 }
05267 
05268 //See if we our in love with the potential bot.
05269 int GetLoveLevel(bot_state_t *bs, bot_state_t *love)
05270 {
05271         int i = 0;
05272         const char *lname = NULL;
05273 
05274         if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
05275         { //There is no love in 1-on-1
05276                 return 0;
05277         }
05278 
05279         if (!bs || !love || !g_entities[love->client].client)
05280         {
05281                 return 0;
05282         }
05283 
05284         if (!bs->lovednum)
05285         {
05286                 return 0;
05287         }
05288 
05289         if (!bot_attachments.integer)
05290         {
05291                 return 1;
05292         }
05293 
05294         lname = g_entities[love->client].client->pers.netname;
05295 
05296         if (!lname)
05297         {
05298                 return 0;
05299         }
05300 
05301         while (i < bs->lovednum)
05302         {
05303                 if (strcmp(bs->loved[i].name, lname) == 0)
05304                 {
05305                         return bs->loved[i].level;
05306                 }
05307 
05308                 i++;
05309         }
05310 
05311         return 0;
05312 }
05313 
05314 //Our loved one was killed. We must become infuriated!
05315 void BotLovedOneDied(bot_state_t *bs, bot_state_t *loved, int lovelevel)
05316 {
05317         if (!loved->lastHurt || !loved->lastHurt->client ||
05318                 loved->lastHurt->s.number == loved->client)
05319         {
05320                 return;
05321         }
05322 
05323         if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
05324         { //There is no love in 1-on-1
05325                 return;
05326         }
05327 
05328         if (!IsTeamplay())
05329         {
05330                 if (lovelevel < 2)
05331                 {
05332                         return;
05333                 }
05334         }
05335         else if (OnSameTeam(&g_entities[bs->client], loved->lastHurt))
05336         { //don't hate teammates no matter what
05337                 return;
05338         }
05339 
05340         if (loved->client == loved->lastHurt->s.number)
05341         {
05342                 return;
05343         }
05344 
05345         if (bs->client == loved->lastHurt->s.number)
05346         { //oops!
05347                 return;
05348         }
05349         
05350         if (!bot_attachments.integer)
05351         {
05352                 return;
05353         }
05354 
05355         if (!PassLovedOneCheck(bs, loved->lastHurt))
05356         { //a loved one killed a loved one.. you cannot hate them
05357                 bs->chatObject = loved->lastHurt;
05358                 bs->chatAltObject = &g_entities[loved->client];
05359                 BotDoChat(bs, "LovedOneKilledLovedOne", 0);
05360                 return;
05361         }
05362 
05363         if (bs->revengeEnemy == loved->lastHurt)
05364         {
05365                 if (bs->revengeHateLevel < bs->loved_death_thresh)
05366                 {
05367                         bs->revengeHateLevel++;
05368 
05369                         if (bs->revengeHateLevel == bs->loved_death_thresh)
05370                         {
05371                                 //broke into the highest anger level
05372                                 //CHAT: Hatred section
05373                                 bs->chatObject = loved->lastHurt;
05374                                 bs->chatAltObject = NULL;
05375                                 BotDoChat(bs, "Hatred", 1);
05376                         }
05377                 }
05378         }
05379         else if (bs->revengeHateLevel < bs->loved_death_thresh-1)
05380         { //only switch hatred if we don't hate the existing revenge-enemy too much
05381                 //CHAT: BelovedKilled section
05382                 bs->chatObject = &g_entities[loved->client];
05383                 bs->chatAltObject = loved->lastHurt;
05384                 BotDoChat(bs, "BelovedKilled", 0);
05385                 bs->revengeHateLevel = 0;
05386                 bs->revengeEnemy = loved->lastHurt;
05387         }
05388 }
05389 
05390 void BotDeathNotify(bot_state_t *bs)
05391 { //in case someone has an emotional attachment to us, we'll notify them
05392         int i = 0;
05393         int ltest = 0;
05394 
05395         while (i < MAX_CLIENTS)
05396         {
05397                 if (botstates[i] && botstates[i]->lovednum)
05398                 {
05399                         ltest = 0;
05400                         while (ltest < botstates[i]->lovednum)
05401                         {
05402                                 if (strcmp(level.clients[bs->client].pers.netname, botstates[i]->loved[ltest].name) == 0)
05403                                 {
05404                                         BotLovedOneDied(botstates[i], bs, botstates[i]->loved[ltest].level);
05405                                         break;
05406                                 }
05407 
05408                                 ltest++;
05409                         }
05410                 }
05411 
05412                 i++;
05413         }
05414 }
05415 
05416 //perform strafe trace checks
05417 void StrafeTracing(bot_state_t *bs)
05418 {
05419         vec3_t mins, maxs;
05420         vec3_t right, rorg, drorg;
05421         trace_t tr;
05422 
05423         mins[0] = -15;
05424         mins[1] = -15;
05425         //mins[2] = -24;
05426         mins[2] = -22;
05427         maxs[0] = 15;
05428         maxs[1] = 15;
05429         maxs[2] = 32;
05430 
05431         AngleVectors(bs->viewangles, NULL, right, NULL);
05432 
05433         if (bs->meleeStrafeDir)
05434         {
05435                 rorg[0] = bs->origin[0] - right[0]*32;
05436                 rorg[1] = bs->origin[1] - right[1]*32;
05437                 rorg[2] = bs->origin[2] - right[2]*32;
05438         }
05439         else
05440         {
05441                 rorg[0] = bs->origin[0] + right[0]*32;
05442                 rorg[1] = bs->origin[1] + right[1]*32;
05443                 rorg[2] = bs->origin[2] + right[2]*32;
05444         }
05445 
05446         trap_Trace(&tr, bs->origin, mins, maxs, rorg, bs->client, MASK_SOLID);
05447 
05448         if (tr.fraction != 1)
05449         {
05450                 bs->meleeStrafeDisable = level.time + Q_irand(500, 1500);
05451         }
05452 
05453         VectorCopy(rorg, drorg);
05454 
05455         drorg[2] -= 32;
05456 
05457         trap_Trace(&tr, rorg, NULL, NULL, drorg, bs->client, MASK_SOLID);
05458 
05459         if (tr.fraction == 1)
05460         { //this may be a dangerous ledge, so don't strafe over it just in case
05461                 bs->meleeStrafeDisable = level.time + Q_irand(500, 1500);
05462         }
05463 }
05464 
05465 //doing primary weapon fire
05466 int PrimFiring(bot_state_t *bs)
05467 {
05468         if (bs->cur_ps.weaponstate != WEAPON_CHARGING &&
05469                 bs->doAttack)
05470         {
05471                 return 1;
05472         }
05473 
05474         if (bs->cur_ps.weaponstate == WEAPON_CHARGING &&
05475                 !bs->doAttack)
05476         {
05477                 return 1;
05478         }
05479 
05480         return 0;
05481 }
05482 
05483 //should we keep our primary weapon from firing?
05484 int KeepPrimFromFiring(bot_state_t *bs)
05485 {
05486         if (bs->cur_ps.weaponstate != WEAPON_CHARGING &&
05487                 bs->doAttack)
05488         {
05489                 bs->doAttack = 0;
05490         }
05491 
05492         if (bs->cur_ps.weaponstate == WEAPON_CHARGING &&
05493                 !bs->doAttack)
05494         {
05495                 bs->doAttack = 1;
05496         }
05497 
05498         return 0;
05499 }
05500 
05501 //doing secondary weapon fire
05502 int AltFiring(bot_state_t *bs)
05503 {
05504         if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT &&
05505                 bs->doAltAttack)
05506         {
05507                 return 1;
05508         }
05509 
05510         if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
05511                 !bs->doAltAttack)
05512         {
05513                 return 1;
05514         }
05515 
05516         return 0;
05517 }
05518 
05519 //should we keep our alt from firing?
05520 int KeepAltFromFiring(bot_state_t *bs)
05521 {
05522         if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT &&
05523                 bs->doAltAttack)
05524         {
05525                 bs->doAltAttack = 0;
05526         }
05527 
05528         if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
05529                 !bs->doAltAttack)
05530         {
05531                 bs->doAltAttack = 1;
05532         }
05533 
05534         return 0;
05535 }
05536 
05537 //Try not to shoot our friends in the back. Or in the face. Or anywhere, really.
05538 gentity_t *CheckForFriendInLOF(bot_state_t *bs)
05539 {
05540         vec3_t fwd;
05541         vec3_t trfrom, trto;
05542         vec3_t mins, maxs;
05543         gentity_t *trent;
05544         trace_t tr;
05545 
05546         mins[0] = -3;
05547         mins[1] = -3;
05548         mins[2] = -3;
05549 
05550         maxs[0] = 3;
05551         maxs[1] = 3;
05552         maxs[2] = 3;
05553 
05554         AngleVectors(bs->viewangles, fwd, NULL, NULL);
05555 
05556         VectorCopy(bs->eye, trfrom);
05557 
05558         trto[0] = trfrom[0] + fwd[0]*2048;
05559         trto[1] = trfrom[1] + fwd[1]*2048;
05560         trto[2] = trfrom[2] + fwd[2]*2048;
05561 
05562         trap_Trace(&tr, trfrom, mins, maxs, trto, bs->client, MASK_PLAYERSOLID);
05563 
05564         if (tr.fraction != 1 && tr.entityNum <= MAX_CLIENTS)
05565         {
05566                 trent = &g_entities[tr.entityNum];
05567 
05568                 if (trent && trent->client)
05569                 {
05570                         if (IsTeamplay() && OnSameTeam(&g_entities[bs->client], trent))
05571                         {
05572                                 return trent;
05573                         }
05574 
05575                         if (botstates[trent->s.number] && GetLoveLevel(bs, botstates[trent->s.number]) > 1)
05576                         {
05577                                 return trent;
05578                         }
05579                 }
05580         }
05581 
05582         return NULL;
05583 }
05584 
05585 void BotScanForLeader(bot_state_t *bs)
05586 { //bots will only automatically obtain a leader if it's another bot using this method.
05587         int i = 0;
05588         gentity_t *ent;
05589 
05590         if (bs->isSquadLeader)
05591         {
05592                 return;
05593         }
05594 
05595         while (i < MAX_CLIENTS)
05596         {
05597                 ent = &g_entities[i];
05598 
05599                 if (ent && ent->client && botstates[i] && botstates[i]->isSquadLeader && bs->client != i)
05600                 {
05601                         if (OnSameTeam(&g_entities[bs->client], ent))
05602                         {
05603                                 bs->squadLeader = ent;
05604                                 break;
05605                         }
05606                         if (GetLoveLevel(bs, botstates[i]) > 1 && !IsTeamplay())
05607                         { //ignore love status regarding squad leaders if we're in teamplay
05608                                 bs->squadLeader = ent;
05609                                 break;
05610                         }
05611                 }
05612 
05613                 i++;
05614         }
05615 }
05616 
05617 //w3rd to the p33pz.
05618 void BotReplyGreetings(bot_state_t *bs)
05619 {
05620         int i = 0;
05621         int numhello = 0;
05622 
05623         while (i < MAX_CLIENTS)
05624         {
05625                 if (botstates[i] &&
05626                         botstates[i]->canChat &&
05627                         i != bs->client)
05628                 {
05629                         botstates[i]->chatObject = &g_entities[bs->client];
05630                         botstates[i]->chatAltObject = NULL;
05631                         if (BotDoChat(botstates[i], "ResponseGreetings", 0))
05632                         {
05633                                 numhello++;
05634                         }
05635                 }
05636 
05637                 if (numhello > 3)
05638                 { //don't let more than 4 bots say hello at once
05639                         return;
05640                 }
05641 
05642                 i++;
05643         }
05644 }
05645 
05646 //try to move in to grab a nearby flag
05647 void CTFFlagMovement(bot_state_t *bs)
05648 {
05649         int diddrop = 0;
05650         gentity_t *desiredDrop = NULL;
05651         vec3_t a, mins, maxs;
05652         trace_t tr;
05653 
05654         mins[0] = -15;
05655         mins[1] = -15;
05656         mins[2] = -7;
05657         maxs[0] = 15;
05658         maxs[1] = 15;
05659         maxs[2] = 7;
05660 
05661         if (bs->wantFlag && (bs->wantFlag->flags & FL_DROPPED_ITEM))
05662         {
05663                 if (bs->staticFlagSpot[0] == bs->wantFlag->s.pos.trBase[0] &&
05664                         bs->staticFlagSpot[1] == bs->wantFlag->s.pos.trBase[1] &&
05665                         bs->staticFlagSpot[2] == bs->wantFlag->s.pos.trBase[2])
05666                 {
05667                         VectorSubtract(bs->origin, bs->wantFlag->s.pos.trBase, a);
05668 
05669                         if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE)
05670                         {
05671                                 VectorCopy(bs->wantFlag->s.pos.trBase, bs->goalPosition);
05672                                 return;
05673                         }
05674                         else
05675                         {
05676                                 bs->wantFlag = NULL;
05677                         }
05678                 }
05679                 else
05680                 {
05681                         bs->wantFlag = NULL;
05682                 }
05683         }
05684         else if (bs->wantFlag)
05685         {
05686                 bs->wantFlag = NULL;
05687         }
05688 
05689         if (flagRed && flagBlue)
05690         {
05691                 if (bs->wpDestination == flagRed ||
05692                         bs->wpDestination == flagBlue)
05693                 {
05694                         if (bs->wpDestination == flagRed && droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM) && droppedRedFlag->classname && strcmp(droppedRedFlag->classname, "freed") != 0)
05695                         {
05696                                 desiredDrop = droppedRedFlag;
05697                                 diddrop = 1;
05698                         }
05699                         if (bs->wpDestination == flagBlue && droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM) && droppedBlueFlag->classname && strcmp(droppedBlueFlag->classname, "freed") != 0)
05700                         {
05701                                 desiredDrop = droppedBlueFlag;
05702                                 diddrop = 1;
05703                         }
05704 
05705                         if (diddrop && desiredDrop)
05706                         {
05707                                 VectorSubtract(bs->origin, desiredDrop->s.pos.trBase, a);
05708 
05709                                 if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE)
05710                                 {
05711                                         trap_Trace(&tr, bs->origin, mins, maxs, desiredDrop->s.pos.trBase, bs->client, MASK_SOLID);
05712 
05713                                         if (tr.fraction == 1 || tr.entityNum == desiredDrop->s.number)
05714                                         {
05715                                                 VectorCopy(desiredDrop->s.pos.trBase, bs->goalPosition);
05716                                                 VectorCopy(desiredDrop->s.pos.trBase, bs->staticFlagSpot);
05717                                                 return;
05718                                         }
05719                                 }
05720                         }
05721                 }
05722         }
05723 }
05724 
05725 //see if we want to make our detpacks blow up
05726 void BotCheckDetPacks(bot_state_t *bs)
05727 {
05728         gentity_t *dp = NULL;
05729         gentity_t *myDet = NULL;
05730         vec3_t a;
05731         float enLen;
05732         float myLen;
05733 
05734         while ( (dp = G_Find( dp, FOFS(classname), "detpack") ) != NULL )
05735         {
05736                 if (dp && dp->parent && dp->parent->s.number == bs->client)
05737                 {
05738                         myDet = dp;
05739                         break;
05740                 }
05741         }
05742 
05743         if (!myDet)
05744         {
05745                 return;
05746         }
05747 
05748         if (!bs->currentEnemy || !bs->currentEnemy->client || !bs->frame_Enemy_Vis)
05749         { //require the enemy to be visilbe just to be fair..
05750 
05751                 //unless..
05752                 if (bs->currentEnemy && bs->currentEnemy->client &&
05753                         (level.time - bs->plantContinue) < 5000)
05754                 { //it's a fresh plant (within 5 seconds) so we should be able to guess
05755                         goto stillmadeit;
05756                 }
05757                 return;
05758         }
05759 
05760 stillmadeit:
05761 
05762         VectorSubtract(bs->currentEnemy->client->ps.origin, myDet->s.pos.trBase, a);
05763         enLen = VectorLength(a);
05764 
05765         VectorSubtract(bs->origin, myDet->s.pos.trBase, a);
05766         myLen = VectorLength(a);
05767 
05768         if (enLen > myLen)
05769         {
05770                 return;
05771         }
05772 
05773         if (enLen < BOT_PLANT_BLOW_DISTANCE && OrgVisible(bs->currentEnemy->client->ps.origin, myDet->s.pos.trBase, bs->currentEnemy->s.number))
05774         { //we could just call the "blow all my detpacks" function here, but I guess that's cheating.
05775                 bs->plantKillEmAll = level.time + 500;
05776         }
05777 }
05778 
05779 //see if it would be beneficial at this time to use one of our inv items
05780 int BotUseInventoryItem(bot_state_t *bs)
05781 {
05782         if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC))
05783         {
05784                 if (g_entities[bs->client].health <= 75)
05785                 {
05786                         bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC, IT_HOLDABLE);
05787                         goto wantuseitem;
05788                 }
05789         }
05790         if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC_BIG))
05791         {
05792                 if (g_entities[bs->client].health <= 50)
05793                 {
05794                         bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC_BIG, IT_HOLDABLE);
05795                         goto wantuseitem;
05796                 }
05797         }
05798         if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER))
05799         {
05800                 if (bs->currentEnemy && bs->frame_Enemy_Vis)
05801                 {
05802                         bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SEEKER, IT_HOLDABLE);
05803                         goto wantuseitem;
05804                 }
05805         }
05806         if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN))
05807         {
05808                 if (bs->currentEnemy && bs->frame_Enemy_Vis)
05809                 {
05810                         bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SENTRY_GUN, IT_HOLDABLE);
05811                         goto wantuseitem;
05812                 }
05813         }
05814         if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD))
05815         {
05816                 if (bs->currentEnemy && bs->frame_Enemy_Vis && bs->runningToEscapeThreat)
05817                 { //this will (hopefully) result in the bot placing the shield down while facing
05818                   //the enemy and running away
05819                         bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SHIELD, IT_HOLDABLE);
05820                         goto wantuseitem;
05821                 }
05822         }
05823 
05824         return 0;
05825 
05826 wantuseitem:
05827         level.clients[bs->client].ps.stats[STAT_HOLDABLE_ITEM] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM];
05828 
05829         return 1;
05830 }
05831 
05832 //trace forward to see if we can plant a detpack or something
05833 int BotSurfaceNear(bot_state_t *bs)
05834 {
05835         trace_t tr;
05836         vec3_t fwd;
05837 
05838         AngleVectors(bs->viewangles, fwd, NULL, NULL);
05839 
05840         fwd[0] = bs->origin[0]+(fwd[0]*64);
05841         fwd[1] = bs->origin[1]+(fwd[1]*64);
05842         fwd[2] = bs->origin[2]+(fwd[2]*64);
05843 
05844         trap_Trace(&tr, bs->origin, NULL, NULL, fwd, bs->client, MASK_SOLID);
05845 
05846         if (tr.fraction != 1)
05847         {
05848                 return 1;
05849         }
05850 
05851         return 0;
05852 }
05853 
05854 //could we block projectiles from the weapon potentially with a light saber?
05855 int BotWeaponBlockable(int weapon)
05856 {
05857         switch (weapon)
05858         {
05859         case WP_STUN_BATON:
05860         case WP_MELEE:
05861                 return 0;
05862         case WP_DISRUPTOR:
05863                 return 0;
05864         case WP_DEMP2:
05865                 return 0;
05866         case WP_ROCKET_LAUNCHER:
05867                 return 0;
05868         case WP_THERMAL:
05869                 return 0;
05870         case WP_TRIP_MINE:
05871                 return 0;
05872         case WP_DET_PACK:
05873                 return 0;
05874         default:
05875                 return 1;
05876         }
05877 }
05878 
05879 void Cmd_EngageDuel_f(gentity_t *ent);
05880 void Cmd_ToggleSaber_f(gentity_t *ent);
05881 
05882 //movement overrides
05883 void Bot_SetForcedMovement(int bot, int forward, int right, int up)
05884 {
05885         bot_state_t *bs;
05886 
05887         bs = botstates[bot];
05888 
05889         if (!bs)
05890         { //not a bot
05891                 return;
05892         }
05893 
05894         if (forward != -1)
05895         {
05896                 if (bs->forceMove_Forward)
05897                 {
05898                         bs->forceMove_Forward = 0;
05899                 }
05900                 else
05901                 {
05902                         bs->forceMove_Forward = forward;
05903                 }
05904         }
05905         if (right != -1)
05906         {
05907                 if (bs->forceMove_Right)
05908                 {
05909                         bs->forceMove_Right = 0;
05910                 }
05911                 else
05912                 {
05913                         bs->forceMove_Right = right;
05914                 }
05915         }
05916         if (up != -1)
05917         {
05918                 if (bs->forceMove_Up)
05919                 {
05920                         bs->forceMove_Up = 0;
05921                 }
05922                 else
05923                 {
05924                         bs->forceMove_Up = up;
05925                 }
05926         }
05927 }
05928 
05929 //the main AI loop.
05930 //please don't be too frightened.
05931 void StandardBotAI(bot_state_t *bs, float thinktime)
05932 {
05933         int wp, enemy;
05934         int desiredIndex;
05935         int goalWPIndex;
05936         int doingFallback = 0;
05937         int fjHalt;
05938         vec3_t a, ang, headlevel, eorg, noz_x, noz_y, dif, a_fo;
05939         float reaction;
05940         float bLeadAmount;
05941         int meleestrafe = 0;
05942         int useTheForce = 0;
05943         int forceHostile = 0;
05944         int cBAI = 0;
05945         gentity_t *friendInLOF = 0;
05946         float mLen;
05947         int visResult = 0;
05948         int selResult = 0;
05949         int mineSelect = 0;
05950         int detSelect = 0;
05951         vec3_t preFrameGAngles;
05952 
05953         if (gDeactivated)
05954         {
05955                 bs->wpCurrent = NULL;
05956                 bs->currentEnemy = NULL;
05957                 bs->wpDestination = NULL;
05958                 bs->wpDirection = 0;
05959                 return;
05960         }
05961 
05962         if (g_entities[bs->client].inuse &&
05963                 g_entities[bs->client].client &&
05964                 g_entities[bs->client].client->sess.sessionTeam == TEAM_SPECTATOR)
05965         {
05966                 bs->wpCurrent = NULL;
05967                 bs->currentEnemy = NULL;
05968                 bs->wpDestination = NULL;
05969                 bs->wpDirection = 0;
05970                 return;
05971         }
05972 
05973 
05974 #ifndef FINAL_BUILD
05975         if (bot_getinthecarrr.integer)
05976         { //stupid vehicle debug, I tire of having to connect another client to test passengers.
05977                 gentity_t *botEnt = &g_entities[bs->client];
05978 
05979                 if (botEnt->inuse && botEnt->client && botEnt->client->ps.m_iVehicleNum)
05980                 { //in a vehicle, so...
05981                         bs->noUseTime = level.time + 5000;
05982 
05983                         if (bot_getinthecarrr.integer != 2)
05984                         {
05985                                 trap_EA_MoveForward(bs->client);
05986 
05987                                 if (bot_getinthecarrr.integer == 3)
05988                                 { //use alt fire
05989                                         trap_EA_Alt_Attack(bs->client);
05990                                 }
05991                         }
05992                 }
05993                 else
05994                 { //find one, get in
05995                         int i = 0;
05996                         gentity_t *vehicle = NULL;
05997                         //find the nearest, manned vehicle
05998                         while (i < MAX_GENTITIES)
05999                         {
06000                                 vehicle = &g_entities[i];
06001 
06002                                 if (vehicle->inuse && vehicle->client && vehicle->s.eType == ET_NPC &&
06003                                         vehicle->s.NPC_class == CLASS_VEHICLE && vehicle->m_pVehicle &&
06004                                         (vehicle->client->ps.m_iVehicleNum || bot_getinthecarrr.integer == 2))
06005                                 { //ok, this is a vehicle, and it has a pilot/passengers
06006                                         break;
06007                                 }
06008                                 i++;
06009                         }
06010                         if (i != MAX_GENTITIES && vehicle)
06011                         { //broke before end so we must've found something
06012                                 vec3_t v;
06013 
06014                                 VectorSubtract(vehicle->client->ps.origin, bs->origin, v);
06015                                 VectorNormalize(v);
06016                                 vectoangles(v, bs->goalAngles);
06017                                 MoveTowardIdealAngles(bs);
06018                                 trap_EA_Move(bs->client, v, 5000.0f);
06019 
06020                                 if (bs->noUseTime < (level.time-400))
06021                                 {
06022                                         bs->noUseTime = level.time + 500;
06023                                 }
06024                         }
06025                 }
06026 
06027                 return;
06028         }
06029 #endif
06030 
06031         if (bot_forgimmick.integer)
06032         {
06033                 bs->wpCurrent = NULL;
06034                 bs->currentEnemy = NULL;
06035                 bs->wpDestination = NULL;
06036                 bs->wpDirection = 0;
06037 
06038                 if (bot_forgimmick.integer == 2)
06039                 { //for debugging saber stuff, this is handy
06040                         trap_EA_Attack(bs->client);
06041                 }
06042 
06043                 if (bot_forgimmick.integer == 3)
06044                 { //for testing cpu usage moving around rmg terrain without AI
06045                         vec3_t mdir;
06046 
06047                         VectorSubtract(bs->origin, vec3_origin, mdir);
06048                         VectorNormalize(mdir);
06049                         trap_EA_Attack(bs->client);
06050                         trap_EA_Move(bs->client, mdir, 5000);
06051                 }
06052 
06053                 if (bot_forgimmick.integer == 4)
06054                 { //constantly move toward client 0
06055                         if (g_entities[0].client && g_entities[0].inuse)
06056                         {
06057                                 vec3_t mdir;
06058 
06059                                 VectorSubtract(g_entities[0].client->ps.origin, bs->origin, mdir);
06060                                 VectorNormalize(mdir);
06061                                 trap_EA_Move(bs->client, mdir, 5000);
06062                         }
06063                 }
06064 
06065                 if (bs->forceMove_Forward)
06066                 {
06067                         if (bs->forceMove_Forward > 0)
06068                         {
06069                                 trap_EA_MoveForward(bs->client);
06070                         }
06071                         else
06072                         {
06073                                 trap_EA_MoveBack(bs->client);
06074                         }
06075                 }
06076                 if (bs->forceMove_Right)
06077                 {
06078                         if (bs->forceMove_Right > 0)
06079                         {
06080                                 trap_EA_MoveRight(bs->client);
06081                         }
06082                         else
06083                         {
06084                                 trap_EA_MoveLeft(bs->client);
06085                         }
06086                 }
06087                 if (bs->forceMove_Up)
06088                 {
06089                         trap_EA_Jump(bs->client);
06090                 }
06091                 return;
06092         }
06093 
06094         if (!bs->lastDeadTime)
06095         { //just spawned in?
06096                 bs->lastDeadTime = level.time;
06097         }
06098 
06099         if (g_entities[bs->client].health < 1)
06100         {
06101                 bs->lastDeadTime = level.time;
06102 
06103                 if (!bs->deathActivitiesDone && bs->lastHurt && bs->lastHurt->client && bs->lastHurt->s.number != bs->client)
06104                 {
06105                         BotDeathNotify(bs);
06106                         if (PassLovedOneCheck(bs, bs->lastHurt))
06107                         {
06108                                 //CHAT: Died
06109                                 bs->chatObject = bs->lastHurt;
06110                                 bs->chatAltObject = NULL;
06111                                 BotDoChat(bs, "Died", 0);
06112                         }
06113                         else if (!PassLovedOneCheck(bs, bs->lastHurt) &&
06114                                 botstates[bs->lastHurt->s.number] &&
06115                                 PassLovedOneCheck(botstates[bs->lastHurt->s.number], &g_entities[bs->client]))
06116                         { //killed by a bot that I love, but that does not love me
06117                                 bs->chatObject = bs->lastHurt;
06118                                 bs->chatAltObject = NULL;
06119                                 BotDoChat(bs, "KilledOnPurposeByLove", 0);
06120                         }
06121 
06122                         bs->deathActivitiesDone = 1;
06123                 }
06124                 
06125                 bs->wpCurrent = NULL;
06126                 bs->currentEnemy = NULL;
06127                 bs->wpDestination = NULL;
06128                 bs->wpCamping = NULL;
06129                 bs->wpCampingTo = NULL;
06130                 bs->wpStoreDest = NULL;
06131                 bs->wpDestIgnoreTime = 0;
06132                 bs->wpDestSwitchTime = 0;
06133                 bs->wpSeenTime = 0;
06134                 bs->wpDirection = 0;
06135 
06136                 if (rand()%10 < 5 &&
06137                         (!bs->doChat || bs->chatTime < level.time))
06138                 {
06139                         trap_EA_Attack(bs->client);
06140                 }
06141 
06142                 return;
06143         }
06144 
06145         VectorCopy(bs->goalAngles, preFrameGAngles);
06146 
06147         bs->doAttack = 0;
06148         bs->doAltAttack = 0;
06149         //reset the attack states
06150 
06151         if (bs->isSquadLeader)
06152         {
06153                 CommanderBotAI(bs);
06154         }
06155         else
06156         {
06157                 BotDoTeamplayAI(bs);
06158         }
06159 
06160         if (!bs->currentEnemy)
06161         {
06162                 bs->frame_Enemy_Vis = 0;
06163         }
06164 
06165         if (bs->revengeEnemy && bs->revengeEnemy->client &&
06166                 bs->revengeEnemy->client->pers.connected != CA_ACTIVE && bs->revengeEnemy->client->pers.connected != CA_AUTHORIZING)
06167         {
06168                 bs->revengeEnemy = NULL;
06169                 bs->revengeHateLevel = 0;
06170         }
06171 
06172         if (bs->currentEnemy && bs->currentEnemy->client &&
06173                 bs->currentEnemy->client->pers.connected != CA_ACTIVE && bs->currentEnemy->client->pers.connected != CA_AUTHORIZING)
06174         {
06175                 bs->currentEnemy = NULL;
06176         }
06177 
06178         fjHalt = 0;
06179 
06180 #ifndef FORCEJUMP_INSTANTMETHOD
06181         if (bs->forceJumpChargeTime > level.time)
06182         {
06183                 useTheForce = 1;
06184                 forceHostile = 0;
06185         }
06186 
06187         if (bs->currentEnemy && bs->currentEnemy->client && bs->frame_Enemy_Vis && bs->forceJumpChargeTime < level.time)
06188 #else
06189         if (bs->currentEnemy && bs->currentEnemy->client && bs->frame_Enemy_Vis)
06190 #endif
06191         {
06192                 VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, a_fo);
06193                 vectoangles(a_fo, a_fo);
06194 
06195                 //do this above all things
06196                 if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PUSH)) && (bs->doForcePush > level.time || bs->cur_ps.fd.forceGripBeingGripped > level.time) && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PUSH]][FP_PUSH] /*&& InFieldOfVision(bs->viewangles, 50, a_fo)*/)
06197                 {
06198                         level.clients[bs->client].ps.fd.forcePowerSelected = FP_PUSH;
06199                         useTheForce = 1;
06200                         forceHostile = 1;
06201                 }
06202                 else if (bs->cur_ps.fd.forceSide == FORCE_DARKSIDE)
06203                 { //try dark side powers
06204                   //in order of priority top to bottom
06205                         if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_GRIP)) && (bs->cur_ps.fd.forcePowersActive & (1 << FP_GRIP)) && InFieldOfVision(bs->viewangles, 50, a_fo))
06206                         { //already gripping someone, so hold it
06207                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_GRIP;
06208                                 useTheForce = 1;
06209                                 forceHostile = 1;
06210                         }
06211                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_LIGHTNING)) && bs->frame_Enemy_Len < FORCE_LIGHTNING_RADIUS && level.clients[bs->client].ps.fd.forcePower > 50 && InFieldOfVision(bs->viewangles, 50, a_fo))
06212                         {
06213                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_LIGHTNING;
06214                                 useTheForce = 1;
06215                                 forceHostile = 1;
06216                         }
06217                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_GRIP)) && bs->frame_Enemy_Len < MAX_GRIP_DISTANCE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_GRIP]][FP_GRIP] && InFieldOfVision(bs->viewangles, 50, a_fo))
06218                         {
06219                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_GRIP;
06220                                 useTheForce = 1;
06221                                 forceHostile = 1;
06222                         }
06223                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_RAGE)) && g_entities[bs->client].health < 25 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_RAGE]][FP_RAGE])
06224                         {
06225                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_RAGE;
06226                                 useTheForce = 1;
06227                                 forceHostile = 0;
06228                         }
06229                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_DRAIN)) && bs->frame_Enemy_Len < MAX_DRAIN_DISTANCE && level.clients[bs->client].ps.fd.forcePower > 50 && InFieldOfVision(bs->viewangles, 50, a_fo) && bs->currentEnemy->client->ps.fd.forcePower > 10 && bs->currentEnemy->client->ps.fd.forceSide == FORCE_LIGHTSIDE)
06230                         {
06231                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_DRAIN;
06232                                 useTheForce = 1;
06233                                 forceHostile = 1;
06234                         }
06235                 }
06236                 else if (bs->cur_ps.fd.forceSide == FORCE_LIGHTSIDE)
06237                 { //try light side powers
06238                         if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && bs->cur_ps.fd.forceGripCripple &&
06239                                  level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB])
06240                         { //absorb to get out
06241                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB;
06242                                 useTheForce = 1;
06243                                 forceHostile = 0;
06244                         }
06245                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && bs->cur_ps.electrifyTime >= level.time &&
06246                                  level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB])
06247                         { //absorb lightning
06248                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB;
06249                                 useTheForce = 1;
06250                                 forceHostile = 0;
06251                         }
06252                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_TELEPATHY)) && bs->frame_Enemy_Len < MAX_TRICK_DISTANCE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TELEPATHY]][FP_TELEPATHY] && InFieldOfVision(bs->viewangles, 50, a_fo) && !(bs->currentEnemy->client->ps.fd.forcePowersActive & (1 << FP_SEE)))
06253                         {
06254                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_TELEPATHY;
06255                                 useTheForce = 1;
06256                                 forceHostile = 1;
06257                         }
06258                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && g_entities[bs->client].health < 75 && bs->currentEnemy->client->ps.fd.forceSide == FORCE_DARKSIDE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB])
06259                         {
06260                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB;
06261                                 useTheForce = 1;
06262                                 forceHostile = 0;
06263                         }
06264                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PROTECT)) && g_entities[bs->client].health < 35 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PROTECT]][FP_PROTECT])
06265                         {
06266                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_PROTECT;
06267                                 useTheForce = 1;
06268                                 forceHostile = 0;
06269                         }
06270                 }
06271 
06272                 if (!useTheForce)
06273                 { //try neutral powers
06274                         if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PUSH)) && bs->cur_ps.fd.forceGripBeingGripped > level.time && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PUSH]][FP_PUSH] && InFieldOfVision(bs->viewangles, 50, a_fo))
06275                         {
06276                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_PUSH;
06277                                 useTheForce = 1;
06278                                 forceHostile = 1;
06279                         }
06280                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_SPEED)) && g_entities[bs->client].health < 25 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_SPEED]][FP_SPEED])
06281                         {
06282                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_SPEED;
06283                                 useTheForce = 1;
06284                                 forceHostile = 0;
06285                         }
06286                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_SEE)) && BotMindTricked(bs->client, bs->currentEnemy->s.number) && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_SEE]][FP_SEE])
06287                         {
06288                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_SEE;
06289                                 useTheForce = 1;
06290                                 forceHostile = 0;
06291                         }
06292                         else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PULL)) && bs->frame_Enemy_Len < 256 && level.clients[bs->client].ps.fd.forcePower > 75 && InFieldOfVision(bs->viewangles, 50, a_fo))
06293                         {
06294                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_PULL;
06295                                 useTheForce = 1;
06296                                 forceHostile = 1;
06297                         }
06298                 }
06299         }
06300 
06301         if (!useTheForce)
06302         { //try powers that we don't care if we have an enemy for
06303                 if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_HEAL)) && g_entities[bs->client].health < 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_HEAL]][FP_HEAL] && bs->cur_ps.fd.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_1)
06304                 {
06305                         level.clients[bs->client].ps.fd.forcePowerSelected = FP_HEAL;
06306                         useTheForce = 1;
06307                         forceHostile = 0;
06308                 }
06309                 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_HEAL)) && g_entities[bs->client].health < 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_HEAL]][FP_HEAL] && !bs->currentEnemy && bs->isCamping > level.time)
06310                 { //only meditate and heal if we're camping
06311                         level.clients[bs->client].ps.fd.forcePowerSelected = FP_HEAL;
06312                         useTheForce = 1;
06313                         forceHostile = 0;
06314                 }
06315         }
06316 
06317         if (useTheForce && forceHostile)
06318         {
06319                 if (bs->currentEnemy && bs->currentEnemy->client &&
06320                         !ForcePowerUsableOn(&g_entities[bs->client], bs->currentEnemy, level.clients[bs->client].ps.fd.forcePowerSelected))
06321                 {
06322                         useTheForce = 0;
06323                         forceHostile = 0;
06324                 }
06325         }
06326 
06327         doingFallback = 0;
06328 
06329         bs->deathActivitiesDone = 0;
06330 
06331         if (BotUseInventoryItem(bs))
06332         {
06333                 if (rand()%10 < 5)
06334                 {
06335                         trap_EA_Use(bs->client);
06336                 }
06337         }
06338 
06339         if (bs->cur_ps.ammo[weaponData[bs->cur_ps.weapon].ammoIndex] < weaponData[bs->cur_ps.weapon].energyPerShot)
06340         {
06341                 if (BotTryAnotherWeapon(bs))
06342                 {
06343                         return;
06344                 }
06345         }
06346         else
06347         {
06348                 if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number &&
06349                         bs->frame_Enemy_Vis && bs->forceWeaponSelect /*&& bs->plantContinue < level.time*/)
06350                 {
06351                         bs->forceWeaponSelect = 0;
06352                 }
06353 
06354                 if (bs->plantContinue > level.time)
06355                 {
06356                         bs->doAttack = 1;
06357                         bs->destinationGrabTime = 0;
06358                 }
06359 
06360                 if (!bs->forceWeaponSelect && bs->cur_ps.hasDetPackPlanted && bs->plantKillEmAll > level.time)
06361                 {
06362                         bs->forceWeaponSelect = WP_DET_PACK;
06363                 }
06364 
06365                 if (bs->forceWeaponSelect)
06366                 {
06367                         selResult = BotSelectChoiceWeapon(bs, bs->forceWeaponSelect, 1);
06368                 }
06369 
06370                 if (selResult)
06371                 {
06372                         if (selResult == 2)
06373                         { //newly selected
06374                                 return;
06375                         }
06376                 }
06377                 else if (BotSelectIdealWeapon(bs))
06378                 {
06379                         return;
06380                 }
06381         }
06382         /*if (BotSelectMelee(bs))
06383         {
06384                 return;
06385         }*/
06386 
06387         reaction = bs->skills.reflex/bs->settings.skill;
06388 
06389         if (reaction < 0)
06390         {
06391                 reaction = 0;
06392         }
06393         if (reaction > 2000)
06394         {
06395                 reaction = 2000;
06396         }
06397 
06398         if (!bs->currentEnemy)
06399         {
06400                 bs->timeToReact = level.time + reaction;
06401         }
06402 
06403         if (bs->cur_ps.weapon == WP_DET_PACK && bs->cur_ps.hasDetPackPlanted && bs->plantKillEmAll > level.time)
06404         {
06405                 bs->doAltAttack = 1;
06406         }
06407 
06408         if (bs->wpCamping)
06409         {
06410                 if (bs->isCamping < level.time)
06411                 {
06412                         bs->wpCamping = NULL;
06413                         bs->isCamping = 0;
06414                 }
06415 
06416                 if (bs->currentEnemy && bs->frame_Enemy_Vis)
06417                 {
06418                         bs->wpCamping = NULL;
06419                         bs->isCamping = 0;
06420                 }
06421         }
06422 
06423         if (bs->wpCurrent &&
06424                 (bs->wpSeenTime < level.time || bs->wpTravelTime < level.time))
06425         {
06426                 bs->wpCurrent = NULL;
06427         }
06428 
06429         if (bs->currentEnemy)
06430         {
06431                 if (bs->enemySeenTime < level.time ||
06432                         !PassStandardEnemyChecks(bs, bs->currentEnemy))
06433                 {
06434                         if (bs->revengeEnemy == bs->currentEnemy &&
06435                                 bs->currentEnemy->health < 1 &&
06436                                 bs->lastAttacked && bs->lastAttacked == bs->currentEnemy)
06437                         {
06438                                 //CHAT: Destroyed hated one [KilledHatedOne section]
06439                                 bs->chatObject = bs->revengeEnemy;
06440                                 bs->chatAltObject = NULL;
06441                                 BotDoChat(bs, "KilledHatedOne", 1);
06442                                 bs->revengeEnemy = NULL;
06443                                 bs->revengeHateLevel = 0;
06444                         }
06445                         else if (bs->currentEnemy->health < 1 && PassLovedOneCheck(bs, bs->currentEnemy) &&
06446                                 bs->lastAttacked && bs->lastAttacked == bs->currentEnemy)
06447                         {
06448                                 //CHAT: Killed
06449                                 bs->chatObject = bs->currentEnemy;
06450                                 bs->chatAltObject = NULL;
06451                                 BotDoChat(bs, "Killed", 0);
06452                         }
06453 
06454                         bs->currentEnemy = NULL;
06455                 }
06456         }
06457 
06458         if (bot_honorableduelacceptance.integer)
06459         {
06460                 if (bs->currentEnemy && bs->currentEnemy->client &&
06461                         bs->cur_ps.weapon == WP_SABER &&
06462                         g_privateDuel.integer &&
06463                         bs->frame_Enemy_Vis &&
06464                         bs->frame_Enemy_Len < 400 &&
06465                         bs->currentEnemy->client->ps.weapon == WP_SABER &&
06466                         bs->currentEnemy->client->ps.saberHolstered)
06467                 {
06468                         vec3_t e_ang_vec;
06469 
06470                         VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, e_ang_vec);
06471 
06472                         if (InFieldOfVision(bs->viewangles, 100, e_ang_vec))
06473                         { //Our enemy has his saber holstered and has challenged us to a duel, so challenge him back
06474                                 if (!bs->cur_ps.saberHolstered)
06475                                 {
06476                                         Cmd_ToggleSaber_f(&g_entities[bs->client]);
06477                                 }
06478                                 else
06479                                 {
06480                                         if (bs->currentEnemy->client->ps.duelIndex == bs->client &&
06481                                                 bs->currentEnemy->client->ps.duelTime > level.time &&
06482                                                 !bs->cur_ps.duelInProgress)
06483                                         {
06484                                                 Cmd_EngageDuel_f(&g_entities[bs->client]);
06485                                         }
06486                                 }
06487 
06488                                 bs->doAttack = 0;
06489                                 bs->doAltAttack = 0;
06490                                 bs->botChallengingTime = level.time + 100;
06491                                 bs->beStill = level.time + 100;
06492                         }
06493                 }
06494         }
06495         //Apparently this "allows you to cheese" when fighting against bots. I'm not sure why you'd want to con bots
06496         //into an easy kill, since they're bots and all. But whatever.
06497 
06498         if (!bs->wpCurrent)
06499         {
06500                 wp = GetNearestVisibleWP(bs->origin, bs->client);
06501 
06502                 if (wp != -1)
06503                 {
06504                         bs->wpCurrent = gWPArray[wp];
06505                         bs->wpSeenTime = level.time + 1500;
06506                         bs->wpTravelTime = level.time + 10000; //never take more than 10 seconds to travel to a waypoint
06507                 }
06508         }
06509 
06510         if (bs->enemySeenTime < level.time || !bs->frame_Enemy_Vis || !bs->currentEnemy ||
06511                 (bs->currentEnemy /*&& bs->cur_ps.weapon == WP_SABER && bs->frame_Enemy_Len > 300*/))
06512         {
06513                 enemy = ScanForEnemies(bs);
06514 
06515                 if (enemy != -1)
06516                 {
06517                         bs->currentEnemy = &g_entities[enemy];
06518                         bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
06519                 }
06520         }
06521 
06522         if (!bs->squadLeader && !bs->isSquadLeader)
06523         {
06524                 BotScanForLeader(bs);
06525         }
06526 
06527         if (!bs->squadLeader && bs->squadCannotLead < level.time)
06528         { //if still no leader after scanning, then become a squad leader
06529                 bs->isSquadLeader = 1;
06530         }
06531 
06532         if (bs->isSquadLeader && bs->squadLeader)
06533         { //we don't follow anyone if we are a leader
06534                 bs->squadLeader = NULL;
06535         }
06536 
06537         //ESTABLISH VISIBILITIES AND DISTANCES FOR THE WHOLE FRAME HERE
06538         if (bs->wpCurrent)
06539         {
06540                 if (g_RMG.integer)
06541                 { //this is somewhat hacky, but in RMG we don't really care about vertical placement because points are scattered across only the terrain.
06542                         vec3_t vecB, vecC;
06543 
06544                         vecB[0] = bs->origin[0];
06545                         vecB[1] = bs->origin[1];
06546                         vecB[2] = bs->origin[2];
06547 
06548                         vecC[0] = bs->wpCurrent->origin[0];
06549                         vecC[1] = bs->wpCurrent->origin[1];
06550                         vecC[2] = vecB[2];
06551 
06552 
06553                         VectorSubtract(vecC, vecB, a);
06554                 }
06555                 else
06556                 {
06557                         VectorSubtract(bs->wpCurrent->origin, bs->origin, a);
06558                 }
06559                 bs->frame_Waypoint_Len = VectorLength(a);
06560 
06561                 visResult = WPOrgVisible(&g_entities[bs->client], bs->origin, bs->wpCurrent->origin, bs->client);
06562 
06563                 if (visResult == 2)
06564                 {
06565                         bs->frame_Waypoint_Vis = 0;
06566                         bs->wpSeenTime = 0;
06567                         bs->wpDestination = NULL;
06568                         bs->wpDestIgnoreTime = level.time + 5000;
06569 
06570                         if (bs->wpDirection)
06571                         {
06572                                 bs->wpDirection = 0;
06573                         }
06574                         else
06575                         {
06576                                 bs->wpDirection = 1;
06577                         }
06578                 }
06579                 else if (visResult)
06580                 {
06581                         bs->frame_Waypoint_Vis = 1;
06582                 }
06583                 else
06584                 {
06585                         bs->frame_Waypoint_Vis = 0;
06586                 }
06587         }
06588 
06589         if (bs->currentEnemy)
06590         {
06591                 if (bs->currentEnemy->client)
06592                 {
06593                         VectorCopy(bs->currentEnemy->client->ps.origin, eorg);
06594                         eorg[2] += bs->currentEnemy->client->ps.viewheight;
06595                 }
06596                 else
06597                 {
06598                         VectorCopy(bs->currentEnemy->s.origin, eorg);
06599                 }
06600 
06601                 VectorSubtract(eorg, bs->eye, a);
06602                 bs->frame_Enemy_Len = VectorLength(a);
06603 
06604                 if (OrgVisible(bs->eye, eorg, bs->client))
06605                 {
06606                         bs->frame_Enemy_Vis = 1;
06607                         VectorCopy(eorg, bs->lastEnemySpotted);
06608                         VectorCopy(bs->origin, bs->hereWhenSpotted);
06609                         bs->lastVisibleEnemyIndex = bs->currentEnemy->s.number;
06610                         //VectorCopy(bs->eye, bs->lastEnemySpotted);
06611                         bs->hitSpotted = 0;
06612                 }
06613                 else
06614                 {
06615                         bs->frame_Enemy_Vis = 0;
06616                 }
06617         }
06618         else
06619         {
06620                 bs->lastVisibleEnemyIndex = ENTITYNUM_NONE;
06621         }
06622         //END
06623 
06624         if (bs->frame_Enemy_Vis)
06625         {
06626                 bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
06627         }
06628 
06629         if (bs->wpCurrent)
06630         {
06631                 int wpTouchDist = BOT_WPTOUCH_DISTANCE;
06632                 WPConstantRoutine(bs);
06633 
06634                 if (!bs->wpCurrent)
06635                 { //WPConstantRoutine has the ability to nullify the waypoint if it fails certain checks, so..
06636                         return;
06637                 }
06638 
06639                 if (bs->wpCurrent->flags & WPFLAG_WAITFORFUNC)
06640                 {
06641                         if (!CheckForFunc(bs->wpCurrent->origin, -1))
06642                         {
06643                                 bs->beStill = level.time + 500; //no func brush under.. wait
06644                         }
06645                 }
06646                 if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC)
06647                 {
06648                         if (CheckForFunc(bs->wpCurrent->origin, -1))
06649                         {
06650                                 bs->beStill = level.time + 500; //func brush under.. wait
06651                         }
06652                 }
06653 
06654                 if (bs->frame_Waypoint_Vis || (bs->wpCurrent->flags & WPFLAG_NOVIS))
06655                 {
06656                         if (g_RMG.integer)
06657                         {
06658                                 bs->wpSeenTime = level.time + 5000; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it
06659                         }
06660                         else
06661                         {
06662                                 bs->wpSeenTime = level.time + 1500; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it
06663                         }
06664                 }
06665                 VectorCopy(bs->wpCurrent->origin, bs->goalPosition);
06666                 if (bs->wpDirection)
06667                 {
06668                         goalWPIndex = bs->wpCurrent->index-1;
06669                 }
06670                 else
06671                 {
06672                         goalWPIndex = bs->wpCurrent->index+1;
06673                 }
06674 
06675                 if (bs->wpCamping)
06676                 {
06677                         VectorSubtract(bs->wpCampingTo->origin, bs->origin, a);
06678                         vectoangles(a, ang);
06679                         VectorCopy(ang, bs->goalAngles);
06680 
06681                         VectorSubtract(bs->origin, bs->wpCamping->origin, a);
06682                         if (VectorLength(a) < 64)
06683                         {
06684                                 VectorCopy(bs->wpCamping->origin, bs->goalPosition);
06685                                 bs->beStill = level.time + 1000;
06686 
06687                                 if (!bs->campStanding)
06688                                 {
06689                                         bs->duckTime = level.time + 1000;
06690                                 }
06691                         }
06692                 }
06693                 else if (gWPArray[goalWPIndex] && gWPArray[goalWPIndex]->inuse &&
06694                         !(gLevelFlags & LEVELFLAG_NOPOINTPREDICTION))
06695                 {
06696                         VectorSubtract(gWPArray[goalWPIndex]->origin, bs->origin, a);
06697                         vectoangles(a, ang);
06698                         VectorCopy(ang, bs->goalAngles);
06699                 }
06700                 else
06701                 {
06702                         VectorSubtract(bs->wpCurrent->origin, bs->origin, a);
06703                         vectoangles(a, ang);
06704                         VectorCopy(ang, bs->goalAngles);
06705                 }
06706 
06707                 if (bs->destinationGrabTime < level.time /*&& (!bs->wpDestination || (bs->currentEnemy && bs->frame_Enemy_Vis))*/)
06708                 {
06709                         GetIdealDestination(bs);
06710                 }
06711                 
06712                 if (bs->wpCurrent && bs->wpDestination)
06713                 {
06714                         if (TotalTrailDistance(bs->wpCurrent->index, bs->wpDestination->index, bs) == -1)
06715                         {
06716                                 bs->wpDestination = NULL;
06717                                 bs->destinationGrabTime = level.time + 10000;
06718                         }
06719                 }
06720 
06721                 if (g_RMG.integer)
06722                 {
06723                         if (bs->frame_Waypoint_Vis)
06724                         {
06725                                 if (bs->wpCurrent && !bs->wpCurrent->flags)
06726                                 {
06727                                         wpTouchDist *= 3;
06728                                 }
06729                         }
06730                 }
06731 
06732                 if (bs->frame_Waypoint_Len < wpTouchDist || (g_RMG.integer && bs->frame_Waypoint_Len < wpTouchDist*2))
06733                 {
06734                         WPTouchRoutine(bs);
06735 
06736                         if (!bs->wpDirection)
06737                         {
06738                                 desiredIndex = bs->wpCurrent->index+1;
06739                         }
06740                         else
06741                         {
06742                                 desiredIndex = bs->wpCurrent->index-1;
06743                         }
06744 
06745                         if (gWPArray[desiredIndex] &&
06746                                 gWPArray[desiredIndex]->inuse &&
06747                                 desiredIndex < gWPNum &&
06748                                 desiredIndex >= 0 &&
06749                                 PassWayCheck(bs, desiredIndex))
06750                         {
06751                                 bs->wpCurrent = gWPArray[desiredIndex];
06752                         }
06753                         else
06754                         {
06755                                 if (bs->wpDestination)
06756                                 {
06757                                         bs->wpDestination = NULL;
06758                                         bs->destinationGrabTime = level.time + 10000;
06759                                 }
06760 
06761                                 if (bs->wpDirection)
06762                                 {
06763                                         bs->wpDirection = 0;
06764                                 }
06765                                 else
06766                                 {
06767                                         bs->wpDirection = 1;
06768                                 }
06769                         }
06770                 }
06771         }
06772         else //We can't find a waypoint, going to need a fallback routine.
06773         {
06774                 /*if (g_gametype.integer == GT_DUEL)*/
06775                 { //helps them get out of messy situations
06776                         /*if ((level.time - bs->forceJumpChargeTime) > 3500)
06777                         {
06778                                 bs->forceJumpChargeTime = level.time + 2000;
06779                                 trap_EA_MoveForward(bs->client);
06780                         }
06781                         */
06782                         bs->jumpTime = level.time + 1500;
06783                         bs->jumpHoldTime = level.time + 1500;
06784                         bs->jDelay = 0;
06785                 }
06786                 doingFallback = BotFallbackNavigation(bs);
06787         }
06788 
06789         if (g_RMG.integer)
06790         { //for RMG if the bot sticks around an area too long, jump around randomly some to spread to a new area (horrible hacky method)
06791                 vec3_t vSubDif;
06792 
06793                 VectorSubtract(bs->origin, bs->lastSignificantAreaChange, vSubDif);
06794                 if (VectorLength(vSubDif) > 1500)
06795                 {
06796                         VectorCopy(bs->origin, bs->lastSignificantAreaChange);
06797                         bs->lastSignificantChangeTime = level.time + 20000;
06798                 }
06799 
06800                 if (bs->lastSignificantChangeTime < level.time)
06801                 {
06802                         bs->iHaveNoIdeaWhereIAmGoing = level.time + 17000;
06803                 }
06804         }
06805 
06806         if (bs->iHaveNoIdeaWhereIAmGoing > level.time && !bs->currentEnemy)
06807         {
06808                 VectorCopy(preFrameGAngles, bs->goalAngles);
06809                 bs->wpCurrent = NULL;
06810                 bs->wpSwitchTime = level.time + 150;
06811                 doingFallback = BotFallbackNavigation(bs);
06812                 bs->jumpTime = level.time + 150;
06813                 bs->jumpHoldTime = level.time + 150;
06814                 bs->jDelay = 0;
06815                 bs->lastSignificantChangeTime = level.time + 25000;
06816         }
06817 
06818         if (bs->wpCurrent && g_RMG.integer)
06819         {
06820                 qboolean doJ = qfalse;
06821 
06822                 if (bs->wpCurrent->origin[2]-192 > bs->origin[2])
06823                 {
06824                         doJ = qtrue;
06825                 }
06826                 else if ((bs->wpTravelTime - level.time) < 5000 && bs->wpCurrent->origin[2]-64 > bs->origin[2])
06827                 {
06828                         doJ = qtrue;
06829                 }
06830                 else if ((bs->wpTravelTime - level.time) < 7000 && (bs->wpCurrent->flags & WPFLAG_RED_FLAG))
06831                 {
06832                         if ((level.time - bs->jumpTime) > 200)
06833                         {
06834                                 bs->jumpTime = level.time + 100;
06835                                 bs->jumpHoldTime = level.time + 100;
06836                                 bs->jDelay = 0;
06837                         }
06838                 }
06839                 else if ((bs->wpTravelTime - level.time) < 7000 && (bs->wpCurrent->flags & WPFLAG_BLUE_FLAG))
06840                 {
06841                         if ((level.time - bs->jumpTime) > 200)
06842                         {
06843                                 bs->jumpTime = level.time + 100;
06844                                 bs->jumpHoldTime = level.time + 100;
06845                                 bs->jDelay = 0;
06846                         }
06847                 }
06848                 else if (bs->wpCurrent->index > 0)
06849                 {
06850                         if ((bs->wpTravelTime - level.time) < 7000)
06851                         {
06852                                 if ((gWPArray[bs->wpCurrent->index-1]->flags & WPFLAG_RED_FLAG) ||
06853                                         (gWPArray[bs->wpCurrent->index-1]->flags & WPFLAG_BLUE_FLAG))
06854                                 {
06855                                         if ((level.time - bs->jumpTime) > 200)
06856                                         {
06857                                                 bs->jumpTime = level.time + 100;
06858                                                 bs->jumpHoldTime = level.time + 100;
06859                                                 bs->jDelay = 0;
06860                                         }
06861                                 }
06862                         }
06863                 }
06864 
06865                 if (doJ)
06866                 {
06867                         bs->jumpTime = level.time + 1500;
06868                         bs->jumpHoldTime = level.time + 1500;
06869                         bs->jDelay = 0;
06870                 }
06871         }
06872 
06873         if (doingFallback)
06874         {
06875                 bs->doingFallback = qtrue;
06876         }
06877         else
06878         {
06879                 bs->doingFallback = qfalse;
06880         }
06881 
06882         if (bs->timeToReact < level.time && bs->currentEnemy && bs->enemySeenTime > level.time + (ENEMY_FORGET_MS - (ENEMY_FORGET_MS*0.2)))
06883         {
06884                 if (bs->frame_Enemy_Vis)
06885                 {
06886                         cBAI = CombatBotAI(bs, thinktime);
06887                 }
06888                 else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
06889                 { //keep charging in case we see him again before we lose track of him
06890                         bs->doAltAttack = 1;
06891                 }
06892                 else if (bs->cur_ps.weaponstate == WEAPON_CHARGING)
06893                 { //keep charging in case we see him again before we lose track of him
06894                         bs->doAttack = 1;
06895                 }
06896 
06897                 if (bs->destinationGrabTime > level.time + 100)
06898                 {
06899                         bs->destinationGrabTime = level.time + 100; //assures that we will continue staying within a general area of where we want to be in a combat situation
06900                 }
06901 
06902                 if (bs->currentEnemy->client)
06903                 {
06904                         VectorCopy(bs->currentEnemy->client->ps.origin, headlevel);
06905                         headlevel[2] += bs->currentEnemy->client->ps.viewheight;
06906                 }
06907                 else
06908                 {
06909                         VectorCopy(bs->currentEnemy->client->ps.origin, headlevel);
06910                 }
06911 
06912                 if (!bs->frame_Enemy_Vis)
06913                 {
06914                         //if (!bs->hitSpotted && VectorLength(a) > 256)
06915                         if (OrgVisible(bs->eye, bs->lastEnemySpotted, -1))
06916                         {
06917                                 VectorCopy(bs->lastEnemySpotted, headlevel);
06918                                 VectorSubtract(headlevel, bs->eye, a);
06919                                 vectoangles(a, ang);
06920                                 VectorCopy(ang, bs->goalAngles);
06921 
06922                                 if (bs->cur_ps.weapon == WP_FLECHETTE &&
06923                                         bs->cur_ps.weaponstate == WEAPON_READY &&
06924                                         bs->currentEnemy && bs->currentEnemy->client)
06925                                 {
06926                                         mLen = VectorLength(a) > 128;
06927                                         if (mLen > 128 && mLen < 1024)
06928                                         {
06929                                                 VectorSubtract(bs->currentEnemy->client->ps.origin, bs->lastEnemySpotted, a);
06930 
06931                                                 if (VectorLength(a) < 300)
06932                                                 {
06933                                                         bs->doAltAttack = 1;
06934                                                 }
06935                                         }
06936                                 }
06937                         }
06938                 }
06939                 else
06940                 {
06941                         bLeadAmount = BotWeaponCanLead(bs);
06942                         if ((bs->skills.accuracy/bs->settings.skill) <= 8 &&
06943                                 bLeadAmount)
06944                         {
06945                                 BotAimLeading(bs, headlevel, bLeadAmount);
06946                         }
06947                         else
06948                         {
06949                                 VectorSubtract(headlevel, bs->eye, a);
06950                                 vectoangles(a, ang);
06951                                 VectorCopy(ang, bs->goalAngles);
06952                         }
06953 
06954                         BotAimOffsetGoalAngles(bs);
06955                 }
06956         }
06957 
06958         if (bs->cur_ps.saberInFlight)
06959         {
06960                 bs->saberThrowTime = level.time + Q_irand(4000, 10000);
06961         }
06962 
06963         if (bs->currentEnemy)
06964         {
06965                 if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER)
06966                 {
06967                         int saberRange = SABER_ATTACK_RANGE;
06968 
06969                         VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, a_fo);
06970                         vectoangles(a_fo, a_fo);
06971 
06972                         if (bs->saberPowerTime < level.time)
06973                         { //Don't just use strong attacks constantly, switch around a bit
06974                                 if (Q_irand(1, 10) <= 5)
06975                                 {
06976                                         bs->saberPower = qtrue;
06977                                 }
06978                                 else
06979                                 {
06980                                         bs->saberPower = qfalse;
06981                                 }
06982 
06983                                 bs->saberPowerTime = level.time + Q_irand(3000, 15000);
06984                         }
06985 
06986                         if ( g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_STAFF
06987                                 && g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_DUAL )
06988                         {
06989                                 if (bs->currentEnemy->health > 75 
06990                                         && g_entities[bs->client].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > 2)
06991                                 {
06992                                         if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_STRONG 
06993                                                 && bs->saberPower)
06994                                         { //if we are up against someone with a lot of health and we have a strong attack available, then h4q them
06995                                                 Cmd_SaberAttackCycle_f(&g_entities[bs->client]);
06996                                         }
06997                                 }
06998                                 else if (bs->currentEnemy->health > 40 
06999                                         && g_entities[bs->client].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > 1)
07000                                 {
07001                                         if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_MEDIUM)
07002                                         { //they're down on health a little, use level 2 if we can
07003                                                 Cmd_SaberAttackCycle_f(&g_entities[bs->client]);
07004                                         }
07005                                 }
07006                                 else
07007                                 {
07008                                         if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_FAST)
07009                                         { //they've gone below 40 health, go at them with quick attacks
07010                                                 Cmd_SaberAttackCycle_f(&g_entities[bs->client]);
07011                                         }
07012                                 }
07013                         }
07014 
07015                         if (g_gametype.integer == GT_SINGLE_PLAYER)
07016                         {
07017                                 saberRange *= 3;
07018                         }
07019 
07020                         if (bs->frame_Enemy_Len <= saberRange)
07021                         {
07022                                 SaberCombatHandling(bs);
07023 
07024                                 if (bs->frame_Enemy_Len < 80)
07025                                 {
07026                                         meleestrafe = 1;
07027                                 }
07028                         }
07029                         else if (bs->saberThrowTime < level.time && !bs->cur_ps.saberInFlight &&
07030                                 (bs->cur_ps.fd.forcePowersKnown & (1 << FP_SABERTHROW)) &&
07031                                 InFieldOfVision(bs->viewangles, 30, a_fo) &&
07032                                 bs->frame_Enemy_Len < BOT_SABER_THROW_RANGE &&
07033                                 bs->cur_ps.fd.saberAnimLevel != SS_STAFF)
07034                         {
07035                                 bs->doAltAttack = 1;
07036                                 bs->doAttack = 0;
07037                         }
07038                         else if (bs->cur_ps.saberInFlight && bs->frame_Enemy_Len > 300 && bs->frame_Enemy_Len < BOT_SABER_THROW_RANGE)
07039                         {
07040                                 bs->doAltAttack = 1;
07041                                 bs->doAttack = 0;
07042                         }
07043                 }
07044                 else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)
07045                 {
07046                         if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE)
07047                         {
07048                                 MeleeCombatHandling(bs);
07049                                 meleestrafe = 1;
07050                         }
07051                 }
07052         }
07053 
07054         if (doingFallback && bs->currentEnemy) //just stand and fire if we have no idea where we are
07055         {
07056                 VectorCopy(bs->origin, bs->goalPosition);
07057         }
07058 
07059         if (bs->forceJumping > level.time)
07060         {
07061                 VectorCopy(bs->origin, noz_x);
07062                 VectorCopy(bs->goalPosition, noz_y);
07063 
07064                 noz_x[2] = noz_y[2];
07065 
07066                 VectorSubtract(noz_x, noz_y, noz_x);
07067 
07068                 if (VectorLength(noz_x) < 32)
07069                 {
07070                         fjHalt = 1;
07071                 }
07072         }
07073 
07074         if (bs->doChat && bs->chatTime > level.time && (!bs->currentEnemy || !bs->frame_Enemy_Vis))
07075         {
07076                 return;
07077         }
07078         else if (bs->doChat && bs->currentEnemy && bs->frame_Enemy_Vis)
07079         {
07080                 //bs->chatTime = level.time + bs->chatTime_stored;
07081                 bs->doChat = 0; //do we want to keep the bot waiting to chat until after the enemy is gone?
07082                 bs->chatTeam = 0;
07083         }
07084         else if (bs->doChat && bs->chatTime <= level.time)
07085         {
07086                 if (bs->chatTeam)
07087                 {
07088                         trap_EA_SayTeam(bs->client, bs->currentChat);
07089                         bs->chatTeam = 0;
07090                 }
07091                 else
07092                 {
07093                         trap_EA_Say(bs->client, bs->currentChat);
07094                 }
07095                 if (bs->doChat == 2)
07096                 {
07097                         BotReplyGreetings(bs);
07098                 }
07099                 bs->doChat = 0;
07100         }
07101 
07102         CTFFlagMovement(bs);
07103 
07104         if (/*bs->wpDestination &&*/ bs->shootGoal &&
07105                 /*bs->wpDestination->associated_entity == bs->shootGoal->s.number &&*/
07106                 bs->shootGoal->health > 0 && bs->shootGoal->takedamage)
07107         {
07108                 dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
07109                 dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
07110                 dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
07111 
07112                 if (!bs->currentEnemy || bs->frame_Enemy_Len > 256)
07113                 { //if someone is close then don't stop shooting them for this
07114                         VectorSubtract(dif, bs->eye, a);
07115                         vectoangles(a, a);
07116                         VectorCopy(a, bs->goalAngles);
07117 
07118                         if (InFieldOfVision(bs->viewangles, 30, a) &&
07119                                 EntityVisibleBox(bs->origin, NULL, NULL, dif, bs->client, bs->shootGoal->s.number))
07120                         {
07121                                 bs->doAttack = 1;
07122                         }
07123                 }
07124         }
07125 
07126         if (bs->cur_ps.hasDetPackPlanted)
07127         { //check if our enemy gets near it and detonate if he does
07128                 BotCheckDetPacks(bs);
07129         }
07130         else if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number && !bs->frame_Enemy_Vis && bs->plantTime < level.time &&
07131                 !bs->doAttack && !bs->doAltAttack)
07132         {
07133                 VectorSubtract(bs->origin, bs->hereWhenSpotted, a);
07134 
07135                 if (bs->plantDecided > level.time || (bs->frame_Enemy_Len < BOT_PLANT_DISTANCE*2 && VectorLength(a) < BOT_PLANT_DISTANCE))
07136                 {
07137                         mineSelect = BotSelectChoiceWeapon(bs, WP_TRIP_MINE, 0);
07138                         detSelect = BotSelectChoiceWeapon(bs, WP_DET_PACK, 0);
07139                         if (bs->cur_ps.hasDetPackPlanted)
07140                         {
07141                                 detSelect = 0;
07142                         }
07143 
07144                         if (bs->plantDecided > level.time && bs->forceWeaponSelect &&
07145                                 bs->cur_ps.weapon == bs->forceWeaponSelect)
07146                         {
07147                                 bs->doAttack = 1;
07148                                 bs->plantDecided = 0;
07149                                 bs->plantTime = level.time + BOT_PLANT_INTERVAL;
07150                                 bs->plantContinue = level.time + 500;
07151                                 bs->beStill = level.time + 500;
07152                         }
07153                         else if (mineSelect || detSelect)
07154                         {
07155                                 if (BotSurfaceNear(bs))
07156                                 {
07157                                         if (!mineSelect)
07158                                         { //if no mines use detpacks, otherwise use mines
07159                                                 mineSelect = WP_DET_PACK;
07160                                         }
07161                                         else
07162                                         {
07163                                                 mineSelect = WP_TRIP_MINE;
07164                                         }
07165 
07166                                         detSelect = BotSelectChoiceWeapon(bs, mineSelect, 1);
07167 
07168                                         if (detSelect && detSelect != 2)
07169                                         { //We have it and it is now our weapon
07170                                                 bs->plantDecided = level.time + 1000;
07171                                                 bs->forceWeaponSelect = mineSelect;
07172                                                 return;
07173                                         }
07174                                         else if (detSelect == 2)
07175                                         {
07176                                                 bs->forceWeaponSelect = mineSelect;
07177                                                 return;
07178                                         }
07179                                 }
07180                         }
07181                 }
07182         }
07183         else if (bs->plantContinue < level.time)
07184         {
07185                 bs->forceWeaponSelect = 0;
07186         }
07187 
07188         if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster && bs->jmState == -1 && gJMSaberEnt && gJMSaberEnt->inuse)
07189         {
07190                 vec3_t saberLen;
07191                 float fSaberLen = 0;
07192 
07193                 VectorSubtract(bs->origin, gJMSaberEnt->r.currentOrigin, saberLen);
07194                 fSaberLen = VectorLength(saberLen);
07195 
07196                 if (fSaberLen < 256)
07197                 {
07198                         if (OrgVisible(bs->origin, gJMSaberEnt->r.currentOrigin, bs->client))
07199                         {
07200                                 VectorCopy(gJMSaberEnt->r.currentOrigin, bs->goalPosition);
07201                         }
07202                 }
07203         }
07204 
07205         if (bs->beStill < level.time && !WaitingForNow(bs, bs->goalPosition) && !fjHalt)
07206         {
07207                 VectorSubtract(bs->goalPosition, bs->origin, bs->goalMovedir);
07208                 VectorNormalize(bs->goalMovedir);
07209 
07210                 if (bs->jumpTime > level.time && bs->jDelay < level.time &&
07211                         level.clients[bs->client].pers.cmd.upmove > 0)
07212                 {
07213                 //      trap_EA_Move(bs->client, bs->origin, 5000);
07214                         bs->beStill = level.time + 200;
07215                 }
07216                 else
07217                 {
07218                         trap_EA_Move(bs->client, bs->goalMovedir, 5000);
07219                 }
07220 
07221                 if (meleestrafe)
07222                 {
07223                         StrafeTracing(bs);
07224                 }
07225 
07226                 if (bs->meleeStrafeDir && meleestrafe && bs->meleeStrafeDisable < level.time)
07227                 {
07228                         trap_EA_MoveRight(bs->client);
07229                 }
07230                 else if (meleestrafe && bs->meleeStrafeDisable < level.time)
07231                 {
07232                         trap_EA_MoveLeft(bs->client);
07233                 }
07234 
07235                 if (BotTrace_Jump(bs, bs->goalPosition))
07236                 {
07237                         bs->jumpTime = level.time + 100;
07238                 }
07239                 else if (BotTrace_Duck(bs, bs->goalPosition))
07240                 {
07241                         bs->duckTime = level.time + 100;
07242                 }
07243 #ifdef BOT_STRAFE_AVOIDANCE
07244                 else
07245                 {
07246                         int strafeAround = BotTrace_Strafe(bs, bs->goalPosition);
07247 
07248                         if (strafeAround == STRAFEAROUND_RIGHT)
07249                         {
07250                                 trap_EA_MoveRight(bs->client);
07251                         }
07252                         else if (strafeAround == STRAFEAROUND_LEFT)
07253                         {
07254                                 trap_EA_MoveLeft(bs->client);
07255                         }
07256                 }
07257 #endif
07258         }
07259 
07260 #ifndef FORCEJUMP_INSTANTMETHOD
07261         if (bs->forceJumpChargeTime > level.time)
07262         {
07263                 bs->jumpTime = 0;
07264         }
07265 #endif
07266 
07267         if (bs->jumpPrep > level.time)
07268         {
07269                 bs->forceJumpChargeTime = 0;
07270         }
07271 
07272         if (bs->forceJumpChargeTime > level.time)
07273         {
07274                 bs->jumpHoldTime = ((bs->forceJumpChargeTime - level.time)/2) + level.time;
07275                 bs->forceJumpChargeTime = 0;
07276         }
07277 
07278         if (bs->jumpHoldTime > level.time)
07279         {
07280                 bs->jumpTime = bs->jumpHoldTime;
07281         }
07282 
07283         if (bs->jumpTime > level.time && bs->jDelay < level.time)
07284         {
07285                 if (bs->jumpHoldTime > level.time)
07286                 {
07287                         trap_EA_Jump(bs->client);
07288                         if (bs->wpCurrent)
07289                         {
07290                                 if ((bs->wpCurrent->origin[2] - bs->origin[2]) < 64)
07291                                 {
07292                                         trap_EA_MoveForward(bs->client);
07293                                 }
07294                         }
07295                         else
07296                         {
07297                                 trap_EA_MoveForward(bs->client);
07298                         }
07299                         if (g_entities[bs->client].client->ps.groundEntityNum == ENTITYNUM_NONE)
07300                         {
07301                                 g_entities[bs->client].client->ps.pm_flags |= PMF_JUMP_HELD;
07302                         }
07303                 }
07304                 else if (!(bs->cur_ps.pm_flags & PMF_JUMP_HELD))
07305                 {
07306                         trap_EA_Jump(bs->client);
07307                 }
07308         }
07309 
07310         if (bs->duckTime > level.time)
07311         {
07312                 trap_EA_Crouch(bs->client);
07313         }
07314 
07315         if ( bs->dangerousObject && bs->dangerousObject->inuse && bs->dangerousObject->health > 0 &&
07316                 bs->dangerousObject->takedamage && (!bs->frame_Enemy_Vis || !bs->currentEnemy) &&
07317                 (BotGetWeaponRange(bs) == BWEAPONRANGE_MID || BotGetWeaponRange(bs) == BWEAPONRANGE_LONG) &&
07318                 bs->cur_ps.weapon != WP_DET_PACK && bs->cur_ps.weapon != WP_TRIP_MINE &&
07319                 !bs->shootGoal )
07320         {
07321                 float danLen;
07322 
07323                 VectorSubtract(bs->dangerousObject->r.currentOrigin, bs->eye, a);
07324 
07325                 danLen = VectorLength(a);
07326 
07327                 if (danLen > 256)
07328                 {
07329                         vectoangles(a, a);
07330                         VectorCopy(a, bs->goalAngles);
07331 
07332                         if (Q_irand(1, 10) < 5)
07333                         {
07334                                 bs->goalAngles[YAW] += Q_irand(0, 3);
07335                                 bs->goalAngles[PITCH] += Q_irand(0, 3);
07336                         }
07337                         else
07338                         {
07339                                 bs->goalAngles[YAW] -= Q_irand(0, 3);
07340                                 bs->goalAngles[PITCH] -= Q_irand(0, 3);
07341                         }
07342 
07343                         if (InFieldOfVision(bs->viewangles, 30, a) &&
07344                                 EntityVisibleBox(bs->origin, NULL, NULL, bs->dangerousObject->r.currentOrigin, bs->client, bs->dangerousObject->s.number))
07345                         {
07346                                 bs->doAttack = 1;
07347                         }                       
07348                 }
07349         }
07350 
07351         if (PrimFiring(bs) ||
07352                 AltFiring(bs))
07353         {
07354                 friendInLOF = CheckForFriendInLOF(bs);
07355 
07356                 if (friendInLOF)
07357                 {
07358                         if (PrimFiring(bs))
07359                         {
07360                                 KeepPrimFromFiring(bs);
07361                         }
07362                         if (AltFiring(bs))
07363                         {
07364                                 KeepAltFromFiring(bs);
07365                         }
07366                         if (useTheForce && forceHostile)
07367                         {
07368                                 useTheForce = 0;
07369                         }
07370 
07371                         if (!useTheForce && friendInLOF->client)
07372                         { //we have a friend here and are not currently using force powers, see if we can help them out
07373                                 if (friendInLOF->health <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL])
07374                                 {
07375                                         level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_HEAL;
07376                                         useTheForce = 1;
07377                                         forceHostile = 0;
07378                                 }
07379                                 else if (friendInLOF->client->ps.fd.forcePower <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE])
07380                                 {
07381                                         level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_FORCE;
07382                                         useTheForce = 1;
07383                                         forceHostile = 0;
07384                                 }
07385                         }
07386                 }
07387         }
07388         else if (g_gametype.integer >= GT_TEAM)
07389         { //still check for anyone to help..
07390                 friendInLOF = CheckForFriendInLOF(bs);
07391 
07392                 if (!useTheForce && friendInLOF)
07393                 {
07394                         if (friendInLOF->health <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL])
07395                         {
07396                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_HEAL;
07397                                 useTheForce = 1;
07398                                 forceHostile = 0;
07399                         }
07400                         else if (friendInLOF->client->ps.fd.forcePower <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE])
07401                         {
07402                                 level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_FORCE;
07403                                 useTheForce = 1;
07404                                 forceHostile = 0;
07405                         }
07406                 }
07407         }
07408 
07409         if (bs->doAttack && bs->cur_ps.weapon == WP_DET_PACK &&
07410                 bs->cur_ps.hasDetPackPlanted)
07411         { //maybe a bit hackish, but bots only want to plant one of these at any given time to avoid complications
07412                 bs->doAttack = 0;
07413         }
07414 
07415         if (bs->doAttack && bs->cur_ps.weapon == WP_SABER &&
07416                 bs->saberDefending && bs->currentEnemy && bs->currentEnemy->client &&
07417                 BotWeaponBlockable(bs->currentEnemy->client->ps.weapon) )
07418         {
07419                 bs->doAttack = 0;
07420         }
07421 
07422         if (bs->cur_ps.saberLockTime > level.time)
07423         {
07424                 if (rand()%10 < 5)
07425                 {
07426                         bs->doAttack = 1;
07427                 }
07428                 else
07429                 {
07430                         bs->doAttack = 0;
07431                 }
07432         }
07433 
07434         if (bs->botChallengingTime > level.time)
07435         {
07436                 bs->doAttack = 0;
07437                 bs->doAltAttack = 0;
07438         }
07439 
07440         if (bs->cur_ps.weapon == WP_SABER &&
07441                 bs->cur_ps.saberInFlight &&
07442                 !bs->cur_ps.saberEntityNum)
07443         { //saber knocked away, keep trying to get it back
07444                 bs->doAttack = 1;
07445                 bs->doAltAttack = 0;
07446         }
07447 
07448         if (bs->doAttack)
07449         {
07450                 trap_EA_Attack(bs->client);
07451         }
07452         else if (bs->doAltAttack)
07453         {
07454                 trap_EA_Alt_Attack(bs->client);
07455         }
07456 
07457         if (useTheForce && forceHostile && bs->botChallengingTime > level.time)
07458         {
07459                 useTheForce = qfalse;
07460         }
07461 
07462         if (useTheForce)
07463         {
07464 #ifndef FORCEJUMP_INSTANTMETHOD
07465                 if (bs->forceJumpChargeTime > level.time)
07466                 {
07467                         level.clients[bs->client].ps.fd.forcePowerSelected = FP_LEVITATION;
07468                         trap_EA_ForcePower(bs->client);
07469                 }
07470                 else
07471                 {
07472 #endif
07473                         if (bot_forcepowers.integer && !g_forcePowerDisable.integer)
07474                         {
07475                                 trap_EA_ForcePower(bs->client);
07476                         }
07477 #ifndef FORCEJUMP_INSTANTMETHOD
07478                 }
07479 #endif
07480         }
07481 
07482         MoveTowardIdealAngles(bs);
07483 }
07484 
07485 int gUpdateVars = 0;
07486 
07487 /*
07488 ==================
07489 BotAIStartFrame
07490 ==================
07491 */
07492 int BotAIStartFrame(int time) {
07493         int i;
07494         int elapsed_time, thinktime;
07495         static int local_time;
07496         static int botlib_residual;
07497         static int lastbotthink_time;
07498 
07499         if (gUpdateVars < level.time)
07500         {
07501                 trap_Cvar_Update(&bot_pvstype);
07502                 trap_Cvar_Update(&bot_camp);
07503                 trap_Cvar_Update(&bot_attachments);
07504                 trap_Cvar_Update(&bot_forgimmick);
07505                 trap_Cvar_Update(&bot_honorableduelacceptance);
07506 #ifndef FINAL_BUILD
07507                 trap_Cvar_Update(&bot_getinthecarrr);
07508 #endif
07509                 gUpdateVars = level.time + 1000;
07510         }
07511 
07512         G_CheckBotSpawn();
07513 
07514         //rww - addl bot frame functions
07515         if (gBotEdit)
07516         {
07517                 trap_Cvar_Update(&bot_wp_info);
07518                 BotWaypointRender();
07519         }
07520 
07521         UpdateEventTracker();
07522         //end rww
07523 
07524         //cap the bot think time
07525         //if the bot think time changed we should reschedule the bots
07526         if (BOT_THINK_TIME != lastbotthink_time) {
07527                 lastbotthink_time = BOT_THINK_TIME;
07528                 BotScheduleBotThink();
07529         }
07530 
07531         elapsed_time = time - local_time;
07532         local_time = time;
07533 
07534         if (elapsed_time > BOT_THINK_TIME) thinktime = elapsed_time;
07535         else thinktime = BOT_THINK_TIME;
07536 
07537         // execute scheduled bot AI
07538         for( i = 0; i < MAX_CLIENTS; i++ ) {
07539                 if( !botstates[i] || !botstates[i]->inuse ) {
07540                         continue;
07541                 }
07542                 //
07543                 botstates[i]->botthink_residual += elapsed_time;
07544                 //
07545                 if ( botstates[i]->botthink_residual >= thinktime ) {
07546                         botstates[i]->botthink_residual -= thinktime;
07547 
07548                         if (g_entities[i].client->pers.connected == CON_CONNECTED) {
07549                                 BotAI(i, (float) thinktime / 1000);
07550                         }
07551                 }
07552         }
07553 
07554         // execute bot user commands every frame
07555         for( i = 0; i < MAX_CLIENTS; i++ ) {
07556                 if( !botstates[i] || !botstates[i]->inuse ) {
07557                         continue;
07558                 }
07559                 if( g_entities[i].client->pers.connected != CON_CONNECTED ) {
07560                         continue;
07561                 }
07562 
07563                 BotUpdateInput(botstates[i], time, elapsed_time);
07564                 trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd);
07565         }
07566 
07567         return qtrue;
07568 }
07569 
07570 /*
07571 ==============
07572 BotAISetup
07573 ==============
07574 */
07575 int BotAISetup( int restart ) {
07576         //rww - new bot cvars..
07577         trap_Cvar_Register(&bot_forcepowers, "bot_forcepowers", "1", CVAR_CHEAT);
07578         trap_Cvar_Register(&bot_forgimmick, "bot_forgimmick", "0", CVAR_CHEAT);
07579         trap_Cvar_Register(&bot_honorableduelacceptance, "bot_honorableduelacceptance", "0", CVAR_CHEAT);
07580         trap_Cvar_Register(&bot_pvstype, "bot_pvstype", "1", CVAR_CHEAT);
07581 #ifndef FINAL_BUILD
07582         trap_Cvar_Register(&bot_getinthecarrr, "bot_getinthecarrr", "0", 0);
07583 #endif
07584 
07585 #ifdef _DEBUG
07586         trap_Cvar_Register(&bot_nogoals, "bot_nogoals", "0", CVAR_CHEAT);
07587         trap_Cvar_Register(&bot_debugmessages, "bot_debugmessages", "0", CVAR_CHEAT);
07588 #endif
07589 
07590         trap_Cvar_Register(&bot_attachments, "bot_attachments", "1", 0);
07591         trap_Cvar_Register(&bot_camp, "bot_camp", "1", 0);
07592 
07593         trap_Cvar_Register(&bot_wp_info, "bot_wp_info", "1", 0);
07594         trap_Cvar_Register(&bot_wp_edit, "bot_wp_edit", "0", CVAR_CHEAT);
07595         trap_Cvar_Register(&bot_wp_clearweight, "bot_wp_clearweight", "1", 0);
07596         trap_Cvar_Register(&bot_wp_distconnect, "bot_wp_distconnect", "1", 0);
07597         trap_Cvar_Register(&bot_wp_visconnect, "bot_wp_visconnect", "1", 0);
07598 
07599         trap_Cvar_Update(&bot_forcepowers);
07600         //end rww
07601 
07602         //if the game is restarted for a tournament
07603         if (restart) {
07604                 return qtrue;
07605         }
07606 
07607         //initialize the bot states
07608         memset( botstates, 0, sizeof(botstates) );
07609 
07610         if (!trap_BotLibSetup())
07611         {
07612                 return qfalse; //wts?!
07613         }
07614 
07615         return qtrue;
07616 }
07617 
07618 /*
07619 ==============
07620 BotAIShutdown
07621 ==============
07622 */
07623 int BotAIShutdown( int restart ) {
07624 
07625         int i;
07626 
07627         //if the game is restarted for a tournament
07628         if ( restart ) {
07629                 //shutdown all the bots in the botlib
07630                 for (i = 0; i < MAX_CLIENTS; i++) {
07631                         if (botstates[i] && botstates[i]->inuse) {
07632                                 BotAIShutdownClient(botstates[i]->client, restart);
07633                         }
07634                 }
07635                 //don't shutdown the bot library
07636         }
07637         else {
07638                 trap_BotLibShutdown();
07639         }
07640         return qtrue;
07641 }
07642