/*
*
*   This program is free software; you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation; either version 2 of the License, or (at
*   your option) any later version.
*
*   This program is distributed in the hope that it will be useful, but
*   WITHOUT ANY WARRANTY; without even the implied warranty of
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*   General Public License for more details.
*
*   You should have received a copy of the GNU General Public License
*   along with this program; if not, write to the Free Software Foundation,
*   Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*
*   In addition, as a special exception, the author gives permission to
*   link the code of this program with the Half-Life Game Engine ("HL
*   Engine") and Modified Game Libraries ("MODs") developed by Valve,
*   L.L.C ("Valve").  You must obey the GNU General Public License in all
*   respects for all of the code used other than the HL Engine and MODs
*   from Valve.  If you modify this file, you may extend this exception
*   to your version of the file, but you are not obligated to do so.  If
*   you do not wish to do so, delete this exception statement from your
*   version.
*
*/

#pragma once

#include "bot/cs_gamestate.h"
#include "bot/cs_bot_manager.h"
#include "bot/cs_bot_chatter.h"

const int MAX_BUY_WEAPON_PRIMARY   = 13;
const int MAX_BUY_WEAPON_SECONDARY = 3;

enum
{
	BOT_PROGGRESS_DRAW = 0, // draw status bar progress
	BOT_PROGGRESS_START,    // init status bar progress
	BOT_PROGGRESS_HIDE,     // hide status bar progress
};

extern int _navAreaCount;
extern int _currentIndex;

class CCSBot;
class BotChatterInterface;

class BotState
{
public:
	virtual void OnEnter(CCSBot *me) {}
	virtual void OnUpdate(CCSBot *me) {}
	virtual void OnExit(CCSBot *me) {}
	virtual const char *GetName() const = 0;
};

class IdleState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual const char *GetName() const { return "Idle"; }
};

class HuntState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "Hunt"; }

	void ClearHuntArea() { m_huntArea = nullptr; }

private:
	CNavArea *m_huntArea;
};

class AttackState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "Attack"; }

	void SetCrouchAndHold(bool crouch) { m_crouchAndHold = crouch; }
	void StopAttacking(CCSBot *me);

protected:
	enum DodgeStateType
	{
		STEADY_ON,
		SLIDE_LEFT,
		SLIDE_RIGHT,
		JUMP,
		NUM_ATTACK_STATES
	} m_dodgeState;

	float m_nextDodgeStateTimestamp;
	CountdownTimer m_repathTimer;
	float m_scopeTimestamp;
	bool m_haveSeenEnemy;
	bool m_isEnemyHidden;
	float m_reacquireTimestamp;
	float m_shieldToggleTimestamp;
	bool m_shieldForceOpen;
	float m_pinnedDownTimestamp;
	bool m_crouchAndHold;
	bool m_didAmbushCheck;
	bool m_dodge;
	bool m_firstDodge;
	bool m_isCoward;
	CountdownTimer m_retreatTimer;
};

class InvestigateNoiseState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "InvestigateNoise"; }

private:
	void AttendCurrentNoise(CCSBot *me);
	Vector m_checkNoisePosition;
};

class BuyState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "Buy"; }

private:
	bool m_isInitialDelay;
	int m_prefRetries;
	int m_prefIndex;
	int m_retries;
	bool m_doneBuying;
	bool m_buyDefuseKit;
	bool m_buyGrenade;
	bool m_buyShield;
	bool m_buyPistol;
};

class MoveToState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "MoveTo"; }

	void SetGoalPosition(const Vector &pos) { m_goalPosition = pos; }
	void SetRouteType(RouteType route) { m_routeType = route; }

private:
	Vector m_goalPosition;
	RouteType m_routeType;
	bool m_radioedPlan;
	bool m_askedForCover;
};

class FetchBombState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual const char *GetName() const { return "FetchBomb"; }
};

class PlantBombState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "PlantBomb"; }
};

class DefuseBombState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "DefuseBomb"; }
};

class HideState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "Hide"; }

public:
	void SetHidingSpot(const Vector &pos) { m_hidingSpot = pos; }
	const Vector &GetHidingSpot() const { return m_hidingSpot; }

	void SetSearchArea(CNavArea *area) { m_searchFromArea = area; }
	void SetSearchRange(float range) { m_range = range; }

	void SetDuration(float time) { m_duration = time; }
	void SetHoldPosition(bool hold) { m_isHoldingPosition = hold; }

	bool IsAtSpot() const { return m_isAtSpot; }

private:
	CNavArea *m_searchFromArea;
	float m_range;

	Vector m_hidingSpot;
	bool m_isAtSpot;
	float m_duration;
	bool m_isHoldingPosition;
	float m_holdPositionTime;
	bool m_heardEnemy;

	float m_firstHeardEnemyTime;
	int m_retry;
	Vector m_leaderAnchorPos;
};

class EscapeFromBombState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "EscapeFromBomb"; }
};

class FollowState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "Follow"; }

	void SetLeader(CBasePlayer *pLeader) { m_leader = pLeader; }

private:
	void ComputeLeaderMotionState(float leaderSpeed);

	EntityHandle<CBasePlayer> m_leader;
	Vector m_lastLeaderPos;
	bool m_isStopped;
	float m_stoppedTimestamp;

	enum LeaderMotionStateType
	{
		INVALID,
		STOPPED,
		WALKING,
		RUNNING

	} m_leaderMotionState;

	IntervalTimer m_leaderMotionStateTime;

	bool m_isSneaking;
	float m_lastSawLeaderTime;
	CountdownTimer m_repathInterval;

	IntervalTimer m_walkTime;
	bool m_isAtWalkSpeed;

	float m_waitTime;
	CountdownTimer m_idleTimer;
};

class UseEntityState: public BotState
{
public:
	virtual void OnEnter(CCSBot *me);
	virtual void OnUpdate(CCSBot *me);
	virtual void OnExit(CCSBot *me);
	virtual const char *GetName() const { return "UseEntity"; }

	void SetEntity(CBaseEntity *pEntity) { m_entity = pEntity; }

private:
	EntityHandle<CBaseEntity> m_entity;
};

// The Counter-strike Bot
class CCSBot: public CBot
{
public:
	CCSBot();																											// constructor initializes all values to zero
	virtual BOOL TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType);		// invoked when injured by something (EXTEND) - returns the amount of damage inflicted
	virtual void Killed(entvars_t *pevAttacker, int iGib);																// invoked when killed (EXTEND)
	virtual void RoundRespawn();
	virtual void Blind(float duration, float holdTime, float fadeTime, int alpha = 255);								// player blinded by a flashbang
	virtual void OnTouchingWeapon(CWeaponBox *box);																		// invoked when in contact with a CWeaponBox

	virtual bool Initialize(const BotProfile *profile);						// (EXTEND) prepare bot for action
	virtual void SpawnBot();												// (EXTEND) spawn the bot into the game

	virtual void Upkeep();													// lightweight maintenance, invoked frequently
	virtual void Update();													// heavyweight algorithms, invoked less often

	virtual void Walk();
	virtual bool Jump(bool mustJump = false);								// returns true if jump was started

	virtual void OnEvent(GameEventType event, CBaseEntity *pEntity = nullptr, CBaseEntity *pOther = nullptr);		// invoked when event occurs in the game (some events have NULL entity)

	#define CHECK_FOV true
	virtual bool IsVisible(const Vector *pos, bool testFOV = false) const;											// return true if we can see the point
	virtual bool IsVisible(CBasePlayer *pPlayer, bool testFOV = false, unsigned char *visParts = nullptr) const;	// return true if we can see any part of the player

	virtual bool IsEnemyPartVisible(VisiblePartType part) const;													// if enemy is visible, return the part we see for our current enemy

