-
Notifications
You must be signed in to change notification settings - Fork 9
/
bot_navigate.cpp
2512 lines (2059 loc) · 93.5 KB
/
bot_navigate.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com
//
// FoXBot - AI Bot for Halflife's Team Fortress Classic
//
// (http://foxbot.net)
//
// bot_navigate.cpp
//
// Copyright (C) 2003 - Tom "Redfox" Simpson
//
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License for more details at:
// http://www.gnu.org/copyleft/gpl.html
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
#include "extdll.h"
#include "util.h"
#include "bot.h"
#include "waypoint.h"
#include "bot_func.h"
#include "bot_job_think.h"
#include "bot_navigate.h"
#include <optional>
#include "bot_weapons.h"
extern bot_weapon_t weapon_defs[MAX_WEAPONS];
extern edict_t* clients[32];
extern bot_t bots[32];
extern int mod_id;
extern WAYPOINT waypoints[MAX_WAYPOINTS];
extern PATH* paths[MAX_WAYPOINTS];
extern int num_waypoints; // number of waypoints currently in use
extern bool g_waypoint_on;
extern edict_t* pent_info_ctfdetect;
extern float is_team_play;
extern bool checked_teamplay;
extern int bot_use_grenades;
extern bool bot_can_use_teleporter;
// Rocket jump globals
extern int RJPoints[MAXRJWAYPOINTS][2];
int CheckTeleporterExitTime = 0;
// extern int flf_bug_fix;
extern bool g_bot_debug;
extern float last_frame_time;
// bit field of waypoint types to ignore when the bot is lost
// and looking for a new current waypoint to head for
static constexpr WPT_INT32 lostBotIgnoreFlags = 0 + (W_FL_DELETED | W_FL_AIMING | W_FL_TFC_PL_DEFEND | W_FL_TFC_PIPETRAP | W_FL_TFC_SENTRY | W_FL_TFC_DETPACK_CLEAR | W_FL_TFC_DETPACK_SEAL | W_FL_SNIPER | W_FL_TFC_TELEPORTER_ENTRANCE |
W_FL_TFC_TELEPORTER_EXIT | W_FL_TFC_JUMP | W_FL_LIFT | W_FL_PATHCHECK);
int spawnAreaWP[4] = { -1, -1, -1, -1 }; // used for tracking the areas where each team spawns
extern int team_allies[4];
// FUNCTION PROTOTYPES
static int BotShouldJumpOver(const bot_t* pBot);
static int BotShouldDuckUnder(const bot_t* pBot);
static bool BotFallenOffCheck(bot_t* pBot);
static bool BotEscapeWaypointTrap(bot_t* pBot, int goalWP);
static bool BotUpdateRoute(bot_t* pBot);
static void BotHandleLadderTraffic(bot_t* pBot);
static void BotCheckForRocketJump(bot_t* pBot);
static void BotCheckForConcJump(bot_t* pBot);
// This function allows bots to report in the waypoint they last spawned
// nearby.
// This should be run each time a bot spawns, so as to keep the information
// up to date on maps such as warpath where the spawn areas keep changing.
void BotUpdateHomeInfo(const bot_t* pBot) {
if (mod_id != TFC_DLL)
return;
// if the spawn location is totally unknown try to update it now
if (spawnAreaWP[pBot->current_team] < 0 && pBot->f_killed_time + 15.0f > gpGlobals->time) {
spawnAreaWP[pBot->current_team] = WaypointFindNearest_V(pBot->pEdict->v.origin, 800.0, pBot->current_team);
return;
}
// keep the spawn area info up to date
if (pBot->current_wp != -1 && pBot->f_killed_time + 4.0f > gpGlobals->time) {
spawnAreaWP[pBot->current_team] = pBot->current_wp;
}
}
// This function should be called each time the map changes.
// It resets the bots knowledge of where they are spawning.
void ResetBotHomeInfo() {
// sanity check
if (mod_id != TFC_DLL)
return;
spawnAreaWP[0] = -1;
spawnAreaWP[1] = -1;
spawnAreaWP[2] = -1;
spawnAreaWP[3] = -1;
}
// This function should be used when the bot has just spawned and has no current
// waypoint yet, because it also updates the bots knowledge of where it spawned.
void BotFindCurrentWaypoint(bot_t* pBot) {
int min_index = -1;
double min_distance_squared = 640000.0; // 800 * 800 = 640000
int runnerUp = -1;
TraceResult tr;
// find the nearest waypoint...
for (int index = 0; index < num_waypoints; index++) {
// skip unwanted waypoints
if (waypoints[index].flags & W_FL_DELETED || waypoints[index].flags & W_FL_AIMING || waypoints[index].flags & lostBotIgnoreFlags)
continue;
// skip this waypoint if it's team specific and teams don't match
if (waypoints[index].flags & W_FL_TEAM_SPECIFIC && (waypoints[index].flags & W_FL_TEAM) != pBot->current_team)
continue;
// don't give the bot the same waypoint it already has,
// some waypoints are cursed
if (index == pBot->current_wp)
continue;
// square the Manhattan distance, this way we can avoid using sqrt()
const Vector distance = waypoints[index].origin - pBot->pEdict->v.origin;
const double distance_squared = static_cast<double>(distance.x * distance.x) + static_cast<double>(distance.y * distance.y) + static_cast<double>(distance.z * distance.z);
// if the waypoint is above the bot only remember it in case no
// other waypoint could be found(higher waypoints may be unreachable)
if (pBot->pEdict->v.origin.z + 50.0f < waypoints[index].origin.z) {
if (runnerUp == -1 && distance_squared < 640000.0) // 800 * 800 = 640000
{
// check if the waypoint is visible to the bot
UTIL_TraceLine(pBot->pEdict->v.origin, waypoints[index].origin, dont_ignore_monsters, pBot->pEdict->v.pContainingEntity, &tr);
// it is visible, so store it
if (tr.flFraction >= 1.0f)
runnerUp = index;
}
continue;
}
// if it's the nearest found so far
if (distance_squared < min_distance_squared) {
// check if the waypoint is visible to the bot
UTIL_TraceLine(pBot->pEdict->v.origin, waypoints[index].origin, dont_ignore_monsters, pBot->pEdict->v.pContainingEntity, &tr);
// it is visible, so store it
if (tr.flFraction >= 1.0f) {
min_index = index;
min_distance_squared = distance_squared;
}
}
}
if (min_index == -1)
pBot->current_wp = runnerUp;
else
pBot->current_wp = min_index;
// give the bot time to arrive at the new waypoint
pBot->f_current_wp_deadline = pBot->f_think_time + BOT_WP_DEADLINE;
// if the bot just spawned make sure it has a current waypoint
if (pBot->current_wp != -1 && pBot->f_killed_time + 4.0f > pBot->f_think_time) {
BotUpdateHomeInfo(pBot); // if the bot just spawned, report where
// WaypointDrawBeam(INDEXENT(1), pBot->pEdict->v.origin + Vector(0, 0, 70),
// waypoints[pBot->current_wp].origin,
// 10, 2, 250, 250, 250, 200, 10);
}
}
// This function will tell the bot to face the map coordinates indicated by v_focus
void BotSetFacing(const bot_t* pBot, Vector v_focus) {
v_focus = v_focus - (pBot->pEdict->v.origin + pBot->pEdict->v.view_ofs);
const Vector bot_angles = UTIL_VecToAngles(v_focus);
pBot->pEdict->v.ideal_yaw = bot_angles.y;
pBot->pEdict->v.idealpitch = bot_angles.x;
BotFixIdealYaw(pBot->pEdict);
BotFixIdealPitch(pBot->pEdict);
}
// This function will tell the bot to face the map coordinates
// indicated by v_focus
void BotMatchFacing(const bot_t* pBot, const Vector& v_source, Vector v_focus) {
v_focus = v_focus - (pBot->pEdict->v.origin + pBot->pEdict->v.view_ofs);
const Vector bot_angles = UTIL_VecToAngles(v_focus);
pBot->pEdict->v.ideal_yaw = bot_angles.y;
pBot->pEdict->v.idealpitch = bot_angles.x;
BotFixIdealYaw(pBot->pEdict);
BotFixIdealPitch(pBot->pEdict);
}
void BotFixIdealPitch(edict_t* pEdict) {
// check for wrap around of angle...
if (pEdict->v.idealpitch > 180)
pEdict->v.idealpitch -= 360;
else if (pEdict->v.idealpitch < -180)
pEdict->v.idealpitch += 360;
}
float BotChangePitch(edict_t* pEdict, float speed) {
const float ideal = pEdict->v.idealpitch;
float current = -pEdict->v.v_angle.x;
// turn from the current v_angle pitch to the idealpitch by selecting
// the quickest way to turn to face that direction
// find the difference in the current and ideal angle
const float diff = fabsf(current - ideal);
// check if the bot is already facing the idealpitch direction...
if (diff <= 0.01f)
return diff; // return number of degrees turned
// should keep turn speed the same under any fps...hopefully
speed *= gpGlobals->time - last_frame_time;
speed *= 10;
// check if difference is less than the max degrees per turn
if (diff < speed)
speed = diff; // just need to turn a little bit (less than max)
// here we have four cases, both angle positive, one positive and
// the other negative, one negative and the other positive, or
// both negative. handle each case separately...
if (current >= 0 && ideal >= 0) // both positive
{
if (current > ideal)
current -= speed;
else
current += speed;
}
else if (current >= 0 && ideal < 0) {
const float current_180 = current - 180;
if (current_180 > ideal)
current += speed;
else
current -= speed;
}
else if (current < 0 && ideal >= 0) {
const float current_180 = current + 180;
if (current_180 > ideal)
current += speed;
else
current -= speed;
}
else // (current < 0) && (ideal < 0) both negative
{
if (current > ideal)
current -= speed;
else
current += speed;
}
// check for wrap around of angle...
if (current > 180)
current -= 360;
else if (current < -180)
current += 360;
pEdict->v.v_angle.x = current + pEdict->v.punchangle.x;
pEdict->v.angles.x = pEdict->v.v_angle.x / 3;
pEdict->v.v_angle.x = -pEdict->v.v_angle.x;
pEdict->v.angles.z = 0;
return speed; // return number of degrees turned
}
void BotFixIdealYaw(edict_t* pEdict) {
// check for wrap around of angle...
if (pEdict->v.ideal_yaw > 180)
pEdict->v.ideal_yaw -= 360;
else if (pEdict->v.ideal_yaw < -180)
pEdict->v.ideal_yaw += 360;
}
float BotChangeYaw(edict_t* pEdict, float speed) {
const float ideal = pEdict->v.ideal_yaw;
float current = pEdict->v.v_angle.y;
// turn from the current v_angle yaw to the ideal_yaw by selecting
// the quickest way to turn to face that direction
// find the difference in the current and ideal angle
const float diff = fabsf(current - ideal);
// check if the bot is already facing the ideal_yaw direction...
if (diff <= 0.01f)
return diff; // return number of degrees turned
speed *= gpGlobals->time - last_frame_time;
speed *= 10;
// check if difference is less than the max degrees per turn
if (diff < speed)
speed = diff; // just need to turn a little bit (less than max)
// here we have four cases, both angle positive, one positive and
// the other negative, one negative and the other positive, or
// both negative. handle each case separately...
if (current >= 0 && ideal >= 0) // both positive
{
if (current > ideal)
current -= speed;
else
current += speed;
}
else if (current >= 0 && ideal < 0) {
const float current_180 = current - 180;
if (current_180 > ideal)
current += speed;
else
current -= speed;
}
else if (current < 0 && ideal >= 0) {
const float current_180 = current + 180;
if (current_180 > ideal)
current += speed;
else
current -= speed;
}
else // (current < 0) && (ideal < 0) both negative
{
if (current > ideal)
current -= speed;
else
current += speed;
}
// check for wrap around of angle...
if (current > 180)
current -= 360;
else if (current < -180)
current += 360;
pEdict->v.v_angle.y = current + pEdict->v.punchangle.y;
pEdict->v.angles.y = pEdict->v.v_angle.y;
pEdict->v.angles.z = 0;
return speed; // return number of degrees turned
}
// This function is useful when you are trying to steer the bot towards some
// map location without using waypoints.
// For example, this function will try to make the bot jump over or duck under obstacles.
void BotNavigateWaypointless(bot_t* pBot) {
pBot->f_move_speed = pBot->f_max_speed;
pBot->pEdict->v.button |= IN_FORWARD;
// give the bot time to return to it's waypoint afterwards
pBot->f_current_wp_deadline = pBot->f_think_time + BOT_WP_DEADLINE;
// navigating waypointless may send the bot too far from it's current
// waypoint, so periodically try to keep it up to date
if (pBot->f_periodicAlert1 < pBot->f_think_time) {
if (!VectorsNearerThan(pBot->pEdict->v.origin, waypoints[pBot->current_wp].origin, 800.0) || !BotCanSeeOrigin(pBot, waypoints[pBot->current_wp].origin))
BotFindCurrentWaypoint(pBot);
}
BotFallenOffCheck(pBot);
// if not obstructed by a player is the bot obstructed by anything else?
if (BotContactThink(pBot) == nullptr) {
// buoyed by water, or on a ladder?
if ((pBot->pEdict->v.waterlevel > WL_FEET_IN_WATER && !(pBot->pEdict->v.flags & FL_ONGROUND)) || pBot->pEdict->v.movetype == MOVETYPE_FLY) {
// hopefully the bot wont get stuck
}
else // not on a ladder or in some water
{
const float botVelocity = pBot->pEdict->v.velocity.Length();
// is the bot getting stuck?
if (botVelocity < 50.0f) {
const int jumpResult = BotShouldJumpOver(pBot);
if (jumpResult == 2) // can the bot jump onto something?
{
pBot->pEdict->v.button |= IN_JUMP;
}
else {
const int duckResult = BotShouldDuckUnder(pBot);
if (duckResult == 2) // can the bot duck under something?
{
// duck down and move forward
pBot->f_duck_time = pBot->f_think_time + 0.3f;
}
else // can't duck or jump - try random movement tricks
{
// some maps(e.g. hunted) have undetectable obstacles
// jumping and/or ducking can overcome some of those
if (pBot->f_periodicAlert1 < pBot->f_think_time && random_long(1, 1000) <= 501) {
if (random_long(1, 1000) <= 501)
pBot->pEdict->v.button |= IN_JUMP;
else
pBot->f_duck_time = pBot->f_think_time + random_float(0.3f, 1.2f);
}
// randomly switch direction to try and get unstuck
if (pBot->f_periodicAlert3 < pBot->f_think_time && random_long(1, 1000) <= 501) {
if (pBot->side_direction == SIDE_DIRECTION_RIGHT)
pBot->side_direction = SIDE_DIRECTION_LEFT;
else
pBot->side_direction = SIDE_DIRECTION_RIGHT;
}
// make sure the bot isn't stuck strafing into a wall
if (BotCheckWallOnRight(pBot))
pBot->side_direction = SIDE_DIRECTION_LEFT;
else if (BotCheckWallOnLeft(pBot))
pBot->side_direction = SIDE_DIRECTION_RIGHT;
if (pBot->side_direction == SIDE_DIRECTION_RIGHT) {
pBot->f_side_speed = pBot->f_max_speed;
pBot->pEdict->v.button |= IN_MOVERIGHT; // so crates can be pushed
}
else {
pBot->f_side_speed = -pBot->f_max_speed;
pBot->pEdict->v.button |= IN_MOVELEFT; // so crates can be pushed
}
}
}
}
}
}
}
// You should call this function when pBot->goto_wp is valid and you want
// the bot to get to it.
// Set navByStrafe to true if you wish the bot to navigate by using axial
// movement speeds only(i.e. without having to look at the next waypoint).
bool BotNavigateWaypoints(bot_t* pBot, bool navByStrafe) {
if (num_waypoints < 1)
return false;
// has the bot been getting stuck a little too often?
if (pBot->routeFailureTally > 1) {
BotEscapeWaypointTrap(pBot, pBot->goto_wp);
pBot->routeFailureTally = 0;
return false;
}
pBot->f_move_speed = pBot->f_max_speed;
pBot->f_side_speed = 0.0f;
// is it time to consider taking some kind of route shortcut?
if (pBot->f_shortcutCheckTime < pBot->f_think_time || pBot->f_shortcutCheckTime - 60.0f > pBot->f_think_time) // sanity check
{
pBot->f_shortcutCheckTime = pBot->f_think_time + 1.0f;
if (BotFindTeleportShortCut(pBot) == false && pBot->bot_skill < 4) {
if (pBot->pEdict->v.playerclass == TFC_CLASS_MEDIC || pBot->pEdict->v.playerclass == TFC_CLASS_SCOUT)
BotCheckForConcJump(pBot);
else if (pBot->pEdict->v.playerclass == TFC_CLASS_SOLDIER)
BotCheckForRocketJump(pBot);
}
}
// try to navigate by strafing if the bot has an enemy
if (pBot->enemy.ptr != nullptr)
navByStrafe = true;
bool botIsSniping = false;
if (pBot->pEdict->v.playerclass == TFC_CLASS_SNIPER && pBot->current_weapon.iId == TF_WEAPON_SNIPERRIFLE && (pBot->f_snipe_time >= pBot->f_think_time || pBot->pEdict->v.button & IN_ATTACK)) {
botIsSniping = true;
}
// this bit of code checks if the bot is getting stuck whilst navigating
// or not. it does so by making sure that the bot is making some kind
// of progress towards it's waypoint
if (!botIsSniping) // this code confuses sniping snipers
{
const float waypointDistance = (pBot->pEdict->v.origin - waypoints[pBot->current_wp].origin).Length();
// if the bots waypoint has changed recently assume the bot isn't stuck
if (pBot->current_wp != pBot->curr_wp_diff) {
pBot->curr_wp_diff = pBot->current_wp;
pBot->f_progressToWaypoint = waypointDistance + 0.1f;
}
// is the bot the nearest to the waypoint it's been so far?
// (add 1.0 because we want to see sizeable, significant progress)
if (waypointDistance + 1.0f < pBot->f_progressToWaypoint) {
pBot->f_progressToWaypoint = waypointDistance;
// no problem - so reset this
pBot->f_navProblemStartTime = 0.0f;
}
else // uh-oh - looks liek troubble!
{
// remember when the poor bots troubles began(tell us about your mother)
if (pBot->f_navProblemStartTime < 0.1f)
pBot->f_navProblemStartTime = pBot->f_think_time;
}
}
BotContactThink(pBot);
// if the bot has run into some kind of movement problem try to
// handle it here
if (!botIsSniping && pBot->f_navProblemStartTime > 0.1f && pBot->f_navProblemStartTime + 0.5f < pBot->f_think_time) {
// face the waypoint when navigation is getting hindered
// and it's been going on for too long
if (pBot->enemy.ptr == nullptr || pBot->f_navProblemStartTime + 4.0f < pBot->f_think_time)
navByStrafe = false;
// buoyed by water, or on a ladder?
if ((pBot->pEdict->v.waterlevel > WL_FEET_IN_WATER && !(pBot->pEdict->v.flags & FL_ONGROUND)) || pBot->pEdict->v.movetype == MOVETYPE_FLY) {
if (pBot->f_navProblemStartTime + 2.0f < pBot->f_think_time) {
job_struct* newJob = InitialiseNewJob(pBot, JOB_GET_UNSTUCK);
if (newJob != nullptr)
SubmitNewJob(pBot, JOB_GET_UNSTUCK, newJob);
}
}
else // not on a ladder or in some water
{
// if the bot is airborne, duck(useful if jumping onto something)
if (!(pBot->pEdict->v.flags & FL_ONGROUND)) {
// in case the bot forgets it is duck-jumping
if (pBot->pEdict->v.velocity.z > 0.0f)
pBot->f_duck_time = pBot->f_think_time + 0.3f;
}
else // not airborne - time to try and get around an obstruction
{
const int jumpResult = BotShouldJumpOver(pBot);
// can the bot jump over something?
if (jumpResult == 2) // 2 == jumpable
{
pBot->pEdict->v.button |= IN_JUMP;
}
else // can the bot duck under something?
{
const int duckResult = BotShouldDuckUnder(pBot);
if (duckResult == 2) // 2 == duckable
{
pBot->f_duck_time = pBot->f_think_time + 0.3f;
}
// can't jump over, can't duck under either, but blocked by something
else if (jumpResult == 1 // 1 = blocked by something
|| duckResult == 1) {
// try to get unstuck, but not too soon
if (pBot->f_navProblemStartTime + 2.0f < pBot->f_think_time) {
job_struct* newJob = InitialiseNewJob(pBot, JOB_GET_UNSTUCK);
if (newJob != nullptr)
SubmitNewJob(pBot, JOB_GET_UNSTUCK, newJob);
}
else // a little too soon for calling JOB_GET_UNSTUCK
{
// randomly switch direction to try and get unstuck
if (pBot->f_periodicAlert3 < pBot->f_think_time && random_long(1, 1000) <= 501) {
// jump too(it might help occasionally)
pBot->pEdict->v.button |= IN_JUMP;
if (pBot->side_direction == SIDE_DIRECTION_RIGHT)
pBot->side_direction = SIDE_DIRECTION_LEFT;
else
pBot->side_direction = SIDE_DIRECTION_RIGHT;
}
// make sure the bot isn't stuck strafing into a wall
if (BotCheckWallOnRight(pBot))
pBot->side_direction = SIDE_DIRECTION_LEFT;
else if (BotCheckWallOnLeft(pBot))
pBot->side_direction = SIDE_DIRECTION_RIGHT;
if (pBot->side_direction == SIDE_DIRECTION_RIGHT) {
pBot->f_side_speed = pBot->f_max_speed;
pBot->pEdict->v.button |= IN_MOVERIGHT; // so crates can be pushed
}
else {
pBot->f_side_speed = -pBot->f_max_speed;
pBot->pEdict->v.button |= IN_MOVELEFT; // so crates can be pushed
}
}
}
else // the bot doesn't appear to be blocked by anything it can see
{
// we don't know what's stalling the bot, but we can try a
// few simple tricks(if the bots been stalled for 2 seconds or more)
if (pBot->f_navProblemStartTime + 2.0f < pBot->f_think_time) {
// if the bot is directly below it's current waypoint and not on a ladder
// then assume the bot doesn't know it's fallen off and fix it
if (pBot->pEdict->v.flags & FL_ONGROUND && pBot->pEdict->v.waterlevel != WL_HEAD_IN_WATER && !(waypoints[pBot->current_wp].flags & W_FL_LIFT) && pBot->pEdict->v.origin.z + 60.0f < waypoints[pBot->current_wp].origin.z &&
(pBot->pEdict->v.origin - waypoints[pBot->current_wp].origin).Length2D() < 15.0f) {
// UTIL_HostSay(pBot->pEdict, 0, "fixed running vertically!");
////DebugMessageOfDoom!
BotFindCurrentWaypoint(pBot);
return false; // to help avoid a repetitive failure
}
// slow down for a couple of seconds(see if that helps)
if (pBot->enemy.ptr == nullptr && pBot->f_navProblemStartTime + 4.0f > pBot->f_think_time)
pBot->f_move_speed = pBot->f_max_speed / 2.0f;
// some maps(e.g. hunted) have undetectable obstacles
// jumping and/or ducking can overcome some of those
if (pBot->f_periodicAlert1 < pBot->f_think_time && random_long(1, 1000) <= 501) {
if (random_long(1, 1000) <= 501)
pBot->pEdict->v.button |= IN_JUMP;
else
pBot->f_duck_time = pBot->f_think_time + random_float(0.3f, 1.2f);
}
}
}
}
}
}
}
// keep navigating the waypoints, unless a serious problem occurred
if (!BotHeadTowardWaypoint(pBot, navByStrafe))
return false;
// allow the bot to react according to the characteristics of it's current waypoint
if (pBot->current_wp != -1) {
// remember how far the bot is from it's current waypoint
const float current_wp_distance = (waypoints[pBot->current_wp].origin - pBot->pEdict->v.origin).Length();
if (navByStrafe == false) {
// slow the bot down as it approaches the final waypoint on it's route
if (pBot->current_wp == pBot->goto_wp) {
if (current_wp_distance < 70.0f)
pBot->f_move_speed = pBot->f_max_speed / 4;
else if (current_wp_distance < 130.0f)
pBot->f_move_speed = pBot->f_max_speed / 2;
}
// slow down if the next waypoint is a walk waypoint...
if (waypoints[pBot->current_wp].flags & W_FL_WALK && !navByStrafe)
pBot->f_move_speed = pBot->f_max_speed / 3;
}
// if the bot is approaching a lift
if (waypoints[pBot->current_wp].flags & W_FL_LIFT) {
BotUseLift(pBot);
}
// check if the bot is using a ladder
if (waypoints[pBot->current_wp].flags & W_FL_LADDER || pBot->pEdict->v.movetype == MOVETYPE_FLY) {
BotHandleLadderTraffic(pBot); // try to avoid ladder traffic jams
// if not stopping on the ladder hold the forwards key
if (pBot->f_pause_time < pBot->f_think_time || pBot->current_wp != pBot->goto_wp) {
pBot->pEdict->v.button |= IN_FORWARD;
}
}
// check if the next waypoint is a jump waypoint...
if (waypoints[pBot->current_wp].flags & W_FL_JUMP) {
if (pBot->pEdict->v.flags & FL_ONGROUND && pBot->f_pause_time < pBot->f_think_time) {
const Vector jumpPoint = waypoints[pBot->current_wp].origin - (pBot->pEdict->v.origin + pBot->pEdict->v.view_ofs);
// pause briefly if the bot is not facing the jump waypoint enough
if (BotInFieldOfView(pBot, jumpPoint) > 20)
pBot->f_pause_time = pBot->f_think_time + 0.2f;
else if (current_wp_distance < 100.0f) // otherwise jump
pBot->pEdict->v.button |= IN_JUMP;
}
if (pBot->pEdict->v.velocity.z > 0.0f)
pBot->f_duck_time = pBot->f_think_time + 0.25f; // for extra clearance
}
// see if the bot is about to get stuck along a blocked route
// e.g. a detpack tunnel
if (pBot->current_wp != pBot->goto_wp && (waypoints[pBot->current_wp].flags & W_FL_PATHCHECK || waypoints[pBot->current_wp].flags & W_FL_TFC_DETPACK_CLEAR || waypoints[pBot->current_wp].flags & W_FL_TFC_DETPACK_SEAL)) {
// remember if the bot is going to a goal waypoint or a route branching waypoint
int nextWP;
if (pBot->branch_waypoint == -1)
nextWP = WaypointRouteFromTo(pBot->current_wp, pBot->goto_wp, pBot->current_team);
else
nextWP = WaypointRouteFromTo(pBot->current_wp, pBot->branch_waypoint, pBot->current_team);
if (nextWP > -1 && BotPathCheck(pBot->current_wp, nextWP) == false)
BotChangeRoute(pBot);
}
}
return true; // all is well so far
}
// This function would probably be a good place to check to see how close
// to the current waypoint the bot is, and if the bot is close enough to
// the desired waypoint then call BotFindWaypoint to find the next one.
// Set navByStrafe to true if you wish the bot to navigate by using axial
// movement speeds only(i.e. without having to look at the next waypoint).
bool BotHeadTowardWaypoint(bot_t* pBot, bool& r_navByStrafe) {
// check if the bot is taking too long to reach it's current waypoint
if (pBot->f_current_wp_deadline < pBot->f_think_time) {
// tidy up the bots current waypoint, in case it's behind a wall
pBot->current_wp = WaypointFindNearest_S(pBot->pEdict->v.origin, pBot->pEdict, REACHABLE_RANGE, pBot->current_team, lostBotIgnoreFlags);
pBot->f_current_wp_deadline = pBot->f_think_time + BOT_WP_DEADLINE;
++pBot->routeFailureTally; // chalk up another route failure
return false; // give up
}
BotFallenOffCheck(pBot);
// if the bot has a destination waypoint and a current waypoint
if (pBot->current_wp != -1 && pBot->goto_wp != -1) {
// turn r_navByStrafe off if the bot is using a ladder unless
// it has seen an enemy recently and isn't getting seriously stuck
if (r_navByStrafe == true && (waypoints[pBot->current_wp].flags & W_FL_LADDER || pBot->pEdict->v.movetype == MOVETYPE_FLY)) {
if (pBot->enemy.ptr == nullptr)
r_navByStrafe = false;
else if (pBot->f_navProblemStartTime > 0.1f && pBot->f_navProblemStartTime + 4.0f < pBot->f_think_time)
r_navByStrafe = true; // Should be true? [APG]RoboCop[CL]
}
// if the bot is not navigating by strafing or is approaching
// a jump waypoint then face towards the current waypoint
if (r_navByStrafe == false || waypoints[pBot->current_wp].flags & W_FL_JUMP) {
const Vector v_direction = waypoints[pBot->current_wp].origin - pBot->pEdict->v.origin;
const Vector bot_angles = UTIL_VecToAngles(v_direction);
pBot->idle_angle = bot_angles.y;
// allow bots to approach non-goal waypoints imprecisely
if (pBot->current_wp != pBot->goto_wp)
pBot->idle_angle += pBot->f_waypoint_drift;
pBot->pEdict->v.ideal_yaw = pBot->idle_angle;
pBot->pEdict->v.idealpitch = bot_angles.x;
}
else // navByStrafe is active
{
// navigate towards the current waypoint using axial movement speeds only
// to do this we need to know the angle of the bots current waypoint
// from the bots view angle
// get the angle towards the bots current waypoint
const Vector offset = waypoints[pBot->current_wp].origin - pBot->pEdict->v.origin;
Vector vang = UTIL_VecToAngles(offset);
vang.y -= pBot->pEdict->v.v_angle.y;
vang.y = 360.0f - vang.y;
// wrap Y if need be
if (vang.y < 0.0f)
vang.y += 360.0f;
else if (vang.y > 360.0f)
vang.y -= 360.0f;
/* // debug stuff
char msg[96];
std::sprintf(msg, "vang.y %f, v_angle %f",
vang.y, pBot->pEdict->v.v_angle.y);
UTIL_HostSay(pBot->pEdict, 0, msg); //DebugMessageOfDoom!*/
// is the waypoint ahead?
if (vang.y > 275.0f || vang.y < 85.0f) {
// do nothing, leave the bots forward speed as it is
}
// if the waypoint is behind, go backwards
else if (vang.y > 95.0f && vang.y < 265.0f)
pBot->f_move_speed = -pBot->f_move_speed;
else
pBot->f_move_speed = 0.0f; // waypoint is not ahead or behind
// if the waypoint is on the left, sidestep left
if (vang.y > 185.0f && vang.y < 355.0f) {
pBot->side_direction = SIDE_DIRECTION_LEFT;
pBot->f_side_speed = -pBot->f_max_speed;
}
// if the waypoint is on the right, sidestep right
else if (vang.y > 5.0f && vang.y < 175.0f) {
pBot->side_direction = SIDE_DIRECTION_RIGHT;
pBot->f_side_speed = pBot->f_max_speed;
}
else
pBot->f_side_speed = 0.0f; // not on left or right, don't strafe sideways
// now to handle vertical movement when swimming(or bobbing on the surface of water)
if (pBot->pEdict->v.waterlevel == WL_HEAD_IN_WATER || (pBot->pEdict->v.waterlevel == WL_WAIST_IN_WATER // on the surface
&& !(pBot->pEdict->v.flags & FL_ONGROUND))) {
// swim up if below the waypoint
if (pBot->pEdict->v.origin.z < waypoints[pBot->current_wp].origin.z - 5.0f) {
// WaypointDrawBeam(INDEXENT(1), pBot->pEdict->v.origin + pBot->pEdict->v.view_ofs,
// pBot->pEdict->v.origin + Vector(0, 0, 100.0),
// 10, 2, 250, 250, 50, 200, 10);
pBot->f_vertical_speed = pBot->f_max_speed;
}
// swim down if above the waypoint
else if (pBot->pEdict->v.origin.z > waypoints[pBot->current_wp].origin.z + 5.0f) {
// WaypointDrawBeam(INDEXENT(1), pBot->pEdict->v.origin + pBot->pEdict->v.view_ofs,
// pBot->pEdict->v.origin + Vector(0, 0, 100.0),
// 10, 2, 250, 250, 250, 200, 10);
pBot->f_vertical_speed = -pBot->f_max_speed;
}
}
}
}
if (BotUpdateRoute(pBot))
return true;
return false;
}
// BotUpdateRoute()
// Selects the next waypoint along the bot's route.
static bool BotUpdateRoute(bot_t* pBot) {
int new_current_wp; // what pBot->current_wp might be set to
const edict_t* pEdict = pBot->pEdict;
// If the bot doesn't have a current waypoint, then lets
// try sending the bot to the closest waypoint.
if (pBot->current_wp < 0) {
// UTIL_HostSay(pBot->pEdict, 0, "BotFindWaypoint, current_wp == -1"); //DebugMessageOfDoom!
// find the nearest visible waypoint
new_current_wp = WaypointFindNearest_S(pBot->pEdict->v.origin, pEdict, REACHABLE_RANGE, pBot->current_team, lostBotIgnoreFlags);
if (new_current_wp != -1) {
pBot->current_wp = new_current_wp;
pBot->f_current_wp_deadline = pBot->f_think_time + BOT_WP_DEADLINE;
return true;
}
return false;
// couldn't find a current waypoint for the bot
}
// the bot already has a current waypoint
new_current_wp = pBot->current_wp;
// if the bot has a new goal waypoint, dump/reset the current branching waypoint
if (pBot->goto_wp != pBot->lastgoto_wp)
pBot->branch_waypoint = -1;
// if the bot took a branching route and has arrived at the branching waypoint
// then continue onwards to the goal waypoint
if (pBot->current_wp == pBot->branch_waypoint) {
// UTIL_HostSay(pBot->pEdict, 0, "resuming from sideroute"); //DebugMessageOfDoom!
pBot->branch_waypoint = -1;
}
pBot->lastgoto_wp = pBot->goto_wp; // allows us to monitor for changed goal waypoints
const float dist = (waypoints[new_current_wp].origin - pBot->pEdict->v.origin).Length();
// try to find the next waypoint on the route to the bots goal
int nextWP;
if (pBot->branch_waypoint == -1)
nextWP = WaypointRouteFromTo(pBot->current_wp, pBot->goto_wp, pBot->current_team);
else // bots current goal is a branching waypoint
nextWP = WaypointRouteFromTo(pBot->current_wp, pBot->branch_waypoint, pBot->current_team);
// figure out how near to the bot's current waypoint the bot has to be
// before it will move on the next waypoint
float needed_distance = 50.0f; // standard distance for most waypoint types
bool heightCheck = true; // used only when climbing ladders
if (pBot->pEdict->v.movetype == MOVETYPE_FLY) {
// if the bot is on a ladder make sure it is just above the waypoint
needed_distance = 20.0f; // got to be near when on a ladder
if (pBot->pEdict->v.origin.z < waypoints[new_current_wp].origin.z || pBot->pEdict->v.origin.z > waypoints[new_current_wp].origin.z + 10.0f)
heightCheck = false;
}
else if (waypoints[new_current_wp].flags & W_FL_JUMP) {
// gotta be similar height or higher than jump waypoint
if (pBot->pEdict->v.origin.z < waypoints[new_current_wp].origin.z - 15.0f || pBot->pEdict->v.flags & FL_ONGROUND)
heightCheck = false;
needed_distance = 80.0f;
}
else if (waypoints[new_current_wp].flags & W_FL_LIFT)
needed_distance = 25.0f; // some lifts are small(e.g. rock2's lifts)
else if (waypoints[new_current_wp].flags & W_FL_WALK)
needed_distance = 20.0f;
bool waypointTouched = false;
// if the bot's near enough to it's current waypoint, and it hasn't
// reached it's destination then pick the next waypoint on the route
if (new_current_wp != pBot->goto_wp && dist < needed_distance && heightCheck == true) {
waypointTouched = true;
}
// this check can solve waypoint circling problems
else if (dist < 100.0f && pBot->f_navProblemStartTime > 0.1f && pBot->f_navProblemStartTime + 0.5f < pBot->f_think_time) {
if (nextWP != -1 && nextWP != pBot->goto_wp) {
const float pathDistance = static_cast<float>(WaypointDistanceFromTo(new_current_wp, nextWP, pBot->current_team));
const float distToNext = (waypoints[nextWP].origin - pBot->pEdict->v.origin).Length();
// see if the bot is near enough to the next waypoint despite
// not touching the current waypoint
if (distToNext < pathDistance && dist + distToNext < pathDistance + 50.0f) {
// WaypointDrawBeam(INDEXENT(1), pBot->pEdict->v.origin,
// pBot->pEdict->v.origin + Vector(0, 0, 100.0),
// 10, 2, 250, 250, 250, 200, 10);
waypointTouched = true;
}
}
}
if (waypointTouched) {
if (nextWP != -1 && nextWP < num_waypoints) {
new_current_wp = nextWP;
pBot->routeFailureTally = 0; // all is well, reset this
}
else // bot has no route to it's goal
{
++pBot->routeFailureTally; // chalk up another route failure
new_current_wp = pBot->current_wp;
}
// set the amount of time to get to the new current waypoint
if (new_current_wp != pBot->current_wp)
pBot->f_current_wp_deadline = pBot->f_think_time + BOT_WP_DEADLINE;
pBot->current_wp = new_current_wp;
// lemming check! try to stop the bot running off a cliff
/* if(nextWP != -1
&& nextWP != pBot->goto_wp
&& static_cast<int>(pBot->pEdict->v.health) < 91
&& !(waypoints[nextWP].flags & W_FL_LADDER)
&& (waypoints[nextWP].origin.z + 300.0) < pBot->pEdict->v.origin.z)
{
char msg[80];
std::sprintf(msg, "<Avoiding drop of height: %f>",
pBot->pEdict->v.origin.z - waypoints[nextWP].origin.z);
UTIL_HostSay(pBot->pEdict, 0, msg);//DebugMessageOfDoom!
BotChangeRoute(pBot);
}*/
// now that the bot has picked the next waypoint on the route
// to its goal, consider trying an alternate route
BotFindSideRoute(pBot);
}
return true;
}
// This is a fairly simple function.
// If the bot is on a ladder going down, jump off if someone is on
// the ladder coming up.
static void BotHandleLadderTraffic(bot_t* pBot) {
// only handle bots that are on ladders heading downwards
if (pBot->current_wp == -1 || pBot->pEdict->v.movetype != MOVETYPE_FLY || waypoints[pBot->current_wp].origin.z > pBot->pEdict->v.origin.z)
return;
// trace a line straight down
TraceResult tr;
UTIL_TraceLine(pBot->pEdict->v.origin, pBot->pEdict->v.origin - Vector(0.0f, 0.0f, 120.0f), dont_ignore_monsters, pBot->pEdict->v.pContainingEntity, &tr);
// see if we detected a player below
if (tr.flFraction < 1.0f && tr.pHit != nullptr) {
// search the world for players...
for (int i = 1; i <= gpGlobals->maxClients; i++) {
const edict_t* pPlayer = INDEXENT(i);
// skip invalid players
if (pPlayer && !pPlayer->free && pPlayer == tr.pHit) {
// jump off the ladder
pBot->pEdict->v.button = 0; // in case IN_FORWARD is active
pBot->pEdict->v.button |= IN_JUMP;
pBot->f_pause_time = pBot->f_think_time + 1.2f; // so the jump will work
// UTIL_HostSay(pBot->pEdict, 0, "ladder jam"); //DebugMessageOfDoom!
break;
}
}
}
}
// An all in one function for handling bot behaviour as they try to use lifts.
void BotUseLift(bot_t* pBot) {
if (pBot->current_wp == pBot->goto_wp)
return; // shouldn't happen, but just in case
// remember if the bot is going to a goal waypoint or a route branching waypoint
int goalWP = pBot->goto_wp;
if (pBot->branch_waypoint != -1)
goalWP = pBot->branch_waypoint;