//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include "hl1_npc_talker.h"
#include "scripted.h"
#include "soundent.h"
#include "animation.h"
#include "entitylist.h"
#include "ai_navigator.h"
#include "ai_motor.h"
#include "player.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "npcevent.h"
#include "ai_interactions.h"
#include "doors.h"

#include "effect_dispatch_data.h"
#include "te_effect_dispatch.h"
#include "hl1_ai_basenpc.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"

ConVar hl1_debug_sentence_volume( "hl1_debug_sentence_volume", "0" );
ConVar hl1_fixup_sentence_sndlevel( "hl1_fixup_sentence_sndlevel", "1" );

//#define TALKER_LOOK 0

BEGIN_DATADESC( CHL1NPCTalker )

	DEFINE_ENTITYFUNC( Touch ),
	DEFINE_FIELD( m_bInBarnacleMouth,	FIELD_BOOLEAN ),
	DEFINE_USEFUNC( FollowerUse ),

END_DATADESC()

void CHL1NPCTalker::RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
		case TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS:
			{
				float distance;

				distance = (m_vecLastPosition - GetLocalOrigin()).Length2D();

				// Walk path until far enough away
				if ( distance > pTask->flTaskData || 
					 GetNavigator()->GetGoalType() == GOALTYPE_NONE )
				{
					TaskComplete();
					GetNavigator()->ClearGoal();		// Stop moving
				}
				break;
			}



		case TASK_TALKER_CLIENT_STARE:
		case TASK_TALKER_LOOK_AT_CLIENT:
		{
			CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
			
			// track head to the client for a while.
			if ( m_NPCState == NPC_STATE_IDLE		&& 
				 !IsMoving()								&&
				 !GetExpresser()->IsSpeaking() )
			{
			
				if ( pPlayer )
				{
					IdleHeadTurn( pPlayer );
				}
			}
			else
			{
				// started moving or talking
				TaskFail( "moved away" );
				return;
			}

			if ( pTask->iTask == TASK_TALKER_CLIENT_STARE )
			{
				// fail out if the player looks away or moves away.
				if ( ( pPlayer->GetAbsOrigin() - GetAbsOrigin() ).Length2D() > TALKER_STARE_DIST )
				{
					// player moved away.
					TaskFail( NO_TASK_FAILURE );
				}

				Vector vForward;
				AngleVectors( GetAbsAngles(), &vForward );
				if ( UTIL_DotPoints( pPlayer->GetAbsOrigin(), GetAbsOrigin(), vForward ) < m_flFieldOfView )
				{
					// player looked away
					TaskFail( "looked away" );
				}
			}

			if ( gpGlobals->curtime > m_flWaitFinished )
			{
				TaskComplete( NO_TASK_FAILURE );
			}

			break;
		}

		case TASK_WAIT_FOR_MOVEMENT:
		{
			if ( GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL)
			{
				// ALERT(at_console, "walking, talking\n");
				IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime );
			}
			else if ( GetEnemy() )
			{
				IdleHeadTurn( GetEnemy() );
			}

			BaseClass::RunTask( pTask );

			break;
		}

		case TASK_FACE_PLAYER:
		{
			CBasePlayer *pPlayer = UTIL_GetLocalPlayer();
			
			if ( pPlayer )
			{
				//GetMotor()->SetIdealYaw( pPlayer->GetAbsOrigin() );
				IdleHeadTurn( pPlayer );
				if ( gpGlobals->curtime > m_flWaitFinished && GetMotor()->DeltaIdealYaw() < 10 )
				{
					TaskComplete();
				}
			}
			else
			{
				TaskFail( FAIL_NO_PLAYER );
			}

			break;
		}

		case TASK_TALKER_EYECONTACT:
		{
			if (!IsMoving() && GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL)
			{
				// ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time );
				IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime );
			}
			
			BaseClass::RunTask( pTask );
			
			break;

		}

				
		default:
		{
			if ( GetExpresser()->IsSpeaking() && GetSpeechTarget() != NULL)
			{
				IdleHeadTurn( GetSpeechTarget(), GetExpresser()->GetTimeSpeechComplete() - gpGlobals->curtime );
			}
			else if ( GetEnemy() && m_NPCState == NPC_STATE_COMBAT )
			{
				IdleHeadTurn( GetEnemy() );
			}
			else if ( GetFollowTarget() )
			{
				IdleHeadTurn( GetFollowTarget() );
			}

			BaseClass::RunTask( pTask );
			break;
		}
	}
}

