//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Entities that capture the player's UI and move it into game design
//			as outputs.
//
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "entitylist.h"
#include "util.h"
#include "physics.h"
#include "entityoutput.h"
#include "player.h"
#include "in_buttons.h"
#include "basecombatweapon.h"
#include "baseviewmodel.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//----------------------------------------------------------------
// Spawn flags
//----------------------------------------------------------------
#define  SF_GAMEUI_FREEZE_PLAYER		32	
#define  SF_GAMEUI_HIDE_WEAPON			64	
#define  SF_GAMEUI_USE_DEACTIVATES		128
#define  SF_GAMEUI_JUMP_DEACTIVATES		256


class CGameUI : public CBaseEntity
{
public:
	DECLARE_CLASS( CGameUI, CBaseEntity );

	DECLARE_DATADESC();

	// Input handlers
	void InputDeactivate( inputdata_t &inputdata );
	void InputActivate( inputdata_t &inputdata );

	void Think( void );
	void Deactivate( CBaseEntity *pActivator );

	float				m_flFieldOfView;
	CHandle<CBaseCombatWeapon>	m_hSaveWeapon;

	COutputEvent		m_playerOn;
	COutputEvent		m_playerOff;

	COutputEvent		m_pressedMoveLeft;
	COutputEvent		m_pressedMoveRight;
	COutputEvent		m_pressedForward;
	COutputEvent		m_pressedBack;
	COutputEvent		m_pressedAttack;
	COutputEvent		m_pressedAttack2;
	
	COutputEvent		m_unpressedMoveLeft;
	COutputEvent		m_unpressedMoveRight;
	COutputEvent		m_unpressedForward;
	COutputEvent		m_unpressedBack;
	COutputEvent		m_unpressedAttack;
	COutputEvent		m_unpressedAttack2;

	COutputFloat		m_xaxis;
	COutputFloat		m_yaxis;
	COutputFloat		m_attackaxis;
	COutputFloat		m_attack2axis;

	bool				m_bForceUpdate;
	int					m_nLastButtonState;

	CHandle<CBasePlayer>	m_player;
};


BEGIN_DATADESC( CGameUI )

	DEFINE_KEYFIELD( m_flFieldOfView, FIELD_FLOAT, "FieldOfView" ),
	DEFINE_FIELD( m_hSaveWeapon, FIELD_EHANDLE ),
	DEFINE_FIELD( m_bForceUpdate, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_player, FIELD_EHANDLE ),
	DEFINE_FIELD( m_nLastButtonState, FIELD_INTEGER ),

	DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ),
	DEFINE_INPUTFUNC( FIELD_STRING, "Activate", InputActivate ),

	DEFINE_OUTPUT( m_playerOn, "PlayerOn" ),
	DEFINE_OUTPUT( m_playerOff, "PlayerOff" ),

	DEFINE_OUTPUT( m_pressedMoveLeft, "PressedMoveLeft" ),
	DEFINE_OUTPUT( m_pressedMoveRight, "PressedMoveRight" ),
	DEFINE_OUTPUT( m_pressedForward, "PressedForward" ),
	DEFINE_OUTPUT( m_pressedBack, "PressedBack" ),
	DEFINE_OUTPUT( m_pressedAttack, "PressedAttack" ),
	DEFINE_OUTPUT( m_pressedAttack2, "PressedAttack2" ),

	DEFINE_OUTPUT( m_unpressedMoveLeft, "UnpressedMoveLeft" ),
	DEFINE_OUTPUT( m_unpressedMoveRight, "UnpressedMoveRight" ),
	DEFINE_OUTPUT( m_unpressedForward, "UnpressedForward" ),
	DEFINE_OUTPUT( m_unpressedBack, "UnpressedBack" ),
	DEFINE_OUTPUT( m_unpressedAttack, "UnpressedAttack" ),
	DEFINE_OUTPUT( m_unpressedAttack2, "UnpressedAttack2" ),

	DEFINE_OUTPUT( m_xaxis, "XAxis" ),
	DEFINE_OUTPUT( m_yaxis, "YAxis" ),
	DEFINE_OUTPUT( m_attackaxis, "AttackAxis" ),
	DEFINE_OUTPUT( m_attack2axis, "Attack2Axis" ),

END_DATADESC()


