1 //
2 // C++ Implementation: bot_ai
3 //
4 // Description: The AI part comes here(navigation, shooting etc)
5 //
6 //
7 // Author: <rickhelmus@gmail.com>
8 //
9
10
11 // Code of CBot - Start
12
13 #include "cube.h"
14 #include "bot.h"
15
16 extern weaponinfo_s WeaponInfoTable[MAX_WEAPONS];
17
GetEnemyPos(playerent * d)18 vec CBot::GetEnemyPos(playerent *d)
19 {
20 // Aim offset idea by botman
21 vec o = m_pMyEnt->weaponsel->type == GUN_SNIPER && d->head.x >= 0 ? d->head : d->o, offset;
22 float flDist = GetDistance(o), flScale;
23
24 if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_ROCKET)
25 {
26 // Bot is using a rocket launcher, aim at enemy feet?
27 if (m_bShootAtFeet && !OUTBORD(d->o.x, d->o.y))
28 {
29 // Only do this when enemy is fairly close to the ground
30 vec end = o;
31 end.z -= 900.0f;
32 traceresult_s tr;
33 TraceLine(o, end, NULL, false, &tr);
34 if ((o.z - tr.end.z) < 8.0f)
35 {
36 end = o;
37 end.z = tr.end.z;
38 if (IsVisible(end))
39 {
40 // Target at ground
41 o.z = tr.end.z;
42 }
43 }
44 }
45
46 if (m_pBotSkill->bCanPredict)
47 {
48 // How higher the skill, how further the bot predicts
49 float flPredictTime = RandomFloat(1.25f, 1.7f) / (m_sSkillNr+1);
50 o = PredictPos(o, d->vel, flPredictTime);
51 }
52 }
53 else
54 {
55 if (m_pBotSkill->bCanPredict)
56 {
57 // How higher the skill, how 'more' the bot predicts
58 float flPredictTime = RandomFloat(0.8f, 1.2f) / (m_sSkillNr+1);
59 o = PredictPos(o, d->vel, flPredictTime);
60 }
61 }
62
63 if (flDist > 60.0f)
64 flScale = 1.0f;
65 else if (flDist > 6.0f)
66 flScale = flDist / 60.0f;
67 else
68 flScale = 0.1f;
69
70 switch (m_sSkillNr)
71 {
72 case 0:
73 // no offset
74 offset.x = 0;
75 offset.y = 0;
76 offset.z = 0;
77 break;
78 case 1:
79 // GOOD, offset a little for x, y, and z
80 offset.x = RandomFloat(-3, 3) * flScale;
81 offset.y = RandomFloat(-3, 3) * flScale;
82 offset.z = RandomFloat(-6, 6) * flScale;
83 break;
84 case 2:
85 // FAIR, offset somewhat for x, y, and z
86 offset.x = RandomFloat(-8, 8) * flScale;
87 offset.y = RandomFloat(-8, 8) * flScale;
88 offset.z = RandomFloat(-12, 12) * flScale;
89 break;
90 case 3:
91 // POOR, offset for x, y, and z
92 offset.x = RandomFloat(-15, 15) * flScale;
93 offset.y = RandomFloat(-15, 15) * flScale;
94 offset.z = RandomFloat(-25, 25) * flScale;
95 break;
96 case 4:
97 // BAD, offset lots for x, y, and z
98 offset.x = RandomFloat(-20, 20) * flScale;
99 offset.y = RandomFloat(-20, 20) * flScale;
100 offset.z = RandomFloat(-35, 35) * flScale;
101 break;
102 }
103
104 o.add(offset);
105 return o;
106 }
107
108 // WIP
BotsAgainstHumans()109 bool CBot::BotsAgainstHumans()
110 {
111 return false;
112 }
113
DetectEnemy(playerent * p)114 bool CBot::DetectEnemy(playerent *p)
115 {
116 return (IsInFOV(p) || (m_pBotSkill->flAlwaysDetectDistance > m_pMyEnt->o.dist(p->o)))
117 && IsVisible(p);
118 }
119
FindEnemy(void)120 bool CBot::FindEnemy(void)
121 {
122 // UNDONE: Enemies are now only scored on their distance
123 if(BotsAgainstHumans())
124 {
125 m_pMyEnt->enemy = NULL;
126 if(player1->state == CS_ALIVE)
127 {
128 m_pMyEnt->enemy = player1;
129 }
130
131 return m_pMyEnt->enemy != NULL;
132 }
133
134 if (m_pMyEnt->enemy) // Bot already has an enemy
135 {
136 // Check if the enemy is still in game
137 bool found = IsInGame(m_pMyEnt->enemy);
138
139 // Check if the enemy is still ingame, still alive, not joined my team and is visible
140 if (found && !isteam(m_pMyEnt->team, m_pMyEnt->enemy->team))
141 {
142 if ((m_pMyEnt->enemy->state == CS_ALIVE) && (IsVisible(m_pMyEnt->enemy)))
143 return true;
144 else
145 m_pPrevEnemy = m_pMyEnt->enemy;
146 }
147 else
148 m_pMyEnt->enemy = NULL;
149 }
150
151 if (m_iEnemySearchDelay > lastmillis) return (m_pMyEnt->enemy!=NULL);
152
153 m_pMyEnt->enemy = NULL;
154
155 // Add enemy searchy delay
156 float MinDelay = m_pBotSkill->flMinEnemySearchDelay;
157 float MaxDelay = m_pBotSkill->flMaxEnemySearchDelay;
158 m_iEnemySearchDelay = lastmillis + int(RandomFloat(MinDelay, MaxDelay) * 1000.0f);
159
160 playerent *pNewEnemy = NULL, *d = NULL;
161 float flDist, flNearestDist = 99999.9f;
162 short EnemyVal, BestEnemyVal = -100;
163
164 // First loop through all players
165 loopv(players)
166 {
167 d = players[i]; // Handy shortcut
168
169 if (d == m_pMyEnt || !d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE))
170 continue;
171
172 // Check if the enemy is visible
173 if(!DetectEnemy(d))
174 continue;
175
176 flDist = GetDistance(d->o);
177 EnemyVal = 1;
178
179 if (flDist < flNearestDist)
180 {
181 EnemyVal+=2;
182 flNearestDist = flDist;
183 }
184
185 if (EnemyVal > BestEnemyVal)
186 {
187 pNewEnemy = d;
188 BestEnemyVal = EnemyVal;
189 }
190 }
191
192 // Then examine the local player
193 if (player1 && !isteam(player1->team, m_pMyEnt->team) &&
194 (player1->state == CS_ALIVE))
195 {
196 // Check if the enemy is visible
197 if(DetectEnemy(player1))
198 {
199 flDist = GetDistance(player1->o);
200 EnemyVal = 1;
201
202 if (flDist < flNearestDist)
203 {
204 EnemyVal+=2;
205 flNearestDist = flDist;
206 }
207
208 if (EnemyVal > BestEnemyVal)
209 {
210 pNewEnemy = player1;
211 BestEnemyVal = EnemyVal;
212 }
213 }
214 }
215 //}
216
217 if (pNewEnemy)
218 {
219 if (!m_pMyEnt->enemy) // Add shoot delay if new enemy is found
220 {
221 float flMinShootDelay = m_pBotSkill->flMinAttackDelay;
222 float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay;
223
224 m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay,
225 flMaxShootDelay) * 1000.0f);
226 }
227
228 if ((m_pMyEnt->enemy != pNewEnemy) && m_pMyEnt->enemy)
229 m_pPrevEnemy = m_pMyEnt->enemy;
230
231 m_pMyEnt->enemy = pNewEnemy;
232 return true;
233 }
234
235 return false;
236 }
237
CheckHunt(void)238 bool CBot::CheckHunt(void)
239 {
240 if (!BotManager.BotsShoot()) return false;
241
242 if (m_pHuntTarget) // Bot already has an enemy to hunt
243 {
244 // Check if the enemy is still in game
245 bool found = IsInGame(m_pHuntTarget);
246
247 // Check if the enemy is still ingame, still alive, not joined my team and is visible
248 if (found && !isteam(m_pMyEnt->team, m_pHuntTarget->team))
249 {
250 if ((m_pHuntTarget->state == CS_ALIVE) && IsReachable(m_vHuntLocation))
251 return true;
252 }
253 else
254 m_pHuntTarget = NULL;
255 }
256
257 if (m_iHuntDelay > lastmillis) return (m_pHuntTarget!=NULL);
258
259 if (m_vHuntLocation!=g_vecZero)
260 m_vPrevHuntLocation = m_vHuntLocation;
261
262 m_pHuntTarget = NULL;
263 m_vHuntLocation = g_vecZero;
264
265 // Add enemy hunt search delay
266 m_iHuntDelay = lastmillis + 1500;
267
268 playerent *pNewEnemy = NULL, *d = NULL;
269 float flDist, flNearestDist = 99999.9f, flNearestOldPosDistToEnemy = 99999.9f;
270 float flNearestOldPosDistToBot = 99999.9f;
271 short EnemyVal, BestEnemyVal = -100;
272 vec BestOldPos;
273
274 // First loop through all players
275 loopv(players)
276 {
277 d = players[i]; // Handy shortcut
278
279 if (d == m_pMyEnt || !d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE))
280 continue;
281
282 flDist = GetDistance(d->o);
283
284 if (flDist > 250.0f) continue;
285
286 EnemyVal = 1;
287
288 if (flDist < flNearestDist)
289 {
290 EnemyVal+=2;
291 flNearestDist = flDist;
292 }
293
294 if (d == m_pPrevEnemy)
295 EnemyVal+=2;
296
297 if (EnemyVal < BestEnemyVal) continue;
298
299 vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero;
300 flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f;
301
302 // Check previous locations of enemy
303 for (int j=0;j<d->history.size();j++)
304 {
305 const vec &v = d->history.getpos(j);
306 if (v==m_vPrevHuntLocation) continue;
307
308 flDist = GetDistance(d->o, v);
309
310 if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v))
311 {
312 flNearestOldPosDistToEnemy = flDist;
313 bestfromenemy = v;
314 }
315 }
316
317 // Check previous locations of bot hisself
318 for (int j=0;j<m_pMyEnt->history.size();j++)
319 {
320 const vec &v = m_pMyEnt->history.getpos(j);
321 if (v==m_vPrevHuntLocation) continue;
322
323 flDist = GetDistance(v);
324
325 if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) &&
326 IsReachable(v))
327 {
328 flNearestOldPosDistToBot = flDist;
329 bestfrombot = v;
330 }
331 }
332
333 if (bestfromenemy!=g_vecZero)
334 {
335 pNewEnemy = d;
336 BestEnemyVal = EnemyVal;
337 BestOldPos = bestfromenemy;
338 }
339 else if (bestfrombot!=g_vecZero)
340 {
341 pNewEnemy = d;
342 BestEnemyVal = EnemyVal;
343 BestOldPos = bestfrombot;
344 }
345
346 // Then examine the local player
347 if (player1 && !isteam(player1->team, m_pMyEnt->team) &&
348 (player1->state == CS_ALIVE) && ((flDist = GetDistance(player1->o)) <= 250.0f))
349 {
350 d = player1;
351 EnemyVal = 1;
352
353 if (flDist < flNearestDist)
354 {
355 EnemyVal+=2;
356 flNearestDist = flDist;
357 }
358
359 if (d == m_pPrevEnemy)
360 EnemyVal+=2;
361
362 if (EnemyVal >= BestEnemyVal)
363 {
364 BestEnemyVal = EnemyVal;
365 vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero;
366 flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f;
367
368 // Check previous locations of enemy
369 for (int j=0;j<d->history.size();j++)
370 {
371 const vec &v = d->history.getpos(j);
372 if (v==m_vPrevHuntLocation) continue;
373
374 flDist = GetDistance(d->o, v);
375
376 if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v))
377 {
378 flNearestOldPosDistToEnemy = flDist;
379 bestfromenemy = v;
380 }
381 }
382
383 // Check previous locations of bot hisself
384 for (int j=0;j<m_pMyEnt->history.size();j++)
385 {
386 const vec &v = m_pMyEnt->history.getpos(j);
387 if (v==m_vPrevHuntLocation) continue;
388
389 flDist = GetDistance(v);
390
391 if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) &&
392 IsReachable(v))
393 {
394 flNearestOldPosDistToBot = flDist;
395 bestfrombot = v;
396 }
397 }
398
399 if (bestfromenemy!=g_vecZero)
400 {
401 pNewEnemy = d;
402 BestEnemyVal = EnemyVal;
403 BestOldPos = bestfromenemy;
404 }
405 else if (bestfrombot!=g_vecZero)
406 {
407 pNewEnemy = d;
408 BestEnemyVal = EnemyVal;
409 BestOldPos = bestfrombot;
410 }
411 }
412 }
413 }
414
415 if (pNewEnemy)
416 {
417 if (!m_pHuntTarget) // Add shoot delay if new enemy is found
418 {
419 float flMinShootDelay = m_pBotSkill->flMinAttackDelay;
420 float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay;
421
422 m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay,
423 flMaxShootDelay) * 1000.0f);
424 }
425
426 if (m_vHuntLocation!=g_vecZero)
427 m_vPrevHuntLocation = m_vHuntLocation;
428
429 m_pHuntTarget = pNewEnemy;
430 m_vHuntLocation = BestOldPos;
431 m_fPrevHuntDist = 0.0f;
432 return true;
433 }
434
435 return false;
436 }
437
HuntEnemy(void)438 bool CBot::HuntEnemy(void)
439 {
440 if (m_iCombatNavTime > lastmillis)
441 {
442 SetMoveDir(m_iMoveDir, false);
443 return true;
444 }
445
446 m_iCombatNavTime = m_iMoveDir = 0;
447
448 bool bDone = false, bNew = false;
449 float flDist = GetDistance(m_vHuntLocation);
450
451 if (flDist <= 3.0f)
452 bDone = true;
453
454 if ((m_fPrevHuntDist > 0.0) && (flDist > m_fPrevHuntDist))
455 bDone = true;
456
457 m_fPrevHuntDist = flDist;
458
459 if ((m_iHuntPlayerUpdateTime < lastmillis) || bDone)
460 {
461 m_iHuntPlayerUpdateTime = lastmillis + 1250;
462
463 short BestPosIndexFromEnemy = -1, BestPosIndexFromBot = -1, j;
464 float NearestDistToEnemy = 9999.9f, NearestDistToBot = 9999.9f;
465
466 // Check previous locations of enemy
467 for (j=0;j<m_pHuntTarget->history.size();j++)
468 {
469 const vec &OldPos = m_pHuntTarget->history.getpos(j);
470
471 if (bDone && m_vHuntLocation==OldPos)
472 continue;
473
474 if (GetDistance(OldPos) > 250.0f)
475 continue;
476
477 flDist = GetDistance(m_pHuntTarget->o, OldPos);
478
479 if ((flDist < NearestDistToEnemy) && (IsReachable(OldPos)))
480 {
481 NearestDistToEnemy = flDist;
482 BestPosIndexFromEnemy = j;
483 break;
484 }
485 }
486
487 // Check previous locations of bot
488 for (j=0;j<m_pMyEnt->history.size();j++)
489 {
490 const vec &OldPos = m_pMyEnt->history.getpos(j);
491
492 if (bDone && m_vHuntLocation==OldPos)
493 continue;
494
495 if (GetDistance(OldPos) > 25.0f)
496 continue;
497
498 flDist = GetDistance(OldPos);
499
500 if ((flDist < NearestDistToBot) && ::IsVisible(m_pHuntTarget->o, OldPos) &&
501 (IsReachable(OldPos)))
502 {
503 NearestDistToBot = flDist;
504 BestPosIndexFromBot = j;
505 break;
506 }
507 }
508
509 if (BestPosIndexFromEnemy > -1)
510 {
511 m_vPrevHuntLocation = m_vHuntLocation;
512 m_vHuntLocation = m_pHuntTarget->history.getpos(BestPosIndexFromEnemy);
513 bNew = true;
514 m_fPrevHuntDist = 0.0f;
515 }
516 else if (BestPosIndexFromBot > -1)
517 {
518 m_vPrevHuntLocation = m_vHuntLocation;
519 m_vHuntLocation = m_pMyEnt->history.getpos(BestPosIndexFromEnemy);
520 bNew = true;
521 m_fPrevHuntDist = 0.0f;
522 }
523 }
524
525 if (!bNew) // Check if current location is still reachable
526 {
527 if (bDone || !IsReachable(m_vHuntLocation))
528 {
529 m_pHuntTarget = NULL;
530 m_vPrevHuntLocation = m_vHuntLocation;
531 m_vHuntLocation = g_vecZero;
532 m_fPrevHuntDist = 0.0f;
533 m_iHuntDelay = lastmillis + 3500;
534 return false;
535 }
536 }
537 else
538 condebug("New hunt pos");
539
540 // Aim to position
541 //AimToVec(m_vHuntLocation);
542
543 int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, m_vHuntLocation);
544
545 if (iMoveDir != DIR_NONE)
546 {
547 m_iMoveDir = iMoveDir;
548 m_iCombatNavTime = lastmillis + 125;
549 }
550
551 bool aimtopos = true;
552
553 if ((lastmillis - m_iSawEnemyTime) > 1500)
554 {
555 if (m_iLookAroundDelay < lastmillis)
556 {
557 if (m_iLookAroundTime > lastmillis)
558 {
559 if (m_iLookAroundUpdateTime < lastmillis)
560 {
561 float flAddAngle;
562 if (m_bLookLeft) flAddAngle = RandomFloat(-110, -80);
563 else flAddAngle = RandomFloat(80, 110);
564 m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->targetyaw + flAddAngle);
565 m_iLookAroundUpdateTime = lastmillis + RandomLong(400, 800);
566 }
567 aimtopos = false;
568 }
569 else if (m_iLookAroundTime > 0)
570 {
571 m_iLookAroundTime = 0;
572 m_iLookAroundDelay = lastmillis + RandomLong(750, 1000);
573 }
574 else
575 m_iLookAroundTime = lastmillis + RandomLong(2200, 3200);
576 }
577 }
578
579 if (aimtopos)
580 AimToVec(m_vHuntLocation);
581
582 debugbeam(m_pMyEnt->o, m_vHuntLocation);
583
584 if (m_fYawToTurn <= 25.0f)
585 m_iHuntLastTurnLessTime = lastmillis;
586
587 // Bot had to turn much for a while?
588 if ((m_iHuntLastTurnLessTime > 0) && (m_iHuntLastTurnLessTime < (lastmillis - 1000)))
589 {
590 m_iHuntPauseTime = lastmillis + 200;
591 }
592
593 if (m_iHuntPauseTime >= lastmillis)
594 {
595 m_pMyEnt->move = 0;
596 m_fPrevHuntDist = 0.0f;
597 }
598 else
599 {
600 // Check if bot has to jump over a wall...
601 if (CheckJump())
602 m_pMyEnt->jumpnext = true;
603 else // Check if bot has to jump to reach this location
604 {
605 float flHeightDiff = m_vHuntLocation.z - m_pMyEnt->o.z;
606 bool bToHigh = false;
607 if (Get2DDistance(m_vHuntLocation) <= 2.0f)
608 {
609 if (flHeightDiff >= 1.5f)
610 {
611 if (flHeightDiff <= JUMP_HEIGHT)
612 {
613 #ifndef RELEASE_BUILD
614 char sz[64];
615 sprintf(sz, "OldPos z diff: %f", m_vHuntLocation.z-m_pMyEnt->o.z);
616 condebug(sz);
617 #endif
618 // Jump if close to pos and the pos is high
619 m_pMyEnt->jumpnext = true;
620 }
621 else
622 bToHigh = true;
623 }
624 }
625
626 if (bToHigh)
627 {
628 m_pHuntTarget = NULL;
629 m_vPrevHuntLocation = m_vHuntLocation;
630 m_vHuntLocation = g_vecZero;
631 m_fPrevHuntDist = 0.0f;
632 m_iHuntDelay = lastmillis + 3500;
633 return false;
634 }
635 }
636 }
637
638 return true;
639 }
640
CheckWeaponSwitch()641 void CBot::CheckWeaponSwitch()
642 {
643 if(m_pMyEnt->nextweaponsel == NULL) m_pMyEnt->weaponchanging = 0;
644 if(!m_pMyEnt->weaponchanging) return;
645
646 int timeprogress = lastmillis-m_pMyEnt->weaponchanging;
647 if(timeprogress>weapon::weaponchangetime)
648 {
649 m_pMyEnt->prevweaponsel = m_pMyEnt->weaponsel;
650 m_pMyEnt->weaponsel = m_pMyEnt->nextweaponsel;
651 if(m_pMyEnt->weaponsel!=NULL) addmsg(SV_WEAPCHANGE, "ri", m_pMyEnt->weaponsel->type); // 2011jan17:ft: message possibly not needed in a local game!?!
652 m_pMyEnt->weaponchanging = 0;
653 m_iChangeWeaponDelay = 0;
654 if(!m_pMyEnt->weaponsel->mag)
655 {
656 tryreload(m_pMyEnt);
657 }
658 }
659 }
660
ShootEnemy()661 void CBot::ShootEnemy()
662 {
663 if(!m_pMyEnt->enemy) return;
664 if(!IsVisible(m_pMyEnt->enemy)) return;
665
666 m_iSawEnemyTime = lastmillis;
667
668 // Aim to enemy
669 vec enemypos = GetEnemyPos(m_pMyEnt->enemy);
670 AimToVec(enemypos);
671
672 // Time to shoot?
673 if (m_iShootDelay < lastmillis)
674 //if ((lastmillis-m_pMyEnt->lastaction) >= m_pMyEnt->gunwait)
675 {
676 if (m_pMyEnt->mag[m_pMyEnt->gunselect])
677 {
678 // If the bot is using a sniper only shoot if crosshair is near the enemy
679 if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_SNIPER)
680 {
681 float yawtoturn = fabs(WrapYZAngle(m_pMyEnt->yaw - m_pMyEnt->targetyaw));
682 float pitchtoturn = fabs(WrapYZAngle(m_pMyEnt->pitch - m_pMyEnt->targetpitch));
683
684 if ((yawtoturn > 5) || (pitchtoturn > 15)) // UNDONE: Should be skill based
685 return;
686 }
687
688 float flDist = GetDistance(enemypos);
689
690 // Check if bot is in fire range
691 if ((flDist < WeaponInfoTable[m_pMyEnt->gunselect].flMinFireDistance) ||
692 (flDist > WeaponInfoTable[m_pMyEnt->gunselect].flMaxFireDistance))
693 return;
694
695 // Now shoot!
696 m_pMyEnt->attacking = true;
697
698 // Get the position the bot is aiming at
699 vec forward, right, up, dest;
700 traceresult_s tr;
701
702 AnglesToVectors(GetViewAngles(), forward, right, up);
703
704 dest = m_pMyEnt->o;
705 forward.mul(1000);
706 dest.add(forward);
707
708 TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr);
709 debugbeam(m_pMyEnt->o, tr.end);
710
711 shoot(m_pMyEnt, tr.end);
712
713 // Add shoot delay
714 m_iShootDelay = lastmillis + GetShootDelay();
715 }
716 }
717 #ifndef RELEASE_BUILD
718 else
719 {
720 char sz[64];
721 sprintf(sz, "shootdelay: %d\n", (m_iShootDelay-lastmillis));
722 AddDebugText(sz);
723 }
724 #endif
725 }
726
ChoosePreferredWeapon()727 bool CBot::ChoosePreferredWeapon()
728 {
729 return true;
730 }
731
GetShootDelay()732 int CBot::GetShootDelay()
733 {
734 // UNDONE
735 return m_pMyEnt->gunwait[m_pMyEnt->gunselect];
736 if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) ||
737 (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_AUTO))
738 return m_pMyEnt->gunwait[m_pMyEnt->gunselect];
739
740 float flMinShootDelay = m_pBotSkill->flMinAttackDelay;
741 float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay;
742 return max(m_pMyEnt->gunwait[m_pMyEnt->gunselect], int(RandomFloat(flMinShootDelay, flMaxShootDelay) * 1000.0f));
743 }
744
CheckReload()745 void CBot::CheckReload() // reload gun if no enemies are around
746 {
747 if(m_pMyEnt->mag[m_pMyEnt->weaponsel->type] >= WeaponInfoTable[m_pMyEnt->weaponsel->type].sMinDesiredAmmo) return; // do not reload if mindesiredammo is satisfied
748 if(m_pMyEnt->enemy && m_pMyEnt->mag[m_pMyEnt->weaponsel->type])
749 {
750 return; // ignore the enemy, if no ammo in mag.
751 }
752 tryreload(m_pMyEnt);
753 return;
754 }
755
CheckScope()756 void CBot::CheckScope()
757 {
758 #define MINSCOPEDIST 15
759 #define MINSCOPETIME 1000
760 if(m_pMyEnt->weaponsel->type != GUN_SNIPER) return;
761 sniperrifle *sniper = (sniperrifle *)m_pMyEnt->weaponsel;
762 if(m_pMyEnt->enemy && m_pMyEnt->o.dist(m_pMyEnt->enemy->o) > MINSCOPEDIST)
763 {
764 sniper->setscope(true);
765 }
766 else if(m_pMyEnt->scoping && lastmillis - sniper->scoped_since < MINSCOPETIME)
767 {
768 sniper->setscope(false);
769 }
770 }
771
772
MainAI()773 void CBot::MainAI()
774 {
775 // Default bots will run forward
776 m_pMyEnt->move = 1;
777
778 // Default bots won't strafe
779 m_pMyEnt->strafe = 0;
780
781 // Whatever the bot is doing, check for needed crouch
782 if(CheckCrouch()) m_pMyEnt->trycrouch = true;
783 else m_pMyEnt->trycrouch = false;
784
785 if (!BotManager.BotsShoot() && m_pMyEnt->enemy)
786 m_pMyEnt->enemy = NULL; // Clear enemy when bots may not shoot
787
788 if (m_bGoToDebugGoal) // For debugging the waypoint navigation
789 {
790 if (!HeadToGoal())
791 {
792 ResetWaypointVars();
793 m_vGoal = g_vecZero;
794 }
795 else
796 AddDebugText("Heading to debug goal...");
797 }
798 if (BotManager.BotsShoot() && FindEnemy()) // Combat
799 {
800 CheckReload();
801 CheckScope();
802 AddDebugText("has enemy");
803 // Use best weapon
804 ChoosePreferredWeapon();
805 // Shoot at enemy
806 ShootEnemy();
807
808 if (m_eCurrentBotState != STATE_ENEMY)
809 {
810 m_vGoal = g_vecZero;
811 ResetWaypointVars();
812 }
813
814 m_eCurrentBotState = STATE_ENEMY;
815 if (!CheckJump())
816 DoCombatNav();
817 }
818 else if (CheckHunt() && HuntEnemy())
819 {
820 CheckReload();
821 CheckScope();
822 AddDebugText("Hunting to %s", m_pHuntTarget->name);
823 m_eCurrentBotState = STATE_HUNT;
824 }
825 // Heading to an interesting entity(ammo, armour etc)
826 else if (CheckItems())
827 {
828 CheckReload();
829 AddDebugText("has ent");
830 m_eCurrentBotState = STATE_ENT;
831 }
832 else if (m_classicsp && DoSPStuff()) // Home to goal, find/follow friends etc.
833 {
834
835 AddDebugText("SP stuff");
836 m_eCurrentBotState = STATE_SP;
837 }
838 else // Normal navigation
839 {
840 CheckReload();
841 if (m_eCurrentBotState != STATE_NORMAL)
842 {
843 m_vGoal = g_vecZero;
844 ResetWaypointVars();
845 }
846
847 m_eCurrentBotState = STATE_NORMAL;
848 bool bDoNormalNav = true;
849
850 AddDebugText("normal nav");
851
852 // Make sure the bot looks straight forward and not up or down
853 m_pMyEnt->pitch = 0;
854
855 // if it is time to look for a waypoint AND if there are waypoints in this
856 // level...
857 if (WaypointClass.m_iWaypointCount >= 1)
858 {
859 // check if we need to find a waypoint...
860 if (CurrentWPIsValid() == false)
861 {
862 if (m_iLookForWaypointTime <= lastmillis)
863 {
864 // find the nearest reachable waypoint
865 waypoint_s *pWP = GetNearestWaypoint(10.0f);
866
867 if (pWP && (pWP != m_pCurrentWaypoint))
868 {
869 SetCurrentWaypoint(pWP);
870 condebug("New nav wp");
871 bDoNormalNav = !HeadToWaypoint();
872 if (bDoNormalNav)
873 ResetWaypointVars();
874 }
875 else
876 ResetWaypointVars();
877
878 m_iLookForWaypointTime = lastmillis + 250;
879 }
880 }
881 else
882 {
883 bDoNormalNav = !HeadToWaypoint();
884 if (bDoNormalNav)
885 ResetWaypointVars();
886 AddDebugText("Using wps for nav");
887 }
888 }
889
890 // If nothing special, do regular (waypointless) navigation
891 if(bDoNormalNav)
892 {
893 // Is the bot underwater?
894 if (UnderWater(m_pMyEnt->o) && WaterNav())
895 {
896 // Bot is under water, navigation happens in WaterNav
897 }
898 // Time to check the environment?
899 else if (m_iCheckEnvDelay < lastmillis)
900 {
901 if (m_vWaterGoal!=g_vecZero) m_vWaterGoal = g_vecZero;
902
903 // Check for stuck and strafe
904 if (UnderWater(m_pMyEnt->o) || !CheckStuck())
905 {
906 // Only do this when the bot is underwater or when the bot isn't stuck
907
908 // Check field of view (FOV)
909 CheckFOV();
910 }
911 }
912
913 // Check if the bot has to strafe
914 CheckStrafe();
915
916 m_pMyEnt->move = 1;
917 }
918 }
919 }
920
DoCombatNav()921 void CBot::DoCombatNav()
922 {
923 if (m_iCombatNavTime > lastmillis)
924 {
925 // If bot has a lower skill and has to turn much, wait
926 if ((m_sSkillNr > 2) && (m_fYawToTurn > 90.0f))
927 {
928 ResetMoveSpeed();
929 }
930 else
931 {
932 SetMoveDir(m_iMoveDir, false);
933 }
934 return;
935 }
936
937 if (m_bCombatJump)
938 {
939 m_pMyEnt->jumpnext = true;
940 m_bCombatJump = false;
941 m_iCombatJumpDelay = lastmillis + RandomLong(1500, 2800);
942 return;
943 }
944
945 m_iMoveDir = DIR_NONE;
946
947 // Check if bot is on top of his enemy
948 float r = m_pMyEnt->radius+m_pMyEnt->enemy->radius;
949 if ((fabs(m_pMyEnt->enemy->o.x-m_pMyEnt->o.x)<r &&
950 fabs(m_pMyEnt->enemy->o.y-m_pMyEnt->o.y)<r) &&
951 ((m_pMyEnt->enemy->o.z+m_pMyEnt->enemy->aboveeye) < (m_pMyEnt->o.z + m_pMyEnt->aboveeye)))
952 {
953 // Try to get off him!
954 condebug("On enemy!");
955 TMultiChoice<int> AwayDirChoices;
956
957 if (IsVisible(LEFT, 4.0f, false))
958 AwayDirChoices.Insert(LEFT);
959 if (IsVisible(RIGHT, 4.0f, false))
960 AwayDirChoices.Insert(RIGHT);
961 if (IsVisible(FORWARD, 4.0f, false))
962 AwayDirChoices.Insert(FORWARD);
963 if (IsVisible(BACKWARD, 4.0f, false))
964 AwayDirChoices.Insert(BACKWARD);
965
966 int iDir;
967 if (AwayDirChoices.GetSelection(iDir))
968 {
969 m_iMoveDir = iDir;
970 m_iCombatNavTime = lastmillis + 500;
971 }
972 }
973
974 float flDist = GetDistance(m_pMyEnt->enemy->o);
975
976 // Check for nearby items?
977 if (((m_iCheckEntsDelay < lastmillis) || m_pTargetEnt) &&
978 m_pBotSkill->bCanSearchItemsInCombat)
979 {
980 m_iCheckEntsDelay = lastmillis + 125;
981 bool bSearchItems = false;
982
983 if (m_pTargetEnt)
984 {
985 // Bot has already found an entity, still valid?
986 vec v(m_pTargetEnt->x, m_pTargetEnt->y,
987 S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight);
988 if ((GetDistance(v) > 25.0f) || !IsVisible(m_pTargetEnt))
989 m_pTargetEnt = NULL;
990 }
991
992 if (!m_pTargetEnt && (m_iCheckEntsDelay <= lastmillis))
993 {
994 if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE)
995 bSearchItems = (flDist >= 8.0f);
996 else
997 bSearchItems = (m_pMyEnt->ammo[m_pMyEnt->gunselect] <=
998 WeaponInfoTable[m_pMyEnt->gunselect].sMinDesiredAmmo);
999
1000 if (bSearchItems)
1001 m_pTargetEnt = SearchForEnts(false, 25.0f, 1.0f);
1002 }
1003
1004 if (m_pTargetEnt)
1005 {
1006 condebug("Combat ent");
1007 vec v(m_pTargetEnt->x, m_pTargetEnt->y,
1008 S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight);
1009
1010 debugbeam(m_pMyEnt->o, v);
1011
1012 float flHeightDiff = v.z - m_pMyEnt->o.z;
1013 bool bToHigh = false;
1014
1015 // Check he height for this ent
1016 if (Get2DDistance(v) <= 2.0f)
1017 {
1018 if (flHeightDiff >= 1.5f)
1019 {
1020 if (flHeightDiff <= JUMP_HEIGHT)
1021 {
1022 #ifndef RELEASE_BUILD
1023 char sz[64];
1024 sprintf(sz, "Ent z diff: %f", v.z-m_pMyEnt->o.z);
1025 condebug(sz);
1026 #endif
1027 m_pMyEnt->jumpnext = true; // Jump if close to ent and the ent is high
1028 }
1029 else
1030 bToHigh = true;
1031 }
1032 }
1033
1034 if (!bToHigh)
1035 {
1036 int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, v);
1037
1038 if (iMoveDir != DIR_NONE)
1039 {
1040 m_iMoveDir = iMoveDir;
1041 m_iCombatNavTime = lastmillis + RandomLong(125, 250);
1042 }
1043
1044 // Check if bot needs to jump over something
1045 vec from = m_pMyEnt->o;
1046 from.z -= 1.0f;
1047 if (!IsVisible(from, iMoveDir, 3.0f, false))
1048 m_pMyEnt->jumpnext = true;
1049
1050 return;
1051 }
1052 }
1053 }
1054
1055 // High skill and enemy is close?
1056 if ((m_sSkillNr <= 1) && (m_fYawToTurn < 80.0f) && (flDist <= 20.0f) &&
1057 (m_iCombatJumpDelay < lastmillis))
1058 {
1059 // Randomly jump a bit, to avoid some basic firepower ;)
1060
1061 // How lower the distance to the enemy, how higher the chance for a jump
1062 short sJumpPercent = (100 - ((short)flDist * 8));
1063 if (RandomLong(1, 100) <= sJumpPercent)
1064 {
1065 // Choose a nice direction to jump to
1066
1067 // Is the enemy close?
1068 if ((GetDistance(m_pMyEnt->enemy->o) <= 4.0f) ||
1069 (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE))
1070 {
1071 m_iMoveDir = FORWARD; // Jump forward
1072 SetMoveDir(FORWARD, false);
1073 m_bCombatJump = true;
1074 }
1075 else if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE)// else jump to a random direction
1076 {
1077 /*
1078 Directions to choose:
1079 - Forward-right
1080 - Right
1081 - Backward-right
1082 - Backward
1083 - Backward-left
1084 - Left
1085 - Forward-left
1086
1087 */
1088
1089 TMultiChoice<int> JumpDirChoices;
1090 short sForwardScore = ((flDist > 8.0f) || (flDist < 4.0f)) ? 20 : 10;
1091 short sBackwardScore = (flDist <= 6.0f) ? 20 : 10;
1092 short sStrafeScore = (flDist < 6.0f) ? 20 : 10;
1093
1094 if (IsVisible((FORWARD | LEFT), 4.0f, false))
1095 JumpDirChoices.Insert((FORWARD | LEFT), sForwardScore);
1096 if (IsVisible((FORWARD | RIGHT), 4.0f, false))
1097 JumpDirChoices.Insert((FORWARD | RIGHT), sForwardScore);
1098 if (IsVisible(BACKWARD, 4.0f, false))
1099 JumpDirChoices.Insert(BACKWARD, sBackwardScore);
1100 if (IsVisible((BACKWARD | LEFT), 4.0f, false))
1101 JumpDirChoices.Insert((BACKWARD | LEFT), sBackwardScore);
1102 if (IsVisible((BACKWARD | RIGHT), 4.0f, false))
1103 JumpDirChoices.Insert((BACKWARD | RIGHT), sBackwardScore);
1104 if (IsVisible(LEFT, 4.0f, false))
1105 JumpDirChoices.Insert(LEFT, sStrafeScore);
1106 if (IsVisible(RIGHT, 4.0f, false))
1107 JumpDirChoices.Insert(RIGHT, sStrafeScore);
1108
1109 int JumpDir;
1110 if (JumpDirChoices.GetSelection(JumpDir))
1111 {
1112 m_iMoveDir = JumpDir;
1113 SetMoveDir(JumpDir, false);
1114 m_bCombatJump = true;
1115 }
1116 }
1117
1118 if (m_bCombatJump)
1119 {
1120 m_iCombatNavTime = lastmillis + RandomLong(125, 250);
1121 return;
1122 }
1123 }
1124 }
1125
1126 if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE)
1127 return; // Simply walk towards enemy if using a melee type
1128
1129 flDist = Get2DDistance(m_pMyEnt->enemy->o);
1130
1131 // Out of desired range for current weapon?
1132 if ((flDist <= WeaponInfoTable[m_pMyEnt->gunselect].flMinDesiredDistance) ||
1133 (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance))
1134 {
1135 if (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance)
1136 {
1137 m_iMoveDir = FORWARD;
1138 }
1139 else
1140 {
1141 m_iMoveDir = BACKWARD;
1142 }
1143
1144 vec src, forward, right, up, dest, MyAngles = GetViewAngles(), o = m_pMyEnt->o;
1145 traceresult_s tr;
1146
1147 // Is it furthest or farthest? bleh
1148 float flFurthestDist = 0;
1149 int bestdir = -1, dir = 0;
1150 bool moveback = (m_pMyEnt->move == -1);
1151
1152 for(int j=-45;j<=45;j+=45)
1153 {
1154 src = MyAngles;
1155 src.y = WrapYZAngle(src.y + j);
1156 src.x = 0.0f;
1157
1158 // If we're moving backwards, trace backwards
1159 if (moveback)
1160 src.y = WrapYZAngle(src.y + 180);
1161
1162 AnglesToVectors(src, forward, right, up);
1163
1164 dest = o;
1165 forward.mul(40);
1166 dest.add(forward);
1167
1168 TraceLine(o, dest, m_pMyEnt, false, &tr);
1169
1170 //debugbeam(origin, end);
1171 flDist = GetDistance(tr.end);
1172
1173 if (flFurthestDist < flDist)
1174 {
1175 flFurthestDist = flDist;
1176 bestdir = dir;
1177 }
1178 dir++;
1179 }
1180
1181 switch(bestdir)
1182 {
1183 case 0:
1184 if (moveback)
1185 m_iMoveDir |= RIGHT; // Strafe right
1186 else
1187 m_iMoveDir |= LEFT; // Strafe left
1188 break;
1189 case 2:
1190 if (moveback)
1191 m_iMoveDir |= LEFT; // Strafe left
1192 else
1193 m_iMoveDir |= RIGHT; // Strafe right
1194 break;
1195 }
1196
1197 if (m_iMoveDir != DIR_NONE)
1198 {
1199 SetMoveDir(m_iMoveDir, false);
1200 m_iCombatNavTime = lastmillis + 500;
1201 }
1202 }
1203 else if (m_pBotSkill->bCircleStrafe) // Circle strafe when in desired range...
1204 {
1205 traceresult_s tr;
1206 vec angles, end, forward, right, up;
1207 TMultiChoice<int> StrafeDirChoices;
1208
1209 // Check the left side...
1210 angles = GetViewAngles();
1211 angles.y = WrapYZAngle(angles.y - 75.0f); // Not 90 degrees because the bot
1212 // doesn't strafe in a straight line
1213 // (aims still to enemy).
1214
1215 AnglesToVectors(angles, forward, right, up);
1216 end = m_pMyEnt->o;
1217 forward.mul(15.0f);
1218 end.add(forward);
1219
1220 TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr);
1221 StrafeDirChoices.Insert(LEFT, (int)GetDistance(m_pMyEnt->o, tr.end));
1222
1223 // Check the right side...
1224 angles = GetViewAngles();
1225 angles.y = WrapYZAngle(angles.y + 75.0f); // Not 90 degrees because the bot
1226 // doesn't strafe in a straight line
1227 // (aims still to enemy).
1228
1229 AnglesToVectors(angles, forward, right, up);
1230 end = m_pMyEnt->o;
1231 forward.mul(15.0f);
1232 end.add(forward);
1233
1234 TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr);
1235 StrafeDirChoices.Insert(RIGHT, (int)GetDistance(m_pMyEnt->o, tr.end));
1236
1237 int StrafeDir;
1238 if (StrafeDirChoices.GetSelection(StrafeDir))
1239 {
1240 m_iMoveDir = StrafeDir;
1241 SetMoveDir(StrafeDir, false);
1242 m_iCombatNavTime = lastmillis + RandomLong(1500, 3000);
1243 }
1244 }
1245 else // Bot can't circle strafe(low skill), just stand still
1246 ResetMoveSpeed();
1247 }
1248
CheckStuck()1249 bool CBot::CheckStuck()
1250 {
1251 if (m_iStuckCheckDelay + (CheckCrouch() ? 2000 : 0) >= lastmillis)
1252 return false;
1253
1254 if ((m_vGoal!=g_vecZero) && (GetDistance(m_vGoal) < 2.0f))
1255 return false;
1256
1257 bool IsStuck = false;
1258
1259 vec CurPos = m_pMyEnt->o, PrevPos = m_vPrevOrigin;
1260 CurPos.z = PrevPos.z = 0;
1261 // Did the bot hardly move the last frame?
1262 if (GetDistance(CurPos, PrevPos) <= 0.1f)
1263 {
1264 if (m_bStuck)
1265 {
1266 if (m_iStuckTime < lastmillis)
1267 IsStuck = true;
1268 }
1269 else
1270 {
1271 m_bStuck = true;
1272 m_iStuckTime = lastmillis + 1000;
1273 }
1274 }
1275 else
1276 {
1277 m_bStuck = false;
1278 m_iStuckTime = 0;
1279 }
1280
1281 if (IsStuck)
1282 {
1283 #ifndef RELEASE_BUILD
1284 char msg[64];
1285 sprintf(msg, "stuck (%f)", GetDistance(m_vPrevOrigin));
1286 condebug(msg);
1287 #endif
1288
1289 m_bStuck = false;
1290 m_iStuckTime = 0;
1291
1292 // Crap bot is stuck, lets just try some random things
1293
1294 // Check if the bot can turn around
1295 vec src = GetViewAngles();
1296 src.x = 0;
1297 vec forward, right, up, dir, dest;
1298 traceresult_s tr;
1299
1300 AnglesToVectors(src, forward, right, up);
1301
1302 // Check the left side...
1303 dir = right;
1304 dest = m_pMyEnt->o;
1305 dir.mul(3);
1306 dest.sub(dir);
1307
1308 TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr);
1309 //debugbeam(m_pMyEnt->o, end);
1310
1311 if (!tr.collided)
1312 {
1313 // Bot can turn left, do so
1314 m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw - 90);
1315 m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500;
1316 return true;
1317 }
1318
1319 // Check the right side...
1320 dir = right;
1321 dest = m_pMyEnt->o;
1322 dir.mul(3);
1323 dest.add(dir);
1324
1325 TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr);
1326 //debugbeam(m_pMyEnt->o, end);
1327
1328 if (!tr.collided)
1329 {
1330 // Bot can turn right, do so
1331 m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 90);
1332 m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500;
1333 return true;
1334 }
1335
1336 // Check if bot can turn 180 degrees
1337 dir = forward;
1338 dest = m_pMyEnt->o;
1339 dir.mul(3);
1340 dest.add(dir);
1341
1342 TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr);
1343 //debugbeam(m_pMyEnt->o, end);
1344
1345 if (!tr.collided)
1346 {
1347 // Bot can turn around, do so
1348 m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180);
1349 m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500;
1350 return true;
1351 }
1352
1353 // Bleh bot couldn't turn, lets just randomly jump :|
1354
1355 condebug("Randomly avoiding stuck...");
1356 if (RandomLong(0, 2) == 0)
1357 m_pMyEnt->jumpnext = true;
1358 else
1359 m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomLong(60, 160));
1360 return true;
1361 }
1362
1363 return false;
1364 }
1365
1366 // Check if a near wall is blocking and we can jump over it
CheckJump()1367 bool CBot::CheckJump()
1368 {
1369 bool bHasGoal = m_vGoal!=g_vecZero;
1370 float flGoalDist = (bHasGoal) ? GetDistance(m_vGoal) : 0.0f;
1371
1372 // if ((bHasGoal) && (flGoalDist < 2.0f))
1373 // return false; UNDONE?
1374
1375 vec start = m_pMyEnt->o;
1376 float flTraceDist = 3.0f;
1377
1378 if (bHasGoal && (flGoalDist < flTraceDist))
1379 flTraceDist = flGoalDist;
1380
1381 // Something blocks at eye hight?
1382 if (!IsVisible(start, FORWARD, flTraceDist, false))
1383 {
1384 // Check if the bot can jump over it
1385 start.z += (JUMP_HEIGHT - 1.0f);
1386 if (IsVisible(start) && !IsVisible(start, FORWARD, flTraceDist, false))
1387 {
1388 // Jump
1389 debugnav("High wall");
1390 m_pMyEnt->jumpnext = true;
1391 return true;
1392 }
1393 }
1394 else
1395 {
1396 // Check if something is blocking at feet height, so the bot can jump over it
1397 start.z -= 1.7f;
1398
1399 // Trace was blocked?
1400 if (!IsVisible(start, FORWARD, flTraceDist, false))
1401 {
1402 //debugbeam(start, end);
1403
1404 // Jump
1405 debugnav("Low wall");
1406 m_pMyEnt->jumpnext = true;
1407 return true;
1408 }
1409 }
1410 return false; // Bot didn't had to jump(or couldn't)
1411 }
1412
CheckCrouch()1413 bool CBot::CheckCrouch()
1414 {
1415 bool bHasGoal = m_vGoal!=g_vecZero;
1416 float flGoalDist = (bHasGoal) ? GetDistance(m_vGoal) : 0.0f;
1417
1418 vec start = m_pMyEnt->o;
1419 vec crouch = vec(0, 0, 2.0f);
1420 float flTraceDist = 3.0f;
1421
1422 if (bHasGoal && (flGoalDist < flTraceDist))
1423 flTraceDist = flGoalDist;
1424
1425 if (!IsVisible(vec(start).add(crouch), FORWARD, flTraceDist, false) && IsVisible(vec(start).sub(crouch), FORWARD, flTraceDist, false)) return true;
1426 return false;
1427 }
1428
CheckStrafe()1429 bool CBot::CheckStrafe()
1430 {
1431 if (m_iStrafeTime >= lastmillis)
1432 {
1433 SetMoveDir(m_iMoveDir, true);
1434 return true;
1435 }
1436
1437 if (m_iStrafeCheckDelay >= lastmillis)
1438 return false;
1439
1440 // Check for near walls
1441 traceresult_s tr;
1442 vec from = m_pMyEnt->o, to, forward, right, up, dir;
1443 float flLeftDist = -1.0f, flRightDist = -1.0f;
1444 bool bStrafe = false;
1445 int iStrafeDir = DIR_NONE;
1446
1447 AnglesToVectors(GetViewAngles(), forward, right, up);
1448
1449 // Check for a near left wall
1450 to = from;
1451 dir = right;
1452 dir.mul(3.0f);
1453 to.sub(dir);
1454 TraceLine(from, to, m_pMyEnt, false, &tr);
1455 if (tr.collided)
1456 flLeftDist = GetDistance(from, tr.end);
1457 //debugbeam(m_pMyEnt->o, to);
1458
1459 // Check for a near right wall
1460 to = from;
1461 dir = right;
1462 dir.mul(3.0f);
1463 to.add(dir);
1464 TraceLine(from, to, m_pMyEnt, false, &tr);
1465 if (tr.collided)
1466 flRightDist = GetDistance(from, tr.end);
1467 //debugbeam(m_pMyEnt->o, to);
1468
1469 if ((flLeftDist == -1.0f) && (flRightDist == -1.0f))
1470 {
1471 dir = right;
1472 dir.mul(m_pMyEnt->radius);
1473
1474 // Check left
1475 from = m_pMyEnt->o;
1476 from.sub(dir);
1477 if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist))
1478 flLeftDist = -1.0f;
1479
1480 // Check right
1481 from = m_pMyEnt->o;
1482 from.add(dir);
1483 if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist))
1484 flRightDist = -1.0f;
1485 }
1486
1487 if ((flLeftDist != -1.0f) && (flRightDist != -1.0f))
1488 {
1489 if (flLeftDist < flRightDist)
1490 {
1491 // Strafe right
1492 bStrafe = true;
1493 iStrafeDir = RIGHT;
1494 }
1495 else if (flRightDist < flLeftDist)
1496 {
1497 // Strafe left
1498 bStrafe = true;
1499 iStrafeDir = LEFT;
1500 }
1501 else
1502 {
1503 // Randomly choose a strafe direction
1504 bStrafe = true;
1505 if (RandomLong(0, 1))
1506 iStrafeDir = LEFT;
1507 else
1508 iStrafeDir = RIGHT;
1509 }
1510 }
1511 else if (flLeftDist != -1.0f)
1512 {
1513 // Strafe right
1514 bStrafe = true;
1515 iStrafeDir = RIGHT;
1516 }
1517 else if (flRightDist != -1.0f)
1518 {
1519 // Strafe left
1520 bStrafe = true;
1521 iStrafeDir = LEFT;
1522 }
1523
1524 if (bStrafe)
1525 {
1526 SetMoveDir(iStrafeDir, true);
1527 m_iMoveDir = iStrafeDir;
1528 m_iStrafeTime = lastmillis + RandomLong(75, 150);
1529 }
1530
1531 return bStrafe;
1532 }
1533
CheckFOV()1534 void CBot::CheckFOV()
1535 {
1536 m_iCheckEnvDelay = lastmillis + RandomLong(125, 250);
1537 vec MyAngles = GetViewAngles();
1538 vec src, forward, right, up, dest, best(0, 0, 0);
1539 vec origin = m_pMyEnt->o;
1540 float flDist, flFurthestDist = 0;
1541 bool WallLeft = false;
1542 traceresult_s tr;
1543
1544 //origin.z -= 1.5; // Slightly under eye level
1545
1546 // Scan 90 degrees FOV
1547 for(int angle=-45;angle<=45;angle+=5)
1548 {
1549 src = MyAngles;
1550 src.y = WrapYZAngle(src.y + angle);
1551
1552 AnglesToVectors(src, forward, right, up);
1553
1554 dest = origin;
1555 forward.mul(40);
1556 dest.add(forward);
1557
1558 TraceLine(origin, dest, m_pMyEnt, false, &tr);
1559
1560 //debugbeam(origin, end);
1561 flDist = GetDistance(tr.end);
1562
1563 if (flFurthestDist < flDist)
1564 {
1565 flFurthestDist = flDist;
1566 best = tr.end;
1567 }
1568 }
1569
1570 if (best.x && best.y && best.z)
1571 {
1572 AimToVec(best);
1573 // Update MyAngles, since their (going to be) change(d)
1574 MyAngles.x = m_pMyEnt->targetpitch;
1575 MyAngles.y = m_pMyEnt->targetyaw;
1576 }
1577
1578 float flNearestHitDist = GetDistance(best);
1579
1580 if (!UnderWater(m_pMyEnt->o) && m_pMyEnt->onfloor)
1581 {
1582 // Check if a near wall is blocking and we can jump over it
1583 if (flNearestHitDist < 4)
1584 {
1585 // Check if the bot can jump over it
1586 src = MyAngles;
1587 src.x = 0;
1588
1589 AnglesToVectors(src, forward, right, up);
1590
1591 vec start = origin;
1592 start.z += 2.0f;
1593 dest = start;
1594 forward.mul(6);
1595 dest.add(forward);
1596
1597 TraceLine(start, dest, m_pMyEnt, false, &tr);
1598 //debugbeam(start, end);
1599
1600 if (!tr.collided)
1601 {
1602 // Jump
1603 debugnav("High wall");
1604 m_pMyEnt->jumpnext = true;
1605 m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500);
1606 return;
1607 }
1608 }
1609 else
1610 {
1611 // Check if something is blocking below us, so the bot can jump over it
1612 src = MyAngles;
1613 src.x = 0;
1614
1615 AnglesToVectors(src, forward, right, up);
1616
1617 vec start = origin;
1618 start.z -= 1.7f;
1619 dest = start;
1620 forward.mul(4);
1621 dest.add(forward);
1622
1623 TraceLine(start, dest, m_pMyEnt, false, &tr);
1624
1625 // Trace was blocked?
1626 if (tr.collided)
1627 {
1628 //debugbeam(start, end);
1629
1630 // Jump
1631 debugnav("Low wall");
1632 m_pMyEnt->jumpnext = true;
1633 m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500);
1634 return;
1635 }
1636 }
1637
1638 // Check if the bot is going to fall...
1639 src = MyAngles;
1640 src.x = 0.0f;
1641 AnglesToVectors(src, forward, right, up);
1642
1643 dest = origin;
1644 forward.mul(3.0f);
1645 dest.add(forward);
1646
1647 TraceLine(origin, dest, m_pMyEnt, false, &tr);
1648
1649 int cx = int(tr.end.x), cy = int(tr.end.y);
1650 short CubesInWater=0;
1651 for(int x=cx-1;x<=(cx+1);x++)
1652 {
1653 for(int y=cy-1;y<=(cy+1);y++)
1654 {
1655 if (OUTBORD(x, y)) continue;
1656 //sqr *s = S(fast_f2nat(x), fast_f2nat(y));
1657 //if (!SOLID(s))
1658 {
1659 vec from(x, y, m_pMyEnt->o.z);
1660 dest = from;
1661 dest.z -= 6.0f;
1662 TraceLine(from, dest, m_pMyEnt, false, &tr);
1663 bool turn = false;
1664 if (UnderWater(tr.end)) CubesInWater++;
1665 if (CubesInWater > 2) turn = true; // Always avoid water
1666 if (!tr.collided && RandomLong(0, 1))
1667 turn = true; // Randomly avoid a fall
1668
1669 if (turn)
1670 {
1671 m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180);
1672 m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500);
1673 debugnav("Water or a fall in front");
1674 return;
1675 }
1676 }
1677 }
1678 }
1679 }
1680
1681 // Is the bot about to head a corner?
1682 if (flNearestHitDist <= 4.0f)
1683 {
1684 src = MyAngles;
1685 src.y = WrapYZAngle(src.y - 45.0f);
1686 AnglesToVectors(src, forward, right, up);
1687
1688 dest = origin;
1689 forward.mul(4.0f);
1690 dest.add(forward);
1691
1692 TraceLine(origin, dest, m_pMyEnt, false, &tr);
1693
1694 WallLeft = (tr.collided);
1695
1696 src = MyAngles;
1697 src.y += WrapYZAngle(src.y + 45.0f);
1698 AnglesToVectors(src, forward, right, up);
1699
1700 dest = origin;
1701 forward.mul(4.0f);
1702 dest.add(forward);
1703
1704 TraceLine(origin, dest, m_pMyEnt, false, &tr);
1705
1706 if (WallLeft && tr.collided)
1707 {
1708 // We're about to hit a corner, turn away
1709 debugnav("Corner");
1710 m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomFloat(160.0f, 200.0f));
1711 m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500);
1712 return;
1713 }
1714 }
1715 }
1716
1717 // Called when bot is underwater
WaterNav()1718 bool CBot::WaterNav()
1719 {
1720 const int iSearchRange = 4;
1721
1722 if (m_vWaterGoal==g_vecZero)
1723 {
1724 AddDebugText("WaterNav");
1725 // Find the nearest and reachable cube which isn't underwater
1726
1727 int cx = int(m_pMyEnt->o.x);
1728 int cy = int(m_pMyEnt->o.y);
1729 float flNearestDist = 9999.0f, flDist;
1730
1731 if (OUTBORD(cx, cy)) return false;
1732
1733 // Check all cubes in range...
1734 for (int x=(cx-iSearchRange);x<=(cx+iSearchRange);x++)
1735 {
1736 for (int y=(cy-iSearchRange);y<=(cy+iSearchRange);y++)
1737 {
1738 sqr *s = S(x, y);
1739
1740 if (SOLID(s)) continue;
1741 if ((x==cx) && (y==cy)) continue;
1742
1743 vec v(x, y, GetCubeFloor(x, y));
1744
1745 if (UnderWater(v)) continue; // Skip, cube is underwater
1746
1747 if (hdr.waterlevel < (v.z - 2.0f)) continue; // Cube is too high
1748
1749 // Check if the bot 'can fit' on the cube(no near obstacles)
1750 bool small_ = false;
1751
1752 for (int a=(x-2);a<=(x+2);a++)
1753 {
1754 if (small_) break;
1755 for (int b=(y-2);b<=(y+2);b++)
1756 {
1757 if ((x==a) && (y==b)) continue;
1758 vec v2(a, b, GetCubeFloor(a, b));
1759 if (v.z < (v2.z-1-JUMP_HEIGHT))
1760 {
1761 small_=true;
1762 break;
1763 }
1764
1765 if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1)))
1766 {
1767 if ((v2.z) < (v.z-2.0f))
1768 {
1769 small_ = true;
1770 break;
1771 }
1772 }
1773
1774 traceresult_s tr;
1775 TraceLine(v, v2, NULL, false, &tr);
1776 if (tr.collided)
1777 {
1778 small_=true;
1779 break;
1780 }
1781 }
1782 if (small_) break;
1783 }
1784 if (small_)
1785 {
1786 debugbeam(m_pMyEnt->o, v);
1787 continue;
1788 }
1789
1790 // Okay, cube is valid.
1791 flDist = GetDistance(v);
1792 if (flDist < flNearestDist)
1793 {
1794 flNearestDist = flDist;
1795 m_vWaterGoal = v;
1796 }
1797 }
1798 }
1799 }
1800
1801 if (m_vWaterGoal!=g_vecZero)
1802 {
1803 AddDebugText("WaterNav");
1804 //debugbeam(m_pMyEnt->o, m_vWaterGoal);
1805 vec aim = m_vWaterGoal;
1806 aim.z += 1.5f; // Aim a bit further up
1807 AimToVec(aim);
1808 if ((RandomLong(1, 100) <= 15) && (Get2DDistance(m_vWaterGoal) <= 7.0f))
1809 m_pMyEnt->jumpnext = true;
1810 return true;
1811 }
1812
1813 return false;
1814 }
1815
CheckItems()1816 bool CBot::CheckItems()
1817 {
1818 if (!m_pCurrentGoalWaypoint && !CheckJump() && CheckStuck())
1819 {
1820 // Don't check for ents a while when stuck
1821 m_iCheckEntsDelay = lastmillis + RandomLong(1000, 2000);
1822 return false;
1823 }
1824
1825 if (m_vGoal==g_vecZero)
1826 m_pTargetEnt = NULL;
1827
1828 if (!m_pTargetEnt)
1829 {
1830 if (m_iCheckEntsDelay > lastmillis)
1831 return false;
1832 else
1833 {
1834 m_pTargetEnt = SearchForEnts(!m_classicsp);
1835 m_iCheckEntsDelay = lastmillis + RandomLong(2500, 5000);
1836 }
1837 }
1838
1839 if (m_pTargetEnt)
1840 {
1841 if (HeadToTargetEnt())
1842 return true;
1843 }
1844
1845 if (m_eCurrentBotState == STATE_ENT)
1846 {
1847 ResetWaypointVars();
1848 m_vGoal = g_vecZero;
1849 m_pTargetEnt = NULL;
1850 }
1851
1852 return false;
1853 }
1854
InUnreachableList(entity * e)1855 bool CBot::InUnreachableList(entity *e)
1856 {
1857 TLinkedList<unreachable_ent_s *>::node_s *p = m_UnreachableEnts.GetFirst();
1858 while(p)
1859 {
1860 if (p->Entry->ent == e) return true;
1861 p = p->next;
1862 }
1863 return false;
1864 }
1865
IsReachable(vec to,float flMaxHeight)1866 bool CBot::IsReachable(vec to, float flMaxHeight)
1867 {
1868 vec from = m_pMyEnt->o;
1869 traceresult_s tr;
1870 float curr_height, last_height;
1871
1872 float distance = GetDistance(from, to);
1873
1874 // is the destination close enough?
1875 //if (distance < REACHABLE_RANGE)
1876 {
1877 if (IsVisible(to))
1878 {
1879 // Look if bot can 'fit trough'
1880 vec src = from, forward, right, up;
1881 AnglesToVectors(GetViewAngles(), forward, right, up);
1882
1883 // Trace from 1 cube to the left
1884 vec temp = right;
1885 temp.mul(1.0f);
1886 src.sub(temp);
1887 if (!::IsVisible(src, to)) return false;
1888
1889 // Trace from 1 cube to the right
1890 src.add(temp);
1891 if (!::IsVisible(src, to)) return false;
1892
1893 if (UnderWater(from) && UnderWater(to))
1894 {
1895 // No need to worry about heights in water
1896 return true;
1897 }
1898 /*
1899 if (to.z > (from.z + JUMP_HEIGHT))
1900 {
1901 vec v_new_src = to;
1902 vec v_new_dest = to;
1903
1904 v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f);
1905
1906 // check if we didn't hit anything, if so then it's in mid-air
1907 if (::IsVisible(v_new_src, v_new_dest, NULL))
1908 {
1909 condebug("to is in midair");
1910 debugbeam(from, to);
1911 return false; // can't reach this one
1912 }
1913 }
1914 */
1915
1916 // check if distance to ground increases more than jump height
1917 // at points between from and to...
1918
1919 vec v_temp = to;
1920 v_temp.sub(from);
1921 vec v_direction = Normalize(v_temp); // 1 unit long
1922 vec v_check = from;
1923 vec v_down = from;
1924
1925 v_down.z = v_down.z - 100.0f; // straight down
1926
1927 TraceLine(v_check, v_down, NULL, false, &tr);
1928
1929 // height from ground
1930 last_height = GetDistance(v_check, tr.end);
1931
1932 distance = GetDistance(to, v_check); // distance from goal
1933
1934 while (distance > 2.0f)
1935 {
1936 // move 2 units closer to the goal
1937 v_temp = v_direction;
1938 v_temp.mul(2.0f);
1939 v_check.add(v_temp);
1940
1941 v_down = v_check;
1942 v_down.z = v_down.z - 100.0f;
1943
1944 TraceLine(v_check, v_down, NULL, false, &tr);
1945
1946 curr_height = GetDistance(v_check, tr.end);
1947
1948 // is the difference in the last height and the current height
1949 // higher that the jump height?
1950 if ((last_height - curr_height) >= flMaxHeight)
1951 {
1952 // can't get there from here...
1953 //condebug("traces failed to to");
1954 debugbeam(from, to);
1955 return false;
1956 }
1957
1958 last_height = curr_height;
1959
1960 distance = GetDistance(to, v_check); // distance from goal
1961 }
1962
1963 return true;
1964 }
1965 }
1966
1967 return false;
1968 }
1969
HearSound(int n,vec * o)1970 void CBot::HearSound(int n, vec *o)
1971 {
1972 // Has the bot already an enemy?
1973 if (m_pMyEnt->enemy) return;
1974
1975
1976 //fixmebot
1977 // Is the sound not interesting?
1978 if(n == S_DIE1 || n == S_DIE2) return;
1979
1980 int soundvol = m_pBotSkill->iMaxHearVolume -
1981 (int)(GetDistance(*o)*3*m_pBotSkill->iMaxHearVolume/255);
1982
1983 if (soundvol == 0) return;
1984
1985 // Look who made the sound(check for the nearest enemy)
1986 float flDist, flNearestDist = 3.0f; // Range of 3 units
1987 playerent *pNearest = NULL;
1988
1989 // Check all players first
1990 loopv(players)
1991 {
1992 playerent *d = players[i];
1993
1994 if (d == m_pMyEnt || !d || (d->state != CS_ALIVE) ||
1995 isteam(m_pMyEnt->team, d->team))
1996 continue;
1997
1998 flDist = GetDistance(*o, d->o);
1999 if ((flDist < flNearestDist) && IsVisible(d))
2000 {
2001 pNearest = d;
2002 flNearestDist = flDist;
2003 }
2004 }
2005
2006 // Check local player
2007 if (player1 && (player1->state == CS_ALIVE) &&
2008 !isteam(m_pMyEnt->team, player1->team))
2009 {
2010 flDist = GetDistance(*o, player1->o);
2011 if ((flDist < flNearestDist) && IsVisible(player1))
2012 {
2013 pNearest = player1;
2014 flNearestDist = flDist;
2015 }
2016 }
2017
2018 if (pNearest)
2019 {
2020 if (m_pMyEnt->enemy != pNearest)
2021 m_iShootDelay = lastmillis + GetShootDelay(); // Add shoot delay when new enemy found
2022
2023 m_pMyEnt->enemy = pNearest;
2024 }
2025 }
2026
IsInFOV(const vec & o)2027 bool CBot::IsInFOV(const vec &o)
2028 {
2029 vec target, dir, forward, right, up;
2030 float flDot, flAngle;
2031
2032 AnglesToVectors(GetViewAngles(), forward, right, up);
2033
2034 // direction the bot is aiming at
2035 dir = forward;
2036 dir.z = 0; // Make 2D
2037
2038 // ideal direction
2039 target = o;
2040 target.sub(m_pMyEnt->o);
2041 target.z = 0.0f; // Make 2D
2042
2043 // angle between these two directions
2044 flDot = target.dot(dir);
2045 flAngle = acos(flDot/(target.magnitude() * dir.magnitude()));
2046
2047 return m_pBotSkill->iFov/2.0f >= flAngle/RAD;
2048 }
2049 // Code of CBot - End
2050