#ifndef BOT_H
#define BOT_H
#include <string.h>
#include "bot_util.h"
#include "bot_io.h"
#include "bot_sense.h"
#include "bot_db.h"
#include "waypoint.h"
#include "bot_types.h"

//extern CBaseOutput cout;
extern CLeftOutput lcout;
extern CRightOutput rcout;
extern CMiddleOutput mcout;

#define BOT_NAME_LEN 32
#define MAX_BOT_NAMES 100
#define EYE_WIDTH 19//29//53
#define MAX_BOT_SKILLS 5
#define MAX_SUPERBOT_TYPES 3
#define SWOOP_BEGIN_DELAY	0.07
#define TAP_DELAY			0.07

// Superbot laser stuff
#define LASER1_KICOST				110 // for each 0.1 sec
#define ABSORB_PL_GAIN				950
#define ABSORB_ATTACK_PL_GAIN		500
#define ABSORB_ATTACK_KI_COST		5
#define ABSORB_DAMAGE_VALUE			0.375f

// sbot weapons
#define SBOT_WEAPON_LASER1			  128

#define dir_left				(1<<1)
#define dir_right				(1<<2)
#define dir_back				(1<<3)
#define dir_forward				(1<<4)
#define dir_up					(1<<5)
#define dir_down				(1<<6)
#define dir_all					(1<<7)
#define dir_upforward			(1<<8)
#define dir_downforward			(1<<9)
#define dir_left_forward_up		(1<<10)
#define dir_right_forward_up	(1<<11)
#define dir_left_back_up		(1<<12)
#define dir_right_back_up		(1<<13)
#define dir_left_forward_down	(1<<14)
#define dir_right_forward_down	(1<<15)
#define dir_left_back_down		(1<<16)
#define dir_right_back_down		(1<<17)
#define dir_left_back			(1<<18)
#define dir_right_back			(1<<19)
#define dir_left_forward		(1<<18)
#define dir_right_forward		(1<<19)

// Swoop flags
#define SWOOP_ATTACK			(1<<1)
#define SWOOP_AVOIDDANGER		(1<<2)
#define SWOOP_GOTODBGOAL		(1<<3)

enum BotVGUIMsgTypes
{
	BOTVGUI_ADDPERSO=0, // Add a new personality to the list
	BOTVGUI_DEFAULTPERSO, // Sets the default personality
	BOTVGUI_BOTSKILL, // Updates skill for a single bot
	BOTVGUI_BOTPERSO, // Updates personality for a single bot
	BOTVGUI_BOTDBROLE, // Updates the bot role for a single bot
	BOTVGUI_DBMODE // Set db mode
};

typedef enum 
{
	USE_COMBO,
	DONT_USE_COMBO,
	RANDOM_COMBO_USE
}EComboUse;

struct eye_s
{
	float distance;
	int contents;
	Vector endVec;
	Vector vecPlaneNormal;
};

typedef enum 
{
	TELE_LEFT,
	TELE_RIGHT,
	TELE_BACK,
	TELE_FORWARD,
	TELE_UP,
	TELE_DOWN,
	TELE_RANDOM
}ETeleDir;

typedef enum
{
	Aggressive = 0,
	Normal,
	Defensive
}EAggressionLevel;

struct enemy_priority_s
{
	short Priority;
	CBasePlayer *pEnemy;
};

typedef enum 
{
	SWOOP_NORMAL,
	SWOOP_OFFSET,
	SWOOP_RANDOM_OFFSET
}ESwoopType;

typedef enum 
{
	START_IDLE,
	START_TEAM_SELECT,
	START_CLASS_SELECT,
	START_MOTD
}EStartMessage;

typedef enum 
{
	MOVE_LEFT,
	MOVE_RIGHT,
	MOVE_NONE
}EMoveDir;

typedef enum
{
	DB_DEFENDER=0, // Bot defends collected dragonballs
	DB_COLLECTER, // Bot tries to mainly find dragonballs, doesn't attack
	DB_ATTACKER, // Collect dbs and attack enemies
	DB_UNASSIGNED
} EBotDBType;

typedef enum
{
	STATE_GOTODB, // Bot heads to a dragon ball
	STATE_GOTOGATHERSPOT, // Bot heads to the db gather spot
	STATE_GOTOGATHEREDSPOT, // Bot heads to the spot where the dragonballs are already gathered
	STATE_DEFENDGATHERSPOT, // Bot defends the gather spot of his team
	STATE_GOTODEFENDSPOT, // Bot heads to his defend spot
	STATE_NOCURRENTDBSTATE
} ECurrentBotDBState;


