//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "gamemovement.h"
#include "sdk_gamerules.h"
#include "in_buttons.h"
#include "movevars_shared.h"
#include "coordsize.h"

#include "ClassicGameRules.h"

#ifdef CLIENT_DLL
	#include "c_sdk_player.h"
#else
	#include "CModPlayer.h"
	
	//	Flag include files	//
	#include "props.h"
	#include "igameevents.h"
	#include "GameEventListener.h"	
	#include "CFlagBase.h"
#endif

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern bool g_bMovementOptimizations;

// Modifier for speed when holding down the ALT key
#define LF_ALT_SLOWDOWN 1.5

class CSDKGameMovement : public CGameMovement
{
public:
	DECLARE_CLASS( CSDKGameMovement, CGameMovement );

	CSDKGameMovement();
	virtual ~CSDKGameMovement();

	void SetPlayerSpeed( void );
	virtual void ProcessMovement( CBasePlayer *pPlayer, CMoveData *pMove );
	virtual bool CanAccelerate();
	virtual bool CheckJumpButton( void );
	virtual void ReduceTimers( void );
	virtual void WalkMove( void );
	virtual void CheckParameters( void );
	virtual void CheckFalling( void );

	virtual const Vector&	GetPlayerMins( void ) const; // uses local player
	virtual const Vector&	GetPlayerMaxs( void ) const; // uses local player

	// IGameMovement interface
	virtual const Vector&	GetPlayerMins( bool ducked ) const { return BaseClass::GetPlayerMins(ducked); }
	virtual const Vector&	GetPlayerMaxs( bool ducked ) const { return BaseClass::GetPlayerMaxs(ducked); }
	virtual const Vector&	GetPlayerViewOffset( bool ducked ) const { return BaseClass::GetPlayerViewOffset(ducked); }

	virtual unsigned int PlayerSolidMask( bool brushOnly = false );

public:
	CSDKPlayer *m_pSDKPlayer;
};


// Expose our interface.
static CSDKGameMovement g_GameMovement;
IGameMovement *g_pGameMovement = ( IGameMovement * )&g_GameMovement;

EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CGameMovement, IGameMovement,INTERFACENAME_GAMEMOVEMENT, g_GameMovement );


// ---------------------------------------------------------------------------------------- //
// CSDKGameMovement.
// ---------------------------------------------------------------------------------------- //

CSDKGameMovement::CSDKGameMovement()
{
}

CSDKGameMovement::~CSDKGameMovement()
{
}

