1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 //
23 
24 /*****************************************************************************
25  * name:		ai_dmq3.c
26  *
27  * desc:		Quake3 bot AI
28  *
29  * $Archive: /MissionPack/code/game/ai_dmq3.c $
30  *
31  *****************************************************************************/
32 
33 
34 #include "g_local.h"
35 #include "../botlib/botlib.h"
36 #include "../botlib/be_aas.h"
37 #include "../botlib/be_ea.h"
38 #include "../botlib/be_ai_char.h"
39 #include "../botlib/be_ai_chat.h"
40 #include "../botlib/be_ai_gen.h"
41 #include "../botlib/be_ai_goal.h"
42 #include "../botlib/be_ai_move.h"
43 #include "../botlib/be_ai_weap.h"
44 //
45 #include "ai_main.h"
46 #include "ai_dmq3.h"
47 #include "ai_chat.h"
48 #include "ai_cmd.h"
49 #include "ai_dmnet.h"
50 #include "ai_team.h"
51 //
52 #include "chars.h"				//characteristics
53 #include "inv.h"				//indexes into the inventory
54 #include "syn.h"				//synonyms
55 #include "match.h"				//string matching types and vars
56 
57 // for the voice chats
58 #include "../../ui/menudef.h" // sos001205 - for q3_ui also
59 
60 // from aasfile.h
61 #define AREACONTENTS_MOVER				1024
62 #define AREACONTENTS_MODELNUMSHIFT		24
63 #define AREACONTENTS_MAXMODELNUM		0xFF
64 #define AREACONTENTS_MODELNUM			(AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT)
65 
66 #define IDEAL_ATTACKDIST			140
67 
68 #define MAX_WAYPOINTS		128
69 //
70 bot_waypoint_t botai_waypoints[MAX_WAYPOINTS];
71 bot_waypoint_t *botai_freewaypoints;
72 
73 //NOTE: not using a cvars which can be updated because the game should be reloaded anyway
74 int gametype;		//game type
75 int maxclients;		//maximum number of clients
76 
77 vmCvar_t bot_grapple;
78 vmCvar_t bot_rocketjump;
79 vmCvar_t bot_fastchat;
80 vmCvar_t bot_nochat;
81 vmCvar_t bot_testrchat;
82 vmCvar_t bot_challenge;
83 vmCvar_t bot_predictobstacles;
84 vmCvar_t g_spSkill;
85 
86 extern vmCvar_t bot_developer;
87 
88 vec3_t lastteleport_origin;		//last teleport event origin
89 float lastteleport_time;		//last teleport event time
90 int max_bspmodelindex;			//maximum BSP model index
91 
92 //CTF flag goals
93 bot_goal_t ctf_redflag;
94 bot_goal_t ctf_blueflag;
95 #ifdef MISSIONPACK
96 bot_goal_t ctf_neutralflag;
97 bot_goal_t redobelisk;
98 bot_goal_t blueobelisk;
99 bot_goal_t neutralobelisk;
100 #endif
101 
102 #define MAX_ALTROUTEGOALS		32
103 
104 int altroutegoals_setup;
105 aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS];
106 int red_numaltroutegoals;
107 aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS];
108 int blue_numaltroutegoals;
109 
110 
111 /*
112 ==================
113 BotSetUserInfo
114 ==================
115 */
BotSetUserInfo(bot_state_t * bs,char * key,char * value)116 void BotSetUserInfo(bot_state_t *bs, char *key, char *value) {
117 	char userinfo[MAX_INFO_STRING];
118 
119 	trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo));
120 	Info_SetValueForKey(userinfo, key, value);
121 	trap_SetUserinfo(bs->client, userinfo);
122 	ClientUserinfoChanged( bs->client );
123 }
124 
125 /*
126 ==================
127 BotCTFCarryingFlag
128 ==================
129 */
BotCTFCarryingFlag(bot_state_t * bs)130 int BotCTFCarryingFlag(bot_state_t *bs) {
131 	if (gametype != GT_CTF) return CTF_FLAG_NONE;
132 
133 	if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED;
134 	else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE;
135 	return CTF_FLAG_NONE;
136 }
137 
138 /*
139 ==================
140 BotTeam
141 ==================
142 */
BotTeam(bot_state_t * bs)143 int BotTeam(bot_state_t *bs) {
144 	char info[1024];
145 
146 	if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
147 		//BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n");
148 		return qfalse;
149 	}
150 	trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info));
151 	//
152 	if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED;
153 	else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE;
154 	return TEAM_FREE;
155 }
156 
157 /*
158 ==================
159 BotOppositeTeam
160 ==================
161 */
BotOppositeTeam(bot_state_t * bs)162 int BotOppositeTeam(bot_state_t *bs) {
163 	switch(BotTeam(bs)) {
164 		case TEAM_RED: return TEAM_BLUE;
165 		case TEAM_BLUE: return TEAM_RED;
166 		default: return TEAM_FREE;
167 	}
168 }
169 
170 /*
171 ==================
172 BotEnemyFlag
173 ==================
174 */
BotEnemyFlag(bot_state_t * bs)175 bot_goal_t *BotEnemyFlag(bot_state_t *bs) {
176 	if (BotTeam(bs) == TEAM_RED) {
177 		return &ctf_blueflag;
178 	}
179 	else {
180 		return &ctf_redflag;
181 	}
182 }
183 
184 /*
185 ==================
186 BotTeamFlag
187 ==================
188 */
BotTeamFlag(bot_state_t * bs)189 bot_goal_t *BotTeamFlag(bot_state_t *bs) {
190 	if (BotTeam(bs) == TEAM_RED) {
191 		return &ctf_redflag;
192 	}
193 	else {
194 		return &ctf_blueflag;
195 	}
196 }
197 
198 
199 /*
200 ==================
201 EntityIsDead
202 ==================
203 */
EntityIsDead(aas_entityinfo_t * entinfo)204 qboolean EntityIsDead(aas_entityinfo_t *entinfo) {
205 	playerState_t ps;
206 
207 	if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) {
208 		//retrieve the current client state
209 		BotAI_GetClientState( entinfo->number, &ps );
210 		if (ps.pm_type != PM_NORMAL) return qtrue;
211 	}
212 	return qfalse;
213 }
214 
215 /*
216 ==================
217 EntityCarriesFlag
218 ==================
219 */
EntityCarriesFlag(aas_entityinfo_t * entinfo)220 qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) {
221 	if ( entinfo->powerups & ( 1 << PW_REDFLAG ) )
222 		return qtrue;
223 	if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) )
224 		return qtrue;
225 #ifdef MISSIONPACK
226 	if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) )
227 		return qtrue;
228 #endif
229 	return qfalse;
230 }
231 
232 /*
233 ==================
234 EntityIsInvisible
235 ==================
236 */
EntityIsInvisible(aas_entityinfo_t * entinfo)237 qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) {
238 	// the flag is always visible
239 	if (EntityCarriesFlag(entinfo)) {
240 		return qfalse;
241 	}
242 	if (entinfo->powerups & (1 << PW_INVIS)) {
243 		return qtrue;
244 	}
245 	return qfalse;
246 }
247 
248 /*
249 ==================
250 EntityIsShooting
251 ==================
252 */
EntityIsShooting(aas_entityinfo_t * entinfo)253 qboolean EntityIsShooting(aas_entityinfo_t *entinfo) {
254 	if (entinfo->flags & EF_FIRING) {
255 		return qtrue;
256 	}
257 	return qfalse;
258 }
259 
260 /*
261 ==================
262 EntityIsChatting
263 ==================
264 */
EntityIsChatting(aas_entityinfo_t * entinfo)265 qboolean EntityIsChatting(aas_entityinfo_t *entinfo) {
266 	if (entinfo->flags & EF_TALK) {
267 		return qtrue;
268 	}
269 	return qfalse;
270 }
271 
272 /*
273 ==================
274 EntityHasQuad
275 ==================
276 */
EntityHasQuad(aas_entityinfo_t * entinfo)277 qboolean EntityHasQuad(aas_entityinfo_t *entinfo) {
278 	if (entinfo->powerups & (1 << PW_QUAD)) {
279 		return qtrue;
280 	}
281 	return qfalse;
282 }
283 
284 #ifdef MISSIONPACK
285 /*
286 ==================
287 EntityHasKamikze
288 ==================
289 */
EntityHasKamikaze(aas_entityinfo_t * entinfo)290 qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) {
291 	if (entinfo->flags & EF_KAMIKAZE) {
292 		return qtrue;
293 	}
294 	return qfalse;
295 }
296 
297 /*
298 ==================
299 EntityCarriesCubes
300 ==================
301 */
EntityCarriesCubes(aas_entityinfo_t * entinfo)302 qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) {
303 	entityState_t state;
304 
305 	if (gametype != GT_HARVESTER)
306 		return qfalse;
307 	//FIXME: get this info from the aas_entityinfo_t ?
308 	BotAI_GetEntityState(entinfo->number, &state);
309 	if (state.generic1 > 0)
310 		return qtrue;
311 	return qfalse;
312 }
313 
314 /*
315 ==================
316 Bot1FCTFCarryingFlag
317 ==================
318 */
Bot1FCTFCarryingFlag(bot_state_t * bs)319 int Bot1FCTFCarryingFlag(bot_state_t *bs) {
320 	if (gametype != GT_1FCTF) return qfalse;
321 
322 	if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue;
323 	return qfalse;
324 }
325 
326 /*
327 ==================
328 BotHarvesterCarryingCubes
329 ==================
330 */
BotHarvesterCarryingCubes(bot_state_t * bs)331 int BotHarvesterCarryingCubes(bot_state_t *bs) {
332 	if (gametype != GT_HARVESTER) return qfalse;
333 
334 	if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue;
335 	if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue;
336 	return qfalse;
337 }
338 #endif
339 
340 /*
341 ==================
342 BotRememberLastOrderedTask
343 ==================
344 */
BotRememberLastOrderedTask(bot_state_t * bs)345 void BotRememberLastOrderedTask(bot_state_t *bs) {
346 	if (!bs->ordered) {
347 		return;
348 	}
349 	bs->lastgoal_decisionmaker = bs->decisionmaker;
350 	bs->lastgoal_ltgtype = bs->ltgtype;
351 	memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t));
352 	bs->lastgoal_teammate = bs->teammate;
353 }
354 
355 /*
356 ==================
357 BotSetTeamStatus
358 ==================
359 */
BotSetTeamStatus(bot_state_t * bs)360 void BotSetTeamStatus(bot_state_t *bs) {
361 #ifdef MISSIONPACK
362 	int teamtask;
363 	aas_entityinfo_t entinfo;
364 
365 	teamtask = TEAMTASK_PATROL;
366 
367 	switch(bs->ltgtype) {
368 		case LTG_TEAMHELP:
369 			break;
370 		case LTG_TEAMACCOMPANY:
371 			BotEntityInfo(bs->teammate, &entinfo);
372 			if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo))
373 				|| ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) {
374 				teamtask = TEAMTASK_ESCORT;
375 			}
376 			else {
377 				teamtask = TEAMTASK_FOLLOW;
378 			}
379 			break;
380 		case LTG_DEFENDKEYAREA:
381 			teamtask = TEAMTASK_DEFENSE;
382 			break;
383 		case LTG_GETFLAG:
384 			teamtask = TEAMTASK_OFFENSE;
385 			break;
386 		case LTG_RUSHBASE:
387 			teamtask = TEAMTASK_DEFENSE;
388 			break;
389 		case LTG_RETURNFLAG:
390 			teamtask = TEAMTASK_RETRIEVE;
391 			break;
392 		case LTG_CAMP:
393 		case LTG_CAMPORDER:
394 			teamtask = TEAMTASK_CAMP;
395 			break;
396 		case LTG_PATROL:
397 			teamtask = TEAMTASK_PATROL;
398 			break;
399 		case LTG_GETITEM:
400 			teamtask = TEAMTASK_PATROL;
401 			break;
402 		case LTG_KILL:
403 			teamtask = TEAMTASK_PATROL;
404 			break;
405 		case LTG_HARVEST:
406 			teamtask = TEAMTASK_OFFENSE;
407 			break;
408 		case LTG_ATTACKENEMYBASE:
409 			teamtask = TEAMTASK_OFFENSE;
410 			break;
411 		default:
412 			teamtask = TEAMTASK_PATROL;
413 			break;
414 	}
415 	BotSetUserInfo(bs, "teamtask", va("%d", teamtask));
416 #endif
417 }
418 
419 /*
420 ==================
421 BotSetLastOrderedTask
422 ==================
423 */
BotSetLastOrderedTask(bot_state_t * bs)424 int BotSetLastOrderedTask(bot_state_t *bs) {
425 
426 	if (gametype == GT_CTF) {
427 		// don't go back to returning the flag if it's at the base
428 		if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) {
429 			if ( BotTeam(bs) == TEAM_RED ) {
430 				if ( bs->redflagstatus == 0 ) {
431 					bs->lastgoal_ltgtype = 0;
432 				}
433 			}
434 			else {
435 				if ( bs->blueflagstatus == 0 ) {
436 					bs->lastgoal_ltgtype = 0;
437 				}
438 			}
439 		}
440 	}
441 
442 	if ( bs->lastgoal_ltgtype ) {
443 		bs->decisionmaker = bs->lastgoal_decisionmaker;
444 		bs->ordered = qtrue;
445 		bs->ltgtype = bs->lastgoal_ltgtype;
446 		memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t));
447 		bs->teammate = bs->lastgoal_teammate;
448 		bs->teamgoal_time = FloatTime() + 300;
449 		BotSetTeamStatus(bs);
450 		//
451 		if ( gametype == GT_CTF ) {
452 			if ( bs->ltgtype == LTG_GETFLAG ) {
453 				bot_goal_t *tb, *eb;
454 				int tt, et;
455 
456 				tb = BotTeamFlag(bs);
457 				eb = BotEnemyFlag(bs);
458 				tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT);
459 				et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT);
460 				// if the travel time towards the enemy base is larger than towards our base
461 				if (et > tt) {
462 					//get an alternative route goal towards the enemy base
463 					BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
464 				}
465 			}
466 		}
467 		return qtrue;
468 	}
469 	return qfalse;
470 }
471 
472 /*
473 ==================
474 BotRefuseOrder
475 ==================
476 */
BotRefuseOrder(bot_state_t * bs)477 void BotRefuseOrder(bot_state_t *bs) {
478 	if (!bs->ordered)
479 		return;
480 	// if the bot was ordered to do something
481 	if ( bs->order_time && bs->order_time > FloatTime() - 10 ) {
482 		trap_EA_Action(bs->client, ACTION_NEGATIVE);
483 		BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO);
484 		bs->order_time = 0;
485 	}
486 }
487 
488 /*
489 ==================
490 BotCTFSeekGoals
491 ==================
492 */
BotCTFSeekGoals(bot_state_t * bs)493 void BotCTFSeekGoals(bot_state_t *bs) {
494 	float rnd, l1, l2;
495 	int flagstatus, c;
496 	vec3_t dir;
497 	aas_entityinfo_t entinfo;
498 
499 	//when carrying a flag in ctf the bot should rush to the base
500 	if (BotCTFCarryingFlag(bs)) {
501 		//if not already rushing to the base
502 		if (bs->ltgtype != LTG_RUSHBASE) {
503 			BotRefuseOrder(bs);
504 			bs->ltgtype = LTG_RUSHBASE;
505 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
506 			bs->rushbaseaway_time = 0;
507 			bs->decisionmaker = bs->client;
508 			bs->ordered = qfalse;
509 			//
510 			switch(BotTeam(bs)) {
511 				case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break;
512 				case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break;
513 				default: VectorSet(dir, 999, 999, 999); break;
514 			}
515 			// if the bot picked up the flag very close to the enemy base
516 			if ( VectorLength(dir) < 128 ) {
517 				// get an alternative route goal through the enemy base
518 				BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
519 			} else {
520 				// don't use any alt route goal, just get the hell out of the base
521 				bs->altroutegoal.areanum = 0;
522 			}
523 			BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE));
524 			BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG);
525 		}
526 		else if (bs->rushbaseaway_time > FloatTime()) {
527 			if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus;
528 			else flagstatus = bs->blueflagstatus;
529 			//if the flag is back
530 			if (flagstatus == 0) {
531 				bs->rushbaseaway_time = 0;
532 			}
533 		}
534 		return;
535 	}
536 	// if the bot decided to follow someone
537 	if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) {
538 		// if the team mate being accompanied no longer carries the flag
539 		BotEntityInfo(bs->teammate, &entinfo);
540 		if (!EntityCarriesFlag(&entinfo)) {
541 			bs->ltgtype = 0;
542 		}
543 	}
544 	//
545 	if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus;
546 	else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus;
547 	//if our team has the enemy flag and our flag is at the base
548 	if (flagstatus == 1) {
549 		//
550 		if (bs->owndecision_time < FloatTime()) {
551 			//if Not defending the base already
552 			if (!(bs->ltgtype == LTG_DEFENDKEYAREA &&
553 					(bs->teamgoal.number == ctf_redflag.number ||
554 					bs->teamgoal.number == ctf_blueflag.number))) {
555 				//if there is a visible team mate flag carrier
556 				c = BotTeamFlagCarrierVisible(bs);
557 				if (c >= 0 &&
558 						// and not already following the team mate flag carrier
559 						(bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) {
560 					//
561 					BotRefuseOrder(bs);
562 					//follow the flag carrier
563 					bs->decisionmaker = bs->client;
564 					bs->ordered = qfalse;
565 					//the team mate
566 					bs->teammate = c;
567 					//last time the team mate was visible
568 					bs->teammatevisible_time = FloatTime();
569 					//no message
570 					bs->teammessage_time = 0;
571 					//no arrive message
572 					bs->arrive_time = 1;
573 					//
574 					BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW);
575 					//get the team goal time
576 					bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
577 					bs->ltgtype = LTG_TEAMACCOMPANY;
578 					bs->formation_dist = 3.5 * 32;		//3.5 meter
579 					BotSetTeamStatus(bs);
580 					bs->owndecision_time = FloatTime() + 5;
581 				}
582 			}
583 		}
584 		return;
585 	}
586 	//if the enemy has our flag
587 	else if (flagstatus == 2) {
588 		//
589 		if (bs->owndecision_time < FloatTime()) {
590 			//if enemy flag carrier is visible
591 			c = BotEnemyFlagCarrierVisible(bs);
592 			if (c >= 0) {
593 				//FIXME: fight enemy flag carrier
594 			}
595 			//if not already doing something important
596 			if (bs->ltgtype != LTG_GETFLAG &&
597 				bs->ltgtype != LTG_RETURNFLAG &&
598 				bs->ltgtype != LTG_TEAMHELP &&
599 				bs->ltgtype != LTG_TEAMACCOMPANY &&
600 				bs->ltgtype != LTG_CAMPORDER &&
601 				bs->ltgtype != LTG_PATROL &&
602 				bs->ltgtype != LTG_GETITEM) {
603 
604 				BotRefuseOrder(bs);
605 				bs->decisionmaker = bs->client;
606 				bs->ordered = qfalse;
607 				//
608 				if (random() < 0.5) {
609 					//go for the enemy flag
610 					bs->ltgtype = LTG_GETFLAG;
611 				}
612 				else {
613 					bs->ltgtype = LTG_RETURNFLAG;
614 				}
615 				//no team message
616 				bs->teammessage_time = 0;
617 				//set the time the bot will stop getting the flag
618 				bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME;
619 				//get an alternative route goal towards the enemy base
620 				BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
621 				//
622 				BotSetTeamStatus(bs);
623 				bs->owndecision_time = FloatTime() + 5;
624 			}
625 		}
626 		return;
627 	}
628 	//if both flags Not at their bases
629 	else if (flagstatus == 3) {
630 		//
631 		if (bs->owndecision_time < FloatTime()) {
632 			// if not trying to return the flag and not following the team flag carrier
633 			if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) {
634 				//
635 				c = BotTeamFlagCarrierVisible(bs);
636 				// if there is a visible team mate flag carrier
637 				if (c >= 0) {
638 					BotRefuseOrder(bs);
639 					//follow the flag carrier
640 					bs->decisionmaker = bs->client;
641 					bs->ordered = qfalse;
642 					//the team mate
643 					bs->teammate = c;
644 					//last time the team mate was visible
645 					bs->teammatevisible_time = FloatTime();
646 					//no message
647 					bs->teammessage_time = 0;
648 					//no arrive message
649 					bs->arrive_time = 1;
650 					//
651 					BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW);
652 					//get the team goal time
653 					bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
654 					bs->ltgtype = LTG_TEAMACCOMPANY;
655 					bs->formation_dist = 3.5 * 32;		//3.5 meter
656 					//
657 					BotSetTeamStatus(bs);
658 					bs->owndecision_time = FloatTime() + 5;
659 				}
660 				else {
661 					BotRefuseOrder(bs);
662 					bs->decisionmaker = bs->client;
663 					bs->ordered = qfalse;
664 					//get the enemy flag
665 					bs->teammessage_time = FloatTime() + 2 * random();
666 					//get the flag
667 					bs->ltgtype = LTG_RETURNFLAG;
668 					//set the time the bot will stop getting the flag
669 					bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME;
670 					//get an alternative route goal towards the enemy base
671 					BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
672 					//
673 					BotSetTeamStatus(bs);
674 					bs->owndecision_time = FloatTime() + 5;
675 				}
676 			}
677 		}
678 		return;
679 	}
680 	// don't just do something wait for the bot team leader to give orders
681 	if (BotTeamLeader(bs)) {
682 		return;
683 	}
684 	// if the bot is ordered to do something
685 	if ( bs->lastgoal_ltgtype ) {
686 		bs->teamgoal_time += 60;
687 	}
688 	// if the bot decided to do something on it's own and has a last ordered goal
689 	if ( !bs->ordered && bs->lastgoal_ltgtype ) {
690 		bs->ltgtype = 0;
691 	}
692 	//if already a CTF or team goal
693 	if (bs->ltgtype == LTG_TEAMHELP ||
694 			bs->ltgtype == LTG_TEAMACCOMPANY ||
695 			bs->ltgtype == LTG_DEFENDKEYAREA ||
696 			bs->ltgtype == LTG_GETFLAG ||
697 			bs->ltgtype == LTG_RUSHBASE ||
698 			bs->ltgtype == LTG_RETURNFLAG ||
699 			bs->ltgtype == LTG_CAMPORDER ||
700 			bs->ltgtype == LTG_PATROL ||
701 			bs->ltgtype == LTG_GETITEM ||
702 			bs->ltgtype == LTG_MAKELOVE_UNDER ||
703 			bs->ltgtype == LTG_MAKELOVE_ONTOP) {
704 		return;
705 	}
706 	//
707 	if (BotSetLastOrderedTask(bs))
708 		return;
709 	//
710 	if (bs->owndecision_time > FloatTime())
711 		return;;
712 	//if the bot is roaming
713 	if (bs->ctfroam_time > FloatTime())
714 		return;
715 	//if the bot has anough aggression to decide what to do
716 	if (BotAggression(bs) < 50)
717 		return;
718 	//set the time to send a message to the team mates
719 	bs->teammessage_time = FloatTime() + 2 * random();
720 	//
721 	if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) {
722 		if (bs->teamtaskpreference & TEAMTP_ATTACKER) {
723 			l1 = 0.7f;
724 		}
725 		else {
726 			l1 = 0.2f;
727 		}
728 		l2 = 0.9f;
729 	}
730 	else {
731 		l1 = 0.4f;
732 		l2 = 0.7f;
733 	}
734 	//get the flag or defend the base
735 	rnd = random();
736 	if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) {
737 		bs->decisionmaker = bs->client;
738 		bs->ordered = qfalse;
739 		bs->ltgtype = LTG_GETFLAG;
740 		//set the time the bot will stop getting the flag
741 		bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME;
742 		//get an alternative route goal towards the enemy base
743 		BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
744 		BotSetTeamStatus(bs);
745 	}
746 	else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) {
747 		bs->decisionmaker = bs->client;
748 		bs->ordered = qfalse;
749 		//
750 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
751 		else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
752 		//set the ltg type
753 		bs->ltgtype = LTG_DEFENDKEYAREA;
754 		//set the time the bot stops defending the base
755 		bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
756 		bs->defendaway_time = 0;
757 		BotSetTeamStatus(bs);
758 	}
759 	else {
760 		bs->ltgtype = 0;
761 		//set the time the bot will stop roaming
762 		bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME;
763 		BotSetTeamStatus(bs);
764 	}
765 	bs->owndecision_time = FloatTime() + 5;
766 #ifdef DEBUG
767 	BotPrintTeamGoal(bs);
768 #endif //DEBUG
769 }
770 
771 /*
772 ==================
773 BotCTFRetreatGoals
774 ==================
775 */
BotCTFRetreatGoals(bot_state_t * bs)776 void BotCTFRetreatGoals(bot_state_t *bs) {
777 	//when carrying a flag in ctf the bot should rush to the base
778 	if (BotCTFCarryingFlag(bs)) {
779 		//if not already rushing to the base
780 		if (bs->ltgtype != LTG_RUSHBASE) {
781 			BotRefuseOrder(bs);
782 			bs->ltgtype = LTG_RUSHBASE;
783 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
784 			bs->rushbaseaway_time = 0;
785 			bs->decisionmaker = bs->client;
786 			bs->ordered = qfalse;
787 			BotSetTeamStatus(bs);
788 		}
789 	}
790 }
791 
792 #ifdef MISSIONPACK
793 /*
794 ==================
795 Bot1FCTFSeekGoals
796 ==================
797 */
Bot1FCTFSeekGoals(bot_state_t * bs)798 void Bot1FCTFSeekGoals(bot_state_t *bs) {
799 	aas_entityinfo_t entinfo;
800 	float rnd, l1, l2;
801 	int c;
802 
803 	//when carrying a flag in ctf the bot should rush to the base
804 	if (Bot1FCTFCarryingFlag(bs)) {
805 		//if not already rushing to the base
806 		if (bs->ltgtype != LTG_RUSHBASE) {
807 			BotRefuseOrder(bs);
808 			bs->ltgtype = LTG_RUSHBASE;
809 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
810 			bs->rushbaseaway_time = 0;
811 			bs->decisionmaker = bs->client;
812 			bs->ordered = qfalse;
813 			//get an alternative route goal towards the enemy base
814 			BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
815 			//
816 			BotSetTeamStatus(bs);
817 			BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG);
818 		}
819 		return;
820 	}
821 	// if the bot decided to follow someone
822 	if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) {
823 		// if the team mate being accompanied no longer carries the flag
824 		BotEntityInfo(bs->teammate, &entinfo);
825 		if (!EntityCarriesFlag(&entinfo)) {
826 			bs->ltgtype = 0;
827 		}
828 	}
829 	//our team has the flag
830 	if (bs->neutralflagstatus == 1) {
831 		if (bs->owndecision_time < FloatTime()) {
832 			// if not already following someone
833 			if (bs->ltgtype != LTG_TEAMACCOMPANY) {
834 				//if there is a visible team mate flag carrier
835 				c = BotTeamFlagCarrierVisible(bs);
836 				if (c >= 0) {
837 					BotRefuseOrder(bs);
838 					//follow the flag carrier
839 					bs->decisionmaker = bs->client;
840 					bs->ordered = qfalse;
841 					//the team mate
842 					bs->teammate = c;
843 					//last time the team mate was visible
844 					bs->teammatevisible_time = FloatTime();
845 					//no message
846 					bs->teammessage_time = 0;
847 					//no arrive message
848 					bs->arrive_time = 1;
849 					//
850 					BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW);
851 					//get the team goal time
852 					bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
853 					bs->ltgtype = LTG_TEAMACCOMPANY;
854 					bs->formation_dist = 3.5 * 32;		//3.5 meter
855 					BotSetTeamStatus(bs);
856 					bs->owndecision_time = FloatTime() + 5;
857 					return;
858 				}
859 			}
860 			//if already a CTF or team goal
861 			if (bs->ltgtype == LTG_TEAMHELP ||
862 					bs->ltgtype == LTG_TEAMACCOMPANY ||
863 					bs->ltgtype == LTG_DEFENDKEYAREA ||
864 					bs->ltgtype == LTG_GETFLAG ||
865 					bs->ltgtype == LTG_RUSHBASE ||
866 					bs->ltgtype == LTG_CAMPORDER ||
867 					bs->ltgtype == LTG_PATROL ||
868 					bs->ltgtype == LTG_ATTACKENEMYBASE ||
869 					bs->ltgtype == LTG_GETITEM ||
870 					bs->ltgtype == LTG_MAKELOVE_UNDER ||
871 					bs->ltgtype == LTG_MAKELOVE_ONTOP) {
872 				return;
873 			}
874 			//if not already attacking the enemy base
875 			if (bs->ltgtype != LTG_ATTACKENEMYBASE) {
876 				BotRefuseOrder(bs);
877 				bs->decisionmaker = bs->client;
878 				bs->ordered = qfalse;
879 				//
880 				if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
881 				else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
882 				//set the ltg type
883 				bs->ltgtype = LTG_ATTACKENEMYBASE;
884 				//set the time the bot will stop getting the flag
885 				bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME;
886 				BotSetTeamStatus(bs);
887 				bs->owndecision_time = FloatTime() + 5;
888 			}
889 		}
890 		return;
891 	}
892 	//enemy team has the flag
893 	else if (bs->neutralflagstatus == 2) {
894 		if (bs->owndecision_time < FloatTime()) {
895 			c = BotEnemyFlagCarrierVisible(bs);
896 			if (c >= 0) {
897 				//FIXME: attack enemy flag carrier
898 			}
899 			//if already a CTF or team goal
900 			if (bs->ltgtype == LTG_TEAMHELP ||
901 					bs->ltgtype == LTG_TEAMACCOMPANY ||
902 					bs->ltgtype == LTG_CAMPORDER ||
903 					bs->ltgtype == LTG_PATROL ||
904 					bs->ltgtype == LTG_GETITEM) {
905 				return;
906 			}
907 			// if not already defending the base
908 			if (bs->ltgtype != LTG_DEFENDKEYAREA) {
909 				BotRefuseOrder(bs);
910 				bs->decisionmaker = bs->client;
911 				bs->ordered = qfalse;
912 				//
913 				if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
914 				else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
915 				//set the ltg type
916 				bs->ltgtype = LTG_DEFENDKEYAREA;
917 				//set the time the bot stops defending the base
918 				bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
919 				bs->defendaway_time = 0;
920 				BotSetTeamStatus(bs);
921 				bs->owndecision_time = FloatTime() + 5;
922 			}
923 		}
924 		return;
925 	}
926 	// don't just do something wait for the bot team leader to give orders
927 	if (BotTeamLeader(bs)) {
928 		return;
929 	}
930 	// if the bot is ordered to do something
931 	if ( bs->lastgoal_ltgtype ) {
932 		bs->teamgoal_time += 60;
933 	}
934 	// if the bot decided to do something on it's own and has a last ordered goal
935 	if ( !bs->ordered && bs->lastgoal_ltgtype ) {
936 		bs->ltgtype = 0;
937 	}
938 	//if already a CTF or team goal
939 	if (bs->ltgtype == LTG_TEAMHELP ||
940 			bs->ltgtype == LTG_TEAMACCOMPANY ||
941 			bs->ltgtype == LTG_DEFENDKEYAREA ||
942 			bs->ltgtype == LTG_GETFLAG ||
943 			bs->ltgtype == LTG_RUSHBASE ||
944 			bs->ltgtype == LTG_RETURNFLAG ||
945 			bs->ltgtype == LTG_CAMPORDER ||
946 			bs->ltgtype == LTG_PATROL ||
947 			bs->ltgtype == LTG_ATTACKENEMYBASE ||
948 			bs->ltgtype == LTG_GETITEM ||
949 			bs->ltgtype == LTG_MAKELOVE_UNDER ||
950 			bs->ltgtype == LTG_MAKELOVE_ONTOP) {
951 		return;
952 	}
953 	//
954 	if (BotSetLastOrderedTask(bs))
955 		return;
956 	//
957 	if (bs->owndecision_time > FloatTime())
958 		return;;
959 	//if the bot is roaming
960 	if (bs->ctfroam_time > FloatTime())
961 		return;
962 	//if the bot has anough aggression to decide what to do
963 	if (BotAggression(bs) < 50)
964 		return;
965 	//set the time to send a message to the team mates
966 	bs->teammessage_time = FloatTime() + 2 * random();
967 	//
968 	if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) {
969 		if (bs->teamtaskpreference & TEAMTP_ATTACKER) {
970 			l1 = 0.7f;
971 		}
972 		else {
973 			l1 = 0.2f;
974 		}
975 		l2 = 0.9f;
976 	}
977 	else {
978 		l1 = 0.4f;
979 		l2 = 0.7f;
980 	}
981 	//get the flag or defend the base
982 	rnd = random();
983 	if (rnd < l1 && ctf_neutralflag.areanum) {
984 		bs->decisionmaker = bs->client;
985 		bs->ordered = qfalse;
986 		bs->ltgtype = LTG_GETFLAG;
987 		//set the time the bot will stop getting the flag
988 		bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME;
989 		BotSetTeamStatus(bs);
990 	}
991 	else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) {
992 		bs->decisionmaker = bs->client;
993 		bs->ordered = qfalse;
994 		//
995 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t));
996 		else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t));
997 		//set the ltg type
998 		bs->ltgtype = LTG_DEFENDKEYAREA;
999 		//set the time the bot stops defending the base
1000 		bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
1001 		bs->defendaway_time = 0;
1002 		BotSetTeamStatus(bs);
1003 	}
1004 	else {
1005 		bs->ltgtype = 0;
1006 		//set the time the bot will stop roaming
1007 		bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME;
1008 		BotSetTeamStatus(bs);
1009 	}
1010 	bs->owndecision_time = FloatTime() + 5;
1011 #ifdef DEBUG
1012 	BotPrintTeamGoal(bs);
1013 #endif //DEBUG
1014 }
1015 
1016 /*
1017 ==================
1018 Bot1FCTFRetreatGoals
1019 ==================
1020 */
Bot1FCTFRetreatGoals(bot_state_t * bs)1021 void Bot1FCTFRetreatGoals(bot_state_t *bs) {
1022 	//when carrying a flag in ctf the bot should rush to the enemy base
1023 	if (Bot1FCTFCarryingFlag(bs)) {
1024 		//if not already rushing to the base
1025 		if (bs->ltgtype != LTG_RUSHBASE) {
1026 			BotRefuseOrder(bs);
1027 			bs->ltgtype = LTG_RUSHBASE;
1028 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
1029 			bs->rushbaseaway_time = 0;
1030 			bs->decisionmaker = bs->client;
1031 			bs->ordered = qfalse;
1032 			//get an alternative route goal towards the enemy base
1033 			BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
1034 			BotSetTeamStatus(bs);
1035 		}
1036 	}
1037 }
1038 
1039 /*
1040 ==================
1041 BotObeliskSeekGoals
1042 ==================
1043 */
BotObeliskSeekGoals(bot_state_t * bs)1044 void BotObeliskSeekGoals(bot_state_t *bs) {
1045 	float rnd, l1, l2;
1046 
1047 	// don't just do something wait for the bot team leader to give orders
1048 	if (BotTeamLeader(bs)) {
1049 		return;
1050 	}
1051 	// if the bot is ordered to do something
1052 	if ( bs->lastgoal_ltgtype ) {
1053 		bs->teamgoal_time += 60;
1054 	}
1055 	//if already a team goal
1056 	if (bs->ltgtype == LTG_TEAMHELP ||
1057 			bs->ltgtype == LTG_TEAMACCOMPANY ||
1058 			bs->ltgtype == LTG_DEFENDKEYAREA ||
1059 			bs->ltgtype == LTG_GETFLAG ||
1060 			bs->ltgtype == LTG_RUSHBASE ||
1061 			bs->ltgtype == LTG_RETURNFLAG ||
1062 			bs->ltgtype == LTG_CAMPORDER ||
1063 			bs->ltgtype == LTG_PATROL ||
1064 			bs->ltgtype == LTG_ATTACKENEMYBASE ||
1065 			bs->ltgtype == LTG_GETITEM ||
1066 			bs->ltgtype == LTG_MAKELOVE_UNDER ||
1067 			bs->ltgtype == LTG_MAKELOVE_ONTOP) {
1068 		return;
1069 	}
1070 	//
1071 	if (BotSetLastOrderedTask(bs))
1072 		return;
1073 	//if the bot is roaming
1074 	if (bs->ctfroam_time > FloatTime())
1075 		return;
1076 	//if the bot has anough aggression to decide what to do
1077 	if (BotAggression(bs) < 50)
1078 		return;
1079 	//set the time to send a message to the team mates
1080 	bs->teammessage_time = FloatTime() + 2 * random();
1081 	//
1082 	if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) {
1083 		if (bs->teamtaskpreference & TEAMTP_ATTACKER) {
1084 			l1 = 0.7f;
1085 		}
1086 		else {
1087 			l1 = 0.2f;
1088 		}
1089 		l2 = 0.9f;
1090 	}
1091 	else {
1092 		l1 = 0.4f;
1093 		l2 = 0.7f;
1094 	}
1095 	//get the flag or defend the base
1096 	rnd = random();
1097 	if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) {
1098 		bs->decisionmaker = bs->client;
1099 		bs->ordered = qfalse;
1100 		//
1101 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t));
1102 		else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t));
1103 		//set the ltg type
1104 		bs->ltgtype = LTG_ATTACKENEMYBASE;
1105 		//set the time the bot will stop attacking the enemy base
1106 		bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME;
1107 		//get an alternate route goal towards the enemy base
1108 		BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
1109 		BotSetTeamStatus(bs);
1110 	}
1111 	else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) {
1112 		bs->decisionmaker = bs->client;
1113 		bs->ordered = qfalse;
1114 		//
1115 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t));
1116 		else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t));
1117 		//set the ltg type
1118 		bs->ltgtype = LTG_DEFENDKEYAREA;
1119 		//set the time the bot stops defending the base
1120 		bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
1121 		bs->defendaway_time = 0;
1122 		BotSetTeamStatus(bs);
1123 	}
1124 	else {
1125 		bs->ltgtype = 0;
1126 		//set the time the bot will stop roaming
1127 		bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME;
1128 		BotSetTeamStatus(bs);
1129 	}
1130 }
1131 
1132 /*
1133 ==================
1134 BotGoHarvest
1135 ==================
1136 */
BotGoHarvest(bot_state_t * bs)1137 void BotGoHarvest(bot_state_t *bs) {
1138 	//
1139 	if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t));
1140 	else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t));
1141 	//set the ltg type
1142 	bs->ltgtype = LTG_HARVEST;
1143 	//set the time the bot will stop harvesting
1144 	bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME;
1145 	bs->harvestaway_time = 0;
1146 	BotSetTeamStatus(bs);
1147 }
1148 
1149 /*
1150 ==================
1151 BotObeliskRetreatGoals
1152 ==================
1153 */
BotObeliskRetreatGoals(bot_state_t * bs)1154 void BotObeliskRetreatGoals(bot_state_t *bs) {
1155 	//nothing special
1156 }
1157 
1158 /*
1159 ==================
1160 BotHarvesterSeekGoals
1161 ==================
1162 */
BotHarvesterSeekGoals(bot_state_t * bs)1163 void BotHarvesterSeekGoals(bot_state_t *bs) {
1164 	aas_entityinfo_t entinfo;
1165 	float rnd, l1, l2;
1166 	int c;
1167 
1168 	//when carrying cubes in harvester the bot should rush to the base
1169 	if (BotHarvesterCarryingCubes(bs)) {
1170 		//if not already rushing to the base
1171 		if (bs->ltgtype != LTG_RUSHBASE) {
1172 			BotRefuseOrder(bs);
1173 			bs->ltgtype = LTG_RUSHBASE;
1174 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
1175 			bs->rushbaseaway_time = 0;
1176 			bs->decisionmaker = bs->client;
1177 			bs->ordered = qfalse;
1178 			//get an alternative route goal towards the enemy base
1179 			BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs));
1180 			//
1181 			BotSetTeamStatus(bs);
1182 		}
1183 		return;
1184 	}
1185 	// don't just do something wait for the bot team leader to give orders
1186 	if (BotTeamLeader(bs)) {
1187 		return;
1188 	}
1189 	// if the bot decided to follow someone
1190 	if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) {
1191 		// if the team mate being accompanied no longer carries the flag
1192 		BotEntityInfo(bs->teammate, &entinfo);
1193 		if (!EntityCarriesCubes(&entinfo)) {
1194 			bs->ltgtype = 0;
1195 		}
1196 	}
1197 	// if the bot is ordered to do something
1198 	if ( bs->lastgoal_ltgtype ) {
1199 		bs->teamgoal_time += 60;
1200 	}
1201 	//if not yet doing something
1202 	if (bs->ltgtype == LTG_TEAMHELP ||
1203 			bs->ltgtype == LTG_TEAMACCOMPANY ||
1204 			bs->ltgtype == LTG_DEFENDKEYAREA ||
1205 			bs->ltgtype == LTG_GETFLAG ||
1206 			bs->ltgtype == LTG_CAMPORDER ||
1207 			bs->ltgtype == LTG_PATROL ||
1208 			bs->ltgtype == LTG_ATTACKENEMYBASE ||
1209 			bs->ltgtype == LTG_HARVEST ||
1210 			bs->ltgtype == LTG_GETITEM ||
1211 			bs->ltgtype == LTG_MAKELOVE_UNDER ||
1212 			bs->ltgtype == LTG_MAKELOVE_ONTOP) {
1213 		return;
1214 	}
1215 	//
1216 	if (BotSetLastOrderedTask(bs))
1217 		return;
1218 	//if the bot is roaming
1219 	if (bs->ctfroam_time > FloatTime())
1220 		return;
1221 	//if the bot has anough aggression to decide what to do
1222 	if (BotAggression(bs) < 50)
1223 		return;
1224 	//set the time to send a message to the team mates
1225 	bs->teammessage_time = FloatTime() + 2 * random();
1226 	//
1227 	c = BotEnemyCubeCarrierVisible(bs);
1228 	if (c >= 0) {
1229 		//FIXME: attack enemy cube carrier
1230 	}
1231 	if (bs->ltgtype != LTG_TEAMACCOMPANY) {
1232 		//if there is a visible team mate carrying cubes
1233 		c = BotTeamCubeCarrierVisible(bs);
1234 		if (c >= 0) {
1235 			//follow the team mate carrying cubes
1236 			bs->decisionmaker = bs->client;
1237 			bs->ordered = qfalse;
1238 			//the team mate
1239 			bs->teammate = c;
1240 			//last time the team mate was visible
1241 			bs->teammatevisible_time = FloatTime();
1242 			//no message
1243 			bs->teammessage_time = 0;
1244 			//no arrive message
1245 			bs->arrive_time = 1;
1246 			//
1247 			BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW);
1248 			//get the team goal time
1249 			bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME;
1250 			bs->ltgtype = LTG_TEAMACCOMPANY;
1251 			bs->formation_dist = 3.5 * 32;		//3.5 meter
1252 			BotSetTeamStatus(bs);
1253 			return;
1254 		}
1255 	}
1256 	//
1257 	if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) {
1258 		if (bs->teamtaskpreference & TEAMTP_ATTACKER) {
1259 			l1 = 0.7f;
1260 		}
1261 		else {
1262 			l1 = 0.2f;
1263 		}
1264 		l2 = 0.9f;
1265 	}
1266 	else {
1267 		l1 = 0.4f;
1268 		l2 = 0.7f;
1269 	}
1270 	//
1271 	rnd = random();
1272 	if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) {
1273 		bs->decisionmaker = bs->client;
1274 		bs->ordered = qfalse;
1275 		BotGoHarvest(bs);
1276 	}
1277 	else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) {
1278 		bs->decisionmaker = bs->client;
1279 		bs->ordered = qfalse;
1280 		//
1281 		if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t));
1282 		else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t));
1283 		//set the ltg type
1284 		bs->ltgtype = LTG_DEFENDKEYAREA;
1285 		//set the time the bot stops defending the base
1286 		bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME;
1287 		bs->defendaway_time = 0;
1288 		BotSetTeamStatus(bs);
1289 	}
1290 	else {
1291 		bs->ltgtype = 0;
1292 		//set the time the bot will stop roaming
1293 		bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME;
1294 		BotSetTeamStatus(bs);
1295 	}
1296 }
1297 
1298 /*
1299 ==================
1300 BotHarvesterRetreatGoals
1301 ==================
1302 */
BotHarvesterRetreatGoals(bot_state_t * bs)1303 void BotHarvesterRetreatGoals(bot_state_t *bs) {
1304 	//when carrying cubes in harvester the bot should rush to the base
1305 	if (BotHarvesterCarryingCubes(bs)) {
1306 		//if not already rushing to the base
1307 		if (bs->ltgtype != LTG_RUSHBASE) {
1308 			BotRefuseOrder(bs);
1309 			bs->ltgtype = LTG_RUSHBASE;
1310 			bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME;
1311 			bs->rushbaseaway_time = 0;
1312 			bs->decisionmaker = bs->client;
1313 			bs->ordered = qfalse;
1314 			BotSetTeamStatus(bs);
1315 		}
1316 		return;
1317 	}
1318 }
1319 #endif
1320 
1321 /*
1322 ==================
1323 BotTeamGoals
1324 ==================
1325 */
BotTeamGoals(bot_state_t * bs,int retreat)1326 void BotTeamGoals(bot_state_t *bs, int retreat) {
1327 
1328 	if ( retreat ) {
1329 		if (gametype == GT_CTF) {
1330 			BotCTFRetreatGoals(bs);
1331 		}
1332 #ifdef MISSIONPACK
1333 		else if (gametype == GT_1FCTF) {
1334 			Bot1FCTFRetreatGoals(bs);
1335 		}
1336 		else if (gametype == GT_OBELISK) {
1337 			BotObeliskRetreatGoals(bs);
1338 		}
1339 		else if (gametype == GT_HARVESTER) {
1340 			BotHarvesterRetreatGoals(bs);
1341 		}
1342 #endif
1343 	}
1344 	else {
1345 		if (gametype == GT_CTF) {
1346 			//decide what to do in CTF mode
1347 			BotCTFSeekGoals(bs);
1348 		}
1349 #ifdef MISSIONPACK
1350 		else if (gametype == GT_1FCTF) {
1351 			Bot1FCTFSeekGoals(bs);
1352 		}
1353 		else if (gametype == GT_OBELISK) {
1354 			BotObeliskSeekGoals(bs);
1355 		}
1356 		else if (gametype == GT_HARVESTER) {
1357 			BotHarvesterSeekGoals(bs);
1358 		}
1359 #endif
1360 	}
1361 	// reset the order time which is used to see if
1362 	// we decided to refuse an order
1363 	bs->order_time = 0;
1364 }
1365 
1366 /*
1367 ==================
1368 BotPointAreaNum
1369 ==================
1370 */
BotPointAreaNum(vec3_t origin)1371 int BotPointAreaNum(vec3_t origin) {
1372 	int areanum, numareas, areas[10];
1373 	vec3_t end;
1374 
1375 	areanum = trap_AAS_PointAreaNum(origin);
1376 	if (areanum) return areanum;
1377 	VectorCopy(origin, end);
1378 	end[2] += 10;
1379 	numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10);
1380 	if (numareas > 0) return areas[0];
1381 	return 0;
1382 }
1383 
1384 /*
1385 ==================
1386 ClientName
1387 ==================
1388 */
ClientName(int client,char * name,int size)1389 char *ClientName(int client, char *name, int size) {
1390 	char buf[MAX_INFO_STRING];
1391 
1392 	if (client < 0 || client >= MAX_CLIENTS) {
1393 		BotAI_Print(PRT_ERROR, "ClientName: client out of range\n");
1394 		return "[client out of range]";
1395 	}
1396 	trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
1397 	strncpy(name, Info_ValueForKey(buf, "n"), size-1);
1398 	name[size-1] = '\0';
1399 	Q_CleanStr( name );
1400 	return name;
1401 }
1402 
1403 /*
1404 ==================
1405 ClientSkin
1406 ==================
1407 */
ClientSkin(int client,char * skin,int size)1408 char *ClientSkin(int client, char *skin, int size) {
1409 	char buf[MAX_INFO_STRING];
1410 
1411 	if (client < 0 || client >= MAX_CLIENTS) {
1412 		BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n");
1413 		return "[client out of range]";
1414 	}
1415 	trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf));
1416 	strncpy(skin, Info_ValueForKey(buf, "model"), size-1);
1417 	skin[size-1] = '\0';
1418 	return skin;
1419 }
1420 
1421 /*
1422 ==================
1423 ClientFromName
1424 ==================
1425 */
ClientFromName(char * name)1426 int ClientFromName(char *name) {
1427 	int i;
1428 	char buf[MAX_INFO_STRING];
1429 	static int maxclients;
1430 
1431 	if (!maxclients)
1432 		maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
1433 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
1434 		trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
1435 		Q_CleanStr( buf );
1436 		if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i;
1437 	}
1438 	return -1;
1439 }
1440 
1441 /*
1442 ==================
1443 ClientOnSameTeamFromName
1444 ==================
1445 */
ClientOnSameTeamFromName(bot_state_t * bs,char * name)1446 int ClientOnSameTeamFromName(bot_state_t *bs, char *name) {
1447 	int i;
1448 	char buf[MAX_INFO_STRING];
1449 	static int maxclients;
1450 
1451 	if (!maxclients)
1452 		maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
1453 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
1454 		if (!BotSameTeam(bs, i))
1455 			continue;
1456 		trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf));
1457 		Q_CleanStr( buf );
1458 		if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i;
1459 	}
1460 	return -1;
1461 }
1462 
1463 /*
1464 ==================
1465 stristr
1466 ==================
1467 */
stristr(char * str,char * charset)1468 char *stristr(char *str, char *charset) {
1469 	int i;
1470 
1471 	while(*str) {
1472 		for (i = 0; charset[i] && str[i]; i++) {
1473 			if (toupper(charset[i]) != toupper(str[i])) break;
1474 		}
1475 		if (!charset[i]) return str;
1476 		str++;
1477 	}
1478 	return NULL;
1479 }
1480 
1481 /*
1482 ==================
1483 EasyClientName
1484 ==================
1485 */
EasyClientName(int client,char * buf,int size)1486 char *EasyClientName(int client, char *buf, int size) {
1487 	int i;
1488 	char *str1, *str2, *ptr, c;
1489 	char name[128];
1490 
1491 	strcpy(name, ClientName(client, name, sizeof(name)));
1492 	for (i = 0; name[i]; i++) name[i] &= 127;
1493 	//remove all spaces
1494 	for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) {
1495 		memmove(ptr, ptr+1, strlen(ptr+1)+1);
1496 	}
1497 	//check for [x] and ]x[ clan names
1498 	str1 = strstr(name, "[");
1499 	str2 = strstr(name, "]");
1500 	if (str1 && str2) {
1501 		if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1);
1502 		else memmove(str2, str1+1, strlen(str1+1)+1);
1503 	}
1504 	//remove Mr prefix
1505 	if ((name[0] == 'm' || name[0] == 'M') &&
1506 			(name[1] == 'r' || name[1] == 'R')) {
1507 		memmove(name, name+2, strlen(name+2)+1);
1508 	}
1509 	//only allow lower case alphabet characters
1510 	ptr = name;
1511 	while(*ptr) {
1512 		c = *ptr;
1513 		if ((c >= 'a' && c <= 'z') ||
1514 				(c >= '0' && c <= '9') || c == '_') {
1515 			ptr++;
1516 		}
1517 		else if (c >= 'A' && c <= 'Z') {
1518 			*ptr += 'a' - 'A';
1519 			ptr++;
1520 		}
1521 		else {
1522 			memmove(ptr, ptr+1, strlen(ptr + 1)+1);
1523 		}
1524 	}
1525 	strncpy(buf, name, size-1);
1526 	buf[size-1] = '\0';
1527 	return buf;
1528 }
1529 
1530 /*
1531 ==================
1532 BotSynonymContext
1533 ==================
1534 */
BotSynonymContext(bot_state_t * bs)1535 int BotSynonymContext(bot_state_t *bs) {
1536 	int context;
1537 
1538 	context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES;
1539 	//
1540 	if (gametype == GT_CTF
1541 #ifdef MISSIONPACK
1542 		|| gametype == GT_1FCTF
1543 #endif
1544 		) {
1545 		if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM;
1546 		else context |= CONTEXT_CTFBLUETEAM;
1547 	}
1548 #ifdef MISSIONPACK
1549 	else if (gametype == GT_OBELISK) {
1550 		if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM;
1551 		else context |= CONTEXT_OBELISKBLUETEAM;
1552 	}
1553 	else if (gametype == GT_HARVESTER) {
1554 		if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM;
1555 		else context |= CONTEXT_HARVESTERBLUETEAM;
1556 	}
1557 #endif
1558 	return context;
1559 }
1560 
1561 /*
1562 ==================
1563 BotChooseWeapon
1564 ==================
1565 */
BotChooseWeapon(bot_state_t * bs)1566 void BotChooseWeapon(bot_state_t *bs) {
1567 	int newweaponnum;
1568 
1569 	if (bs->cur_ps.weaponstate == WEAPON_RAISING ||
1570 			bs->cur_ps.weaponstate == WEAPON_DROPPING) {
1571 		trap_EA_SelectWeapon(bs->client, bs->weaponnum);
1572 	}
1573 	else {
1574 		newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory);
1575 		if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime();
1576 		bs->weaponnum = newweaponnum;
1577 		//BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
1578 		trap_EA_SelectWeapon(bs->client, bs->weaponnum);
1579 	}
1580 }
1581 
1582 /*
1583 ==================
1584 BotSetupForMovement
1585 ==================
1586 */
BotSetupForMovement(bot_state_t * bs)1587 void BotSetupForMovement(bot_state_t *bs) {
1588 	bot_initmove_t initmove;
1589 
1590 	memset(&initmove, 0, sizeof(bot_initmove_t));
1591 	VectorCopy(bs->cur_ps.origin, initmove.origin);
1592 	VectorCopy(bs->cur_ps.velocity, initmove.velocity);
1593 	VectorClear(initmove.viewoffset);
1594 	initmove.viewoffset[2] += bs->cur_ps.viewheight;
1595 	initmove.entitynum = bs->entitynum;
1596 	initmove.client = bs->client;
1597 	initmove.thinktime = bs->thinktime;
1598 	//set the onground flag
1599 	if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND;
1600 	//set the teleported flag
1601 	if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) {
1602 		initmove.or_moveflags |= MFL_TELEPORTED;
1603 	}
1604 	//set the waterjump flag
1605 	if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) {
1606 		initmove.or_moveflags |= MFL_WATERJUMP;
1607 	}
1608 	//set presence type
1609 	if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH;
1610 	else initmove.presencetype = PRESENCE_NORMAL;
1611 	//
1612 	if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK;
1613 	//
1614 	VectorCopy(bs->viewangles, initmove.viewangles);
1615 	//
1616 	trap_BotInitMoveState(bs->ms, &initmove);
1617 }
1618 
1619 /*
1620 ==================
1621 BotCheckItemPickup
1622 ==================
1623 */
BotCheckItemPickup(bot_state_t * bs,int * oldinventory)1624 void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) {
1625 #ifdef MISSIONPACK
1626 	int offence, leader;
1627 
1628 	if (gametype <= GT_TEAM)
1629 		return;
1630 
1631 	offence = -1;
1632 	// go into offence if picked up the kamikaze or invulnerability
1633 	if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) {
1634 		offence = qtrue;
1635 	}
1636 	if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) {
1637 		offence = qtrue;
1638 	}
1639 	// if not already wearing the kamikaze or invulnerability
1640 	if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) {
1641 		if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) {
1642 			offence = qtrue;
1643 		}
1644 		if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) {
1645 			offence = qtrue;
1646 		}
1647 		if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) {
1648 			offence = qfalse;
1649 		}
1650 		if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) {
1651 			offence = qfalse;
1652 		}
1653 	}
1654 
1655 	if (offence >= 0) {
1656 		leader = ClientFromName(bs->teamleader);
1657 		if (offence) {
1658 			if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) {
1659 				// if we have a bot team leader
1660 				if (BotTeamLeader(bs)) {
1661 					// tell the leader we want to be on offence
1662 					BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE);
1663 					//BotAI_BotInitialChat(bs, "wantoffence", NULL);
1664 					//trap_BotEnterChat(bs->cs, leader, CHAT_TELL);
1665 				}
1666 				else if (g_spSkill.integer <= 3) {
1667 					if ( bs->ltgtype != LTG_GETFLAG &&
1668 						 bs->ltgtype != LTG_ATTACKENEMYBASE &&
1669 						 bs->ltgtype != LTG_HARVEST ) {
1670 						//
1671 						if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) &&
1672 							(gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) {
1673 							// tell the leader we want to be on offence
1674 							BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE);
1675 							//BotAI_BotInitialChat(bs, "wantoffence", NULL);
1676 							//trap_BotEnterChat(bs->cs, leader, CHAT_TELL);
1677 						}
1678 					}
1679 					bs->teamtaskpreference |= TEAMTP_ATTACKER;
1680 				}
1681 			}
1682 			bs->teamtaskpreference &= ~TEAMTP_DEFENDER;
1683 		}
1684 		else {
1685 			if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) {
1686 				// if we have a bot team leader
1687 				if (BotTeamLeader(bs)) {
1688 					// tell the leader we want to be on defense
1689 					BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE);
1690 					//BotAI_BotInitialChat(bs, "wantdefence", NULL);
1691 					//trap_BotEnterChat(bs->cs, leader, CHAT_TELL);
1692 				}
1693 				else if (g_spSkill.integer <= 3) {
1694 					if ( bs->ltgtype != LTG_DEFENDKEYAREA ) {
1695 						//
1696 						if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) &&
1697 							(gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) {
1698 							// tell the leader we want to be on defense
1699 							BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE);
1700 							//BotAI_BotInitialChat(bs, "wantdefence", NULL);
1701 							//trap_BotEnterChat(bs->cs, leader, CHAT_TELL);
1702 						}
1703 					}
1704 				}
1705 				bs->teamtaskpreference |= TEAMTP_DEFENDER;
1706 			}
1707 			bs->teamtaskpreference &= ~TEAMTP_ATTACKER;
1708 		}
1709 	}
1710 #endif
1711 }
1712 
1713 /*
1714 ==================
1715 BotUpdateInventory
1716 ==================
1717 */
BotUpdateInventory(bot_state_t * bs)1718 void BotUpdateInventory(bot_state_t *bs) {
1719 	int oldinventory[MAX_ITEMS];
1720 
1721 	memcpy(oldinventory, bs->inventory, sizeof(oldinventory));
1722 	//armor
1723 	bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR];
1724 	//weapons
1725 	bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0;
1726 	bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0;
1727 	bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0;
1728 	bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0;
1729 	bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0;
1730 	bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0;
1731 	bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0;
1732 	bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0;
1733 	bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0;
1734 	bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0;
1735 #ifdef MISSIONPACK
1736 	bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;;
1737 	bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;;
1738 	bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;;
1739 #endif
1740 	//ammo
1741 	bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN];
1742 	bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN];
1743 	bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER];
1744 	bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN];
1745 	bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING];
1746 	bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER];
1747 	bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN];
1748 	bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG];
1749 #ifdef MISSIONPACK
1750 	bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN];
1751 	bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER];
1752 	bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN];
1753 #endif
1754 	//powerups
1755 	bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH];
1756 	bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER;
1757 	bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT;
1758 #ifdef MISSIONPACK
1759 	bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE;
1760 	bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL;
1761 	bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY;
1762 #endif
1763 	bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0;
1764 	bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0;
1765 	bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0;
1766 	bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0;
1767 	bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0;
1768 	bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0;
1769 #ifdef MISSIONPACK
1770 	bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT;
1771 	bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD;
1772 	bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER;
1773 	bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN;
1774 #endif
1775 	bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0;
1776 	bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0;
1777 #ifdef MISSIONPACK
1778 	bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0;
1779 	if (BotTeam(bs) == TEAM_RED) {
1780 		bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1;
1781 		bs->inventory[INVENTORY_BLUECUBE] = 0;
1782 	}
1783 	else {
1784 		bs->inventory[INVENTORY_REDCUBE] = 0;
1785 		bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1;
1786 	}
1787 #endif
1788 	BotCheckItemPickup(bs, oldinventory);
1789 }
1790 
1791 /*
1792 ==================
1793 BotUpdateBattleInventory
1794 ==================
1795 */
BotUpdateBattleInventory(bot_state_t * bs,int enemy)1796 void BotUpdateBattleInventory(bot_state_t *bs, int enemy) {
1797 	vec3_t dir;
1798 	aas_entityinfo_t entinfo;
1799 
1800 	BotEntityInfo(enemy, &entinfo);
1801 	VectorSubtract(entinfo.origin, bs->origin, dir);
1802 	bs->inventory[ENEMY_HEIGHT] = (int) dir[2];
1803 	dir[2] = 0;
1804 	bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir);
1805 	//FIXME: add num visible enemies and num visible team mates to the inventory
1806 }
1807 
1808 #ifdef MISSIONPACK
1809 /*
1810 ==================
1811 BotUseKamikaze
1812 ==================
1813 */
1814 #define KAMIKAZE_DIST		1024
1815 
BotUseKamikaze(bot_state_t * bs)1816 void BotUseKamikaze(bot_state_t *bs) {
1817 	int c, teammates, enemies;
1818 	aas_entityinfo_t entinfo;
1819 	vec3_t dir, target;
1820 	bot_goal_t *goal;
1821 	bsp_trace_t trace;
1822 
1823 	//if the bot has no kamikaze
1824 	if (bs->inventory[INVENTORY_KAMIKAZE] <= 0)
1825 		return;
1826 	if (bs->kamikaze_time > FloatTime())
1827 		return;
1828 	bs->kamikaze_time = FloatTime() + 0.2;
1829 	if (gametype == GT_CTF) {
1830 		//never use kamikaze if the team flag carrier is visible
1831 		if (BotCTFCarryingFlag(bs))
1832 			return;
1833 		c = BotTeamFlagCarrierVisible(bs);
1834 		if (c >= 0) {
1835 			BotEntityInfo(c, &entinfo);
1836 			VectorSubtract(entinfo.origin, bs->origin, dir);
1837 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST))
1838 				return;
1839 		}
1840 		c = BotEnemyFlagCarrierVisible(bs);
1841 		if (c >= 0) {
1842 			BotEntityInfo(c, &entinfo);
1843 			VectorSubtract(entinfo.origin, bs->origin, dir);
1844 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) {
1845 				trap_EA_Use(bs->client);
1846 				return;
1847 			}
1848 		}
1849 	}
1850 	else if (gametype == GT_1FCTF) {
1851 		//never use kamikaze if the team flag carrier is visible
1852 		if (Bot1FCTFCarryingFlag(bs))
1853 			return;
1854 		c = BotTeamFlagCarrierVisible(bs);
1855 		if (c >= 0) {
1856 			BotEntityInfo(c, &entinfo);
1857 			VectorSubtract(entinfo.origin, bs->origin, dir);
1858 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST))
1859 				return;
1860 		}
1861 		c = BotEnemyFlagCarrierVisible(bs);
1862 		if (c >= 0) {
1863 			BotEntityInfo(c, &entinfo);
1864 			VectorSubtract(entinfo.origin, bs->origin, dir);
1865 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) {
1866 				trap_EA_Use(bs->client);
1867 				return;
1868 			}
1869 		}
1870 	}
1871 	else if (gametype == GT_OBELISK) {
1872 		switch(BotTeam(bs)) {
1873 			case TEAM_RED: goal = &blueobelisk; break;
1874 			default: goal = &redobelisk; break;
1875 		}
1876 		//if the obelisk is visible
1877 		VectorCopy(goal->origin, target);
1878 		target[2] += 1;
1879 		VectorSubtract(bs->origin, target, dir);
1880 		if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) {
1881 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
1882 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
1883 				trap_EA_Use(bs->client);
1884 				return;
1885 			}
1886 		}
1887 	}
1888 	else if (gametype == GT_HARVESTER) {
1889 		//
1890 		if (BotHarvesterCarryingCubes(bs))
1891 			return;
1892 		//never use kamikaze if a team mate carrying cubes is visible
1893 		c = BotTeamCubeCarrierVisible(bs);
1894 		if (c >= 0) {
1895 			BotEntityInfo(c, &entinfo);
1896 			VectorSubtract(entinfo.origin, bs->origin, dir);
1897 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST))
1898 				return;
1899 		}
1900 		c = BotEnemyCubeCarrierVisible(bs);
1901 		if (c >= 0) {
1902 			BotEntityInfo(c, &entinfo);
1903 			VectorSubtract(entinfo.origin, bs->origin, dir);
1904 			if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) {
1905 				trap_EA_Use(bs->client);
1906 				return;
1907 			}
1908 		}
1909 	}
1910 	//
1911 	BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST);
1912 	//
1913 	if (enemies > 2 && enemies > teammates+1) {
1914 		trap_EA_Use(bs->client);
1915 		return;
1916 	}
1917 }
1918 
1919 /*
1920 ==================
1921 BotUseInvulnerability
1922 ==================
1923 */
BotUseInvulnerability(bot_state_t * bs)1924 void BotUseInvulnerability(bot_state_t *bs) {
1925 	int c;
1926 	vec3_t dir, target;
1927 	bot_goal_t *goal;
1928 	bsp_trace_t trace;
1929 
1930 	//if the bot has no invulnerability
1931 	if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0)
1932 		return;
1933 	if (bs->invulnerability_time > FloatTime())
1934 		return;
1935 	bs->invulnerability_time = FloatTime() + 0.2;
1936 	if (gametype == GT_CTF) {
1937 		//never use kamikaze if the team flag carrier is visible
1938 		if (BotCTFCarryingFlag(bs))
1939 			return;
1940 		c = BotEnemyFlagCarrierVisible(bs);
1941 		if (c >= 0)
1942 			return;
1943 		//if near enemy flag and the flag is visible
1944 		switch(BotTeam(bs)) {
1945 			case TEAM_RED: goal = &ctf_blueflag; break;
1946 			default: goal = &ctf_redflag; break;
1947 		}
1948 		//if the obelisk is visible
1949 		VectorCopy(goal->origin, target);
1950 		target[2] += 1;
1951 		VectorSubtract(bs->origin, target, dir);
1952 		if (VectorLengthSquared(dir) < Square(200)) {
1953 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
1954 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
1955 				trap_EA_Use(bs->client);
1956 				return;
1957 			}
1958 		}
1959 	}
1960 	else if (gametype == GT_1FCTF) {
1961 		//never use kamikaze if the team flag carrier is visible
1962 		if (Bot1FCTFCarryingFlag(bs))
1963 			return;
1964 		c = BotEnemyFlagCarrierVisible(bs);
1965 		if (c >= 0)
1966 			return;
1967 		//if near enemy flag and the flag is visible
1968 		switch(BotTeam(bs)) {
1969 			case TEAM_RED: goal = &ctf_blueflag; break;
1970 			default: goal = &ctf_redflag; break;
1971 		}
1972 		//if the obelisk is visible
1973 		VectorCopy(goal->origin, target);
1974 		target[2] += 1;
1975 		VectorSubtract(bs->origin, target, dir);
1976 		if (VectorLengthSquared(dir) < Square(200)) {
1977 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
1978 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
1979 				trap_EA_Use(bs->client);
1980 				return;
1981 			}
1982 		}
1983 	}
1984 	else if (gametype == GT_OBELISK) {
1985 		switch(BotTeam(bs)) {
1986 			case TEAM_RED: goal = &blueobelisk; break;
1987 			default: goal = &redobelisk; break;
1988 		}
1989 		//if the obelisk is visible
1990 		VectorCopy(goal->origin, target);
1991 		target[2] += 1;
1992 		VectorSubtract(bs->origin, target, dir);
1993 		if (VectorLengthSquared(dir) < Square(300)) {
1994 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
1995 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
1996 				trap_EA_Use(bs->client);
1997 				return;
1998 			}
1999 		}
2000 	}
2001 	else if (gametype == GT_HARVESTER) {
2002 		//
2003 		if (BotHarvesterCarryingCubes(bs))
2004 			return;
2005 		c = BotEnemyCubeCarrierVisible(bs);
2006 		if (c >= 0)
2007 			return;
2008 		//if near enemy base and enemy base is visible
2009 		switch(BotTeam(bs)) {
2010 			case TEAM_RED: goal = &blueobelisk; break;
2011 			default: goal = &redobelisk; break;
2012 		}
2013 		//if the obelisk is visible
2014 		VectorCopy(goal->origin, target);
2015 		target[2] += 1;
2016 		VectorSubtract(bs->origin, target, dir);
2017 		if (VectorLengthSquared(dir) < Square(200)) {
2018 			BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
2019 			if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
2020 				trap_EA_Use(bs->client);
2021 				return;
2022 			}
2023 		}
2024 	}
2025 }
2026 #endif
2027 
2028 /*
2029 ==================
2030 BotBattleUseItems
2031 ==================
2032 */
BotBattleUseItems(bot_state_t * bs)2033 void BotBattleUseItems(bot_state_t *bs) {
2034 	if (bs->inventory[INVENTORY_HEALTH] < 40) {
2035 		if (bs->inventory[INVENTORY_TELEPORTER] > 0) {
2036 			if (!BotCTFCarryingFlag(bs)
2037 #ifdef MISSIONPACK
2038 				&& !Bot1FCTFCarryingFlag(bs)
2039 				&& !BotHarvesterCarryingCubes(bs)
2040 #endif
2041 				) {
2042 				trap_EA_Use(bs->client);
2043 			}
2044 		}
2045 	}
2046 	if (bs->inventory[INVENTORY_HEALTH] < 60) {
2047 		if (bs->inventory[INVENTORY_MEDKIT] > 0) {
2048 			trap_EA_Use(bs->client);
2049 		}
2050 	}
2051 #ifdef MISSIONPACK
2052 	BotUseKamikaze(bs);
2053 	BotUseInvulnerability(bs);
2054 #endif
2055 }
2056 
2057 /*
2058 ==================
2059 BotSetTeleportTime
2060 ==================
2061 */
BotSetTeleportTime(bot_state_t * bs)2062 void BotSetTeleportTime(bot_state_t *bs) {
2063 	if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) {
2064 		bs->teleport_time = FloatTime();
2065 	}
2066 	bs->last_eFlags = bs->cur_ps.eFlags;
2067 }
2068 
2069 /*
2070 ==================
2071 BotIsDead
2072 ==================
2073 */
BotIsDead(bot_state_t * bs)2074 qboolean BotIsDead(bot_state_t *bs) {
2075 	return (bs->cur_ps.pm_type == PM_DEAD);
2076 }
2077 
2078 /*
2079 ==================
2080 BotIsObserver
2081 ==================
2082 */
BotIsObserver(bot_state_t * bs)2083 qboolean BotIsObserver(bot_state_t *bs) {
2084 	char buf[MAX_INFO_STRING];
2085 	if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue;
2086 	trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf));
2087 	if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue;
2088 	return qfalse;
2089 }
2090 
2091 /*
2092 ==================
2093 BotIntermission
2094 ==================
2095 */
BotIntermission(bot_state_t * bs)2096 qboolean BotIntermission(bot_state_t *bs) {
2097 	//NOTE: we shouldn't be looking at the game code...
2098 	if (level.intermissiontime) return qtrue;
2099 	return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION);
2100 }
2101 
2102 /*
2103 ==================
2104 BotInLavaOrSlime
2105 ==================
2106 */
BotInLavaOrSlime(bot_state_t * bs)2107 qboolean BotInLavaOrSlime(bot_state_t *bs) {
2108 	vec3_t feet;
2109 
2110 	VectorCopy(bs->origin, feet);
2111 	feet[2] -= 23;
2112 	return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME));
2113 }
2114 
2115 /*
2116 ==================
2117 BotCreateWayPoint
2118 ==================
2119 */
BotCreateWayPoint(char * name,vec3_t origin,int areanum)2120 bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) {
2121 	bot_waypoint_t *wp;
2122 	vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8};
2123 
2124 	wp = botai_freewaypoints;
2125 	if ( !wp ) {
2126 		BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" );
2127 		return NULL;
2128 	}
2129 	botai_freewaypoints = botai_freewaypoints->next;
2130 
2131 	Q_strncpyz( wp->name, name, sizeof(wp->name) );
2132 	VectorCopy(origin, wp->goal.origin);
2133 	VectorCopy(waypointmins, wp->goal.mins);
2134 	VectorCopy(waypointmaxs, wp->goal.maxs);
2135 	wp->goal.areanum = areanum;
2136 	wp->next = NULL;
2137 	wp->prev = NULL;
2138 	return wp;
2139 }
2140 
2141 /*
2142 ==================
2143 BotFindWayPoint
2144 ==================
2145 */
BotFindWayPoint(bot_waypoint_t * waypoints,char * name)2146 bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) {
2147 	bot_waypoint_t *wp;
2148 
2149 	for (wp = waypoints; wp; wp = wp->next) {
2150 		if (!Q_stricmp(wp->name, name)) return wp;
2151 	}
2152 	return NULL;
2153 }
2154 
2155 /*
2156 ==================
2157 BotFreeWaypoints
2158 ==================
2159 */
BotFreeWaypoints(bot_waypoint_t * wp)2160 void BotFreeWaypoints(bot_waypoint_t *wp) {
2161 	bot_waypoint_t *nextwp;
2162 
2163 	for (; wp; wp = nextwp) {
2164 		nextwp = wp->next;
2165 		wp->next = botai_freewaypoints;
2166 		botai_freewaypoints = wp;
2167 	}
2168 }
2169 
2170 /*
2171 ==================
2172 BotInitWaypoints
2173 ==================
2174 */
BotInitWaypoints(void)2175 void BotInitWaypoints(void) {
2176 	int i;
2177 
2178 	botai_freewaypoints = NULL;
2179 	for (i = 0; i < MAX_WAYPOINTS; i++) {
2180 		botai_waypoints[i].next = botai_freewaypoints;
2181 		botai_freewaypoints = &botai_waypoints[i];
2182 	}
2183 }
2184 
2185 /*
2186 ==================
2187 TeamPlayIsOn
2188 ==================
2189 */
TeamPlayIsOn(void)2190 int TeamPlayIsOn(void) {
2191 	return ( gametype >= GT_TEAM );
2192 }
2193 
2194 /*
2195 ==================
2196 BotAggression
2197 ==================
2198 */
BotAggression(bot_state_t * bs)2199 float BotAggression(bot_state_t *bs) {
2200 	//if the bot has quad
2201 	if (bs->inventory[INVENTORY_QUAD]) {
2202 		//if the bot is not holding the gauntlet or the enemy is really nearby
2203 		if (bs->weaponnum != WP_GAUNTLET ||
2204 			bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) {
2205 			return 70;
2206 		}
2207 	}
2208 	//if the enemy is located way higher than the bot
2209 	if (bs->inventory[ENEMY_HEIGHT] > 200) return 0;
2210 	//if the bot is very low on health
2211 	if (bs->inventory[INVENTORY_HEALTH] < 60) return 0;
2212 	//if the bot is low on health
2213 	if (bs->inventory[INVENTORY_HEALTH] < 80) {
2214 		//if the bot has insufficient armor
2215 		if (bs->inventory[INVENTORY_ARMOR] < 40) return 0;
2216 	}
2217 	//if the bot can use the bfg
2218 	if (bs->inventory[INVENTORY_BFG10K] > 0 &&
2219 			bs->inventory[INVENTORY_BFGAMMO] > 7) return 100;
2220 	//if the bot can use the railgun
2221 	if (bs->inventory[INVENTORY_RAILGUN] > 0 &&
2222 			bs->inventory[INVENTORY_SLUGS] > 5) return 95;
2223 	//if the bot can use the lightning gun
2224 	if (bs->inventory[INVENTORY_LIGHTNING] > 0 &&
2225 			bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 90;
2226 	//if the bot can use the rocketlauncher
2227 	if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
2228 			bs->inventory[INVENTORY_ROCKETS] > 5) return 90;
2229 	//if the bot can use the plasmagun
2230 	if (bs->inventory[INVENTORY_PLASMAGUN] > 0 &&
2231 			bs->inventory[INVENTORY_CELLS] > 40) return 85;
2232 	//if the bot can use the grenade launcher
2233 	if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
2234 			bs->inventory[INVENTORY_GRENADES] > 10) return 80;
2235 	//if the bot can use the shotgun
2236 	if (bs->inventory[INVENTORY_SHOTGUN] > 0 &&
2237 			bs->inventory[INVENTORY_SHELLS] > 10) return 50;
2238 	//otherwise the bot is not feeling too good
2239 	return 0;
2240 }
2241 
2242 /*
2243 ==================
2244 BotFeelingBad
2245 ==================
2246 */
BotFeelingBad(bot_state_t * bs)2247 float BotFeelingBad(bot_state_t *bs) {
2248 	if (bs->weaponnum == WP_GAUNTLET) {
2249 		return 100;
2250 	}
2251 	if (bs->inventory[INVENTORY_HEALTH] < 40) {
2252 		return 100;
2253 	}
2254 	if (bs->weaponnum == WP_MACHINEGUN) {
2255 		return 90;
2256 	}
2257 	if (bs->inventory[INVENTORY_HEALTH] < 60) {
2258 		return 80;
2259 	}
2260 	return 0;
2261 }
2262 
2263 /*
2264 ==================
2265 BotWantsToRetreat
2266 ==================
2267 */
BotWantsToRetreat(bot_state_t * bs)2268 int BotWantsToRetreat(bot_state_t *bs) {
2269 	aas_entityinfo_t entinfo;
2270 
2271 	if (gametype == GT_CTF) {
2272 		//always retreat when carrying a CTF flag
2273 		if (BotCTFCarryingFlag(bs))
2274 			return qtrue;
2275 	}
2276 #ifdef MISSIONPACK
2277 	else if (gametype == GT_1FCTF) {
2278 		//if carrying the flag then always retreat
2279 		if (Bot1FCTFCarryingFlag(bs))
2280 			return qtrue;
2281 	}
2282 	else if (gametype == GT_OBELISK) {
2283 		//the bots should be dedicated to attacking the enemy obelisk
2284 		if (bs->ltgtype == LTG_ATTACKENEMYBASE) {
2285 			if (bs->enemy != redobelisk.entitynum ||
2286 						bs->enemy != blueobelisk.entitynum) {
2287 				return qtrue;
2288 			}
2289 		}
2290 		if (BotFeelingBad(bs) > 50) {
2291 			return qtrue;
2292 		}
2293 		return qfalse;
2294 	}
2295 	else if (gametype == GT_HARVESTER) {
2296 		//if carrying cubes then always retreat
2297 		if (BotHarvesterCarryingCubes(bs)) return qtrue;
2298 	}
2299 #endif
2300 	//
2301 	if (bs->enemy >= 0) {
2302 		//if the enemy is carrying a flag
2303 		BotEntityInfo(bs->enemy, &entinfo);
2304 		if (EntityCarriesFlag(&entinfo))
2305 			return qfalse;
2306 	}
2307 	//if the bot is getting the flag
2308 	if (bs->ltgtype == LTG_GETFLAG)
2309 		return qtrue;
2310 	//
2311 	if (BotAggression(bs) < 50)
2312 		return qtrue;
2313 	return qfalse;
2314 }
2315 
2316 /*
2317 ==================
2318 BotWantsToChase
2319 ==================
2320 */
BotWantsToChase(bot_state_t * bs)2321 int BotWantsToChase(bot_state_t *bs) {
2322 	aas_entityinfo_t entinfo;
2323 
2324 	if (gametype == GT_CTF) {
2325 		//never chase when carrying a CTF flag
2326 		if (BotCTFCarryingFlag(bs))
2327 			return qfalse;
2328 		//always chase if the enemy is carrying a flag
2329 		BotEntityInfo(bs->enemy, &entinfo);
2330 		if (EntityCarriesFlag(&entinfo))
2331 			return qtrue;
2332 	}
2333 #ifdef MISSIONPACK
2334 	else if (gametype == GT_1FCTF) {
2335 		//never chase if carrying the flag
2336 		if (Bot1FCTFCarryingFlag(bs))
2337 			return qfalse;
2338 		//always chase if the enemy is carrying a flag
2339 		BotEntityInfo(bs->enemy, &entinfo);
2340 		if (EntityCarriesFlag(&entinfo))
2341 			return qtrue;
2342 	}
2343 	else if (gametype == GT_OBELISK) {
2344 		//the bots should be dedicated to attacking the enemy obelisk
2345 		if (bs->ltgtype == LTG_ATTACKENEMYBASE) {
2346 			if (bs->enemy != redobelisk.entitynum ||
2347 						bs->enemy != blueobelisk.entitynum) {
2348 				return qfalse;
2349 			}
2350 		}
2351 	}
2352 	else if (gametype == GT_HARVESTER) {
2353 		//never chase if carrying cubes
2354 		if (BotHarvesterCarryingCubes(bs))
2355 			return qfalse;
2356 	}
2357 #endif
2358 	//if the bot is getting the flag
2359 	if (bs->ltgtype == LTG_GETFLAG)
2360 		return qfalse;
2361 	//
2362 	if (BotAggression(bs) > 50)
2363 		return qtrue;
2364 	return qfalse;
2365 }
2366 
2367 /*
2368 ==================
2369 BotWantsToHelp
2370 ==================
2371 */
BotWantsToHelp(bot_state_t * bs)2372 int BotWantsToHelp(bot_state_t *bs) {
2373 	return qtrue;
2374 }
2375 
2376 /*
2377 ==================
2378 BotCanAndWantsToRocketJump
2379 ==================
2380 */
BotCanAndWantsToRocketJump(bot_state_t * bs)2381 int BotCanAndWantsToRocketJump(bot_state_t *bs) {
2382 	float rocketjumper;
2383 
2384 	//if rocket jumping is disabled
2385 	if (!bot_rocketjump.integer) return qfalse;
2386 	//if no rocket launcher
2387 	if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse;
2388 	//if low on rockets
2389 	if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse;
2390 	//never rocket jump with the Quad
2391 	if (bs->inventory[INVENTORY_QUAD]) return qfalse;
2392 	//if low on health
2393 	if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse;
2394 	//if not full health
2395 	if (bs->inventory[INVENTORY_HEALTH] < 90) {
2396 		//if the bot has insufficient armor
2397 		if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse;
2398 	}
2399 	rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1);
2400 	if (rocketjumper < 0.5) return qfalse;
2401 	return qtrue;
2402 }
2403 
2404 /*
2405 ==================
2406 BotHasPersistantPowerupAndWeapon
2407 ==================
2408 */
BotHasPersistantPowerupAndWeapon(bot_state_t * bs)2409 int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) {
2410 #ifdef MISSIONPACK
2411 	// if the bot does not have a persistant powerup
2412 	if (!bs->inventory[INVENTORY_SCOUT] &&
2413 		!bs->inventory[INVENTORY_GUARD] &&
2414 		!bs->inventory[INVENTORY_DOUBLER] &&
2415 		!bs->inventory[INVENTORY_AMMOREGEN] ) {
2416 		return qfalse;
2417 	}
2418 #endif
2419 	//if the bot is very low on health
2420 	if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse;
2421 	//if the bot is low on health
2422 	if (bs->inventory[INVENTORY_HEALTH] < 80) {
2423 		//if the bot has insufficient armor
2424 		if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse;
2425 	}
2426 	//if the bot can use the bfg
2427 	if (bs->inventory[INVENTORY_BFG10K] > 0 &&
2428 			bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue;
2429 	//if the bot can use the railgun
2430 	if (bs->inventory[INVENTORY_RAILGUN] > 0 &&
2431 			bs->inventory[INVENTORY_SLUGS] > 5) return qtrue;
2432 	//if the bot can use the lightning gun
2433 	if (bs->inventory[INVENTORY_LIGHTNING] > 0 &&
2434 			bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue;
2435 	//if the bot can use the rocketlauncher
2436 	if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
2437 			bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue;
2438 	//
2439 	if (bs->inventory[INVENTORY_NAILGUN] > 0 &&
2440 			bs->inventory[INVENTORY_NAILS] > 5) return qtrue;
2441 	//
2442 	if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 &&
2443 			bs->inventory[INVENTORY_MINES] > 5) return qtrue;
2444 	//
2445 	if (bs->inventory[INVENTORY_CHAINGUN] > 0 &&
2446 			bs->inventory[INVENTORY_BELT] > 40) return qtrue;
2447 	//if the bot can use the plasmagun
2448 	if (bs->inventory[INVENTORY_PLASMAGUN] > 0 &&
2449 			bs->inventory[INVENTORY_CELLS] > 20) return qtrue;
2450 	return qfalse;
2451 }
2452 
2453 /*
2454 ==================
2455 BotGoCamp
2456 ==================
2457 */
BotGoCamp(bot_state_t * bs,bot_goal_t * goal)2458 void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) {
2459 	float camper;
2460 
2461 	bs->decisionmaker = bs->client;
2462 	//set message time to zero so bot will NOT show any message
2463 	bs->teammessage_time = 0;
2464 	//set the ltg type
2465 	bs->ltgtype = LTG_CAMP;
2466 	//set the team goal
2467 	memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t));
2468 	//get the team goal time
2469 	camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1);
2470 	if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999;
2471 	else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15;
2472 	//set the last time the bot started camping
2473 	bs->camp_time = FloatTime();
2474 	//the teammate that requested the camping
2475 	bs->teammate = 0;
2476 	//do NOT type arrive message
2477 	bs->arrive_time = 1;
2478 }
2479 
2480 /*
2481 ==================
2482 BotWantsToCamp
2483 ==================
2484 */
BotWantsToCamp(bot_state_t * bs)2485 int BotWantsToCamp(bot_state_t *bs) {
2486 	float camper;
2487 	int cs, traveltime, besttraveltime;
2488 	bot_goal_t goal, bestgoal;
2489 
2490 	camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1);
2491 	if (camper < 0.1) return qfalse;
2492 	//if the bot has a team goal
2493 	if (bs->ltgtype == LTG_TEAMHELP ||
2494 			bs->ltgtype == LTG_TEAMACCOMPANY ||
2495 			bs->ltgtype == LTG_DEFENDKEYAREA ||
2496 			bs->ltgtype == LTG_GETFLAG ||
2497 			bs->ltgtype == LTG_RUSHBASE ||
2498 			bs->ltgtype == LTG_CAMP ||
2499 			bs->ltgtype == LTG_CAMPORDER ||
2500 			bs->ltgtype == LTG_PATROL) {
2501 		return qfalse;
2502 	}
2503 	//if camped recently
2504 	if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse;
2505 	//
2506 	if (random() > camper) {
2507 		bs->camp_time = FloatTime();
2508 		return qfalse;
2509 	}
2510 	//if the bot isn't healthy anough
2511 	if (BotAggression(bs) < 50) return qfalse;
2512 	//the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo
2513 	if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) &&
2514 		(bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) &&
2515 		(bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) {
2516 		return qfalse;
2517 	}
2518 	//find the closest camp spot
2519 	besttraveltime = 99999;
2520 	for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) {
2521 		traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT);
2522 		if (traveltime && traveltime < besttraveltime) {
2523 			besttraveltime = traveltime;
2524 			memcpy(&bestgoal, &goal, sizeof(bot_goal_t));
2525 		}
2526 	}
2527 	if (besttraveltime > 150) return qfalse;
2528 	//ok found a camp spot, go camp there
2529 	BotGoCamp(bs, &bestgoal);
2530 	bs->ordered = qfalse;
2531 	//
2532 	return qtrue;
2533 }
2534 
2535 /*
2536 ==================
2537 BotDontAvoid
2538 ==================
2539 */
BotDontAvoid(bot_state_t * bs,char * itemname)2540 void BotDontAvoid(bot_state_t *bs, char *itemname) {
2541 	bot_goal_t goal;
2542 	int num;
2543 
2544 	num = trap_BotGetLevelItemGoal(-1, itemname, &goal);
2545 	while(num >= 0) {
2546 		trap_BotRemoveFromAvoidGoals(bs->gs, goal.number);
2547 		num = trap_BotGetLevelItemGoal(num, itemname, &goal);
2548 	}
2549 }
2550 
2551 /*
2552 ==================
2553 BotGoForPowerups
2554 ==================
2555 */
BotGoForPowerups(bot_state_t * bs)2556 void BotGoForPowerups(bot_state_t *bs) {
2557 
2558 	//don't avoid any of the powerups anymore
2559 	BotDontAvoid(bs, "Quad Damage");
2560 	BotDontAvoid(bs, "Regeneration");
2561 	BotDontAvoid(bs, "Battle Suit");
2562 	BotDontAvoid(bs, "Speed");
2563 	BotDontAvoid(bs, "Invisibility");
2564 	//BotDontAvoid(bs, "Flight");
2565 	//reset the long term goal time so the bot will go for the powerup
2566 	//NOTE: the long term goal type doesn't change
2567 	bs->ltg_time = 0;
2568 }
2569 
2570 /*
2571 ==================
2572 BotRoamGoal
2573 ==================
2574 */
BotRoamGoal(bot_state_t * bs,vec3_t goal)2575 void BotRoamGoal(bot_state_t *bs, vec3_t goal) {
2576 	int pc, i;
2577 	float len, rnd;
2578 	vec3_t dir, bestorg, belowbestorg;
2579 	bsp_trace_t trace;
2580 
2581 	for (i = 0; i < 10; i++) {
2582 		//start at the bot origin
2583 		VectorCopy(bs->origin, bestorg);
2584 		rnd = random();
2585 		if (rnd > 0.25) {
2586 			//add a random value to the x-coordinate
2587 			if (random() < 0.5) bestorg[0] -= 800 * random() + 100;
2588 			else bestorg[0] += 800 * random() + 100;
2589 		}
2590 		if (rnd < 0.75) {
2591 			//add a random value to the y-coordinate
2592 			if (random() < 0.5) bestorg[1] -= 800 * random() + 100;
2593 			else bestorg[1] += 800 * random() + 100;
2594 		}
2595 		//add a random value to the z-coordinate (NOTE: 48 = maxjump?)
2596 		bestorg[2] += 2 * 48 * crandom();
2597 		//trace a line from the origin to the roam target
2598 		BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID);
2599 		//direction and length towards the roam target
2600 		VectorSubtract(trace.endpos, bs->origin, dir);
2601 		len = VectorNormalize(dir);
2602 		//if the roam target is far away anough
2603 		if (len > 200) {
2604 			//the roam target is in the given direction before walls
2605 			VectorScale(dir, len * trace.fraction - 40, dir);
2606 			VectorAdd(bs->origin, dir, bestorg);
2607 			//get the coordinates of the floor below the roam target
2608 			belowbestorg[0] = bestorg[0];
2609 			belowbestorg[1] = bestorg[1];
2610 			belowbestorg[2] = bestorg[2] - 800;
2611 			BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID);
2612 			//
2613 			if (!trace.startsolid) {
2614 				trace.endpos[2]++;
2615 				pc = trap_PointContents(trace.endpos, bs->entitynum);
2616 				if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) {
2617 					VectorCopy(bestorg, goal);
2618 					return;
2619 				}
2620 			}
2621 		}
2622 	}
2623 	VectorCopy(bestorg, goal);
2624 }
2625 
2626 /*
2627 ==================
2628 BotAttackMove
2629 ==================
2630 */
BotAttackMove(bot_state_t * bs,int tfl)2631 bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) {
2632 	int movetype, i, attackentity;
2633 	float attack_skill, jumper, croucher, dist, strafechange_time;
2634 	float attack_dist, attack_range;
2635 	vec3_t forward, backward, sideward, hordir, up = {0, 0, 1};
2636 	aas_entityinfo_t entinfo;
2637 	bot_moveresult_t moveresult;
2638 	bot_goal_t goal;
2639 
2640 	attackentity = bs->enemy;
2641 	//
2642 	if (bs->attackchase_time > FloatTime()) {
2643 		//create the chase goal
2644 		goal.entitynum = attackentity;
2645 		goal.areanum = bs->lastenemyareanum;
2646 		VectorCopy(bs->lastenemyorigin, goal.origin);
2647 		VectorSet(goal.mins, -8, -8, -8);
2648 		VectorSet(goal.maxs, 8, 8, 8);
2649 		//initialize the movement state
2650 		BotSetupForMovement(bs);
2651 		//move towards the goal
2652 		trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl);
2653 		return moveresult;
2654 	}
2655 	//
2656 	memset(&moveresult, 0, sizeof(bot_moveresult_t));
2657 	//
2658 	attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
2659 	jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1);
2660 	croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
2661 	//if the bot is really stupid
2662 	if (attack_skill < 0.2) return moveresult;
2663 	//initialize the movement state
2664 	BotSetupForMovement(bs);
2665 	//get the enemy entity info
2666 	BotEntityInfo(attackentity, &entinfo);
2667 	//direction towards the enemy
2668 	VectorSubtract(entinfo.origin, bs->origin, forward);
2669 	//the distance towards the enemy
2670 	dist = VectorNormalize(forward);
2671 	VectorNegate(forward, backward);
2672 	//walk, crouch or jump
2673 	movetype = MOVE_WALK;
2674 	//
2675 	if (bs->attackcrouch_time < FloatTime() - 1) {
2676 		if (random() < jumper) {
2677 			movetype = MOVE_JUMP;
2678 		}
2679 		//wait at least one second before crouching again
2680 		else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) {
2681 			bs->attackcrouch_time = FloatTime() + croucher * 5;
2682 		}
2683 	}
2684 	if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH;
2685 	//if the bot should jump
2686 	if (movetype == MOVE_JUMP) {
2687 		//if jumped last frame
2688 		if (bs->attackjump_time > FloatTime()) {
2689 			movetype = MOVE_WALK;
2690 		}
2691 		else {
2692 			bs->attackjump_time = FloatTime() + 1;
2693 		}
2694 	}
2695 	if (bs->cur_ps.weapon == WP_GAUNTLET) {
2696 		attack_dist = 0;
2697 		attack_range = 0;
2698 	}
2699 	else {
2700 		attack_dist = IDEAL_ATTACKDIST;
2701 		attack_range = 40;
2702 	}
2703 	//if the bot is stupid
2704 	if (attack_skill <= 0.4) {
2705 		//just walk to or away from the enemy
2706 		if (dist > attack_dist + attack_range) {
2707 			if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult;
2708 		}
2709 		if (dist < attack_dist - attack_range) {
2710 			if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult;
2711 		}
2712 		return moveresult;
2713 	}
2714 	//increase the strafe time
2715 	bs->attackstrafe_time += bs->thinktime;
2716 	//get the strafe change time
2717 	strafechange_time = 0.4 + (1 - attack_skill) * 0.2;
2718 	if (attack_skill > 0.7) strafechange_time += crandom() * 0.2;
2719 	//if the strafe direction should be changed
2720 	if (bs->attackstrafe_time > strafechange_time) {
2721 		//some magic number :)
2722 		if (random() > 0.935) {
2723 			//flip the strafe direction
2724 			bs->flags ^= BFL_STRAFERIGHT;
2725 			bs->attackstrafe_time = 0;
2726 		}
2727 	}
2728 	//
2729 	for (i = 0; i < 2; i++) {
2730 		hordir[0] = forward[0];
2731 		hordir[1] = forward[1];
2732 		hordir[2] = 0;
2733 		VectorNormalize(hordir);
2734 		//get the sideward vector
2735 		CrossProduct(hordir, up, sideward);
2736 		//reverse the vector depending on the strafe direction
2737 		if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward);
2738 		//randomly go back a little
2739 		if (random() > 0.9) {
2740 			VectorAdd(sideward, backward, sideward);
2741 		}
2742 		else {
2743 			//walk forward or backward to get at the ideal attack distance
2744 			if (dist > attack_dist + attack_range) {
2745 				VectorAdd(sideward, forward, sideward);
2746 			}
2747 			else if (dist < attack_dist - attack_range) {
2748 				VectorAdd(sideward, backward, sideward);
2749 			}
2750 		}
2751 		//perform the movement
2752 		if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype))
2753 			return moveresult;
2754 		//movement failed, flip the strafe direction
2755 		bs->flags ^= BFL_STRAFERIGHT;
2756 		bs->attackstrafe_time = 0;
2757 	}
2758 	//bot couldn't do any usefull movement
2759 //	bs->attackchase_time = AAS_Time() + 6;
2760 	return moveresult;
2761 }
2762 
2763 /*
2764 ==================
2765 BotSameTeam
2766 ==================
2767 */
BotSameTeam(bot_state_t * bs,int entnum)2768 int BotSameTeam(bot_state_t *bs, int entnum) {
2769 	char info1[1024], info2[1024];
2770 
2771 	if (bs->client < 0 || bs->client >= MAX_CLIENTS) {
2772 		//BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
2773 		return qfalse;
2774 	}
2775 	if (entnum < 0 || entnum >= MAX_CLIENTS) {
2776 		//BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n");
2777 		return qfalse;
2778 	}
2779 	if ( gametype >= GT_TEAM ) {
2780 		trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1));
2781 		trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2));
2782 		//
2783 		if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue;
2784 	}
2785 	return qfalse;
2786 }
2787 
2788 /*
2789 ==================
2790 InFieldOfVision
2791 ==================
2792 */
InFieldOfVision(vec3_t viewangles,float fov,vec3_t angles)2793 qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles)
2794 {
2795 	int i;
2796 	float diff, angle;
2797 
2798 	for (i = 0; i < 2; i++) {
2799 		angle = AngleMod(viewangles[i]);
2800 		angles[i] = AngleMod(angles[i]);
2801 		diff = angles[i] - angle;
2802 		if (angles[i] > angle) {
2803 			if (diff > 180.0) diff -= 360.0;
2804 		}
2805 		else {
2806 			if (diff < -180.0) diff += 360.0;
2807 		}
2808 		if (diff > 0) {
2809 			if (diff > fov * 0.5) return qfalse;
2810 		}
2811 		else {
2812 			if (diff < -fov * 0.5) return qfalse;
2813 		}
2814 	}
2815 	return qtrue;
2816 }
2817 
2818 /*
2819 ==================
2820 BotEntityVisible
2821 
2822 returns visibility in the range [0, 1] taking fog and water surfaces into account
2823 ==================
2824 */
BotEntityVisible(int viewer,vec3_t eye,vec3_t viewangles,float fov,int ent)2825 float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) {
2826 	int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
2827 	float squaredfogdist, waterfactor, vis, bestvis;
2828 	bsp_trace_t trace;
2829 	aas_entityinfo_t entinfo;
2830 	vec3_t dir, entangles, start, end, middle;
2831 
2832 	//calculate middle of bounding box
2833 	BotEntityInfo(ent, &entinfo);
2834 	VectorAdd(entinfo.mins, entinfo.maxs, middle);
2835 	VectorScale(middle, 0.5, middle);
2836 	VectorAdd(entinfo.origin, middle, middle);
2837 	//check if entity is within field of vision
2838 	VectorSubtract(middle, eye, dir);
2839 	vectoangles(dir, entangles);
2840 	if (!InFieldOfVision(viewangles, fov, entangles)) return 0;
2841 	//
2842 	pc = trap_AAS_PointContents(eye);
2843 	infog = (pc & CONTENTS_FOG);
2844 	inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER));
2845 	//
2846 	bestvis = 0;
2847 	for (i = 0; i < 3; i++) {
2848 		//if the point is not in potential visible sight
2849 		//if (!AAS_inPVS(eye, middle)) continue;
2850 		//
2851 		contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP;
2852 		passent = viewer;
2853 		hitent = ent;
2854 		VectorCopy(eye, start);
2855 		VectorCopy(middle, end);
2856 		//if the entity is in water, lava or slime
2857 		if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
2858 			contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
2859 		}
2860 		//if eye is in water, lava or slime
2861 		if (inwater) {
2862 			if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) {
2863 				passent = ent;
2864 				hitent = viewer;
2865 				VectorCopy(middle, start);
2866 				VectorCopy(eye, end);
2867 			}
2868 			contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
2869 		}
2870 		//trace from start to end
2871 		BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask);
2872 		//if water was hit
2873 		waterfactor = 1.0;
2874 		if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) {
2875 			//if the water surface is translucent
2876 			if (1) {
2877 				//trace through the water
2878 				contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER);
2879 				BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask);
2880 				waterfactor = 0.5;
2881 			}
2882 		}
2883 		//if a full trace or the hitent was hit
2884 		if (trace.fraction >= 1 || trace.ent == hitent) {
2885 			//check for fog, assuming there's only one fog brush where
2886 			//either the viewer or the entity is in or both are in
2887 			otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG);
2888 			if (infog && otherinfog) {
2889 				VectorSubtract(trace.endpos, eye, dir);
2890 				squaredfogdist = VectorLengthSquared(dir);
2891 			}
2892 			else if (infog) {
2893 				VectorCopy(trace.endpos, start);
2894 				BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG);
2895 				VectorSubtract(eye, trace.endpos, dir);
2896 				squaredfogdist = VectorLengthSquared(dir);
2897 			}
2898 			else if (otherinfog) {
2899 				VectorCopy(trace.endpos, end);
2900 				BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG);
2901 				VectorSubtract(end, trace.endpos, dir);
2902 				squaredfogdist = VectorLengthSquared(dir);
2903 			}
2904 			else {
2905 				//if the entity and the viewer are not in fog assume there's no fog in between
2906 				squaredfogdist = 0;
2907 			}
2908 			//decrease visibility with the view distance through fog
2909 			vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001));
2910 			//if entering water visibility is reduced
2911 			vis *= waterfactor;
2912 			//
2913 			if (vis > bestvis) bestvis = vis;
2914 			//if pretty much no fog
2915 			if (bestvis >= 0.95) return bestvis;
2916 		}
2917 		//check bottom and top of bounding box as well
2918 		if (i == 0) middle[2] += entinfo.mins[2];
2919 		else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2];
2920 	}
2921 	return bestvis;
2922 }
2923 
2924 /*
2925 ==================
2926 BotFindEnemy
2927 ==================
2928 */
BotFindEnemy(bot_state_t * bs,int curenemy)2929 int BotFindEnemy(bot_state_t *bs, int curenemy) {
2930 	int i, healthdecrease;
2931 	float f, alertness, easyfragger, vis;
2932 	float squaredist, cursquaredist;
2933 	aas_entityinfo_t entinfo, curenemyinfo;
2934 	vec3_t dir, angles;
2935 
2936 	alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1);
2937 	easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1);
2938 	//check if the health decreased
2939 	healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
2940 	//remember the current health value
2941 	bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
2942 	//
2943 	if (curenemy >= 0) {
2944 		BotEntityInfo(curenemy, &curenemyinfo);
2945 		if (EntityCarriesFlag(&curenemyinfo)) return qfalse;
2946 		VectorSubtract(curenemyinfo.origin, bs->origin, dir);
2947 		cursquaredist = VectorLengthSquared(dir);
2948 	}
2949 	else {
2950 		cursquaredist = 0;
2951 	}
2952 #ifdef MISSIONPACK
2953 	if (gametype == GT_OBELISK) {
2954 		vec3_t target;
2955 		bot_goal_t *goal;
2956 		bsp_trace_t trace;
2957 
2958 		if (BotTeam(bs) == TEAM_RED)
2959 			goal = &blueobelisk;
2960 		else
2961 			goal = &redobelisk;
2962 		//if the obelisk is visible
2963 		VectorCopy(goal->origin, target);
2964 		target[2] += 1;
2965 		BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID);
2966 		if (trace.fraction >= 1 || trace.ent == goal->entitynum) {
2967 			if (goal->entitynum == bs->enemy) {
2968 				return qfalse;
2969 			}
2970 			bs->enemy = goal->entitynum;
2971 			bs->enemysight_time = FloatTime();
2972 			bs->enemysuicide = qfalse;
2973 			bs->enemydeath_time = 0;
2974 			bs->enemyvisible_time = FloatTime();
2975 			return qtrue;
2976 		}
2977 	}
2978 #endif
2979 	//
2980 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
2981 
2982 		if (i == bs->client) continue;
2983 		//if it's the current enemy
2984 		if (i == curenemy) continue;
2985 		//
2986 		BotEntityInfo(i, &entinfo);
2987 		//
2988 		if (!entinfo.valid) continue;
2989 		//if the enemy isn't dead and the enemy isn't the bot self
2990 		if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
2991 		//if the enemy is invisible and not shooting
2992 		if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
2993 			continue;
2994 		}
2995 		//if not an easy fragger don't shoot at chatting players
2996 		if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue;
2997 		//
2998 		if (lastteleport_time > FloatTime() - 3) {
2999 			VectorSubtract(entinfo.origin, lastteleport_origin, dir);
3000 			if (VectorLengthSquared(dir) < Square(70)) continue;
3001 		}
3002 		//calculate the distance towards the enemy
3003 		VectorSubtract(entinfo.origin, bs->origin, dir);
3004 		squaredist = VectorLengthSquared(dir);
3005 		//if this entity is not carrying a flag
3006 		if (!EntityCarriesFlag(&entinfo))
3007 		{
3008 			//if this enemy is further away than the current one
3009 			if (curenemy >= 0 && squaredist > cursquaredist) continue;
3010 		} //end if
3011 		//if the bot has no
3012 		if (squaredist > Square(900.0 + alertness * 4000.0)) continue;
3013 		//if on the same team
3014 		if (BotSameTeam(bs, i)) continue;
3015 		//if the bot's health decreased or the enemy is shooting
3016 		if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo)))
3017 			f = 360;
3018 		else
3019 			f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9));
3020 		//check if the enemy is visible
3021 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i);
3022 		if (vis <= 0) continue;
3023 		//if the enemy is quite far away, not shooting and the bot is not damaged
3024 		if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo))
3025 		{
3026 			//check if we can avoid this enemy
3027 			VectorSubtract(bs->origin, entinfo.origin, dir);
3028 			vectoangles(dir, angles);
3029 			//if the bot isn't in the fov of the enemy
3030 			if (!InFieldOfVision(entinfo.angles, 90, angles)) {
3031 				//update some stuff for this enemy
3032 				BotUpdateBattleInventory(bs, i);
3033 				//if the bot doesn't really want to fight
3034 				if (BotWantsToRetreat(bs)) continue;
3035 			}
3036 		}
3037 		//found an enemy
3038 		bs->enemy = entinfo.number;
3039 		if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2;
3040 		else bs->enemysight_time = FloatTime();
3041 		bs->enemysuicide = qfalse;
3042 		bs->enemydeath_time = 0;
3043 		bs->enemyvisible_time = FloatTime();
3044 		return qtrue;
3045 	}
3046 	return qfalse;
3047 }
3048 
3049 /*
3050 ==================
3051 BotTeamFlagCarrierVisible
3052 ==================
3053 */
BotTeamFlagCarrierVisible(bot_state_t * bs)3054 int BotTeamFlagCarrierVisible(bot_state_t *bs) {
3055 	int i;
3056 	float vis;
3057 	aas_entityinfo_t entinfo;
3058 
3059 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
3060 		if (i == bs->client)
3061 			continue;
3062 		//
3063 		BotEntityInfo(i, &entinfo);
3064 		//if this player is active
3065 		if (!entinfo.valid)
3066 			continue;
3067 		//if this player is carrying a flag
3068 		if (!EntityCarriesFlag(&entinfo))
3069 			continue;
3070 		//if the flag carrier is not on the same team
3071 		if (!BotSameTeam(bs, i))
3072 			continue;
3073 		//if the flag carrier is not visible
3074 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
3075 		if (vis <= 0)
3076 			continue;
3077 		//
3078 		return i;
3079 	}
3080 	return -1;
3081 }
3082 
3083 /*
3084 ==================
3085 BotTeamFlagCarrier
3086 ==================
3087 */
BotTeamFlagCarrier(bot_state_t * bs)3088 int BotTeamFlagCarrier(bot_state_t *bs) {
3089 	int i;
3090 	aas_entityinfo_t entinfo;
3091 
3092 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
3093 		if (i == bs->client)
3094 			continue;
3095 		//
3096 		BotEntityInfo(i, &entinfo);
3097 		//if this player is active
3098 		if (!entinfo.valid)
3099 			continue;
3100 		//if this player is carrying a flag
3101 		if (!EntityCarriesFlag(&entinfo))
3102 			continue;
3103 		//if the flag carrier is not on the same team
3104 		if (!BotSameTeam(bs, i))
3105 			continue;
3106 		//
3107 		return i;
3108 	}
3109 	return -1;
3110 }
3111 
3112 /*
3113 ==================
3114 BotEnemyFlagCarrierVisible
3115 ==================
3116 */
BotEnemyFlagCarrierVisible(bot_state_t * bs)3117 int BotEnemyFlagCarrierVisible(bot_state_t *bs) {
3118 	int i;
3119 	float vis;
3120 	aas_entityinfo_t entinfo;
3121 
3122 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
3123 		if (i == bs->client)
3124 			continue;
3125 		//
3126 		BotEntityInfo(i, &entinfo);
3127 		//if this player is active
3128 		if (!entinfo.valid)
3129 			continue;
3130 		//if this player is carrying a flag
3131 		if (!EntityCarriesFlag(&entinfo))
3132 			continue;
3133 		//if the flag carrier is on the same team
3134 		if (BotSameTeam(bs, i))
3135 			continue;
3136 		//if the flag carrier is not visible
3137 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
3138 		if (vis <= 0)
3139 			continue;
3140 		//
3141 		return i;
3142 	}
3143 	return -1;
3144 }
3145 
3146 /*
3147 ==================
3148 BotVisibleTeamMatesAndEnemies
3149 ==================
3150 */
BotVisibleTeamMatesAndEnemies(bot_state_t * bs,int * teammates,int * enemies,float range)3151 void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) {
3152 	int i;
3153 	float vis;
3154 	aas_entityinfo_t entinfo;
3155 	vec3_t dir;
3156 
3157 	if (teammates)
3158 		*teammates = 0;
3159 	if (enemies)
3160 		*enemies = 0;
3161 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
3162 		if (i == bs->client)
3163 			continue;
3164 		//
3165 		BotEntityInfo(i, &entinfo);
3166 		//if this player is active
3167 		if (!entinfo.valid)
3168 			continue;
3169 		//if this player is carrying a flag
3170 		if (!EntityCarriesFlag(&entinfo))
3171 			continue;
3172 		//if not within range
3173 		VectorSubtract(entinfo.origin, bs->origin, dir);
3174 		if (VectorLengthSquared(dir) > Square(range))
3175 			continue;
3176 		//if the flag carrier is not visible
3177 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
3178 		if (vis <= 0)
3179 			continue;
3180 		//if the flag carrier is on the same team
3181 		if (BotSameTeam(bs, i)) {
3182 			if (teammates)
3183 				(*teammates)++;
3184 		}
3185 		else {
3186 			if (enemies)
3187 				(*enemies)++;
3188 		}
3189 	}
3190 }
3191 
3192 #ifdef MISSIONPACK
3193 /*
3194 ==================
3195 BotTeamCubeCarrierVisible
3196 ==================
3197 */
BotTeamCubeCarrierVisible(bot_state_t * bs)3198 int BotTeamCubeCarrierVisible(bot_state_t *bs) {
3199 	int i;
3200 	float vis;
3201 	aas_entityinfo_t entinfo;
3202 
3203 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
3204 		if (i == bs->client) continue;
3205 		//
3206 		BotEntityInfo(i, &entinfo);
3207 		//if this player is active
3208 		if (!entinfo.valid) continue;
3209 		//if this player is carrying a flag
3210 		if (!EntityCarriesCubes(&entinfo)) continue;
3211 		//if the flag carrier is not on the same team
3212 		if (!BotSameTeam(bs, i)) continue;
3213 		//if the flag carrier is not visible
3214 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
3215 		if (vis <= 0) continue;
3216 		//
3217 		return i;
3218 	}
3219 	return -1;
3220 }
3221 
3222 /*
3223 ==================
3224 BotEnemyCubeCarrierVisible
3225 ==================
3226 */
BotEnemyCubeCarrierVisible(bot_state_t * bs)3227 int BotEnemyCubeCarrierVisible(bot_state_t *bs) {
3228 	int i;
3229 	float vis;
3230 	aas_entityinfo_t entinfo;
3231 
3232 	for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
3233 		if (i == bs->client)
3234 			continue;
3235 		//
3236 		BotEntityInfo(i, &entinfo);
3237 		//if this player is active
3238 		if (!entinfo.valid)
3239 			continue;
3240 		//if this player is carrying a flag
3241 		if (!EntityCarriesCubes(&entinfo)) continue;
3242 		//if the flag carrier is on the same team
3243 		if (BotSameTeam(bs, i))
3244 			continue;
3245 		//if the flag carrier is not visible
3246 		vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i);
3247 		if (vis <= 0)
3248 			continue;
3249 		//
3250 		return i;
3251 	}
3252 	return -1;
3253 }
3254 #endif
3255 
3256 /*
3257 ==================
3258 BotAimAtEnemy
3259 ==================
3260 */
BotAimAtEnemy(bot_state_t * bs)3261 void BotAimAtEnemy(bot_state_t *bs) {
3262 	int i, enemyvisible;
3263 	float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
3264 	vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
3265 	vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4};
3266 	weaponinfo_t wi;
3267 	aas_entityinfo_t entinfo;
3268 	bot_goal_t goal;
3269 	bsp_trace_t trace;
3270 	vec3_t target;
3271 
3272 	//if the bot has no enemy
3273 	if (bs->enemy < 0) {
3274 		return;
3275 	}
3276 	//get the enemy entity information
3277 	BotEntityInfo(bs->enemy, &entinfo);
3278 	//if this is not a player (should be an obelisk)
3279 	if (bs->enemy >= MAX_CLIENTS) {
3280 		//if the obelisk is visible
3281 		VectorCopy(entinfo.origin, target);
3282 #ifdef MISSIONPACK
3283 		// if attacking an obelisk
3284 		if ( bs->enemy == redobelisk.entitynum ||
3285 			bs->enemy == blueobelisk.entitynum ) {
3286 			target[2] += 32;
3287 		}
3288 #endif
3289 		//aim at the obelisk
3290 		VectorSubtract(target, bs->eye, dir);
3291 		vectoangles(dir, bs->ideal_viewangles);
3292 		//set the aim target before trying to attack
3293 		VectorCopy(target, bs->aimtarget);
3294 		return;
3295 	}
3296 	//
3297 	//BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
3298 	//
3299 	aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1);
3300 	aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1);
3301 	//
3302 	if (aim_skill > 0.95) {
3303 		//don't aim too early
3304 		reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1);
3305 		if (bs->enemysight_time > FloatTime() - reactiontime) return;
3306 		if (bs->teleport_time > FloatTime() - reactiontime) return;
3307 	}
3308 
3309 	//get the weapon information
3310 	trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
3311 	//get the weapon specific aim accuracy and or aim skill
3312 	if (wi.number == WP_MACHINEGUN) {
3313 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1);
3314 	}
3315 	else if (wi.number == WP_SHOTGUN) {
3316 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1);
3317 	}
3318 	else if (wi.number == WP_GRENADE_LAUNCHER) {
3319 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1);
3320 		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1);
3321 	}
3322 	else if (wi.number == WP_ROCKET_LAUNCHER) {
3323 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1);
3324 		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1);
3325 	}
3326 	else if (wi.number == WP_LIGHTNING) {
3327 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1);
3328 	}
3329 	else if (wi.number == WP_RAILGUN) {
3330 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1);
3331 	}
3332 	else if (wi.number == WP_PLASMAGUN) {
3333 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1);
3334 		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1);
3335 	}
3336 	else if (wi.number == WP_BFG) {
3337 		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1);
3338 		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1);
3339 	}
3340 	//
3341 	if (aim_accuracy <= 0) aim_accuracy = 0.0001f;
3342 	//get the enemy entity information
3343 	BotEntityInfo(bs->enemy, &entinfo);
3344 	//if the enemy is invisible then shoot crappy most of the time
3345 	if (EntityIsInvisible(&entinfo)) {
3346 		if (random() > 0.1) aim_accuracy *= 0.4f;
3347 	}
3348 	//
3349 	VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity);
3350 	VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity);
3351 	//enemy origin and velocity is remembered every 0.5 seconds
3352 	if (bs->enemyposition_time < FloatTime()) {
3353 		//
3354 		bs->enemyposition_time = FloatTime() + 0.5;
3355 		VectorCopy(enemyvelocity, bs->enemyvelocity);
3356 		VectorCopy(entinfo.origin, bs->enemyorigin);
3357 	}
3358 	//if not extremely skilled
3359 	if (aim_skill < 0.9) {
3360 		VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
3361 		//if the enemy moved a bit
3362 		if (VectorLengthSquared(dir) > Square(48)) {
3363 			//if the enemy changed direction
3364 			if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) {
3365 				//aim accuracy should be worse now
3366 				aim_accuracy *= 0.7f;
3367 			}
3368 		}
3369 	}
3370 	//check visibility of enemy
3371 	enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy);
3372 	//if the enemy is visible
3373 	if (enemyvisible) {
3374 		//
3375 		VectorCopy(entinfo.origin, bestorigin);
3376 		bestorigin[2] += 8;
3377 		//get the start point shooting from
3378 		//NOTE: the x and y projectile start offsets are ignored
3379 		VectorCopy(bs->origin, start);
3380 		start[2] += bs->cur_ps.viewheight;
3381 		start[2] += wi.offset[2];
3382 		//
3383 		BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT);
3384 		//if the enemy is NOT hit
3385 		if (trace.fraction <= 1 && trace.ent != entinfo.number) {
3386 			bestorigin[2] += 16;
3387 		}
3388 		//if it is not an instant hit weapon the bot might want to predict the enemy
3389 		if (wi.speed) {
3390 			//
3391 			VectorSubtract(bestorigin, bs->origin, dir);
3392 			dist = VectorLength(dir);
3393 			VectorSubtract(entinfo.origin, bs->enemyorigin, dir);
3394 			//if the enemy is NOT pretty far away and strafing just small steps left and right
3395 			if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) {
3396 				//if skilled anough do exact prediction
3397 				if (aim_skill > 0.8 &&
3398 						//if the weapon is ready to fire
3399 						bs->cur_ps.weaponstate == WEAPON_READY) {
3400 					aas_clientmove_t move;
3401 					vec3_t origin;
3402 
3403 					VectorSubtract(entinfo.origin, bs->origin, dir);
3404 					//distance towards the enemy
3405 					dist = VectorLength(dir);
3406 					//direction the enemy is moving in
3407 					VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
3408 					//
3409 					VectorScale(dir, 1 / entinfo.update_time, dir);
3410 					//
3411 					VectorCopy(entinfo.origin, origin);
3412 					origin[2] += 1;
3413 					//
3414 					VectorClear(cmdmove);
3415 					//AAS_ClearShownDebugLines();
3416 					trap_AAS_PredictClientMovement(&move, bs->enemy, origin,
3417 														PRESENCE_CROUCH, qfalse,
3418 														dir, cmdmove, 0,
3419 														dist * 10 / wi.speed, 0.1f, 0, 0, qfalse);
3420 					VectorCopy(move.endpos, bestorigin);
3421 					//BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed);
3422 				}
3423 				//if not that skilled do linear prediction
3424 				else if (aim_skill > 0.4) {
3425 					VectorSubtract(entinfo.origin, bs->origin, dir);
3426 					//distance towards the enemy
3427 					dist = VectorLength(dir);
3428 					//direction the enemy is moving in
3429 					VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
3430 					dir[2] = 0;
3431 					//
3432 					speed = VectorNormalize(dir) / entinfo.update_time;
3433 					//botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
3434 					//best spot to aim at
3435 					VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin);
3436 				}
3437 			}
3438 		}
3439 		//if the projectile does radial damage
3440 		if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) {
3441 			//if the enemy isn't standing significantly higher than the bot
3442 			if (entinfo.origin[2] < bs->origin[2] + 16) {
3443 				//try to aim at the ground in front of the enemy
3444 				VectorCopy(entinfo.origin, end);
3445 				end[2] -= 64;
3446 				BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT);
3447 				//
3448 				VectorCopy(bestorigin, groundtarget);
3449 				if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16;
3450 				else groundtarget[2] = trace.endpos[2] - 8;
3451 				//trace a line from projectile start to ground target
3452 				BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT);
3453 				//if hitpoint is not vertically too far from the ground target
3454 				if (fabs(trace.endpos[2] - groundtarget[2]) < 50) {
3455 					VectorSubtract(trace.endpos, groundtarget, dir);
3456 					//if the hitpoint is near anough the ground target
3457 					if (VectorLengthSquared(dir) < Square(60)) {
3458 						VectorSubtract(trace.endpos, start, dir);
3459 						//if the hitpoint is far anough from the bot
3460 						if (VectorLengthSquared(dir) > Square(100)) {
3461 							//check if the bot is visible from the ground target
3462 							trace.endpos[2] += 1;
3463 							BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT);
3464 							if (trace.fraction >= 1) {
3465 								//botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
3466 								VectorCopy(groundtarget, bestorigin);
3467 							}
3468 						}
3469 					}
3470 				}
3471 			}
3472 		}
3473 		bestorigin[0] += 20 * crandom() * (1 - aim_accuracy);
3474 		bestorigin[1] += 20 * crandom() * (1 - aim_accuracy);
3475 		bestorigin[2] += 10 * crandom() * (1 - aim_accuracy);
3476 	}
3477 	else {
3478 		//
3479 		VectorCopy(bs->lastenemyorigin, bestorigin);
3480 		bestorigin[2] += 8;
3481 		//if the bot is skilled anough
3482 		if (aim_skill > 0.5) {
3483 			//do prediction shots around corners
3484 			if (wi.number == WP_BFG ||
3485 				wi.number == WP_ROCKET_LAUNCHER ||
3486 				wi.number == WP_GRENADE_LAUNCHER) {
3487 				//create the chase goal
3488 				goal.entitynum = bs->client;
3489 				goal.areanum = bs->areanum;
3490 				VectorCopy(bs->eye, goal.origin);
3491 				VectorSet(goal.mins, -8, -8, -8);
3492 				VectorSet(goal.maxs, 8, 8, 8);
3493 				//
3494 				if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) {
3495 					VectorSubtract(target, bs->eye, dir);
3496 					if (VectorLengthSquared(dir) > Square(80)) {
3497 						VectorCopy(target, bestorigin);
3498 						bestorigin[2] -= 20;
3499 					}
3500 				}
3501 				aim_accuracy = 1;
3502 			}
3503 		}
3504 	}
3505 	//
3506 	if (enemyvisible) {
3507 		BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT);
3508 		VectorCopy(trace.endpos, bs->aimtarget);
3509 	}
3510 	else {
3511 		VectorCopy(bestorigin, bs->aimtarget);
3512 	}
3513 	//get aim direction
3514 	VectorSubtract(bestorigin, bs->eye, dir);
3515 	//
3516 	if (wi.number == WP_MACHINEGUN ||
3517 		wi.number == WP_SHOTGUN ||
3518 		wi.number == WP_LIGHTNING ||
3519 		wi.number == WP_RAILGUN) {
3520 		//distance towards the enemy
3521 		dist = VectorLength(dir);
3522 		if (dist > 150) dist = 150;
3523 		f = 0.6 + dist / 150 * 0.4;
3524 		aim_accuracy *= f;
3525 	}
3526 	//add some random stuff to the aim direction depending on the aim accuracy
3527 	if (aim_accuracy < 0.8) {
3528 		VectorNormalize(dir);
3529 		for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy);
3530 	}
3531 	//set the ideal view angles
3532 	vectoangles(dir, bs->ideal_viewangles);
3533 	//take the weapon spread into account for lower skilled bots
3534 	bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy);
3535 	bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
3536 	bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy);
3537 	bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
3538 	//if the bots should be really challenging
3539 	if (bot_challenge.integer) {
3540 		//if the bot is really accurate and has the enemy in view for some time
3541 		if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) {
3542 			//set the view angles directly
3543 			if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360;
3544 			VectorCopy(bs->ideal_viewangles, bs->viewangles);
3545 			trap_EA_View(bs->client, bs->viewangles);
3546 		}
3547 	}
3548 }
3549 
3550 /*
3551 ==================
3552 BotCheckAttack
3553 ==================
3554 */
BotCheckAttack(bot_state_t * bs)3555 void BotCheckAttack(bot_state_t *bs) {
3556 	float points, reactiontime, fov, firethrottle;
3557 	int attackentity;
3558 	bsp_trace_t bsptrace;
3559 	//float selfpreservation;
3560 	vec3_t forward, right, start, end, dir, angles;
3561 	weaponinfo_t wi;
3562 	bsp_trace_t trace;
3563 	aas_entityinfo_t entinfo;
3564 	vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
3565 
3566 	attackentity = bs->enemy;
3567 	//
3568 	BotEntityInfo(attackentity, &entinfo);
3569 	// if not attacking a player
3570 	if (attackentity >= MAX_CLIENTS) {
3571 #ifdef MISSIONPACK
3572 		// if attacking an obelisk
3573 		if ( entinfo.number == redobelisk.entitynum ||
3574 			entinfo.number == blueobelisk.entitynum ) {
3575 			// if obelisk is respawning return
3576 			if ( g_entities[entinfo.number].activator &&
3577 				g_entities[entinfo.number].activator->s.frame == 2 ) {
3578 				return;
3579 			}
3580 		}
3581 #endif
3582 	}
3583 	//
3584 	reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1);
3585 	if (bs->enemysight_time > FloatTime() - reactiontime) return;
3586 	if (bs->teleport_time > FloatTime() - reactiontime) return;
3587 	//if changing weapons
3588 	if (bs->weaponchange_time > FloatTime() - 0.1) return;
3589 	//check fire throttle characteristic
3590 	if (bs->firethrottlewait_time > FloatTime()) return;
3591 	firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1);
3592 	if (bs->firethrottleshoot_time < FloatTime()) {
3593 		if (random() > firethrottle) {
3594 			bs->firethrottlewait_time = FloatTime() + firethrottle;
3595 			bs->firethrottleshoot_time = 0;
3596 		}
3597 		else {
3598 			bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle;
3599 			bs->firethrottlewait_time = 0;
3600 		}
3601 	}
3602 	//
3603 	//
3604 	VectorSubtract(bs->aimtarget, bs->eye, dir);
3605 	//
3606 	if (bs->weaponnum == WP_GAUNTLET) {
3607 		if (VectorLengthSquared(dir) > Square(60)) {
3608 			return;
3609 		}
3610 	}
3611 	if (VectorLengthSquared(dir) < Square(100))
3612 		fov = 120;
3613 	else
3614 		fov = 50;
3615 	//
3616 	vectoangles(dir, angles);
3617 	if (!InFieldOfVision(bs->viewangles, fov, angles))
3618 		return;
3619 	BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
3620 	if (bsptrace.fraction < 1 && bsptrace.ent != attackentity)
3621 		return;
3622 
3623 	//get the weapon info
3624 	trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi);
3625 	//get the start point shooting from
3626 	VectorCopy(bs->origin, start);
3627 	start[2] += bs->cur_ps.viewheight;
3628 	AngleVectors(bs->viewangles, forward, right, NULL);
3629 	start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
3630 	start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
3631 	start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
3632 	//end point aiming at
3633 	VectorMA(start, 1000, forward, end);
3634 	//a little back to make sure not inside a very close enemy
3635 	VectorMA(start, -12, forward, start);
3636 	BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT);
3637 	//if the entity is a client
3638 	if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) {
3639 		if (trace.ent != attackentity) {
3640 			//if a teammate is hit
3641 			if (BotSameTeam(bs, trace.ent))
3642 				return;
3643 		}
3644 	}
3645 	//if won't hit the enemy or not attacking a player (obelisk)
3646 	if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) {
3647 		//if the projectile does radial damage
3648 		if (wi.proj.damagetype & DAMAGETYPE_RADIAL) {
3649 			if (trace.fraction * 1000 < wi.proj.radius) {
3650 				points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5;
3651 				if (points > 0) {
3652 					return;
3653 				}
3654 			}
3655 			//FIXME: check if a teammate gets radial damage
3656 		}
3657 	}
3658 	//if fire has to be release to activate weapon
3659 	if (wi.flags & WFL_FIRERELEASED) {
3660 		if (bs->flags & BFL_ATTACKED) {
3661 			trap_EA_Attack(bs->client);
3662 		}
3663 	}
3664 	else {
3665 		trap_EA_Attack(bs->client);
3666 	}
3667 	bs->flags ^= BFL_ATTACKED;
3668 }
3669 
3670 /*
3671 ==================
3672 BotMapScripts
3673 ==================
3674 */
BotMapScripts(bot_state_t * bs)3675 void BotMapScripts(bot_state_t *bs) {
3676 	char info[1024];
3677 	char mapname[128];
3678 	int i, shootbutton;
3679 	float aim_accuracy;
3680 	aas_entityinfo_t entinfo;
3681 	vec3_t dir;
3682 
3683 	trap_GetServerinfo(info, sizeof(info));
3684 
3685 	strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1);
3686 	mapname[sizeof(mapname)-1] = '\0';
3687 
3688 	if (!Q_stricmp(mapname, "q3tourney6")) {
3689 		vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680};
3690 		vec3_t buttonorg = {304, 352, 920};
3691 		//NOTE: NEVER use the func_bobbing in q3tourney6
3692 		bs->tfl &= ~TFL_FUNCBOB;
3693 		//if the bot is below the bounding box
3694 		if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) {
3695 			if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) {
3696 				if (bs->origin[2] < mins[2]) {
3697 					return;
3698 				}
3699 			}
3700 		}
3701 		shootbutton = qfalse;
3702 		//if an enemy is below this bounding box then shoot the button
3703 		for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) {
3704 
3705 			if (i == bs->client) continue;
3706 			//
3707 			BotEntityInfo(i, &entinfo);
3708 			//
3709 			if (!entinfo.valid) continue;
3710 			//if the enemy isn't dead and the enemy isn't the bot self
3711 			if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue;
3712 			//
3713 			if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) {
3714 				if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) {
3715 					if (entinfo.origin[2] < mins[2]) {
3716 						//if there's a team mate below the crusher
3717 						if (BotSameTeam(bs, i)) {
3718 							shootbutton = qfalse;
3719 							break;
3720 						}
3721 						else {
3722 							shootbutton = qtrue;
3723 						}
3724 					}
3725 				}
3726 			}
3727 		}
3728 		if (shootbutton) {
3729 			bs->flags |= BFL_IDEALVIEWSET;
3730 			VectorSubtract(buttonorg, bs->eye, dir);
3731 			vectoangles(dir, bs->ideal_viewangles);
3732 			aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1);
3733 			bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy);
3734 			bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]);
3735 			bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy);
3736 			bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]);
3737 			//
3738 			if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) {
3739 				trap_EA_Attack(bs->client);
3740 			}
3741 		}
3742 	}
3743 	else if (!Q_stricmp(mapname, "mpq3tourney6")) {
3744 		//NOTE: NEVER use the func_bobbing in mpq3tourney6
3745 		bs->tfl &= ~TFL_FUNCBOB;
3746 	}
3747 }
3748 
3749 /*
3750 ==================
3751 BotSetMovedir
3752 ==================
3753 */
3754 static vec3_t VEC_UP		= {0, -1,  0};
3755 static vec3_t MOVEDIR_UP	= {0,  0,  1};
3756 static vec3_t VEC_DOWN		= {0, -2,  0};
3757 static vec3_t MOVEDIR_DOWN	= {0,  0, -1};
3758 
BotSetMovedir(vec3_t angles,vec3_t movedir)3759 void BotSetMovedir(vec3_t angles, vec3_t movedir) {
3760 	if (VectorCompare(angles, VEC_UP)) {
3761 		VectorCopy(MOVEDIR_UP, movedir);
3762 	}
3763 	else if (VectorCompare(angles, VEC_DOWN)) {
3764 		VectorCopy(MOVEDIR_DOWN, movedir);
3765 	}
3766 	else {
3767 		AngleVectors(angles, movedir, NULL, NULL);
3768 	}
3769 }
3770 
3771 /*
3772 ==================
3773 BotModelMinsMaxs
3774 
3775 this is ugly
3776 ==================
3777 */
BotModelMinsMaxs(int modelindex,int eType,int contents,vec3_t mins,vec3_t maxs)3778 int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) {
3779 	gentity_t *ent;
3780 	int i;
3781 
3782 	ent = &g_entities[0];
3783 	for (i = 0; i < level.num_entities; i++, ent++) {
3784 		if ( !ent->inuse ) {
3785 			continue;
3786 		}
3787 		if ( eType && ent->s.eType != eType) {
3788 			continue;
3789 		}
3790 		if ( contents && ent->r.contents != contents) {
3791 			continue;
3792 		}
3793 		if (ent->s.modelindex == modelindex) {
3794 			if (mins)
3795 				VectorAdd(ent->r.currentOrigin, ent->r.mins, mins);
3796 			if (maxs)
3797 				VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs);
3798 			return i;
3799 		}
3800 	}
3801 	if (mins)
3802 		VectorClear(mins);
3803 	if (maxs)
3804 		VectorClear(maxs);
3805 	return 0;
3806 }
3807 
3808 /*
3809 ==================
3810 BotFuncButtonGoal
3811 ==================
3812 */
BotFuncButtonActivateGoal(bot_state_t * bs,int bspent,bot_activategoal_t * activategoal)3813 int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) {
3814 	int i, areas[10], numareas, modelindex, entitynum;
3815 	char model[128];
3816 	float lip, dist, health, angle;
3817 	vec3_t size, start, end, mins, maxs, angles, points[10];
3818 	vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs;
3819 	vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1};
3820 	bsp_trace_t bsptrace;
3821 
3822 	activategoal->shoot = qfalse;
3823 	VectorClear(activategoal->target);
3824 	//create a bot goal towards the button
3825 	trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model));
3826 	if (!*model)
3827 		return qfalse;
3828 	modelindex = atoi(model+1);
3829 	if (!modelindex)
3830 		return qfalse;
3831 	VectorClear(angles);
3832 	entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs);
3833 	//get the lip of the button
3834 	trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip);
3835 	if (!lip) lip = 4;
3836 	//get the move direction from the angle
3837 	trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle);
3838 	VectorSet(angles, 0, angle, 0);
3839 	BotSetMovedir(angles, movedir);
3840 	//button size
3841 	VectorSubtract(maxs, mins, size);
3842 	//button origin
3843 	VectorAdd(mins, maxs, origin);
3844 	VectorScale(origin, 0.5, origin);
3845 	//touch distance of the button
3846 	dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2];
3847 	dist *= 0.5;
3848 	//
3849 	trap_AAS_FloatForBSPEpairKey(bspent, "health", &health);
3850 	//if the button is shootable
3851 	if (health) {
3852 		//calculate the shoot target
3853 		VectorMA(origin, -dist, movedir, goalorigin);
3854 		//
3855 		VectorCopy(goalorigin, activategoal->target);
3856 		activategoal->shoot = qtrue;
3857 		//
3858 		BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT);
3859 		// if the button is visible from the current position
3860 		if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) {
3861 			//
3862 			activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button
3863 			activategoal->goal.number = 0;
3864 			activategoal->goal.flags = 0;
3865 			VectorCopy(bs->origin, activategoal->goal.origin);
3866 			activategoal->goal.areanum = bs->areanum;
3867 			VectorSet(activategoal->goal.mins, -8, -8, -8);
3868 			VectorSet(activategoal->goal.maxs, 8, 8, 8);
3869 			//
3870 			return qtrue;
3871 		}
3872 		else {
3873 			//create a goal from where the button is visible and shoot at the button from there
3874 			//add bounding box size to the dist
3875 			trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
3876 			for (i = 0; i < 3; i++) {
3877 				if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
3878 				else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
3879 			}
3880 			//calculate the goal origin
3881 			VectorMA(origin, -dist, movedir, goalorigin);
3882 			//
3883 			VectorCopy(goalorigin, start);
3884 			start[2] += 24;
3885 			VectorCopy(start, end);
3886 			end[2] -= 512;
3887 			numareas = trap_AAS_TraceAreas(start, end, areas, points, 10);
3888 			//
3889 			for (i = numareas-1; i >= 0; i--) {
3890 				if (trap_AAS_AreaReachability(areas[i])) {
3891 					break;
3892 				}
3893 			}
3894 			if (i < 0) {
3895 				// FIXME: trace forward and maybe in other directions to find a valid area
3896 			}
3897 			if (i >= 0) {
3898 				//
3899 				VectorCopy(points[i], activategoal->goal.origin);
3900 				activategoal->goal.areanum = areas[i];
3901 				VectorSet(activategoal->goal.mins, 8, 8, 8);
3902 				VectorSet(activategoal->goal.maxs, -8, -8, -8);
3903 				//
3904 				for (i = 0; i < 3; i++)
3905 				{
3906 					if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]);
3907 					else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]);
3908 				} //end for
3909 				//
3910 				activategoal->goal.entitynum = entitynum;
3911 				activategoal->goal.number = 0;
3912 				activategoal->goal.flags = 0;
3913 				return qtrue;
3914 			}
3915 		}
3916 		return qfalse;
3917 	}
3918 	else {
3919 		//add bounding box size to the dist
3920 		trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
3921 		for (i = 0; i < 3; i++) {
3922 			if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
3923 			else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
3924 		}
3925 		//calculate the goal origin
3926 		VectorMA(origin, -dist, movedir, goalorigin);
3927 		//
3928 		VectorCopy(goalorigin, start);
3929 		start[2] += 24;
3930 		VectorCopy(start, end);
3931 		end[2] -= 100;
3932 		numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
3933 		//
3934 		for (i = 0; i < numareas; i++) {
3935 			if (trap_AAS_AreaReachability(areas[i])) {
3936 				break;
3937 			}
3938 		}
3939 		if (i < numareas) {
3940 			//
3941 			VectorCopy(origin, activategoal->goal.origin);
3942 			activategoal->goal.areanum = areas[i];
3943 			VectorSubtract(mins, origin, activategoal->goal.mins);
3944 			VectorSubtract(maxs, origin, activategoal->goal.maxs);
3945 			//
3946 			for (i = 0; i < 3; i++)
3947 			{
3948 				if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]);
3949 				else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]);
3950 			} //end for
3951 			//
3952 			activategoal->goal.entitynum = entitynum;
3953 			activategoal->goal.number = 0;
3954 			activategoal->goal.flags = 0;
3955 			return qtrue;
3956 		}
3957 	}
3958 	return qfalse;
3959 }
3960 
3961 /*
3962 ==================
3963 BotFuncDoorGoal
3964 ==================
3965 */
BotFuncDoorActivateGoal(bot_state_t * bs,int bspent,bot_activategoal_t * activategoal)3966 int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) {
3967 	int modelindex, entitynum;
3968 	char model[MAX_INFO_STRING];
3969 	vec3_t mins, maxs, origin, angles;
3970 
3971 	//shoot at the shootable door
3972 	trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model));
3973 	if (!*model)
3974 		return qfalse;
3975 	modelindex = atoi(model+1);
3976 	if (!modelindex)
3977 		return qfalse;
3978 	VectorClear(angles);
3979 	entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs);
3980 	//door origin
3981 	VectorAdd(mins, maxs, origin);
3982 	VectorScale(origin, 0.5, origin);
3983 	VectorCopy(origin, activategoal->target);
3984 	activategoal->shoot = qtrue;
3985 	//
3986 	activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door
3987 	activategoal->goal.number = 0;
3988 	activategoal->goal.flags = 0;
3989 	VectorCopy(bs->origin, activategoal->goal.origin);
3990 	activategoal->goal.areanum = bs->areanum;
3991 	VectorSet(activategoal->goal.mins, -8, -8, -8);
3992 	VectorSet(activategoal->goal.maxs, 8, 8, 8);
3993 	return qtrue;
3994 }
3995 
3996 /*
3997 ==================
3998 BotTriggerMultipleGoal
3999 ==================
4000 */
BotTriggerMultipleActivateGoal(bot_state_t * bs,int bspent,bot_activategoal_t * activategoal)4001 int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) {
4002 	int i, areas[10], numareas, modelindex, entitynum;
4003 	char model[128];
4004 	vec3_t start, end, mins, maxs, angles;
4005 	vec3_t origin, goalorigin;
4006 
4007 	activategoal->shoot = qfalse;
4008 	VectorClear(activategoal->target);
4009 	//create a bot goal towards the trigger
4010 	trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model));
4011 	if (!*model)
4012 		return qfalse;
4013 	modelindex = atoi(model+1);
4014 	if (!modelindex)
4015 		return qfalse;
4016 	VectorClear(angles);
4017 	entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs);
4018 	//trigger origin
4019 	VectorAdd(mins, maxs, origin);
4020 	VectorScale(origin, 0.5, origin);
4021 	VectorCopy(origin, goalorigin);
4022 	//
4023 	VectorCopy(goalorigin, start);
4024 	start[2] += 24;
4025 	VectorCopy(start, end);
4026 	end[2] -= 100;
4027 	numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
4028 	//
4029 	for (i = 0; i < numareas; i++) {
4030 		if (trap_AAS_AreaReachability(areas[i])) {
4031 			break;
4032 		}
4033 	}
4034 	if (i < numareas) {
4035 		VectorCopy(origin, activategoal->goal.origin);
4036 		activategoal->goal.areanum = areas[i];
4037 		VectorSubtract(mins, origin, activategoal->goal.mins);
4038 		VectorSubtract(maxs, origin, activategoal->goal.maxs);
4039 		//
4040 		activategoal->goal.entitynum = entitynum;
4041 		activategoal->goal.number = 0;
4042 		activategoal->goal.flags = 0;
4043 		return qtrue;
4044 	}
4045 	return qfalse;
4046 }
4047 
4048 /*
4049 ==================
4050 BotPopFromActivateGoalStack
4051 ==================
4052 */
BotPopFromActivateGoalStack(bot_state_t * bs)4053 int BotPopFromActivateGoalStack(bot_state_t *bs) {
4054 	if (!bs->activatestack)
4055 		return qfalse;
4056 	BotEnableActivateGoalAreas(bs->activatestack, qtrue);
4057 	bs->activatestack->inuse = qfalse;
4058 	bs->activatestack->justused_time = FloatTime();
4059 	bs->activatestack = bs->activatestack->next;
4060 	return qtrue;
4061 }
4062 
4063 /*
4064 ==================
4065 BotPushOntoActivateGoalStack
4066 ==================
4067 */
BotPushOntoActivateGoalStack(bot_state_t * bs,bot_activategoal_t * activategoal)4068 int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) {
4069 	int i, best;
4070 	float besttime;
4071 
4072 	best = -1;
4073 	besttime = FloatTime() + 9999;
4074 	//
4075 	for (i = 0; i < MAX_ACTIVATESTACK; i++) {
4076 		if (!bs->activategoalheap[i].inuse) {
4077 			if (bs->activategoalheap[i].justused_time < besttime) {
4078 				besttime = bs->activategoalheap[i].justused_time;
4079 				best = i;
4080 			}
4081 		}
4082 	}
4083 	if (best != -1) {
4084 		memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t));
4085 		bs->activategoalheap[best].inuse = qtrue;
4086 		bs->activategoalheap[best].next = bs->activatestack;
4087 		bs->activatestack = &bs->activategoalheap[best];
4088 		return qtrue;
4089 	}
4090 	return qfalse;
4091 }
4092 
4093 /*
4094 ==================
4095 BotClearActivateGoalStack
4096 ==================
4097 */
BotClearActivateGoalStack(bot_state_t * bs)4098 void BotClearActivateGoalStack(bot_state_t *bs) {
4099 	while(bs->activatestack)
4100 		BotPopFromActivateGoalStack(bs);
4101 }
4102 
4103 /*
4104 ==================
4105 BotEnableActivateGoalAreas
4106 ==================
4107 */
BotEnableActivateGoalAreas(bot_activategoal_t * activategoal,int enable)4108 void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) {
4109 	int i;
4110 
4111 	if (activategoal->areasdisabled == !enable)
4112 		return;
4113 	for (i = 0; i < activategoal->numareas; i++)
4114 		trap_AAS_EnableRoutingArea( activategoal->areas[i], enable );
4115 	activategoal->areasdisabled = !enable;
4116 }
4117 
4118 /*
4119 ==================
4120 BotIsGoingToActivateEntity
4121 ==================
4122 */
BotIsGoingToActivateEntity(bot_state_t * bs,int entitynum)4123 int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) {
4124 	bot_activategoal_t *a;
4125 	int i;
4126 
4127 	for (a = bs->activatestack; a; a = a->next) {
4128 		if (a->time < FloatTime())
4129 			continue;
4130 		if (a->goal.entitynum == entitynum)
4131 			return qtrue;
4132 	}
4133 	for (i = 0; i < MAX_ACTIVATESTACK; i++) {
4134 		if (bs->activategoalheap[i].inuse)
4135 			continue;
4136 		//
4137 		if (bs->activategoalheap[i].goal.entitynum == entitynum) {
4138 			// if the bot went for this goal less than 2 seconds ago
4139 			if (bs->activategoalheap[i].justused_time > FloatTime() - 2)
4140 				return qtrue;
4141 		}
4142 	}
4143 	return qfalse;
4144 }
4145 
4146 /*
4147 ==================
4148 BotGetActivateGoal
4149 
4150   returns the number of the bsp entity to activate
4151   goal->entitynum will be set to the game entity to activate
4152 ==================
4153 */
4154 //#define OBSTACLEDEBUG
4155 
BotGetActivateGoal(bot_state_t * bs,int entitynum,bot_activategoal_t * activategoal)4156 int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) {
4157 	int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t;
4158 	char model[MAX_INFO_STRING], tmpmodel[128];
4159 	char target[128], classname[128];
4160 	float health;
4161 	char targetname[10][128];
4162 	aas_entityinfo_t entinfo;
4163 	aas_areainfo_t areainfo;
4164 	vec3_t origin, angles, absmins, absmaxs;
4165 
4166 	memset(activategoal, 0, sizeof(bot_activategoal_t));
4167 	BotEntityInfo(entitynum, &entinfo);
4168 	Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex);
4169 	for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
4170 		if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue;
4171 		if (!strcmp(model, tmpmodel)) break;
4172 	}
4173 	if (!ent) {
4174 		BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model);
4175 		return 0;
4176 	}
4177 	trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname));
4178 	if (!*classname) {
4179 		BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model);
4180 		return 0;
4181 	}
4182 	//if it is a door
4183 	if (!strcmp(classname, "func_door")) {
4184 		if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) {
4185 			//if the door has health then the door must be shot to open
4186 			if (health) {
4187 				BotFuncDoorActivateGoal(bs, ent, activategoal);
4188 				return ent;
4189 			}
4190 		}
4191 		//
4192 		trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags);
4193 		// if the door starts open then just wait for the door to return
4194 		if ( spawnflags & 1 )
4195 			return 0;
4196 		//get the door origin
4197 		if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) {
4198 			VectorClear(origin);
4199 		}
4200 		//if the door is open or opening already
4201 		if (!VectorCompare(origin, entinfo.origin))
4202 			return 0;
4203 		// store all the areas the door is in
4204 		trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model));
4205 		if (*model) {
4206 			modelindex = atoi(model+1);
4207 			if (modelindex) {
4208 				VectorClear(angles);
4209 				BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs);
4210 				//
4211 				numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2);
4212 				// store the areas with reachabilities first
4213 				for (i = 0; i < numareas; i++) {
4214 					if (activategoal->numareas >= MAX_ACTIVATEAREAS)
4215 						break;
4216 					if ( !trap_AAS_AreaReachability(areas[i]) ) {
4217 						continue;
4218 					}
4219 					trap_AAS_AreaInfo(areas[i], &areainfo);
4220 					if (areainfo.contents & AREACONTENTS_MOVER) {
4221 						activategoal->areas[activategoal->numareas++] = areas[i];
4222 					}
4223 				}
4224 				// store any remaining areas
4225 				for (i = 0; i < numareas; i++) {
4226 					if (activategoal->numareas >= MAX_ACTIVATEAREAS)
4227 						break;
4228 					if ( trap_AAS_AreaReachability(areas[i]) ) {
4229 						continue;
4230 					}
4231 					trap_AAS_AreaInfo(areas[i], &areainfo);
4232 					if (areainfo.contents & AREACONTENTS_MOVER) {
4233 						activategoal->areas[activategoal->numareas++] = areas[i];
4234 					}
4235 				}
4236 			}
4237 		}
4238 	}
4239 	// if the bot is blocked by or standing on top of a button
4240 	if (!strcmp(classname, "func_button")) {
4241 		return 0;
4242 	}
4243 	// get the targetname so we can find an entity with a matching target
4244 	if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) {
4245 		if (bot_developer.integer) {
4246 			BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model);
4247 		}
4248 		return 0;
4249 	}
4250 	// allow tree-like activation
4251 	cur_entities[0] = trap_AAS_NextBSPEntity(0);
4252 	for (i = 0; i >= 0 && i < 10;) {
4253 		for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) {
4254 			if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue;
4255 			if (!strcmp(targetname[i], target)) {
4256 				cur_entities[i] = trap_AAS_NextBSPEntity(ent);
4257 				break;
4258 			}
4259 		}
4260 		if (!ent) {
4261 			if (bot_developer.integer) {
4262 				BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]);
4263 			}
4264 			i--;
4265 			continue;
4266 		}
4267 		if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) {
4268 			if (bot_developer.integer) {
4269 				BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]);
4270 			}
4271 			continue;
4272 		}
4273 		// BSP button model
4274 		if (!strcmp(classname, "func_button")) {
4275 			//
4276 			if (!BotFuncButtonActivateGoal(bs, ent, activategoal))
4277 				continue;
4278 			// if the bot tries to activate this button already
4279 			if ( bs->activatestack && bs->activatestack->inuse &&
4280 				 bs->activatestack->goal.entitynum == activategoal->goal.entitynum &&
4281 				 bs->activatestack->time > FloatTime() &&
4282 				 bs->activatestack->start_time < FloatTime() - 2)
4283 				continue;
4284 			// if the bot is in a reachability area
4285 			if ( trap_AAS_AreaReachability(bs->areanum) ) {
4286 				// disable all areas the blocking entity is in
4287 				BotEnableActivateGoalAreas( activategoal, qfalse );
4288 				//
4289 				t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl);
4290 				// if the button is not reachable
4291 				if (!t) {
4292 					continue;
4293 				}
4294 				activategoal->time = FloatTime() + t * 0.01 + 5;
4295 			}
4296 			return ent;
4297 		}
4298 		// invisible trigger multiple box
4299 		else if (!strcmp(classname, "trigger_multiple")) {
4300 			//
4301 			if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal))
4302 				continue;
4303 			// if the bot tries to activate this trigger already
4304 			if ( bs->activatestack && bs->activatestack->inuse &&
4305 				 bs->activatestack->goal.entitynum == activategoal->goal.entitynum &&
4306 				 bs->activatestack->time > FloatTime() &&
4307 				 bs->activatestack->start_time < FloatTime() - 2)
4308 				continue;
4309 			// if the bot is in a reachability area
4310 			if ( trap_AAS_AreaReachability(bs->areanum) ) {
4311 				// disable all areas the blocking entity is in
4312 				BotEnableActivateGoalAreas( activategoal, qfalse );
4313 				//
4314 				t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl);
4315 				// if the trigger is not reachable
4316 				if (!t) {
4317 					continue;
4318 				}
4319 				activategoal->time = FloatTime() + t * 0.01 + 5;
4320 			}
4321 			return ent;
4322 		}
4323 		else if (!strcmp(classname, "func_timer")) {
4324 			// just skip the func_timer
4325 			continue;
4326 		}
4327 		// the actual button or trigger might be linked through a target_relay or target_delay
4328 		else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) {
4329 			if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) {
4330 				i++;
4331 				cur_entities[i] = trap_AAS_NextBSPEntity(0);
4332 			}
4333 		}
4334 	}
4335 #ifdef OBSTACLEDEBUG
4336 	BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]);
4337 #endif
4338 	return 0;
4339 }
4340 
4341 /*
4342 ==================
4343 BotGoForActivateGoal
4344 ==================
4345 */
BotGoForActivateGoal(bot_state_t * bs,bot_activategoal_t * activategoal)4346 int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) {
4347 	aas_entityinfo_t activateinfo;
4348 
4349 	activategoal->inuse = qtrue;
4350 	if (!activategoal->time)
4351 		activategoal->time = FloatTime() + 10;
4352 	activategoal->start_time = FloatTime();
4353 	BotEntityInfo(activategoal->goal.entitynum, &activateinfo);
4354 	VectorCopy(activateinfo.origin, activategoal->origin);
4355 	//
4356 	if (BotPushOntoActivateGoalStack(bs, activategoal)) {
4357 		// enter the activate entity AI node
4358 		AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal");
4359 		return qtrue;
4360 	}
4361 	else {
4362 		// enable any routing areas that were disabled
4363 		BotEnableActivateGoalAreas(activategoal, qtrue);
4364 		return qfalse;
4365 	}
4366 }
4367 
4368 /*
4369 ==================
4370 BotPrintActivateGoalInfo
4371 ==================
4372 */
BotPrintActivateGoalInfo(bot_state_t * bs,bot_activategoal_t * activategoal,int bspent)4373 void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) {
4374 	char netname[MAX_NETNAME];
4375 	char classname[128];
4376 	char buf[128];
4377 
4378 	ClientName(bs->client, netname, sizeof(netname));
4379 	trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname));
4380 	if (activategoal->shoot) {
4381 		Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n",
4382 						netname, classname,
4383 						activategoal->goal.origin[0],
4384 						activategoal->goal.origin[1],
4385 						activategoal->goal.origin[2],
4386 						activategoal->goal.areanum);
4387 	}
4388 	else {
4389 		Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n",
4390 						netname, classname,
4391 						activategoal->goal.origin[0],
4392 						activategoal->goal.origin[1],
4393 						activategoal->goal.origin[2],
4394 						activategoal->goal.areanum);
4395 	}
4396 	trap_EA_Say(bs->client, buf);
4397 }
4398 
4399 /*
4400 ==================
4401 BotRandomMove
4402 ==================
4403 */
BotRandomMove(bot_state_t * bs,bot_moveresult_t * moveresult)4404 void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) {
4405 	vec3_t dir, angles;
4406 
4407 	angles[0] = 0;
4408 	angles[1] = random() * 360;
4409 	angles[2] = 0;
4410 	AngleVectors(angles, dir, NULL, NULL);
4411 
4412 	trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK);
4413 
4414 	moveresult->failure = qfalse;
4415 	VectorCopy(dir, moveresult->movedir);
4416 }
4417 
4418 /*
4419 ==================
4420 BotAIBlocked
4421 
4422 Very basic handling of bots being blocked by other entities.
4423 Check what kind of entity is blocking the bot and try to activate
4424 it. If that's not an option then try to walk around or over the entity.
4425 Before the bot ends in this part of the AI it should predict which doors to
4426 open, which buttons to activate etc.
4427 ==================
4428 */
BotAIBlocked(bot_state_t * bs,bot_moveresult_t * moveresult,int activate)4429 void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) {
4430 	int movetype, bspent;
4431 	vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1};
4432 	aas_entityinfo_t entinfo;
4433 	bot_activategoal_t activategoal;
4434 
4435 	// if the bot is not blocked by anything
4436 	if (!moveresult->blocked) {
4437 		bs->notblocked_time = FloatTime();
4438 		return;
4439 	}
4440 	// if stuck in a solid area
4441 	if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) {
4442 		// move in a random direction in the hope to get out
4443 		BotRandomMove(bs, moveresult);
4444 		//
4445 		return;
4446 	}
4447 	// get info for the entity that is blocking the bot
4448 	BotEntityInfo(moveresult->blockentity, &entinfo);
4449 #ifdef OBSTACLEDEBUG
4450 	ClientName(bs->client, netname, sizeof(netname));
4451 	BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex);
4452 #endif // OBSTACLEDEBUG
4453 	// if blocked by a bsp model and the bot wants to activate it
4454 	if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) {
4455 		// find the bsp entity which should be activated in order to get the blocking entity out of the way
4456 		bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal);
4457 		if (bspent) {
4458 			//
4459 			if (bs->activatestack && !bs->activatestack->inuse)
4460 				bs->activatestack = NULL;
4461 			// if not already trying to activate this entity
4462 			if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) {
4463 				//
4464 				BotGoForActivateGoal(bs, &activategoal);
4465 			}
4466 			// if ontop of an obstacle or
4467 			// if the bot is not in a reachability area it'll still
4468 			// need some dynamic obstacle avoidance, otherwise return
4469 			if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) &&
4470 				trap_AAS_AreaReachability(bs->areanum))
4471 				return;
4472 		}
4473 		else {
4474 			// enable any routing areas that were disabled
4475 			BotEnableActivateGoalAreas(&activategoal, qtrue);
4476 		}
4477 	}
4478 	// just some basic dynamic obstacle avoidance code
4479 	hordir[0] = moveresult->movedir[0];
4480 	hordir[1] = moveresult->movedir[1];
4481 	hordir[2] = 0;
4482 	// if no direction just take a random direction
4483 	if (VectorNormalize(hordir) < 0.1) {
4484 		VectorSet(angles, 0, 360 * random(), 0);
4485 		AngleVectors(angles, hordir, NULL, NULL);
4486 	}
4487 	//
4488 	//if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP;
4489 	//else
4490 	movetype = MOVE_WALK;
4491 	// if there's an obstacle at the bot's feet and head then
4492 	// the bot might be able to crouch through
4493 	VectorCopy(bs->origin, start);
4494 	start[2] += 18;
4495 	VectorMA(start, 5, hordir, end);
4496 	VectorSet(mins, -16, -16, -24);
4497 	VectorSet(maxs, 16, 16, 4);
4498 	//
4499 	//bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID);
4500 	//if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH;
4501 	// get the sideward vector
4502 	CrossProduct(hordir, up, sideward);
4503 	//
4504 	if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward);
4505 	// try to crouch straight forward?
4506 	if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) {
4507 		// perform the movement
4508 		if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) {
4509 			// flip the avoid direction flag
4510 			bs->flags ^= BFL_AVOIDRIGHT;
4511 			// flip the direction
4512 			// VectorNegate(sideward, sideward);
4513 			VectorMA(sideward, -1, hordir, sideward);
4514 			// move in the other direction
4515 			trap_BotMoveInDirection(bs->ms, sideward, 400, movetype);
4516 		}
4517 	}
4518 	//
4519 	if (bs->notblocked_time < FloatTime() - 0.4) {
4520 		// just reset goals and hope the bot will go into another direction?
4521 		// is this still needed??
4522 		if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
4523 		else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
4524 	}
4525 }
4526 
4527 /*
4528 ==================
4529 BotAIPredictObstacles
4530 
4531 Predict the route towards the goal and check if the bot
4532 will be blocked by certain obstacles. When the bot has obstacles
4533 on it's path the bot should figure out if they can be removed
4534 by activating certain entities.
4535 ==================
4536 */
BotAIPredictObstacles(bot_state_t * bs,bot_goal_t * goal)4537 int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) {
4538 	int modelnum, entitynum, bspent;
4539 	bot_activategoal_t activategoal;
4540 	aas_predictroute_t route;
4541 
4542 	if (!bot_predictobstacles.integer)
4543 		return qfalse;
4544 
4545 	// always predict when the goal change or at regular intervals
4546 	if (bs->predictobstacles_goalareanum == goal->areanum &&
4547 		bs->predictobstacles_time > FloatTime() - 6) {
4548 		return qfalse;
4549 	}
4550 	bs->predictobstacles_goalareanum = goal->areanum;
4551 	bs->predictobstacles_time = FloatTime();
4552 
4553 	// predict at most 100 areas or 10 seconds ahead
4554 	trap_AAS_PredictRoute(&route, bs->areanum, bs->origin,
4555 							goal->areanum, bs->tfl, 100, 1000,
4556 							RSE_USETRAVELTYPE|RSE_ENTERCONTENTS,
4557 							AREACONTENTS_MOVER, TFL_BRIDGE, 0);
4558 	// if bot has to travel through an area with a mover
4559 	if (route.stopevent & RSE_ENTERCONTENTS) {
4560 		// if the bot will run into a mover
4561 		if (route.endcontents & AREACONTENTS_MOVER) {
4562 			//NOTE: this only works with bspc 2.1 or higher
4563 			modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT;
4564 			if (modelnum) {
4565 				//
4566 				entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL);
4567 				if (entitynum) {
4568 					//NOTE: BotGetActivateGoal already checks if the door is open or not
4569 					bspent = BotGetActivateGoal(bs, entitynum, &activategoal);
4570 					if (bspent) {
4571 						//
4572 						if (bs->activatestack && !bs->activatestack->inuse)
4573 							bs->activatestack = NULL;
4574 						// if not already trying to activate this entity
4575 						if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) {
4576 							//
4577 							//BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum);
4578 							//
4579 							BotGoForActivateGoal(bs, &activategoal);
4580 							return qtrue;
4581 						}
4582 						else {
4583 							// enable any routing areas that were disabled
4584 							BotEnableActivateGoalAreas(&activategoal, qtrue);
4585 						}
4586 					}
4587 				}
4588 			}
4589 		}
4590 	}
4591 	else if (route.stopevent & RSE_USETRAVELTYPE) {
4592 		if (route.endtravelflags & TFL_BRIDGE) {
4593 			//FIXME: check if the bridge is available to travel over
4594 		}
4595 	}
4596 	return qfalse;
4597 }
4598 
4599 /*
4600 ==================
4601 BotCheckConsoleMessages
4602 ==================
4603 */
BotCheckConsoleMessages(bot_state_t * bs)4604 void BotCheckConsoleMessages(bot_state_t *bs) {
4605 	char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr;
4606 	float chat_reply;
4607 	int context, handle;
4608 	bot_consolemessage_t m;
4609 	bot_match_t match;
4610 
4611 	//the name of this bot
4612 	ClientName(bs->client, botname, sizeof(botname));
4613 	//
4614 	while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) {
4615 		//if the chat state is flooded with messages the bot will read them quickly
4616 		if (trap_BotNumConsoleMessages(bs->cs) < 10) {
4617 			//if it is a chat message the bot needs some time to read it
4618 			if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break;
4619 		}
4620 		//
4621 		ptr = m.message;
4622 		//if it is a chat message then don't unify white spaces and don't
4623 		//replace synonyms in the netname
4624 		if (m.type == CMS_CHAT) {
4625 			//
4626 			if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
4627 				ptr = m.message + match.variables[MESSAGE].offset;
4628 			}
4629 		}
4630 		//unify the white spaces in the message
4631 		trap_UnifyWhiteSpaces(ptr);
4632 		//replace synonyms in the right context
4633 		context = BotSynonymContext(bs);
4634 		trap_BotReplaceSynonyms(ptr, context);
4635 		//if there's no match
4636 		if (!BotMatchMessage(bs, m.message)) {
4637 			//if it is a chat message
4638 			if (m.type == CMS_CHAT && !bot_nochat.integer) {
4639 				//
4640 				if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) {
4641 					trap_BotRemoveConsoleMessage(bs->cs, handle);
4642 					continue;
4643 				}
4644 				//don't use eliza chats with team messages
4645 				if (match.subtype & ST_TEAM) {
4646 					trap_BotRemoveConsoleMessage(bs->cs, handle);
4647 					continue;
4648 				}
4649 				//
4650 				trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname));
4651 				trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message));
4652 				//if this is a message from the bot self
4653 				if (bs->client == ClientFromName(netname)) {
4654 					trap_BotRemoveConsoleMessage(bs->cs, handle);
4655 					continue;
4656 				}
4657 				//unify the message
4658 				trap_UnifyWhiteSpaces(message);
4659 				//
4660 				trap_Cvar_Update(&bot_testrchat);
4661 				if (bot_testrchat.integer) {
4662 					//
4663 					trap_BotLibVarSet("bot_testrchat", "1");
4664 					//if bot replies with a chat message
4665 					if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
4666 															NULL, NULL,
4667 															NULL, NULL,
4668 															NULL, NULL,
4669 															botname, netname)) {
4670 						BotAI_Print(PRT_MESSAGE, "------------------------\n");
4671 					}
4672 					else {
4673 						BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n");
4674 					}
4675 				}
4676 				//if at a valid chat position and not chatting already and not in teamplay
4677 				else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) {
4678 					chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1);
4679 					if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) {
4680 						//if bot replies with a chat message
4681 						if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY,
4682 																NULL, NULL,
4683 																NULL, NULL,
4684 																NULL, NULL,
4685 																botname, netname)) {
4686 							//remove the console message
4687 							trap_BotRemoveConsoleMessage(bs->cs, handle);
4688 							bs->stand_time = FloatTime() + BotChatTime(bs);
4689 							AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat");
4690 							//EA_Say(bs->client, bs->cs.chatmessage);
4691 							break;
4692 						}
4693 					}
4694 				}
4695 			}
4696 		}
4697 		//remove the console message
4698 		trap_BotRemoveConsoleMessage(bs->cs, handle);
4699 	}
4700 }
4701 
4702 /*
4703 ==================
4704 BotCheckEvents
4705 ==================
4706 */
BotCheckForGrenades(bot_state_t * bs,entityState_t * state)4707 void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) {
4708 	// if this is not a grenade
4709 	if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER)
4710 		return;
4711 	// try to avoid the grenade
4712 	trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS);
4713 }
4714 
4715 #ifdef MISSIONPACK
4716 /*
4717 ==================
4718 BotCheckForProxMines
4719 ==================
4720 */
BotCheckForProxMines(bot_state_t * bs,entityState_t * state)4721 void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) {
4722 	// if this is not a prox mine
4723 	if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER)
4724 		return;
4725 	// if this prox mine is from someone on our own team
4726 	if (state->generic1 == BotTeam(bs))
4727 		return;
4728 	// if the bot doesn't have a weapon to deactivate the mine
4729 	if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) &&
4730 		!(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) &&
4731 		!(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) {
4732 		return;
4733 	}
4734 	// try to avoid the prox mine
4735 	trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS);
4736 	//
4737 	if (bs->numproxmines >= MAX_PROXMINES)
4738 		return;
4739 	bs->proxmines[bs->numproxmines] = state->number;
4740 	bs->numproxmines++;
4741 }
4742 
4743 /*
4744 ==================
4745 BotCheckForKamikazeBody
4746 ==================
4747 */
BotCheckForKamikazeBody(bot_state_t * bs,entityState_t * state)4748 void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) {
4749 	// if this entity is not wearing the kamikaze
4750 	if (!(state->eFlags & EF_KAMIKAZE))
4751 		return;
4752 	// if this entity isn't dead
4753 	if (!(state->eFlags & EF_DEAD))
4754 		return;
4755 	//remember this kamikaze body
4756 	bs->kamikazebody = state->number;
4757 }
4758 #endif
4759 
4760 /*
4761 ==================
4762 BotCheckEvents
4763 ==================
4764 */
BotCheckEvents(bot_state_t * bs,entityState_t * state)4765 void BotCheckEvents(bot_state_t *bs, entityState_t *state) {
4766 	int event;
4767 	char buf[128];
4768 #ifdef MISSIONPACK
4769 	aas_entityinfo_t entinfo;
4770 #endif
4771 
4772 	//NOTE: this sucks, we're accessing the gentity_t directly
4773 	//but there's no other fast way to do it right now
4774 	if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) {
4775 		return;
4776 	}
4777 	bs->entityeventTime[state->number] = g_entities[state->number].eventTime;
4778 	//if it's an event only entity
4779 	if (state->eType > ET_EVENTS) {
4780 		event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS;
4781 	}
4782 	else {
4783 		event = state->event & ~EV_EVENT_BITS;
4784 	}
4785 	//
4786 	switch(event) {
4787 		//client obituary event
4788 		case EV_OBITUARY:
4789 		{
4790 			int target, attacker, mod;
4791 
4792 			target = state->otherEntityNum;
4793 			attacker = state->otherEntityNum2;
4794 			mod = state->eventParm;
4795 			//
4796 			if (target == bs->client) {
4797 				bs->botdeathtype = mod;
4798 				bs->lastkilledby = attacker;
4799 				//
4800 				if (target == attacker ||
4801 					target == ENTITYNUM_NONE ||
4802 					target == ENTITYNUM_WORLD) bs->botsuicide = qtrue;
4803 				else bs->botsuicide = qfalse;
4804 				//
4805 				bs->num_deaths++;
4806 			}
4807 			//else if this client was killed by the bot
4808 			else if (attacker == bs->client) {
4809 				bs->enemydeathtype = mod;
4810 				bs->lastkilledplayer = target;
4811 				bs->killedenemy_time = FloatTime();
4812 				//
4813 				bs->num_kills++;
4814 			}
4815 			else if (attacker == bs->enemy && target == attacker) {
4816 				bs->enemysuicide = qtrue;
4817 			}
4818 			//
4819 #ifdef MISSIONPACK
4820 			if (gametype == GT_1FCTF) {
4821 				//
4822 				BotEntityInfo(target, &entinfo);
4823 				if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) {
4824 					if (!BotSameTeam(bs, target)) {
4825 						bs->neutralflagstatus = 3;	//enemy dropped the flag
4826 						bs->flagstatuschanged = qtrue;
4827 					}
4828 				}
4829 			}
4830 #endif
4831 			break;
4832 		}
4833 		case EV_GLOBAL_SOUND:
4834 		{
4835 			if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
4836 				BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
4837 				break;
4838 			}
4839 			trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
4840 			/*
4841 			if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) {
4842 				//red flag is returned
4843 				bs->redflagstatus = 0;
4844 				bs->flagstatuschanged = qtrue;
4845 			}
4846 			else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) {
4847 				//blue flag is returned
4848 				bs->blueflagstatus = 0;
4849 				bs->flagstatuschanged = qtrue;
4850 			}
4851 			else*/
4852 #ifdef MISSIONPACK
4853 			if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) {
4854 				//the kamikaze respawned so dont avoid it
4855 				BotDontAvoid(bs, "Kamikaze");
4856 			}
4857 			else
4858 #endif
4859 				if (!strcmp(buf, "sound/items/poweruprespawn.wav")) {
4860 				//powerup respawned... go get it
4861 				BotGoForPowerups(bs);
4862 			}
4863 			break;
4864 		}
4865 		case EV_GLOBAL_TEAM_SOUND:
4866 		{
4867 			if (gametype == GT_CTF) {
4868 				switch(state->eventParm) {
4869 					case GTS_RED_CAPTURE:
4870 						bs->blueflagstatus = 0;
4871 						bs->redflagstatus = 0;
4872 						bs->flagstatuschanged = qtrue;
4873 						break; //see BotMatch_CTF
4874 					case GTS_BLUE_CAPTURE:
4875 						bs->blueflagstatus = 0;
4876 						bs->redflagstatus = 0;
4877 						bs->flagstatuschanged = qtrue;
4878 						break; //see BotMatch_CTF
4879 					case GTS_RED_RETURN:
4880 						//blue flag is returned
4881 						bs->blueflagstatus = 0;
4882 						bs->flagstatuschanged = qtrue;
4883 						break;
4884 					case GTS_BLUE_RETURN:
4885 						//red flag is returned
4886 						bs->redflagstatus = 0;
4887 						bs->flagstatuschanged = qtrue;
4888 						break;
4889 					case GTS_RED_TAKEN:
4890 						//blue flag is taken
4891 						bs->blueflagstatus = 1;
4892 						bs->flagstatuschanged = qtrue;
4893 						break; //see BotMatch_CTF
4894 					case GTS_BLUE_TAKEN:
4895 						//red flag is taken
4896 						bs->redflagstatus = 1;
4897 						bs->flagstatuschanged = qtrue;
4898 						break; //see BotMatch_CTF
4899 				}
4900 			}
4901 #ifdef MISSIONPACK
4902 			else if (gametype == GT_1FCTF) {
4903 				switch(state->eventParm) {
4904 					case GTS_RED_CAPTURE:
4905 						bs->neutralflagstatus = 0;
4906 						bs->flagstatuschanged = qtrue;
4907 						break;
4908 					case GTS_BLUE_CAPTURE:
4909 						bs->neutralflagstatus = 0;
4910 						bs->flagstatuschanged = qtrue;
4911 						break;
4912 					case GTS_RED_RETURN:
4913 						//flag has returned
4914 						bs->neutralflagstatus = 0;
4915 						bs->flagstatuschanged = qtrue;
4916 						break;
4917 					case GTS_BLUE_RETURN:
4918 						//flag has returned
4919 						bs->neutralflagstatus = 0;
4920 						bs->flagstatuschanged = qtrue;
4921 						break;
4922 					case GTS_RED_TAKEN:
4923 						bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c
4924 						bs->flagstatuschanged = qtrue;
4925 						break;
4926 					case GTS_BLUE_TAKEN:
4927 						bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c
4928 						bs->flagstatuschanged = qtrue;
4929 						break;
4930 				}
4931 			}
4932 #endif
4933 			break;
4934 		}
4935 		case EV_PLAYER_TELEPORT_IN:
4936 		{
4937 			VectorCopy(state->origin, lastteleport_origin);
4938 			lastteleport_time = FloatTime();
4939 			break;
4940 		}
4941 		case EV_GENERAL_SOUND:
4942 		{
4943 			//if this sound is played on the bot
4944 			if (state->number == bs->client) {
4945 				if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) {
4946 					BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm);
4947 					break;
4948 				}
4949 				//check out the sound
4950 				trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf));
4951 				//if falling into a death pit
4952 				if (!strcmp(buf, "*falling1.wav")) {
4953 					//if the bot has a personal teleporter
4954 					if (bs->inventory[INVENTORY_TELEPORTER] > 0) {
4955 						//use the holdable item
4956 						trap_EA_Use(bs->client);
4957 					}
4958 				}
4959 			}
4960 			break;
4961 		}
4962 		case EV_FOOTSTEP:
4963 		case EV_FOOTSTEP_METAL:
4964 		case EV_FOOTSPLASH:
4965 		case EV_FOOTWADE:
4966 		case EV_SWIM:
4967 		case EV_FALL_SHORT:
4968 		case EV_FALL_MEDIUM:
4969 		case EV_FALL_FAR:
4970 		case EV_STEP_4:
4971 		case EV_STEP_8:
4972 		case EV_STEP_12:
4973 		case EV_STEP_16:
4974 		case EV_JUMP_PAD:
4975 		case EV_JUMP:
4976 		case EV_TAUNT:
4977 		case EV_WATER_TOUCH:
4978 		case EV_WATER_LEAVE:
4979 		case EV_WATER_UNDER:
4980 		case EV_WATER_CLEAR:
4981 		case EV_ITEM_PICKUP:
4982 		case EV_GLOBAL_ITEM_PICKUP:
4983 		case EV_NOAMMO:
4984 		case EV_CHANGE_WEAPON:
4985 		case EV_FIRE_WEAPON:
4986 			//FIXME: either add to sound queue or mark player as someone making noise
4987 			break;
4988 		case EV_USE_ITEM0:
4989 		case EV_USE_ITEM1:
4990 		case EV_USE_ITEM2:
4991 		case EV_USE_ITEM3:
4992 		case EV_USE_ITEM4:
4993 		case EV_USE_ITEM5:
4994 		case EV_USE_ITEM6:
4995 		case EV_USE_ITEM7:
4996 		case EV_USE_ITEM8:
4997 		case EV_USE_ITEM9:
4998 		case EV_USE_ITEM10:
4999 		case EV_USE_ITEM11:
5000 		case EV_USE_ITEM12:
5001 		case EV_USE_ITEM13:
5002 		case EV_USE_ITEM14:
5003 			break;
5004 	}
5005 }
5006 
5007 /*
5008 ==================
5009 BotCheckSnapshot
5010 ==================
5011 */
BotCheckSnapshot(bot_state_t * bs)5012 void BotCheckSnapshot(bot_state_t *bs) {
5013 	int ent;
5014 	entityState_t state;
5015 
5016 	//remove all avoid spots
5017 	trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR);
5018 	//reset kamikaze body
5019 	bs->kamikazebody = 0;
5020 	//reset number of proxmines
5021 	bs->numproxmines = 0;
5022 	//
5023 	ent = 0;
5024 	while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) {
5025 		//check the entity state for events
5026 		BotCheckEvents(bs, &state);
5027 		//check for grenades the bot should avoid
5028 		BotCheckForGrenades(bs, &state);
5029 		//
5030 #ifdef MISSIONPACK
5031 		//check for proximity mines which the bot should deactivate
5032 		BotCheckForProxMines(bs, &state);
5033 		//check for dead bodies with the kamikaze effect which should be gibbed
5034 		BotCheckForKamikazeBody(bs, &state);
5035 #endif
5036 	}
5037 	//check the player state for events
5038 	BotAI_GetEntityState(bs->client, &state);
5039 	//copy the player state events to the entity state
5040 	state.event = bs->cur_ps.externalEvent;
5041 	state.eventParm = bs->cur_ps.externalEventParm;
5042 	//
5043 	BotCheckEvents(bs, &state);
5044 }
5045 
5046 /*
5047 ==================
5048 BotCheckAir
5049 ==================
5050 */
BotCheckAir(bot_state_t * bs)5051 void BotCheckAir(bot_state_t *bs) {
5052 	if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) {
5053 		if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
5054 			return;
5055 		}
5056 	}
5057 	bs->lastair_time = FloatTime();
5058 }
5059 
5060 /*
5061 ==================
5062 BotAlternateRoute
5063 ==================
5064 */
BotAlternateRoute(bot_state_t * bs,bot_goal_t * goal)5065 bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) {
5066 	int t;
5067 
5068 	// if the bot has an alternative route goal
5069 	if (bs->altroutegoal.areanum) {
5070 		//
5071 		if (bs->reachedaltroutegoal_time)
5072 			return goal;
5073 		// travel time towards alternative route goal
5074 		t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl);
5075 		if (t && t < 20) {
5076 			//BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n");
5077 			bs->reachedaltroutegoal_time = FloatTime();
5078 		}
5079 		memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t));
5080 		return &bs->altroutegoal;
5081 	}
5082 	return goal;
5083 }
5084 
5085 /*
5086 ==================
5087 BotGetAlternateRouteGoal
5088 ==================
5089 */
BotGetAlternateRouteGoal(bot_state_t * bs,int base)5090 int BotGetAlternateRouteGoal(bot_state_t *bs, int base) {
5091 	aas_altroutegoal_t *altroutegoals;
5092 	bot_goal_t *goal;
5093 	int numaltroutegoals, rnd;
5094 
5095 	if (base == TEAM_RED) {
5096 		altroutegoals = red_altroutegoals;
5097 		numaltroutegoals = red_numaltroutegoals;
5098 	}
5099 	else {
5100 		altroutegoals = blue_altroutegoals;
5101 		numaltroutegoals = blue_numaltroutegoals;
5102 	}
5103 	if (!numaltroutegoals)
5104 		return qfalse;
5105 	rnd = (float) random() * numaltroutegoals;
5106 	if (rnd >= numaltroutegoals)
5107 		rnd = numaltroutegoals-1;
5108 	goal = &bs->altroutegoal;
5109 	goal->areanum = altroutegoals[rnd].areanum;
5110 	VectorCopy(altroutegoals[rnd].origin, goal->origin);
5111 	VectorSet(goal->mins, -8, -8, -8);
5112 	VectorSet(goal->maxs, 8, 8, 8);
5113 	goal->entitynum = 0;
5114 	goal->iteminfo = 0;
5115 	goal->number = 0;
5116 	goal->flags = 0;
5117 	//
5118 	bs->reachedaltroutegoal_time = 0;
5119 	return qtrue;
5120 }
5121 
5122 /*
5123 ==================
5124 BotSetupAlternateRouteGoals
5125 ==================
5126 */
BotSetupAlternativeRouteGoals(void)5127 void BotSetupAlternativeRouteGoals(void) {
5128 
5129 	if (altroutegoals_setup)
5130 		return;
5131 #ifdef MISSIONPACK
5132 	if (gametype == GT_CTF) {
5133 		if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0)
5134 			BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n");
5135 		if (ctf_neutralflag.areanum) {
5136 			//
5137 			red_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
5138 										ctf_neutralflag.origin, ctf_neutralflag.areanum,
5139 										ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT,
5140 										red_altroutegoals, MAX_ALTROUTEGOALS,
5141 										ALTROUTEGOAL_CLUSTERPORTALS|
5142 										ALTROUTEGOAL_VIEWPORTALS);
5143 			blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
5144 										ctf_neutralflag.origin, ctf_neutralflag.areanum,
5145 										ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT,
5146 										blue_altroutegoals, MAX_ALTROUTEGOALS,
5147 										ALTROUTEGOAL_CLUSTERPORTALS|
5148 										ALTROUTEGOAL_VIEWPORTALS);
5149 		}
5150 	}
5151 	else if (gametype == GT_1FCTF) {
5152 		//
5153 		red_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
5154 									ctf_neutralflag.origin, ctf_neutralflag.areanum,
5155 									ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT,
5156 									red_altroutegoals, MAX_ALTROUTEGOALS,
5157 									ALTROUTEGOAL_CLUSTERPORTALS|
5158 									ALTROUTEGOAL_VIEWPORTALS);
5159 		blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
5160 									ctf_neutralflag.origin, ctf_neutralflag.areanum,
5161 									ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT,
5162 									blue_altroutegoals, MAX_ALTROUTEGOALS,
5163 									ALTROUTEGOAL_CLUSTERPORTALS|
5164 									ALTROUTEGOAL_VIEWPORTALS);
5165 	}
5166 	else if (gametype == GT_OBELISK) {
5167 		if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0)
5168 			BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n");
5169 		//
5170 		red_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
5171 									neutralobelisk.origin, neutralobelisk.areanum,
5172 									redobelisk.origin, redobelisk.areanum, TFL_DEFAULT,
5173 									red_altroutegoals, MAX_ALTROUTEGOALS,
5174 									ALTROUTEGOAL_CLUSTERPORTALS|
5175 									ALTROUTEGOAL_VIEWPORTALS);
5176 		blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
5177 									neutralobelisk.origin, neutralobelisk.areanum,
5178 									blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT,
5179 									blue_altroutegoals, MAX_ALTROUTEGOALS,
5180 									ALTROUTEGOAL_CLUSTERPORTALS|
5181 									ALTROUTEGOAL_VIEWPORTALS);
5182 	}
5183 	else if (gametype == GT_HARVESTER) {
5184 		//
5185 		red_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
5186 									neutralobelisk.origin, neutralobelisk.areanum,
5187 									redobelisk.origin, redobelisk.areanum, TFL_DEFAULT,
5188 									red_altroutegoals, MAX_ALTROUTEGOALS,
5189 									ALTROUTEGOAL_CLUSTERPORTALS|
5190 									ALTROUTEGOAL_VIEWPORTALS);
5191 		blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals(
5192 									neutralobelisk.origin, neutralobelisk.areanum,
5193 									blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT,
5194 									blue_altroutegoals, MAX_ALTROUTEGOALS,
5195 									ALTROUTEGOAL_CLUSTERPORTALS|
5196 									ALTROUTEGOAL_VIEWPORTALS);
5197 	}
5198 #endif
5199 	altroutegoals_setup = qtrue;
5200 }
5201 
5202 /*
5203 ==================
5204 BotDeathmatchAI
5205 ==================
5206 */
BotDeathmatchAI(bot_state_t * bs,float thinktime)5207 void BotDeathmatchAI(bot_state_t *bs, float thinktime) {
5208 	char gender[144], name[144], buf[144];
5209 	char userinfo[MAX_INFO_STRING];
5210 	int i;
5211 
5212 	//if the bot has just been setup
5213 	if (bs->setupcount > 0) {
5214 		bs->setupcount--;
5215 		if (bs->setupcount > 0) return;
5216 		//get the gender characteristic
5217 		trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender));
5218 		//set the bot gender
5219 		trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo));
5220 		Info_SetValueForKey(userinfo, "sex", gender);
5221 		trap_SetUserinfo(bs->client, userinfo);
5222 		//set the team
5223 		if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) {
5224 			Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team);
5225 			trap_EA_Command(bs->client, buf);
5226 		}
5227 		//set the chat gender
5228 		if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE);
5229 		else if (gender[0] == 'f')  trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE);
5230 		else  trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS);
5231 		//set the chat name
5232 		ClientName(bs->client, name, sizeof(name));
5233 		trap_BotSetChatName(bs->cs, name, bs->client);
5234 		//
5235 		bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
5236 		bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
5237 		//
5238 		bs->setupcount = 0;
5239 		//
5240 		BotSetupAlternativeRouteGoals();
5241 	}
5242 	//no ideal view set
5243 	bs->flags &= ~BFL_IDEALVIEWSET;
5244 	//
5245 	if (!BotIntermission(bs)) {
5246 		//set the teleport time
5247 		BotSetTeleportTime(bs);
5248 		//update some inventory values
5249 		BotUpdateInventory(bs);
5250 		//check out the snapshot
5251 		BotCheckSnapshot(bs);
5252 		//check for air
5253 		BotCheckAir(bs);
5254 	}
5255 	//check the console messages
5256 	BotCheckConsoleMessages(bs);
5257 	//if not in the intermission and not in observer mode
5258 	if (!BotIntermission(bs) && !BotIsObserver(bs)) {
5259 		//do team AI
5260 		BotTeamAI(bs);
5261 	}
5262 	//if the bot has no ai node
5263 	if (!bs->ainode) {
5264 		AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node");
5265 	}
5266 	//if the bot entered the game less than 8 seconds ago
5267 	if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) {
5268 		if (BotChat_EnterGame(bs)) {
5269 			bs->stand_time = FloatTime() + BotChatTime(bs);
5270 			AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game");
5271 		}
5272 		bs->entergamechat = qtrue;
5273 	}
5274 	//reset the node switches from the previous frame
5275 	BotResetNodeSwitches();
5276 	//execute AI nodes
5277 	for (i = 0; i < MAX_NODESWITCHES; i++) {
5278 		if (bs->ainode(bs)) break;
5279 	}
5280 	//if the bot removed itself :)
5281 	if (!bs->inuse) return;
5282 	//if the bot executed too many AI nodes
5283 	if (i >= MAX_NODESWITCHES) {
5284 		trap_BotDumpGoalStack(bs->gs);
5285 		trap_BotDumpAvoidGoals(bs->gs);
5286 		BotDumpNodeSwitches(bs);
5287 		ClientName(bs->client, name, sizeof(name));
5288 		BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES);
5289 	}
5290 	//
5291 	bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
5292 	bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
5293 }
5294 
5295 /*
5296 ==================
5297 BotSetEntityNumForGoalWithModel
5298 ==================
5299 */
BotSetEntityNumForGoalWithModel(bot_goal_t * goal,int eType,char * modelname)5300 void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) {
5301 	gentity_t *ent;
5302 	int i, modelindex;
5303 	vec3_t dir;
5304 
5305 	modelindex = G_ModelIndex( modelname );
5306 	ent = &g_entities[0];
5307 	for (i = 0; i < level.num_entities; i++, ent++) {
5308 		if ( !ent->inuse ) {
5309 			continue;
5310 		}
5311 		if ( eType && ent->s.eType != eType) {
5312 			continue;
5313 		}
5314 		if (ent->s.modelindex != modelindex) {
5315 			continue;
5316 		}
5317 		VectorSubtract(goal->origin, ent->s.origin, dir);
5318 		if (VectorLengthSquared(dir) < Square(10)) {
5319 			goal->entitynum = i;
5320 			return;
5321 		}
5322 	}
5323 }
5324 
5325 /*
5326 ==================
5327 BotSetEntityNumForGoal
5328 ==================
5329 */
BotSetEntityNumForGoal(bot_goal_t * goal,char * classname)5330 void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) {
5331 	gentity_t *ent;
5332 	int i;
5333 	vec3_t dir;
5334 
5335 	ent = &g_entities[0];
5336 	for (i = 0; i < level.num_entities; i++, ent++) {
5337 		if ( !ent->inuse ) {
5338 			continue;
5339 		}
5340 		if ( !Q_stricmp(ent->classname, classname) ) {
5341 			continue;
5342 		}
5343 		VectorSubtract(goal->origin, ent->s.origin, dir);
5344 		if (VectorLengthSquared(dir) < Square(10)) {
5345 			goal->entitynum = i;
5346 			return;
5347 		}
5348 	}
5349 }
5350 
5351 /*
5352 ==================
5353 BotGoalForBSPEntity
5354 ==================
5355 */
BotGoalForBSPEntity(char * classname,bot_goal_t * goal)5356 int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) {
5357 	char value[MAX_INFO_STRING];
5358 	vec3_t origin, start, end;
5359 	int ent, numareas, areas[10];
5360 
5361 	memset(goal, 0, sizeof(bot_goal_t));
5362 	for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
5363 		if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value)))
5364 			continue;
5365 		if (!strcmp(value, classname)) {
5366 			if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin))
5367 				return qfalse;
5368 			VectorCopy(origin, goal->origin);
5369 			VectorCopy(origin, start);
5370 			start[2] -= 32;
5371 			VectorCopy(origin, end);
5372 			end[2] += 32;
5373 			numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10);
5374 			if (!numareas)
5375 				return qfalse;
5376 			goal->areanum = areas[0];
5377 			return qtrue;
5378 		}
5379 	}
5380 	return qfalse;
5381 }
5382 
5383 /*
5384 ==================
5385 BotSetupDeathmatchAI
5386 ==================
5387 */
BotSetupDeathmatchAI(void)5388 void BotSetupDeathmatchAI(void) {
5389 	int ent, modelnum;
5390 	char model[128];
5391 
5392 	gametype = trap_Cvar_VariableIntegerValue("g_gametype");
5393 	maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients");
5394 
5395 	trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0);
5396 	trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0);
5397 	trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0);
5398 	trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0);
5399 	trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0);
5400 	trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0);
5401 	trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0);
5402 	trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0);
5403 	//
5404 	if (gametype == GT_CTF) {
5405 		if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0)
5406 			BotAI_Print(PRT_WARNING, "CTF without Red Flag\n");
5407 		if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0)
5408 			BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n");
5409 	}
5410 #ifdef MISSIONPACK
5411 	else if (gametype == GT_1FCTF) {
5412 		if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0)
5413 			BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n");
5414 		if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0)
5415 			BotAI_Print(PRT_WARNING, "CTF without Red Flag\n");
5416 		if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0)
5417 			BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n");
5418 	}
5419 	else if (gametype == GT_OBELISK) {
5420 		if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0)
5421 			BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n");
5422 		BotSetEntityNumForGoal(&redobelisk, "team_redobelisk");
5423 		if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0)
5424 			BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n");
5425 		BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk");
5426 	}
5427 	else if (gametype == GT_HARVESTER) {
5428 		if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0)
5429 			BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n");
5430 		BotSetEntityNumForGoal(&redobelisk, "team_redobelisk");
5431 		if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0)
5432 			BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n");
5433 		BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk");
5434 		if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0)
5435 			BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n");
5436 		BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk");
5437 	}
5438 #endif
5439 
5440 	max_bspmodelindex = 0;
5441 	for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) {
5442 		if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue;
5443 		if (model[0] == '*') {
5444 			modelnum = atoi(model+1);
5445 			if (modelnum > max_bspmodelindex)
5446 				max_bspmodelindex = modelnum;
5447 		}
5448 	}
5449 	//initialize the waypoint heap
5450 	BotInitWaypoints();
5451 }
5452 
5453 /*
5454 ==================
5455 BotShutdownDeathmatchAI
5456 ==================
5457 */
BotShutdownDeathmatchAI(void)5458 void BotShutdownDeathmatchAI(void) {
5459 	altroutegoals_setup = qfalse;
5460 }
5461