public:
	void Disconnect();

	// behavior properties
	float GetCombatRange() const;
	bool IsRogue() const;					// return true if we dont listen to teammates or pursue scenario goals
	void SetRogue(bool rogue);
	bool IsHurrying() const;				// return true if we are in a hurry
	void Hurry(float duration);				// force bot to hurry
	bool IsSafe() const;					// return true if we are in a safe region
	bool IsWellPastSafe() const;			// return true if it is well past the early, "safe", part of the round
	bool IsEndOfSafeTime() const;			// return true if we were in the safe time last update, but not now
	float GetSafeTimeRemaining() const;		// return the amount of "safe time" we have left
	float GetSafeTime() const;				// return what we think the total "safe time" for this map is
	//bool IsUnhealthy() const;				// returns true if bot is low on health

	// behaviors
	void Idle();
	void Hide(CNavArea *searchFromArea = nullptr, float duration = -1.0f, float hideRange = 750.0f, bool holdPosition = false);						// DEPRECATED: Use TryToHide() instead
	#define USE_NEAREST true
	bool TryToHide(CNavArea *searchFromArea = nullptr, float duration = -1.0f, float hideRange = 750.0f, bool holdPosition = false, bool useNearest = false);			// try to hide nearby, return false if cannot

	void Hide(const Vector *hidingSpot, float duration = -1.0f, bool holdPosition = false);			// move to the given hiding place
	bool IsHiding() const;																			// returns true if bot is currently hiding
	bool IsAtHidingSpot() const;																	// return true if we are hiding and at our hiding spot
	bool TryToRetreat();																			// retreat to a nearby hiding spot, away from enemies

	void Hunt();
	bool IsHunting() const;								// returns true if bot is currently hunting

	void Attack(CBasePlayer *victim);
	void FireWeaponAtEnemy();							// fire our active weapon towards our current enemy
	void StopAttacking();
	bool IsAttacking() const;							// returns true if bot is currently engaging a target

	void MoveTo(const Vector *pos, RouteType route = SAFEST_ROUTE); // move to potentially distant position
	bool IsMovingTo() const;							// return true if we are in the MoveTo state

	void PlantBomb();

	void FetchBomb();									// bomb has been dropped - go get it
	bool NoticeLooseBomb() const;						// return true if we noticed the bomb on the ground or on radar
	bool CanSeeLooseBomb() const;						// return true if we directly see the loose bomb
	bool IsCarryingBomb() const;

	void DefuseBomb();
	bool IsDefusingBomb() const;						// returns true if bot is currently defusing the bomb
	bool CanSeePlantedBomb() const;						// return true if we directly see the planted bomb

	void EscapeFromBomb();
	bool IsEscapingFromBomb() const;					// return true if we are escaping from the bomb

	void RescueHostages();
	void UseEntity(CBaseEntity *pEntity);				// use the entity

	bool IsBuying() const;

	void Panic(CBasePlayer *pEnemy);					// look around in panic
	void Follow(CBasePlayer *pPlayer);					// begin following given Player
	void ContinueFollowing();							// continue following our leader after finishing what we were doing
	void StopFollowing();								// stop following
	bool IsFollowing() const;							// return true if we are following someone (not necessarily in the follow state)
	CBasePlayer *GetFollowLeader();						// return the leader we are following
	float GetFollowDuration() const;					// return how long we've been following our leader
	bool CanAutoFollow() const;							// return true if we can auto-follow

	bool IsNotMoving() const;							// return true if we are currently standing still

	void AimAtEnemy();									// point our weapon towards our enemy
	void StopAiming();									// stop aiming at enemy
	bool IsAimingAtEnemy() const;						// returns true if we are trying to aim at an enemy

	bool IsSurprised() const;							// return true if we are "surprised"
	float GetSurpriseDelay() const;
	void ClearSurpriseDelay();

	float GetStateTimestamp() const;					// get time current state was entered

	bool IsDoingScenario() const;						// return true if we will do scenario-related tasks

	// scenario / gamestate
	CSGameState *GetGameState();						// return an interface to this bot's gamestate
	const CSGameState *GetGameState() const;			// return an interface to this bot's gamestate

	bool IsAtBombsite();								// return true if we are in a bomb planting zone
	bool GuardRandomZone(float range = 500.0f);			// pick a random zone and hide near it

	bool IsBusy() const;								// return true if we are busy doing something important

	// high-level tasks
	enum TaskType
	{
		SEEK_AND_DESTROY,
		PLANT_BOMB,
		FIND_TICKING_BOMB,
		DEFUSE_BOMB,
		GUARD_TICKING_BOMB,
		GUARD_BOMB_DEFUSER,
		GUARD_LOOSE_BOMB,
		GUARD_BOMB_ZONE,
		ESCAPE_FROM_BOMB,
		HOLD_POSITION,
		FOLLOW,
		VIP_ESCAPE,
		GUARD_VIP_ESCAPE_ZONE,
		COLLECT_HOSTAGES,
		RESCUE_HOSTAGES,
		GUARD_HOSTAGES,
		GUARD_HOSTAGE_RESCUE_ZONE,
		MOVE_TO_LAST_KNOWN_ENEMY_POSITION,
		MOVE_TO_SNIPER_SPOT,
		SNIPING,

		NUM_TASKS
	};

	void SetTask(TaskType task, CBaseEntity *pEntity = nullptr);				// set our current "task"
	TaskType GetTask() const;
	CBaseEntity *GetTaskEntity();

	// behavior modifiers
	enum DispositionType
	{
		ENGAGE_AND_INVESTIGATE,	// engage enemies on sight and investigate enemy noises
		OPPORTUNITY_FIRE,		// engage enemies on sight, but only look towards enemy noises, dont investigate
		SELF_DEFENSE,			// only engage if fired on, or very close to enemy
		IGNORE_ENEMIES,			// ignore all enemies - useful for ducking around corners, running away, etc

		NUM_DISPOSITIONS
	};

	void SetDisposition(DispositionType disposition);			// define how we react to enemies
	DispositionType GetDisposition() const;						// return enum describing current disposition

	void IgnoreEnemies(float duration);							// ignore enemies for a short duration

	enum MoraleType
	{
		TERRIBLE = -3,
		BAD = -2,
		NEGATIVE = -1,
		NEUTRAL = 0,
		POSITIVE = 1,
		GOOD = 2,
		EXCELLENT = 3,
	};

	MoraleType GetMorale() const;								// return enum describing current morale
	void IncreaseMorale();
	void DecreaseMorale();

	// listening for noises
	bool IsNoiseHeard() const;									// return true if we have heard a noise
	bool ShouldInvestigateNoise(float *retNoiseDist = nullptr);
	void InvestigateNoise();									// investigate recent enemy noise
	const Vector *GetNoisePosition() const;						// return position of last heard noise, or NULL if none heard
	CNavArea *GetNoiseArea() const;								// return area where noise was heard
	void ForgetNoise();											// clear the last heard noise
	bool CanSeeNoisePosition() const;							// return true if we directly see where we think the noise came from
	float GetNoiseRange() const;								// return approximate distance to last noise heard

	bool CanHearNearbyEnemyGunfire(float range = -1.0f) const;	// return true if we hear nearby threatening enemy gunfire within given range (-1 == infinite)
	PriorityType GetNoisePriority() const;						// return priority of last heard noise

	// radio and chatter
	void SendRadioMessage(GameEventType event);					// send voice chatter
	BotChatterInterface *GetChatter();							// return an interface to this bot's chatter system
	bool RespondToHelpRequest(CBasePlayer *them, Place place, float maxRange = -1.0f);	// decide if we should move to help the player, return true if we will
	void StartVoiceFeedback(float duration);
	bool IsUsingVoice() const;									// new-style "voice" chatter gets voice feedback

	// enemies
	// BOTPORT: GetEnemy() collides with GetEnemy() in CBaseEntity - need to use different nomenclature

	void SetEnemy(CBasePlayer *pEnemy);							// set given player as our current enemy
	CBasePlayer *GetEnemy();
	int GetNearbyEnemyCount() const;							// return max number of nearby enemies we've seen recently
	unsigned int GetEnemyPlace() const;							// return location where we see the majority of our enemies
	bool CanSeeBomber() const;									// return true if we can see the bomb carrier
	CBasePlayer *GetBomber() const;

	int GetNearbyFriendCount() const;							// return number of nearby teammates
	CBasePlayer *GetClosestVisibleFriend() const;				// return the closest friend that we can see
	CBasePlayer *GetClosestVisibleHumanFriend() const;			// return the closest human friend that we can see

	bool IsOutnumbered() const;									// return true if we are outnumbered by enemies
	int OutnumberedCount() const;								// return number of enemies we are outnumbered by

	#define ONLY_VISIBLE_ENEMIES true
	CBasePlayer *GetImportantEnemy(bool checkVisibility = false) const;			// return the closest "important" enemy for the given scenario (bomb carrier, VIP, hostage escorter)

	void UpdateReactionQueue();									// update our reaction time queue
	CBasePlayer *GetRecognizedEnemy();							// return the most dangerous threat we are "conscious" of
	bool IsRecognizedEnemyReloading();							// return true if the enemy we are "conscious" of is reloading
	bool IsRecognizedEnemyProtectedByShield();					// return true if the enemy we are "conscious" of is hiding behind a shield
	float GetRangeToNearestRecognizedEnemy();					// return distance to closest enemy we are "conscious" of

	CBasePlayer *GetAttacker() const;							// return last enemy that hurt us
	float GetTimeSinceAttacked() const;							// return duration since we were last injured by an attacker
	float GetFirstSawEnemyTimestamp() const;					// time since we saw any enemies
	float GetLastSawEnemyTimestamp() const;
	float GetTimeSinceLastSawEnemy() const;
	float GetTimeSinceAcquiredCurrentEnemy() const;
	bool HasNotSeenEnemyForLongTime() const;					// return true if we haven't seen an enemy for "a long time"
	const Vector &GetLastKnownEnemyPosition() const;
	bool IsEnemyVisible() const;								// is our current enemy visible
	float GetEnemyDeathTimestamp() const;
	bool IsFriendInLineOfFire();								// return true if a friend is in our weapon's way
	bool IsAwareOfEnemyDeath() const;							// return true if we *noticed* that our enemy died
	int GetLastVictimID() const;								// return the ID (entindex) of the last victim we killed, or zero

	// navigation
	bool HasPath() const;
	void DestroyPath();

	float GetFeetZ() const;										// return Z of bottom of feet

	enum PathResult
	{
		PROGRESSING,	// we are moving along the path
		END_OF_PATH,	// we reached the end of the path
		PATH_FAILURE,	// we failed to reach the end of the path
	};
	#define NO_SPEED_CHANGE false
	PathResult UpdatePathMovement(bool allowSpeedChange = true);				// move along our computed path - if allowSpeedChange is true, bot will walk when near goal to ensure accuracy

	bool AStarSearch(CNavArea *startArea, CNavArea *goalArea);					// find shortest path from startArea to goalArea - don't actually buid the path
	bool ComputePath(CNavArea *goalArea, const Vector *goal, RouteType route);	// compute path to goal position
	bool StayOnNavMesh();
	CNavArea *GetLastKnownArea() const;									// return the last area we know we were inside of
	const Vector &GetPathEndpoint() const;								// return final position of our current path
	float GetPathDistanceRemaining() const;								// eturn estimated distance left to travel along path
	void ResetStuckMonitor();
	bool IsAreaVisible(CNavArea *area) const;							// is any portion of the area visible to this bot
	const Vector &GetPathPosition(int numpath) const;
	bool GetSimpleGroundHeightWithFloor(const Vector *pos, float *height, Vector *normal = nullptr);	// find "simple" ground height, treating current nav area as part of the floor

	Place GetPlace() const;												// get our current radio chatter place

	bool IsUsingLadder() const;											// returns true if we are in the process of negotiating a ladder
	void GetOffLadder();

	void SetGoalEntity(CBaseEntity *pEntity);

	template <typename T = CBaseEntity>
	T *GetGoalEntity();

	bool IsNearJump() const;											// return true if nearing a jump in the path
	float GetApproximateFallDamage(float height) const;					// return how much damage will will take from the given fall height

	void ForceRun(float duration);										// force the bot to run if it moves for the given duration
	void Wiggle();														// random movement, for getting un-stuck

	bool IsFriendInTheWay(const Vector *goalPos) const;					// return true if a friend is between us and the given position
	void FeelerReflexAdjustment(Vector *goalPosition);					// do reflex avoidance movements if our "feelers" are touched

	// looking around
	void SetLookAngles(float yaw, float pitch);			// set our desired look angles
	void UpdateLookAngles();							// move actual view angles towards desired ones
	void UpdateLookAround(bool updateNow = false);		// update "looking around" mechanism
	void InhibitLookAround(float duration);				// block all "look at" and "looking around" behavior for given duration - just look ahead

	// TODO: Clean up notion of "forward angle" and "look ahead angle"
	void SetForwardAngle(float angle);					// define our forward facing
	void SetLookAheadAngle(float angle);				// define default look ahead angle

	// look at the given point in space for the given duration (-1 means forever)
	void SetLookAt(const char *desc, const Vector *pos, PriorityType pri, float duration = -1.0f, bool clearIfClose = false, float angleTolerance = 5.0f);
	void ClearLookAt();												// stop looking at a point in space and just look ahead
	bool IsLookingAtSpot(PriorityType pri = PRIORITY_LOW) const;	// return true if we are looking at spot with equal or higher priority
	bool IsViewMoving(float angleVelThreshold = 1.0f) const;		// returns true if bot's view angles are rotating (not still)

	const Vector &GetEyePosition() const
	{
		m_eyePos = pev->origin + pev->view_ofs;
		return m_eyePos;
	}
	float ComputeWeaponSightRange();					// return line-of-sight distance to obstacle along weapon fire ray
	bool IsSignificantlyCloser(const CBasePlayer *testPlayer, const CBasePlayer *referencePlayer) const;	// return true if testPlayer is significantly closer than referencePlayer

	// approach points
	void ComputeApproachPoints();						// determine the set of "approach points" representing where the enemy can enter this region
	void UpdateApproachPoints();						// recompute the approach point set if we have moved far enough to invalidate the current ones
	void ClearApproachPoints();
	void DrawApproachPoints();										// for debugging
	float GetHidingSpotCheckTimestamp(HidingSpot *spot) const;		// return time when given spot was last checked
	void SetHidingSpotCheckTimestamp(HidingSpot *spot);				// set the timestamp of the given spot to now

	// weapon query and equip
	#define MUST_EQUIP true
	void EquipBestWeapon(bool mustEquip = false);	// equip the best weapon we are carrying that has ammo
	void EquipPistol();								// equip our pistol
	void EquipKnife();								// equip our knife

	#define DONT_USE_SMOKE_GRENADE true
	bool EquipGrenade(bool noSmoke = false);		// equip a grenade, return false if we cant

	bool IsUsingKnife() const;						// returns true if we have knife equipped
	bool IsUsingPistol() const;						// returns true if we have pistol equipped
	bool IsUsingGrenade() const;					// returns true if we have grenade equipped
	bool IsUsingSniperRifle() const;				// returns true if using a "sniper" rifle
	bool IsUsingAWP() const;						// returns true if we have AWP equipped

	bool IsSniper() const;							// return true if we have a sniper rifle in our inventory
	bool IsSniping() const;							// return true if we are actively sniping (moving to sniper spot or settled in)
	bool IsUsingShotgun() const;					// returns true if using a shotgun
	bool IsUsingMachinegun() const;					// returns true if using the big 'ol machinegun
	void ThrowGrenade(const Vector *target);		// begin the process of throwing the grenade
	bool IsThrowingGrenade() const;					// return true if we are in the process of throwing a grenade
	bool HasGrenade() const;						// return true if we have a grenade in our inventory

	bool DoesActiveWeaponHaveSilencer() const;
	bool IsUsingHEGrenade() const;
	void StartRapidFire();
	void StopRapidFire();
	bool IsRapidFiring() const;

	enum ZoomType { NO_ZOOM, LOW_ZOOM, HIGH_ZOOM };
	ZoomType GetZoomLevel() const;					// return the current zoom level of our weapon

	bool AdjustZoom(float range);					// change our zoom level to be appropriate for the given range

	bool IsPrimaryWeaponEmpty() const;				// return true if primary weapon doesn't exist or is totally out of ammo
	bool IsPistolEmpty() const;						// return true if secondary weapon doesn't exist or is totally out of ammo

	int GetHostageEscortCount() const;
	void IncreaseHostageEscortCount();
	float GetRangeToFarthestEscortedHostage() const;
	void ResetWaitForHostagePatience();
	void ResetValues();								// reset internal data to initial state
	void BotDeathThink();
	CBasePlayer *FindNearbyPlayer();
	void AdjustSafeTime();							// called when enemy seen to adjust safe time for this round
	void EXPORT BotTouch(CBaseEntity *pOther);
	bool HasAnyAmmo(CBasePlayerWeapon *weapon) const;

