#include "gen_bot_header.h"
#include "teamplay_gamerules.h"

#define PLDROP_HEALTH					 5
#define ASCEND_KI_COST					100			//cost of ascending

void ShowInGameBotHelpWindow();

CBotManager BotManager;
bool PrintingInGameHelp = false;

extern int gmsgMOTD;
extern int gmsgServerName;
extern int gmsgBotVGUI;

//harSens: Linux compile fix
extern CLeftOutput lcout;

inline edict_t *CREATE_FAKE_CLIENT( const char *netname )
{
   return (*g_engfuncs.pfnCreateFakeClient)( netname );
}

inline char *GET_INFOBUFFER( edict_t *e )
{
   return (*g_engfuncs.pfnGetInfoKeyBuffer)( e );
}

inline char *GET_INFO_KEY_VALUE( char *infobuffer, char *key )
{
   return (g_engfuncs.pfnInfoKeyValue( infobuffer, key ));
}

cvar_t CBotManager::sv_num_bots = {"sv_numbots", "0.0", FCVAR_SERVER };
cvar_t CBotManager::sv_bot_skill = {"sv_botskill", "1.0", FCVAR_SERVER };
cvar_t CBotManager::sv_botsshoot = {"sv_botsshoot", "1.0", FCVAR_SERVER };
cvar_t CBotManager::sv_botssearchenemies = {"sv_botssearchenemies", "1.0", FCVAR_SERVER };
cvar_t CBotManager::sv_observebots = {"sv_observebots", "0.0", FCVAR_SERVER };
cvar_t CBotManager::sv_idlebots = {"sv_idlebots", "0.0", FCVAR_SERVER };
cvar_t CBotManager::sv_botsusediscs = {"sv_botsusediscs", "1.0", FCVAR_SERVER };
cvar_t CBotManager::sv_botsmove = {"sv_botsmove", "1.0", FCVAR_SERVER };
cvar_t CBotManager::sv_botschat = {"sv_botschat", "1.0", FCVAR_SERVER };
cvar_t CBotManager::sv_botsbalanceteams = {"sv_botsbalanceteams", "0.0", FCVAR_SERVER };
 
LINK_ENTITY_TO_CLASS( bot, CBot );

// ==================================================================
// Code of CBot - Begin
// ==================================================================

void CBot::CreateInit(short Team, short Class, short Skill, const char *szPerso, short SBotVal,
					  EBotDBType BotDBType)
{
	pev->flags |= FL_FAKECLIENT;
    m_fCreateTime = gpGlobals->time;
    m_bNotStarted = true;  // hasn't joined game yet
	m_fStartUpDelay = gpGlobals->time + 0.15f;

	if (g_teamplay)
		m_eStartAction = START_TEAM_SELECT;
	else
		m_eStartAction = START_CLASS_SELECT;

	m_bNeedToInitialize = false;  // don't need to initialize yet

	m_pLastEnemy = NULL;
	m_pKiller = NULL;

    m_pBotSkill = &BotManager.m_BotSkills[Skill - 1];  // 0 based for array indexes
	m_bSkillNr = Skill - 1;

    m_sBotTeam = -1;
    m_sBotClass = -1;

	if ((Team == 1) || (Team == 2))
		m_sBotTeam = Team;

	if ((Class >= 1) && (Class <= 9))
		m_sBotClass = Class;

	if ((!szPerso) || (*szPerso == 0))
	{
		if (BotManager.m_szDefaultPersonality[0])
			szPerso = BotManager.m_szDefaultPersonality;
	}

	m_pPersonality = NULL;

	if ((szPerso) && (*szPerso) && (!FStrEq(szPerso, "random_perso")))
	{
		m_pPersonality = BotManager.FindPersoByName(szPerso);
	}

	if (!m_pPersonality)
		m_pPersonality = BotManager.GetRandPerso();

	if (!m_pPersonality)
		cout << "Error couldn't find personality!\n";

	m_lVisiblePlayers = m_lValidPlayers = 0;

	m_pCurrentBotTask = NULL;
	m_pMeleeComboInfoList = NULL;
	m_pBotCombo = NULL;

	if (SBotVal > 3)
		SBotVal = RANDOM_LONG(1, 3);
	m_sSBotType = SBotVal;

	if (SBotVal > 0)
		m_pSBotSettings = &BotManager.m_SBotSettings[SBotVal];

	m_bKicked = false;

	if (BotManager.IsDBMode())
	{
		if (BotDBType != DB_UNASSIGNED)
			m_eBotDBType = BotDBType;
		else
			BotDBManager.AssignBotRole(this);
	}

	// If there is a host, update the bot info on the client
	if (!IS_DEDICATED_SERVER() && UTIL_IsValidPlayer(BotManager.GetHost(), false, false))
	{
		MESSAGE_BEGIN( MSG_ONE, gmsgBotVGUI, NULL, BotManager.GetHost()->pev);
			WRITE_BYTE( byte(BOTVGUI_BOTSKILL) ); // Update skill for this bot
			WRITE_BYTE( entindex() );
			WRITE_BYTE(m_bSkillNr + 1);
		MESSAGE_END();

		MESSAGE_BEGIN( MSG_ONE, gmsgBotVGUI, NULL, BotManager.GetHost()->pev);
			WRITE_BYTE( byte(BOTVGUI_BOTPERSO) ); // Update personality for this bot
			WRITE_BYTE( entindex() );
			WRITE_STRING(m_pPersonality->perso_name);
		MESSAGE_END();

		if (BotManager.IsDBMode())
		{
			MESSAGE_BEGIN( MSG_ONE, gmsgBotVGUI, NULL, BotManager.GetHost()->pev);
				WRITE_BYTE( byte(BOTVGUI_BOTDBROLE) ); // Update bot db role for this bot
				WRITE_BYTE( entindex() );
				WRITE_BYTE( byte(m_eBotDBType) + 1 );
			MESSAGE_END();
		}
	}
}

void CBot::LetBotJoinTeam()
{
	if ((m_sBotTeam != 1) && (m_sBotTeam != 2)) 
         m_sBotTeam = -1;

    if (m_sBotTeam == -1)
	{
		int CountGood, CountEvil, Index;
		CBasePlayer *pPlayer;
		CountGood = CountEvil = 0;

		for(Index=1;Index<=gpGlobals->maxClients;Index++)
		{
			pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(Index);
			if (UTIL_IsValidPlayer(pPlayer, false, false))
			{
				if (pPlayer->pev->team == GOOD_TEAM)
					CountGood++;
				else if (pPlayer->pev->team == EVIL_TEAM)
					CountEvil++;
			}
		}

		if (CountGood > CountEvil)
			m_sBotTeam = 2;
		else if (CountGood < CountEvil)
			m_sBotTeam = 1;
		else
			m_sBotTeam = RANDOM_LONG(1, 2);
	}

	// Let the bot join the desired team
	g_pGameRules->ClientCommand(this, "jointeam");
	// Let the bot select a class after this
	m_eStartAction = START_CLASS_SELECT;
}

void CBot::LetBotPickAClass()
{
	const char *szDesiredClass = NULL;

	switch(m_sBotClass)
	{
	case 1:
		szDesiredClass = "buu";
		break;
	case 2:
		szDesiredClass = "goku";
		break;
	case 3:
		szDesiredClass = "gohan";
		break;
	case 4:
		szDesiredClass = "krillin";
		break;
	case 5:
		szDesiredClass = "frieza";
		break;
	case 6:
		szDesiredClass = "piccolo";
		break;
	case 7:
		szDesiredClass = "trunks";
		break;
	case 8:
		szDesiredClass = "vegeta";
		break;
	case 9:
		szDesiredClass = "cell";
		break;
	}

	if ((szDesiredClass) && (g_pGameRules->IsValidClass(szDesiredClass, this)))
		ChangeClass(szDesiredClass);
	else // Class wasn't valid or bot should pick a random class.
	{
		TMultiChoice<const char*> BotClassChoice;

		if (g_pGameRules->IsValidClass("buu", this))
			BotClassChoice.Insert("buu");
		if (g_pGameRules->IsValidClass("goku", this))
			BotClassChoice.Insert("goku");
		if (g_pGameRules->IsValidClass("gohan", this))
			BotClassChoice.Insert("gohan");
		if (g_pGameRules->IsValidClass("krillin", this))
			BotClassChoice.Insert("krillin");
		if (g_pGameRules->IsValidClass("frieza", this))
			BotClassChoice.Insert("frieza");
		if (g_pGameRules->IsValidClass("piccolo", this))
			BotClassChoice.Insert("piccolo");
		if (g_pGameRules->IsValidClass("trunks", this))
			BotClassChoice.Insert("trunks");
		if (g_pGameRules->IsValidClass("vegeta", this))
			BotClassChoice.Insert("vegeta");
		if (g_pGameRules->IsValidClass("cell", this))
			BotClassChoice.Insert("cell");

		if ((BotClassChoice.GetSelection(szDesiredClass)) &&
			(g_pGameRules->IsValidClass(szDesiredClass, this)))
			ChangeClass(szDesiredClass);
		else
			cout << "Error: Bot " << STRING(pev->netname) << " couldn't pick a class!\n";
	}

	m_bNotStarted = false; // Bot is now started(so ingame).
	 if (RANDOM_LONG(1, 100) <= 50)
		 ChatClass.LetBotSayAWelcomeSentence(this); // Let him greet

}

void CBot::CheckSBotStuff()
{
	if (pev->renderfx != kRenderFxGlowShell)
		pev->rendermode = kRenderTransTexture;

	// when on low health, fade in - fade out or when transparency_val == -1
	if ((pev->health < 10) || (BotManager.m_SBotSettings[m_sSBotType-1].transparency_val == -1) &&
		(pev->renderfx != kRenderFxGlowShell))
	{
		if (m_sTransparantValue < 20)
			m_sTransparantFactor = 1;
		else if (m_sTransparantValue >= 255)
			m_sTransparantFactor = -1;

		m_sTransparantValue += (3 * m_sTransparantFactor);
	
		if (m_sTransparantValue > 255)
			m_sTransparantValue = 255;
		else if (m_sTransparantValue < 0)
			m_sTransparantValue = 0;

		pev->renderamt = m_sTransparantValue;
	}
	else
	{
		if (pev->renderfx != kRenderFxGlowShell)
			pev->renderamt = BotManager.m_SBotSettings[m_sSBotType-1].transparency_val;
	}

	if (m_fUpdateStatsDelay < gpGlobals->time)
	{
		m_fUpdateStatsDelay = gpGlobals->time + 1.0;
		if (m_iPowerLevel < BotManager.m_SBotSettings[m_sSBotType-1].pl)
		{
			SetPowerLevel(BotManager.m_SBotSettings[m_sSBotType-1].pl);
			SetMaxPowerLevel(BotManager.m_SBotSettings[m_sSBotType-1].pl);
		}
		
		if (GetKi() < 1000)
		{
			if (BotManager.m_SBotSettings[m_sSBotType-1].ki_gain == -1)
				SetKi(1000);
			else
			{
				int NewKiValue = GetKi() + BotManager.m_SBotSettings[m_sSBotType-1].ki_gain;
				if (NewKiValue > 1000)
					NewKiValue = 1000;
				SetKi(NewKiValue);
			}
		}

		if (GetHealth() < m_iMaxHealth)
		{
			if (BotManager.m_SBotSettings[m_sSBotType-1].health_gain == -1)
				SetHealth(m_iMaxHealth);
			else
			{
				short NewHealth = pev->health + BotManager.m_SBotSettings[m_sSBotType-1].health_gain;
				if (NewHealth > m_iMaxHealth)
					NewHealth = m_iMaxHealth;
				SetHealth(NewHealth);
			}
		}
	}

	// Bot is not in team evil?
	if (pev->team != EVIL_TEAM)
	{
		// Let him join team evil
		m_sBotTeam = EVIL_TEAM;
		g_pGameRules->ClientCommand(this, "jointeam");
	}

	char s[10];
	sprintf(s,"[SBot%i]",m_sSBotType);

	char BotName[32] = "";
	sprintf(BotName, STRING(pev->netname));

	bool NotDoubleNamed = true;

	if (strstr(BotName,s) == NULL)
		NotDoubleNamed = false;

    if ((strncmp(s, STRING(pev->netname), 7) != 0) &&
		(NotDoubleNamed == false))
	{
        char *infobuffer;
        int clientIndex;
        infobuffer = g_engfuncs.pfnGetInfoKeyBuffer( edict() );
	    clientIndex = ENTINDEX( edict() );
	    char newname[32];
 	    sprintf(newname, "%s%s", s, STRING(pev->netname));
  
        g_engfuncs.pfnSetClientKeyValue( clientIndex, infobuffer, "name", newname );
    }
}