void CSDKGameMovement::SetPlayerSpeed( void )
{
	float stamina = m_pSDKPlayer->m_Shared.GetStamina();
	if ( mv->m_nButtons & IN_DUCK )
	{
		// Gets cut into fraction later
		mv->m_flClientMaxSpeed = m_pSDKPlayer->m_Shared.m_flRunSpeed;
	}
	else
	{
		float flMaxSpeed;
		if ( ( m_pSDKPlayer->m_Shared.IsSprinting() ) && ( stamina > 30.0f ) )
		{
			flMaxSpeed = m_pSDKPlayer->m_Shared.m_flSprintSpeed;	//sprinting
		}
		else
		{
			flMaxSpeed = m_pSDKPlayer->m_Shared.m_flRunSpeed;	//jogging
		}

		mv->m_flClientMaxSpeed = flMaxSpeed;
		mv->m_flMaxSpeed = flMaxSpeed;
	}

	if ( mv->m_nButtons & IN_ALT1 )
	{
		mv->m_flClientMaxSpeed /= LF_ALT_SLOWDOWN;
		mv->m_flMaxSpeed /= LF_ALT_SLOWDOWN;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CSDKGameMovement::CheckParameters( void )
{
	SetPlayerSpeed();
	BaseClass::CheckParameters();
}

void CSDKGameMovement::CheckFalling( void )
{
	// if we landed on the ground
	if ( player->GetGroundEntity() != NULL && !IsDead() )
	{
		if ( player->m_Local.m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHOLD )
		{
//Tony; left for playing a sound if you want.
//			CPASFilter filter( player->GetAbsOrigin() );
//			filter.UsePredictionRules();
//			player->EmitSound( filter, player->entindex(), "Player.JumpLanding" );
		}

		if ( m_pSDKPlayer->m_Shared.IsJumping() )
		{
			m_pSDKPlayer->m_Shared.SetJumping( false );
		}
	}

	BaseClass::CheckFalling();
}
void CSDKGameMovement::ProcessMovement( CBasePlayer *pBasePlayer, CMoveData *pMove )
{
	//Store the player pointer
	m_pSDKPlayer = ToSDKPlayer( pBasePlayer );
	Assert( m_pSDKPlayer );

#ifndef CLIENT_DLL
	CModPlayer *pModPlayer = ToModPlayer( pBasePlayer );

	if ( pModPlayer->HasFlag() )
	{
		CFlagBase *pFlag = pModPlayer->GetFlag();
		pFlag->SetAbsOrigin( m_pSDKPlayer->GetAbsOrigin() + Vector( 0.0f, 0.0f, 100.0f ) );
	}
#endif

	BaseClass::ProcessMovement( pBasePlayer, pMove );
}

bool CSDKGameMovement::CanAccelerate()
{
	// Only allow the player to accelerate when in certain states.
	SDKPlayerState curState = m_pSDKPlayer->State_Get();
	if ( curState == STATE_ACTIVE )
	{
		return player->GetWaterJumpTime() == 0;
	}
	else if ( player->IsObserver() )
	{
		return true;
	}
	else
	{	
		return false;
	}
}


void CSDKGameMovement::WalkMove( void )
{
	float flSpeedCheck = m_pSDKPlayer->GetAbsVelocity().Length2D();

	int bSprintButtonPressed = mv->m_nButtons & IN_SPEED;
	bool bMoveButtonPressed = ( mv->m_nButtons & IN_FORWARD ) || ( mv->m_nButtons & IN_MOVELEFT ) ||
		( mv->m_nButtons & IN_MOVERIGHT ) || ( mv->m_nButtons & IN_BACK );

	bool bDucked = m_pSDKPlayer->m_Shared.IsDucking();
	bool bSpeedCheck = abs( flSpeedCheck ) > 80;

	float flStamina = m_pSDKPlayer->m_Shared.GetStamina();

	if( bSprintButtonPressed && bMoveButtonPressed &&
		!bDucked &&	bSpeedCheck && flStamina > 0.0f )
	{
		m_pSDKPlayer->SetSprinting( true );
	}
	else
	{
		m_pSDKPlayer->SetSprinting( false );
	}

	BaseClass::WalkMove();
}

//-----------------------------------------------------------------------------
// Purpose: Recover stamina
//-----------------------------------------------------------------------------
void CSDKGameMovement::ReduceTimers( void )
{
	Vector vecPlayerVelocity = m_pSDKPlayer->GetAbsVelocity();

#if defined ( SDK_USE_STAMINA ) || defined ( SDK_USE_SPRINTING )
	float flStamina = m_pSDKPlayer->m_Shared.GetStamina();
	
	float fl2DVelocitySquared = vecPlayerVelocity.x * vecPlayerVelocity.x + 
								vecPlayerVelocity.y * vecPlayerVelocity.y;	

	if ( !( mv->m_nButtons & IN_SPEED ) )
	{
		m_pSDKPlayer->m_Shared.ResetSprintPenalty();
	}

	if ( mv->m_nButtons & IN_SPEED && flStamina > 0 )
	{
		m_pSDKPlayer->m_Shared.SetSprinting( true );
	}
	else
	{
		m_pSDKPlayer->m_Shared.SetSprinting( false );
	}

	bool bSprinting = m_pSDKPlayer->m_Shared.IsSprinting();

	if ( ( g_pGameRules->GetGameModeMask() & GAMEMODE_CLASSIC ) &&
		( ClassicGameRules()->GetCurrentPhaseID() == PHASE_BUILD ) )
	{ 
		m_pSDKPlayer->m_Shared.SetStamina( 100 );
	}
	else
	{
		// If we're holding the sprint key and also actually moving, remove some stamina
		if ( bSprinting && fl2DVelocitySquared > 10000 ) // speed > 100
		{
			flStamina -= m_pSDKPlayer->m_Shared.m_flStaminaDrainRate * gpGlobals->frametime;
		}
		else if ( flStamina < 10 )
		{
			flStamina += (m_pSDKPlayer->m_Shared.m_flStaminaRestoreRate / 1.15) * gpGlobals->frametime;
		}
		else
		{
			flStamina += m_pSDKPlayer->m_Shared.m_flStaminaRestoreRate * gpGlobals->frametime;
		}

		flStamina = clamp( abs( flStamina ), 0.0f, 100.0f );

		m_pSDKPlayer->m_Shared.SetStamina( flStamina );	
	}

#endif

	BaseClass::ReduceTimers();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CSDKGameMovement::CheckJumpButton( void )
{
	m_pSDKPlayer->m_Shared.SetJumping( true );

	return BaseClass::CheckJumpButton();
}

//-----------------------------------------------------------------------------
// Purpose: Allow bots etc to use slightly different solid masks
//-----------------------------------------------------------------------------
unsigned int CSDKGameMovement::PlayerSolidMask( bool brushOnly )
{
	int mask = 0;
#if defined ( SDK_USE_TEAMS )
	switch ( player->GetTeamNumber() )
	{
	case SDK_TEAM_BLUE:
		mask = CONTENTS_TEAM1;
		break;

	case SDK_TEAM_RED:
		mask = CONTENTS_TEAM2;
		break;
	}
#endif // SDK_USE_TEAMS
	return ( mask | BaseClass::PlayerSolidMask( brushOnly ) );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : 
// Output : const Vector
//-----------------------------------------------------------------------------
const Vector& CSDKGameMovement::GetPlayerMins( void ) const
{
	if ( !player )
	{
		return vec3_origin;
	}

	if ( player->IsObserver() )
	{
		return VEC_OBS_HULL_MIN;	
	}
	else
	{
		if ( player->m_Local.m_bDucked )
			return VEC_DUCK_HULL_MIN;
		else
			return VEC_HULL_MIN;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : 
// Output : const Vector
//-----------------------------------------------------------------------------
const Vector& CSDKGameMovement::GetPlayerMaxs( void ) const
{	
	if ( !player )
	{
		return vec3_origin;
	}
	if ( player->IsObserver() )
	{
		return VEC_OBS_HULL_MAX;	
	}
	else
	{
		if ( player->m_Local.m_bDucked )
			return VEC_DUCK_HULL_MAX;
		else
			return VEC_HULL_MAX;
	}
}

