//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: The robots for use in the Robot Destruction TF2 game mode.
//
//=========================================================================//
#ifndef ROBOT_DESTRUCTION_ROBOT_H
#define ROBOT_DESTRUCTION_ROBOT_H
#pragma once

#include "cbase.h"

#ifdef GAME_DLL
	#include "tf_shareddefs.h"
	#include "pathtrack.h"
	#include "NextBotGroundLocomotion.h"
	#include "NextBotIntentionInterface.h"
	#include "NextBotBehavior.h"
	#include "NextBot.h"
	#include "../server/NextBot/Path/NextBotPathFollow.h"
	#include "../server/NextBot/Path/NextBotPath.h"
	#include "../server/tf/halloween/headless_hatman_body.h"
	#include "tf_obj_dispenser.h"
#else
	#include "c_obj_dispenser.h"
#endif

#ifdef CLIENT_DLL
	#define CTFRobotDestruction_Robot C_TFRobotDestruction_Robot
	#define CRobotDispenser C_RobotDispenser
#endif

#include "props_shared.h"

enum eRobotType
{
	ROBOT_TYPE_FRUSTUM = 0,
	ROBOT_TYPE_SPHERE,
	ROBOT_TYPE_KING,

	NUM_ROBOT_TYPES
};

enum eRobotUIState
{
	ROBOT_STATE_INACIVE = 0,
	ROBOT_STATE_ACTIVE,
	ROBOT_STATE_DEAD,
	ROBOT_STATE_SHIELDED,

	NUM_ROBOT_STATES
};

struct RobotData_t
{
public:
	enum EStringDataKey_t
	{
		MODEL_KEY = 0,
		DAMAGED_MODEL_KEY,
		HURT_SOUND_KEY,
		DEATH_SOUND_KEY,
		COLLIDE_SOUND_KEY,
		IDLE_SOUND_KEY
	};

	enum EFloatDataKey_t
	{
		HEALTH_BAR_OFFSET_KEY
	};

	RobotData_t( const char* pszModelName
			   , const char* pszDamagedModelName
			   , const char *pszHurtSound
			   , const char *pszDeathSound
			   , const char *pszCollideSound
			   , const char *pszIdleSound
			   , float flHealthBarOffset )
	{
		m_stringMap.SetLessFunc( DefLessFunc(int) );
		m_floatMap.SetLessFunc( DefLessFunc(int) );
		m_stringMap.Insert( MODEL_KEY, pszModelName );
		m_stringMap.Insert( DAMAGED_MODEL_KEY, pszDamagedModelName );
		m_stringMap.Insert( HURT_SOUND_KEY, pszHurtSound );
		m_stringMap.Insert( DEATH_SOUND_KEY, pszDeathSound );
		m_stringMap.Insert( COLLIDE_SOUND_KEY, pszCollideSound );
		m_stringMap.Insert( IDLE_SOUND_KEY, pszIdleSound );
		m_floatMap.Insert( HEALTH_BAR_OFFSET_KEY, flHealthBarOffset );
	}

	const char* GetStringData( EStringDataKey_t key ) const
	{
		return GetData< const char * >( m_stringMap, (int)key );
	}

	float GetFloatData( EFloatDataKey_t key ) const
	{
		return GetData< float >( m_floatMap, (int)key );
	}

	void Precache();

private:

	template< typename T >
	T GetData( const CUtlMap< int, T >& map, int nKey ) const
	{
		int nIndex = map.Find( nKey );
		if ( nIndex != map.InvalidIndex() )
		{
			return map.Element( nIndex );
		}

		AssertMsg1( 0, "No entry for key %d", nKey );
		return T(0);
	}

	CUtlMap< int, const char * > m_stringMap;
	CUtlMap< int, float > m_floatMap;
};

class CTFRobotDestruction_Robot;

#ifdef GAME_DLL

