~ubuntu-branches/ubuntu/hardy/openarena/hardy-backports

« back to all changes in this revision

Viewing changes to code/game/ai_dmnet.c

  • Committer: Bazaar Package Importer
  • Author(s): Bruno "Fuddl" Kleinert
  • Date: 2007-01-20 12:28:09 UTC
  • Revision ID: james.westby@ubuntu.com-20070120122809-2yza5ojt7nqiyiam
Tags: upstream-0.6.0
ImportĀ upstreamĀ versionĀ 0.6.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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_dmnet.c
 
26
 *
 
27
 * desc:                Quake3 bot AI
 
28
 *
 
29
 * $Archive: /MissionPack/code/game/ai_dmnet.c $
 
30
 *
 
31
 *****************************************************************************/
 
32
 
 
33
#include "g_local.h"
 
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"
 
43
//
 
44
#include "ai_main.h"
 
45
#include "ai_dmq3.h"
 
46
#include "ai_chat.h"
 
47
#include "ai_cmd.h"
 
48
#include "ai_dmnet.h"
 
49
#include "ai_team.h"
 
50
//data file headers
 
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
 
55
 
 
56
// for the voice chats
 
57
#include "../../ui/menudef.h"
 
58
 
 
59
//goal flag, see ../botlib/be_ai_goal.h for the other GFL_*
 
60
#define GFL_AIR                 128
 
61
 
 
62
int numnodeswitches;
 
63
char nodeswitch[MAX_NODESWITCHES+1][144];
 
64
 
 
65
#define LOOKAHEAD_DISTANCE                      300
 
66
 
 
67
/*
 
68
==================
 
69
BotResetNodeSwitches
 
70
==================
 
71
*/
 
72
void BotResetNodeSwitches(void) {
 
73
        numnodeswitches = 0;
 
74
}
 
75
 
 
76
/*
 
77
==================
 
78
BotDumpNodeSwitches
 
79
==================
 
80
*/
 
81
void BotDumpNodeSwitches(bot_state_t *bs) {
 
82
        int i;
 
83
        char netname[MAX_NETNAME];
 
84
 
 
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]);
 
89
        }
 
90
        BotAI_Print(PRT_FATAL, "");
 
91
}
 
92
 
 
93
/*
 
94
==================
 
95
BotRecordNodeSwitch
 
96
==================
 
97
*/
 
98
void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) {
 
99
        char netname[MAX_NETNAME];
 
100
 
 
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);
 
103
#ifdef DEBUG
 
104
        if (0) {
 
105
                BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]);
 
106
        }
 
107
#endif //DEBUG
 
108
        numnodeswitches++;
 
109
}
 
110
 
 
111
/*
 
112
==================
 
113
BotGetAirGoal
 
114
==================
 
115
*/
 
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};
 
119
        int areanum;
 
120
 
 
121
        //trace up until we hit solid
 
122
        VectorCopy(bs->origin, end);
 
123
        end[2] += 1000;
 
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);
 
131
                if (areanum) {
 
132
                        VectorCopy(bsptrace.endpos, goal->origin);
 
133
                        goal->origin[2] -= 2;
 
134
                        goal->areanum = areanum;
 
135
                        goal->mins[0] = -15;
 
136
                        goal->mins[1] = -15;
 
137
                        goal->mins[2] = -1;
 
138
                        goal->maxs[0] = 15;
 
139
                        goal->maxs[1] = 15;
 
140
                        goal->maxs[2] = 1;
 
141
                        goal->flags = GFL_AIR;
 
142
                        goal->number = 0;
 
143
                        goal->iteminfo = 0;
 
144
                        goal->entitynum = 0;
 
145
                        return qtrue;
 
146
                }
 
147
        }
 
148
        return qfalse;
 
149
}
 
150
 
 
151
/*
 
152
==================
 
153
BotGoForAir
 
154
==================
 
155
*/
 
156
int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) {
 
157
        bot_goal_t goal;
 
158
 
 
159
        //if the bot needs air
 
160
        if (bs->lastair_time < FloatTime() - 6) {
 
161
                //
 
162
#ifdef DEBUG
 
163
                //BotAI_Print(PRT_MESSAGE, "going for air\n");
 
164
#endif //DEBUG
 
165
                //if we can find an air goal
 
166
                if (BotGetAirGoal(bs, &goal)) {
 
167
                        trap_BotPushGoal(bs->gs, &goal);
 
168
                        return qtrue;
 
169
                }
 
170
                else {
 
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))) {
 
176
                                        return qtrue;
 
177
                                }
 
178
                                trap_BotPopGoal(bs->gs);
 
179
                        }
 
180
                        trap_BotResetAvoidGoals(bs->gs);
 
181
                }
 
182
        }
 
183
        return qfalse;
 
184
}
 
185
 
 
186
/*
 
187
==================
 
188
BotNearbyGoal
 
189
==================
 
190
*/
 
191
int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) {
 
192
        int ret;
 
193
 
 
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
 
202
                        range = 50;
 
203
                }
 
204
        }
 
205
        //
 
206
        ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range);
 
207
        /*
 
208
        if (ret)
 
209
        {
 
210
                char buf[128];
 
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);
 
215
        }
 
216
    */
 
217
        return ret;
 
218
}
 
219
 
 
220
/*
 
221
==================
 
222
BotReachedGoal
 
223
==================
 
224
*/
 
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);
 
231
                        }
 
232
                        return qtrue;
 
233
                }
 
234
                //if the goal isn't there
 
235
                if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) {
 
236
                        /*
 
237
                        float avoidtime;
 
238
                        int t;
 
239
 
 
240
                        avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number);
 
241
                        if (avoidtime > 0) {
 
242
                                t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl);
 
243
                                if ((float) t * 0.009 < avoidtime)
 
244
                                        return qtrue;
 
245
                        }
 
246
                        */
 
247
                        return qtrue;
 
248
                }
 
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)) {
 
254
                                                return qtrue;
 
255
                                        }
 
256
                                }
 
257
                        }
 
258
                }
 
259
        }
 
260
        else if (goal->flags & GFL_AIR) {
 
261
                //if touching the goal
 
262
                if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
 
263
                //if the bot got air
 
264
                if (bs->lastair_time > FloatTime() - 1) return qtrue;
 
265
        }
 
266
        else {
 
267
                //if touching the goal
 
268
                if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue;
 
269
        }
 
270
        return qfalse;
 
271
}
 
272
 
 
273
/*
 
274
==================
 
275
BotGetItemLongTermGoal
 
276
==================
 
277
*/
 
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");
 
282
                bs->ltg_time = 0;
 
283
        }
 
284
        //if the bot touches the current goal
 
285
        else if (BotReachedGoal(bs, goal)) {
 
286
                BotChooseWeapon(bs);
 
287
                bs->ltg_time = 0;
 
288
        }
 
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)));
 
294
                //choose a new goal
 
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)) {
 
297
                        /*
 
298
                        char buf[128];
 
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);
 
303
            */
 
304
                        bs->ltg_time = FloatTime() + 20;
 
305
                }
 
306
                else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though
 
307
                        //
 
308
#ifdef DEBUG
 
309
                        char netname[128];
 
310
 
 
311
                        BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname)));
 
312
#endif
 
313
                        //trap_BotDumpAvoidGoals(bs->gs);
 
314
                        //reset the avoid goals and the avoid reach
 
315
                        trap_BotResetAvoidGoals(bs->gs);
 
316
                        trap_BotResetAvoidReach(bs->ms);
 
317
                }
 
318
                //get the goal at the top of the stack
 
319
                return trap_BotGetTopGoal(bs->gs, goal);
 
320
        }
 
321
        return qtrue;
 
322
}
 