void CBot::BotPreThink()
{
	pev->flags |= FL_FAKECLIENT; // Mark this player as a bot.
	pev->button = 0; // Reset all the buttons.

	if (m_bMadePlayerList)
		m_bMadePlayerList = false;

	if (m_bPlayerCount)
		m_bPlayerCount = 0;

    if (m_szName[0] == 0)  // name filled in yet?
		strcpy(m_szName, STRING(pev->netname));

	// Bot was killed a few secs ago? and does he know who killed him?
	if (((gpGlobals->time - m_fBotSpawnTime) < 2.5f) && (m_pKiller))
	{
		ChatClass.LetBotWhine(this, m_pKiller);
		m_pKiller = NULL; // So bots won't whine more then once and won't 'use' this player more then once
	}

    // does bot need to say a message and time to say a message?
    if ((m_bBotShouldSay) && (m_fBotSayTime < gpGlobals->time))
    {
		m_bBotShouldSay = false;

	   if (BotManager.ShouldBotsChat())
		   UTIL_HostSay(this, 0, m_szBotSayMsg);
    }

	// Bot is a super bot?
	if (IsSuperBot())
		CheckSBotStuff(); // Handle super bot stuff(cheating...)

	// Deleting the a star lists?
	if (m_bDeletingAStarLists)
	{
		int BotBit = (1<<(entindex() - 1));
		int iMaxCycles = 10 * BotManager.GetMSec(), iCurrentCycles = 0;

		while((m_AStarOpenList.Empty() == false) && (iCurrentCycles < iMaxCycles))
		{
			iCurrentCycles++;
			m_AStarOpenList.Pop()->iOpenIndexes &= ~BotBit;
		}

		while((m_AStarClosedList.Empty() == false) && (iCurrentCycles < iMaxCycles))
		{
			iCurrentCycles++;
			m_AStarClosedList.Pop()->iClosedIndexes &= ~BotBit;
		}

		while((m_AStarGoalOpenList.Empty() == false) && (iCurrentCycles < iMaxCycles))
		{
			iCurrentCycles++;
			m_AStarGoalOpenList.Pop()->iGoalOpenIndexes &= ~BotBit;
		}

		while((m_AStarGoalClosedList.Empty() == false) && (iCurrentCycles < iMaxCycles))
		{
			iCurrentCycles++;
			m_AStarGoalClosedList.Pop()->iGoalClosedIndexes &= ~BotBit;
		}

		// Done resetting?
		if (m_AStarOpenList.Empty() && m_AStarClosedList.Empty() &&
			m_AStarGoalOpenList.Empty() && m_AStarGoalClosedList.Empty())
		{
			m_bDeletingAStarLists = false;
		}
	}

#ifdef _DEBUG
	if (BotManager.m_bDrawWaypointGoals)
	{
		if (m_pCurrentGoalWaypoint)
			UTIL_DrawBeam(BotManager.GetHost(), pev->origin, m_pCurrentGoalWaypoint->v_origin, 10, 1, 0, 0, 255, 250, 10); 
		if (m_pCurrentWaypoint)
			UTIL_DrawBeam(BotManager.GetHost(), pev->origin, m_pCurrentWaypoint->v_origin, 10, 1, 255, 255, 255, 250, 10);
		if (m_pCurrentGoalFloodWP)
			UTIL_DrawBeam(BotManager.GetHost(), pev->origin, m_pCurrentGoalFloodWP->v_origin, 10, 1, 0, 0, 255, 250, 10); 
		if (m_pCurrentFloodWP)
			UTIL_DrawBeam(BotManager.GetHost(), pev->origin, m_pCurrentFloodWP->v_origin, 10, 1, 255, 255, 255, 250, 10);

		if (!m_AStarNodeList.Empty())
		{
			/*modified by harSens
			TLinkedList<flood_wp_s *>::node_s *pNode = m_AStarNodeList.GetFirst();
			*/
			node_s<flood_wp_s *> *pNode = m_AStarNodeList.GetFirst();
			while (pNode && pNode->next)
			{
				UTIL_DrawBeam(BotManager.GetHost(), pNode->Entry->v_origin + Vector(0, 0, 30),
							  pNode->next->Entry->v_origin + Vector(0, 0, 30), 5, 2, 0,
							  255, 0, 250, 10);

				pNode = pNode->next;
			}
		}
	}

#endif
}

void CBot::BotThink()
{
	// If bot is not started yet
	if (m_bNotStarted)
	{
		PrintAI("Selecting team/class...\n");
		if (m_fStartUpDelay <= gpGlobals->time)
		{
			if (g_teamplay && ((pev->team == 0) || ((!m_szTeamName) || (*m_szTeamName == 0))))
			{
				LetBotJoinTeam();
				m_fStartUpDelay = gpGlobals->time + 0.15f;
			}
			else if ((!m_pClass) || (m_pClass->Classify() == PC_BASE))
			{
				LetBotPickAClass();
			}
			else
				m_bNotStarted = false;
		}/*
			switch (m_eStartAction)
			{
			case START_TEAM_SELECT:
				LetBotJoinTeam();
				break;
			case START_CLASS_SELECT:
				LetBotPickAClass();
				break;
			}
			m_fStartUpDelay = gpGlobals->time + 0.15f;
		}*/

		g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, GetMaxSpeed(),
									 0, 0, pev->button, 0,
                                     BotManager.GetMSec() );
        return;
	}

    // if the bot is dead, randomly press fire to respawn...
    if ((pev->health < 1) || (pev->deadflag != DEAD_NO) || (m_fObserverFlag))
    {
		PrintAI("Bot tries to respawn\n");
		//if (m_fObserverFlag)
		//	StopObserving(); // Bots shouldn't observing

		if (RANDOM_LONG(1, 100) > 50)
			pev->button = IN_ATTACK;

		g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, 0.0f,
									 0, 0, pev->button, 0,
                                     BotManager.GetMSec() );
        return;
    }

	if (m_fWishing) // Bot may wish?
	{
		PrintAI("Wishing...\n");
		char *Command;

		switch (RANDOM_LONG(1, 3))
	   	{
		case 1:
			Command = "immortality";
			break;
		case 2:
			Command = "strength";
			break;
		case 3:
			Command = "wishpoint";
			break;
		}

		g_pGameRules->ClientCommand(this, Command); // Make the wish

		g_engfuncs.pfnRunPlayerMove(edict( ), pev->v_angle, 0.0f, 0, 0, pev->button, 0,
									BotManager.GetMSec() );
		return; // Skip this frame
    }
	
	// Bot may select a cookie?
	
	//do we have a candy victim?
	if (m_pCandyVictim)
	{
		//is the candy victim still about to be candified?
		if (m_pCandyVictim->m_fBecomeCandy)
		{
			// Cookie choice suggested by Shad0w
			
			// Low on health?
		    if (pev->health < 80)
				MakeCandy("cookie"); // Take a cookie(recovers all health)
			else if (pev->health < 120) // Did the bot lost a bit health?
				MakeCandy("chocolate"); // Take a choco, recovers a bit health and gives a bit pl
			else
				MakeCandy("donut"); // Take a donut when the bot has fair enough health(gives pl)
		}
	}

    if ((m_fTurboTime < gpGlobals->time) && (m_fTurboTime > 0.0f))
    {
		ToggleTurbo(false); // Turn turbo off
		m_fTurboTime = 0.0f;
    }

    m_fMoveSpeed = GetMaxSpeed();  // set to max speed

	CheckNecessarilyThings(); // Check for things like swoop and powerstruggling
	BotSense(); // Sense the envouriment, mainly danger reaction.
	CheckBotInternalStatus(); // Check for things like powerup
	CheckBotMiscThings(); // Check for things like chat or enemies

    // Still nothing todo ?
    if (m_pCurrentBotTask == NULL)
    {
		// Ok then just fly through the map...
		MakeTask(TaskNormalMovement);
    }

    BotPerformTasks(); // do the job
    BotAim(); // Aim at the desired angle

    if (!BotManager.BotsMove())
    {
		m_fMoveSpeed = m_fStrafeSpeed = m_fUpDownSpeed = 0.0f;
    }
	
	if (pev->movetype == MOVETYPE_PLAYERSWOOP)
	{
		if (BotManager.BotsMove() && m_sSwoopFlags)
		{
			// hack hack...set the right swoop button here otherwise bots won't swoop right
			SetSwoopButton();
		}
		else
			ResetMoveSpeed();
	}

    if (GetMaxSpeed() < m_fStrafeSpeed)
    {
		m_fStrafeSpeed = GetMaxSpeed();
    }

    if (GetMaxSpeed() < m_fMoveSpeed)
    {
		m_fMoveSpeed = GetMaxSpeed();
    }

    if (m_fMoveSpeed > 0.0)
		pev->button |= IN_FORWARD;
	else if (m_fMoveSpeed < 0.0)
		pev->button |= IN_BACK;
	if (m_fStrafeSpeed > 0.0)
	    pev->button |= IN_MOVERIGHT;
    else if (m_fStrafeSpeed < 0.0)
	    pev->button |= IN_MOVELEFT;

    // save the previous forward-move speed.
    m_fPrevSpeed = m_fMoveSpeed;

	pev->maxspeed = GetMaxSpeed(); // Otherwise bots won't be able to move
	g_engfuncs.pfnRunPlayerMove(edict(), pev->v_angle, m_fMoveSpeed, m_fStrafeSpeed,
								m_fUpDownSpeed, pev->button, 0, BotManager.GetMSec());

    // Reset vars
    m_fStrafeSpeed = 0.0;
    m_fUpDownSpeed = 0.0;
    m_lVisiblePlayers = 0;
    m_lValidPlayers = 0;
}

void CBot::Touch(CBaseEntity *pOther)
{
	m_pTouchEnt = pOther;
	CBasePlayer::Touch(pOther);
}

