1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 /*****************************************************************************
25 * name: ai_main.c
26 *
27 * desc: Quake3 bot AI
28 *
29 * $Archive: /MissionPack/code/game/ai_main.c $
30 * $Author: osman $
31 * $Revision: 1.5 $
32 * $Modtime: 6/06/01 1:11p $
33 * $Date: 2003/03/15 23:43:59 $
34 *
35 *****************************************************************************/
36
37
38 #include "g_local.h"
39 #include "qcommon/q_shared.h"
40 #include "botlib/botlib.h" //bot lib interface
41 #include "botlib/be_aas.h"
42 #include "botlib/be_ea.h"
43 #include "botlib/be_ai_char.h"
44 #include "botlib/be_ai_chat.h"
45 #include "botlib/be_ai_gen.h"
46 #include "botlib/be_ai_goal.h"
47 #include "botlib/be_ai_move.h"
48 #include "botlib/be_ai_weap.h"
49 //
50 #include "ai_main.h"
51 #include "w_saber.h"
52 //
53 #include "chars.h"
54 #include "inv.h"
55
56 /*
57 #define BOT_CTF_DEBUG 1
58 */
59
60 #define BOT_THINK_TIME 0
61
62 //bot states
63 bot_state_t *botstates[MAX_CLIENTS];
64 //number of bots
65 int numbots;
66 //floating point time
67 float floattime;
68 //time to do a regular update
69 float regularupdate_time;
70 //
71
72 //for siege:
73 extern int rebel_attackers;
74 extern int imperial_attackers;
75
76 boteventtracker_t gBotEventTracker[MAX_CLIENTS];
77
78 //rww - new bot cvars..
79 vmCvar_t bot_forcepowers;
80 vmCvar_t bot_forgimmick;
81 vmCvar_t bot_honorableduelacceptance;
82 vmCvar_t bot_pvstype;
83 vmCvar_t bot_normgpath;
84 #ifndef FINAL_BUILD
85 vmCvar_t bot_getinthecarrr;
86 #endif
87
88 #ifdef _DEBUG
89 vmCvar_t bot_nogoals;
90 vmCvar_t bot_debugmessages;
91 #endif
92
93 vmCvar_t bot_attachments;
94 vmCvar_t bot_camp;
95
96 vmCvar_t bot_wp_info;
97 vmCvar_t bot_wp_edit;
98 vmCvar_t bot_wp_clearweight;
99 vmCvar_t bot_wp_distconnect;
100 vmCvar_t bot_wp_visconnect;
101 //end rww
102
103 wpobject_t *flagRed;
104 wpobject_t *oFlagRed;
105 wpobject_t *flagBlue;
106 wpobject_t *oFlagBlue;
107
108 gentity_t *eFlagRed;
109 gentity_t *droppedRedFlag;
110 gentity_t *eFlagBlue;
111 gentity_t *droppedBlueFlag;
112
113 char *ctfStateNames[] = {
114 "CTFSTATE_NONE",
115 "CTFSTATE_ATTACKER",
116 "CTFSTATE_DEFENDER",
117 "CTFSTATE_RETRIEVAL",
118 "CTFSTATE_GUARDCARRIER",
119 "CTFSTATE_GETFLAGHOME",
120 "CTFSTATE_MAXCTFSTATES"
121 };
122
123 char *ctfStateDescriptions[] = {
124 "I'm not occupied",
125 "I'm attacking the enemy's base",
126 "I'm defending our base",
127 "I'm getting our flag back",
128 "I'm escorting our flag carrier",
129 "I've got the enemy's flag"
130 };
131
132 char *siegeStateDescriptions[] = {
133 "I'm not occupied",
134 "I'm attempting to complete the current objective",
135 "I'm preventing the enemy from completing their objective"
136 };
137
138 char *teamplayStateDescriptions[] = {
139 "I'm not occupied",
140 "I'm following my squad commander",
141 "I'm assisting my commanding",
142 "I'm attempting to regroup and form a new squad"
143 };
144
BotStraightTPOrderCheck(gentity_t * ent,int ordernum,bot_state_t * bs)145 void BotStraightTPOrderCheck(gentity_t *ent, int ordernum, bot_state_t *bs)
146 {
147 switch (ordernum)
148 {
149 case 0:
150 if (bs->squadLeader == ent)
151 {
152 bs->teamplayState = 0;
153 bs->squadLeader = NULL;
154 }
155 break;
156 case TEAMPLAYSTATE_FOLLOWING:
157 bs->teamplayState = ordernum;
158 bs->isSquadLeader = 0;
159 bs->squadLeader = ent;
160 bs->wpDestSwitchTime = 0;
161 break;
162 case TEAMPLAYSTATE_ASSISTING:
163 bs->teamplayState = ordernum;
164 bs->isSquadLeader = 0;
165 bs->squadLeader = ent;
166 bs->wpDestSwitchTime = 0;
167 break;
168 default:
169 bs->teamplayState = ordernum;
170 break;
171 }
172 }
173
BotSelectWeapon(int client,int weapon)174 void BotSelectWeapon(int client, int weapon)
175 {
176 if (weapon <= WP_NONE)
177 {
178 // assert(0);
179 return;
180 }
181 trap->EA_SelectWeapon(client, weapon);
182 }
183
BotReportStatus(bot_state_t * bs)184 void BotReportStatus(bot_state_t *bs)
185 {
186 if (level.gametype == GT_TEAM)
187 {
188 trap->EA_SayTeam(bs->client, teamplayStateDescriptions[bs->teamplayState]);
189 }
190 else if (level.gametype == GT_SIEGE)
191 {
192 trap->EA_SayTeam(bs->client, siegeStateDescriptions[bs->siegeState]);
193 }
194 else if (level.gametype == GT_CTF || level.gametype == GT_CTY)
195 {
196 trap->EA_SayTeam(bs->client, ctfStateDescriptions[bs->ctfState]);
197 }
198 }
199
200 //accept a team order from a player
BotOrder(gentity_t * ent,int clientnum,int ordernum)201 void BotOrder(gentity_t *ent, int clientnum, int ordernum)
202 {
203 int stateMin = 0;
204 int stateMax = 0;
205 int i = 0;
206
207 if (!ent || !ent->client || !ent->client->sess.teamLeader)
208 {
209 return;
210 }
211
212 if (clientnum != -1 && !botstates[clientnum])
213 {
214 return;
215 }
216
217 if (clientnum != -1 && !OnSameTeam(ent, &g_entities[clientnum]))
218 {
219 return;
220 }
221
222 if (level.gametype != GT_CTF && level.gametype != GT_CTY && level.gametype != GT_SIEGE && level.gametype != GT_TEAM)
223 {
224 return;
225 }
226
227 if (level.gametype == GT_CTF || level.gametype == GT_CTY)
228 {
229 stateMin = CTFSTATE_NONE;
230 stateMax = CTFSTATE_MAXCTFSTATES;
231 }
232 else if (level.gametype == GT_SIEGE)
233 {
234 stateMin = SIEGESTATE_NONE;
235 stateMax = SIEGESTATE_MAXSIEGESTATES;
236 }
237 else if (level.gametype == GT_TEAM)
238 {
239 stateMin = TEAMPLAYSTATE_NONE;
240 stateMax = TEAMPLAYSTATE_MAXTPSTATES;
241 }
242
243 if ((ordernum < stateMin && ordernum != -1) || ordernum >= stateMax)
244 {
245 return;
246 }
247
248 if (clientnum != -1)
249 {
250 if (ordernum == -1)
251 {
252 BotReportStatus(botstates[clientnum]);
253 }
254 else
255 {
256 BotStraightTPOrderCheck(ent, ordernum, botstates[clientnum]);
257 botstates[clientnum]->state_Forced = ordernum;
258 botstates[clientnum]->chatObject = ent;
259 botstates[clientnum]->chatAltObject = NULL;
260 if (BotDoChat(botstates[clientnum], "OrderAccepted", 1))
261 {
262 botstates[clientnum]->chatTeam = 1;
263 }
264 }
265 }
266 else
267 {
268 while (i < MAX_CLIENTS)
269 {
270 if (botstates[i] && OnSameTeam(ent, &g_entities[i]))
271 {
272 if (ordernum == -1)
273 {
274 BotReportStatus(botstates[i]);
275 }
276 else
277 {
278 BotStraightTPOrderCheck(ent, ordernum, botstates[i]);
279 botstates[i]->state_Forced = ordernum;
280 botstates[i]->chatObject = ent;
281 botstates[i]->chatAltObject = NULL;
282 if (BotDoChat(botstates[i], "OrderAccepted", 0))
283 {
284 botstates[i]->chatTeam = 1;
285 }
286 }
287 }
288
289 i++;
290 }
291 }
292 }
293
294 //See if bot is mindtricked by the client in question
BotMindTricked(int botClient,int enemyClient)295 int BotMindTricked(int botClient, int enemyClient)
296 {
297 forcedata_t *fd;
298
299 if (!g_entities[enemyClient].client)
300 {
301 return 0;
302 }
303
304 fd = &g_entities[enemyClient].client->ps.fd;
305
306 if (!fd)
307 {
308 return 0;
309 }
310
311 if (botClient > 47)
312 {
313 if (fd->forceMindtrickTargetIndex4 & (1 << (botClient-48)))
314 {
315 return 1;
316 }
317 }
318 else if (botClient > 31)
319 {
320 if (fd->forceMindtrickTargetIndex3 & (1 << (botClient-32)))
321 {
322 return 1;
323 }
324 }
325 else if (botClient > 15)
326 {
327 if (fd->forceMindtrickTargetIndex2 & (1 << (botClient-16)))
328 {
329 return 1;
330 }
331 }
332 else
333 {
334 if (fd->forceMindtrickTargetIndex & (1 << botClient))
335 {
336 return 1;
337 }
338 }
339
340 return 0;
341 }
342
343 int BotGetWeaponRange(bot_state_t *bs);
344 int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent);
345
346 void ExitLevel( void );
347
BotAI_Print(int type,char * fmt,...)348 void QDECL BotAI_Print(int type, char *fmt, ...) { return; }
349
350 qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower );
351
IsTeamplay(void)352 int IsTeamplay(void)
353 {
354 if ( level.gametype < GT_TEAM )
355 {
356 return 0;
357 }
358
359 return 1;
360 }
361
362 /*
363 ==================
364 BotAI_GetClientState
365 ==================
366 */
BotAI_GetClientState(int clientNum,playerState_t * state)367 int BotAI_GetClientState( int clientNum, playerState_t *state ) {
368 gentity_t *ent;
369
370 ent = &g_entities[clientNum];
371 if ( !ent->inuse ) {
372 return qfalse;
373 }
374 if ( !ent->client ) {
375 return qfalse;
376 }
377
378 memcpy( state, &ent->client->ps, sizeof(playerState_t) );
379 return qtrue;
380 }
381
382 /*
383 ==================
384 BotAI_GetEntityState
385 ==================
386 */
BotAI_GetEntityState(int entityNum,entityState_t * state)387 int BotAI_GetEntityState( int entityNum, entityState_t *state ) {
388 gentity_t *ent;
389
390 ent = &g_entities[entityNum];
391 memset( state, 0, sizeof(entityState_t) );
392 if (!ent->inuse) return qfalse;
393 if (!ent->r.linked) return qfalse;
394 if (ent->r.svFlags & SVF_NOCLIENT) return qfalse;
395 memcpy( state, &ent->s, sizeof(entityState_t) );
396 return qtrue;
397 }
398
399 /*
400 ==================
401 BotAI_GetSnapshotEntity
402 ==================
403 */
BotAI_GetSnapshotEntity(int clientNum,int sequence,entityState_t * state)404 int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) {
405 int entNum;
406
407 entNum = trap->BotGetSnapshotEntity( clientNum, sequence );
408 if ( entNum == -1 ) {
409 memset(state, 0, sizeof(entityState_t));
410 return -1;
411 }
412
413 BotAI_GetEntityState( entNum, state );
414
415 return sequence + 1;
416 }
417
418 /*
419 ==============
420 BotEntityInfo
421 ==============
422 */
BotEntityInfo(int entnum,aas_entityinfo_t * info)423 void BotEntityInfo(int entnum, aas_entityinfo_t *info) {
424 trap->AAS_EntityInfo(entnum, info);
425 }
426
427 /*
428 ==============
429 NumBots
430 ==============
431 */
NumBots(void)432 int NumBots(void) {
433 return numbots;
434 }
435
436 /*
437 ==============
438 AngleDifference
439 ==============
440 */
AngleDifference(float ang1,float ang2)441 float AngleDifference(float ang1, float ang2) {
442 float diff;
443
444 diff = ang1 - ang2;
445 if (ang1 > ang2) {
446 if (diff > 180.0) diff -= 360.0;
447 }
448 else {
449 if (diff < -180.0) diff += 360.0;
450 }
451 return diff;
452 }
453
454 /*
455 ==============
456 BotChangeViewAngle
457 ==============
458 */
BotChangeViewAngle(float angle,float ideal_angle,float speed)459 float BotChangeViewAngle(float angle, float ideal_angle, float speed) {
460 float move;
461
462 angle = AngleMod(angle);
463 ideal_angle = AngleMod(ideal_angle);
464 if (angle == ideal_angle) return angle;
465 move = ideal_angle - angle;
466 if (ideal_angle > angle) {
467 if (move > 180.0) move -= 360.0;
468 }
469 else {
470 if (move < -180.0) move += 360.0;
471 }
472 if (move > 0) {
473 if (move > speed) move = speed;
474 }
475 else {
476 if (move < -speed) move = -speed;
477 }
478 return AngleMod(angle + move);
479 }
480
481 /*
482 ==============
483 BotChangeViewAngles
484 ==============
485 */
BotChangeViewAngles(bot_state_t * bs,float thinktime)486 void BotChangeViewAngles(bot_state_t *bs, float thinktime) {
487 float diff, factor, maxchange, anglespeed, disired_speed;
488 int i;
489
490 if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
491
492 if (bs->currentEnemy && bs->frame_Enemy_Vis)
493 {
494 if (bs->settings.skill <= 1)
495 {
496 factor = (bs->skills.turnspeed_combat*0.4f)*bs->settings.skill;
497 }
498 else if (bs->settings.skill <= 2)
499 {
500 factor = (bs->skills.turnspeed_combat*0.6f)*bs->settings.skill;
501 }
502 else if (bs->settings.skill <= 3)
503 {
504 factor = (bs->skills.turnspeed_combat*0.8f)*bs->settings.skill;
505 }
506 else
507 {
508 factor = bs->skills.turnspeed_combat*bs->settings.skill;
509 }
510 }
511 else
512 {
513 factor = bs->skills.turnspeed;
514 }
515
516 if (factor > 1)
517 {
518 factor = 1;
519 }
520 if (factor < 0.001)
521 {
522 factor = 0.001f;
523 }
524
525 maxchange = bs->skills.maxturn;
526
527 //if (maxchange < 240) maxchange = 240;
528 maxchange *= thinktime;
529 for (i = 0; i < 2; i++) {
530 bs->viewangles[i] = AngleMod(bs->viewangles[i]);
531 bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]);
532 diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]);
533 disired_speed = diff * factor;
534 bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed);
535 if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange;
536 if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange;
537 anglespeed = bs->viewanglespeed[i];
538 if (anglespeed > maxchange) anglespeed = maxchange;
539 if (anglespeed < -maxchange) anglespeed = -maxchange;
540 bs->viewangles[i] += anglespeed;
541 bs->viewangles[i] = AngleMod(bs->viewangles[i]);
542 bs->viewanglespeed[i] *= 0.45 * (1 - factor);
543 }
544 if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360;
545 trap->EA_View(bs->client, bs->viewangles);
546 }
547
548 /*
549 ==============
550 BotInputToUserCommand
551 ==============
552 */
BotInputToUserCommand(bot_input_t * bi,usercmd_t * ucmd,int delta_angles[3],int time,int useTime)553 void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time, int useTime) {
554 vec3_t angles, forward, right;
555 short temp;
556 int j;
557 float f, r, u, m;
558
559 //clear the whole structure
560 memset(ucmd, 0, sizeof(usercmd_t));
561 //the duration for the user command in milli seconds
562 ucmd->serverTime = time;
563 //
564 if (bi->actionflags & ACTION_DELAYEDJUMP) {
565 bi->actionflags |= ACTION_JUMP;
566 bi->actionflags &= ~ACTION_DELAYEDJUMP;
567 }
568 //set the buttons
569 if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK;
570 if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK;
571 if (bi->actionflags & ACTION_ALT_ATTACK) ucmd->buttons |= BUTTON_ALT_ATTACK;
572 // if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK;
573 if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE;
574 if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE;
575 if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING;
576
577 if (bi->actionflags & ACTION_FORCEPOWER) ucmd->buttons |= BUTTON_FORCEPOWER;
578
579 if (useTime < level.time && Q_irand(1, 10) < 5)
580 { //for now just hit use randomly in case there's something useable around
581 ucmd->buttons |= BUTTON_USE;
582 }
583 #if 0
584 // Here's an interesting bit. The bots in TA used buttons to do additional gestures.
585 // I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2.
586 // We can always add some back in if we want though.
587 if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE;
588 if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE;
589 if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG;
590 if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE;
591 if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL;
592 if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME;
593 #endif //0
594
595 if (bi->weapon == WP_NONE)
596 {
597 #ifdef _DEBUG
598 // Com_Printf("WARNING: Bot tried to use WP_NONE!\n");
599 #endif
600 bi->weapon = WP_BRYAR_PISTOL;
601 }
602
603 //
604 ucmd->weapon = bi->weapon;
605 //set the view angles
606 //NOTE: the ucmd->angles are the angles WITHOUT the delta angles
607 ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]);
608 ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]);
609 ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]);
610 //subtract the delta angles
611 for (j = 0; j < 3; j++) {
612 temp = ucmd->angles[j] - delta_angles[j];
613 ucmd->angles[j] = temp;
614 }
615 //NOTE: movement is relative to the REAL view angles
616 //get the horizontal forward and right vector
617 //get the pitch in the range [-180, 180]
618 if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH];
619 else angles[PITCH] = 0;
620 angles[YAW] = bi->viewangles[YAW];
621 angles[ROLL] = 0;
622 AngleVectors(angles, forward, right, NULL);
623 //bot input speed is in the range [0, 400]
624 bi->speed = bi->speed * 127 / 400;
625 //set the view independent movement
626 f = DotProduct(forward, bi->dir);
627 r = DotProduct(right, bi->dir);
628 u = fabs(forward[2]) * bi->dir[2];
629 m = fabs(f);
630
631 if (fabs(r) > m) {
632 m = fabs(r);
633 }
634
635 if (fabs(u) > m) {
636 m = fabs(u);
637 }
638
639 if (m > 0) {
640 f *= bi->speed / m;
641 r *= bi->speed / m;
642 u *= bi->speed / m;
643 }
644
645 ucmd->forwardmove = f;
646 ucmd->rightmove = r;
647 ucmd->upmove = u;
648 //normal keyboard movement
649 if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove = 127;
650 if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove = -127;
651 if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove = -127;
652 if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove = 127;
653 //jump/moveup
654 if (bi->actionflags & ACTION_JUMP) ucmd->upmove = 127;
655 //crouch/movedown
656 if (bi->actionflags & ACTION_CROUCH) ucmd->upmove = -127;
657 }
658
659 /*
660 ==============
661 BotUpdateInput
662 ==============
663 */
BotUpdateInput(bot_state_t * bs,int time,int elapsed_time)664 void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) {
665 bot_input_t bi;
666 int j;
667
668 //add the delta angles to the bot's current view angles
669 for (j = 0; j < 3; j++) {
670 bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
671 }
672 //change the bot view angles
673 BotChangeViewAngles(bs, (float) elapsed_time / 1000);
674 //retrieve the bot input
675 trap->EA_GetInput(bs->client, (float) time / 1000, &bi);
676 //respawn hack
677 if (bi.actionflags & ACTION_RESPAWN) {
678 if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK);
679 }
680 //convert the bot input to a usercmd
681 BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time, bs->noUseTime);
682 //subtract the delta angles
683 for (j = 0; j < 3; j++) {
684 bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
685 }
686 }
687
688 /*
689 ==============
690 BotAIRegularUpdate
691 ==============
692 */
BotAIRegularUpdate(void)693 void BotAIRegularUpdate(void) {
694 if (regularupdate_time < FloatTime()) {
695 trap->BotUpdateEntityItems();
696 regularupdate_time = FloatTime() + 0.3;
697 }
698 }
699
700 /*
701 ==============
702 RemoveColorEscapeSequences
703 ==============
704 */
RemoveColorEscapeSequences(char * text)705 void RemoveColorEscapeSequences( char *text ) {
706 int i, l;
707
708 l = 0;
709 for ( i = 0; text[i]; i++ ) {
710 if (Q_IsColorStringExt(&text[i])) {
711 i++;
712 continue;
713 }
714 if (text[i] > 0x7E)
715 continue;
716 text[l++] = text[i];
717 }
718 text[l] = '\0';
719 }
720
721
722 /*
723 ==============
724 BotAI
725 ==============
726 */
BotAI(int client,float thinktime)727 int BotAI(int client, float thinktime) {
728 bot_state_t *bs;
729 char buf[1024], *args;
730 int j;
731 #ifdef _DEBUG
732 int start = 0;
733 int end = 0;
734 #endif
735
736 trap->EA_ResetInput(client);
737 //
738 bs = botstates[client];
739 if (!bs || !bs->inuse) {
740 BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client);
741 return qfalse;
742 }
743
744 //retrieve the current client state
745 BotAI_GetClientState( client, &bs->cur_ps );
746
747 //retrieve any waiting server commands
748 while( trap->BotGetServerCommand(client, buf, sizeof(buf)) ) {
749 //have buf point to the command and args to the command arguments
750 args = strchr( buf, ' ');
751 if (!args) continue;
752 *args++ = '\0';
753
754 //remove color espace sequences from the arguments
755 RemoveColorEscapeSequences( args );
756
757 if (!Q_stricmp(buf, "cp "))
758 { /*CenterPrintf*/ }
759 else if (!Q_stricmp(buf, "cs"))
760 { /*ConfigStringModified*/ }
761 else if (!Q_stricmp(buf, "scores"))
762 { /*FIXME: parse scores?*/ }
763 else if (!Q_stricmp(buf, "clientLevelShot"))
764 { /*ignore*/ }
765 }
766 //add the delta angles to the bot's current view angles
767 for (j = 0; j < 3; j++) {
768 bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
769 }
770 //increase the local time of the bot
771 bs->ltime += thinktime;
772 //
773 bs->thinktime = thinktime;
774 //origin of the bot
775 VectorCopy(bs->cur_ps.origin, bs->origin);
776 //eye coordinates of the bot
777 VectorCopy(bs->cur_ps.origin, bs->eye);
778 bs->eye[2] += bs->cur_ps.viewheight;
779 //get the area the bot is in
780
781 #ifdef _DEBUG
782 start = trap->Milliseconds();
783 #endif
784 StandardBotAI(bs, thinktime);
785 #ifdef _DEBUG
786 end = trap->Milliseconds();
787
788 trap->Cvar_Update(&bot_debugmessages);
789
790 if (bot_debugmessages.integer)
791 {
792 Com_Printf("Single AI frametime: %i\n", (end - start));
793 }
794 #endif
795
796 //subtract the delta angles
797 for (j = 0; j < 3; j++) {
798 bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j]));
799 }
800 //everything was ok
801 return qtrue;
802 }
803
804 /*
805 ==================
806 BotScheduleBotThink
807 ==================
808 */
BotScheduleBotThink(void)809 void BotScheduleBotThink(void) {
810 int i, botnum;
811
812 botnum = 0;
813
814 for( i = 0; i < MAX_CLIENTS; i++ ) {
815 if( !botstates[i] || !botstates[i]->inuse ) {
816 continue;
817 }
818 //initialize the bot think residual time
819 botstates[i]->botthink_residual = BOT_THINK_TIME * botnum / numbots;
820 botnum++;
821 }
822 }
823
PlayersInGame(void)824 int PlayersInGame(void)
825 {
826 int i = 0;
827 gentity_t *ent;
828 int pl = 0;
829
830 while (i < MAX_CLIENTS)
831 {
832 ent = &g_entities[i];
833
834 if (ent && ent->client && ent->client->pers.connected == CON_CONNECTED)
835 {
836 pl++;
837 }
838
839 i++;
840 }
841
842 return pl;
843 }
844
845 /*
846 ==============
847 BotAISetupClient
848 ==============
849 */
BotAISetupClient(int client,struct bot_settings_s * settings,qboolean restart)850 int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) {
851 bot_state_t *bs;
852
853 if (!botstates[client]) botstates[client] = (bot_state_t *) B_Alloc(sizeof(bot_state_t)); //G_Alloc(sizeof(bot_state_t));
854 //rww - G_Alloc bad! B_Alloc good.
855
856 memset(botstates[client], 0, sizeof(bot_state_t));
857
858 bs = botstates[client];
859
860 if (bs && bs->inuse) {
861 BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client);
862 return qfalse;
863 }
864
865 memcpy(&bs->settings, settings, sizeof(bot_settings_t));
866
867 bs->client = client; //need to know the client number before doing personality stuff
868
869 //initialize weapon weight defaults..
870 bs->botWeaponWeights[WP_NONE] = 0;
871 bs->botWeaponWeights[WP_STUN_BATON] = 1;
872 bs->botWeaponWeights[WP_SABER] = 10;
873 bs->botWeaponWeights[WP_BRYAR_PISTOL] = 11;
874 bs->botWeaponWeights[WP_BLASTER] = 12;
875 bs->botWeaponWeights[WP_DISRUPTOR] = 13;
876 bs->botWeaponWeights[WP_BOWCASTER] = 14;
877 bs->botWeaponWeights[WP_REPEATER] = 15;
878 bs->botWeaponWeights[WP_DEMP2] = 16;
879 bs->botWeaponWeights[WP_FLECHETTE] = 17;
880 bs->botWeaponWeights[WP_ROCKET_LAUNCHER] = 18;
881 bs->botWeaponWeights[WP_THERMAL] = 14;
882 bs->botWeaponWeights[WP_TRIP_MINE] = 0;
883 bs->botWeaponWeights[WP_DET_PACK] = 0;
884 bs->botWeaponWeights[WP_MELEE] = 1;
885
886 BotUtilizePersonality(bs);
887
888 if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
889 {
890 bs->botWeaponWeights[WP_SABER] = 13;
891 }
892
893 //allocate a goal state
894 bs->gs = trap->BotAllocGoalState(client);
895
896 //allocate a weapon state
897 bs->ws = trap->BotAllocWeaponState();
898
899 bs->inuse = qtrue;
900 bs->entitynum = client;
901 bs->setupcount = 4;
902 bs->entergame_time = FloatTime();
903 bs->ms = trap->BotAllocMoveState();
904 numbots++;
905
906 //NOTE: reschedule the bot thinking
907 BotScheduleBotThink();
908
909 if (PlayersInGame())
910 { //don't talk to yourself
911 BotDoChat(bs, "GeneralGreetings", 0);
912 }
913
914 return qtrue;
915 }
916
917 /*
918 ==============
919 BotAIShutdownClient
920 ==============
921 */
BotAIShutdownClient(int client,qboolean restart)922 int BotAIShutdownClient(int client, qboolean restart) {
923 bot_state_t *bs;
924
925 bs = botstates[client];
926 if (!bs || !bs->inuse) {
927 //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client);
928 return qfalse;
929 }
930
931 trap->BotFreeMoveState(bs->ms);
932 //free the goal state`
933 trap->BotFreeGoalState(bs->gs);
934 //free the weapon weights
935 trap->BotFreeWeaponState(bs->ws);
936 //
937 //clear the bot state
938 memset(bs, 0, sizeof(bot_state_t));
939 //set the inuse flag to qfalse
940 bs->inuse = qfalse;
941 //there's one bot less
942 numbots--;
943 //everything went ok
944 return qtrue;
945 }
946
947 /*
948 ==============
949 BotResetState
950
951 called when a bot enters the intermission or observer mode and
952 when the level is changed
953 ==============
954 */
BotResetState(bot_state_t * bs)955 void BotResetState(bot_state_t *bs) {
956 int client, entitynum, inuse;
957 int movestate, goalstate, weaponstate;
958 bot_settings_t settings;
959 playerState_t ps; //current player state
960 float entergame_time;
961
962 //save some things that should not be reset here
963 memcpy(&settings, &bs->settings, sizeof(bot_settings_t));
964 memcpy(&ps, &bs->cur_ps, sizeof(playerState_t));
965 inuse = bs->inuse;
966 client = bs->client;
967 entitynum = bs->entitynum;
968 movestate = bs->ms;
969 goalstate = bs->gs;
970 weaponstate = bs->ws;
971 entergame_time = bs->entergame_time;
972 //reset the whole state
973 memset(bs, 0, sizeof(bot_state_t));
974 //copy back some state stuff that should not be reset
975 bs->ms = movestate;
976 bs->gs = goalstate;
977 bs->ws = weaponstate;
978 memcpy(&bs->cur_ps, &ps, sizeof(playerState_t));
979 memcpy(&bs->settings, &settings, sizeof(bot_settings_t));
980 bs->inuse = inuse;
981 bs->client = client;
982 bs->entitynum = entitynum;
983 bs->entergame_time = entergame_time;
984 //reset several states
985 if (bs->ms) trap->BotResetMoveState(bs->ms);
986 if (bs->gs) trap->BotResetGoalState(bs->gs);
987 if (bs->ws) trap->BotResetWeaponState(bs->ws);
988 if (bs->gs) trap->BotResetAvoidGoals(bs->gs);
989 if (bs->ms) trap->BotResetAvoidReach(bs->ms);
990 }
991
992 /*
993 ==============
994 BotAILoadMap
995 ==============
996 */
BotAILoadMap(int restart)997 int BotAILoadMap( int restart ) {
998 int i;
999
1000 for (i = 0; i < MAX_CLIENTS; i++) {
1001 if (botstates[i] && botstates[i]->inuse) {
1002 BotResetState( botstates[i] );
1003 botstates[i]->setupcount = 4;
1004 }
1005 }
1006
1007 return qtrue;
1008 }
1009
1010 //rww - bot ai
1011
1012 //standard visibility check
OrgVisible(vec3_t org1,vec3_t org2,int ignore)1013 int OrgVisible(vec3_t org1, vec3_t org2, int ignore)
1014 {
1015 trace_t tr;
1016
1017 trap->Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID, qfalse, 0, 0 );
1018
1019 if (tr.fraction == 1)
1020 {
1021 return 1;
1022 }
1023
1024 return 0;
1025 }
1026
1027 //special waypoint visibility check
WPOrgVisible(gentity_t * bot,vec3_t org1,vec3_t org2,int ignore)1028 int WPOrgVisible(gentity_t *bot, vec3_t org1, vec3_t org2, int ignore)
1029 {
1030 trace_t tr;
1031 gentity_t *ownent;
1032
1033 trap->Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID, qfalse, 0, 0);
1034
1035 if (tr.fraction == 1)
1036 {
1037 trap->Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_PLAYERSOLID, qfalse, 0, 0);
1038
1039 if (tr.fraction != 1 && tr.entityNum != ENTITYNUM_NONE && g_entities[tr.entityNum].s.eType == ET_SPECIAL)
1040 {
1041 if (g_entities[tr.entityNum].parent && g_entities[tr.entityNum].parent->client)
1042 {
1043 ownent = g_entities[tr.entityNum].parent;
1044
1045 if (OnSameTeam(bot, ownent) || bot->s.number == ownent->s.number)
1046 {
1047 return 1;
1048 }
1049 }
1050 return 2;
1051 }
1052
1053 return 1;
1054 }
1055
1056 return 0;
1057 }
1058
1059 //visibility check with hull trace
OrgVisibleBox(vec3_t org1,vec3_t mins,vec3_t maxs,vec3_t org2,int ignore)1060 int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore)
1061 {
1062 trace_t tr;
1063
1064 if (RMG.integer)
1065 {
1066 trap->Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID, qfalse, 0, 0);
1067 }
1068 else
1069 {
1070 trap->Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID, qfalse, 0, 0);
1071 }
1072
1073 if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
1074 {
1075 return 1;
1076 }
1077
1078 return 0;
1079 }
1080
1081 //see if there's a func_* ent under the given pos.
1082 //kind of badly done, but this shouldn't happen
1083 //often.
CheckForFunc(vec3_t org,int ignore)1084 int CheckForFunc(vec3_t org, int ignore)
1085 {
1086 gentity_t *fent;
1087 vec3_t under;
1088 trace_t tr;
1089
1090 VectorCopy(org, under);
1091
1092 under[2] -= 64;
1093
1094 trap->Trace(&tr, org, NULL, NULL, under, ignore, MASK_SOLID, qfalse, 0, 0);
1095
1096 if (tr.fraction == 1)
1097 {
1098 return 0;
1099 }
1100
1101 fent = &g_entities[tr.entityNum];
1102
1103 if (!fent)
1104 {
1105 return 0;
1106 }
1107
1108 if (strstr(fent->classname, "func_"))
1109 {
1110 return 1; //there's a func brush here
1111 }
1112
1113 return 0;
1114 }
1115
1116 //perform pvs check based on rmg or not
BotPVSCheck(const vec3_t p1,const vec3_t p2)1117 qboolean BotPVSCheck( const vec3_t p1, const vec3_t p2 )
1118 {
1119 if (RMG.integer && bot_pvstype.integer)
1120 {
1121 vec3_t subPoint;
1122 VectorSubtract(p1, p2, subPoint);
1123
1124 if (VectorLength(subPoint) > 5000)
1125 {
1126 return qfalse;
1127 }
1128 return qtrue;
1129 }
1130
1131 return trap->InPVS(p1, p2);
1132 }
1133
1134 //get the index to the nearest visible waypoint in the global trail
GetNearestVisibleWP(vec3_t org,int ignore)1135 int GetNearestVisibleWP(vec3_t org, int ignore)
1136 {
1137 int i;
1138 float bestdist;
1139 float flLen;
1140 int bestindex;
1141 vec3_t a, mins, maxs;
1142
1143 i = 0;
1144 if (RMG.integer)
1145 {
1146 bestdist = 300;
1147 }
1148 else
1149 {
1150 bestdist = 800;//99999;
1151 //don't trace over 800 units away to avoid GIANT HORRIBLE SPEED HITS ^_^
1152 }
1153 bestindex = -1;
1154
1155 mins[0] = -15;
1156 mins[1] = -15;
1157 mins[2] = -1;
1158 maxs[0] = 15;
1159 maxs[1] = 15;
1160 maxs[2] = 1;
1161
1162 while (i < gWPNum)
1163 {
1164 if (gWPArray[i] && gWPArray[i]->inuse)
1165 {
1166 VectorSubtract(org, gWPArray[i]->origin, a);
1167 flLen = VectorLength(a);
1168
1169 if (flLen < bestdist && (RMG.integer || BotPVSCheck(org, gWPArray[i]->origin)) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore))
1170 {
1171 bestdist = flLen;
1172 bestindex = i;
1173 }
1174 }
1175
1176 i++;
1177 }
1178
1179 return bestindex;
1180 }
1181
1182 //wpDirection
1183 //0 == FORWARD
1184 //1 == BACKWARD
1185
1186 //see if this is a valid waypoint to pick up in our
1187 //current state (whatever that may be)
PassWayCheck(bot_state_t * bs,int windex)1188 int PassWayCheck(bot_state_t *bs, int windex)
1189 {
1190 if (!gWPArray[windex] || !gWPArray[windex]->inuse)
1191 { //bad point index
1192 return 0;
1193 }
1194
1195 if (RMG.integer)
1196 {
1197 if ((gWPArray[windex]->flags & WPFLAG_RED_FLAG) ||
1198 (gWPArray[windex]->flags & WPFLAG_BLUE_FLAG))
1199 { //red or blue flag, we'd like to get here
1200 return 1;
1201 }
1202 }
1203
1204 if (bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_FWD))
1205 { //we're not travelling in a direction on the trail that will allow us to pass this point
1206 return 0;
1207 }
1208 else if (!bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_BACK))
1209 { //we're not travelling in a direction on the trail that will allow us to pass this point
1210 return 0;
1211 }
1212
1213 if (bs->wpCurrent && gWPArray[windex]->forceJumpTo &&
1214 gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) &&
1215 bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo)
1216 { //waypoint requires force jump level greater than our current one to pass
1217 return 0;
1218 }
1219
1220 return 1;
1221 }
1222
1223 //tally up the distance between two waypoints
TotalTrailDistance(int start,int end,bot_state_t * bs)1224 float TotalTrailDistance(int start, int end, bot_state_t *bs)
1225 {
1226 int beginat;
1227 int endat;
1228 float distancetotal;
1229
1230 distancetotal = 0;
1231
1232 if (start > end)
1233 {
1234 beginat = end;
1235 endat = start;
1236 }
1237 else
1238 {
1239 beginat = start;
1240 endat = end;
1241 }
1242
1243 while (beginat < endat)
1244 {
1245 if (beginat >= gWPNum || !gWPArray[beginat] || !gWPArray[beginat]->inuse)
1246 { //invalid waypoint index
1247 return -1;
1248 }
1249
1250 if (!RMG.integer)
1251 {
1252 if ((end > start && gWPArray[beginat]->flags & WPFLAG_ONEWAY_BACK) ||
1253 (start > end && gWPArray[beginat]->flags & WPFLAG_ONEWAY_FWD))
1254 { //a one-way point, this means this path cannot be travelled to the final point
1255 return -1;
1256 }
1257 }
1258
1259 #if 0 //disabled force jump checks for now
1260 if (gWPArray[beginat]->forceJumpTo)
1261 {
1262 if (gWPArray[beginat-1] && gWPArray[beginat-1]->origin[2]+64 < gWPArray[beginat]->origin[2])
1263 {
1264 gdif = gWPArray[beginat]->origin[2] - gWPArray[beginat-1]->origin[2];
1265 }
1266
1267 if (gdif)
1268 {
1269 if (bs && bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[beginat]->forceJumpTo)
1270 {
1271 return -1;
1272 }
1273 }
1274 }
1275
1276 if (bs->wpCurrent && gWPArray[windex]->forceJumpTo &&
1277 gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) &&
1278 bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo)
1279 {
1280 return -1;
1281 }
1282 #endif
1283
1284 distancetotal += gWPArray[beginat]->disttonext;
1285
1286 beginat++;
1287 }
1288
1289 return distancetotal;
1290 }
1291
1292 //see if there's a route shorter than our current one to get
1293 //to the final destination we currently desire
CheckForShorterRoutes(bot_state_t * bs,int newwpindex)1294 void CheckForShorterRoutes(bot_state_t *bs, int newwpindex)
1295 {
1296 float bestlen;
1297 float checklen;
1298 int bestindex;
1299 int i;
1300 int fj;
1301
1302 i = 0;
1303 fj = 0;
1304
1305 if (!bs->wpDestination)
1306 {
1307 return;
1308 }
1309
1310 //set our traversal direction based on the index of the point
1311 if (newwpindex < bs->wpDestination->index)
1312 {
1313 bs->wpDirection = 0;
1314 }
1315 else if (newwpindex > bs->wpDestination->index)
1316 {
1317 bs->wpDirection = 1;
1318 }
1319
1320 //can't switch again yet
1321 if (bs->wpSwitchTime > level.time)
1322 {
1323 return;
1324 }
1325
1326 //no neighboring points to check off of
1327 if (!gWPArray[newwpindex]->neighbornum)
1328 {
1329 return;
1330 }
1331
1332 //get the trail distance for our wp
1333 bestindex = newwpindex;
1334 bestlen = TotalTrailDistance(newwpindex, bs->wpDestination->index, bs);
1335
1336 while (i < gWPArray[newwpindex]->neighbornum)
1337 { //now go through the neighbors and check the distance to the desired point from each neighbor
1338 checklen = TotalTrailDistance(gWPArray[newwpindex]->neighbors[i].num, bs->wpDestination->index, bs);
1339
1340 if (checklen < bestlen-64 || bestlen == -1)
1341 { //this path covers less distance, let's take it instead
1342 if (bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] >= gWPArray[newwpindex]->neighbors[i].forceJumpTo)
1343 {
1344 bestlen = checklen;
1345 bestindex = gWPArray[newwpindex]->neighbors[i].num;
1346
1347 if (gWPArray[newwpindex]->neighbors[i].forceJumpTo)
1348 {
1349 fj = gWPArray[newwpindex]->neighbors[i].forceJumpTo;
1350 }
1351 else
1352 {
1353 fj = 0;
1354 }
1355 }
1356 }
1357
1358 i++;
1359 }
1360
1361 if (bestindex != newwpindex && bestindex != -1)
1362 { //we found a path we want to switch to, let's do it
1363 bs->wpCurrent = gWPArray[bestindex];
1364 bs->wpSwitchTime = level.time + 3000;
1365
1366 if (fj)
1367 { //do we have to force jump to get to this neighbor?
1368 #ifndef FORCEJUMP_INSTANTMETHOD
1369 bs->forceJumpChargeTime = level.time + 1000;
1370 bs->beStill = level.time + 1000;
1371 bs->forceJumping = bs->forceJumpChargeTime;
1372 #else
1373 bs->beStill = level.time + 500;
1374 bs->jumpTime = level.time + fj*1200;
1375 bs->jDelay = level.time + 200;
1376 bs->forceJumping = bs->jumpTime;
1377 #endif
1378 }
1379 }
1380 }
1381
1382 //check for flags on the waypoint we're currently travelling to
1383 //and perform the desired behavior based on the flag
WPConstantRoutine(bot_state_t * bs)1384 void WPConstantRoutine(bot_state_t *bs)
1385 {
1386 if (!bs->wpCurrent)
1387 {
1388 return;
1389 }
1390
1391 if (bs->wpCurrent->flags & WPFLAG_DUCK)
1392 { //duck while travelling to this point
1393 bs->duckTime = level.time + 100;
1394 }
1395
1396 #ifndef FORCEJUMP_INSTANTMETHOD
1397 if (bs->wpCurrent->flags & WPFLAG_JUMP)
1398 { //jump while travelling to this point
1399 float heightDif = (bs->wpCurrent->origin[2] - bs->origin[2]+16);
1400
1401 if (bs->origin[2]+16 >= bs->wpCurrent->origin[2])
1402 { //don't need to jump, we're already higher than this point
1403 heightDif = 0;
1404 }
1405
1406 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))
1407 { //alright, let's jump
1408 bs->forceJumpChargeTime = level.time + 1000;
1409 if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE && bs->jumpPrep < (level.time-300))
1410 {
1411 bs->jumpPrep = level.time + 700;
1412 }
1413 bs->beStill = level.time + 300;
1414 bs->jumpTime = 0;
1415
1416 if (bs->wpSeenTime < (level.time + 600))
1417 {
1418 bs->wpSeenTime = level.time + 600;
1419 }
1420 }
1421 else if (heightDif > 64 && !(bs->cur_ps.fd.forcePowersKnown & (1 << FP_LEVITATION)))
1422 { //this point needs force jump to reach and we don't have it
1423 //Kill the current point and turn around
1424 bs->wpCurrent = NULL;
1425 if (bs->wpDirection)
1426 {
1427 bs->wpDirection = 0;
1428 }
1429 else
1430 {
1431 bs->wpDirection = 1;
1432 }
1433
1434 return;
1435 }
1436 }
1437 #endif
1438
1439 if (bs->wpCurrent->forceJumpTo)
1440 {
1441 #ifdef FORCEJUMP_INSTANTMETHOD
1442 if (bs->origin[2]+16 < bs->wpCurrent->origin[2])
1443 {
1444 bs->jumpTime = level.time + 100;
1445 }
1446 #else
1447
1448 if (bs->cur_ps.fd.forceJumpCharge < (forceJumpStrength[bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION]]-100))
1449 {
1450 bs->forceJumpChargeTime = level.time + 200;
1451 }
1452 #endif
1453 }
1454 }
1455
1456 //check if our ctf state is to guard the base
BotCTFGuardDuty(bot_state_t * bs)1457 qboolean BotCTFGuardDuty(bot_state_t *bs)
1458 {
1459 if (level.gametype != GT_CTF && level.gametype != GT_CTY)
1460 {
1461 return qfalse;
1462 }
1463
1464 if (bs->ctfState == CTFSTATE_DEFENDER)
1465 {
1466 return qtrue;
1467 }
1468
1469 return qfalse;
1470 }
1471
1472 //when we reach the waypoint we are travelling to,
1473 //this function will be called. We will perform any
1474 //checks for flags on the current wp and activate
1475 //any "touch" events based on that.
WPTouchRoutine(bot_state_t * bs)1476 void WPTouchRoutine(bot_state_t *bs)
1477 {
1478 int lastNum;
1479
1480 if (!bs->wpCurrent)
1481 {
1482 return;
1483 }
1484
1485 bs->wpTravelTime = level.time + 10000;
1486
1487 if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC)
1488 { //don't try to use any nearby map objects for a little while
1489 bs->noUseTime = level.time + 4000;
1490 }
1491
1492 #ifdef FORCEJUMP_INSTANTMETHOD
1493 if ((bs->wpCurrent->flags & WPFLAG_JUMP) && bs->wpCurrent->forceJumpTo)
1494 { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is
1495 //handled elsewhere.
1496 bs->jumpTime = level.time + 100;
1497 }
1498 #else
1499 if ((bs->wpCurrent->flags & WPFLAG_JUMP) && !bs->wpCurrent->forceJumpTo)
1500 { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is
1501 //handled elsewhere.
1502 bs->jumpTime = level.time + 100;
1503 }
1504 #endif
1505
1506 if (bs->isCamper && bot_camp.integer && (BotIsAChickenWuss(bs) || BotCTFGuardDuty(bs) || bs->isCamper == 2) && ((bs->wpCurrent->flags & WPFLAG_SNIPEORCAMP) || (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)) &&
1507 bs->cur_ps.weapon != WP_SABER && bs->cur_ps.weapon != WP_MELEE && bs->cur_ps.weapon != WP_STUN_BATON)
1508 { //if we're a camper and a chicken then camp
1509 if (bs->wpDirection)
1510 {
1511 lastNum = bs->wpCurrent->index+1;
1512 }
1513 else
1514 {
1515 lastNum = bs->wpCurrent->index-1;
1516 }
1517
1518 if (gWPArray[lastNum] && gWPArray[lastNum]->inuse && gWPArray[lastNum]->index && bs->isCamping < level.time)
1519 {
1520 bs->isCamping = level.time + rand()%15000 + 30000;
1521 bs->wpCamping = bs->wpCurrent;
1522 bs->wpCampingTo = gWPArray[lastNum];
1523
1524 if (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)
1525 {
1526 bs->campStanding = qtrue;
1527 }
1528 else
1529 {
1530 bs->campStanding = qfalse;
1531 }
1532 }
1533
1534 }
1535 else if ((bs->cur_ps.weapon == WP_SABER || bs->cur_ps.weapon == WP_STUN_BATON || bs->cur_ps.weapon == WP_MELEE) &&
1536 bs->isCamping > level.time)
1537 { //don't snipe/camp with a melee weapon, that would be silly
1538 bs->isCamping = 0;
1539 bs->wpCampingTo = NULL;
1540 bs->wpCamping = NULL;
1541 }
1542
1543 if (bs->wpDestination)
1544 {
1545 if (bs->wpCurrent->index == bs->wpDestination->index)
1546 {
1547 bs->wpDestination = NULL;
1548
1549 if (bs->runningLikeASissy)
1550 { //this obviously means we're scared and running, so we'll want to keep our navigational priorities less delayed
1551 bs->destinationGrabTime = level.time + 500;
1552 }
1553 else
1554 {
1555 bs->destinationGrabTime = level.time + 3500;
1556 }
1557 }
1558 else
1559 {
1560 CheckForShorterRoutes(bs, bs->wpCurrent->index);
1561 }
1562 }
1563 }
1564
1565 //could also slowly lerp toward, but for now
1566 //just copying straight over.
MoveTowardIdealAngles(bot_state_t * bs)1567 void MoveTowardIdealAngles(bot_state_t *bs)
1568 {
1569 VectorCopy(bs->goalAngles, bs->ideal_viewangles);
1570 }
1571
1572 #define BOT_STRAFE_AVOIDANCE
1573
1574 #ifdef BOT_STRAFE_AVOIDANCE
1575 #define STRAFEAROUND_RIGHT 1
1576 #define STRAFEAROUND_LEFT 2
1577
1578 //do some trace checks for strafing to get an idea of where we
1579 //are and if we should move to avoid obstacles.
BotTrace_Strafe(bot_state_t * bs,vec3_t traceto)1580 int BotTrace_Strafe(bot_state_t *bs, vec3_t traceto)
1581 {
1582 vec3_t playerMins = {-15, -15, /*DEFAULT_MINS_2*/-8};
1583 vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2};
1584 vec3_t from, to;
1585 vec3_t dirAng, dirDif;
1586 vec3_t forward, right;
1587 trace_t tr;
1588
1589 if (bs->cur_ps.groundEntityNum == ENTITYNUM_NONE)
1590 { //don't do this in the air, it can be.. dangerous.
1591 return 0;
1592 }
1593
1594 VectorSubtract(traceto, bs->origin, dirAng);
1595 VectorNormalize(dirAng);
1596 vectoangles(dirAng, dirAng);
1597
1598 if (AngleDifference(bs->viewangles[YAW], dirAng[YAW]) > 60 ||
1599 AngleDifference(bs->viewangles[YAW], dirAng[YAW]) < -60)
1600 { //If we aren't facing the direction we're going here, then we've got enough excuse to be too stupid to strafe around anyway
1601 return 0;
1602 }
1603
1604 VectorCopy(bs->origin, from);
1605 VectorCopy(traceto, to);
1606
1607 VectorSubtract(to, from, dirDif);
1608 VectorNormalize(dirDif);
1609 vectoangles(dirDif, dirDif);
1610
1611 AngleVectors(dirDif, forward, 0, 0);
1612
1613 to[0] = from[0] + forward[0]*32;
1614 to[1] = from[1] + forward[1]*32;
1615 to[2] = from[2] + forward[2]*32;
1616
1617 trap->Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID, qfalse, 0, 0);
1618
1619 if (tr.fraction == 1)
1620 {
1621 return 0;
1622 }
1623
1624 AngleVectors(dirAng, 0, right, 0);
1625
1626 from[0] += right[0]*32;
1627 from[1] += right[1]*32;
1628 from[2] += right[2]*16;
1629
1630 to[0] += right[0]*32;
1631 to[1] += right[1]*32;
1632 to[2] += right[2]*32;
1633
1634 trap->Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID, qfalse, 0, 0);
1635
1636 if (tr.fraction == 1)
1637 {
1638 return STRAFEAROUND_RIGHT;
1639 }
1640
1641 from[0] -= right[0]*64;
1642 from[1] -= right[1]*64;
1643 from[2] -= right[2]*64;
1644
1645 to[0] -= right[0]*64;
1646 to[1] -= right[1]*64;
1647 to[2] -= right[2]*64;
1648
1649 trap->Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID, qfalse, 0, 0);
1650
1651 if (tr.fraction == 1)
1652 {
1653 return STRAFEAROUND_LEFT;
1654 }
1655
1656 return 0;
1657 }
1658 #endif
1659
1660 //Similar to the trace check, but we want to trace to see
1661 //if there's anything we can jump over.
BotTrace_Jump(bot_state_t * bs,vec3_t traceto)1662 int BotTrace_Jump(bot_state_t *bs, vec3_t traceto)
1663 {
1664 vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod;
1665 trace_t tr;
1666 int orTr;
1667
1668 VectorSubtract(traceto, bs->origin, a);
1669 vectoangles(a, a);
1670
1671 AngleVectors(a, fwd, NULL, NULL);
1672
1673 traceto_mod[0] = bs->origin[0] + fwd[0]*4;
1674 traceto_mod[1] = bs->origin[1] + fwd[1]*4;
1675 traceto_mod[2] = bs->origin[2] + fwd[2]*4;
1676
1677 mins[0] = -15;
1678 mins[1] = -15;
1679 mins[2] = -18;
1680 maxs[0] = 15;
1681 maxs[1] = 15;
1682 maxs[2] = 32;
1683
1684 trap->Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID, qfalse, 0, 0);
1685
1686 if (tr.fraction == 1)
1687 {
1688 return 0;
1689 }
1690
1691 orTr = tr.entityNum;
1692
1693 VectorCopy(bs->origin, tracefrom_mod);
1694
1695 tracefrom_mod[2] += 41;
1696 traceto_mod[2] += 41;
1697
1698 mins[0] = -15;
1699 mins[1] = -15;
1700 mins[2] = 0;
1701 maxs[0] = 15;
1702 maxs[1] = 15;
1703 maxs[2] = 8;
1704
1705 trap->Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID, qfalse, 0, 0);
1706
1707 if (tr.fraction == 1)
1708 {
1709 if (orTr >= 0 && orTr < MAX_CLIENTS && botstates[orTr] && botstates[orTr]->jumpTime > level.time)
1710 {
1711 return 0; //so bots don't try to jump over each other at the same time
1712 }
1713
1714 if (bs->currentEnemy && bs->currentEnemy->s.number == orTr && (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER || BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE))
1715 {
1716 return 0;
1717 }
1718
1719 return 1;
1720 }
1721
1722 return 0;
1723 }
1724
1725 //And yet another check to duck under any obstacles.
BotTrace_Duck(bot_state_t * bs,vec3_t traceto)1726 int BotTrace_Duck(bot_state_t *bs, vec3_t traceto)
1727 {
1728 vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod;
1729 trace_t tr;
1730
1731 VectorSubtract(traceto, bs->origin, a);
1732 vectoangles(a, a);
1733
1734 AngleVectors(a, fwd, NULL, NULL);
1735
1736 traceto_mod[0] = bs->origin[0] + fwd[0]*4;
1737 traceto_mod[1] = bs->origin[1] + fwd[1]*4;
1738 traceto_mod[2] = bs->origin[2] + fwd[2]*4;
1739
1740 mins[0] = -15;
1741 mins[1] = -15;
1742 mins[2] = -23;
1743 maxs[0] = 15;
1744 maxs[1] = 15;
1745 maxs[2] = 8;
1746
1747 trap->Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID, qfalse, 0, 0);
1748
1749 if (tr.fraction != 1)
1750 {
1751 return 0;
1752 }
1753
1754 VectorCopy(bs->origin, tracefrom_mod);
1755
1756 tracefrom_mod[2] += 31;//33;
1757 traceto_mod[2] += 31;//33;
1758
1759 mins[0] = -15;
1760 mins[1] = -15;
1761 mins[2] = 0;
1762 maxs[0] = 15;
1763 maxs[1] = 15;
1764 maxs[2] = 32;
1765
1766 trap->Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID, qfalse, 0, 0);
1767
1768 if (tr.fraction != 1)
1769 {
1770 return 1;
1771 }
1772
1773 return 0;
1774 }
1775
1776 //check of the potential enemy is a valid one
PassStandardEnemyChecks(bot_state_t * bs,gentity_t * en)1777 int PassStandardEnemyChecks(bot_state_t *bs, gentity_t *en)
1778 {
1779 if (!bs || !en)
1780 { //shouldn't happen
1781 return 0;
1782 }
1783
1784 if (!en->client)
1785 { //not a client, don't care about him
1786 return 0;
1787 }
1788
1789 if (en->health < 1)
1790 { //he's already dead
1791 return 0;
1792 }
1793
1794 if (!en->takedamage)
1795 { //a client that can't take damage?
1796 return 0;
1797 }
1798
1799 if (bs->doingFallback &&
1800 (gLevelFlags & LEVELFLAG_IGNOREINFALLBACK))
1801 { //we screwed up in our nav routines somewhere and we've reverted to a fallback state to
1802 //try to get back on the trail. If the level specifies to ignore enemies in this state,
1803 //then ignore them.
1804 return 0;
1805 }
1806
1807 if (en->client->ps.pm_type == PM_INTERMISSION ||
1808 en->client->ps.pm_type == PM_SPECTATOR ||
1809 en->client->sess.sessionTeam == TEAM_SPECTATOR)
1810 { //don't attack spectators
1811 return 0;
1812 }
1813
1814 if (!en->client->pers.connected)
1815 { //a "zombie" client?
1816 return 0;
1817 }
1818
1819 if (!en->s.solid)
1820 { //shouldn't happen
1821 return 0;
1822 }
1823
1824 if (bs->client == en->s.number)
1825 { //don't attack yourself
1826 return 0;
1827 }
1828
1829 if (OnSameTeam(&g_entities[bs->client], en))
1830 { //don't attack teammates
1831 return 0;
1832 }
1833
1834 if (BotMindTricked(bs->client, en->s.number))
1835 {
1836 if (bs->currentEnemy && bs->currentEnemy->s.number == en->s.number)
1837 { //if mindtricked by this enemy, then be less "aware" of them, even though
1838 //we know they're there.
1839 vec3_t vs;
1840 float vLen = 0;
1841
1842 VectorSubtract(bs->origin, en->client->ps.origin, vs);
1843 vLen = VectorLength(vs);
1844
1845 if (vLen > 64 /*&& (level.time - en->client->dangerTime) > 150*/)
1846 {
1847 return 0;
1848 }
1849 }
1850 }
1851
1852 if (en->client->ps.duelInProgress && en->client->ps.duelIndex != bs->client)
1853 { //don't attack duelists unless you're dueling them
1854 return 0;
1855 }
1856
1857 if (bs->cur_ps.duelInProgress && en->s.number != bs->cur_ps.duelIndex)
1858 { //ditto, the other way around
1859 return 0;
1860 }
1861
1862 if (level.gametype == GT_JEDIMASTER && !en->client->ps.isJediMaster && !bs->cur_ps.isJediMaster)
1863 { //rules for attacking non-JM in JM mode
1864 vec3_t vs;
1865 float vLen = 0;
1866
1867 if (!g_friendlyFire.integer)
1868 { //can't harm non-JM in JM mode if FF is off
1869 return 0;
1870 }
1871
1872 VectorSubtract(bs->origin, en->client->ps.origin, vs);
1873 vLen = VectorLength(vs);
1874
1875 if (vLen > 350)
1876 {
1877 return 0;
1878 }
1879 }
1880
1881 return 1;
1882 }
1883
1884 //Notifies the bot that he has taken damage from "attacker".
BotDamageNotification(gclient_t * bot,gentity_t * attacker)1885 void BotDamageNotification(gclient_t *bot, gentity_t *attacker)
1886 {
1887 bot_state_t *bs;
1888 bot_state_t *bs_a;
1889 int i;
1890
1891 if (!bot || !attacker || !attacker->client)
1892 {
1893 return;
1894 }
1895
1896 if (bot->ps.clientNum >= MAX_CLIENTS)
1897 { //an NPC.. do nothing for them.
1898 return;
1899 }
1900
1901 if (attacker->s.number >= MAX_CLIENTS)
1902 { //if attacker is an npc also don't care I suppose.
1903 return;
1904 }
1905
1906 bs_a = botstates[attacker->s.number];
1907
1908 if (bs_a)
1909 { //if the client attacking us is a bot as well
1910 bs_a->lastAttacked = &g_entities[bot->ps.clientNum];
1911 i = 0;
1912
1913 while (i < MAX_CLIENTS)
1914 {
1915 if (botstates[i] &&
1916 i != bs_a->client &&
1917 botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum])
1918 {
1919 botstates[i]->lastAttacked = NULL;
1920 }
1921
1922 i++;
1923 }
1924 }
1925 else //got attacked by a real client, so no one gets rights to lastAttacked
1926 {
1927 i = 0;
1928
1929 while (i < MAX_CLIENTS)
1930 {
1931 if (botstates[i] &&
1932 botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum])
1933 {
1934 botstates[i]->lastAttacked = NULL;
1935 }
1936
1937 i++;
1938 }
1939 }
1940
1941 bs = botstates[bot->ps.clientNum];
1942
1943 if (!bs)
1944 {
1945 return;
1946 }
1947
1948 bs->lastHurt = attacker;
1949
1950 if (bs->currentEnemy)
1951 { //we don't care about the guy attacking us if we have an enemy already
1952 return;
1953 }
1954
1955 if (!PassStandardEnemyChecks(bs, attacker))
1956 { //the person that hurt us is not a valid enemy
1957 return;
1958 }
1959
1960 if (PassLovedOneCheck(bs, attacker))
1961 { //the person that hurt us is the one we love!
1962 bs->currentEnemy = attacker;
1963 bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
1964 }
1965 }
1966
1967 //perform cheap "hearing" checks based on the event catching
1968 //system
BotCanHear(bot_state_t * bs,gentity_t * en,float endist)1969 int BotCanHear(bot_state_t *bs, gentity_t *en, float endist)
1970 {
1971 float minlen;
1972
1973 if (!en || !en->client)
1974 {
1975 return 0;
1976 }
1977
1978 if (en && en->client && en->client->ps.otherSoundTime > level.time)
1979 { //they made a noise in recent time
1980 minlen = en->client->ps.otherSoundLen;
1981 goto checkStep;
1982 }
1983
1984 if (en && en->client && en->client->ps.footstepTime > level.time)
1985 { //they made a footstep
1986 minlen = 256;
1987 goto checkStep;
1988 }
1989
1990 if (gBotEventTracker[en->s.number].eventTime < level.time)
1991 { //no recent events to check
1992 return 0;
1993 }
1994
1995 switch(gBotEventTracker[en->s.number].events[gBotEventTracker[en->s.number].eventSequence & (MAX_PS_EVENTS-1)])
1996 { //did the last event contain a sound?
1997 case EV_GLOBAL_SOUND:
1998 minlen = 256;
1999 break;
2000 case EV_FIRE_WEAPON:
2001 case EV_ALT_FIRE:
2002 case EV_SABER_ATTACK:
2003 minlen = 512;
2004 break;
2005 case EV_STEP_4:
2006 case EV_STEP_8:
2007 case EV_STEP_12:
2008 case EV_STEP_16:
2009 case EV_FOOTSTEP:
2010 case EV_FOOTSTEP_METAL:
2011 case EV_FOOTWADE:
2012 minlen = 256;
2013 break;
2014 case EV_JUMP:
2015 case EV_ROLL:
2016 minlen = 256;
2017 break;
2018 default:
2019 minlen = 999999;
2020 break;
2021 }
2022 checkStep:
2023 if (BotMindTricked(bs->client, en->s.number))
2024 { //if mindtricked by this person, cut down on the minlen so they can't "hear" as well
2025 minlen /= 4;
2026 }
2027
2028 if (endist <= minlen)
2029 { //we heard it
2030 return 1;
2031 }
2032
2033 return 0;
2034 }
2035
2036 //check for new events
UpdateEventTracker(void)2037 void UpdateEventTracker(void)
2038 {
2039 int i;
2040
2041 i = 0;
2042
2043 while (i < MAX_CLIENTS)
2044 {
2045 if (gBotEventTracker[i].eventSequence != level.clients[i].ps.eventSequence)
2046 { //updated event
2047 gBotEventTracker[i].eventSequence = level.clients[i].ps.eventSequence;
2048 gBotEventTracker[i].events[0] = level.clients[i].ps.events[0];
2049 gBotEventTracker[i].events[1] = level.clients[i].ps.events[1];
2050 gBotEventTracker[i].eventTime = level.time + 0.5;
2051 }
2052
2053 i++;
2054 }
2055 }
2056
2057 //check if said angles are within our fov
InFieldOfVision(vec3_t viewangles,float fov,vec3_t angles)2058 int InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
2059 {
2060 int i;
2061 float diff, angle;
2062
2063 for (i = 0; i < 2; i++)
2064 {
2065 angle = AngleMod(viewangles[i]);
2066 angles[i] = AngleMod(angles[i]);
2067 diff = angles[i] - angle;
2068 if (angles[i] > angle)
2069 {
2070 if (diff > 180.0)
2071 {
2072 diff -= 360.0;
2073 }
2074 }
2075 else
2076 {
2077 if (diff < -180.0)
2078 {
2079 diff += 360.0;
2080 }
2081 }
2082 if (diff > 0)
2083 {
2084 if (diff > fov * 0.5)
2085 {
2086 return 0;
2087 }
2088 }
2089 else
2090 {
2091 if (diff < -fov * 0.5)
2092 {
2093 return 0;
2094 }
2095 }
2096 }
2097 return 1;
2098 }
2099
2100 //We cannot hurt the ones we love. Unless of course this
2101 //function says we can.
PassLovedOneCheck(bot_state_t * bs,gentity_t * ent)2102 int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent)
2103 {
2104 int i;
2105 bot_state_t *loved;
2106
2107 if (!bs->lovednum)
2108 {
2109 return 1;
2110 }
2111
2112 if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
2113 { //There is no love in 1-on-1
2114 return 1;
2115 }
2116
2117 i = 0;
2118
2119 if (!botstates[ent->s.number])
2120 { //not a bot
2121 return 1;
2122 }
2123
2124 if (!bot_attachments.integer)
2125 {
2126 return 1;
2127 }
2128
2129 loved = botstates[ent->s.number];
2130
2131 while (i < bs->lovednum)
2132 {
2133 if (strcmp(level.clients[loved->client].pers.netname, bs->loved[i].name) == 0)
2134 {
2135 if (!IsTeamplay() && bs->loved[i].level < 2)
2136 { //if FFA and level of love is not greater than 1, just don't care
2137 return 1;
2138 }
2139 else if (IsTeamplay() && !OnSameTeam(&g_entities[bs->client], &g_entities[loved->client]) && bs->loved[i].level < 2)
2140 { //is teamplay, but not on same team and level < 2
2141 return 1;
2142 }
2143 else
2144 {
2145 return 0;
2146 }
2147 }
2148
2149 i++;
2150 }
2151
2152 return 1;
2153 }
2154
2155 qboolean G_ThereIsAMaster(void);
2156
2157 //standard check to find a new enemy.
ScanForEnemies(bot_state_t * bs)2158 int ScanForEnemies(bot_state_t *bs)
2159 {
2160 vec3_t a;
2161 float distcheck;
2162 float closest;
2163 int bestindex;
2164 int i;
2165 float hasEnemyDist = 0;
2166 qboolean noAttackNonJM = qfalse;
2167
2168 closest = 999999;
2169 i = 0;
2170 bestindex = -1;
2171
2172 if (bs->currentEnemy)
2173 { //only switch to a new enemy if he's significantly closer
2174 hasEnemyDist = bs->frame_Enemy_Len;
2175 }
2176
2177 if (bs->currentEnemy && bs->currentEnemy->client &&
2178 bs->currentEnemy->client->ps.isJediMaster)
2179 { //The Jedi Master must die.
2180 return -1;
2181 }
2182
2183 if (level.gametype == GT_JEDIMASTER)
2184 {
2185 if (G_ThereIsAMaster() && !bs->cur_ps.isJediMaster)
2186 { //if friendly fire is on in jedi master we can attack people that bug us
2187 if (!g_friendlyFire.integer)
2188 {
2189 noAttackNonJM = qtrue;
2190 }
2191 else
2192 {
2193 closest = 128; //only get mad at people if they get close enough to you to anger you, or hurt you
2194 }
2195 }
2196 }
2197
2198 while (i <= MAX_CLIENTS)
2199 {
2200 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]))
2201 {
2202 VectorSubtract(g_entities[i].client->ps.origin, bs->eye, a);
2203 distcheck = VectorLength(a);
2204 vectoangles(a, a);
2205
2206 if (g_entities[i].client->ps.isJediMaster)
2207 { //make us think the Jedi Master is close so we'll attack him above all
2208 distcheck = 1;
2209 }
2210
2211 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))
2212 {
2213 if (BotMindTricked(bs->client, i))
2214 {
2215 if (distcheck < 256 || (level.time - g_entities[i].client->dangerTime) < 100)
2216 {
2217 if (!hasEnemyDist || distcheck < (hasEnemyDist - 128))
2218 { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out
2219 if (!noAttackNonJM || g_entities[i].client->ps.isJediMaster)
2220 {
2221 closest = distcheck;
2222 bestindex = i;
2223 }
2224 }
2225 }
2226 }
2227 else
2228 {
2229 if (!hasEnemyDist || distcheck < (hasEnemyDist - 128))
2230 { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out
2231 if (!noAttackNonJM || g_entities[i].client->ps.isJediMaster)
2232 {
2233 closest = distcheck;
2234 bestindex = i;
2235 }
2236 }
2237 }
2238 }
2239 }
2240 i++;
2241 }
2242
2243 return bestindex;
2244 }
2245
WaitingForNow(bot_state_t * bs,vec3_t goalpos)2246 int WaitingForNow(bot_state_t *bs, vec3_t goalpos)
2247 { //checks if the bot is doing something along the lines of waiting for an elevator to raise up
2248 vec3_t xybot, xywp, a;
2249
2250 if (!bs->wpCurrent)
2251 {
2252 return 0;
2253 }
2254
2255 if ((int)goalpos[0] != (int)bs->wpCurrent->origin[0] ||
2256 (int)goalpos[1] != (int)bs->wpCurrent->origin[1] ||
2257 (int)goalpos[2] != (int)bs->wpCurrent->origin[2])
2258 {
2259 return 0;
2260 }
2261
2262 VectorCopy(bs->origin, xybot);
2263 VectorCopy(bs->wpCurrent->origin, xywp);
2264
2265 xybot[2] = 0;
2266 xywp[2] = 0;
2267
2268 VectorSubtract(xybot, xywp, a);
2269
2270 if (VectorLength(a) < 16 && bs->frame_Waypoint_Len > 100)
2271 {
2272 if (CheckForFunc(bs->origin, bs->client))
2273 {
2274 return 1; //we're probably standing on an elevator and riding up/down. Or at least we hope so.
2275 }
2276 }
2277 else if (VectorLength(a) < 64 && bs->frame_Waypoint_Len > 64 &&
2278 CheckForFunc(bs->origin, bs->client))
2279 {
2280 bs->noUseTime = level.time + 2000;
2281 }
2282
2283 return 0;
2284 }
2285
2286 //get an ideal distance for us to be at in relation to our opponent
2287 //based on our weapon.
BotGetWeaponRange(bot_state_t * bs)2288 int BotGetWeaponRange(bot_state_t *bs)
2289 {
2290 switch (bs->cur_ps.weapon)
2291 {
2292 case WP_STUN_BATON:
2293 case WP_MELEE:
2294 return BWEAPONRANGE_MELEE;
2295 case WP_SABER:
2296 return BWEAPONRANGE_SABER;
2297 case WP_BRYAR_PISTOL:
2298 return BWEAPONRANGE_MID;
2299 case WP_BLASTER:
2300 return BWEAPONRANGE_MID;
2301 case WP_DISRUPTOR:
2302 return BWEAPONRANGE_MID;
2303 case WP_BOWCASTER:
2304 return BWEAPONRANGE_LONG;
2305 case WP_REPEATER:
2306 return BWEAPONRANGE_MID;
2307 case WP_DEMP2:
2308 return BWEAPONRANGE_LONG;
2309 case WP_FLECHETTE:
2310 return BWEAPONRANGE_LONG;
2311 case WP_ROCKET_LAUNCHER:
2312 return BWEAPONRANGE_LONG;
2313 case WP_THERMAL:
2314 return BWEAPONRANGE_LONG;
2315 case WP_TRIP_MINE:
2316 return BWEAPONRANGE_LONG;
2317 case WP_DET_PACK:
2318 return BWEAPONRANGE_LONG;
2319 default:
2320 return BWEAPONRANGE_MID;
2321 }
2322 }
2323
2324 //see if we want to run away from the opponent for whatever reason
BotIsAChickenWuss(bot_state_t * bs)2325 int BotIsAChickenWuss(bot_state_t *bs)
2326 {
2327 int bWRange;
2328
2329 if (gLevelFlags & LEVELFLAG_IMUSTNTRUNAWAY)
2330 { //The level says we mustn't run away!
2331 return 0;
2332 }
2333
2334 if (level.gametype == GT_SINGLE_PLAYER)
2335 { //"coop" (not really)
2336 return 0;
2337 }
2338
2339 if (level.gametype == GT_JEDIMASTER && !bs->cur_ps.isJediMaster)
2340 { //Then you may know no fear.
2341 //Well, unless he's strong.
2342 if (bs->currentEnemy && bs->currentEnemy->client &&
2343 bs->currentEnemy->client->ps.isJediMaster &&
2344 bs->currentEnemy->health > 40 &&
2345 bs->cur_ps.weapon < WP_ROCKET_LAUNCHER)
2346 { //explosive weapons are most effective against the Jedi Master
2347 goto jmPass;
2348 }
2349 return 0;
2350 }
2351
2352 if (level.gametype == GT_CTF && bs->currentEnemy && bs->currentEnemy->client)
2353 {
2354 if (bs->currentEnemy->client->ps.powerups[PW_REDFLAG] ||
2355 bs->currentEnemy->client->ps.powerups[PW_BLUEFLAG])
2356 { //don't be afraid of flag carriers, they must die!
2357 return 0;
2358 }
2359 }
2360
2361 jmPass:
2362 if (bs->chickenWussCalculationTime > level.time)
2363 {
2364 return 2; //don't want to keep going between two points...
2365 }
2366
2367 if (bs->cur_ps.fd.forcePowersActive & (1 << FP_RAGE))
2368 { //don't run while raging
2369 return 0;
2370 }
2371
2372 if (level.gametype == GT_JEDIMASTER && !bs->cur_ps.isJediMaster)
2373 { //be frightened of the jedi master? I guess in this case.
2374 return 1;
2375 }
2376
2377 bs->chickenWussCalculationTime = level.time + MAX_CHICKENWUSS_TIME;
2378
2379 if (g_entities[bs->client].health < BOT_RUN_HEALTH)
2380 { //we're low on health, let's get away
2381 return 1;
2382 }
2383
2384 bWRange = BotGetWeaponRange(bs);
2385
2386 if (bWRange == BWEAPONRANGE_MELEE || bWRange == BWEAPONRANGE_SABER)
2387 {
2388 if (bWRange != BWEAPONRANGE_SABER || !bs->saberSpecialist)
2389 { //run away if we're using melee, or if we're using a saber and not a "saber specialist"
2390 return 1;
2391 }
2392 }
2393
2394 if (bs->cur_ps.weapon == WP_BRYAR_PISTOL)
2395 { //the bryar is a weak weapon, so just try to find a new one if it's what you're having to use
2396 return 1;
2397 }
2398
2399 if (bs->currentEnemy && bs->currentEnemy->client &&
2400 bs->currentEnemy->client->ps.weapon == WP_SABER &&
2401 bs->frame_Enemy_Len < 512 && bs->cur_ps.weapon != WP_SABER)
2402 { //if close to an enemy with a saber and not using a saber, then try to back off
2403 return 1;
2404 }
2405
2406 if ((level.time-bs->cur_ps.electrifyTime) < 16000)
2407 { //lightning is dangerous.
2408 return 1;
2409 }
2410
2411 //didn't run, reset the timer
2412 bs->chickenWussCalculationTime = 0;
2413
2414 return 0;
2415 }
2416
2417 //look for "bad things". bad things include detpacks, thermal detonators,
2418 //and other dangerous explodey items.
GetNearestBadThing(bot_state_t * bs)2419 gentity_t *GetNearestBadThing(bot_state_t *bs)
2420 {
2421 int i = 0;
2422 float glen;
2423 vec3_t hold;
2424 int bestindex = 0;
2425 float bestdist = 800; //if not within a radius of 800, it's no threat anyway
2426 int foundindex = 0;
2427 float factor = 0;
2428 gentity_t *ent;
2429 trace_t tr;
2430
2431 while (i < level.num_entities)
2432 {
2433 ent = &g_entities[i];
2434
2435 if ( (ent &&
2436 !ent->client &&
2437 ent->inuse &&
2438 ent->damage &&
2439 /*(ent->s.weapon == WP_THERMAL || ent->s.weapon == WP_FLECHETTE)*/
2440 ent->s.weapon &&
2441 ent->splashDamage) ||
2442 (ent &&
2443 ent->genericValue5 == 1000 &&
2444 ent->inuse &&
2445 ent->health > 0 &&
2446 ent->genericValue3 != bs->client &&
2447 g_entities[ent->genericValue3].client && !OnSameTeam(&g_entities[bs->client], &g_entities[ent->genericValue3])) )
2448 { //try to escape from anything with a non-0 s.weapon and non-0 damage. This hopefully only means dangerous projectiles.
2449 //Or a sentry gun if bolt_Head == 1000. This is a terrible hack, yes.
2450 VectorSubtract(bs->origin, ent->r.currentOrigin, hold);
2451 glen = VectorLength(hold);
2452
2453 if (ent->s.weapon != WP_THERMAL && ent->s.weapon != WP_FLECHETTE &&
2454 ent->s.weapon != WP_DET_PACK && ent->s.weapon != WP_TRIP_MINE)
2455 {
2456 factor = 0.5;
2457
2458 if (ent->s.weapon && glen <= 256 && bs->settings.skill > 2)
2459 { //it's a projectile so push it away
2460 bs->doForcePush = level.time + 700;
2461 //trap->Print("PUSH PROJECTILE\n");
2462 }
2463 }
2464 else
2465 {
2466 factor = 1;
2467 }
2468
2469 if (ent->s.weapon == WP_ROCKET_LAUNCHER &&
2470 (ent->r.ownerNum == bs->client ||
2471 (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS &&
2472 g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) )
2473 { //don't be afraid of your own rockets or your teammates' rockets
2474 factor = 0;
2475 }
2476
2477 if (ent->s.weapon == WP_DET_PACK &&
2478 (ent->r.ownerNum == bs->client ||
2479 (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS &&
2480 g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) )
2481 { //don't be afraid of your own detpacks or your teammates' detpacks
2482 factor = 0;
2483 }
2484
2485 if (ent->s.weapon == WP_TRIP_MINE &&
2486 (ent->r.ownerNum == bs->client ||
2487 (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS &&
2488 g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) )
2489 { //don't be afraid of your own trip mines or your teammates' trip mines
2490 factor = 0;
2491 }
2492
2493 if (ent->s.weapon == WP_THERMAL &&
2494 (ent->r.ownerNum == bs->client ||
2495 (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS &&
2496 g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) )
2497 { //don't be afraid of your own thermals or your teammates' thermals
2498 factor = 0;
2499 }
2500
2501 if (glen < bestdist*factor && BotPVSCheck(bs->origin, ent->s.pos.trBase))
2502 {
2503 trap->Trace(&tr, bs->origin, NULL, NULL, ent->s.pos.trBase, bs->client, MASK_SOLID, qfalse, 0, 0);
2504
2505 if (tr.fraction == 1 || tr.entityNum == ent->s.number)
2506 {
2507 bestindex = i;
2508 bestdist = glen;
2509 foundindex = 1;
2510 }
2511 }
2512 }
2513
2514 if (ent && !ent->client && ent->inuse && ent->damage && ent->s.weapon && ent->r.ownerNum < MAX_CLIENTS && ent->r.ownerNum >= 0)
2515 { //if we're in danger of a projectile belonging to someone and don't have an enemy, set the enemy to them
2516 gentity_t *projOwner = &g_entities[ent->r.ownerNum];
2517
2518 if (projOwner && projOwner->inuse && projOwner->client)
2519 {
2520 if (!bs->currentEnemy)
2521 {
2522 if (PassStandardEnemyChecks(bs, projOwner))
2523 {
2524 if (PassLovedOneCheck(bs, projOwner))
2525 {
2526 VectorSubtract(bs->origin, ent->r.currentOrigin, hold);
2527 glen = VectorLength(hold);
2528
2529 if (glen < 512)
2530 {
2531 bs->currentEnemy = projOwner;
2532 bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
2533 }
2534 }
2535 }
2536 }
2537 }
2538 }
2539
2540 i++;
2541 }
2542
2543 if (foundindex)
2544 {
2545 bs->dontGoBack = level.time + 1500;
2546 return &g_entities[bestindex];
2547 }
2548 else
2549 {
2550 return NULL;
2551 }
2552 }
2553
2554 //Keep our CTF priorities on defending our team's flag
BotDefendFlag(bot_state_t * bs)2555 int BotDefendFlag(bot_state_t *bs)
2556 {
2557 wpobject_t *flagPoint;
2558 vec3_t a;
2559
2560 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
2561 {
2562 flagPoint = flagRed;
2563 }
2564 else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE)
2565 {
2566 flagPoint = flagBlue;
2567 }
2568 else
2569 {
2570 return 0;
2571 }
2572
2573 if (!flagPoint)
2574 {
2575 return 0;
2576 }
2577
2578 VectorSubtract(bs->origin, flagPoint->origin, a);
2579
2580 if (VectorLength(a) > BASE_GUARD_DISTANCE)
2581 {
2582 bs->wpDestination = flagPoint;
2583 }
2584
2585 return 1;
2586 }
2587
2588 //Keep our CTF priorities on getting the other team's flag
BotGetEnemyFlag(bot_state_t * bs)2589 int BotGetEnemyFlag(bot_state_t *bs)
2590 {
2591 wpobject_t *flagPoint;
2592 vec3_t a;
2593
2594 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
2595 {
2596 flagPoint = flagBlue;
2597 }
2598 else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE)
2599 {
2600 flagPoint = flagRed;
2601 }
2602 else
2603 {
2604 return 0;
2605 }
2606
2607 if (!flagPoint)
2608 {
2609 return 0;
2610 }
2611
2612 VectorSubtract(bs->origin, flagPoint->origin, a);
2613
2614 if (VectorLength(a) > BASE_GETENEMYFLAG_DISTANCE)
2615 {
2616 bs->wpDestination = flagPoint;
2617 }
2618
2619 return 1;
2620 }
2621
2622 //Our team's flag is gone, so try to get it back
BotGetFlagBack(bot_state_t * bs)2623 int BotGetFlagBack(bot_state_t *bs)
2624 {
2625 int i = 0;
2626 int myFlag = 0;
2627 int foundCarrier = 0;
2628 int tempInt = 0;
2629 gentity_t *ent = NULL;
2630 vec3_t usethisvec;
2631
2632 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
2633 {
2634 myFlag = PW_REDFLAG;
2635 }
2636 else
2637 {
2638 myFlag = PW_BLUEFLAG;
2639 }
2640
2641 while (i < MAX_CLIENTS)
2642 {
2643 ent = &g_entities[i];
2644
2645 if (ent && ent->client && ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
2646 {
2647 foundCarrier = 1;
2648 break;
2649 }
2650
2651 i++;
2652 }
2653
2654 if (!foundCarrier)
2655 {
2656 return 0;
2657 }
2658
2659 if (!ent)
2660 {
2661 return 0;
2662 }
2663
2664 if (bs->wpDestSwitchTime < level.time)
2665 {
2666 if (ent->client)
2667 {
2668 VectorCopy(ent->client->ps.origin, usethisvec);
2669 }
2670 else
2671 {
2672 VectorCopy(ent->s.origin, usethisvec);
2673 }
2674
2675 tempInt = GetNearestVisibleWP(usethisvec, 0);
2676
2677 if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
2678 {
2679 bs->wpDestination = gWPArray[tempInt];
2680 bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
2681 }
2682 }
2683
2684 return 1;
2685 }
2686
2687 //Someone else on our team has the enemy flag, so try to get
2688 //to their assistance
BotGuardFlagCarrier(bot_state_t * bs)2689 int BotGuardFlagCarrier(bot_state_t *bs)
2690 {
2691 int i = 0;
2692 int enemyFlag = 0;
2693 int foundCarrier = 0;
2694 int tempInt = 0;
2695 gentity_t *ent = NULL;
2696 vec3_t usethisvec;
2697
2698 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
2699 {
2700 enemyFlag = PW_BLUEFLAG;
2701 }
2702 else
2703 {
2704 enemyFlag = PW_REDFLAG;
2705 }
2706
2707 while (i < MAX_CLIENTS)
2708 {
2709 ent = &g_entities[i];
2710
2711 if (ent && ent->client && ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
2712 {
2713 foundCarrier = 1;
2714 break;
2715 }
2716
2717 i++;
2718 }
2719
2720 if (!foundCarrier)
2721 {
2722 return 0;
2723 }
2724
2725 if (!ent)
2726 {
2727 return 0;
2728 }
2729
2730 if (bs->wpDestSwitchTime < level.time)
2731 {
2732 if (ent->client)
2733 {
2734 VectorCopy(ent->client->ps.origin, usethisvec);
2735 }
2736 else
2737 {
2738 VectorCopy(ent->s.origin, usethisvec);
2739 }
2740
2741 tempInt = GetNearestVisibleWP(usethisvec, 0);
2742
2743 if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
2744 {
2745 bs->wpDestination = gWPArray[tempInt];
2746 bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
2747 }
2748 }
2749
2750 return 1;
2751 }
2752
2753 //We have the flag, let's get it home.
BotGetFlagHome(bot_state_t * bs)2754 int BotGetFlagHome(bot_state_t *bs)
2755 {
2756 wpobject_t *flagPoint;
2757 vec3_t a;
2758
2759 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
2760 {
2761 flagPoint = flagRed;
2762 }
2763 else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE)
2764 {
2765 flagPoint = flagBlue;
2766 }
2767 else
2768 {
2769 return 0;
2770 }
2771
2772 if (!flagPoint)
2773 {
2774 return 0;
2775 }
2776
2777 VectorSubtract(bs->origin, flagPoint->origin, a);
2778
2779 if (VectorLength(a) > BASE_FLAGWAIT_DISTANCE)
2780 {
2781 bs->wpDestination = flagPoint;
2782 }
2783
2784 return 1;
2785 }
2786
GetNewFlagPoint(wpobject_t * wp,gentity_t * flagEnt,int team)2787 void GetNewFlagPoint(wpobject_t *wp, gentity_t *flagEnt, int team)
2788 { //get the nearest possible waypoint to the flag since it's not in its original position
2789 int i = 0;
2790 vec3_t a, mins, maxs;
2791 float bestdist;
2792 float testdist;
2793 int bestindex = 0;
2794 int foundindex = 0;
2795 trace_t tr;
2796
2797 mins[0] = -15;
2798 mins[1] = -15;
2799 mins[2] = -5;
2800 maxs[0] = 15;
2801 maxs[1] = 15;
2802 maxs[2] = 5;
2803
2804 VectorSubtract(wp->origin, flagEnt->s.pos.trBase, a);
2805
2806 bestdist = VectorLength(a);
2807
2808 if (bestdist <= WP_KEEP_FLAG_DIST)
2809 {
2810 trap->Trace(&tr, wp->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID, qfalse, 0, 0);
2811
2812 if (tr.fraction == 1)
2813 { //this point is good
2814 return;
2815 }
2816 }
2817
2818 while (i < gWPNum)
2819 {
2820 VectorSubtract(gWPArray[i]->origin, flagEnt->s.pos.trBase, a);
2821 testdist = VectorLength(a);
2822
2823 if (testdist < bestdist)
2824 {
2825 trap->Trace(&tr, gWPArray[i]->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID, qfalse, 0, 0);
2826
2827 if (tr.fraction == 1)
2828 {
2829 foundindex = 1;
2830 bestindex = i;
2831 bestdist = testdist;
2832 }
2833 }
2834
2835 i++;
2836 }
2837
2838 if (foundindex)
2839 {
2840 if (team == TEAM_RED)
2841 {
2842 flagRed = gWPArray[bestindex];
2843 }
2844 else
2845 {
2846 flagBlue = gWPArray[bestindex];
2847 }
2848 }
2849 }
2850
2851 //See if our CTF state should take priority in our nav routines
CTFTakesPriority(bot_state_t * bs)2852 int CTFTakesPriority(bot_state_t *bs)
2853 {
2854 gentity_t *ent = NULL;
2855 int enemyFlag = 0;
2856 int myFlag = 0;
2857 int enemyHasOurFlag = 0;
2858 //int weHaveEnemyFlag = 0;
2859 int numOnMyTeam = 0;
2860 int numOnEnemyTeam = 0;
2861 int numAttackers = 0;
2862 int numDefenders = 0;
2863 int i = 0;
2864 int idleWP;
2865 int dosw = 0;
2866 wpobject_t *dest_sw = NULL;
2867 #ifdef BOT_CTF_DEBUG
2868 vec3_t t;
2869
2870 trap->Print("CTFSTATE: %s\n", ctfStateNames[bs->ctfState]);
2871 #endif
2872
2873 if (level.gametype != GT_CTF && level.gametype != GT_CTY)
2874 {
2875 return 0;
2876 }
2877
2878 if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
2879 (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME)
2880 { //get the nearest weapon laying around base before heading off for battle
2881 idleWP = GetBestIdleGoal(bs);
2882
2883 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
2884 {
2885 if (bs->wpDestSwitchTime < level.time)
2886 {
2887 bs->wpDestination = gWPArray[idleWP];
2888 }
2889 return 1;
2890 }
2891 }
2892 else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
2893 (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_CTF &&
2894 bs->wpDestination && bs->wpDestination->weight)
2895 {
2896 dest_sw = bs->wpDestination;
2897 dosw = 1;
2898 }
2899
2900 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
2901 {
2902 myFlag = PW_REDFLAG;
2903 }
2904 else
2905 {
2906 myFlag = PW_BLUEFLAG;
2907 }
2908
2909 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
2910 {
2911 enemyFlag = PW_BLUEFLAG;
2912 }
2913 else
2914 {
2915 enemyFlag = PW_REDFLAG;
2916 }
2917
2918 if (!flagRed || !flagBlue ||
2919 !flagRed->inuse || !flagBlue->inuse ||
2920 !eFlagRed || !eFlagBlue)
2921 {
2922 return 0;
2923 }
2924
2925 #ifdef BOT_CTF_DEBUG
2926 VectorCopy(flagRed->origin, t);
2927 t[2] += 128;
2928 G_TestLine(flagRed->origin, t, 0x0000ff, 500);
2929
2930 VectorCopy(flagBlue->origin, t);
2931 t[2] += 128;
2932 G_TestLine(flagBlue->origin, t, 0x0000ff, 500);
2933 #endif
2934
2935 if (droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM))
2936 {
2937 GetNewFlagPoint(flagRed, droppedRedFlag, TEAM_RED);
2938 }
2939 else
2940 {
2941 flagRed = oFlagRed;
2942 }
2943
2944 if (droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM))
2945 {
2946 GetNewFlagPoint(flagBlue, droppedBlueFlag, TEAM_BLUE);
2947 }
2948 else
2949 {
2950 flagBlue = oFlagBlue;
2951 }
2952
2953 if (!bs->ctfState)
2954 {
2955 return 0;
2956 }
2957
2958 i = 0;
2959
2960 while (i < MAX_CLIENTS)
2961 {
2962 ent = &g_entities[i];
2963
2964 if (ent && ent->client)
2965 {
2966 /*if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
2967 {
2968 weHaveEnemyFlag = 1;
2969 }
2970 else */if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
2971 {
2972 enemyHasOurFlag = 1;
2973 }
2974
2975 if (OnSameTeam(&g_entities[bs->client], ent))
2976 {
2977 numOnMyTeam++;
2978 }
2979 else
2980 {
2981 numOnEnemyTeam++;
2982 }
2983
2984 if (botstates[ent->s.number])
2985 {
2986 if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER ||
2987 botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL)
2988 {
2989 numAttackers++;
2990 }
2991 else
2992 {
2993 numDefenders++;
2994 }
2995 }
2996 else
2997 { //assume real players to be attackers in our logic
2998 numAttackers++;
2999 }
3000 }
3001 i++;
3002 }
3003
3004 if (bs->cur_ps.powerups[enemyFlag])
3005 {
3006 if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag)
3007 {
3008 bs->ctfState = CTFSTATE_RETRIEVAL;
3009 }
3010 else
3011 {
3012 bs->ctfState = CTFSTATE_GETFLAGHOME;
3013 }
3014 }
3015 else if (bs->ctfState == CTFSTATE_GETFLAGHOME)
3016 {
3017 bs->ctfState = 0;
3018 }
3019
3020 if (bs->state_Forced)
3021 {
3022 bs->ctfState = bs->state_Forced;
3023 }
3024
3025 if (bs->ctfState == CTFSTATE_DEFENDER)
3026 {
3027 if (BotDefendFlag(bs))
3028 {
3029 goto success;
3030 }
3031 }
3032
3033 if (bs->ctfState == CTFSTATE_ATTACKER)
3034 {
3035 if (BotGetEnemyFlag(bs))
3036 {
3037 goto success;
3038 }
3039 }
3040
3041 if (bs->ctfState == CTFSTATE_RETRIEVAL)
3042 {
3043 if (BotGetFlagBack(bs))
3044 {
3045 goto success;
3046 }
3047 else
3048 { //can't find anyone on another team being a carrier, so ignore this priority
3049 bs->ctfState = 0;
3050 }
3051 }
3052
3053 if (bs->ctfState == CTFSTATE_GUARDCARRIER)
3054 {
3055 if (BotGuardFlagCarrier(bs))
3056 {
3057 goto success;
3058 }
3059 else
3060 { //can't find anyone on our team being a carrier, so ignore this priority
3061 bs->ctfState = 0;
3062 }
3063 }
3064
3065 if (bs->ctfState == CTFSTATE_GETFLAGHOME)
3066 {
3067 if (BotGetFlagHome(bs))
3068 {
3069 goto success;
3070 }
3071 }
3072
3073 return 0;
3074
3075 success:
3076 if (dosw)
3077 { //allow ctf code to run, but if after a particular item then keep going after it
3078 bs->wpDestination = dest_sw;
3079 }
3080
3081 return 1;
3082 }
3083
EntityVisibleBox(vec3_t org1,vec3_t mins,vec3_t maxs,vec3_t org2,int ignore,int ignore2)3084 int EntityVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore, int ignore2)
3085 {
3086 trace_t tr;
3087
3088 trap->Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID, qfalse, 0, 0);
3089
3090 if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid)
3091 {
3092 return 1;
3093 }
3094 else if (tr.entityNum != ENTITYNUM_NONE && tr.entityNum == ignore2)
3095 {
3096 return 1;
3097 }
3098
3099 return 0;
3100 }
3101
3102 //Get the closest objective for siege and go after it
Siege_TargetClosestObjective(bot_state_t * bs,int flag)3103 int Siege_TargetClosestObjective(bot_state_t *bs, int flag)
3104 {
3105 int i = 0;
3106 int bestindex = -1;
3107 float testdistance = 0;
3108 float bestdistance = 999999999.9f;
3109 gentity_t *goalent;
3110 vec3_t a, dif;
3111 vec3_t mins, maxs;
3112
3113 mins[0] = -1;
3114 mins[1] = -1;
3115 mins[2] = -1;
3116
3117 maxs[0] = 1;
3118 maxs[1] = 1;
3119 maxs[2] = 1;
3120
3121 if ( bs->wpDestination && (bs->wpDestination->flags & flag) && bs->wpDestination->associated_entity != ENTITYNUM_NONE &&
3122 &g_entities[bs->wpDestination->associated_entity] && g_entities[bs->wpDestination->associated_entity].use )
3123 {
3124 goto hasPoint;
3125 }
3126
3127 while (i < gWPNum)
3128 {
3129 if ( gWPArray[i] && gWPArray[i]->inuse && (gWPArray[i]->flags & flag) && gWPArray[i]->associated_entity != ENTITYNUM_NONE &&
3130 &g_entities[gWPArray[i]->associated_entity] && g_entities[gWPArray[i]->associated_entity].use )
3131 {
3132 VectorSubtract(gWPArray[i]->origin, bs->origin, a);
3133 testdistance = VectorLength(a);
3134
3135 if (testdistance < bestdistance)
3136 {
3137 bestdistance = testdistance;
3138 bestindex = i;
3139 }
3140 }
3141
3142 i++;
3143 }
3144
3145 if (bestindex != -1)
3146 {
3147 bs->wpDestination = gWPArray[bestindex];
3148 }
3149 else
3150 {
3151 return 0;
3152 }
3153 hasPoint:
3154 goalent = &g_entities[bs->wpDestination->associated_entity];
3155
3156 if (!goalent)
3157 {
3158 return 0;
3159 }
3160
3161 VectorSubtract(bs->origin, bs->wpDestination->origin, a);
3162
3163 testdistance = VectorLength(a);
3164
3165 dif[0] = (goalent->r.absmax[0]+goalent->r.absmin[0])/2;
3166 dif[1] = (goalent->r.absmax[1]+goalent->r.absmin[1])/2;
3167 dif[2] = (goalent->r.absmax[2]+goalent->r.absmin[2])/2;
3168 //brush models can have tricky origins, so this is our hacky method of getting the center point
3169
3170 if (goalent->takedamage && testdistance < BOT_MIN_SIEGE_GOAL_SHOOT &&
3171 EntityVisibleBox(bs->origin, mins, maxs, dif, bs->client, goalent->s.number))
3172 {
3173 bs->shootGoal = goalent;
3174 bs->touchGoal = NULL;
3175 }
3176 else if (goalent->use && testdistance < BOT_MIN_SIEGE_GOAL_TRAVEL)
3177 {
3178 bs->shootGoal = NULL;
3179 bs->touchGoal = goalent;
3180 }
3181 else
3182 { //don't know how to handle this goal object!
3183 bs->shootGoal = NULL;
3184 bs->touchGoal = NULL;
3185 }
3186
3187 if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE ||
3188 BotGetWeaponRange(bs) == BWEAPONRANGE_SABER)
3189 {
3190 bs->shootGoal = NULL; //too risky
3191 }
3192
3193 if (bs->touchGoal)
3194 {
3195 //trap->Print("Please, master, let me touch it!\n");
3196 VectorCopy(dif, bs->goalPosition);
3197 }
3198
3199 return 1;
3200 }
3201
Siege_DefendFromAttackers(bot_state_t * bs)3202 void Siege_DefendFromAttackers(bot_state_t *bs)
3203 { //this may be a little cheap, but the best way to find our defending point is probably
3204 //to just find the nearest person on the opposing team since they'll most likely
3205 //be on offense in this situation
3206 int wpClose = -1;
3207 int i = 0;
3208 float testdist = 999999;
3209 int bestindex = -1;
3210 float bestdist = 999999;
3211 gentity_t *ent;
3212 vec3_t a;
3213
3214 while (i < MAX_CLIENTS)
3215 {
3216 ent = &g_entities[i];
3217
3218 if (ent && ent->client && ent->client->sess.sessionTeam != g_entities[bs->client].client->sess.sessionTeam &&
3219 ent->health > 0 && ent->client->sess.sessionTeam != TEAM_SPECTATOR)
3220 {
3221 VectorSubtract(ent->client->ps.origin, bs->origin, a);
3222
3223 testdist = VectorLength(a);
3224
3225 if (testdist < bestdist)
3226 {
3227 bestindex = i;
3228 bestdist = testdist;
3229 }
3230 }
3231
3232 i++;
3233 }
3234
3235 if (bestindex == -1)
3236 {
3237 return;
3238 }
3239
3240 wpClose = GetNearestVisibleWP(g_entities[bestindex].client->ps.origin, -1);
3241
3242 if (wpClose != -1 && gWPArray[wpClose] && gWPArray[wpClose]->inuse)
3243 {
3244 bs->wpDestination = gWPArray[wpClose];
3245 bs->destinationGrabTime = level.time + 10000;
3246 }
3247 }
3248
3249 //how many defenders on our team?
Siege_CountDefenders(bot_state_t * bs)3250 int Siege_CountDefenders(bot_state_t *bs)
3251 {
3252 int i = 0;
3253 int num = 0;
3254 gentity_t *ent;
3255 bot_state_t *bot;
3256
3257 while (i < MAX_CLIENTS)
3258 {
3259 ent = &g_entities[i];
3260 bot = botstates[i];
3261
3262 if (ent && ent->client && bot)
3263 {
3264 if (bot->siegeState == SIEGESTATE_DEFENDER &&
3265 ent->client->sess.sessionTeam == g_entities[bs->client].client->sess.sessionTeam)
3266 {
3267 num++;
3268 }
3269 }
3270
3271 i++;
3272 }
3273
3274 return num;
3275 }
3276
3277 //how many other players on our team?
Siege_CountTeammates(bot_state_t * bs)3278 int Siege_CountTeammates(bot_state_t *bs)
3279 {
3280 int i = 0;
3281 int num = 0;
3282 gentity_t *ent;
3283
3284 while (i < MAX_CLIENTS)
3285 {
3286 ent = &g_entities[i];
3287
3288 if (ent && ent->client)
3289 {
3290 if (ent->client->sess.sessionTeam == g_entities[bs->client].client->sess.sessionTeam)
3291 {
3292 num++;
3293 }
3294 }
3295
3296 i++;
3297 }
3298
3299 return num;
3300 }
3301
3302 //see if siege objective completion should take priority in our
3303 //nav routines.
SiegeTakesPriority(bot_state_t * bs)3304 int SiegeTakesPriority(bot_state_t *bs)
3305 {
3306 int attacker;
3307 //int flagForDefendableObjective;
3308 int flagForAttackableObjective;
3309 int defenders, teammates;
3310 int idleWP;
3311 wpobject_t *dest_sw = NULL;
3312 int dosw = 0;
3313 gclient_t *bcl;
3314 vec3_t dif;
3315 trace_t tr;
3316
3317 if (level.gametype != GT_SIEGE)
3318 {
3319 return 0;
3320 }
3321
3322 bcl = g_entities[bs->client].client;
3323
3324 if (!bcl)
3325 {
3326 return 0;
3327 }
3328
3329 if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
3330 (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME)
3331 { //get the nearest weapon laying around base before heading off for battle
3332 idleWP = GetBestIdleGoal(bs);
3333
3334 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
3335 {
3336 if (bs->wpDestSwitchTime < level.time)
3337 {
3338 bs->wpDestination = gWPArray[idleWP];
3339 }
3340 return 1;
3341 }
3342 }
3343 else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL &&
3344 (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_TIME &&
3345 bs->wpDestination && bs->wpDestination->weight)
3346 {
3347 dest_sw = bs->wpDestination;
3348 dosw = 1;
3349 }
3350
3351 if (bcl->sess.sessionTeam == SIEGETEAM_TEAM1)
3352 {
3353 attacker = imperial_attackers;
3354 //flagForDefendableObjective = WPFLAG_SIEGE_REBELOBJ;
3355 flagForAttackableObjective = WPFLAG_SIEGE_IMPERIALOBJ;
3356 }
3357 else
3358 {
3359 attacker = rebel_attackers;
3360 //flagForDefendableObjective = WPFLAG_SIEGE_IMPERIALOBJ;
3361 flagForAttackableObjective = WPFLAG_SIEGE_REBELOBJ;
3362 }
3363
3364 if (attacker)
3365 {
3366 bs->siegeState = SIEGESTATE_ATTACKER;
3367 }
3368 else
3369 {
3370 bs->siegeState = SIEGESTATE_DEFENDER;
3371 defenders = Siege_CountDefenders(bs);
3372 teammates = Siege_CountTeammates(bs);
3373
3374 if (defenders > teammates/3 && teammates > 1)
3375 { //devote around 1/4 of our team to completing our own side goals even if we're a defender.
3376 //If we have no side goals we will realize that later on and join the defenders
3377 bs->siegeState = SIEGESTATE_ATTACKER;
3378 }
3379 }
3380
3381 if (bs->state_Forced)
3382 {
3383 bs->siegeState = bs->state_Forced;
3384 }
3385
3386 if (bs->siegeState == SIEGESTATE_ATTACKER)
3387 {
3388 if (!Siege_TargetClosestObjective(bs, flagForAttackableObjective))
3389 { //looks like we have no goals other than to keep the other team from completing objectives
3390 Siege_DefendFromAttackers(bs);
3391 if (bs->shootGoal)
3392 {
3393 dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
3394 dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
3395 dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
3396
3397 if (!BotPVSCheck(bs->origin, dif))
3398 {
3399 bs->shootGoal = NULL;
3400 }
3401 else
3402 {
3403 trap->Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID, qfalse, 0, 0);
3404
3405 if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number)
3406 {
3407 bs->shootGoal = NULL;
3408 }
3409 }
3410 }
3411 }
3412 }
3413 else if (bs->siegeState == SIEGESTATE_DEFENDER)
3414 {
3415 Siege_DefendFromAttackers(bs);
3416 if (bs->shootGoal)
3417 {
3418 dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
3419 dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
3420 dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
3421
3422 if (!BotPVSCheck(bs->origin, dif))
3423 {
3424 bs->shootGoal = NULL;
3425 }
3426 else
3427 {
3428 trap->Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID, qfalse, 0, 0);
3429
3430 if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number)
3431 {
3432 bs->shootGoal = NULL;
3433 }
3434 }
3435 }
3436 }
3437 else
3438 { //get busy!
3439 Siege_TargetClosestObjective(bs, flagForAttackableObjective);
3440 if (bs->shootGoal)
3441 {
3442 dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
3443 dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
3444 dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
3445
3446 if (!BotPVSCheck(bs->origin, dif))
3447 {
3448 bs->shootGoal = NULL;
3449 }
3450 else
3451 {
3452 trap->Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID, qfalse, 0, 0);
3453
3454 if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number)
3455 {
3456 bs->shootGoal = NULL;
3457 }
3458 }
3459 }
3460 }
3461
3462 if (dosw)
3463 { //allow siege objective code to run, but if after a particular item then keep going after it
3464 bs->wpDestination = dest_sw;
3465 }
3466
3467 return 1;
3468 }
3469
3470 //see if jedi master priorities should take priority in our nav
3471 //routines.
JMTakesPriority(bot_state_t * bs)3472 int JMTakesPriority(bot_state_t *bs)
3473 {
3474 int i = 0;
3475 int wpClose = -1;
3476 gentity_t *theImportantEntity = NULL;
3477
3478 if (level.gametype != GT_JEDIMASTER)
3479 {
3480 return 0;
3481 }
3482
3483 if (bs->cur_ps.isJediMaster)
3484 {
3485 return 0;
3486 }
3487
3488 //jmState becomes the index for the one who carries the saber. If jmState is -1 then the saber is currently
3489 //without an owner
3490 bs->jmState = -1;
3491
3492 while (i < MAX_CLIENTS)
3493 {
3494 if (g_entities[i].client && g_entities[i].inuse &&
3495 g_entities[i].client->ps.isJediMaster)
3496 {
3497 bs->jmState = i;
3498 break;
3499 }
3500
3501 i++;
3502 }
3503
3504 if (bs->jmState != -1)
3505 {
3506 theImportantEntity = &g_entities[bs->jmState];
3507 }
3508 else
3509 {
3510 theImportantEntity = gJMSaberEnt;
3511 }
3512
3513 if (theImportantEntity && theImportantEntity->inuse && bs->destinationGrabTime < level.time)
3514 {
3515 if (theImportantEntity->client)
3516 {
3517 wpClose = GetNearestVisibleWP(theImportantEntity->client->ps.origin, theImportantEntity->s.number);
3518 }
3519 else
3520 {
3521 wpClose = GetNearestVisibleWP(theImportantEntity->r.currentOrigin, theImportantEntity->s.number);
3522 }
3523
3524 if (wpClose != -1 && gWPArray[wpClose] && gWPArray[wpClose]->inuse)
3525 {
3526 /*
3527 Com_Printf("BOT GRABBED IDEAL JM LOCATION\n");
3528 if (bs->wpDestination != gWPArray[wpClose])
3529 {
3530 Com_Printf("IDEAL WAS NOT ALREADY IDEAL\n");
3531
3532 if (!bs->wpDestination)
3533 {
3534 Com_Printf("IDEAL WAS NULL\n");
3535 }
3536 }
3537 */
3538 bs->wpDestination = gWPArray[wpClose];
3539 bs->destinationGrabTime = level.time + 4000;
3540 }
3541 }
3542
3543 return 1;
3544 }
3545
3546 //see if we already have an item/powerup/etc. that is associated
3547 //with this waypoint.
BotHasAssociated(bot_state_t * bs,wpobject_t * wp)3548 int BotHasAssociated(bot_state_t *bs, wpobject_t *wp)
3549 {
3550 gentity_t *as;
3551
3552 if (wp->associated_entity == ENTITYNUM_NONE)
3553 { //make it think this is an item we have so we don't go after nothing
3554 return 1;
3555 }
3556
3557 as = &g_entities[wp->associated_entity];
3558
3559 if (!as || !as->item)
3560 {
3561 return 0;
3562 }
3563
3564 if (as->item->giType == IT_WEAPON)
3565 {
3566 if (bs->cur_ps.stats[STAT_WEAPONS] & (1 << as->item->giTag))
3567 {
3568 return 1;
3569 }
3570
3571 return 0;
3572 }
3573 else if (as->item->giType == IT_HOLDABLE)
3574 {
3575 if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << as->item->giTag))
3576 {
3577 return 1;
3578 }
3579
3580 return 0;
3581 }
3582 else if (as->item->giType == IT_POWERUP)
3583 {
3584 if (bs->cur_ps.powerups[as->item->giTag])
3585 {
3586 return 1;
3587 }
3588
3589 return 0;
3590 }
3591 else if (as->item->giType == IT_AMMO)
3592 {
3593 if (bs->cur_ps.ammo[as->item->giTag] > 10) //hack
3594 {
3595 return 1;
3596 }
3597
3598 return 0;
3599 }
3600
3601 return 0;
3602 }
3603
3604 //we don't really have anything we want to do right now,
3605 //let's just find the best thing to do given the current
3606 //situation.
GetBestIdleGoal(bot_state_t * bs)3607 int GetBestIdleGoal(bot_state_t *bs)
3608 {
3609 int i = 0;
3610 int highestweight = 0;
3611 int desiredindex = -1;
3612 int dist_to_weight = 0;
3613 int traildist;
3614
3615 if (!bs->wpCurrent)
3616 {
3617 return -1;
3618 }
3619
3620 if (bs->isCamper != 2)
3621 {
3622 if (bs->randomNavTime < level.time)
3623 {
3624 if (Q_irand(1, 10) < 5)
3625 {
3626 bs->randomNav = 1;
3627 }
3628 else
3629 {
3630 bs->randomNav = 0;
3631 }
3632
3633 bs->randomNavTime = level.time + Q_irand(5000, 15000);
3634 }
3635 }
3636
3637 if (bs->randomNav)
3638 { //stop looking for items and/or camping on them
3639 return -1;
3640 }
3641
3642 while (i < gWPNum)
3643 {
3644 if (gWPArray[i] &&
3645 gWPArray[i]->inuse &&
3646 (gWPArray[i]->flags & WPFLAG_GOALPOINT) &&
3647 gWPArray[i]->weight > highestweight &&
3648 !BotHasAssociated(bs, gWPArray[i]))
3649 {
3650 traildist = TotalTrailDistance(bs->wpCurrent->index, i, bs);
3651
3652 if (traildist != -1)
3653 {
3654 dist_to_weight = (int)traildist/10000;
3655 dist_to_weight = (gWPArray[i]->weight)-dist_to_weight;
3656
3657 if (dist_to_weight > highestweight)
3658 {
3659 highestweight = dist_to_weight;
3660 desiredindex = i;
3661 }
3662 }
3663 }
3664
3665 i++;
3666 }
3667
3668 return desiredindex;
3669 }
3670
3671 //go through the list of possible priorities for navigating
3672 //and work out the best destination point.
GetIdealDestination(bot_state_t * bs)3673 void GetIdealDestination(bot_state_t *bs)
3674 {
3675 int tempInt, cWPIndex, bChicken, idleWP;
3676 float distChange, plusLen, minusLen;
3677 vec3_t usethisvec, a;
3678 gentity_t *badthing;
3679
3680 #ifdef _DEBUG
3681 trap->Cvar_Update(&bot_nogoals);
3682
3683 if (bot_nogoals.integer)
3684 {
3685 return;
3686 }
3687 #endif
3688
3689 if (!bs->wpCurrent)
3690 {
3691 return;
3692 }
3693
3694 if ((level.time - bs->escapeDirTime) > 4000)
3695 {
3696 badthing = GetNearestBadThing(bs);
3697 }
3698 else
3699 {
3700 badthing = NULL;
3701 }
3702
3703 if (badthing && badthing->inuse &&
3704 badthing->health > 0 && badthing->takedamage)
3705 {
3706 bs->dangerousObject = badthing;
3707 }
3708 else
3709 {
3710 bs->dangerousObject = NULL;
3711 }
3712
3713 if (!badthing && bs->wpDestIgnoreTime > level.time)
3714 {
3715 return;
3716 }
3717
3718 if (!badthing && bs->dontGoBack > level.time)
3719 {
3720 if (bs->wpDestination)
3721 {
3722 bs->wpStoreDest = bs->wpDestination;
3723 }
3724 bs->wpDestination = NULL;
3725 return;
3726 }
3727 else if (!badthing && bs->wpStoreDest)
3728 { //after we finish running away, switch back to our original destination
3729 bs->wpDestination = bs->wpStoreDest;
3730 bs->wpStoreDest = NULL;
3731 }
3732
3733 if (badthing && bs->wpCamping)
3734 {
3735 bs->wpCamping = NULL;
3736 }
3737
3738 if (bs->wpCamping)
3739 {
3740 bs->wpDestination = bs->wpCamping;
3741 return;
3742 }
3743
3744 if (!badthing && CTFTakesPriority(bs))
3745 {
3746 if (bs->ctfState)
3747 {
3748 bs->runningToEscapeThreat = 1;
3749 }
3750 return;
3751 }
3752 else if (!badthing && SiegeTakesPriority(bs))
3753 {
3754 if (bs->siegeState)
3755 {
3756 bs->runningToEscapeThreat = 1;
3757 }
3758 return;
3759 }
3760 else if (!badthing && JMTakesPriority(bs))
3761 {
3762 bs->runningToEscapeThreat = 1;
3763 }
3764
3765 if (badthing)
3766 {
3767 bs->runningLikeASissy = level.time + 100;
3768
3769 if (bs->wpDestination)
3770 {
3771 bs->wpStoreDest = bs->wpDestination;
3772 }
3773 bs->wpDestination = NULL;
3774
3775 if (bs->wpDirection)
3776 {
3777 tempInt = bs->wpCurrent->index+1;
3778 }
3779 else
3780 {
3781 tempInt = bs->wpCurrent->index-1;
3782 }
3783
3784 if (gWPArray[tempInt] && gWPArray[tempInt]->inuse && bs->escapeDirTime < level.time)
3785 {
3786 VectorSubtract(badthing->s.pos.trBase, bs->wpCurrent->origin, a);
3787 plusLen = VectorLength(a);
3788 VectorSubtract(badthing->s.pos.trBase, gWPArray[tempInt]->origin, a);
3789 minusLen = VectorLength(a);
3790
3791 if (plusLen < minusLen)
3792 {
3793 if (bs->wpDirection)
3794 {
3795 bs->wpDirection = 0;
3796 }
3797 else
3798 {
3799 bs->wpDirection = 1;
3800 }
3801
3802 bs->wpCurrent = gWPArray[tempInt];
3803
3804 bs->escapeDirTime = level.time + Q_irand(500, 1000);//Q_irand(1000, 1400);
3805
3806 //trap->Print("Escaping from scary bad thing [%s]\n", badthing->classname);
3807 }
3808 }
3809 //trap->Print("Run away run away run away!\n");
3810 return;
3811 }
3812
3813 distChange = 0; //keep the compiler from complaining
3814
3815 tempInt = BotGetWeaponRange(bs);
3816
3817 if (tempInt == BWEAPONRANGE_MELEE)
3818 {
3819 distChange = 1;
3820 }
3821 else if (tempInt == BWEAPONRANGE_SABER)
3822 {
3823 distChange = 1;
3824 }
3825 else if (tempInt == BWEAPONRANGE_MID)
3826 {
3827 distChange = 128;
3828 }
3829 else if (tempInt == BWEAPONRANGE_LONG)
3830 {
3831 distChange = 300;
3832 }
3833
3834 if (bs->revengeEnemy && bs->revengeEnemy->health > 0 &&
3835 bs->revengeEnemy->client && bs->revengeEnemy->client->pers.connected == CON_CONNECTED)
3836 { //if we hate someone, always try to get to them
3837 if (bs->wpDestSwitchTime < level.time)
3838 {
3839 if (bs->revengeEnemy->client)
3840 {
3841 VectorCopy(bs->revengeEnemy->client->ps.origin, usethisvec);
3842 }
3843 else
3844 {
3845 VectorCopy(bs->revengeEnemy->s.origin, usethisvec);
3846 }
3847
3848 tempInt = GetNearestVisibleWP(usethisvec, 0);
3849
3850 if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
3851 {
3852 bs->wpDestination = gWPArray[tempInt];
3853 bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000);
3854 }
3855 }
3856 }
3857 else if (bs->squadLeader && bs->squadLeader->health > 0 &&
3858 bs->squadLeader->client && bs->squadLeader->client->pers.connected == CON_CONNECTED)
3859 {
3860 if (bs->wpDestSwitchTime < level.time)
3861 {
3862 if (bs->squadLeader->client)
3863 {
3864 VectorCopy(bs->squadLeader->client->ps.origin, usethisvec);
3865 }
3866 else
3867 {
3868 VectorCopy(bs->squadLeader->s.origin, usethisvec);
3869 }
3870
3871 tempInt = GetNearestVisibleWP(usethisvec, 0);
3872
3873 if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
3874 {
3875 bs->wpDestination = gWPArray[tempInt];
3876 bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000);
3877 }
3878 }
3879 }
3880 else if (bs->currentEnemy)
3881 {
3882 if (bs->currentEnemy->client)
3883 {
3884 VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
3885 }
3886 else
3887 {
3888 VectorCopy(bs->currentEnemy->s.origin, usethisvec);
3889 }
3890
3891 bChicken = BotIsAChickenWuss(bs);
3892 bs->runningToEscapeThreat = bChicken;
3893
3894 if (bs->frame_Enemy_Len < distChange || (bChicken && bChicken != 2))
3895 {
3896 cWPIndex = bs->wpCurrent->index;
3897
3898 if (bs->frame_Enemy_Len > 400)
3899 { //good distance away, start running toward a good place for an item or powerup or whatever
3900 idleWP = GetBestIdleGoal(bs);
3901
3902 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
3903 {
3904 bs->wpDestination = gWPArray[idleWP];
3905 }
3906 }
3907 else if (gWPArray[cWPIndex-1] && gWPArray[cWPIndex-1]->inuse &&
3908 gWPArray[cWPIndex+1] && gWPArray[cWPIndex+1]->inuse)
3909 {
3910 VectorSubtract(gWPArray[cWPIndex+1]->origin, usethisvec, a);
3911 plusLen = VectorLength(a);
3912 VectorSubtract(gWPArray[cWPIndex-1]->origin, usethisvec, a);
3913 minusLen = VectorLength(a);
3914
3915 if (minusLen > plusLen)
3916 {
3917 bs->wpDestination = gWPArray[cWPIndex-1];
3918 }
3919 else
3920 {
3921 bs->wpDestination = gWPArray[cWPIndex+1];
3922 }
3923 }
3924 }
3925 else if (bChicken != 2 && bs->wpDestSwitchTime < level.time)
3926 {
3927 tempInt = GetNearestVisibleWP(usethisvec, 0);
3928
3929 if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1)
3930 {
3931 bs->wpDestination = gWPArray[tempInt];
3932
3933 if (level.gametype == GT_SINGLE_PLAYER)
3934 { //be more aggressive
3935 bs->wpDestSwitchTime = level.time + Q_irand(300, 1000);
3936 }
3937 else
3938 {
3939 bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000);
3940 }
3941 }
3942 }
3943 }
3944
3945 if (!bs->wpDestination && bs->wpDestSwitchTime < level.time)
3946 {
3947 //trap->Print("I need something to do\n");
3948 idleWP = GetBestIdleGoal(bs);
3949
3950 if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse)
3951 {
3952 bs->wpDestination = gWPArray[idleWP];
3953 }
3954 }
3955 }
3956
3957 //commander CTF AI - tell other bots in the so-called
3958 //"squad" what to do.
CommanderBotCTFAI(bot_state_t * bs)3959 void CommanderBotCTFAI(bot_state_t *bs)
3960 {
3961 int i = 0;
3962 gentity_t *ent;
3963 int squadmates = 0;
3964 gentity_t *squad[MAX_CLIENTS];
3965 int defendAttackPriority = 0; //0 == attack, 1 == defend
3966 int guardDefendPriority = 0; //0 == defend, 1 == guard
3967 int attackRetrievePriority = 0; //0 == retrieve, 1 == attack
3968 int myFlag = 0;
3969 int enemyFlag = 0;
3970 int enemyHasOurFlag = 0;
3971 int weHaveEnemyFlag = 0;
3972 int numOnMyTeam = 0;
3973 int numOnEnemyTeam = 0;
3974 int numAttackers = 0;
3975 int numDefenders = 0;
3976
3977 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
3978 {
3979 myFlag = PW_REDFLAG;
3980 }
3981 else
3982 {
3983 myFlag = PW_BLUEFLAG;
3984 }
3985
3986 if (level.clients[bs->client].sess.sessionTeam == TEAM_RED)
3987 {
3988 enemyFlag = PW_BLUEFLAG;
3989 }
3990 else
3991 {
3992 enemyFlag = PW_REDFLAG;
3993 }
3994
3995 while (i < MAX_CLIENTS)
3996 {
3997 ent = &g_entities[i];
3998
3999 if (ent && ent->client)
4000 {
4001 if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent))
4002 {
4003 weHaveEnemyFlag = 1;
4004 }
4005 else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent))
4006 {
4007 enemyHasOurFlag = 1;
4008 }
4009
4010 if (OnSameTeam(&g_entities[bs->client], ent))
4011 {
4012 numOnMyTeam++;
4013 }
4014 else
4015 {
4016 numOnEnemyTeam++;
4017 }
4018
4019 if (botstates[ent->s.number])
4020 {
4021 if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER ||
4022 botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL)
4023 {
4024 numAttackers++;
4025 }
4026 else
4027 {
4028 numDefenders++;
4029 }
4030 }
4031 else
4032 { //assume real players to be attackers in our logic
4033 numAttackers++;
4034 }
4035 }
4036 i++;
4037 }
4038
4039 i = 0;
4040
4041 while (i < MAX_CLIENTS)
4042 {
4043 ent = &g_entities[i];
4044
4045 if (ent && ent->client && botstates[i] && botstates[i]->squadLeader && botstates[i]->squadLeader->s.number == bs->client && i != bs->client)
4046 {
4047 squad[squadmates] = ent;
4048 squadmates++;
4049 }
4050
4051 i++;
4052 }
4053
4054 squad[squadmates] = &g_entities[bs->client];
4055 squadmates++;
4056
4057 i = 0;
4058
4059 if (enemyHasOurFlag && !weHaveEnemyFlag)
4060 { //start off with an attacker instead of a retriever if we don't have the enemy flag yet so that they can't capture it first.
4061 //after that we focus on getting our flag back.
4062 attackRetrievePriority = 1;
4063 }
4064
4065 while (i < squadmates)
4066 {
4067 if (squad[i] && squad[i]->client && botstates[squad[i]->s.number])
4068 {
4069 if (botstates[squad[i]->s.number]->ctfState != CTFSTATE_GETFLAGHOME)
4070 { //never tell a bot to stop trying to bring the flag to the base
4071 if (defendAttackPriority)
4072 {
4073 if (weHaveEnemyFlag)
4074 {
4075 if (guardDefendPriority)
4076 {
4077 botstates[squad[i]->s.number]->ctfState = CTFSTATE_GUARDCARRIER;
4078 guardDefendPriority = 0;
4079 }
4080 else
4081 {
4082 botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER;
4083 guardDefendPriority = 1;
4084 }
4085 }
4086 else
4087 {
4088 botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER;
4089 }
4090 defendAttackPriority = 0;
4091 }
4092 else
4093 {
4094 if (enemyHasOurFlag)
4095 {
4096 if (attackRetrievePriority)
4097 {
4098 botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER;
4099 attackRetrievePriority = 0;
4100 }
4101 else
4102 {
4103 botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL;
4104 attackRetrievePriority = 1;
4105 }
4106 }
4107 else
4108 {
4109 botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER;
4110 }
4111 defendAttackPriority = 1;
4112 }
4113 }
4114 else if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag)
4115 { //I'm the only one on my team who will attack and the enemy has my flag, I have to go after him
4116 botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL;
4117 }
4118 }
4119
4120 i++;
4121 }
4122 }
4123
4124 //similar to ctf ai, for siege
CommanderBotSiegeAI(bot_state_t * bs)4125 void CommanderBotSiegeAI(bot_state_t *bs)
4126 {
4127 int i = 0;
4128 int squadmates = 0;
4129 int commanded = 0;
4130 int teammates = 0;
4131 gentity_t *squad[MAX_CLIENTS];
4132 gentity_t *ent;
4133 bot_state_t *bst;
4134
4135 while (i < MAX_CLIENTS)
4136 {
4137 ent = &g_entities[i];
4138
4139 if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number])
4140 {
4141 bst = botstates[ent->s.number];
4142
4143 if (bst && !bst->isSquadLeader && !bst->state_Forced)
4144 {
4145 squad[squadmates] = ent;
4146 squadmates++;
4147 }
4148 else if (bst && !bst->isSquadLeader && bst->state_Forced)
4149 { //count them as commanded
4150 commanded++;
4151 }
4152 }
4153
4154 if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent))
4155 {
4156 teammates++;
4157 }
4158
4159 i++;
4160 }
4161
4162 if (!squadmates)
4163 {
4164 return;
4165 }
4166
4167 //tell squad mates to do what I'm doing, up to half of team, let the other half make their own decisions
4168 i = 0;
4169
4170 while (i < squadmates && squad[i])
4171 {
4172 bst = botstates[squad[i]->s.number];
4173
4174 if (commanded > teammates/2)
4175 {
4176 break;
4177 }
4178
4179 if (bst)
4180 {
4181 bst->state_Forced = bs->siegeState;
4182 bst->siegeState = bs->siegeState;
4183 commanded++;
4184 }
4185
4186 i++;
4187 }
4188 }
4189
4190 //teamplay ffa squad ai
BotDoTeamplayAI(bot_state_t * bs)4191 void BotDoTeamplayAI(bot_state_t *bs)
4192 {
4193 if (bs->state_Forced)
4194 {
4195 bs->teamplayState = bs->state_Forced;
4196 }
4197
4198 if (bs->teamplayState == TEAMPLAYSTATE_REGROUP)
4199 { //force to find a new leader
4200 bs->squadLeader = NULL;
4201 bs->isSquadLeader = 0;
4202 }
4203 }
4204
4205 //like ctf and siege commander ai, instruct the squad
CommanderBotTeamplayAI(bot_state_t * bs)4206 void CommanderBotTeamplayAI(bot_state_t *bs)
4207 {
4208 int i = 0;
4209 int squadmates = 0;
4210 int teammates = 0;
4211 int teammate_indanger = -1;
4212 int teammate_helped = 0;
4213 int foundsquadleader = 0;
4214 int worsthealth = 50;
4215 gentity_t *squad[MAX_CLIENTS];
4216 gentity_t *ent;
4217 bot_state_t *bst;
4218
4219 while (i < MAX_CLIENTS)
4220 {
4221 ent = &g_entities[i];
4222
4223 if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number])
4224 {
4225 bst = botstates[ent->s.number];
4226
4227 if (foundsquadleader && bst && bst->isSquadLeader)
4228 { //never more than one squad leader
4229 bst->isSquadLeader = 0;
4230 }
4231
4232 if (bst && !bst->isSquadLeader)
4233 {
4234 squad[squadmates] = ent;
4235 squadmates++;
4236 }
4237 else if (bst)
4238 {
4239 foundsquadleader = 1;
4240 }
4241 }
4242
4243 if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent))
4244 {
4245 teammates++;
4246
4247 if (ent->health < worsthealth)
4248 {
4249 teammate_indanger = ent->s.number;
4250 worsthealth = ent->health;
4251 }
4252 }
4253
4254 i++;
4255 }
4256
4257 if (!squadmates)
4258 {
4259 return;
4260 }
4261
4262 i = 0;
4263
4264 while (i < squadmates && squad[i])
4265 {
4266 bst = botstates[squad[i]->s.number];
4267
4268 if (bst && !bst->state_Forced)
4269 { //only order if this guy is not being ordered directly by the real player team leader
4270 if (teammate_indanger >= 0 && !teammate_helped)
4271 { //send someone out to help whoever needs help most at the moment
4272 bst->teamplayState = TEAMPLAYSTATE_ASSISTING;
4273 bst->squadLeader = &g_entities[teammate_indanger];
4274 teammate_helped = 1;
4275 }
4276 else if ((teammate_indanger == -1 || teammate_helped) && bst->teamplayState == TEAMPLAYSTATE_ASSISTING)
4277 { //no teammates need help badly, but this guy is trying to help them anyway, so stop
4278 bst->teamplayState = TEAMPLAYSTATE_FOLLOWING;
4279 bst->squadLeader = &g_entities[bs->client];
4280 }
4281
4282 if (bs->squadRegroupInterval < level.time && Q_irand(1, 10) < 5)
4283 { //every so often tell the squad to regroup for the sake of variation
4284 if (bst->teamplayState == TEAMPLAYSTATE_FOLLOWING)
4285 {
4286 bst->teamplayState = TEAMPLAYSTATE_REGROUP;
4287 }
4288
4289 bs->isSquadLeader = 0;
4290 bs->squadCannotLead = level.time + 500;
4291 bs->squadRegroupInterval = level.time + Q_irand(45000, 65000);
4292 }
4293 }
4294
4295 i++;
4296 }
4297 }
4298
4299 //pick which commander ai to use based on gametype
CommanderBotAI(bot_state_t * bs)4300 void CommanderBotAI(bot_state_t *bs)
4301 {
4302 if (level.gametype == GT_CTF || level.gametype == GT_CTY)
4303 {
4304 CommanderBotCTFAI(bs);
4305 }
4306 else if (level.gametype == GT_SIEGE)
4307 {
4308 CommanderBotSiegeAI(bs);
4309 }
4310 else if (level.gametype == GT_TEAM)
4311 {
4312 CommanderBotTeamplayAI(bs);
4313 }
4314 }
4315
4316 //close range combat routines
MeleeCombatHandling(bot_state_t * bs)4317 void MeleeCombatHandling(bot_state_t *bs)
4318 {
4319 vec3_t usethisvec;
4320 vec3_t downvec;
4321 vec3_t midorg;
4322 vec3_t a;
4323 vec3_t fwd;
4324 vec3_t mins, maxs;
4325 trace_t tr;
4326 int en_down;
4327 int me_down;
4328 int mid_down;
4329
4330 if (!bs->currentEnemy)
4331 {
4332 return;
4333 }
4334
4335 if (bs->currentEnemy->client)
4336 {
4337 VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
4338 }
4339 else
4340 {
4341 VectorCopy(bs->currentEnemy->s.origin, usethisvec);
4342 }
4343
4344 if (bs->meleeStrafeTime < level.time)
4345 {
4346 if (bs->meleeStrafeDir)
4347 {
4348 bs->meleeStrafeDir = 0;
4349 }
4350 else
4351 {
4352 bs->meleeStrafeDir = 1;
4353 }
4354
4355 bs->meleeStrafeTime = level.time + Q_irand(500, 1800);
4356 }
4357
4358 mins[0] = -15;
4359 mins[1] = -15;
4360 mins[2] = -24;
4361 maxs[0] = 15;
4362 maxs[1] = 15;
4363 maxs[2] = 32;
4364
4365 VectorCopy(usethisvec, downvec);
4366 downvec[2] -= 4096;
4367
4368 trap->Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID, qfalse, 0, 0);
4369
4370 en_down = (int)tr.endpos[2];
4371
4372 VectorCopy(bs->origin, downvec);
4373 downvec[2] -= 4096;
4374
4375 trap->Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID, qfalse, 0, 0);
4376
4377 me_down = (int)tr.endpos[2];
4378
4379 VectorSubtract(usethisvec, bs->origin, a);
4380 vectoangles(a, a);
4381 AngleVectors(a, fwd, NULL, NULL);
4382
4383 midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2;
4384 midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2;
4385 midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2;
4386
4387 VectorCopy(midorg, downvec);
4388 downvec[2] -= 4096;
4389
4390 trap->Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID, qfalse, 0, 0);
4391
4392 mid_down = (int)tr.endpos[2];
4393
4394 if (me_down == en_down &&
4395 en_down == mid_down)
4396 {
4397 VectorCopy(usethisvec, bs->goalPosition);
4398 }
4399 }
4400
4401 //saber combat routines (it's simple, but it works)
SaberCombatHandling(bot_state_t * bs)4402 void SaberCombatHandling(bot_state_t *bs)
4403 {
4404 vec3_t usethisvec;
4405 vec3_t downvec;
4406 vec3_t midorg;
4407 vec3_t a;
4408 vec3_t fwd;
4409 vec3_t mins, maxs;
4410 trace_t tr;
4411 int en_down;
4412 int me_down;
4413 int mid_down;
4414
4415 if (!bs->currentEnemy)
4416 {
4417 return;
4418 }
4419
4420 if (bs->currentEnemy->client)
4421 {
4422 VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec);
4423 }
4424 else
4425 {
4426 VectorCopy(bs->currentEnemy->s.origin, usethisvec);
4427 }
4428
4429 if (bs->meleeStrafeTime < level.time)
4430 {
4431 if (bs->meleeStrafeDir)
4432 {
4433 bs->meleeStrafeDir = 0;
4434 }
4435 else
4436 {
4437 bs->meleeStrafeDir = 1;
4438 }
4439
4440 bs->meleeStrafeTime = level.time + Q_irand(500, 1800);
4441 }
4442
4443 mins[0] = -15;
4444 mins[1] = -15;
4445 mins[2] = -24;
4446 maxs[0] = 15;
4447 maxs[1] = 15;
4448 maxs[2] = 32;
4449
4450 VectorCopy(usethisvec, downvec);
4451 downvec[2] -= 4096;
4452
4453 trap->Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID, qfalse, 0, 0);
4454
4455 en_down = (int)tr.endpos[2];
4456
4457 if (tr.startsolid || tr.allsolid)
4458 {
4459 en_down = 1;
4460 me_down = 2;
4461 }
4462 else
4463 {
4464 VectorCopy(bs->origin, downvec);
4465 downvec[2] -= 4096;
4466
4467 trap->Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID, qfalse, 0, 0);
4468
4469 me_down = (int)tr.endpos[2];
4470
4471 if (tr.startsolid || tr.allsolid)
4472 {
4473 en_down = 1;
4474 me_down = 2;
4475 }
4476 }
4477
4478 VectorSubtract(usethisvec, bs->origin, a);
4479 vectoangles(a, a);
4480 AngleVectors(a, fwd, NULL, NULL);
4481
4482 midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2;
4483 midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2;
4484 midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2;
4485
4486 VectorCopy(midorg, downvec);
4487 downvec[2] -= 4096;
4488
4489 trap->Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID, qfalse, 0, 0);
4490
4491 mid_down = (int)tr.endpos[2];
4492
4493 if (me_down == en_down &&
4494 en_down == mid_down)
4495 {
4496 if (usethisvec[2] > (bs->origin[2]+32) &&
4497 bs->currentEnemy->client &&
4498 bs->currentEnemy->client->ps.groundEntityNum == ENTITYNUM_NONE)
4499 {
4500 bs->jumpTime = level.time + 100;
4501 }
4502
4503 if (bs->frame_Enemy_Len > 128)
4504 { //be ready to attack
4505 bs->saberDefending = 0;
4506 bs->saberDefendDecideTime = level.time + Q_irand(1000, 2000);
4507 }
4508 else
4509 {
4510 if (bs->saberDefendDecideTime < level.time)
4511 {
4512 if (bs->saberDefending)
4513 {
4514 bs->saberDefending = 0;
4515 }
4516 else
4517 {
4518 bs->saberDefending = 1;
4519 }
4520
4521 bs->saberDefendDecideTime = level.time + Q_irand(500, 2000);
4522 }
4523 }
4524
4525 if (bs->frame_Enemy_Len < 54)
4526 {
4527 VectorCopy(bs->origin, bs->goalPosition);
4528 bs->saberBFTime = 0;
4529 }
4530 else
4531 {
4532 VectorCopy(usethisvec, bs->goalPosition);
4533 }
4534
4535 if (bs->currentEnemy && bs->currentEnemy->client)
4536 {
4537 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)
4538 {
4539 bs->beStill = level.time + Q_irand(500, 1000);
4540 bs->saberSTime = level.time + Q_irand(1200, 1800);
4541 }
4542 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))
4543 {
4544 vec3_t vs;
4545 vec3_t groundcheck;
4546 int idealDist;
4547 int checkIncr = 0;
4548
4549 VectorSubtract(bs->origin, usethisvec, vs);
4550 VectorNormalize(vs);
4551
4552 if (BG_SaberInKata(bs->currentEnemy->client->ps.saberMove) || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK_DUAL)
4553 {
4554 idealDist = 256;
4555 }
4556 else
4557 {
4558 idealDist = 64;
4559 }
4560
4561 while (checkIncr < idealDist)
4562 {
4563 bs->goalPosition[0] = bs->origin[0] + vs[0]*checkIncr;
4564 bs->goalPosition[1] = bs->origin[1] + vs[1]*checkIncr;
4565 bs->goalPosition[2] = bs->origin[2] + vs[2]*checkIncr;
4566
4567 if (bs->saberBTime < level.time)
4568 {
4569 bs->saberBFTime = level.time + Q_irand(900, 1300);
4570 bs->saberBTime = level.time + Q_irand(300, 700);
4571 }
4572
4573 VectorCopy(bs->goalPosition, groundcheck);
4574
4575 groundcheck[2] -= 64;
4576
4577 trap->Trace(&tr, bs->goalPosition, NULL, NULL, groundcheck, bs->client, MASK_SOLID, qfalse, 0, 0);
4578
4579 if (tr.fraction == 1.0f)
4580 { //don't back off of a ledge
4581 VectorCopy(usethisvec, bs->goalPosition);
4582 break;
4583 }
4584 checkIncr += 64;
4585 }
4586 }
4587 else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len >= 75)
4588 {
4589 bs->saberBFTime = level.time + Q_irand(700, 1300);
4590 bs->saberBTime = 0;
4591 }
4592 }
4593
4594 /*AngleVectors(bs->viewangles, NULL, fwd, NULL);
4595
4596 if (bs->meleeStrafeDir)
4597 {
4598 bs->goalPosition[0] += fwd[0]*16;
4599 bs->goalPosition[1] += fwd[1]*16;
4600 bs->goalPosition[2] += fwd[2]*16;
4601 }
4602 else
4603 {
4604 bs->goalPosition[0] -= fwd[0]*16;
4605 bs->goalPosition[1] -= fwd[1]*16;
4606 bs->goalPosition[2] -= fwd[2]*16;
4607 }*/
4608 }
4609 else if (bs->frame_Enemy_Len <= 56)
4610 {
4611 bs->doAttack = 1;
4612 bs->saberDefending = 0;
4613 }
4614 }
4615
4616 //should we be "leading" our aim with this weapon? And if
4617 //so, by how much?
BotWeaponCanLead(bot_state_t * bs)4618 float BotWeaponCanLead(bot_state_t *bs)
4619 {
4620 switch ( bs->cur_ps.weapon )
4621 {
4622 case WP_BRYAR_PISTOL:
4623 return 0.5f;
4624 case WP_BLASTER:
4625 return 0.35f;
4626 case WP_BOWCASTER:
4627 return 0.5f;
4628 case WP_REPEATER:
4629 return 0.45f;
4630 case WP_THERMAL:
4631 return 0.5f;
4632 case WP_DEMP2:
4633 return 0.35f;
4634 case WP_ROCKET_LAUNCHER:
4635 return 0.7f;
4636 default:
4637 return 0.0f;
4638 }
4639 }
4640
4641 //offset the desired view angles with aim leading in mind
BotAimLeading(bot_state_t * bs,vec3_t headlevel,float leadAmount)4642 void BotAimLeading(bot_state_t *bs, vec3_t headlevel, float leadAmount)
4643 {
4644 int x;
4645 vec3_t predictedSpot;
4646 vec3_t movementVector;
4647 vec3_t a, ang;
4648 float vtotal;
4649
4650 if (!bs->currentEnemy ||
4651 !bs->currentEnemy->client)
4652 {
4653 return;
4654 }
4655
4656 if (!bs->frame_Enemy_Len)
4657 {
4658 return;
4659 }
4660
4661 vtotal = 0;
4662
4663 if (bs->currentEnemy->client->ps.velocity[0] < 0)
4664 {
4665 vtotal += -bs->currentEnemy->client->ps.velocity[0];
4666 }
4667 else
4668 {
4669 vtotal += bs->currentEnemy->client->ps.velocity[0];
4670 }
4671
4672 if (bs->currentEnemy->client->ps.velocity[1] < 0)
4673 {
4674 vtotal += -bs->currentEnemy->client->ps.velocity[1];
4675 }
4676 else
4677 {
4678 vtotal += bs->currentEnemy->client->ps.velocity[1];
4679 }
4680
4681 if (bs->currentEnemy->client->ps.velocity[2] < 0)
4682 {
4683 vtotal += -bs->currentEnemy->client->ps.velocity[2];
4684 }
4685 else
4686 {
4687 vtotal += bs->currentEnemy->client->ps.velocity[2];
4688 }
4689
4690 //trap->Print("Leadin target with a velocity total of %f\n", vtotal);
4691
4692 VectorCopy(bs->currentEnemy->client->ps.velocity, movementVector);
4693
4694 VectorNormalize(movementVector);
4695
4696 x = bs->frame_Enemy_Len*leadAmount; //hardly calculated with an exact science, but it works
4697
4698 if (vtotal > 400)
4699 {
4700 vtotal = 400;
4701 }
4702
4703 if (vtotal)
4704 {
4705 x = (bs->frame_Enemy_Len*0.9)*leadAmount*(vtotal*0.0012); //hardly calculated with an exact science, but it works
4706 }
4707 else
4708 {
4709 x = (bs->frame_Enemy_Len*0.9)*leadAmount; //hardly calculated with an exact science, but it works
4710 }
4711
4712 predictedSpot[0] = headlevel[0] + (movementVector[0]*x);
4713 predictedSpot[1] = headlevel[1] + (movementVector[1]*x);
4714 predictedSpot[2] = headlevel[2] + (movementVector[2]*x);
4715
4716 VectorSubtract(predictedSpot, bs->eye, a);
4717 vectoangles(a, ang);
4718 VectorCopy(ang, bs->goalAngles);
4719 }
4720
4721 //wobble our aim around based on our sk1llz
BotAimOffsetGoalAngles(bot_state_t * bs)4722 void BotAimOffsetGoalAngles(bot_state_t *bs)
4723 {
4724 int i;
4725 float accVal;
4726 i = 0;
4727
4728 if (bs->skills.perfectaim)
4729 {
4730 return;
4731 }
4732
4733 if (bs->aimOffsetTime > level.time)
4734 {
4735 if (bs->aimOffsetAmtYaw)
4736 {
4737 bs->goalAngles[YAW] += bs->aimOffsetAmtYaw;
4738 }
4739
4740 if (bs->aimOffsetAmtPitch)
4741 {
4742 bs->goalAngles[PITCH] += bs->aimOffsetAmtPitch;
4743 }
4744
4745 while (i <= 2)
4746 {
4747 if (bs->goalAngles[i] > 360)
4748 {
4749 bs->goalAngles[i] -= 360;
4750 }
4751
4752 if (bs->goalAngles[i] < 0)
4753 {
4754 bs->goalAngles[i] += 360;
4755 }
4756
4757 i++;
4758 }
4759 return;
4760 }
4761
4762 accVal = bs->skills.accuracy/bs->settings.skill;
4763
4764 if (bs->currentEnemy && BotMindTricked(bs->client, bs->currentEnemy->s.number))
4765 { //having to judge where they are by hearing them, so we should be quite inaccurate here
4766 accVal *= 7;
4767
4768 if (accVal < 30)
4769 {
4770 accVal = 30;
4771 }
4772 }
4773
4774 if (bs->revengeEnemy && bs->revengeHateLevel &&
4775 bs->currentEnemy == bs->revengeEnemy)
4776 { //bot becomes more skilled as anger level raises
4777 accVal = accVal/bs->revengeHateLevel;
4778 }
4779
4780 if (bs->currentEnemy && bs->frame_Enemy_Vis)
4781 { //assume our goal is aiming at the enemy, seeing as he's visible and all
4782 if (!bs->currentEnemy->s.pos.trDelta[0] &&
4783 !bs->currentEnemy->s.pos.trDelta[1] &&
4784 !bs->currentEnemy->s.pos.trDelta[2])
4785 {
4786 accVal = 0; //he's not even moving, so he shouldn't really be hard to hit.
4787 }
4788 else
4789 {
4790 accVal += accVal*0.25; //if he's moving he's this much harder to hit
4791 }
4792
4793 if (g_entities[bs->client].s.pos.trDelta[0] ||
4794 g_entities[bs->client].s.pos.trDelta[1] ||
4795 g_entities[bs->client].s.pos.trDelta[2])
4796 {
4797 accVal += accVal*0.15; //make it somewhat harder to aim if we're moving also
4798 }
4799 }
4800
4801 if (accVal > 90)
4802 {
4803 accVal = 90;
4804 }
4805 if (accVal < 1)
4806 {
4807 accVal = 0;
4808 }
4809
4810 if (!accVal)
4811 {
4812 bs->aimOffsetAmtYaw = 0;
4813 bs->aimOffsetAmtPitch = 0;
4814 return;
4815 }
4816
4817 if (rand()%10 <= 5)
4818 {
4819 bs->aimOffsetAmtYaw = rand()%(int)accVal;
4820 }
4821 else
4822 {
4823 bs->aimOffsetAmtYaw = -(rand()%(int)accVal);
4824 }
4825
4826 if (rand()%10 <= 5)
4827 {
4828 bs->aimOffsetAmtPitch = rand()%(int)accVal;
4829 }
4830 else
4831 {
4832 bs->aimOffsetAmtPitch = -(rand()%(int)accVal);
4833 }
4834
4835 bs->aimOffsetTime = level.time + rand()%500 + 200;
4836 }
4837
4838 //do we want to alt fire with this weapon?
ShouldSecondaryFire(bot_state_t * bs)4839 int ShouldSecondaryFire(bot_state_t *bs)
4840 {
4841 int weap;
4842 int dif;
4843 float rTime;
4844
4845 weap = bs->cur_ps.weapon;
4846
4847 if (bs->cur_ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].altEnergyPerShot)
4848 {
4849 return 0;
4850 }
4851
4852 if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && bs->cur_ps.weapon == WP_ROCKET_LAUNCHER)
4853 {
4854 float heldTime = (level.time - bs->cur_ps.weaponChargeTime);
4855
4856 rTime = bs->cur_ps.rocketLockTime;
4857
4858 if (rTime < 1)
4859 {
4860 rTime = bs->cur_ps.rocketLastValidTime;
4861 }
4862
4863 if (heldTime > 5000)
4864 { //just give up and release it if we can't manage a lock in 5 seconds
4865 return 2;
4866 }
4867
4868 if (rTime > 0)
4869 {
4870 dif = ( level.time - rTime ) / ( 1200.0f / 16.0f );
4871
4872 if (dif >= 10)
4873 {
4874 return 2;
4875 }
4876 else if (bs->frame_Enemy_Len > 250)
4877 {
4878 return 1;
4879 }
4880 }
4881 else if (bs->frame_Enemy_Len > 250)
4882 {
4883 return 1;
4884 }
4885 }
4886 else if ((bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) && (level.time - bs->cur_ps.weaponChargeTime) > bs->altChargeTime)
4887 {
4888 return 2;
4889 }
4890 else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
4891 {
4892 return 1;
4893 }
4894
4895 if (weap == WP_BRYAR_PISTOL && bs->frame_Enemy_Len < 300)
4896 {
4897 return 1;
4898 }
4899 else if (weap == WP_BOWCASTER && bs->frame_Enemy_Len > 300)
4900 {
4901 return 1;
4902 }
4903 else if (weap == WP_REPEATER && bs->frame_Enemy_Len < 600 && bs->frame_Enemy_Len > 250)
4904 {
4905 return 1;
4906 }
4907 else if (weap == WP_BLASTER && bs->frame_Enemy_Len < 300)
4908 {
4909 return 1;
4910 }
4911 else if (weap == WP_ROCKET_LAUNCHER && bs->frame_Enemy_Len > 250)
4912 {
4913 return 1;
4914 }
4915
4916 return 0;
4917 }
4918
4919 //standard weapon combat routines
CombatBotAI(bot_state_t * bs,float thinktime)4920 int CombatBotAI(bot_state_t *bs, float thinktime)
4921 {
4922 vec3_t eorg, a;
4923 int secFire;
4924 float fovcheck;
4925
4926 if (!bs->currentEnemy)
4927 {
4928 return 0;
4929 }
4930
4931 if (bs->currentEnemy->client)
4932 {
4933 VectorCopy(bs->currentEnemy->client->ps.origin, eorg);
4934 }
4935 else
4936 {
4937 VectorCopy(bs->currentEnemy->s.origin, eorg);
4938 }
4939
4940 VectorSubtract(eorg, bs->eye, a);
4941 vectoangles(a, a);
4942
4943 if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER)
4944 {
4945 if (bs->frame_Enemy_Len <= SABER_ATTACK_RANGE)
4946 {
4947 bs->doAttack = 1;
4948 }
4949 }
4950 else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)
4951 {
4952 if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE)
4953 {
4954 bs->doAttack = 1;
4955 }
4956 }
4957 else
4958 {
4959 if (bs->cur_ps.weapon == WP_THERMAL || bs->cur_ps.weapon == WP_ROCKET_LAUNCHER)
4960 { //be careful with the hurty weapons
4961 fovcheck = 40;
4962
4963 if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
4964 bs->cur_ps.weapon == WP_ROCKET_LAUNCHER)
4965 { //if we're charging the weapon up then we can hold fire down within a normal fov
4966 fovcheck = 60;
4967 }
4968 }
4969 else
4970 {
4971 fovcheck = 60;
4972 }
4973
4974 if (bs->cur_ps.weaponstate == WEAPON_CHARGING ||
4975 bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
4976 {
4977 fovcheck = 160;
4978 }
4979
4980 if (bs->frame_Enemy_Len < 128)
4981 {
4982 fovcheck *= 2;
4983 }
4984
4985 if (InFieldOfVision(bs->viewangles, fovcheck, a))
4986 {
4987 if (bs->cur_ps.weapon == WP_THERMAL)
4988 {
4989 if (((level.time - bs->cur_ps.weaponChargeTime) < (bs->frame_Enemy_Len*2) &&
4990 (level.time - bs->cur_ps.weaponChargeTime) < 4000 &&
4991 bs->frame_Enemy_Len > 64) ||
4992 (bs->cur_ps.weaponstate != WEAPON_CHARGING &&
4993 bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT))
4994 {
4995 if (bs->cur_ps.weaponstate != WEAPON_CHARGING && bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT)
4996 {
4997 if (bs->frame_Enemy_Len > 512 && bs->frame_Enemy_Len < 800)
4998 {
4999 bs->doAltAttack = 1;
5000 //bs->doAttack = 1;
5001 }
5002 else
5003 {
5004 bs->doAttack = 1;
5005 //bs->doAltAttack = 1;
5006 }
5007 }
5008
5009 if (bs->cur_ps.weaponstate == WEAPON_CHARGING)
5010 {
5011 bs->doAttack = 1;
5012 }
5013 else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
5014 {
5015 bs->doAltAttack = 1;
5016 }
5017 }
5018 }
5019 else
5020 {
5021 secFire = ShouldSecondaryFire(bs);
5022
5023 if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT &&
5024 bs->cur_ps.weaponstate != WEAPON_CHARGING)
5025 {
5026 bs->altChargeTime = Q_irand(500, 1000);
5027 }
5028
5029 if (secFire == 1)
5030 {
5031 bs->doAltAttack = 1;
5032 }
5033 else if (!secFire)
5034 {
5035 if (bs->cur_ps.weapon != WP_THERMAL)
5036 {
5037 if (bs->cur_ps.weaponstate != WEAPON_CHARGING ||
5038 bs->altChargeTime > (level.time - bs->cur_ps.weaponChargeTime))
5039 {
5040 bs->doAttack = 1;
5041 }
5042 }
5043 else
5044 {
5045 bs->doAttack = 1;
5046 }
5047 }
5048
5049 if (secFire == 2)
5050 { //released a charge
5051 return 1;
5052 }
5053 }
5054 }
5055 }
5056
5057 return 0;
5058 }
5059
5060 //we messed up and got off the normal path, let's fall
5061 //back to jumping around and turning in random
5062 //directions off walls to see if we can get back to a
5063 //good place.
BotFallbackNavigation(bot_state_t * bs)5064 int BotFallbackNavigation(bot_state_t *bs)
5065 {
5066 vec3_t b_angle, fwd, trto, mins, maxs;
5067 trace_t tr;
5068
5069 if (bs->currentEnemy && bs->frame_Enemy_Vis)
5070 {
5071 return 2; //we're busy
5072 }
5073
5074 mins[0] = -15;
5075 mins[1] = -15;
5076 mins[2] = 0;
5077 maxs[0] = 15;
5078 maxs[1] = 15;
5079 maxs[2] = 32;
5080
5081 bs->goalAngles[PITCH] = 0;
5082 bs->goalAngles[ROLL] = 0;
5083
5084 VectorCopy(bs->goalAngles, b_angle);
5085
5086 AngleVectors(b_angle, fwd, NULL, NULL);
5087
5088 trto[0] = bs->origin[0] + fwd[0]*16;
5089 trto[1] = bs->origin[1] + fwd[1]*16;
5090 trto[2] = bs->origin[2] + fwd[2]*16;
5091
5092 trap->Trace(&tr, bs->origin, mins, maxs, trto, ENTITYNUM_NONE, MASK_SOLID, qfalse, 0, 0);
5093
5094 if (tr.fraction == 1)
5095 {
5096 VectorCopy(trto, bs->goalPosition);
5097 return 1; //success!
5098 }
5099 else
5100 {
5101 bs->goalAngles[YAW] = rand()%360;
5102 }
5103
5104 return 0;
5105 }
5106
BotTryAnotherWeapon(bot_state_t * bs)5107 int BotTryAnotherWeapon(bot_state_t *bs)
5108 { //out of ammo, resort to the first weapon we come across that has ammo
5109 int i;
5110
5111 i = 1;
5112
5113 while (i < WP_NUM_WEAPONS)
5114 {
5115 if (bs->cur_ps.ammo[weaponData[i].ammoIndex] >= weaponData[i].energyPerShot &&
5116 (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
5117 {
5118 bs->virtualWeapon = i;
5119 BotSelectWeapon(bs->client, i);
5120 //bs->cur_ps.weapon = i;
5121 //level.clients[bs->client].ps.weapon = i;
5122 return 1;
5123 }
5124
5125 i++;
5126 }
5127
5128 if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1)
5129 { //should always have this.. shouldn't we?
5130 bs->virtualWeapon = 1;
5131 BotSelectWeapon(bs->client, 1);
5132 //bs->cur_ps.weapon = 1;
5133 //level.clients[bs->client].ps.weapon = 1;
5134 return 1;
5135 }
5136
5137 return 0;
5138 }
5139
5140 //is this weapon available to us?
BotWeaponSelectable(bot_state_t * bs,int weapon)5141 qboolean BotWeaponSelectable(bot_state_t *bs, int weapon)
5142 {
5143 if (weapon == WP_NONE)
5144 {
5145 return qfalse;
5146 }
5147
5148 if (bs->cur_ps.ammo[weaponData[weapon].ammoIndex] >= weaponData[weapon].energyPerShot &&
5149 (bs->cur_ps.stats[STAT_WEAPONS] & (1 << weapon)))
5150 {
5151 return qtrue;
5152 }
5153
5154 return qfalse;
5155 }
5156
5157 //select the best weapon we can
BotSelectIdealWeapon(bot_state_t * bs)5158 int BotSelectIdealWeapon(bot_state_t *bs)
5159 {
5160 int i;
5161 int bestweight = -1;
5162 int bestweapon = 0;
5163
5164 i = 0;
5165
5166 while (i < WP_NUM_WEAPONS)
5167 {
5168 if (bs->cur_ps.ammo[weaponData[i].ammoIndex] >= weaponData[i].energyPerShot &&
5169 bs->botWeaponWeights[i] > bestweight &&
5170 (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
5171 {
5172 if (i == WP_THERMAL)
5173 { //special case..
5174 if (bs->currentEnemy && bs->frame_Enemy_Len < 700)
5175 {
5176 bestweight = bs->botWeaponWeights[i];
5177 bestweapon = i;
5178 }
5179 }
5180 else
5181 {
5182 bestweight = bs->botWeaponWeights[i];
5183 bestweapon = i;
5184 }
5185 }
5186
5187 i++;
5188 }
5189
5190 if ( bs->currentEnemy && bs->frame_Enemy_Len < 300 &&
5191 (bestweapon == WP_BRYAR_PISTOL || bestweapon == WP_BLASTER || bestweapon == WP_BOWCASTER) &&
5192 (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) )
5193 {
5194 bestweapon = WP_SABER;
5195 bestweight = 1;
5196 }
5197
5198 if ( bs->currentEnemy && bs->frame_Enemy_Len > 300 &&
5199 bs->currentEnemy->client && bs->currentEnemy->client->ps.weapon != WP_SABER &&
5200 (bestweapon == WP_SABER) )
5201 { //if the enemy is far away, and we have our saber selected, see if we have any good distance weapons instead
5202 if (BotWeaponSelectable(bs, WP_DISRUPTOR))
5203 {
5204 bestweapon = WP_DISRUPTOR;
5205 bestweight = 1;
5206 }
5207 else if (BotWeaponSelectable(bs, WP_ROCKET_LAUNCHER))
5208 {
5209 bestweapon = WP_ROCKET_LAUNCHER;
5210 bestweight = 1;
5211 }
5212 else if (BotWeaponSelectable(bs, WP_BOWCASTER))
5213 {
5214 bestweapon = WP_BOWCASTER;
5215 bestweight = 1;
5216 }
5217 else if (BotWeaponSelectable(bs, WP_BLASTER))
5218 {
5219 bestweapon = WP_BLASTER;
5220 bestweight = 1;
5221 }
5222 else if (BotWeaponSelectable(bs, WP_REPEATER))
5223 {
5224 bestweapon = WP_REPEATER;
5225 bestweight = 1;
5226 }
5227 else if (BotWeaponSelectable(bs, WP_DEMP2))
5228 {
5229 bestweapon = WP_DEMP2;
5230 bestweight = 1;
5231 }
5232 }
5233
5234 //assert(bs->cur_ps.weapon > 0 && bestweapon > 0);
5235
5236 if (bestweight != -1 && bs->cur_ps.weapon != bestweapon && bs->virtualWeapon != bestweapon)
5237 {
5238 bs->virtualWeapon = bestweapon;
5239 BotSelectWeapon(bs->client, bestweapon);
5240 //bs->cur_ps.weapon = bestweapon;
5241 //level.clients[bs->client].ps.weapon = bestweapon;
5242 return 1;
5243 }
5244
5245 //assert(bs->cur_ps.weapon > 0);
5246
5247 return 0;
5248 }
5249
5250 //check/select the chosen weapon
BotSelectChoiceWeapon(bot_state_t * bs,int weapon,int doselection)5251 int BotSelectChoiceWeapon(bot_state_t *bs, int weapon, int doselection)
5252 { //if !doselection then bot will only check if he has the specified weapon and return 1 (yes) or 0 (no)
5253 int i;
5254 int hasit = 0;
5255
5256 i = 0;
5257
5258 while (i < WP_NUM_WEAPONS)
5259 {
5260 if (bs->cur_ps.ammo[weaponData[i].ammoIndex] > weaponData[i].energyPerShot &&
5261 i == weapon &&
5262 (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i)))
5263 {
5264 hasit = 1;
5265 break;
5266 }
5267
5268 i++;
5269 }
5270
5271 if (hasit && bs->cur_ps.weapon != weapon && doselection && bs->virtualWeapon != weapon)
5272 {
5273 bs->virtualWeapon = weapon;
5274 BotSelectWeapon(bs->client, weapon);
5275 //bs->cur_ps.weapon = weapon;
5276 //level.clients[bs->client].ps.weapon = weapon;
5277 return 2;
5278 }
5279
5280 if (hasit)
5281 {
5282 return 1;
5283 }
5284
5285 return 0;
5286 }
5287
5288 //override our standard weapon choice with a melee weapon
BotSelectMelee(bot_state_t * bs)5289 int BotSelectMelee(bot_state_t *bs)
5290 {
5291 if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1)
5292 {
5293 bs->virtualWeapon = 1;
5294 BotSelectWeapon(bs->client, 1);
5295 //bs->cur_ps.weapon = 1;
5296 //level.clients[bs->client].ps.weapon = 1;
5297 return 1;
5298 }
5299
5300 return 0;
5301 }
5302
5303 //See if we our in love with the potential bot.
GetLoveLevel(bot_state_t * bs,bot_state_t * love)5304 int GetLoveLevel(bot_state_t *bs, bot_state_t *love)
5305 {
5306 int i = 0;
5307 const char *lname = NULL;
5308
5309 if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
5310 { //There is no love in 1-on-1
5311 return 0;
5312 }
5313
5314 if (!bs || !love || !g_entities[love->client].client)
5315 {
5316 return 0;
5317 }
5318
5319 if (!bs->lovednum)
5320 {
5321 return 0;
5322 }
5323
5324 if (!bot_attachments.integer)
5325 {
5326 return 1;
5327 }
5328
5329 lname = g_entities[love->client].client->pers.netname;
5330
5331 if (!lname)
5332 {
5333 return 0;
5334 }
5335
5336 while (i < bs->lovednum)
5337 {
5338 if (strcmp(bs->loved[i].name, lname) == 0)
5339 {
5340 return bs->loved[i].level;
5341 }
5342
5343 i++;
5344 }
5345
5346 return 0;
5347 }
5348
5349 //Our loved one was killed. We must become infuriated!
BotLovedOneDied(bot_state_t * bs,bot_state_t * loved,int lovelevel)5350 void BotLovedOneDied(bot_state_t *bs, bot_state_t *loved, int lovelevel)
5351 {
5352 if (!loved->lastHurt || !loved->lastHurt->client ||
5353 loved->lastHurt->s.number == loved->client)
5354 {
5355 return;
5356 }
5357
5358 if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
5359 { //There is no love in 1-on-1
5360 return;
5361 }
5362
5363 if (!IsTeamplay())
5364 {
5365 if (lovelevel < 2)
5366 {
5367 return;
5368 }
5369 }
5370 else if (OnSameTeam(&g_entities[bs->client], loved->lastHurt))
5371 { //don't hate teammates no matter what
5372 return;
5373 }
5374
5375 if (loved->client == loved->lastHurt->s.number)
5376 {
5377 return;
5378 }
5379
5380 if (bs->client == loved->lastHurt->s.number)
5381 { //oops!
5382 return;
5383 }
5384
5385 if (!bot_attachments.integer)
5386 {
5387 return;
5388 }
5389
5390 if (!PassLovedOneCheck(bs, loved->lastHurt))
5391 { //a loved one killed a loved one.. you cannot hate them
5392 bs->chatObject = loved->lastHurt;
5393 bs->chatAltObject = &g_entities[loved->client];
5394 BotDoChat(bs, "LovedOneKilledLovedOne", 0);
5395 return;
5396 }
5397
5398 if (bs->revengeEnemy == loved->lastHurt)
5399 {
5400 if (bs->revengeHateLevel < bs->loved_death_thresh)
5401 {
5402 bs->revengeHateLevel++;
5403
5404 if (bs->revengeHateLevel == bs->loved_death_thresh)
5405 {
5406 //broke into the highest anger level
5407 //CHAT: Hatred section
5408 bs->chatObject = loved->lastHurt;
5409 bs->chatAltObject = NULL;
5410 BotDoChat(bs, "Hatred", 1);
5411 }
5412 }
5413 }
5414 else if (bs->revengeHateLevel < bs->loved_death_thresh-1)
5415 { //only switch hatred if we don't hate the existing revenge-enemy too much
5416 //CHAT: BelovedKilled section
5417 bs->chatObject = &g_entities[loved->client];
5418 bs->chatAltObject = loved->lastHurt;
5419 BotDoChat(bs, "BelovedKilled", 0);
5420 bs->revengeHateLevel = 0;
5421 bs->revengeEnemy = loved->lastHurt;
5422 }
5423 }
5424
BotDeathNotify(bot_state_t * bs)5425 void BotDeathNotify(bot_state_t *bs)
5426 { //in case someone has an emotional attachment to us, we'll notify them
5427 int i = 0;
5428 int ltest = 0;
5429
5430 while (i < MAX_CLIENTS)
5431 {
5432 if (botstates[i] && botstates[i]->lovednum)
5433 {
5434 ltest = 0;
5435 while (ltest < botstates[i]->lovednum)
5436 {
5437 if (strcmp(level.clients[bs->client].pers.netname, botstates[i]->loved[ltest].name) == 0)
5438 {
5439 BotLovedOneDied(botstates[i], bs, botstates[i]->loved[ltest].level);
5440 break;
5441 }
5442
5443 ltest++;
5444 }
5445 }
5446
5447 i++;
5448 }
5449 }
5450
5451 //perform strafe trace checks
StrafeTracing(bot_state_t * bs)5452 void StrafeTracing(bot_state_t *bs)
5453 {
5454 vec3_t mins, maxs;
5455 vec3_t right, rorg, drorg;
5456 trace_t tr;
5457
5458 mins[0] = -15;
5459 mins[1] = -15;
5460 //mins[2] = -24;
5461 mins[2] = -22;
5462 maxs[0] = 15;
5463 maxs[1] = 15;
5464 maxs[2] = 32;
5465
5466 AngleVectors(bs->viewangles, NULL, right, NULL);
5467
5468 if (bs->meleeStrafeDir)
5469 {
5470 rorg[0] = bs->origin[0] - right[0]*32;
5471 rorg[1] = bs->origin[1] - right[1]*32;
5472 rorg[2] = bs->origin[2] - right[2]*32;
5473 }
5474 else
5475 {
5476 rorg[0] = bs->origin[0] + right[0]*32;
5477 rorg[1] = bs->origin[1] + right[1]*32;
5478 rorg[2] = bs->origin[2] + right[2]*32;
5479 }
5480
5481 trap->Trace(&tr, bs->origin, mins, maxs, rorg, bs->client, MASK_SOLID, qfalse, 0, 0);
5482
5483 if (tr.fraction != 1)
5484 {
5485 bs->meleeStrafeDisable = level.time + Q_irand(500, 1500);
5486 }
5487
5488 VectorCopy(rorg, drorg);
5489
5490 drorg[2] -= 32;
5491
5492 trap->Trace(&tr, rorg, NULL, NULL, drorg, bs->client, MASK_SOLID, qfalse, 0, 0);
5493
5494 if (tr.fraction == 1)
5495 { //this may be a dangerous ledge, so don't strafe over it just in case
5496 bs->meleeStrafeDisable = level.time + Q_irand(500, 1500);
5497 }
5498 }
5499
5500 //doing primary weapon fire
PrimFiring(bot_state_t * bs)5501 int PrimFiring(bot_state_t *bs)
5502 {
5503 if (bs->cur_ps.weaponstate != WEAPON_CHARGING &&
5504 bs->doAttack)
5505 {
5506 return 1;
5507 }
5508
5509 if (bs->cur_ps.weaponstate == WEAPON_CHARGING &&
5510 !bs->doAttack)
5511 {
5512 return 1;
5513 }
5514
5515 return 0;
5516 }
5517
5518 //should we keep our primary weapon from firing?
KeepPrimFromFiring(bot_state_t * bs)5519 int KeepPrimFromFiring(bot_state_t *bs)
5520 {
5521 if (bs->cur_ps.weaponstate != WEAPON_CHARGING &&
5522 bs->doAttack)
5523 {
5524 bs->doAttack = 0;
5525 }
5526
5527 if (bs->cur_ps.weaponstate == WEAPON_CHARGING &&
5528 !bs->doAttack)
5529 {
5530 bs->doAttack = 1;
5531 }
5532
5533 return 0;
5534 }
5535
5536 //doing secondary weapon fire
AltFiring(bot_state_t * bs)5537 int AltFiring(bot_state_t *bs)
5538 {
5539 if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT &&
5540 bs->doAltAttack)
5541 {
5542 return 1;
5543 }
5544
5545 if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
5546 !bs->doAltAttack)
5547 {
5548 return 1;
5549 }
5550
5551 return 0;
5552 }
5553
5554 //should we keep our alt from firing?
KeepAltFromFiring(bot_state_t * bs)5555 int KeepAltFromFiring(bot_state_t *bs)
5556 {
5557 if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT &&
5558 bs->doAltAttack)
5559 {
5560 bs->doAltAttack = 0;
5561 }
5562
5563 if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT &&
5564 !bs->doAltAttack)
5565 {
5566 bs->doAltAttack = 1;
5567 }
5568
5569 return 0;
5570 }
5571
5572 //Try not to shoot our friends in the back. Or in the face. Or anywhere, really.
CheckForFriendInLOF(bot_state_t * bs)5573 gentity_t *CheckForFriendInLOF(bot_state_t *bs)
5574 {
5575 vec3_t fwd;
5576 vec3_t trfrom, trto;
5577 vec3_t mins, maxs;
5578 gentity_t *trent;
5579 trace_t tr;
5580
5581 mins[0] = -3;
5582 mins[1] = -3;
5583 mins[2] = -3;
5584
5585 maxs[0] = 3;
5586 maxs[1] = 3;
5587 maxs[2] = 3;
5588
5589 AngleVectors(bs->viewangles, fwd, NULL, NULL);
5590
5591 VectorCopy(bs->eye, trfrom);
5592
5593 trto[0] = trfrom[0] + fwd[0]*2048;
5594 trto[1] = trfrom[1] + fwd[1]*2048;
5595 trto[2] = trfrom[2] + fwd[2]*2048;
5596
5597 trap->Trace(&tr, trfrom, mins, maxs, trto, bs->client, MASK_PLAYERSOLID, qfalse, 0, 0);
5598
5599 if (tr.fraction != 1 && tr.entityNum <= MAX_CLIENTS)
5600 {
5601 trent = &g_entities[tr.entityNum];
5602
5603 if (trent && trent->client)
5604 {
5605 if (IsTeamplay() && OnSameTeam(&g_entities[bs->client], trent))
5606 {
5607 return trent;
5608 }
5609
5610 if (botstates[trent->s.number] && GetLoveLevel(bs, botstates[trent->s.number]) > 1)
5611 {
5612 return trent;
5613 }
5614 }
5615 }
5616
5617 return NULL;
5618 }
5619
BotScanForLeader(bot_state_t * bs)5620 void BotScanForLeader(bot_state_t *bs)
5621 { //bots will only automatically obtain a leader if it's another bot using this method.
5622 int i = 0;
5623 gentity_t *ent;
5624
5625 if (bs->isSquadLeader)
5626 {
5627 return;
5628 }
5629
5630 while (i < MAX_CLIENTS)
5631 {
5632 ent = &g_entities[i];
5633
5634 if (ent && ent->client && botstates[i] && botstates[i]->isSquadLeader && bs->client != i)
5635 {
5636 if (OnSameTeam(&g_entities[bs->client], ent))
5637 {
5638 bs->squadLeader = ent;
5639 break;
5640 }
5641 if (GetLoveLevel(bs, botstates[i]) > 1 && !IsTeamplay())
5642 { //ignore love status regarding squad leaders if we're in teamplay
5643 bs->squadLeader = ent;
5644 break;
5645 }
5646 }
5647
5648 i++;
5649 }
5650 }
5651
5652 //w3rd to the p33pz.
BotReplyGreetings(bot_state_t * bs)5653 void BotReplyGreetings(bot_state_t *bs)
5654 {
5655 int i = 0;
5656 int numhello = 0;
5657
5658 while (i < MAX_CLIENTS)
5659 {
5660 if (botstates[i] &&
5661 botstates[i]->canChat &&
5662 i != bs->client)
5663 {
5664 botstates[i]->chatObject = &g_entities[bs->client];
5665 botstates[i]->chatAltObject = NULL;
5666 if (BotDoChat(botstates[i], "ResponseGreetings", 0))
5667 {
5668 numhello++;
5669 }
5670 }
5671
5672 if (numhello > 3)
5673 { //don't let more than 4 bots say hello at once
5674 return;
5675 }
5676
5677 i++;
5678 }
5679 }
5680
5681 //try to move in to grab a nearby flag
CTFFlagMovement(bot_state_t * bs)5682 void CTFFlagMovement(bot_state_t *bs)
5683 {
5684 int diddrop = 0;
5685 gentity_t *desiredDrop = NULL;
5686 vec3_t a, mins, maxs;
5687 trace_t tr;
5688
5689 mins[0] = -15;
5690 mins[1] = -15;
5691 mins[2] = -7;
5692 maxs[0] = 15;
5693 maxs[1] = 15;
5694 maxs[2] = 7;
5695
5696 if (bs->wantFlag && (bs->wantFlag->flags & FL_DROPPED_ITEM))
5697 {
5698 if (bs->staticFlagSpot[0] == bs->wantFlag->s.pos.trBase[0] &&
5699 bs->staticFlagSpot[1] == bs->wantFlag->s.pos.trBase[1] &&
5700 bs->staticFlagSpot[2] == bs->wantFlag->s.pos.trBase[2])
5701 {
5702 VectorSubtract(bs->origin, bs->wantFlag->s.pos.trBase, a);
5703
5704 if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE)
5705 {
5706 VectorCopy(bs->wantFlag->s.pos.trBase, bs->goalPosition);
5707 return;
5708 }
5709 else
5710 {
5711 bs->wantFlag = NULL;
5712 }
5713 }
5714 else
5715 {
5716 bs->wantFlag = NULL;
5717 }
5718 }
5719 else if (bs->wantFlag)
5720 {
5721 bs->wantFlag = NULL;
5722 }
5723
5724 if (flagRed && flagBlue)
5725 {
5726 if (bs->wpDestination == flagRed ||
5727 bs->wpDestination == flagBlue)
5728 {
5729 if (bs->wpDestination == flagRed && droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM) && droppedRedFlag->classname && strcmp(droppedRedFlag->classname, "freed") != 0)
5730 {
5731 desiredDrop = droppedRedFlag;
5732 diddrop = 1;
5733 }
5734 if (bs->wpDestination == flagBlue && droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM) && droppedBlueFlag->classname && strcmp(droppedBlueFlag->classname, "freed") != 0)
5735 {
5736 desiredDrop = droppedBlueFlag;
5737 diddrop = 1;
5738 }
5739
5740 if (diddrop && desiredDrop)
5741 {
5742 VectorSubtract(bs->origin, desiredDrop->s.pos.trBase, a);
5743
5744 if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE)
5745 {
5746 trap->Trace(&tr, bs->origin, mins, maxs, desiredDrop->s.pos.trBase, bs->client, MASK_SOLID, qfalse, 0, 0);
5747
5748 if (tr.fraction == 1 || tr.entityNum == desiredDrop->s.number)
5749 {
5750 VectorCopy(desiredDrop->s.pos.trBase, bs->goalPosition);
5751 VectorCopy(desiredDrop->s.pos.trBase, bs->staticFlagSpot);
5752 return;
5753 }
5754 }
5755 }
5756 }
5757 }
5758 }
5759
5760 //see if we want to make our detpacks blow up
BotCheckDetPacks(bot_state_t * bs)5761 void BotCheckDetPacks(bot_state_t *bs)
5762 {
5763 gentity_t *dp = NULL;
5764 gentity_t *myDet = NULL;
5765 vec3_t a;
5766 float enLen;
5767 float myLen;
5768
5769 while ( (dp = G_Find( dp, FOFS(classname), "detpack") ) != NULL )
5770 {
5771 if (dp && dp->parent && dp->parent->s.number == bs->client)
5772 {
5773 myDet = dp;
5774 break;
5775 }
5776 }
5777
5778 if (!myDet)
5779 {
5780 return;
5781 }
5782
5783 if (!bs->currentEnemy || !bs->currentEnemy->client || !bs->frame_Enemy_Vis)
5784 { //require the enemy to be visilbe just to be fair..
5785
5786 //unless..
5787 if (bs->currentEnemy && bs->currentEnemy->client &&
5788 (level.time - bs->plantContinue) < 5000)
5789 { //it's a fresh plant (within 5 seconds) so we should be able to guess
5790 goto stillmadeit;
5791 }
5792 return;
5793 }
5794
5795 stillmadeit:
5796
5797 VectorSubtract(bs->currentEnemy->client->ps.origin, myDet->s.pos.trBase, a);
5798 enLen = VectorLength(a);
5799
5800 VectorSubtract(bs->origin, myDet->s.pos.trBase, a);
5801 myLen = VectorLength(a);
5802
5803 if (enLen > myLen)
5804 {
5805 return;
5806 }
5807
5808 if (enLen < BOT_PLANT_BLOW_DISTANCE && OrgVisible(bs->currentEnemy->client->ps.origin, myDet->s.pos.trBase, bs->currentEnemy->s.number))
5809 { //we could just call the "blow all my detpacks" function here, but I guess that's cheating.
5810 bs->plantKillEmAll = level.time + 500;
5811 }
5812 }
5813
5814 //see if it would be beneficial at this time to use one of our inv items
BotUseInventoryItem(bot_state_t * bs)5815 int BotUseInventoryItem(bot_state_t *bs)
5816 {
5817 if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC))
5818 {
5819 if (g_entities[bs->client].health <= 75)
5820 {
5821 bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC, IT_HOLDABLE);
5822 goto wantuseitem;
5823 }
5824 }
5825 if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC_BIG))
5826 {
5827 if (g_entities[bs->client].health <= 50)
5828 {
5829 bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC_BIG, IT_HOLDABLE);
5830 goto wantuseitem;
5831 }
5832 }
5833 if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER))
5834 {
5835 if (bs->currentEnemy && bs->frame_Enemy_Vis)
5836 {
5837 bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SEEKER, IT_HOLDABLE);
5838 goto wantuseitem;
5839 }
5840 }
5841 if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN))
5842 {
5843 if (bs->currentEnemy && bs->frame_Enemy_Vis)
5844 {
5845 bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SENTRY_GUN, IT_HOLDABLE);
5846 goto wantuseitem;
5847 }
5848 }
5849 if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD))
5850 {
5851 if (bs->currentEnemy && bs->frame_Enemy_Vis && bs->runningToEscapeThreat)
5852 { //this will (hopefully) result in the bot placing the shield down while facing
5853 //the enemy and running away
5854 bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SHIELD, IT_HOLDABLE);
5855 goto wantuseitem;
5856 }
5857 }
5858
5859 return 0;
5860
5861 wantuseitem:
5862 level.clients[bs->client].ps.stats[STAT_HOLDABLE_ITEM] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM];
5863
5864 return 1;
5865 }
5866
5867 //trace forward to see if we can plant a detpack or something
BotSurfaceNear(bot_state_t * bs)5868 int BotSurfaceNear(bot_state_t *bs)
5869 {
5870 trace_t tr;
5871 vec3_t fwd;
5872
5873 AngleVectors(bs->viewangles, fwd, NULL, NULL);
5874
5875 fwd[0] = bs->origin[0]+(fwd[0]*64);
5876 fwd[1] = bs->origin[1]+(fwd[1]*64);
5877 fwd[2] = bs->origin[2]+(fwd[2]*64);
5878
5879 trap->Trace(&tr, bs->origin, NULL, NULL, fwd, bs->client, MASK_SOLID, qfalse, 0, 0);
5880
5881 if (tr.fraction != 1)
5882 {
5883 return 1;
5884 }
5885
5886 return 0;
5887 }
5888
5889 //could we block projectiles from the weapon potentially with a light saber?
BotWeaponBlockable(int weapon)5890 int BotWeaponBlockable(int weapon)
5891 {
5892 switch (weapon)
5893 {
5894 case WP_STUN_BATON:
5895 case WP_MELEE:
5896 return 0;
5897 case WP_DISRUPTOR:
5898 return 0;
5899 case WP_DEMP2:
5900 return 0;
5901 case WP_ROCKET_LAUNCHER:
5902 return 0;
5903 case WP_THERMAL:
5904 return 0;
5905 case WP_TRIP_MINE:
5906 return 0;
5907 case WP_DET_PACK:
5908 return 0;
5909 default:
5910 return 1;
5911 }
5912 }
5913
5914 void Cmd_EngageDuel_f(gentity_t *ent);
5915 void Cmd_ToggleSaber_f(gentity_t *ent);
5916
5917 //movement overrides
Bot_SetForcedMovement(int bot,int forward,int right,int up)5918 void Bot_SetForcedMovement(int bot, int forward, int right, int up)
5919 {
5920 bot_state_t *bs;
5921
5922 bs = botstates[bot];
5923
5924 if (!bs)
5925 { //not a bot
5926 return;
5927 }
5928
5929 if (forward != -1)
5930 {
5931 if (bs->forceMove_Forward)
5932 {
5933 bs->forceMove_Forward = 0;
5934 }
5935 else
5936 {
5937 bs->forceMove_Forward = forward;
5938 }
5939 }
5940 if (right != -1)
5941 {
5942 if (bs->forceMove_Right)
5943 {
5944 bs->forceMove_Right = 0;
5945 }
5946 else
5947 {
5948 bs->forceMove_Right = right;
5949 }
5950 }
5951 if (up != -1)
5952 {
5953 if (bs->forceMove_Up)
5954 {
5955 bs->forceMove_Up = 0;
5956 }
5957 else
5958 {
5959 bs->forceMove_Up = up;
5960 }
5961 }
5962 }
5963
5964 //the main AI loop.
5965 //please don't be too frightened.
StandardBotAI(bot_state_t * bs,float thinktime)5966 void StandardBotAI(bot_state_t *bs, float thinktime)
5967 {
5968 int wp, enemy;
5969 int desiredIndex;
5970 int goalWPIndex;
5971 int doingFallback = 0;
5972 int fjHalt;
5973 vec3_t a, ang, headlevel, eorg, noz_x, noz_y, dif, a_fo;
5974 float reaction;
5975 float bLeadAmount;
5976 int meleestrafe = 0;
5977 int useTheForce = 0;
5978 int forceHostile = 0;
5979 gentity_t *friendInLOF = 0;
5980 float mLen;
5981 int visResult = 0;
5982 int selResult = 0;
5983 int mineSelect = 0;
5984 int detSelect = 0;
5985 vec3_t preFrameGAngles;
5986
5987 if (gDeactivated)
5988 {
5989 bs->wpCurrent = NULL;
5990 bs->currentEnemy = NULL;
5991 bs->wpDestination = NULL;
5992 bs->wpDirection = 0;
5993 return;
5994 }
5995
5996 if (g_entities[bs->client].inuse &&
5997 g_entities[bs->client].client &&
5998 g_entities[bs->client].client->sess.sessionTeam == TEAM_SPECTATOR)
5999 {
6000 bs->wpCurrent = NULL;
6001 bs->currentEnemy = NULL;
6002 bs->wpDestination = NULL;
6003 bs->wpDirection = 0;
6004 return;
6005 }
6006
6007
6008 #ifndef FINAL_BUILD
6009 if (bot_getinthecarrr.integer)
6010 { //stupid vehicle debug, I tire of having to connect another client to test passengers.
6011 gentity_t *botEnt = &g_entities[bs->client];
6012
6013 if (botEnt->inuse && botEnt->client && botEnt->client->ps.m_iVehicleNum)
6014 { //in a vehicle, so...
6015 bs->noUseTime = level.time + 5000;
6016
6017 if (bot_getinthecarrr.integer != 2)
6018 {
6019 trap->EA_MoveForward(bs->client);
6020
6021 if (bot_getinthecarrr.integer == 3)
6022 { //use alt fire
6023 trap->EA_Alt_Attack(bs->client);
6024 }
6025 }
6026 }
6027 else
6028 { //find one, get in
6029 int i = 0;
6030 gentity_t *vehicle = NULL;
6031 //find the nearest, manned vehicle
6032 while (i < MAX_GENTITIES)
6033 {
6034 vehicle = &g_entities[i];
6035
6036 if (vehicle->inuse && vehicle->client && vehicle->s.eType == ET_NPC &&
6037 vehicle->s.NPC_class == CLASS_VEHICLE && vehicle->m_pVehicle &&
6038 (vehicle->client->ps.m_iVehicleNum || bot_getinthecarrr.integer == 2))
6039 { //ok, this is a vehicle, and it has a pilot/passengers
6040 break;
6041 }
6042 i++;
6043 }
6044 if (i != MAX_GENTITIES && vehicle)
6045 { //broke before end so we must've found something
6046 vec3_t v;
6047
6048 VectorSubtract(vehicle->client->ps.origin, bs->origin, v);
6049 VectorNormalize(v);
6050 vectoangles(v, bs->goalAngles);
6051 MoveTowardIdealAngles(bs);
6052 trap->EA_Move(bs->client, v, 5000.0f);
6053
6054 if (bs->noUseTime < (level.time-400))
6055 {
6056 bs->noUseTime = level.time + 500;
6057 }
6058 }
6059 }
6060
6061 return;
6062 }
6063 #endif
6064
6065 if (bot_forgimmick.integer)
6066 {
6067 bs->wpCurrent = NULL;
6068 bs->currentEnemy = NULL;
6069 bs->wpDestination = NULL;
6070 bs->wpDirection = 0;
6071
6072 if (bot_forgimmick.integer == 2)
6073 { //for debugging saber stuff, this is handy
6074 trap->EA_Attack(bs->client);
6075 }
6076
6077 if (bot_forgimmick.integer == 3)
6078 { //for testing cpu usage moving around rmg terrain without AI
6079 vec3_t mdir;
6080
6081 VectorSubtract(bs->origin, vec3_origin, mdir);
6082 VectorNormalize(mdir);
6083 trap->EA_Attack(bs->client);
6084 trap->EA_Move(bs->client, mdir, 5000);
6085 }
6086
6087 if (bot_forgimmick.integer == 4)
6088 { //constantly move toward client 0
6089 if (g_entities[0].client && g_entities[0].inuse)
6090 {
6091 vec3_t mdir;
6092
6093 VectorSubtract(g_entities[0].client->ps.origin, bs->origin, mdir);
6094 VectorNormalize(mdir);
6095 trap->EA_Move(bs->client, mdir, 5000);
6096 }
6097 }
6098
6099 if (bs->forceMove_Forward)
6100 {
6101 if (bs->forceMove_Forward > 0)
6102 {
6103 trap->EA_MoveForward(bs->client);
6104 }
6105 else
6106 {
6107 trap->EA_MoveBack(bs->client);
6108 }
6109 }
6110 if (bs->forceMove_Right)
6111 {
6112 if (bs->forceMove_Right > 0)
6113 {
6114 trap->EA_MoveRight(bs->client);
6115 }
6116 else
6117 {
6118 trap->EA_MoveLeft(bs->client);
6119 }
6120 }
6121 if (bs->forceMove_Up)
6122 {
6123 trap->EA_Jump(bs->client);
6124 }
6125 return;
6126 }
6127
6128 if (!bs->lastDeadTime)
6129 { //just spawned in?
6130 bs->lastDeadTime = level.time;
6131 }
6132
6133 if (g_entities[bs->client].health < 1)
6134 {
6135 bs->lastDeadTime = level.time;
6136
6137 if (!bs->deathActivitiesDone && bs->lastHurt && bs->lastHurt->client && bs->lastHurt->s.number != bs->client)
6138 {
6139 BotDeathNotify(bs);
6140 if (PassLovedOneCheck(bs, bs->lastHurt))
6141 {
6142 //CHAT: Died
6143 bs->chatObject = bs->lastHurt;
6144 bs->chatAltObject = NULL;
6145 BotDoChat(bs, "Died", 0);
6146 }
6147 else if (!PassLovedOneCheck(bs, bs->lastHurt) &&
6148 botstates[bs->lastHurt->s.number] &&
6149 PassLovedOneCheck(botstates[bs->lastHurt->s.number], &g_entities[bs->client]))
6150 { //killed by a bot that I love, but that does not love me
6151 bs->chatObject = bs->lastHurt;
6152 bs->chatAltObject = NULL;
6153 BotDoChat(bs, "KilledOnPurposeByLove", 0);
6154 }
6155
6156 bs->deathActivitiesDone = 1;
6157 }
6158
6159 bs->wpCurrent = NULL;
6160 bs->currentEnemy = NULL;
6161 bs->wpDestination = NULL;
6162 bs->wpCamping = NULL;
6163 bs->wpCampingTo = NULL;
6164 bs->wpStoreDest = NULL;
6165 bs->wpDestIgnoreTime = 0;
6166 bs->wpDestSwitchTime = 0;
6167 bs->wpSeenTime = 0;
6168 bs->wpDirection = 0;
6169
6170 if (rand()%10 < 5 &&
6171 (!bs->doChat || bs->chatTime < level.time))
6172 {
6173 trap->EA_Attack(bs->client);
6174 }
6175
6176 return;
6177 }
6178
6179 VectorCopy(bs->goalAngles, preFrameGAngles);
6180
6181 bs->doAttack = 0;
6182 bs->doAltAttack = 0;
6183 //reset the attack states
6184
6185 if (bs->isSquadLeader)
6186 {
6187 CommanderBotAI(bs);
6188 }
6189 else
6190 {
6191 BotDoTeamplayAI(bs);
6192 }
6193
6194 if (!bs->currentEnemy)
6195 {
6196 bs->frame_Enemy_Vis = 0;
6197 }
6198
6199 if (bs->revengeEnemy && bs->revengeEnemy->client &&
6200 bs->revengeEnemy->client->pers.connected != CON_CONNECTED && bs->revengeEnemy->client->pers.connected != CON_CONNECTING)
6201 {
6202 bs->revengeEnemy = NULL;
6203 bs->revengeHateLevel = 0;
6204 }
6205
6206 if (bs->currentEnemy && bs->currentEnemy->client &&
6207 bs->currentEnemy->client->pers.connected != CON_CONNECTED && bs->currentEnemy->client->pers.connected != CON_CONNECTING)
6208 {
6209 bs->currentEnemy = NULL;
6210 }
6211
6212 fjHalt = 0;
6213
6214 #ifndef FORCEJUMP_INSTANTMETHOD
6215 if (bs->forceJumpChargeTime > level.time)
6216 {
6217 useTheForce = 1;
6218 forceHostile = 0;
6219 }
6220
6221 if (bs->currentEnemy && bs->currentEnemy->client && bs->frame_Enemy_Vis && bs->forceJumpChargeTime < level.time)
6222 #else
6223 if (bs->currentEnemy && bs->currentEnemy->client && bs->frame_Enemy_Vis)
6224 #endif
6225 {
6226 VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, a_fo);
6227 vectoangles(a_fo, a_fo);
6228
6229 //do this above all things
6230 if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PUSH)) && (bs->doForcePush > level.time || bs->cur_ps.fd.forceGripBeingGripped > level.time) && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PUSH]][FP_PUSH] /*&& InFieldOfVision(bs->viewangles, 50, a_fo)*/)
6231 {
6232 level.clients[bs->client].ps.fd.forcePowerSelected = FP_PUSH;
6233 useTheForce = 1;
6234 forceHostile = 1;
6235 }
6236 else if (bs->cur_ps.fd.forceSide == FORCE_DARKSIDE)
6237 { //try dark side powers
6238 //in order of priority top to bottom
6239 if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_GRIP)) && (bs->cur_ps.fd.forcePowersActive & (1 << FP_GRIP)) && InFieldOfVision(bs->viewangles, 50, a_fo))
6240 { //already gripping someone, so hold it
6241 level.clients[bs->client].ps.fd.forcePowerSelected = FP_GRIP;
6242 useTheForce = 1;
6243 forceHostile = 1;
6244 }
6245 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_LIGHTNING)) && bs->frame_Enemy_Len < FORCE_LIGHTNING_RADIUS && level.clients[bs->client].ps.fd.forcePower > 50 && InFieldOfVision(bs->viewangles, 50, a_fo))
6246 {
6247 level.clients[bs->client].ps.fd.forcePowerSelected = FP_LIGHTNING;
6248 useTheForce = 1;
6249 forceHostile = 1;
6250 }
6251 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_GRIP)) && bs->frame_Enemy_Len < MAX_GRIP_DISTANCE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_GRIP]][FP_GRIP] && InFieldOfVision(bs->viewangles, 50, a_fo))
6252 {
6253 level.clients[bs->client].ps.fd.forcePowerSelected = FP_GRIP;
6254 useTheForce = 1;
6255 forceHostile = 1;
6256 }
6257 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_RAGE)) && g_entities[bs->client].health < 25 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_RAGE]][FP_RAGE])
6258 {
6259 level.clients[bs->client].ps.fd.forcePowerSelected = FP_RAGE;
6260 useTheForce = 1;
6261 forceHostile = 0;
6262 }
6263 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_DRAIN)) && bs->frame_Enemy_Len < MAX_DRAIN_DISTANCE && level.clients[bs->client].ps.fd.forcePower > 50 && InFieldOfVision(bs->viewangles, 50, a_fo) && bs->currentEnemy->client->ps.fd.forcePower > 10 && bs->currentEnemy->client->ps.fd.forceSide == FORCE_LIGHTSIDE)
6264 {
6265 level.clients[bs->client].ps.fd.forcePowerSelected = FP_DRAIN;
6266 useTheForce = 1;
6267 forceHostile = 1;
6268 }
6269 }
6270 else if (bs->cur_ps.fd.forceSide == FORCE_LIGHTSIDE)
6271 { //try light side powers
6272 if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && bs->cur_ps.fd.forceGripCripple &&
6273 level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB])
6274 { //absorb to get out
6275 level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB;
6276 useTheForce = 1;
6277 forceHostile = 0;
6278 }
6279 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && bs->cur_ps.electrifyTime >= level.time &&
6280 level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB])
6281 { //absorb lightning
6282 level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB;
6283 useTheForce = 1;
6284 forceHostile = 0;
6285 }
6286 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_TELEPATHY)) && bs->frame_Enemy_Len < MAX_TRICK_DISTANCE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TELEPATHY]][FP_TELEPATHY] && InFieldOfVision(bs->viewangles, 50, a_fo) && !(bs->currentEnemy->client->ps.fd.forcePowersActive & (1 << FP_SEE)))
6287 {
6288 level.clients[bs->client].ps.fd.forcePowerSelected = FP_TELEPATHY;
6289 useTheForce = 1;
6290 forceHostile = 1;
6291 }
6292 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && g_entities[bs->client].health < 75 && bs->currentEnemy->client->ps.fd.forceSide == FORCE_DARKSIDE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB])
6293 {
6294 level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB;
6295 useTheForce = 1;
6296 forceHostile = 0;
6297 }
6298 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PROTECT)) && g_entities[bs->client].health < 35 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PROTECT]][FP_PROTECT])
6299 {
6300 level.clients[bs->client].ps.fd.forcePowerSelected = FP_PROTECT;
6301 useTheForce = 1;
6302 forceHostile = 0;
6303 }
6304 }
6305
6306 if (!useTheForce)
6307 { //try neutral powers
6308 if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PUSH)) && bs->cur_ps.fd.forceGripBeingGripped > level.time && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PUSH]][FP_PUSH] && InFieldOfVision(bs->viewangles, 50, a_fo))
6309 {
6310 level.clients[bs->client].ps.fd.forcePowerSelected = FP_PUSH;
6311 useTheForce = 1;
6312 forceHostile = 1;
6313 }
6314 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_SPEED)) && g_entities[bs->client].health < 25 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_SPEED]][FP_SPEED])
6315 {
6316 level.clients[bs->client].ps.fd.forcePowerSelected = FP_SPEED;
6317 useTheForce = 1;
6318 forceHostile = 0;
6319 }
6320 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_SEE)) && BotMindTricked(bs->client, bs->currentEnemy->s.number) && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_SEE]][FP_SEE])
6321 {
6322 level.clients[bs->client].ps.fd.forcePowerSelected = FP_SEE;
6323 useTheForce = 1;
6324 forceHostile = 0;
6325 }
6326 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PULL)) && bs->frame_Enemy_Len < 256 && level.clients[bs->client].ps.fd.forcePower > 75 && InFieldOfVision(bs->viewangles, 50, a_fo))
6327 {
6328 level.clients[bs->client].ps.fd.forcePowerSelected = FP_PULL;
6329 useTheForce = 1;
6330 forceHostile = 1;
6331 }
6332 }
6333 }
6334
6335 if (!useTheForce)
6336 { //try powers that we don't care if we have an enemy for
6337 if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_HEAL)) && g_entities[bs->client].health < 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_HEAL]][FP_HEAL] && bs->cur_ps.fd.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_1)
6338 {
6339 level.clients[bs->client].ps.fd.forcePowerSelected = FP_HEAL;
6340 useTheForce = 1;
6341 forceHostile = 0;
6342 }
6343 else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_HEAL)) && g_entities[bs->client].health < 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_HEAL]][FP_HEAL] && !bs->currentEnemy && bs->isCamping > level.time)
6344 { //only meditate and heal if we're camping
6345 level.clients[bs->client].ps.fd.forcePowerSelected = FP_HEAL;
6346 useTheForce = 1;
6347 forceHostile = 0;
6348 }
6349 }
6350
6351 if (useTheForce && forceHostile)
6352 {
6353 if (bs->currentEnemy && bs->currentEnemy->client &&
6354 !ForcePowerUsableOn(&g_entities[bs->client], bs->currentEnemy, level.clients[bs->client].ps.fd.forcePowerSelected))
6355 {
6356 useTheForce = 0;
6357 forceHostile = 0;
6358 }
6359 }
6360
6361 doingFallback = 0;
6362
6363 bs->deathActivitiesDone = 0;
6364
6365 if (BotUseInventoryItem(bs))
6366 {
6367 if (rand()%10 < 5)
6368 {
6369 trap->EA_Use(bs->client);
6370 }
6371 }
6372
6373 if (bs->cur_ps.ammo[weaponData[bs->cur_ps.weapon].ammoIndex] < weaponData[bs->cur_ps.weapon].energyPerShot)
6374 {
6375 if (BotTryAnotherWeapon(bs))
6376 {
6377 return;
6378 }
6379 }
6380 else
6381 {
6382 if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number &&
6383 bs->frame_Enemy_Vis && bs->forceWeaponSelect /*&& bs->plantContinue < level.time*/)
6384 {
6385 bs->forceWeaponSelect = 0;
6386 }
6387
6388 if (bs->plantContinue > level.time)
6389 {
6390 bs->doAttack = 1;
6391 bs->destinationGrabTime = 0;
6392 }
6393
6394 if (!bs->forceWeaponSelect && bs->cur_ps.hasDetPackPlanted && bs->plantKillEmAll > level.time)
6395 {
6396 bs->forceWeaponSelect = WP_DET_PACK;
6397 }
6398
6399 if (bs->forceWeaponSelect)
6400 {
6401 selResult = BotSelectChoiceWeapon(bs, bs->forceWeaponSelect, 1);
6402 }
6403
6404 if (selResult)
6405 {
6406 if (selResult == 2)
6407 { //newly selected
6408 return;
6409 }
6410 }
6411 else if (BotSelectIdealWeapon(bs))
6412 {
6413 return;
6414 }
6415 }
6416 /*if (BotSelectMelee(bs))
6417 {
6418 return;
6419 }*/
6420
6421 reaction = bs->skills.reflex/bs->settings.skill;
6422
6423 if (reaction < 0)
6424 {
6425 reaction = 0;
6426 }
6427 if (reaction > 2000)
6428 {
6429 reaction = 2000;
6430 }
6431
6432 if (!bs->currentEnemy)
6433 {
6434 bs->timeToReact = level.time + reaction;
6435 }
6436
6437 if (bs->cur_ps.weapon == WP_DET_PACK && bs->cur_ps.hasDetPackPlanted && bs->plantKillEmAll > level.time)
6438 {
6439 bs->doAltAttack = 1;
6440 }
6441
6442 if (bs->wpCamping)
6443 {
6444 if (bs->isCamping < level.time)
6445 {
6446 bs->wpCamping = NULL;
6447 bs->isCamping = 0;
6448 }
6449
6450 if (bs->currentEnemy && bs->frame_Enemy_Vis)
6451 {
6452 bs->wpCamping = NULL;
6453 bs->isCamping = 0;
6454 }
6455 }
6456
6457 if (bs->wpCurrent &&
6458 (bs->wpSeenTime < level.time || bs->wpTravelTime < level.time))
6459 {
6460 bs->wpCurrent = NULL;
6461 }
6462
6463 if (bs->currentEnemy)
6464 {
6465 if (bs->enemySeenTime < level.time ||
6466 !PassStandardEnemyChecks(bs, bs->currentEnemy))
6467 {
6468 if (bs->revengeEnemy == bs->currentEnemy &&
6469 bs->currentEnemy->health < 1 &&
6470 bs->lastAttacked && bs->lastAttacked == bs->currentEnemy)
6471 {
6472 //CHAT: Destroyed hated one [KilledHatedOne section]
6473 bs->chatObject = bs->revengeEnemy;
6474 bs->chatAltObject = NULL;
6475 BotDoChat(bs, "KilledHatedOne", 1);
6476 bs->revengeEnemy = NULL;
6477 bs->revengeHateLevel = 0;
6478 }
6479 else if (bs->currentEnemy->health < 1 && PassLovedOneCheck(bs, bs->currentEnemy) &&
6480 bs->lastAttacked && bs->lastAttacked == bs->currentEnemy)
6481 {
6482 //CHAT: Killed
6483 bs->chatObject = bs->currentEnemy;
6484 bs->chatAltObject = NULL;
6485 BotDoChat(bs, "Killed", 0);
6486 }
6487
6488 bs->currentEnemy = NULL;
6489 }
6490 }
6491
6492 if (bot_honorableduelacceptance.integer)
6493 {
6494 if (bs->currentEnemy && bs->currentEnemy->client &&
6495 bs->cur_ps.weapon == WP_SABER &&
6496 g_privateDuel.integer &&
6497 bs->frame_Enemy_Vis &&
6498 bs->frame_Enemy_Len < 400 &&
6499 bs->currentEnemy->client->ps.weapon == WP_SABER &&
6500 bs->currentEnemy->client->ps.saberHolstered)
6501 {
6502 vec3_t e_ang_vec;
6503
6504 VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, e_ang_vec);
6505
6506 if (InFieldOfVision(bs->viewangles, 100, e_ang_vec))
6507 { //Our enemy has his saber holstered and has challenged us to a duel, so challenge him back
6508 if (!bs->cur_ps.saberHolstered)
6509 {
6510 Cmd_ToggleSaber_f(&g_entities[bs->client]);
6511 }
6512 else
6513 {
6514 if (bs->currentEnemy->client->ps.duelIndex == bs->client &&
6515 bs->currentEnemy->client->ps.duelTime > level.time &&
6516 !bs->cur_ps.duelInProgress)
6517 {
6518 Cmd_EngageDuel_f(&g_entities[bs->client]);
6519 }
6520 }
6521
6522 bs->doAttack = 0;
6523 bs->doAltAttack = 0;
6524 bs->botChallengingTime = level.time + 100;
6525 bs->beStill = level.time + 100;
6526 }
6527 }
6528 }
6529 //Apparently this "allows you to cheese" when fighting against bots. I'm not sure why you'd want to con bots
6530 //into an easy kill, since they're bots and all. But whatever.
6531
6532 if (!bs->wpCurrent)
6533 {
6534 wp = GetNearestVisibleWP(bs->origin, bs->client);
6535
6536 if (wp != -1)
6537 {
6538 bs->wpCurrent = gWPArray[wp];
6539 bs->wpSeenTime = level.time + 1500;
6540 bs->wpTravelTime = level.time + 10000; //never take more than 10 seconds to travel to a waypoint
6541 }
6542 }
6543
6544 if (bs->enemySeenTime < level.time || !bs->frame_Enemy_Vis || !bs->currentEnemy ||
6545 (bs->currentEnemy /*&& bs->cur_ps.weapon == WP_SABER && bs->frame_Enemy_Len > 300*/))
6546 {
6547 enemy = ScanForEnemies(bs);
6548
6549 if (enemy != -1)
6550 {
6551 bs->currentEnemy = &g_entities[enemy];
6552 bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
6553 }
6554 }
6555
6556 if (!bs->squadLeader && !bs->isSquadLeader)
6557 {
6558 BotScanForLeader(bs);
6559 }
6560
6561 if (!bs->squadLeader && bs->squadCannotLead < level.time)
6562 { //if still no leader after scanning, then become a squad leader
6563 bs->isSquadLeader = 1;
6564 }
6565
6566 if (bs->isSquadLeader && bs->squadLeader)
6567 { //we don't follow anyone if we are a leader
6568 bs->squadLeader = NULL;
6569 }
6570
6571 //ESTABLISH VISIBILITIES AND DISTANCES FOR THE WHOLE FRAME HERE
6572 if (bs->wpCurrent)
6573 {
6574 if (RMG.integer)
6575 { //this is somewhat hacky, but in RMG we don't really care about vertical placement because points are scattered across only the terrain.
6576 vec3_t vecB, vecC;
6577
6578 vecB[0] = bs->origin[0];
6579 vecB[1] = bs->origin[1];
6580 vecB[2] = bs->origin[2];
6581
6582 vecC[0] = bs->wpCurrent->origin[0];
6583 vecC[1] = bs->wpCurrent->origin[1];
6584 vecC[2] = vecB[2];
6585
6586
6587 VectorSubtract(vecC, vecB, a);
6588 }
6589 else
6590 {
6591 VectorSubtract(bs->wpCurrent->origin, bs->origin, a);
6592 }
6593 bs->frame_Waypoint_Len = VectorLength(a);
6594
6595 visResult = WPOrgVisible(&g_entities[bs->client], bs->origin, bs->wpCurrent->origin, bs->client);
6596
6597 if (visResult == 2)
6598 {
6599 bs->frame_Waypoint_Vis = 0;
6600 bs->wpSeenTime = 0;
6601 bs->wpDestination = NULL;
6602 bs->wpDestIgnoreTime = level.time + 5000;
6603
6604 if (bs->wpDirection)
6605 {
6606 bs->wpDirection = 0;
6607 }
6608 else
6609 {
6610 bs->wpDirection = 1;
6611 }
6612 }
6613 else if (visResult)
6614 {
6615 bs->frame_Waypoint_Vis = 1;
6616 }
6617 else
6618 {
6619 bs->frame_Waypoint_Vis = 0;
6620 }
6621 }
6622
6623 if (bs->currentEnemy)
6624 {
6625 if (bs->currentEnemy->client)
6626 {
6627 VectorCopy(bs->currentEnemy->client->ps.origin, eorg);
6628 eorg[2] += bs->currentEnemy->client->ps.viewheight;
6629 }
6630 else
6631 {
6632 VectorCopy(bs->currentEnemy->s.origin, eorg);
6633 }
6634
6635 VectorSubtract(eorg, bs->eye, a);
6636 bs->frame_Enemy_Len = VectorLength(a);
6637
6638 if (OrgVisible(bs->eye, eorg, bs->client))
6639 {
6640 bs->frame_Enemy_Vis = 1;
6641 VectorCopy(eorg, bs->lastEnemySpotted);
6642 VectorCopy(bs->origin, bs->hereWhenSpotted);
6643 bs->lastVisibleEnemyIndex = bs->currentEnemy->s.number;
6644 //VectorCopy(bs->eye, bs->lastEnemySpotted);
6645 bs->hitSpotted = 0;
6646 }
6647 else
6648 {
6649 bs->frame_Enemy_Vis = 0;
6650 }
6651 }
6652 else
6653 {
6654 bs->lastVisibleEnemyIndex = ENTITYNUM_NONE;
6655 }
6656 //END
6657
6658 if (bs->frame_Enemy_Vis)
6659 {
6660 bs->enemySeenTime = level.time + ENEMY_FORGET_MS;
6661 }
6662
6663 if (bs->wpCurrent)
6664 {
6665 int wpTouchDist = BOT_WPTOUCH_DISTANCE;
6666 WPConstantRoutine(bs);
6667
6668 if (!bs->wpCurrent)
6669 { //WPConstantRoutine has the ability to nullify the waypoint if it fails certain checks, so..
6670 return;
6671 }
6672
6673 if (bs->wpCurrent->flags & WPFLAG_WAITFORFUNC)
6674 {
6675 if (!CheckForFunc(bs->wpCurrent->origin, -1))
6676 {
6677 bs->beStill = level.time + 500; //no func brush under.. wait
6678 }
6679 }
6680 if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC)
6681 {
6682 if (CheckForFunc(bs->wpCurrent->origin, -1))
6683 {
6684 bs->beStill = level.time + 500; //func brush under.. wait
6685 }
6686 }
6687
6688 if (bs->frame_Waypoint_Vis || (bs->wpCurrent->flags & WPFLAG_NOVIS))
6689 {
6690 if (RMG.integer)
6691 {
6692 bs->wpSeenTime = level.time + 5000; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it
6693 }
6694 else
6695 {
6696 bs->wpSeenTime = level.time + 1500; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it
6697 }
6698 }
6699 VectorCopy(bs->wpCurrent->origin, bs->goalPosition);
6700 if (bs->wpDirection)
6701 {
6702 goalWPIndex = bs->wpCurrent->index-1;
6703 }
6704 else
6705 {
6706 goalWPIndex = bs->wpCurrent->index+1;
6707 }
6708
6709 if (bs->wpCamping)
6710 {
6711 VectorSubtract(bs->wpCampingTo->origin, bs->origin, a);
6712 vectoangles(a, ang);
6713 VectorCopy(ang, bs->goalAngles);
6714
6715 VectorSubtract(bs->origin, bs->wpCamping->origin, a);
6716 if (VectorLength(a) < 64)
6717 {
6718 VectorCopy(bs->wpCamping->origin, bs->goalPosition);
6719 bs->beStill = level.time + 1000;
6720
6721 if (!bs->campStanding)
6722 {
6723 bs->duckTime = level.time + 1000;
6724 }
6725 }
6726 }
6727 else if (gWPArray[goalWPIndex] && gWPArray[goalWPIndex]->inuse &&
6728 !(gLevelFlags & LEVELFLAG_NOPOINTPREDICTION))
6729 {
6730 VectorSubtract(gWPArray[goalWPIndex]->origin, bs->origin, a);
6731 vectoangles(a, ang);
6732 VectorCopy(ang, bs->goalAngles);
6733 }
6734 else
6735 {
6736 VectorSubtract(bs->wpCurrent->origin, bs->origin, a);
6737 vectoangles(a, ang);
6738 VectorCopy(ang, bs->goalAngles);
6739 }
6740
6741 if (bs->destinationGrabTime < level.time /*&& (!bs->wpDestination || (bs->currentEnemy && bs->frame_Enemy_Vis))*/)
6742 {
6743 GetIdealDestination(bs);
6744 }
6745
6746 if (bs->wpCurrent && bs->wpDestination)
6747 {
6748 if (TotalTrailDistance(bs->wpCurrent->index, bs->wpDestination->index, bs) == -1)
6749 {
6750 bs->wpDestination = NULL;
6751 bs->destinationGrabTime = level.time + 10000;
6752 }
6753 }
6754
6755 if (RMG.integer)
6756 {
6757 if (bs->frame_Waypoint_Vis)
6758 {
6759 if (bs->wpCurrent && !bs->wpCurrent->flags)
6760 {
6761 wpTouchDist *= 3;
6762 }
6763 }
6764 }
6765
6766 if (bs->frame_Waypoint_Len < wpTouchDist || (RMG.integer && bs->frame_Waypoint_Len < wpTouchDist*2))
6767 {
6768 WPTouchRoutine(bs);
6769
6770 if (!bs->wpDirection)
6771 {
6772 desiredIndex = bs->wpCurrent->index+1;
6773 }
6774 else
6775 {
6776 desiredIndex = bs->wpCurrent->index-1;
6777 }
6778
6779 if (gWPArray[desiredIndex] &&
6780 gWPArray[desiredIndex]->inuse &&
6781 desiredIndex < gWPNum &&
6782 desiredIndex >= 0 &&
6783 PassWayCheck(bs, desiredIndex))
6784 {
6785 bs->wpCurrent = gWPArray[desiredIndex];
6786 }
6787 else
6788 {
6789 if (bs->wpDestination)
6790 {
6791 bs->wpDestination = NULL;
6792 bs->destinationGrabTime = level.time + 10000;
6793 }
6794
6795 if (bs->wpDirection)
6796 {
6797 bs->wpDirection = 0;
6798 }
6799 else
6800 {
6801 bs->wpDirection = 1;
6802 }
6803 }
6804 }
6805 }
6806 else //We can't find a waypoint, going to need a fallback routine.
6807 {
6808 /*if (level.gametype == GT_DUEL)*/
6809 { //helps them get out of messy situations
6810 /*if ((level.time - bs->forceJumpChargeTime) > 3500)
6811 {
6812 bs->forceJumpChargeTime = level.time + 2000;
6813 trap->EA_MoveForward(bs->client);
6814 }
6815 */
6816 bs->jumpTime = level.time + 1500;
6817 bs->jumpHoldTime = level.time + 1500;
6818 bs->jDelay = 0;
6819 }
6820 doingFallback = BotFallbackNavigation(bs);
6821 }
6822
6823 if (RMG.integer)
6824 { //for RMG if the bot sticks around an area too long, jump around randomly some to spread to a new area (horrible hacky method)
6825 vec3_t vSubDif;
6826
6827 VectorSubtract(bs->origin, bs->lastSignificantAreaChange, vSubDif);
6828 if (VectorLength(vSubDif) > 1500)
6829 {
6830 VectorCopy(bs->origin, bs->lastSignificantAreaChange);
6831 bs->lastSignificantChangeTime = level.time + 20000;
6832 }
6833
6834 if (bs->lastSignificantChangeTime < level.time)
6835 {
6836 bs->iHaveNoIdeaWhereIAmGoing = level.time + 17000;
6837 }
6838 }
6839
6840 if (bs->iHaveNoIdeaWhereIAmGoing > level.time && !bs->currentEnemy)
6841 {
6842 VectorCopy(preFrameGAngles, bs->goalAngles);
6843 bs->wpCurrent = NULL;
6844 bs->wpSwitchTime = level.time + 150;
6845 doingFallback = BotFallbackNavigation(bs);
6846 bs->jumpTime = level.time + 150;
6847 bs->jumpHoldTime = level.time + 150;
6848 bs->jDelay = 0;
6849 bs->lastSignificantChangeTime = level.time + 25000;
6850 }
6851
6852 if (bs->wpCurrent && RMG.integer)
6853 {
6854 qboolean doJ = qfalse;
6855
6856 if (bs->wpCurrent->origin[2]-192 > bs->origin[2])
6857 {
6858 doJ = qtrue;
6859 }
6860 else if ((bs->wpTravelTime - level.time) < 5000 && bs->wpCurrent->origin[2]-64 > bs->origin[2])
6861 {
6862 doJ = qtrue;
6863 }
6864 else if ((bs->wpTravelTime - level.time) < 7000 && (bs->wpCurrent->flags & WPFLAG_RED_FLAG))
6865 {
6866 if ((level.time - bs->jumpTime) > 200)
6867 {
6868 bs->jumpTime = level.time + 100;
6869 bs->jumpHoldTime = level.time + 100;
6870 bs->jDelay = 0;
6871 }
6872 }
6873 else if ((bs->wpTravelTime - level.time) < 7000 && (bs->wpCurrent->flags & WPFLAG_BLUE_FLAG))
6874 {
6875 if ((level.time - bs->jumpTime) > 200)
6876 {
6877 bs->jumpTime = level.time + 100;
6878 bs->jumpHoldTime = level.time + 100;
6879 bs->jDelay = 0;
6880 }
6881 }
6882 else if (bs->wpCurrent->index > 0)
6883 {
6884 if ((bs->wpTravelTime - level.time) < 7000)
6885 {
6886 if ((gWPArray[bs->wpCurrent->index-1]->flags & WPFLAG_RED_FLAG) ||
6887 (gWPArray[bs->wpCurrent->index-1]->flags & WPFLAG_BLUE_FLAG))
6888 {
6889 if ((level.time - bs->jumpTime) > 200)
6890 {
6891 bs->jumpTime = level.time + 100;
6892 bs->jumpHoldTime = level.time + 100;
6893 bs->jDelay = 0;
6894 }
6895 }
6896 }
6897 }
6898
6899 if (doJ)
6900 {
6901 bs->jumpTime = level.time + 1500;
6902 bs->jumpHoldTime = level.time + 1500;
6903 bs->jDelay = 0;
6904 }
6905 }
6906
6907 if (doingFallback)
6908 {
6909 bs->doingFallback = qtrue;
6910 }
6911 else
6912 {
6913 bs->doingFallback = qfalse;
6914 }
6915
6916 if (bs->timeToReact < level.time && bs->currentEnemy && bs->enemySeenTime > level.time + (ENEMY_FORGET_MS - (ENEMY_FORGET_MS*0.2)))
6917 {
6918 if (bs->frame_Enemy_Vis)
6919 {
6920 CombatBotAI(bs, thinktime);
6921 }
6922 else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT)
6923 { //keep charging in case we see him again before we lose track of him
6924 bs->doAltAttack = 1;
6925 }
6926 else if (bs->cur_ps.weaponstate == WEAPON_CHARGING)
6927 { //keep charging in case we see him again before we lose track of him
6928 bs->doAttack = 1;
6929 }
6930
6931 if (bs->destinationGrabTime > level.time + 100)
6932 {
6933 bs->destinationGrabTime = level.time + 100; //assures that we will continue staying within a general area of where we want to be in a combat situation
6934 }
6935
6936 if (bs->currentEnemy->client)
6937 {
6938 VectorCopy(bs->currentEnemy->client->ps.origin, headlevel);
6939 headlevel[2] += bs->currentEnemy->client->ps.viewheight;
6940 }
6941 else
6942 {
6943 VectorCopy(bs->currentEnemy->client->ps.origin, headlevel);
6944 }
6945
6946 if (!bs->frame_Enemy_Vis)
6947 {
6948 //if (!bs->hitSpotted && VectorLength(a) > 256)
6949 if (OrgVisible(bs->eye, bs->lastEnemySpotted, -1))
6950 {
6951 VectorCopy(bs->lastEnemySpotted, headlevel);
6952 VectorSubtract(headlevel, bs->eye, a);
6953 vectoangles(a, ang);
6954 VectorCopy(ang, bs->goalAngles);
6955
6956 if (bs->cur_ps.weapon == WP_FLECHETTE &&
6957 bs->cur_ps.weaponstate == WEAPON_READY &&
6958 bs->currentEnemy && bs->currentEnemy->client)
6959 {
6960 mLen = VectorLength(a) > 128;
6961 if (mLen > 128 && mLen < 1024)
6962 {
6963 VectorSubtract(bs->currentEnemy->client->ps.origin, bs->lastEnemySpotted, a);
6964
6965 if (VectorLength(a) < 300)
6966 {
6967 bs->doAltAttack = 1;
6968 }
6969 }
6970 }
6971 }
6972 }
6973 else
6974 {
6975 bLeadAmount = BotWeaponCanLead(bs);
6976 if ((bs->skills.accuracy/bs->settings.skill) <= 8 &&
6977 bLeadAmount)
6978 {
6979 BotAimLeading(bs, headlevel, bLeadAmount);
6980 }
6981 else
6982 {
6983 VectorSubtract(headlevel, bs->eye, a);
6984 vectoangles(a, ang);
6985 VectorCopy(ang, bs->goalAngles);
6986 }
6987
6988 BotAimOffsetGoalAngles(bs);
6989 }
6990 }
6991
6992 if (bs->cur_ps.saberInFlight)
6993 {
6994 bs->saberThrowTime = level.time + Q_irand(4000, 10000);
6995 }
6996
6997 if (bs->currentEnemy)
6998 {
6999 if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER)
7000 {
7001 int saberRange = SABER_ATTACK_RANGE;
7002
7003 VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, a_fo);
7004 vectoangles(a_fo, a_fo);
7005
7006 if (bs->saberPowerTime < level.time)
7007 { //Don't just use strong attacks constantly, switch around a bit
7008 if (Q_irand(1, 10) <= 5)
7009 {
7010 bs->saberPower = qtrue;
7011 }
7012 else
7013 {
7014 bs->saberPower = qfalse;
7015 }
7016
7017 bs->saberPowerTime = level.time + Q_irand(3000, 15000);
7018 }
7019
7020 if ( g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_STAFF
7021 && g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_DUAL )
7022 {
7023 if (bs->currentEnemy->health > 75
7024 && g_entities[bs->client].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > 2)
7025 {
7026 if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_STRONG
7027 && bs->saberPower)
7028 { //if we are up against someone with a lot of health and we have a strong attack available, then h4q them
7029 Cmd_SaberAttackCycle_f(&g_entities[bs->client]);
7030 }
7031 }
7032 else if (bs->currentEnemy->health > 40
7033 && g_entities[bs->client].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > 1)
7034 {
7035 if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_MEDIUM)
7036 { //they're down on health a little, use level 2 if we can
7037 Cmd_SaberAttackCycle_f(&g_entities[bs->client]);
7038 }
7039 }
7040 else
7041 {
7042 if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_FAST)
7043 { //they've gone below 40 health, go at them with quick attacks
7044 Cmd_SaberAttackCycle_f(&g_entities[bs->client]);
7045 }
7046 }
7047 }
7048
7049 if (level.gametype == GT_SINGLE_PLAYER)
7050 {
7051 saberRange *= 3;
7052 }
7053
7054 if (bs->frame_Enemy_Len <= saberRange)
7055 {
7056 SaberCombatHandling(bs);
7057
7058 if (bs->frame_Enemy_Len < 80)
7059 {
7060 meleestrafe = 1;
7061 }
7062 }
7063 else if (bs->saberThrowTime < level.time && !bs->cur_ps.saberInFlight &&
7064 (bs->cur_ps.fd.forcePowersKnown & (1 << FP_SABERTHROW)) &&
7065 InFieldOfVision(bs->viewangles, 30, a_fo) &&
7066 bs->frame_Enemy_Len < BOT_SABER_THROW_RANGE &&
7067 bs->cur_ps.fd.saberAnimLevel != SS_STAFF)
7068 {
7069 bs->doAltAttack = 1;
7070 bs->doAttack = 0;
7071 }
7072 else if (bs->cur_ps.saberInFlight && bs->frame_Enemy_Len > 300 && bs->frame_Enemy_Len < BOT_SABER_THROW_RANGE)
7073 {
7074 bs->doAltAttack = 1;
7075 bs->doAttack = 0;
7076 }
7077 }
7078 else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)
7079 {
7080 if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE)
7081 {
7082 MeleeCombatHandling(bs);
7083 meleestrafe = 1;
7084 }
7085 }
7086 }
7087
7088 if (doingFallback && bs->currentEnemy) //just stand and fire if we have no idea where we are
7089 {
7090 VectorCopy(bs->origin, bs->goalPosition);
7091 }
7092
7093 if (bs->forceJumping > level.time)
7094 {
7095 VectorCopy(bs->origin, noz_x);
7096 VectorCopy(bs->goalPosition, noz_y);
7097
7098 noz_x[2] = noz_y[2];
7099
7100 VectorSubtract(noz_x, noz_y, noz_x);
7101
7102 if (VectorLength(noz_x) < 32)
7103 {
7104 fjHalt = 1;
7105 }
7106 }
7107
7108 if (bs->doChat && bs->chatTime > level.time && (!bs->currentEnemy || !bs->frame_Enemy_Vis))
7109 {
7110 return;
7111 }
7112 else if (bs->doChat && bs->currentEnemy && bs->frame_Enemy_Vis)
7113 {
7114 //bs->chatTime = level.time + bs->chatTime_stored;
7115 bs->doChat = 0; //do we want to keep the bot waiting to chat until after the enemy is gone?
7116 bs->chatTeam = 0;
7117 }
7118 else if (bs->doChat && bs->chatTime <= level.time)
7119 {
7120 if (bs->chatTeam)
7121 {
7122 trap->EA_SayTeam(bs->client, bs->currentChat);
7123 bs->chatTeam = 0;
7124 }
7125 else
7126 {
7127 trap->EA_Say(bs->client, bs->currentChat);
7128 }
7129 if (bs->doChat == 2)
7130 {
7131 BotReplyGreetings(bs);
7132 }
7133 bs->doChat = 0;
7134 }
7135
7136 CTFFlagMovement(bs);
7137
7138 if (/*bs->wpDestination &&*/ bs->shootGoal &&
7139 /*bs->wpDestination->associated_entity == bs->shootGoal->s.number &&*/
7140 bs->shootGoal->health > 0 && bs->shootGoal->takedamage)
7141 {
7142 dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2;
7143 dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2;
7144 dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2;
7145
7146 if (!bs->currentEnemy || bs->frame_Enemy_Len > 256)
7147 { //if someone is close then don't stop shooting them for this
7148 VectorSubtract(dif, bs->eye, a);
7149 vectoangles(a, a);
7150 VectorCopy(a, bs->goalAngles);
7151
7152 if (InFieldOfVision(bs->viewangles, 30, a) &&
7153 EntityVisibleBox(bs->origin, NULL, NULL, dif, bs->client, bs->shootGoal->s.number))
7154 {
7155 bs->doAttack = 1;
7156 }
7157 }
7158 }
7159
7160 if (bs->cur_ps.hasDetPackPlanted)
7161 { //check if our enemy gets near it and detonate if he does
7162 BotCheckDetPacks(bs);
7163 }
7164 else if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number && !bs->frame_Enemy_Vis && bs->plantTime < level.time &&
7165 !bs->doAttack && !bs->doAltAttack)
7166 {
7167 VectorSubtract(bs->origin, bs->hereWhenSpotted, a);
7168
7169 if (bs->plantDecided > level.time || (bs->frame_Enemy_Len < BOT_PLANT_DISTANCE*2 && VectorLength(a) < BOT_PLANT_DISTANCE))
7170 {
7171 mineSelect = BotSelectChoiceWeapon(bs, WP_TRIP_MINE, 0);
7172 detSelect = BotSelectChoiceWeapon(bs, WP_DET_PACK, 0);
7173 if (bs->cur_ps.hasDetPackPlanted)
7174 {
7175 detSelect = 0;
7176 }
7177
7178 if (bs->plantDecided > level.time && bs->forceWeaponSelect &&
7179 bs->cur_ps.weapon == bs->forceWeaponSelect)
7180 {
7181 bs->doAttack = 1;
7182 bs->plantDecided = 0;
7183 bs->plantTime = level.time + BOT_PLANT_INTERVAL;
7184 bs->plantContinue = level.time + 500;
7185 bs->beStill = level.time + 500;
7186 }
7187 else if (mineSelect || detSelect)
7188 {
7189 if (BotSurfaceNear(bs))
7190 {
7191 if (!mineSelect)
7192 { //if no mines use detpacks, otherwise use mines
7193 mineSelect = WP_DET_PACK;
7194 }
7195 else
7196 {
7197 mineSelect = WP_TRIP_MINE;
7198 }
7199
7200 detSelect = BotSelectChoiceWeapon(bs, mineSelect, 1);
7201
7202 if (detSelect && detSelect != 2)
7203 { //We have it and it is now our weapon
7204 bs->plantDecided = level.time + 1000;
7205 bs->forceWeaponSelect = mineSelect;
7206 return;
7207 }
7208 else if (detSelect == 2)
7209 {
7210 bs->forceWeaponSelect = mineSelect;
7211 return;
7212 }
7213 }
7214 }
7215 }
7216 }
7217 else if (bs->plantContinue < level.time)
7218 {
7219 bs->forceWeaponSelect = 0;
7220 }
7221
7222 if (level.gametype == GT_JEDIMASTER && !bs->cur_ps.isJediMaster && bs->jmState == -1 && gJMSaberEnt && gJMSaberEnt->inuse)
7223 {
7224 vec3_t saberLen;
7225 float fSaberLen = 0;
7226
7227 VectorSubtract(bs->origin, gJMSaberEnt->r.currentOrigin, saberLen);
7228 fSaberLen = VectorLength(saberLen);
7229
7230 if (fSaberLen < 256)
7231 {
7232 if (OrgVisible(bs->origin, gJMSaberEnt->r.currentOrigin, bs->client))
7233 {
7234 VectorCopy(gJMSaberEnt->r.currentOrigin, bs->goalPosition);
7235 }
7236 }
7237 }
7238
7239 if (bs->beStill < level.time && !WaitingForNow(bs, bs->goalPosition) && !fjHalt)
7240 {
7241 VectorSubtract(bs->goalPosition, bs->origin, bs->goalMovedir);
7242 VectorNormalize(bs->goalMovedir);
7243
7244 if (bs->jumpTime > level.time && bs->jDelay < level.time &&
7245 level.clients[bs->client].pers.cmd.upmove > 0)
7246 {
7247 // trap->EA_Move(bs->client, bs->origin, 5000);
7248 bs->beStill = level.time + 200;
7249 }
7250 else
7251 {
7252 trap->EA_Move(bs->client, bs->goalMovedir, 5000);
7253 }
7254
7255 if (meleestrafe)
7256 {
7257 StrafeTracing(bs);
7258 }
7259
7260 if (bs->meleeStrafeDir && meleestrafe && bs->meleeStrafeDisable < level.time)
7261 {
7262 trap->EA_MoveRight(bs->client);
7263 }
7264 else if (meleestrafe && bs->meleeStrafeDisable < level.time)
7265 {
7266 trap->EA_MoveLeft(bs->client);
7267 }
7268
7269 if (BotTrace_Jump(bs, bs->goalPosition))
7270 {
7271 bs->jumpTime = level.time + 100;
7272 }
7273 else if (BotTrace_Duck(bs, bs->goalPosition))
7274 {
7275 bs->duckTime = level.time + 100;
7276 }
7277 #ifdef BOT_STRAFE_AVOIDANCE
7278 else
7279 {
7280 int strafeAround = BotTrace_Strafe(bs, bs->goalPosition);
7281
7282 if (strafeAround == STRAFEAROUND_RIGHT)
7283 {
7284 trap->EA_MoveRight(bs->client);
7285 }
7286 else if (strafeAround == STRAFEAROUND_LEFT)
7287 {
7288 trap->EA_MoveLeft(bs->client);
7289 }
7290 }
7291 #endif
7292 }
7293
7294 #ifndef FORCEJUMP_INSTANTMETHOD
7295 if (bs->forceJumpChargeTime > level.time)
7296 {
7297 bs->jumpTime = 0;
7298 }
7299 #endif
7300
7301 if (bs->jumpPrep > level.time)
7302 {
7303 bs->forceJumpChargeTime = 0;
7304 }
7305
7306 if (bs->forceJumpChargeTime > level.time)
7307 {
7308 bs->jumpHoldTime = ((bs->forceJumpChargeTime - level.time)/2) + level.time;
7309 bs->forceJumpChargeTime = 0;
7310 }
7311
7312 if (bs->jumpHoldTime > level.time)
7313 {
7314 bs->jumpTime = bs->jumpHoldTime;
7315 }
7316
7317 if (bs->jumpTime > level.time && bs->jDelay < level.time)
7318 {
7319 if (bs->jumpHoldTime > level.time)
7320 {
7321 trap->EA_Jump(bs->client);
7322 if (bs->wpCurrent)
7323 {
7324 if ((bs->wpCurrent->origin[2] - bs->origin[2]) < 64)
7325 {
7326 trap->EA_MoveForward(bs->client);
7327 }
7328 }
7329 else
7330 {
7331 trap->EA_MoveForward(bs->client);
7332 }
7333 if (g_entities[bs->client].client->ps.groundEntityNum == ENTITYNUM_NONE)
7334 {
7335 g_entities[bs->client].client->ps.pm_flags |= PMF_JUMP_HELD;
7336 }
7337 }
7338 else if (!(bs->cur_ps.pm_flags & PMF_JUMP_HELD))
7339 {
7340 trap->EA_Jump(bs->client);
7341 }
7342 }
7343
7344 if (bs->duckTime > level.time)
7345 {
7346 trap->EA_Crouch(bs->client);
7347 }
7348
7349 if ( bs->dangerousObject && bs->dangerousObject->inuse && bs->dangerousObject->health > 0 &&
7350 bs->dangerousObject->takedamage && (!bs->frame_Enemy_Vis || !bs->currentEnemy) &&
7351 (BotGetWeaponRange(bs) == BWEAPONRANGE_MID || BotGetWeaponRange(bs) == BWEAPONRANGE_LONG) &&
7352 bs->cur_ps.weapon != WP_DET_PACK && bs->cur_ps.weapon != WP_TRIP_MINE &&
7353 !bs->shootGoal )
7354 {
7355 float danLen;
7356
7357 VectorSubtract(bs->dangerousObject->r.currentOrigin, bs->eye, a);
7358
7359 danLen = VectorLength(a);
7360
7361 if (danLen > 256)
7362 {
7363 vectoangles(a, a);
7364 VectorCopy(a, bs->goalAngles);
7365
7366 if (Q_irand(1, 10) < 5)
7367 {
7368 bs->goalAngles[YAW] += Q_irand(0, 3);
7369 bs->goalAngles[PITCH] += Q_irand(0, 3);
7370 }
7371 else
7372 {
7373 bs->goalAngles[YAW] -= Q_irand(0, 3);
7374 bs->goalAngles[PITCH] -= Q_irand(0, 3);
7375 }
7376
7377 if (InFieldOfVision(bs->viewangles, 30, a) &&
7378 EntityVisibleBox(bs->origin, NULL, NULL, bs->dangerousObject->r.currentOrigin, bs->client, bs->dangerousObject->s.number))
7379 {
7380 bs->doAttack = 1;
7381 }
7382 }
7383 }
7384
7385 if (PrimFiring(bs) ||
7386 AltFiring(bs))
7387 {
7388 friendInLOF = CheckForFriendInLOF(bs);
7389
7390 if (friendInLOF)
7391 {
7392 if (PrimFiring(bs))
7393 {
7394 KeepPrimFromFiring(bs);
7395 }
7396 if (AltFiring(bs))
7397 {
7398 KeepAltFromFiring(bs);
7399 }
7400 if (useTheForce && forceHostile)
7401 {
7402 useTheForce = 0;
7403 }
7404
7405 if (!useTheForce && friendInLOF->client)
7406 { //we have a friend here and are not currently using force powers, see if we can help them out
7407 if (friendInLOF->health <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL])
7408 {
7409 level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_HEAL;
7410 useTheForce = 1;
7411 forceHostile = 0;
7412 }
7413 else if (friendInLOF->client->ps.fd.forcePower <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE])
7414 {
7415 level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_FORCE;
7416 useTheForce = 1;
7417 forceHostile = 0;
7418 }
7419 }
7420 }
7421 }
7422 else if (level.gametype >= GT_TEAM)
7423 { //still check for anyone to help..
7424 friendInLOF = CheckForFriendInLOF(bs);
7425
7426 if (!useTheForce && friendInLOF)
7427 {
7428 if (friendInLOF->health <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL])
7429 {
7430 level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_HEAL;
7431 useTheForce = 1;
7432 forceHostile = 0;
7433 }
7434 else if (friendInLOF->client->ps.fd.forcePower <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE])
7435 {
7436 level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_FORCE;
7437 useTheForce = 1;
7438 forceHostile = 0;
7439 }
7440 }
7441 }
7442
7443 if (bs->doAttack && bs->cur_ps.weapon == WP_DET_PACK &&
7444 bs->cur_ps.hasDetPackPlanted)
7445 { //maybe a bit hackish, but bots only want to plant one of these at any given time to avoid complications
7446 bs->doAttack = 0;
7447 }
7448
7449 if (bs->doAttack && bs->cur_ps.weapon == WP_SABER &&
7450 bs->saberDefending && bs->currentEnemy && bs->currentEnemy->client &&
7451 BotWeaponBlockable(bs->currentEnemy->client->ps.weapon) )
7452 {
7453 bs->doAttack = 0;
7454 }
7455
7456 if (bs->cur_ps.saberLockTime > level.time)
7457 {
7458 if (rand()%10 < 5)
7459 {
7460 bs->doAttack = 1;
7461 }
7462 else
7463 {
7464 bs->doAttack = 0;
7465 }
7466 }
7467
7468 if (bs->botChallengingTime > level.time)
7469 {
7470 bs->doAttack = 0;
7471 bs->doAltAttack = 0;
7472 }
7473
7474 if (bs->cur_ps.weapon == WP_SABER &&
7475 bs->cur_ps.saberInFlight &&
7476 !bs->cur_ps.saberEntityNum)
7477 { //saber knocked away, keep trying to get it back
7478 bs->doAttack = 1;
7479 bs->doAltAttack = 0;
7480 }
7481
7482 if (bs->doAttack)
7483 {
7484 trap->EA_Attack(bs->client);
7485 }
7486 else if (bs->doAltAttack)
7487 {
7488 trap->EA_Alt_Attack(bs->client);
7489 }
7490
7491 if (useTheForce && forceHostile && bs->botChallengingTime > level.time)
7492 {
7493 useTheForce = qfalse;
7494 }
7495
7496 if (useTheForce)
7497 {
7498 #ifndef FORCEJUMP_INSTANTMETHOD
7499 if (bs->forceJumpChargeTime > level.time)
7500 {
7501 level.clients[bs->client].ps.fd.forcePowerSelected = FP_LEVITATION;
7502 trap->EA_ForcePower(bs->client);
7503 }
7504 else
7505 {
7506 #endif
7507 if (bot_forcepowers.integer && !g_forcePowerDisable.integer)
7508 {
7509 trap->EA_ForcePower(bs->client);
7510 }
7511 #ifndef FORCEJUMP_INSTANTMETHOD
7512 }
7513 #endif
7514 }
7515
7516 MoveTowardIdealAngles(bs);
7517 }
7518
7519 int gUpdateVars = 0;
7520
7521 /*
7522 ==================
7523 BotAIStartFrame
7524 ==================
7525 */
BotAIStartFrame(int time)7526 int BotAIStartFrame(int time) {
7527 int i;
7528 int elapsed_time, thinktime;
7529 static int local_time;
7530 // static int botlib_residual;
7531 static int lastbotthink_time;
7532
7533 if (gUpdateVars < level.time)
7534 {
7535 trap->Cvar_Update(&bot_pvstype);
7536 trap->Cvar_Update(&bot_camp);
7537 trap->Cvar_Update(&bot_attachments);
7538 trap->Cvar_Update(&bot_forgimmick);
7539 trap->Cvar_Update(&bot_honorableduelacceptance);
7540 #ifndef FINAL_BUILD
7541 trap->Cvar_Update(&bot_getinthecarrr);
7542 #endif
7543 gUpdateVars = level.time + 1000;
7544 }
7545
7546 G_CheckBotSpawn();
7547
7548 //rww - addl bot frame functions
7549 if (gBotEdit)
7550 {
7551 trap->Cvar_Update(&bot_wp_info);
7552 BotWaypointRender();
7553 }
7554
7555 UpdateEventTracker();
7556 //end rww
7557
7558 //cap the bot think time
7559 //if the bot think time changed we should reschedule the bots
7560 if (BOT_THINK_TIME != lastbotthink_time) {
7561 lastbotthink_time = BOT_THINK_TIME;
7562 BotScheduleBotThink();
7563 }
7564
7565 elapsed_time = time - local_time;
7566 local_time = time;
7567
7568 if (elapsed_time > BOT_THINK_TIME) thinktime = elapsed_time;
7569 else thinktime = BOT_THINK_TIME;
7570
7571 // execute scheduled bot AI
7572 for( i = 0; i < MAX_CLIENTS; i++ ) {
7573 if( !botstates[i] || !botstates[i]->inuse ) {
7574 continue;
7575 }
7576 //
7577 botstates[i]->botthink_residual += elapsed_time;
7578 //
7579 if ( botstates[i]->botthink_residual >= thinktime ) {
7580 botstates[i]->botthink_residual -= thinktime;
7581
7582 if (g_entities[i].client->pers.connected == CON_CONNECTED) {
7583 BotAI(i, (float) thinktime / 1000);
7584 }
7585 }
7586 }
7587
7588 // execute bot user commands every frame
7589 for( i = 0; i < MAX_CLIENTS; i++ ) {
7590 if( !botstates[i] || !botstates[i]->inuse ) {
7591 continue;
7592 }
7593 if( g_entities[i].client->pers.connected != CON_CONNECTED ) {
7594 continue;
7595 }
7596
7597 BotUpdateInput(botstates[i], time, elapsed_time);
7598 trap->BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd);
7599 }
7600
7601 return qtrue;
7602 }
7603
7604 /*
7605 ==============
7606 BotAISetup
7607 ==============
7608 */
BotAISetup(int restart)7609 int BotAISetup( int restart ) {
7610 //rww - new bot cvars..
7611 trap->Cvar_Register(&bot_forcepowers, "bot_forcepowers", "1", CVAR_CHEAT);
7612 trap->Cvar_Register(&bot_forgimmick, "bot_forgimmick", "0", CVAR_CHEAT);
7613 trap->Cvar_Register(&bot_honorableduelacceptance, "bot_honorableduelacceptance", "0", CVAR_CHEAT);
7614 trap->Cvar_Register(&bot_pvstype, "bot_pvstype", "1", CVAR_CHEAT);
7615 #ifndef FINAL_BUILD
7616 trap->Cvar_Register(&bot_getinthecarrr, "bot_getinthecarrr", "0", 0);
7617 #endif
7618
7619 #ifdef _DEBUG
7620 trap->Cvar_Register(&bot_nogoals, "bot_nogoals", "0", CVAR_CHEAT);
7621 trap->Cvar_Register(&bot_debugmessages, "bot_debugmessages", "0", CVAR_CHEAT);
7622 #endif
7623
7624 trap->Cvar_Register(&bot_attachments, "bot_attachments", "1", 0);
7625 trap->Cvar_Register(&bot_camp, "bot_camp", "1", 0);
7626
7627 trap->Cvar_Register(&bot_wp_info, "bot_wp_info", "1", 0);
7628 trap->Cvar_Register(&bot_wp_edit, "bot_wp_edit", "0", CVAR_CHEAT);
7629 trap->Cvar_Register(&bot_wp_clearweight, "bot_wp_clearweight", "1", 0);
7630 trap->Cvar_Register(&bot_wp_distconnect, "bot_wp_distconnect", "1", 0);
7631 trap->Cvar_Register(&bot_wp_visconnect, "bot_wp_visconnect", "1", 0);
7632
7633 trap->Cvar_Update(&bot_forcepowers);
7634 //end rww
7635
7636 //if the game is restarted for a tournament
7637 if (restart) {
7638 return qtrue;
7639 }
7640
7641 //initialize the bot states
7642 memset( botstates, 0, sizeof(botstates) );
7643
7644 if (!trap->BotLibSetup())
7645 {
7646 return qfalse; //wts?!
7647 }
7648
7649 return qtrue;
7650 }
7651
7652 /*
7653 ==============
7654 BotAIShutdown
7655 ==============
7656 */
BotAIShutdown(int restart)7657 int BotAIShutdown( int restart ) {
7658
7659 int i;
7660
7661 //if the game is restarted for a tournament
7662 if ( restart ) {
7663 //shutdown all the bots in the botlib
7664 for (i = 0; i < MAX_CLIENTS; i++) {
7665 if (botstates[i] && botstates[i]->inuse) {
7666 BotAIShutdownClient(botstates[i]->client, restart);
7667 }
7668 }
7669 //don't shutdown the bot library
7670 }
7671 else {
7672 trap->BotLibShutdown();
7673 }
7674 return qtrue;
7675 }
7676