struct RobotSpawnData_t
{
	RobotSpawnData_t()
		: m_eType( ROBOT_TYPE_FRUSTUM )
		, m_nRobotHealth( 100 )
		, m_nPoints( 0 )
		, m_nNumGibs( 0 )
		, m_pszPathName( NULL )
		, m_pszGroupName( NULL )
	{}

	RobotSpawnData_t &operator=( const RobotSpawnData_t& rhs )
	{
		m_eType = rhs.m_eType;
		m_nRobotHealth = rhs.m_nRobotHealth;
		m_nPoints = rhs.m_nPoints;
		m_nNumGibs = rhs.m_nNumGibs;
		m_pszPathName = rhs.m_pszPathName;
		m_pszGroupName = rhs.m_pszGroupName;

		return *this;
	}

	eRobotType m_eType;
	int m_nRobotHealth;
	int m_nPoints;
	int m_nNumGibs;
	const char *m_pszPathName;
	const char *m_pszGroupName;
};

//----------------------------------------------------------------------------
class CRobotLocomotion : public NextBotGroundLocomotion
{
public:
	CRobotLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot ) { }
	virtual ~CRobotLocomotion() { }

	virtual float GetGroundSpeed() const OVERRIDE;
	virtual float GetRunSpeed( void ) const OVERRIDE;			// get maximum running speed
	virtual float GetStepHeight( void ) const OVERRIDE;				// if delta Z is greater than this, we have to jump to get up
	virtual float GetMaxJumpHeight( void ) const OVERRIDE;			// return maximum height of a jump

	virtual bool ShouldCollideWith( const CBaseEntity *object ) const OVERRIDE;

private:
	virtual float GetMaxYawRate( void ) const OVERRIDE;				// return max rate of yaw rotation
};


//----------------------------------------------------------------------------
class CRobotIntention : public IIntention
{
public:
	CRobotIntention( class CTFRobotDestruction_Robot *me );
	virtual ~CRobotIntention();

	virtual void Reset( void ) OVERRIDE;
	virtual void Update( void ) OVERRIDE;

	virtual QueryResultType			IsPositionAllowed( const INextBot *me, const Vector &pos ) const OVERRIDE;	// is the a place we can be?

	virtual INextBotEventResponder *FirstContainedResponder( void ) const OVERRIDE  { return m_behavior; }
	virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const OVERRIDE { return NULL; }

private:
	Behavior< CTFRobotDestruction_Robot > *m_behavior;
};

//---------------------------------------------------------------------------------------------
class CRobotBehavior : public Action< CTFRobotDestruction_Robot >
{
public:
	virtual Action< CTFRobotDestruction_Robot > *InitialContainedAction( CTFRobotDestruction_Robot *me ) OVERRIDE;
	virtual ActionResult< CTFRobotDestruction_Robot >	OnStart( CTFRobotDestruction_Robot *me, Action< CTFRobotDestruction_Robot > *priorAction ) OVERRIDE;
	virtual ActionResult< CTFRobotDestruction_Robot >	Update( CTFRobotDestruction_Robot *me, float interval ) OVERRIDE;
	virtual EventDesiredResult< CTFRobotDestruction_Robot > OnInjured( CTFRobotDestruction_Robot *me, const CTakeDamageInfo &info );
	EventDesiredResult< CTFRobotDestruction_Robot > OnContact( CTFRobotDestruction_Robot *me, CBaseEntity *pOther, CGameTrace *result = NULL );
	virtual const char *GetName( void ) const	{ return "RobotBehavior"; }		// return name of this action

private:
	CountdownTimer m_SpeakTimer;
	CountdownTimer m_IdleSpeakTimer;
};
#endif

class CRobotDispenser : 
#ifdef GAME_DLL
	public CObjectDispenser
#else
	public C_ObjectDispenser