bool CHL1NPCTalker::ShouldGib( const CTakeDamageInfo &info )
{
	if ( info.GetDamageType() & DMG_NEVERGIB )
		 return false;

	if ( ( g_pGameRules->Damage_ShouldGibCorpse( info.GetDamageType() ) && m_iHealth < GIB_HEALTH_VALUE ) || ( info.GetDamageType() & DMG_ALWAYSGIB ) )
		 return true;
	
	return false;
	
}

void CHL1NPCTalker::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
		case TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS:
		{
			GetNavigator()->SetMovementActivity( ACT_WALK );
			break;
		}
		case TASK_TALKER_SPEAK:
			// ask question or make statement
			FIdleSpeak();
			TaskComplete();
			break;
		default:
			BaseClass::StartTask( pTask );
			break;
	}
}

//=========================================================
// FIdleSpeak
// ask question of nearby friend, or make statement
//=========================================================
int CHL1NPCTalker::FIdleSpeak ( void )
{ 
	if (!IsOkToSpeak())
		return FALSE;

	// if there is a friend nearby to speak to, play sentence, set friend's response time, return
	// try to talk to any standing or sitting scientists nearby
	CBaseEntity *pentFriend = FindNearestFriend( false );
	CHL1NPCTalker *pentTalker = dynamic_cast<CHL1NPCTalker *>( pentFriend );
	if (pentTalker && random->RandomInt(0,1) )
	{
		Speak( TLK_QUESTION );
		SetSpeechTarget( pentFriend );

		pentTalker->SetSpeechTarget( this );
		pentTalker->SetCondition( COND_TALKER_RESPOND_TO_QUESTION );
		pentTalker->SetSchedule( SCHED_TALKER_IDLE_RESPONSE );
		pentTalker->GetExpresser()->BlockSpeechUntil( GetExpresser()->GetTimeSpeechComplete() );

		GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + random->RandomFloat(4.8, 5.2) );

		//DevMsg( "Asking some question!\n" );
		return TRUE;
	}
	else if ( random->RandomInt(0,1)) 	// otherwise, play an idle statement
	{
		//DevMsg( "Making idle statement!\n" );

		Speak( TLK_IDLE );
		// set global min delay for next conversation
		GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + random->RandomFloat(4.8, 5.2) );
		return TRUE;
	}

	// never spoke
	GetExpresser()->BlockSpeechUntil( 0 );
	m_flNextIdleSpeechTime = gpGlobals->curtime + 3;
	return FALSE;
}



bool CHL1NPCTalker::IsValidSpeechTarget( int flags, CBaseEntity *pEntity )
{
	if ( pEntity == this )
		return false;

	CHL1NPCTalker *pentTarget = dynamic_cast<CHL1NPCTalker *>( pEntity );
	if ( pentTarget )
	{
		if ( !(flags & AIST_IGNORE_RELATIONSHIP) )
		{
			if ( pEntity->IsPlayer() )
			{
				if ( !IsPlayerAlly( (CBasePlayer *)pEntity ) )
					return false;
			}
			else
			{
				if ( IRelationType( pEntity ) != D_LI )
					return false;
			}
		}		

		if ( !pEntity->IsAlive() )
			// don't dead people
			return false;

		// Ignore no-target entities
		if ( pEntity->GetFlags() & FL_NOTARGET )
			return false;

		CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();
		if ( pNPC )
		{
			// If not a NPC for some reason, or in a script.
			//if ( (pNPC->m_NPCState == NPC_STATE_SCRIPT || pNPC->m_NPCState == NPC_STATE_PRONE))
			//	return false;

			if ( pNPC->IsInAScript() )
				return false;

			// Don't bother people who don't want to be bothered
			if ( !pNPC->CanBeUsedAsAFriend() )
				return false;
		}

		if ( flags & AIST_FACING_TARGET )
		{
			if ( pEntity->IsPlayer() )
				return HasCondition( COND_SEE_PLAYER );
			else if ( !FInViewCone( pEntity ) )
				return false;
		}

		return FVisible( pEntity );
	}
	else
		return BaseClass::IsValidSpeechTarget( flags, pEntity );
}


