00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018 #include "g_local.h"
00019 #include "q_shared.h"
00020 #include "botlib.h"
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
00039
00040
00041 #define MAX_PATH 144
00042
00043 #define BOT_THINK_TIME 0
00044
00045
00046 bot_state_t *botstates[MAX_CLIENTS];
00047
00048 int numbots;
00049
00050 float floattime;
00051
00052 float regularupdate_time;
00053
00054
00055
00056 extern int rebel_attackers;
00057 extern int imperial_attackers;
00058
00059 boteventtracker_t gBotEventTracker[MAX_CLIENTS];
00060
00061
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
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
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
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
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
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
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
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
00405
00406
00407 void BotEntityInfo(int entnum, aas_entityinfo_t *info) {
00408 trap_AAS_EntityInfo(entnum, info);
00409 }
00410
00411
00412
00413
00414
00415
00416 int NumBots(void) {
00417 return numbots;
00418 }
00419
00420
00421
00422
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
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
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
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
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
00543 memset(ucmd, 0, sizeof(usercmd_t));
00544
00545
00546
00547 ucmd->serverTime = time;
00548
00549 if (bi->actionflags & ACTION_DELAYEDJUMP) {
00550 bi->actionflags |= ACTION_JUMP;
00551 bi->actionflags &= ~ACTION_DELAYEDJUMP;
00552 }
00553
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
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 {
00566 ucmd->buttons |= BUTTON_USE;
00567 }
00568 #if 0
00569
00570
00571
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
00584 #endif
00585 bi->weapon = WP_BRYAR_PISTOL;
00586 }
00587
00588
00589 ucmd->weapon = bi->weapon;
00590
00591
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
00596 for (j = 0; j < 3; j++) {
00597 temp = ucmd->angles[j] - delta_angles[j];
00598 ucmd->angles[j] = temp;
00599 }
00600
00601
00602
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
00609 bi->speed = bi->speed * 127 / 400;
00610
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
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
00620 if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127;
00621
00622 if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127;
00623
00624
00625
00626 }
00627
00628
00629
00630
00631
00632
00633 void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) {
00634 bot_input_t bi;
00635 int j;
00636
00637
00638 for (j = 0; j < 3; j++) {
00639 bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00640 }
00641
00642 BotChangeViewAngles(bs, (float) elapsed_time / 1000);
00643
00644 trap_EA_GetInput(bs->client, (float) time / 1000, &bi);
00645
00646 if (bi.actionflags & ACTION_RESPAWN) {
00647 if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK);
00648 }
00649
00650 BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time, bs->noUseTime);
00651
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
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
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
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
00714 BotAI_GetClientState( client, &bs->cur_ps );
00715
00716
00717 while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) {
00718
00719 args = strchr( buf, ' ');
00720 if (!args) continue;
00721 *args++ = '\0';
00722
00723
00724 RemoveColorEscapeSequences( args );
00725
00726 if (!Q_stricmp(buf, "cp "))
00727 { }
00728 else if (!Q_stricmp(buf, "cs"))
00729 { }
00730 else if (!Q_stricmp(buf, "scores"))
00731 { }
00732 else if (!Q_stricmp(buf, "clientLevelShot"))
00733 { }
00734 }
00735
00736 for (j = 0; j < 3; j++) {
00737 bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00738 }
00739
00740 bs->ltime += thinktime;
00741
00742 bs->thinktime = thinktime;
00743
00744 VectorCopy(bs->cur_ps.origin, bs->origin);
00745
00746 VectorCopy(bs->cur_ps.origin, bs->eye);
00747 bs->eye[2] += bs->cur_ps.viewheight;
00748
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
00766 for (j = 0; j < 3; j++) {
00767 bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
00768 }
00769
00770 return qtrue;
00771 }
00772
00773
00774
00775
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
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
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));
00823
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;
00837
00838
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
00863 bs->gs = trap_BotAllocGoalState(client);
00864
00865
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
00876 BotScheduleBotThink();
00877
00878 if (PlayersInGame())
00879 {
00880 BotDoChat(bs, "GeneralGreetings", 0);
00881 }
00882
00883 return qtrue;
00884 }
00885
00886
00887
00888
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
00897 return qfalse;
00898 }
00899
00900 trap_BotFreeMoveState(bs->ms);
00901
00902 trap_BotFreeGoalState(bs->gs);
00903
00904 trap_BotFreeWeaponState(bs->ws);
00905
00906
00907 memset(bs, 0, sizeof(bot_state_t));
00908
00909 bs->inuse = qfalse;
00910
00911 numbots--;
00912
00913 return qtrue;
00914 }
00915
00916
00917
00918
00919
00920
00921
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;
00929 float entergame_time;
00930
00931
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
00942 memset(bs, 0, sizeof(bot_state_t));
00943
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
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
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
00980
00981
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
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
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
01051
01052
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;
01080 }
01081
01082 return 0;
01083 }
01084
01085
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
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;
01120
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
01152
01153
01154
01155
01156
01157 int PassWayCheck(bot_state_t *bs, int windex)
01158 {
01159 if (!gWPArray[windex] || !gWPArray[windex]->inuse)
01160 {
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 {
01169 return 1;
01170 }
01171 }
01172
01173 if (bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_FWD))
01174 {
01175 return 0;
01176 }
01177 else if (!bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_BACK))
01178 {
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 {
01186 return 0;
01187 }
01188
01189 return 1;
01190 }
01191
01192
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 {
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 {
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
01262
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
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
01290 if (bs->wpSwitchTime > level.time)
01291 {
01292 return;
01293 }
01294
01295
01296 if (!gWPArray[newwpindex]->neighbornum)
01297 {
01298 return;
01299 }
01300
01301
01302 bestindex = newwpindex;
01303 bestlen = TotalTrailDistance(newwpindex, bs->wpDestination->index, bs);
01304
01305 while (i < gWPArray[newwpindex]->neighbornum)
01306 {
01307 checklen = TotalTrailDistance(gWPArray[newwpindex]->neighbors[i].num, bs->wpDestination->index, bs);
01308
01309 if (checklen < bestlen-64 || bestlen == -1)
01310 {
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 {
01332 bs->wpCurrent = gWPArray[bestindex];
01333 bs->wpSwitchTime = level.time + 3000;
01334
01335 if (fj)
01336 {
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
01352
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 {
01362 bs->duckTime = level.time + 100;
01363 }
01364
01365 #ifndef FORCEJUMP_INSTANTMETHOD
01366 if (bs->wpCurrent->flags & WPFLAG_JUMP)
01367 {
01368 float heightDif = (bs->wpCurrent->origin[2] - bs->origin[2]+16);
01369
01370 if (bs->origin[2]+16 >= bs->wpCurrent->origin[2])
01371 {
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 {
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 {
01392
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 {
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
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
01449
01450
01451
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 {
01465 bs->noUseTime = level.time + 4000;
01466 }
01467
01468 #ifdef FORCEJUMP_INSTANTMETHOD
01469 if ((bs->wpCurrent->flags & WPFLAG_JUMP) && bs->wpCurrent->forceJumpTo)
01470 {
01471
01472 bs->jumpTime = level.time + 100;
01473 }
01474 #else
01475 if ((bs->wpCurrent->flags & WPFLAG_JUMP) && !bs->wpCurrent->forceJumpTo)
01476 {
01477
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 {
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 {
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 {
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
01542
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
01555
01556 int BotTrace_Strafe(bot_state_t *bs, vec3_t traceto)
01557 {
01558 vec3_t playerMins = {-15, -15, -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 {
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 {
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
01637
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;
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
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;
01733 traceto_mod[2] += 31;
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
01753 int PassStandardEnemyChecks(bot_state_t *bs, gentity_t *en)
01754 {
01755 if (!bs || !en)
01756 {
01757 return 0;
01758 }
01759
01760 if (!en->client)
01761 {
01762 return 0;
01763 }
01764
01765 if (en->health < 1)
01766 {
01767 return 0;
01768 }
01769
01770 if (!en->takedamage)
01771 {
01772 return 0;
01773 }
01774
01775 if (bs->doingFallback &&
01776 (gLevelFlags & LEVELFLAG_IGNOREINFALLBACK))
01777 {
01778
01779
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 {
01787 return 0;
01788 }
01789
01790 if (!en->client->pers.connected)
01791 {
01792 return 0;
01793 }
01794
01795 if (!en->s.solid)
01796 {
01797 return 0;
01798 }
01799
01800 if (bs->client == en->s.number)
01801 {
01802 return 0;
01803 }
01804
01805 if (OnSameTeam(&g_entities[bs->client], en))
01806 {
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 {
01814
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 )
01822 {
01823 return 0;
01824 }
01825 }
01826 }
01827
01828 if (en->client->ps.duelInProgress && en->client->ps.duelIndex != bs->client)
01829 {
01830 return 0;
01831 }
01832
01833 if (bs->cur_ps.duelInProgress && en->s.number != bs->cur_ps.duelIndex)
01834 {
01835 return 0;
01836 }
01837
01838 if (g_gametype.integer == GT_JEDIMASTER && !en->client->ps.isJediMaster && !bs->cur_ps.isJediMaster)
01839 {
01840 vec3_t vs;
01841 float vLen = 0;
01842
01843 if (!g_friendlyFire.integer)
01844 {
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
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 {
01874 return;
01875 }
01876
01877 if (attacker->s.number >= MAX_CLIENTS)
01878 {
01879 return;
01880 }
01881
01882 bs_a = botstates[attacker->s.number];
01883
01884 if (bs_a)
01885 {
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
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 {
01928 return;
01929 }
01930
01931 if (!PassStandardEnemyChecks(bs, attacker))
01932 {
01933 return;
01934 }
01935
01936 if (PassLovedOneCheck(bs, attacker))
01937 {
01938 bs->currentEnemy = attacker;
01939 bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
01940 }
01941 }
01942
01943
01944
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 {
01956 minlen = en->client->ps.otherSoundLen;
01957 goto checkStep;
01958 }
01959
01960 if (en && en->client && en->client->ps.footstepTime > level.time)
01961 {
01962 minlen = 256;
01963 goto checkStep;
01964 }
01965
01966 if (gBotEventTracker[en->s.number].eventTime < level.time)
01967 {
01968 return 0;
01969 }
01970
01971 switch(gBotEventTracker[en->s.number].events[gBotEventTracker[en->s.number].eventSequence & (MAX_PS_EVENTS-1)])
01972 {
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 {
02001 minlen /= 4;
02002 }
02003
02004 if (endist <= minlen)
02005 {
02006 return 1;
02007 }
02008
02009 return 0;
02010 }
02011
02012
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 {
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
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
02077
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 {
02090 return 1;
02091 }
02092
02093 i = 0;
02094
02095 if (!botstates[ent->s.number])
02096 {
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 {
02113 return 1;
02114 }
02115 else if (IsTeamplay() && !OnSameTeam(&g_entities[bs->client], &g_entities[loved->client]) && bs->loved[i].level < 2)
02116 {
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
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 {
02150 hasEnemyDist = bs->frame_Enemy_Len;
02151 }
02152
02153 if (bs->currentEnemy && bs->currentEnemy->client &&
02154 bs->currentEnemy->client->ps.isJediMaster)
02155 {
02156 return -1;
02157 }
02158
02159 if (g_gametype.integer == GT_JEDIMASTER)
02160 {
02161 if (G_ThereIsAMaster() && !bs->cur_ps.isJediMaster)
02162 {
02163 if (!g_friendlyFire.integer)
02164 {
02165 noAttackNonJM = qtrue;
02166 }
02167 else
02168 {
02169 closest = 128;
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 {
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 {
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 {
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 {
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;
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
02263
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
02301 int BotIsAChickenWuss(bot_state_t *bs)
02302 {
02303 int bWRange;
02304
02305 if (gLevelFlags & LEVELFLAG_IMUSTNTRUNAWAY)
02306 {
02307 return 0;
02308 }
02309
02310 if (g_gametype.integer == GT_SINGLE_PLAYER)
02311 {
02312 return 0;
02313 }
02314
02315 if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster)
02316 {
02317
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 {
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 {
02333 return 0;
02334 }
02335 }
02336
02337 jmPass:
02338 if (bs->chickenWussCalculationTime > level.time)
02339 {
02340 return 2;
02341 }
02342
02343 if (bs->cur_ps.fd.forcePowersActive & (1 << FP_RAGE))
02344 {
02345 return 0;
02346 }
02347
02348 if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster)
02349 {
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 {
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 {
02366 return 1;
02367 }
02368 }
02369
02370 if (bs->cur_ps.weapon == WP_BRYAR_PISTOL)
02371 {
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 {
02379 return 1;
02380 }
02381
02382 if ((level.time-bs->cur_ps.electrifyTime) < 16000)
02383 {
02384 return 1;
02385 }
02386
02387
02388 bs->chickenWussCalculationTime = 0;
02389
02390 return 0;
02391 }
02392
02393
02394
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;
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
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 {
02425
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 {
02436 bs->doForcePush = level.time + 700;
02437
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 {
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 {
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
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
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
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
02640
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
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 {
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 {
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
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 {
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 {
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 {
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 {
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 {
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
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
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 {
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;
03143 }
03144
03145 if (bs->touchGoal)
03146 {
03147
03148 VectorCopy(dif, bs->goalPosition);
03149 }
03150
03151 return 1;
03152 }
03153
03154 void Siege_DefendFromAttackers(bot_state_t *bs)
03155 {
03156
03157
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
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
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
03255
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 {
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 {
03328
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 {
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 {
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 {
03416 bs->wpDestination = dest_sw;
03417 }
03418
03419 return 1;
03420 }
03421
03422
03423
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
03441
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
03480
03481
03482
03483
03484
03485
03486
03487
03488
03489
03490 bs->wpDestination = gWPArray[wpClose];
03491 bs->destinationGrabTime = level.time + 4000;
03492 }
03493 }
03494
03495 return 1;
03496 }
03497
03498
03499
03500 int BotHasAssociated(bot_state_t *bs, wpobject_t *wp)
03501 {
03502 gentity_t *as;
03503
03504 if (wp->associated_entity == ENTITYNUM_NONE)
03505 {
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)
03546 {
03547 return 1;
03548 }
03549
03550 return 0;
03551 }
03552
03553 return 0;
03554 }
03555
03556
03557
03558
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 {
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
03624
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 {
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);
03757
03758
03759 }
03760 }
03761
03762 return;
03763 }
03764
03765 distChange = 0;
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 {
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 {
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 {
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
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
03910
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;
03918 int guardDefendPriority = 0;
03919 int attackRetrievePriority = 0;
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 {
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 {
04013
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 {
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 {
04068 botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL;
04069 }
04070 }
04071
04072 i++;
04073 }
04074 }
04075
04076
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 {
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
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
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 {
04152 bs->squadLeader = NULL;
04153 bs->isSquadLeader = 0;
04154 }
04155 }
04156
04157
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 {
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 {
04222 if (teammate_indanger >= 0 && !teammate_helped)
04223 {
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 {
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 {
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
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
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
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 {
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 {
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
04547
04548
04549
04550
04551
04552
04553
04554
04555
04556
04557
04558
04559
04560 }
04561 else if (bs->frame_Enemy_Len <= 56)
04562 {
04563 bs->doAttack = 1;
04564 bs->saberDefending = 0;
04565 }
04566 }
04567
04568
04569
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
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
04656
04657 VectorCopy(bs->currentEnemy->client->ps.velocity, movementVector);
04658
04659 VectorNormalize(movementVector);
04660
04661 x = bs->frame_Enemy_Len*leadAmount;
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);
04671 }
04672 else
04673 {
04674 x = (bs->frame_Enemy_Len*0.9)*leadAmount;
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
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 {
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 {
04742 accVal = accVal/bs->revengeHateLevel;
04743 }
04744
04745 if (bs->currentEnemy && bs->frame_Enemy_Vis)
04746 {
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;
04752 }
04753 else
04754 {
04755 accVal += accVal*0.25;
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;
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
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 {
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
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 {
04926 fovcheck = 40;
04927
04928 if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
04929 bs->cur_ps.weapon == WP_ROCKET_LAUNCHER)
04930 {
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
04966 }
04967 else
04968 {
04969 bs->doAttack = 1;
04970
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 {
05016 return 1;
05017 }
05018 }
05019 }
05020 }
05021
05022 return 0;
05023 }
05024
05025
05026
05027
05028
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;
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;
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 {
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
05086
05087 return 1;
05088 }
05089
05090 i++;
05091 }
05092
05093 if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1)
05094 {
05095 bs->virtualWeapon = 1;
05096 BotSelectWeapon(bs->client, 1);
05097
05098
05099 return 1;
05100 }
05101
05102 return 0;
05103 }
05104
05105
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
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 {
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 {
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
05200
05201 if (bestweight != -1 && bs->cur_ps.weapon != bestweapon && bs->virtualWeapon != bestweapon)
05202 {
05203 bs->virtualWeapon = bestweapon;
05204 BotSelectWeapon(bs->client, bestweapon);
05205
05206
05207 return 1;
05208 }
05209
05210
05211
05212 return 0;
05213 }
05214
05215
05216 int BotSelectChoiceWeapon(bot_state_t *bs, int weapon, int doselection)
05217 {
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
05241
05242 return 2;
05243 }
05244
05245 if (hasit)
05246 {
05247 return 1;
05248 }
05249
05250 return 0;
05251 }
05252
05253
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
05261
05262 return 1;
05263 }
05264
05265 return 0;
05266 }
05267
05268
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 {
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
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 {
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 {
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 {
05347 return;
05348 }
05349
05350 if (!bot_attachments.integer)
05351 {
05352 return;
05353 }
05354
05355 if (!PassLovedOneCheck(bs, loved->lastHurt))
05356 {
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
05372
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 {
05381
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 {
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
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
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 {
05461 bs->meleeStrafeDisable = level.time + Q_irand(500, 1500);
05462 }
05463 }
05464
05465
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
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
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
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
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 {
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 {
05608 bs->squadLeader = ent;
05609 break;
05610 }
05611 }
05612
05613 i++;
05614 }
05615 }
05616
05617
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 {
05639 return;
05640 }
05641
05642 i++;
05643 }
05644 }
05645
05646
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->