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