#endif
{
#ifdef GAME_DLL
	DECLARE_CLASS( CRobotDispenser, CObjectDispenser )
#else
	DECLARE_CLASS( CRobotDispenser, C_ObjectDispenser )
#endif
	DECLARE_NETWORKCLASS();
	DECLARE_DATADESC();
public:
#ifdef GAME_DLL
	CRobotDispenser();

	virtual void Spawn( void ) OVERRIDE;
	virtual void OnGoActive( void ) OVERRIDE;
	virtual void GetControlPanelInfo( int nPanelIndex, const char *&pPanelName ) OVERRIDE;
	virtual void SetModel( const char *pModel ) OVERRIDE;
	virtual float GetDispenserRadius( void ) OVERRIDE { return 128; }
	virtual float GetHealRate() const OVERRIDE { return 5.f; }

	virtual int DispenseMetal( CTFPlayer * ) OVERRIDE { return 0; }
	virtual bool DispenseAmmo( CTFPlayer * ) OVERRIDE { return false; }

private:

	virtual void PlayActiveSound() OVERRIDE { /*DO NOTHING*/ }
#endif
};

class CTFRobotDestruction_RobotAnimController
{
public:
	CTFRobotDestruction_RobotAnimController( CTFRobotDestruction_Robot *pOuter );
	void Update();
	void Impulse( const Vector& vecImpulse );
private:
	void Approach( Vector& vecIn, const Vector& vecTarget, float flRate );
	void GetPoseParams();

	Vector m_vecOldOrigin;
	Vector m_vecLean;
	Vector m_vecImpulse;
	CTFRobotDestruction_Robot *m_pOuter;
	
	struct PoseParams_t
	{
		int m_nMoveX;
		int m_nMoveY;
	} m_poseParams;
};

#ifdef GAME_DLL
	typedef NextBotCombatCharacter RobotBaseClass;
#else
	typedef CBaseCombatCharacter RobotBaseClass;
#endif

class CTFRobotDestruction_Robot : public RobotBaseClass
#ifdef CLIENT_DLL
	, public CGameEventListener
#endif
{
	DECLARE_DATADESC();
	DECLARE_CLASS( CTFRobotDestruction_Robot, RobotBaseClass )
	DECLARE_NETWORKCLASS();
public:

	CTFRobotDestruction_Robot( void );
	virtual ~CTFRobotDestruction_Robot( void );
	static void StaticPrecache( void );
	virtual void Precache( void ) OVERRIDE;

	virtual void Spawn( void ) OVERRIDE;
	virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const OVERRIDE;
#ifdef CLIENT_DLL
	virtual float GetHealthBarHeightOffset( void ) const OVERRIDE;
	virtual void OnDataChanged( DataUpdateType_t type ) OVERRIDE;
	virtual int	GetHealth( void ) const OVERRIDE { return m_iHealth; }
	virtual int	GetMaxHealth( void ) const OVERRIDE { return m_iMaxHealth; }
	virtual bool IsHealthBarVisible( void ) const OVERRIDE { return true; }
	virtual void UpdateClientSideAnimation( void ) OVERRIDE;
	virtual void FireGameEvent( IGameEvent *event ) OVERRIDE;
	virtual CStudioHdr* OnNewModel() OVERRIDE;
	virtual void FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) OVERRIDE;
	void UpdateDamagedEffects( void );