323
 
 
324
/*
 
325
==================
 
326
BotGetLongTermGoal
 
327
 
 
328
we could also create a seperate AI node for every long term goal type
 
329
however this saves us a lot of code
 
330
==================
 
331
*/
 
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];
 
336
        int areanum;
 
337
        float croucher;
 
338
        aas_entityinfo_t entinfo, botinfo;
 
339
        bot_waypoint_t *wp;
 
340
 
 
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;
 
349
                }
 
350
                //if trying to help the team mate for more than a minute
 
351
                if (bs->teamgoal_time < FloatTime())
 
352
                        bs->ltgtype = 0;
 
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);
 
363
                                return qfalse;
 
364
                        }
 
365
                }
 
366
                else {
 
367
                        //last time the bot was NOT visible
 
368
                        bs->teammatevisible_time = FloatTime();
 
369
                }
 
370
                //if the entity information is valid (entity in PVS)
 
371
                if (entinfo.valid) {
 
372
                        areanum = BotPointAreaNum(entinfo.origin);
 
373
                        if (areanum && trap_AAS_AreaReachability(areanum)) {
 
374
                                //update team goal
 
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);
 
380
                        }
 
381
                }
 
382
                memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
 
383
                return qtrue;
 
384
        }
 
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;
 
394
                }
 
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);
 
399
                        bs->ltgtype = 0;
 
400
                }
 
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)) {
 
409
                                //
 
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);
 
424
                                                                dir[2] = 0;
 
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) {
 
430
                                                                        // back up
 
431
                                                                        BotSetupForMovement(bs);
 
432
                                                                        trap_BotMoveInDirection(bs->ms, dir2, 400, MOVE_WALK);
 
433
                                                                }
 
434
                                                        }
 
435
                                                }
 
436
                                        }
 
437
                                }
 
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;
 
444
                                        }
 
445
                                }
 
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) {
 
450
                                        //if not arrived yet
 
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();
 
456
                                        }
 
457
                                        //if the bot wants to crouch
 
458
                                        else if (bs->attackcrouch_time > FloatTime()) {
 
459
                                                trap_EA_Crouch(bs->client);
 
460
                                        }
 
461
                                        //else do some model taunts
 
462
                                        else if (random() < bs->thinktime * 0.05) {
 
463
                                                //do a gesture :)
 
464
                                                trap_EA_Gesture(bs->client);
 
465
                                        }
 
466
                                }
 
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;
 
472
                                }
 
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;
 
479
                                }
 
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");
 
490
                                        return qfalse;
 
491
                                }
 
492
                                //
 
493
                                trap_BotResetAvoidReach(bs->ms);
 
494
                                return qfalse;
 
495
                        }
 
496
                }
 
497
                //if the entity information is valid (entity in PVS)
 
498
                if (entinfo.valid) {
 
499
                        areanum = BotPointAreaNum(entinfo.origin);
 
500
                        if (areanum && trap_AAS_AreaReachability(areanum)) {
 
501
                                //update team goal
 
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);
 
507
                        }
 
508
                }
 
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);
 
515
                        bs->ltgtype = 0;
 
516
                        // just to make sure the bot won't spam this message
 
517
                        bs->teammatevisible_time = FloatTime();
 
518
                }
 
519
                return qtrue;
 
520
        }
 
521
        //
 
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;
 
526
                }
 
527
        }
 
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;
 
538
                }
 
539
                //set the bot goal
 
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);
 
546
                        bs->ltgtype = 0;
 
547
                }
 
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;
 
555
                        }
 
556
                        else {
 
557
                                bs->defendaway_range = 350;
 
558
                        }
 
559
                }
 
560
                return qtrue;
 
561
        }
 
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;
 
570
                }
 
571
                //
 
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;
 
577
                        bs->ltgtype = 0;
 
578
                }
 
579
                //
 
580
                if (bs->teamgoal_time < FloatTime()) {
 
581
                        bs->ltgtype = 0;
 
582
                }
 
583
                //just roam around
 
584
                return BotGetItemLongTermGoal(bs, tfl, goal);
 
585
        }
 
586
        //get an item
 
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;
 
596
                }
 
597
                //set the bot goal
 
598
                memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
 
599
                //stop after some time
 
600
                if (bs->teamgoal_time < FloatTime()) {
 
601
                        bs->ltgtype = 0;
 
602
                }
 
603
                //
 
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);
 
608
                        bs->ltgtype = 0;
 
609
                }
 
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);
 
614
                        bs->ltgtype = 0;
 
615
                }
 
616
                return qtrue;
 
617
        }
 
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);
 
627
                        }
 
628
                        bs->teammessage_time = 0;
 
629
                }
 
630
                //set the bot goal
 
631
                memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t));
 
632
                //
 
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);
 
637
                        }
 
638
                        bs->ltgtype = 0;
 
639
                }
 
640
                //if really near the camp spot
 
641
                VectorSubtract(goal->origin, bs->origin, dir);
 
642
                if (VectorLengthSquared(dir) < Square(60))
 
643
                {
 
644
                        //if not arrived yet
 
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);
 
650
                                }
 
651
                                bs->arrive_time = FloatTime();
 
652
                        }
 
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;
 
659
                        }
 
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;
 
666
                                }
 
667
                        }
 
668
                        //if the bot wants to crouch
 
669
                        if (bs->attackcrouch_time > FloatTime()) {
 
670
                                trap_EA_Crouch(bs->client);
 
671
                        }
 
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);
 
679
                                        //
 
680
                                        if (bs->lastgoal_ltgtype == LTG_CAMPORDER) {
 
681
                                                bs->lastgoal_ltgtype = 0;
 
682
                                        }
 
683
                                }
 
684
                                bs->ltgtype = 0;
 
685
                        }
 
686
                        //
 
687
                        if (bs->camp_range > 0) {
 
688
                                //FIXME: move around a bit
 
689
                        }
 
690
                        //
 
691
                        trap_BotResetAvoidReach(bs->ms);
 
692
                        return qfalse;
 
693
                }
 
694
                return qtrue;
 
695
        }
 
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()) {
 
700
                        strcpy(buf, "");
 
701
                        for (wp = bs->patrolpoints; wp; wp = wp->next) {
 
702
                                strcat(buf, wp->name);
 
703
                                if (wp->next) strcat(buf, " to ");
 
704
                        }
 
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;
 
710
                }
 
711
                //
 
712
                if (!bs->curpatrolpoint) {
 
713
                        bs->ltgtype = 0;
 
714
                        return qfalse;
 
715
                }
 
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;
 
721
                                }
 
722
                                else {
 
723
                                        bs->curpatrolpoint = bs->curpatrolpoint->next;
 
724
                                        bs->patrolflags &= ~PATROL_BACK;
 
725
                                }
 
726
                        }
 
727
                        else {
 
728
                                if (bs->curpatrolpoint->next) {
 
729
                                        bs->curpatrolpoint = bs->curpatrolpoint->next;
 
730
                                }
 
731
                                else {
 
732
                                        bs->curpatrolpoint = bs->curpatrolpoint->prev;
 
733
                                        bs->patrolflags |= PATROL_BACK;
 
734
                                }
 
735
                        }
 
736
                }
 
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);
 
741
                        bs->ltgtype = 0;
 
742
                }
 
743
                if (!bs->curpatrolpoint) {
 
744
                        bs->ltgtype = 0;
 
745
                        return qfalse;
 
746
                }
 
747
                memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t));
 
748
                return qtrue;
 
749
        }
 
750
#ifdef CTF
 
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;
 
760
                        }
 
761
                        //
 
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;
 
766
                        }
 
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;
 
773
                                }
 
774
                                bs->ltgtype = 0;
 
