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