struct bot_personality_s
{
	char perso_name[32]; // the name of this personality

	// weapon use percents
	short int melee_percent; // used for melee and sword
	short int kiblast_percent; // used for kiblast and renzoku
	short int lightbeam_percent; // used for genericbeam and burning attack
    short int mediumbeam_percent; // used for masenko and powerbeam
	short int heavybeam_percent; // used for kame,gallit gun,mouthblast etc
	short int disc_percent; // used for discs
	short int laser_percent; // used for eye-laser and fingerlaser
	short int heavyprojectile_percent; // used for bb,sb and finishing buster
	short int kametorp_percent; // used for kame torpedo's
	short int solarflare_percent; // percent that a bot will blind his enemy when in danger

	// melee related percents
	short int meleethrow_percent; // percent that a bot will throw someone instead of charging a combo
	short int meleekick_percent; // percent that a bot will use secondary fire when charging a combo 
	short int simplemelee_percent; // percent that a bot will use simple melee
	short int meleeweakbutton_percent; // percent that a bot will use a weak button when charging a combo
	short int meleemediumbutton_percent; // percent that a bot will use a medium button when charging a combo
	short int meleestrongbutton_percent; // percent that a bot will use a strong button when charging a combo
	short int meleecombo_percent; // percent that a bot will try to make a melee combo

	// chat percents
	short int chat_percent; // percent of time to chat
    short int taunt_percent; // percent of time to taunt after kill
    short int whine_percent; // percent of time to whine after death
    short int chat_tag_percent; // percent of the time to drop clan tag
    short int chat_drop_percent; // percent of the time to drop characters
    short int chat_swap_percent; // percent of the time to swap characters
    short int chat_lower_percent; // percent of the time to lowercase chat

	// other percent(s)
	short int teleport_percent; // percent that the bot will teleport
	short int swoop_percent; // percent that a bot will swoop away to avoid danger
	short int beamexplode_percent; // percent that a bot will let a beam explode in 
								   // their enemy's face
	short int fly_percent; // how much will a bot fly ?
	short int stopfly_percent; // how much will a bot stop with flying(for a while) to avoid danger ?
	short int jump_percent; // percent that a bot will avoid danger by jumping when on the ground
	short int block_percent; // percent that a bot will avoid danger by blocking

	EAggressionLevel aggression_level; // defensive, normal or aggressive

	bool transkiller; // does the bot still attack his enemy when he ascends ?

	// constructor
	bot_personality_s(const char *szName) { strcpy(perso_name, szName); InitPerso(); };

	void InitPerso(void); // Give all member vars a random value
};

struct sbot_setting_s
{
	short ki_gain;
	short health_gain;
	long pl;
	int transparency_val;
	short laser_percent;
};

struct bot_skill_s
{
	float flMinGenReactionDelay; // Minimal general reaction time
	float flMaxGenReactionDelay; // Maximal general reaction time
	float flMinAimXSpeed; // Minimal X aim speed in degrees(base value)
	float flMaxAimXSpeed; // Maximal X aim speed in degrees(base value)
	float flMinAimYSpeed; // Minimal Y aim speed in degrees(base value)
	float flMaxAimYSpeed; // Maximal Y aim speed in degrees(base value)
	float flMinAimXOffset; // Minimal X aim offset in degrees(base value)
	float flMaxAimXOffset; // Maximal X aim offset in degrees(base value)
	float flMinAimYOffset; // Minimal Y aim offset in degrees(base value)
	float flMaxAimYOffset; // Maximal Y aim offset in degrees(base value)
	float flMinAttackDelay; // Minimal delay for when a bot can attack again
	float flMaxAttackDelay; // Maximal delay for when a bot can attack again
	float flMinEnemySearchDelay; // Minimal delay for when a bot can search for an enemy again
	float flMaxEnemySearchDelay; // Maximal delay for when a bot can search for an enemy again
	float flExplodeBeamDistance; // Minimal distance from the bot's attack to his enemy to let
			  					 // his beam explode
	short RandomComboButtonPercent; // Percent that a bot should press a random button when
									// defending a combo
	bool bUsesTurbo; // Does the bot with this skill use turbo?
	bool bUsesMeleeCombos; // Does the bot with this skill use melee combos?
	bool bUsesChainSwoop; // Does the bot with this skill uses chainswoop?
};

