//----------------------------------------------------------------
// ClientMoveable.cpp
//
// Copyright 2002-2004 Raven Software
//----------------------------------------------------------------




#include "../Game_local.h"

/*
===============================================================================

rvClientMoveable

===============================================================================
*/
const idEventDef CL_FadeOut( "<fadeOut>", "d" );
const idEventDef CL_ClearDepthHack ( "<clearDepthHack>" );

static const float BOUNCE_SOUND_MIN_VELOCITY	= 100.0f;
static const float BOUNCE_SOUND_MAX_VELOCITY	= 200.0f;
static const int   BOUNCE_SOUND_DELAY			= 200;

CLASS_DECLARATION( rvClientEntity, rvClientMoveable )
	EVENT( CL_FadeOut,			rvClientMoveable::Event_FadeOut )
	EVENT( CL_ClearDepthHack,	rvClientMoveable::Event_ClearDepthHack )
END_CLASS


/*
================
rvClientMoveable::rvClientMoveable
================
*/
rvClientMoveable::rvClientMoveable ( void ) {
	memset ( &renderEntity, 0, sizeof(renderEntity) );
	entityDefHandle = -1;
	scale.Init( 0, 0, 1.0f, 1.0f );
}

/*
================
rvClientMoveable::~rvClientMoveable
================
*/
rvClientMoveable::~rvClientMoveable ( void ) {
	FreeEntityDef ( );

	// Remove any trail effect if there is one
	if ( trailEffect ) {
		trailEffect->Stop ( );
	}
}

/*
================
rvClientMoveable::FreeEntityDef
================
*/
void rvClientMoveable::FreeEntityDef ( void ) {
	if ( entityDefHandle >= 0 ) {
		gameRenderWorld->FreeEntityDef ( entityDefHandle );
		entityDefHandle = -1;
	}	
}

idVec3 simpleTri[3] =
{
	idVec3( -1.0, -1.0, 0.0 ),
	idVec3( 0.0, 2.0, 0.0 ),
	idVec3( 2.0, 0.0, 0.0 )
};

/*
================
rvClientMoveable::Spawn
================
*/
void rvClientMoveable::Spawn ( void ) {
	// parse static models the same way the editor display does
	gameEdit->ParseSpawnArgsToRenderEntity( &spawnArgs, &renderEntity );

	idTraceModel	trm;
	int				clipShrink;
	idStr			clipModelName;

	// check if a clip model is set
	spawnArgs.GetString( "clipmodel", "", clipModelName );
	if ( !clipModelName.Length ()  ) {
		clipModelName = spawnArgs.GetString( "model" );		// use the visual model
	}

	if ( clipModelName == SIMPLE_TRI_NAME ) {
		trm.SetupPolygon( simpleTri, 3 );
	} else {
		clipModelName.BackSlashesToSlashes();

		if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) {
			gameLocal.Error( "rvClientMoveable '%d': cannot load collision model %s", entityNumber, clipModelName.c_str() );
			return;
		}
	}

	// if the model should be shrunk
	clipShrink = spawnArgs.GetInt( "clipshrink" );
	if ( clipShrink != 0 ) {
		trm.Shrink( clipShrink * CM_CLIP_EPSILON );
	}

	physicsObj.SetSelf ( gameLocal.entities[ENTITYNUM_CLIENT] );		
	physicsObj.SetClipModel ( new idClipModel( trm ), spawnArgs.GetFloat ( "density", "0.5" ), entityNumber );

	physicsObj.SetOrigin( GetOrigin() );
	physicsObj.SetAxis( GetAxis() );
	physicsObj.SetBouncyness( spawnArgs.GetFloat( "bouncyness", "0.6" ) );
	physicsObj.SetFriction( spawnArgs.GetFloat("linear_friction", "0.6"), spawnArgs.GetFloat( "angular_friction", "0.6"), spawnArgs.GetFloat("friction", "0.05") );
	physicsObj.SetGravity( gameLocal.GetCurrentGravity(this) );
	physicsObj.SetContents( 0 );
	// abahr: changed to MASK_SHOT_RENDERMODEL because brass was getting pinched between the player and the wall in some cases
	//			may want to try something cheaper.
	physicsObj.SetClipMask( CONTENTS_OPAQUE ); // MASK_SHOT_RENDERMODEL | CONTENTS_CORPSE | CONTENTS_MOVEABLECLIP | CONTENTS_WATER );
	physicsObj.Activate ( );

	trailEffect = gameLocal.PlayEffect ( spawnArgs, "fx_trail", physicsObj.GetCenterMass(), GetAxis(), true );	
	trailAttenuateSpeed = spawnArgs.GetFloat ( "trailAttenuateSpeed", "200" );
	
	bounceSoundShader = declManager->FindSound ( spawnArgs.GetString ( "snd_bounce" ), false );
	bounceSoundTime   = 0;
	mPlayBounceSoundOnce = spawnArgs.GetBool("bounce_sound_once");
	mHasBounced = false;
	
	scale.Init( gameLocal.GetTime(), SEC2MS(spawnArgs.GetFloat("scale_reset_duration", "0.2")), Max(VECTOR_EPSILON, spawnArgs.GetFloat("scale", "1.0f")), 1.0f );
}

