//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements doors that move when you look at them.
//
// $NoKeywords: $
//=============================================================================//


#include "cbase.h"
#include "basecombatcharacter.h"
#include "entitylist.h"
#include "func_movelinear.h"

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

#define SF_LDOOR_THRESHOLD			8192
#define SF_LDOOR_INVERT				16384
#define SF_LDOOR_FROM_OPEN			32768


class CLookDoor : public CFuncMoveLinear
{
public:
	DECLARE_CLASS( CLookDoor, CFuncMoveLinear );

	void	Spawn( void );
	void	MoveThink( void );

	// Inputs
	void InputInvertOn( inputdata_t &inputdata );
	void InputInvertOff( inputdata_t &inputdata );

	float	m_flProximityDistance;	// How far before I start reacting
	float	m_flProximityOffset;	
	float	m_flFieldOfView;
	
	EHANDLE	m_hLooker;				// Who is looking

	DECLARE_DATADESC();
};


class CLookDoorThinker : public CLogicalEntity
{
public:
	DECLARE_CLASS( CLookDoorThinker, CLogicalEntity );

	void	LookThink( void );
	EHANDLE	m_hLookDoor;				// Who owns me
	
	DECLARE_DATADESC();
};


BEGIN_DATADESC( CLookDoorThinker )

	DEFINE_FIELD( m_hLookDoor, FIELD_EHANDLE ),

	// Function Pointers
	DEFINE_FUNCTION(LookThink),

END_DATADESC()


LINK_ENTITY_TO_CLASS( lookdoorthinker, CLookDoorThinker );


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CLookDoorThinker::LookThink(void)
{
	if (m_hLookDoor)
	{
		((CLookDoor*)(CBaseEntity*)m_hLookDoor)->MoveThink();
		SetNextThink( gpGlobals->curtime + 0.01f );
	}
	else
	{
		UTIL_Remove(this);
	}
}


BEGIN_DATADESC( CLookDoor )

	DEFINE_KEYFIELD( m_flProximityDistance,	FIELD_FLOAT, "ProximityDistance"),
	DEFINE_KEYFIELD( m_flProximityOffset,	FIELD_FLOAT, "ProximityOffset"),
	DEFINE_KEYFIELD( m_flFieldOfView,		FIELD_FLOAT, "FieldOfView" ),
	DEFINE_FIELD(m_hLooker,				FIELD_EHANDLE),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID,		"InvertOn",		InputInvertOn ),
	DEFINE_INPUTFUNC( FIELD_VOID,		"InvertOff",	InputInvertOff ),

	// Function Pointers
	DEFINE_FUNCTION(MoveThink),

END_DATADESC()

LINK_ENTITY_TO_CLASS( func_lookdoor, CLookDoor );


//------------------------------------------------------------------------------
// Purpose : Input handlers.
//------------------------------------------------------------------------------
void CLookDoor::InputInvertOn( inputdata_t &inputdata )
{
	m_spawnflags |= SF_LDOOR_INVERT;
}

void CLookDoor::InputInvertOff( inputdata_t &inputdata )
{
	m_spawnflags &= ~SF_LDOOR_INVERT;
}


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CLookDoor::Spawn(void)
{
	BaseClass::Spawn();

	if (m_target == NULL_STRING)
	{
		Warning( "ERROR: DoorLook (%s) given no target.  Rejecting spawn.\n",GetDebugName());
		return;
	}
	CLookDoorThinker* pLookThinker = (CLookDoorThinker*)CreateEntityByName("lookdoorthinker");
	if (pLookThinker)
	{
		pLookThinker->SetThink(&CLookDoorThinker::LookThink);
		pLookThinker->m_hLookDoor = this;
		pLookThinker->SetNextThink( gpGlobals->curtime + 0.1f );
	}
}


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CLookDoor::MoveThink(void)
{
	// --------------------------------
	// Make sure we have a looker
	// --------------------------------
	if (m_hLooker == NULL)
	{
		m_hLooker = (CBaseEntity*)gEntList.FindEntityByName( NULL, m_target );

		if (m_hLooker == NULL)
		{
			return;
		}
	}

	//--------------------------------------
	// Calculate an orgin for the door
	//--------------------------------------
	Vector vOrigin = WorldSpaceCenter() - GetAbsOrigin();

	// If FROM_OPEN flag is set, door proximity is measured
	// from the open and not the closed position
	if (FBitSet (m_spawnflags, SF_LDOOR_FROM_OPEN))
	{
		vOrigin += m_vecPosition2;
	}

	// ------------------------------------------------------
	//  First add movement based on proximity
	// ------------------------------------------------------
	float flProxMove = 0;
	if (m_flProximityDistance > 0)
	{
		float flDist = (m_hLooker->GetAbsOrigin() - vOrigin).Length()-m_flProximityOffset;
		if (flDist < 0) flDist = 0;

		if (flDist < m_flProximityDistance)
		{
			if (FBitSet (m_spawnflags, SF_LDOOR_THRESHOLD))
			{
				flProxMove = 1.0;
			}
			else
			{
				flProxMove = 1-flDist/m_flProximityDistance;
			}
		}
	}

	// ------------------------------------------------------
	//  Then add movement based on view angle
	// ------------------------------------------------------
	float flViewMove = 0;
	if (m_flFieldOfView > 0)
	{
		// ----------------------------------------
		// Check that toucher is facing the target
		// ----------------------------------------
		Assert( dynamic_cast< CBaseCombatCharacter* >( m_hLooker.Get() ) );
		CBaseCombatCharacter* pBCC = (CBaseCombatCharacter*)m_hLooker.Get();
		Vector vTouchDir = pBCC->EyeDirection3D( );
		Vector vTargetDir =  vOrigin - pBCC->EyePosition();
		VectorNormalize(vTargetDir);

		float flDotPr = DotProduct(vTouchDir,vTargetDir);
		if (flDotPr < m_flFieldOfView)
		{
			flViewMove = 0.0;
		}
		else
		{
			flViewMove = (flDotPr-m_flFieldOfView)/(1.0 - m_flFieldOfView);
		}
	}

	//---------------------------------------
	// Summate the two moves
	//---------------------------------------
	float flMove = flProxMove + flViewMove;
	if (flMove > 1.0)
	{
		flMove = 1.0;
	}

	// If behavior is inverted do the reverse
	if (FBitSet (m_spawnflags, SF_LDOOR_INVERT))
	{
		flMove = 1-flMove;
	}

	// Move the door
	SetPosition( flMove );
}