private:
	friend class CCSBotManager;

	// TODO: Get rid of these
	friend class AttackState;
	friend class BuyState;

	char m_name[64];						// copied from STRING(pev->netname) for debugging

	// behavior properties
	float m_combatRange;					// desired distance between us and them during gunplay
	mutable bool m_isRogue;					// if true, the bot is a "rogue" and listens to no-one
	mutable CountdownTimer m_rogueTimer;
	MoraleType m_morale;					// our current morale, based on our win/loss history
	bool m_diedLastRound;					// true if we died last round
	float m_safeTime;						// duration at the beginning of the round where we feel "safe"
	bool m_wasSafe;							// true if we were in the safe time last update
	NavRelativeDirType m_blindMoveDir;		// which way to move when we're blind
	bool m_blindFire;						// if true, fire weapon while blinded

	// TODO: implement through CountdownTimer
	float m_surpriseDelay;					// when we were surprised
	float m_surpriseTimestamp;

	bool m_isFollowing;						// true if we are following someone
	EntityHandle<CBasePlayer> m_leader;		// the ID of who we are following
	float m_followTimestamp;				// when we started following
	float m_allowAutoFollowTime;			// time when we can auto follow

	CountdownTimer m_hurryTimer;			// if valid, bot is in a hurry

	// instances of each possible behavior state, to avoid dynamic memory allocation during runtime
	IdleState m_idleState;
	HuntState m_huntState;
	AttackState m_attackState;
	InvestigateNoiseState m_investigateNoiseState;
	BuyState m_buyState;
	MoveToState m_moveToState;
	FetchBombState m_fetchBombState;
	PlantBombState m_plantBombState;
	DefuseBombState m_defuseBombState;
	HideState m_hideState;
	EscapeFromBombState m_escapeFromBombState;
	FollowState m_followState;
	UseEntityState m_useEntityState;

	// TODO: Allow multiple simultaneous state machines (look around, etc)
	void SetState(BotState *state);				// set the current behavior state
	BotState *m_state;							// current behavior state
	float m_stateTimestamp;						// time state was entered
	bool m_isAttacking;							// if true, special Attack state is overriding the state machine

	TaskType m_task;							// our current task
	EntityHandle<CBaseEntity> m_taskEntity;		// an entity used for our task

	// navigation
	Vector m_goalPosition;
	EHandle m_goalEntity;
	void MoveTowardsPosition(const Vector *pos);				// move towards position, independant of view angle
	void MoveAwayFromPosition(const Vector *pos);				// move away from position, independant of view angle
	void StrafeAwayFromPosition(const Vector *pos);				// strafe (sidestep) away from position, independant of view angle
	void StuckCheck();											// check if we have become stuck

	CNavArea *m_currentArea;					// the nav area we are standing on
	CNavArea *m_lastKnownArea;					// the last area we were in
	EntityHandle<CBasePlayer> m_avoid;			// higher priority player we need to make way for
	float m_avoidTimestamp;
	bool m_isJumpCrouching;
	bool m_isJumpCrouched;
	float m_jumpCrouchTimestamp;

	// path navigation data
	enum { MAX_PATH_LENGTH = 256 };
	struct ConnectInfo
	{
		CNavArea *area;				// the area along the path
		NavTraverseType how;		// how to enter this area from the previous one
		Vector pos;					// our movement goal position at this point in the path
		const CNavLadder *ladder;	// if "how" refers to a ladder, this is it
	}
	m_path[MAX_PATH_LENGTH];
	int m_pathLength;
	int m_pathIndex;
	float m_areaEnteredTimestamp;
	void BuildTrivialPath(const Vector *goal);									// build trivial path to goal, assuming we are already in the same area
	bool FindGrenadeTossPathTarget(Vector *pos);

	CountdownTimer m_repathTimer;												// must have elapsed before bot can pathfind again

	bool ComputePathPositions();												// determine actual path positions bot will move between along the path
	void SetupLadderMovement();
	void SetPathIndex(int newIndex);											// set the current index along the path
	void DrawPath();
	int FindOurPositionOnPath(Vector *close, bool local = false) const;												// compute the closest point to our current position on our path
	int FindPathPoint(float aheadRange, Vector *point, int *prevIndex = nullptr);									// compute a point a fixed distance ahead along our path.
	bool FindClosestPointOnPath(const Vector *worldPos, int startIndex, int endIndex, Vector *close) const;			// compute closest point on path to given point
	bool IsStraightLinePathWalkable(const Vector *goal) const;														// test for un-jumpable height change, or unrecoverable fall

	mutable CountdownTimer m_avoidFriendTimer;	// used to throttle how often we check for friends in our path
	mutable bool m_isFriendInTheWay;			// true if a friend is blocking our path
	CountdownTimer m_politeTimer;				// we'll wait for friend to move until this runs out
	bool m_isWaitingBehindFriend;				// true if we are waiting for a friend to move

	#define ONLY_JUMP_DOWN true
	bool DiscontinuityJump(float ground, bool onlyJumpDown = false, bool mustJump = false);					// check if we need to jump due to height change

	enum LadderNavState
	{
		APPROACH_ASCENDING_LADDER,		// prepare to scale a ladder
		APPROACH_DESCENDING_LADDER,		// prepare to go down ladder
		FACE_ASCENDING_LADDER,
		FACE_DESCENDING_LADDER,
		MOUNT_ASCENDING_LADDER,			// move toward ladder until "on" it
		MOUNT_DESCENDING_LADDER,		// move toward ladder until "on" it
		ASCEND_LADDER,					// go up the ladder
		DESCEND_LADDER,					// go down the ladder
		DISMOUNT_ASCENDING_LADDER,		// get off of the ladder
		DISMOUNT_DESCENDING_LADDER,		// get off of the ladder
		MOVE_TO_DESTINATION,			// dismount ladder and move to destination area
	}
	m_pathLadderState;
	bool m_pathLadderFaceIn;						// if true, face towards ladder, otherwise face away
	const CNavLadder *m_pathLadder;					// the ladder we need to use to reach the next area
	bool UpdateLadderMovement();					// called by UpdatePathMovement()
	NavRelativeDirType m_pathLadderDismountDir;		// which way to dismount
	float m_pathLadderDismountTimestamp;			// time when dismount started
	float m_pathLadderEnd;							// if ascending, z of top, if descending z of bottom
	void ComputeLadderEndpoint(bool isAscending);
	float m_pathLadderTimestamp;					// time when we started using ladder - for timeout check

	CountdownTimer m_mustRunTimer;					// if nonzero, bot cannot walk

	// game scenario mechanisms
	CSGameState m_gameState;

	// hostages mechanism
	byte m_hostageEscortCount;
	void UpdateHostageEscortCount();
	float m_hostageEscortCountTimestamp;
	bool m_isWaitingForHostage;
	CountdownTimer m_inhibitWaitingForHostageTimer;
	CountdownTimer m_waitForHostageTimer;

	// listening mechanism
	Vector m_noisePosition;				// position we last heard non-friendly noise
	float m_noiseTimestamp;				// when we heard it (can get zeroed)
	CNavArea *m_noiseArea;				// the nav area containing the noise
	float m_noiseCheckTimestamp;
	PriorityType m_noisePriority;		// priority of currently heard noise
	bool UpdateLookAtNoise();			// return true if we decided to look towards the most recent noise source
	bool m_isNoiseTravelRangeChecked;

	// "looking around" mechanism
	float m_lookAroundStateTimestamp;	// time of next state change
	float m_lookAheadAngle;				// our desired forward look angle
	float m_forwardAngle;				// our current forward facing direction
	float m_inhibitLookAroundTimestamp;	// time when we can look around again

	enum LookAtSpotState
	{
		NOT_LOOKING_AT_SPOT,	// not currently looking at a point in space
		LOOK_TOWARDS_SPOT,		// in the process of aiming at m_lookAtSpot
		LOOK_AT_SPOT,			// looking at m_lookAtSpot
		NUM_LOOK_AT_SPOT_STATES
	}
	m_lookAtSpotState;

	Vector m_lookAtSpot;						// the spot we're currently looking at
	PriorityType m_lookAtSpotPriority;
	float m_lookAtSpotDuration;					// how long we need to look at the spot
	float m_lookAtSpotTimestamp;				// when we actually began looking at the spot
	float m_lookAtSpotAngleTolerance;			// how exactly we must look at the spot
	bool m_lookAtSpotClearIfClose;				// if true, the look at spot is cleared if it gets close to us
	const char *m_lookAtDesc;					// for debugging
	void UpdateLookAt();
	void UpdatePeripheralVision();				// update enounter spot timestamps, etc
	float m_peripheralTimestamp;

	enum { MAX_APPROACH_POINTS = 16 };
	Vector m_approachPoint[MAX_APPROACH_POINTS];
	unsigned char m_approachPointCount;
	Vector m_approachPointViewPosition;			// the position used when computing current approachPoint set

	bool BendLineOfSight(const Vector *eye, const Vector *point, Vector *bend) const;	// "bend" our line of sight until we can see the target point. Return bend point, false if cant bend.
	bool FindApproachPointNearestPath(Vector *pos);										// find the approach point that is nearest to our current path, ahead of us

	bool m_isWaitingToTossGrenade;				// lining up throw
	CountdownTimer m_tossGrenadeTimer;			// timeout timer for grenade tossing

	SpotEncounter *m_spotEncounter;				// the spots we will encounter as we move thru our current area
	float m_spotCheckTimestamp;					// when to check next encounter spot

	// TODO: Add timestamp for each possible client to hiding spots
	enum { MAX_CHECKED_SPOTS = 64 };
	struct HidingSpotCheckInfo
	{
		HidingSpot *spot;
		float timestamp;
	}
	m_checkedHidingSpot[MAX_CHECKED_SPOTS];
	int m_checkedHidingSpotCount;

	// view angle mechanism
	float m_lookPitch;						// our desired look pitch angle
	float m_lookPitchVel;
	float m_lookYaw;						// our desired look yaw angle
	float m_lookYawVel;

	// aim angle mechanism
	mutable Vector m_eyePos;
	Vector m_aimOffset;						// current error added to victim's position to get actual aim spot
	Vector m_aimOffsetGoal;					// desired aim offset
	float m_aimOffsetTimestamp;				// time of next offset adjustment
	float m_aimSpreadTimestamp;				// time used to determine max spread as it begins to tighten up
	void SetAimOffset(float accuracy);		// set the current aim offset
	void UpdateAimOffset();					// wiggle aim error based on m_accuracy
	Vector m_aimSpot;						// the spot we are currently aiming to fire at

	// attack state data
	DispositionType m_disposition;				// how we will react to enemies
	CountdownTimer m_ignoreEnemiesTimer;		// how long will we ignore enemies
	mutable EntityHandle<CBasePlayer> m_enemy;	// our current enemy
	bool m_isEnemyVisible;						// result of last visibility test on enemy
	unsigned char m_visibleEnemyParts;			// which parts of the visible enemy do we see
	Vector m_lastEnemyPosition;					// last place we saw the enemy
	float m_lastSawEnemyTimestamp;
	float m_firstSawEnemyTimestamp;
	float m_currentEnemyAcquireTimestamp;
	float m_enemyDeathTimestamp;			// if m_enemy is dead, this is when he died
	bool m_isLastEnemyDead;					// true if we killed or saw our last enemy die
	int m_nearbyEnemyCount;					// max number of enemies we've seen recently
	unsigned int m_enemyPlace;				// the location where we saw most of our enemies

	struct WatchInfo
	{
		float timestamp;
		bool isEnemy;
	}
	m_watchInfo[MAX_CLIENTS];
	mutable EntityHandle<CBasePlayer> m_bomber;						// points to bomber if we can see him

	int m_nearbyFriendCount;										// number of nearby teammates
	mutable EntityHandle<CBasePlayer> m_closestVisibleFriend;		// the closest friend we can see
	mutable EntityHandle<CBasePlayer> m_closestVisibleHumanFriend;	// the closest human friend we can see