void CBot::Spawn()
{
	CBasePlayer::Spawn();

	pev->flags = FL_CLIENT | FL_FAKECLIENT;

	// Reset most of the bot member vars...

	m_pEntToBlock = NULL;
	m_fSearchForSensuDelay = 0.0f;
	m_pTargetSensuBean = NULL;

	m_fAttackSenseDelay = 0.0f;

	m_sKiGainTarget = 0;

	// Spawn variabeles
    m_fBotSpawnTime = gpGlobals->time; // Time the bot (re)spawned

	m_sCombatNavDir = 0;
	m_fCombatNavDelay = 0.0;

	m_pTouchEnt = NULL;

	m_bSelectCookie = false;
	m_pCandyTarget = NULL;
	m_fCheckCookieTime = 0.0f;

	m_fCheckKameTorpTime = 0.0;
	m_fLastKameTorpLeftOverCheck = 0.0;

	m_pGoalDB = NULL;
	m_fDBCheckDelay = 0.0f;
	m_bSelectWish = false;
	m_eCurrentBotDBState = STATE_NOCURRENTDBSTATE;

	m_fAbsorbHealthTime = 0.0;
	m_fLastSoundPlayedTime = 0.0;

	m_eTaskAfterTask = TaskNone;
	KillCurrentTask();

	m_fWallJumpCheckDelay = 0.0;

	m_fSearchEnemyDelay = 0.0;
	m_fHasAnEnemyTime = 0.0;

	m_sJumpDir = 0;

	memset(m_EnemyList,0,sizeof(m_EnemyList));
	memset(m_pPlayerList,0,sizeof(m_pPlayerList));

	m_bMadePlayerList = false;
	m_bPlayerCount = 0;

	m_sMoveDir = 0;

	m_fCheckUpDownTime = 0.0;

	m_sDangerLevel = 0;
	m_iDangerType = 0;

	m_fAimDelay = 0.0;
	m_vAimVec = WrapAngles(pev->v_angle);
	m_vBodyVec = m_vAimVec;
	m_fYawSpeed = 0.0;
	m_fPitchSpeed = 0.0;

	m_eSwoopType = SWOOP_NORMAL;
	m_vSwoopOffset = g_vecZero;
	m_fAttacksWhileSwoopCheckDelay = 0.0f;
	ClearMeleeComboList();
	m_iComboChargeButton = m_iOldComboChargeButton = 0;
	m_fNextComboChargeButtonTime = 0.0f;
	m_iComboChargeHitButtons = m_iOldComboChargeHitButtons = 0;
	m_sSwoopFlags = 0;

	m_fSwitchChargeButtonDelay = 0.0f;

	m_vBeamJumpSpot = g_vecZero;

	DeleteCombos();

	m_fTeleComboDelay = 0.0;


	m_pHuntPlayer = NULL;
	m_fHuntDelay = 0.0f;
	m_HuntPos.Pos = Vector(0,0,0);
	m_HuntPos.flCreateTime = 0.0;
    m_fPrevHuntDist = 0.0f;

	m_bBotShouldSay = FALSE;
	m_fBotSayTime = 0.0;
	m_szBotSayMsg[0] = 0;
	m_fBotChatTime = gpGlobals->time;
	m_fReplyDelay = gpGlobals->time;
	m_bBotSayKilled = FALSE;
	m_fBotSayKilledTime = 0.0;

	ResetMoveSpeed();
	m_eStrafeDir = MOVE_NONE;

	m_fLastHitTime = 0.0;
	m_iDamageTaken = 0;

	m_fTeleportDelay = 0.0;
	m_iTeleportDir = 0;

	m_fTurboTime = 0.0f;
	m_fBotPowerUpDelay = 0.0;
	v_prev_origin = Vector(9999.0, 9999.0, 9999.0);

	m_fPrevSpeed = 0.0;

	m_fDontAvoidWallTime = 0.0;

	// pick a wander direction (50% of the time to the left, 50% to the right)
	if (RANDOM_LONG(1, 100) <= 50)
		m_eWanderDir = MOVE_LEFT;
	else
		m_eWanderDir = MOVE_RIGHT;

	m_pBotEnemy = NULL;
	
    m_fShootDelay = 0.0f;
    m_fHoldAttackTime = 0.0f;
    m_pCurrentWeaponInfo = NULL;

	m_fPauseCheckTime = 0.0;

	// sbot vars
	m_fUpdateStatsDelay = 0.0f;
	m_sTransparantValue = 255;
	m_sTransparantFactor = -1;
	m_fLastBeamDrawTime = 0.0f;
    m_fAbsorbHealthTime = 0.0f;
    m_fLastSoundPlayedTime = 0.0f;

	
	// waypoint vars
	m_fLookForWaypointTime = 0.0f;
	m_fWaypointTime = 0.0;
	m_pCurrentWaypoint = NULL;
	m_pCurrentGoalWaypoint = NULL;
	m_sCurrentWPIndex = m_sCurrentGoalWPIndex = -1;
	m_iPrevWaypointIndexes[0] = -1;
	m_iPrevWaypointIndexes[1] = -1;
	m_iPrevWaypointIndexes[2] = -1;
	m_iPrevWaypointIndexes[3] = -1;
	m_iPrevWaypointIndexes[4] = -1;
	m_fRandomWaypointTime = gpGlobals->time;
	m_fPrevWaypointDistance = 0.0;
	m_fWaypointHeadLastTurnLessTime = 0.0f;
	m_fWaypointHeadPauseTime = 0.0f;
	// Flood stuff
	m_pCurrentFloodWP = NULL;
	m_pCurrentGoalFloodWP = NULL;
	m_AStarNodeList.DeleteAllNodes();
	m_bCalculatingAStarPath = false;
}

void CBot::BotKilled(entvars_t *pAttacker)
{
	// Store the player who killed this bot(so the bot can whine)
	if (pAttacker)
	{
		CBaseEntity *pEnt = CBaseEntity::Instance(pAttacker);
		if (pEnt->IsPlayer())
			m_pKiller = (CBasePlayer *)pEnt;
		else if (pEnt->pev->owner)
			m_pKiller = (CBasePlayer *)CBaseEntity::Instance(pEnt->pev->owner);
	}
}

void CBot::ToggleTurbo(bool TurnOn)
{
	if (TurnOn)
	{
		if (!m_fTurbo)
			StartTurbo();		
	}
	else
	{
		if (m_fTurbo)
			StopTurbo();		
	}
}

// Fills an array with visible and valid players for the bot
void CBot::MakePlayerList()
{
	byte i;
    short BotPlayerIndex = entindex();

    for (i=0;i<32;i++)
		m_pPlayerList[i] = NULL;

    m_bPlayerCount = 0;

    CBasePlayer *pPlayer;
    CBot *pBot;

    // search the world for players...
    for (i = 1; i <= gpGlobals->maxClients; i++)
    {
		pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(i);

	    // Already checked by a previous bot?
	    if (m_lValidPlayers & (1<<(i-1)))
		{
			if (m_lVisiblePlayers & (1<<(i-1)))
			{
				m_pPlayerList[m_bPlayerCount] = pPlayer;
			    m_bPlayerCount++;
			}
		    continue;
		}

        // skip invalid players and skip self (i.e. this bot)
        if ((this != pPlayer) && (UTIL_IsValidPlayer(pPlayer)))
		{
			bool IsBot = UTIL_IsBot(pPlayer);
			
			if (IsBot)
			{
				pBot = UTIL_GetBotPointer(pPlayer);
				if (pBot)
				{
					// This bot needs to think after this bot, so mark that this bot is valid for him
					// to save some cpu usage.
					pBot->m_lValidPlayers |= (1<<(BotPlayerIndex-1));
				}
			} 

			m_lValidPlayers |= (1<<(i-1));

			// see if bot can see the player...
			if (FVisible(pPlayer->pev->origin))
			{
				m_pPlayerList[m_bPlayerCount] = pPlayer;
				m_bPlayerCount++;
				m_lVisiblePlayers |= (1<<(i-1));

				if (IsBot)
				{
					// This bot needs to think after this bot, so mark that this bot is visible
					// for him to save some cpu usage.
					pBot->m_lVisiblePlayers |= (1<<(BotPlayerIndex-1));
				}
			}
		}
    }

	// We made the list, don't need to do it more then once each frame
	m_bMadePlayerList = true; 
}

bool CBot::BotCanAscend()
{
	//spectators can't transform
	if (FBitSet(pev->flags, FL_SPECTATOR))
		return false;

	//can't ascend if your health is too low
	if (pev->health<PLDROP_HEALTH)
		return false;

	
	//doing something special?
	if (m_fStrangled || m_fBecomeCandy || m_fBecomeCandy || m_fCharging 
		|| m_fPowerUp || m_fTurbo || m_fBlock || m_fHoldDisc || 
		m_fAscending || m_fMelee || m_Melee.m_fPowerhit || m_fShield || 
		!IsAlive() || (m_fControl && !m_fPowerStruggling) || m_fSpawnInvulnerable)
		return false;
	
	//enough ki to ascend?
	if (pev->fuser4<ASCEND_KI_COST)
		return false;
	
	float flTransFromPL = m_pClass->GetTransFormPowerLevel();

	//only transform if powerlevel is high enough
	if (m_iPowerLevel < flTransFromPL)
		return false;

	return true;
}

bool CBot::BotCanPowerUp()
{
	if ((FBitSet(pev->flags, FL_SPECTATOR)) || m_fStrangled ||(m_fBecomeCandy) || (m_fPowerUp) ||
		(m_fCharging) || (m_fControl) || (!IsAlive()) || (m_fObserverFlag) || (m_fHoldDisc) || 
		(m_fAscending) || (m_fMelee) || (m_Melee.m_fPowerhit) || (m_fShield))
		return false;

	return true;
}

CBaseEntity *CBot::SearchSensuBean()
{
	CBaseEntity *pBean = NULL;
	CBaseEntity *pNearestBean = NULL;
	float flDist, flNearestDist = 9999.9f;

	while ((pBean = UTIL_FindEntityByClassname(pBean, "item_sensubeanbag")) != NULL)
	{
		if (IsNull(pBean))
			continue;

		if (pBean->pev->effects & EF_NODRAW)
			continue;

		flDist = GetDistance(pBean);
		if (flDist < flNearestDist)
		{
			if (FVisible(pBean))
			{
				pNearestBean = pBean;
				flNearestDist = flDist;
			}
		}
	}

	while ((pBean = UTIL_FindEntityByClassname(pBean, "bean")) != NULL)
	{
		if (IsNull(pBean))
			continue;

		if (pBean->pev->effects & EF_NODRAW)
			continue;

		flDist = GetDistance(pBean);
		if (flDist < flNearestDist)
		{
			if (FVisible(pBean))
			{
				pNearestBean = pBean;
				flNearestDist = flDist;
			}
		}
	}

	return pNearestBean;
}

void CBot::SetSkill(short NewSkillNr)
{
	if ((NewSkillNr >= 1) && (NewSkillNr <= 5))
	{
		m_bSkillNr = NewSkillNr - 1; // Array based
		m_pBotSkill = &BotManager.m_BotSkills[m_bSkillNr];

		// If there is a host, update the bot info on the client
		if (!IS_DEDICATED_SERVER() && UTIL_IsValidPlayer(BotManager.GetHost(), false, false))
		{
			MESSAGE_BEGIN( MSG_ONE, gmsgBotVGUI, NULL, BotManager.GetHost()->pev);
				WRITE_BYTE( byte(BOTVGUI_BOTSKILL) ); // Update skill for this bot
				WRITE_BYTE( entindex() );
				WRITE_BYTE(m_bSkillNr + 1);
			MESSAGE_END();
		}
	}
}

void CBot::SetPerso(bot_personality_s *pPerso)
{
	if (pPerso)
	{
		m_pPersonality = pPerso;

		// If there is a host, update the bot info on the client
		if (!IS_DEDICATED_SERVER() && UTIL_IsValidPlayer(BotManager.GetHost(), false, false))
		{
			MESSAGE_BEGIN( MSG_ONE, gmsgBotVGUI, NULL, BotManager.GetHost()->pev);
				WRITE_BYTE( byte(BOTVGUI_BOTPERSO) ); // Update personality for this bot
				WRITE_BYTE( entindex() );
				WRITE_STRING(m_pPersonality->perso_name);
			MESSAGE_END();
		}
	}
}

void CBot::SetDBRole(EBotDBType BotDBRole)
{
	m_eBotDBType = BotDBRole;

	// If there is a host, update the bot info on the client
	if (!IS_DEDICATED_SERVER() && UTIL_IsValidPlayer(BotManager.GetHost(), false, false))
	{
		MESSAGE_BEGIN( MSG_ONE, gmsgBotVGUI, NULL, BotManager.GetHost()->pev);
			WRITE_BYTE( byte(BOTVGUI_BOTDBROLE) ); // Update bot db role for this bot
			WRITE_BYTE( entindex() );
			WRITE_BYTE( byte(BotDBRole) + 1 );
		MESSAGE_END();
	}
}

// ==================================================================
// Code of CBot - End
// ==================================================================

// ==================================================================
// Code of CBotManager - Begin
// ==================================================================

CBotManager::CBotManager() : m_bNumberOfBots(0), m_bPrevNumberBots(0), m_fBalanceTeamsDelay(0.0),
							 m_fAddBotDelay(30.0), pHost(NULL), m_sMinBots(-1), m_sMaxBots(-1),
							 m_fRestoreBotsTime(0.0), m_bRestoringBots(false), 
							 m_bSpawnTimeReset(false), m_fOldPLCatchupValue(0.0), m_bSBotCount(0),
							 m_fSBotRulesDelay(0.0), m_fBotCFGPauseTime(0.0), m_pBotCFGFile(NULL),
							 m_bCheckNumBotsCVar(true), m_fCheckSkillCVarDelay(0.0),
							 m_sPrevSkillCVarVal(1), m_bNeedToReadCFGFile(true),
							 m_sBotNameCount(0), m_iMSecNum(0), m_fMSecDel(0.0), m_fMSecVal(0.0f),
							 m_fPrevPlayermoveTime(0.0f), m_fFrameTime(0.0f),
							 m_bFoundBotCommand(false),	 m_bHPCheat(false), 
							 m_bDBMode(false), m_bSendPersoInfoToClient(false),
							 m_bSendDBInfoToClient(true), m_bDrawWaypointGoals(false)
{
	m_pKickedBots = new TLinkedList<kicked_bot_s*>;
	m_szDefaultPersonality[0] = 0;
}

CBotManager::~CBotManager()
{
	if (m_pKickedBots)
	{
		while (m_pKickedBots->Empty() == false)
			delete m_pKickedBots->Pop();

		delete m_pKickedBots;
	}

	while(m_BotPersonalities.Empty() == false)
		delete m_BotPersonalities.Pop();

	DeleteBotsToAddList();
}

