1 /*
2 ===========================================================================
3 
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (“RTCW SP Source Code”).
8 
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 
30 /*****************************************************************************
31  * name:		ai_dmq3.c
32  *
33  * desc:		Quake3 bot AI
34  *
35  *
36  *****************************************************************************/
37 
38 
39 #include "g_local.h"
40 #include "../botlib/botlib.h"
41 #include "../botlib/be_aas.h"
42 #include "../botlib/be_ea.h"
43 #include "../botlib/be_ai_char.h"
44 #include "../botlib/be_ai_chat.h"
45 #include "../botlib/be_ai_gen.h"
46 #include "../botlib/be_ai_goal.h"
47 #include "../botlib/be_ai_move.h"
48 #include "../botlib/be_ai_weap.h"
49 #include "../botlib/botai.h"
50 //
51 #include "ai_main.h"
52 #include "ai_dmq3.h"
53 #include "ai_chat.h"
54 #include "ai_cmd.h"
55 #include "ai_dmnet.h"
56 #include "ai_team.h"
57 //
58 #include "chars.h"               //characteristics
59 #include "inv.h"             //indexes into the inventory
60 #include "syn.h"             //synonyms
61 #include "match.h"               //string matching types and vars
62 
63 #define IDEAL_ATTACKDIST            140
64 #define WEAPONINDEX_MACHINEGUN  2
65 
66 #define MAX_WAYPOINTS           128
67 
68 //////////////////
69 // from aasfile.h
70 #define AREACONTENTS_MOVER              1024
71 #define AREACONTENTS_MODELNUMSHIFT      24
72 #define AREACONTENTS_MAXMODELNUM        0xFF
73 #define AREACONTENTS_MODELNUM           ( AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT )
74 //////////////////
75 //
76 bot_waypoint_t botai_waypoints[MAX_WAYPOINTS];
77 bot_waypoint_t  *botai_freewaypoints;
78 
79 //NOTE: not using a cvar which can be updated because the game should be reloaded anyway
80 int gametype;       //game type
81 
82 // Rafael gameskill
83 int gameskill;
84 
85 vmCvar_t bot_grapple;
86 vmCvar_t bot_rocketjump;
87 vmCvar_t bot_fastchat;
88 vmCvar_t bot_nochat;
89 vmCvar_t bot_testrchat;
90 
91 vec3_t lastteleport_origin;
92 float lastteleport_time;
93 //true when the map changed
94 int max_bspmodelindex;  //maximum BSP model index
95 
96 //CTF flag goals
97 bot_goal_t ctf_redflag;
98 bot_goal_t ctf_blueflag;
99 
100 #ifdef CTF
101 /*
102 ==================
103 BotCTFCarryingFlag
104 ==================
105 */
BotCTFCarryingFlag(bot_state_t * bs)106 int BotCTFCarryingFlag( bot_state_t *bs ) {
107 	if ( gametype != GT_CTF ) {
108 		return CTF_FLAG_NONE;
109 	}
110 
111 	if ( bs->inventory[INVENTORY_REDFLAG] > 0 ) {
112 		return CTF_FLAG_RED;
113 	} else if ( bs->inventory[INVENTORY_BLUEFLAG] > 0 ) {
114 		return CTF_FLAG_BLUE;
115 	}
116 	return CTF_FLAG_NONE;
117 }
118 
119 /*
120 ==================
121 BotCTFTeam
122 ==================
123 */
BotCTFTeam(bot_state_t * bs)124 int BotCTFTeam( bot_state_t *bs ) {
125 	char skin[128], *p;
126 
127 	if ( gametype != GT_CTF ) {
128 		return CTF_TEAM_NONE;
129 	}
130 	ClientSkin( bs->client, skin, sizeof( skin ) );
131 	p = strchr( skin, '/' );
132 	if ( !p ) {
133 		p = skin;
134 	} else { p++;}
135 	if ( Q_stricmp( p, CTF_SKIN_REDTEAM ) == 0 ) {
136 		return CTF_TEAM_RED;
137 	}
138 	if ( Q_stricmp( p, CTF_SKIN_BLUETEAM ) == 0 ) {
139 		return CTF_TEAM_BLUE;
140 	}
141 	return CTF_TEAM_NONE;
142 }
143 
144 /*
145 ==================
146 BotCTFRetreatGoals
147 ==================
148 */
BotCTFRetreatGoals(bot_state_t * bs)149 void BotCTFRetreatGoals( bot_state_t *bs ) {
150 	//when carrying a flag in ctf the bot should rush to the base
151 	if ( BotCTFCarryingFlag( bs ) ) {
152 		//if not already rushing to the base
153 		if ( bs->ltgtype != LTG_RUSHBASE ) {
154 			bs->ltgtype = LTG_RUSHBASE;
155 			bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
156 			bs->rushbaseaway_time = 0;
157 		}
158 	}
159 }
160 
161 /*
162 ==================
163 BotCTFSeekGoals
164 ==================
165 */
BotCTFSeekGoals(bot_state_t * bs)166 void BotCTFSeekGoals( bot_state_t *bs ) {
167 	float rnd;
168 
169 	//when carrying a flag in ctf the bot should rush to the base
170 	if ( BotCTFCarryingFlag( bs ) ) {
171 		//if not already rushing to the base
172 		if ( bs->ltgtype != LTG_RUSHBASE ) {
173 			bs->ltgtype = LTG_RUSHBASE;
174 			bs->teamgoal_time = trap_AAS_Time() + CTF_RUSHBASE_TIME;
175 			bs->rushbaseaway_time = 0;
176 		}
177 		return;
178 	}
179 	//if the bot is roaming
180 	if ( bs->ctfroam_time > trap_AAS_Time() ) {
181 		return;
182 	}
183 	//if already a CTF or team goal
184 	if ( bs->ltgtype == LTG_TEAMHELP ||
185 		 bs->ltgtype == LTG_TEAMACCOMPANY ||
186 		 bs->ltgtype == LTG_DEFENDKEYAREA ||
187 		 bs->ltgtype == LTG_GETFLAG ||
188 		 bs->ltgtype == LTG_RUSHBASE ||
189 		 bs->ltgtype == LTG_CAMPORDER ||
190 		 bs->ltgtype == LTG_PATROL ) {
191 		return;
192 	}
193 	//if the bot has enough aggression to decide what to do
194 	if ( BotAggression( bs ) < 50 ) {
195 		return;
196 	}
197 	//set the time to send a message to the team mates
198 	bs->teammessage_time = trap_AAS_Time() + 2 * random();
199 	//get the flag or defend the base
200 	rnd = random();
201 	if ( rnd < 0.33 && ctf_redflag.areanum && ctf_blueflag.areanum ) {
202 		bs->ltgtype = LTG_GETFLAG;
203 		//set the time the bot will stop getting the flag
204 		bs->teamgoal_time = trap_AAS_Time() + CTF_GETFLAG_TIME;
205 	} else if ( rnd < 0.66 && ctf_redflag.areanum && ctf_blueflag.areanum )     {
206 		//FIXME: do not always use the base flag
207 		if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) {
208 			memcpy( &bs->teamgoal, &ctf_redflag, sizeof( bot_goal_t ) );
209 		} else { memcpy( &bs->teamgoal, &ctf_blueflag, sizeof( bot_goal_t ) );}
210 		//set the ltg type
211 		bs->ltgtype = LTG_DEFENDKEYAREA;
212 		//set the time the bot stop defending the base
213 		bs->teamgoal_time = trap_AAS_Time() + TEAM_DEFENDKEYAREA_TIME;
214 		bs->defendaway_time = 0;
215 	} else {
216 		bs->ltgtype = 0;
217 		//set the time the bot will stop roaming
218 		bs->ctfroam_time = trap_AAS_Time() + CTF_ROAM_TIME;
219 	}
220 #ifdef DEBUG
221 	BotPrintTeamGoal( bs );
222 #endif //DEBUG
223 }
224 
225 #endif //CTF
226 
227 /*
228 ==================
229 BotPointAreaNum
230 ==================
231 */
BotPointAreaNum(vec3_t origin)232 int BotPointAreaNum( vec3_t origin ) {
233 	int areanum, numareas, areas[1];
234 	vec3_t end, ofs;
235 	#define BOTAREA_JIGGLE_DIST     32
236 
237 	areanum = trap_AAS_PointAreaNum( origin );
238 	if ( areanum ) {
239 		return areanum;
240 	}
241 	VectorCopy( origin, end );
242 	end[2] += 10;
243 	numareas = trap_AAS_TraceAreas( origin, end, areas, NULL, 1 );
244 	if ( numareas > 0 ) {
245 		return areas[0];
246 	}
247 
248 	// Ridah, jiggle them around to look for a fuzzy area, helps LARGE characters reach destinations that are against walls
249 	ofs[2] = 10;
250 	for ( ofs[0] = -BOTAREA_JIGGLE_DIST; ofs[0] <= BOTAREA_JIGGLE_DIST; ofs[0] += BOTAREA_JIGGLE_DIST * 2 ) {
251 		for ( ofs[1] = -BOTAREA_JIGGLE_DIST; ofs[1] <= BOTAREA_JIGGLE_DIST; ofs[1] += BOTAREA_JIGGLE_DIST * 2 ) {
252 			VectorAdd( origin, ofs, end );
253 			numareas = trap_AAS_TraceAreas( origin, end, areas, NULL, 1 );
254 			if ( numareas > 0 ) {
255 				return areas[0];
256 			}
257 		}
258 	}
259 
260 	return 0;
261 }
262 
263 /*
264 ==================
265 ClientName
266 ==================
267 */
ClientName(int client,char * name,int size)268 char *ClientName( int client, char *name, int size ) {
269 	char buf[MAX_INFO_STRING];
270 
271 	if ( client < 0 || client >= MAX_CLIENTS ) {
272 		BotAI_Print( PRT_ERROR, "ClientName: client out of range\n" );
273 		return "[client out of range]";
274 	}
275 	trap_GetConfigstring( CS_PLAYERS + client, buf, sizeof( buf ) );
276 	strncpy( name, Info_ValueForKey( buf, "n" ), size - 1 );
277 	name[size - 1] = '\0';
278 	Q_CleanStr( name );
279 	return name;
280 }
281 
282 /*
283 ==================
284 ClientSkin
285 ==================
286 */
ClientSkin(int client,char * skin,int size)287 char *ClientSkin( int client, char *skin, int size ) {
288 	char buf[MAX_INFO_STRING];
289 
290 	if ( client < 0 || client >= MAX_CLIENTS ) {
291 		BotAI_Print( PRT_ERROR, "ClientSkin: client out of range\n" );
292 		return "[client out of range]";
293 	}
294 	trap_GetConfigstring( CS_PLAYERS + client, buf, sizeof( buf ) );
295 	strncpy( skin, Info_ValueForKey( buf, "model" ), size - 1 );
296 	skin[size - 1] = '\0';
297 	return skin;
298 }
299 
300 /*
301 ==================
302 ClientFromName
303 ==================
304 */
ClientFromName(char * name)305 int ClientFromName( char *name ) {
306 	int i;
307 	char buf[MAX_INFO_STRING];
308 
309 	for ( i = 0; i < level.maxclients; i++ ) {
310 		trap_GetConfigstring( CS_PLAYERS + i, buf, sizeof( buf ) );
311 		Q_CleanStr( buf );
312 		if ( !Q_stricmp( Info_ValueForKey( buf, "n" ), name ) ) {
313 			return i;
314 		}
315 	}
316 	return -1;
317 }
318 
319 /*
320 ==================
321 stristr
322 ==================
323 */
stristr(char * str,char * charset)324 char *stristr( char *str, char *charset ) {
325 	int i;
326 
327 	while ( *str ) {
328 		for ( i = 0; charset[i] && str[i]; i++ ) {
329 			if ( toupper( charset[i] ) != toupper( str[i] ) ) {
330 				break;
331 			}
332 		}
333 		if ( !charset[i] ) {
334 			return str;
335 		}
336 		str++;
337 	}
338 	return NULL;
339 }
340 
341 /*
342 ==================
343 EasyClientName
344 ==================
345 */
EasyClientName(int client,char * buf,int size)346 char *EasyClientName( int client, char *buf, int size ) {
347 	int i;
348 	char *str1, *str2, *ptr, c;
349 	char name[128] = {0};
350 
351 	ClientName(client, name, sizeof(name));
352 	for ( i = 0; name[i]; i++ ) name[i] &= 127;
353 	//remove all spaces
354 	for ( ptr = strstr( name, " " ); ptr; ptr = strstr( name, " " ) ) {
355 		memmove( ptr, ptr + 1, strlen( ptr + 1 ) + 1 );
356 	}
357 	//check for [x] and ]x[ clan names
358 	str1 = strstr( name, "[" );
359 	str2 = strstr( name, "]" );
360 	if ( str1 && str2 ) {
361 		if ( str2 > str1 ) {
362 			memmove( str1, str2 + 1, strlen( str2 + 1 ) + 1 );
363 		} else { memmove( str2, str1 + 1, strlen( str1 + 1 ) + 1 );}
364 	}
365 	//remove Mr prefix
366 	if ( ( name[0] == 'm' || name[0] == 'M' ) &&
367 		 ( name[1] == 'r' || name[1] == 'R' ) ) {
368 		memmove( name, name + 2, strlen( name + 2 ) + 1 );
369 	}
370 	//only allow lower case alphabet characters
371 	ptr = name;
372 	while ( *ptr ) {
373 		c = *ptr;
374 		if ( ( c >= 'a' && c <= 'z' ) ||
375 			 ( c >= '0' && c <= '9' ) || c == '_' ) {
376 			ptr++;
377 		} else if ( c >= 'A' && c <= 'Z' )       {
378 			*ptr += 'a' - 'A';
379 			ptr++;
380 		} else {
381 			memmove( ptr, ptr + 1, strlen( ptr + 1 ) + 1 );
382 		}
383 	}
384 	strncpy( buf, name, size - 1 );
385 	buf[size - 1] = '\0';
386 	return buf;
387 }
388 
389 /*
390 ==================
391 BotChooseWeapon
392 ==================
393 */
BotChooseWeapon(bot_state_t * bs)394 void BotChooseWeapon( bot_state_t *bs ) {
395 	int newweaponnum;
396 
397 	if (    bs->cur_ps.weaponstate == WEAPON_RAISING ||
398 			bs->cur_ps.weaponstate == WEAPON_RAISING_TORELOAD ||    //----(SA)	added
399 			bs->cur_ps.weaponstate == WEAPON_DROPPING ||
400 			bs->cur_ps.weaponstate == WEAPON_DROPPING_TORELOAD ) {   //----(SA)	added
401 		trap_EA_SelectWeapon( bs->client, bs->weaponnum );
402 	} else {
403 		newweaponnum = trap_BotChooseBestFightWeapon( bs->ws, bs->inventory );
404 		if ( bs->weaponnum != newweaponnum ) {
405 			bs->weaponchange_time = trap_AAS_Time();
406 		}
407 		bs->weaponnum = newweaponnum;
408 		//BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum);
409 		trap_EA_SelectWeapon( bs->client, bs->weaponnum );
410 	}
411 }
412 
413 /*
414 ==================
415 BotSetupForMovement
416 ==================
417 */
BotSetupForMovement(bot_state_t * bs)418 void BotSetupForMovement( bot_state_t *bs ) {
419 	bot_initmove_t initmove;
420 
421 	memset( &initmove, 0, sizeof( bot_initmove_t ) );
422 	VectorCopy( bs->cur_ps.origin, initmove.origin );
423 	VectorCopy( bs->cur_ps.velocity, initmove.velocity );
424 	VectorCopy( bs->cur_ps.origin, initmove.viewoffset );
425 	initmove.viewoffset[2] += bs->cur_ps.viewheight;
426 	initmove.entitynum = bs->entitynum;
427 	initmove.client = bs->client;
428 	initmove.thinktime = bs->thinktime;
429 	//set the onground flag
430 	if ( bs->cur_ps.groundEntityNum != ENTITYNUM_NONE ) {
431 		initmove.or_moveflags |= MFL_ONGROUND;
432 	}
433 	//set the teleported flag
434 	if ( ( bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK ) && ( bs->cur_ps.pm_time > 0 ) ) {
435 		initmove.or_moveflags |= MFL_TELEPORTED;
436 	}
437 	//set the waterjump flag
438 	if ( ( bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP ) && ( bs->cur_ps.pm_time > 0 ) ) {
439 		initmove.or_moveflags |= MFL_WATERJUMP;
440 	}
441 	//set presence type
442 	if ( bs->cur_ps.pm_flags & PMF_DUCKED ) {
443 		initmove.presencetype = PRESENCE_CROUCH;
444 	} else { initmove.presencetype = PRESENCE_NORMAL;}
445 	//
446 	if ( bs->walker > 0.5 ) {
447 		initmove.or_moveflags |= MFL_WALK;
448 	}
449 	//
450 	VectorCopy( bs->viewangles, initmove.viewangles );
451 	//
452 	trap_BotInitMoveState( bs->ms, &initmove );
453 }
454 
455 /*
456 ==================
457 BotUpdateInventory
458 ==================
459 */
BotUpdateInventory(bot_state_t * bs)460 void BotUpdateInventory( bot_state_t *bs ) {
461 	//armor
462 	bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR];
463 	//weapons
464 	bs->inventory[INVENTORY_LUGER]              =   COM_BitCheck( bs->cur_ps.weapons, ( WP_LUGER ) );
465 	bs->inventory[INVENTORY_MAUSER]             =   COM_BitCheck( bs->cur_ps.weapons, ( WP_MAUSER ) );
466 	bs->inventory[INVENTORY_MP40]               =   COM_BitCheck( bs->cur_ps.weapons, ( WP_MP40 ) );
467 	bs->inventory[INVENTORY_GRENADELAUNCHER]    =   COM_BitCheck( bs->cur_ps.weapons, ( WP_GRENADE_LAUNCHER ) );
468 	bs->inventory[INVENTORY_VENOM]              =   COM_BitCheck( bs->cur_ps.weapons, ( WP_VENOM ) );
469 	bs->inventory[INVENTORY_FLAMETHROWER]       =   COM_BitCheck( bs->cur_ps.weapons, ( WP_FLAMETHROWER ) );
470 	bs->inventory[INVENTORY_GAUNTLET]           =   COM_BitCheck( bs->cur_ps.weapons, ( WP_GAUNTLET ) );
471 
472 	// ammo
473 	bs->inventory[INVENTORY_9MM]            = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_MP40 )];
474 	bs->inventory[INVENTORY_792MM]          = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_MAUSER )];
475 	bs->inventory[INVENTORY_GRENADES]       = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_GRENADE_LAUNCHER )];
476 	bs->inventory[INVENTORY_127MM]          = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_VENOM )];
477 	bs->inventory[INVENTORY_FUEL]           = bs->cur_ps.ammo[BG_FindAmmoForWeapon( WP_FLAMETHROWER )];
478 
479 	//powerups
480 	bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH];
481 	bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER;
482 	bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT;
483 	bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0;
484 	bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0;
485 	bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0;
486 	bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0;
487 	bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0;
488 	bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0;
489 	bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0;
490 	bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0;
491 	//
492 }
493 
494 /*
495 ==================
496 BotUpdateBattleInventory
497 ==================
498 */
BotUpdateBattleInventory(bot_state_t * bs,int enemy)499 void BotUpdateBattleInventory( bot_state_t *bs, int enemy ) {
500 	vec3_t dir;
501 	aas_entityinfo_t entinfo;
502 
503 	BotEntityInfo( enemy, &entinfo );
504 	VectorSubtract( entinfo.origin, bs->origin, dir );
505 	bs->inventory[ENEMY_HEIGHT] = (int) dir[2];
506 	dir[2] = 0;
507 	bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength( dir );
508 	//FIXME: add num visible enemies and num visible team mates to the inventory
509 }
510 
511 /*
512 ==================
513 BotBattleUseItems
514 ==================
515 */
BotBattleUseItems(bot_state_t * bs)516 void BotBattleUseItems( bot_state_t *bs ) {
517 	if ( bs->inventory[INVENTORY_HEALTH] < 40 ) {
518 		if ( bs->inventory[INVENTORY_TELEPORTER] > 0 ) {
519 			trap_EA_Use( bs->client );
520 		}
521 		if ( bs->inventory[INVENTORY_MEDKIT] > 0 ) {
522 			trap_EA_Use( bs->client );
523 		}
524 	}
525 }
526 
527 /*
528 ==================
529 BotSetTeleportTime
530 ==================
531 */
BotSetTeleportTime(bot_state_t * bs)532 void BotSetTeleportTime( bot_state_t *bs ) {
533 	if ( ( bs->cur_ps.eFlags ^ bs->last_eFlags ) & EF_TELEPORT_BIT ) {
534 		bs->teleport_time = trap_AAS_Time();
535 	}
536 	bs->last_eFlags = bs->cur_ps.eFlags;
537 }
538 
539 /*
540 ==================
541 BotIsDead
542 ==================
543 */
BotIsDead(bot_state_t * bs)544 qboolean BotIsDead( bot_state_t *bs ) {
545 	return ( bs->cur_ps.pm_type == PM_DEAD );
546 }
547 
548 /*
549 ==================
550 BotIsObserver
551 ==================
552 */
BotIsObserver(bot_state_t * bs)553 qboolean BotIsObserver( bot_state_t *bs ) {
554 	char buf[MAX_INFO_STRING];
555 	if ( bs->cur_ps.pm_type == PM_SPECTATOR ) {
556 		return qtrue;
557 	}
558 	trap_GetConfigstring( CS_PLAYERS + bs->client, buf, sizeof( buf ) );
559 	if ( atoi( Info_ValueForKey( buf, "t" ) ) == TEAM_SPECTATOR ) {
560 		return qtrue;
561 	}
562 	return qfalse;
563 }
564 
565 /*
566 ==================
567 BotIntermission
568 ==================
569 */
BotIntermission(bot_state_t * bs)570 qboolean BotIntermission( bot_state_t *bs ) {
571 	//NOTE: we shouldn't look at the game code...
572 	if ( level.intermissiontime ) {
573 		return qtrue;
574 	}
575 	return ( bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION );
576 }
577 
578 
579 /*
580 ==============
581 BotInLava
582 ==============
583 */
BotInLava(bot_state_t * bs)584 qboolean BotInLava( bot_state_t *bs ) {
585 	vec3_t feet;
586 
587 	VectorCopy( bs->origin, feet );
588 	feet[2] -= 23;
589 	return ( trap_AAS_PointContents( feet ) & CONTENTS_LAVA );
590 }
591 
592 /*
593 ==============
594 BotInSlime
595 ==============
596 */
BotInSlime(bot_state_t * bs)597 qboolean BotInSlime( bot_state_t *bs ) {
598 	vec3_t feet;
599 
600 	VectorCopy( bs->origin, feet );
601 	feet[2] -= 23;
602 	return ( trap_AAS_PointContents( feet ) & CONTENTS_SLIME );
603 }
604 
605 /*
606 ==================
607 EntityIsDead
608 ==================
609 */
EntityIsDead(aas_entityinfo_t * entinfo)610 qboolean EntityIsDead( aas_entityinfo_t *entinfo ) {
611 	playerState_t ps;
612 
613 	if ( entinfo->number >= 0 && entinfo->number < MAX_CLIENTS ) {
614 		//retrieve the current client state
615 		if ( !BotAI_GetClientState( entinfo->number, &ps ) ) {
616 			return qfalse;
617 		}
618 
619 		if ( ps.pm_type != PM_NORMAL ) {
620 			return qtrue;
621 		}
622 	}
623 	return qfalse;
624 }
625 
626 /*
627 ==================
628 EntityIsInvisible
629 ==================
630 */
EntityIsInvisible(aas_entityinfo_t * entinfo)631 qboolean EntityIsInvisible( aas_entityinfo_t *entinfo ) {
632 	if ( entinfo->powerups & ( 1 << PW_INVIS ) ) {
633 		return qtrue;
634 	}
635 	return qfalse;
636 }
637 
638 /*
639 ==================
640 EntityIsShooting
641 ==================
642 */
EntityIsShooting(aas_entityinfo_t * entinfo)643 qboolean EntityIsShooting( aas_entityinfo_t *entinfo ) {
644 	if ( entinfo->flags & EF_FIRING ) {
645 		return qtrue;
646 	}
647 	return qfalse;
648 }
649 
650 /*
651 ==================
652 EntityIsChatting
653 ==================
654 */
EntityIsChatting(aas_entityinfo_t * entinfo)655 qboolean EntityIsChatting( aas_entityinfo_t *entinfo ) {
656 	if ( entinfo->flags & EF_TALK ) {
657 		return qtrue;
658 	}
659 	return qfalse;
660 }
661 
662 /*
663 ==================
664 EntityHasQuad
665 ==================
666 */
EntityHasQuad(aas_entityinfo_t * entinfo)667 qboolean EntityHasQuad( aas_entityinfo_t *entinfo ) {
668 	if ( entinfo->powerups & ( 1 << PW_QUAD ) ) {
669 		return qtrue;
670 	}
671 	return qfalse;
672 }
673 
674 /*
675 ==================
676 BotCreateWayPoint
677 ==================
678 */
BotCreateWayPoint(char * name,vec3_t origin,int areanum)679 bot_waypoint_t *BotCreateWayPoint( char *name, vec3_t origin, int areanum ) {
680 	bot_waypoint_t *wp;
681 	vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8};
682 
683 	wp = botai_freewaypoints;
684 	if ( !wp ) {
685 		BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" );
686 		return NULL;
687 	}
688 	botai_freewaypoints = botai_freewaypoints->next;
689 
690 	Q_strncpyz( wp->name, name, sizeof( wp->name ) );
691 	VectorCopy( origin, wp->goal.origin );
692 	VectorCopy( waypointmins, wp->goal.mins );
693 	VectorCopy( waypointmaxs, wp->goal.maxs );
694 	wp->goal.areanum = areanum;
695 	wp->next = NULL;
696 	wp->prev = NULL;
697 	return wp;
698 }
699 
700 /*
701 ==================
702 BotFindWayPoint
703 ==================
704 */
BotFindWayPoint(bot_waypoint_t * waypoints,char * name)705 bot_waypoint_t *BotFindWayPoint( bot_waypoint_t *waypoints, char *name ) {
706 	bot_waypoint_t *wp;
707 
708 	for ( wp = waypoints; wp; wp = wp->next ) {
709 		if ( !Q_stricmp( wp->name, name ) ) {
710 			return wp;
711 		}
712 	}
713 	return NULL;
714 }
715 
716 /*
717 ==================
718 BotFreeWaypoints
719 ==================
720 */
BotFreeWaypoints(bot_waypoint_t * wp)721 void BotFreeWaypoints( bot_waypoint_t *wp ) {
722 	bot_waypoint_t *nextwp;
723 
724 	for (; wp; wp = nextwp ) {
725 		nextwp = wp->next;
726 		wp->next = botai_freewaypoints;
727 		botai_freewaypoints = wp;
728 	}
729 }
730 
731 /*
732 ==================
733 BotInitWaypoints
734 ==================
735 */
BotInitWaypoints(void)736 void BotInitWaypoints( void ) {
737 	int i;
738 
739 	botai_freewaypoints = NULL;
740 	for ( i = 0; i < MAX_WAYPOINTS; i++ ) {
741 		botai_waypoints[i].next = botai_freewaypoints;
742 		botai_freewaypoints = &botai_waypoints[i];
743 	}
744 }
745 
746 /*
747 ==================
748 TeamPlayIsOn
749 ==================
750 */
TeamPlayIsOn(void)751 int TeamPlayIsOn( void ) {
752 	return ( gametype == GT_TEAM || gametype == GT_CTF );
753 }
754 
755 /*
756 ==================
757 BotAggression
758 
759 FIXME: move this to external fuzzy logic
760 
761   NOTE!!: I made no changes to this code for wolf weapon awareness.  (SA)
762 ==================
763 */
BotAggression(bot_state_t * bs)764 float BotAggression( bot_state_t *bs ) {
765 	//if the bot has quad
766 	if ( bs->inventory[INVENTORY_QUAD] ) {
767 		//if the bot is not holding the gauntlet or the enemy is really nearby
768 		if ( bs->weaponnum != WP_GAUNTLET ||
769 			 bs->inventory[ENEMY_HORIZONTAL_DIST] < 80 ) {
770 			return 70;
771 		}
772 	}
773 	//if the enemy is located way higher than the bot
774 	if ( bs->inventory[ENEMY_HEIGHT] > 200 ) {
775 		return 0;
776 	}
777 	//if the bot is very low on health
778 	if ( bs->inventory[INVENTORY_HEALTH] < 60 ) {
779 		return 0;
780 	}
781 	//if the bot is low on health
782 	if ( bs->inventory[INVENTORY_HEALTH] < 80 ) {
783 		//if the bot has insufficient armor
784 		if ( bs->inventory[INVENTORY_ARMOR] < 40 ) {
785 			return 0;
786 		}
787 	}
788 //	//if the bot can use the bfg
789 //	if (bs->inventory[INVENTORY_BFG10K] > 0 &&
790 //			bs->inventory[INVENTORY_BFGAMMO] > 7) return 100;
791 //	//if the bot can use the railgun
792 //	if (bs->inventory[INVENTORY_RAILGUN] > 0 &&
793 //			bs->inventory[INVENTORY_SLUGS] > 5) return 95;
794 	//if the bot can use the lightning gun
795 	if ( bs->inventory[INVENTORY_FLAMETHROWER] > 0 &&
796 		 bs->inventory[INVENTORY_FUEL] > 50 ) {
797 		return 90;
798 	}
799 	//if the bot can use the rocketlauncher
800 	if ( bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 &&
801 		 bs->inventory[INVENTORY_ROCKETS] > 5 ) {
802 		return 90;
803 	}
804 	//if the bot can use the SP5
805 	if ( bs->inventory[INVENTORY_SP5] > 0 &&
806 		 bs->inventory[INVENTORY_SP5AMMO] > 40 ) {
807 		return 85;
808 	}
809 	//if the bot can use the grenade launcher
810 	if ( bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 &&
811 		 bs->inventory[INVENTORY_GRENADES] > 10 ) {
812 		return 80;
813 	}
814 //	//if the bot can use the shotgun
815 //	if (bs->inventory[INVENTORY_SHOTGUN] > 0 &&
816 //			bs->inventory[INVENTORY_SHELLS] > 10) return 50;
817 	//otherwise the bot is not feeling too good
818 	return 0;
819 }
820 
821 /*
822 ==================
823 BotWantsToRetreat
824 ==================
825 */
BotWantsToRetreat(bot_state_t * bs)826 int BotWantsToRetreat( bot_state_t *bs ) {
827 #ifdef CTF
828 	//always retreat when carrying a CTF flag
829 	if ( BotCTFCarryingFlag( bs ) ) {
830 		return qtrue;
831 	}
832 	//if the bot is getting the flag
833 	if ( bs->ltgtype == LTG_GETFLAG ) {
834 		return qtrue;
835 	}
836 #endif //CTF
837 	if ( BotAggression( bs ) < 50 ) {
838 		return qtrue;
839 	}
840 	return qfalse;
841 }
842 
843 /*
844 ==================
845 BotWantsToChase
846 ==================
847 */
BotWantsToChase(bot_state_t * bs)848 int BotWantsToChase( bot_state_t *bs ) {
849 #ifdef CTF
850 	//always retreat when carrying a CTF flag
851 	if ( BotCTFCarryingFlag( bs ) ) {
852 		return qfalse;
853 	}
854 	//if the bot is getting the flag
855 	if ( bs->ltgtype == LTG_GETFLAG ) {
856 		return qfalse;
857 	}
858 #endif //CTF
859 	if ( BotAggression( bs ) > 50 ) {
860 		return qtrue;
861 	}
862 	return qfalse;
863 }
864 
865 /*
866 ==================
867 BotWantsToHelp
868 ==================
869 */
BotWantsToHelp(bot_state_t * bs)870 int BotWantsToHelp( bot_state_t *bs ) {
871 	return qtrue;
872 }
873 
874 /*
875 ==================
876 BotCanAndWantsToRocketJump
877 ==================
878 */
BotCanAndWantsToRocketJump(bot_state_t * bs)879 int BotCanAndWantsToRocketJump( bot_state_t *bs ) {
880 	float rocketjumper;
881 
882 	//if rocket jumping is disabled
883 	if ( !bot_rocketjump.integer ) {
884 		return qfalse;
885 	}
886 	//if no rocket launcher
887 	if ( bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 ) {
888 		return qfalse;
889 	}
890 	//if low on rockets
891 	if ( bs->inventory[INVENTORY_ROCKETS] < 3 ) {
892 		return qfalse;
893 	}
894 	//never rocket jump with the Quad
895 	if ( bs->inventory[INVENTORY_QUAD] ) {
896 		return qfalse;
897 	}
898 	//if low on health
899 	if ( bs->inventory[INVENTORY_HEALTH] < 60 ) {
900 		return qfalse;
901 	}
902 	//if not full health
903 	if ( bs->inventory[INVENTORY_HEALTH] < 90 ) {
904 		//if the bot has insufficient armor
905 		if ( bs->inventory[INVENTORY_ARMOR] < 40 ) {
906 			return qfalse;
907 		}
908 	}
909 	rocketjumper = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1 );
910 	if ( rocketjumper < 0.5 ) {
911 		return qfalse;
912 	}
913 	return qtrue;
914 }
915 
916 /*
917 ==================
918 BotGoCamp
919 ==================
920 */
BotGoCamp(bot_state_t * bs,bot_goal_t * goal)921 void BotGoCamp( bot_state_t *bs, bot_goal_t *goal ) {
922 	float camper;
923 
924 	//set message time to zero so bot will NOT show any message
925 	bs->teammessage_time = 0;
926 	//set the ltg type
927 	bs->ltgtype = LTG_CAMP;
928 	//set the team goal
929 	memcpy( &bs->teamgoal, goal, sizeof( bot_goal_t ) );
930 	//get the team goal time
931 	camper = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CAMPER, 0, 1 );
932 	if ( camper > 0.99 ) {
933 		bs->teamgoal_time = 99999;
934 	} else { bs->teamgoal_time = 120 + 180 * camper + random() * 15;}
935 	//set the last time the bot started camping
936 	bs->camp_time = trap_AAS_Time();
937 	//the teammate that requested the camping
938 	bs->teammate = 0;
939 	//do NOT type arrive message
940 	bs->arrive_time = 1;
941 }
942 
943 /*
944 ==================
945 BotWantsToCamp
946 ==================
947 */
BotWantsToCamp(bot_state_t * bs)948 int BotWantsToCamp( bot_state_t *bs ) {
949 	float camper;
950 	int cs, traveltime, besttraveltime;
951 	bot_goal_t goal, bestgoal;
952 
953 	camper = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CAMPER, 0, 1 );
954 	if ( camper < 0.1 ) {
955 		return qfalse;
956 	}
957 	//if the bot has a team goal
958 	if ( bs->ltgtype == LTG_TEAMHELP ||
959 		 bs->ltgtype == LTG_TEAMACCOMPANY ||
960 		 bs->ltgtype == LTG_DEFENDKEYAREA ||
961 		 bs->ltgtype == LTG_GETFLAG ||
962 		 bs->ltgtype == LTG_RUSHBASE ||
963 		 bs->ltgtype == LTG_CAMP ||
964 		 bs->ltgtype == LTG_CAMPORDER ||
965 		 bs->ltgtype == LTG_PATROL ) {
966 		return qfalse;
967 	}
968 	//if camped recently
969 	if ( bs->camp_time > trap_AAS_Time() - 60 + 300 * ( 1 - camper ) ) {
970 		return qfalse;
971 	}
972 	//
973 	if ( random() > camper ) {
974 		bs->camp_time = trap_AAS_Time();
975 		return qfalse;
976 	}
977 	//if the bot isn't healthy enough
978 	if ( BotAggression( bs ) < 50 ) {
979 		return qfalse;
980 	}
981 	//the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo
982 	if ( ( bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS] < 10 )
983 //		&& (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10)
984 //		&& (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)
985 		 ) {
986 		return qfalse;
987 	}
988 	//find the closest camp spot
989 	besttraveltime = 99999;
990 	for ( cs = trap_BotGetNextCampSpotGoal( 0, &goal ); cs; cs = trap_BotGetNextCampSpotGoal( cs, &goal ) ) {
991 		traveltime = trap_AAS_AreaTravelTimeToGoalArea( bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT );
992 		if ( traveltime && traveltime < besttraveltime ) {
993 			besttraveltime = traveltime;
994 			memcpy( &bestgoal, &goal, sizeof( bot_goal_t ) );
995 		}
996 	}
997 	if ( besttraveltime > 150 ) {
998 		return qfalse;
999 	}
1000 	//ok found a camp spot, go camp there
1001 	BotGoCamp( bs, &bestgoal );
1002 	//
1003 	return qtrue;
1004 }
1005 
1006 /*
1007 ==================
1008 BotDontAvoid
1009 ==================
1010 */
BotDontAvoid(bot_state_t * bs,char * itemname)1011 void BotDontAvoid( bot_state_t *bs, char *itemname ) {
1012 	bot_goal_t goal;
1013 	int num;
1014 
1015 	num = trap_BotGetLevelItemGoal( -1, itemname, &goal );
1016 	while ( num >= 0 ) {
1017 		trap_BotRemoveFromAvoidGoals( bs->gs, goal.number );
1018 		num = trap_BotGetLevelItemGoal( num, itemname, &goal );
1019 	}
1020 }
1021 
1022 /*
1023 ==================
1024 BotGoForPowerups
1025 ==================
1026 */
BotGoForPowerups(bot_state_t * bs)1027 void BotGoForPowerups( bot_state_t *bs ) {
1028 
1029 	//don't avoid any of the powerups anymore
1030 	BotDontAvoid( bs, "Quad Damage" );
1031 	BotDontAvoid( bs, "Regeneration" );
1032 	BotDontAvoid( bs, "Battle Suit" );
1033 	BotDontAvoid( bs, "Speed" );
1034 	BotDontAvoid( bs, "Invisibility" );
1035 	//BotDontAvoid(bs, "Flight");
1036 	//reset the long term goal time so the bot will go for the powerup
1037 	//NOTE: the long term goal type doesn't change
1038 	bs->ltg_time = 0;
1039 }
1040 
1041 /*
1042 ==================
1043 BotRoamGoal
1044 ==================
1045 */
BotRoamGoal(bot_state_t * bs,vec3_t goal)1046 void BotRoamGoal( bot_state_t *bs, vec3_t goal ) {
1047 	float len, r1, r2, sign, n;
1048 	int pc;
1049 	vec3_t dir, bestorg = {0}, belowbestorg;
1050 	bsp_trace_t trace;
1051 
1052 	for ( n = 0; n < 10; n++ ) {
1053 		//start at the bot origin
1054 		VectorCopy( bs->origin, bestorg );
1055 		r1 = random();
1056 		if ( r1 < 0.8 ) {
1057 			//add a random value to the x-coordinate
1058 			r2 = random();
1059 			if ( r2 < 0.5 ) {
1060 				sign = -1;
1061 			} else { sign = 1;}
1062 			bestorg[0] += sign * 700 * random() + 50;
1063 		}
1064 		if ( r1 > 0.2 ) {
1065 			//add a random value to the y-coordinate
1066 			r2 = random();
1067 			if ( r2 < 0.5 ) {
1068 				sign = -1;
1069 			} else { sign = 1;}
1070 			bestorg[1] += sign * 700 * random() + 50;
1071 		}
1072 		//add a random value to the z-coordinate (NOTE: 48 = maxjump?)
1073 		bestorg[2] += 3 * 48 * random() - 2 * 48 - 1;
1074 		//trace a line from the origin to the roam target
1075 		BotAI_Trace( &trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID );
1076 		//direction and length towards the roam target
1077 		VectorSubtract( bestorg, bs->origin, dir );
1078 		len = VectorNormalize( dir );
1079 		//if the roam target is far away enough
1080 		if ( len > 200 ) {
1081 			//the roam target is in the given direction before walls
1082 			VectorScale( dir, len * trace.fraction - 40, dir );
1083 			VectorAdd( bs->origin, dir, bestorg );
1084 			//get the coordinates of the floor below the roam target
1085 			belowbestorg[0] = bestorg[0];
1086 			belowbestorg[1] = bestorg[1];
1087 			belowbestorg[2] = bestorg[2] - 800;
1088 			BotAI_Trace( &trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID );
1089 			//
1090 			if ( !trace.startsolid ) {
1091 				trace.endpos[2]++;
1092 				pc = trap_PointContents( trace.endpos,bs->entitynum );
1093 				if ( !( pc & CONTENTS_LAVA ) ) {    //----(SA)	modified since slime is no longer deadly
1094 //				if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) {
1095 					VectorCopy( bestorg, goal );
1096 					return;
1097 				}
1098 			}
1099 		}
1100 	}
1101 	VectorCopy( bestorg, goal );
1102 }
1103 
1104 /*
1105 ==================
1106 BotAttackMove
1107 ==================
1108 */
BotAttackMove(bot_state_t * bs,int tfl)1109 bot_moveresult_t BotAttackMove( bot_state_t *bs, int tfl ) {
1110 	int movetype, i;
1111 	float attack_skill, jumper, croucher, dist, strafechange_time;
1112 	float attack_dist, attack_range;
1113 	vec3_t forward, backward, sideward, hordir, up = {0, 0, 1};
1114 	aas_entityinfo_t entinfo;
1115 	bot_moveresult_t moveresult;
1116 	bot_goal_t goal;
1117 
1118 	if ( bs->attackchase_time > trap_AAS_Time() ) {
1119 		//create the chase goal
1120 		goal.entitynum = bs->enemy;
1121 		goal.areanum = bs->lastenemyareanum;
1122 		VectorCopy( bs->lastenemyorigin, goal.origin );
1123 		VectorSet( goal.mins, -8, -8, -8 );
1124 		VectorSet( goal.maxs, 8, 8, 8 );
1125 		//initialize the movement state
1126 		BotSetupForMovement( bs );
1127 		//move towards the goal
1128 		trap_BotMoveToGoal( &moveresult, bs->ms, &goal, tfl );
1129 		return moveresult;
1130 	}
1131 	//
1132 	memset( &moveresult, 0, sizeof( bot_moveresult_t ) );
1133 	//
1134 	attack_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1 );
1135 	jumper = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_JUMPER, 0, 1 );
1136 	croucher = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CROUCHER, 0, 1 );
1137 	//if the bot is really stupid
1138 	if ( attack_skill < 0.2 ) {
1139 		return moveresult;
1140 	}
1141 	//initialize the movement state
1142 	BotSetupForMovement( bs );
1143 	//get the enemy entity info
1144 	BotEntityInfo( bs->enemy, &entinfo );
1145 	//direction towards the enemy
1146 	VectorSubtract( entinfo.origin, bs->origin, forward );
1147 	//the distance towards the enemy
1148 	dist = VectorNormalize( forward );
1149 	VectorNegate( forward, backward );
1150 	//walk, crouch or jump
1151 	movetype = MOVE_WALK;
1152 	//
1153 	if ( bs->attackcrouch_time < trap_AAS_Time() - 1 ) {
1154 		if ( random() < jumper ) {
1155 			movetype = MOVE_JUMP;
1156 		}
1157 		//wait at least one second before crouching again
1158 		else if ( bs->attackcrouch_time < trap_AAS_Time() - 1 && random() < croucher ) {
1159 			bs->attackcrouch_time = trap_AAS_Time() + croucher * 5;
1160 		}
1161 	}
1162 	if ( bs->attackcrouch_time > trap_AAS_Time() ) {
1163 		movetype = MOVE_CROUCH;
1164 	}
1165 	//if the bot should jump
1166 	if ( movetype == MOVE_JUMP ) {
1167 		//if jumped last frame
1168 		if ( bs->attackjump_time > trap_AAS_Time() ) {
1169 			movetype = MOVE_WALK;
1170 		} else {
1171 			bs->attackjump_time = trap_AAS_Time() + 1;
1172 		}
1173 	}
1174 	if ( bs->cur_ps.weapon == WP_GAUNTLET ) {
1175 		attack_dist = 0;
1176 		attack_range = 0;
1177 	} else {
1178 		attack_dist = IDEAL_ATTACKDIST;
1179 		attack_range = 40;
1180 	}
1181 	//if the bot is stupid
1182 	if ( attack_skill <= 0.4 ) {
1183 		//just walk to or away from the enemy
1184 		if ( dist > attack_dist + attack_range ) {
1185 			if ( trap_BotMoveInDirection( bs->ms, forward, 400, movetype ) ) {
1186 				return moveresult;
1187 			}
1188 		}
1189 		if ( dist < attack_dist - attack_range ) {
1190 			if ( trap_BotMoveInDirection( bs->ms, backward, 400, movetype ) ) {
1191 				return moveresult;
1192 			}
1193 		}
1194 		return moveresult;
1195 	}
1196 	//increase the strafe time
1197 	bs->attackstrafe_time += bs->thinktime;
1198 	//get the strafe change time
1199 	strafechange_time = 0.4 + ( 1 - attack_skill ) * 0.2;
1200 	if ( attack_skill > 0.7 ) {
1201 		strafechange_time += crandom() * 0.2;
1202 	}
1203 	//if the strafe direction should be changed
1204 	if ( bs->attackstrafe_time > strafechange_time ) {
1205 		//some magic number :)
1206 		if ( random() > 0.935 ) {
1207 			//flip the strafe direction
1208 			bs->flags ^= BFL_STRAFERIGHT;
1209 			bs->attackstrafe_time = 0;
1210 		}
1211 	}
1212 	//
1213 	for ( i = 0; i < 2; i++ ) {
1214 		hordir[0] = forward[0];
1215 		hordir[1] = forward[1];
1216 		hordir[2] = 0;
1217 		VectorNormalize( hordir );
1218 		//get the sideward vector
1219 		CrossProduct( hordir, up, sideward );
1220 		//reverse the vector depending on the strafe direction
1221 		if ( bs->flags & BFL_STRAFERIGHT ) {
1222 			VectorNegate( sideward, sideward );
1223 		}
1224 		//randomly go back a little
1225 		if ( random() > 0.9 ) {
1226 			VectorAdd( sideward, backward, sideward );
1227 		} else {
1228 			//walk forward or backward to get at the ideal attack distance
1229 			if ( dist > attack_dist + attack_range ) {
1230 				VectorAdd( sideward, forward, sideward );
1231 			} else if ( dist < attack_dist - attack_range ) {
1232 				VectorAdd( sideward, backward, sideward );
1233 			}
1234 		}
1235 		//perform the movement
1236 		if ( trap_BotMoveInDirection( bs->ms, sideward, 400, movetype ) ) {
1237 			return moveresult;
1238 		}
1239 		//movement failed, flip the strafe direction
1240 		bs->flags ^= BFL_STRAFERIGHT;
1241 		bs->attackstrafe_time = 0;
1242 	}
1243 	//bot couldn't do any usefull movement
1244 //	bs->attackchase_time = AAS_Time() + 6;
1245 	return moveresult;
1246 }
1247 
1248 /*
1249 ==================
1250 BotSameTeam
1251 ==================
1252 */
BotSameTeam(bot_state_t * bs,int entnum)1253 int BotSameTeam( bot_state_t *bs, int entnum ) {
1254 
1255 	if ( bs->client < 0 || bs->client >= MAX_CLIENTS ) {
1256 		return qfalse;
1257 	}
1258 	if ( entnum < 0 || entnum >= MAX_CLIENTS ) {
1259 		return qfalse;
1260 	}
1261 	if (gametype >= GT_TEAM) {
1262 		if (level.clients[bs->client].sess.sessionTeam == level.clients[entnum].sess.sessionTeam)
1263 			 return qtrue;
1264 	}
1265 
1266 	return qfalse;
1267 }
1268 
1269 /*
1270 ==================
1271 InFieldOfVision
1272 ==================
1273 */
InFieldOfVision(vec3_t viewangles,float fov,vec3_t angles)1274 qboolean InFieldOfVision( vec3_t viewangles, float fov, vec3_t angles ) {
1275 	int i;
1276 	float diff, angle;
1277 
1278 	for ( i = 0; i < 2; i++ ) {
1279 		angle = AngleMod( viewangles[i] );
1280 		angles[i] = AngleMod( angles[i] );
1281 		diff = angles[i] - angle;
1282 		if ( angles[i] > angle ) {
1283 			if ( diff > 180.0 ) {
1284 				diff -= 360.0;
1285 			}
1286 		} else {
1287 			if ( diff < -180.0 ) {
1288 				diff += 360.0;
1289 			}
1290 		}
1291 		if ( diff > 0 ) {
1292 			if ( diff > fov * 0.5 ) {
1293 				return qfalse;
1294 			}
1295 		} else {
1296 			if ( diff < -fov * 0.5 ) {
1297 				return qfalse;
1298 			}
1299 		}
1300 	}
1301 	return qtrue;
1302 }
1303 
1304 /*
1305 ==================
1306 BotEntityVisible
1307 
1308 returns visibility in the range [0, 1] taking fog and water surfaces into account
1309 ==================
1310 */
BotEntityVisible(int viewer,vec3_t eye,vec3_t viewangles,float fov,int ent)1311 float BotEntityVisible( int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent ) {
1312 	int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc;
1313 	float fogdist, waterfactor, vis, bestvis;
1314 	bsp_trace_t trace;
1315 	aas_entityinfo_t entinfo;
1316 	vec3_t dir, entangles, start, end, middle;
1317 
1318 	BotEntityInfo( ent, &entinfo );
1319 	if (!entinfo.valid) {
1320 		return 0;
1321 	}
1322 
1323 	//calculate middle of bounding box
1324 	VectorAdd( entinfo.mins, entinfo.maxs, middle );
1325 	VectorScale( middle, 0.5, middle );
1326 	VectorAdd( entinfo.origin, middle, middle );
1327 	//check if entity is within field of vision
1328 	VectorSubtract( middle, eye, dir );
1329 	vectoangles( dir, entangles );
1330 	if ( !InFieldOfVision( viewangles, fov, entangles ) ) {
1331 		return 0;
1332 	}
1333 	//
1334 	pc = trap_AAS_PointContents( eye );
1335 	infog = ( pc & CONTENTS_SOLID );
1336 	inwater = ( pc & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) );
1337 	//
1338 	bestvis = 0;
1339 	for ( i = 0; i < 3; i++ ) {
1340 		//if the point is not in potential visible sight
1341 		//if (!AAS_inPVS(eye, middle)) continue;
1342 		//
1343 		contents_mask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
1344 		passent = viewer;
1345 		hitent = ent;
1346 		VectorCopy( eye, start );
1347 		VectorCopy( middle, end );
1348 		//if the entity is in water, lava or slime
1349 		if ( trap_AAS_PointContents( middle ) & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) {
1350 			contents_mask |= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
1351 		}
1352 		//if eye is in water, lava or slime
1353 		if ( inwater ) {
1354 			if ( !( contents_mask & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) ) {
1355 				passent = ent;
1356 				hitent = viewer;
1357 				VectorCopy( middle, start );
1358 				VectorCopy( eye, end );
1359 			}
1360 			contents_mask ^= ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
1361 		}
1362 		//trace from start to end
1363 		BotAI_Trace( &trace, start, NULL, NULL, end, passent, contents_mask );
1364 		//if water was hit
1365 		waterfactor = 1.0;
1366 		//note: trace.contents is always 0, see BotAI_Trace
1367 		if ( trace.contents & ( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER ) ) {
1368 			//if the water surface is translucent
1369 			if ( 1 ) {
1370 				//trace through the water
1371 				contents_mask &= ~( CONTENTS_LAVA | CONTENTS_SLIME | CONTENTS_WATER );
1372 				BotAI_Trace( &trace, trace.endpos, NULL, NULL, end, passent, contents_mask );
1373 				waterfactor = 0.5;
1374 			}
1375 		}
1376 		//if a full trace or the hitent was hit
1377 		if ( trace.fraction >= 1 || trace.ent == hitent ) {
1378 			//check for fog, assuming there's only one fog brush where
1379 			//either the viewer or the entity is in or both are in
1380 			otherinfog = ( trap_AAS_PointContents( middle ) & CONTENTS_FOG );
1381 			if ( infog && otherinfog ) {
1382 				VectorSubtract( trace.endpos, eye, dir );
1383 				fogdist = VectorLength( dir );
1384 			} else if ( infog )     {
1385 				VectorCopy( trace.endpos, start );
1386 				BotAI_Trace( &trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG );
1387 				VectorSubtract( eye, trace.endpos, dir );
1388 				fogdist = VectorLength( dir );
1389 			} else if ( otherinfog )     {
1390 				VectorCopy( trace.endpos, end );
1391 				BotAI_Trace( &trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG );
1392 				VectorSubtract( end, trace.endpos, dir );
1393 				fogdist = VectorLength( dir );
1394 			} else {
1395 				//if the entity and the viewer are not in fog assume there's no fog in between
1396 				fogdist = 0;
1397 			}
1398 			//decrease visibility with the view distance through fog
1399 			vis = 1 / ( ( fogdist * fogdist * 0.001 ) < 1 ? 1 : ( fogdist * fogdist * 0.001 ) );
1400 			//if entering water visibility is reduced
1401 			vis *= waterfactor;
1402 			//
1403 			if ( vis > bestvis ) {
1404 				bestvis = vis;
1405 			}
1406 			//if pretty much no fog
1407 			if ( bestvis >= 0.95 ) {
1408 				return bestvis;
1409 			}
1410 		}
1411 		//check bottom and top of bounding box as well
1412 		if ( i == 0 ) {
1413 			middle[2] += entinfo.mins[2];
1414 		} else if ( i == 1 ) {
1415 			middle[2] += entinfo.maxs[2] - entinfo.mins[2];
1416 		}
1417 	}
1418 	return bestvis;
1419 }
1420 
1421 /*
1422 ==================
1423 BotFindEnemy
1424 ==================
1425 */
BotFindEnemy(bot_state_t * bs,int curenemy)1426 int BotFindEnemy( bot_state_t *bs, int curenemy ) {
1427 	int i, healthdecrease;
1428 	float fov, dist, curdist, alertness, easyfragger, vis;
1429 	aas_entityinfo_t entinfo, curenemyinfo;
1430 	vec3_t dir, angles;
1431 
1432 	alertness = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_ALERTNESS, 0, 1 );
1433 	easyfragger = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1 );
1434 	//check if the health decreased
1435 	healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH];
1436 	//remember the current health value
1437 	bs->lasthealth = bs->inventory[INVENTORY_HEALTH];
1438 	//
1439 	if ( curenemy >= 0 ) {
1440 		BotEntityInfo( curenemy, &curenemyinfo );
1441 		VectorSubtract( curenemyinfo.origin, bs->origin, dir );
1442 		curdist = VectorLength( dir );
1443 	} else {
1444 		curdist = 0;
1445 	}
1446 	//
1447 	for ( i = 0; i < level.maxclients; i++ ) {
1448 
1449 		if ( i == bs->client ) {
1450 			continue;
1451 		}
1452 		//if it's the current enemy
1453 		if ( i == curenemy ) {
1454 			continue;
1455 		}
1456 		//if the enemy has targeting disabled
1457 		if (g_entities[i].flags & FL_NOTARGET) {
1458 			continue;
1459 		}
1460 		//
1461 		BotEntityInfo( i, &entinfo );
1462 		//
1463 		if ( !entinfo.valid ) {
1464 			continue;
1465 		}
1466 		//if the enemy isn't dead and the enemy isn't the bot self
1467 		if ( EntityIsDead( &entinfo ) || entinfo.number == bs->entitynum ) {
1468 			continue;
1469 		}
1470 		//if the enemy is invisible and not shooting
1471 		if ( EntityIsInvisible( &entinfo ) && !EntityIsShooting( &entinfo ) ) {
1472 			continue;
1473 		}
1474 		//if not an easy fragger don't shoot at chatting players
1475 		if ( easyfragger < 0.5 && EntityIsChatting( &entinfo ) ) {
1476 			continue;
1477 		}
1478 		//
1479 		if ( lastteleport_time > trap_AAS_Time() - 3 ) {
1480 			VectorSubtract( entinfo.origin, lastteleport_origin, dir );
1481 			if ( VectorLength( dir ) < 70 ) {
1482 				continue;
1483 			}
1484 		}
1485 		//calculate the distance towards the enemy
1486 		VectorSubtract( entinfo.origin, bs->origin, dir );
1487 		dist = VectorLength( dir );
1488 		//if this enemy is further away than the current one
1489 		if ( curenemy >= 0 && dist > curdist ) {
1490 			continue;
1491 		}
1492 		//if the bot has no
1493 		if ( dist > 900 + alertness * 4000 ) {
1494 			continue;
1495 		}
1496 		//if on the same team
1497 		if ( BotSameTeam( bs, i ) ) {
1498 			continue;
1499 		}
1500 		//if the bot's health decreased or the enemy is shooting
1501 		if ( curenemy < 0 && ( healthdecrease || EntityIsShooting( &entinfo ) ) ) {
1502 			fov = 360;
1503 		} else { fov = 90 + 270 - ( 270 - ( dist > 810 ? 810 : dist ) / 3 );}
1504 		//check if the enemy visibility
1505 		vis = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, fov, i );
1506 		if ( vis <= 0 ) {
1507 			continue;
1508 		}
1509 		//if the enemy is quite far away, not shooting and the bot is not damaged
1510 		if ( curenemy < 0 && dist > 200 && !healthdecrease && !EntityIsShooting( &entinfo ) ) {
1511 			//check if we can avoid this enemy
1512 			VectorSubtract( bs->origin, entinfo.origin, dir );
1513 			vectoangles( dir, angles );
1514 			//if the bot isn't in the fov of the enemy
1515 			if ( !InFieldOfVision( entinfo.angles, 120, angles ) ) {
1516 				//update some stuff for this enemy
1517 				BotUpdateBattleInventory( bs, i );
1518 				//if the bot doesn't really want to fight
1519 				if ( BotWantsToRetreat( bs ) ) {
1520 					continue;
1521 				}
1522 			}
1523 		}
1524 		//found an enemy
1525 		bs->enemy = entinfo.number;
1526 		if ( curenemy >= 0 ) {
1527 			bs->enemysight_time = trap_AAS_Time() - 2;
1528 		} else { bs->enemysight_time = trap_AAS_Time();}
1529 		bs->enemysuicide = qfalse;
1530 		bs->enemydeath_time = 0;
1531 		return qtrue;
1532 	}
1533 	return qfalse;
1534 }
1535 
1536 /*
1537 ==================
1538 BotAimAtEnemy
1539 ==================
1540 */
BotAimAtEnemy(bot_state_t * bs)1541 void BotAimAtEnemy( bot_state_t *bs ) {
1542 	int i, enemyvisible;
1543 	float dist, f, aim_skill, aim_accuracy, speed, reactiontime;
1544 	vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity;
1545 	vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4};
1546 	weaponinfo_t wi;
1547 	aas_entityinfo_t entinfo;
1548 	bot_goal_t goal;
1549 	bsp_trace_t trace;
1550 	vec3_t target;
1551 
1552 	//if the bot has no enemy
1553 	if ( bs->enemy < 0 ) {
1554 		return;
1555 	}
1556 	//
1557 	//BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy);
1558 	//
1559 	aim_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1 );
1560 	aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1 );
1561 	//
1562 	if ( aim_skill > 0.95 ) {
1563 		//don't aim too early
1564 		reactiontime = 0.5 * trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 );
1565 		if ( bs->enemysight_time > trap_AAS_Time() - reactiontime ) {
1566 			return;
1567 		}
1568 		if ( bs->teleport_time > trap_AAS_Time() - reactiontime ) {
1569 			return;
1570 		}
1571 	}
1572 
1573 	//get the weapon information
1574 	trap_BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi );
1575 	//get the weapon specific aim accuracy and or aim skill
1576 //----(SA) commented out the weapons that aren't ours.
1577 //----(SA) if we're not using this routine at all and my changes are irrelivant, please let me know.
1578 //	if (wi.number == WP_MACHINEGUN) {
1579 //		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1);
1580 //	}
1581 //	if (wi.number == WP_SHOTGUN) {
1582 //		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1);
1583 //	}
1584 	if ( wi.number == WP_GRENADE_LAUNCHER ) {
1585 		aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1 );
1586 		aim_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1 );
1587 	}
1588 	if ( wi.number == WP_FLAMETHROWER ) {
1589 		aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1 );
1590 	}
1591 //	if (wi.number == WP_RAILGUN) {
1592 //		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1);
1593 //	}
1594 	if ( wi.number == WP_SILENCER ) {
1595 		aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY_SP5, 0, 1 );
1596 		aim_skill = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_SKILL_SP5, 0, 1 );
1597 	}
1598 //	if (wi.number == WP_BFG) {
1599 //		aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1);
1600 //		aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1);
1601 //	}
1602 	//
1603 	if ( aim_accuracy <= 0 ) {
1604 		aim_accuracy = 0.0001;
1605 	}
1606 	//get the enemy entity information
1607 	BotEntityInfo( bs->enemy, &entinfo );
1608 	//if the enemy is invisible then shoot crappy most of the time
1609 	if ( EntityIsInvisible( &entinfo ) ) {
1610 		if ( random() > 0.1 ) {
1611 			aim_accuracy *= 0.4;
1612 		}
1613 	}
1614 	//
1615 	VectorSubtract( entinfo.origin, entinfo.lastvisorigin, enemyvelocity );
1616 	VectorScale( enemyvelocity, 1 / entinfo.update_time, enemyvelocity );
1617 	//enemy origin and velocity is remembered every 0.5 seconds
1618 	if ( bs->enemyposition_time < trap_AAS_Time() ) {
1619 		//
1620 		bs->enemyposition_time = trap_AAS_Time() + 0.5;
1621 		VectorCopy( enemyvelocity, bs->enemyvelocity );
1622 		VectorCopy( entinfo.origin, bs->enemyorigin );
1623 	}
1624 	//if not extremely skilled
1625 	if ( aim_skill < 0.9 ) {
1626 		VectorSubtract( entinfo.origin, bs->enemyorigin, dir );
1627 		//if the enemy moved a bit
1628 		if ( VectorLength( dir ) > 48 ) {
1629 			//if the enemy changed direction
1630 			if ( DotProduct( bs->enemyvelocity, enemyvelocity ) < 0 ) {
1631 				//aim accuracy should be worse now
1632 				aim_accuracy *= 0.7;
1633 			}
1634 		}
1635 	}
1636 	//check visibility of enemy
1637 	enemyvisible = BotEntityVisible( bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy );
1638 	//if the enemy is visible
1639 	if ( enemyvisible ) {
1640 		//
1641 		VectorCopy( entinfo.origin, bestorigin );
1642 		bestorigin[2] += 8;
1643 		//get the start point shooting from
1644 		//NOTE: the x and y projectile start offsets are ignored
1645 		VectorCopy( bs->origin, start );
1646 		start[2] += bs->cur_ps.viewheight;
1647 		start[2] += wi.offset[2];
1648 		//
1649 		BotAI_Trace( &trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT );
1650 		//if the enemy is NOT hit
1651 		if ( trace.fraction <= 1 && trace.ent != entinfo.number ) {
1652 			bestorigin[2] += 16;
1653 		}
1654 		//if it is not an instant hit weapon the bot might want to predict the enemy
1655 		if ( wi.speed ) {
1656 			//
1657 			VectorSubtract( bestorigin, bs->origin, dir );
1658 			dist = VectorLength( dir );
1659 			VectorSubtract( entinfo.origin, bs->enemyorigin, dir );
1660 			//if the enemy is NOT pretty far away and strafing just small steps left and right
1661 			if ( !( dist > 100 && VectorLength( dir ) < 32 ) ) {
1662 				//if skilled enough do exact prediction
1663 				if ( aim_skill > 0.8 &&
1664 					 //if the weapon is ready to fire
1665 					 bs->cur_ps.weaponstate == WEAPON_READY ) {
1666 					aas_clientmove_t move;
1667 					vec3_t origin;
1668 
1669 					VectorSubtract( entinfo.origin, bs->origin, dir );
1670 					//distance towards the enemy
1671 					dist = VectorLength( dir );
1672 					//direction the enemy is moving in
1673 					VectorSubtract( entinfo.origin, entinfo.lastvisorigin, dir );
1674 					//
1675 					VectorScale( dir, 1 / entinfo.update_time, dir );
1676 					//
1677 					VectorCopy( entinfo.origin, origin );
1678 					origin[2] += 1;
1679 					//
1680 					VectorClear( cmdmove );
1681 					//AAS_ClearShownDebugLines();
1682 					trap_AAS_PredictClientMovement( &move, bs->enemy, origin,
1683 													PRESENCE_CROUCH, qfalse,
1684 													dir, cmdmove, 0,
1685 													dist * 10 / wi.speed, 0.1, 0, 0, qfalse );
1686 					VectorCopy( move.endpos, bestorigin );
1687 					//BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", trap_AAS_Time(), VectorLength(dir), dist * 10 / wi.speed);
1688 				}
1689 				//if not that skilled do linear prediction
1690 				else if ( aim_skill > 0.4 ) {
1691 					VectorSubtract( entinfo.origin, bs->origin, dir );
1692 					//distance towards the enemy
1693 					dist = VectorLength( dir );
1694 					//direction the enemy is moving in
1695 					VectorSubtract( entinfo.origin, entinfo.lastvisorigin, dir );
1696 					dir[2] = 0;
1697 					//
1698 					speed = VectorNormalize( dir ) / entinfo.update_time;
1699 					//botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed);
1700 					//best spot to aim at
1701 					VectorMA( entinfo.origin, ( dist / wi.speed ) * speed, dir, bestorigin );
1702 				}
1703 			}
1704 		}
1705 		//if the projectile does radial damage
1706 		if ( aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL ) {
1707 			//if the enemy isn't standing significantly higher than the bot
1708 			if ( entinfo.origin[2] < bs->origin[2] + 16 ) {
1709 				//try to aim at the ground in front of the enemy
1710 				VectorCopy( entinfo.origin, end );
1711 				end[2] -= 64;
1712 				BotAI_Trace( &trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT );
1713 				//
1714 				VectorCopy( bestorigin, groundtarget );
1715 				if ( trace.startsolid ) {
1716 					groundtarget[2] = entinfo.origin[2] - 16;
1717 				} else { groundtarget[2] = trace.endpos[2] - 8;}
1718 				//trace a line from projectile start to ground target
1719 				BotAI_Trace( &trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT );
1720 				//if hitpoint is not vertically too far from the ground target
1721 				if ( fabs( trace.endpos[2] - groundtarget[2] ) < 50 ) {
1722 					VectorSubtract( trace.endpos, groundtarget, dir );
1723 					//if the hitpoint is near enough the ground target
1724 					if ( VectorLength( dir ) < 60 ) {
1725 						VectorSubtract( trace.endpos, start, dir );
1726 						//if the hitpoint is far enough from the bot
1727 						if ( VectorLength( dir ) > 100 ) {
1728 							//check if the bot is visible from the ground target
1729 							trace.endpos[2] += 1;
1730 							BotAI_Trace( &trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT );
1731 							if ( trace.fraction >= 1 ) {
1732 								//botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time());
1733 								VectorCopy( groundtarget, bestorigin );
1734 							}
1735 						}
1736 					}
1737 				}
1738 			}
1739 		}
1740 		bestorigin[0] += 20 * crandom() * ( 1 - aim_accuracy );
1741 		bestorigin[1] += 20 * crandom() * ( 1 - aim_accuracy );
1742 		bestorigin[2] += 10 * crandom() * ( 1 - aim_accuracy );
1743 	} else {
1744 		//
1745 		VectorCopy( bs->lastenemyorigin, bestorigin );
1746 		bestorigin[2] += 8;
1747 		//if the bot is skilled enough
1748 		if ( aim_skill > 0.5 ) {
1749 			//do prediction shots around corners
1750 //			if (wi.number == WP_BFG ||	//----(SA)	removing old weapon references
1751 			if ( wi.number == WP_GRENADE_LAUNCHER ) {
1752 				//create the chase goal
1753 				goal.entitynum = bs->client;
1754 				goal.areanum = bs->areanum;
1755 				VectorCopy( bs->eye, goal.origin );
1756 				VectorSet( goal.mins, -8, -8, -8 );
1757 				VectorSet( goal.maxs, 8, 8, 8 );
1758 				//
1759 				if ( trap_BotPredictVisiblePosition( bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target ) ) {
1760 					VectorCopy( target, bestorigin );
1761 					bestorigin[2] -= 20;
1762 				}
1763 				aim_accuracy = 1;
1764 			}
1765 		}
1766 	}
1767 	//
1768 	if ( enemyvisible ) {
1769 		BotAI_Trace( &trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT );
1770 		VectorCopy( trace.endpos, bs->aimtarget );
1771 	} else {
1772 		VectorCopy( bestorigin, bs->aimtarget );
1773 	}
1774 	//get aim direction
1775 	VectorSubtract( bestorigin, bs->eye, dir );
1776 	//
1777 	if ( wi.number == WP_FLAMETHROWER ) {
1778 //	if (wi.number == WP_MACHINEGUN ||	//----(SA)	removing old weapon references
1779 //		wi.number == WP_SHOTGUN ||
1780 //		wi.number == WP_RAILGUN) {
1781 		//distance towards the enemy
1782 		dist = VectorLength( dir );
1783 		if ( dist > 150 ) {
1784 			dist = 150;
1785 		}
1786 		f = 0.6 + dist / 150 * 0.4;
1787 		aim_accuracy *= f;
1788 	}
1789 	//add some random stuff to the aim direction depending on the aim accuracy
1790 	if ( aim_accuracy < 0.8 ) {
1791 		VectorNormalize( dir );
1792 		for ( i = 0; i < 3; i++ ) dir[i] += 0.3 * crandom() * ( 1 - aim_accuracy );
1793 	}
1794 	//set the ideal view angles
1795 	vectoangles( dir, bs->ideal_viewangles );
1796 	//take the weapon spread into account for lower skilled bots
1797 	bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * ( 1 - aim_accuracy );
1798 	bs->ideal_viewangles[PITCH] = AngleMod( bs->ideal_viewangles[PITCH] );
1799 	bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * ( 1 - aim_accuracy );
1800 	bs->ideal_viewangles[YAW] = AngleMod( bs->ideal_viewangles[YAW] );
1801 	//if the bot is really accurate and has the enemy in view for some time
1802 	if ( aim_accuracy > 0.9 && bs->enemysight_time < trap_AAS_Time() - 1 ) {
1803 		//set the view angles directly
1804 		if ( bs->ideal_viewangles[PITCH] > 180 ) {
1805 			bs->ideal_viewangles[PITCH] -= 360;
1806 		}
1807 		VectorCopy( bs->ideal_viewangles, bs->viewangles );
1808 		trap_EA_View( bs->client, bs->viewangles );
1809 	}
1810 }
1811 
1812 /*
1813 ==================
1814 BotCheckAttack
1815 ==================
1816 */
BotCheckAttack(bot_state_t * bs)1817 void BotCheckAttack( bot_state_t *bs ) {
1818 	float points, reactiontime, fov, firethrottle;
1819 	bsp_trace_t bsptrace;
1820 	//float selfpreservation;
1821 	vec3_t forward, right, start, end, dir, angles;
1822 	weaponinfo_t wi;
1823 	bsp_trace_t trace;
1824 	aas_entityinfo_t entinfo;
1825 	vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8};
1826 
1827 	if ( bs->enemy < 0 ) {
1828 		return;
1829 	}
1830 	//
1831 	reactiontime = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1 );
1832 	if ( bs->enemysight_time > trap_AAS_Time() - reactiontime ) {
1833 		return;
1834 	}
1835 	if ( bs->teleport_time > trap_AAS_Time() - reactiontime ) {
1836 		return;
1837 	}
1838 	//if changing weapons
1839 	if ( bs->weaponchange_time > trap_AAS_Time() - 0.1 ) {
1840 		return;
1841 	}
1842 	//check fire throttle characteristic
1843 	if ( bs->firethrottlewait_time > trap_AAS_Time() ) {
1844 		return;
1845 	}
1846 	firethrottle = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1 );
1847 	if ( bs->firethrottleshoot_time < trap_AAS_Time() ) {
1848 		if ( random() > firethrottle ) {
1849 			bs->firethrottlewait_time = trap_AAS_Time() + firethrottle;
1850 			bs->firethrottleshoot_time = 0;
1851 		} else {
1852 			bs->firethrottleshoot_time = trap_AAS_Time() + 1 - firethrottle;
1853 			bs->firethrottlewait_time = 0;
1854 		}
1855 	}
1856 	//
1857 	BotEntityInfo( bs->enemy, &entinfo );
1858 	VectorSubtract( entinfo.origin, bs->eye, dir );
1859 	//
1860 	if ( VectorLength( dir ) < 100 ) {
1861 		fov = 120;
1862 	} else { fov = 50;}
1863 	/*
1864 	//if the enemy isn't visible
1865 	if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, fov, bs->enemy)) {
1866 		//botimport.Print(PRT_MESSAGE, "enemy not visible\n");
1867 		return;
1868 	}*/
1869 	vectoangles( dir, angles );
1870 	if ( !InFieldOfVision( bs->viewangles, fov, angles ) ) {
1871 		return;
1872 	}
1873 	BotAI_Trace( &bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID | CONTENTS_PLAYERCLIP );
1874 	if ( bsptrace.fraction < 1 && bsptrace.ent != bs->enemy ) {
1875 		return;
1876 	}
1877 
1878 	//get the weapon info
1879 	trap_BotGetWeaponInfo( bs->ws, bs->weaponnum, &wi );
1880 	//get the start point shooting from
1881 	VectorCopy( bs->origin, start );
1882 	start[2] += bs->cur_ps.viewheight;
1883 	AngleVectors( bs->viewangles, forward, right, NULL );
1884 	start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1];
1885 	start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1];
1886 	start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2];
1887 	//end point aiming at
1888 	VectorMA( start, 1000, forward, end );
1889 	//a little back to make sure not inside a very close enemy
1890 	VectorMA( start, -12, forward, start );
1891 	BotAI_Trace( &trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT );  //----(SA) should this maybe check the weapon type and adjust the clipflag?  it seems like this is probably fine as-is, but I thought I'd note it.
1892 	//if won't hit the enemy
1893 	if ( trace.ent != bs->enemy ) {
1894 		//if the entity is a client
1895 		if ( trace.ent >= 0 && trace.ent < MAX_CLIENTS ) {
1896 			//if a teammate is hit
1897 			if ( BotSameTeam( bs, trace.ent ) ) {
1898 				return;
1899 			}
1900 		}
1901 		//if the projectile does a radial damage
1902 		if ( wi.proj.damagetype & DAMAGETYPE_RADIAL ) {
1903 			if ( trace.fraction * 1000 < wi.proj.radius ) {
1904 				points = ( wi.proj.damage - 0.5 * trace.fraction * 1000 ) * 0.5;
1905 				if ( points > 0 ) {
1906 //					selfpreservation = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_SELFPRESERVATION, 0, 1);
1907 //					if (random() < selfpreservation) return;
1908 					return;
1909 				}
1910 			}
1911 			//FIXME: check if a teammate gets radial damage
1912 		}
1913 	}
1914 	//if fire has to be release to activate weapon
1915 	if ( wi.flags & WFL_FIRERELEASED ) {
1916 		if ( bs->flags & BFL_ATTACKED ) {
1917 			trap_EA_Attack( bs->client );
1918 		}
1919 	} else {
1920 		trap_EA_Attack( bs->client );
1921 	}
1922 	bs->flags ^= BFL_ATTACKED;
1923 }
1924 
1925 /*
1926 ==================
1927 BotMapScripts
1928 ==================
1929 */
BotMapScripts(bot_state_t * bs)1930 void BotMapScripts( bot_state_t *bs ) {
1931 	char info[1024];
1932 	char mapname[128];
1933 	int i, shootbutton;
1934 	float aim_accuracy;
1935 	aas_entityinfo_t entinfo;
1936 	vec3_t dir;
1937 
1938 	trap_GetServerinfo( info, sizeof( info ) );
1939 
1940 	strncpy( mapname, Info_ValueForKey( info, "mapname" ), sizeof( mapname ) - 1 );
1941 	mapname[sizeof( mapname ) - 1] = '\0';
1942 
1943 	if (!Q_stricmp(mapname, "q3tourney6") || !Q_stricmp(mapname, "q3tourney6_ctf") || !Q_stricmp(mapname, "mpq3tourney6")) {
1944 		vec3_t mins = {694, 200, 480}, maxs = {968, 472, 680};
1945 		vec3_t buttonorg = {304, 352, 920};
1946 		//NOTE: NEVER use the func_bobbing in q3tourney6
1947 		bs->tfl &= ~TFL_FUNCBOB;
1948 		//crush area is higher in mpq3tourney6
1949 		if (!Q_stricmp(mapname, "mpq3tourney6")) {
1950 			mins[2] += 64;
1951 			maxs[2] += 64;
1952 		}
1953 		//if the bot is in the bounding box of the crush area
1954 		if ( bs->origin[0] > mins[0] && bs->origin[0] < maxs[0] ) {
1955 			if ( bs->origin[1] > mins[1] && bs->origin[1] < maxs[1] ) {
1956 				if (bs->origin[2] > mins[2] && bs->origin[2] < maxs[2]) {
1957 					return;
1958 				}
1959 			}
1960 		}
1961 		shootbutton = qfalse;
1962 		//if an enemy is in the bounding box then shoot the button
1963 		for ( i = 0; i < level.maxclients; i++ ) {
1964 
1965 			if ( i == bs->client ) {
1966 				continue;
1967 			}
1968 			//
1969 			BotEntityInfo( i, &entinfo );
1970 			//
1971 			if ( !entinfo.valid ) {
1972 				continue;
1973 			}
1974 			//if the enemy isn't dead and the enemy isn't the bot self
1975 			if ( EntityIsDead( &entinfo ) || entinfo.number == bs->entitynum ) {
1976 				continue;
1977 			}
1978 			//
1979 			if ( entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0] ) {
1980 				if ( entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1] ) {
1981 					if (entinfo.origin[2] > mins[2] && entinfo.origin[2] < maxs[2]) {
1982 						//if there's a team mate below the crusher
1983 						if ( BotSameTeam( bs, i ) ) {
1984 							shootbutton = qfalse;
1985 							break;
1986 						} else if (bs->enemy == i) {
1987 							shootbutton = qtrue;
1988 						}
1989 					}
1990 				}
1991 			}
1992 		}
1993 		if ( shootbutton ) {
1994 			bs->flags |= BFL_IDEALVIEWSET;
1995 			VectorSubtract( buttonorg, bs->eye, dir );
1996 			vectoangles( dir, bs->ideal_viewangles );
1997 			aim_accuracy = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1 );
1998 			bs->ideal_viewangles[PITCH] += 8 * crandom() * ( 1 - aim_accuracy );
1999 			bs->ideal_viewangles[PITCH] = AngleMod( bs->ideal_viewangles[PITCH] );
2000 			bs->ideal_viewangles[YAW] += 8 * crandom() * ( 1 - aim_accuracy );
2001 			bs->ideal_viewangles[YAW] = AngleMod( bs->ideal_viewangles[YAW] );
2002 			//
2003 			if ( InFieldOfVision( bs->viewangles, 20, bs->ideal_viewangles ) ) {
2004 				trap_EA_Attack( bs->client );
2005 			}
2006 		}
2007 	}
2008 }
2009 
2010 /*
2011 ==================
2012 BotCheckButtons
2013 ==================
2014 */
2015 /*
2016 void CheckButtons(void)
2017 {
2018 	int modelindex, i, numbuttons = 0;
2019 	char *classname, *model;
2020 	float lip, health, dist;
2021 	bsp_entity_t *ent;
2022 	vec3_t mins, maxs, size, origin, angles, movedir, goalorigin;
2023 	vec3_t start, end, bboxmins, bboxmaxs;
2024 	aas_trace_t trace;
2025 
2026 	for (ent = entities; ent; ent = ent->next)
2027 	{
2028 		classname = AAS_ValueForBSPEpairKey(ent, "classname");
2029 		if (!strcmp(classname, "func_button"))
2030 		{
2031 			//create a bot goal towards the button
2032 			model = AAS_ValueForBSPEpairKey(ent, "model");
2033 			modelindex = AAS_IndexFromModel(model);
2034 			//if the model is not loaded
2035 			if (!modelindex) modelindex = atoi(model+1);
2036 			VectorClear(angles);
2037 			AAS_BSPModelMinsMaxsOrigin(modelindex - 1, angles, mins, maxs, NULL);
2038 			//get the lip of the button
2039 			lip = AAS_FloatForBSPEpairKey(ent, "lip");
2040 			if (!lip) lip = 4;
2041 			//get the move direction from the angle
2042 			VectorSet(angles, 0, AAS_FloatForBSPEpairKey(ent, "angle"), 0);
2043 			AAS_SetMovedir(angles, movedir);
2044 			//button size
2045 			VectorSubtract(maxs, mins, size);
2046 			//button origin
2047 			VectorAdd(mins, maxs, origin);
2048 			VectorScale(origin, 0.5, origin);
2049 			//touch distance of the button
2050 			dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2];// - lip;
2051 			dist *= 0.5;
2052 			//
2053 			health = AAS_FloatForBSPEpairKey(ent, "health");
2054 			//if the button is shootable
2055 			if (health)
2056 			{
2057 				//calculate the goal origin
2058 				VectorMA(origin, -dist, movedir, goalorigin);
2059 				AAS_DrawPermanentCross(goalorigin, 4, LINECOLOR_BLUE);
2060 			} //end if
2061 			else
2062 			{
2063 				//add bounding box size to the dist
2064 				AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs);
2065 				for (i = 0; i < 3; i++)
2066 				{
2067 					if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]);
2068 					else dist += fabs(movedir[i]) * fabs(bboxmins[i]);
2069 				} //end for
2070 				//calculate the goal origin
2071 				VectorMA(origin, -dist, movedir, goalorigin);
2072 				//
2073 				VectorCopy(goalorigin, start);
2074 				start[2] += 24;
2075 				VectorSet(end, start[0], start[1], start[2] - 100);
2076 				trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1);
2077 				if (!trace.startsolid)
2078 				{
2079 					VectorCopy(trace.endpos, goalorigin);
2080 				} //end if
2081 				//
2082 				AAS_DrawPermanentCross(goalorigin, 4, LINECOLOR_YELLOW);
2083 				//
2084 				VectorSubtract(mins, origin, mins);
2085 				VectorSubtract(maxs, origin, maxs);
2086 				//
2087 				VectorAdd(mins, origin, start);
2088 				AAS_DrawPermanentCross(start, 4, LINECOLOR_BLUE);
2089 				VectorAdd(maxs, origin, start);
2090 				AAS_DrawPermanentCross(start, 4, LINECOLOR_BLUE);
2091 			} //end else
2092 			if (++numbuttons > 5) return;
2093 		} //end if
2094 	} //end for
2095 } //end of the function CheckButtons
2096 */
2097 
2098 /*
2099 ==================
2100 BotEntityToActivate
2101 ==================
2102 */
2103 //#define OBSTACLEDEBUG
2104 
BotEntityToActivate(int entitynum)2105 int BotEntityToActivate( int entitynum ) {
2106 	int i, ent, cur_entities[10];
2107 	char model[MAX_INFO_STRING], tmpmodel[128];
2108 	char target[128], classname[128];
2109 	float health;
2110 	char targetname[10][128];
2111 	aas_entityinfo_t entinfo;
2112 
2113 	BotEntityInfo( entitynum, &entinfo );
2114 	Com_sprintf( model, sizeof( model ), "*%d", entinfo.modelindex );
2115 	for ( ent = trap_AAS_NextBSPEntity( 0 ); ent; ent = trap_AAS_NextBSPEntity( ent ) ) {
2116 		if ( !trap_AAS_ValueForBSPEpairKey( ent, "model", tmpmodel, sizeof( tmpmodel ) ) ) {
2117 			continue;
2118 		}
2119 		if ( !strcmp( model, tmpmodel ) ) {
2120 			break;
2121 		}
2122 	}
2123 	if ( !ent ) {
2124 		BotAI_Print( PRT_ERROR, "BotEntityToActivate: no entity found with model %s\n", model );
2125 		return 0;
2126 	}
2127 	trap_AAS_ValueForBSPEpairKey( ent, "classname", classname, sizeof( classname ) );
2128 	if ( !*classname ) {
2129 		BotAI_Print( PRT_ERROR, "BotEntityToActivate: entity with model %s has no classname\n", model );
2130 		return 0;
2131 	}
2132 	//if it is a door
2133 	if ( !strcmp( classname, "func_door" ) ) {
2134 		if ( trap_AAS_FloatForBSPEpairKey( ent, "health", &health ) ) {
2135 			//if health the door must be shot to open
2136 			if ( health ) {
2137 				return ent;
2138 			}
2139 		}
2140 	}
2141 	//get the targetname so we can find an entity with a matching target
2142 	if ( !trap_AAS_ValueForBSPEpairKey( ent, "targetname", targetname[0], sizeof( targetname[0] ) ) ) {
2143 #ifdef OBSTACLEDEBUG
2144 		BotAI_Print( PRT_ERROR, "BotEntityToActivate: entity with model \"%s\" has no targetname\n", model );
2145 #endif //OBSTACLEDEBUG
2146 		return 0;
2147 	}
2148 	cur_entities[0] = trap_AAS_NextBSPEntity( 0 );
2149 	for ( i = 0; i >= 0 && i < 10; ) {
2150 		for ( ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity( ent ) ) {
2151 			if ( !trap_AAS_ValueForBSPEpairKey( ent, "target", target, sizeof( target ) ) ) {
2152 				continue;
2153 			}
2154 			if ( !strcmp( targetname[i], target ) ) {
2155 				cur_entities[i] = trap_AAS_NextBSPEntity( ent );
2156 				break;
2157 			}
2158 		}
2159 		if ( !ent ) {
2160 			BotAI_Print( PRT_ERROR, "BotEntityToActivate: no entity with target \"%s\"\n", targetname[i] );
2161 			i--;
2162 			continue;
2163 		}
2164 		if ( !trap_AAS_ValueForBSPEpairKey( ent, "classname", classname, sizeof( classname ) ) ) {
2165 			BotAI_Print( PRT_ERROR, "BotEntityToActivate: entity with target \"%s\" has no classname\n", targetname[i] );
2166 			continue;
2167 		}
2168 		if ( !strcmp( classname, "func_button" ) ) {
2169 			//BSP button model
2170 			return ent;
2171 		} else if ( !strcmp( classname, "trigger_multiple" ) )        {
2172 			//invisible trigger multiple box
2173 			return ent;
2174 		} else {
2175 			i--;
2176 		}
2177 	}
2178 	BotAI_Print( PRT_ERROR, "BotEntityToActivate: unknown activator with classname \"%s\"\n", classname );
2179 	return 0;
2180 }
2181 
2182 /*
2183 ==================
2184 BotSetMovedir
2185 ==================
2186 */
2187 vec3_t VEC_UP           = {0, -1,  0};
2188 vec3_t MOVEDIR_UP       = {0,  0,  1};
2189 vec3_t VEC_DOWN     = {0, -2,  0};
2190 vec3_t MOVEDIR_DOWN = {0,  0, -1};
2191 
BotSetMovedir(vec3_t angles,vec3_t movedir)2192 void BotSetMovedir( vec3_t angles, vec3_t movedir ) {
2193 	if ( VectorCompare( angles, VEC_UP ) ) {
2194 		VectorCopy( MOVEDIR_UP, movedir );
2195 	} else if ( VectorCompare( angles, VEC_DOWN ) )       {
2196 		VectorCopy( MOVEDIR_DOWN, movedir );
2197 	} else {
2198 		AngleVectors( angles, movedir, NULL, NULL );
2199 	}
2200 }
2201 
BotModelMinsMaxs(int modelindex,vec3_t mins,vec3_t maxs)2202 void BotModelMinsMaxs( int modelindex, vec3_t mins, vec3_t maxs ) {
2203 	gentity_t *ent;
2204 	int i;
2205 
2206 	ent = &g_entities[0];
2207 	for ( i = 0; i < level.num_entities; i++, ent++ ) {
2208 		if ( !ent->inuse ) {
2209 			continue;
2210 		}
2211 		if ( ent->s.modelindex == modelindex ) {
2212 			VectorCopy( ent->r.mins, mins );
2213 			VectorCopy( ent->r.maxs, maxs );
2214 			return;
2215 		}
2216 	}
2217 	VectorClear( mins );
2218 	VectorClear( maxs );
2219 }
2220 
2221 /*
2222 ==================
2223 BotAIBlocked
2224 ==================
2225 */
BotAIBlocked(bot_state_t * bs,bot_moveresult_t * moveresult,int activate)2226 void BotAIBlocked( bot_state_t *bs, bot_moveresult_t *moveresult, int activate ) {
2227 	int movetype, ent, i, areas[10], numareas, modelindex;
2228 	char classname[128], model[128];
2229 #ifdef OBSTACLEDEBUG
2230 	char buf[128];
2231 #endif
2232 	float lip, dist, health, angle;
2233 	vec3_t hordir, size, start, end, mins, maxs, sideward, angles;
2234 	vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs;
2235 	vec3_t up = {0, 0, 1}, extramins = {-1, -1, -1}, extramaxs = {1, 1, 1};
2236 	aas_entityinfo_t entinfo;
2237 /*
2238 	bsp_trace_t bsptrace;
2239 */
2240 #ifdef OBSTACLEDEBUG
2241 	char netname[MAX_NETNAME];
2242 #endif
2243 
2244 	if ( !moveresult->blocked ) {
2245 		return;
2246 	}
2247 	//
2248 	BotEntityInfo( moveresult->blockentity, &entinfo );
2249 #ifdef OBSTACLEDEBUG
2250 	ClientName( bs->client, netname, sizeof( netname ) );
2251 	BotAI_Print( PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex );
2252 #endif //OBSTACLEDEBUG
2253 	   //if blocked by a bsp model and the bot wants to activate it if possible
2254 	if ( entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex && activate ) {
2255 		//find the bsp entity which should be activated in order to remove
2256 		//the blocking entity
2257 		ent = BotEntityToActivate( entinfo.number );
2258 		if ( !ent ) {
2259 			strcpy( classname, "" );
2260 #ifdef OBSTACLEDEBUG
2261 			BotAI_Print( PRT_MESSAGE, "%s: can't find activator for blocking entity\n", ClientName( bs->client, netname, sizeof( netname ) ) );
2262 #endif //OBSTACLEDEBUG
2263 		} else {
2264 			trap_AAS_ValueForBSPEpairKey( ent, "classname", classname, sizeof( classname ) );
2265 #ifdef OBSTACLEDEBUG
2266 			ClientName( bs->client, netname, sizeof( netname ) );
2267 			BotAI_Print( PRT_MESSAGE, "%s: I should activate %s\n", netname, classname );
2268 #endif //OBSTACLEDEBUG
2269 		}
2270 #ifdef OBSTACLEDEBUG
2271 //		ClientName(bs->client, netname, sizeof(netname));
2272 //		BotAI_Print(PRT_MESSAGE, "%s: I've got no brain cells for activating entities\n", netname);
2273 #endif //OBSTACLEDEBUG
2274 	   /*
2275 	   //the bot should now activate one of the following entities
2276 	   //"func_button", "trigger_multiple", "func_door"
2277 	   //all these activators use BSP models, so it should be a matter of
2278 	   //finding where this model is located using AAS and then activating
2279 	   //by walking against the model it or shooting at it
2280 	   //
2281 	   //if it is a door we should shoot at
2282 	   if (!strcmp(classname, "func_door"))
2283 	   {
2284 		   //get the door model
2285 		   model = AAS_ValueForBSPEpairKey(ent, "model");
2286 		   modelindex = AAS_IndexFromModel(model);
2287 		   //if the model is not loaded
2288 		   if (!modelindex) return;
2289 		   VectorClear(angles);
2290 		   AAS_BSPModelMinsMaxsOrigin(modelindex - 1, angles, mins, maxs, NULL);
2291 		   //get a goal to shoot at
2292 		   VectorAdd(maxs, mins, goalorigin);
2293 		   VectorScale(goalorigin, 0.5, goalorigin);
2294 		   VectorSubtract(goalorigin, bs->origin, movedir);
2295 		   //
2296 		   vectoangles(movedir, moveresult->ideal_viewangles);
2297 		   moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
2298 		   //select the blaster
2299 		   EA_UseItem(bs->client, "Blaster");
2300 		   //shoot
2301 		   EA_Attack(bs->client);
2302 		   //
2303 		   return;
2304 	   } //end if*/
2305 		if ( !strcmp( classname, "func_button" ) ) {
2306 			//create a bot goal towards the button
2307 			trap_AAS_ValueForBSPEpairKey( ent, "model", model, sizeof( model ) );
2308 			modelindex = atoi( model + 1 );
2309 			//if the model is not loaded
2310 			if ( !modelindex ) {
2311 				return;
2312 			}
2313 			VectorClear( angles );
2314 			BotModelMinsMaxs( modelindex, mins, maxs );
2315 			//get the lip of the button
2316 			trap_AAS_FloatForBSPEpairKey( ent, "lip", &lip );
2317 			if ( !lip ) {
2318 				lip = 4;
2319 			}
2320 			//get the move direction from the angle
2321 			trap_AAS_FloatForBSPEpairKey( ent, "angle", &angle );
2322 			VectorSet( angles, 0, angle, 0 );
2323 			BotSetMovedir( angles, movedir );
2324 			//button size
2325 			VectorSubtract( maxs, mins, size );
2326 			//button origin
2327 			VectorAdd( mins, maxs, origin );
2328 			VectorScale( origin, 0.5, origin );
2329 			//touch distance of the button
2330 			dist = fabs( movedir[0] ) * size[0] + fabs( movedir[1] ) * size[1] + fabs( movedir[2] ) * size[2];
2331 			dist *= 0.5;
2332 			//
2333 			trap_AAS_FloatForBSPEpairKey( ent, "health", &health );
2334 			//if the button is shootable
2335 			if ( health ) {
2336 				//calculate the goal origin
2337 				VectorMA( origin, -dist, movedir, goalorigin );
2338 				//
2339 				//AAS_ClearShownDebugLines();
2340 				//AAS_DrawArrow(bs->origin, goalorigin, LINECOLOR_BLUE, LINECOLOR_YELLOW);
2341 				//
2342 				VectorSubtract( goalorigin, bs->origin, movedir );
2343 				vectoangles( movedir, moveresult->ideal_viewangles );
2344 				moveresult->flags |= MOVERESULT_MOVEMENTVIEW;
2345 				//select the blaster
2346 				trap_EA_SelectWeapon( bs->client, WEAPONINDEX_MACHINEGUN );
2347 				//shoot
2348 				trap_EA_Attack( bs->client );
2349 				return;
2350 			} //end if
2351 			else
2352 			{
2353 				//add bounding box size to the dist
2354 				trap_AAS_PresenceTypeBoundingBox( PRESENCE_CROUCH, bboxmins, bboxmaxs );
2355 				for ( i = 0; i < 3; i++ )
2356 				{
2357 					if ( movedir[i] < 0 ) {
2358 						dist += fabs( movedir[i] ) * fabs( bboxmaxs[i] );
2359 					} else { dist += fabs( movedir[i] ) * fabs( bboxmins[i] );}
2360 				} //end for
2361 				  //calculate the goal origin
2362 				VectorMA( origin, -dist, movedir, goalorigin );
2363 				//
2364 				VectorCopy( goalorigin, start );
2365 				start[2] += 24;
2366 				VectorCopy( start, end );
2367 				end[2] -= 100;
2368 				numareas = trap_AAS_TraceAreas( start, end, areas, NULL, 10 );
2369 				//
2370 				for ( i = 0; i < numareas; i++ ) {
2371 					if ( trap_AAS_AreaReachability( areas[i] ) ) {
2372 						break;
2373 					}
2374 				}
2375 				if ( i < numareas ) {
2376 					//
2377 #ifdef OBSTACLEDEBUG
2378 					if ( bs->activatemessage_time < trap_AAS_Time() ) {
2379 						Com_sprintf( buf, sizeof( buf ), "I have to activate a button at %1.1f %1.1f %1.1f in area %d\n",
2380 									 goalorigin[0], goalorigin[1], goalorigin[2], areas[i] );
2381 						trap_EA_Say( bs->client, buf );
2382 						bs->activatemessage_time = trap_AAS_Time() + 5;
2383 					} //end if
2384 #endif //OBSTACLEDEBUG
2385 					//
2386 					//VectorMA(origin, -dist, movedir, goalorigin);
2387 					//
2388 					VectorCopy( origin, bs->activategoal.origin );
2389 					bs->activategoal.areanum = areas[i];
2390 					VectorSubtract( mins, origin, bs->activategoal.mins );
2391 					VectorSubtract( maxs, origin, bs->activategoal.maxs );
2392 					//
2393 					VectorAdd( bs->activategoal.mins, extramins, bs->activategoal.mins );
2394 					VectorAdd( bs->activategoal.maxs, extramaxs, bs->activategoal.maxs );
2395 					//
2396 					bs->activategoal.entitynum = entinfo.number;
2397 					bs->activategoal.number = 0;
2398 					bs->activategoal.flags = 0;
2399 					bs->activate_time = trap_AAS_Time() + 10;
2400 					AIEnter_Seek_ActivateEntity( bs );
2401 				} //end if
2402 				else
2403 				{
2404 #ifdef OBSTACLEDEBUG
2405 					BotAI_Print( PRT_MESSAGE, "button area has no reachabilities\n" );
2406 #endif //OBSTACLEDEBUG
2407 					if ( bs->ainode == AINode_Seek_NBG ) {
2408 						bs->nbg_time = 0;
2409 					} else if ( bs->ainode == AINode_Seek_LTG ) {
2410 						bs->ltg_time = 0;
2411 					}
2412 				} //end else
2413 			} //end else
2414 		} //end if
2415 		  /*
2416 		  if (!strcmp(classname, "trigger_multiple"))
2417 		  {
2418 			  //create a bot goal towards the trigger
2419 			  model = AAS_ValueForBSPEpairKey(ent, "model");
2420 			  modelindex = AAS_IndexFromModel(model);
2421 			  //if the model is not precached (bad thing but happens) assume model is "*X"
2422 			  if (!modelindex) modelindex = atoi(model+1);
2423 			  VectorClear(angles);
2424 			  AAS_BSPModelMinsMaxsOrigin(modelindex - 1, angles, mins, maxs, NULL);
2425 			  VectorAdd(mins, maxs, mid);
2426 			  VectorScale(mid, 0.5, mid);
2427 			  VectorCopy(mid, start);
2428 			  start[2] = maxs[2] + 24;
2429 			  VectorSet(end, start[0], start[1], start[2] - 100);
2430 			  trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1);
2431 			  if (trace.startsolid) return;
2432 			  //trace.endpos is now the goal origin
2433 			  VectorCopy(trace.endpos, goalorigin);
2434 			  //
2435   #ifdef OBSTACLEDEBUG
2436 			  if (bs->activatemessage_time < AAS_Time())
2437 			  {
2438 				  Com_sprintf(buf, sizeof(buf), "I have to activate a trigger at %1.1f %1.1f %1.1f in area %d\n",
2439 								  goalorigin[0], goalorigin[1], goalorigin[2], AAS_PointAreaNum(goalorigin));
2440 				  EA_Say(bs->client, buf);
2441 				  bs->activatemessage_time = AAS_Time() + 5;
2442 			  } //end if* /
2443   #endif //OBSTACLEDEBUG
2444 			  //
2445 			  VectorCopy(mid, bs->activategoal.origin);
2446 			  bs->activategoal.areanum = AAS_PointAreaNum(goalorigin);
2447 			  VectorSubtract(mins, mid, bs->activategoal.mins);
2448 			  VectorSubtract(maxs, mid, bs->activategoal.maxs);
2449 			  bs->activategoal.entitynum = entinfo.number;
2450 			  bs->activategoal.number = 0;
2451 			  bs->activategoal.flags = 0;
2452 			  bs->activate_time = AAS_Time() + 10;
2453 			  if (!AAS_AreaReachability(bs->activategoal.areanum))
2454 			  {
2455   #ifdef OBSTACLEDEBUG
2456 				  botimport.Print(PRT_MESSAGE, "trigger area has no reachabilities\n");
2457   #endif //OBSTACLEDEBUG
2458 				  if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0;
2459 				  else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0;
2460 			  } //end if
2461 			  else
2462 			  {
2463 				  AIEnter_Seek_ActivateEntity(bs);
2464 			  } //end else
2465 			  return;
2466 		  } //end if*/
2467 	}
2468 	//just some basic dynamic obstacle avoidance code
2469 	hordir[0] = moveresult->movedir[0];
2470 	hordir[1] = moveresult->movedir[1];
2471 	hordir[2] = 0;
2472 	//if no direction just take a random direction
2473 	if ( VectorNormalize( hordir ) < 0.1 ) {
2474 		VectorSet( angles, 0, 360 * random(), 0 );
2475 		AngleVectors( angles, hordir, NULL, NULL );
2476 	}
2477 	//
2478 //	if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP;
2479 //	else
2480 	movetype = MOVE_WALK;
2481 	//if there's an obstacle at the bot's feet and head then
2482 	//the bot might be able to crouch through
2483 	//VectorCopy( bs->origin, start );
2484 	//start[2] += 18;
2485 	//VectorMA( start, 5, hordir, end );
2486 	//VectorSet( mins, -16, -16, -24 );
2487 	//VectorSet( maxs, 16, 16, 4 );
2488 	//
2489 //	bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID);
2490 //	if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH;
2491 	//get the sideward vector
2492 	CrossProduct( hordir, up, sideward );
2493 	//
2494 	if ( bs->flags & BFL_AVOIDRIGHT ) {
2495 		VectorNegate( sideward, sideward );
2496 	}
2497 	//try to crouch straight forward?
2498 	if ( movetype != MOVE_CROUCH || !trap_BotMoveInDirection( bs->ms, hordir, 400, movetype ) ) {
2499 		//perform the movement
2500 		if ( !trap_BotMoveInDirection( bs->ms, sideward, 400, movetype ) ) {
2501 			//flip the avoid direction flag
2502 			bs->flags ^= BFL_AVOIDRIGHT;
2503 			//flip the direction
2504 			VectorNegate( sideward, sideward );
2505 			//move in the other direction
2506 			trap_BotMoveInDirection( bs->ms, sideward, 400, movetype );
2507 		}
2508 	}
2509 	//just reset goals and hope the bot will go into another direction
2510 	//still needed??
2511 	if ( bs->ainode == AINode_Seek_NBG ) {
2512 		bs->nbg_time = 0;
2513 	} else if ( bs->ainode == AINode_Seek_LTG ) {
2514 		bs->ltg_time = 0;
2515 	}
2516 }
2517 
2518 /*
2519 ==================
2520 BotCheckConsoleMessages
2521 ==================
2522 */
BotCheckConsoleMessages(bot_state_t * bs)2523 void BotCheckConsoleMessages( bot_state_t *bs ) {
2524 	char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME];
2525 	float chat_reply;
2526 	int context, handle;
2527 	bot_consolemessage_t m;
2528 	bot_match_t match;
2529 
2530 	//the name of this bot
2531 	ClientName( bs->client, botname, sizeof( botname ) );
2532 	//
2533 	while ( ( handle = trap_BotNextConsoleMessage( bs->cs, &m ) ) != 0 ) {
2534 		//if the chat state is flooded with messages the bot will read them quickly
2535 		if ( trap_BotNumConsoleMessages( bs->cs ) < 10 ) {
2536 			//if it is a chat message the bot needs some time to read it
2537 			if ( m.type == CMS_CHAT && m.time > trap_AAS_Time() - ( 1 + random() ) ) {
2538 				break;
2539 			}
2540 		}
2541 		//unify the white spaces in the message
2542 		trap_UnifyWhiteSpaces( m.message );
2543 		//replace synonyms in the right context
2544 		context = CONTEXT_NORMAL | CONTEXT_NEARBYITEM | CONTEXT_NAMES;
2545 		if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) {
2546 			context |= CONTEXT_CTFREDTEAM;
2547 		} else { context |= CONTEXT_CTFBLUETEAM;}
2548 		trap_BotReplaceSynonyms( m.message, context );
2549 		//if there's no match
2550 		if ( !BotMatchMessage( bs, m.message ) ) {
2551 			//if it is a chat message
2552 			if ( m.type == CMS_CHAT && !bot_nochat.integer ) {
2553 				//
2554 				if ( !trap_BotFindMatch( m.message, &match, MTCONTEXT_REPLYCHAT ) ) {
2555 					trap_BotRemoveConsoleMessage( bs->cs, handle );
2556 					continue;
2557 				}
2558 				//don't use eliza chats with team messages
2559 				if ( match.subtype & ST_TEAM ) {
2560 					trap_BotRemoveConsoleMessage( bs->cs, handle );
2561 					continue;
2562 				}
2563 				//
2564 				trap_BotMatchVariable( &match, NETNAME, netname, sizeof( netname ) );
2565 				trap_BotMatchVariable( &match, MESSAGE, message, sizeof( message ) );
2566 				//if this is a message from the bot self
2567 				if ( !Q_stricmp( netname, botname ) ) {
2568 					trap_BotRemoveConsoleMessage( bs->cs, handle );
2569 					continue;
2570 				}
2571 				//unify the message
2572 				trap_UnifyWhiteSpaces( message );
2573 				//
2574 				trap_Cvar_Update( &bot_testrchat );
2575 				if ( bot_testrchat.integer ) {
2576 					//
2577 					trap_BotLibVarSet( "bot_testrchat", "1" );
2578 					//if bot replies with a chat message
2579 					if ( trap_BotReplyChat( bs->cs, message, context, CONTEXT_REPLY,
2580 											NULL, NULL,
2581 											NULL, NULL,
2582 											NULL, NULL,
2583 											botname, netname ) ) {
2584 						BotAI_Print( PRT_MESSAGE, "------------------------\n" );
2585 					} else {
2586 						BotAI_Print( PRT_MESSAGE, "**** no valid reply ****\n" );
2587 					}
2588 				}
2589 				//if at a valid chat position and not chatting already
2590 				else if ( bs->ainode != AINode_Stand && BotValidChatPosition( bs ) ) {
2591 					chat_reply = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1 );
2592 					if ( random() < 1.5 / ( NumBots() + 1 ) && random() < chat_reply ) {
2593 						//if bot replies with a chat message
2594 						if ( trap_BotReplyChat( bs->cs, message, context, CONTEXT_REPLY,
2595 												NULL, NULL,
2596 												NULL, NULL,
2597 												NULL, NULL,
2598 												botname, netname ) ) {
2599 							//remove the console message
2600 							trap_BotRemoveConsoleMessage( bs->cs, handle );
2601 							bs->stand_time = trap_AAS_Time() + BotChatTime( bs );
2602 							AIEnter_Stand( bs );
2603 							//EA_Say(bs->client, bs->cs.chatmessage);
2604 							break;
2605 						}
2606 					}
2607 				}
2608 			}
2609 		}
2610 		//remove the console message
2611 		trap_BotRemoveConsoleMessage( bs->cs, handle );
2612 	}
2613 }
2614 
2615 /*
2616 ==================
2617 BotCheckEvents
2618 ==================
2619 */
BotCheckEvents(bot_state_t * bs,entityState_t * state)2620 void BotCheckEvents( bot_state_t *bs, entityState_t *state ) {
2621 	int event;
2622 	char buf[128];
2623 	//
2624 	//this sucks, we're accessing the gentity_t directly but there's no other fast way
2625 	//to do it right now
2626 	if ( bs->entityeventTime[state->number] == g_entities[state->number].eventTime ) {
2627 		return;
2628 	}
2629 	bs->entityeventTime[state->number] = g_entities[state->number].eventTime;
2630 	//if it's an event only entity
2631 	if ( state->eType > ET_EVENTS ) {
2632 		event = ( state->eType - ET_EVENTS ) & ~EV_EVENT_BITS;
2633 	} else {
2634 		event = state->event & ~EV_EVENT_BITS;
2635 	}
2636 	//
2637 	switch ( event ) {
2638 		//client obituary event
2639 	case EV_OBITUARY:
2640 	{
2641 		int target, attacker, mod;
2642 
2643 		target = state->otherEntityNum;
2644 		attacker = state->otherEntityNum2;
2645 		mod = state->eventParm;
2646 		//
2647 		if ( target == bs->client ) {
2648 			bs->botdeathtype = mod;
2649 			bs->lastkilledby = attacker;
2650 			//
2651 			if ( target == attacker ) {
2652 				bs->botsuicide = qtrue;
2653 			} else { bs->botsuicide = qfalse;}
2654 			//
2655 			bs->num_deaths++;
2656 		}
2657 		//else if this client was killed by the bot
2658 		else if ( attacker == bs->client ) {
2659 			bs->enemydeathtype = mod;
2660 			bs->lastkilledplayer = target;
2661 			bs->killedenemy_time = trap_AAS_Time();
2662 			//
2663 			bs->num_kills++;
2664 		} else if ( attacker == bs->enemy && target == attacker )     {
2665 			bs->enemysuicide = qtrue;
2666 		}
2667 		break;
2668 	}
2669 	case EV_GLOBAL_SOUND:
2670 	{
2671 		if ( state->eventParm < 0 || state->eventParm >= MAX_SOUNDS ) {
2672 			BotAI_Print( PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm );
2673 			break;
2674 		}
2675 		trap_GetConfigstring( CS_SOUNDS + state->eventParm, buf, sizeof( buf ) );
2676 		if ( !strcmp( buf, "sound/teamplay/flagret_red.wav" ) ) {
2677 			//red flag is returned
2678 			bs->redflagstatus = 0;
2679 			bs->flagstatuschanged = qtrue;
2680 		} else if ( !strcmp( buf, "sound/teamplay/flagret_blu.wav" ) )        {
2681 			//blue flag is returned
2682 			bs->blueflagstatus = 0;
2683 			bs->flagstatuschanged = qtrue;
2684 		} else if ( !strcmp( buf, "sound/items/poweruprespawn.wav" ) )        {
2685 			//powerup respawned... go get it
2686 			BotGoForPowerups( bs );
2687 		}
2688 		break;
2689 	}
2690 	case EV_PLAYER_TELEPORT_IN:
2691 	{
2692 		VectorCopy( state->origin, lastteleport_origin );
2693 		lastteleport_time = trap_AAS_Time();
2694 		break;
2695 	}
2696 	case EV_GENERAL_SOUND:
2697 	{
2698 		//if this sound is played on the bot
2699 		if ( state->number == bs->client ) {
2700 			if ( state->eventParm < 0 || state->eventParm >= MAX_SOUNDS ) {
2701 				BotAI_Print( PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm );
2702 				break;
2703 			}
2704 			//check out the sound
2705 			trap_GetConfigstring( CS_SOUNDS + state->eventParm, buf, sizeof( buf ) );
2706 			//if falling into a death pit
2707 			if ( !strcmp( buf, "*falling1.wav" ) ) {
2708 				//if the bot has a personal teleporter
2709 				if ( bs->inventory[INVENTORY_TELEPORTER] > 0 ) {
2710 					//use the holdable item
2711 					trap_EA_Use( bs->client );
2712 				}
2713 			}
2714 		}
2715 		break;
2716 	}
2717 	}
2718 }
2719 
2720 /*
2721 ==================
2722 BotCheckSnapshot
2723 ==================
2724 */
BotCheckSnapshot(bot_state_t * bs)2725 void BotCheckSnapshot( bot_state_t *bs ) {
2726 	int ent;
2727 	entityState_t state;
2728 
2729 	//
2730 	ent = 0;
2731 	while ( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) {
2732 		//check the entity state for events
2733 		BotCheckEvents( bs, &state );
2734 	}
2735 	//check the player state for events
2736 	BotAI_GetEntityState( bs->client, &state );
2737 	//copy the player state events to the entity state
2738 	//state.event = bs->cur_ps.externalEvent;
2739 	//state.eventParm = bs->cur_ps.externalEventParm;
2740 	//
2741 	BotCheckEvents( bs, &state );
2742 }
2743 
2744 /*
2745 ==================
2746 BotCheckAir
2747 ==================
2748 */
BotCheckAir(bot_state_t * bs)2749 void BotCheckAir( bot_state_t *bs ) {
2750 	if ( bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0 ) {
2751 		if ( trap_AAS_PointContents( bs->eye ) & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) {
2752 			return;
2753 		}
2754 	}
2755 	bs->lastair_time = trap_AAS_Time();
2756 }
2757 
2758 /*
2759 ==================
2760 BotDeathmatchAI
2761 ==================
2762 */
BotDeathmatchAI(bot_state_t * bs,float thinktime)2763 void BotDeathmatchAI( bot_state_t *bs, float thinktime ) {
2764 	char gender[144], name[144];
2765 	char userinfo[MAX_INFO_STRING];
2766 	int i;
2767 
2768 	//if the bot has just been setup
2769 	if ( bs->setupcount > 0 ) {
2770 		bs->setupcount--;
2771 		if ( bs->setupcount > 0 ) {
2772 			return;
2773 		}
2774 		//get the gender characteristic
2775 		trap_Characteristic_String( bs->character, CHARACTERISTIC_GENDER, gender, sizeof( gender ) );
2776 		//set the bot gender
2777 		trap_GetUserinfo( bs->client, userinfo, sizeof( userinfo ) );
2778 		Info_SetValueForKey( userinfo, "sex", gender );
2779 		trap_SetUserinfo( bs->client, userinfo );
2780 		//set the chat gender
2781 		if ( gender[0] == 'm' ) {
2782 			trap_BotSetChatGender( bs->cs, CHAT_GENDERMALE );
2783 		} else if ( gender[0] == 'f' ) {
2784 			trap_BotSetChatGender( bs->cs, CHAT_GENDERFEMALE );
2785 		} else { trap_BotSetChatGender( bs->cs, CHAT_GENDERLESS );}
2786 		//set the chat name
2787 		ClientName( bs->client, name, sizeof( name ) );
2788 		trap_BotSetChatName( bs->cs, name );
2789 		//
2790 		bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
2791 		bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
2792 		//
2793 		bs->setupcount = 0;
2794 	}
2795 	//no ideal view set
2796 	bs->flags &= ~BFL_IDEALVIEWSET;
2797 	//set the teleport time
2798 	BotSetTeleportTime( bs );
2799 	//update some inventory values
2800 	BotUpdateInventory( bs );
2801 	//check the console messages
2802 	BotCheckConsoleMessages( bs );
2803 	//check out the snapshot
2804 	BotCheckSnapshot( bs );
2805 	//check for air
2806 	BotCheckAir( bs );
2807 	//if not in the intermission and not in observer mode
2808 	if ( !BotIntermission( bs ) && !BotIsObserver( bs ) ) {
2809 		//do team AI
2810 		BotTeamAI( bs );
2811 	}
2812 	//if the bot has no ai node
2813 	if ( !bs->ainode ) {
2814 		AIEnter_Seek_LTG( bs );
2815 	}
2816 	//if the bot entered the game less than 8 seconds ago
2817 	if ( !bs->entergamechat && bs->entergame_time > trap_AAS_Time() - 8 ) {
2818 		if ( BotChat_EnterGame( bs ) ) {
2819 			bs->stand_time = trap_AAS_Time() + BotChatTime( bs );
2820 			AIEnter_Stand( bs );
2821 		}
2822 		bs->entergamechat = qtrue;
2823 	}
2824 	//reset the node switches from the previous frame
2825 	BotResetNodeSwitches();
2826 	//execute AI nodes
2827 	for ( i = 0; i < MAX_NODESWITCHES; i++ ) {
2828 		if ( bs->ainode( bs ) ) {
2829 			break;
2830 		}
2831 	}
2832 	//if the bot removed itself :)
2833 	if ( !bs->inuse ) {
2834 		return;
2835 	}
2836 	//if the bot executed too many AI nodes
2837 	if ( i >= MAX_NODESWITCHES ) {
2838 		trap_BotDumpGoalStack( bs->gs );
2839 		trap_BotDumpAvoidGoals( bs->gs );
2840 		BotDumpNodeSwitches( bs );
2841 		ClientName( bs->client, name, sizeof( name ) );
2842 		BotAI_Print( PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, trap_AAS_Time(), MAX_NODESWITCHES );
2843 	}
2844 	//
2845 	bs->lastframe_health = bs->inventory[INVENTORY_HEALTH];
2846 	bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS];
2847 }
2848 
2849 /*
2850 ==================
2851 BotSetupDeathmatchAI
2852 ==================
2853 */
BotSetupDeathmatchAI(void)2854 void BotSetupDeathmatchAI( void ) {
2855 	int ent, modelnum;
2856 	char model[128];
2857 
2858 	gametype = trap_Cvar_VariableIntegerValue( "g_gametype" );
2859 
2860 	// Rafael gameskill
2861 	gameskill = trap_Cvar_VariableIntegerValue( "g_gameskill" );
2862 	// done
2863 
2864 	trap_Cvar_Register( &bot_rocketjump, "bot_rocketjump", "1", 0 );
2865 	trap_Cvar_Register( &bot_grapple, "bot_grapple", "0", 0 );
2866 	trap_Cvar_Register( &bot_fastchat, "bot_fastchat", "0", 0 );
2867 	trap_Cvar_Register( &bot_nochat, "bot_nochat", "0", 0 );
2868 	trap_Cvar_Register( &bot_testrchat, "bot_testrchat", "0", 0 );
2869 	//
2870 	if ( gametype == GT_CTF ) {
2871 		if ( trap_BotGetLevelItemGoal( -1, "Red Flag", &ctf_redflag ) < 0 ) {
2872 			BotAI_Print( PRT_WARNING, "One Flag CTF without Red Flag\n" );
2873 		}
2874 		if ( trap_BotGetLevelItemGoal( -1, "Blue Flag", &ctf_blueflag ) < 0 ) {
2875 			BotAI_Print( PRT_WARNING, "One Flag CTF without Blue Flag\n" );
2876 		}
2877 	}
2878 
2879 	max_bspmodelindex = 0;
2880 	for ( ent = trap_AAS_NextBSPEntity( 0 ); ent; ent = trap_AAS_NextBSPEntity( ent ) ) {
2881 		if ( !trap_AAS_ValueForBSPEpairKey( ent, "model", model, sizeof( model ) ) ) {
2882 			continue;
2883 		}
2884 		if ( model[0] == '*' ) {
2885 			modelnum = atoi( model + 1 );
2886 			if ( modelnum > max_bspmodelindex ) {
2887 				max_bspmodelindex = modelnum;
2888 			}
2889 		}
2890 	}
2891 	//initialize the waypoint heap
2892 	BotInitWaypoints();
2893 }
2894 
2895 /*
2896 ==================
2897 BotShutdownDeathmatchAI
2898 ==================
2899 */
BotShutdownDeathmatchAI(void)2900 void BotShutdownDeathmatchAI( void ) {
2901 }
2902