/*
================
rvClientMoveable::SetOrigin
================
*/
void rvClientMoveable::SetOrigin( const idVec3& origin ) {
	rvClientEntity::SetOrigin( origin );
	physicsObj.SetOrigin( origin );
}

/*
================
rvClientMoveable::SetAxis
================
*/
void rvClientMoveable::SetAxis( const idMat3& axis ) {
	rvClientEntity::SetAxis( axis );
	physicsObj.SetAxis( axis );
}

/*
================
rvClientMoveable::SetOwner
================
*/
void rvClientMoveable::SetOwner( idEntity* owner ) {
	physicsObj.GetClipModel()->SetOwner( owner );
	physicsObj.GetClipModel()->SetEntity( owner );
}

/*
================
rvClientMoveable::Think
================
*/
void rvClientMoveable::Think ( void ) {
	if( bindMaster && (bindMaster->GetRenderEntity()->hModel && bindMaster->GetModelDefHandle() == -1) ) {
		return;
	}

	RunPhysics ( );	

	// Special case the sound update to use the center mass since the origin may be in an odd place
	idSoundEmitter *emitter = soundSystem->EmitterForIndex( SOUNDWORLD_GAME, refSound.referenceSoundHandle );
	if ( emitter ) {
		refSound.origin = worldOrigin;
		refSound.velocity = worldVelocity;
		emitter->UpdateEmitter( refSound.origin, refSound.velocity, refSound.listenerId, &refSound.parms );
	}

	// Keep the trail effect following
	if ( trailEffect ) {
		float speed;
		speed = idMath::ClampFloat ( 0, trailAttenuateSpeed, worldVelocity.LengthFast ( ) );
		if ( physicsObj.IsAtRest ( ) ) {
			trailEffect->Stop ( );
			trailEffect = NULL;
		} else {
			trailEffect->SetOrigin ( worldOrigin );
			trailEffect->SetAxis ( worldAxis );
			trailEffect->Attenuate ( speed / trailAttenuateSpeed );
		}
	}

	renderEntity.origin = worldOrigin;
	renderEntity.axis = worldAxis * scale.GetCurrentValue( gameLocal.GetTime() );

	// add to refresh list
	if ( entityDefHandle == -1 ) {
		entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
	} else {
		gameRenderWorld->UpdateEntityDef( entityDefHandle, &renderEntity );
	}		
}

/*
================
rvClientMoveable::GetPhysics
================
*/
idPhysics* rvClientMoveable::GetPhysics ( void ) const {
	return (idPhysics*)&physicsObj;
}

/*
================
rvClientMoveable::Collide
================
*/
bool rvClientMoveable::Collide ( const trace_t &collision, const idVec3 &velocity ) {	
	if (mPlayBounceSoundOnce && mHasBounced)
	{
		return false;
	}
	if ( bounceSoundShader && gameLocal.time > bounceSoundTime ) {
		float speed;
		speed = velocity.LengthFast ( );
		if ( speed > BOUNCE_SOUND_MIN_VELOCITY ) {
			StartSoundShader ( bounceSoundShader, SND_CHANNEL_BODY, 0 );
			bounceSoundTime = BOUNCE_SOUND_DELAY;
			mHasBounced = true;
		}
	}	
		
	return false;
}

/*
================
rvClientMoveable::Save
================
*/
void rvClientMoveable::Save( idSaveGame *savefile ) const {
	savefile->WriteRenderEntity( renderEntity );
	savefile->WriteInt( entityDefHandle );

	trailEffect.Save( savefile );
	savefile->WriteFloat( trailAttenuateSpeed );
		
	savefile->WriteStaticObject( physicsObj );
	
	savefile->WriteInt( bounceSoundTime );
	savefile->WriteSoundShader( bounceSoundShader );

	savefile->WriteBool(mPlayBounceSoundOnce);
	savefile->WriteBool(mHasBounced);

	// TOSAVE: idInterpolate<float>	scale;
}