struct weapon_info_s
{
	int iId;				// The Id of this weapon
	char szName[64];		// The name of this weapon("weapon_blah")
	bool bUseSecondary;		// Should the bot use secondary fire for this weapon?
	bool bIsChargeble;		// Is this attack Chargeble?
	bool bCanBeamJump;		// Can you beamjump with this attack?
	bool bControllable;		// Can you control this attack?
	float flHoldFireTime;	// Time to hold fire for this weapon(not needed for chargebles)
	float flMinDist;		// Minimal distance when a bot can use this weapon(base value)
	float flMaxDist;		// Maximal distance when a bot can use this weapon(base value)
};

struct meleecombo_info_s
{
	MELEE_STRENGTH MeleeStrength; // Button strength type
	int Buttons; // Buttons to press

	// Constructor
	meleecombo_info_s(MELEE_STRENGTH MS, int B) : MeleeStrength(MS), Buttons(B) {};
};

struct unreachable_node_s
{
	flood_wp_s *pNode;
	float flUnreachableTime;

	unreachable_node_s(void) : pNode(NULL), flUnreachableTime(0.0f) {};
	unreachable_node_s(flood_wp_s *p, float t) : pNode(p), flUnreachableTime(t) {};
};

class CBaseTask;
class CBotCombo;
struct old_position_s;
class CBotManager;
class CChatClass;

class CBot: public CBasePlayer
{
	// Private member vars
public:

	// Spawn variabeles
	bool m_bNeedToInitialize;
	float m_fStartUpDelay;
	bool m_bNotStarted;
	EStartMessage m_eStartAction;
    short m_sBotClass; // class the bot wants to choose
    float m_fBotSpawnTime; // Time the bot (re)spawned
	float m_fCreateTime;

	// Settings
	byte m_bSkillNr;
	bot_skill_s *m_pBotSkill;
	bot_personality_s *m_pPersonality;

	// Misc stuff
	bool m_bKicked;
	float m_fKickTime;
    bool m_bShouldFly;
    float m_fCheckFlyDelay;
    short m_sDangerLevel;
    int m_iDangerType;
    bool m_bMadePlayerList;
    CBasePlayer *m_pPlayerList[32];
    long m_lVisiblePlayers;
    long m_lValidPlayers;
    byte m_bPlayerCount;
    float m_fTeleComboDelay;
    float m_fTurboTime;
    float m_fLastHitTime;
	int m_iDamageTaken;
    CBaseEntity *m_pTouchEnt;
	char m_szName[BOT_NAME_LEN+1];
	float m_fAttackSenseDelay;
	float m_fSearchForSensuDelay;
	EHANDLE m_pEntToBlock;
	virtual void BotKilled(entvars_t *pAttacker);

	// 'Global' task vars
	float m_fCheckKameTorpTime;
	bool m_bChargeKameTorp;
	float m_fLastKameTorpLeftOverCheck;
	short m_sJumpDir;
	float m_fWallJumpCheckDelay;
	short m_sMoveDir;
	float m_fMoveTime;
    float m_fTeleportDelay;
    int m_iTeleportDir;
    float m_fBotPowerUpDelay;
	bool m_bLetBeamExplode;
    float m_fLastAscendCheck;
    bool m_bSelectCookie; // Yummie!
	float m_fCheckCookieTime;
	CBaseEntity *m_pCandyTarget;
    float m_fPauseCheckTime;
	Vector m_vBeamJumpSpot;
	short m_sKiGainTarget;
	CBaseEntity *m_pTargetSensuBean;

	// DB vars
	EHANDLE m_pGoalDB;
    float m_fDBCheckDelay;
    bool m_bSelectWish;
	EBotDBType m_eBotDBType;
	ECurrentBotDBState m_eCurrentBotDBState;

	// Combat vars
	float m_fSearchEnemyDelay;
	float m_fHasAnEnemyTime;
	enemy_priority_s m_EnemyList[4];
	float m_fCombatNavDelay;
	short m_sCombatNavDir;
    CBasePlayer *m_pBotEnemy;
    CBasePlayer *m_pLastEnemy;
    float m_fShootDelay;
    float m_fHoldAttackTime;
    weapon_info_s *m_pCurrentWeaponInfo;
	TLinkedList<int> m_MeleeDefendButtons;
	TLinkedList<meleecombo_info_s *> *m_pMeleeComboInfoList;
	float m_fNextComboChargeButtonTime;
	int m_iComboChargeButton;
	int m_iOldComboChargeButton;
	int m_iComboChargeHitButtons;
	int m_iOldComboChargeHitButtons;
	float m_fSwitchChargeButtonDelay;