775
                        }
 
776
                        //stop after 3 minutes
 
777
                        if (bs->teamgoal_time < FloatTime()) {
 
778
                                bs->ltgtype = 0;
 
779
                        }
 
780
                        BotAlternateRoute(bs, goal);
 
781
                        return qtrue;
 
782
                }
 
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;
 
789
                        }
 
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
 
802
                                }
 
803
                                else {
 
804
                                        bs->ltgtype = 0;
 
805
                                }
 
806
                        }
 
807
                        BotAlternateRoute(bs, goal);
 
808
                        return qtrue;
 
809
                }
 
810
                //returning flag
 
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;
 
818
                        }
 
819
                        //
 
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;
 
824
                        }
 
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()) {
 
829
                                bs->ltgtype = 0;
 
830
                        }
 
831
                        BotAlternateRoute(bs, goal);
 
832
                        return qtrue;
 
833
                }
 
834
        }
 
835
#endif //CTF
 
836
#ifdef MISSIONPACK
 
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;
 
845
                        }
 
846
                        memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t));
 
847
                        //if touching the flag
 
848
                        if (trap_BotTouchingGoal(bs->origin, goal)) {
 
849
                                bs->ltgtype = 0;
 
850
                        }
 
851
                        //stop after 3 minutes
 
852
                        if (bs->teamgoal_time < FloatTime()) {
 
853
                                bs->ltgtype = 0;
 
854
                        }
 
855
                        return qtrue;
 
856
                }
 
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;
 
863
                        }
 
864
                        //if not carrying the flag anymore
 
865
                        if (!Bot1FCTFCarryingFlag(bs)) {
 
866
                                bs->ltgtype = 0;
 
867
                        }
 
868
                        //quit rushing after 2 minutes
 
869
                        if (bs->teamgoal_time < FloatTime()) {
 
870
                                bs->ltgtype = 0;
 
871
                        }
 
872
                        //if touching the base flag the bot should loose the enemy flag
 
873
                        if (trap_BotTouchingGoal(bs->origin, goal)) {
 
874
                                bs->ltgtype = 0;
 
875
                        }
 
876
                        BotAlternateRoute(bs, goal);
 
877
                        return qtrue;
 
878
                }
 
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;
 
888
                        }
 
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;
 
893
                        }
 
894
                        //quit rushing after 2 minutes
 
895
                        if (bs->teamgoal_time < FloatTime()) {
 
896
                                bs->ltgtype = 0;
 
897
                        }
 
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();
 
901
                        }
 
902
                        return qtrue;
 
903
                }
 
904
                //returning flag
 
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;
 
912
                        }
 
913
                        //
 
914
                        if (bs->teamgoal_time < FloatTime()) {
 
915
                                bs->ltgtype = 0;
 
916
                        }
 
917
                        //just roam around
 
918
                        return BotGetItemLongTermGoal(bs, tfl, goal);
 
919
                }
 
920
        }
 
921
        else if (gametype == GT_OBELISK) {
 
922
                if (bs->ltgtype == LTG_ATTACKENEMYBASE &&
 
923
                                bs->attackaway_time < FloatTime()) {
 
924
 
 
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;
 
931
                        }
 
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;
 
936
                        }
 
937
                        //if the bot no longer wants to attack the obelisk
 
938
                        if (BotFeelingBad(bs) > 50) {
 
939
                                return BotGetItemLongTermGoal(bs, tfl, goal);
 
940
                        }
 
941
                        //if touching the obelisk
 
942
                        if (trap_BotTouchingGoal(bs->origin, goal)) {
 
943
                                bs->attackaway_time = FloatTime() + 3 + 5 * random();
 
944
                        }
 
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();
 
949
                        }
 
950
                        //quit rushing after 2 minutes
 
951
                        if (bs->teamgoal_time < FloatTime()) {
 
952
                                bs->ltgtype = 0;
 
953
                        }
 
954
                        BotAlternateRoute(bs, goal);
 
955
                        //just move towards the obelisk
 
956
                        return qtrue;
 
957
                }
 
958
        }
 
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;
 
966
                        }
 
967
                        //if not carrying any cubes
 
968
                        if (!BotHarvesterCarryingCubes(bs)) {
 
969
                                BotGoHarvest(bs);
 
970
                                return qfalse;
 
971
                        }
 
972
                        //quit rushing after 2 minutes
 
973
                        if (bs->teamgoal_time < FloatTime()) {
 
974
                                BotGoHarvest(bs);
 
975
                                return qfalse;
 
976
                        }
 
977
                        //if touching the base flag the bot should loose the enemy flag
 
978
                        if (trap_BotTouchingGoal(bs->origin, goal)) {
 
979
                                BotGoHarvest(bs);
 
980
                                return qfalse;
 
981
                        }
 
982
                        BotAlternateRoute(bs, goal);
 
983
                        return qtrue;
 
984
                }
 
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;
 
994
                        }
 
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;
 
999
                        }
 
1000
                        //quit rushing after 2 minutes
 
1001
                        if (bs->teamgoal_time < FloatTime()) {
 
1002
                                bs->ltgtype = 0;
 
1003
                        }
 
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();
 
1007
                        }
 
1008
                        return qtrue;
 
1009
                }
 
1010
                //harvest cubes
 
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;
 
1019
                        }
 
1020
                        memcpy(goal, &neutralobelisk, sizeof(bot_goal_t));
 
1021
                        //
 
1022
                        if (bs->teamgoal_time < FloatTime()) {
 
1023
                                bs->ltgtype = 0;
 
1024
                        }
 
1025
                        //
 
1026
                        if (trap_BotTouchingGoal(bs->origin, goal)) {
 
1027
                                bs->harvestaway_time = FloatTime() + 4 + 3 * random();
 
1028
                        }
 
1029
                        return qtrue;
 
1030
                }
 
1031
        }
 
1032
#endif
 
1033
        //normal goal stuff
 
1034
        return BotGetItemLongTermGoal(bs, tfl, goal);
 
1035
}
 
1036
 
 
1037
/*
 
1038
==================
 
1039
BotLongTermGoal
 
1040
==================
 
1041
*/
 
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];
 
1045
        float squaredist;
 
1046
        int areanum;
 
1047
        vec3_t dir;
 
1048
 
 
1049
        //FIXME: also have air long term goals?
 
1050
        //
 
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);
 
1056
                        bs->lead_time = 0;
 
1057
                        return BotGetLongTermGoal(bs, tfl, retreat, goal);
 
1058
                }
 
1059
                //
 
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();
 
1064
                }
 
1065
                //get entity information of the companion
 
1066
                BotEntityInfo(bs->lead_teammate, &entinfo);
 
1067
                //
 
1068
                if (entinfo.valid) {
 
1069
                        areanum = BotPointAreaNum(entinfo.origin);
 
1070
                        if (areanum && trap_AAS_AreaReachability(areanum)) {
 
1071
                                //update team goal
 
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);
 
1077
                        }
 
1078
                }
 
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();
 
1082
                }
 
1083
                //if the team mate is not visible for 1 seconds
 
1084
                if (bs->leadvisible_time < FloatTime() - 1) {
 
1085
                        bs->leadbackup_time = FloatTime() + 2;
 
1086
                }
 
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();
 
1096
                        }
 
1097
                        //if very close to the team mate
 
1098
                        if (squaredist < Square(100)) {
 
1099
                                bs->leadbackup_time = 0;
 
1100
                        }
 
1101
                        //the bot should go back to the team mate
 
1102
                        memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t));
 
1103
                        return qtrue;
 
1104
                }
 
1105
                else {
 
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();
 
1112
                                }
 
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
 
1118
                                return qfalse;
 
1119
                        }
 
