//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Spawn, think, and use functions for common brush entities.
//
//=============================================================================//

#include "cbase.h"
#include "doors.h"
#include "mathlib/mathlib.h"
#include "physics.h"
#include "ndebugoverlay.h"
#include "engine/IEngineSound.h"
#include "globals.h"
#include "filters.h"

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

#define		SF_BRUSH_ACCDCC	16// brush should accelerate and decelerate when toggled
#define		SF_BRUSH_HURT		32// rotating brush that inflicts pain based on rotation speed
#define		SF_ROTATING_NOT_SOLID	64	// some special rotating objects are not solid.

// =================== FUNC_WALL ==============================================
class CFuncWall : public CBaseEntity
{
public:
	DECLARE_DATADESC();
	DECLARE_CLASS( CFuncWall, CBaseEntity );
	void	Spawn( void );
	bool	CreateVPhysics( void );
	void	Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );

	int		m_nState;
};

LINK_ENTITY_TO_CLASS( func_wall, CFuncWall );

//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CFuncWall )

	DEFINE_FIELD( m_nState,	FIELD_INTEGER ),

END_DATADESC()

void CFuncWall::Spawn( void )
{
	SetLocalAngles( vec3_angle );
	SetMoveType( MOVETYPE_PUSH );  // so it doesn't get pushed by anything
	SetModel( STRING( GetModelName() ) );
	
	// If it can't move/go away, it's really part of the world
	AddFlag( FL_WORLDBRUSH );

	// set manual mode
	CreateVPhysics();
}


bool CFuncWall::CreateVPhysics( void )
{
	SetSolid( SOLID_BSP );
	IPhysicsObject *pPhys = VPhysicsInitStatic();
	if ( pPhys )
	{
		int contents = modelinfo->GetModelContents( GetModelIndex() );
		if ( ! (contents & (MASK_SOLID|MASK_PLAYERSOLID|MASK_NPCSOLID)) )
		{
			// leave the physics shadow there in case it has crap constrained to it
			// but disable collisions with it
			pPhys->EnableCollisions( false );
		}
	}

	return true;
}


void CFuncWall::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	if ( ShouldToggle( useType, m_nState ) )
	{
		m_nState = 1 - m_nState;
	}
}


#define SF_WALL_START_OFF		0x0001

class CFuncWallToggle : public CFuncWall
{
public:
	DECLARE_CLASS( CFuncWallToggle, CFuncWall );

	DECLARE_DATADESC();

	void	Spawn( void );
	void	Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );

	void	InputToggle( inputdata_t &inputdata );

	void	TurnOff( void );
	void	TurnOn( void );
	bool	IsOn( void );
};

BEGIN_DATADESC( CFuncWallToggle )

	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),

END_DATADESC()


LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWallToggle );

void CFuncWallToggle::Spawn( void )
{
	BaseClass::Spawn();
	if ( HasSpawnFlags( SF_WALL_START_OFF ) )
		TurnOff();
	
	SetMoveType( MOVETYPE_PUSH );
}


void CFuncWallToggle::TurnOff( void )
{
	IPhysicsObject *pPhys = VPhysicsGetObject();
	if ( pPhys )
	{
		pPhys->EnableCollisions( false );
	}
	AddSolidFlags( FSOLID_NOT_SOLID );
	AddEffects( EF_NODRAW );
}


void CFuncWallToggle::TurnOn( void )
{
	IPhysicsObject *pPhys = VPhysicsGetObject();
	if ( pPhys )
	{
		pPhys->EnableCollisions( true );
	}
	RemoveSolidFlags( FSOLID_NOT_SOLID );
	RemoveEffects( EF_NODRAW );
}


bool CFuncWallToggle::IsOn( void )
{
	if ( IsSolidFlagSet( FSOLID_NOT_SOLID ) )
		return false;
	return true;
}

void CFuncWallToggle::InputToggle( inputdata_t &inputdata )
{
	int status = IsOn();

	if ( ShouldToggle( USE_TOGGLE, status ) )
	{
		if ( status )
			TurnOff();
		else
			TurnOn();
	}
}

//Adrian - Is this function needed at all?
void CFuncWallToggle::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	int status = IsOn();

	if ( ShouldToggle( useType, status ) )
	{
		if ( status )
			TurnOff();
		else
			TurnOn();
	}
}

//============================== FUNC_VEHICLECLIP =====================================
class CFuncVehicleClip : public CBaseEntity
{
public:
	DECLARE_CLASS( CFuncVehicleClip, CBaseEntity );
	DECLARE_DATADESC();

	void Spawn();
	bool CreateVPhysics( void );

	void InputEnable( inputdata_t &data );
	void InputDisable( inputdata_t &data );

private:
};

BEGIN_DATADESC( CFuncVehicleClip )

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

END_DATADESC()

LINK_ENTITY_TO_CLASS( func_vehicleclip, CFuncVehicleClip );

void CFuncVehicleClip::Spawn()
{

	SetLocalAngles( vec3_angle );
	SetMoveType( MOVETYPE_PUSH );  // so it doesn't get pushed by anything
	SetModel( STRING( GetModelName() ) );
	
	// It's part of the world
	AddFlag( FL_WORLDBRUSH );

	CreateVPhysics();

	AddEffects( EF_NODRAW );		// make entity invisible

	SetCollisionGroup( COLLISION_GROUP_VEHICLE_CLIP );
}

