//========= Copyright © 1996-2003, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
//=============================================================================

#include "cbase.h"
#include "tf_walker_base.h"


#include "in_buttons.h"
#include "shake.h"


static float MAX_WALKER_VEL = 100;


IMPLEMENT_SERVERCLASS_ST( CWalkerBase, DT_WalkerBase )
END_SEND_TABLE()


CWalkerBase::CWalkerBase()
{
	m_vSteerVelocity.Init();
	m_iMovePoseParamX = -1;
	m_iMovePoseParamY = -1;
	m_bWalkMode = false;
	m_flDontMakeSoundsUntil = 0;
	m_flPlaybackSpeedBoost = 1;
	m_flVelocityDecayRate = 80;
	m_LastButtons = 0;
	m_vLastCmdViewAngles.Init();
}


void CWalkerBase::SpawnWalker(
	const char *pModelName,
	int objectType,
	const Vector &vPlacementMins,
	const Vector &vPlacementMaxs,
	int iHealth,
	int nMaxPassengers,
	float flPlaybackSpeedBoost
	)
{
	SetModel( pModelName );
	SetType( objectType );

	UTIL_SetSize( this, vPlacementMins, vPlacementMaxs );
	m_iHealth = iHealth;
	m_flPlaybackSpeedBoost = flPlaybackSpeedBoost;

	m_takedamage = DAMAGE_YES;
	SetMaxPassengerCount( nMaxPassengers );
	
	

	// The model should be set before the derived class calls our Spawn().
	Assert( GetModel() );

	// By default, all walkers use the walk_box animation as they move.
	m_iMovePoseParamX = LookupPoseParameter( "move_x" );
	m_iMovePoseParamY = LookupPoseParameter( "move_y" );
	EnableWalkMode( true );

	// The base class spawn sets a default collision group, so this needs to
	// be called post.
	SetCollisionGroup( COLLISION_GROUP_VEHICLE );
	
	BaseClass::Spawn();


	// HACKHACK: this is just so CBaseObject doesn't call StudioFrameAdvance for us. We should probably have 
	// a specific flag for this behavior.
	m_fObjectFlags |= OF_DOESNT_HAVE_A_MODEL;
	m_fObjectFlags &= ~OF_MUST_BE_BUILT_ON_ATTACHMENT;


	// We animate, so let's not use manual mode for now.
	NetworkStateManualMode( false );
	SetMoveType( MOVETYPE_STEP );
	AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST );
	
	Relink();
	
	EnableServerIK();

	SetContextThink( WalkerThink, gpGlobals->curtime, "WalkerThink" );
}

void CWalkerBase::EnableWalkMode( bool bEnable )
{
	m_bWalkMode = bEnable;

	// Stop any movement..
	m_vSteerVelocity.Init();

	if ( bEnable )
	{
		ResetSequence( LookupSequence( "walk_box" ) );

		// HACK: there should be a better way to this.. like CBaseAnimating::ResetAnimation,
		// or ResetSequence should do it.
		m_flCycle = 0;
	}
}


void CWalkerBase::AdjustInitialBuildAngles()
{
	QAngle vNewAngles = GetAbsAngles();
	vNewAngles[YAW] += 90;
	SetAbsAngles( vNewAngles );
}