int CHL1NPCTalker::SelectSchedule ( void )
{
	switch( m_NPCState )
	{
	case NPC_STATE_PRONE:
		{
			if (m_bInBarnacleMouth)
			{
				return SCHED_HL1TALKER_BARNACLE_CHOMP;
			}
			else
			{
				return SCHED_HL1TALKER_BARNACLE_HIT;
			}
		}
	}

	return BaseClass::SelectSchedule();
}

void CHL1NPCTalker::Precache()
{
	BaseClass::Precache();

	PrecacheScriptSound( "Barney.Close" );
}

bool CHL1NPCTalker::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
{
	if (interactionType == g_interactionBarnacleVictimDangle)
	{
		// Force choosing of a new schedule
		ClearSchedule( "NPC talker being eaten by a barnacle" );
		m_bInBarnacleMouth	= true;
		return true;
	}
	else if ( interactionType == g_interactionBarnacleVictimReleased )
	{
		SetState ( NPC_STATE_IDLE );

		CPASAttenuationFilter filter( this );

		CSoundParameters params;

		if ( GetParametersForSound( "Barney.Close", params, NULL ) )
		{
			EmitSound_t ep( params );
			ep.m_nPitch = GetExpresser()->GetVoicePitch();

			EmitSound( filter, entindex(), ep );
		}

		m_bInBarnacleMouth	= false;
		SetAbsVelocity( vec3_origin );
		SetMoveType( MOVETYPE_STEP );
		return true;
	}
	else if ( interactionType == g_interactionBarnacleVictimGrab )
	{
		if ( GetFlags() & FL_ONGROUND )
		{
			SetGroundEntity( NULL );
		}
		
		if ( GetState() == NPC_STATE_SCRIPT )
		{
			if ( m_hCine )
			{
				 m_hCine->CancelScript();
			}
		}

		SetState( NPC_STATE_PRONE );
		ClearSchedule( "NPC talker grabbed by a barnacle" );
		
		CTakeDamageInfo info;
		PainSound( info );
		return true;
	}
	return false;
}

void CHL1NPCTalker::StartFollowing(	CBaseEntity *pLeader )
{
	if ( !HasSpawnFlags( SF_NPC_GAG ) )
	{
		if ( m_iszUse != NULL_STRING )
		{
			PlaySentence( STRING( m_iszUse ), 0.0f );
		}
		else
		{
			Speak( TLK_STARTFOLLOW );
		}

		SetSpeechTarget( pLeader );
	}

	BaseClass::StartFollowing( pLeader );
}

int CHL1NPCTalker::PlayScriptedSentence( const char *pszSentence, float delay, float volume, soundlevel_t soundlevel, bool bConcurrent, CBaseEntity *pListener )
{
	if( hl1_debug_sentence_volume.GetBool() )
	{
		Msg( "SENTENCE: %s Vol:%f SndLevel:%d\n", GetDebugName(), volume, soundlevel );
	}

	if( hl1_fixup_sentence_sndlevel.GetBool() )
	{
		if( soundlevel < SNDLVL_TALKING )
		{
			soundlevel = SNDLVL_TALKING;
		}
	}

	return BaseClass::PlayScriptedSentence( pszSentence, delay, volume, soundlevel, bConcurrent, pListener );
}

Disposition_t CHL1NPCTalker::IRelationType( CBaseEntity *pTarget )
{
	if ( pTarget->IsPlayer() )
	{
		if ( HasMemory( bits_MEMORY_PROVOKED ) )
		{
			return D_HT;
		}
	}

	return BaseClass::IRelationType( pTarget );
}