1120
                }
 
1121
        }
 
1122
        return BotGetLongTermGoal(bs, tfl, retreat, goal);
 
1123
}
 
1124
 
 
1125
/*
 
1126
==================
 
1127
AIEnter_Intermission
 
1128
==================
 
1129
*/
 
1130
void AIEnter_Intermission(bot_state_t *bs, char *s) {
 
1131
        BotRecordNodeSwitch(bs, "intermission", "", s);
 
1132
        //reset the bot state
 
1133
        BotResetState(bs);
 
1134
        //check for end level chat
 
1135
        if (BotChat_EndLevel(bs)) {
 
1136
                trap_BotEnterChat(bs->cs, 0, bs->chatto);
 
1137
        }
 
1138
        bs->ainode = AINode_Intermission;
 
1139
}
 
1140
 
 
1141
/*
 
1142
==================
 
1143
AINode_Intermission
 
1144
==================
 
1145
*/
 
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);
 
1151
                }
 
1152
                else {
 
1153
                        bs->stand_time = FloatTime() + 2;
 
1154
                }
 
1155
                AIEnter_Stand(bs, "intermission: chat");
 
1156
        }
 
1157
        return qtrue;
 
1158
}
 
1159
 
 
1160
/*
 
1161
==================
 
1162
AIEnter_Observer
 
1163
==================
 
1164
*/
 
1165
void AIEnter_Observer(bot_state_t *bs, char *s) {
 
1166
        BotRecordNodeSwitch(bs, "observer", "", s);
 
1167
        //reset the bot state
 
1168
        BotResetState(bs);
 
1169
        bs->ainode = AINode_Observer;
 
1170
}
 
1171
 
 
1172
/*
 
1173
==================
 
1174
AINode_Observer
 
1175
==================
 
1176
*/
 
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");
 
1181
        }
 
1182
        return qtrue;
 
1183
}
 
1184
 
 
1185
/*
 
1186
==================
 
1187
AIEnter_Stand
 
1188
==================
 
1189
*/
 
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;
 
1194
}
 
1195
 
 
1196
/*
 
1197
==================
 
1198
AINode_Stand
 
1199
==================
 
1200
*/
 
1201
int AINode_Stand(bot_state_t *bs) {
 
1202
 
 
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;
 
1208
                }
 
1209
        }
 
1210
        if (bs->standfindenemy_time < FloatTime()) {
 
1211
                if (BotFindEnemy(bs, -1)) {
 
1212
                        AIEnter_Battle_Fight(bs, "stand: found enemy");
 
1213
                        return qfalse;
 
1214
                }
 
1215
                bs->standfindenemy_time = FloatTime() + 1;
 
1216
        }
 
1217
        // put up chat icon
 
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");
 
1223
                return qfalse;
 
1224
        }
 
1225
        //
 
1226
        return qtrue;
 
1227
}
 
1228
 
 
1229
/*
 
1230
==================
 
1231
AIEnter_Respawn
 
1232
==================
 
1233
*/
 
1234
void AIEnter_Respawn(bot_state_t *bs, char *s) {
 
1235
        BotRecordNodeSwitch(bs, "respawn", "", s);
 
1236
        //reset some states
 
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();
 
1245
        }
 
1246
        else {
 
1247
                bs->respawn_time = FloatTime() + 1 + random();
 
1248
                bs->respawnchat_time = 0;
 
1249
        }
 
1250
        //set respawn state
 
1251
        bs->respawn_wait = qfalse;
 
1252
        bs->ainode = AINode_Respawn;
 
1253
}
 
1254
 
 
1255
/*
 
1256
==================
 
1257
AINode_Respawn
 
1258
==================
 
1259
*/
 
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");
 
1265
                }
 
1266
                else {
 
1267
                        trap_EA_Respawn(bs->client);
 
1268
                }
 
1269
        }
 
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);
 
1275
                //
 
1276
                if (bs->respawnchat_time) {
 
1277
                        trap_BotEnterChat(bs->cs, 0, bs->chatto);
 
1278
                        bs->enemy = -1;
 
1279
                }
 
1280
        }
 
1281
        if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) {
 
1282
                trap_EA_Talk(bs->client);
 
1283
        }
 
1284
        //
 
1285
        return qtrue;
 
1286
}
 
1287
 
 
1288
/*
 
1289
==================
 
1290
BotSelectActivateWeapon
 
1291
==================
 
1292
*/
 
1293
int BotSelectActivateWeapon(bot_state_t *bs) {
 
1294
        //
 
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;
 
1303
#ifdef MISSIONPACK
 
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;
 
1308
#endif
 
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;
 
1315
        else {
 
1316
                return -1;
 
1317
        }
 
1318
}
 
1319
 
 
1320
/*
 
1321
==================
 
1322
BotClearPath
 
1323
 
 
1324
 try to deactivate obstacles like proximity mines on the bot's path
 
1325
==================
 
1326
*/
 
1327
void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) {
 
1328
        int i, bestmine;
 
1329
        float dist, bestdist;
 
1330
        vec3_t target, dir;
 
1331
        bsp_trace_t bsptrace;
 
1332
        entityState_t state;
 
1333
 
 
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)) ) {
 
1338
                        //
 
1339
                        BotAI_GetEntityState(bs->kamikazebody, &state);
 
1340
                        VectorCopy(state.pos.trBase, target);
 
1341
                        target[2] += 8;
 
1342
                        VectorSubtract(target, bs->eye, dir);
 
1343
                        vectoangles(dir, moveresult->ideal_viewangles);
 
1344
                        //
 
1345
                        moveresult->weapon = BotSelectActivateWeapon(bs);
 
1346
                        if (moveresult->weapon == -1) {
 
1347
                                // FIXME: run away!
 
1348
                                moveresult->weapon = 0;
 
1349
                        }
 
1350
                        if (moveresult->weapon) {
 
1351
                                //
 
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)) {
 
1357
                                                //
 
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);
 
1363
                                                }
 
1364
                                        }
 
1365
                                }
 
1366
                        }
 
1367
                }
 
1368
        }
 
1369
        if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) {
 
1370
                bs->blockedbyavoidspot_time = FloatTime() + 5;
 
1371
        }
 
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)) ) {
 
1375
                bestdist = 300;
 
1376
                bestmine = -1;
 
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) {
 
1382
                                bestdist = dist;
 
1383
                                bestmine = i;
 
1384
                        }
 
1385
                }
 
1386
                if (bestmine != -1) {
 
1387
                        //
 
1388
                        // state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE
 
1389
                        //
 
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);
 
1394
                        target[2] += 2;
 
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;
 
1404
                        else {
 
1405
                                moveresult->weapon = 0;
 
1406
                        }
 
1407
                        if (moveresult->weapon) {
 
1408
                                //
 
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)) {
 
1414
                                                //
 
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);
 
1420
                                                }
 
1421
                                        }
 
1422
                                }
 
1423
                        }
 
1424
                }
 
1425
        }
 
1426
}
 
1427
 
 
1428
/*
 
1429
==================
 
1430
AIEnter_Seek_ActivateEntity
 
1431
==================
 
1432
*/
 
1433
void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) {
 
1434
        BotRecordNodeSwitch(bs, "activate entity", "", s);
 
1435
        bs->ainode = AINode_Seek_ActivateEntity;
 
1436
}
 
1437
 
 
1438
/*
 
1439
==================
 
1440
AINode_Seek_Activate_Entity
 
1441
==================
 
1442
*/
 