LINK_ENTITY_TO_CLASS( game_ui, CGameUI );
	

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CGameUI::InputDeactivate( inputdata_t &inputdata )
{
	Deactivate( inputdata.pActivator );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CGameUI::Deactivate( CBaseEntity *pActivator )
{
	CBasePlayer *pPlayer = m_player;

	AssertMsg(pPlayer, "CGameUI deactivated without a player!");

	if (pPlayer)
	{
		// Re-enable player motion
		if ( FBitSet( m_spawnflags, SF_GAMEUI_FREEZE_PLAYER ) )
		{
			m_player->RemoveFlag( FL_ATCONTROLS );
		}

		// Restore weapons
		if ( FBitSet( m_spawnflags, SF_GAMEUI_HIDE_WEAPON ) )
		{
			// Turn the hud back on
			pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION;

			if ( m_hSaveWeapon.Get() )
			{
				m_player->Weapon_Switch( m_hSaveWeapon.Get() );
				m_hSaveWeapon = NULL;
			}

			if ( pPlayer->GetActiveWeapon() )
			{
				pPlayer->GetActiveWeapon()->Deploy();
			}
		}

		// Announce that the player is no longer controlling through us
		m_playerOff.FireOutput( pPlayer, this, 0 );

		// Clear out the axis controls
		m_xaxis.Set( 0, pPlayer, this );
		m_yaxis.Set( 0, pPlayer, this );
		m_attackaxis.Set( 0, pPlayer, this );
		m_attack2axis.Set( 0, pPlayer, this );
		m_nLastButtonState = 0;
		m_player = NULL;
	}
	else
	{
		Warning("%s Deactivate(): I have no player when called by %s!\n", GetEntityName().ToCStr(), pActivator->GetEntityName().ToCStr());
	}
	
	// Stop thinking
	SetNextThink( TICK_NEVER_THINK );
}


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CGameUI::InputActivate( inputdata_t &inputdata )
{
	CBasePlayer *pPlayer;

	// Determine if we're specifying this as an override parameter
	if ( inputdata.value.StringID() != NULL_STRING )
	{
		CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.String(), this, inputdata.pActivator, inputdata.pCaller );
		if ( pEntity == NULL || pEntity->IsPlayer() == false )
		{
			Warning( "%s InputActivate: entity %s not found or is not a player!\n", GetEntityName().ToCStr(), inputdata.value.String() );
			return;
		}

		pPlayer = ToBasePlayer( pEntity );
	}
	else
	{
		// Otherwise try to use the activator
		if ( inputdata.pActivator == NULL || inputdata.pActivator->IsPlayer() == false )
		{
			Warning( "%s InputActivate: invalid or missing !activator!\n", GetEntityName().ToCStr() );
			return;
		}

		pPlayer = ToBasePlayer( inputdata.pActivator );
	}

	// If another player is already using these controls3, ignore this activation
	if ( m_player.Get() != NULL && pPlayer != m_player.Get() )
	{
		// TODO: We could allow this by calling Deactivate() at this point and continuing on -- jdw
		return;
	}

	// Setup our internal data
	m_player = pPlayer;
	m_playerOn.FireOutput( pPlayer, this, 0 );

	// Turn the hud off
	SetNextThink( gpGlobals->curtime );

	// Disable player's motion
	if ( FBitSet( m_spawnflags, SF_GAMEUI_FREEZE_PLAYER ) )
	{
		m_player->AddFlag( FL_ATCONTROLS );
	}

	// Store off and hide the currently held weapon
	if ( FBitSet( m_spawnflags, SF_GAMEUI_HIDE_WEAPON ) )
	{
		m_player->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION;

		if ( m_player->GetActiveWeapon() )
		{
			m_hSaveWeapon = m_player->GetActiveWeapon();

			m_player->GetActiveWeapon()->Holster();
			m_player->ClearActiveWeapon();
			m_player->HideViewModels();
		}
	}

	// We must update our state
	m_bForceUpdate = true;
}