void CHL1NPCTalker::Touch( CBaseEntity *pOther )
{
	if ( m_NPCState == NPC_STATE_SCRIPT )
		 return;

	BaseClass::Touch(pOther);
}

void CHL1NPCTalker::StopFollowing( void )
{
	if ( !(m_afMemory & bits_MEMORY_PROVOKED) )
	{
		if ( !HasSpawnFlags( SF_NPC_GAG ) )
		{
			if ( m_iszUnUse != NULL_STRING )
			{
				PlaySentence( STRING( m_iszUnUse ), 0.0f );
			}
			else
			{
				Speak( TLK_STOPFOLLOW );
			}

			SetSpeechTarget( GetFollowTarget() );
		}
	}

	BaseClass::StopFollowing();
}

void CHL1NPCTalker::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
	if ( info.GetDamage() >= 1.0 && !(info.GetDamageType() & DMG_SHOCK ) )
	{
		UTIL_BloodImpact( ptr->endpos, vecDir, BloodColor(), 4 );
	}

	BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator );
}

void CHL1NPCTalker::FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
	// Don't allow use during a scripted_sentence
	if ( GetUseTime() > gpGlobals->curtime )
		return;

	if ( m_hCine && !m_hCine->CanInterrupt() )
		 return;

	if ( pCaller != NULL && pCaller->IsPlayer() )
	{
		// Pre-disaster followers can't be used
		if ( m_spawnflags & SF_NPC_PREDISASTER )
		{
			SetSpeechTarget( pCaller );
			DeclineFollowing();
			return;
		}
	}

	BaseClass::FollowerUse( pActivator, pCaller, useType, value );
}

int CHL1NPCTalker::TranslateSchedule( int scheduleType )
{
	return BaseClass::TranslateSchedule( scheduleType );
}

float CHL1NPCTalker::PickLookTarget( bool bExcludePlayers, float minTime, float maxTime )
{
	return random->RandomFloat( 5.0f, 10.0f );
}

void CHL1NPCTalker::IdleHeadTurn( CBaseEntity *pTarget, float flDuration, float flImportance )
{
	// Must be able to turn our head
	if (!(CapabilitiesGet() & bits_CAP_TURN_HEAD))
		return;

	// If the target is invalid, or we're in a script, do nothing
	if ( ( !pTarget ) || ( m_NPCState == NPC_STATE_SCRIPT ) )
		return;

	// Fill in a duration if we haven't specified one
	if ( flDuration == 0.0f )
	{
		 flDuration = random->RandomFloat( 2.0, 4.0 );
	}

	// Add a look target
	AddLookTarget( pTarget, 1.0, flDuration );
}

void CHL1NPCTalker::SetHeadDirection( const Vector &vTargetPos, float flInterval)
{
#ifdef TALKER_LOOK
	// Draw line in body, head, and eye directions
	Vector vEyePos = EyePosition();
	Vector vHeadDir = HeadDirection3D();
	Vector vBodyDir = BodyDirection2D();

	//UNDONE <<TODO>>
	// currently eye dir just returns head dir, so use vTargetPos for now
	//Vector vEyeDir;	w
	//EyeDirection3D(&vEyeDir);
	NDebugOverlay::Line( vEyePos, vEyePos+(50*vHeadDir), 255, 0, 0, false, 0.1 );
	NDebugOverlay::Line( vEyePos, vEyePos+(50*vBodyDir), 0, 255, 0, false, 0.1 );
	NDebugOverlay::Line( vEyePos, vTargetPos, 0, 0, 255, false, 0.1 );
#endif

}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CHL1NPCTalker::CorpseGib( const CTakeDamageInfo &info )
{
	CEffectData	data;
	
	data.m_vOrigin = WorldSpaceCenter();
	data.m_vNormal = data.m_vOrigin - info.GetDamagePosition();
	VectorNormalize( data.m_vNormal );
	
	data.m_flScale = RemapVal( m_iHealth, 0, -500, 1, 3 );
	data.m_flScale = clamp( data.m_flScale, 1, 3 );

    data.m_nMaterial = 1;
	data.m_nHitBox = -m_iHealth;

	data.m_nColor = BloodColor();
	
	DispatchEffect( "HL1Gib", data );

	CSoundEnt::InsertSound( SOUND_MEAT, GetAbsOrigin(), 256, 0.5f, this );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CHL1NPCTalker::OnObstructingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor, float distClear, AIMoveResult_t *pResult )
{
	// If we can't get through the door, try and open it
	if ( BaseClass::OnObstructingDoor( pMoveGoal, pDoor, distClear, pResult ) )
	{
		if  ( IsMoveBlocked( *pResult ) && pMoveGoal->directTrace.vHitNormal != vec3_origin )
		{
			// Can't do anything if the door's locked
			if ( !pDoor->m_bLocked && !pDoor->HasSpawnFlags(SF_DOOR_NONPCS) )
			{
				// Tell the door to open
				variant_t emptyVariant;
				pDoor->AcceptInput( "Open", this, this, emptyVariant, USE_TOGGLE );
				*pResult = AIMR_OK;
			}
		}
		return true;
	}

	return false;
}