1443
int AINode_Seek_ActivateEntity(bot_state_t *bs) {
 
1444
        bot_goal_t *goal;
 
1445
        vec3_t target, dir, ideal_viewangles;
 
1446
        bot_moveresult_t moveresult;
 
1447
        int targetvisible;
 
1448
        bsp_trace_t bsptrace;
 
1449
        aas_entityinfo_t entinfo;
 
1450
 
 
1451
        if (BotIsObserver(bs)) {
 
1452
                BotClearActivateGoalStack(bs);
 
1453
                AIEnter_Observer(bs, "active entity: observer");
 
1454
                return qfalse;
 
1455
        }
 
1456
        //if in the intermission
 
1457
        if (BotIntermission(bs)) {
 
1458
                BotClearActivateGoalStack(bs);
 
1459
                AIEnter_Intermission(bs, "activate entity: intermission");
 
1460
                return qfalse;
 
1461
        }
 
1462
        //respawn if dead
 
1463
        if (BotIsDead(bs)) {
 
1464
                BotClearActivateGoalStack(bs);
 
1465
                AIEnter_Respawn(bs, "activate entity: bot dead");
 
1466
                return qfalse;
 
1467
        }
 
1468
        //
 
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
 
1474
        BotMapScripts(bs);
 
1475
        // no enemy
 
1476
        bs->enemy = -1;
 
1477
        // if the bot has no activate goal
 
1478
        if (!bs->activatestack) {
 
1479
                BotClearActivateGoalStack(bs);
 
1480
                AIEnter_Seek_NBG(bs, "activate entity: no goal");
 
1481
                return qfalse;
 
1482
        }
 
1483
        //
 
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) {
 
1489
                //
 
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);
 
1501
                                }
 
1502
                        }
 
1503
                }
 
1504
        }
 
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)) {
 
1511
#ifdef DEBUG
 
1512
                        BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n");
 
1513
#endif //DEBUG
 
1514
                        bs->activatestack->time = 0;
 
1515
                }
 
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;
 
1522
                                return qfalse;
 
1523
                        }
 
1524
                        AIEnter_Seek_NBG(bs, "activate entity: time out");
 
1525
                        return qfalse;
 
1526
                }
 
1527
                memset(&moveresult, 0, sizeof(bot_moveresult_t));
 
1528
        }
 
1529
        else {
 
1530
                // if the bot has no goal
 
1531
                if (!goal) {
 
1532
                        bs->activatestack->time = 0;
 
1533
                }
 
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)) {
 
1538
#ifdef DEBUG
 
1539
                                BotAI_Print(PRT_MESSAGE, "touched button or trigger\n");
 
1540
#endif //DEBUG
 
1541
                                bs->activatestack->time = 0;
 
1542
                        }
 
1543
                }
 
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;
 
1550
                                return qfalse;
 
1551
                        }
 
1552
                        AIEnter_Seek_NBG(bs, "activate entity: activated");
 
1553
                        return qfalse;
 
1554
                }
 
1555
                //predict obstacles
 
1556
                if (BotAIPredictObstacles(bs, goal))
 
1557
                        return qfalse;
 
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);
 
1566
                        //
 
1567
                        bs->activatestack->time = 0;
 
1568
                }
 
1569
                //check if the bot is blocked
 
1570
                BotAIBlocked(bs, &moveresult, qtrue);
 
1571
        }
 
1572
        //
 
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;
 
1581
                }
 
1582
                // if there's no weapon yet used for the movement
 
1583
                if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) {
 
1584
                        moveresult.flags |= MOVERESULT_MOVEMENTWEAPON;
 
1585
                        //
 
1586
                        bs->activatestack->weapon = BotSelectActivateWeapon(bs);
 
1587
                        if (bs->activatestack->weapon == -1) {
 
1588
                                //FIXME: find a decent weapon first
 
1589
                                bs->activatestack->weapon = 0;
 
1590
                        }
 
1591
                        moveresult.weapon = bs->activatestack->weapon;
 
1592
                }
 
1593
        }
 
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);
 
1597
        }
 
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;
 
1605
                }
 
1606
        }
 
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);
 
1611
                }
 
1612
                else {
 
1613
                        vectoangles(moveresult.movedir, bs->ideal_viewangles);
 
1614
                }
 
1615
                bs->ideal_viewangles[2] *= 0.5;
 
1616
        }
 
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");
 
1625
                }
 
1626
                else {
 
1627
                        trap_BotResetLastAvoidReach(bs->ms);
 
1628
                        //empty the goal stack
 
1629
                        trap_BotEmptyGoalStack(bs->gs);
 
1630
                        //go fight
 
1631
                        AIEnter_Battle_Fight(bs, "activate entity: found enemy");
 
1632
                }
 
1633
                BotClearActivateGoalStack(bs);
 
1634
        }
 
1635
        return qtrue;
 
1636
}
 
1637
 
 
1638
/*
 
1639
==================
 
1640
AIEnter_Seek_NBG
 
1641
==================
 
1642
*/
 
1643
void AIEnter_Seek_NBG(bot_state_t *bs, char *s) {
 
1644
        bot_goal_t goal;
 
1645
        char buf[144];
 
1646
 
 
1647
        if (trap_BotGetTopGoal(bs->gs, &goal)) {
 
1648
                trap_BotGoalName(goal.number, buf, 144);
 
1649
                BotRecordNodeSwitch(bs, "seek NBG", buf, s);
 
1650
        }
 
1651
        else {
 
1652
                BotRecordNodeSwitch(bs, "seek NBG", "no goal", s);
 
1653
        }
 
1654
        bs->ainode = AINode_Seek_NBG;
 
1655
}
 
1656
 
 
1657
/*
 
1658
==================
 
1659
AINode_Seek_NBG
 
1660
==================
 
1661
*/
 
1662
int AINode_Seek_NBG(bot_state_t *bs) {
 
1663
        bot_goal_t goal;
 
1664
        vec3_t target, dir;
 
1665
        bot_moveresult_t moveresult;
 
1666
 
 
1667
        if (BotIsObserver(bs)) {
 
1668
                AIEnter_Observer(bs, "seek nbg: observer");
 
1669
                return qfalse;
 
1670
        }
 
1671
        //if in the intermission
 
1672
        if (BotIntermission(bs)) {
 
1673
                AIEnter_Intermission(bs, "seek nbg: intermision");
 
1674
                return qfalse;
 
1675
        }
 
1676
        //respawn if dead
 
1677
        if (BotIsDead(bs)) {
 
1678
                AIEnter_Respawn(bs, "seek nbg: bot dead");
 
1679
                return qfalse;
 
1680
        }
 
1681
        //
 
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;
 
1686
        //
 
1687
        if (BotCanAndWantsToRocketJump(bs)) {
 
1688
                bs->tfl |= TFL_ROCKETJUMP;
 
1689
        }
 
1690
        //map specific code
 
1691
        BotMapScripts(bs);
 
1692
        //no enemy
 
1693
        bs->enemy = -1;
 
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);
 
1699
                bs->nbg_time = 0;
 
1700
        }
 
1701
        //
 
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");
 
1710
                return qfalse;
 
1711
        }
 
1712
        //predict obstacles
 
1713
        if (BotAIPredictObstacles(bs, &goal))
 
1714
                return qfalse;
 
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);
 
1723
                bs->nbg_time = 0;
 
1724
        }
 
1725
        //check if the bot is blocked
 
1726
        BotAIBlocked(bs, &moveresult, qtrue);
 
1727
        //
 
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);
 
1732
        }
 
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;
 
1740
                }
 
1741
        }
 
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);
 
1747
                }
 
1748
                //FIXME: look at cluster portals?
 
1749
                else vectoangles(moveresult.movedir, bs->ideal_viewangles);
 
1750
                bs->ideal_viewangles[2] *= 0.5;
 
1751
        }
 
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");
 
1759
                }
 