#else
	virtual void HandleAnimEvent( animevent_t *pEvent ) OVERRIDE;
	virtual bool IsRemovedOnReset( void ) const { return false; }
	virtual void UpdateOnRemove( void ) OVERRIDE;
	virtual void Event_Killed( const CTakeDamageInfo &info ) OVERRIDE;
	virtual int OnTakeDamage( const CTakeDamageInfo &info ) OVERRIDE;
	virtual void TraceAttack( const CTakeDamageInfo &inputInfo, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) OVERRIDE;
	virtual void UpdateAnimsThink( void );
	
	void RepairSelfThink( void );
	bool GetShieldedState( void ) const { return m_bShielded; }
	CPathTrack *GetNextPath( void ) const { return m_hNextPath; }
	void ArriveAtPath( void );
	void SetRobotSpawnData( const RobotSpawnData_t& data ) { m_spawnData = data; m_eType = data.m_eType; }
	const RobotSpawnData_t &GetRobotSpawnData() const { return m_spawnData; }
	void SetGroup( class CTFRobotDestruction_RobotGroup* pGroup ) { m_hGroup.Set( pGroup ); }
	void SetSpawn( class CTFRobotDestruction_RobotSpawn* pSpawn ) { m_hSpawn.Set( pSpawn ); }
	void EnableUber( void );
	void DisableUber( void );

	// INextBot
	virtual CRobotIntention	*GetIntentionInterface( void ) const		{ return m_intention; }
	virtual CRobotLocomotion	*GetLocomotionInterface( void ) const	{ return m_locomotor; }
	virtual CHeadlessHatmanBody	*GetBodyInterface( void ) const			{ return m_body; }
	void SetNewActivity( Activity activity );
	void SetIsPanicked( bool bPanicked ) { m_bIsPanicked = bPanicked; }
	bool GetIsPanicked( void ) const { return m_bIsPanicked; }

	//Inputs
	void InputStopAndUseComputer( inputdata_t &inputdata );
private:

	void PlayDeathEffects( void );
	void ModifyDamage( CTakeDamageInfo *info ) const;
	void SpewBars( int nNumToSpew );
	void SpewBarsThink( void );
	void SelfDestructThink( void );
	void SpewGibs( void );
#endif
private:

	int							m_iHealth;
	int							m_iMaxHealth;
	CUtlVector<breakmodel_t>	m_aGibs;
	CUtlVector<breakmodel_t>	m_aSpawnProps;
	CTFRobotDestruction_RobotAnimController m_animController;
	CNetworkVar( bool, m_bShielded );
	CNetworkVar( eRobotType, m_eType );
#ifdef CLIENT_DLL
	HPARTICLEFFECT	m_hDamagedParticleEffect;
#else
	CRobotDispenser				*m_pDispenser;
	RobotSpawnData_t			m_spawnData;
	CHandle< CPathTrack >		m_hNextPath;
	int							m_nPointsSpewed;
	IMPLEMENT_NETWORK_VAR_FOR_DERIVED( m_iHealth );
	CHandle< CTFRobotDestruction_RobotGroup > m_hGroup;
	CHandle< CTFRobotDestruction_RobotSpawn > m_hSpawn;

	CRobotIntention *m_intention;
	CRobotLocomotion *m_locomotor;
	CHeadlessHatmanBody *m_body;
	bool m_bIsPanicked;
#endif
};

#ifdef GAME_DLL
//--------------------------------------------------------------------------------------------------------------
class CRobotPathCost : public IPathCost
{
public:
	CRobotPathCost( CTFRobotDestruction_Robot *me )
	{
		m_me = me;
	}

	// return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed
	virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const
	{
		if ( fromArea == NULL )
		{
			// first area in path, no cost
			return 0.0f;
		}
		else
		{
			if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) )
			{
				// our locomotor says we can't move here
				return -1.0f;
			}

			// compute distance traveled along path so far
			float dist;

			if ( ladder )
			{
				dist = ladder->m_length;
			}
			else if ( length > 0.0 )
			{
				// optimization to avoid recomputing length
				dist = length;
			}
			else
			{
				dist = ( area->GetCenter() - fromArea->GetCenter() ).Length();
			}

			// check height change
			float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area );
			if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() )
			{
				if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() )
				{
					// too high to reach
					return -1.0f;
				}

				// jumping is slower than flat ground
				const float jumpPenalty = 5.0f;
				dist += jumpPenalty * dist;
			}
			else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() )
			{
				// too far to drop
				return -1.0f;
			}

			return dist + fromArea->GetCostSoFar();
		}
	}

	CTFRobotDestruction_Robot *m_me;
};
#endif // GAME_DLL
#endif // ROBOT_DESTRUCTION_ROBOT_H