#ifdef REGAMEDLL_ADD
	IntervalTimer m_attentionInterval;								// time between attention checks
#endif

	CBasePlayer *m_attacker;				// last enemy that hurt us (may not be same as m_enemy)
	float m_attackedTimestamp;				// when we were hurt by the m_attacker

	int m_lastVictimID;						// the entindex of the last victim we killed, or zero
	bool m_isAimingAtEnemy;					// if true, we are trying to aim at our enemy
	bool m_isRapidFiring;					// if true, RunUpkeep() will toggle our primary attack as fast as it can
	IntervalTimer m_equipTimer;				// how long have we had our current weapon equipped
	bool DoEquip(CBasePlayerWeapon *pWeapon);	// equip the given item

	void ReloadCheck();						// reload our weapon if we must
	void SilencerCheck();					// use silencer

	float m_fireWeaponTimestamp;

	// reaction time system
	enum { MAX_ENEMY_QUEUE = 20 };
	struct ReactionState
	{
		// NOTE: player position & orientation is not currently stored separately
		EntityHandle<CBasePlayer> player;
		bool isReloading;
		bool isProtectedByShield;
	}
	m_enemyQueue[MAX_ENEMY_QUEUE];				// round-robin queue for simulating reaction times

	byte m_enemyQueueIndex;
	byte m_enemyQueueCount;
	byte m_enemyQueueAttendIndex;				// index of the timeframe we are "conscious" of

	CBasePlayer *FindMostDangerousThreat();		// return most dangerous threat in my field of view (feeds into reaction time queue)

	// stuck detection
	bool m_isStuck;
	float m_stuckTimestamp;					// time when we got stuck
	Vector m_stuckSpot;						// the location where we became stuck
	NavRelativeDirType m_wiggleDirection;
	float m_wiggleTimestamp;
	float m_stuckJumpTimestamp;				// time for next jump when stuck

	enum { MAX_VEL_SAMPLES = 5 };
	float m_avgVel[MAX_VEL_SAMPLES];
	int m_avgVelIndex;
	int m_avgVelCount;
	Vector m_lastOrigin;

	// chatter mechanism
	GameEventType m_lastRadioCommand;			// last radio command we recieved
	void RespondToRadioCommands();
	bool IsRadioCommand(GameEventType event) const;	// returns true if the radio message is an order to do something

	#define NO_FORCE false
	void EndVoiceFeedback(bool force = true);
	float m_lastRadioRecievedTimestamp;			// time we recieved a radio message
	float m_lastRadioSentTimestamp;				// time when we send a radio message
	EntityHandle<CBasePlayer> m_radioSubject;	// who issued the radio message
	Vector m_radioPosition;						// position referred to in radio message
	float m_voiceFeedbackStartTimestamp;
	float m_voiceFeedbackEndTimestamp;			// new-style "voice" chatter gets voice feedback
	BotChatterInterface m_chatter;

	// learn map mechanism
	const CNavNode *m_navNodeList;
	CNavNode *m_currentNode;
	NavDirType m_generationDir;
	NavAreaList::iterator m_analyzeIter;

	enum ProcessType
	{
		PROCESS_NORMAL,
		PROCESS_LEARN,
		PROCESS_ANALYZE_ALPHA,
		PROCESS_ANALYZE_BETA,
		PROCESS_SAVE,
	}
	m_processMode;
	CountdownTimer m_mumbleTimer;
	CountdownTimer m_booTimer;
	CountdownTimer m_relocateTimer;

	CNavNode *AddNode(const Vector *destPos, const Vector *normal, NavDirType dir, CNavNode *source);
	void StartLearnProcess();
	void UpdateLearnProcess();
	bool LearnStep();
	void StartAnalyzeAlphaProcess();
	void UpdateAnalyzeAlphaProcess();
	bool AnalyzeAlphaStep();
	void StartAnalyzeBetaProcess();
	void UpdateAnalyzeBetaProcess();
	bool AnalyzeBetaStep();
	void StartSaveProcess();
	void UpdateSaveProcess();
	void StartNormalProcess();