bool CFuncVehicleClip::CreateVPhysics( void )
{
	SetSolid( SOLID_BSP );
	VPhysicsInitStatic();

	return true;
}

void CFuncVehicleClip::InputEnable( inputdata_t &data )
{
	IPhysicsObject *pPhys = VPhysicsGetObject();
	if ( pPhys )
	{
		pPhys->EnableCollisions( true );
	}
	RemoveSolidFlags( FSOLID_NOT_SOLID );
}

void CFuncVehicleClip::InputDisable( inputdata_t &data )
{
	IPhysicsObject *pPhys = VPhysicsGetObject();
	if ( pPhys )
	{
		pPhys->EnableCollisions( false );
	}
	AddSolidFlags( FSOLID_NOT_SOLID );
}

//============================= FUNC_CONVEYOR =======================================

#define SF_CONVEYOR_VISUAL		0x0001
#define SF_CONVEYOR_NOTSOLID	0x0002

class CFuncConveyor : public CFuncWall
{
public:
	DECLARE_CLASS( CFuncConveyor, CFuncWall );
	DECLARE_DATADESC();
	DECLARE_SERVERCLASS();

	CFuncConveyor();

	void	Spawn( void );
	void	Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	void	UpdateSpeed( float flNewSpeed );

	void	GetGroundVelocityToApply( Vector &vecGroundVel );

	// Input handlers.
	void	InputToggleDirection( inputdata_t &inputdata );
	void	InputSetSpeed( inputdata_t &inputdata );

private:

	Vector m_vecMoveDir;
	CNetworkVar( float, m_flConveyorSpeed );
};

LINK_ENTITY_TO_CLASS( func_conveyor, CFuncConveyor );

BEGIN_DATADESC( CFuncConveyor )

	DEFINE_INPUTFUNC( FIELD_VOID, "ToggleDirection", InputToggleDirection ),
	DEFINE_INPUTFUNC( FIELD_VOID, "SetSpeed", InputSetSpeed ),

	DEFINE_KEYFIELD( m_vecMoveDir, FIELD_VECTOR, "movedir" ),
	DEFINE_FIELD( m_flConveyorSpeed, FIELD_FLOAT ),

END_DATADESC()


IMPLEMENT_SERVERCLASS_ST(CFuncConveyor, DT_FuncConveyor)
	SendPropFloat( SENDINFO(m_flConveyorSpeed), 0, SPROP_NOSCALE ),
END_SEND_TABLE()


CFuncConveyor::CFuncConveyor()
{
	m_flConveyorSpeed = 0.0;
}

void CFuncConveyor::Spawn( void )
{
	// Convert movedir from angles to a vector
	QAngle angMoveDir = QAngle( m_vecMoveDir.x, m_vecMoveDir.y, m_vecMoveDir.z );
	AngleVectors( angMoveDir, &m_vecMoveDir );

	BaseClass::Spawn();

	if ( !HasSpawnFlags(SF_CONVEYOR_VISUAL) )
		AddFlag( FL_CONVEYOR );

	// HACKHACK - This is to allow for some special effects
	if ( HasSpawnFlags( SF_CONVEYOR_NOTSOLID ) )
	{
		AddSolidFlags( FSOLID_NOT_SOLID );
	}

	if ( m_flSpeed == 0 )
		m_flSpeed = 100;

	UpdateSpeed( m_flSpeed );
}


void CFuncConveyor::UpdateSpeed( float flNewSpeed )
{
	m_flConveyorSpeed = flNewSpeed;
}


void CFuncConveyor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
    m_flSpeed = -m_flSpeed;
	UpdateSpeed( m_flSpeed );
}


void CFuncConveyor::InputToggleDirection( inputdata_t &inputdata )
{
	Use( inputdata.pActivator, inputdata.pCaller, USE_TOGGLE, 0 );
}


