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