	// Super bot vars
	short m_sSBotType;
	sbot_setting_s *m_pSBotSettings;
	float m_fUpdateStatsDelay;
	short m_sTransparantValue;
	short m_sTransparantFactor;
	float m_fLastBeamDrawTime;
    float m_fAbsorbHealthTime;
    float m_fLastSoundPlayedTime;
	char m_szOldName[BOT_NAME_LEN+1];

	// Swoop vars
    ESwoopType m_eSwoopType;
	Vector m_vSwoopOffset;
	float m_fAttacksWhileSwoopCheckDelay;
	CBaseEntity *m_pSwoopAvoidEnt; // Ent to avoid while avoid-swoop
	short m_sSwoopFlags;

	// Variabeles for aiming
    float m_fAimDelay;
    float m_fAimTrackingTime;
    float m_fAimXAngleDelta;
    float m_fAimYAngleDelta;
    Vector m_vAimVec;
    Vector m_vBodyVec;
    float m_fYawSpeed;
    float m_fPitchSpeed;
	float m_fPrevYawSpeed;
	float m_fPrevPitchSpeed;
    Vector m_vAimSpeed;

    // chat variabeles
    bool  m_bBotShouldSay;
    float m_fBotSayTime;
    char  m_szBotSayMsg[256];
    float m_fBotChatTime;
    CBasePlayer *m_pKiller;
    bool  m_bBotSayKilled;
    float m_fBotSayKilledTime;
	float m_fReplyDelay;

	// Movement variabeles
    float m_fStrafeTime;
    EMoveDir m_eStrafeDir;
	float m_fStrafeSpeed;
    float m_fUpDownSpeed;
    float m_fMoveSpeed;

	// Unstuck variabeles(unused atm)
    Vector v_prev_origin;
    float f_dont_check_stuck;
	float m_fPrevSpeed;

	// Navigation variabeles
    float m_fDontAvoidWallTime;
    float m_fLookDelay;
    eye_s m_eye[EYE_WIDTH];
    EMoveDir m_eWanderDir;
	float m_fCheckUpDownTime;

	// Waypoint variabeles
    float m_fWaypointTime;
    waypoint_s *m_pCurrentWaypoint;
	waypoint_s *m_pCurrentGoalWaypoint;
    int m_iPrevWaypointIndexes[5];
	short m_sCurrentWPIndex;
	short m_sCurrentGoalWPIndex;
    float m_fRandomWaypointTime;
    float m_fPrevWaypointDistance;
	float m_fLookForWaypointTime;
	float m_fWaypointHeadLastTurnLessTime; // Last time the didn't had to turn much wile heading to a WP
	float m_fWaypointHeadPauseTime; // Pause time to give the bot time to aim.
	// Flood stuff
	flood_wp_s *m_pCurrentFloodWP;
	flood_wp_s *m_pCurrentGoalFloodWP;
	TLinkedList<flood_wp_s *> m_AStarNodeList;
	TPriorList<flood_wp_s *, float> m_AStarOpenList;
	TLinkedList<flood_wp_s *> m_AStarClosedList;
	TLinkedList<flood_wp_s *> m_AStarGoalNodeList;
	TPriorList<flood_wp_s *, float> m_AStarGoalOpenList;
	TLinkedList<flood_wp_s *> m_AStarGoalClosedList;
	bool m_bCalculatingAStarPath;
	bool m_bDeletingAStarLists;
	TLinkedList<unreachable_node_s> m_UnreachableNodes;

	// Hunting variabeles
    CBasePlayer *m_pHuntPlayer;
    old_position_s m_HuntPos;
    old_position_s m_LastHuntPos;
	float m_fHuntDelay;
    float m_fPrevHuntDist;

	// Public vars
	short m_sBotTeam; // team the bot wants to join;
	CBaseTask *m_pCurrentBotTask;
	ETaskCode m_eTaskAfterTask; // Since its not possible to create a task in a task...
	ETaskCode m_ePrevTask;
	CBotCombo *m_pBotCombo;

	// Member functions

	void BotPreThink(void);
	virtual void BotThink(void);
	void Spawn(void);
	void Touch(CBaseEntity *pOther);
	void CheckSBotStuff(void);

	virtual void CreateInit(short Team, short Class, short Skill, const char *szPerso, short SBotVal,
							EBotDBType BotDBType);
	void LetBotJoinTeam(void);
	void LetBotPickAClass(void);