1760
                else {
 
1761
                        trap_BotResetLastAvoidReach(bs->ms);
 
1762
                        //empty the goal stack
 
1763
                        trap_BotEmptyGoalStack(bs->gs);
 
1764
                        //go fight
 
1765
                        AIEnter_Battle_Fight(bs, "seek nbg: found enemy");
 
1766
                }
 
1767
        }
 
1768
        return qtrue;
 
1769
}
 
1770
 
 
1771
/*
 
1772
==================
 
1773
AIEnter_Seek_LTG
 
1774
==================
 
1775
*/
 
1776
void AIEnter_Seek_LTG(bot_state_t *bs, char *s) {
 
1777
        bot_goal_t goal;
 
1778
        char buf[144];
 
1779
 
 
1780
        if (trap_BotGetTopGoal(bs->gs, &goal)) {
 
1781
                trap_BotGoalName(goal.number, buf, 144);
 
1782
                BotRecordNodeSwitch(bs, "seek LTG", buf, s);
 
1783
        }
 
1784
        else {
 
1785
                BotRecordNodeSwitch(bs, "seek LTG", "no goal", s);
 
1786
        }
 
1787
        bs->ainode = AINode_Seek_LTG;
 
1788
}
 
1789
 
 
1790
/*
 
1791
==================
 
1792
AINode_Seek_LTG
 
1793
==================
 
1794
*/
 
1795
int AINode_Seek_LTG(bot_state_t *bs)
 
1796
{
 
1797
        bot_goal_t goal;
 
1798
        vec3_t target, dir;
 
1799
        bot_moveresult_t moveresult;
 
1800
        int range;
 
1801
        //char buf[128];
 
1802
        //bot_goal_t tmpgoal;
 
1803
 
 
1804
        if (BotIsObserver(bs)) {
 
1805
                AIEnter_Observer(bs, "seek ltg: observer");
 
1806
                return qfalse;
 
1807
        }
 
1808
        //if in the intermission
 
1809
        if (BotIntermission(bs)) {
 
1810
                AIEnter_Intermission(bs, "seek ltg: intermission");
 
1811
                return qfalse;
 
1812
        }
 
1813
        //respawn if dead
 
1814
        if (BotIsDead(bs)) {
 
1815
                AIEnter_Respawn(bs, "seek ltg: bot dead");
 
1816
                return qfalse;
 
1817
        }
 
1818
        //
 
1819
        if (BotChat_Random(bs)) {
 
1820
                bs->stand_time = FloatTime() + BotChatTime(bs);
 
1821
                AIEnter_Stand(bs, "seek ltg: random chat");
 
1822
                return qfalse;
 
1823
        }
 
1824
        //
 
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;
 
1829
        //
 
1830
        if (BotCanAndWantsToRocketJump(bs)) {
 
1831
                bs->tfl |= TFL_ROCKETJUMP;
 
1832
        }
 
1833
        //map specific code
 
1834
        BotMapScripts(bs);
 
1835
        //no enemy
 
1836
        bs->enemy = -1;
 
1837
        //
 
1838
        if (bs->killedenemy_time > FloatTime() - 2) {
 
1839
                if (random() < bs->thinktime * 1) {
 
1840
                        trap_EA_Gesture(bs->client);
 
1841
                }
 
1842
        }
 
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");
 
1848
                        return qfalse;
 
1849
                }
 
1850
                else {
 
1851
                        trap_BotResetLastAvoidReach(bs->ms);
 
1852
                        //empty the goal stack
 
1853
                        trap_BotEmptyGoalStack(bs->gs);
 
1854
                        //go fight
 
1855
                        AIEnter_Battle_Fight(bs, "seek ltg: found enemy");
 
1856
                        return qfalse;
 
1857
                }
 
1858
        }
 
1859
        //
 
1860
        BotTeamGoals(bs, qfalse);
 
1861
        //get the current long term goal
 
1862
        if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) {
 
1863
                return qtrue;
 
1864
        }
 
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
 
1869
                BotWantsToCamp(bs);
 
1870
                //
 
1871
                if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400;
 
1872
                else range = 150;
 
1873
                //
 
1874
#ifdef CTF
 
1875
                if (gametype == GT_CTF) {
 
1876
                        //if carrying a flag the bot shouldn't be distracted too much
 
1877
                        if (BotCTFCarryingFlag(bs))
 
1878
                                range = 50;
 
1879
                }
 
1880
#endif //CTF
 
1881
#ifdef MISSIONPACK
 
1882
                else if (gametype == GT_1FCTF) {
 
1883
                        if (Bot1FCTFCarryingFlag(bs))
 
1884
                                range = 50;
 
1885
                }
 
1886
                else if (gametype == GT_HARVESTER) {
 
1887
                        if (BotHarvesterCarryingCubes(bs))
 
1888
                                range = 80;
 
1889
                }
 
1890
#endif
 
1891
                //
 
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");
 
1901
                        return qfalse;
 
1902
                }
 
1903
        }
 
1904
        //predict obstacles
 
1905
        if (BotAIPredictObstacles(bs, &goal))
 
1906
                return qfalse;
 
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);
 
1916
                bs->ltg_time = 0;
 
1917
        }
 
1918
        //
 
1919
        BotAIBlocked(bs, &moveresult, qtrue);
 
1920
        //
 
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);
 
1925
        }
 
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;
 
1933
                }
 
1934
        }
 
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);
 
1939
                }
 
1940
                //FIXME: look at cluster portals?
 
1941
                else if (VectorLengthSquared(moveresult.movedir)) {
 
1942
                        vectoangles(moveresult.movedir, bs->ideal_viewangles);
 
1943
                }
 
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;
 
1949
                }
 
1950
                bs->ideal_viewangles[2] *= 0.5;
 
1951
        }
 
1952
        //if the weapon is used for the bot movement
 
1953
        if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon;
 
1954
        //
 
1955
        return qtrue;
 
1956
}
 
1957
 
 
1958
/*
 
1959
==================
 
1960
AIEnter_Battle_Fight
 
1961
==================
 
1962
*/
 
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;
 
1967
}
 
1968
 
 
1969
/*
 
1970
==================
 
1971
AIEnter_Battle_Fight
 
1972
==================
 
1973
*/
 
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;
 
1979
}
 
1980
 
 
1981
/*
 
1982
==================
 
1983
AINode_Battle_Fight
 
1984
==================
 
1985
*/
 
1986
int AINode_Battle_Fight(bot_state_t *bs) {
 
1987
        int areanum;
 
1988
        vec3_t target;
 
1989
        aas_entityinfo_t entinfo;
 
1990
        bot_moveresult_t moveresult;
 
1991
 
 
1992
        if (BotIsObserver(bs)) {
 
1993
                AIEnter_Observer(bs, "battle fight: observer");
 
1994
                return qfalse;
 
1995
        }
 
1996
 
 
1997
        //if in the intermission
 
1998
        if (BotIntermission(bs)) {
 
1999
                AIEnter_Intermission(bs, "battle fight: intermission");
 
2000
                return qfalse;
 
2001
        }
 
2002
        //respawn if dead
 
2003
        if (BotIsDead(bs)) {
 
2004
                AIEnter_Respawn(bs, "battle fight: bot dead");
 
2005
                return qfalse;
 
2006
        }
 
2007
        //if there is another better enemy
 
2008
        if (BotFindEnemy(bs, bs->enemy)) {
 
2009
#ifdef DEBUG
 
2010
                BotAI_Print(PRT_MESSAGE, "found new better enemy\n");
 
2011
#endif
 
2012
        }
 
2013
        //if no enemy
 
2014
        if (bs->enemy < 0) {
 
2015
                AIEnter_Seek_LTG(bs, "battle fight: no enemy");
 
2016
                return qfalse;
 
2017
        }
 
2018
        //
 
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);
 
