2
===========================================================================
3
Copyright (C) 1999-2005 Id Software, Inc.
5
This file is part of Quake III Arena source code.
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.
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.
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
===========================================================================
24
/*****************************************************************************
29
* $Archive: /MissionPack/code/game/ai_dmnet.c $
31
*****************************************************************************/
34
#include "../botlib/botlib.h"
35
#include "../botlib/be_aas.h"
36
#include "../botlib/be_ea.h"
37
#include "../botlib/be_ai_char.h"
38
#include "../botlib/be_ai_chat.h"
39
#include "../botlib/be_ai_gen.h"
40
#include "../botlib/be_ai_goal.h"
41
#include "../botlib/be_ai_move.h"
42
#include "../botlib/be_ai_weap.h"
51
#include "chars.h" //characteristics
52
#include "inv.h" //indexes into the inventory
53
#include "syn.h" //synonyms
54
#include "match.h" //string matching types and vars
56
// for the voice chats
57
#include "../../ui/menudef.h"
59
//goal flag, see ../botlib/be_ai_goal.h for the other GFL_*
63
char nodeswitch[MAX_NODESWITCHES+1][144];
65
#define LOOKAHEAD_DISTANCE 300
72
void BotResetNodeSwitches(void) {
81
void BotDumpNodeSwitches(bot_state_t *bs) {
83
char netname[MAX_NETNAME];
85
ClientName(bs->client, netname, sizeof(netname));
86
BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES);
87
for (i = 0; i < numnodeswitches; i++) {
88
BotAI_Print(PRT_MESSAGE, nodeswitch[i]);
90
BotAI_Print(PRT_FATAL, "");
98
void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) {
99
char netname[MAX_NETNAME];
101
ClientName(bs->client, netname, sizeof(netname));
102
Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s);
105
BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]);
116
int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) {
117
bsp_trace_t bsptrace;
118
vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2};
121
//trace up until we hit solid
122
VectorCopy(bs->origin, end);
124
BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP);
125
//trace down until we hit water
126
VectorCopy(bsptrace.endpos, end);
127
BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA);
128
//if we found the water surface
129
if (bsptrace.fraction > 0) {
130
areanum = BotPointAreaNum(bsptrace.endpos);
132
VectorCopy(bsptrace.endpos, goal->origin);
133
goal->origin[2] -= 2;
134
goal->areanum = areanum;
141
goal->flags = GFL_AIR;
156
int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) {
159
//if the bot needs air
160
if (bs->lastair_time < FloatTime() - 6) {
163
//BotAI_Print(PRT_MESSAGE, "going for air\n");
165
//if we can find an air goal
166
if (BotGetAirGoal(bs, &goal)) {
167
trap_BotPushGoal(bs->gs, &goal);
171
//get a nearby goal outside the water
172
while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) {
173
trap_BotGetTopGoal(bs->gs, &goal);
174
//if the goal is not in water
175
if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) {
178
trap_BotPopGoal(bs->gs);
180
trap_BotResetAvoidGoals(bs->gs);
191
int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) {
194
//check if the bot should go for air
195
if (BotGoForAir(bs, tfl, ltg, range)) return qtrue;
196
//if the bot is carrying the enemy flag
197
if (BotCTFCarryingFlag(bs)) {
198
//if the bot is just a few secs away from the base
199
if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
200
bs->teamgoal.areanum, TFL_DEFAULT) < 300) {
201
//make the range really small
206
ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range);
211
//get the goal at the top of the stack
212
trap_BotGetTopGoal(bs->gs, &goal);
213
trap_BotGoalName(goal.number, buf, sizeof(buf));
214
BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf);
225
int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) {
226
if (goal->flags & GFL_ITEM) {
227
//if touching the goal
228
if (trap_BotTouchingGoal(bs->origin, goal)) {
229
if (!(goal->flags & GFL_DROPPED)) {
230
trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1);
234
//if the goal isn't there
235
if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) {
240
avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number);
242
t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl);
243
if ((float) t * 0.009 < avoidtime)
249
//if in the goal area and below or above the goal and not swimming
250
if (bs->areanum == goal->areanum) {
251
if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) {
252
if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) {
253
if (!trap_AAS_Swimming(bs->origin)) {
260
else if (goal->flags & GFL_AIR) {
261
//if touching the goal
262
if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
264
if (bs->lastair_time > FloatTime() - 1) return qtrue;
267
//if touching the goal
268
if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
275
BotGetItemLongTermGoal
278
int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) {
279
//if the bot has no goal
280
if (!trap_BotGetTopGoal(bs->gs, goal)) {
281
//BotAI_Print(PRT_MESSAGE, "no ltg on stack\n");
284
//if the bot touches the current goal
285
else if (BotReachedGoal(bs, goal)) {
289
//if it is time to find a new long term goal
290
if (bs->ltg_time < FloatTime()) {
291
//pop the current goal from the stack
292
trap_BotPopGoal(bs->gs);
293
//BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname)));
295
//BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client);
296
if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) {
299
//get the goal at the top of the stack
300
trap_BotGetTopGoal(bs->gs, goal);
301
trap_BotGoalName(goal->number, buf, sizeof(buf));
302
BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf);
304
bs->ltg_time = FloatTime() + 20;
306
else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though
311
BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname)));
313
//trap_BotDumpAvoidGoals(bs->gs);
314
//reset the avoid goals and the avoid reach
315
trap_BotResetAvoidGoals(bs->gs);
316
trap_BotResetAvoidReach(bs->ms);
318
//get the goal at the top of the stack
319
return trap_BotGetTopGoal(bs->gs, goal);
328
we could also create a seperate AI node for every long term goal type
329
however this saves us a lot of code
332
int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) {
333
vec3_t target, dir, dir2;
334
char netname[MAX_NETNAME];
335
char buf[MAX_MESSAGE_SIZE];
338
aas_entityinfo_t entinfo, botinfo;
341
if (bs->ltgtype == LTG_TEAMHELP && !retreat) {
342
//check for bot typing status message
343
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
344
BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
345
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
346
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
347
trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
348
bs->teammessage_time = 0;
350
//if trying to help the team mate for more than a minute
351
if (bs->teamgoal_time < FloatTime())
353
//if the team mate IS visible for quite some time
354
if (bs->teammatevisible_time < FloatTime() - 10) bs->ltgtype = 0;
355
//get entity information of the companion
356
BotEntityInfo(bs->teammate, &entinfo);
357
//if the team mate is visible
358
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) {
359
//if close just stand still there
360
VectorSubtract(entinfo.origin, bs->origin, dir);
361
if (VectorLengthSquared(dir) < Square(100)) {
362
trap_BotResetAvoidReach(bs->ms);
367
//last time the bot was NOT visible
368
bs->teammatevisible_time = FloatTime();
370
//if the entity information is valid (entity in PVS)
372
areanum = BotPointAreaNum(entinfo.origin);
373
if (areanum && trap_AAS_AreaReachability(areanum)) {
375
bs->teamgoal.entitynum = bs->teammate;
376
bs->teamgoal.areanum = areanum;
377
VectorCopy(entinfo.origin, bs->teamgoal.origin);
378
VectorSet(bs->teamgoal.mins, -8, -8, -8);
379
VectorSet(bs->teamgoal.maxs, 8, 8, 8);
382
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
385
//if the bot accompanies someone
386
if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) {
387
//check for bot typing status message
388
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
389
BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
390
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
391
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
392
trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
393
bs->teammessage_time = 0;
395
//if accompanying the companion for 3 minutes
396
if (bs->teamgoal_time < FloatTime()) {
397
BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
398
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
401
//get entity information of the companion
402
BotEntityInfo(bs->teammate, &entinfo);
403
//if the companion is visible
404
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) {
405
//update visible time
406
bs->teammatevisible_time = FloatTime();
407
VectorSubtract(entinfo.origin, bs->origin, dir);
408
if (VectorLengthSquared(dir) < Square(bs->formation_dist)) {
410
// if the client being followed bumps into this bot then
411
// the bot should back up
412
BotEntityInfo(bs->entitynum, &botinfo);
413
// if the followed client is not standing ontop of the bot
414
if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) {
415
// if the bounding boxes touch each other
416
if (botinfo.origin[0] + botinfo.maxs[0] > entinfo.origin[0] + entinfo.mins[0] - 4&&
417
botinfo.origin[0] + botinfo.mins[0] < entinfo.origin[0] + entinfo.maxs[0] + 4) {
418
if (botinfo.origin[1] + botinfo.maxs[1] > entinfo.origin[1] + entinfo.mins[1] - 4 &&
419
botinfo.origin[1] + botinfo.mins[1] < entinfo.origin[1] + entinfo.maxs[1] + 4) {
420
if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2] - 4 &&
421
botinfo.origin[2] + botinfo.mins[2] < entinfo.origin[2] + entinfo.maxs[2] + 4) {
422
// if the followed client looks in the direction of this bot
423
AngleVectors(entinfo.angles, dir, NULL, NULL);
425
VectorNormalize(dir);
426
//VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir);
427
VectorSubtract(bs->origin, entinfo.origin, dir2);
428
VectorNormalize(dir2);
429
if (DotProduct(dir, dir2) > 0.7) {
431
BotSetupForMovement(bs);
432
trap_BotMoveInDirection(bs->ms, dir2, 400, MOVE_WALK);
438
//check if the bot wants to crouch
439
//don't crouch if crouched less than 5 seconds ago
440
if (bs->attackcrouch_time < FloatTime() - 5) {
441
croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
442
if (random() < bs->thinktime * croucher) {
443
bs->attackcrouch_time = FloatTime() + 5 + croucher * 15;
446
//don't crouch when swimming
447
if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1;
448
//if not arrived yet or arived some time ago
449
if (bs->arrive_time < FloatTime() - 2) {
451
if (!bs->arrive_time) {
452
trap_EA_Gesture(bs->client);
453
BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
454
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
455
bs->arrive_time = FloatTime();
457
//if the bot wants to crouch
458
else if (bs->attackcrouch_time > FloatTime()) {
459
trap_EA_Crouch(bs->client);
461
//else do some model taunts
462
else if (random() < bs->thinktime * 0.05) {
464
trap_EA_Gesture(bs->client);
467
//if just arrived look at the companion
468
if (bs->arrive_time > FloatTime() - 2) {
469
VectorSubtract(entinfo.origin, bs->origin, dir);
470
vectoangles(dir, bs->ideal_viewangles);
471
bs->ideal_viewangles[2] *= 0.5;
473
//else look strategically around for enemies
474
else if (random() < bs->thinktime * 0.8) {
475
BotRoamGoal(bs, target);
476
VectorSubtract(target, bs->origin, dir);
477
vectoangles(dir, bs->ideal_viewangles);
478
bs->ideal_viewangles[2] *= 0.5;
480
//check if the bot wants to go for air
481
if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) {
482
trap_BotResetLastAvoidReach(bs->ms);
483
//get the goal at the top of the stack
484
//trap_BotGetTopGoal(bs->gs, &tmpgoal);
485
//trap_BotGoalName(tmpgoal.number, buf, 144);
486
//BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf);
487
//time the bot gets to pick up the nearby goal item
488
bs->nbg_time = FloatTime() + 8;
489
AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air");
493
trap_BotResetAvoidReach(bs->ms);
497
//if the entity information is valid (entity in PVS)
499
areanum = BotPointAreaNum(entinfo.origin);
500
if (areanum && trap_AAS_AreaReachability(areanum)) {
502
bs->teamgoal.entitynum = bs->teammate;
503
bs->teamgoal.areanum = areanum;
504
VectorCopy(entinfo.origin, bs->teamgoal.origin);
505
VectorSet(bs->teamgoal.mins, -8, -8, -8);
506
VectorSet(bs->teamgoal.maxs, 8, 8, 8);
509
//the goal the bot should go for
510
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
511
//if the companion is NOT visible for too long
512
if (bs->teammatevisible_time < FloatTime() - 60) {
513
BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
514
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
516
// just to make sure the bot won't spam this message
517
bs->teammatevisible_time = FloatTime();
522
if (bs->ltgtype == LTG_DEFENDKEYAREA) {
523
if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin,
524
bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) {
525
bs->defendaway_time = 0;
528
//if defending a key area
529
if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat &&
530
bs->defendaway_time < FloatTime()) {
531
//check for bot typing status message
532
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
533
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
534
BotAI_BotInitialChat(bs, "defend_start", buf, NULL);
535
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
536
BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE);
537
bs->teammessage_time = 0;
540
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
541
//stop after 2 minutes
542
if (bs->teamgoal_time < FloatTime()) {
543
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
544
BotAI_BotInitialChat(bs, "defend_stop", buf, NULL);
545
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
548
//if very close... go away for some time
549
VectorSubtract(goal->origin, bs->origin, dir);
550
if (VectorLengthSquared(dir) < Square(70)) {
551
trap_BotResetAvoidReach(bs->ms);
552
bs->defendaway_time = FloatTime() + 3 + 3 * random();
553
if (BotHasPersistantPowerupAndWeapon(bs)) {
554
bs->defendaway_range = 100;
557
bs->defendaway_range = 350;
562
//going to kill someone
563
if (bs->ltgtype == LTG_KILL && !retreat) {
564
//check for bot typing status message
565
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
566
EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
567
BotAI_BotInitialChat(bs, "kill_start", buf, NULL);
568
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
569
bs->teammessage_time = 0;
572
if (bs->lastkilledplayer == bs->teamgoal.entitynum) {
573
EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf));
574
BotAI_BotInitialChat(bs, "kill_done", buf, NULL);
575
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
576
bs->lastkilledplayer = -1;
580
if (bs->teamgoal_time < FloatTime()) {
584
return BotGetItemLongTermGoal(bs, tfl, goal);
587
if (bs->ltgtype == LTG_GETITEM && !retreat) {
588
//check for bot typing status message
589
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
590
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
591
BotAI_BotInitialChat(bs, "getitem_start", buf, NULL);
592
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
593
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
594
trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
595
bs->teammessage_time = 0;
598
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
599
//stop after some time
600
if (bs->teamgoal_time < FloatTime()) {
604
if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) {
605
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
606
BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL);
607
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
610
else if (BotReachedGoal(bs, goal)) {
611
trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf));
612
BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL);
613
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
618
//if camping somewhere
619
if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) {
620
//check for bot typing status message
621
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
622
if (bs->ltgtype == LTG_CAMPORDER) {
623
BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
624
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
625
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
626
trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
628
bs->teammessage_time = 0;
631
memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
633
if (bs->teamgoal_time < FloatTime()) {
634
if (bs->ltgtype == LTG_CAMPORDER) {
635
BotAI_BotInitialChat(bs, "camp_stop", NULL);
636
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
640
//if really near the camp spot
641
VectorSubtract(goal->origin, bs->origin, dir);
642
if (VectorLengthSquared(dir) < Square(60))
645
if (!bs->arrive_time) {
646
if (bs->ltgtype == LTG_CAMPORDER) {
647
BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL);
648
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
649
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION);
651
bs->arrive_time = FloatTime();
653
//look strategically around for enemies
654
if (random() < bs->thinktime * 0.8) {
655
BotRoamGoal(bs, target);
656
VectorSubtract(target, bs->origin, dir);
657
vectoangles(dir, bs->ideal_viewangles);
658
bs->ideal_viewangles[2] *= 0.5;
660
//check if the bot wants to crouch
661
//don't crouch if crouched less than 5 seconds ago
662
if (bs->attackcrouch_time < FloatTime() - 5) {
663
croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1);
664
if (random() < bs->thinktime * croucher) {
665
bs->attackcrouch_time = FloatTime() + 5 + croucher * 15;
668
//if the bot wants to crouch
669
if (bs->attackcrouch_time > FloatTime()) {
670
trap_EA_Crouch(bs->client);
672
//don't crouch when swimming
673
if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1;
674
//make sure the bot is not gonna drown
675
if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) {
676
if (bs->ltgtype == LTG_CAMPORDER) {
677
BotAI_BotInitialChat(bs, "camp_stop", NULL);
678
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
680
if (bs->lastgoal_ltgtype == LTG_CAMPORDER) {
681
bs->lastgoal_ltgtype = 0;
687
if (bs->camp_range > 0) {
688
//FIXME: move around a bit
691
trap_BotResetAvoidReach(bs->ms);
696
//patrolling along several waypoints
697
if (bs->ltgtype == LTG_PATROL && !retreat) {
698
//check for bot typing status message
699
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
701
for (wp = bs->patrolpoints; wp; wp = wp->next) {
702
strcat(buf, wp->name);
703
if (wp->next) strcat(buf, " to ");
705
BotAI_BotInitialChat(bs, "patrol_start", buf, NULL);
706
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
707
BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES);
708
trap_EA_Action(bs->client, ACTION_AFFIRMATIVE);
709
bs->teammessage_time = 0;
712
if (!bs->curpatrolpoint) {
716
//if the bot touches the current goal
717
if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) {
718
if (bs->patrolflags & PATROL_BACK) {
719
if (bs->curpatrolpoint->prev) {
720
bs->curpatrolpoint = bs->curpatrolpoint->prev;
723
bs->curpatrolpoint = bs->curpatrolpoint->next;
724
bs->patrolflags &= ~PATROL_BACK;
728
if (bs->curpatrolpoint->next) {
729
bs->curpatrolpoint = bs->curpatrolpoint->next;
732
bs->curpatrolpoint = bs->curpatrolpoint->prev;
733
bs->patrolflags |= PATROL_BACK;
737
//stop after 5 minutes
738
if (bs->teamgoal_time < FloatTime()) {
739
BotAI_BotInitialChat(bs, "patrol_stop", NULL);
740
trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL);
743
if (!bs->curpatrolpoint) {
747
memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t));
751
if (gametype == GT_CTF) {
752
//if going for enemy flag
753
if (bs->ltgtype == LTG_GETFLAG) {
754
//check for bot typing status message
755
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
756
BotAI_BotInitialChat(bs, "captureflag_start", NULL);
757
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
758
BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG);
759
bs->teammessage_time = 0;
762
switch(BotTeam(bs)) {
763
case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break;
764
case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break;
765
default: bs->ltgtype = 0; return qfalse;
767
//if touching the flag
768
if (trap_BotTouchingGoal(bs->origin, goal)) {
769
// make sure the bot knows the flag isn't there anymore
770
switch(BotTeam(bs)) {
771
case TEAM_RED: bs->blueflagstatus = 1; break;
772
case TEAM_BLUE: bs->redflagstatus = 1; break;
776
//stop after 3 minutes
777
if (bs->teamgoal_time < FloatTime()) {
780
BotAlternateRoute(bs, goal);
783
//if rushing to the base
784
if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) {
785
switch(BotTeam(bs)) {
786
case TEAM_RED: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break;
787
case TEAM_BLUE: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break;
788
default: bs->ltgtype = 0; return qfalse;
790
//if not carrying the flag anymore
791
if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0;
792
//quit rushing after 2 minutes
793
if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0;
794
//if touching the base flag the bot should loose the enemy flag
795
if (trap_BotTouchingGoal(bs->origin, goal)) {
796
//if the bot is still carrying the enemy flag then the
797
//base flag is gone, now just walk near the base a bit
798
if (BotCTFCarryingFlag(bs)) {
799
trap_BotResetAvoidReach(bs->ms);
800
bs->rushbaseaway_time = FloatTime() + 5 + 10 * random();
801
//FIXME: add chat to tell the others to get back the flag
807
BotAlternateRoute(bs, goal);
811
if (bs->ltgtype == LTG_RETURNFLAG) {
812
//check for bot typing status message
813
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
814
BotAI_BotInitialChat(bs, "returnflag_start", NULL);
815
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
816
BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG);
817
bs->teammessage_time = 0;
820
switch(BotTeam(bs)) {
821
case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break;
822
case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break;
823
default: bs->ltgtype = 0; return qfalse;
825
//if touching the flag
826
if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0;
827
//stop after 3 minutes
828
if (bs->teamgoal_time < FloatTime()) {
831
BotAlternateRoute(bs, goal);
837
else if (gametype == GT_1FCTF) {
838
if (bs->ltgtype == LTG_GETFLAG) {
839
//check for bot typing status message
840
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
841
BotAI_BotInitialChat(bs, "captureflag_start", NULL);
842
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
843
BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG);
844
bs->teammessage_time = 0;
846
memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t));
847
//if touching the flag
848
if (trap_BotTouchingGoal(bs->origin, goal)) {
851
//stop after 3 minutes
852
if (bs->teamgoal_time < FloatTime()) {
857
//if rushing to the base
858
if (bs->ltgtype == LTG_RUSHBASE) {
859
switch(BotTeam(bs)) {
860
case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break;
861
case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break;
862
default: bs->ltgtype = 0; return qfalse;
864
//if not carrying the flag anymore
865
if (!Bot1FCTFCarryingFlag(bs)) {
868
//quit rushing after 2 minutes
869
if (bs->teamgoal_time < FloatTime()) {
872
//if touching the base flag the bot should loose the enemy flag
873
if (trap_BotTouchingGoal(bs->origin, goal)) {
876
BotAlternateRoute(bs, goal);
879
//attack the enemy base
880
if (bs->ltgtype == LTG_ATTACKENEMYBASE &&
881
bs->attackaway_time < FloatTime()) {
882
//check for bot typing status message
883
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
884
BotAI_BotInitialChat(bs, "attackenemybase_start", NULL);
885
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
886
BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE);
887
bs->teammessage_time = 0;
889
switch(BotTeam(bs)) {
890
case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break;
891
case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break;
892
default: bs->ltgtype = 0; return qfalse;
894
//quit rushing after 2 minutes
895
if (bs->teamgoal_time < FloatTime()) {
898
//if touching the base flag the bot should loose the enemy flag
899
if (trap_BotTouchingGoal(bs->origin, goal)) {
900
bs->attackaway_time = FloatTime() + 2 + 5 * random();
905
if (bs->ltgtype == LTG_RETURNFLAG) {
906
//check for bot typing status message
907
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
908
BotAI_BotInitialChat(bs, "returnflag_start", NULL);
909
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
910
BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG);
911
bs->teammessage_time = 0;
914
if (bs->teamgoal_time < FloatTime()) {
918
return BotGetItemLongTermGoal(bs, tfl, goal);
921
else if (gametype == GT_OBELISK) {
922
if (bs->ltgtype == LTG_ATTACKENEMYBASE &&
923
bs->attackaway_time < FloatTime()) {
925
//check for bot typing status message
926
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
927
BotAI_BotInitialChat(bs, "attackenemybase_start", NULL);
928
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
929
BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE);
930
bs->teammessage_time = 0;
932
switch(BotTeam(bs)) {
933
case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break;
934
case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break;
935
default: bs->ltgtype = 0; return qfalse;
937
//if the bot no longer wants to attack the obelisk
938
if (BotFeelingBad(bs) > 50) {
939
return BotGetItemLongTermGoal(bs, tfl, goal);
941
//if touching the obelisk
942
if (trap_BotTouchingGoal(bs->origin, goal)) {
943
bs->attackaway_time = FloatTime() + 3 + 5 * random();
945
// or very close to the obelisk
946
VectorSubtract(bs->origin, goal->origin, dir);
947
if (VectorLengthSquared(dir) < Square(60)) {
948
bs->attackaway_time = FloatTime() + 3 + 5 * random();
950
//quit rushing after 2 minutes
951
if (bs->teamgoal_time < FloatTime()) {
954
BotAlternateRoute(bs, goal);
955
//just move towards the obelisk
959
else if (gametype == GT_HARVESTER) {
960
//if rushing to the base
961
if (bs->ltgtype == LTG_RUSHBASE) {
962
switch(BotTeam(bs)) {
963
case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break;
964
case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break;
965
default: BotGoHarvest(bs); return qfalse;
967
//if not carrying any cubes
968
if (!BotHarvesterCarryingCubes(bs)) {
972
//quit rushing after 2 minutes
973
if (bs->teamgoal_time < FloatTime()) {
977
//if touching the base flag the bot should loose the enemy flag
978
if (trap_BotTouchingGoal(bs->origin, goal)) {
982
BotAlternateRoute(bs, goal);
985
//attack the enemy base
986
if (bs->ltgtype == LTG_ATTACKENEMYBASE &&
987
bs->attackaway_time < FloatTime()) {
988
//check for bot typing status message
989
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
990
BotAI_BotInitialChat(bs, "attackenemybase_start", NULL);
991
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
992
BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE);
993
bs->teammessage_time = 0;
995
switch(BotTeam(bs)) {
996
case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break;
997
case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break;
998
default: bs->ltgtype = 0; return qfalse;
1000
//quit rushing after 2 minutes
1001
if (bs->teamgoal_time < FloatTime()) {
1004
//if touching the base flag the bot should loose the enemy flag
1005
if (trap_BotTouchingGoal(bs->origin, goal)) {
1006
bs->attackaway_time = FloatTime() + 2 + 5 * random();
1011
if (bs->ltgtype == LTG_HARVEST &&
1012
bs->harvestaway_time < FloatTime()) {
1013
//check for bot typing status message
1014
if (bs->teammessage_time && bs->teammessage_time < FloatTime()) {
1015
BotAI_BotInitialChat(bs, "harvest_start", NULL);
1016
trap_BotEnterChat(bs->cs, 0, CHAT_TEAM);
1017
BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE);
1018
bs->teammessage_time = 0;
1020
memcpy(goal, &neutralobelisk, sizeof(bot_goal_t));
1022
if (bs->teamgoal_time < FloatTime()) {
1026
if (trap_BotTouchingGoal(bs->origin, goal)) {
1027
bs->harvestaway_time = FloatTime() + 4 + 3 * random();
1034
return BotGetItemLongTermGoal(bs, tfl, goal);
1042
int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) {
1043
aas_entityinfo_t entinfo;
1044
char teammate[MAX_MESSAGE_SIZE];
1049
//FIXME: also have air long term goals?
1051
//if the bot is leading someone and not retreating
1052
if (bs->lead_time > 0 && !retreat) {
1053
if (bs->lead_time < FloatTime()) {
1054
BotAI_BotInitialChat(bs, "lead_stop", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
1055
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
1057
return BotGetLongTermGoal(bs, tfl, retreat, goal);
1060
if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) {
1061
BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
1062
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
1063
bs->leadmessage_time = FloatTime();
1065
//get entity information of the companion
1066
BotEntityInfo(bs->lead_teammate, &entinfo);
1068
if (entinfo.valid) {
1069
areanum = BotPointAreaNum(entinfo.origin);
1070
if (areanum && trap_AAS_AreaReachability(areanum)) {
1072
bs->lead_teamgoal.entitynum = bs->lead_teammate;
1073
bs->lead_teamgoal.areanum = areanum;
1074
VectorCopy(entinfo.origin, bs->lead_teamgoal.origin);
1075
VectorSet(bs->lead_teamgoal.mins, -8, -8, -8);
1076
VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8);
1079
//if the team mate is visible
1080
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) {
1081
bs->leadvisible_time = FloatTime();
1083
//if the team mate is not visible for 1 seconds
1084
if (bs->leadvisible_time < FloatTime() - 1) {
1085
bs->leadbackup_time = FloatTime() + 2;
1087
//distance towards the team mate
1088
VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir);
1089
squaredist = VectorLengthSquared(dir);
1090
//if backing up towards the team mate
1091
if (bs->leadbackup_time > FloatTime()) {
1092
if (bs->leadmessage_time < FloatTime() - 20) {
1093
BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
1094
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
1095
bs->leadmessage_time = FloatTime();
1097
//if very close to the team mate
1098
if (squaredist < Square(100)) {
1099
bs->leadbackup_time = 0;
1101
//the bot should go back to the team mate
1102
memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t));
1106
//if quite distant from the team mate
1107
if (squaredist > Square(500)) {
1108
if (bs->leadmessage_time < FloatTime() - 20) {
1109
BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL);
1110
trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL);
1111
bs->leadmessage_time = FloatTime();
1113
//look at the team mate
1114
VectorSubtract(entinfo.origin, bs->origin, dir);
1115
vectoangles(dir, bs->ideal_viewangles);
1116
bs->ideal_viewangles[2] *= 0.5;
1117
//just wait for the team mate
1122
return BotGetLongTermGoal(bs, tfl, retreat, goal);
1127
AIEnter_Intermission
1130
void AIEnter_Intermission(bot_state_t *bs, char *s) {
1131
BotRecordNodeSwitch(bs, "intermission", "", s);
1132
//reset the bot state
1134
//check for end level chat
1135
if (BotChat_EndLevel(bs)) {
1136
trap_BotEnterChat(bs->cs, 0, bs->chatto);
1138
bs->ainode = AINode_Intermission;
1146
int AINode_Intermission(bot_state_t *bs) {
1147
//if the intermission ended
1148
if (!BotIntermission(bs)) {
1149
if (BotChat_StartLevel(bs)) {
1150
bs->stand_time = FloatTime() + BotChatTime(bs);
1153
bs->stand_time = FloatTime() + 2;
1155
AIEnter_Stand(bs, "intermission: chat");
1165
void AIEnter_Observer(bot_state_t *bs, char *s) {
1166
BotRecordNodeSwitch(bs, "observer", "", s);
1167
//reset the bot state
1169
bs->ainode = AINode_Observer;
1177
int AINode_Observer(bot_state_t *bs) {
1178
//if the bot left observer mode
1179
if (!BotIsObserver(bs)) {
1180
AIEnter_Stand(bs, "observer: left observer");
1190
void AIEnter_Stand(bot_state_t *bs, char *s) {
1191
BotRecordNodeSwitch(bs, "stand", "", s);
1192
bs->standfindenemy_time = FloatTime() + 1;
1193
bs->ainode = AINode_Stand;
1201
int AINode_Stand(bot_state_t *bs) {
1203
//if the bot's health decreased
1204
if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
1205
if (BotChat_HitTalking(bs)) {
1206
bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1;
1207
bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1;
1210
if (bs->standfindenemy_time < FloatTime()) {
1211
if (BotFindEnemy(bs, -1)) {
1212
AIEnter_Battle_Fight(bs, "stand: found enemy");
1215
bs->standfindenemy_time = FloatTime() + 1;
1218
trap_EA_Talk(bs->client);
1219
// when done standing
1220
if (bs->stand_time < FloatTime()) {
1221
trap_BotEnterChat(bs->cs, 0, bs->chatto);
1222
AIEnter_Seek_LTG(bs, "stand: time out");
1234
void AIEnter_Respawn(bot_state_t *bs, char *s) {
1235
BotRecordNodeSwitch(bs, "respawn", "", s);
1237
trap_BotResetMoveState(bs->ms);
1238
trap_BotResetGoalState(bs->gs);
1239
trap_BotResetAvoidGoals(bs->gs);
1240
trap_BotResetAvoidReach(bs->ms);
1241
//if the bot wants to chat
1242
if (BotChat_Death(bs)) {
1243
bs->respawn_time = FloatTime() + BotChatTime(bs);
1244
bs->respawnchat_time = FloatTime();
1247
bs->respawn_time = FloatTime() + 1 + random();
1248
bs->respawnchat_time = 0;
1251
bs->respawn_wait = qfalse;
1252
bs->ainode = AINode_Respawn;
1260
int AINode_Respawn(bot_state_t *bs) {
1261
// if waiting for the actual respawn
1262
if (bs->respawn_wait) {
1263
if (!BotIsDead(bs)) {
1264
AIEnter_Seek_LTG(bs, "respawn: respawned");
1267
trap_EA_Respawn(bs->client);
1270
else if (bs->respawn_time < FloatTime()) {
1271
// wait until respawned
1272
bs->respawn_wait = qtrue;
1273
// elementary action respawn
1274
trap_EA_Respawn(bs->client);
1276
if (bs->respawnchat_time) {
1277
trap_BotEnterChat(bs->cs, 0, bs->chatto);
1281
if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) {
1282
trap_EA_Talk(bs->client);
1290
BotSelectActivateWeapon
1293
int BotSelectActivateWeapon(bot_state_t *bs) {
1295
if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_BULLETS] > 0)
1296
return WEAPONINDEX_MACHINEGUN;
1297
else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0)
1298
return WEAPONINDEX_SHOTGUN;
1299
else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0)
1300
return WEAPONINDEX_PLASMAGUN;
1301
else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0)
1302
return WEAPONINDEX_LIGHTNING;
1304
else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 0)
1305
return WEAPONINDEX_CHAINGUN;
1306
else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0)
1307
return WEAPONINDEX_NAILGUN;
1309
else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0)
1310
return WEAPONINDEX_RAILGUN;
1311
else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0)
1312
return WEAPONINDEX_ROCKET_LAUNCHER;
1313
else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0)
1314
return WEAPONINDEX_BFG;
1324
try to deactivate obstacles like proximity mines on the bot's path
1327
void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) {
1329
float dist, bestdist;
1331
bsp_trace_t bsptrace;
1332
entityState_t state;
1334
// if there is a dead body wearing kamikze nearby
1335
if (bs->kamikazebody) {
1336
// if the bot's view angles and weapon are not used for movement
1337
if ( !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) {
1339
BotAI_GetEntityState(bs->kamikazebody, &state);
1340
VectorCopy(state.pos.trBase, target);
1342
VectorSubtract(target, bs->eye, dir);
1343
vectoangles(dir, moveresult->ideal_viewangles);
1345
moveresult->weapon = BotSelectActivateWeapon(bs);
1346
if (moveresult->weapon == -1) {
1348
moveresult->weapon = 0;
1350
if (moveresult->weapon) {
1352
moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW;
1353
// if holding the right weapon
1354
if (bs->cur_ps.weapon == moveresult->weapon) {
1355
// if the bot is pretty close with it's aim
1356
if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) {
1358
BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT);
1359
// if the mine is visible from the current position
1360
if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) {
1361
// shoot at the mine
1362
trap_EA_Attack(bs->client);
1369
if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) {
1370
bs->blockedbyavoidspot_time = FloatTime() + 5;
1372
// if blocked by an avoid spot and the view angles and weapon are used for movement
1373
if (bs->blockedbyavoidspot_time > FloatTime() &&
1374
!(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) {
1377
for (i = 0; i < bs->numproxmines; i++) {
1378
BotAI_GetEntityState(bs->proxmines[i], &state);
1379
VectorSubtract(state.pos.trBase, bs->origin, dir);
1380
dist = VectorLength(dir);
1381
if (dist < bestdist) {
1386
if (bestmine != -1) {
1388
// state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE
1390
// deactivate prox mines in the bot's path by shooting
1391
// rockets or plasma cells etc. at them
1392
BotAI_GetEntityState(bs->proxmines[bestmine], &state);
1393
VectorCopy(state.pos.trBase, target);
1395
VectorSubtract(target, bs->eye, dir);
1396
vectoangles(dir, moveresult->ideal_viewangles);
1397
// if the bot has a weapon that does splash damage
1398
if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0)
1399
moveresult->weapon = WEAPONINDEX_PLASMAGUN;
1400
else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0)
1401
moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER;
1402
else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0)
1403
moveresult->weapon = WEAPONINDEX_BFG;
1405
moveresult->weapon = 0;
1407
if (moveresult->weapon) {
1409
moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW;
1410
// if holding the right weapon
1411
if (bs->cur_ps.weapon == moveresult->weapon) {
1412
// if the bot is pretty close with it's aim
1413
if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) {
1415
BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT);
1416
// if the mine is visible from the current position
1417
if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) {
1418
// shoot at the mine
1419
trap_EA_Attack(bs->client);
1430
AIEnter_Seek_ActivateEntity
1433
void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) {
1434
BotRecordNodeSwitch(bs, "activate entity", "", s);
1435
bs->ainode = AINode_Seek_ActivateEntity;
1440
AINode_Seek_Activate_Entity
1443
int AINode_Seek_ActivateEntity(bot_state_t *bs) {
1445
vec3_t target, dir, ideal_viewangles;
1446
bot_moveresult_t moveresult;
1448
bsp_trace_t bsptrace;
1449
aas_entityinfo_t entinfo;
1451
if (BotIsObserver(bs)) {
1452
BotClearActivateGoalStack(bs);
1453
AIEnter_Observer(bs, "active entity: observer");
1456
//if in the intermission
1457
if (BotIntermission(bs)) {
1458
BotClearActivateGoalStack(bs);
1459
AIEnter_Intermission(bs, "activate entity: intermission");
1463
if (BotIsDead(bs)) {
1464
BotClearActivateGoalStack(bs);
1465
AIEnter_Respawn(bs, "activate entity: bot dead");
1469
bs->tfl = TFL_DEFAULT;
1470
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
1471
// if in lava or slime the bot should be able to get out
1472
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
1473
// map specific code
1477
// if the bot has no activate goal
1478
if (!bs->activatestack) {
1479
BotClearActivateGoalStack(bs);
1480
AIEnter_Seek_NBG(bs, "activate entity: no goal");
1484
goal = &bs->activatestack->goal;
1485
// initialize target being visible to false
1486
targetvisible = qfalse;
1487
// if the bot has to shoot at a target to activate something
1488
if (bs->activatestack->shoot) {
1490
BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT);
1491
// if the shootable entity is visible from the current position
1492
if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) {
1493
targetvisible = qtrue;
1494
// if holding the right weapon
1495
if (bs->cur_ps.weapon == bs->activatestack->weapon) {
1496
VectorSubtract(bs->activatestack->target, bs->eye, dir);
1497
vectoangles(dir, ideal_viewangles);
1498
// if the bot is pretty close with it's aim
1499
if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) {
1500
trap_EA_Attack(bs->client);
1505
// if the shoot target is visible
1506
if (targetvisible) {
1507
// get the entity info of the entity the bot is shooting at
1508
BotEntityInfo(goal->entitynum, &entinfo);
1509
// if the entity the bot shoots at moved
1510
if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) {
1512
BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n");
1514
bs->activatestack->time = 0;
1516
// if the activate goal has been activated or the bot takes too long
1517
if (bs->activatestack->time < FloatTime()) {
1518
BotPopFromActivateGoalStack(bs);
1519
// if there are more activate goals on the stack
1520
if (bs->activatestack) {
1521
bs->activatestack->time = FloatTime() + 10;
1524
AIEnter_Seek_NBG(bs, "activate entity: time out");
1527
memset(&moveresult, 0, sizeof(bot_moveresult_t));
1530
// if the bot has no goal
1532
bs->activatestack->time = 0;
1534
// if the bot does not have a shoot goal
1535
else if (!bs->activatestack->shoot) {
1536
//if the bot touches the current goal
1537
if (trap_BotTouchingGoal(bs->origin, goal)) {
1539
BotAI_Print(PRT_MESSAGE, "touched button or trigger\n");
1541
bs->activatestack->time = 0;
1544
// if the activate goal has been activated or the bot takes too long
1545
if (bs->activatestack->time < FloatTime()) {
1546
BotPopFromActivateGoalStack(bs);
1547
// if there are more activate goals on the stack
1548
if (bs->activatestack) {
1549
bs->activatestack->time = FloatTime() + 10;
1552
AIEnter_Seek_NBG(bs, "activate entity: activated");
1556
if (BotAIPredictObstacles(bs, goal))
1558
//initialize the movement state
1559
BotSetupForMovement(bs);
1560
//move towards the goal
1561
trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl);
1562
//if the movement failed
1563
if (moveresult.failure) {
1564
//reset the avoid reach, otherwise bot is stuck in current area
1565
trap_BotResetAvoidReach(bs->ms);
1567
bs->activatestack->time = 0;
1569
//check if the bot is blocked
1570
BotAIBlocked(bs, &moveresult, qtrue);
1573
BotClearPath(bs, &moveresult);
1574
// if the bot has to shoot to activate
1575
if (bs->activatestack->shoot) {
1576
// if the view angles aren't yet used for the movement
1577
if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) {
1578
VectorSubtract(bs->activatestack->target, bs->eye, dir);
1579
vectoangles(dir, moveresult.ideal_viewangles);
1580
moveresult.flags |= MOVERESULT_MOVEMENTVIEW;
1582
// if there's no weapon yet used for the movement
1583
if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) {
1584
moveresult.flags |= MOVERESULT_MOVEMENTWEAPON;
1586
bs->activatestack->weapon = BotSelectActivateWeapon(bs);
1587
if (bs->activatestack->weapon == -1) {
1588
//FIXME: find a decent weapon first
1589
bs->activatestack->weapon = 0;
1591
moveresult.weapon = bs->activatestack->weapon;
1594
// if the ideal view angles are set for movement
1595
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
1596
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
1598
// if waiting for something
1599
else if (moveresult.flags & MOVERESULT_WAITING) {
1600
if (random() < bs->thinktime * 0.8) {
1601
BotRoamGoal(bs, target);
1602
VectorSubtract(target, bs->origin, dir);
1603
vectoangles(dir, bs->ideal_viewangles);
1604
bs->ideal_viewangles[2] *= 0.5;
1607
else if (!(bs->flags & BFL_IDEALVIEWSET)) {
1608
if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) {
1609
VectorSubtract(target, bs->origin, dir);
1610
vectoangles(dir, bs->ideal_viewangles);
1613
vectoangles(moveresult.movedir, bs->ideal_viewangles);
1615
bs->ideal_viewangles[2] *= 0.5;
1617
// if the weapon is used for the bot movement
1618
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON)
1619
bs->weaponnum = moveresult.weapon;
1620
// if there is an enemy
1621
if (BotFindEnemy(bs, -1)) {
1622
if (BotWantsToRetreat(bs)) {
1623
//keep the current long term goal and retreat
1624
AIEnter_Battle_NBG(bs, "activate entity: found enemy");
1627
trap_BotResetLastAvoidReach(bs->ms);
1628
//empty the goal stack
1629
trap_BotEmptyGoalStack(bs->gs);
1631
AIEnter_Battle_Fight(bs, "activate entity: found enemy");
1633
BotClearActivateGoalStack(bs);
1643
void AIEnter_Seek_NBG(bot_state_t *bs, char *s) {
1647
if (trap_BotGetTopGoal(bs->gs, &goal)) {
1648
trap_BotGoalName(goal.number, buf, 144);
1649
BotRecordNodeSwitch(bs, "seek NBG", buf, s);
1652
BotRecordNodeSwitch(bs, "seek NBG", "no goal", s);
1654
bs->ainode = AINode_Seek_NBG;
1662
int AINode_Seek_NBG(bot_state_t *bs) {
1665
bot_moveresult_t moveresult;
1667
if (BotIsObserver(bs)) {
1668
AIEnter_Observer(bs, "seek nbg: observer");
1671
//if in the intermission
1672
if (BotIntermission(bs)) {
1673
AIEnter_Intermission(bs, "seek nbg: intermision");
1677
if (BotIsDead(bs)) {
1678
AIEnter_Respawn(bs, "seek nbg: bot dead");
1682
bs->tfl = TFL_DEFAULT;
1683
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
1684
//if in lava or slime the bot should be able to get out
1685
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
1687
if (BotCanAndWantsToRocketJump(bs)) {
1688
bs->tfl |= TFL_ROCKETJUMP;
1694
//if the bot has no goal
1695
if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0;
1696
//if the bot touches the current goal
1697
else if (BotReachedGoal(bs, &goal)) {
1698
BotChooseWeapon(bs);
1702
if (bs->nbg_time < FloatTime()) {
1703
//pop the current goal from the stack
1704
trap_BotPopGoal(bs->gs);
1705
//check for new nearby items right away
1706
//NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches
1707
bs->check_time = FloatTime() + 0.05;
1708
//go back to seek ltg
1709
AIEnter_Seek_LTG(bs, "seek nbg: time out");
1713
if (BotAIPredictObstacles(bs, &goal))
1715
//initialize the movement state
1716
BotSetupForMovement(bs);
1717
//move towards the goal
1718
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
1719
//if the movement failed
1720
if (moveresult.failure) {
1721
//reset the avoid reach, otherwise bot is stuck in current area
1722
trap_BotResetAvoidReach(bs->ms);
1725
//check if the bot is blocked
1726
BotAIBlocked(bs, &moveresult, qtrue);
1728
BotClearPath(bs, &moveresult);
1729
//if the viewangles are used for the movement
1730
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
1731
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
1733
//if waiting for something
1734
else if (moveresult.flags & MOVERESULT_WAITING) {
1735
if (random() < bs->thinktime * 0.8) {
1736
BotRoamGoal(bs, target);
1737
VectorSubtract(target, bs->origin, dir);
1738
vectoangles(dir, bs->ideal_viewangles);
1739
bs->ideal_viewangles[2] *= 0.5;
1742
else if (!(bs->flags & BFL_IDEALVIEWSET)) {
1743
if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal);
1744
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
1745
VectorSubtract(target, bs->origin, dir);
1746
vectoangles(dir, bs->ideal_viewangles);
1748
//FIXME: look at cluster portals?
1749
else vectoangles(moveresult.movedir, bs->ideal_viewangles);
1750
bs->ideal_viewangles[2] *= 0.5;
1752
//if the weapon is used for the bot movement
1753
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
1754
//if there is an enemy
1755
if (BotFindEnemy(bs, -1)) {
1756
if (BotWantsToRetreat(bs)) {
1757
//keep the current long term goal and retreat
1758
AIEnter_Battle_NBG(bs, "seek nbg: found enemy");
1761
trap_BotResetLastAvoidReach(bs->ms);
1762
//empty the goal stack
1763
trap_BotEmptyGoalStack(bs->gs);
1765
AIEnter_Battle_Fight(bs, "seek nbg: found enemy");
1776
void AIEnter_Seek_LTG(bot_state_t *bs, char *s) {
1780
if (trap_BotGetTopGoal(bs->gs, &goal)) {
1781
trap_BotGoalName(goal.number, buf, 144);
1782
BotRecordNodeSwitch(bs, "seek LTG", buf, s);
1785
BotRecordNodeSwitch(bs, "seek LTG", "no goal", s);
1787
bs->ainode = AINode_Seek_LTG;
1795
int AINode_Seek_LTG(bot_state_t *bs)
1799
bot_moveresult_t moveresult;
1802
//bot_goal_t tmpgoal;
1804
if (BotIsObserver(bs)) {
1805
AIEnter_Observer(bs, "seek ltg: observer");
1808
//if in the intermission
1809
if (BotIntermission(bs)) {
1810
AIEnter_Intermission(bs, "seek ltg: intermission");
1814
if (BotIsDead(bs)) {
1815
AIEnter_Respawn(bs, "seek ltg: bot dead");
1819
if (BotChat_Random(bs)) {
1820
bs->stand_time = FloatTime() + BotChatTime(bs);
1821
AIEnter_Stand(bs, "seek ltg: random chat");
1825
bs->tfl = TFL_DEFAULT;
1826
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
1827
//if in lava or slime the bot should be able to get out
1828
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
1830
if (BotCanAndWantsToRocketJump(bs)) {
1831
bs->tfl |= TFL_ROCKETJUMP;
1838
if (bs->killedenemy_time > FloatTime() - 2) {
1839
if (random() < bs->thinktime * 1) {
1840
trap_EA_Gesture(bs->client);
1843
//if there is an enemy
1844
if (BotFindEnemy(bs, -1)) {
1845
if (BotWantsToRetreat(bs)) {
1846
//keep the current long term goal and retreat
1847
AIEnter_Battle_Retreat(bs, "seek ltg: found enemy");
1851
trap_BotResetLastAvoidReach(bs->ms);
1852
//empty the goal stack
1853
trap_BotEmptyGoalStack(bs->gs);
1855
AIEnter_Battle_Fight(bs, "seek ltg: found enemy");
1860
BotTeamGoals(bs, qfalse);
1861
//get the current long term goal
1862
if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) {
1865
//check for nearby goals periodicly
1866
if (bs->check_time < FloatTime()) {
1867
bs->check_time = FloatTime() + 0.5;
1868
//check if the bot wants to camp
1871
if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400;
1875
if (gametype == GT_CTF) {
1876
//if carrying a flag the bot shouldn't be distracted too much
1877
if (BotCTFCarryingFlag(bs))
1882
else if (gametype == GT_1FCTF) {
1883
if (Bot1FCTFCarryingFlag(bs))
1886
else if (gametype == GT_HARVESTER) {
1887
if (BotHarvesterCarryingCubes(bs))
1892
if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
1893
trap_BotResetLastAvoidReach(bs->ms);
1894
//get the goal at the top of the stack
1895
//trap_BotGetTopGoal(bs->gs, &tmpgoal);
1896
//trap_BotGoalName(tmpgoal.number, buf, 144);
1897
//BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf);
1898
//time the bot gets to pick up the nearby goal item
1899
bs->nbg_time = FloatTime() + 4 + range * 0.01;
1900
AIEnter_Seek_NBG(bs, "ltg seek: nbg");
1905
if (BotAIPredictObstacles(bs, &goal))
1907
//initialize the movement state
1908
BotSetupForMovement(bs);
1909
//move towards the goal
1910
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
1911
//if the movement failed
1912
if (moveresult.failure) {
1913
//reset the avoid reach, otherwise bot is stuck in current area
1914
trap_BotResetAvoidReach(bs->ms);
1915
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
1919
BotAIBlocked(bs, &moveresult, qtrue);
1921
BotClearPath(bs, &moveresult);
1922
//if the viewangles are used for the movement
1923
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
1924
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
1926
//if waiting for something
1927
else if (moveresult.flags & MOVERESULT_WAITING) {
1928
if (random() < bs->thinktime * 0.8) {
1929
BotRoamGoal(bs, target);
1930
VectorSubtract(target, bs->origin, dir);
1931
vectoangles(dir, bs->ideal_viewangles);
1932
bs->ideal_viewangles[2] *= 0.5;
1935
else if (!(bs->flags & BFL_IDEALVIEWSET)) {
1936
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
1937
VectorSubtract(target, bs->origin, dir);
1938
vectoangles(dir, bs->ideal_viewangles);
1940
//FIXME: look at cluster portals?
1941
else if (VectorLengthSquared(moveresult.movedir)) {
1942
vectoangles(moveresult.movedir, bs->ideal_viewangles);
1944
else if (random() < bs->thinktime * 0.8) {
1945
BotRoamGoal(bs, target);
1946
VectorSubtract(target, bs->origin, dir);
1947
vectoangles(dir, bs->ideal_viewangles);
1948
bs->ideal_viewangles[2] *= 0.5;
1950
bs->ideal_viewangles[2] *= 0.5;
1952
//if the weapon is used for the bot movement
1953
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
1960
AIEnter_Battle_Fight
1963
void AIEnter_Battle_Fight(bot_state_t *bs, char *s) {
1964
BotRecordNodeSwitch(bs, "battle fight", "", s);
1965
trap_BotResetLastAvoidReach(bs->ms);
1966
bs->ainode = AINode_Battle_Fight;
1971
AIEnter_Battle_Fight
1974
void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) {
1975
BotRecordNodeSwitch(bs, "battle fight", "", s);
1976
trap_BotResetLastAvoidReach(bs->ms);
1977
bs->ainode = AINode_Battle_Fight;
1978
bs->flags |= BFL_FIGHTSUICIDAL;
1986
int AINode_Battle_Fight(bot_state_t *bs) {
1989
aas_entityinfo_t entinfo;
1990
bot_moveresult_t moveresult;
1992
if (BotIsObserver(bs)) {
1993
AIEnter_Observer(bs, "battle fight: observer");
1997
//if in the intermission
1998
if (BotIntermission(bs)) {
1999
AIEnter_Intermission(bs, "battle fight: intermission");
2003
if (BotIsDead(bs)) {
2004
AIEnter_Respawn(bs, "battle fight: bot dead");
2007
//if there is another better enemy
2008
if (BotFindEnemy(bs, bs->enemy)) {
2010
BotAI_Print(PRT_MESSAGE, "found new better enemy\n");
2014
if (bs->enemy < 0) {
2015
AIEnter_Seek_LTG(bs, "battle fight: no enemy");
2019
BotEntityInfo(bs->enemy, &entinfo);
2020
//if the enemy is dead
2021
if (bs->enemydeath_time) {
2022
if (bs->enemydeath_time < FloatTime() - 1.0) {
2023
bs->enemydeath_time = 0;
2024
if (bs->enemysuicide) {
2025
BotChat_EnemySuicide(bs);
2027
if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) {
2028
bs->stand_time = FloatTime() + BotChatTime(bs);
2029
AIEnter_Stand(bs, "battle fight: enemy dead");
2033
AIEnter_Seek_LTG(bs, "battle fight: enemy dead");
2039
if (EntityIsDead(&entinfo)) {
2040
bs->enemydeath_time = FloatTime();
2043
//if the enemy is invisible and not shooting the bot looses track easily
2044
if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) {
2045
if (random() < 0.2) {
2046
AIEnter_Seek_LTG(bs, "battle fight: invisible");
2051
VectorCopy(entinfo.origin, target);
2052
// if not a player enemy
2053
if (bs->enemy >= MAX_CLIENTS) {
2055
// if attacking an obelisk
2056
if ( bs->enemy == redobelisk.entitynum ||
2057
bs->enemy == blueobelisk.entitynum ) {
2062
//update the reachability area and origin if possible
2063
areanum = BotPointAreaNum(target);
2064
if (areanum && trap_AAS_AreaReachability(areanum)) {
2065
VectorCopy(target, bs->lastenemyorigin);
2066
bs->lastenemyareanum = areanum;
2068
//update the attack inventory values
2069
BotUpdateBattleInventory(bs, bs->enemy);
2070
//if the bot's health decreased
2071
if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) {
2072
if (BotChat_HitNoDeath(bs)) {
2073
bs->stand_time = FloatTime() + BotChatTime(bs);
2074
AIEnter_Stand(bs, "battle fight: chat health decreased");
2078
//if the bot hit someone
2079
if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) {
2080
if (BotChat_HitNoKill(bs)) {
2081
bs->stand_time = FloatTime() + BotChatTime(bs);
2082
AIEnter_Stand(bs, "battle fight: chat hit someone");
2086
//if the enemy is not visible
2087
if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
2088
if (BotWantsToChase(bs)) {
2089
AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight");
2093
AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight");
2097
//use holdable items
2098
BotBattleUseItems(bs);
2100
bs->tfl = TFL_DEFAULT;
2101
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
2102
//if in lava or slime the bot should be able to get out
2103
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
2105
if (BotCanAndWantsToRocketJump(bs)) {
2106
bs->tfl |= TFL_ROCKETJUMP;
2108
//choose the best weapon to fight with
2109
BotChooseWeapon(bs);
2110
//do attack movements
2111
moveresult = BotAttackMove(bs, bs->tfl);
2112
//if the movement failed
2113
if (moveresult.failure) {
2114
//reset the avoid reach, otherwise bot is stuck in current area
2115
trap_BotResetAvoidReach(bs->ms);
2116
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
2120
BotAIBlocked(bs, &moveresult, qfalse);
2123
//attack the enemy if possible
2125
//if the bot wants to retreat
2126
if (!(bs->flags & BFL_FIGHTSUICIDAL)) {
2127
if (BotWantsToRetreat(bs)) {
2128
AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat");
2137
AIEnter_Battle_Chase
2140
void AIEnter_Battle_Chase(bot_state_t *bs, char *s) {
2141
BotRecordNodeSwitch(bs, "battle chase", "", s);
2142
bs->chase_time = FloatTime();
2143
bs->ainode = AINode_Battle_Chase;
2151
int AINode_Battle_Chase(bot_state_t *bs)
2155
bot_moveresult_t moveresult;
2158
if (BotIsObserver(bs)) {
2159
AIEnter_Observer(bs, "battle chase: observer");
2162
//if in the intermission
2163
if (BotIntermission(bs)) {
2164
AIEnter_Intermission(bs, "battle chase: intermission");
2168
if (BotIsDead(bs)) {
2169
AIEnter_Respawn(bs, "battle chase: bot dead");
2173
if (bs->enemy < 0) {
2174
AIEnter_Seek_LTG(bs, "battle chase: no enemy");
2177
//if the enemy is visible
2178
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
2179
AIEnter_Battle_Fight(bs, "battle chase");
2182
//if there is another enemy
2183
if (BotFindEnemy(bs, -1)) {
2184
AIEnter_Battle_Fight(bs, "battle chase: better enemy");
2187
//there is no last enemy area
2188
if (!bs->lastenemyareanum) {
2189
AIEnter_Seek_LTG(bs, "battle chase: no enemy area");
2193
bs->tfl = TFL_DEFAULT;
2194
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
2195
//if in lava or slime the bot should be able to get out
2196
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
2198
if (BotCanAndWantsToRocketJump(bs)) {
2199
bs->tfl |= TFL_ROCKETJUMP;
2203
//create the chase goal
2204
goal.entitynum = bs->enemy;
2205
goal.areanum = bs->lastenemyareanum;
2206
VectorCopy(bs->lastenemyorigin, goal.origin);
2207
VectorSet(goal.mins, -8, -8, -8);
2208
VectorSet(goal.maxs, 8, 8, 8);
2209
//if the last seen enemy spot is reached the enemy could not be found
2210
if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0;
2211
//if there's no chase time left
2212
if (!bs->chase_time || bs->chase_time < FloatTime() - 10) {
2213
AIEnter_Seek_LTG(bs, "battle chase: time out");
2216
//check for nearby goals periodicly
2217
if (bs->check_time < FloatTime()) {
2218
bs->check_time = FloatTime() + 1;
2221
if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
2222
//the bot gets 5 seconds to pick up the nearby goal item
2223
bs->nbg_time = FloatTime() + 0.1 * range + 1;
2224
trap_BotResetLastAvoidReach(bs->ms);
2225
AIEnter_Battle_NBG(bs, "battle chase: nbg");
2230
BotUpdateBattleInventory(bs, bs->enemy);
2231
//initialize the movement state
2232
BotSetupForMovement(bs);
2233
//move towards the goal
2234
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
2235
//if the movement failed
2236
if (moveresult.failure) {
2237
//reset the avoid reach, otherwise bot is stuck in current area
2238
trap_BotResetAvoidReach(bs->ms);
2239
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
2243
BotAIBlocked(bs, &moveresult, qfalse);
2245
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
2246
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
2248
else if (!(bs->flags & BFL_IDEALVIEWSET)) {
2249
if (bs->chase_time > FloatTime() - 2) {
2253
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
2254
VectorSubtract(target, bs->origin, dir);
2255
vectoangles(dir, bs->ideal_viewangles);
2258
vectoangles(moveresult.movedir, bs->ideal_viewangles);
2261
bs->ideal_viewangles[2] *= 0.5;
2263
//if the weapon is used for the bot movement
2264
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
2265
//if the bot is in the area the enemy was last seen in
2266
if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0;
2267
//if the bot wants to retreat (the bot could have been damage during the chase)
2268
if (BotWantsToRetreat(bs)) {
2269
AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat");
2277
AIEnter_Battle_Retreat
2280
void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) {
2281
BotRecordNodeSwitch(bs, "battle retreat", "", s);
2282
bs->ainode = AINode_Battle_Retreat;
2287
AINode_Battle_Retreat
2290
int AINode_Battle_Retreat(bot_state_t *bs) {
2292
aas_entityinfo_t entinfo;
2293
bot_moveresult_t moveresult;
2295
float attack_skill, range;
2298
if (BotIsObserver(bs)) {
2299
AIEnter_Observer(bs, "battle retreat: observer");
2302
//if in the intermission
2303
if (BotIntermission(bs)) {
2304
AIEnter_Intermission(bs, "battle retreat: intermission");
2308
if (BotIsDead(bs)) {
2309
AIEnter_Respawn(bs, "battle retreat: bot dead");
2313
if (bs->enemy < 0) {
2314
AIEnter_Seek_LTG(bs, "battle retreat: no enemy");
2318
BotEntityInfo(bs->enemy, &entinfo);
2319
if (EntityIsDead(&entinfo)) {
2320
AIEnter_Seek_LTG(bs, "battle retreat: enemy dead");
2323
//if there is another better enemy
2324
if (BotFindEnemy(bs, bs->enemy)) {
2326
BotAI_Print(PRT_MESSAGE, "found new better enemy\n");
2330
bs->tfl = TFL_DEFAULT;
2331
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
2332
//if in lava or slime the bot should be able to get out
2333
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
2336
//update the attack inventory values
2337
BotUpdateBattleInventory(bs, bs->enemy);
2338
//if the bot doesn't want to retreat anymore... probably picked up some nice items
2339
if (BotWantsToChase(bs)) {
2340
//empty the goal stack, when chasing, only the enemy is the goal
2341
trap_BotEmptyGoalStack(bs->gs);
2342
//go chase the enemy
2343
AIEnter_Battle_Chase(bs, "battle retreat: wants to chase");
2346
//update the last time the enemy was visible
2347
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
2348
bs->enemyvisible_time = FloatTime();
2349
VectorCopy(entinfo.origin, target);
2350
// if not a player enemy
2351
if (bs->enemy >= MAX_CLIENTS) {
2353
// if attacking an obelisk
2354
if ( bs->enemy == redobelisk.entitynum ||
2355
bs->enemy == blueobelisk.entitynum ) {
2360
//update the reachability area and origin if possible
2361
areanum = BotPointAreaNum(target);
2362
if (areanum && trap_AAS_AreaReachability(areanum)) {
2363
VectorCopy(target, bs->lastenemyorigin);
2364
bs->lastenemyareanum = areanum;
2367
//if the enemy is NOT visible for 4 seconds
2368
if (bs->enemyvisible_time < FloatTime() - 4) {
2369
AIEnter_Seek_LTG(bs, "battle retreat: lost enemy");
2372
//else if the enemy is NOT visible
2373
else if (bs->enemyvisible_time < FloatTime()) {
2374
//if there is another enemy
2375
if (BotFindEnemy(bs, -1)) {
2376
AIEnter_Battle_Fight(bs, "battle retreat: another enemy");
2381
BotTeamGoals(bs, qtrue);
2382
//use holdable items
2383
BotBattleUseItems(bs);
2384
//get the current long term goal while retreating
2385
if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) {
2386
AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out");
2389
//check for nearby goals periodicly
2390
if (bs->check_time < FloatTime()) {
2391
bs->check_time = FloatTime() + 1;
2394
if (gametype == GT_CTF) {
2395
//if carrying a flag the bot shouldn't be distracted too much
2396
if (BotCTFCarryingFlag(bs))
2401
else if (gametype == GT_1FCTF) {
2402
if (Bot1FCTFCarryingFlag(bs))
2405
else if (gametype == GT_HARVESTER) {
2406
if (BotHarvesterCarryingCubes(bs))
2411
if (BotNearbyGoal(bs, bs->tfl, &goal, range)) {
2412
trap_BotResetLastAvoidReach(bs->ms);
2413
//time the bot gets to pick up the nearby goal item
2414
bs->nbg_time = FloatTime() + range / 100 + 1;
2415
AIEnter_Battle_NBG(bs, "battle retreat: nbg");
2419
//initialize the movement state
2420
BotSetupForMovement(bs);
2421
//move towards the goal
2422
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
2423
//if the movement failed
2424
if (moveresult.failure) {
2425
//reset the avoid reach, otherwise bot is stuck in current area
2426
trap_BotResetAvoidReach(bs->ms);
2427
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
2431
BotAIBlocked(bs, &moveresult, qfalse);
2432
//choose the best weapon to fight with
2433
BotChooseWeapon(bs);
2434
//if the view is fixed for the movement
2435
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
2436
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
2438
else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET)
2439
&& !(bs->flags & BFL_IDEALVIEWSET) ) {
2440
attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
2441
//if the bot is skilled anough
2442
if (attack_skill > 0.3) {
2446
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
2447
VectorSubtract(target, bs->origin, dir);
2448
vectoangles(dir, bs->ideal_viewangles);
2451
vectoangles(moveresult.movedir, bs->ideal_viewangles);
2453
bs->ideal_viewangles[2] *= 0.5;
2456
//if the weapon is used for the bot movement
2457
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
2458
//attack the enemy if possible
2469
void AIEnter_Battle_NBG(bot_state_t *bs, char *s) {
2470
BotRecordNodeSwitch(bs, "battle NBG", "", s);
2471
bs->ainode = AINode_Battle_NBG;
2479
int AINode_Battle_NBG(bot_state_t *bs) {
2482
aas_entityinfo_t entinfo;
2483
bot_moveresult_t moveresult;
2487
if (BotIsObserver(bs)) {
2488
AIEnter_Observer(bs, "battle nbg: observer");
2491
//if in the intermission
2492
if (BotIntermission(bs)) {
2493
AIEnter_Intermission(bs, "battle nbg: intermission");
2497
if (BotIsDead(bs)) {
2498
AIEnter_Respawn(bs, "battle nbg: bot dead");
2502
if (bs->enemy < 0) {
2503
AIEnter_Seek_NBG(bs, "battle nbg: no enemy");
2507
BotEntityInfo(bs->enemy, &entinfo);
2508
if (EntityIsDead(&entinfo)) {
2509
AIEnter_Seek_NBG(bs, "battle nbg: enemy dead");
2513
bs->tfl = TFL_DEFAULT;
2514
if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK;
2515
//if in lava or slime the bot should be able to get out
2516
if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME;
2518
if (BotCanAndWantsToRocketJump(bs)) {
2519
bs->tfl |= TFL_ROCKETJUMP;
2523
//update the last time the enemy was visible
2524
if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) {
2525
bs->enemyvisible_time = FloatTime();
2526
VectorCopy(entinfo.origin, target);
2527
// if not a player enemy
2528
if (bs->enemy >= MAX_CLIENTS) {
2530
// if attacking an obelisk
2531
if ( bs->enemy == redobelisk.entitynum ||
2532
bs->enemy == blueobelisk.entitynum ) {
2537
//update the reachability area and origin if possible
2538
areanum = BotPointAreaNum(target);
2539
if (areanum && trap_AAS_AreaReachability(areanum)) {
2540
VectorCopy(target, bs->lastenemyorigin);
2541
bs->lastenemyareanum = areanum;
2544
//if the bot has no goal or touches the current goal
2545
if (!trap_BotGetTopGoal(bs->gs, &goal)) {
2548
else if (BotReachedGoal(bs, &goal)) {
2552
if (bs->nbg_time < FloatTime()) {
2553
//pop the current goal from the stack
2554
trap_BotPopGoal(bs->gs);
2555
//if the bot still has a goal
2556
if (trap_BotGetTopGoal(bs->gs, &goal))
2557
AIEnter_Battle_Retreat(bs, "battle nbg: time out");
2559
AIEnter_Battle_Fight(bs, "battle nbg: time out");
2563
//initialize the movement state
2564
BotSetupForMovement(bs);
2565
//move towards the goal
2566
trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl);
2567
//if the movement failed
2568
if (moveresult.failure) {
2569
//reset the avoid reach, otherwise bot is stuck in current area
2570
trap_BotResetAvoidReach(bs->ms);
2571
//BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype);
2575
BotAIBlocked(bs, &moveresult, qfalse);
2576
//update the attack inventory values
2577
BotUpdateBattleInventory(bs, bs->enemy);
2578
//choose the best weapon to fight with
2579
BotChooseWeapon(bs);
2580
//if the view is fixed for the movement
2581
if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
2582
VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
2584
else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET)
2585
&& !(bs->flags & BFL_IDEALVIEWSET)) {
2586
attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1);
2587
//if the bot is skilled anough and the enemy is visible
2588
if (attack_skill > 0.3) {
2589
//&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)
2593
if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
2594
VectorSubtract(target, bs->origin, dir);
2595
vectoangles(dir, bs->ideal_viewangles);
2598
vectoangles(moveresult.movedir, bs->ideal_viewangles);
2600
bs->ideal_viewangles[2] *= 0.5;
2603
//if the weapon is used for the bot movement
2604
if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
2605
//attack the enemy if possible