#ifdef REGAMEDLL_ADD
	bool IsNoticable(const CBasePlayer *pPlayer, unsigned char visibleParts) const;	// return true if we "notice" given player
#endif
};

// Inlines
inline float CCSBot::GetCombatRange() const
{
	return m_combatRange;
}

inline void CCSBot::SetRogue(bool rogue)
{
	m_isRogue = rogue;
}

inline void CCSBot::Hurry(float duration)
{
	m_hurryTimer.Start(duration);
}

inline float CCSBot::GetSafeTime() const
{
	return m_safeTime;
}

inline bool CCSBot::IsCarryingBomb() const
{
	return m_bHasC4;
}

inline bool CCSBot::IsFollowing() const
{
	return m_isFollowing;
}

inline CBasePlayer *CCSBot::GetFollowLeader()
{
	return m_leader;
}

inline float CCSBot::GetFollowDuration() const
{
	return gpGlobals->time - m_followTimestamp;
}

inline bool CCSBot::CanAutoFollow() const
{
	return (gpGlobals->time > m_allowAutoFollowTime);
}

inline void CCSBot::AimAtEnemy()
{
	m_isAimingAtEnemy = true;
}

inline void CCSBot::StopAiming()
{
	m_isAimingAtEnemy = false;
}

inline bool CCSBot::IsAimingAtEnemy() const
{
	return m_isAimingAtEnemy;
}

