//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Spawn and use functions for editor-placed triggers.
//
//=============================================================================//

#include "cbase.h"
#include "ai_basenpc.h"
#include "player.h"
#include "saverestore.h"
#include "gamerules.h"
#include "entityapi.h"
#include "entitylist.h"
#include "ndebugoverlay.h"
#include "globalstate.h"
#include "filters.h"
#include "vstdlib/random.h"
#include "triggers.h"
#include "saverestoretypes.h"
#include "hierarchy.h"
#include "bspfile.h"
#include "saverestore_utlvector.h"
#include "physics_saverestore.h"
#include "te_effect_dispatch.h"
#include "ammodef.h"
#include "iservervehicle.h"
#include "movevars_shared.h"
#include "physics_prop_ragdoll.h"
#include "props.h"
#include "RagdollBoogie.h"
#include "EntityParticleTrail.h"
#include "in_buttons.h"

#ifdef HL2_DLL
#include "hl2_player.h"
#endif

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

#define DEBUG_TRANSITIONS_VERBOSE	2
ConVar g_debug_transitions( "g_debug_transitions", "0", FCVAR_NONE, "Set to 1 and restart the map to be warned if the map has no trigger_transition volumes. Set to 2 to see a dump of all entities & associated results during a transition." );

// Global list of triggers that care about weapon fire
// Doesn't need saving, the triggers re-add themselves on restore.
CUtlVector< CHandle<CTriggerMultiple> >	g_hWeaponFireTriggers;

extern bool		g_fGameOver;
ConVar showtriggers( "showtriggers", "0", FCVAR_CHEAT, "Shows trigger brushes" );

bool IsTriggerClass( CBaseEntity *pEntity );

// Command to dynamically toggle trigger visibility
void Cmd_ShowtriggersToggle_f( void )
{
	// Loop through the entities in the game and make visible anything derived from CBaseTrigger
	CBaseEntity *pEntity = gEntList.FirstEnt();
	while ( pEntity )
	{
		if ( IsTriggerClass(pEntity) )
		{
			// If a classname is specified, only show triggles of that type
			if ( engine->Cmd_Argc() > 1 )
			{
				const char *sClassname = engine->Cmd_Argv(1);
				if ( sClassname && sClassname[0] )
				{
					if ( !FClassnameIs( pEntity, sClassname ) )
					{
						pEntity = gEntList.NextEnt( pEntity );
						continue;
					}
				}
			}

			if ( pEntity->IsEffectActive( EF_NODRAW ) )
			{
				pEntity->RemoveEffects( EF_NODRAW );
			}
			else
			{
				pEntity->AddEffects( EF_NODRAW );
			}
		}

		pEntity = gEntList.NextEnt( pEntity );
	}
}

static ConCommand showtriggers_toggle( "showtriggers_toggle", Cmd_ShowtriggersToggle_f, "Toggle show triggers", FCVAR_CHEAT );

// Global Savedata for base trigger
BEGIN_DATADESC( CBaseTrigger )

	// Keyfields
	DEFINE_KEYFIELD( m_iFilterName,	FIELD_STRING,	"filtername" ),
	DEFINE_FIELD( m_hFilter,	FIELD_EHANDLE ),
	DEFINE_KEYFIELD( m_bDisabled,		FIELD_BOOLEAN,	"StartDisabled" ),
	//DEFINE_UTLVECTOR( m_hTouchingEntities, FIELD_EHANDLE ),		// Don't Save, it's recreated on restore

	// Inputs	
	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),

	// Outputs
	DEFINE_OUTPUT( m_OnStartTouch, "OnStartTouch"),
	DEFINE_OUTPUT( m_OnEndTouch, "OnEndTouch"),
	DEFINE_OUTPUT( m_OnEndTouchAll, "OnEndTouchAll"),

END_DATADESC()


LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger );


//------------------------------------------------------------------------------
// Purpose: Input handler to turn on this trigger.
//------------------------------------------------------------------------------
void CBaseTrigger::InputEnable( inputdata_t &inputdata )
{ 
	Enable();
}


//------------------------------------------------------------------------------
// Purpose: Input handler to turn off this trigger.
//------------------------------------------------------------------------------
void CBaseTrigger::InputDisable( inputdata_t &inputdata )
{ 
	Disable();
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
void CBaseTrigger::Spawn()
{
	if( HasSpawnFlags( SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS ) )
	{
		// Automatically set this trigger to work with NPC's.
		AddSpawnFlags( SF_TRIGGER_ALLOW_NPCS );
	}

	if ( HasSpawnFlags( SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES ) )
	{
		AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS );
	}

	if ( HasSpawnFlags( SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES ) )
	{
		AddSpawnFlags( SF_TRIGGER_ALLOW_CLIENTS );
	}

	BaseClass::Spawn();
}


//------------------------------------------------------------------------------
// Purpose: Turns on this trigger.
//------------------------------------------------------------------------------
void CBaseTrigger::Enable( void )
{
	m_bDisabled = false;
	if (!IsSolidFlagSet( FSOLID_TRIGGER ))
	{
		AddSolidFlags( FSOLID_TRIGGER ); 
		PhysicsTouchTriggers();
	}
}


//------------------------------------------------------------------------------
// Purpose :
//------------------------------------------------------------------------------
void CBaseTrigger::Activate( void ) 
{ 
	// Get a handle to my filter entity if there is one
	if (m_iFilterName != NULL_STRING)
	{
		m_hFilter = dynamic_cast<CBaseFilter *>(gEntList.FindEntityByName( NULL, m_iFilterName, NULL ));
	}
	BaseClass::Activate();
}


//------------------------------------------------------------------------------
// Purpose: Turns off this trigger.
//------------------------------------------------------------------------------
void CBaseTrigger::Disable( void )
{ 
	m_bDisabled = true;
	if (IsSolidFlagSet(FSOLID_TRIGGER))
	{
		RemoveSolidFlags( FSOLID_TRIGGER ); 
		PhysicsTouchTriggers();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CBaseTrigger::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		// --------------
		// Print Target
		// --------------
		char tempstr[255];
		if (IsSolidFlagSet(FSOLID_TRIGGER)) 
		{
			Q_strncpy(tempstr,"State: Enabled",sizeof(tempstr));
		}
		else
		{
			Q_strncpy(tempstr,"State: Disabled",sizeof(tempstr));
		}
		NDebugOverlay::EntityText(entindex(),text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseTrigger::InitTrigger( )
{
	SetSolid( GetParent() ? SOLID_VPHYSICS : SOLID_BSP );	
	AddSolidFlags( FSOLID_NOT_SOLID );
	if (m_bDisabled)
	{
		RemoveSolidFlags( FSOLID_TRIGGER );	
	}
	else
	{
		AddSolidFlags( FSOLID_TRIGGER );	
	}

	SetMoveType( MOVETYPE_NONE );
	SetModel( STRING( GetModelName() ) );    // set size and link into world
	if ( showtriggers.GetInt() == 0 )
	{
		AddEffects( EF_NODRAW );
	}

	m_hTouchingEntities.Purge();
}


//-----------------------------------------------------------------------------
// Purpose: Returns true if this entity passes the filter criterea, false if not.
// Input  : pOther - The entity to be filtered.
//-----------------------------------------------------------------------------
bool CBaseTrigger::PassesTriggerFilters(CBaseEntity *pOther)
{
	// First test spawn flag filters
	if ( HasSpawnFlags(SF_TRIGGER_ALLOW_ALL) ||
		(HasSpawnFlags(SF_TRIGGER_ALLOW_CLIENTS) && (pOther->GetFlags() & FL_CLIENT)) ||
		(HasSpawnFlags(SF_TRIGGER_ALLOW_NPCS) && (pOther->GetFlags() & FL_NPC)) ||
		(HasSpawnFlags(SF_TRIGGER_ALLOW_PUSHABLES) && FClassnameIs(pOther, "func_pushable")) ||
		(HasSpawnFlags(SF_TRIGGER_ALLOW_PHYSICS) && pOther->GetMoveType() == MOVETYPE_VPHYSICS))
	{
		bool bOtherIsPlayer = pOther->IsPlayer();
		if( HasSpawnFlags(SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS) && !bOtherIsPlayer )
		{
			CAI_BaseNPC *pNPC = pOther->MyNPCPointer();

			if( !pNPC || !pNPC->IsPlayerAlly() )
			{
				return false;
			}
		}

		if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES) && bOtherIsPlayer )
		{
			if ( !((CBasePlayer*)pOther)->IsInAVehicle() )
				return false;
		}

		if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES) && bOtherIsPlayer )
		{
			if ( ((CBasePlayer*)pOther)->IsInAVehicle() )
				return false;
		}

		CBaseFilter *pFilter = m_hFilter.Get();
		return (!pFilter) ? true : pFilter->PassesFilter(pOther);
	}
	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Called when an entity starts touching us.