void CBotManager::Think()
{
	CalcMSec(); // Calculate the msec val for the bots
	BotFillThink();
	BotBalanceTeamsThink();
	SBotRulesThink();

	if (IsDBMode())
		BotDBManager.Think();

    if (PrintingInGameHelp)
		ShowInGameBotHelpWindow();

	// Busy with processing the bot config file?
	if ((m_pBotCFGFile) && (m_fBotCFGPauseTime >= 1.0) && (m_fBotCFGPauseTime <= gpGlobals->time))
		BotCFGFileThink();

	WaypointClass.Think();

	// Should we send the bot personality names to the client?
	if (m_bSendPersoInfoToClient && UTIL_IsValidPlayer(GetHost(), false, false))
	{
		SendPersoInfoToClient();
		m_bSendPersoInfoToClient = false;
	}

	// Is the bot skill cvar changed?
	if (m_sPrevSkillCVarVal != sv_bot_skill.value)
	{
		ChangeBotSkill(sv_bot_skill.value + 1);
		m_sPrevSkillCVarVal = sv_bot_skill.value;
	}

	if (g_pGameRules && m_bSendDBInfoToClient)
	{
		if (IS_DEDICATED_SERVER())
			m_bSendDBInfoToClient = false;
		else
		{
			if (UTIL_IsValidPlayer(GetHost(), false, false))
			{
				m_bSendDBInfoToClient = false;
				MESSAGE_BEGIN( MSG_ONE, gmsgBotVGUI, NULL, &pHost->v);
					WRITE_BYTE( byte(BOTVGUI_DBMODE) ); // Update db mode
					WRITE_BYTE( IsDBMode() );
				MESSAGE_END();
			}
		}
	}

	byte b;
	// Loop through all players and let every bot 'think'
    for (b = 1; b <= gpGlobals->maxClients; b++)
    {
		CBasePlayer *pPlayer;

        pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(b);

		// if invalid then continue with next index...
        if (!UTIL_IsValidPlayer(pPlayer, false, false))
			continue;

#ifdef _DEBUG
		if (m_bHPCheat)
		{
			// For handy testing...
			pPlayer->pev->health = pPlayer->m_iMaxHealth;
			pPlayer->pev->fuser4 = 1000;
		}
#endif

        // check if this is a bot
        if (UTIL_IsBot(pPlayer))
		{
			CBot *pBot = (CBot *)pPlayer;

            // call the think functions for this bot...
			pBot->BotPreThink();
            pBot->BotThink();
		}
    }

#ifdef _DEBUG
/*
	if (UTIL_IsValidPlayer(GetHost()))
	{
		CBaseEntity *pEnt = NULL;
		while((pEnt = UTIL_FindEntityByClassname(pEnt, "trigger_teleport")) != NULL)
		{
			UTIL_DrawBeam(GetHost(), GetHost()->pev->origin, VecBModelOrigin(pEnt->pev), 10, 1, 255, 0, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x + pEnt->pev->size.x, pEnt->pev->absmin.y, pEnt->pev->absmin.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x, pEnt->pev->absmin.y + pEnt->pev->size.y, pEnt->pev->absmin.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x, pEnt->pev->absmin.y, pEnt->pev->absmin.z + pEnt->pev->size.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x, pEnt->pev->absmin.y + pEnt->pev->size.y, pEnt->pev->absmin.z + pEnt->pev->size.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x + pEnt->pev->size.x, pEnt->pev->absmin.y, pEnt->pev->absmin.z + pEnt->pev->size.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x + pEnt->pev->size.x, pEnt->pev->absmin.y + pEnt->pev->size.y, pEnt->pev->absmin.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x + pEnt->pev->size.x, pEnt->pev->absmin.y + pEnt->pev->size.y, pEnt->pev->absmin.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x + pEnt->pev->size.x, pEnt->pev->absmin.y, pEnt->pev->absmin.z + pEnt->pev->size.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x + pEnt->pev->size.x, pEnt->pev->absmin.y + pEnt->pev->size.y, pEnt->pev->absmin.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x, pEnt->pev->absmin.y + pEnt->pev->size.y, pEnt->pev->absmin.z + pEnt->pev->size.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x + pEnt->pev->size.x, pEnt->pev->absmin.y, pEnt->pev->absmin.z + pEnt->pev->size.z), 10, 1, 0, 255, 0, 250, 10);
			UTIL_DrawBeam (GetHost(), GetHost()->pev->origin, Vector (pEnt->pev->absmin.x, pEnt->pev->absmin.y + pEnt->pev->size.y, pEnt->pev->absmin.z + pEnt->pev->size.z), 10, 1, 0, 255, 0, 250, 10);
		}
	}*/

	extern bool DisplayBotStuff;
	if (DisplayBotStuff)
		DisplayDebugTexts();

    // Variabeles to prevent overflows....
    bool DisplayNextOne = true;
    short lcoutLength = strlen(lcout.TextToDisplay);
    short rcoutLength = strlen(rcout.TextToDisplay);

    if (strlen(lcout.TextToDisplay) > 500)
		DisplayNextOne = false;

    lcout.Display();

    if (DisplayNextOne)
    {
		if ((lcoutLength + rcoutLength) > 500)
			DisplayNextOne = false;

	    rcout.Display();
    }

    if (DisplayNextOne)
		mcout.Display();
#endif
}

void CBotManager::SBotRulesThink()
{
	if (m_fSBotRulesDelay > gpGlobals->time)
		return;

	if (AreSBotsIngame() == false)
		return; // There are no superbots ingame so return

	m_fSBotRulesDelay = gpGlobals->time + 0.75; // No need to do this every frame

	// Check if mp_plcathcup is off, we don't want that normal player can beat superbots :)
	if (CVAR_GET_FLOAT("mp_plcatchup") > 0.0)
		CVAR_SET_FLOAT("mp_plcatchup", 0.0);
}

void CBotManager::LoadBotNames()
{
	FILE *bot_name_fp;
    char bot_name_filename[256];
    int str_index;
    char name_buffer[80];
    int length, index;

    BuildBotFileName(bot_name_filename, "bot_names.txt", NULL);

    bot_name_fp = fopen(bot_name_filename, "r");

    (*g_engfuncs.pfnServerPrint) ("Reading bot_names file... ");
    if (bot_name_fp != NULL)
    {
		while ((m_sBotNameCount < MAX_BOT_NAMES) && (fgets(name_buffer, 80, bot_name_fp) != NULL))
		{
			length = strlen(name_buffer);

			if (name_buffer[length-1] == '\n')
			{
				name_buffer[length-1] = 0;  // remove '\n'
				length--;
			}

			str_index = 0;
			while (str_index < length)
			{
				if ((name_buffer[str_index] < ' ') || (name_buffer[str_index] > '~') ||
					(name_buffer[str_index] == '"'))
				for (index=str_index; index < length; index++)
					name_buffer[index] = name_buffer[index+1];
 
				str_index++;
			}

			if (name_buffer[0] != 0)
			{
				strncpy(m_BotNames[m_sBotNameCount], name_buffer, BOT_NAME_LEN);

				m_sBotNameCount++;
			}
		}
		fclose(bot_name_fp);
		(*g_engfuncs.pfnServerPrint) ("done\n");
    }
    else
		(*g_engfuncs.pfnServerPrint) ("file not found\n");
}

void CBotManager::PickBotName(char *szOutput)
{
	int name_index, index;
    bool used;
    int attempts = 0;
/*
    if (RANDOM_LONG(1, 100) <= 50)
    {
		// see if a name exists from a kicked bot (if so, reuse it)
		TLinkedList<kicked_bot_s*>::node_s *pNode = m_pKickedBots->GetFirst();

	    while(pNode)
		{
			if (pNode->Entry->szBotName[0])
			{
				strcpy(szOutput, pNode->Entry->szBotName);
			    return;
			}
			pNode = pNode->next;   
		}
    }
*/
    name_index = RANDOM_LONG(1, m_sBotNameCount) - 1;  // zero based

    // check make sure this name isn't used
    used = TRUE;

    while (used)
    {
		used = FALSE;

        for (index = 1; index <= gpGlobals->maxClients; index++)
		{
			CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(index);

			// if invalid then continue with next index...
		    if (!UTIL_IsValidPlayer(pPlayer, false, false))
				continue;

			if (strcmp(m_BotNames[name_index], STRING(pPlayer->pev->netname)) == 0)
			{
				used = TRUE;
				break;
			}
		}
		 
        if (used)
		{
			name_index++;

			if (name_index == m_sBotNameCount)
				name_index = 0;

			attempts++;

			if (attempts == m_sBotNameCount)
				used = FALSE;  // break out of loop even if already used
		}
    }

	strcpy(szOutput, m_BotNames[name_index]);
}

void CBotManager::ChangeBotBoolSetting(bool &Setting, short Choice, const char *VarName)
{
	if (Choice == 1)
		Setting = true;
	else if (Choice == 0)
		Setting = false;
	else if (Choice == -1)
		Setting ^= 1; // Toggle it

	if (Setting)
		cout << VarName << " is now ENABLED\n";
	else
		cout << VarName << " is now DISABLED\n";
}

void CBotManager::ChangePersoPercentSetting(short bot_personality_s::*Setting, short Choice, const char *VarName)
{
	if (Choice == -1)
		Choice = RANDOM_LONG(0, 100); // -1 means take a random percent

	if ((Choice < 0) || (Choice > 100))
	{
		cout << "Wrong value! Should be between -1 and 100\n";
		return;
	}

	/*modified by harSens
	TLinkedList<bot_personality_s *>::node_s *pNode = m_BotPersonalities.GetFirst();
	*/
	node_s <bot_personality_s *>*pNode = m_BotPersonalities.GetFirst();

	for(;pNode;pNode=pNode->next)
		pNode->Entry->*Setting = Choice;

	cout << "The personality setting " << VarName << " is now for each personality " << Choice <<
			"\n";
}

void CBotManager::ChangeBotSkill(short Skill)
{
	if (Skill == -1) // -1 means take a random value
		Skill = RANDOM_LONG(1, 5);

	if ((Skill < 1) || (Skill > 5))
	{
		cout << "Invalid botskill ! Choose a number between 1(best) and 5(worst)\n";
		return;
	}

	byte b;
	// Change every bot skill to Skill
    for (b = 1; b <= gpGlobals->maxClients; b++)
    {
		CBasePlayer *pPlayer;

        pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(b);

		// if invalid then continue with next index...
        if (!UTIL_IsValidPlayer(pPlayer, false, false))
			continue;

        // check if this is a bot
        if (UTIL_IsBot(pPlayer))
		{
			CBot *pBot = UTIL_GetBotPointer(pPlayer);
			if ((!pBot) || (pBot->IsSuperBot()))
				continue;

            pBot->SetSkill(Skill);
		}
    }

	if (CVAR_GET_FLOAT("sv_botskill") != (Skill - 1))
		CVAR_SET_FLOAT("sv_botskill", Skill - 1);

	char szSkillName[10] = "";
	switch(Skill)
	{
	case 1:
		strcpy(szSkillName, "Best");
		break;
	case 2:
		strcpy(szSkillName, "Good");
		break;
	case 3:
		strcpy(szSkillName, "Medium");
		break;
	case 4:
		strcpy(szSkillName, "Weak");
		break;
	case 5:
		strcpy(szSkillName, "Bad");
		break;
	}
	cout << "EVERY bot has now the skill " << szSkillName << "\n";
}

void CBotManager::ChangeAggressionLevel(EAggressionLevel AggrLevel)
{
	/*modified by harSens
	TLinkedList<bot_personality_s *>::node_s *pNode = m_BotPersonalities.GetFirst();
	*/
	node_s<bot_personality_s *> *pNode = m_BotPersonalities.GetFirst();

	for(;pNode;pNode=pNode->next)
		pNode->Entry->aggression_level = AggrLevel;

	char szAggrLevel[25] = "";
	switch(AggrLevel)
	{
	case Defensive:
		strcpy(szAggrLevel, "Defensive");
		break;
	case Normal:
		strcpy(szAggrLevel, "Normal");
		break;
	case Aggressive:
		strcpy(szAggrLevel, "Aggressive");
		break;
	}	
	cout << "The personality setting aggression level is now for each personality " << 
			szAggrLevel << "\n";
}

void CBotManager::ChangeSBotLaserPercent(short Percent)
{
	if (Percent == -1)
		Percent = RANDOM_LONG(1, 100); // -1 means random
	else if ((Percent < -1) && (Percent > 100))
	{
		cout << "Wrong percentage!\n";
		return;
	}

	byte b;
	for (b=0;b<3;b++)
		m_SBotSettings[b].laser_percent = Percent;

	cout << "Super bot laser percent for EVERY superbot is now: " << Percent << "\n";
}