inline bool CCSBot::IsSurprised() const
{
	if (gpGlobals->time - m_surpriseTimestamp < 5.0f)
		return true;

	return false;
}

inline float CCSBot::GetSurpriseDelay() const
{
	if (!IsSurprised())
		return 0.0f;

	return m_surpriseDelay;
}

inline void CCSBot::ClearSurpriseDelay()
{
	m_surpriseDelay = 0.0f;
	m_surpriseTimestamp = 0.0f;
}

inline float CCSBot::GetStateTimestamp() const
{
	return m_stateTimestamp;
}

inline CSGameState *CCSBot::GetGameState()
{
	return &m_gameState;
}

inline const CSGameState *CCSBot::GetGameState() const
{
	return &m_gameState;
}

inline bool CCSBot::IsAtBombsite()
{
	return (m_signals.GetState() & SIGNAL_BOMB) == SIGNAL_BOMB;
}

inline CCSBot::MoraleType CCSBot::GetMorale() const
{
	return m_morale;
}

inline bool CCSBot::IsNoiseHeard() const
{
	if (m_noiseTimestamp <= 0.0f)
		return false;

	// primitive reaction time simulation - cannot "hear" noise until reaction time has elapsed
	if (gpGlobals->time - m_noiseTimestamp >= GetProfile()->GetReactionTime())
		return true;

	return false;
}

inline void CCSBot::SetTask(TaskType task, CBaseEntity *pEntity)
{
	m_task = task;
	m_taskEntity = pEntity;
}

inline CCSBot::TaskType CCSBot::GetTask() const
{
	return m_task;
}

inline CBaseEntity *CCSBot::GetTaskEntity()
{
	return m_taskEntity;
}

inline CNavArea *CCSBot::GetNoiseArea() const
{
	return m_noiseArea;
}

inline void CCSBot::ForgetNoise()
{
	m_noiseTimestamp = 0.0f;
}

inline PriorityType CCSBot::GetNoisePriority() const
{
	return m_noisePriority;
}

inline BotChatterInterface *CCSBot::GetChatter()
{
	return &m_chatter;
}

inline bool CCSBot::IsUsingVoice() const
{
	return (m_voiceFeedbackEndTimestamp != 0.0f);
}

inline CBasePlayer *CCSBot::GetEnemy()
{
	return m_enemy;
}

inline int CCSBot::GetNearbyEnemyCount() const
{
	return Q_min(GetEnemiesRemaining(), m_nearbyEnemyCount);
}

inline unsigned int CCSBot::GetEnemyPlace() const
{
	return m_enemyPlace;
}

inline bool CCSBot::CanSeeBomber() const
{
	return m_bomber.IsValid();
}

inline CBasePlayer *CCSBot::GetBomber() const
{
	return m_bomber;
}

inline int CCSBot::GetNearbyFriendCount() const
{
	return Q_min(GetFriendsRemaining(), m_nearbyFriendCount);
}

inline CBasePlayer *CCSBot::GetClosestVisibleFriend() const
{
	return m_closestVisibleFriend;
}

inline CBasePlayer *CCSBot::GetClosestVisibleHumanFriend() const
{
	return m_closestVisibleHumanFriend;
}

inline float CCSBot::GetTimeSinceAttacked() const
{
	return gpGlobals->time - m_attackedTimestamp;
}

