1 //
2 // C++ Implementation: bot
3 //
4 // Description: Code for botmanager
5 //
6 // Main bot file
7 //
8 // Author:  Rick <rickhelmus@gmail.com>
9 //
10 //
11 //
12 
13 #include "cube.h"
14 #include "bot.h"
15 
16 
17 #ifndef VANILLA_CUBE // UNDONE
18 bool dedserv = false;
19 #define CS_DEDHOST 0xFF
20 #endif
21 
22 extern void respawnself();
23 
24 CBotManager BotManager;
25 
26 // Bot manager class begin
27 
~CBotManager(void)28 CBotManager::~CBotManager(void)
29 {
30     EndMap();
31     ClearStoredBots();
32 }
33 
Init()34 void CBotManager::Init()
35 {
36     m_pBotToView = NULL;
37 
38     m_bBotsShoot = true;
39     m_bIdleBots = false;
40     m_iFrameTime = 0;
41     m_iPrevTime = lastmillis;
42     m_sBotSkill = 1; // Default all bots have the skill 'Good'
43 
44     CreateSkillData();
45     LoadBotNamesFile();
46     LoadBotTeamsFile();
47     //WaypointClass.Init();
48     lsrand(time(NULL));
49 }
50 
Think()51 void CBotManager::Think()
52 {
53     if (m_bInit)
54     {
55        Init();
56        m_bInit = false;
57     }
58     if (m_pBotToView) ViewBot();
59     AddDebugText("m_sMaxAStarBots: %d", m_sMaxAStarBots);
60     AddDebugText("m_sCurrentTriggerNr: %d", m_sCurrentTriggerNr);
61     short x, y;
62     WaypointClass.GetNodeIndexes(player1->o, &x, &y);
63     AddDebugText("x: %d y: %d", x, y);
64 
65     m_iFrameTime = lastmillis - m_iPrevTime;
66     if (m_iFrameTime > 250) m_iFrameTime = 250;
67     m_iPrevTime = lastmillis;
68 
69     // Is it time to re-add bots?
70     if ((m_fReAddBotDelay < lastmillis) && (m_fReAddBotDelay != -1.0f))
71     {
72        while(m_StoredBots.Empty() == false)
73        {
74           CStoredBot *pStoredBot = m_StoredBots.Pop();
75           ReAddBot(pStoredBot);
76           delete pStoredBot;
77        }
78        m_fReAddBotDelay = -1.0f;
79     }
80     // If this is a ded server check if there are any players, if not bots should be idle
81     if (dedserv)
82     {
83        bool botsbeidle = true;
84        loopv(players) { if (players[i] && (players[i]->state != CS_DEDHOST)) { botsbeidle = false; break; } }
85        if (botsbeidle) return;
86     }
87     // Let all bots 'think'
88     loopv(bots)
89     {
90        if (!bots[i]) continue;
91        if (bots[i]->pBot)
92        {
93           bots[i]->pBot->CheckWeaponSwitch(); // 2011jan17:ft: fix non-shooting bots
94           bots[i]->pBot->Think();
95        }
96        else condebug("Error: pBot == NULL in bot ent\n");
97     }
98 }
99 
LoadBotNamesFile()100 void CBotManager::LoadBotNamesFile()
101 {
102     // Init bot names array first
103     for (int i=0;i<100;i++)
104         strcpy(m_szBotNames[i], "Bot");
105 
106     m_sBotNameCount = 0;
107 
108     // Load bot file
109     char szNameFileName[256];
110     MakeBotFileName("bot_names.txt", NULL, NULL, szNameFileName);
111     FILE *fp = fopen(szNameFileName, "r");
112     char szNameBuffer[256];
113     int iIndex, iStrIndex;
114 
115     if (!fp)
116     {
117         conoutf("Warning: Couldn't load bot names file");
118         return;
119     }
120 
121     while (fgets(szNameBuffer, 80, fp) != NULL)
122     {
123         if (m_sBotNameCount >= 150)
124         {
125             conoutf("Warning: Max bot names reached(150), ignoring the rest of the"
126                    "names");
127             break;
128         }
129 
130         short length = (short)strlen(szNameBuffer);
131 
132         if (szNameBuffer[length-1] == '\n')
133         {
134             szNameBuffer[length-1] = 0;  // remove '\n'
135             length--;
136         }
137 
138         iStrIndex = 0;
139         while (iStrIndex < length)
140         {
141             if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') ||
142                 (szNameBuffer[iStrIndex] == '"'))
143             {
144                 for (iIndex=iStrIndex; iIndex < length; iIndex++)
145                     szNameBuffer[iIndex] = szNameBuffer[iIndex+1];
146             }
147 
148             iStrIndex++;
149         }
150 
151         if (szNameBuffer[0] != 0)
152         {
153             if (strlen(szNameBuffer) >= 16)
154             {    conoutf("Warning: bot name \"%s\" has to many characters(16 is max)",
155                        szNameBuffer);
156             }
157             copystring(m_szBotNames[m_sBotNameCount], szNameBuffer, 16);
158             m_sBotNameCount++;
159         }
160     }
161     fclose(fp);
162 }
163 
GetBotName()164 const char *CBotManager::GetBotName()
165 {
166     const char *szOutput = NULL;
167     TMultiChoice<const char *> BotNameChoices;
168     short ChoiceVal;
169 
170     for(int j=0;j<m_sBotNameCount;j++)
171     {
172         ChoiceVal = 50;
173 
174         loopv(players)
175         {
176             if (players[i] && (players[i]->state != CS_DEDHOST) &&
177                 !strcasecmp(players[i]->name, m_szBotNames[j]))
178                 ChoiceVal -= 10;
179         }
180 
181         loopv(bots)
182         {
183             if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j])))
184                 ChoiceVal -= 10;
185         }
186 
187         if ((player1->state != CS_DEDHOST) && !strcasecmp(player1->name, m_szBotNames[j]))
188             ChoiceVal -= 10;
189 
190         if (ChoiceVal <= 0)
191             ChoiceVal = 1;
192 
193         BotNameChoices.Insert(m_szBotNames[j], ChoiceVal);
194     }
195 
196     // Couldn't find a selection?
197     if (!BotNameChoices.GetSelection(szOutput))
198         szOutput = "Bot";
199 
200     return szOutput;
201 }
202 
LoadBotTeamsFile()203 void CBotManager::LoadBotTeamsFile()
204 {
205     // Init bot teams array first
206     for (int i=0;i<20;i++)
207         strcpy(m_szBotTeams[i], "b0ts");
208 
209     m_sBotTeamCount = 0;
210 
211     // Load bot file
212     char szNameFileName[256];
213     MakeBotFileName("bot_teams.txt", NULL, NULL, szNameFileName);
214     FILE *fp = fopen(szNameFileName, "r");
215     char szNameBuffer[256];
216     int iIndex, iStrIndex;
217 
218     if (!fp)
219     {
220         conoutf("Warning: Couldn't load bot teams file");
221         return;
222     }
223 
224     while ((m_sBotTeamCount < 20) && (fgets(szNameBuffer, 80, fp) != NULL))
225     {
226         short length = (short)strlen(szNameBuffer);
227 
228         if (szNameBuffer[length-1] == '\n')
229         {
230             szNameBuffer[length-1] = 0;  // remove '\n'
231             length--;
232         }
233 
234         iStrIndex = 0;
235         while (iStrIndex < length)
236         {
237             if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') ||
238                 (szNameBuffer[iStrIndex] == '"'))
239             {
240                 for (iIndex=iStrIndex; iIndex < length; iIndex++)
241                     szNameBuffer[iIndex] = szNameBuffer[iIndex+1];
242             }
243 
244             iStrIndex++;
245         }
246 
247         if (szNameBuffer[0] != 0)
248         {
249             copystring(m_szBotTeams[m_sBotTeamCount], szNameBuffer, 5);
250             m_sBotTeamCount++;
251         }
252     }
253     fclose(fp);
254 }
255 
GetBotTeam()256 const char *CBotManager::GetBotTeam()
257 {
258     int teamsize[2] = {0, 0};
259     int biggestTeam = 0;
260     if(team_isactive(player1->team))
261     {
262         teamsize[player1->team]++;
263     }
264     loopv(players) if(players[i] && team_isactive(players[i]->team))
265     {
266         teamsize[players[i]->team]++;
267     }
268     biggestTeam = teamsize[1] > teamsize[0];
269     return teamnames[biggestTeam ^ 1];
270 
271     // Following code will never be executed.
272     // (bot teams aren't ready, we use normal teams instead)
273     const char *szOutput = NULL;
274     TMultiChoice<const char *> BotTeamChoices;
275     short ChoiceVal;
276 
277     for(int j=0;j<m_sBotTeamCount;j++)
278     {
279         ChoiceVal = 50;
280         /* UNDONE?
281         loopv(players)
282         {
283             if (players[i] && (!strcasecmp(players[i]->name, m_szBotNames[j])))
284                 ChoiceVal -= 10;
285         }
286 
287         loopv(bots)
288         {
289             if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j])))
290                 ChoiceVal -= 10;
291         }
292 
293         if (!strcasecmp(player1->name, m_szBotNames[j]))
294             ChoiceVal -= 10;
295 
296         if (ChoiceVal <= 0)
297             ChoiceVmonsterclearal = 1;*/
298 
299         BotTeamChoices.Insert(m_szBotTeams[j], ChoiceVal);
300     }
301 
302     // Couldn't find a selection?
303     if (!BotTeamChoices.GetSelection(szOutput))
304         szOutput = "b0t";
305 
306     return szOutput;
307 }
308 
RenderBots()309 void CBotManager::RenderBots()
310 {
311     //static bool drawblue;
312 
313     loopv(bots)
314     {
315         if (bots[i] && (bots[i] != m_pBotToView))
316         {
317             /*drawblue = (m_sp || isteam(player1->team, bots[i]->team));
318             renderclient(bots[i], drawblue, "playermodels/counterterrorist", 1.6f);*/
319             renderclient(bots[i]);
320         }
321     }
322 }
323 
EndMap()324 void CBotManager::EndMap()
325 {
326     // Remove all bots
327     loopv(bots)
328     {
329         if(!bots[i] || !bots[i]->pBot)
330             continue;
331 
332         // Store bots so they can be re-added after map change
333         if (bots[i]->pBot && bots[i]->name[0] && team_isactive(bots[i]->team))
334         {
335             CStoredBot *pStoredBot = new CStoredBot(bots[i]->name, (char *) team_string(bots[i]->team),
336                                             bots[i]->pBot->m_sSkillNr);
337             m_StoredBots.AddNode(pStoredBot);
338         }
339         delete bots[i]->pBot;
340 
341         bots[i]->pBot = NULL;
342         freebotent(bots[i]);
343     }
344     bots.setsize(0);
345     condebug("Cleared all bots");
346     m_fReAddBotDelay = lastmillis + 7500;
347     //if(ishost()) WaypointClass.SaveWPExpFile(); //UNDONE
348 }
349 
BeginMap(const char * szMapName)350 void CBotManager::BeginMap(const char *szMapName)
351 {
352     EndMap(); // End previous map
353 
354     WaypointClass.Init();
355     WaypointClass.SetMapName(szMapName);
356     if (!WaypointClass.LoadWaypoints())
357         WaypointClass.StartFlood();
358     //WaypointClass.LoadWPExpFile(); // UNDONE
359 
360     CalculateMaxAStarCount();
361     m_sUsingAStarBotsCount = 0;
362     PickNextTrigger();
363 }
364 
GetBotIndex(botent * m)365 int CBotManager::GetBotIndex(botent *m)
366 {
367     loopv(bots)
368     {
369         if (!bots[i])
370             continue;
371 
372         if (bots[i] == m)
373             return i;
374     }
375 
376     return -1;
377 }
378 
LetBotsUpdateStats()379 void CBotManager::LetBotsUpdateStats()
380 {
381     loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->m_bSendC2SInit = false;
382 }
383 
LetBotsHear(int n,vec * loc)384 void CBotManager::LetBotsHear(int n, vec *loc)
385 {
386     if (bots.length() == 0 || !loc) return;
387 
388     loopv(bots)
389     {
390         if (!bots[i] || !bots[i]->pBot || (bots[i]->state == CS_DEAD)) continue;
391         bots[i]->pBot->HearSound(n, loc);
392     }
393 }
394 
395 // Notify all bots of a new waypoint
AddWaypoint(node_s * pNode)396 void CBotManager::AddWaypoint(node_s *pNode)
397 {
398     if (bots.length())
399     {
400         short x, y;
401         waypoint_s *pWP;
402 
403         loopv(bots)
404         {
405             if (!bots[i] || !bots[i]->pBot) continue;
406 
407             pWP = new waypoint_s;
408             pWP->pNode = pNode;
409             WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y);
410             bots[i]->pBot->m_WaypointList[x][y].AddNode(pWP);
411 
412 #ifndef RELEASE_BUILD
413             if (!bots[i]->pBot->GetWPFromNode(pNode)) condebug("Error adding bot wp!");
414 #endif
415         }
416     }
417 
418     CalculateMaxAStarCount();
419 }
420 
421 // Notify all bots of a deleted waypoint
DelWaypoint(node_s * pNode)422 void CBotManager::DelWaypoint(node_s *pNode)
423 {
424     if (bots.length())
425     {
426         short x, y;
427         TLinkedList<waypoint_s *>::node_s *p;
428 
429         loopv(bots)
430         {
431             if (!bots[i] || !bots[i]->pBot) continue;
432 
433             WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y);
434             p = bots[i]->pBot->m_WaypointList[x][y].GetFirst();
435 
436             while(p)
437             {
438                 if (p->Entry->pNode == pNode)
439                 {
440                     delete p->Entry;
441                     bots[i]->pBot->m_WaypointList[x][y].DeleteNode(p);
442                     break;
443                 }
444                 p = p->next;
445             }
446         }
447     }
448 
449     CalculateMaxAStarCount();
450 }
451 
MakeBotFileName(const char * szFileName,const char * szDir1,const char * szDir2,char * szOutput)452 void CBotManager::MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, char *szOutput)
453 {
454     const char *DirSeperator;
455 
456 #ifdef WIN32
457     DirSeperator = "\\";
458     strcpy(szOutput, "bot\\");
459 #else
460     DirSeperator = "/";
461     strcpy(szOutput, "bot/");
462 #endif
463 
464     if (szDir1)
465     {
466         strcat(szOutput, szDir1);
467         strcat(szOutput, DirSeperator);
468     }
469 
470     if (szDir2)
471     {
472         strcat(szOutput, szDir2);
473         strcat(szOutput, DirSeperator);
474     }
475 
476     strcat(szOutput, szFileName);
477 }
478 
CreateSkillData()479 void CBotManager::CreateSkillData()
480 {
481     // First give the bot skill structure some default data
482     InitSkillData();
483 
484     // Now see if we can load the skill.cfg file
485     char SkillFileName[256] = "";
486     FILE *pSkillFile = NULL;
487     int SkillNr = -1;
488     float value = 0;
489 
490     MakeBotFileName("bot_skill.cfg", NULL, NULL, SkillFileName);
491 
492     pSkillFile = fopen(SkillFileName, "r");
493 
494     conoutf("Reading bot_skill.cfg file... ");
495 
496     if (pSkillFile == NULL) // file doesn't exist
497     {
498         conoutf("skill file not found, default settings will be used\n");
499         return;
500     }
501 
502     int ch;
503     char cmd_line[256];
504     int cmd_index;
505     char *cmd, *arg1;
506 
507     while (pSkillFile)
508     {
509         cmd_index = 0;
510         cmd_line[cmd_index] = 0;
511 
512         ch = fgetc(pSkillFile);
513 
514         // skip any leading blanks
515         while (ch == ' ')
516             ch = fgetc(pSkillFile);
517 
518         while ((ch != EOF) && (ch != '\r') && (ch != '\n'))
519         {
520             if (ch == '\t')  // convert tabs to spaces
521                 ch = ' ';
522 
523             cmd_line[cmd_index] = ch;
524 
525             ch = fgetc(pSkillFile);
526 
527             // skip multiple spaces in input file
528             while ((cmd_line[cmd_index] == ' ') && (ch == ' '))
529                 ch = fgetc(pSkillFile);
530 
531             cmd_index++;
532         }
533 
534         if (ch == '\r')  // is it a carriage return?
535         {
536             ch = fgetc(pSkillFile);  // skip the linefeed
537         }
538 
539         // if reached end of file, then close it
540         if (ch == EOF)
541         {
542             fclose(pSkillFile);
543             pSkillFile = NULL;
544         }
545 
546         cmd_line[cmd_index] = 0;  // terminate the command line
547 
548         cmd_index = 0;
549         cmd = cmd_line;
550         arg1 = NULL;
551 
552         // skip to blank or end of string...
553         while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0))
554              cmd_index++;
555 
556         if (cmd_line[cmd_index] == ' ')
557         {
558              cmd_line[cmd_index++] = 0;
559              arg1 = &cmd_line[cmd_index];
560         }
561 
562         if ((cmd_line[0] == '#') || (cmd_line[0] == 0))
563             continue;  // skip if comment or blank line
564 
565 
566         if (strcasecmp(cmd, "[SKILL1]") == 0)
567             SkillNr = 0;
568         else if (strcasecmp(cmd, "[SKILL2]") == 0)
569             SkillNr = 1;
570         else if (strcasecmp(cmd, "[SKILL3]") == 0)
571             SkillNr = 2;
572         else if (strcasecmp(cmd, "[SKILL4]") == 0)
573             SkillNr = 3;
574         else if (strcasecmp(cmd, "[SKILL5]") == 0)
575             SkillNr = 4;
576 
577         if ((arg1 == NULL) || (*arg1 == 0))
578             continue;
579 
580         if (SkillNr == -1) // Not in a skill block yet?
581             continue;
582 
583         value = atof(arg1);
584 
585         if (strcasecmp(cmd, "min_x_aim_speed") == 0)
586         {
587             m_BotSkills[SkillNr].flMinAimXSpeed = value;
588         }
589         else if (strcasecmp(cmd, "max_x_aim_speed") == 0)
590         {
591             m_BotSkills[SkillNr].flMaxAimXSpeed = value;
592         }
593         else if (strcasecmp(cmd, "min_y_aim_speed") == 0)
594         {
595             m_BotSkills[SkillNr].flMinAimYSpeed = value;
596         }
597         else if (strcasecmp(cmd, "max_y_aim_speed") == 0)
598         {
599             m_BotSkills[SkillNr].flMaxAimYSpeed = value;
600         }
601         else if (strcasecmp(cmd, "min_x_aim_offset") == 0)
602         {
603             m_BotSkills[SkillNr].flMinAimXOffset = value;
604         }
605         else if (strcasecmp(cmd, "max_x_aim_offset") == 0)
606         {
607             m_BotSkills[SkillNr].flMaxAimXOffset = value;
608         }
609         else if (strcasecmp(cmd, "min_y_aim_offset") == 0)
610         {
611             m_BotSkills[SkillNr].flMinAimYOffset = value;
612         }
613         else if (strcasecmp(cmd, "max_y_aim_offset") == 0)
614         {
615             m_BotSkills[SkillNr].flMaxAimYOffset = value;
616         }
617         else if (strcasecmp(cmd, "min_attack_delay") == 0)
618         {
619             m_BotSkills[SkillNr].flMinAttackDelay = value;
620         }
621         else if (strcasecmp(cmd, "max_attack_delay") == 0)
622         {
623             m_BotSkills[SkillNr].flMaxAttackDelay = value;
624         }
625         else if (strcasecmp(cmd, "min_enemy_search_delay") == 0)
626         {
627             m_BotSkills[SkillNr].flMinEnemySearchDelay = value;
628         }
629         else if (strcasecmp(cmd, "max_enemy_search_delay") == 0)
630         {
631             m_BotSkills[SkillNr].flMaxEnemySearchDelay = value;
632         }
633         else if (strcasecmp(cmd, "max_always_detect_distance") == 0)
634         {
635             m_BotSkills[SkillNr].flAlwaysDetectDistance = value;
636         }
637         else if (strcasecmp(cmd, "shoot_at_feet_percent") == 0)
638         {
639             if (value < 0) value = 0;
640             else if (value > 100) value = 100;
641             m_BotSkills[SkillNr].sShootAtFeetWithRLPercent = (short)value;
642         }
643         else if (strcasecmp(cmd, "can_predict_position") == 0)
644         {
645             m_BotSkills[SkillNr].bCanPredict = value!=0;
646         }
647         else if (strcasecmp(cmd, "field_of_view") == 0)
648         {
649             if (value < 80) value = 80;
650             else if (value > 240) value = 120;
651             m_BotSkills[SkillNr].iFov = (int)value;
652         }
653         else if (strcasecmp(cmd, "max_hear_volume") == 0)
654         {
655             if (value < 0) value = 0;
656             else if (value > 255) value = 100;
657             m_BotSkills[SkillNr].iMaxHearVolume = (int)value;
658         }
659         else if (strcasecmp(cmd, "can_circle_strafe") == 0)
660         {
661             m_BotSkills[SkillNr].bCircleStrafe = value!=0;
662         }
663         else if (strcasecmp(cmd, "can_search_items_in_combat") == 0)
664         {
665             m_BotSkills[SkillNr].bCanSearchItemsInCombat = value!=0;
666         }
667     }
668 
669     conoutf("done");
670 }
671 
InitSkillData()672 void CBotManager::InitSkillData()
673 {
674     // Best skill
675     m_BotSkills[0].flMinReactionDelay = 0.015f;
676     m_BotSkills[0].flMaxReactionDelay = 0.035f;
677     m_BotSkills[0].flMinAimXOffset = 15.0f;
678     m_BotSkills[0].flMaxAimXOffset = 20.0f;
679     m_BotSkills[0].flMinAimYOffset = 10.0f;
680     m_BotSkills[0].flMaxAimYOffset = 15.0f;
681     m_BotSkills[0].flMinAimXSpeed = 330.0f;
682     m_BotSkills[0].flMaxAimXSpeed = 355.0f;
683     m_BotSkills[0].flMinAimYSpeed = 400.0f;
684     m_BotSkills[0].flMaxAimYSpeed = 450.0f;
685     m_BotSkills[0].flMinAttackDelay = 0.1f;
686     m_BotSkills[0].flMaxAttackDelay = 0.4f;
687     m_BotSkills[0].flMinEnemySearchDelay = 0.09f;
688     m_BotSkills[0].flMaxEnemySearchDelay = 0.12f;
689     m_BotSkills[0].flAlwaysDetectDistance = 12.0f;
690     m_BotSkills[0].sShootAtFeetWithRLPercent = 85;
691     m_BotSkills[0].bCanPredict = true;
692     m_BotSkills[0].iMaxHearVolume = 75;
693     m_BotSkills[0].iFov = 200;
694     m_BotSkills[0].bCircleStrafe = true;
695     m_BotSkills[0].bCanSearchItemsInCombat = true;
696 
697     // Good skill
698     m_BotSkills[1].flMinReactionDelay = 0.035f;
699     m_BotSkills[1].flMaxReactionDelay = 0.045f;
700     m_BotSkills[1].flMinAimXOffset = 20.0f;
701     m_BotSkills[1].flMaxAimXOffset = 25.0f;
702     m_BotSkills[1].flMinAimYOffset = 15.0f;
703     m_BotSkills[1].flMaxAimYOffset = 20.0f;
704     m_BotSkills[1].flMinAimXSpeed = 250.0f;
705     m_BotSkills[1].flMaxAimXSpeed = 265.0f;
706     m_BotSkills[1].flMinAimYSpeed = 260.0f;
707     m_BotSkills[1].flMaxAimYSpeed = 285.0f;
708     m_BotSkills[1].flMinAttackDelay = 0.3f;
709     m_BotSkills[1].flMaxAttackDelay = 0.6f;
710     m_BotSkills[1].flMinEnemySearchDelay = 0.12f;
711     m_BotSkills[1].flMaxEnemySearchDelay = 0.17f;
712     m_BotSkills[1].flAlwaysDetectDistance = 10.0f;
713     m_BotSkills[1].sShootAtFeetWithRLPercent = 60;
714     m_BotSkills[1].bCanPredict = true;
715     m_BotSkills[1].iMaxHearVolume = 60;
716     m_BotSkills[1].iFov = 160;
717     m_BotSkills[1].bCircleStrafe = true;
718     m_BotSkills[1].bCanSearchItemsInCombat = true;
719 
720     // Medium skill
721     m_BotSkills[2].flMinReactionDelay = 0.075f;
722     m_BotSkills[2].flMaxReactionDelay = 0.010f;
723     m_BotSkills[2].flMinAimXOffset = 25.0f;
724     m_BotSkills[2].flMaxAimXOffset = 30.0f;
725     m_BotSkills[2].flMinAimYOffset = 20.0f;
726     m_BotSkills[2].flMaxAimYOffset = 25.0f;
727     m_BotSkills[2].flMinAimXSpeed = 190.0f;
728     m_BotSkills[2].flMaxAimXSpeed = 125.0f;
729     m_BotSkills[2].flMinAimYSpeed = 210.0f;
730     m_BotSkills[2].flMaxAimYSpeed = 240.0f;
731     m_BotSkills[2].flMinAttackDelay = 0.75f;
732     m_BotSkills[2].flMaxAttackDelay = 1.0f;
733     m_BotSkills[2].flMinEnemySearchDelay = 0.18f;
734     m_BotSkills[2].flMaxEnemySearchDelay = 0.22f;
735     m_BotSkills[2].flAlwaysDetectDistance = 8.0f;
736     m_BotSkills[2].sShootAtFeetWithRLPercent = 25;
737     m_BotSkills[2].bCanPredict = false;
738     m_BotSkills[2].iMaxHearVolume = 45;
739     m_BotSkills[2].iFov = 130;
740     m_BotSkills[2].bCircleStrafe = true;
741     m_BotSkills[2].bCanSearchItemsInCombat = false;
742 
743     // Worse skill
744     m_BotSkills[3].flMinReactionDelay = 0.15f;
745     m_BotSkills[3].flMaxReactionDelay = 0.20f;
746     m_BotSkills[3].flMinAimXOffset = 30.0f;
747     m_BotSkills[3].flMaxAimXOffset = 35.0f;
748     m_BotSkills[3].flMinAimYOffset = 25.0f;
749     m_BotSkills[3].flMaxAimYOffset = 30.0f;
750     m_BotSkills[3].flMinAimXSpeed = 155.0f;
751     m_BotSkills[3].flMaxAimXSpeed = 170.0f;
752     m_BotSkills[3].flMinAimYSpeed = 160.0f;
753     m_BotSkills[3].flMaxAimYSpeed = 210.0f;
754     m_BotSkills[3].flMinAttackDelay = 1.2f;
755     m_BotSkills[3].flMaxAttackDelay = 1.6f;
756     m_BotSkills[3].flMinEnemySearchDelay = 0.25f;
757     m_BotSkills[3].flMaxEnemySearchDelay = 0.30f;
758     m_BotSkills[3].flAlwaysDetectDistance = 6.0f;
759     m_BotSkills[3].sShootAtFeetWithRLPercent = 10;
760     m_BotSkills[3].bCanPredict = false;
761     m_BotSkills[3].iMaxHearVolume = 30;
762     m_BotSkills[3].iFov = 120;
763     m_BotSkills[3].bCircleStrafe = false;
764     m_BotSkills[3].bCanSearchItemsInCombat = false;
765 
766     // Bad skill
767     m_BotSkills[4].flMinReactionDelay = 0.30f;
768     m_BotSkills[4].flMaxReactionDelay = 0.50f;
769     m_BotSkills[4].flMinAimXOffset = 35.0f;
770     m_BotSkills[4].flMaxAimXOffset = 40.0f;
771     m_BotSkills[4].flMinAimYOffset = 30.0f;
772     m_BotSkills[4].flMaxAimYOffset = 35.0f;
773     m_BotSkills[4].flMinAimXSpeed = 45.0f;
774     m_BotSkills[4].flMaxAimXSpeed = 60.0f;
775     m_BotSkills[4].flMinAimYSpeed = 125.0f;
776     m_BotSkills[4].flMaxAimYSpeed = 180.0f;
777     m_BotSkills[4].flMinAttackDelay = 1.5f;
778     m_BotSkills[4].flMaxAttackDelay = 2.0f;
779     m_BotSkills[4].flMinEnemySearchDelay = 0.30f;
780     m_BotSkills[4].flMaxEnemySearchDelay = 0.36f;
781     m_BotSkills[4].flAlwaysDetectDistance = 4.0f;
782     m_BotSkills[4].sShootAtFeetWithRLPercent = 0;
783     m_BotSkills[4].bCanPredict = false;
784     m_BotSkills[4].iMaxHearVolume = 15;
785     m_BotSkills[4].iFov = 110;
786     m_BotSkills[4].bCircleStrafe = false;
787     m_BotSkills[4].bCanSearchItemsInCombat = false;
788 }
789 
ChangeBotSkill(short Skill,botent * bot)790 void CBotManager::ChangeBotSkill(short Skill, botent *bot)
791 {
792     static const char *SkillNames[5] = { "best", "good", "medium", "worse", "bad" };
793 
794     if (bot && bot->pBot)
795     {
796         // Only change skill of a single bot
797         bot->pBot->m_pBotSkill = &m_BotSkills[Skill];
798         bot->pBot->m_sSkillNr = Skill;
799         conoutf("Skill of %s is now %s", bot->name, SkillNames[Skill]);
800         return;
801     }
802 
803     // Change skill of all bots
804     loopv(bots)
805     {
806         if (!bots[i] || !bots[i]->pBot) continue;
807 
808         bots[i]->pBot->m_pBotSkill = &m_BotSkills[Skill];
809         bots[i]->pBot->m_sSkillNr = Skill;
810     }
811 
812     // Change default bot skill
813     m_sBotSkill = Skill;
814 
815     conoutf("Skill of all bots is now %s", SkillNames[Skill]);
816 }
817 
ViewBot()818 void CBotManager::ViewBot()
819 {
820     // Check if this bot is still in game
821     bool bFound = false;
822     loopv(bots)
823     {
824         if (bots[i] == m_pBotToView)
825         {
826             bFound = true;
827             break;
828         }
829     }
830 
831     if (!bFound)
832     {
833         DisableBotView();
834         return;
835     }
836 
837     player1->state = CS_DEAD; // Fake dead
838 
839     player1->o = m_pBotToView->o;
840     player1->o.z += 1.0f;
841     player1->yaw = m_pBotToView->yaw;
842     player1->pitch = m_pBotToView->pitch;
843     player1->roll = m_pBotToView->roll;
844     player1->radius = 0; // Don't collide
845     player1->resetinterp();
846 }
847 
DisableBotView()848 void CBotManager::DisableBotView()
849 {
850     m_pBotToView = NULL;
851     respawnself();
852     player1->radius = 1.1f;
853 }
854 
CalculateMaxAStarCount()855 void CBotManager::CalculateMaxAStarCount()
856 {
857     if (WaypointClass.m_iWaypointCount > 0) // Are there any waypoints?
858     {
859         m_sMaxAStarBots = 8 - short(ceil((float)WaypointClass.m_iWaypointCount /
860                                1000.0f));
861         if (m_sMaxAStarBots < 1)
862             m_sMaxAStarBots = 1;
863     }
864     else
865         m_sMaxAStarBots = 1;
866 }
867 
PickNextTrigger()868 void CBotManager::PickNextTrigger()
869 {
870     short lowest = -1;
871     bool found0 = false; // True if found a trigger with nr 0
872 
873     loopv(ents)
874     {
875         entity &e = ents[i];
876 
877 #if defined AC_CUBE
878 /*        if ((e.type != TRIGGER) || !e.spawned)
879             continue;*/
880 #elif defined VANILLA_CUBE
881         if ((e.type != CARROT) || !e.spawned)
882             continue;
883 #endif
884         if (OUTBORD(e.x, e.y)) continue;
885 
886         vec o(e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight);
887 
888         node_s *pWptNearEnt = NULL;
889 
890         pWptNearEnt = WaypointClass.GetNearestTriggerWaypoint(o, 2.0f);
891 
892         if (pWptNearEnt)
893         {
894             if ((pWptNearEnt->sTriggerNr > 0) &&
895                 ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1)))
896                 lowest = pWptNearEnt->sTriggerNr;
897             if (pWptNearEnt->sTriggerNr == 0) found0 = true;
898         }
899 
900 #ifdef WP_FLOOD
901         pWptNearEnt = WaypointClass.GetNearestTriggerFloodWP(o, 2.0f);
902 
903         if (pWptNearEnt)
904         {
905             if ((pWptNearEnt->sTriggerNr > 0) &&
906                 ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1)))
907                 lowest = pWptNearEnt->sTriggerNr;
908             if (pWptNearEnt->sTriggerNr == 0) found0 = true;
909         }
910 
911 #endif
912     }
913 
914     if ((lowest == -1) && found0) lowest = 0;
915 
916     if (lowest != -1)
917         m_sCurrentTriggerNr = lowest;
918 }
919 
CreateBot(const char * team,const char * skill,const char * name)920 botent *CBotManager::CreateBot(const char *team, const char *skill, const char *name)
921 {
922     if (m_bInit)
923     {
924        Init();
925        m_bInit = false;
926     }
927 
928     botent *m = newbotent();
929     if (!m) return NULL;
930     loopi(NUMGUNS) m->ammo[i] = m->mag[i] = 0;
931     m->lifesequence = 0;
932     setskin(m, rnd(6));
933     // Create new bot class, dependand on the current mod
934 #if defined VANILLA_CUBE
935     m->pBot = new CCubeBot;
936 #elif defined AC_CUBE
937     m->pBot = new CACBot;
938 #else
939     #error "Unsupported mod!"
940 #endif
941     m->type = ENT_BOT;
942     m->pBot->m_pMyEnt = m;
943     m->pBot->m_iLastBotUpdate = 0;
944     m->pBot->m_bSendC2SInit = false;
945 
946     if (name && *name) copystring(m->name, name, 16);
947     else copystring(m->name, BotManager.GetBotName(), 16);
948 
949     updateclientname((playerent *)m);
950 
951     const char *tempteam = team && *team && strcmp(team, "random") ? team : BotManager.GetBotTeam();
952     loopi(TEAM_NUM) if(!strcmp(teamnames[i], tempteam)) { m->team = i; break; }
953 
954     if (skill && *skill && strcmp(skill, "random"))
955     {
956        if (!strcasecmp(skill, "best")) m->pBot->m_sSkillNr = 0;
957        else if (!strcasecmp(skill, "good")) m->pBot->m_sSkillNr = 1;
958        else if (!strcasecmp(skill, "medium")) m->pBot->m_sSkillNr = 2;
959        else if (!strcasecmp(skill, "worse")) m->pBot->m_sSkillNr = 3;
960        else if (!strcasecmp(skill, "bad")) m->pBot->m_sSkillNr = 4;
961        else
962        {
963           conoutf("Wrong skill specified. Should be best, good, medium, "
964                  "worse or bad");
965           conoutf("Using default skill instead...");
966           m->pBot->m_sSkillNr = BotManager.m_sBotSkill;
967        }
968     }
969     else // No skill specified, use default
970     m->pBot->m_sSkillNr = BotManager.m_sBotSkill;
971     m->pBot->m_pBotSkill = &BotManager.m_BotSkills[m->pBot->m_sSkillNr];
972     // Sync waypoints
973     m->pBot->SyncWaypoints();
974     m->pBot->Spawn();
975     bots.add(m);
976     return m;
977 }
978 
botmode()979 bool botmode()
980 {
981     if(m_botmode) return true;
982     conoutf("the current game mode does not support bots");
983     return false;
984 }
985 
986 // Bot manager class end
987 
addbot(char * arg1,char * arg2,char * arg3)988 void addbot(char *arg1, char *arg2, char *arg3)
989 {
990     if(!botmode()) return;
991     botent *b = BotManager.CreateBot(arg1, arg2, arg3);
992     if (b) conoutf("Bot connected: %s", b->name);
993     else { conoutf("Error: Couldn't create bot!"); return; }
994 }
995 COMMAND(addbot, "sss");
996 
addnbot(char * arg1,char * arg2,char * arg3)997 void addnbot(char *arg1, char *arg2, char *arg3)
998 {
999     if(!botmode()) return;
1000     if (!arg1 || !arg1[0]) return;
1001 
1002     int i = atoi(arg1);
1003 
1004     while(i > 0)
1005     {
1006         addbot(arg2, arg3, NULL);
1007         i--;
1008     }
1009 }
1010 COMMAND(addnbot, "sss");
1011 
botsshoot(int * Shoot)1012 void botsshoot(int *Shoot)
1013 {
1014     switch(*Shoot)
1015     {
1016         case 0:
1017             BotManager.SetBotsShoot(false);
1018             conoutf("Bots won't shoot");
1019             break;
1020         case 1:
1021             BotManager.SetBotsShoot(true);
1022             conoutf("Bots will shoot");
1023             break;
1024         default:
1025             intret(BotManager.BotsShoot()); break;
1026     }
1027 }
1028 
1029 COMMAND(botsshoot, "i");
1030 
idlebots(int * Idle)1031 void idlebots(int *Idle)
1032 {
1033     switch(*Idle)
1034     {
1035         case 0:
1036             BotManager.SetIdleBots(false);
1037             conoutf("Bots aren't idle");
1038             break;
1039         case 1:
1040             BotManager.SetIdleBots(true);
1041             conoutf("Bots are idle");
1042             break;
1043         default:
1044             intret(BotManager.IdleBots()); break;
1045     }
1046 }
1047 
1048 COMMAND(idlebots, "i");
1049 
drawbeamtobots()1050 void drawbeamtobots()
1051 {
1052     if(!botmode()) return;
1053     loopv(bots)
1054     {
1055         if (bots[i])
1056             particle_trail(PART_SMOKE, 500, player1->o, bots[i]->o);
1057     }
1058 }
1059 
1060 COMMAND(drawbeamtobots, "");
1061 
kickbot(const char * szName)1062 void kickbot(const char *szName)
1063 {
1064     if(!botmode()) return;
1065     if (!szName || !(*szName))
1066         return;
1067 
1068     int iBotInd = -1;
1069     loopv(bots)
1070     {
1071         if (!bots[i]) continue;
1072 
1073         if (!strcmp(bots[i]->name, szName))
1074         {
1075             iBotInd = i;
1076             break;
1077         }
1078     }
1079 
1080     if (iBotInd != -1)
1081     {
1082         botent *d = bots[iBotInd];
1083         if(d->name[0]) conoutf("bot %s disconnected", d->name);
1084         delete d->pBot;
1085         bots.remove(iBotInd);
1086         freebotent(d);
1087     }
1088 }
1089 
1090 COMMAND(kickbot, "s");
1091 
kickallbots(void)1092 void kickallbots(void)
1093 {
1094     BotManager.ClearStoredBots();
1095 
1096     loopv(bots)
1097     {
1098         if (bots[i])
1099         {
1100             if(bots[i]->name[0]) conoutf("bot %s disconnected", bots[i]->name);
1101             delete bots[i]->pBot;
1102             freebotent(bots[i]);
1103         }
1104     };
1105 
1106     bots.setsize(0);
1107 }
1108 
1109 COMMAND(kickallbots, "");
1110 
togglebotview(char * bot)1111 void togglebotview(char *bot)
1112 {
1113     if(!botmode()) return;
1114   /**
1115       Disable in arena modes, this command causes the game to go in an "infinite loop"
1116       due to player1 automatically suiciding thus causing a new round to begin.
1117   */
1118   if(m_arena) { conoutf("togglebotview is not allowed in %s", modestr(gamemode, modeacronyms > 0)); return; }
1119     if (BotManager.m_pBotToView)
1120         BotManager.DisableBotView();
1121     else if (bot && *bot)
1122     {
1123         loopv(bots)
1124         {
1125             if (!bots[i]) continue;
1126 
1127             if (!strcmp(bots[i]->name, bot))
1128             {
1129                 BotManager.EnableBotView(bots[i]);
1130                 break;
1131             }
1132         }
1133     }
1134 }
1135 
1136 COMMAND(togglebotview, "s");
1137 
botskill(char * bot,char * skill)1138 void botskill(char *bot, char *skill)
1139 {
1140     if (!skill || !(*skill))
1141         return;
1142 
1143     short SkillNr;
1144 
1145     if (!strcasecmp(skill, "best"))
1146         SkillNr = 0;
1147     else if (!strcasecmp(skill, "good"))
1148         SkillNr = 1;
1149     else if (!strcasecmp(skill, "medium"))
1150         SkillNr = 2;
1151     else if (!strcasecmp(skill, "worse"))
1152         SkillNr = 3;
1153     else if (!strcasecmp(skill, "bad"))
1154         SkillNr = 4;
1155     else
1156     {
1157         conoutf("Wrong skill specified. Should be best, good, medium, worse or bad");
1158         return;
1159     }
1160 
1161     if (bot)
1162      {
1163          loopv(bots)
1164          {
1165              if (bots[i] && !strcmp(bots[i]->name, bot))
1166              {
1167                  BotManager.ChangeBotSkill(SkillNr, bots[i]);
1168                  break;
1169              }
1170          }
1171      }
1172      else
1173          BotManager.ChangeBotSkill(SkillNr);
1174 }
1175 
1176 COMMAND(botskill, "ss");
1177 
botskillall(char * skill)1178 void botskillall(char *skill)
1179 {
1180     botskill(NULL, skill);
1181 }
1182 
1183 COMMAND(botskillall, "s");
1184 
1185 #ifndef RELEASE_BUILD
1186 
1187 #ifdef VANILLA_CUBE
drawbeamtocarrots()1188 void drawbeamtocarrots()
1189 {
1190     loopv(ents)
1191     {
1192         entity &e = ents[i];
1193         vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight };
1194         if ((e.type != CARROT) || !e.spawned) continue;
1195         particle_trail(PART_SMOKE, 500, player1->o, o);
1196     }
1197 }
1198 
1199 COMMAND(drawbeamtocarrots, "");
1200 
drawbeamtoteleporters()1201 void drawbeamtoteleporters()
1202 {
1203     loopv(ents)
1204     {
1205         entity &e = ents[i];
1206         vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight };
1207         if (e.type != TELEPORT) continue;
1208         particle_trail(PART_SMOKE, 500, player1->o, o);
1209     }
1210 }
1211 
1212 COMMAND(drawbeamtoteleporters, "");
1213 #endif
1214 
telebot(void)1215 void telebot(void)
1216 {
1217     vec dest = player1->o, forward, right, up;
1218     vec angles(player1->pitch, player1->yaw, player1->roll);
1219     traceresult_s tr;
1220 
1221     AnglesToVectors(angles, forward, right, up);
1222     forward.mul(4.0f);
1223     dest.add(forward);
1224 
1225     TraceLine(player1->o, dest, player1, true, &tr);
1226 
1227     if (!tr.collided)
1228     {
1229         // Get the first bot
1230         loopv(bots)
1231         {
1232             if (!bots[i] || !bots[i]->pBot) continue;
1233             bots[i]->o = tr.end;
1234             bots[i]->resetinterp();
1235             break;
1236         }
1237     }
1238 }
1239 
1240 COMMAND(telebot, "");
1241 
testvisible(int iDir)1242 void testvisible(int iDir)
1243 {
1244 
1245     vec angles, end, forward, right, up;
1246     traceresult_s tr;
1247     int Dir;
1248 
1249     switch(iDir)
1250     {
1251         case 0: default: Dir = FORWARD; break;
1252         case 1: Dir = BACKWARD; break;
1253         case 2: Dir = LEFT; break;
1254         case 3: Dir = RIGHT; break;
1255         case 4: Dir = UP; break;
1256         case 5: Dir = DOWN; break;
1257     }
1258 
1259     vec from = player1->o;
1260     from.z -= (player1->eyeheight - 1.25f);
1261     end = from;
1262     makevec(&angles, player1->pitch, player1->yaw, player1->roll);
1263     angles.x=0;
1264 
1265     if (Dir & UP)
1266         angles.x = WrapXAngle(angles.x + 45.0f);
1267     else if (Dir & DOWN)
1268         angles.x = WrapXAngle(angles.x - 45.0f);
1269 
1270     if ((Dir & FORWARD) || (Dir & BACKWARD))
1271     {
1272         if (Dir & BACKWARD)
1273             angles.y = WrapYZAngle(angles.y + 180.0f);
1274 
1275         if (Dir & LEFT)
1276         {
1277             if (Dir & FORWARD)
1278                 angles.y = WrapYZAngle(angles.y - 45.0f);
1279             else
1280                 angles.y = WrapYZAngle(angles.y + 45.0f);
1281         }
1282         else if (Dir & RIGHT)
1283         {
1284             if (Dir & FORWARD)
1285                 angles.y = WrapYZAngle(angles.y + 45.0f);
1286             else
1287                 angles.y = WrapYZAngle(angles.y - 45.0f);
1288         }
1289     }
1290     else if (Dir & LEFT)
1291         angles.y = WrapYZAngle(angles.y - 90.0f);
1292     else if (Dir & RIGHT)
1293         angles.y = WrapYZAngle(angles.y + 90.0f);
1294     else if (Dir & UP)
1295         angles.x = WrapXAngle(angles.x + 90.0f);
1296     else if (Dir & DOWN)
1297         angles.x = WrapXAngle(angles.x - 90.0f);
1298 
1299     AnglesToVectors(angles, forward, right, up);
1300 
1301     forward.mul(20.0f);
1302     end.add(forward);
1303 
1304     TraceLine(from, end, player1, false, &tr);
1305 
1306     //debugbeam(from, tr.end);
1307     char sz[250];
1308     sprintf(sz, "dist: %f; hit: %d", GetDistance(from, tr.end), tr.collided);
1309     condebug(sz);
1310 }
1311 
1312 COMMANDF(testvisible, "i", (int *dir) { testvisible(*dir); });
1313 
mapsize(void)1314 void mapsize(void)
1315 {
1316     switch(ssize)
1317     {
1318         case 128:  intret(7);  break;
1319         case 256:  intret(8);  break;
1320         case 512:  intret(9);  break;
1321         case 1024: intret(10); break;
1322         case 2048: intret(11); break;
1323         case 4096: intret(12); break;
1324         default:   intret(6);  break;
1325     }
1326 }
1327 
1328 COMMAND(mapsize, "");
1329 
1330 #endif
1331