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