	// Misc stuff
	void MakePlayerList(void);
	void MakeCombo(EComboType CT);
	void DeleteCombos();
	bool IsSuperBot(void) { return (m_sSBotType > 0); };
	void SetSkill(short NewSkillNr);
	void SetPerso(bot_personality_s *pPerso);
	void SetDBRole(EBotDBType BotDBRole);
	bool DangerousPlForBot(long EnemyPl);
	bool BotHasWeapon(int iId);
	void ToggleTurbo(bool TurnOn);
	bool EntIsInFOV(CBaseEntity *pEnt) { return (EntIsInFieldOfView(this, pEnt)); };
	bool EntHeadsToBot(CBaseEntity *pEnt) { return (UTIL_GetHeadingPercentage(this, pEnt) >= 65); };
	bool PlayerIsInMeleeMode(CBasePlayer *pPlayer);
	short GetBestEscapeSwoopDir(bool CheckBackwards);
	void SetSwoopButton(void);
	EComboType MakeAttackChainSwoop(void);
	EComboType MakeNormalChainSwoop(void);
	EComboType MakeAttackAvoidChainSwoop(void);
	EComboType MakeAwayChainSwoop(CBaseEntity *pEntity);
	float GetCollisionDist(int iDir, float flTraceDist) { return ::GetCollisionDist(this, iDir, flTraceDist); };

	// Bot Move functions
	void GoToAnPreviousLocation(void);
	float BotChangeYaw(float Speed, float Ideal);
	float BotChangePitch(float Speed, float Ideal);
	void ResetMoveSpeed(void);
	void SetMoveDir(int MoveDir);
	void CheckStrafe(bool CheckWall = true);
	void TurnAround(void);
	void BotCombatMovement(void);
	bool TurnMuchForNextWP(void); // Bot has to turn much for his next waypoint?
	bool AStar(void);
	float AStarCost(flood_wp_s *pWP1, flood_wp_s *pWP2);
	bool InvalidGoalFloodWP(flood_wp_s *pGoal);
	flood_wp_s *GetNextFloodWP(void);
	virtual BOOL FVisible(CBaseEntity *pEntity);
	virtual BOOL FVisible(const Vector &vecOrigin);

	// Waypoint functions
	bool HeadToWaypoint(void);
	bool FindWaypoint(void);
	bool ReachedGoalWP(void);
	bool ReachedGoalFloodWP(void);
	bool CurrentWPIsValid(void);
	bool CurrentFloodWPIsValid(void);
	void ResetWaypointVars(void);
	void SetCurrentWaypoint(short sIndex);
	void SetGoalWaypoint(short sIndex);
	bool HeadToGoal(void);
	void SetCurrentFloodWP(flood_wp_s *pFloodWP);
	void SetGoalFloodWP(flood_wp_s *pFloodWP);

	// Functions to determine if a task should be made.
	bool BotShouldFallDown(void);
	bool BotCanBeamJump(bool AttackIsReleased);
	bool BotCanAscend(void);
	bool BotCanPowerUp(void);
	bool MakeKameTorps(short DesiredTorpCount);
	bool BotShouldMakeKameTorps(void);
	bool CheckDragonBalls(void);
	bool SearchGatheredSpot(void);
	bool SearchDBGatherSpot(void);
	bool SearchDBDefendSpot(void);
	bool SearchDragonBall(void);
	bool MakeTeleMelee(CBasePlayer *pPlayer);
	CBaseEntity *SearchSensuBean(void);

	// Combat functions
	bool CanAttackPlayer(CBasePlayer *pPlayer);
	bool HasWeapon(int iId);
	void AimAtEnemy(CBasePlayer *pEnemy);
	bool CheckForHuntEnemy(void);
	bool EnemyIsInList(CBasePlayer *pEnemy);
	CBasePlayer *BotFindEnemy(void);
	CBasePlayer *GetBestEnemyFromList(void);
	void ManageEnemyList(void);
	void SelectWeapon(char *item_name);
	bool IsValidEnemy(CBasePlayer *pClient);
	void AddShootDelay(void);
	void ShootHealthBeam(CBasePlayer *pPlayer);
	bool EnemyIsInMeleeRange(CBasePlayer *pPlayer);
	bool AttackIsBeam(weapon_info_s *pWeaponInfo);
	CBaseEntity *GetNearestHostileAttack(float flMinDist, float flMaxDist);
	EComboType AvoidAttacksWhileSwoop(void); // returns a combo type when a chainswoop should be made
	void MakeMeleeComboList(void);
	void ClearMeleeComboList(void);
	void SetComboChargeButton(MELEE_STRENGTH MeleeStrength, int ChargeButtons);
	void ChargeMeleeCombo(void);
	bool CanMeleeKickPlayer(CBasePlayer *pPlayer);

