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