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