2026
                        }
 
2027
                        if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) {
 
2028
                                bs->stand_time = FloatTime() + BotChatTime(bs);
 
2029
                                AIEnter_Stand(bs, "battle fight: enemy dead");
 
2030
                        }
 
2031
                        else {
 
2032
                                bs->ltg_time = 0;
 
2033
                                AIEnter_Seek_LTG(bs, "battle fight: enemy dead");
 
2034
                        }
 
2035
                        return qfalse;
 
2036
                }
 
2037
        }
 
2038
        else {
 
2039
                if (EntityIsDead(&entinfo)) {
 
2040
                        bs->enemydeath_time = FloatTime();
 
2041
                }
 
2042
        }
 
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");
 
2047
                        return qfalse;
 
2048
                }
 
2049
        }
 
2050
        //
 
2051
        VectorCopy(entinfo.origin, target);
 
2052
        // if not a player enemy
 
2053
        if (bs->enemy >= MAX_CLIENTS) {
 
2054
#ifdef MISSIONPACK
 
2055
                // if attacking an obelisk
 
2056
                if ( bs->enemy == redobelisk.entitynum ||
 
2057
                        bs->enemy == blueobelisk.entitynum ) {
 
2058
                        target[2] += 16;
 
2059
                }
 
2060
#endif
 
2061
        }
 
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;
 
2067
        }
 
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");
 
2075
                        return qfalse;
 
2076
                }
 
2077
        }
 
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");
 
2083
                        return qfalse;
 
2084
                }
 
2085
        }
 
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");
 
2090
                        return qfalse;
 
2091
                }
 
2092
                else {
 
2093
                        AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight");
 
2094
                        return qfalse;
 
2095
                }
 
2096
        }
 
2097
        //use holdable items
 
2098
        BotBattleUseItems(bs);
 
2099
        //
 
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;
 
2104
        //
 
2105
        if (BotCanAndWantsToRocketJump(bs)) {
 
2106
                bs->tfl |= TFL_ROCKETJUMP;
 
2107
        }
 
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);
 
2117
                bs->ltg_time = 0;
 
2118
        }
 
2119
        //
 
2120
        BotAIBlocked(bs, &moveresult, qfalse);
 
2121
        //aim at the enemy
 
2122
        BotAimAtEnemy(bs);
 
2123
        //attack the enemy if possible
 
2124
        BotCheckAttack(bs);
 
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");
 
2129
                        return qtrue;
 
2130
                }
 
2131
        }
 
2132
        return qtrue;
 
2133
}
 
2134
 
 
2135
/*
 
2136
==================
 
2137
AIEnter_Battle_Chase
 
2138
==================
 
2139
*/
 
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;
 
2144
}
 
2145
 
 
2146
/*
 
2147
==================
 
2148
AINode_Battle_Chase
 
2149
==================
 
2150
*/
 
2151
int AINode_Battle_Chase(bot_state_t *bs)
 
2152
{
 
2153
        bot_goal_t goal;
 
2154
        vec3_t target, dir;
 
2155
        bot_moveresult_t moveresult;
 
2156
        float range;
 
2157
 
 
2158
        if (BotIsObserver(bs)) {
 
2159
                AIEnter_Observer(bs, "battle chase: observer");
 
2160
                return qfalse;
 
2161
        }
 
2162
        //if in the intermission
 
2163
        if (BotIntermission(bs)) {
 
2164
                AIEnter_Intermission(bs, "battle chase: intermission");
 
2165
                return qfalse;
 
2166
        }
 
2167
        //respawn if dead
 
2168
        if (BotIsDead(bs)) {
 
2169
                AIEnter_Respawn(bs, "battle chase: bot dead");
 
2170
                return qfalse;
 
2171
        }
 
2172
        //if no enemy
 
2173
        if (bs->enemy < 0) {
 
2174
                AIEnter_Seek_LTG(bs, "battle chase: no enemy");
 
2175
                return qfalse;
 
2176
        }
 
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");
 
2180
                return qfalse;
 
2181
        }
 
2182
        //if there is another enemy
 
2183
        if (BotFindEnemy(bs, -1)) {
 
2184
                AIEnter_Battle_Fight(bs, "battle chase: better enemy");
 
2185
                return qfalse;
 
2186
        }
 
2187
        //there is no last enemy area
 
2188
        if (!bs->lastenemyareanum) {
 
2189
                AIEnter_Seek_LTG(bs, "battle chase: no enemy area");
 
2190
                return qfalse;
 
2191
        }
 
2192
        //
 
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;
 
2197
        //
 
2198
        if (BotCanAndWantsToRocketJump(bs)) {
 
2199
                bs->tfl |= TFL_ROCKETJUMP;
 
2200
        }
 
2201
        //map specific code
 
2202
        BotMapScripts(bs);
 
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");
 
2214
                return qfalse;
 
2215
        }
 
2216
        //check for nearby goals periodicly
 
2217
        if (bs->check_time < FloatTime()) {
 
2218
                bs->check_time = FloatTime() + 1;
 
2219
                range = 150;
 
2220
                //
 
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");
 
2226
                        return qfalse;
 
2227
                }
 
2228
        }
 
2229
        //
 
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);
 
2240
                bs->ltg_time = 0;
 
2241
        }
 
2242
        //
 
2243
        BotAIBlocked(bs, &moveresult, qfalse);
 
2244
        //
 
2245
        if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) {
 
2246
                VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles);
 
2247
        }
 
2248
        else if (!(bs->flags & BFL_IDEALVIEWSET)) {
 
2249
                if (bs->chase_time > FloatTime() - 2) {
 
2250
                        BotAimAtEnemy(bs);
 
2251
                }
 
2252
                else {
 
2253
                        if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
 
2254
                                VectorSubtract(target, bs->origin, dir);
 
2255
                                vectoangles(dir, bs->ideal_viewangles);
 
2256
                        }
 
2257
                        else {
 
2258
                                vectoangles(moveresult.movedir, bs->ideal_viewangles);
 
2259
                        }
 
2260
                }
 
2261
                bs->ideal_viewangles[2] *= 0.5;
 
2262
        }
 
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");
 
2270
                return qtrue;
 
2271
        }
 
2272
        return qtrue;
 
2273
}
 
2274
 
 
2275
/*
 
2276
==================
 
2277
AIEnter_Battle_Retreat
 
2278
==================
 
2279
*/
 
2280
void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) {
 
2281
        BotRecordNodeSwitch(bs, "battle retreat", "", s);
 
2282
        bs->ainode = AINode_Battle_Retreat;
 
2283
}
 
2284
 
 
2285
/*
 
2286
==================
 
2287
AINode_Battle_Retreat
 
2288
==================
 
2289
*/
 