	// Task functions
	void MakeTask(ETaskCode TaskCode);
	virtual void KillCurrentTask(void);
	void MakeAvoidSwoopTask(void);
	virtual void CheckNecessarilyThings(void);
	virtual void CheckBotInternalStatus(void);
	virtual void CheckBotMiscThings(void);
	void BotPerformTasks(void);

	// Aim functions
	void BotAim(void);
	void SetIdealAngles(Vector v_ideal) { m_vAimVec = v_ideal; };
	void AimToVec(const Vector &v_vec)	{ SetIdealAngles(GetAimAngles(v_vec)); };
	Vector GetAimAngles(Vector v_vec) { return WrapAngles(UTIL_VecToAngles(v_vec-pev->origin)); };

	// Sense functions
	void BotSense(void);
	void SenseSurroundingAttacks(danger_info_s &DangerInfo);
	void SenseSurroundingPlayers(danger_info_s &DangerInfo);
	void SenseSurroundingSounds(danger_info_s &DangerInfo);
	void SenseHit(danger_info_s &DangerInfo);
	EAttackType ClassifyAttack(const char *szAttackName);

	// Dragonball functions
	int SearchNearestWPForDBSpot(void);
	Vector *GetDBGatherSpot(void);

	// Player stats stuff
	int GetKi(void)					{ return pev->fuser4; };
	void SetKi(int NewKi)			{ pev->fuser4 = NewKi; };
	int GetHealth(void)				{ return pev->health; };
	void SetHealth(int NewHealth)	{ pev->health = NewHealth; };

	float GetDistance(Vector v_other) { return (v_other - pev->origin).Length(); };
	float GetDistance(CBaseEntity *pEnt) { return GetDistance(pEnt->pev->origin); };

    // Bots should return FALSE for this, they can't receive NET messages
    virtual BOOL IsNetClient( void ) { return FALSE; };
	virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType);
	virtual bool IsSPBot(void) { return false; };
};

// Bot used for coop and hazard course
class CSPBot: public CBot
{
	char m_szChatFileToUse[128];
	bool m_bChat;
	int m_iStartPL;
	int m_iExtraPLGainForKiller;
	int m_iStartingForm;
	int m_iLives;
	char m_szDeathTrigger[32];
	bool m_bRespawning;
	int m_iBotMaxHealth;
	int m_iBotMaxSpeed;

public:
	CBaseEntity *m_pSpawnPoint;

	void Spawn(void);
	virtual void BotThink(void);
	virtual bool IsSPBot(void) { return true; };
	virtual void BotKilled(entvars_t *pAttacker);
	virtual void CreateInit(short Team, short Class, short Skill, const char *szPerso, short SBotVal,
							EBotDBType BotDBType);

	static CSPBot *CreateMe(byte Team, byte Class, int StartPL, int StartForm, int MaxHealth,
							int MaxSpeed, const char *PersoFile, int Lifes,
							int ExtraPLGainForKiller, bool Chat, const char *ChatFile,
							const char *DeathTrigger, const char *Name, byte Skill,
							CBaseEntity *pSpawnPoint);
};

class CBaseTeacherTask;

// Bot Teacher, used for hazard course
class CBotTeacher: public CSPBot
{
	EBotTeacherType m_eBotTeacherType;
	char m_szEndTrigger[32];
	char m_szScriptFile[256];
	CBaseEntity *m_pSpawnPoint;
	TLinkedList<CBaseTeacherTask *> m_TaskList;

public:
	void CreateInit(EBotTeacherType BotTeacherType);
	virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) { return 0; };
	virtual void CheckNecessarilyThings(void) {};
	virtual void CheckBotInternalStatus(void) {};
	virtual void CheckBotMiscThings(void) {};
	virtual void KillCurrentTask(void);
	void MakeTeacherTaskList(void);

	static CBotTeacher *CreateMe(EBotTeacherType BotTeacherType, const char *ScriptFile,
								 const char *EndTrigger, CBaseEntity *pSpawnPoint);
};

class CBotManager
{
	enum ESpawnState
	{
		SPAWN_IDLE,
		SPAWN_NEED_TO_RESPAWN,
		SPAWN_IS_RESPAWNING,
		SPAWN_NOTHING
	};

	struct kicked_bot_s
	{
		byte Skill;
		char szPersonality[32];
		short BotClass;
		short BotTeam;
		char szBotName[BOT_NAME_LEN + 1];
		short SBotType;
		EBotDBType BotDBType;
		CBasePlayer *pLastEnemy;
		float KickTime;