/*
================
rvClientMoveable::Restore
================
*/
void rvClientMoveable::Restore( idRestoreGame *savefile ) {
	savefile->ReadRenderEntity( renderEntity, NULL );
	savefile->ReadInt( entityDefHandle );

	trailEffect.Restore( savefile );
	savefile->ReadFloat( trailAttenuateSpeed );
		
	savefile->ReadStaticObject( physicsObj );
	
	savefile->ReadInt( bounceSoundTime );
	savefile->ReadSoundShader( bounceSoundShader );

	savefile->ReadBool(mPlayBounceSoundOnce);
	savefile->ReadBool(mHasBounced);

	// restore must retrieve entityDefHandle from the renderer
 	if ( entityDefHandle != -1 ) {
 		entityDefHandle = gameRenderWorld->AddEntityDef( &renderEntity );
 	}

	// TORESTORE: idInterpolate<float>	scale;
}

/*
================
rvClientMoveable::Event_FadeOut
================
*/
void rvClientMoveable::Event_FadeOut ( int duration ) {
	renderEntity.noShadow = !G_ShadowMapCorpseShadowsEnabled();
	renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f;
	PostEventMS ( &EV_Remove, duration );	
}

/*
================
rvClientMoveable::Event_ClearDepthHack
================
*/
void rvClientMoveable::Event_ClearDepthHack ( void ) {
	renderEntity.weaponDepthHackInViewID = 0;
}

/*
================
rvClientMoveable::SpawnClientMoveables
================
*/
void rvClientMoveable::SpawnClientMoveables( idEntity* ent, const char *type, idList<rvClientMoveable *>* list ) {
	const idKeyValue *kv;
	idVec3 origin;
	idMat3 axis;
	
	if( list == NULL || type == NULL ) {
		return;
	}

	// drop all items
	kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), NULL );
	while ( kv ) {
		origin = ent->GetPhysics()->GetOrigin();
		axis = ent->GetPhysics()->GetAxis();

// RAVEN BEGIN
// jnewquist: Use accessor for static class type 
		if( ent->IsType( idAnimatedEntity::GetClassType() ) ) {
// RAVEN END
			idAnimatedEntity* animEnt = static_cast<idAnimatedEntity*>(ent);
			jointHandle_t clientMoveableJoint;
	
			const char* clientMoveableJointName = ent->spawnArgs.GetString( va( "%s_joint", kv->GetKey().c_str() + 4 ) );
		
			// use a joint if specified
			if ( idStr::Icmp( clientMoveableJointName, "") ) {
				clientMoveableJoint = animEnt->GetAnimator()->GetJointHandle( clientMoveableJointName );

				if ( !animEnt->GetJointWorldTransform( clientMoveableJoint, gameLocal.time, origin, axis ) ) {
					gameLocal.Warning( "%s refers to invalid joint '%s' on entity '%s'\n", va( "%s_joint", kv->GetKey().c_str() + 4 ), clientMoveableJointName, ent->name.c_str() );
					origin = ent->GetPhysics()->GetOrigin();
					axis = ent->GetPhysics()->GetAxis();
				}
			} 
		} 

		// spawn the entity
		const idDict* entityDef = gameLocal.FindEntityDefDict ( kv->GetValue().c_str(), false );
	
		if ( entityDef == NULL ) {
			gameLocal.Warning( "%s refers to invalid entity def '%s' on entity '%s'\n", kv->GetKey().c_str(), kv->GetValue().c_str(), ent->name.c_str() );
			break;
		}
	
		rvClientMoveable* newModel = NULL;
		// force spawnclass to rvClientMoveable
		gameLocal.SpawnClientEntityDef( *entityDef, (rvClientEntity**)(&newModel), false, "rvClientMoveable" );

		if( !newModel ) {
			gameLocal.Warning( "error spawning client moveable (invalid entity def '%s' on entity '%s')\n", kv->GetValue().c_str(), ent->name.c_str() );
			break;
		}
		newModel->SetOrigin ( origin );
		newModel->SetAxis( axis );

		list->Append( newModel );
		kv = ent->spawnArgs.MatchPrefix( va( "def_%s", type ), kv );
	}
}

