//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "cbase.h"
#include <stdarg.h>
#include "gamerules.h"
#include "player.h"
#include "model_types.h"
#include "movehelper_server.h"
#include "shake.h"				// For screen fade constants
#include "engine/IEngineSound.h"

extern IPhysicsCollision *physcollision;


//-----------------------------------------------------------------------------
// Implementation of the movehelper on the server
//-----------------------------------------------------------------------------

class CMoveHelperServer : public IMoveHelperServer
{
public:
	CMoveHelperServer( void );
	virtual ~CMoveHelperServer();

	// Methods associated with a particular entity
	virtual	char const*		GetName( EntityHandle_t handle ) const;

	// Touch list...
	virtual void	ResetTouchList( void );
	virtual bool	AddToTouched( const trace_t &tr, const Vector& impactvelocity );
 	virtual void	ProcessImpacts( void );

	virtual bool	PlayerFallingDamage( void );
	virtual void	PlayerSetAnimation( PLAYER_ANIM eAnim );

	// Numbered line printf
	virtual void	Con_NPrintf( int idx, char const* fmt, ... );
	
	// These have separate server vs client impementations
	virtual void	StartSound( const Vector& origin, int channel, char const* sample, float volume, soundlevel_t soundlevel, int fFlags, int pitch );
	virtual void	StartSound( const Vector& origin, const char *soundname ); 

	virtual void	PlaybackEventFull( int flags, int clientindex, unsigned short eventindex, float delay, Vector& origin, Vector& angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 );
	virtual IPhysicsSurfaceProps *GetSurfaceProps( void );

	void			SetHost( CBasePlayer *host );

	virtual bool IsWorldEntity( const CBaseHandle &handle );

private:
	CBasePlayer*	m_pHostPlayer;

	// results, tallied on client and server, but only used by server to run SV_Impact.
	// we store off our velocity in the trace_t structure so that we can determine results
	// of shoving boxes etc. around.
	struct touchlist_t
	{
		Vector	deltavelocity;
		trace_t trace;
	};

	CUtlVector<touchlist_t>	m_TouchList;
};


//-----------------------------------------------------------------------------
// Singleton
//-----------------------------------------------------------------------------

IMPLEMENT_MOVEHELPER();

IMoveHelperServer* MoveHelperServer()
{
	static CMoveHelperServer s_MoveHelperServer;
	return &s_MoveHelperServer;
}


//-----------------------------------------------------------------------------
// Converts the entity handle into a edict_t
//-----------------------------------------------------------------------------

static inline edict_t* GetEdict( EntityHandle_t handle )
{
	return gEntList.GetEdict( handle );
}


//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------

CMoveHelperServer::CMoveHelperServer( void ) : m_TouchList( 0, 128 )
{
	m_pHostPlayer = 0;
	SetSingleton( this );
}

CMoveHelperServer::~CMoveHelperServer( void )
{
	SetSingleton( 0 );
}

//-----------------------------------------------------------------------------
// Indicates which player we're going to move
//-----------------------------------------------------------------------------

void CMoveHelperServer::SetHost( CBasePlayer *host )
{
	m_pHostPlayer = host;

	// In case any stuff is ever left over, sigh...
	ResetTouchList();
}


//-----------------------------------------------------------------------------
// Returns the name for debugging purposes
//-----------------------------------------------------------------------------

char const* CMoveHelperServer::GetName( EntityHandle_t handle ) const
{
	// This ain't pertickulerly fast, but it's for debugging anyways
	edict_t* pEdict = GetEdict(handle);
	CBaseEntity *ent = CBaseEntity::Instance( pEdict );
	
	// Is it the world?
	if (ENTINDEX(pEdict) == 0)
		return STRING(gpGlobals->mapname);

	// Is it a model?
	if ( ent && ent->GetModelName() != NULL_STRING )
		return STRING( ent->GetModelName() );

	if ( pEdict->classname != NULL_STRING )
	{
		return STRING( pEdict->classname );
	}

	return "?";
}	

//-----------------------------------------------------------------------------
// When we do a collision test, we report everything we hit.. 
//-----------------------------------------------------------------------------

void CMoveHelperServer::ResetTouchList( void )
{
	m_TouchList.RemoveAll();
}


//-----------------------------------------------------------------------------
// When a collision occurs, we add it to the touched list
//-----------------------------------------------------------------------------

bool CMoveHelperServer::AddToTouched( const trace_t &tr, const Vector& impactvelocity )
{
	Assert( m_pHostPlayer );

	// Trace missed
	if ( !tr.m_pEnt )
		return false;

	if ( tr.m_pEnt == m_pHostPlayer )
	{
		Assert( !"CMoveHelperServer::AddToTouched:  Tried to add self to touchlist!!!" );
		return false;
	}

	// Check for duplicate entities
	for ( int j = m_TouchList.Size(); --j >= 0; )
	{
		if ( m_TouchList[j].trace.m_pEnt == tr.m_pEnt )
		{
			return false;
		}
	}
	
	int i = m_TouchList.AddToTail();
	m_TouchList[i].trace = tr;
	VectorCopy( impactvelocity, m_TouchList[i].deltavelocity );

	return true;
}


