//
// C++ Implementation: bot
//
// Description: Code for botmanager
//
// Main bot file
//
// Author:  Rick <rickhelmus@gmail.com>
//
//
//

#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