// Input  : pOther - The entity that is touching us.
//-----------------------------------------------------------------------------
void CBaseTrigger::StartTouch(CBaseEntity *pOther)
{
	if (PassesTriggerFilters(pOther) )
	{
		EHANDLE hOther;
		hOther = pOther;

		m_hTouchingEntities.AddToTail( hOther );
		m_OnStartTouch.FireOutput(pOther, this);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called when an entity stops touching us.
// Input  : pOther - The entity that was touching us.
//-----------------------------------------------------------------------------
void CBaseTrigger::EndTouch(CBaseEntity *pOther)
{
	if ( IsTouching( pOther ) )
	{
		EHANDLE hOther;
		hOther = pOther;
		m_hTouchingEntities.FindAndRemove( hOther );
		
		//FIXME: Without this, triggers fire their EndTouch outputs when they are disabled!
		//if ( !m_bDisabled )
		//{
			m_OnEndTouch.FireOutput(pOther, this);
		//}

		// If there are no more entities touching this trigger, fire the lost all touches
		// Loop through the touching entities backwards. Clean out old ones, and look for existing
		bool bFoundOtherTouchee = false;
		int iSize = m_hTouchingEntities.Count();
		for ( int i = iSize-1; i >= 0; i-- )
		{
			EHANDLE hOther;
			hOther = m_hTouchingEntities[i];

			if ( !hOther )
			{
				m_hTouchingEntities.Remove( i );
			}
			else
			{
				bFoundOtherTouchee = true;
			}
		}

		//FIXME: Without this, triggers fire their EndTouch outputs when they are disabled!
		// Didn't find one?
		if ( !bFoundOtherTouchee /*&& !m_bDisabled*/ )
		{
			m_OnEndTouchAll.FireOutput(pOther, this);
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Return true if the specified entity is touching us
//-----------------------------------------------------------------------------
bool CBaseTrigger::IsTouching( CBaseEntity *pOther )
{
	EHANDLE hOther;
	hOther = pOther;
	return ( m_hTouchingEntities.Find( hOther ) != m_hTouchingEntities.InvalidIndex() );
}

//-----------------------------------------------------------------------------
// Purpose: Return a pointer to the first entity of the specified type being touched by this trigger
//-----------------------------------------------------------------------------
CBaseEntity *CBaseTrigger::GetTouchedEntityOfType( const char *sClassName )
{
	int iCount = m_hTouchingEntities.Count();
	for ( int i = 0; i < iCount; i++ )
	{
		CBaseEntity *pEntity = m_hTouchingEntities[i];
		if ( FClassnameIs( pEntity, sClassName ) )
			return pEntity;
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Purpose: Toggles this trigger between enabled and disabled.
//-----------------------------------------------------------------------------
void CBaseTrigger::InputToggle( inputdata_t &inputdata )
{
	if (IsSolidFlagSet( FSOLID_TRIGGER ))
	{
		RemoveSolidFlags(FSOLID_TRIGGER);
	}
	else
	{
		AddSolidFlags(FSOLID_TRIGGER);
	}

	PhysicsTouchTriggers();
}


//-----------------------------------------------------------------------------
// Purpose: Removes anything that touches it. If the trigger has a targetname,
//			firing it will toggle state.
//-----------------------------------------------------------------------------
class CTriggerRemove : public CBaseTrigger
{
public:
	DECLARE_CLASS( CTriggerRemove, CBaseTrigger );

	void Spawn( void );
	void Touch( CBaseEntity *pOther );
	
	DECLARE_DATADESC();
	
	// Outputs
	COutputEvent m_OnRemove;
};

BEGIN_DATADESC( CTriggerRemove )

	// Outputs
	DEFINE_OUTPUT( m_OnRemove, "OnRemove" ),

END_DATADESC()


LINK_ENTITY_TO_CLASS( trigger_remove, CTriggerRemove );


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTriggerRemove::Spawn( void )
{
	BaseClass::Spawn();
	InitTrigger();
}


//-----------------------------------------------------------------------------
// Purpose: Trigger hurt that causes radiation will do a radius check and set
//			the player's geiger counter level according to distance from center
//			of trigger.
//-----------------------------------------------------------------------------
void CTriggerRemove::Touch( CBaseEntity *pOther )
{
	if (!PassesTriggerFilters(pOther))
		return;

	UTIL_Remove( pOther );
}



//-----------------------------------------------------------------------------
// Purpose: Hurts anything that touches it. If the trigger has a targetname,
//			firing it will toggle state.
//-----------------------------------------------------------------------------
class CTriggerHurt : public CBaseTrigger
{
public:
	CTriggerHurt()
	{
		// This field came along after levels were built so the field defaults to 20 here in the constructor.
		m_flDamageCap = 20.0f;
	}

	DECLARE_CLASS( CTriggerHurt, CBaseTrigger );

	void Spawn( void );
	void RadiationThink( void );
	void HurtThink( void );
	void Touch( CBaseEntity *pOther );
	void EndTouch( CBaseEntity *pOther );
	bool HurtEntity( CBaseEntity *pOther, float damage );
	int HurtAllTouchers( float dt );
	
	DECLARE_DATADESC();
	
	float	m_flOriginalDamage;	// Damage as specified by the level designer.
	float	m_flDamage;			// Damage per second.
	float	m_flDamageCap;		// Maximum damage per second.
	float	m_flLastDmgTime;	// Time that we last applied damage.
	float	m_flDmgResetTime;	// For forgiveness, the time to reset the counter that accumulates damage.
	int		m_bitsDamageInflict;	// DMG_ damage type that the door or tigger does
	int		m_damageModel;

	enum
	{
		DAMAGEMODEL_NORMAL = 0,
		DAMAGEMODEL_DOUBLE_FORGIVENESS,
	};

	// Outputs
	COutputEvent m_OnHurt;
	COutputEvent m_OnHurtPlayer;

	CUtlVector<EHANDLE>	m_hurtEntities;
};

BEGIN_DATADESC( CTriggerHurt )

	// Function Pointers
	DEFINE_FUNCTION( RadiationThink ),
	DEFINE_FUNCTION( HurtThink ),

	// Fields
	DEFINE_FIELD( m_flOriginalDamage, FIELD_FLOAT ),
	DEFINE_KEYFIELD( m_flDamage, FIELD_FLOAT, "damage" ),
	DEFINE_KEYFIELD( m_flDamageCap, FIELD_FLOAT, "damagecap" ),
	DEFINE_KEYFIELD( m_bitsDamageInflict, FIELD_INTEGER, "damagetype" ),
	DEFINE_KEYFIELD( m_damageModel, FIELD_INTEGER, "damagemodel" ),

	DEFINE_FIELD( m_flLastDmgTime, FIELD_TIME ),
	DEFINE_FIELD( m_flDmgResetTime, FIELD_TIME ),
	DEFINE_UTLVECTOR( m_hurtEntities, FIELD_EHANDLE ),

	// Inputs
	DEFINE_INPUT( m_flDamage, FIELD_FLOAT, "SetDamage" ),

	// Outputs
	DEFINE_OUTPUT( m_OnHurt, "OnHurt" ),
	DEFINE_OUTPUT( m_OnHurtPlayer, "OnHurtPlayer" ),

END_DATADESC()


LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt );


//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerHurt::Spawn( void )
{
	BaseClass::Spawn();

	InitTrigger();

	m_flOriginalDamage = m_flDamage;

	SetNextThink( TICK_NEVER_THINK );
	SetThink( NULL );
	if (m_bitsDamageInflict & DMG_RADIATION)
	{
		SetThink ( &CTriggerHurt::RadiationThink );
		SetNextThink( gpGlobals->curtime + random->RandomFloat(0.0, 0.5) );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Trigger hurt that causes radiation will do a radius check and set
//			the player's geiger counter level according to distance from center
//			of trigger.
//-----------------------------------------------------------------------------
void CTriggerHurt::RadiationThink( void )
{
	// check to see if a player is in pvs
	// if not, continue	
	Vector vecSurroundMins, vecSurroundMaxs;
	CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
	CBasePlayer *pPlayer = static_cast<CBasePlayer *>(UTIL_FindClientInPVS( vecSurroundMins, vecSurroundMaxs ));

	if (pPlayer)
	{
		// get range to player;
		float flRange = CollisionProp()->CalcDistanceFromPoint( pPlayer->WorldSpaceCenter() );
		flRange *= 3.0f;
		pPlayer->NotifyNearbyRadiationSource(flRange);
	}

	float dt = gpGlobals->curtime - m_flLastDmgTime;
	if ( dt >= 0.5 )
	{
		HurtAllTouchers( dt );
	}

	SetNextThink( gpGlobals->curtime + 0.25 );
}


//-----------------------------------------------------------------------------
// Purpose: When touched, a hurt trigger does m_flDamage points of damage each half-second.
// Input  : pOther - The entity that is touching us.
//-----------------------------------------------------------------------------
bool CTriggerHurt::HurtEntity( CBaseEntity *pOther, float damage )
{
	if ( !pOther->m_takedamage || !PassesTriggerFilters(pOther) )
		return false;

	if ( damage < 0 )
	{
		pOther->TakeHealth( -damage, m_bitsDamageInflict );
	}
	else
	{
		// The damage position is the nearest point on the damaged entity
		// to the trigger's center. Not perfect, but better than nothing.
		Vector vecCenter = CollisionProp()->WorldSpaceCenter();

		Vector vecDamagePos;
		pOther->CollisionProp()->CalcNearestPoint( vecCenter, &vecDamagePos );

		CTakeDamageInfo info( this, this, damage, m_bitsDamageInflict );
		info.SetDamagePosition( vecDamagePos );
		GuessDamageForce( &info, ( vecDamagePos - vecCenter ), vecDamagePos );
		pOther->TakeDamage( info );
	}

	if (pOther->IsPlayer())
	{
		m_OnHurtPlayer.FireOutput(pOther, this);
	}
	else
	{
		m_OnHurt.FireOutput(pOther, this);
	}
	m_hurtEntities.AddToTail( EHANDLE(pOther) );
	//NDebugOverlay::Box( pOther->GetAbsOrigin(), pOther->WorldAlignMins(), pOther->WorldAlignMaxs(), 255,0,0,0,0.5 );
	return true;
}

void CTriggerHurt::HurtThink()
{
	// if I hurt anyone, think again
	if ( HurtAllTouchers( 0.5 ) <= 0 )
	{
		SetThink(NULL);
	}
	else
	{
		SetNextThink( gpGlobals->curtime + 0.5f );
	}
}

void CTriggerHurt::EndTouch( CBaseEntity *pOther )
{
	if (PassesTriggerFilters(pOther))
	{
		EHANDLE hOther;
		hOther = pOther;

		// if this guy has never taken damage, hurt him now
		if ( !m_hurtEntities.HasElement( hOther ) )
		{
			HurtEntity( pOther, m_flDamage * 0.5 );
		}
	}
	BaseClass::EndTouch( pOther );
}

//-----------------------------------------------------------------------------
// Purpose: called from RadiationThink() as well as HurtThink()
//			This function applies damage to any entities currently touching the
//			trigger
// Input  : dt - time since last call
// Output : int - number of entities actually hurt
//-----------------------------------------------------------------------------
#define TRIGGER_HURT_FORGIVE_TIME	3.0f	// time in seconds
int CTriggerHurt::HurtAllTouchers( float dt )
{
	int hurtCount = 0;
	// half second worth of damage
	float fldmg = m_flDamage * dt;
	m_flLastDmgTime = gpGlobals->curtime;

	m_hurtEntities.RemoveAll();

	touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
	if ( root )
	{
		for ( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
		{
			CBaseEntity *pTouch = link->entityTouched;
			if ( pTouch )
			{
				if ( HurtEntity( pTouch, fldmg ) )
				{
					hurtCount++;
				}
			}
		}
	}

	if( m_damageModel == DAMAGEMODEL_DOUBLE_FORGIVENESS )
	{
		if( hurtCount == 0 )
		{
			if( gpGlobals->curtime > m_flDmgResetTime  )
			{
				// Didn't hurt anyone. Reset the damage if it's time. (hence, the forgiveness)
				m_flDamage = m_flOriginalDamage;
			}
		}
		else
		{
			// Hurt someone! double the damage
			m_flDamage *= 2.0f;

			if( m_flDamage > m_flDamageCap )
			{
				// Clamp
				m_flDamage = m_flDamageCap;
			}

			// Now, put the damage reset time into the future. The forgive time is how long the trigger
			// must go without harming anyone in order that its accumulated damage be reset to the amount
			// set by the level designer. This is a stop-gap for an exploit where players could hop through
			// slime and barely take any damage because the trigger would reset damage anytime there was no
			// one in the trigger when this function was called. (sjb)
			m_flDmgResetTime = gpGlobals->curtime + TRIGGER_HURT_FORGIVE_TIME;
		}
	}

	return hurtCount;
}

void CTriggerHurt::Touch( CBaseEntity *pOther )
{
	if ( m_pfnThink == NULL )
	{
		SetThink( &CTriggerHurt::HurtThink );
		SetNextThink( gpGlobals->curtime );
	}
}


// ##################################################################################
//	>> TriggerMultiple
// ##################################################################################
LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple );


BEGIN_DATADESC( CTriggerMultiple )

	// Function Pointers
	DEFINE_FUNCTION(MultiTouch),
	DEFINE_FUNCTION(MultiWaitOver ),

	// Outputs
	DEFINE_OUTPUT(m_OnTrigger, "OnTrigger")

END_DATADESC()



//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerMultiple::Spawn( void )
{
	BaseClass::Spawn();

	InitTrigger();

	if (m_flWait == 0)
	{
		m_flWait = 0.2;
	}

	ASSERTSZ(m_iHealth == 0, "trigger_multiple with health");
	SetTouch( &CTriggerMultiple::MultiTouch );
}


//-----------------------------------------------------------------------------
// Purpose: Touch function. Activates the trigger.
// Input  : pOther - The thing that touched us.
//-----------------------------------------------------------------------------
void CTriggerMultiple::MultiTouch(CBaseEntity *pOther)
{
	if (PassesTriggerFilters(pOther))
	{
		ActivateMultiTrigger( pOther );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pActivator - 
//-----------------------------------------------------------------------------
void CTriggerMultiple::ActivateMultiTrigger(CBaseEntity *pActivator)
{
	if (GetNextThink() > gpGlobals->curtime)
		return;         // still waiting for reset time

	m_hActivator = pActivator;

	m_OnTrigger.FireOutput(m_hActivator, this);

	if (m_flWait > 0)
	{
		SetThink( &CTriggerMultiple::MultiWaitOver );
		SetNextThink( gpGlobals->curtime + m_flWait );
	}
	else
	{
		// we can't just remove (self) here, because this is a touch function
		// called while C code is looping through area links...
		SetTouch( NULL );
		SetNextThink( gpGlobals->curtime + 0.1f );
		SetThink(  &CTriggerMultiple::SUB_Remove );
	}
}


//-----------------------------------------------------------------------------
// Purpose: The wait time has passed, so set back up for another activation
//-----------------------------------------------------------------------------
void CTriggerMultiple::MultiWaitOver( void )
{
	SetThink( NULL );
}

// ##################################################################################
//	>> TriggerOnce
// ##################################################################################
class CTriggerOnce : public CTriggerMultiple
{
	DECLARE_CLASS( CTriggerOnce, CTriggerMultiple );
public:

	void Spawn( void );
};

LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce );

void CTriggerOnce::Spawn( void )
{
	BaseClass::Spawn();

	m_flWait = -1;
}


// ##################################################################################
//	>> TriggerLook
//
//  Triggers once when player is looking at m_target
//
// ##################################################################################
#define SF_TRIGGERLOOK_FIREONCE		128
#define SF_TRIGGERLOOK_USEVELOCITY	256

class CTriggerLook : public CTriggerOnce
{
	DECLARE_CLASS( CTriggerLook, CTriggerOnce );
public:

	EHANDLE m_hLookTarget;
	float m_flFieldOfView;
	float m_flLookTime;			// How long must I look for
	float m_flLookTimeTotal;	// How long have I looked
	float m_flLookTimeLast;		// When did I last look
	float m_flTimeoutDuration;	// Number of seconds after start touch to fire anyway
	bool m_bTimeoutFired;		// True if the OnTimeout output fired since the last StartTouch.
	EHANDLE m_hActivator;		// The entity that triggered us.

	void Spawn( void );
	void Touch( CBaseEntity *pOther );
	void StartTouch(CBaseEntity *pOther);
	void EndTouch( CBaseEntity *pOther );
	int	 DrawDebugTextOverlays(void);

	DECLARE_DATADESC();

private:

	void Trigger(CBaseEntity *pActivator, bool bTimeout);
	void TimeoutThink();

	COutputEvent m_OnTimeout;
};

LINK_ENTITY_TO_CLASS( trigger_look, CTriggerLook );
BEGIN_DATADESC( CTriggerLook )

	DEFINE_FIELD( m_hLookTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_flLookTimeTotal, FIELD_FLOAT ),
	DEFINE_FIELD( m_flLookTimeLast, FIELD_TIME ),
	DEFINE_KEYFIELD( m_flTimeoutDuration, FIELD_FLOAT, "timeout" ),
	DEFINE_FIELD( m_bTimeoutFired, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ),

	DEFINE_OUTPUT( m_OnTimeout, "OnTimeout" ),

	DEFINE_FUNCTION( TimeoutThink ),

	// Inputs
	DEFINE_INPUT( m_flFieldOfView,		FIELD_FLOAT,	"FieldOfView" ),
	DEFINE_INPUT( m_flLookTime,			FIELD_FLOAT,	"LookTime" ),

END_DATADESC()


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerLook::Spawn( void )
{
	m_hLookTarget = NULL;
	m_flLookTimeTotal = -1;
	m_bTimeoutFired = false;

	BaseClass::Spawn();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pOther - 
//-----------------------------------------------------------------------------
void CTriggerLook::StartTouch(CBaseEntity *pOther)
{
	BaseClass::StartTouch(pOther);

	if (pOther->IsPlayer() && m_flTimeoutDuration)
	{
		m_bTimeoutFired = false;
		m_hActivator = pOther;
		SetThink(&CTriggerLook::TimeoutThink);
		SetNextThink(gpGlobals->curtime + m_flTimeoutDuration);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTriggerLook::TimeoutThink(void)
{
	Trigger(m_hActivator, true);
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerLook::EndTouch(CBaseEntity *pOther)
{
	BaseClass::EndTouch(pOther);

	if (pOther->IsPlayer())
	{
		SetThink(NULL);
		SetNextThink(0);

		m_flLookTimeTotal = -1;
	}
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerLook::Touch(CBaseEntity *pOther)
{
	// Don't fire the OnTrigger if we've already fired the OnTimeout. This will be
	// reset in OnEndTouch.
	if (m_bTimeoutFired)
		return;

	// --------------------------------
	// Make sure we have a look target
	// --------------------------------
	if (m_hLookTarget == NULL)
	{
		m_hLookTarget = GetNextTarget();
		if (m_hLookTarget == NULL)
		{
			return;
		}
	}

	// This is designed for single player only
	// so we'll always have the same player
	if (pOther->IsPlayer())
	{
		// ----------------------------------------
		// Check that toucher is facing the target
		// ----------------------------------------
		Vector vLookDir;
		if ( HasSpawnFlags( SF_TRIGGERLOOK_USEVELOCITY ) )
		{
			vLookDir = pOther->GetAbsVelocity();
			if ( vLookDir == vec3_origin )
			{
				// See if they're in a vehicle
				CBasePlayer *pPlayer = (CBasePlayer *)pOther;
				if ( pPlayer->IsInAVehicle() )
				{
					vLookDir = pPlayer->GetVehicle()->GetVehicleEnt()->GetSmoothedVelocity();
				}
			}
			VectorNormalize( vLookDir );
		}
		else
		{
			vLookDir = ((CBaseCombatCharacter*)pOther)->EyeDirection3D( );
		}

		Vector vTargetDir = m_hLookTarget->GetAbsOrigin() - pOther->EyePosition();
		VectorNormalize(vTargetDir);

		float fDotPr = DotProduct(vLookDir,vTargetDir);
		if (fDotPr > m_flFieldOfView)
		{
			// Is it the first time I'm looking?
			if (m_flLookTimeTotal == -1)
			{
				m_flLookTimeLast	= gpGlobals->curtime;
				m_flLookTimeTotal	= 0;
			}
			else
			{
				m_flLookTimeTotal	+= gpGlobals->curtime - m_flLookTimeLast;
				m_flLookTimeLast	=  gpGlobals->curtime;
			}

			if (m_flLookTimeTotal >= m_flLookTime)
			{
				Trigger(pOther, false);
			}
		}
		else
		{
			m_flLookTimeTotal	= -1;
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Called when the trigger is fired by look logic or timeout.
//-----------------------------------------------------------------------------
void CTriggerLook::Trigger(CBaseEntity *pActivator, bool bTimeout)
{
	if (bTimeout)
	{
		// Fired due to timeout (player never looked at the target).
		m_OnTimeout.FireOutput(pActivator, this);

		// Don't fire the OnTrigger for this toucher.
		m_bTimeoutFired = true;
	}
	else
	{
		// Fire because the player looked at the target.
		m_OnTrigger.FireOutput(pActivator, this);
		m_flLookTimeTotal = -1;

		// Cancel the timeout think.
		SetThink(NULL);
		SetNextThink(0);
	}

	if (HasSpawnFlags(SF_TRIGGERLOOK_FIREONCE))
	{
		SetThink(&CTriggerLook::SUB_Remove);
		SetNextThink(gpGlobals->curtime);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CTriggerLook::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		// ----------------
		// Print Look time
		// ----------------
		char tempstr[255];
		Q_snprintf(tempstr,sizeof(tempstr),"Time:   %3.2f",m_flLookTime - max(0,m_flLookTimeTotal));
		NDebugOverlay::EntityText(entindex(),text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}


// ##################################################################################
//	>> TriggerVolume
// ##################################################################################
class CTriggerVolume : public CPointEntity	// Derive from point entity so this doesn't move across levels
{
public:
	DECLARE_CLASS( CTriggerVolume, CPointEntity );

	void		Spawn( void );
};

LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume );

// Define space that travels across a level transition
void CTriggerVolume::Spawn( void )
{
	SetSolid( SOLID_BSP );
	AddSolidFlags( FSOLID_NOT_SOLID );
	SetMoveType( MOVETYPE_NONE );
	SetModel( STRING( GetModelName() ) );    // set size and link into world
	if ( showtriggers.GetInt() == 0 )
	{
		AddEffects( EF_NODRAW );
	}
}

#define SF_CHANGELEVEL_NOTOUCH		0x0002
#define SF_CHANGELEVEL_CHAPTER		0x0004

#define cchMapNameMost 32

enum
{
	TRANSITION_VOLUME_SCREENED_OUT = 0,
	TRANSITION_VOLUME_NOT_FOUND = 1,
	TRANSITION_VOLUME_PASSED = 2,
};


//------------------------------------------------------------------------------
// Reesponsible for changing levels when the player touches it
//------------------------------------------------------------------------------
class CChangeLevel : public CBaseTrigger
{
	DECLARE_DATADESC();

public:
	DECLARE_CLASS( CChangeLevel, CBaseTrigger );

	void Spawn( void );
	void Activate( void );
	bool KeyValue( const char *szKeyName, const char *szValue );

	static int ChangeList( levellist_t *pLevelList, int maxList );

private:
	void TouchChangeLevel( CBaseEntity *pOther );
	void ChangeLevelNow( CBaseEntity *pActivator );

	void InputChangeLevel( inputdata_t &inputdata );

	bool IsEntityInTransition( CBaseEntity *pEntity );
	void NotifyEntitiesOutOfTransition();

	static CBaseEntity *FindLandmark( const char *pLandmarkName );
	static int AddTransitionToList( levellist_t *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark );
	static int InTransitionVolume( CBaseEntity *pEntity, const char *pVolumeName );

	// Builds the list of entities to save when moving across a transition
	static int BuildChangeLevelList( levellist_t *pLevelList, int maxList );

	// Builds the list of entities to bring across a particular transition
	static int BuildEntityTransitionList( CBaseEntity *pLandmarkEntity, const char *pLandmarkName, CBaseEntity **ppEntList, int *pEntityFlags, int nMaxList );

	// Adds a single entity to the transition list, if appropriate. Returns the new count
	static int AddEntityToTransitionList( CBaseEntity *pEntity, int flags, int nCount, CBaseEntity **ppEntList, int *pEntityFlags );

	// Adds in all entities depended on by entities near the transition
	static int AddDependentEntities( int nCount, CBaseEntity **ppEntList, int *pEntityFlags, int nMaxList );

	// Figures out save flags for the entity
	static int ComputeEntitySaveFlags( CBaseEntity *pEntity );

private:
	char m_szMapName[cchMapNameMost];		// trigger_changelevel only:  next map
	char m_szLandmarkName[cchMapNameMost];		// trigger_changelevel only:  landmark on next map
	bool m_bTouched;

	// Outputs
	COutputEvent m_OnChangeLevel;
};


LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel );

// Global Savedata for changelevel trigger
BEGIN_DATADESC( CChangeLevel )

	DEFINE_AUTO_ARRAY( m_szMapName, FIELD_CHARACTER ),
	DEFINE_AUTO_ARRAY( m_szLandmarkName, FIELD_CHARACTER ),
//	DEFINE_FIELD( m_touchTime, FIELD_TIME ),	// don't save
//	DEFINE_FIELD( m_bTouched, FIELD_BOOLEAN ),

	// Function Pointers
	DEFINE_FUNCTION( TouchChangeLevel ),

	DEFINE_INPUTFUNC( FIELD_VOID, "ChangeLevel", InputChangeLevel ),

	// Outputs
	DEFINE_OUTPUT( m_OnChangeLevel, "OnChangeLevel"),

END_DATADESC()


//
// Cache user-entity-field values until spawn is called.
//

bool CChangeLevel::KeyValue( const char *szKeyName, const char *szValue )
{
	if (FStrEq(szKeyName, "map"))
	{
		if (strlen(szValue) >= cchMapNameMost)
		{
			Warning( "Map name '%s' too long (32 chars)\n", szValue );
			Assert(0);
		}
		Q_strncpy(m_szMapName, szValue, sizeof(m_szMapName));
	}
	else if (FStrEq(szKeyName, "landmark"))
	{
		if (strlen(szValue) >= cchMapNameMost)
		{
			Warning( "Landmark name '%s' too long (32 chars)\n", szValue );
			Assert(0);
		}
		
		Q_strncpy(m_szLandmarkName, szValue, sizeof( m_szLandmarkName ));
	}
	else
		return BaseClass::KeyValue( szKeyName, szValue );

	return true;
}



void CChangeLevel::Spawn( void )
{
	if ( FStrEq( m_szMapName, "" ) )
	{
		Msg( "a trigger_changelevel doesn't have a map" );
	}

	if ( FStrEq( m_szLandmarkName, "" ) )
	{
		Msg( "trigger_changelevel to %s doesn't have a landmark", m_szMapName );
	}

	InitTrigger();
	
	if ( !HasSpawnFlags(SF_CHANGELEVEL_NOTOUCH) )
	{
		SetTouch( &CChangeLevel::TouchChangeLevel );
	}

//	Msg( "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName );
}

void CChangeLevel::Activate( void )
{
	BaseClass::Activate();

	if ( gpGlobals->eLoadType == MapLoad_NewGame )
	{
		if ( HasSpawnFlags( SF_CHANGELEVEL_CHAPTER ) )
		{
			VPhysicsInitStatic();
			RemoveSolidFlags( FSOLID_NOT_SOLID | FSOLID_TRIGGER );
			SetTouch( NULL );
			return;
		}
	}

	// Level transitions will bust if they are in solid
	CBaseEntity *pLandmark = FindLandmark( m_szLandmarkName );
	if ( pLandmark )
	{
		int clusterIndex = engine->GetClusterForOrigin( pLandmark->GetAbsOrigin() );
		if ( clusterIndex < 0 )
		{
			Warning( "trigger_changelevel to map %s has a landmark embedded in solid!\n"
					"This will break level transitions!\n", m_szMapName ); 
		}

		if ( g_debug_transitions.GetInt() )
		{
			if ( !gEntList.FindEntityByClassname( NULL, "trigger_transition" ) )
			{
				Warning( "Map has no trigger_transition volumes for landmark %s\n", m_szLandmarkName );
			}
		}
	}

	m_bTouched = false;
}


static char st_szNextMap[cchMapNameMost];
static char st_szNextSpot[cchMapNameMost];

// Used to show debug for only the transition volume we're currently in
static int g_iDebuggingTransition = 0;

CBaseEntity *CChangeLevel::FindLandmark( const char *pLandmarkName )
{
	CBaseEntity *pentLandmark;

	pentLandmark = gEntList.FindEntityByName( NULL, pLandmarkName, NULL );
	while ( pentLandmark )
	{
		// Found the landmark
		if ( FClassnameIs( pentLandmark, "info_landmark" ) )
			return pentLandmark;
		else
			pentLandmark = gEntList.FindEntityByName( pentLandmark, pLandmarkName, NULL );
	}
	Warning( "Can't find landmark %s\n", pLandmarkName );
	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: Allows level transitions to be triggered by buttons, etc.
//-----------------------------------------------------------------------------
void CChangeLevel::InputChangeLevel( inputdata_t &inputdata )
{
	// Ignore changelevel transitions if the player's dead
	if ( gpGlobals->maxClients == 1 )
	{
		CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
		if ( pPlayer && !pPlayer->IsAlive() )
			return;
	}

	ChangeLevelNow( inputdata.pActivator );
}


//-----------------------------------------------------------------------------
// Purpose: Performs the level change and fires targets.
// Input  : pActivator - 
//-----------------------------------------------------------------------------
bool CChangeLevel::IsEntityInTransition( CBaseEntity *pEntity )
{
	int transitionState = InTransitionVolume(pEntity, m_szLandmarkName);
	if ( transitionState == TRANSITION_VOLUME_SCREENED_OUT )
	{
		return false;
	}

	// look for a landmark entity		
	CBaseEntity	*pLandmark = FindLandmark( m_szLandmarkName );

	if ( !pLandmark )
		return false;

	// no transition volumes, check PVS of landmark
	if ( transitionState == TRANSITION_VOLUME_NOT_FOUND )
	{
		byte pvs[MAX_MAP_CLUSTERS/8];
		int clusterIndex = engine->GetClusterForOrigin( pLandmark->GetAbsOrigin() );
		engine->GetPVSForCluster( clusterIndex, sizeof(pvs), pvs );
		Vector vecSurroundMins, vecSurroundMaxs;
		pEntity->CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );

		if ( !engine->CheckBoxInPVS( vecSurroundMins, vecSurroundMaxs, pvs, sizeof( pvs ) ) )
		{
			return false;
		}
	}

	return true;
}

void CChangeLevel::NotifyEntitiesOutOfTransition()
{
	CBaseEntity *pEnt = gEntList.FirstEnt();
	while ( pEnt )
	{
		// Found the landmark
		if ( pEnt->ObjectCaps() & FCAP_NOTIFY_NOT_IN_TRANSITION )
		{
			if ( !IsEntityInTransition( pEnt ) )
			{
				variant_t emptyVariant;
				pEnt->AcceptInput( "OutsideTransition", this, this, emptyVariant, 0 );
				//pEnt->OnOutsideTransition();
			}
		}
		pEnt = gEntList.NextEnt( pEnt );
	}

}

void CChangeLevel::ChangeLevelNow( CBaseEntity *pActivator )
{
	CBaseEntity	*pLandmark;
	levellist_t	levels[16];

	Assert(!FStrEq(m_szMapName, ""));

	// Don't work in deathmatch
	if ( g_pGameRules->IsDeathmatch() )
		return;

	// Some people are firing these multiple times in a frame, disable
	if ( m_bTouched )
		return;

	m_bTouched = true;

	CBaseEntity *pPlayer = (pActivator && pActivator->IsPlayer()) ? pActivator : UTIL_GetLocalPlayer();

	int transitionState = InTransitionVolume(pPlayer, m_szLandmarkName);
	if ( transitionState == TRANSITION_VOLUME_SCREENED_OUT )
	{
		DevMsg( 2, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName );
		return;
	}

	// look for a landmark entity		
	pLandmark = FindLandmark( m_szLandmarkName );

	if ( !pLandmark )
		return;

	// no transition volumes, check PVS of landmark
	if ( transitionState == TRANSITION_VOLUME_NOT_FOUND )
	{
		byte pvs[MAX_MAP_CLUSTERS/8];
		int clusterIndex = engine->GetClusterForOrigin( pLandmark->GetAbsOrigin() );
		engine->GetPVSForCluster( clusterIndex, sizeof(pvs), pvs );
		if ( pPlayer )
		{
			Vector vecSurroundMins, vecSurroundMaxs;
			pPlayer->CollisionProp()->WorldSpaceSurroundingBounds( &vecSurroundMins, &vecSurroundMaxs );
			bool playerInPVS = engine->CheckBoxInPVS( vecSurroundMins, vecSurroundMaxs, pvs, sizeof( pvs ) );

			//Assert( playerInPVS );
			if ( !playerInPVS )
			{
				Warning( "Player isn't in the landmark's (%s) PVS, aborting\n", m_szLandmarkName );
#ifndef HL1_DLL
				// HL1 works even with these errors!
				return;
#endif
			}
		}
	}

	g_iDebuggingTransition = 0;
	st_szNextSpot[0] = 0;	// Init landmark to NULL
	Q_strncpy(st_szNextSpot, m_szLandmarkName,sizeof(st_szNextSpot));
	// This object will get removed in the call to engine->ChangeLevel, copy the params into "safe" memory
	Q_strncpy(st_szNextMap, m_szMapName, sizeof(st_szNextMap));

	m_hActivator = pActivator;

	m_OnChangeLevel.FireOutput(pActivator, this);

	NotifyEntitiesOutOfTransition();


////	Msg( "Level touches %d levels\n", ChangeList( levels, 16 ) );
	if ( g_debug_transitions.GetInt() )
	{
		Msg( "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot );
	}

	// If we're debugging, don't actually change level
	if ( g_debug_transitions.GetInt() == 0 )
	{
		engine->ChangeLevel( st_szNextMap, st_szNextSpot );
	}
	else
	{
		// Build a change list so we can see what would be transitioning
		CSaveRestoreData *pSaveData = SaveInit( 0 );
		if ( pSaveData )
		{
			g_pGameSaveRestoreBlockSet->PreSave( pSaveData );
			pSaveData->levelInfo.connectionCount = BuildChangeList( pSaveData->levelInfo.levelList, MAX_LEVEL_CONNECTIONS );
			g_pGameSaveRestoreBlockSet->PostSave();
		}

		SetTouch( NULL );
	}
}

//
// GLOBALS ASSUMED SET:  st_szNextMap
//
void CChangeLevel::TouchChangeLevel( CBaseEntity *pOther )
{
	CBasePlayer *pPlayer = ToBasePlayer(pOther);
	if ( !pPlayer )
		return;

	if ( !pPlayer->IsInAVehicle() && pPlayer->GetMoveType() == MOVETYPE_NOCLIP )
	{
		DevMsg("In level transition: %s %s\n", st_szNextMap, st_szNextSpot );
		return;
	}

	ChangeLevelNow( pOther );
}


// Add a transition to the list, but ignore duplicates 
// (a designer may have placed multiple trigger_changelevels with the same landmark)
int CChangeLevel::AddTransitionToList( levellist_t *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark )
{
	int i;

	if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark )
		return 0;

	// Ignore changelevels to the level we're ready in. Mapmakers love to do this!
	if ( stricmp( pMapName, STRING(gpGlobals->mapname) ) == 0 )
		return 0;

	for ( i = 0; i < listCount; i++ )
	{
		if ( pLevelList[i].pentLandmark == pentLandmark && stricmp( pLevelList[i].mapName, pMapName ) == 0 )
			return 0;
	}
	Q_strncpy( pLevelList[listCount].mapName, pMapName, sizeof(pLevelList[listCount].mapName) );
	Q_strncpy( pLevelList[listCount].landmarkName, pLandmarkName, sizeof(pLevelList[listCount].landmarkName) );
	pLevelList[listCount].pentLandmark = pentLandmark;

	CBaseEntity *ent = CBaseEntity::Instance( pentLandmark );
	Assert( ent );

	pLevelList[listCount].vecLandmarkOrigin = ent->GetAbsOrigin();

	return 1;
}

int BuildChangeList( levellist_t *pLevelList, int maxList )
{
	return CChangeLevel::ChangeList( pLevelList, maxList );
}

struct collidelist_t
{
	const CPhysCollide	*pCollide;
	Vector			origin;
	QAngle			angles;
};


// NOTE: This routine is relatively slow.  If you need to use it for per-frame work, consider that fact.
// UNDONE: Expand this to the full matrix of solid types on each side and move into enginetrace
static bool TestEntityTriggerIntersection_Accurate( CBaseEntity *pTrigger, CBaseEntity *pEntity )
{
	Assert( pTrigger->GetSolid() == SOLID_BSP );

	if ( pTrigger->Intersects( pEntity ) )	// It touches one, it's in the volume
	{
		switch ( pEntity->GetSolid() )
		{
		case SOLID_BBOX:
			{
				ICollideable *pCollide = pTrigger->CollisionProp();
				Ray_t ray;
				trace_t tr;
				ray.Init( pEntity->GetAbsOrigin(), pEntity->GetAbsOrigin(), pEntity->WorldAlignMins(), pEntity->WorldAlignMaxs() );
				enginetrace->ClipRayToCollideable( ray, MASK_ALL, pCollide, &tr );

				if ( tr.startsolid )
					return true;
			}
			break;
		case SOLID_BSP:
		case SOLID_VPHYSICS:
			{
				CPhysCollide *pTriggerCollide = modelinfo->GetVCollide( pTrigger->GetModelIndex() )->solids[0];
				Assert( pTriggerCollide );

				CUtlVector<collidelist_t> collideList;
				IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
				int physicsCount = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
				if ( physicsCount )
				{
					for ( int i = 0; i < physicsCount; i++ )
					{
						const CPhysCollide *pCollide = pList[i]->GetCollide();
						if ( pCollide )
						{
							collidelist_t element;
							element.pCollide = pCollide;
							pList[i]->GetPosition( &element.origin, &element.angles );
							collideList.AddToTail( element );
						}
					}
				}
				else
				{
					vcollide_t *pVCollide = modelinfo->GetVCollide( pEntity->GetModelIndex() );
					if ( pVCollide && pVCollide->solidCount )
					{
						collidelist_t element;
						element.pCollide = pVCollide->solids[0];
						element.origin = pEntity->GetAbsOrigin();
						element.angles = pEntity->GetAbsAngles();
						collideList.AddToTail( element );
					}
				}
				for ( int i = collideList.Count()-1; i >= 0; --i )
				{
					const collidelist_t &element = collideList[i];
					trace_t tr;
					physcollision->TraceCollide( element.origin, element.origin, element.pCollide, element.angles, pTriggerCollide, pTrigger->GetAbsOrigin(), pTrigger->GetAbsAngles(), &tr );
					if ( tr.startsolid )
						return true;
				}
			}
			break;

		default:
			return true;
		}
	}
	return false;
}

int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, const char *pVolumeName )
{
	CBaseEntity *pVolume;

	if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION )
		return TRANSITION_VOLUME_PASSED;

	// If you're following another entity, follow it through the transition (weapons follow the player)
	pEntity = pEntity->GetRootMoveParent();

	int inVolume = TRANSITION_VOLUME_NOT_FOUND;	// Unless we find a trigger_transition, everything is in the volume

	pVolume = gEntList.FindEntityByName( NULL, pVolumeName, NULL );
	while ( pVolume )
	{
		if ( pVolume && FClassnameIs( pVolume, "trigger_transition" ) )
		{
			if ( TestEntityTriggerIntersection_Accurate(pVolume,  pEntity ) )	// It touches one, it's in the volume
				return TRANSITION_VOLUME_PASSED;

			inVolume = TRANSITION_VOLUME_SCREENED_OUT;	// Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go!
		}
		pVolume = gEntList.FindEntityByName( pVolume, pVolumeName, NULL );
	}
	return inVolume;
}


//------------------------------------------------------------------------------
// Builds the list of entities to save when moving across a transition
//------------------------------------------------------------------------------
int CChangeLevel::BuildChangeLevelList( levellist_t *pLevelList, int maxList )
{
	int nCount = 0;

	CBaseEntity *pentChangelevel = gEntList.FindEntityByClassname( NULL, "trigger_changelevel" );
	while ( pentChangelevel )
	{
		CChangeLevel *pTrigger = dynamic_cast<CChangeLevel *>(pentChangelevel);
		if ( pTrigger )
		{
			// Find the corresponding landmark
			CBaseEntity *pentLandmark = FindLandmark( pTrigger->m_szLandmarkName );
			if ( pentLandmark )
			{
				// Build a list of unique transitions
				if ( AddTransitionToList( pLevelList, nCount, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark->edict() ) )
				{
					++nCount;
					if ( nCount >= maxList )		// FULL!!
						break;
				}
			}
		}
		pentChangelevel = gEntList.FindEntityByClassname( pentChangelevel, "trigger_changelevel" );
	}

	return nCount;
}


//------------------------------------------------------------------------------
// Adds a single entity to the transition list, if appropriate. Returns the new count
//------------------------------------------------------------------------------
int CChangeLevel::ComputeEntitySaveFlags( CBaseEntity *pEntity )
{
	if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE )
	{
		Msg( "Trying %s (%s): ", pEntity->GetClassname(), pEntity->GetDebugName() );
	}

	int caps = pEntity->ObjectCaps();
	if ( caps & FCAP_DONT_SAVE )
	{
		if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE )
		{
			Msg( "IGNORED due to being marked \"Don't save\".\n" );
		}
		return 0;
	}

	// If this entity can be moved or is global, mark it
	int flags = 0;
	if ( caps & FCAP_ACROSS_TRANSITION )
	{
		flags |= FENTTABLE_MOVEABLE;
	}
	if ( pEntity->m_iGlobalname != NULL_STRING && !pEntity->IsDormant() )
	{
		flags |= FENTTABLE_GLOBAL;
	}
	
	if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE && !flags )
	{
		Msg( "IGNORED, no across_transition flag & no globalname\n" );
	}

	return flags;
}


//------------------------------------------------------------------------------
// Adds a single entity to the transition list, if appropriate. Returns the new count
//------------------------------------------------------------------------------
inline int CChangeLevel::AddEntityToTransitionList( CBaseEntity *pEntity, int flags, int nCount, CBaseEntity **ppEntList, int *pEntityFlags )
{
	ppEntList[ nCount ] = pEntity;
	pEntityFlags[ nCount ] = flags;
	++nCount;

	// If we're debugging, make it visible
	if ( g_iDebuggingTransition )
	{
		if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE )
		{
			// In verbose mode we've already printed out what the entity is
			Msg("ADDED.\n");
		}
		else
		{
			// In non-verbose mode, we just print this line
			Msg( "ADDED %s (%s) to transition.\n", pEntity->GetClassname(), pEntity->GetDebugName() );
		}

		pEntity->m_debugOverlays |= (OVERLAY_BBOX_BIT | OVERLAY_NAME_BIT);
	}

	return nCount;
}


//------------------------------------------------------------------------------
// Builds the list of entities to bring across a particular transition
//------------------------------------------------------------------------------
int CChangeLevel::BuildEntityTransitionList( CBaseEntity *pLandmarkEntity, const char *pLandmarkName,
	CBaseEntity **ppEntList, int *pEntityFlags, int nMaxList )
{
	int iEntity = 0;

	// Only show debug for the transition to the level we're going to
	if ( g_debug_transitions.GetInt() && pLandmarkEntity->NameMatches(st_szNextSpot) )
	{
		g_iDebuggingTransition = g_debug_transitions.GetInt();

		// Show us where the landmark entity is
		pLandmarkEntity->m_debugOverlays |= (OVERLAY_PIVOT_BIT | OVERLAY_BBOX_BIT | OVERLAY_NAME_BIT);
	}
	else
	{
		g_iDebuggingTransition = 0;
	}

	// Follow the linked list of entities in the PVS of the transition landmark
	CBaseEntity *pEntity = NULL; 
	while ( (pEntity = UTIL_EntitiesInPVS( pLandmarkEntity, pEntity)) != NULL )
	{
		int flags = ComputeEntitySaveFlags( pEntity );
		if ( !flags )
			continue;

		// Check to make sure the entity isn't screened out by a trigger_transition
		if ( !InTransitionVolume( pEntity, pLandmarkName ) )
		{
			if ( g_iDebuggingTransition == DEBUG_TRANSITIONS_VERBOSE )
			{
				Msg( "IGNORED, outside transition volume.\n" );
			}
			continue;
		}

		if ( iEntity >= nMaxList )
		{
			Warning( "Too many entities across a transition!\n" );
			Assert( 0 );
			return iEntity;
		}

		iEntity = AddEntityToTransitionList( pEntity, flags, iEntity, ppEntList, pEntityFlags );
	}

	return iEntity;
}


//------------------------------------------------------------------------------
// Tests bits in a bitfield
//------------------------------------------------------------------------------
static inline bool IsBitSet( char *pBuf, int nBit )
{
	return (pBuf[ nBit >> 3 ] & ( 1 << (nBit & 0x7) )) != 0;
}

static inline void SetBit( char *pBuf, int nBit )
{
	pBuf[ nBit >> 3 ] |= 1 << (nBit & 0x7);
}


//------------------------------------------------------------------------------
// Adds in all entities depended on by entities near the transition
//------------------------------------------------------------------------------
#define MAX_ENTITY_BYTE_COUNT	(NUM_ENT_ENTRIES >> 3)
int CChangeLevel::AddDependentEntities( int nCount, CBaseEntity **ppEntList, int *pEntityFlags, int nMaxList )
{
	char pEntitiesSaved[MAX_ENTITY_BYTE_COUNT];
	memset( pEntitiesSaved, 0, MAX_ENTITY_BYTE_COUNT * sizeof(char) );
			  
	// Populate the initial bitfield
	int i;
	for ( i = 0; i < nCount; ++i )
	{
		// NOTE: Must use GetEntryIndex because we're saving non-networked entities
		int nEntIndex = ppEntList[i]->GetRefEHandle().GetEntryIndex();

		// We shouldn't already have this entity in the list!
		Assert( !IsBitSet( pEntitiesSaved, nEntIndex ) );

		// Mark the entity as being in the list
		SetBit( pEntitiesSaved, nEntIndex );
	}

	IEntitySaveUtils *pSaveUtils = GetEntitySaveUtils();

	// Iterate over entities whose dependencies we've not yet processed
	// NOTE: nCount will change value during this loop in AddEntityToTransitionList
	for ( i = 0; i < nCount; ++i )
	{
		CBaseEntity *pEntity = ppEntList[i];

		// Find dependencies in the hash.
		int nDepCount = pSaveUtils->GetEntityDependencyCount( pEntity );
		if ( !nDepCount )
			continue;

		CBaseEntity **ppDependentEntities = (CBaseEntity**)stackalloc( nDepCount * sizeof(CBaseEntity*) );
		pSaveUtils->GetEntityDependencies( pEntity, nDepCount, ppDependentEntities );
		for ( int j = 0; j < nDepCount; ++j )
		{
			CBaseEntity *pDependent = ppDependentEntities[j];
			if ( !pDependent )
				continue;

			// NOTE: Must use GetEntryIndex because we're saving non-networked entities
			int nEntIndex = pDependent->GetRefEHandle().GetEntryIndex();

			// Don't re-add it if it's already in the list
			if ( IsBitSet( pEntitiesSaved, nEntIndex ) )
				continue;

			// Mark the entity as being in the list
			SetBit( pEntitiesSaved, nEntIndex );

			int flags = ComputeEntitySaveFlags( pEntity );
			if ( flags )
			{
				if ( nCount >= nMaxList )
				{
					Warning( "Too many entities across a transition!\n" );
					Assert( 0 );
					return false;
				}

				if ( g_debug_transitions.GetInt() )
				{
					Msg( "ADDED DEPENDANCY: %s (%s)\n", pEntity->GetClassname(), pEntity->GetDebugName() );
				}

				nCount = AddEntityToTransitionList( pEntity, flags, nCount, ppEntList, pEntityFlags );
			}
			else
			{
				Warning("Warning!! Save dependency is linked to an entity that doesn't want to be saved!\n");
			}
		}
	}

	return nCount;
}


//------------------------------------------------------------------------------
// This builds the list of all transitions on this level and which entities 
// are in their PVS's and can / should be moved across.
//------------------------------------------------------------------------------

// We can only ever move 512 entities across a transition
#define MAX_ENTITY 512

// FIXME: This has grown into a complicated beast. Can we make this more elegant?
int CChangeLevel::ChangeList( levellist_t *pLevelList, int maxList )
{
	// Find all of the possible level changes on this BSP
	int count = BuildChangeLevelList( pLevelList, maxList );

	if ( !gpGlobals->pSaveData || ( static_cast<CSaveRestoreData *>(gpGlobals->pSaveData)->NumEntities() == 0 ) )
		return count;

	CSave saveHelper( static_cast<CSaveRestoreData *>(gpGlobals->pSaveData) );

	// For each level change, find nearby entities and save them
	int	i;
	for ( i = 0; i < count; i++ )
	{
		CBaseEntity *pEntList[ MAX_ENTITY ];
		int			 entityFlags[ MAX_ENTITY ];

		// First, figure out which entities are near the transition
		CBaseEntity *pLandmarkEntity = CBaseEntity::Instance( pLevelList[i].pentLandmark );
		int iEntity = BuildEntityTransitionList( pLandmarkEntity, pLevelList[i].landmarkName, pEntList, entityFlags, MAX_ENTITY );

		// FIXME: Activate if we have a dependency problem on level transition
		// Next, add in all entities depended on by entities near the transition
//		iEntity = AddDependentEntities( iEntity, pEntList, entityFlags, MAX_ENTITY );

		int j;
		for ( j = 0; j < iEntity; j++ )
		{
			// Mark entity table with 1<<i
			int index = saveHelper.EntityIndex( pEntList[j] );
			// Flag it with the level number
			saveHelper.EntityFlagsSet( index, entityFlags[j] | (1<<i) );
		}
	}

	return count;
}


//-----------------------------------------------------------------------------
// Purpose: A trigger that pushes the player, NPCs, or objects.
//-----------------------------------------------------------------------------
class CTriggerPush : public CBaseTrigger
{
public:
	DECLARE_CLASS( CTriggerPush, CBaseTrigger );

	void Spawn( void );
	void Touch( CBaseEntity *pOther );
	void Untouch( CBaseEntity *pOther );

	Vector m_vecPushDir;

	DECLARE_DATADESC();
};

BEGIN_DATADESC( CTriggerPush )
	DEFINE_KEYFIELD( m_vecPushDir, FIELD_VECTOR, "pushdir" ),
END_DATADESC()

LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush );


//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerPush::Spawn( )
{
	// Convert pushdir from angles to a vector
	Vector vecAbsDir;
	QAngle angPushDir = QAngle(m_vecPushDir.x, m_vecPushDir.y, m_vecPushDir.z);
	AngleVectors(angPushDir, &vecAbsDir);

	// Transform the vector into entity space
	VectorIRotate( vecAbsDir, EntityToWorldTransform(), m_vecPushDir );

	BaseClass::Spawn();

	InitTrigger();

	if (m_flSpeed == 0)
	{
		m_flSpeed = 100;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pOther - 
//-----------------------------------------------------------------------------
void CTriggerPush::Touch( CBaseEntity *pOther )
{
	if ( !pOther->IsSolid() || (pOther->GetMoveType() == MOVETYPE_PUSH || pOther->GetMoveType() == MOVETYPE_NONE ) )
		return;

	if (!PassesTriggerFilters(pOther))
		return;

	// FIXME: If something is hierarchically attached, should we try to push the parent?
	if (pOther->GetMoveParent())
		return;

	// Transform the push dir into global space
	Vector vecAbsDir;
	VectorRotate( m_vecPushDir, EntityToWorldTransform(), vecAbsDir );

	// Instant trigger, just transfer velocity and remove
	if (HasSpawnFlags(SF_TRIG_PUSH_ONCE))
	{
		pOther->ApplyAbsVelocityImpulse( m_flSpeed * vecAbsDir );

		if ( vecAbsDir.z > 0 )
		{
			pOther->SetGroundEntity( NULL );
		}
		UTIL_Remove( this );
		return;
	}

	switch( pOther->GetMoveType() )
	{
	case MOVETYPE_NONE:
	case MOVETYPE_PUSH:
	case MOVETYPE_NOCLIP:
		break;

	case MOVETYPE_VPHYSICS:
		{
			IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
			if ( pPhys )
			{
				// UNDONE: Assume the velocity is for a 100kg object, scale with mass
				pPhys->ApplyForceCenter( m_flSpeed * vecAbsDir * 100.0f * gpGlobals->frametime );
				return;
			}
		}
		break;

	default:
		{
#if defined( HL2_DLL )
			// HACK HACK  HL2 players on ladders will only be disengaged if the sf is set, otherwise no push occurs.
			if ( pOther->IsPlayer() && 
				 pOther->GetMoveType() == MOVETYPE_LADDER )
			{
				if ( !HasSpawnFlags(SF_TRIG_PUSH_AFFECT_PLAYER_ON_LADDER) )
				{
					// Ignore the push
					return;
				}
			}
#endif

			Vector vecPush = (m_flSpeed * vecAbsDir);
			if ( pOther->GetFlags() & FL_BASEVELOCITY )
			{
				vecPush = vecPush + pOther->GetBaseVelocity();
			}
			if ( vecPush.z > 0 && (pOther->GetFlags() & FL_ONGROUND) )
			{
				pOther->SetGroundEntity( NULL );
				Vector origin = pOther->GetAbsOrigin();
				origin.z += 1.0f;
				pOther->SetAbsOrigin( origin );
			}

#ifdef HL1_DLL
			// Apply the z velocity as a force so it counteracts gravity properly
			Vector vecImpulse( 0, 0, vecPush.z * 0.025 );//magic hack number

			pOther->ApplyAbsVelocityImpulse( vecImpulse );

			// apply x, y as a base velocity so we travel at constant speed on conveyors
			vecPush.z = 0;
#endif			

			pOther->SetBaseVelocity( vecPush );
			pOther->AddFlag( FL_BASEVELOCITY );
		}
		break;
	}
}


//-----------------------------------------------------------------------------
// Teleport trigger
//-----------------------------------------------------------------------------
const int SF_TELEPORT_PRESERVE_ANGLES = 0x20;	// Preserve angles even when a local landmark is not specified

class CTriggerTeleport : public CBaseTrigger
{
public:
	DECLARE_CLASS( CTriggerTeleport, CBaseTrigger );

	void Spawn( void );
	void Touch( CBaseEntity *pOther );

	string_t m_iLandmark;

	DECLARE_DATADESC();
};

LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport );

BEGIN_DATADESC( CTriggerTeleport )

	DEFINE_KEYFIELD( m_iLandmark, FIELD_STRING, "landmark" ),

END_DATADESC()



void CTriggerTeleport::Spawn( void )
{
	InitTrigger();
}


//-----------------------------------------------------------------------------
// Purpose: Teleports the entity that touched us to the location of our target,
//			setting the toucher's angles to our target's angles if they are a
//			player.
//
//			If a landmark was specified, the toucher is offset from the target
//			by their initial offset from the landmark and their angles are
//			left alone.
//
// Input  : pOther - The entity that touched us.
//-----------------------------------------------------------------------------
void CTriggerTeleport::Touch( CBaseEntity *pOther )
{
	CBaseEntity	*pentTarget = NULL;

	if (!PassesTriggerFilters(pOther))
	{
		return;
	}

	pentTarget = gEntList.FindEntityByName( pentTarget, m_target, pOther );
	if (!pentTarget)
	{
	   return;
	}
	
	//
	// If a landmark was specified, offset the player relative to the landmark.
	//
	CBaseEntity	*pentLandmark = NULL;
	Vector vecLandmarkOffset(0, 0, 0);
	if (m_iLandmark != NULL_STRING)
	{
		pentLandmark = gEntList.FindEntityByName(pentLandmark, m_iLandmark, pOther );
		if (pentLandmark)
		{
			vecLandmarkOffset = pOther->GetAbsOrigin() - pentLandmark->GetAbsOrigin();
		}
	}

	pOther->SetGroundEntity( NULL );
	
	Vector tmp = pentTarget->GetAbsOrigin();

	if (!pentLandmark && pOther->IsPlayer())
	{
		// make origin adjustments in case the teleportee is a player. (origin in center, not at feet)
		tmp.z -= pOther->WorldAlignMins().z;
	}

	//
	// Only modify the toucher's angles and zero their velocity if no landmark was specified.
	//
	const QAngle *pAngles = NULL;
	Vector *pVelocity = NULL;

#ifdef HL1_DLL
	Vector vecZero(0,0,0);		
#endif

	if (!pentLandmark && !HasSpawnFlags(SF_TELEPORT_PRESERVE_ANGLES) )
	{
		pAngles = &pentTarget->GetAbsAngles();

#ifdef HL1_DLL
		pVelocity = &vecZero;
#else
		pVelocity = NULL;	//BUGBUG - This does not set the player's velocity to zero!!!
#endif
	}

	tmp += vecLandmarkOffset;
	pOther->Teleport( &tmp, pAngles, pVelocity );
}


LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity );


//-----------------------------------------------------------------------------
// Purpose: Saves the game when the player touches the trigger. Can be enabled or disabled
//-----------------------------------------------------------------------------
class CTriggerToggleSave : public CBaseTrigger
{
public:
	DECLARE_CLASS( CTriggerToggleSave, CBaseTrigger );

	void Spawn( void );
	void Touch( CBaseEntity *pOther );

	void InputEnable( inputdata_t &inputdata )
	{
		m_bDisabled = false;
	}

	void InputDisable( inputdata_t &inputdata )
	{
		m_bDisabled = true;
	}

	bool	m_bDisabled;		// Initial state
	
	DECLARE_DATADESC();
};

BEGIN_DATADESC( CTriggerToggleSave )
	DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ),

	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
END_DATADESC()

LINK_ENTITY_TO_CLASS( trigger_togglesave, CTriggerToggleSave );


//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been set.
//-----------------------------------------------------------------------------
void CTriggerToggleSave::Spawn( void )
{
	if ( g_pGameRules->IsDeathmatch() )
	{
		UTIL_Remove( this );
		return;
	}

	InitTrigger();
}


//-----------------------------------------------------------------------------
// Purpose: Performs the autosave when the player touches us.
// Input  : pOther - 
//-----------------------------------------------------------------------------
void CTriggerToggleSave::Touch( CBaseEntity *pOther )
{
	if( m_bDisabled )
		return;

	// Only save on clients
	if ( !pOther->IsPlayer() )
		return;
    
	// Can be re-enabled
	m_bDisabled = true;

	engine->ServerCommand( "autosave\n" );
}

//-----------------------------------------------------------------------------
// Purpose: Saves the game when the player touches the trigger.
//-----------------------------------------------------------------------------
class CTriggerSave : public CBaseTrigger
{
public:
	DECLARE_CLASS( CTriggerSave, CBaseTrigger );

	void Spawn( void );
	void Touch( CBaseEntity *pOther );
	DECLARE_DATADESC();

	bool m_bForceNewLevelUnit;
};


BEGIN_DATADESC( CTriggerSave )

	DEFINE_KEYFIELD( m_bForceNewLevelUnit, FIELD_BOOLEAN, "NewLevelUnit" ),

END_DATADESC()
LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave );


//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been set.
//-----------------------------------------------------------------------------
void CTriggerSave::Spawn( void )
{
	if ( g_pGameRules->IsDeathmatch() )
	{
		UTIL_Remove( this );
		return;
	}

	InitTrigger();
}


//-----------------------------------------------------------------------------
// Purpose: Performs the autosave when the player touches us.
// Input  : pOther - 
//-----------------------------------------------------------------------------
void CTriggerSave::Touch( CBaseEntity *pOther )
{
	// Only save on clients
	if ( !pOther->IsPlayer() )
		return;

    // this is a one-way transition - there is no way to return to the previous map.
	if ( m_bForceNewLevelUnit )
	{
		engine->ClearSaveDir();
	}
	UTIL_Remove( this );
	engine->ServerCommand( "autosave\n" );
}


class CTriggerGravity : public CBaseTrigger
{
public:
	DECLARE_CLASS( CTriggerGravity, CBaseTrigger );
	DECLARE_DATADESC();

	void Spawn( void );
	void GravityTouch( CBaseEntity *pOther );
};
LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity );

BEGIN_DATADESC( CTriggerGravity )

	// Function Pointers
	DEFINE_FUNCTION(GravityTouch),

END_DATADESC()

void CTriggerGravity::Spawn( void )
{
	BaseClass::Spawn();
	InitTrigger();
	SetTouch( &CTriggerGravity::GravityTouch );
}

void CTriggerGravity::GravityTouch( CBaseEntity *pOther )
{
	// Only save on clients
	if ( !pOther->IsPlayer() )
		return;

	pOther->SetGravity( GetGravity() );
}


// this is a really bad idea.
class CAI_ChangeTarget : public CBaseEntity
{
public:
	DECLARE_CLASS( CAI_ChangeTarget, CBaseEntity );

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

	int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }

	DECLARE_DATADESC();

private:
	string_t	m_iszNewTarget;
};
LINK_ENTITY_TO_CLASS( ai_changetarget, CAI_ChangeTarget );

BEGIN_DATADESC( CAI_ChangeTarget )

	DEFINE_KEYFIELD( m_iszNewTarget, FIELD_STRING, "m_iszNewTarget" ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),

END_DATADESC()


void CAI_ChangeTarget::InputActivate( inputdata_t &inputdata )
{
	CBaseEntity *pTarget = NULL;

	while ((pTarget = gEntList.FindEntityByName( pTarget, m_target, inputdata.pActivator )) != NULL)
	{
		pTarget->m_target = m_iszNewTarget;
		CAI_BaseNPC *pNPC = pTarget->MyNPCPointer( );
		if (pNPC)
		{
			pNPC->SetGoalEnt( NULL );
		}
	}
}








//-----------------------------------------------------------------------------
// Purpose: Change an NPC's hint group to something new
//-----------------------------------------------------------------------------
class CAI_ChangeHintGroup : public CBaseEntity
{
public:
	DECLARE_CLASS( CAI_ChangeHintGroup, CBaseEntity );

	int ObjectCaps( void ) { return BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }

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

	DECLARE_DATADESC();

private:
	CAI_BaseNPC *FindQualifiedNPC( CAI_BaseNPC *pPrev );

	int			m_iSearchType;
	string_t	m_strSearchName;
	string_t	m_strNewHintGroup;
	float		m_flRadius;
	bool		m_bHintGroupNavLimiting;
};
LINK_ENTITY_TO_CLASS( ai_changehintgroup, CAI_ChangeHintGroup );

BEGIN_DATADESC( CAI_ChangeHintGroup )

	DEFINE_KEYFIELD( m_iSearchType, FIELD_INTEGER, "SearchType" ),
	DEFINE_KEYFIELD( m_strSearchName, FIELD_STRING, "SearchName" ),
	DEFINE_KEYFIELD( m_strNewHintGroup, FIELD_STRING, "NewHintGroup" ),
	DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "Radius" ),
	DEFINE_KEYFIELD( m_bHintGroupNavLimiting,	FIELD_BOOLEAN, "hintlimiting" ),

	DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ),

END_DATADESC()

CAI_BaseNPC *CAI_ChangeHintGroup::FindQualifiedNPC( CAI_BaseNPC *pPrev )
{
	CBaseEntity *pEntity = pPrev;
	CAI_BaseNPC *pResult = NULL;
	const char *pszSearchName = STRING(m_strSearchName);
	while ( !pResult )
	{
		// Find a candidate
		switch ( m_iSearchType )
		{
			case 0:
			{
				pEntity = gEntList.FindEntityByNameWithin( pEntity, pszSearchName, GetLocalOrigin(), m_flRadius );
				break;
			}
			
			case 1:
			{
				pEntity = gEntList.FindEntityByClassnameWithin( pEntity, pszSearchName, GetLocalOrigin(), m_flRadius );
				break;
			}

			case 2:
			{
				pEntity = gEntList.FindEntityInSphere( pEntity, GetLocalOrigin(), ( m_flRadius != 0.0 ) ? m_flRadius : FLT_MAX );
				break;
			}
		}

		if ( !pEntity )
			return NULL;

		// Qualify
		pResult = pEntity->MyNPCPointer();
		if ( pResult && m_iSearchType == 2 && (!FStrEq( STRING(pResult->GetHintGroup()), pszSearchName ) ) )
		{
			pResult = NULL;
		}
	}

	return pResult;
}

void CAI_ChangeHintGroup::InputActivate( inputdata_t &inputdata )
{
	CAI_BaseNPC *pTarget = NULL;

	while((pTarget = FindQualifiedNPC( pTarget )) != NULL)
	{
		pTarget->SetHintGroup( m_strNewHintGroup, m_bHintGroupNavLimiting );
	}
}




#define SF_CAMERA_PLAYER_POSITION		1
#define SF_CAMERA_PLAYER_TARGET			2
#define SF_CAMERA_PLAYER_TAKECONTROL	4
#define SF_CAMERA_PLAYER_INFINITE_WAIT	8
#define SF_CAMERA_PLAYER_SNAP_TO		16
#define SF_CAMERA_PLAYER_NOT_SOLID		32
#define SF_CAMERA_PLAYER_INTERRUPT		64


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class CTriggerCamera : public CBaseEntity
{
public:
	DECLARE_CLASS( CTriggerCamera, CBaseEntity );

	void Spawn( void );
	bool KeyValue( const char *szKeyName, const char *szValue );
	void Enable( void );
	void Disable( void );
	void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	void FollowTarget( void );
	void Move(void);

	// Always transmit to clients so they know where to move the view to
	virtual int UpdateTransmitState();
	
	DECLARE_DATADESC();

	// Input handlers
	void InputEnable( inputdata_t &inputdata );
	void InputDisable( inputdata_t &inputdata );

private:
	EHANDLE m_hPlayer;
	EHANDLE m_hTarget;
	CBaseEntity *m_pPath;
	string_t m_sPath;
	float m_flWait;
	float m_flReturnTime;
	float m_flStopTime;
	float m_moveDistance;
	float m_targetSpeed;
	float m_initialSpeed;
	float m_acceleration;
	float m_deceleration;
	int	  m_state;
	Vector m_vecMoveDir;
	string_t m_iszTargetAttachment;
	int	  m_iAttachmentIndex;
	bool  m_bSnapToGoal;

	int   m_nPlayerButtons;

private:
	COutputEvent m_OnEndFollow;
};

LINK_ENTITY_TO_CLASS( point_viewcontrol, CTriggerCamera );

BEGIN_DATADESC( CTriggerCamera )

	DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ),
	DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ),
	DEFINE_FIELD( m_pPath, FIELD_CLASSPTR ),
	DEFINE_FIELD( m_sPath, FIELD_STRING ),
	DEFINE_FIELD( m_flWait, FIELD_FLOAT ),
	DEFINE_FIELD( m_flReturnTime, FIELD_TIME ),
	DEFINE_FIELD( m_flStopTime, FIELD_TIME ),
	DEFINE_FIELD( m_moveDistance, FIELD_FLOAT ),
	DEFINE_FIELD( m_targetSpeed, FIELD_FLOAT ),
	DEFINE_FIELD( m_initialSpeed, FIELD_FLOAT ),
	DEFINE_FIELD( m_acceleration, FIELD_FLOAT ),
	DEFINE_FIELD( m_deceleration, FIELD_FLOAT ),
	DEFINE_FIELD( m_state, FIELD_INTEGER ),
	DEFINE_FIELD( m_vecMoveDir, FIELD_VECTOR ),
	DEFINE_KEYFIELD( m_iszTargetAttachment, FIELD_STRING, "targetattachment" ),
	DEFINE_FIELD( m_iAttachmentIndex, FIELD_INTEGER ),
	DEFINE_FIELD( m_bSnapToGoal, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_nPlayerButtons, FIELD_INTEGER ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),

	// Function Pointers
	DEFINE_FUNCTION( FollowTarget ),
	DEFINE_OUTPUT( m_OnEndFollow, "OnEndFollow" ),

END_DATADESC()

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

	SetMoveType( MOVETYPE_NOCLIP );
	SetSolid( SOLID_NONE );								// Remove model & collisions
	SetRenderColorA( 0 );								// The engine won't draw this model if this is set to 0 and blending is on
	m_nRenderMode = kRenderTransTexture;

	m_state = USE_OFF;
	
	m_initialSpeed = m_flSpeed;

	if ( m_acceleration == 0 )
		m_acceleration = 500;

	if ( m_deceleration == 0 )
		m_deceleration = 500;

	UpdateTransmitState();
}

int CTriggerCamera::UpdateTransmitState()
{
	// always tranmit if currently used by a monitor
	if ( m_state == USE_ON )
	{
		return SetTransmitState( FL_EDICT_ALWAYS );
	}
	else
	{
		return SetTransmitState( FL_EDICT_DONTSEND );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTriggerCamera::KeyValue( const char *szKeyName, const char *szValue )
{
	if (FStrEq(szKeyName, "wait"))
	{
		m_flWait = atof(szValue);
	}
	else if (FStrEq(szKeyName, "moveto"))
	{
		m_sPath = AllocPooledString( szValue );
	}
	else if (FStrEq(szKeyName, "acceleration"))
	{
		m_acceleration = atof( szValue );
	}
	else if (FStrEq(szKeyName, "deceleration"))
	{
		m_deceleration = atof( szValue );
	}
	else
		return BaseClass::KeyValue( szKeyName, szValue );

	return true;
}

//------------------------------------------------------------------------------
// Purpose: Input handler to turn on this trigger.
//------------------------------------------------------------------------------
void CTriggerCamera::InputEnable( inputdata_t &inputdata )
{ 
	m_hPlayer = inputdata.pActivator;
	Enable();
}


//------------------------------------------------------------------------------
// Purpose: Input handler to turn off this trigger.
//------------------------------------------------------------------------------
void CTriggerCamera::InputDisable( inputdata_t &inputdata )
{ 
	Disable();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTriggerCamera::Enable( void )
{
	m_state = USE_ON;

	if ( !m_hPlayer || !m_hPlayer->IsPlayer() )
	{
		m_hPlayer = UTIL_GetLocalPlayer();
	}

	if ( !m_hPlayer )
	{
		UpdateTransmitState();
		return;
	}

	if ( m_hPlayer->IsPlayer() )
	{
		 m_nPlayerButtons = ((CBasePlayer*)m_hPlayer.Get())->m_nButtons;
	}
	
	if ( HasSpawnFlags( SF_CAMERA_PLAYER_NOT_SOLID ) )
	{
		m_hPlayer->AddSolidFlags( FSOLID_NOT_SOLID );
	}
	
	m_flReturnTime = gpGlobals->curtime + m_flWait;
	m_flSpeed = m_initialSpeed;
	m_targetSpeed = m_initialSpeed;

	if ( HasSpawnFlags( SF_CAMERA_PLAYER_SNAP_TO ) )
	{
		m_bSnapToGoal = true;
	}

	if ( HasSpawnFlags(SF_CAMERA_PLAYER_TARGET ) )
	{
		m_hTarget = m_hPlayer;
	}
	else
	{
		m_hTarget = GetNextTarget();
	}

	// If we don't have a target, ignore the attachment / etc
	if ( m_hTarget )
	{
		m_iAttachmentIndex = 0;
		if ( m_iszTargetAttachment != NULL_STRING )
		{
			if ( !m_hTarget->GetBaseAnimating() )
			{
				Warning("%s tried to target an attachment (%s) on target %s, which has no model.\n", GetClassname(), STRING(m_iszTargetAttachment), STRING(m_hTarget->GetEntityName()) );
			}
			else
			{
				m_iAttachmentIndex = m_hTarget->GetBaseAnimating()->LookupAttachment( STRING(m_iszTargetAttachment) );
				if ( !m_iAttachmentIndex )
				{
					Warning("%s could not find attachment %s on target %s.\n", GetClassname(), STRING(m_iszTargetAttachment), STRING(m_hTarget->GetEntityName()) );
				}
			}
		}
	}

	if (HasSpawnFlags(SF_CAMERA_PLAYER_TAKECONTROL ) )
	{
		((CBasePlayer*)m_hPlayer.Get())->EnableControl(FALSE);
	}

	if ( m_sPath != NULL_STRING )
	{
		m_pPath = gEntList.FindEntityByName( NULL, m_sPath, m_hPlayer );
	}
	else
	{
		m_pPath = NULL;
	}

	m_flStopTime = gpGlobals->curtime;
	if ( m_pPath )
	{
		if ( m_pPath->m_flSpeed != 0 )
			m_targetSpeed = m_pPath->m_flSpeed;
		
		m_flStopTime += m_pPath->GetDelay();
	}

	// copy over player information
	if (HasSpawnFlags(SF_CAMERA_PLAYER_POSITION ) )
	{
		UTIL_SetOrigin( this, m_hPlayer->EyePosition() );
		SetLocalAngles( QAngle( m_hPlayer->GetLocalAngles().x, m_hPlayer->GetLocalAngles().y, 0 ) );
		SetAbsVelocity( m_hPlayer->GetAbsVelocity() );
	}
	else
	{
		SetAbsVelocity( vec3_origin );
	}

	((CBasePlayer*)m_hPlayer.Get())->SetViewEntity( this );

	// Only track if we have a target
	if ( m_hTarget )
	{
		// follow the player down
		SetThink( &CTriggerCamera::FollowTarget );
		SetNextThink( gpGlobals->curtime );
	}

	m_moveDistance = 0;
	Move();

	UpdateTransmitState();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTriggerCamera::Disable( void )
{
	if ( m_hPlayer && m_hPlayer->IsAlive() )
	{
		if ( HasSpawnFlags( SF_CAMERA_PLAYER_NOT_SOLID ) )
		{
			m_hPlayer->RemoveSolidFlags( FSOLID_NOT_SOLID );
		}

		((CBasePlayer*)m_hPlayer.Get())->SetViewEntity( m_hPlayer );
		((CBasePlayer*)m_hPlayer.Get())->EnableControl(TRUE);
	}

	m_state = USE_OFF;
	m_flReturnTime = gpGlobals->curtime;
	SetThink( NULL );

	m_OnEndFollow.FireOutput(this, this); // dvsents2: what is the best name for this output?
	SetLocalAngularVelocity( vec3_angle );

	UpdateTransmitState();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if ( !ShouldToggle( useType, m_state ) )
		return;

	// Toggle state
	if ( m_state != USE_OFF )
	{
		Disable();
	}
	else
	{
		m_hPlayer = pActivator;
		Enable();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTriggerCamera::FollowTarget( )
{
	if (m_hPlayer == NULL)
		return;

	if ( m_hTarget == NULL )
	{
		Disable();
		return;
	}

	if ( !HasSpawnFlags(SF_CAMERA_PLAYER_INFINITE_WAIT) && (!m_hTarget || m_flReturnTime < gpGlobals->curtime) )
	{
		Disable();
		return;
	}

	QAngle vecGoal;
	if ( m_iAttachmentIndex )
	{
		Vector vecOrigin;
		m_hTarget->GetBaseAnimating()->GetAttachment( m_iAttachmentIndex, vecOrigin );
		VectorAngles( vecOrigin - GetLocalOrigin(), vecGoal );
	}
	else
	{
		if ( m_hTarget )
		{
			VectorAngles( m_hTarget->GetLocalOrigin() - GetLocalOrigin(), vecGoal );
		}
		else
		{
			// Use the viewcontroller's angles
			vecGoal = GetAbsAngles();
		}
	}

	// Should we just snap to the goal angles?
	if ( m_bSnapToGoal ) 
	{
		SetAbsAngles( vecGoal );
		m_bSnapToGoal = false;
	}
	else
	{
		// UNDONE: Can't we just use UTIL_AngleDiff here?
		QAngle angles = GetLocalAngles();

		if (angles.y > 360)
			angles.y -= 360;

		if (angles.y < 0)
			angles.y += 360;

		SetLocalAngles( angles );

		float dx = vecGoal.x - GetLocalAngles().x;
		float dy = vecGoal.y - GetLocalAngles().y;

		if (dx < -180) 
			dx += 360;
		if (dx > 180) 
			dx = dx - 360;
		
		if (dy < -180) 
			dy += 360;
		if (dy > 180) 
			dy = dy - 360;

		QAngle vecAngVel;
		vecAngVel.Init( dx * 40 * gpGlobals->frametime, dy * 40 * gpGlobals->frametime, GetLocalAngularVelocity().z );
		SetLocalAngularVelocity(vecAngVel);
	}

	if (!HasSpawnFlags(SF_CAMERA_PLAYER_TAKECONTROL))	
	{
		SetAbsVelocity( GetAbsVelocity() * 0.8 );
		if (GetAbsVelocity().Length( ) < 10.0)
		{
			SetAbsVelocity( vec3_origin );
		}
	}

	SetNextThink( gpGlobals->curtime );

	Move();
}

void CTriggerCamera::Move()
{
	if ( HasSpawnFlags( SF_CAMERA_PLAYER_INTERRUPT ) )
	{
		if ( m_hPlayer )
		{
			CBasePlayer *pPlayer = ToBasePlayer( m_hPlayer );

			if ( pPlayer  )
			{
				int buttonsChanged = m_nPlayerButtons ^ pPlayer->m_nButtons;

				if ( buttonsChanged && pPlayer->m_nButtons )
				{
					 Disable();
					 return;
				}

				m_nPlayerButtons = pPlayer->m_nButtons;
			}
		}
	}
	
	// Not moving on a path, return
	if (!m_pPath)
		return;

	// Subtract movement from the previous frame
	m_moveDistance -= m_flSpeed * gpGlobals->frametime;

	// Have we moved enough to reach the target?
	if ( m_moveDistance <= 0 )
	{
		variant_t emptyVariant;
		m_pPath->AcceptInput( "InPass", this, this, emptyVariant, 0 );
		// Time to go to the next target
		m_pPath = m_pPath->GetNextTarget();

		// Set up next corner
		if ( !m_pPath )
		{
			SetAbsVelocity( vec3_origin );
		}
		else 
		{
			if ( m_pPath->m_flSpeed != 0 )
				m_targetSpeed = m_pPath->m_flSpeed;

			m_vecMoveDir = m_pPath->GetLocalOrigin() - GetLocalOrigin();
			m_moveDistance = VectorNormalize( m_vecMoveDir );
			m_flStopTime = gpGlobals->curtime + m_pPath->GetDelay();
		}
	}

	if ( m_flStopTime > gpGlobals->curtime )
		m_flSpeed = UTIL_Approach( 0, m_flSpeed, m_deceleration * gpGlobals->frametime );
	else
		m_flSpeed = UTIL_Approach( m_targetSpeed, m_flSpeed, m_acceleration * gpGlobals->frametime );

	float fraction = 2 * gpGlobals->frametime;
	SetAbsVelocity( ((m_vecMoveDir * m_flSpeed) * fraction) + (GetAbsVelocity() * (1-fraction)) );
}


//-----------------------------------------------------------------------------
// Purpose: Starts/stops cd audio tracks
//-----------------------------------------------------------------------------
class CTriggerCDAudio : public CBaseTrigger
{
public:
	DECLARE_CLASS( CTriggerCDAudio, CBaseTrigger );

	void Spawn( void );

	virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	void PlayTrack( void );
	void Touch ( CBaseEntity *pOther );
};

LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio );


//-----------------------------------------------------------------------------
// Purpose: Changes tracks or stops CD when player touches
// Input  : pOther - The entity that touched us.
//-----------------------------------------------------------------------------
void CTriggerCDAudio::Touch ( CBaseEntity *pOther )
{
	if ( !pOther->IsPlayer() )
	{
		return;
	}

	PlayTrack();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTriggerCDAudio::Spawn( void )
{
	BaseClass::Spawn();
	InitTrigger();
}


void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	PlayTrack();
}


//-----------------------------------------------------------------------------
// Purpose: Issues a client command to play a given CD track. Called from
//			trigger_cdaudio and target_cdaudio.
// Input  : iTrack - Track number to play.
//-----------------------------------------------------------------------------
static void PlayCDTrack( int iTrack )
{
	edict_t *pClient;
	
	// manually find the single player. 
	pClient = engine->PEntityOfEntIndex( 1 );

	Assert(gpGlobals->maxClients == 1);
	
	// Can't play if the client is not connected!
	if ( !pClient )
		return;

	// UNDONE: Move this to engine sound
	if ( iTrack < -1 || iTrack > 30 )
	{
		Warning( "TriggerCDAudio - Track %d out of range\n" );
		return;
	}

	if ( iTrack == -1 )
	{
		engine->ClientCommand ( pClient, "cd pause\n");
	}
	else
	{
		char string [ 64 ];

		Q_snprintf( string,sizeof(string), "cd play %3d\n", iTrack );
		engine->ClientCommand ( pClient, string);
	}
}


// only plays for ONE client, so only use in single play!
void CTriggerCDAudio::PlayTrack( void )
{
	PlayCDTrack( (int)m_iHealth );
	
	SetTouch( NULL );
	UTIL_Remove( this );
}


//-----------------------------------------------------------------------------
// Purpose: Measures the proximity to a specified entity of any entities within
//			the trigger, provided they are within a given radius of the specified
//			entity. The nearest entity distance is output as a number from [0 - 1].
//-----------------------------------------------------------------------------
class CTriggerProximity : public CBaseTrigger
{
public:

	DECLARE_CLASS( CTriggerProximity, CBaseTrigger );

	virtual void Spawn(void);
	virtual void Activate(void);
	virtual void StartTouch(CBaseEntity *pOther);
	virtual void EndTouch(CBaseEntity *pOther);

	void MeasureThink(void);

protected:

	EHANDLE m_hMeasureTarget;
	string_t m_iszMeasureTarget;		// The entity from which we measure proximities.
	float m_fRadius;			// The radius around the measure target that we measure within.
	int m_nTouchers;			// Number of entities touching us.

	// Outputs
	COutputFloat m_NearestEntityDistance;

	DECLARE_DATADESC();
};


BEGIN_DATADESC( CTriggerProximity )

	// Functions
	DEFINE_FUNCTION(MeasureThink),

	// Keys
	DEFINE_KEYFIELD(m_iszMeasureTarget, FIELD_STRING, "measuretarget"),
	DEFINE_FIELD( m_hMeasureTarget, FIELD_EHANDLE ),
	DEFINE_KEYFIELD(m_fRadius, FIELD_FLOAT, "radius"),
	DEFINE_FIELD( m_nTouchers, FIELD_INTEGER ),

	// Outputs
	DEFINE_OUTPUT(m_NearestEntityDistance, "NearestEntityDistance"),

END_DATADESC()



LINK_ENTITY_TO_CLASS(trigger_proximity, CTriggerProximity);
LINK_ENTITY_TO_CLASS(logic_proximity, CPointEntity);


//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerProximity::Spawn(void)
{
	// Avoid divide by zero in MeasureThink!
	if (m_fRadius == 0)
	{
		m_fRadius = 32;
	}

	InitTrigger();
}


//-----------------------------------------------------------------------------
// Purpose: Called after all entities have spawned and after a load game.
//			Finds the reference point from which to measure.
//-----------------------------------------------------------------------------
void CTriggerProximity::Activate(void)
{
	BaseClass::Activate();
	m_hMeasureTarget = gEntList.FindEntityByName(NULL, m_iszMeasureTarget, NULL);

	//
	// Disable our Touch function if we were given a bad measure target.
	//
	if ((m_hMeasureTarget == NULL) || (m_hMeasureTarget->edict() == NULL))
	{
		Warning( "TriggerProximity - Missing measure target or measure target with no origin!\n");
	}
}


//-----------------------------------------------------------------------------
// Purpose: Decrements the touch count and cancels the think if the count reaches
//			zero.
// Input  : pOther - 
//-----------------------------------------------------------------------------
void CTriggerProximity::StartTouch(CBaseEntity *pOther)
{
	BaseClass::StartTouch( pOther );

	if ( PassesTriggerFilters( pOther ) )
	{
		m_nTouchers++;	

		SetThink( &CTriggerProximity::MeasureThink );
		SetNextThink( gpGlobals->curtime );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Decrements the touch count and cancels the think if the count reaches
//			zero.
// Input  : pOther - 
//-----------------------------------------------------------------------------
void CTriggerProximity::EndTouch(CBaseEntity *pOther)
{
	BaseClass::EndTouch( pOther );

	if ( PassesTriggerFilters( pOther ) )
	{
		m_nTouchers--;

		if ( m_nTouchers == 0 )
		{
			SetThink( NULL );
			SetNextThink( TICK_NEVER_THINK );
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Think function called every frame as long as we have entities touching
//			us that we care about. Finds the closest entity to the measure
//			target and outputs the distance as a normalized value from [0..1].
//-----------------------------------------------------------------------------
void CTriggerProximity::MeasureThink( void )
{
	if ( ( m_hMeasureTarget == NULL ) || ( m_hMeasureTarget->edict() == NULL ) )
	{
		SetThink(NULL);
		SetNextThink( TICK_NEVER_THINK );
		return;
	}

	//
	// Traverse our list of touchers and find the entity that is closest to the
	// measure target.
	//
	float fMinDistance = m_fRadius + 100;
	CBaseEntity *pNearestEntity = NULL;

	touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
	if ( root )
	{
		touchlink_t *pLink = root->nextLink;
		while ( pLink != root )
		{
			CBaseEntity *pEntity = pLink->entityTouched;

			// If this is an entity that we care about, check its distance.
			if ( ( pEntity != NULL ) && PassesTriggerFilters( pEntity ) )
			{
				float flDistance = (pEntity->GetLocalOrigin() - m_hMeasureTarget->GetLocalOrigin()).Length();
				if (flDistance < fMinDistance)
				{
					fMinDistance = flDistance;
					pNearestEntity = pEntity;
				}
			}

			pLink = pLink->nextLink;
		}
	}

	// Update our output with the nearest entity distance, normalized to [0..1].
	if ( fMinDistance <= m_fRadius )
	{
		fMinDistance /= m_fRadius;
		if ( fMinDistance != m_NearestEntityDistance.Get() )
		{
			m_NearestEntityDistance.Set( fMinDistance, pNearestEntity, this );
		}
	}

	SetNextThink( gpGlobals->curtime );
}


// ##################################################################################
//	>> TriggerWind
//
//  Blows physics objects in the trigger
//
// ##################################################################################

#define MAX_WIND_CHANGE		5.0f

//------------------------------------------------------------------------------
// Purpose :
// Input   :
// Output  :
//------------------------------------------------------------------------------
class CPhysicsWind : public IMotionEvent
{
	DECLARE_SIMPLE_DATADESC();

public:
	simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
	{
		// If we have no windspeed, we're not doing anything
		if ( !m_flWindSpeed )
			return IMotionEvent::SIM_NOTHING;

		// Get a cosine modulated noise between 5 and 20 that is object specific
		int nNoiseMod = 5+(int)pObject%15; // 

		// Turn wind yaw direction into a vector and add noise
		QAngle vWindAngle = vec3_angle;	
		vWindAngle[1] = m_nWindYaw+(30*cos(nNoiseMod * gpGlobals->curtime + nNoiseMod));
		Vector vWind;
		AngleVectors(vWindAngle,&vWind);

		// Add lift with noise
		vWind.z = 1.1 + (1.0 * sin(nNoiseMod * gpGlobals->curtime + nNoiseMod));

		linear = 3*vWind*m_flWindSpeed;
		angular = vec3_origin;
		return IMotionEvent::SIM_GLOBAL_FORCE;	
	}

	int		m_nWindYaw;
	float	m_flWindSpeed;
};

BEGIN_SIMPLE_DATADESC( CPhysicsWind )

	DEFINE_FIELD( m_nWindYaw,		FIELD_INTEGER ),
	DEFINE_FIELD( m_flWindSpeed,	FIELD_FLOAT ),

END_DATADESC()


extern short g_sModelIndexSmoke;
extern float	GetFloorZ(const Vector &origin);
#define WIND_THINK_CONTEXT		"WindThinkContext"

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
class CTriggerWind : public CBaseVPhysicsTrigger
{
	DECLARE_CLASS( CTriggerWind, CBaseVPhysicsTrigger );
public:
	DECLARE_DATADESC();

	void	Spawn( void );
	bool	KeyValue( const char *szKeyName, const char *szValue );
	void	OnRestore();
	void	UpdateOnRemove();
	bool	CreateVPhysics();
	void	StartTouch( CBaseEntity *pOther );
	void	EndTouch( CBaseEntity *pOther );
	void	WindThink( void );
	int		DrawDebugTextOverlays( void );

	// Input handlers
	void	InputEnable( inputdata_t &inputdata );
	void	InputSetSpeed( inputdata_t &inputdata );

private:
	int 	m_nSpeedBase;	// base line for how hard the wind blows
	int		m_nSpeedNoise;	// noise added to wind speed +/-
	int		m_nSpeedCurrent;// current wind speed
	int		m_nSpeedTarget;	// wind speed I'm approaching

	int		m_nDirBase;		// base line for direction the wind blows (yaw)
	int		m_nDirNoise;	// noise added to wind direction
	int		m_nDirCurrent;	// the current wind direction
	int		m_nDirTarget;	// wind direction I'm approaching

	int		m_nHoldBase;	// base line for how long to wait before changing wind
	int		m_nHoldNoise;	// noise added to how long to wait before changing wind

	bool	m_bSwitch;		// when does wind change

	IPhysicsMotionController*	m_pWindController;
	CPhysicsWind				m_WindCallback;

};

LINK_ENTITY_TO_CLASS( trigger_wind, CTriggerWind );

BEGIN_DATADESC( CTriggerWind )

	DEFINE_FIELD( m_nSpeedCurrent, FIELD_INTEGER),
	DEFINE_FIELD( m_nSpeedTarget,	FIELD_INTEGER),
	DEFINE_FIELD( m_nDirBase,		FIELD_INTEGER),
	DEFINE_FIELD( m_nDirCurrent,	FIELD_INTEGER),
	DEFINE_FIELD( m_nDirTarget,	FIELD_INTEGER),
	DEFINE_FIELD( m_bSwitch,		FIELD_BOOLEAN),

	DEFINE_FIELD( m_nSpeedBase,		FIELD_INTEGER ),
	DEFINE_KEYFIELD( m_nSpeedNoise,	FIELD_INTEGER, "SpeedNoise"),
	DEFINE_KEYFIELD( m_nDirNoise,	FIELD_INTEGER, "DirectionNoise"),
	DEFINE_KEYFIELD( m_nHoldBase,	FIELD_INTEGER, "HoldTime"),
	DEFINE_KEYFIELD( m_nHoldNoise,	FIELD_INTEGER, "HoldNoise"),

	DEFINE_PHYSPTR( m_pWindController ),
	DEFINE_EMBEDDED( m_WindCallback ),

	DEFINE_FUNCTION( WindThink ),

	DEFINE_INPUTFUNC( FIELD_INTEGER, "SetSpeed", InputSetSpeed ),

END_DATADESC()


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::Spawn( void )
{
	m_bSwitch = true;
	m_nDirBase = GetLocalAngles().y;

	BaseClass::Spawn();

	m_nSpeedCurrent = m_nSpeedBase;
	m_nDirCurrent = m_nDirBase;

	SetContextThink( &CTriggerWind::WindThink, gpGlobals->curtime, WIND_THINK_CONTEXT );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CTriggerWind::KeyValue( const char *szKeyName, const char *szValue )
{
	// Done here to avoid collision with CBaseEntity's speed key
	if ( FStrEq(szKeyName, "Speed") )
	{
		m_nSpeedBase = atoi( szValue );
	}
	else
		return BaseClass::KeyValue( szKeyName, szValue );

	return true;
}

//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
bool CTriggerWind::CreateVPhysics()
{
	BaseClass::CreateVPhysics();

	m_pWindController = physenv->CreateMotionController( &m_WindCallback );
	return true;
}

//------------------------------------------------------------------------------
// Cleanup
//------------------------------------------------------------------------------
void CTriggerWind::UpdateOnRemove()
{
	if ( m_pWindController )
	{
		physenv->DestroyMotionController( m_pWindController );
		m_pWindController = NULL;
	}

	BaseClass::UpdateOnRemove();
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::OnRestore()
{
	BaseClass::OnRestore();
	if ( m_pWindController )
	{
		m_pWindController->SetEventHandler( &m_WindCallback );
	}
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::StartTouch(CBaseEntity *pOther)
{
	if ( !PassesTriggerFilters(pOther) )
		return;
	if ( pOther->IsPlayer() )
		return;

	IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
	if ( pPhys)
	{
		m_pWindController->AttachObject( pPhys, false );
		pPhys->Wake();
	}
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::EndTouch(CBaseEntity *pOther)
{
	if ( !PassesTriggerFilters(pOther) )
		return;
	if ( pOther->IsPlayer() )
		return;

	IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
	if ( pPhys && m_pWindController )
	{
		m_pWindController->DetachObject( pPhys );
	}
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::InputEnable( inputdata_t &inputdata )
{
	BaseClass::InputEnable( inputdata );
	SetContextThink( &CTriggerWind::WindThink, gpGlobals->curtime + 0.1f, WIND_THINK_CONTEXT );
}

//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::WindThink( void )
{
	// By default...
	SetContextThink( &CTriggerWind::WindThink, gpGlobals->curtime + 0.1, WIND_THINK_CONTEXT );

	// Is it time to change the wind?
	if (m_bSwitch)
	{
		m_bSwitch = false;

		// Set new target direction and speed
		m_nSpeedTarget = m_nSpeedBase + random->RandomInt( -m_nSpeedNoise, m_nSpeedNoise );
		m_nDirTarget = UTIL_AngleMod( m_nDirBase + random->RandomInt(-m_nDirNoise, m_nDirNoise) );
	}
	else
	{
		bool bDone = true;
		// either ramp up, or sleep till change
		if (abs(m_nSpeedTarget - m_nSpeedCurrent) > MAX_WIND_CHANGE)
		{
			m_nSpeedCurrent += (m_nSpeedTarget > m_nSpeedCurrent) ? MAX_WIND_CHANGE : -MAX_WIND_CHANGE;
			bDone = false;
		}

		if (abs(m_nDirTarget - m_nDirCurrent) > MAX_WIND_CHANGE)
		{

			m_nDirCurrent = UTIL_ApproachAngle( m_nDirTarget, m_nDirCurrent, MAX_WIND_CHANGE );
			bDone = false;
		}
		
		if (bDone)
		{
			m_nSpeedCurrent = m_nSpeedTarget;
			SetContextThink( &CTriggerWind::WindThink, m_nHoldBase + random->RandomFloat(-m_nHoldNoise,m_nHoldNoise), WIND_THINK_CONTEXT );
			m_bSwitch = true;
		}
	}

	// If we're starting to blow, where we weren't before, wake up all our objects
	if ( m_nSpeedCurrent )
	{
		m_pWindController->WakeObjects();
	}

	// store the wind data in the controller callback
	m_WindCallback.m_nWindYaw = m_nDirCurrent;
	if ( m_bDisabled )
	{
		m_WindCallback.m_flWindSpeed = 0;
	}
	else
	{
		m_WindCallback.m_flWindSpeed = m_nSpeedCurrent;
	}
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerWind::InputSetSpeed( inputdata_t &inputdata )
{
	// Set new speed and mark to switch
	m_nSpeedBase = inputdata.value.Int();
	m_bSwitch = true;
}


//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CTriggerWind::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		// --------------
		// Print Target
		// --------------
		char tempstr[255];
		Q_snprintf(tempstr,sizeof(tempstr),"Dir: %i (%i)",m_nDirCurrent,m_nDirTarget);
		NDebugOverlay::EntityText(entindex(),text_offset,tempstr,0);
		text_offset++;

		Q_snprintf(tempstr,sizeof(tempstr),"Speed: %i (%i)",m_nSpeedCurrent,m_nSpeedTarget);
		NDebugOverlay::EntityText(entindex(),text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}


// ##################################################################################
//	>> TriggerImpact
//
//  Blows physics objects in the trigger
//
// ##################################################################################
#define TRIGGERIMPACT_VIEWKICK_SCALE 0.1

class CTriggerImpact : public CTriggerMultiple
{
	DECLARE_CLASS( CTriggerImpact, CTriggerMultiple );
public:
	DECLARE_DATADESC();

	float	m_flMagnitude;
	float	m_flNoise;
	float	m_flViewkick;

	void	Spawn( void );
	void	StartTouch( CBaseEntity *pOther );

	// Inputs
	void InputSetMagnitude( inputdata_t &inputdata );
	void InputImpact( inputdata_t &inputdata );

	// Outputs
	COutputVector	m_pOutputForce;		// Output force in case anyone else wants to use it

	// Debug
	int		DrawDebugTextOverlays(void);
};

LINK_ENTITY_TO_CLASS( trigger_impact, CTriggerImpact );

BEGIN_DATADESC( CTriggerImpact )

	DEFINE_KEYFIELD( m_flMagnitude,	FIELD_FLOAT, "Magnitude"),
	DEFINE_KEYFIELD( m_flNoise,		FIELD_FLOAT, "Noise"),
	DEFINE_KEYFIELD( m_flViewkick,	FIELD_FLOAT, "Viewkick"),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_VOID,  "Impact", InputImpact ),
	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMagnitude", InputSetMagnitude ),

	// Outputs
	DEFINE_OUTPUT(m_pOutputForce, "ImpactForce"),

	// Function Pointers
	DEFINE_FUNCTION( Disable ),


END_DATADESC()


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerImpact::Spawn( void )
{	
	// Clamp date in case user made an error
	m_flNoise = clamp(m_flNoise,0,1);
	m_flViewkick = clamp(m_flViewkick,0,1);

	// Always start disabled
	m_bDisabled = true;
	BaseClass::Spawn();
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerImpact::InputImpact( inputdata_t &inputdata )
{
	// Output the force vector in case anyone else wants to use it
	Vector vDir;
	AngleVectors( GetLocalAngles(),&vDir );
	m_pOutputForce.Set( m_flMagnitude * vDir, inputdata.pActivator, inputdata.pCaller);

	// Enable long enough to throw objects inside me
	Enable();
	SetNextThink( gpGlobals->curtime + 0.1f );
	SetThink(&CTriggerImpact::Disable);
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerImpact::StartTouch(CBaseEntity *pOther)
{
	//If the entity is valid and has physics, hit it
	if ( ( pOther != NULL  ) && ( pOther->VPhysicsGetObject() != NULL ) )
	{
		Vector vDir;
		AngleVectors( GetLocalAngles(),&vDir );
		vDir += RandomVector(-m_flNoise,m_flNoise);
		pOther->VPhysicsGetObject()->ApplyForceCenter( m_flMagnitude * vDir );
	}

	// If the player, so a view kick
	if (pOther->IsPlayer() && fabs(m_flMagnitude)>0 )
	{
		Vector vDir;
		AngleVectors( GetLocalAngles(),&vDir );

		float flPunch = -m_flViewkick*m_flMagnitude*TRIGGERIMPACT_VIEWKICK_SCALE;
		pOther->ViewPunch( QAngle( vDir.y * flPunch, 0, vDir.x * flPunch ) );
	}
}


//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CTriggerImpact::InputSetMagnitude( inputdata_t &inputdata )
{
	m_flMagnitude = inputdata.value.Float();
}


//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CTriggerImpact::DrawDebugTextOverlays(void) 
{
	int text_offset = BaseClass::DrawDebugTextOverlays();

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[255];
		Q_snprintf(tempstr,sizeof(tempstr),"Magnitude: %3.2f",m_flMagnitude);
		NDebugOverlay::EntityText(entindex(),text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;
}

//-----------------------------------------------------------------------------
// Purpose: Disables auto movement on players that touch it
//-----------------------------------------------------------------------------

const int SF_TRIGGER_MOVE_AUTODISABLE				= 0x80; // disable auto movement

class CTriggerPlayerMovement : public CBaseTrigger
{
	DECLARE_CLASS( CTriggerPlayerMovement, CBaseTrigger );
public:

	void Spawn( void );
	void StartTouch( CBaseEntity *pOther );
	void EndTouch( CBaseEntity *pOther );
	
	DECLARE_DATADESC();

};

BEGIN_DATADESC( CTriggerPlayerMovement )

END_DATADESC()


LINK_ENTITY_TO_CLASS( trigger_playermovement, CTriggerPlayerMovement );


//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been handled.
//-----------------------------------------------------------------------------
void CTriggerPlayerMovement::Spawn( void )
{
	if( HasSpawnFlags( SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS ) )
	{
		// @Note (toml 01-07-04): fix up spawn flag collision coding error. Remove at some point once all maps fixed up please!
		DevMsg("*** trigger_playermovement using obsolete spawnflag. Remove and reset with new value for \"Disable auto player movement\"\n" );
		RemoveSpawnFlags(SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS);
		AddSpawnFlags(SF_TRIGGER_MOVE_AUTODISABLE);
	}
	BaseClass::Spawn();

	InitTrigger();
}


// UNDONE: This will not support a player touching more than one of these
// UNDONE: Do we care?  If so, ref count automovement in the player?
void CTriggerPlayerMovement::StartTouch( CBaseEntity *pOther )
{	
	if (!PassesTriggerFilters(pOther))
		return;

	CBasePlayer *pPlayer = ToBasePlayer( pOther );

	if ( !pPlayer )
		return;

	// UNDONE: Currently this is the only operation this trigger can do
	if ( HasSpawnFlags(SF_TRIGGER_MOVE_AUTODISABLE) )
	{
		pPlayer->m_Local.m_bAllowAutoMovement = false;
	}
}

void CTriggerPlayerMovement::EndTouch( CBaseEntity *pOther )
{
	if (!PassesTriggerFilters(pOther))
		return;

	CBasePlayer *pPlayer = ToBasePlayer( pOther );

	if ( !pPlayer )
		return;

	if ( HasSpawnFlags(SF_TRIGGER_MOVE_AUTODISABLE) )
	{
		pPlayer->m_Local.m_bAllowAutoMovement = true;
	}
}

//------------------------------------------------------------------------------
// Base VPhysics trigger implementation
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// Save/load
//------------------------------------------------------------------------------
BEGIN_DATADESC( CBaseVPhysicsTrigger )
	DEFINE_KEYFIELD( m_bDisabled,		FIELD_BOOLEAN,	"StartDisabled" ),
	DEFINE_KEYFIELD( m_iFilterName,	FIELD_STRING,	"filtername" ),
	DEFINE_FIELD( m_hFilter,	FIELD_EHANDLE ),

	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
END_DATADESC()

//------------------------------------------------------------------------------
// Spawn
//------------------------------------------------------------------------------
void CBaseVPhysicsTrigger::Spawn()
{
	Precache();

	SetSolid( SOLID_VPHYSICS );	
	AddSolidFlags( FSOLID_NOT_SOLID );

	// NOTE: Don't make yourself FSOLID_TRIGGER here or you'll get game 
	// collisions AND vphysics collisions.  You don't want any game collisions
	// so just use FSOLID_NOT_SOLID

	SetMoveType( MOVETYPE_NONE );
	SetModel( STRING( GetModelName() ) );    // set size and link into world
	if ( showtriggers.GetInt() == 0 )
	{
		AddEffects( EF_NODRAW );
	}

	CreateVPhysics();
}

//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
bool CBaseVPhysicsTrigger::CreateVPhysics()
{
	IPhysicsObject *pPhysics;
	if ( !HasSpawnFlags( SF_VPHYSICS_MOTION_MOVEABLE ) )
	{
		pPhysics = VPhysicsInitStatic();
	}
	else
	{
		pPhysics = VPhysicsInitShadow( false, false );
	}

	pPhysics->BecomeTrigger();
	return true;
}

//------------------------------------------------------------------------------
// Cleanup
//------------------------------------------------------------------------------
void CBaseVPhysicsTrigger::UpdateOnRemove()
{
	if ( VPhysicsGetObject())
	{
		VPhysicsGetObject()->RemoveTrigger();
	}

	BaseClass::UpdateOnRemove();
}

//------------------------------------------------------------------------------
// Activate
//------------------------------------------------------------------------------
void CBaseVPhysicsTrigger::Activate( void ) 
{ 
	// Get a handle to my filter entity if there is one
	if (m_iFilterName != NULL_STRING)
	{
		m_hFilter = dynamic_cast<CBaseFilter *>(gEntList.FindEntityByName( NULL, m_iFilterName, NULL ));
	}

	BaseClass::Activate();
}

//------------------------------------------------------------------------------
// Inputs
//------------------------------------------------------------------------------
void CBaseVPhysicsTrigger::InputToggle( inputdata_t &inputdata )
{
	if ( m_bDisabled )
	{
		InputEnable( inputdata );
	}
	else
	{
		InputDisable( inputdata );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseVPhysicsTrigger::InputEnable( inputdata_t &inputdata )
{
	if ( m_bDisabled )
	{
		m_bDisabled = false;
		if ( VPhysicsGetObject())
		{
			VPhysicsGetObject()->EnableCollisions( true );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseVPhysicsTrigger::InputDisable( inputdata_t &inputdata )
{
	if ( !m_bDisabled )
	{
		m_bDisabled = true;
		if ( VPhysicsGetObject())
		{
			VPhysicsGetObject()->EnableCollisions( false );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseVPhysicsTrigger::StartTouch( CBaseEntity *pOther )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseVPhysicsTrigger::EndTouch( CBaseEntity *pOther )
{
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseVPhysicsTrigger::PassesTriggerFilters( CBaseEntity *pOther )
{
	if ( pOther->GetMoveType() != MOVETYPE_VPHYSICS && !pOther->IsPlayer() )
		return false;

	// First test spawn flag filters
	if ( HasSpawnFlags(SF_TRIGGER_ALLOW_ALL) ||
		(HasSpawnFlags(SF_TRIGGER_ALLOW_CLIENTS) && (pOther->GetFlags() & FL_CLIENT)) ||
		(HasSpawnFlags(SF_TRIGGER_ALLOW_NPCS) && (pOther->GetFlags() & FL_NPC)) ||
		(HasSpawnFlags(SF_TRIGGER_ALLOW_PUSHABLES) && FClassnameIs(pOther, "func_pushable")) ||
		(HasSpawnFlags(SF_TRIGGER_ALLOW_PHYSICS) && pOther->GetMoveType() == MOVETYPE_VPHYSICS))
	{
		bool bOtherIsPlayer = pOther->IsPlayer();
		if( HasSpawnFlags(SF_TRIGGER_ONLY_PLAYER_ALLY_NPCS) && !bOtherIsPlayer )
		{
			CAI_BaseNPC *pNPC = pOther->MyNPCPointer();

			if( !pNPC || !pNPC->IsPlayerAlly() )
			{
				return false;
			}
		}

		if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_IN_VEHICLES) && bOtherIsPlayer )
		{
			if ( !((CBasePlayer*)pOther)->IsInAVehicle() )
				return false;
		}

		if ( HasSpawnFlags(SF_TRIGGER_ONLY_CLIENTS_OUT_OF_VEHICLES) && bOtherIsPlayer )
		{
			if ( ((CBasePlayer*)pOther)->IsInAVehicle() )
				return false;
		}

		CBaseFilter *pFilter = m_hFilter.Get();
		return (!pFilter) ? true : pFilter->PassesFilter(pOther);
	}
	return false;
}

//=====================================================================================================================
//-----------------------------------------------------------------------------
// Purpose: VPhysics trigger that changes the motion of vphysics objects that touch it
//-----------------------------------------------------------------------------
class CTriggerVPhysicsMotion : public CBaseVPhysicsTrigger, public IMotionEvent
{
	DECLARE_CLASS( CTriggerVPhysicsMotion, CBaseVPhysicsTrigger );

public:
	void Spawn();
	void Precache();
	virtual void UpdateOnRemove();
	bool CreateVPhysics();
	void OnRestore();

	// UNDONE: Pass trigger event in or change Start/EndTouch.  Add ITriggerVPhysics perhaps?
	// BUGBUG: If a player touches two of these, his movement will screw up.
	// BUGBUG: If a player uses crouch/uncrouch it will generate touch events and clear the motioncontroller flag
	void StartTouch( CBaseEntity *pOther );
	void EndTouch( CBaseEntity *pOther );

	void InputSetVelocityLimitTime( inputdata_t &inputdata );

	float LinearLimit();

	inline bool HasGravityScale() { return m_gravityScale != 1.0 ? true : false; }
	inline bool HasAirDensity() { return m_addAirDensity != 0 ? true : false; }
	inline bool HasLinearLimit() { return LinearLimit() != 0.0f; }
	inline bool HasLinearScale() { return m_linearScale != 1.0 ? true : false; }
	inline bool HasAngularLimit() { return m_angularLimit != 0 ? true : false; }
	inline bool HasAngularScale() { return m_angularScale != 1.0 ? true : false; }
	inline bool HasLinearForce() { return m_linearForce != 0.0 ? true : false; }

	DECLARE_DATADESC();

	virtual simresult_e	Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );

private:
	IPhysicsMotionController	*m_pController;

	EntityParticleTrailInfo_t	m_ParticleTrail;

	float						m_gravityScale;
	float						m_addAirDensity;
	float						m_linearLimit;
	float						m_linearLimitDelta;
	float						m_linearLimitTime;
	float						m_linearLimitStart;
	float						m_linearLimitStartTime;
	float						m_linearScale;
	float						m_angularLimit;
	float						m_angularScale;
	float						m_linearForce;
	QAngle						m_linearForceAngles;
};


//------------------------------------------------------------------------------
// Save/load
//------------------------------------------------------------------------------
BEGIN_DATADESC( CTriggerVPhysicsMotion )
	DEFINE_PHYSPTR( m_pController ),
	DEFINE_EMBEDDED( m_ParticleTrail ),
	DEFINE_INPUT( m_gravityScale, FIELD_FLOAT, "SetGravityScale" ),
	DEFINE_INPUT( m_addAirDensity, FIELD_FLOAT, "SetAdditionalAirDensity" ),
	DEFINE_INPUT( m_linearLimit, FIELD_FLOAT, "SetVelocityLimit" ),
	DEFINE_INPUT( m_linearLimitDelta, FIELD_FLOAT, "SetVelocityLimitDelta" ),
	DEFINE_FIELD( m_linearLimitTime, FIELD_FLOAT ),
	DEFINE_FIELD( m_linearLimitStart, FIELD_TIME ),
	DEFINE_FIELD( m_linearLimitStartTime, FIELD_TIME ),
	DEFINE_INPUT( m_linearScale, FIELD_FLOAT, "SetVelocityScale" ),
	DEFINE_INPUT( m_angularLimit, FIELD_FLOAT, "SetAngVelocityLimit" ),
	DEFINE_INPUT( m_angularScale, FIELD_FLOAT, "SetAngVelocityScale" ),
	DEFINE_INPUT( m_linearForce, FIELD_FLOAT, "SetLinearForce" ),
	DEFINE_INPUT( m_linearForceAngles, FIELD_VECTOR, "SetLinearForceAngles" ),

	DEFINE_INPUTFUNC( FIELD_STRING, "SetVelocityLimitTime", InputSetVelocityLimitTime ),
END_DATADESC()

LINK_ENTITY_TO_CLASS( trigger_vphysics_motion, CTriggerVPhysicsMotion );


//------------------------------------------------------------------------------
// Spawn
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::Spawn()
{
	Precache();

	BaseClass::Spawn();
}

//------------------------------------------------------------------------------
// Precache
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::Precache()
{
	if ( m_ParticleTrail.m_strMaterialName != NULL_STRING )
	{
		PrecacheMaterial( STRING(m_ParticleTrail.m_strMaterialName) ); 
	}
}

//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
float CTriggerVPhysicsMotion::LinearLimit()
{
	if ( m_linearLimitTime == 0.0f )
		return m_linearLimit;

	float dt = gpGlobals->curtime - m_linearLimitStartTime;
	if ( dt >= m_linearLimitTime )
	{
		m_linearLimitTime = 0.0;
		return m_linearLimit;
	}

	dt /= m_linearLimitTime;
	float flLimit = RemapVal( dt, 0.0f, 1.0f, m_linearLimitStart, m_linearLimit );
	return flLimit;
}

	
//------------------------------------------------------------------------------
// Create VPhysics
//------------------------------------------------------------------------------
bool CTriggerVPhysicsMotion::CreateVPhysics()
{
	BaseClass::CreateVPhysics();

	m_pController = physenv->CreateMotionController( this );
	return true;
}


//------------------------------------------------------------------------------
// Cleanup
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::UpdateOnRemove()
{
	if ( m_pController )
	{
		physenv->DestroyMotionController( m_pController );
		m_pController = NULL;
	}

	BaseClass::UpdateOnRemove();
}


//------------------------------------------------------------------------------
// Restore
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::OnRestore()
{
	BaseClass::OnRestore();
	if ( m_pController )
	{
		m_pController->SetEventHandler( this );
	}
}

//------------------------------------------------------------------------------
// Start/End Touch
//------------------------------------------------------------------------------
// UNDONE: Pass trigger event in or change Start/EndTouch.  Add ITriggerVPhysics perhaps?
// BUGBUG: If a player touches two of these, his movement will screw up.
// BUGBUG: If a player uses crouch/uncrouch it will generate touch events and clear the motioncontroller flag
void CTriggerVPhysicsMotion::StartTouch( CBaseEntity *pOther )
{
	BaseClass::StartTouch( pOther );

	if ( !PassesTriggerFilters(pOther) )
		return;

	CBasePlayer *pPlayer = ToBasePlayer( pOther );
	if ( pPlayer )
	{
		pPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, true );
		pPlayer->m_Local.m_bSlowMovement = true;
	}

	triggerevent_t event;
	PhysGetTriggerEvent( &event, this );
	if ( event.pObject )
	{
		// these all get done again on save/load, so check
		m_pController->AttachObject( event.pObject, true );
	}

	if ( m_ParticleTrail.m_strMaterialName != NULL_STRING )
	{
		CEntityParticleTrail::Create( pOther, m_ParticleTrail, this ); 
	}

	if ( pOther->GetBaseAnimating() && pOther->GetBaseAnimating()->IsRagdoll() )
	{
		CRagdollBoogie::IncrementSuppressionCount( pOther );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CTriggerVPhysicsMotion::EndTouch( CBaseEntity *pOther )
{
	BaseClass::EndTouch( pOther );

	if ( !PassesTriggerFilters(pOther) )
		return;

	CBasePlayer *pPlayer = ToBasePlayer( pOther );
	if ( pPlayer )
	{
		pPlayer->SetPhysicsFlag( PFLAG_VPHYSICS_MOTIONCONTROLLER, false );
		pPlayer->m_Local.m_bSlowMovement = false;
	}
	triggerevent_t event;
	PhysGetTriggerEvent( &event, this );
	if ( event.pObject && m_pController )
	{
		m_pController->DetachObject( event.pObject );
	}

	if ( m_ParticleTrail.m_strMaterialName != NULL_STRING )
	{
		CEntityParticleTrail::Destroy( pOther, m_ParticleTrail ); 
	}

	if ( pOther->GetBaseAnimating() && pOther->GetBaseAnimating()->IsRagdoll() )
	{
		CRagdollBoogie::DecrementSuppressionCount( pOther );
	}
}


//------------------------------------------------------------------------------
// Inputs
//------------------------------------------------------------------------------
void CTriggerVPhysicsMotion::InputSetVelocityLimitTime( inputdata_t &inputdata )
{
	m_linearLimitStart = LinearLimit();
	m_linearLimitStartTime = gpGlobals->curtime;

	float args[2];
	UTIL_StringToFloatArray( args, 2, inputdata.value.String() );
	m_linearLimit = args[0];
	m_linearLimitTime = args[1];
}

//------------------------------------------------------------------------------
// Apply the forces to the entity
//------------------------------------------------------------------------------
IMotionEvent::simresult_e CTriggerVPhysicsMotion::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
{
	if ( m_bDisabled )
		return SIM_NOTHING;

	linear.Init();
	angular.Init();

	if ( HasGravityScale() )
	{
		// assume object already has 1.0 gravities applied to it, so apply the additional amount
		linear.z -= (m_gravityScale-1) * sv_gravity.GetFloat();
	}

	if ( HasLinearForce() )
	{
		Vector vecForceDir;
		AngleVectors( m_linearForceAngles, &vecForceDir );
		VectorMA( linear, m_linearForce, vecForceDir, linear );
	}

	if ( HasAirDensity() || HasLinearLimit() || HasLinearScale() || HasAngularLimit() || HasAngularScale() )
	{
		Vector vel;
		AngularImpulse angVel;
		pObject->GetVelocity( &vel, &angVel );
		vel += linear * deltaTime; // account for gravity scale

		Vector unitVel = vel;
		Vector unitAngVel = angVel;

		float speed = VectorNormalize( unitVel );
		float angSpeed = VectorNormalize( unitAngVel );

		float speedScale = 0.0;
		float angSpeedScale = 0.0;

		if ( HasAirDensity() )
		{
			float linearDrag = -0.5 * m_addAirDensity * pObject->CalculateLinearDrag( unitVel ) * deltaTime;
			if ( linearDrag < -1 )
			{
				linearDrag = -1;
			}
			speedScale += linearDrag / deltaTime;
			float angDrag = -0.5 * m_addAirDensity * pObject->CalculateAngularDrag( unitAngVel ) * deltaTime;
			if ( angDrag < -1 )
			{
				angDrag = -1;
			}
			angSpeedScale += angDrag / deltaTime;
		}

		if ( HasLinearLimit() && speed > m_linearLimit )
		{
			float flDeltaVel = (LinearLimit() - speed) / deltaTime;
			if ( m_linearLimitDelta != 0.0f )
			{
				float flMaxDeltaVel = -m_linearLimitDelta / deltaTime;
				if ( flDeltaVel < flMaxDeltaVel )
				{
					flDeltaVel = flMaxDeltaVel;
				}
			}
			VectorMA( linear, flDeltaVel, unitVel, linear );
		}
		if ( HasAngularLimit() && angSpeed > m_angularLimit )
		{
			angular += ((m_angularLimit - angSpeed)/deltaTime) * unitAngVel;
		}
		if ( HasLinearScale() )
		{
			speedScale = ( (speedScale+1) * m_linearScale ) - 1;
		}
		if ( HasAngularScale() )
		{
			angSpeedScale = ( (angSpeedScale+1) * m_angularScale ) - 1;
		}
		linear += vel * speedScale;
		angular += angVel * angSpeedScale;
	}

	return SIM_GLOBAL_ACCELERATION;
}



#ifdef HL1_DLL
//----------------------------------------------------------------------------------
// func_friction
//----------------------------------------------------------------------------------
class CFrictionModifier : public CBaseTrigger
{
	DECLARE_CLASS( CFrictionModifier, CBaseTrigger );

public:
	void		Spawn( void );
	bool		KeyValue( const char *szKeyName, const char *szValue );

	virtual void StartTouch(CBaseEntity *pOther);
	virtual void EndTouch(CBaseEntity *pOther);

	virtual int	ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; }
	float		m_frictionFraction;

	DECLARE_DATADESC();

};

LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier );

BEGIN_DATADESC( CFrictionModifier )
	DEFINE_FIELD( m_frictionFraction, FIELD_FLOAT ),
END_DATADESC()

// Modify an entity's friction
void CFrictionModifier::Spawn( void )
{
	BaseClass::Spawn();

	InitTrigger();
}

// Sets toucher's friction to m_frictionFraction (1.0 = normal friction)
bool CFrictionModifier::KeyValue( const char *szKeyName, const char *szValue )
{
	if (FStrEq(szKeyName, "modifier"))
	{
		m_frictionFraction = atof(szValue) / 100.0;
	}
	else
	{
		BaseClass::KeyValue( szKeyName, szValue );
	}
	return true;
}

void CFrictionModifier::StartTouch( CBaseEntity *pOther )
{
	if ( !pOther->IsPlayer() )		// ignore player
	{
		pOther->SetFriction( m_frictionFraction );
	}
}

void CFrictionModifier::EndTouch( CBaseEntity *pOther )
{
	if ( !pOther->IsPlayer() )		// ignore player
	{
		pOther->SetFriction( 1.0f );
	}
}

#endif //HL1_DLL

bool IsTriggerClass( CBaseEntity *pEntity )
{
	if ( NULL != dynamic_cast<CBaseTrigger *>(pEntity) )
		return true;

	if ( NULL != dynamic_cast<CTriggerVPhysicsMotion *>(pEntity) )
		return true;

	if ( NULL != dynamic_cast<CTriggerVolume *>(pEntity) )
		return true;
	
	return false;
}