void CBotManager::ChangeBotFlyPercent(short Percent)
{
	if (Percent == -1)
		Percent = RANDOM_LONG(0, 100); // -1 means take a random percent

	if ((Percent < 0) || (Percent > 100))
	{
		cout << "Wrong value! Should be between -1 and 100\n";
		return;
	}
	/*modified by harSens
	TLinkedList<bot_personality_s *>::node_s *pNode = m_BotPersonalities.GetFirst();
	*/
	node_s<bot_personality_s *> *pNode = m_BotPersonalities.GetFirst();
	for(;pNode;pNode=pNode->next)
		pNode->Entry->fly_percent = Percent;


	// Notfiy all bots to update their fly status
	for(int i=1;i<=gpGlobals->maxClients;i++)
	{
		CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(i);

		if (UTIL_IsValidPlayer(pPlayer, false, false) == false)
			continue;

		if (UTIL_IsBot(pPlayer))
		{
			CBot *pBot = UTIL_GetBotPointer(pPlayer);
			pBot->m_fCheckFlyDelay = 0.0f;
		}
	}

	cout << "The personality setting fly_percent is now for each personality " << Percent << "\n";
}

void CBotManager::BotFill(byte Count, short Team, short Class, short Skill, const char *szPerso,
						  EBotDBType BotDBType)
{
	for (short s=0;s<Count;s++)
		m_BotsToAddList.AddNode(new bot_fill_data_s(Skill, Team, Class, szPerso, BotDBType));
}

void CBotManager::SBotFill(byte Count, short Class, const char *szPerso, short Type,
						   EBotDBType BotDBType)
{
	for (short s=0;s<Count;s++)
		m_BotsToAddList.AddNode(new bot_fill_data_s(-1, -1, Class, szPerso, BotDBType, Type));
}

void CBotManager::DeleteBotsToAddList()
{
	while(!m_BotsToAddList.Empty())
		delete m_BotsToAddList.Pop();
}

void CBotManager::CreateBot(short Team, short Class, const char *Name, short Skill,
							const char *szPerso, EBotDBType BotDBType)
{
	edict_t *BotEnt;
	char c_name[BOT_NAME_LEN+1];
	int i, j, length;

	if ((Name != NULL) && (*Name != 0))
    {
		strncpy(c_name, Name, BOT_NAME_LEN-1);
		c_name[BOT_NAME_LEN] = 0;  // make sure c_name is null terminated
    }
	else
	{
		if (m_sBotNameCount > 0)
			BotManager.PickBotName(c_name);
		else
			strcpy(c_name, "Bot");
    }

	if ((Skill < 1) || (Skill > 5))
		Skill = sv_bot_skill.value + 1;

    length = strlen(c_name);

	// remove any illegal characters from name...
    for (i = 0; i < length; i++)
    {
		if ((c_name[i] < ' ') || (c_name[i] > '~') || (c_name[i] == '"'))
		{
			for (j = i; j < length; j++)  // shuffle chars left (and null)
				c_name[j] = c_name[j+1];
			length--;
		}               
    }

    BotEnt = CREATE_FAKE_CLIENT( c_name );

    if (FNullEnt( BotEnt ))
    {
		ALERT(at_console, "Max. Players reached.  Can't create bot!\n");
	}
    else
    {
		char ptr[128];  // allocate space for message from ClientConnect
		
		ALERT(at_console, "Creating bot...\n");

		FREE_PRIVATE(BotEnt); // IMPORTANT: Fix the kick bot problem 
		BotEnt->pvPrivateData = NULL;

		CBot *pBot = GetClassPtr( (CBot *) VARS(BotEnt) );
		if (!pBot)
		{
			ALERT(at_console, "Error ! Couldn't create bot\n");
			return;
		}

		BotEnt->v.frags = 0;

		// Connect the bot to the server
		ClientConnect( BotEnt, c_name, "127.0.0.1", ptr );

		// Pieter van Dijk - use instead of DispatchSpawn() - Hip Hip Hurray!
		ClientPutInServer( BotEnt );

		// initialize all the variables for this bot...
		pBot->CreateInit(Team, Class, Skill, szPerso, -1, BotDBType);

		m_bNumberOfBots++; // We created another bot :)
		m_fAddBotDelay = gpGlobals->time + 5.0;
	}   
}

void CBotManager::CreateSuperBot(short Class, const char *Name, const char *szPerso,
								 short SBotType, EBotDBType BotDBType)
{
	edict_t *BotEnt;
	char szTemp[100];
	char c_name[BOT_NAME_LEN+1];
	int i, j, length;

	if (SBotType < 1)
		SBotType = 1;
	else if (SBotType > 3)
		SBotType = 3;

	if ((Name != NULL) && (*Name != 0))
    {
		strncpy(szTemp, Name, BOT_NAME_LEN-1);
		szTemp[BOT_NAME_LEN] = 0;  // make sure its null terminated
    }
	else
	{
		if (m_sBotNameCount > 0)
			BotManager.PickBotName(szTemp);
		else
			strcpy(szTemp, "Bot");
    }

	sprintf(c_name, "[SBot%i]", SBotType);
	strncat(c_name, szTemp, ((BOT_NAME_LEN-1) - strlen(c_name)));

    length = strlen(c_name);

	// remove any illegal characters from name...
    for (i = 0; i < length; i++)
    {
		if ((c_name[i] < ' ') || (c_name[i] > '~') || (c_name[i] == '"'))
		{
			for (j = i; j < length; j++)  // shuffle chars left (and null)
				c_name[j] = c_name[j+1];
			length--;
		}               
    }

    BotEnt = CREATE_FAKE_CLIENT( c_name );

    if (FNullEnt( BotEnt ))
    {
		ALERT(at_console, "Max. Players reached.  Can't create bot!\n");
	}
    else
    {
		char ptr[128];  // allocate space for message from ClientConnect
		
		ALERT(at_console, "Creating bot...\n");

		FREE_PRIVATE(BotEnt); // IMPORTANT: Fix the kick bot problem 
		BotEnt->pvPrivateData = NULL;
		BotEnt->v.frags = 0;

		CBot *pBot = GetClassPtr( (CBot *) VARS(BotEnt) );
		if (!pBot)
		{
			ALERT(at_console, "Error ! Couldn't create bot\n");
			return;
		}

		// Connect the bot to the server
		ClientConnect( BotEnt, c_name, "127.0.0.1", ptr );

		// Pieter van Dijk - use instead of DispatchSpawn() - Hip Hip Hurray!
		ClientPutInServer( BotEnt );

		// initialize all the variables for this bot...
		pBot->CreateInit(2, Class, 1, szPerso, SBotType, BotDBType);

		m_bNumberOfBots++; // We created another bot :)
		m_fAddBotDelay = gpGlobals->time + 5.0;

		if (m_bSBotCount == 0)
		{
			StoreOldPLCatchupValue();
			CVAR_SET_FLOAT("mp_plcathcup", 0.0);
		}

		m_bSBotCount++;
	}   
}

void CBotManager::CalcMSec()
{
	// Leon Hartwig method to calculate the msec value....
    m_fMSecVal = (int) ((gpGlobals->time - m_fPrevPlayermoveTime) * 1000);
    if (m_fMSecVal > 255)
		m_fMSecVal = 255;
   
    m_fFrameTime = gpGlobals->time - m_fPrevPlayermoveTime;  // calculate frame time
    m_fPrevPlayermoveTime = gpGlobals->time; // save date at which the last RunPlayerMove call happened
}

void CBotManager::KickBots(short Team)
{
	// If not teamplay kick all bots
	if (!g_teamplay)
		Team = -1;

	byte b;
    for (b = 1; b <= gpGlobals->maxClients; b++)
    {
		CBasePlayer *pPlayer;

        pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(b);

		// if invalid then continue with next index...
        if (!UTIL_IsValidPlayer(pPlayer, false, false))
			continue;

        // check if this is a bot
        if (UTIL_IsBot(pPlayer))
		{
			CBot *pBot = UTIL_GetBotPointer(pPlayer);
			if (!pBot)
				continue;

            // Do we got a team match or do we want to kick allbots?
			if ((pBot->pev->team == Team) || (Team == -1))
			{
				pBot->m_bKicked = true; // Set this so the bot won't come back after a map change

				char cmd[64];			 		  
				sprintf(cmd, "kick \"%s\"\n", STRING(pBot->pev->netname));
				SERVER_COMMAND(cmd);  // kick the bot using (kick "name")
			}
		}
    }

	m_bCheckNumBotsCVar = false;
	DeleteBotsToAddList();

	switch(Team)
	{
	case GOOD_TEAM:
		cout << "All bots removed in team good\n";
		break;
	case EVIL_TEAM:
		cout << "All bots removed in team evil\n";
		break;
	default:
		cout << "All bots removed\n";
		break;
	}
}

void CBotManager::KickLastBot(bool DisplayMsg)
{
	byte b;
    for (b = gpGlobals->maxClients; b >= 1; b--)
    {
		CBasePlayer *pPlayer;

        pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(b);

		// if invalid then continue with next index...
        if (!UTIL_IsValidPlayer(pPlayer, false, false))
			continue;

        // check if this is a bot
        if (UTIL_IsBot(pPlayer))
		{
			CBot *pBot = UTIL_GetBotPointer(pPlayer);
			if (!pBot)
				continue;

			pBot->m_bKicked = true; // Set this so the bot won't come back after a map change

			char cmd[64];			 		  
			sprintf(cmd, "kick \"%s\"\n", STRING(pBot->pev->netname));
			SERVER_COMMAND(cmd);  // kick the bot using (kick "name")
			
			break;
		}
    }
	
	m_bCheckNumBotsCVar = false;
	DeleteBotsToAddList();

	if (DisplayMsg)
		cout << "Last bot kicked\n";
}

void CBotManager::KickBot(const char *BotName)
{
	CBasePlayer *pPlayer = FindPlayerByName(BotName);

	if (pPlayer && UTIL_IsBot(pPlayer))
	{
		CBot *pBot = UTIL_GetBotPointer(pPlayer);
		pBot->m_bKicked = true; // Set this so the bot won't come back after a map change
		SERVER_COMMAND(UTIL_VarArgs("kick \"%s\"\n", STRING(pBot->pev->netname)));
	}
}

// Restore bots after a map change
void CBotManager::RestoreBots()
{
	// are we currently respawning bots and is it time to spawn one yet?
    if ((m_fRestoreBotsTime >= 1.0) && (m_fRestoreBotsTime <= gpGlobals->time))
    {
        if (m_pKickedBots->Empty() == false)
		{
			if (!m_bRestoringBots)
				m_bRestoringBots = true;

			kicked_bot_s *pKickedBot = m_pKickedBots->Pop();

			if (pKickedBot->SBotType > 0)
				CreateSuperBot(pKickedBot->BotClass, pKickedBot->szBotName,
							   pKickedBot->szPersonality, pKickedBot->SBotType,
							   pKickedBot->BotDBType);
			else
				CreateBot(pKickedBot->BotTeam, pKickedBot->BotClass, pKickedBot->szBotName, 
						  pKickedBot->Skill + 1, pKickedBot->szPersonality,
						  pKickedBot->BotDBType);
            
            m_fRestoreBotsTime = gpGlobals->time + 2.0;  // set next respawn time
			delete pKickedBot; // Clear this one because its respawned...
		}
        else
        {
            m_fRestoreBotsTime = 0.0;
			m_bRestoringBots = false;
		}
	}
	else if (m_bRestoringBots)
		m_bRestoringBots = false;
}

void CBotManager::SetMinBots(short MinBots)
{
	if ((MinBots >= -1) && (MinBots <= 31))
		m_sMinBots = MinBots;
	else
		cout << "Invalid min_bots argument, should be between -1 and 31\n";
}

void CBotManager::SetMaxBots(short MaxBots)
{
	if ((MaxBots >= -1) && (MaxBots <= 31))
		m_sMaxBots = MaxBots;
	else
		cout << "Invalid max_bots argument, should be between -1 and 31\n";
}