void CWalkerBase::WalkerThink()
{
	float dt = GetTimeDelta();
	
	// Decay our velocity.
	if ( m_bWalkMode )
	{
		m_flPlaybackRate = m_flPlaybackSpeedBoost;


		float flDecayRate = m_flVelocityDecayRate;

		float flLen = m_vSteerVelocity.Length();
		Vector2DNormalize( m_vSteerVelocity );

		float flDecayAmt = flDecayRate * dt;
		flLen = max( 0, flLen - flDecayAmt );
		m_vSteerVelocity *= flLen;


		// Setup our pose parameters.
		SetPoseParameter( m_iMovePoseParamX, RemapVal( m_vSteerVelocity.x, -MAX_WALKER_VEL, MAX_WALKER_VEL, -1, 1 ) );
		SetPoseParameter( m_iMovePoseParamY, RemapVal( m_vSteerVelocity.y, -MAX_WALKER_VEL, MAX_WALKER_VEL, -1, 1 ) );

		// Use an idle animation if they're not moving.
		int iWantedSequence = LookupSequence( "walk_box" );	
		if ( m_vSteerVelocity.x == 0 && m_vSteerVelocity.y == 0 )
		{
			iWantedSequence = LookupSequence( "idle" );

			// HACK: HL2 Strider has no idle
			if ( iWantedSequence == -1 )
				iWantedSequence = LookupSequence( "ragdoll" );
		}

		if ( iWantedSequence != -1 && GetSequence() != iWantedSequence )
			ResetSequence( iWantedSequence );
	}


	// Now ask the model how far it thought it moved based on the animation.
	// Turns out the animation thinks it's moving just a tiny bit, even when we're centered on the idle animation,
	// so we just force it not to move here if we know we're not supposed to move.
	if ( m_vSteerVelocity.Length() > 0 ) 
	{
		Vector vNewPos = GetWalkerLocalMovement();
				
		SetLocalOrigin( vNewPos );
		Relink();
	}


	// Hard-coded for now. These should come from the vehicle's script eventually.
	// Now slowly rotate towards the player's eye angles.
	CBasePlayer *pPlayer = GetPassenger( VEHICLE_DRIVER );
	if ( pPlayer )
	{
		static float flAccelRate = 180;
		static float flRotateRate = 60;


		// Figure out a force to apply to our current velocity.
		Vector2D vAccel( 0, 0 );

		if ( m_LastButtons & IN_FORWARD )
			vAccel.x += flAccelRate;

		if ( m_LastButtons & IN_BACK )
			vAccel.x -= flAccelRate;

		if ( m_LastButtons & IN_MOVELEFT )
			vAccel.y -= flAccelRate;

		if ( m_LastButtons & IN_MOVERIGHT )
			vAccel.y += flAccelRate;

		m_vSteerVelocity += vAccel * dt;

		
		m_vSteerVelocity.x = clamp( m_vSteerVelocity.x, -MAX_WALKER_VEL, MAX_WALKER_VEL );
		m_vSteerVelocity.y = clamp( m_vSteerVelocity.y, -MAX_WALKER_VEL, MAX_WALKER_VEL );


		float wantedYaw = m_vLastCmdViewAngles[YAW];
		QAngle curAngles = GetAbsAngles();
		curAngles[YAW] = ApproachAngle( wantedYaw, curAngles[YAW], flRotateRate * dt );
		SetAbsAngles( curAngles );	
	}


	DispatchAnimEvents( this );

	
	// Get another think.
	SetContextThink( WalkerThink, gpGlobals->curtime + dt, "WalkerThink" );
}


Vector CWalkerBase::GetWalkerLocalMovement()
{
	bool bIgnored;
	Vector vNewPos;
	QAngle vNewAngles;
	GetIntervalMovement( m_flAnimTime - m_flPrevAnimTime, bIgnored, vNewPos, vNewAngles );
	return vNewPos;
}


const Vector2D& CWalkerBase::GetSteerVelocity() const
{
	return m_vSteerVelocity;
}



void CWalkerBase::Spawn()
{
	// Derived classes should call SpawnWalker instead of chaining down to CWalkerBase::Spawn().
	Assert( false );
}


void CWalkerBase::Activate()
{
	WalkerActivate();
	BaseClass::Activate();
}

void CWalkerBase::WalkerActivate( void )
{
	// Until we're finished building, turn off vphysics-based motion
	SetSolid( SOLID_VPHYSICS );
	VPhysicsInitStatic();

	SetPoseParameter( m_iMovePoseParamX, 0 );
	SetPoseParameter( m_iMovePoseParamY, 0 );
}


void CWalkerBase::SetVelocityDecayRate( float flDecayRate )
{
	m_flVelocityDecayRate = flDecayRate;
}


float CWalkerBase::GetTimeDelta() const
{
	return 0.1;
}


void CWalkerBase::SetupMove( CBasePlayer *pPlayer, CUserCmd *ucmd, IMoveHelper *pHelper, CMoveData *move )
{
	// This calls StudioFrameAdvance for us.
	//BaseClass::SetupMove( pPlayer, ucmd, pHelper, move );

	// Lose control when the player dies
	if ( pPlayer->IsAlive() == false )
	{
		m_LastButtons = 0;
		return;
	}

	// Only the driver gets to drive.
	int nRole = GetPassengerRole( pPlayer );
	if ( nRole != VEHICLE_DRIVER )
		return;

	m_LastButtons = ucmd->buttons;
	m_vLastCmdViewAngles = ucmd->viewangles;
}


bool CWalkerBase::IsPassengerVisible( int nRole )
{
	return true;
}


bool CWalkerBase::StartBuilding( CBaseEntity *pBuilder )
{
	if ( !BaseClass::StartBuilding( pBuilder ) )
		return false;

	WalkerActivate();
	return true;
}