		// construction
		kicked_bot_s(void){};
		kicked_bot_s(byte CurrentSkill, const char *szPerso, short Class, short Team,
				     const char *PrevName, short SType, CBasePlayer *pPrevEnemy,
					 EBotDBType DBType) : Skill(CurrentSkill), BotClass(Class), BotTeam(Team),
				     SBotType(SType), BotDBType(DBType), KickTime(gpGlobals->time)
		{ 
			strncpy(szBotName, PrevName, BOT_NAME_LEN-1);
			szBotName[BOT_NAME_LEN] = 0;
			strcpy(szPersonality, szPerso);
		};
	};

	struct bot_fill_data_s
	{
		short Skill, Team, Class, SBotType;
		char szPersonality[32];
		EBotDBType BotDBType;
		bot_fill_data_s(short S, short T, short C, const char *P, EBotDBType DB,
						short ST = -1) : Skill(S), Team(T), Class(C), SBotType(ST),
										 BotDBType(DB) { strcpy(szPersonality, P); };
	};

	// List to store kicked bots, so we can restore them on mapchange
	TLinkedList<kicked_bot_s*> *m_pKickedBots;

	// Misc vars
	byte m_bNumberOfBots;
	byte m_bPrevNumberBots;
	float m_fBalanceTeamsDelay;
	edict_t *pHost;
	bool m_bDBMode; // Are we playing capture the dragonball?
	bool m_bSendPersoInfoToClient;
	bool m_bSendDBInfoToClient;
		
	// Fill variabeles
	float m_fAddBotDelay;
	bool m_bFillingBots;
	TLinkedList<bot_fill_data_s *> m_BotsToAddList;
	short m_sMinBots;
	short m_sMaxBots;
	float m_fRestoreBotsTime;
	bool m_bRestoringBots;
	bool m_bSpawnTimeReset;

	// Super bot stuff
	float m_fOldPLCatchupValue;
	byte m_bSBotCount;
	float m_fSBotRulesDelay;

	float m_fBotCFGPauseTime;
	FILE *m_pBotCFGFile;
	bool m_bNeedToReadCFGFile;
	short m_sBotNameCount;
	char m_BotNames[MAX_BOT_NAMES][BOT_NAME_LEN+1];

	// CVar stuff
	static cvar_t sv_num_bots;
	static cvar_t sv_bot_skill;
	static cvar_t sv_botsshoot;
	static cvar_t sv_botssearchenemies;
	static cvar_t sv_observebots;
	static cvar_t sv_idlebots;
	static cvar_t sv_botsusediscs;
	static cvar_t sv_botsmove;
	static cvar_t sv_botschat;
	static cvar_t sv_botsbalanceteams;
	bool m_bCheckNumBotsCVar;
	float m_fCheckSkillCVarDelay;
	short m_sPrevSkillCVarVal;

	// Variabeles for calculating the frame time
    int m_iMSecNum;
    float m_fMSecDel;
    float m_fMSecVal;
    float m_fPrevPlayermoveTime;

	friend class CBot;
	friend class CSPBot;

public:
	byte m_bNumberOfPlayers;
	bool m_bFoundBotCommand;
    float m_fFrameTime;

	// General bot settings
	/*bool m_bBotsSearchEnemies;
	bool m_bBotsShoot;
	bool m_bBotsMove;
	bool m_bBotsUseDiscs;
	bool m_bObserverMode;
	bool m_bBotsAreIdle;
	bool m_bBalanceTeams;
	bool m_bShouldBotsChat;*/
	TLinkedList<bot_personality_s*> m_BotPersonalities;
	char m_szDefaultPersonality[32];
	bot_skill_s m_BotSkills[MAX_BOT_SKILLS];
	sbot_setting_s m_SBotSettings[MAX_SUPERBOT_TYPES];

	bool m_bHPCheat; // For handy testing :)
	bool m_bDrawWaypointGoals;
	Vector m_vBotGoal;

	CBotManager(void);
	~CBotManager(void);

	void Think(void);
	void BotBalanceTeamsThink(void);

	void CalcMSec(void);
	float GetMSec(void)		{ return m_fMSecVal; };

/*
	bool BotsSearchEnemies(void)		{ return m_bBotsSearchEnemies; };
	bool BotsShoot(void)				{ return m_bBotsShoot; };
	bool BotsMove(void)					{ return m_bBotsMove; };
	bool BotsUseDiscs(void)				{ return m_bBotsUseDiscs; };
	bool ObserverMode(void)				{ return m_bObserverMode; };
	bool BotsAreIdle(void)				{ return m_bBotsAreIdle; };
	bool BotsBalanceTeams(void)			{ return m_bBalanceTeams; };
	bool ShouldBotsChat(void)			{ return m_bShouldBotsChat; };
*/