void CBotManager::BotFillThink()
{
	if ((gpGlobals->time < 15.0f) || (!g_pGameRules))
		return; // First let the server start

	if (!IS_DEDICATED_SERVER() && !m_bSpawnTimeReset)
	{
		CBasePlayer *pHost = GetHost();
		if (UTIL_IsValidPlayer(pHost, true, false))
		{
			m_bSpawnTimeReset = true;

			if (m_fRestoreBotsTime >= 1.0)
				m_fRestoreBotsTime = min(m_fRestoreBotsTime, gpGlobals->time + (float)1.0);

            if (m_fBotCFGPauseTime >= 1.0)
				m_fBotCFGPauseTime = min(m_fBotCFGPauseTime, gpGlobals->time + (float)1.0);

            if (m_fAddBotDelay >= 1.0)
				m_fAddBotDelay = min(m_fAddBotDelay, gpGlobals->time + (float)1.0);
		}
	}

	RestoreBots();

	if (m_bRestoringBots)
		return; // if restoring bots don't go any further

	if (m_fAddBotDelay > gpGlobals->time)
		return; // Wait with adding bots

	if (m_bCheckNumBotsCVar)
	{
		if (m_bNumberOfPlayers < (gpGlobals->maxClients - 1))
		{
			float NumBotsCVarVal = CVAR_GET_FLOAT("sv_numbots");

			if (NumBotsCVarVal > m_bNumberOfBots)
			{
				CreateBot(-1, -1, NULL, -1, NULL, DB_UNASSIGNED);
				return;
			}
			else
				m_bCheckNumBotsCVar = false;
		}
		else
			m_bCheckNumBotsCVar = false;
	}

	// Are we filling the server?
	if ((m_BotsToAddList.Empty() == false) && (m_bNumberOfPlayers < gpGlobals->maxClients))
	{
		bot_fill_data_s *pNode = m_BotsToAddList.Pop();

		// Add a superbot?
		if (pNode->SBotType > 0)
			CreateSuperBot(pNode->Class, NULL, pNode->szPersonality, pNode->SBotType,
						   pNode->BotDBType);
		else
			CreateBot(pNode->Team, pNode->Class, NULL, pNode->Skill, pNode->szPersonality,
					  pNode->BotDBType);

		delete pNode;
	}

    // if there are currently less than the maximum number of "players"
    // then add another bot using the default skill level...
    if ((m_bNumberOfPlayers < m_sMaxBots) && (m_sMaxBots != -1))
	{
		CreateBot(-1, -1, NULL, -1, NULL, DB_UNASSIGNED);
	}
}

// Gets called in the engine func ClientConnect()
void CBotManager::PlayerConnect(const char *pszAddress)
{
	m_bNumberOfPlayers++;

	// Is this NOT a bot joining the server?
	if (strcmp(pszAddress, "127.0.0.1") != 0)
	{
		m_fAddBotDelay = gpGlobals->time + 30.0f; // Give the client time to join

		// if there are currently more than the minimum number of bots running
		// then kick one of the bots off the server...
		if ((m_bNumberOfPlayers > m_sMinBots) && (m_sMinBots != -1))
		{
			KickLastBot(false);
		}
	}
}

void CBotManager::DisconnectPlayer(edict_t *pEntity)
{
	m_bNumberOfPlayers--;

	CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pEntity );

	if (!pPlayer || !UTIL_IsBot(pPlayer))
		return;

	CBot *pBot = (CBot *)(pPlayer);

	if (!pBot)
		return;

	if (IsDBMode())
		BotDBManager.BotDisconnect(pBot);

	m_bNumberOfBots--;

	if (pBot->m_sSBotType > 0) // Is it a super bot?
	{
		m_bSBotCount--;

		// All are superbots now kicked?
		if (m_bSBotCount < 1)
		{
			RestoreOldPLCatchupValue();
		}
	}

	// Not kicked by the host?
	if (!pBot->m_bKicked)
	{
		short PersoNr = -1;

		// Store this bot so we can add him after a mapchange
		kicked_bot_s *pKickedBot = new kicked_bot_s(pBot->m_bSkillNr, pBot->m_pPersonality->perso_name, 
												    pBot->m_sBotClass, pBot->m_sBotTeam,
													pBot->m_szName, pBot->m_sSBotType,
													pBot->m_pLastEnemy, pBot->m_eBotDBType);
		m_pKickedBots->AddNode(pKickedBot);
	}

	// Free dynamically allocated stuff
	pBot->DeleteCombos();
	pBot->KillCurrentTask();
	pBot->m_MeleeDefendButtons.DeleteAllNodes();
	pBot->ClearMeleeComboList();
	pBot->m_AStarNodeList.DeleteAllNodes();
	pBot->m_UnreachableNodes.DeleteAllNodes();

	// Free A* lists
	int BotBit = (1 << (pBot->entindex() - 1));
	while (pBot->m_AStarOpenList.Empty() == false)
		pBot->m_AStarOpenList.Pop()->iOpenIndexes &= ~BotBit;	

	while (pBot->m_AStarClosedList.Empty() == false)
		pBot->m_AStarClosedList.Pop()->iClosedIndexes &= ~BotBit;

	while (pBot->m_AStarGoalOpenList.Empty() == false)
		pBot->m_AStarGoalOpenList.Pop()->iGoalOpenIndexes &= ~BotBit;	

	while (pBot->m_AStarGoalClosedList.Empty() == false)
		pBot->m_AStarGoalClosedList.Pop()->iGoalClosedIndexes &= ~BotBit;
}

void CBotManager::BotBalanceTeamsThink()
{
	if (m_fBalanceTeamsDelay < gpGlobals->time)
	{
		m_fBalanceTeamsDelay = gpGlobals->time + 5.0;

		// Check if bots should balance teams?
		if ((BotsBalanceTeams()) && (m_bNumberOfBots > 0) && (g_teamplay))
		{
			CBasePlayer *pPlayer;
			byte b;
			byte TeamGoodCount = 0;
			byte TeamEvilCount = 0;

			// Count all players on team good and team evil
			for(b=1;b<=gpGlobals->maxClients;b++)
			{
				pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(b);

				if (UTIL_IsValidPlayer(pPlayer, true, false) == false)
					continue;

				if (pPlayer->pev->team == GOOD_TEAM)
					TeamGoodCount++;
			    if (pPlayer->pev->team == EVIL_TEAM)
					TeamEvilCount++;
			}

			CBot *pBot;

			if ((TeamGoodCount - 1) > TeamEvilCount)
			{
				for(b=1;b<=gpGlobals->maxClients;b++)
				{
					pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(b);

					if (UTIL_IsValidPlayer(pPlayer, false, false) == false)
						continue;

					// Check if this is a bot
					if (!UTIL_IsBot(pPlayer))
						continue;

					pBot = UTIL_GetBotPointer(pPlayer);
					if (!pBot)
						continue; // Just in case...

					if (pBot->pev->team == GOOD_TEAM) // is this bot in team good?
					{
						// let the bot join team evil
						pBot->m_sBotTeam = EVIL_TEAM;
						g_pGameRules->ClientCommand(pBot, "jointeam");

						TeamGoodCount--; 
						if (TeamGoodCount <= TeamEvilCount)
							break;
					}
				}
			}
			else if ((TeamEvilCount - 1) > TeamGoodCount)
			{
				for(b=1;b<=gpGlobals->maxClients;b++)
				{
					pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(b);

					if (UTIL_IsValidPlayer(pPlayer, false, false) == false)
						continue;

					// Check if this is a bot
					if (!UTIL_IsBot(pPlayer))
						continue;

					pBot = UTIL_GetBotPointer(pPlayer);
					if (!pBot)
						continue; // Just in case...

					if (pBot->pev->team == EVIL_TEAM) // is this bot in team evil?
					{
						// let the bot join team good
						pBot->m_sBotTeam = GOOD_TEAM;
						g_pGameRules->ClientCommand(pBot, "jointeam");

						TeamEvilCount--; 
						if (TeamEvilCount <= TeamEvilCount)
							break;
					}
				}
			}
		}
	}
}

// Make random super bot settings or read them out of the file
// 'sbot.cfg'
void CBotManager::CreateSuperbotSettings()
{
	char SBotFileName[256] = "";
	FILE *SBotFile = NULL;
	int SBotNr = -1;
    int value = 0;

	memset(BotManager.m_SBotSettings,0,sizeof(BotManager.m_SBotSettings));

	BuildBotFileName(SBotFileName, "sbot.cfg", NULL);

	SBotFile = fopen(SBotFileName, "r");

	// make random sbot settings, in case the file doesnt exist or has 
	// missing settings
	for (SBotNr = 0;SBotNr < 3;SBotNr++)
	{
		BotManager.m_SBotSettings[SBotNr].ki_gain = RANDOM_LONG(-1, 5);
		BotManager.m_SBotSettings[SBotNr].health_gain = RANDOM_LONG(-1, 5);
		BotManager.m_SBotSettings[SBotNr].pl = RANDOM_LONG(3500000, 10000000);
		BotManager.m_SBotSettings[SBotNr].transparency_val = RANDOM_LONG(1, 255);
		BotManager.m_SBotSettings[SBotNr].laser_percent = RANDOM_LONG(0, 100);
	}

	(*g_engfuncs.pfnServerPrint) ("Reading sbot.cfg file... ");

	if (SBotFile == NULL) // file doesn't exist
	{
		(*g_engfuncs.pfnServerPrint) ("file not found, random settings will be used\n");
		return;
	}

	int ch;
	char cmd_line[256];
	int cmd_index;
	static char server_cmd[280];
	char *cmd, *arg1, *arg2, *arg3, *arg4;

	while (SBotFile)
	{
		cmd_index = 0;
		cmd_line[cmd_index] = 0;

		ch = fgetc(SBotFile);

		// skip any leading blanks
		while (ch == ' ')
		ch = fgetc(SBotFile);

		while ((ch != EOF) && (ch != '\r') && (ch != '\n'))
   		{
			if (ch == '\t')  // convert tabs to spaces
		      ch = ' ';

			cmd_line[cmd_index] = ch;

			ch = fgetc(SBotFile);

			// skip multiple spaces in input file
			while ((cmd_line[cmd_index] == ' ') &&
		             (ch == ' '))      
         		ch = fgetc(SBotFile);

		      cmd_index++;
   		}

		if (ch == '\r')  // is it a carriage return?
		{
			ch = fgetc(SBotFile);  // skip the linefeed
   		}

		// if reached end of file, then close it
		if (ch == EOF)
   		{
		    fclose(SBotFile);
			SBotFile = NULL;
		}

		cmd_line[cmd_index] = 0;  // terminate the command line

		// copy the command line to a server command buffer...
		strcpy(server_cmd, cmd_line);
		strcat(server_cmd, "\n");

		cmd_index = 0;
		cmd = cmd_line;
		arg1 = arg2 = arg3 = arg4 = 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];

		    // 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;
		        arg2 = &cmd_line[cmd_index];

		        // 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;
		            arg3 = &cmd_line[cmd_index];

            		// 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;
		                arg4 = &cmd_line[cmd_index];
					}
				}
			}
		}

		if ((cmd_line[0] == '#') || (cmd_line[0] == 0))
			continue;  // skip if comment or blank line

		if (strcmp(cmd, "[SUPERBOT]") == 0)
			SBotNr = 0; // sbot1
		else if (strcmp(cmd, "[MEGABOT]") == 0)
			SBotNr = 1; // sbot 2
		else if (strcmp(cmd, "[ULTIMATEBOT]") == 0)
			SBotNr = 2; // sbot 3

		if ((arg1 == NULL) || (*arg1 == 0))
		    continue;

		if (SBotNr == -1)
			continue;

		value = atoi(arg1);

		if (strcmp(cmd, "ki_gain") == 0)
		{
			if ((value >= -1) && (value <= 1000))
				BotManager.m_SBotSettings[SBotNr].ki_gain = value;
			else
				BotManager.m_SBotSettings[SBotNr].ki_gain = RANDOM_LONG(-1, 5);
		}
		else if (strcmp(cmd, "health_gain") == 0)
		{
			if ((value >= -1) && (value <= 250))
				BotManager.m_SBotSettings[SBotNr].health_gain = value;
			else
				BotManager.m_SBotSettings[SBotNr].health_gain = RANDOM_LONG(-1, 5);
		}
		else if (strcmp(cmd, "powerlevel") == 0)
		{
			if ((value >= 650000) && (value <= 99999999))
				BotManager.m_SBotSettings[SBotNr].pl = value;
			else
				BotManager.m_SBotSettings[SBotNr].pl = RANDOM_LONG(650000, 10000000);
		}
		else if (strcmp(cmd, "transparency_val") == 0)
		{
			if ((value == -1) || ((value >= 1) && (value <= 255)))
				BotManager.m_SBotSettings[SBotNr].transparency_val = value;
			else
				BotManager.m_SBotSettings[SBotNr].transparency_val = RANDOM_LONG(1, 255);
		}
		else if (strcmp(cmd, "sbotlaser_percent") == 0)
		{
			if ((value >= 0) && (value <= 100))
				BotManager.m_SBotSettings[SBotNr].laser_percent = value;
			else
				BotManager.m_SBotSettings[SBotNr].laser_percent = RANDOM_LONG(0, 100);
		}			 		
	}
	(*g_engfuncs.pfnServerPrint) ("done\n");
}