void CFuncConveyor::InputSetSpeed( inputdata_t &inputdata )
{
    m_flSpeed = inputdata.value.Float();
	UpdateSpeed( m_flSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Returns the velocity imparted to players standing on us.
//-----------------------------------------------------------------------------
void CFuncConveyor::GetGroundVelocityToApply( Vector &vecGroundVel )
{
	vecGroundVel = m_vecMoveDir * m_flSpeed;
}


// =================== FUNC_ILLUSIONARY ==============================================
// A simple entity that looks solid but lets you walk through it.
class CFuncIllusionary : public CBaseEntity 
{
	DECLARE_CLASS( CFuncIllusionary, CBaseEntity );
public:
	void Spawn( void );
};

LINK_ENTITY_TO_CLASS( func_illusionary, CFuncIllusionary );

void CFuncIllusionary::Spawn( void )
{
	SetLocalAngles( vec3_angle );
	SetMoveType( MOVETYPE_NONE );  
	SetSolid( SOLID_NONE );
	SetModel( STRING( GetModelName() ) );
}


//-----------------------------------------------------------------------------
// Purpose: A rotating brush entity.
//
//			You need to have an origin brush as part of this entity.  The  
//			center of that brush will be the point around which it is rotated.
//
//			It will rotate around the Z axis by default. Spawnflags can be set
//			to make it rotate around the X or Y axes.
//
//			The direction of rotation is also controlled by a spawnflag.
//-----------------------------------------------------------------------------
class CFuncRotating : public CBaseEntity
{
	DECLARE_CLASS( CFuncRotating, CBaseEntity );
public:
	// basic functions
	void Spawn( void  );
	void Precache( void  );
	bool CreateVPhysics( void );
	void SpinUpMove( void );
	void SpinDownMove( void );
	bool KeyValue( const char *szKeyName, const char *szValue );
	void HurtTouch ( CBaseEntity *pOther );
	void RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
	void RotateMove( void );
	void ReverseMove( void );
	void RampPitchVol( void );
	void Blocked( CBaseEntity *pOther );
	void SetTargetSpeed( float flSpeed );
	void UpdateSpeed( float flNewSpeed );
	
	int	 DrawDebugTextOverlays(void);

	DECLARE_DATADESC();
	DECLARE_SERVERCLASS();

protected:
	bool SpinDown( float flTargetSpeed );
	float GetMoveSpeed( float flSpeed );

	float GetNextMoveInterval() const;

	// Input handlers
	void InputSetSpeed( inputdata_t &inputdata );
	void InputStart( inputdata_t &inputdata );
	void InputStop( inputdata_t &inputdata );
	void InputStartForward( inputdata_t &inputdata );
	void InputStartBackward( inputdata_t &inputdata );
	void InputToggle( inputdata_t &inputdata );
	void InputReverse( inputdata_t &inputdata );
	void InputStopAtStartPos( inputdata_t &inputdata );

	QAngle	m_vecMoveAng;

	float m_flFanFriction;
	float m_flAttenuation;
	float m_flVolume;
	float m_flTargetSpeed;			// Target value for m_flSpeed, used for spinning up and down.
	float m_flMaxSpeed;				// Maximum value for m_flSpeed, used for ramping sound effects.
	float m_flBlockDamage;			// Damage inflicted when blocked.
	string_t m_NoiseRunning;
	bool m_bReversed;

	QAngle	m_angStart;
	bool m_bStopAtStartPos;

	bool m_bSolidBsp;				// Brush is SOLID_BSP

public:
	Vector m_vecClientOrigin;
	QAngle m_vecClientAngles;
};

LINK_ENTITY_TO_CLASS( func_rotating, CFuncRotating );


BEGIN_DATADESC( CFuncRotating )

	DEFINE_FIELD( m_vecMoveAng, FIELD_VECTOR ),
	DEFINE_FIELD( m_flFanFriction, FIELD_FLOAT ),
	DEFINE_FIELD( m_flAttenuation, FIELD_FLOAT ),
	DEFINE_FIELD( m_flVolume, FIELD_FLOAT ),
	DEFINE_FIELD( m_flTargetSpeed, FIELD_FLOAT ),
	DEFINE_KEYFIELD( m_flMaxSpeed, FIELD_FLOAT, "maxspeed" ),
	DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ),
	DEFINE_KEYFIELD( m_NoiseRunning, FIELD_SOUNDNAME, "message" ),
	DEFINE_FIELD( m_bReversed, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_angStart, FIELD_VECTOR ),
	DEFINE_FIELD( m_bStopAtStartPos, FIELD_BOOLEAN ),
	DEFINE_KEYFIELD( m_bSolidBsp, FIELD_BOOLEAN, "solidbsp" ),

	// Function Pointers
	DEFINE_FUNCTION( SpinUpMove ),
	DEFINE_FUNCTION( SpinDownMove ),
	DEFINE_FUNCTION( HurtTouch ),
	DEFINE_FUNCTION( RotatingUse ),
	DEFINE_FUNCTION( RotateMove ),
	DEFINE_FUNCTION( ReverseMove ),

	// Inputs
	DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Start", InputStart ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Stop", InputStop ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Reverse", InputReverse ),
	DEFINE_INPUTFUNC( FIELD_VOID, "StartForward", InputStartForward ),
	DEFINE_INPUTFUNC( FIELD_VOID, "StartBackward", InputStartBackward ),
	DEFINE_INPUTFUNC( FIELD_VOID, "StopAtStartPos", InputStopAtStartPos ),

END_DATADESC()

extern void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
void SendProxy_FuncRotatingOrigin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
{
#ifdef TF_DLL
	CFuncRotating *entity = (CFuncRotating*)pStruct;
	Assert( entity );

	if ( entity->HasSpawnFlags(SF_BRUSH_ROTATE_CLIENTSIDE) )
	{
		const Vector *v = &entity->m_vecClientOrigin;
		pOut->m_Vector[ 0 ] = v->x;
		pOut->m_Vector[ 1 ] = v->y;
		pOut->m_Vector[ 2 ] = v->z;
		return;
	}
#endif

	SendProxy_Origin( pProp, pStruct, pData, pOut, iElement, objectID );
}

/*
extern void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
void SendProxy_FuncRotatingAngles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
{
	CFuncRotating *entity = (CFuncRotating*)pStruct;
	Assert( entity );
	if ( entity->HasSpawnFlags(SF_BRUSH_ROTATE_CLIENTSIDE) )
	{
		const QAngle *a = &entity->m_vecClientAngles;
		pOut->m_Vector[ 0 ] = anglemod( a->x );
		pOut->m_Vector[ 1 ] = anglemod( a->y );
		pOut->m_Vector[ 2 ] = anglemod( a->z );
		return;
	}

	SendProxy_Angles( pProp, pStruct, pData, pOut, iElement, objectID );
}
*/
void SendProxy_FuncRotatingAngle( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID)
{
	CFuncRotating *entity = (CFuncRotating*)pStruct;
	Assert( entity );

	vec_t const *qa = (vec_t *)pData;
	vec_t const *ea = entity->GetLocalAngles().Base();
	NOTE_UNUSED(ea);
	// Assert its actually an index into m_angRotation if not this won't work

	Assert( (uintp)qa >= (uintp)ea && (uintp)qa < (uintp)ea + sizeof( QAngle ));

#ifdef TF_DLL
	if ( entity->HasSpawnFlags(SF_BRUSH_ROTATE_CLIENTSIDE) )
	{
		const QAngle *a = &entity->m_vecClientAngles;

		pOut->m_Float = anglemod( (*a)[ qa - ea ] );
		return;
	}
#endif

	pOut->m_Float = anglemod( *qa );

	Assert( IsFinite( pOut->m_Float ) );
}

IMPLEMENT_SERVERCLASS_ST(CFuncRotating, DT_FuncRotating)
	SendPropVector(SENDINFO(m_vecOrigin), -1,  SPROP_COORD|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_FuncRotatingOrigin ),
	SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 0), 13, SPROP_CHANGES_OFTEN, SendProxy_FuncRotatingAngle ),
	SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 1), 13, SPROP_CHANGES_OFTEN, SendProxy_FuncRotatingAngle ),
	SendPropAngle( SENDINFO_VECTORELEM(m_angRotation, 2), 13, SPROP_CHANGES_OFTEN, SendProxy_FuncRotatingAngle ),

	SendPropFloat(SENDINFO(m_flSimulationTime))