inline float CCSBot::GetFirstSawEnemyTimestamp() const
{
	return m_firstSawEnemyTimestamp;
}

inline float CCSBot::GetLastSawEnemyTimestamp() const
{
	return m_lastSawEnemyTimestamp;
}

inline float CCSBot::GetTimeSinceLastSawEnemy() const
{
	return gpGlobals->time - m_lastSawEnemyTimestamp;
}

inline float CCSBot::GetTimeSinceAcquiredCurrentEnemy() const
{
	return gpGlobals->time - m_currentEnemyAcquireTimestamp;
}

inline const Vector &CCSBot::GetLastKnownEnemyPosition() const
{
	return m_lastEnemyPosition;
}

inline bool CCSBot::IsEnemyVisible() const
{
	return m_isEnemyVisible;
}

inline float CCSBot::GetEnemyDeathTimestamp() const
{
	return m_enemyDeathTimestamp;
}

inline int CCSBot::GetLastVictimID() const
{
	return m_lastVictimID;
}

inline bool CCSBot::HasPath() const
{
	return m_pathLength != 0;
}

inline void CCSBot::DestroyPath()
{
	m_pathLength = 0;
	m_pathLadder = nullptr;
}

inline CNavArea *CCSBot::GetLastKnownArea() const
{
	return m_lastKnownArea;
}

inline const Vector &CCSBot::GetPathEndpoint() const
{
	return m_path[m_pathLength - 1].pos;
}

inline const Vector &CCSBot::GetPathPosition(int numpath) const
{
	return m_path[numpath].pos;
}

inline bool CCSBot::IsUsingLadder() const
{
	return m_pathLadder != nullptr;
}

inline void CCSBot::SetGoalEntity(CBaseEntity *pEntity)
{
	m_goalEntity = pEntity;
}

template <typename T>
inline T *CCSBot::GetGoalEntity()
{
	return m_goalEntity.Get<T>();
}

inline void CCSBot::ForceRun(float duration)
{
	Run();
	m_mustRunTimer.Start(duration);
}

inline void CCSBot::SetLookAngles(float yaw, float pitch)
{
	m_lookYaw = yaw;
	m_lookPitch = pitch;
}

inline void CCSBot::SetForwardAngle(float angle)
{
	m_forwardAngle = angle;
}

inline void CCSBot::SetLookAheadAngle(float angle)
{
	m_lookAheadAngle = angle;
}

inline void CCSBot::ClearLookAt()
{
	//PrintIfWatched("ClearLookAt()\n");
	m_lookAtSpotState = NOT_LOOKING_AT_SPOT;
	m_lookAtDesc = nullptr;
}

inline bool CCSBot::IsLookingAtSpot(PriorityType pri) const
{
	if (m_lookAtSpotState != NOT_LOOKING_AT_SPOT && m_lookAtSpotPriority >= pri)
		return true;

	return false;
}

inline bool CCSBot::IsViewMoving(float angleVelThreshold) const
{
	if (m_lookYawVel < angleVelThreshold && m_lookYawVel > -angleVelThreshold &&
		m_lookPitchVel < angleVelThreshold && m_lookPitchVel > -angleVelThreshold)
	{
		return false;
	}

	return true;
}

inline bool CCSBot::IsSignificantlyCloser(const CBasePlayer *testPlayer, const CBasePlayer *referencePlayer) const
{
	if (!referencePlayer || !testPlayer)
		return true;

	float testDist = (pev->origin - testPlayer->pev->origin).Length();
	float referenceDist = (pev->origin - referencePlayer->pev->origin).Length();

	const float significantRangeFraction = 0.7f;
	if (testDist < referenceDist * significantRangeFraction)
		return true;

	return false;
}

inline void CCSBot::ClearApproachPoints()
{
	m_approachPointCount = 0;
}

inline bool CCSBot::IsThrowingGrenade() const
{
	return m_isWaitingToTossGrenade;
}

inline void CCSBot::StartRapidFire()
{
	m_isRapidFiring = true;
}

inline void CCSBot::StopRapidFire()
{
	m_isRapidFiring = false;
}

inline CCSBot::ZoomType CCSBot::GetZoomLevel() const
{
	if (m_iFOV > 60.0f)
		return NO_ZOOM;

	if (m_iFOV > 25.0f)
		return LOW_ZOOM;

	return HIGH_ZOOM;
}

inline int CCSBot::GetHostageEscortCount() const
{
	return m_hostageEscortCount;
}

inline void CCSBot::IncreaseHostageEscortCount()
{
	m_hostageEscortCount++;
}

inline void CCSBot::ResetWaitForHostagePatience()
{
	m_isWaitingForHostage = false;
	m_inhibitWaitingForHostageTimer.Invalidate();
}

inline float CCSBot::GetFeetZ() const
{
	if (IsCrouching())
	{
		const Vector crouch(0, 0, -StepHeight);
		return (pev->origin + crouch).z;
	}
	else
	{
		const Vector stand(0, 0, -HalfHumanHeight);
		return (pev->origin + stand).z;
	}
}

inline const Vector *CCSBot::GetNoisePosition() const
{
	if (m_noiseTimestamp > 0.0f)
		return &m_noisePosition;

	return nullptr;
}

inline bool CCSBot::IsAwareOfEnemyDeath() const
{
	if (GetEnemyDeathTimestamp() == 0.0f)
		return false;

	if (!m_enemy.IsValid())
		return true;

	if (!m_enemy->IsAlive() && gpGlobals->time - GetEnemyDeathTimestamp() > (1.0f - GetProfile()->GetSkill()))
		return true;

	return false;
}

inline bool CCSBot::IsNotMoving() const
{
	const float stillSpeed = 10.0f;
	return pev->velocity.IsLengthLessThan(stillSpeed);
}

inline bool CCSBot::HasAnyAmmo(CBasePlayerWeapon *weapon) const
{
	return (weapon->m_iClip != 0 || m_rgAmmo[weapon->m_iPrimaryAmmoType] > 0);
}

class CollectRetreatSpotsFunctor
{
public:
	CollectRetreatSpotsFunctor(CCSBot *me, float range)
	{
		m_me = me;
		m_count = 0;
		m_range = range;
	}

	enum { MAX_SPOTS = 256 };

	bool operator()(CNavArea *area)
	{
		// collect all the hiding spots in this area
		for (auto const spot : *area->GetHidingSpotList())
		{
			if (m_count >= MAX_SPOTS)
				break;

			// make sure hiding spot is in range
			if (m_range > 0.0f)
			{
				if ((*spot->GetPosition() - m_me->pev->origin).IsLengthGreaterThan(m_range))
					continue;
			}

			// if a Player is using this hiding spot, don't consider it
			if (IsSpotOccupied(m_me, spot->GetPosition()))
			{
				// player is in hiding spot
				// TODO: Check if player is moving or sitting still
				continue;
			}

			// don't select spot if an enemy can see it
			if (UTIL_IsVisibleToTeam(*spot->GetPosition() + Vector(0, 0, HalfHumanHeight), OtherTeam(m_me->m_iTeam)))
				continue;

			// don't select spot if it is closest to an enemy
			CBasePlayer *owner = UTIL_GetClosestPlayer(spot->GetPosition());
			if (owner && m_me->m_iTeam != owner->m_iTeam)
				continue;

			m_spot[m_count++] = spot->GetPosition();
		}

		// if we've filled up, stop searching
		if (m_count == MAX_SPOTS)
			return false;

		return true;
	}

	CCSBot *m_me;
	float m_range;

	const Vector *m_spot[MAX_SPOTS];
	int m_count;
};