void CBotManager::InitSkillDate()
{
	// Best skill
	m_BotSkills[0].bUsesTurbo = true;
	m_BotSkills[0].flMinGenReactionDelay = 0.015f;
	m_BotSkills[0].flMaxGenReactionDelay = 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 = 350.0f;
	m_BotSkills[0].flMaxAimYSpeed = 375.0f;
	m_BotSkills[0].flMinAttackDelay = 0.1f;
	m_BotSkills[0].flMaxAttackDelay = 0.4f;
	m_BotSkills[0].flMinEnemySearchDelay = 0.09f;
	m_BotSkills[0].flMinEnemySearchDelay = 0.12f;
	m_BotSkills[0].flExplodeBeamDistance = 150.0f;
	m_BotSkills[0].RandomComboButtonPercent = 0;
	m_BotSkills[0].bUsesMeleeCombos = true;
	m_BotSkills[0].bUsesChainSwoop = true;

	// Good skill
	m_BotSkills[1].bUsesTurbo = true;
	m_BotSkills[1].flMinGenReactionDelay = 0.035f;
	m_BotSkills[1].flMaxGenReactionDelay = 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].flMinEnemySearchDelay = 0.17f;
	m_BotSkills[1].flExplodeBeamDistance = 165.0f;
	m_BotSkills[1].RandomComboButtonPercent = 15;
	m_BotSkills[1].bUsesMeleeCombos = true;
	m_BotSkills[1].bUsesChainSwoop = true;

	// Medium skill
	m_BotSkills[2].bUsesTurbo = true;
	m_BotSkills[2].flMinGenReactionDelay = 0.075f;
	m_BotSkills[2].flMaxGenReactionDelay = 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.5f;
	m_BotSkills[2].flMaxAttackDelay = 1.1f;
	m_BotSkills[2].flMinEnemySearchDelay = 0.18f;
	m_BotSkills[2].flMinEnemySearchDelay = 0.22f;
	m_BotSkills[2].flExplodeBeamDistance = 230.0f;
	m_BotSkills[2].RandomComboButtonPercent = 25;
	m_BotSkills[2].bUsesMeleeCombos = false;
	m_BotSkills[2].bUsesChainSwoop = false;

	// Weak skill
	m_BotSkills[3].bUsesTurbo = false;
	m_BotSkills[3].flMinGenReactionDelay = 0.15f;
	m_BotSkills[3].flMaxGenReactionDelay = 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 = 175.0f;
	m_BotSkills[3].flMaxAimYSpeed = 195.0f;
	m_BotSkills[3].flMinAttackDelay = 1.2f;
	m_BotSkills[3].flMaxAttackDelay = 1.6f;
	m_BotSkills[3].flMinEnemySearchDelay = 0.25f;
	m_BotSkills[3].flMinEnemySearchDelay = 0.30f;
	m_BotSkills[3].flExplodeBeamDistance = 250.0f;
	m_BotSkills[3].RandomComboButtonPercent = 50;
	m_BotSkills[3].bUsesMeleeCombos = false;
	m_BotSkills[3].bUsesChainSwoop = false;

	// Bad skill
	m_BotSkills[4].bUsesTurbo = false;
	m_BotSkills[4].flMinGenReactionDelay = 0.30f;
	m_BotSkills[4].flMaxGenReactionDelay = 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 = 60.0f;
	m_BotSkills[4].flMaxAimXSpeed = 90.0f;
	m_BotSkills[4].flMinAimYSpeed = 800.0f;
	m_BotSkills[4].flMaxAimYSpeed = 110.0f;
	m_BotSkills[4].flMinAttackDelay = 1.5f;
	m_BotSkills[4].flMaxAttackDelay = 2.0f;
	m_BotSkills[4].flMinEnemySearchDelay = 0.30f;
	m_BotSkills[4].flMinEnemySearchDelay = 0.36f;
	m_BotSkills[4].flExplodeBeamDistance = 340.0f;
	m_BotSkills[4].RandomComboButtonPercent = 75;
	m_BotSkills[4].bUsesMeleeCombos = false;
	m_BotSkills[4].bUsesChainSwoop = false;
}

void CBotManager::CreateSkillData()
{
	// First give the bot skill structure some default data
	InitSkillDate();

	// Now see if we can load the skill.cfg file
	char SkillFileName[256] = "";
	FILE *DelayFile = NULL;
	int SkillNr = -1;
    float value = 0;

	BuildBotFileName(SkillFileName, "skill.cfg", NULL);

	DelayFile = fopen(SkillFileName, "r");

	(*g_engfuncs.pfnServerPrint) ("Reading skill.cfg file... ");

	if (DelayFile == NULL) // file doesn't exist
	{
		(*g_engfuncs.pfnServerPrint) ("file not found, default settings will be used\n");
		return;
	}

	int ch;
	char cmd_line[256];
	int cmd_index;
	char *cmd, *arg1;

	while (DelayFile)
	{    
		cmd_index = 0;
		cmd_line[cmd_index] = 0;

		ch = fgetc(DelayFile);

		// skip any leading blanks
		while (ch == ' ')
			ch = fgetc(DelayFile);

		while ((ch != EOF) && (ch != '\r') && (ch != '\n'))
   		{
			if (ch == '\t')  // convert tabs to spaces
		      ch = ' ';

			cmd_line[cmd_index] = ch;

			ch = fgetc(DelayFile);

			// skip multiple spaces in input file
			while ((cmd_line[cmd_index] == ' ') &&
		             (ch == ' '))      
         		ch = fgetc(DelayFile);

		      cmd_index++;
   		}

		if (ch == '\r')  // is it a carriage return?
		{
			ch = fgetc(DelayFile);  // skip the linefeed
   		}

		// if reached end of file, then close it
		if (ch == EOF)
   		{
		    fclose(DelayFile);
			DelayFile = 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 (stricmp(cmd, "[SKILL1]") == 0)
			SkillNr = 0;
		else if (stricmp(cmd, "[SKILL2]") == 0)
			SkillNr = 1;
		else if (stricmp(cmd, "[SKILL3]") == 0)
			SkillNr = 2;
		else if (stricmp(cmd, "[SKILL4]") == 0)
			SkillNr = 3;
		else if (stricmp(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 (stricmp(cmd, "min_general_delay") == 0)
		{
//			clog << "GenMinDelay for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMinGenReactionDelay = value;
		}
		else if (stricmp(cmd, "max_general_delay") == 0)
		{
//			clog << "GenMaxDelay for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMaxGenReactionDelay = value;
		}
		else if (stricmp(cmd, "min_x_aim_speed") == 0)
		{
//			clog << "MinAimSpeed for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMinAimXSpeed = value;
		}
		else if (stricmp(cmd, "max_x_aim_speed") == 0)
		{
//			clog << "MaxAimSpeed for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMaxAimXSpeed = value;
		}
		else if (stricmp(cmd, "min_y_aim_speed") == 0)
		{
//			clog << "MinAimSpeed for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMinAimYSpeed = value;
		}
		else if (stricmp(cmd, "max_y_aim_speed") == 0)
		{
//			clog << "MaxAimSpeed for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMaxAimYSpeed = value;
		}
		else if (stricmp(cmd, "min_x_aim_offset") == 0)
		{
//			clog << "MinXAimOffset for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMinAimXOffset = value;
		}
		else if (stricmp(cmd, "max_x_aim_offset") == 0)
		{
//			clog << "MinXAimOffset for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMaxAimXOffset = value;
		}
		else if (stricmp(cmd, "min_y_aim_offset") == 0)
		{
//			clog << "MinYAimOffset for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMinAimYOffset = value;
		}
		else if (stricmp(cmd, "max_y_aim_offset") == 0)
		{
//			clog << "MaxYAimOffset for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMaxAimYOffset = value;
		}
		else if (stricmp(cmd, "min_attack_delay") == 0)
		{
//			clog << "min attack delay for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMinAttackDelay = value;
		}
		else if (stricmp(cmd, "max_attack_delay") == 0)
		{
//			clog << "max attack delay for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMaxAttackDelay = value;
		}
		else if (stricmp(cmd, "min_beam_explode_distance") == 0)
		{
//			clog << "min beam explode dist for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flExplodeBeamDistance = value;
		}
		else if (stricmp(cmd, "min_enemy_search_delay") == 0)
		{
//			clog << "min enemy search delay for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMinEnemySearchDelay = value;
		}
		else if (stricmp(cmd, "max_enemy_search_delay") == 0)
		{
//			clog << "max enemy search delay for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].flMaxEnemySearchDelay = value;
		}
		else if (stricmp(cmd, "random_combo_button_percent") == 0)
		{
//			clog << "random combo button for skill " << SkillNr << " is " << value << "\n";
			m_BotSkills[SkillNr].RandomComboButtonPercent = static_cast<short>(value);
		}
		else if (stricmp(cmd, "uses_turbo"))
			m_BotSkills[SkillNr].bUsesTurbo = (value != 0);
		else if (stricmp(cmd, "uses_meleecombos"))
			m_BotSkills[SkillNr].bUsesMeleeCombos = (value != 0);
		else if (stricmp(cmd, "uses_chainswoop"))
			m_BotSkills[SkillNr].bUsesChainSwoop = (value != 0);
	}

	(*g_engfuncs.pfnServerPrint) ("done\n");
}

void CBotManager::OpenBotCFGFile()
{
	if (BotManager.m_bNeedToReadCFGFile == false)
		return;

	char filename[256];
	char mapname[64];
	char msg[256];

    m_bNeedToReadCFGFile = false; // only do this once

	// check if mapname_bot.cfg file exists...

    strcpy(mapname, STRING(gpGlobals->mapname));
    strcat(mapname, "_bot.cfg");

    BuildBotFileName(filename, "maps", mapname);

	if ((m_pBotCFGFile = fopen(filename, "r")) != NULL)
	{
		sprintf(msg, "Executing %s\n", filename);
		ALERT( at_console, msg );
	}
	else
	{
		BuildBotFileName(filename, "bot.cfg", NULL);

		sprintf(msg, "Executing %s\n", filename);
        ALERT( at_console, msg );

		m_pBotCFGFile = fopen(filename, "r");

		if (m_pBotCFGFile == NULL)
			ALERT( at_console, "bot.cfg file not found\n" );
	}
	
	if (IS_DEDICATED_SERVER())
		m_fBotCFGPauseTime = gpGlobals->time + 5.0;
	else
		m_fBotCFGPauseTime = gpGlobals->time + 20.0;
}

void CBotManager::BotCFGFileThink()
{
	int ch;
    char cmd_line[256];
    int cmd_index;
    static char server_cmd[280];
    char *cmd, *arg1, *arg2, *arg3, *arg4, *arg5, *arg6, *arg7;
    char msg[180];

    if (m_fBotCFGPauseTime > gpGlobals->time)
		return;

    if (m_pBotCFGFile == NULL)
		return;
     
    cmd_index = 0;
    cmd_line[cmd_index] = 0;

    ch = fgetc(m_pBotCFGFile);

    // skip any leading blanks
    while ((ch == ' ') && (ch != EOF))
		ch = fgetc(m_pBotCFGFile);

    while ((ch != EOF) && (ch != '\r') && (ch != '\n'))
    {
		if (ch == '\t')  // convert tabs to spaces
			ch = ' ';

        cmd_line[cmd_index] = ch;

        ch = fgetc(m_pBotCFGFile);

        // skip multiple spaces in input file
		while ((cmd_line[cmd_index] == ' ') && (ch == ' '))
			ch = fgetc(m_pBotCFGFile);

		cmd_index++;
    }
	
	if (ch == '\r')  // is it a carriage return?
    {
		ch = fgetc(m_pBotCFGFile);  // skip the linefeed
    }

    // if reached end of file, then close it
    if (ch == EOF)
    {
		fclose(m_pBotCFGFile);

        m_pBotCFGFile = NULL;

        m_fBotCFGPauseTime = 0.0;
    }

	cmd_line[cmd_index] = 0;  // terminate the command line

	// copy the command line to a server command buffer...
    strcpy(server_cmd, cmd_line);
	strcat(server_cmd, "\n");

	cmd_index = 0;
	cmd = cmd_line;
	arg1 = arg2 = arg3 = arg4 = arg5 = arg6 = arg7 = 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];

		// 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;
			arg2 = &cmd_line[cmd_index];

			// 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;
				arg3 = &cmd_line[cmd_index];

				// 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;
					arg4 = &cmd_line[cmd_index];

					// 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;
						arg5 = &cmd_line[cmd_index];

						// 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;
							arg6 = &cmd_line[cmd_index];

							// 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;
								arg7 = &cmd_line[cmd_index];
							}
						}
					}
				}
			}
		}
    }

    if ((cmd_line[0] == '#') || (cmd_line[0] == 0))
		return;  // return if comment or blank line

	if ((!arg1) || (*arg1 == 0))
		return; // return if no argument

	if (strcmp(cmd, "addbot") == 0)
    {
		short Team = -1, Class = -1, Skill = -1, Count = -1;
		const char *szName = ((arg3) && (*arg3) && (arg3[0] != '*')) ? arg3 : NULL;
		const char *szPerso = ((arg5) && (*arg5) && (arg5[0] != '*')) ? arg5 : NULL;
		EBotDBType BotDBType = DB_UNASSIGNED;

		if ((arg1) && (*arg1))
			Team = (g_teamplay) ? atoi(arg1) : -1;
		if ((arg2) && (*arg2))
			Class = atoi(arg2);
		if ((arg4) && (*arg4))
			Skill = atoi(arg4);
		if ((arg6) && (*arg6))
			Count = atoi(arg6);

		if ((arg7) && (*arg7))
		{
			int DBType = atoi(arg7);
			if ((DBType >= 1) && (DBType <= 3))
				BotDBType = EBotDBType(DBType-1);
		}

		if (Count < 2)
			CreateBot(Team, Class, szName, Skill, szPerso, BotDBType);
		else
			BotFill(Count, Team, Class, Skill, szPerso, BotDBType);

		// have to delay here or engine gives "Tried to write to
		// uninitialized sizebuf_t" error and crashes...

		m_fBotCFGPauseTime = gpGlobals->time + 2.0;
		m_fAddBotDelay = gpGlobals->time + 5.0;

		return;
    }

    if (strcmp(cmd, "addsbot") == 0)
    {
		if (!g_teamplay)
		{
			cout << "Cannot create Super Bots in free fight!\n";
			return;
		}

		short Class = -1, Type = -1, Count = -1;
		const char *szName = ((arg6) && (*arg6) && (arg4[0] != '*')) ? arg6 : NULL;
		const char *szPerso = ((arg3) && (*arg3) && (arg3[0] != '*')) ? arg3 : NULL;
		EBotDBType BotDBType = DB_UNASSIGNED;

		if ((arg1) && (*arg1) && (arg1[0] != '*'))
			Type = atoi(arg1);
		if ((arg2) && (*arg2) && (arg2[0] != '*'))
			Class = atoi(arg2);
		if ((arg5) && (*arg5) && (arg5[0] != '*'))
			Count = atoi(arg5);

		if ((arg4) && (*arg4))
		{
			int DBType = atoi(arg4);
			if ((DBType >= 1) && (DBType <= 3))
				BotDBType = EBotDBType(DBType-1);
		}

		if (Count < 2)
			CreateSuperBot(Class, szName, szPerso, Type, BotDBType);
		else
			SBotFill(Count, Class, szPerso, Type, BotDBType);

		// have to delay here or engine gives "Tried to write to
		// uninitialized sizebuf_t" error and crashes...

		m_fBotCFGPauseTime = gpGlobals->time + 2.0;
		m_fAddBotDelay = gpGlobals->time + 5.0;

		return;
    }

	if (strcmp(cmd, "botskill") == 0)
    {
		int temp = atoi(arg1);

		if ((temp >= 1) && (temp <= 5))
		{
			ChangeBotSkill(temp);  // set default bot skill level
		}

		return;
    }