//-----------------------------------------------------------------------------
// After we built the touch list, deal with all the impacts...
//-----------------------------------------------------------------------------
void CMoveHelperServer::ProcessImpacts( void )
{
	Assert( m_pHostPlayer );

	// Relink in order to build absorigin and absmin/max to reflect any changes
	//  from prediction.  Relink will early out on SOLID_NOT
	engine->RelinkEntity( m_pHostPlayer->pev, true );

	// Don't bother if the player ain't solid
	if ( m_pHostPlayer->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
	{
		return;
	}

	// Save off the velocity, cause we need to temporarily reset it
	Vector vel = m_pHostPlayer->GetAbsVelocity();

	// Touch other objects that were intersected during the movement.
	for (int i = 0 ; i < m_TouchList.Size(); i++)
	{
		CBaseHandle entindex = m_TouchList[i].trace.m_pEnt->GetRefEHandle();

		// We should have culled negative indices by now
		Assert( entindex.IsValid() );

		edict_t* ent = GetEdict( entindex );
		if (!ent)
			continue;

		// Run the impact function as if we had run it during movement.
		CBaseEntity *entity = GetContainingEntity( ent );
		if ( !entity )
			continue;

		Assert( entity != m_pHostPlayer );
		// Don't ever collide with self!!!!
		if ( entity == m_pHostPlayer )
			continue;

		// Reconstruct trace results.
		m_TouchList[i].trace.m_pEnt = CBaseEntity::Instance( ent );

		// Use the velocity we had when we collided, so boxes will move, etc.
		m_pHostPlayer->SetAbsVelocity( m_TouchList[i].deltavelocity );
		
		entity->PhysicsImpact( m_pHostPlayer, m_TouchList[i].trace );
	}

	// Restore the velocity
	m_pHostPlayer->SetAbsVelocity( vel );

	// So no stuff is ever left over, sigh...
	ResetTouchList();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : origin - 
//			*soundname - 
//-----------------------------------------------------------------------------
void CMoveHelperServer::StartSound( const Vector& origin, const char *soundname )
{
	CSingleUserRecipientFilter filter( m_pHostPlayer );
	// FIXME, these sounds should not go to the host entity ( SND_NOTHOST )
	if ( gpGlobals->maxClients == 1 )
	{
		// Always send sounds down in SP
		CBaseEntity::EmitSound( filter, m_pHostPlayer->entindex(), soundname, &origin );
	}
	else
	{
		filter.UsePredictionRules();
		CBaseEntity::EmitSound( filter, m_pHostPlayer->entindex(), 
			soundname, &origin );
	}
}

//-----------------------------------------------------------------------------
// plays a sound
//-----------------------------------------------------------------------------
void CMoveHelperServer::StartSound( const Vector& origin, int channel, char const* sample, 
						float volume, soundlevel_t soundlevel, int fFlags, int pitch )
{

	CSingleUserRecipientFilter filter( m_pHostPlayer );
	// FIXME, these sounds should not go to the host entity ( SND_NOTHOST )
	if ( gpGlobals->maxClients == 1 )
	{
		// Always send sounds down in SP
		CBaseEntity::EmitSound( filter, m_pHostPlayer->entindex(), channel, sample, 
			volume, soundlevel, fFlags, pitch, &origin );
	}
	else
	{
		filter.UsePredictionRules();

		CBaseEntity::EmitSound( filter, m_pHostPlayer->entindex(), channel, sample, volume, 
			soundlevel, fFlags, pitch, &origin );
	}
}


//-----------------------------------------------------------------------------
// Umm...
//-----------------------------------------------------------------------------
void CMoveHelperServer::PlaybackEventFull( int flags, int clientindex, unsigned short eventindex, float delay, Vector& origin, Vector& angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 )
{
	// FIXME, Redo with new event system parameter stuff
}

IPhysicsSurfaceProps *CMoveHelperServer::GetSurfaceProps( void )
{
	extern IPhysicsSurfaceProps *physprops;
	return physprops;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bDeveloper - 
//			*pFormat - 
//			... - 
//-----------------------------------------------------------------------------
void CMoveHelperServer::Con_NPrintf( int idx, char const* pFormat, ...)
{
	// Doesn't do anything on server
}

//-----------------------------------------------------------------------------
// Purpose: Called when the player falls onto a surface fast enough to take
//			damage, according to the rules in CGameMovement::CheckFalling.
// Output : Returns true if the player survived the fall, false if they died.
//-----------------------------------------------------------------------------
bool CMoveHelperServer::PlayerFallingDamage( void )
{
	float flFallDamage = g_pGameRules->FlPlayerFallDamage( m_pHostPlayer );
	if ( flFallDamage > 0 )
	{
		StartSound( m_pHostPlayer->GetAbsOrigin(), "Player.FallDamage" );
		m_pHostPlayer->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), flFallDamage, DMG_FALL ) ); 
	}

	if ( m_pHostPlayer->m_iHealth <= 0 )
	{
		if ( g_pGameRules->FlPlayerFallDeathDoesScreenFade( m_pHostPlayer ) )
		{
			color32 black = {0, 0, 0, 255};
			UTIL_ScreenFade( m_pHostPlayer, black, 0, 9999, FFADE_OUT | FFADE_STAYOUT );
		}
		return(false);
	}

	return(true);
}


//-----------------------------------------------------------------------------
// Purpose: Sets an animation in the player.
// Input  : eAnim - Animation to set.
//-----------------------------------------------------------------------------
void CMoveHelperServer::PlayerSetAnimation( PLAYER_ANIM eAnim )
{
	m_pHostPlayer->SetAnimation( eAnim );
}

bool CMoveHelperServer::IsWorldEntity( const CBaseHandle &handle )
{
	return handle == CBaseEntity::Instance( 0 );
}