// HL1 version - never return Ragdoll as the automatic schedule at the end of a 
// scripted sequence
int CHL1NPCTalker::SelectDeadSchedule()
{
	// Alread dead (by animation event maybe?)
	// Is it safe to set it to SCHED_NONE?
	if ( m_lifeState == LIFE_DEAD )
		 return SCHED_NONE;

	CleanupOnDeath();
	return SCHED_DIE;
}


AI_BEGIN_CUSTOM_NPC( monster_hl1talker, CHL1NPCTalker )

	DECLARE_TASK( TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS )

	//=========================================================
	// > SCHED_HL1TALKER_MOVE_AWAY_FOLLOW
	//=========================================================
	DEFINE_SCHEDULE 
	(
		SCHED_HL1TALKER_FOLLOW_MOVE_AWAY,

		"	Tasks"
		"		 TASK_SET_FAIL_SCHEDULE					SCHEDULE:SCHED_TARGET_FACE"
		"		 TASK_STORE_LASTPOSITION				0"
		"		 TASK_MOVE_AWAY_PATH					100"
		"		 TASK_HL1TALKER_FOLLOW_WALK_PATH_FOR_UNITS	100"
		"		 TASK_STOP_MOVING						0"
		"		 TASK_FACE_PLAYER						0"
		"		 TASK_SET_ACTIVITY						ACT_IDLE"
		""
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_HL1TALKER_IDLE_SPEAK_WAIT
	//=========================================================
	DEFINE_SCHEDULE 
	(
		SCHED_HL1TALKER_IDLE_SPEAK_WAIT,

		"	Tasks"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE"	// Stop and talk
		"		TASK_FACE_PLAYER			0"
		""
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_HEAR_DANGER"
	)

	//=========================================================
	// > SCHED_HL1TALKER_BARNACLE_HIT
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HL1TALKER_BARNACLE_HIT,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_PLAY_SEQUENCE			ACTIVITY:ACT_BARNACLE_HIT"
		"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_HL1TALKER_BARNACLE_PULL"
		""
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_HL1TALKER_BARNACLE_PULL
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HL1TALKER_BARNACLE_PULL,

		"	Tasks"
		"		 TASK_PLAY_SEQUENCE			ACTIVITY:ACT_BARNACLE_PULL"
		""
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_HL1TALKER_BARNACLE_CHOMP
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HL1TALKER_BARNACLE_CHOMP,

		"	Tasks"
		"		 TASK_PLAY_SEQUENCE			ACTIVITY:ACT_BARNACLE_CHOMP"
		"		 TASK_SET_SCHEDULE			SCHEDULE:SCHED_HL1TALKER_BARNACLE_CHEW"
		""
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_HL1TALKER_BARNACLE_CHEW
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_HL1TALKER_BARNACLE_CHEW,

		"	Tasks"
		"		 TASK_PLAY_SEQUENCE			ACTIVITY:ACT_BARNACLE_CHEW"
	)

AI_END_CUSTOM_NPC()