class FarthestHostage
{
public:
	FarthestHostage(const CCSBot *me)
	{
		m_me = me;
		m_farRange = -1.0f;
	}
	bool operator()(CHostage *hostage)
	{
		if (hostage->IsFollowing(m_me))
		{
			float range = (hostage->Center() - m_me->pev->origin).Length();

			if (range > m_farRange)
			{
				m_farRange = range;
			}
		}

		return true;
	}

	const CCSBot *m_me;
	float m_farRange;
};

// Functor used with NavAreaBuildPath()
class PathCost
{
public:
	PathCost(CCSBot *pBot, RouteType route = SAFEST_ROUTE)
	{
		m_bot = pBot;
		m_route = route;
	}
	float operator()(CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder)
	{
		const float baseDangerFactor = 100.0f;

		// respond to the danger modulated by our aggression (even super-aggressives pay SOME attention to danger)
		float dangerFactor = (1.0f - (0.95f * m_bot->GetProfile()->GetAggression())) * baseDangerFactor;

		if (fromArea == nullptr)
		{
			if (m_route == FASTEST_ROUTE)
				return 0.0f;

			// first area in path, cost is just danger
			return dangerFactor * area->GetDanger(m_bot->m_iTeam - 1);
		}
		else if ((fromArea->GetAttributes() & NAV_JUMP) && (area->GetAttributes() & NAV_JUMP))
		{
			// cannot actually walk in jump areas - disallow moving from jump area to jump area
			return -1.0f;
		}
		else
		{
			// compute distance from previous area to this area
			float dist;
			if (ladder)
			{
				// ladders are slow to use
				const float ladderPenalty = 1.0f;
				dist = ladderPenalty * ladder->m_length;

				// if we are currently escorting hostages, avoid ladders (hostages are confused by them)
				//if (m_bot->GetHostageEscortCount())
				//	dist *= 100.0f;
			}
			else
			{
				dist = (*area->GetCenter() - *fromArea->GetCenter()).Length();
			}

			// compute distance travelled along path so far
			float cost = dist + fromArea->GetCostSoFar();

			// zombies ignore all path penalties
			if (cv_bot_zombie.value > 0.0f)
				return cost;

			// add cost of "jump down" pain unless we're jumping into water
			if (!area->IsConnected(fromArea, NUM_DIRECTIONS))
			{
				// this is a "jump down" (one way drop) transition - estimate damage we will take to traverse it
				float fallDistance = -fromArea->ComputeHeightChange(area);

				// if it's a drop-down ladder, estimate height from the bottom of the ladder to the lower area
				//if (ladder && ladder->m_bottom.z < fromArea->GetCenter()->z && ladder->m_bottom.z > area->GetCenter()->z)
				//{
				//	fallDistance = ladder->m_bottom.z - area->GetCenter()->z;
				//}

				float fallDamage = m_bot->GetApproximateFallDamage(fallDistance);

				if (fallDamage > 0.0f)
				{
					// if the fall would kill us, don't use it
					const float deathFallMargin = 10.0f;
					if (fallDamage + deathFallMargin >= m_bot->pev->health)
						return -1.0f;

					// if we need to get there in a hurry, ignore minor pain
					const float painTolerance = 15.0f * m_bot->GetProfile()->GetAggression() + 10.0f;
					if (m_route != FASTEST_ROUTE || fallDamage > painTolerance)
					{
						// cost is proportional to how much it hurts when we fall
						// 10 points - not a big deal, 50 points - ouch!
						cost += 100.0f * fallDamage * fallDamage;
					}
				}
			}

			// if this is a "crouch" area, add penalty
			if (area->GetAttributes() & NAV_CROUCH)
			{
				// these areas are very slow to move through
				real_t crouchPenalty = (m_route == FASTEST_ROUTE) ? 20.0f : 5.0f;

				// avoid crouch areas if we are rescuing hostages
				if (m_bot->GetHostageEscortCount())
				{
					crouchPenalty *= 3.0f;
				}

				cost += crouchPenalty * dist;
			}

			// if this is a "jump" area, add penalty
			if (area->GetAttributes() & NAV_JUMP)
			{
				// jumping can slow you down
				//const float jumpPenalty = (m_route == FASTEST_ROUTE) ? 100.0f : 0.5f;
				const float jumpPenalty = 1.0f;
				cost += jumpPenalty * dist;
			}

			if (m_route == SAFEST_ROUTE)
			{
				// add in the danger of this path - danger is per unit length travelled
				cost += dist * dangerFactor * area->GetDanger(m_bot->m_iTeam - 1);
			}

			if (!m_bot->IsAttacking())
			{
				// add in cost of teammates in the way
				// approximate density of teammates based on area
				float size = (area->GetSizeX() + area->GetSizeY()) / 2.0f;

				// degenerate check
				if (size >= 1.0f)
				{
					// cost is proportional to the density of teammates in this area
					const float costPerFriendPerUnit = 50000.0f;
					cost += costPerFriendPerUnit * float(area->GetPlayerCount(m_bot->m_iTeam, m_bot)) / size;
				}
			}

			return cost;
		}

		return 0.0f;
	}

private:
	CCSBot *m_bot;
	RouteType m_route;
};

class FollowTargetCollector
{
public:
	FollowTargetCollector(CBasePlayer *pPlayer)
	{
		m_player = pPlayer;
		m_forward.x = pPlayer->pev->velocity.x;
		m_forward.y = pPlayer->pev->velocity.y;

		float speed = m_forward.NormalizeInPlace();

		const float walkSpeed = 100.0f;
		if (speed < walkSpeed)
		{
			m_cutoff.x = pPlayer->pev->origin.x;
			m_cutoff.y = pPlayer->pev->origin.y;

			m_forward.x = 0.0f;
			m_forward.y = 0.0f;
		}
		else
		{
			const float k = 1.5f;
			real_t trimSpeed = (speed < 200.0f) ? speed : 200.0f;

			m_cutoff.x = pPlayer->pev->origin.x + k * trimSpeed * m_forward.x;
			m_cutoff.y = pPlayer->pev->origin.y + k * trimSpeed * m_forward.y;
		}

		m_targetAreaCount = 0;
	}

	enum { MAX_TARGET_AREAS = 128 };
	bool operator()(CNavArea *area)
	{
		if (m_targetAreaCount >= MAX_TARGET_AREAS)
			return false;

		// only use two-way connections
		if (!area->GetParent() || area->IsConnected(area->GetParent(), NUM_DIRECTIONS))
		{
			if (m_forward.IsZero())
			{
				m_targetArea[m_targetAreaCount++] = area;
			}
			else
			{
				// collect areas in the direction of the player's forward motion
				Vector2D to(((*area->GetCenter()).x - m_cutoff.x), (*area->GetCenter()).y - m_cutoff.y);
				to.NormalizeInPlace();

				if (DotProduct(to, m_forward) > 0.7071f)
				{
					m_targetArea[m_targetAreaCount++] = area;
				}
			}
		}

		return (m_targetAreaCount < MAX_TARGET_AREAS);
	}

	CBasePlayer *m_player;
	Vector2D m_forward;
	Vector2D m_cutoff;
	CNavArea *m_targetArea[MAX_TARGET_AREAS];
	int m_targetAreaCount;
};

void InstallBotControl();
void Bot_ServerCommand();
void Bot_RegisterCVars();
int GetBotFollowCount(CBasePlayer *pLeader);
const Vector *FindNearbyRetreatSpot(CCSBot *me, float maxRange);

void drawProgressMeter(float progress, char *title);
void startProgressMeter(const char *title);
void hideProgressMeter();

bool isSniperRifle(CBasePlayerItem *item);
float StayOnLadderLine(CCSBot *me, const CNavLadder *ladder);