2290
int AINode_Battle_Retreat(bot_state_t *bs) {
 
2291
        bot_goal_t goal;
 
2292
        aas_entityinfo_t entinfo;
 
2293
        bot_moveresult_t moveresult;
 
2294
        vec3_t target, dir;
 
2295
        float attack_skill, range;
 
2296
        int areanum;
 
2297
 
 
2298
        if (BotIsObserver(bs)) {
 
2299
                AIEnter_Observer(bs, "battle retreat: observer");
 
2300
                return qfalse;
 
2301
        }
 
2302
        //if in the intermission
 
2303
        if (BotIntermission(bs)) {
 
2304
                AIEnter_Intermission(bs, "battle retreat: intermission");
 
2305
                return qfalse;
 
2306
        }
 
2307
        //respawn if dead
 
2308
        if (BotIsDead(bs)) {
 
2309
                AIEnter_Respawn(bs, "battle retreat: bot dead");
 
2310
                return qfalse;
 
2311
        }
 
2312
        //if no enemy
 
2313
        if (bs->enemy < 0) {
 
2314
                AIEnter_Seek_LTG(bs, "battle retreat: no enemy");
 
2315
                return qfalse;
 
2316
        }
 
2317
        //
 
2318
        BotEntityInfo(bs->enemy, &entinfo);
 
2319
        if (EntityIsDead(&entinfo)) {
 
2320
                AIEnter_Seek_LTG(bs, "battle retreat: enemy dead");
 
2321
                return qfalse;
 
2322
        }
 
2323
        //if there is another better enemy
 
2324
        if (BotFindEnemy(bs, bs->enemy)) {
 
2325
#ifdef DEBUG
 
2326
                BotAI_Print(PRT_MESSAGE, "found new better enemy\n");
 
2327
#endif
 
2328
        }
 
2329
        //
 
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;
 
2334
        //map specific code
 
2335
        BotMapScripts(bs);
 
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");
 
2344
                return qfalse;
 
2345
        }
 
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) {
 
2352
#ifdef MISSIONPACK
 
2353
                        // if attacking an obelisk
 
2354
                        if ( bs->enemy == redobelisk.entitynum ||
 
2355
                                bs->enemy == blueobelisk.entitynum ) {
 
2356
                                target[2] += 16;
 
2357
                        }
 
2358
#endif
 
2359
                }
 
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;
 
2365
                }
 
2366
        }
 
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");
 
2370
                return qfalse;
 
2371
        }
 
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");
 
2377
                        return qfalse;
 
2378
                }
 
2379
        }
 
2380
        //
 
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");
 
2387
                return qfalse;
 
2388
        }
 
2389
        //check for nearby goals periodicly
 
2390
        if (bs->check_time < FloatTime()) {
 
2391
                bs->check_time = FloatTime() + 1;
 
2392
                range = 150;
 
2393
#ifdef CTF
 
2394
                if (gametype == GT_CTF) {
 
2395
                        //if carrying a flag the bot shouldn't be distracted too much
 
2396
                        if (BotCTFCarryingFlag(bs))
 
2397
                                range = 50;
 
2398
                }
 
2399
#endif //CTF
 
2400
#ifdef MISSIONPACK
 
2401
                else if (gametype == GT_1FCTF) {
 
2402
                        if (Bot1FCTFCarryingFlag(bs))
 
2403
                                range = 50;
 
2404
                }
 
2405
                else if (gametype == GT_HARVESTER) {
 
2406
                        if (BotHarvesterCarryingCubes(bs))
 
2407
                                range = 80;
 
2408
                }
 
2409
#endif
 
2410
                //
 
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");
 
2416
                        return qfalse;
 
2417
                }
 
2418
        }
 
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);
 
2428
                bs->ltg_time = 0;
 
2429
        }
 
2430
        //
 
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);
 
2437
        }
 
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) {
 
2443
                        BotAimAtEnemy(bs);
 
2444
                }
 
2445
                else {
 
2446
                        if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
 
2447
                                VectorSubtract(target, bs->origin, dir);
 
2448
                                vectoangles(dir, bs->ideal_viewangles);
 
2449
                        }
 
2450
                        else {
 
2451
                                vectoangles(moveresult.movedir, bs->ideal_viewangles);
 
2452
                        }
 
2453
                        bs->ideal_viewangles[2] *= 0.5;
 
2454
                }
 
2455
        }
 
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
 
2459
        BotCheckAttack(bs);
 
2460
        //
 
2461
        return qtrue;
 
2462
}
 
2463
 
 
2464
/*
 
2465
==================
 
2466
AIEnter_Battle_NBG
 
2467
==================
 
2468
*/
 
2469
void AIEnter_Battle_NBG(bot_state_t *bs, char *s) {
 
2470
        BotRecordNodeSwitch(bs, "battle NBG", "", s);
 
2471
        bs->ainode = AINode_Battle_NBG;
 
2472
}
 
2473
 
 
2474
/*
 
2475
==================
 
2476
AINode_Battle_NBG
 
2477
==================
 
2478
*/
 
2479
int AINode_Battle_NBG(bot_state_t *bs) {
 
2480
        int areanum;
 
2481
        bot_goal_t goal;
 
2482
        aas_entityinfo_t entinfo;
 
2483
        bot_moveresult_t moveresult;
 
2484
        float attack_skill;
 
2485
        vec3_t target, dir;
 
2486
 
 
2487
        if (BotIsObserver(bs)) {
 
2488
                AIEnter_Observer(bs, "battle nbg: observer");
 
2489
                return qfalse;
 
2490
        }
 
2491
        //if in the intermission
 
2492
        if (BotIntermission(bs)) {
 
2493
                AIEnter_Intermission(bs, "battle nbg: intermission");
 
2494
                return qfalse;
 
2495
        }
 
2496
        //respawn if dead
 
2497
        if (BotIsDead(bs)) {
 
2498
                AIEnter_Respawn(bs, "battle nbg: bot dead");
 
2499
                return qfalse;
 
2500
        }
 
2501
        //if no enemy
 
2502
        if (bs->enemy < 0) {
 
2503
                AIEnter_Seek_NBG(bs, "battle nbg: no enemy");
 
2504
                return qfalse;
 
2505
        }
 
2506
        //
 
2507
        BotEntityInfo(bs->enemy, &entinfo);
 
2508
        if (EntityIsDead(&entinfo)) {
 
2509
                AIEnter_Seek_NBG(bs, "battle nbg: enemy dead");
 
2510
                return qfalse;
 
2511
        }
 
2512
        //
 
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;
 
2517
        //
 
2518
        if (BotCanAndWantsToRocketJump(bs)) {
 
2519
                bs->tfl |= TFL_ROCKETJUMP;
 
2520
        }
 
2521
        //map specific code
 
2522
        BotMapScripts(bs);
 
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) {
 
2529
#ifdef MISSIONPACK
 
2530
                        // if attacking an obelisk
 
2531
                        if ( bs->enemy == redobelisk.entitynum ||
 
2532
                                bs->enemy == blueobelisk.entitynum ) {
 
2533
                                target[2] += 16;
 
2534
                        }
 
2535
#endif
 
2536
                }
 
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;
 
2542
                }
 
2543
        }
 
2544
        //if the bot has no goal or touches the current goal
 
2545
        if (!trap_BotGetTopGoal(bs->gs, &goal)) {
 
2546
                bs->nbg_time = 0;
 
2547
        }
 
2548
        else if (BotReachedGoal(bs, &goal)) {
 
2549
                bs->nbg_time = 0;
 
2550
        }
 
2551
        //
 
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");
 
2558
                else
 
2559
                        AIEnter_Battle_Fight(bs, "battle nbg: time out");
 
2560
                //
 
2561
                return qfalse;
 
2562
        }
 
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);
 
2572
                bs->nbg_time = 0;
 
2573
        }
 
2574
        //
 
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);
 
2583
        }
 
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)
 
2590
                        BotAimAtEnemy(bs);
 
2591
                }
 
2592
                else {
 
2593
                        if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) {
 
2594
                                VectorSubtract(target, bs->origin, dir);
 
2595
                                vectoangles(dir, bs->ideal_viewangles);
 
2596
                        }
 
2597
                        else {
 
2598
                                vectoangles(moveresult.movedir, bs->ideal_viewangles);
 
2599
                        }
 
2600
                        bs->ideal_viewangles[2] *= 0.5;
 
2601
                }
 
2602
        }
 
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
 
2606
        BotCheckAttack(bs);
 
2607
        //
 
2608
        return qtrue;
 
2609
}
 
2610