/*
	if (strcmp(cmd, "shouldbotschat") == 0)
    {
		int temp = atoi(arg1);

		if (temp)
			m_bShouldBotsChat = true;
		else
			m_bShouldBotsChat = false;

		return;
    }

    if (FStrEq(cmd, "botsusediscs"))
    {
		if ((arg1 != NULL) && (*arg1 != 0))
		{
			int temp = atoi(arg1);
		   
		    if (temp)
				m_bBotsUseDiscs = true;
		    else
				m_bBotsUseDiscs = false;
		}
        return;
	}  

    if (strcmp(cmd, "botshoot") == 0)
    {
		int temp = atoi(arg1);

		if (temp)
			m_bBotsShoot = true;
		else
			m_bBotsShoot = false;

		return;
    }
*/
    if (strcmp(cmd, "min_bots") == 0)
    {
		m_sMinBots = atoi( arg1 );

		if ((m_sMinBots < 0) || (m_sMinBots > 31))
			m_sMinBots = -1;

		return;
    }

    if (strcmp(cmd, "max_bots") == 0)
    {
		m_sMaxBots = atoi( arg1 );

		if ((m_sMaxBots < 0) || (m_sMaxBots > 31))
			m_sMaxBots = -1;

		return;
    }

    if (strcmp(cmd, "pause") == 0)
    {
		if ((arg1) && (*arg1))
			m_fBotCFGPauseTime = gpGlobals->time + atof( arg1 );

		return;
    }
/*
    if (FStrEq(cmd, "bot_balanceteams"))
    {
		if ((arg1 != NULL) && (*arg1 != 0))
		{
			int temp = atoi(arg1);

		    if (temp)
				m_bBalanceTeams = true;
			else
			    m_bBalanceTeams = false;
		}
	    return;
    }
*/   
    sprintf(msg, "executing server command: %s\n", server_cmd);
    UTIL_Printf(msg);

    SERVER_COMMAND(server_cmd);
}

void CBotManager::MapInit()
{
	// Reset all timers
	m_fAddBotDelay = gpGlobals->time + 30.0;
	m_fRestoreBotsTime = 0.0;
	m_fBalanceTeamsDelay = 0.0;
	m_fSBotRulesDelay = 0.0;
	m_fBotCFGPauseTime = 0.0;

	DeleteBotsToAddList();
	m_bRestoringBots = false;
	m_bCheckNumBotsCVar = true;

    char filename[256];
    char mapname[128];

	// check if mapname_bot.cfg file exists...
    strcpy(mapname, STRING(gpGlobals->mapname));
	strcat(mapname, "_bot.cfg");

	sprintf(filename, "esf\\maps\\%s\\", mapname);

    if ((m_pBotCFGFile = fopen(filename, "r")) != NULL)
	{
		cout << "Executing " << filename << "\n";

		// Don't respawn any bots, just remove the list
		while (m_pKickedBots->Empty() == false)
			delete m_pKickedBots->Pop();
			

        if (IS_DEDICATED_SERVER())
			m_fBotCFGPauseTime = gpGlobals->time + 5.0;
		else
			m_fBotCFGPauseTime = gpGlobals->time + 20.0;
	}
    else
	{
		// execute bot.cfg file
		BotManager.OpenBotCFGFile();

		// set the respawn time
		if (IS_DEDICATED_SERVER())
			m_fRestoreBotsTime = gpGlobals->time + 5.0;
		else
			m_fRestoreBotsTime = gpGlobals->time + 30.0;
	}

	WaypointClass.Init();
    WaypointClass.LoadWaypoints();

	BotDBManager.LoadCustomGatherDBSpots();
	BotDBManager.AssignDBGatherSpots();

    PRECACHE_SOUND("weapons/xbow_hit1.wav");      // waypoint add
    PRECACHE_SOUND("weapons/mine_activate.wav");  // waypoint delete
    PRECACHE_SOUND("common/wpn_hudoff.wav");      // path add/delete start
    PRECACHE_SOUND("common/wpn_hudon.wav");       // path add/delete done
    PRECACHE_SOUND("common/wpn_moveselect.wav");  // path add/delete cancel
    PRECACHE_SOUND("common/wpn_denyselect.wav");  // path add/delete error
	PRECACHE_SOUND("weapons/basicbeam_chargeoriginal.wav"); // for sbot 'weapon'

	m_iSpriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr");

    m_bSpawnTimeReset = false;
    m_bPrevNumberBots = m_bNumberOfBots;
    m_bNumberOfBots = 0;
}	

void CBotManager::MapFree()
{
	WaypointClass.Clear(); // Free all memory used by waypoints
	
    // kick any bot off of the server after time/frag limit...
    for (int index = 1; index <= gpGlobals->maxClients; index++)
    {
		CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex(index);

		if ((UTIL_IsValidPlayer(pPlayer, false, false) && UTIL_IsBot(pPlayer)))
		{
			CBot *pBot = UTIL_GetBotPointer(pPlayer);

			if (pBot)
			{
				char cmd[64];
				sprintf(cmd, "kick \"%s\"\n", STRING(pBot->pev->netname));
				SERVER_COMMAND(cmd);  // kick the bot using (kick "name")
			}
		}
	}

	WaypointFlooder.DestroyFloodWps();
}

void CBotManager::GameInit()
{
	CVAR_REGISTER (&sv_num_bots);
	CVAR_REGISTER (&sv_bot_skill);
	CVAR_REGISTER (&sv_botsshoot);
	CVAR_REGISTER (&sv_botssearchenemies);
	CVAR_REGISTER (&sv_observebots);
	CVAR_REGISTER (&sv_idlebots);
	CVAR_REGISTER (&sv_botsusediscs);
	CVAR_REGISTER (&sv_botsmove);
	CVAR_REGISTER (&sv_botschat);
	CVAR_REGISTER (&sv_botsbalanceteams);
	
	LoadBotNames(); // load bot names
	ChatClass.ReadChatFile(); // load chat sentences
	CreateSuperbotSettings(); // load/create sbot settings
	CreateSkillData(); // Create/Load skill settings
	CreateBotPersonalities(); // load/create bot personalities

    (*g_engfuncs.pfnAddServerCommand)("esf_bot", HandleNormalBotCommands);
    (*g_engfuncs.pfnAddServerCommand)("bot_help", ShowInGameBotHelpWindow);
}

// ==================================================================
// Code of CBotManager - End
// ==================================================================

#define chunk_size 100
#define max_size 1536

// MOTD code ripped from the sdk...
void ShowInGameBotHelpWindow()
{
	if ((IS_DEDICATED_SERVER() == false) && (!BotManager.GetHost()))
		return;

	static bool Started = false;
	static int length = 0;
	static int char_count = 0;
	static char *pFileList;
	static char *aFileList = pFileList = NULL;
	static char OldServerName[256] = "";
	static char chunk[chunk_size+1];

	if (gmsgMOTD == 0)
		gmsgMOTD = REG_USER_MSG("MOTD", -1);

	if (gmsgServerName == 0)
		gmsgServerName = REG_USER_MSG("ServerName", -1);

	if (Started == false)
	{
		PrintingInGameHelp = true;
		length = char_count = 0;
#ifdef __linux__
		char *szFileName = "esf_bot/ingame_help.txt";
#else
		char *szFileName = "esf_bot\\ingame_help.txt";
#endif
		aFileList = pFileList = (char*)LOAD_FILE_FOR_ME(szFileName , &length );
		Started = true;
		strcpy(OldServerName, CVAR_GET_STRING("hostname"));

		if (IS_DEDICATED_SERVER())
			cout << "In game bot help\n\n";
		else
		{
			// send the server name
			MESSAGE_BEGIN( MSG_ONE, gmsgServerName, NULL, BotManager.GetHost()->pev );
				WRITE_STRING( "In game bot help" );
			MESSAGE_END();
		}
	}

	// read it chunk-by-chunk,  and send it in parts

	if ( pFileList && *pFileList && char_count < max_size )
	{	
		if ( strlen( pFileList ) < chunk_size )
		{
			strcpy( chunk, pFileList );
		}
		else
		{
			strncpy( chunk, pFileList, chunk_size );
			chunk[chunk_size] = 0;		// strncpy doesn't always append the null terminator
		}

		char_count += strlen( chunk );
		if ( char_count < max_size )
			pFileList = aFileList + char_count; 
		else
			*pFileList = 0;

		if (IS_DEDICATED_SERVER())
			cout << chunk;
		else
		{
			MESSAGE_BEGIN( MSG_ONE, gmsgMOTD, NULL, BotManager.GetHost()->pev );
				WRITE_BYTE( *pFileList ? FALSE : TRUE );	// FALSE means there is still more message to come
				WRITE_STRING( chunk );
			MESSAGE_END();
		}
	}

	if ((!pFileList) || (*pFileList == NULL) || (char_count > max_size))
	{
		FREE_FILE( aFileList );

		if (IS_DEDICATED_SERVER() == false)
		{
			// Restore server name(I think...)
			MESSAGE_BEGIN( MSG_ONE, gmsgServerName, NULL, BotManager.GetHost()->pev );
				WRITE_STRING( OldServerName );
			MESSAGE_END();
		}

		char_count = length = 0;
		Started = PrintingInGameHelp = false;
		*chunk = NULL;
	}
}