//------------------------------------------------------------------------------
// Purpose: Samples the player's inputs and fires outputs based on what buttons
//			are currently held down.
//------------------------------------------------------------------------------
void CGameUI::Think( void )
{
	CBasePlayer *pPlayer = m_player;

	// If player is gone, stop thinking
	if (pPlayer == NULL)
	{
		SetNextThink( TICK_NEVER_THINK );
		return;
	}

	// If we're forcing an update, state with a clean button state
	if ( m_bForceUpdate )
	{
		m_nLastButtonState = pPlayer->m_nButtons;
	}

	// ------------------------------------------------
	// Check that toucher is facing the UI within
	// the field of view tolerance.  If not disconnect
	// ------------------------------------------------
	if (m_flFieldOfView > -1)
	{
		Vector vPlayerFacing;
		pPlayer->EyeVectors( &vPlayerFacing );
		Vector vPlayerToUI =  GetAbsOrigin() - pPlayer->WorldSpaceCenter();
		VectorNormalize(vPlayerToUI);

		float flDotPr = DotProduct(vPlayerFacing,vPlayerToUI);
		if (flDotPr < m_flFieldOfView)
		{
			Deactivate( pPlayer );
			return;
		}
	}

	pPlayer->AddFlag( FL_ONTRAIN );
	SetNextThink( gpGlobals->curtime );

	// Deactivate if they jump or press +use.
	// FIXME: prevent the use from going through in player.cpp
	if ((( pPlayer->m_afButtonPressed & IN_USE ) && ( m_spawnflags & SF_GAMEUI_USE_DEACTIVATES )) ||
		(( pPlayer->m_afButtonPressed & IN_JUMP ) && ( m_spawnflags & SF_GAMEUI_JUMP_DEACTIVATES )))
	{
		Deactivate( pPlayer );
		return;
	}

	// Determine what's different
	int nButtonsChanged = ( pPlayer->m_nButtons ^ m_nLastButtonState );

	//
	// Handle all our possible input triggers
	//

	if ( nButtonsChanged & IN_MOVERIGHT )
	{
		if ( m_nLastButtonState & IN_MOVERIGHT )
		{
			m_unpressedMoveRight.FireOutput( pPlayer, this, 0 );
		}
		else
		{
			m_pressedMoveRight.FireOutput( pPlayer, this, 0 );
		}
	}

	if ( nButtonsChanged & IN_MOVELEFT )
	{
		if ( m_nLastButtonState & IN_MOVELEFT )
		{
			m_unpressedMoveLeft.FireOutput( pPlayer, this, 0 );
		}
		else
		{
			m_pressedMoveLeft.FireOutput( pPlayer, this, 0 );
		}
	}

	if ( nButtonsChanged & IN_FORWARD )
	{
		if ( m_nLastButtonState & IN_FORWARD )
		{
			m_unpressedForward.FireOutput( pPlayer, this, 0 );
		}
		else
		{
			m_pressedForward.FireOutput( pPlayer, this, 0 );
		}
	}

	if ( nButtonsChanged & IN_BACK )
	{
		if ( m_nLastButtonState & IN_BACK )
		{
			m_unpressedBack.FireOutput( pPlayer, this, 0 );
		}
		else
		{
			m_pressedBack.FireOutput( pPlayer, this, 0 );
		}
	}

	if ( nButtonsChanged & IN_ATTACK )
	{
		if ( m_nLastButtonState & IN_ATTACK )
		{
			m_unpressedAttack.FireOutput( pPlayer, this, 0 );
		}
		else
		{
			m_pressedAttack.FireOutput( pPlayer, this, 0 );
		}
	}

	if ( nButtonsChanged & IN_ATTACK2 )
	{
		if ( m_nLastButtonState & IN_ATTACK2 )
		{
			m_unpressedAttack2.FireOutput( pPlayer, this, 0 );
		}
		else
		{
			m_pressedAttack2.FireOutput( pPlayer, this, 0 );
		}
	}

	// Setup for the next frame
	m_nLastButtonState = pPlayer->m_nButtons;

	float x = 0, y = 0, attack = 0, attack2 = 0;
	if ( pPlayer->m_nButtons & IN_MOVERIGHT )
	{
		x = 1;
	}
	else if ( pPlayer->m_nButtons & IN_MOVELEFT )
	{
		x = -1;
	}

	if ( pPlayer->m_nButtons & IN_FORWARD )
	{
		y = 1;
	}
	else if ( pPlayer->m_nButtons & IN_BACK )
	{
		y = -1;
	}

	if ( pPlayer->m_nButtons & IN_ATTACK )
	{
		attack = 1;
	}

	if ( pPlayer->m_nButtons & IN_ATTACK2 )
	{
		attack2 = 1;
	}

	//
	// Fire the analog outputs if they changed.
	//
	if ( m_bForceUpdate || ( m_xaxis.Get() != x ) )
	{
		m_xaxis.Set( x, pPlayer, this );
	}

	if ( m_bForceUpdate || ( m_yaxis.Get() != y ) )
	{
		m_yaxis.Set( y, pPlayer, this );
	}

	if ( m_bForceUpdate || ( m_attackaxis.Get() != attack ) )
	{
		m_attackaxis.Set( attack, pPlayer, this );
	}

	if ( m_bForceUpdate || ( m_attack2axis.Get() != attack2 ) )
	{
		m_attack2axis.Set( attack2, pPlayer, this );
	}

	m_bForceUpdate = false;
}