END_SEND_TABLE()



//-----------------------------------------------------------------------------
// Purpose: Handles keyvalues from the BSP. Called before spawning.
//-----------------------------------------------------------------------------
bool CFuncRotating::KeyValue( const char *szKeyName, const char *szValue )
{
	if (FStrEq(szKeyName, "fanfriction"))
	{
		m_flFanFriction = atof(szValue)/100;
	}
	else if (FStrEq(szKeyName, "Volume"))
	{
		m_flVolume = atof(szValue) / 10.0;
		m_flVolume = clamp(m_flVolume, 0.0f, 1.0f);
	}
	else
	{ 
		return BaseClass::KeyValue( szKeyName, szValue );
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Called when spawning, after keyvalues have been set.
//-----------------------------------------------------------------------------
void CFuncRotating::Spawn( )
{
#ifdef TF_DLL
	AddSpawnFlags( SF_BRUSH_ROTATE_CLIENTSIDE );
#endif

	//
	// Maintain compatibility with previous maps.
	//
	if (m_flVolume == 0.0)
	{
		m_flVolume = 1.0;
	}

	//
	// If the designer didn't set a sound attenuation, default to one.
	//
	if ( HasSpawnFlags(SF_BRUSH_ROTATE_SMALLRADIUS) )
	{
		m_flAttenuation = ATTN_IDLE;
	}
	else if ( HasSpawnFlags(SF_BRUSH_ROTATE_MEDIUMRADIUS) )
	{
		m_flAttenuation = ATTN_STATIC;
	}
	else if ( HasSpawnFlags(SF_BRUSH_ROTATE_LARGERADIUS) )
	{
		m_flAttenuation = ATTN_NORM;
	}
	else
	{
		m_flAttenuation = ATTN_NORM;
	}

	//
	// Prevent divide by zero if level designer forgets friction!
	//
	if ( m_flFanFriction == 0 )
	{
		m_flFanFriction = 1;
	}
	
	//
	// Build the axis of rotation based on spawnflags.
	//
	if ( HasSpawnFlags(SF_BRUSH_ROTATE_Z_AXIS) )
	{
		m_vecMoveAng = QAngle(0,0,1);
	}
	else if ( HasSpawnFlags(SF_BRUSH_ROTATE_X_AXIS) )
	{
		m_vecMoveAng = QAngle(1,0,0);
	}
	else
	{
		m_vecMoveAng = QAngle(0,1,0);	// y-axis
	}

	//
	// Check for reverse rotation.
	//
	if ( HasSpawnFlags(SF_BRUSH_ROTATE_BACKWARDS) )
	{
		m_vecMoveAng = m_vecMoveAng * -1;
	}

	SetSolid( SOLID_VPHYSICS );

	//
	// Some rotating objects like fake volumetric lights will not be solid.
	//
	if ( HasSpawnFlags(SF_ROTATING_NOT_SOLID) )
	{
		AddSolidFlags( FSOLID_NOT_SOLID );
		SetMoveType( MOVETYPE_PUSH );
	}
	else
	{
		RemoveSolidFlags( FSOLID_NOT_SOLID );
		SetMoveType( MOVETYPE_PUSH );
	}

	SetModel( STRING( GetModelName() ) );

	SetUse( &CFuncRotating::RotatingUse );

	//
	// Did level designer forget to assign a maximum speed? Prevent a divide by
	// zero in RampPitchVol as well as allowing the rotator to work.
	//
	m_flMaxSpeed = fabs( m_flMaxSpeed );
	if (m_flMaxSpeed == 0)
	{
		m_flMaxSpeed = 100;
	}

	//
	// If the brush should be initially rotating, use it in a little while.
	//
	if ( HasSpawnFlags(SF_BRUSH_ROTATE_START_ON) )
	{		
		SetThink( &CFuncRotating::SUB_CallUseToggle );
		SetNextThink( gpGlobals->curtime + .2 );	// leave a magic delay for client to start up
	}

	//
	// Can this brush inflict pain?
	//
	if ( HasSpawnFlags(SF_BRUSH_HURT) )
	{
		SetTouch( &CFuncRotating::HurtTouch );
	}

	//
	// Set speed to 0 in case there's an old "speed" key lying around.
	//
	m_flSpeed = 0;
	
	Precache( );
	CreateVPhysics();

	m_angStart = GetLocalAngles();
	
	// Slam the object back to solid - if we really want it to be solid.
	if ( m_bSolidBsp )
	{
		SetSolid( SOLID_BSP );
	}

#ifdef TF_DLL
	if ( HasSpawnFlags(SF_BRUSH_ROTATE_CLIENTSIDE) )
	{
		m_vecClientOrigin = GetLocalOrigin();
		m_vecClientAngles = GetLocalAngles();
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CFuncRotating::CreateVPhysics( void )
{
	if ( !IsSolidFlagSet( FSOLID_NOT_SOLID ))
	{
		VPhysicsInitShadow( false, false );
	}
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CFuncRotating::Precache( void )
{
	//
	// Set up rotation sound.
	//
	char *szSoundFile = ( char * )STRING( m_NoiseRunning );
	if ( !m_NoiseRunning || strlen( szSoundFile ) == 0 )
	{
		// No sound set up, use the null sound.
		m_NoiseRunning = AllocPooledString("DoorSound.Null");
	}
	PrecacheScriptSound( STRING( m_NoiseRunning ) );
	
	if (GetLocalAngularVelocity() != vec3_angle )
	{
		//
		// If fan was spinning, and we went through transition or save/restore,
		// make sure we restart the sound.  1.5 sec delay is a magic number.
		//
		SetMoveDone( &CFuncRotating::SpinUpMove );
		SetMoveDoneTime( 1.5 );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Will hurt others based on how fast the brush is spinning.
// Input  : pOther - 
//-----------------------------------------------------------------------------
void CFuncRotating::HurtTouch ( CBaseEntity *pOther )
{
	// we can't hurt this thing, so we're not concerned with it
	if ( !pOther->m_takedamage )
		return;

	// calculate damage based on rotation speed
	m_flBlockDamage = GetLocalAngularVelocity().Length() / 10;

#ifdef HL1_DLL
	if( m_flBlockDamage > 0 )
#endif
	{
		pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) );
	
		Vector vecNewVelocity = pOther->GetAbsOrigin() - WorldSpaceCenter();
		VectorNormalize(vecNewVelocity);
		vecNewVelocity *= m_flBlockDamage;
		pOther->SetAbsVelocity( vecNewVelocity );
	}
}


#define FANPITCHMIN		30
#define FANPITCHMAX		100


//-----------------------------------------------------------------------------
// Purpose: Ramp pitch and volume up to maximum values, based on the difference
//			between how fast we're going vs how fast we can go.
//-----------------------------------------------------------------------------
void CFuncRotating::RampPitchVol( void )
{
	//
	// Calc volume and pitch as % of maximum vol and pitch.
	//
	float fpct = fabs(m_flSpeed) / m_flMaxSpeed;
	float fvol = clamp(m_flVolume * fpct, 0.f, 1.f);			  // slowdown volume ramps down to 0

	float fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * fpct;	
	
	int pitch = clamp(FastFloatToSmallInt(fpitch), 0, 255);
	if (pitch == PITCH_NORM)
	{
		pitch = PITCH_NORM - 1;
	}

	//
	// Update the fan's volume and pitch.
	//
	CPASAttenuationFilter filter( GetAbsOrigin(), m_flAttenuation );
	filter.MakeReliable();

	EmitSound_t ep;
	ep.m_nChannel = CHAN_STATIC;
	ep.m_pSoundName = STRING(m_NoiseRunning);
	ep.m_flVolume = fvol;
	ep.m_SoundLevel = ATTN_TO_SNDLVL( m_flAttenuation );
	ep.m_nFlags = SND_CHANGE_PITCH | SND_CHANGE_VOL;
	ep.m_nPitch = pitch;

	EmitSound( filter, entindex(), ep );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : float
//-----------------------------------------------------------------------------
float CFuncRotating::GetNextMoveInterval() const
{
	if ( m_bStopAtStartPos )
	{
		return TICK_INTERVAL;
	}
	return 0.1f;
}

//-----------------------------------------------------------------------------
// Purpose: Sets the current speed to the given value and manages the sound effects.
// Input  : flNewSpeed - New speed in degrees per second.
//-----------------------------------------------------------------------------
void CFuncRotating::UpdateSpeed( float flNewSpeed )
{
	float flOldSpeed = m_flSpeed;
	m_flSpeed = clamp( flNewSpeed, -m_flMaxSpeed, m_flMaxSpeed );

	if ( m_bStopAtStartPos )
	{
		int checkAxis = 2;
		// See if we got close to the starting orientation
		if ( m_vecMoveAng[0] != 0 )
		{
			checkAxis = 0;
		}
		else if ( m_vecMoveAng[1] != 0 )
		{
			checkAxis = 1;
		}

		float angDelta = anglemod( GetLocalAngles()[ checkAxis ] - m_angStart[ checkAxis ] );
		if ( angDelta > 180.0f )
		{
			angDelta -= 360.0f;
		}

		if ( flNewSpeed < 100 )
		{
			if ( flNewSpeed <= 25 && fabs( angDelta ) < 1.0f )
			{
				m_flTargetSpeed = 0;
				m_bStopAtStartPos = false;
				m_flSpeed = 0.0f;

				SetLocalAngles( m_angStart );
			}
			else if ( fabs( angDelta ) > 90.0f )
			{
				// Keep rotating at same speed for now
				m_flSpeed = flOldSpeed;
			}
			else
			{
				float minSpeed =  fabs( angDelta );
				if ( minSpeed < 20 )
					minSpeed = 20;
	
				m_flSpeed = flOldSpeed > 0.0f ? minSpeed : -minSpeed;
			}
		}
	}


	if ( ( flOldSpeed == 0 ) && ( m_flSpeed != 0 ) )
	{
		// Starting to move - emit the sound.
		CPASAttenuationFilter filter( GetAbsOrigin(), m_flAttenuation );
		filter.MakeReliable();
	
		EmitSound_t ep;
		ep.m_nChannel = CHAN_STATIC;
		ep.m_pSoundName = STRING(m_NoiseRunning);
		ep.m_flVolume = 0.01;
		ep.m_SoundLevel = ATTN_TO_SNDLVL( m_flAttenuation );
		ep.m_nPitch = FANPITCHMIN;

		EmitSound( filter, entindex(), ep );
		RampPitchVol();
	}
	else if ( ( flOldSpeed != 0 ) && ( m_flSpeed == 0 ) )
	{
		// Stopping - stop the sound.
		StopSound( entindex(), CHAN_STATIC, STRING(m_NoiseRunning) );
		
	}
	else
	{
		// Changing speed - adjust the pitch and volume.
		RampPitchVol();
	}

	SetLocalAngularVelocity( m_vecMoveAng * m_flSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Think function. Accelerates a func_rotating to a higher angular velocity.
//-----------------------------------------------------------------------------
void CFuncRotating::SpinUpMove( void )
{
	//
	// Calculate our new speed.
	//
	bool bSpinUpDone = false;
	float flNewSpeed = fabs( m_flSpeed ) + 0.2 * m_flMaxSpeed * m_flFanFriction;
	if ( fabs( flNewSpeed ) >=  fabs( m_flTargetSpeed ) )
	{
		// Reached our target speed.
		flNewSpeed = m_flTargetSpeed;
		bSpinUpDone = !m_bStopAtStartPos;
	}
	else if ( m_flTargetSpeed < 0 )
	{
		// Spinning up in reverse - negate the speed.
		flNewSpeed *= -1;
	}

	//
	// Apply the new speed, adjust sound pitch and volume.
	//
	UpdateSpeed( flNewSpeed );

	//
	// If we've met or exceeded target speed, stop spinning up.
	//
	if ( bSpinUpDone )
	{
		SetMoveDone( &CFuncRotating::RotateMove );
		RotateMove();
	} 

	SetMoveDoneTime( GetNextMoveInterval() );
}


//-----------------------------------------------------------------------------
// Purpose: Decelerates the rotator from a higher speed to a lower one.
// Input  : flTargetSpeed - Speed to spin down to.
// Output : Returns true if we reached the target speed, false otherwise.
//-----------------------------------------------------------------------------
bool CFuncRotating::SpinDown( float flTargetSpeed )
{
	//
	// Bleed off a little speed due to friction.
	//
	bool bSpinDownDone = false;
	float flNewSpeed = fabs( m_flSpeed ) - 0.1 * m_flMaxSpeed * m_flFanFriction;
	if ( flNewSpeed < 0 )
	{
		flNewSpeed = 0;
	}

	if ( fabs( flNewSpeed ) <= fabs( flTargetSpeed ) )
	{
		// Reached our target speed.
		flNewSpeed = flTargetSpeed;
		bSpinDownDone = !m_bStopAtStartPos;
	}
	else if ( m_flSpeed < 0 )
	{
		// Spinning down in reverse - negate the speed.
		flNewSpeed *= -1;
	}

	//
	// Apply the new speed, adjust sound pitch and volume.
	//
	UpdateSpeed( flNewSpeed );

	//
	// If we've met or exceeded target speed, stop spinning down.
	//
	return bSpinDownDone;
}


//-----------------------------------------------------------------------------
// Purpose: Think function. Decelerates a func_rotating to a lower angular velocity.
//-----------------------------------------------------------------------------
void CFuncRotating::SpinDownMove( void )
{
	//
	// If we've met or exceeded target speed, stop spinning down.
	//
	if ( SpinDown( m_flTargetSpeed ) )
	{
		SetMoveDone( &CFuncRotating::RotateMove );
		RotateMove();
	}
	else
	{
		SetMoveDoneTime( GetNextMoveInterval() );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Think function for reversing directions. Spins down to zero, then
//			starts spinning up to the target speed.
//-----------------------------------------------------------------------------
void CFuncRotating::ReverseMove( void )
{
	if ( SpinDown( 0 ) )
	{
		// We've reached zero - spin back up to the target speed.
		SetTargetSpeed( m_flTargetSpeed );
	}
	else
	{
		SetMoveDoneTime( GetNextMoveInterval() );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Think function. Called while rotating at a constant angular velocity.
//-----------------------------------------------------------------------------
void CFuncRotating::RotateMove( void )
{
	SetMoveDoneTime( 10 );

	if ( m_bStopAtStartPos )
	{
		SetMoveDoneTime( GetNextMoveInterval() );
		int checkAxis = 2;

		// See if we got close to the starting orientation
		if ( m_vecMoveAng[0] != 0 )
		{
			checkAxis = 0;
		}
		else if ( m_vecMoveAng[1] != 0 )
		{
			checkAxis = 1;
		}

		float angDelta = anglemod( GetLocalAngles()[ checkAxis ] - m_angStart[ checkAxis ] );
		if ( angDelta > 180.0f )
			angDelta -= 360.0f;

		QAngle avel = GetLocalAngularVelocity();
		// Delta per tick
		QAngle avelpertick = avel * TICK_INTERVAL;

		if ( fabs( angDelta ) < fabs( avelpertick[ checkAxis ] ) )
		{
			SetTargetSpeed( 0 );
			SetLocalAngles( m_angStart );
			m_bStopAtStartPos = false;
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Used for debug output. Returns the given speed considering our current
//			direction of rotation, so that positive values are forward and negative
//			values are backward.
// Input  : flSpeed - Angular speed in degrees per second.
//-----------------------------------------------------------------------------
float CFuncRotating::GetMoveSpeed( float flSpeed )
{
	if ( m_vecMoveAng[0] != 0 )
	{
		return flSpeed * m_vecMoveAng[0];
	}

	if ( m_vecMoveAng[1] != 0 )
	{
		return flSpeed * m_vecMoveAng[1];
	}

	return flSpeed * m_vecMoveAng[2];
}


//-----------------------------------------------------------------------------
// Purpose: Sets a new angular velocity to achieve.
// Input  : flSpeed - Target angular velocity in degrees per second.
//-----------------------------------------------------------------------------
void CFuncRotating::SetTargetSpeed( float flSpeed )
{
	//
	// Make sure the sign is correct - positive for forward rotation,
	// negative for reverse rotation.
	//
	flSpeed = fabs( flSpeed );
	if ( m_bReversed )
	{
		flSpeed *= -1;
	}

	m_flTargetSpeed = flSpeed;

	//
	// If we don't accelerate, change to the new speed instantly.
	//
	if ( !HasSpawnFlags(SF_BRUSH_ACCDCC ) )
	{
		UpdateSpeed( m_flTargetSpeed );
		SetMoveDone( &CFuncRotating::RotateMove );
	}
	//
	// Otherwise deal with acceleration/deceleration:
	//
	else
	{
		//
		// Check for reversing directions.
		//
		if ((( m_flSpeed > 0 ) && ( m_flTargetSpeed < 0 )) || 
			(( m_flSpeed < 0 ) && ( m_flTargetSpeed > 0 )))
		{
			SetMoveDone( &CFuncRotating::ReverseMove );
		}
		//
		// If we are below the new target speed, spin up to the target speed.
		//
		else if ( fabs( m_flSpeed ) < fabs( m_flTargetSpeed ) )
		{
			SetMoveDone( &CFuncRotating::SpinUpMove );
		}
		//
		// If we are above the new target speed, spin down to the target speed.
		//
		else if ( fabs( m_flSpeed ) > fabs( m_flTargetSpeed ) )
		{
			SetMoveDone( &CFuncRotating::SpinDownMove );
		}
		//
		// We are already at the new target speed. Just keep rotating.
		//
		else
		{
			SetMoveDone( &CFuncRotating::RotateMove );
		}
	}

	SetMoveDoneTime( GetNextMoveInterval() );
}


//-----------------------------------------------------------------------------
// Purpose: Called when a rotating brush is used by the player.
// Input  : pActivator - 
//			pCaller - 
//			useType - 
//			value - 
//-----------------------------------------------------------------------------
void CFuncRotating::RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	//
	// If the rotator is spinning, stop it.
	//
	if ( m_flSpeed != 0 )
	{
		SetTargetSpeed( 0 );
	}
	//
	// Rotator is not moving, so start it.
	//
	else
	{
		SetTargetSpeed( m_flMaxSpeed );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler that reverses the direction of rotation.
//-----------------------------------------------------------------------------
void CFuncRotating::InputReverse( inputdata_t &inputdata )
{
	m_bStopAtStartPos = false;
	m_bReversed = !m_bReversed;
	SetTargetSpeed( m_flSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for setting the speed of the rotator.
// Input  : Float target angular velocity as a ratio of maximum speed [0, 1].
//-----------------------------------------------------------------------------
void CFuncRotating::InputSetSpeed( inputdata_t &inputdata )
{
	m_bStopAtStartPos = false;
	float flSpeed = inputdata.value.Float();
	m_bReversed = flSpeed < 0 ? true : false;
	flSpeed = fabs(flSpeed);
	SetTargetSpeed( clamp( flSpeed, 0.f, 1.f ) * m_flMaxSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler to start the rotator spinning.
//-----------------------------------------------------------------------------
void CFuncRotating::InputStart( inputdata_t &inputdata )
{
	m_bStopAtStartPos = false;
	SetTargetSpeed( m_flMaxSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler to start the rotator spinning.
//-----------------------------------------------------------------------------
void CFuncRotating::InputStartForward( inputdata_t &inputdata )
{
	m_bReversed = false;
	SetTargetSpeed( m_flMaxSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler to start the rotator spinning.
//-----------------------------------------------------------------------------
void CFuncRotating::InputStartBackward( inputdata_t &inputdata )
{
	m_bStopAtStartPos = false;
	m_bReversed = true;
	SetTargetSpeed( m_flMaxSpeed );
}


//-----------------------------------------------------------------------------
// Purpose: Input handler to stop the rotator from spinning.
//-----------------------------------------------------------------------------
void CFuncRotating::InputStop( inputdata_t &inputdata )
{
	m_bStopAtStartPos = false;
	SetTargetSpeed( 0 );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &inputdata - 
//-----------------------------------------------------------------------------
void CFuncRotating::InputStopAtStartPos( inputdata_t &inputdata )
{
	m_bStopAtStartPos = true;
	SetTargetSpeed( 0 );
	SetMoveDoneTime( GetNextMoveInterval() );
}

//-----------------------------------------------------------------------------
// Purpose: Starts the rotator if it is still, stops it if it is spinning.
//-----------------------------------------------------------------------------
void CFuncRotating::InputToggle( inputdata_t &inputdata )
{	
	if (m_flSpeed > 0)
	{
		SetTargetSpeed( 0 );
	}
	else
	{
		SetTargetSpeed( m_flMaxSpeed );
	}
}


//-----------------------------------------------------------------------------
// Purpose: An entity has blocked the brush.
// Input  : pOther - 
//-----------------------------------------------------------------------------
void CFuncRotating::Blocked( CBaseEntity *pOther )
{
#ifdef HL1_DLL
	if( m_flBlockDamage > 0 )
#endif
		pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) );
}


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

	if (m_debugOverlays & OVERLAY_TEXT_BIT) 
	{
		char tempstr[512];
		Q_snprintf( tempstr, sizeof( tempstr ),"Speed cur (target): %3.2f (%3.2f)", GetMoveSpeed( m_flSpeed ), GetMoveSpeed( m_flTargetSpeed ) );
		EntityText(text_offset,tempstr,0);
		text_offset++;
	}
	return text_offset;

}


class CFuncVPhysicsClip : public CBaseEntity
{
	DECLARE_DATADESC();
	DECLARE_CLASS( CFuncVPhysicsClip, CBaseEntity );

public:
	void Spawn();
	void Activate();
	bool CreateVPhysics( void );

	bool EntityPassesFilter( CBaseEntity *pOther );
	bool ForceVPhysicsCollide( CBaseEntity *pEntity );

	void InputEnable( inputdata_t &inputdata );
	void InputDisable( inputdata_t &inputdata );

private:

	string_t						m_iFilterName;
	CHandle<CBaseFilter>			m_hFilter;
	bool							m_bDisabled;
};

// Global Savedata for base trigger
BEGIN_DATADESC( CFuncVPhysicsClip )

	// Keyfields
	DEFINE_KEYFIELD( m_iFilterName,	FIELD_STRING,	"filtername" ),
	DEFINE_FIELD( m_hFilter,	FIELD_EHANDLE ),
	DEFINE_FIELD( m_bDisabled,	FIELD_BOOLEAN ),

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

END_DATADESC()


LINK_ENTITY_TO_CLASS( func_clip_vphysics, CFuncVPhysicsClip );

void CFuncVPhysicsClip::Spawn( void )
{
	SetMoveType( MOVETYPE_PUSH );  // so it doesn't get pushed by anything
	SetSolid( SOLID_VPHYSICS );
	AddSolidFlags( FSOLID_NOT_SOLID );
	SetModel( STRING( GetModelName() ) );
	AddEffects( EF_NODRAW );
	CreateVPhysics();
	VPhysicsGetObject()->EnableCollisions( !m_bDisabled );
}


bool CFuncVPhysicsClip::CreateVPhysics( void )
{
	VPhysicsInitStatic();
	return true;
}


void CFuncVPhysicsClip::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 ));
	}
	BaseClass::Activate();
}

bool CFuncVPhysicsClip::EntityPassesFilter( CBaseEntity *pOther )
{
	CBaseFilter* pFilter = (CBaseFilter*)(m_hFilter.Get());

	if ( pFilter )
		return pFilter->PassesFilter( this, pOther );

	if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS && pOther->VPhysicsGetObject()->IsMoveable() )
		return true;
	
	return false;
}


bool CFuncVPhysicsClip::ForceVPhysicsCollide( CBaseEntity *pEntity )
{
	return EntityPassesFilter(pEntity);
}

void CFuncVPhysicsClip::InputEnable( inputdata_t &inputdata )
{
	VPhysicsGetObject()->EnableCollisions(true);
	m_bDisabled = false;
}

void CFuncVPhysicsClip::InputDisable( inputdata_t &inputdata )
{
	VPhysicsGetObject()->EnableCollisions(false);
	m_bDisabled = true;
}