	bool BotsSearchEnemies(void)		{ return (sv_botssearchenemies.value != 0.0f); };
	bool BotsShoot(void)				{ return (sv_botsshoot.value != 0.0f); };
	bool BotsMove(void)					{ return (sv_botsmove.value != 0.0f); };
	bool BotsUseDiscs(void)				{ return (sv_botsusediscs.value != 0.0f); };
	bool ObserverMode(void)				{ return (sv_observebots.value != 0.0f); };
	bool BotsAreIdle(void)				{ return (sv_idlebots.value != 0.0f); };
	bool BotsBalanceTeams(void)			{ return (sv_botsbalanceteams.value != 0.0f); };
	bool ShouldBotsChat(void)			{ return (sv_botschat.value != 0.0f); };

	// Note: the following functions have effect on ALL bots
	void ChangeBotBoolSetting(bool &Setting, short Choice, const char *VarName);
	void ChangePersoPercentSetting(short bot_personality_s::*Setting, short Choice, 
								   const char *VarName);
	void ChangeBotSkill(short Skill);
	void ChangeAggressionLevel(EAggressionLevel AggrLevel);
	void ChangeSBotLaserPercent(short Percent);
	void ChangeBotFlyPercent(short Percent);

	void SetMinBots(short MinBots);
	void SetMaxBots(short MaxBots);

	void KickBots(short Team); // if team == -1 kick all bots
	void KickLastBot(bool DisplayMsg);
	void KickBot(const char *BotName);
	void DisconnectPlayer(edict_t *pEntity);

	void CreateBot(short Team, short Class, const char *Name, short Skill, const char *szPerso,
				   EBotDBType BotDBType);
	void CreateSuperBot(short Class, const char *Name, const char *szPerso, short SBotType,
						EBotDBType BotDBType);
 
	void RestoreBots(void); // Add the kicked bots again because of a map change

	void BotFillThink(void);
	void BotFill(byte Count, short Team, short Class, short Skill, const char *szPerso,
				 EBotDBType BotDBType);
	void SBotFill(byte Count, short Class, const char *szPerso, short Type, EBotDBType BotDBType);
	void DeleteBotsToAddList(void);

	void PlayerConnect(const char *pszAddress);

	// Superbot stuff
	void RestoreOldPLCatchupValue(void) { CVAR_SET_FLOAT("mp_plcatchup", m_fOldPLCatchupValue); };
	void StoreOldPLCatchupValue(void) { m_fOldPLCatchupValue = CVAR_GET_FLOAT("mp_plcatchup"); };
	bool AreSBotsIngame(void) { return (m_bSBotCount > 0); };
	void SBotRulesThink(void);

	void LoadChatTexts(void);
	void LoadBotNames(void);
	void PickBotName(char *szOutput);
	void CreateBotPersonalities(void);
	void MakeRandomPersonalities(void);
	void CreateSuperbotSettings(void);
	void InitSkillDate(void);
	void CreateSkillData(void);
	void BotCFGFileThink(void);
	void OpenBotCFGFile(void);

	bot_personality_s *GetRandPerso(void);
	bot_personality_s *FindPersoByName(const char *szName);
	void SendPersoInfoToClient(void);

	void MapInit(void);
	void GameInit(void);
	void MapFree(void);

	// Dragonball funcs
	void SetDBMode(void) { m_bDBMode = true; };
	bool IsDBMode(void) { return m_bDBMode; };

	CBot *GetBotFromEdict(edict_t *pEdict);
	CBot *GetLastBot(void);
	CBasePlayer *GetHost(void)	{ return (!IS_DEDICATED_SERVER()) ? (CBasePlayer *)CBaseEntity::Instance(pHost) : NULL; };
	void SetHost(edict_t *pEntity) { pHost = pEntity; };

	bool HandleBotCommands(edict_t *pEntity); // returns true when found a bot command
	void HandleNonDedBotCommands(void);
	void HandleBotDebugCommands(void);
};

void HandleNormalBotCommands(void);

#define PRINT_BOT_AI // set this on to print out some special AI events(works only if _DEBUG is set)

#ifdef PRINT_BOT_AI

#define PrintAI(Text) lcout << Text

#else

#define PrintAI(Text) (void)0

#endif

#endif
