text: //========= Copyright Valve Corporation, All rights reserved. ============//
  //
  // Purpose: 
  //
  // $NoKeywords: $
  //=============================================================================//
  #include "cbase.h"
  #include "c_ai_basenpc.h"
  #include "engine/ivdebugoverlay.h"
  
  #if defined( HL2_DLL ) || defined( HL2_EPISODIC )
  #include "c_basehlplayer.h"
  #endif
  
  #include "death_pose.h"
  
  // memdbgon must be the last include file in a .cpp file!!!
  #include "tier0/memdbgon.h"
  
  #define PING_MAX_TIME	2.0
  
  IMPLEMENT_CLIENTCLASS_DT( C_AI_BaseNPC, DT_AI_BaseNPC, CAI_BaseNPC )
  	RecvPropInt( RECVINFO( m_lifeState ) ),
  	RecvPropBool( RECVINFO( m_bPerformAvoidance ) ),
  	RecvPropBool( RECVINFO( m_bIsMoving ) ),
  	RecvPropBool( RECVINFO( m_bFadeCorpse ) ),
  	RecvPropInt( RECVINFO ( m_iDeathPose) ),
  	RecvPropInt( RECVINFO( m_iDeathFrame) ),
  	RecvPropInt( RECVINFO( m_iSpeedModRadius ) ),
  	RecvPropInt( RECVINFO( m_iSpeedModSpeed ) ),
  	RecvPropInt( RECVINFO( m_bSpeedModActive ) ),
  	RecvPropBool( RECVINFO( m_bImportanRagdoll ) ),
  	RecvPropFloat( RECVINFO( m_flTimePingEffect ) ),
  #ifdef HOE_DLL
  	RecvPropEHandle( RECVINFO( m_hDeathSound ) ),
  #endif
  END_RECV_TABLE()
  
  extern ConVar cl_npc_speedmod_intime;
  
  bool NPC_IsImportantNPC( C_BaseAnimating *pAnimating )
  {
  	C_AI_BaseNPC *pBaseNPC = dynamic_cast < C_AI_BaseNPC* > ( pAnimating );
  
  	if ( pBaseNPC == NULL )
  		return false;
  
  	return pBaseNPC->ImportantRagdoll();
  }
  
  C_AI_BaseNPC::C_AI_BaseNPC()
  {
  }
  
  //-----------------------------------------------------------------------------
  // Makes ragdolls ignore npcclip brushes
  //-----------------------------------------------------------------------------
  unsigned int C_AI_BaseNPC::PhysicsSolidMaskForEntity( void ) const 
  {
  	// This allows ragdolls to move through npcclip brushes
  	if ( !IsRagdoll() )
  	{
  		return MASK_NPCSOLID; 
  	}
  	return MASK_SOLID;
  }
  
  
  void C_AI_BaseNPC::ClientThink( void )
  {
  	BaseClass::ClientThink();
  
  #ifdef HL2_DLL
  	C_BaseHLPlayer *pPlayer = dynamic_cast<C_BaseHLPlayer*>( C_BasePlayer::GetLocalPlayer() );
  
  	if ( ShouldModifyPlayerSpeed() == true )
  	{
  		if ( pPlayer )
  		{
  			float flDist = (GetAbsOrigin() - pPlayer->GetAbsOrigin()).LengthSqr();
  
  			if ( flDist <= GetSpeedModifyRadius() )
  			{
  				if ( pPlayer->m_hClosestNPC )
  				{
  					if ( pPlayer->m_hClosestNPC != this )
  					{
  						float flDistOther = (pPlayer->m_hClosestNPC->GetAbsOrigin() - pPlayer->GetAbsOrigin()).Length();
  
  						//If I'm closer than the other NPC then replace it with myself.
  						if ( flDist < flDistOther )
  						{
  							pPlayer->m_hClosestNPC = this;
  							pPlayer->m_flSpeedModTime = gpGlobals->curtime + cl_npc_speedmod_intime.GetFloat();
  						}
  					}
  				}
  				else
  				{
  					pPlayer->m_hClosestNPC = this;
  					pPlayer->m_flSpeedModTime = gpGlobals->curtime + cl_npc_speedmod_intime.GetFloat();
  				}
  			}
  		}
  	}
  #endif // HL2_DLL
  
  #ifdef HL2_EPISODIC
  	C_BaseHLPlayer *pPlayer = dynamic_cast<C_BaseHLPlayer*>( C_BasePlayer::GetLocalPlayer() );
  
  	if ( pPlayer && m_flTimePingEffect > gpGlobals->curtime )
  	{
  		float fPingEffectTime = m_flTimePingEffect - gpGlobals->curtime;
  		
  		if ( fPingEffectTime > 0.0f )
  		{
  			Vector vRight, vUp;
  			Vector vMins, vMaxs;
  
  			float fFade;
  
  			if( fPingEffectTime <= 1.0f )
  			{
  				fFade = 1.0f - (1.0f - fPingEffectTime);
  			}
  			else
  			{
  				fFade = 1.0f;
  			}
  
  			GetRenderBounds( vMins, vMaxs );
  			AngleVectors (pPlayer->GetAbsAngles(), NULL, &vRight, &vUp );
  			Vector p1 = GetAbsOrigin() + vRight * vMins.x + vUp * vMins.z;
  			Vector p2 = GetAbsOrigin() + vRight * vMaxs.x + vUp * vMins.z;
  			Vector p3 = GetAbsOrigin() + vUp * vMaxs.z;
  
  			int r = 0 * fFade;
  			int g = 255 * fFade;
  			int b = 0 * fFade;
  
  			debugoverlay->AddLineOverlay( p1, p2, r, g, b, true, 0.05f );
  			debugoverlay->AddLineOverlay( p2, p3, r, g, b, true, 0.05f );
  			debugoverlay->AddLineOverlay( p3, p1, r, g, b, true, 0.05f );
  		}
  	}
  #endif
  }
  
  void C_AI_BaseNPC::OnDataChanged( DataUpdateType_t type )
  {
  	BaseClass::OnDataChanged( type );
  
  	if ( ( ShouldModifyPlayerSpeed() == true ) || ( m_flTimePingEffect > gpGlobals->curtime ) )
  	{
  		SetNextClientThink( CLIENT_THINK_ALWAYS );
  	}
  }
  
  void C_AI_BaseNPC::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt )
  {
  	ForceSetupBonesAtTime( pDeltaBones0, gpGlobals->curtime - boneDt );
  	GetRagdollCurSequenceWithDeathPose( this, pDeltaBones1, gpGlobals->curtime, m_iDeathPose, m_iDeathFrame );
  	float ragdollCreateTime = PhysGetSyncCreateTime();
  	if ( ragdollCreateTime != gpGlobals->curtime )
  	{
  		// The next simulation frame begins before the end of this frame
  		// so initialize the ragdoll at that time so that it will reach the current
  		// position at curtime.  Otherwise the ragdoll will simulate forward from curtime
  		// and pop into the future a bit at this point of transition
  		ForceSetupBonesAtTime( pCurrentBones, ragdollCreateTime );
  	}
  	else
  	{
  		SetupBones( pCurrentBones, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, gpGlobals->curtime );
  	}
  }
  
  #ifdef HOE_DLL
  class C_NPCDeathSound : public C_BaseEntity
  {
  public:
  	DECLARE_CLASS( C_NPCDeathSound, C_BaseEntity );
  	DECLARE_CLIENTCLASS();
  	DECLARE_DATADESC();
  
  	C_NPCDeathSound()
  	{
  		return;
  	};
  
  	void Activate( void )
  	{
  		BaseClass::Activate();
  
  		float flDuration = 2.0f;
  		if ( m_szSoundName[0] == '!' )
  		{
  			int sentenceIndex = engine->SentenceIndexFromName( m_szSoundName + 1 );
  			if ( sentenceIndex >= 0 )
  			{
  				char name[32];
  				Q_snprintf( name, sizeof(name), "!%d", sentenceIndex );
  
  				EmitSound( name, 0.0f, &flDuration );
  			}
  		}
  		else if ( m_szSoundName[0] )
  		{
  			EmitSound( m_szSoundName, 0.0f, &flDuration );
  		}
  	}
  
  	bool GetSoundSpatialization( SpatializationInfo_t &info )
  	{
  		// The reason we don't just SetParent() on the ragdoll is because
  		// this is a client-server entity while the ragdoll is clientside.
  		if ( m_hSoundSource )
  		{
  			return m_hSoundSource->GetSoundSpatialization( info );
  		}
  		return false;
  	}
  
  	EHANDLE m_hSoundSource; // entity to emit the sound from (the ragdoll)
  	char m_szSoundName[128];
  };
  
  BEGIN_DATADESC( C_NPCDeathSound )
  	DEFINE_AUTO_ARRAY( m_szSoundName, FIELD_CHARACTER ),
  	DEFINE_FIELD( m_hSoundSource, FIELD_EHANDLE ),
  END_DATADESC()
  
  IMPLEMENT_CLIENTCLASS_DT( C_NPCDeathSound, DT_NPCDeathSound, CNPCDeathSound )
  	RecvPropString( RECVINFO( m_szSoundName ) ),
  END_RECV_TABLE()
  
  LINK_ENTITY_TO_CLASS( npc_death_sound, C_NPCDeathSound );
  
  C_BaseAnimating *C_AI_BaseNPC::BecomeRagdollOnClient()
  {
  	C_BaseAnimating *pRagdoll = BaseClass::BecomeRagdollOnClient();
  	if ( pRagdoll && pRagdoll->m_pRagdoll )
  	{
  #if 1
  		C_BaseEntity *pEnt = m_hDeathSound.Get();
  		if ( pEnt != NULL  )
  		{
  			/*
  			Nope.  You can't parent a client-server entity only on the client.
  			pEnt->SetParent( pRagdoll );
  			*/
  			C_NPCDeathSound *pSnd = dynamic_cast<C_NPCDeathSound *>( pEnt );
  			if ( pSnd )
  			{
  				pSnd->m_hSoundSource = pRagdoll;
  				pSnd->Activate();
  			}
  		}
  #elif 0
  		/*pRagdoll->*/EmitSound( "NPC_Mikeforce.Die" );
  #else
  		C_NPCDeathSound *pDeathSoundEmitter = CREATE_ENTITY( C_NPCDeathSound, "npc_death_sound" );
  		if ( pDeathSoundEmitter )
  		{
  			Q_strcpy( pDeathSoundEmitter->m_szSoundName, m_szDeathSound );
  			pDeathSoundEmitter->Spawn();
  			pDeathSoundEmitter->SetAbsOrigin( pRagdoll->GetAbsOrigin() );
  //			pDeathSoundEmitter->SetParent( pRagdoll );
  			pDeathSoundEmitter->Activate();
  		}
  #endif
  		extern void HOENPCBecameRagdoll( int entindex, void *ragdoll );
  		HOENPCBecameRagdoll( entindex(), pRagdoll );
  	}
  	return pRagdoll;
  }
  
  #endif // HOE_DLL
  