Files
assaultcube-/source/src/bot/botmanager.cpp
T
ac-stef 6bafdb0037 allow particle type names in md2emit and md3emit
md[2|3]emit <tag name> <particle type> <attrib1> <attrib2>

  particle type may now be numeric 0..19 or one of SPARK, SMOKE, ECLOSEST,
  BLOOD, DEMOTRACK, FIREBALL, SHOTLINE, BULLETHOLE, BLOODSTAIN, SCORCH,
  HUDMUZZLEFLASH, MUZZLEFLASH, ELIGHT, ESPAWN, EAMMO, EPICKUP, EMODEL,
  ECARROT, ELADDER, EFLAG

also: cleanup

DOCREF md2emit md3emit
2017-05-16 17:50:00 +02:00

1220 lines
31 KiB
C++

//
// C++ Implementation: bot
//
// Description: Code for botmanager
//
// Main bot file
//
// Author: Rick <[email protected]>
//
//
//
#include "cube.h"
#include "bot.h"
extern void respawnself();
CBotManager BotManager;
// Bot manager class begin
CBotManager::~CBotManager(void)
{
EndMap();
ClearStoredBots();
}
void CBotManager::Init()
{
m_pBotToView = NULL;
m_iFrameTime = 0;
m_iPrevTime = lastmillis;
m_sBotSkill = 1; // Default all bots have the skill 'Good'
CreateSkillData();
LoadBotNamesFile();
LoadBotTeamsFile();
//WaypointClass.Init();
lsrand(time(NULL));
}
void CBotManager::Think()
{
if (m_bInit)
{
Init();
m_bInit = false;
}
if (m_pBotToView) ViewBot();
AddDebugText("m_sMaxAStarBots: %d", m_sMaxAStarBots);
AddDebugText("m_sCurrentTriggerNr: %d", m_sCurrentTriggerNr);
short x, y;
WaypointClass.GetNodeIndexes(player1->o, &x, &y);
AddDebugText("x: %d y: %d", x, y);
m_iFrameTime = lastmillis - m_iPrevTime;
if (m_iFrameTime > 250) m_iFrameTime = 250;
m_iPrevTime = lastmillis;
// Is it time to re-add bots?
if ((m_fReAddBotDelay < lastmillis) && (m_fReAddBotDelay != -1.0f))
{
while(m_StoredBots.Empty() == false)
{
CStoredBot *pStoredBot = m_StoredBots.Pop();
ReAddBot(pStoredBot);
delete pStoredBot;
}
m_fReAddBotDelay = -1.0f;
}
// Let all bots 'think'
loopv(bots)
{
if (!bots[i]) continue;
if (bots[i]->pBot)
{
bots[i]->pBot->CheckWeaponSwitch(); // 2011jan17:ft: fix non-shooting bots
bots[i]->pBot->Think();
}
else condebug("Error: pBot == NULL in bot ent\n");
}
}
void CBotManager::LoadBotNamesFile()
{
// Init bot names array first
for (int i=0;i<100;i++)
strcpy(m_szBotNames[i], "Bot");
m_sBotNameCount = 0;
// Load bot file
char szNameFileName[256];
MakeBotFileName("bot_names.txt", NULL, NULL, szNameFileName);
FILE *fp = fopen(szNameFileName, "r");
char szNameBuffer[256];
int iIndex, iStrIndex;
if (!fp)
{
conoutf("Warning: Couldn't load bot names file");
return;
}
while (fgets(szNameBuffer, 80, fp) != NULL)
{
if (m_sBotNameCount >= 150)
{
conoutf("Warning: Max bot names reached(150), ignoring the rest of the"
"names");
break;
}
short length = (short)strlen(szNameBuffer);
if (szNameBuffer[length-1] == '\n')
{
szNameBuffer[length-1] = 0; // remove '\n'
length--;
}
iStrIndex = 0;
while (iStrIndex < length)
{
if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') ||
(szNameBuffer[iStrIndex] == '"'))
{
for (iIndex=iStrIndex; iIndex < length; iIndex++)
szNameBuffer[iIndex] = szNameBuffer[iIndex+1];
}
iStrIndex++;
}
if (szNameBuffer[0] != 0)
{
if (strlen(szNameBuffer) >= 16)
{ conoutf("Warning: bot name \"%s\" has to many characters(16 is max)",
szNameBuffer);
}
copystring(m_szBotNames[m_sBotNameCount], szNameBuffer, 16);
m_sBotNameCount++;
}
}
fclose(fp);
}
const char *CBotManager::GetBotName()
{
const char *szOutput = NULL;
TMultiChoice<const char *> BotNameChoices;
short ChoiceVal;
for(int j=0;j<m_sBotNameCount;j++)
{
ChoiceVal = 50;
loopv(bots)
{
if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j])))
{
ChoiceVal = 0;
break;
}
}
if (!strcasecmp(player1->name, m_szBotNames[j]))
ChoiceVal = 0;
if(ChoiceVal) BotNameChoices.Insert(m_szBotNames[j], ChoiceVal);
}
// Couldn't find a selection?
if (!BotNameChoices.GetSelection(szOutput))
szOutput = "Bot";
return szOutput;
}
void CBotManager::LoadBotTeamsFile()
{
// Init bot teams array first
for (int i=0;i<20;i++)
strcpy(m_szBotTeams[i], "b0ts");
m_sBotTeamCount = 0;
// Load bot file
char szNameFileName[256];
MakeBotFileName("bot_teams.txt", NULL, NULL, szNameFileName);
FILE *fp = fopen(szNameFileName, "r");
char szNameBuffer[256];
int iIndex, iStrIndex;
if (!fp)
{
conoutf("Warning: Couldn't load bot teams file");
return;
}
while ((m_sBotTeamCount < 20) && (fgets(szNameBuffer, 80, fp) != NULL))
{
short length = (short)strlen(szNameBuffer);
if (szNameBuffer[length-1] == '\n')
{
szNameBuffer[length-1] = 0; // remove '\n'
length--;
}
iStrIndex = 0;
while (iStrIndex < length)
{
if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') ||
(szNameBuffer[iStrIndex] == '"'))
{
for (iIndex=iStrIndex; iIndex < length; iIndex++)
szNameBuffer[iIndex] = szNameBuffer[iIndex+1];
}
iStrIndex++;
}
if (szNameBuffer[0] != 0)
{
copystring(m_szBotTeams[m_sBotTeamCount], szNameBuffer, 5);
m_sBotTeamCount++;
}
}
fclose(fp);
}
const char *CBotManager::GetBotTeam()
{
int teamsize[2] = {0, 0};
int biggestTeam = 0;
if(team_isactive(player1->team))
{
teamsize[player1->team]++;
}
loopv(bots) if(bots[i] && team_isactive(bots[i]->team))
{
teamsize[bots[i]->team]++;
}
biggestTeam = teamsize[1] > teamsize[0];
return teamnames[biggestTeam ^ 1];
// Following code will never be executed.
// (bot teams aren't ready, we use normal teams instead)
const char *szOutput = NULL;
TMultiChoice<const char *> BotTeamChoices;
short ChoiceVal;
for(int j=0;j<m_sBotTeamCount;j++)
{
ChoiceVal = 50;
/* UNDONE?
loopv(bots)
{
if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j])))
ChoiceVal -= 10;
}
if (!strcasecmp(player1->name, m_szBotNames[j]))
ChoiceVal -= 10;
if (ChoiceVal <= 0)
ChoiceVmonsterclearal = 1;*/
BotTeamChoices.Insert(m_szBotTeams[j], ChoiceVal);
}
// Couldn't find a selection?
if (!BotTeamChoices.GetSelection(szOutput))
szOutput = "b0t";
return szOutput;
}
void CBotManager::RenderBots()
{
//static bool drawblue;
loopv(bots)
{
if (bots[i] && (bots[i] != m_pBotToView))
{
renderclientp(bots[i]);
}
}
}
void CBotManager::EndMap()
{
// Remove all bots
loopv(bots)
{
if(!bots[i] || !bots[i]->pBot)
continue;
// Store bots so they can be re-added after map change
if (bots[i]->pBot && bots[i]->name[0] && team_isactive(bots[i]->team))
{
CStoredBot *pStoredBot = new CStoredBot(bots[i]->name, (char *) team_string(bots[i]->team),
bots[i]->pBot->m_sSkillNr);
m_StoredBots.AddNode(pStoredBot);
}
delete bots[i]->pBot;
bots[i]->pBot = NULL;
freebotent(bots[i]);
}
bots.setsize(0);
condebug("Cleared all bots");
m_fReAddBotDelay = lastmillis + 7500;
//if(ishost()) WaypointClass.SaveWPExpFile(); //UNDONE
}
void CBotManager::BeginMap(const char *szMapName)
{
EndMap(); // End previous map
WaypointClass.Init();
WaypointClass.SetMapName(szMapName);
if (!WaypointClass.LoadWaypoints())
WaypointClass.StartFlood();
//WaypointClass.LoadWPExpFile(); // UNDONE
CalculateMaxAStarCount();
m_sUsingAStarBotsCount = 0;
PickNextTrigger();
}
int CBotManager::GetBotIndex(botent *m)
{
loopv(bots)
{
if (!bots[i])
continue;
if (bots[i] == m)
return i;
}
return -1;
}
void CBotManager::LetBotsUpdateStats()
{
loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->m_bSendC2SInit = false;
}
void CBotManager::LetBotsHear(int n, vec *loc)
{
if (bots.length() == 0 || !loc) return;
loopv(bots)
{
if (!bots[i] || !bots[i]->pBot || (bots[i]->state == CS_DEAD)) continue;
bots[i]->pBot->HearSound(n, loc);
}
}
// Notify all bots of a new waypoint
void CBotManager::AddWaypoint(node_s *pNode)
{
if (bots.length())
{
short x, y;
waypoint_s *pWP;
loopv(bots)
{
if (!bots[i] || !bots[i]->pBot) continue;
pWP = new waypoint_s;
pWP->pNode = pNode;
WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y);
bots[i]->pBot->m_WaypointList[x][y].AddNode(pWP);
#ifndef RELEASE_BUILD
if (!bots[i]->pBot->GetWPFromNode(pNode)) condebug("Error adding bot wp!");
#endif
}
}
CalculateMaxAStarCount();
}
// Notify all bots of a deleted waypoint
void CBotManager::DelWaypoint(node_s *pNode)
{
if (bots.length())
{
short x, y;
TLinkedList<waypoint_s *>::node_s *p;
loopv(bots)
{
if (!bots[i] || !bots[i]->pBot) continue;
WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y);
p = bots[i]->pBot->m_WaypointList[x][y].GetFirst();
while(p)
{
if (p->Entry->pNode == pNode)
{
delete p->Entry;
bots[i]->pBot->m_WaypointList[x][y].DeleteNode(p);
break;
}
p = p->next;
}
}
}
CalculateMaxAStarCount();
}
void CBotManager::MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, char *szOutput)
{
const char *DirSeperator;
#ifdef WIN32
DirSeperator = "\\";
strcpy(szOutput, "bot\\");
#else
DirSeperator = "/";
strcpy(szOutput, "bot/");
#endif
if (szDir1)
{
strcat(szOutput, szDir1);
strcat(szOutput, DirSeperator);
}
if (szDir2)
{
strcat(szOutput, szDir2);
strcat(szOutput, DirSeperator);
}
strcat(szOutput, szFileName);
}
void CBotManager::CreateSkillData()
{
// First give the bot skill structure some default data
InitSkillData();
// Now see if we can load the skill.cfg file
char SkillFileName[256] = "";
FILE *pSkillFile = NULL;
int SkillNr = -1;
float value = 0;
MakeBotFileName("bot_skill.cfg", NULL, NULL, SkillFileName);
pSkillFile = fopen(SkillFileName, "r");
conoutf("Reading bot_skill.cfg file... ");
if (pSkillFile == NULL) // file doesn't exist
{
conoutf("skill file not found, default settings will be used\n");
return;
}
int ch;
char cmd_line[256];
int cmd_index;
char *cmd, *arg1;
while (pSkillFile)
{
cmd_index = 0;
cmd_line[cmd_index] = 0;
ch = fgetc(pSkillFile);
// skip any leading blanks
while (ch == ' ')
ch = fgetc(pSkillFile);
while ((ch != EOF) && (ch != '\r') && (ch != '\n'))
{
if (ch == '\t') // convert tabs to spaces
ch = ' ';
cmd_line[cmd_index] = ch;
ch = fgetc(pSkillFile);
// skip multiple spaces in input file
while ((cmd_line[cmd_index] == ' ') && (ch == ' '))
ch = fgetc(pSkillFile);
cmd_index++;
}
if (ch == '\r') // is it a carriage return?
{
ch = fgetc(pSkillFile); // skip the linefeed
}
// if reached end of file, then close it
if (ch == EOF)
{
fclose(pSkillFile);
pSkillFile = NULL;
}
cmd_line[cmd_index] = 0; // terminate the command line
cmd_index = 0;
cmd = cmd_line;
arg1 = NULL;
// skip to blank or end of string...
while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0))
cmd_index++;
if (cmd_line[cmd_index] == ' ')
{
cmd_line[cmd_index++] = 0;
arg1 = &cmd_line[cmd_index];
}
if ((cmd_line[0] == '#') || (cmd_line[0] == 0))
continue; // skip if comment or blank line
if (strcasecmp(cmd, "[SKILL1]") == 0)
SkillNr = 0;
else if (strcasecmp(cmd, "[SKILL2]") == 0)
SkillNr = 1;
else if (strcasecmp(cmd, "[SKILL3]") == 0)
SkillNr = 2;
else if (strcasecmp(cmd, "[SKILL4]") == 0)
SkillNr = 3;
else if (strcasecmp(cmd, "[SKILL5]") == 0)
SkillNr = 4;
if ((arg1 == NULL) || (*arg1 == 0))
continue;
if (SkillNr == -1) // Not in a skill block yet?
continue;
value = atof(arg1);
if (strcasecmp(cmd, "min_x_aim_speed") == 0)
{
m_BotSkills[SkillNr].flMinAimXSpeed = value;
}
else if (strcasecmp(cmd, "max_x_aim_speed") == 0)
{
m_BotSkills[SkillNr].flMaxAimXSpeed = value;
}
else if (strcasecmp(cmd, "min_y_aim_speed") == 0)
{
m_BotSkills[SkillNr].flMinAimYSpeed = value;
}
else if (strcasecmp(cmd, "max_y_aim_speed") == 0)
{
m_BotSkills[SkillNr].flMaxAimYSpeed = value;
}
else if (strcasecmp(cmd, "min_x_aim_offset") == 0)
{
m_BotSkills[SkillNr].flMinAimXOffset = value;
}
else if (strcasecmp(cmd, "max_x_aim_offset") == 0)
{
m_BotSkills[SkillNr].flMaxAimXOffset = value;
}
else if (strcasecmp(cmd, "min_y_aim_offset") == 0)
{
m_BotSkills[SkillNr].flMinAimYOffset = value;
}
else if (strcasecmp(cmd, "max_y_aim_offset") == 0)
{
m_BotSkills[SkillNr].flMaxAimYOffset = value;
}
else if (strcasecmp(cmd, "min_attack_delay") == 0)
{
m_BotSkills[SkillNr].flMinAttackDelay = value;
}
else if (strcasecmp(cmd, "max_attack_delay") == 0)
{
m_BotSkills[SkillNr].flMaxAttackDelay = value;
}
else if (strcasecmp(cmd, "min_enemy_search_delay") == 0)
{
m_BotSkills[SkillNr].flMinEnemySearchDelay = value;
}
else if (strcasecmp(cmd, "max_enemy_search_delay") == 0)
{
m_BotSkills[SkillNr].flMaxEnemySearchDelay = value;
}
else if (strcasecmp(cmd, "max_always_detect_distance") == 0)
{
m_BotSkills[SkillNr].flAlwaysDetectDistance = value;
}
else if (strcasecmp(cmd, "can_predict_position") == 0)
{
m_BotSkills[SkillNr].bCanPredict = value!=0;
}
else if (strcasecmp(cmd, "field_of_view") == 0)
{
if (value < 80) value = 80;
else if (value > 240) value = 120;
m_BotSkills[SkillNr].iFov = (int)value;
}
else if (strcasecmp(cmd, "max_hear_volume") == 0)
{
if (value < 0) value = 0;
else if (value > 255) value = 100;
m_BotSkills[SkillNr].iMaxHearVolume = (int)value;
}
else if (strcasecmp(cmd, "can_circle_strafe") == 0)
{
m_BotSkills[SkillNr].bCircleStrafe = value!=0;
}
else if (strcasecmp(cmd, "can_search_items_in_combat") == 0)
{
m_BotSkills[SkillNr].bCanSearchItemsInCombat = value!=0;
}
}
conoutf("done");
}
void CBotManager::InitSkillData()
{
// Best skill
m_BotSkills[0].flMinReactionDelay = 0.015f;
m_BotSkills[0].flMaxReactionDelay = 0.035f;
m_BotSkills[0].flMinAimXOffset = 15.0f;
m_BotSkills[0].flMaxAimXOffset = 20.0f;
m_BotSkills[0].flMinAimYOffset = 10.0f;
m_BotSkills[0].flMaxAimYOffset = 15.0f;
m_BotSkills[0].flMinAimXSpeed = 330.0f;
m_BotSkills[0].flMaxAimXSpeed = 355.0f;
m_BotSkills[0].flMinAimYSpeed = 400.0f;
m_BotSkills[0].flMaxAimYSpeed = 450.0f;
m_BotSkills[0].flMinAttackDelay = 0.1f;
m_BotSkills[0].flMaxAttackDelay = 0.4f;
m_BotSkills[0].flMinEnemySearchDelay = 0.09f;
m_BotSkills[0].flMaxEnemySearchDelay = 0.12f;
m_BotSkills[0].flAlwaysDetectDistance = 12.0f;
m_BotSkills[0].bCanPredict = true;
m_BotSkills[0].iMaxHearVolume = 75;
m_BotSkills[0].iFov = 200;
m_BotSkills[0].bCircleStrafe = true;
m_BotSkills[0].bCanSearchItemsInCombat = true;
// Good skill
m_BotSkills[1].flMinReactionDelay = 0.035f;
m_BotSkills[1].flMaxReactionDelay = 0.045f;
m_BotSkills[1].flMinAimXOffset = 20.0f;
m_BotSkills[1].flMaxAimXOffset = 25.0f;
m_BotSkills[1].flMinAimYOffset = 15.0f;
m_BotSkills[1].flMaxAimYOffset = 20.0f;
m_BotSkills[1].flMinAimXSpeed = 250.0f;
m_BotSkills[1].flMaxAimXSpeed = 265.0f;
m_BotSkills[1].flMinAimYSpeed = 260.0f;
m_BotSkills[1].flMaxAimYSpeed = 285.0f;
m_BotSkills[1].flMinAttackDelay = 0.3f;
m_BotSkills[1].flMaxAttackDelay = 0.6f;
m_BotSkills[1].flMinEnemySearchDelay = 0.12f;
m_BotSkills[1].flMaxEnemySearchDelay = 0.17f;
m_BotSkills[1].flAlwaysDetectDistance = 10.0f;
m_BotSkills[1].bCanPredict = true;
m_BotSkills[1].iMaxHearVolume = 60;
m_BotSkills[1].iFov = 160;
m_BotSkills[1].bCircleStrafe = true;
m_BotSkills[1].bCanSearchItemsInCombat = true;
// Medium skill
m_BotSkills[2].flMinReactionDelay = 0.075f;
m_BotSkills[2].flMaxReactionDelay = 0.010f;
m_BotSkills[2].flMinAimXOffset = 25.0f;
m_BotSkills[2].flMaxAimXOffset = 30.0f;
m_BotSkills[2].flMinAimYOffset = 20.0f;
m_BotSkills[2].flMaxAimYOffset = 25.0f;
m_BotSkills[2].flMinAimXSpeed = 190.0f;
m_BotSkills[2].flMaxAimXSpeed = 125.0f;
m_BotSkills[2].flMinAimYSpeed = 210.0f;
m_BotSkills[2].flMaxAimYSpeed = 240.0f;
m_BotSkills[2].flMinAttackDelay = 0.75f;
m_BotSkills[2].flMaxAttackDelay = 1.0f;
m_BotSkills[2].flMinEnemySearchDelay = 0.18f;
m_BotSkills[2].flMaxEnemySearchDelay = 0.22f;
m_BotSkills[2].flAlwaysDetectDistance = 8.0f;
m_BotSkills[2].bCanPredict = false;
m_BotSkills[2].iMaxHearVolume = 45;
m_BotSkills[2].iFov = 130;
m_BotSkills[2].bCircleStrafe = true;
m_BotSkills[2].bCanSearchItemsInCombat = false;
// Worse skill
m_BotSkills[3].flMinReactionDelay = 0.15f;
m_BotSkills[3].flMaxReactionDelay = 0.20f;
m_BotSkills[3].flMinAimXOffset = 30.0f;
m_BotSkills[3].flMaxAimXOffset = 35.0f;
m_BotSkills[3].flMinAimYOffset = 25.0f;
m_BotSkills[3].flMaxAimYOffset = 30.0f;
m_BotSkills[3].flMinAimXSpeed = 155.0f;
m_BotSkills[3].flMaxAimXSpeed = 170.0f;
m_BotSkills[3].flMinAimYSpeed = 160.0f;
m_BotSkills[3].flMaxAimYSpeed = 210.0f;
m_BotSkills[3].flMinAttackDelay = 1.2f;
m_BotSkills[3].flMaxAttackDelay = 1.6f;
m_BotSkills[3].flMinEnemySearchDelay = 0.25f;
m_BotSkills[3].flMaxEnemySearchDelay = 0.30f;
m_BotSkills[3].flAlwaysDetectDistance = 6.0f;
m_BotSkills[3].bCanPredict = false;
m_BotSkills[3].iMaxHearVolume = 30;
m_BotSkills[3].iFov = 120;
m_BotSkills[3].bCircleStrafe = false;
m_BotSkills[3].bCanSearchItemsInCombat = false;
// Bad skill
m_BotSkills[4].flMinReactionDelay = 0.30f;
m_BotSkills[4].flMaxReactionDelay = 0.50f;
m_BotSkills[4].flMinAimXOffset = 35.0f;
m_BotSkills[4].flMaxAimXOffset = 40.0f;
m_BotSkills[4].flMinAimYOffset = 30.0f;
m_BotSkills[4].flMaxAimYOffset = 35.0f;
m_BotSkills[4].flMinAimXSpeed = 45.0f;
m_BotSkills[4].flMaxAimXSpeed = 60.0f;
m_BotSkills[4].flMinAimYSpeed = 125.0f;
m_BotSkills[4].flMaxAimYSpeed = 180.0f;
m_BotSkills[4].flMinAttackDelay = 1.5f;
m_BotSkills[4].flMaxAttackDelay = 2.0f;
m_BotSkills[4].flMinEnemySearchDelay = 0.30f;
m_BotSkills[4].flMaxEnemySearchDelay = 0.36f;
m_BotSkills[4].flAlwaysDetectDistance = 4.0f;
m_BotSkills[4].bCanPredict = false;
m_BotSkills[4].iMaxHearVolume = 15;
m_BotSkills[4].iFov = 110;
m_BotSkills[4].bCircleStrafe = false;
m_BotSkills[4].bCanSearchItemsInCombat = false;
}
void CBotManager::ChangeBotSkill(short Skill, botent *bot)
{
static const char *SkillNames[5] = { "best", "good", "medium", "worse", "bad" };
if (bot && bot->pBot)
{
// Only change skill of a single bot
bot->pBot->m_pBotSkill = &m_BotSkills[Skill];
bot->pBot->m_sSkillNr = Skill;
conoutf("Skill of %s is now %s", bot->name, SkillNames[Skill]);
return;
}
// Change skill of all bots
loopv(bots)
{
if (!bots[i] || !bots[i]->pBot) continue;
bots[i]->pBot->m_pBotSkill = &m_BotSkills[Skill];
bots[i]->pBot->m_sSkillNr = Skill;
}
// Change default bot skill
m_sBotSkill = Skill;
conoutf("Skill of all bots is now %s", SkillNames[Skill]);
}
void CBotManager::ViewBot()
{
// Check if this bot is still in game
bool bFound = false;
loopv(bots)
{
if (bots[i] == m_pBotToView)
{
bFound = true;
break;
}
}
if (!bFound)
{
DisableBotView();
return;
}
player1->state = CS_DEAD; // Fake dead
player1->o = m_pBotToView->o;
player1->o.z += 1.0f;
player1->yaw = m_pBotToView->yaw;
player1->pitch = m_pBotToView->pitch;
player1->roll = m_pBotToView->roll;
player1->radius = 0; // Don't collide
player1->resetinterp();
}
void CBotManager::DisableBotView()
{
m_pBotToView = NULL;
respawnself();
player1->radius = 1.1f;
}
void CBotManager::CalculateMaxAStarCount()
{
if (WaypointClass.m_iWaypointCount > 0) // Are there any waypoints?
{
m_sMaxAStarBots = 8 - short(ceil((float)WaypointClass.m_iWaypointCount /
1000.0f));
if (m_sMaxAStarBots < 1)
m_sMaxAStarBots = 1;
}
else
m_sMaxAStarBots = 1;
}
void CBotManager::PickNextTrigger()
{
short lowest = -1;
bool found0 = false; // True if found a trigger with nr 0
loopv(ents)
{
entity &e = ents[i];
if (OUTBORD(e.x, e.y)) continue;
vec o(e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight);
node_s *pWptNearEnt = NULL;
pWptNearEnt = WaypointClass.GetNearestTriggerWaypoint(o, 2.0f);
if (pWptNearEnt)
{
if ((pWptNearEnt->sTriggerNr > 0) &&
((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1)))
lowest = pWptNearEnt->sTriggerNr;
if (pWptNearEnt->sTriggerNr == 0) found0 = true;
}
#ifdef WP_FLOOD
pWptNearEnt = WaypointClass.GetNearestTriggerFloodWP(o, 2.0f);
if (pWptNearEnt)
{
if ((pWptNearEnt->sTriggerNr > 0) &&
((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1)))
lowest = pWptNearEnt->sTriggerNr;
if (pWptNearEnt->sTriggerNr == 0) found0 = true;
}
#endif
}
if ((lowest == -1) && found0) lowest = 0;
if (lowest != -1)
m_sCurrentTriggerNr = lowest;
}
botent *CBotManager::CreateBot(const char *team, const char *skill, const char *name)
{
if (m_bInit)
{
Init();
m_bInit = false;
}
botent *m = newbotent();
if (!m) return NULL;
loopi(NUMGUNS) m->ammo[i] = m->mag[i] = 0;
m->lifesequence = 0;
m->pBot = new CACBot;
m->type = ENT_BOT;
m->pBot->m_pMyEnt = m;
m->pBot->m_iLastBotUpdate = 0;
m->pBot->m_bSendC2SInit = false;
if (name && *name) copystring(m->name, name, 16);
else copystring(m->name, BotManager.GetBotName(), 16);
updateclientname((playerent *)m);
const char *tempteam = team && *team && strcmp(team, "random") ? team : BotManager.GetBotTeam();
m->team = team_base(teamatoi(tempteam));
setskin(m, m->team == TEAM_CLA ? rnd(4) : rnd(6), m->team);
if (skill && *skill && strcmp(skill, "random"))
{
if (!strcasecmp(skill, "best")) m->pBot->m_sSkillNr = 0;
else if (!strcasecmp(skill, "good")) m->pBot->m_sSkillNr = 1;
else if (!strcasecmp(skill, "medium")) m->pBot->m_sSkillNr = 2;
else if (!strcasecmp(skill, "worse")) m->pBot->m_sSkillNr = 3;
else if (!strcasecmp(skill, "bad")) m->pBot->m_sSkillNr = 4;
else
{
conoutf("Wrong skill specified. Should be best, good, medium, "
"worse or bad");
conoutf("Using default skill instead...");
m->pBot->m_sSkillNr = BotManager.m_sBotSkill;
}
}
else // No skill specified, use default
m->pBot->m_sSkillNr = BotManager.m_sBotSkill;
m->pBot->m_pBotSkill = &BotManager.m_BotSkills[m->pBot->m_sSkillNr];
// Sync waypoints
m->pBot->SyncWaypoints();
m->pBot->Spawn();
bots.add(m);
return m;
}
bool botmode()
{
if(m_botmode) return true;
conoutf("the current game mode does not support bots");
return false;
}
// Bot manager class end
void addbot(char *arg1, char *arg2, char *arg3)
{
if(!botmode()) return;
botent *b = BotManager.CreateBot(arg1, arg2, arg3);
if (b) conoutf("Bot connected: %s", b->name);
else { conoutf("Error: Couldn't create bot!"); return; }
}
COMMAND(addbot, "sss");
void addnbot(char *arg1, char *arg2, char *arg3)
{
if(!botmode()) return;
if (!arg1 || !arg1[0]) return;
int i = atoi(arg1);
while(i > 0)
{
addbot(arg2, arg3, NULL);
i--;
}
}
COMMAND(addnbot, "sss");
void botsshoot(int *Shoot)
{
switch(*Shoot)
{
case 0:
BotManager.SetBotsShoot(false);
conoutf("Bots won't shoot");
break;
case 1:
BotManager.SetBotsShoot(true);
conoutf("Bots will shoot");
break;
default:
intret(BotManager.BotsShoot()); break;
}
}
COMMAND(botsshoot, "i");
void idlebots(int *Idle)
{
switch(*Idle)
{
case 0:
BotManager.SetIdleBots(false);
conoutf("Bots aren't idle");
break;
case 1:
BotManager.SetIdleBots(true);
conoutf("Bots are idle");
break;
default:
intret(BotManager.IdleBots()); break;
}
}
COMMAND(idlebots, "i");
void kickbot(const char *szName)
{
if(!botmode()) return;
if (!szName || !(*szName))
return;
int iBotInd = -1;
loopv(bots)
{
if (!bots[i]) continue;
if (!strcmp(bots[i]->name, szName))
{
iBotInd = i;
break;
}
}
if (iBotInd != -1)
{
botent *d = bots[iBotInd];
if(d->name[0]) conoutf("bot %s disconnected", d->name);
delete d->pBot;
bots.remove(iBotInd);
freebotent(d);
}
}
COMMAND(kickbot, "s");
void kickallbots(void)
{
BotManager.ClearStoredBots();
loopv(bots)
{
if (bots[i])
{
if(bots[i]->name[0]) conoutf("bot %s disconnected", bots[i]->name);
delete bots[i]->pBot;
freebotent(bots[i]);
}
};
bots.setsize(0);
}
COMMAND(kickallbots, "");
void togglebotview(char *bot)
{
if(!botmode()) return;
/**
Disable in arena modes, this command causes the game to go in an "infinite loop"
due to player1 automatically suiciding thus causing a new round to begin.
*/
if(m_arena) { conoutf("togglebotview is not allowed in %s", modestr(gamemode, modeacronyms > 0)); return; }
if (BotManager.m_pBotToView)
BotManager.DisableBotView();
else if (bot && *bot)
{
loopv(bots)
{
if (!bots[i]) continue;
if (!strcmp(bots[i]->name, bot))
{
BotManager.EnableBotView(bots[i]);
break;
}
}
}
}
COMMAND(togglebotview, "s");
void botskill(char *bot, char *skill)
{
if (!skill || !(*skill))
return;
short SkillNr;
if (!strcasecmp(skill, "best"))
SkillNr = 0;
else if (!strcasecmp(skill, "good"))
SkillNr = 1;
else if (!strcasecmp(skill, "medium"))
SkillNr = 2;
else if (!strcasecmp(skill, "worse"))
SkillNr = 3;
else if (!strcasecmp(skill, "bad"))
SkillNr = 4;
else
{
conoutf("Wrong skill specified. Should be best, good, medium, worse or bad");
return;
}
if (bot)
{
loopv(bots)
{
if (bots[i] && !strcmp(bots[i]->name, bot))
{
BotManager.ChangeBotSkill(SkillNr, bots[i]);
break;
}
}
}
else
BotManager.ChangeBotSkill(SkillNr);
}
COMMAND(botskill, "ss");
void botskillall(char *skill)
{
botskill(NULL, skill);
}
COMMAND(botskillall, "s");
#ifndef RELEASE_BUILD
void telebot(void)
{
vec dest = player1->o, forward, right, up;
vec angles(player1->pitch, player1->yaw, player1->roll);
traceresult_s tr;
AnglesToVectors(angles, forward, right, up);
forward.mul(4.0f);
dest.add(forward);
TraceLine(player1->o, dest, player1, true, &tr);
if (!tr.collided)
{
// Get the first bot
loopv(bots)
{
if (!bots[i] || !bots[i]->pBot) continue;
bots[i]->o = tr.end;
bots[i]->resetinterp();
break;
}
}
}
COMMAND(telebot, "");
void testvisible(int iDir)
{
vec angles, end, forward, right, up;
traceresult_s tr;
int Dir;
switch(iDir)
{
case 0: default: Dir = FORWARD; break;
case 1: Dir = BACKWARD; break;
case 2: Dir = LEFT; break;
case 3: Dir = RIGHT; break;
case 4: Dir = UP; break;
case 5: Dir = DOWN; break;
}
vec from = player1->o;
from.z -= (player1->eyeheight - 1.25f);
end = from;
makevec(&angles, player1->pitch, player1->yaw, player1->roll);
angles.x=0;
if (Dir & UP)
angles.x = WrapXAngle(angles.x + 45.0f);
else if (Dir & DOWN)
angles.x = WrapXAngle(angles.x - 45.0f);
if ((Dir & FORWARD) || (Dir & BACKWARD))
{
if (Dir & BACKWARD)
angles.y = WrapYZAngle(angles.y + 180.0f);
if (Dir & LEFT)
{
if (Dir & FORWARD)
angles.y = WrapYZAngle(angles.y - 45.0f);
else
angles.y = WrapYZAngle(angles.y + 45.0f);
}
else if (Dir & RIGHT)
{
if (Dir & FORWARD)
angles.y = WrapYZAngle(angles.y + 45.0f);
else
angles.y = WrapYZAngle(angles.y - 45.0f);
}
}
else if (Dir & LEFT)
angles.y = WrapYZAngle(angles.y - 90.0f);
else if (Dir & RIGHT)
angles.y = WrapYZAngle(angles.y + 90.0f);
else if (Dir & UP)
angles.x = WrapXAngle(angles.x + 90.0f);
else if (Dir & DOWN)
angles.x = WrapXAngle(angles.x - 90.0f);
AnglesToVectors(angles, forward, right, up);
forward.mul(20.0f);
end.add(forward);
TraceLine(from, end, player1, false, &tr);
//debugbeam(from, tr.end);
char sz[250];
sprintf(sz, "dist: %f; hit: %d", GetDistance(from, tr.end), tr.collided);
condebug(sz);
}
COMMANDF(testvisible, "i", (int *dir) { testvisible(*dir); });
#endif