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