//=========== (C) Copyright 2000 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose: Bullseyes act as targets for other NPC's to attack and to trigger
//			events 
//
// $Workfile:     $
// $Date:         $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================

#include	"cbase.h"
#include	"AI_Default.h"
#include	"AI_Task.h"
#include	"AI_Schedule.h"
#include	"AI_Node.h"
#include	"AI_Hull.h"
#include	"AI_Hint.h"
#include	"AI_Route.h"
#include	"hl1_npc_scientist.h"
#include	"soundent.h"
#include	"game.h"
#include	"NPCEvent.h"
#include	"EntityList.h"
#include	"activitylist.h"
#include	"animation.h"
#include	"engine/IEngineSound.h"
#include	"ai_navigator.h"
#include	"AI_Behavior_Follow.h"
#include	"AI_Criteria.h"
#include	"doors.h"

#define SC_PLFEAR	"SC_PLFEAR"
#define SC_FEAR		"SC_FEAR"
#define SC_HEAL		"SC_HEAL"
#define SC_SCREAM	"SC_SCREAM"
#define SC_POK		"SC_POK"

ConVar	sk_scientist_health( "sk_scientist_health","20");
ConVar	sk_scientist_heal( "sk_scientist_heal","25");

#define		NUM_SCIENTIST_HEADS		4 // four heads available for scientist model
enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 };


int ACT_EXCITED;

//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define		SCIENTIST_AE_HEAL		( 1 )
#define		SCIENTIST_AE_NEEDLEON	( 2 )
#define		SCIENTIST_AE_NEEDLEOFF	( 3 )

//=======================================================
// Scientist
//=======================================================

LINK_ENTITY_TO_CLASS( monster_scientist, CNPC_Scientist );

//IMPLEMENT_SERVERCLASS_ST( CNPC_Scientist, DT_NPC_Scientist )
//END_SEND_TABLE()


//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CNPC_Scientist )
	DEFINE_FIELD( CNPC_Scientist, m_flFearTime, FIELD_TIME ),
	DEFINE_FIELD( CNPC_Scientist, m_flHealTime, FIELD_TIME ),
	DEFINE_FIELD( CNPC_Scientist, m_flPainTime, FIELD_TIME ),
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CNPC_Scientist::Precache( void )
{
	engine->PrecacheModel( "models/scientist.mdl" );

	enginesound->PrecacheSound("scientist/sci_pain1.wav");
	enginesound->PrecacheSound("scientist/sci_pain2.wav");
	enginesound->PrecacheSound("scientist/sci_pain3.wav");
	enginesound->PrecacheSound("scientist/sci_pain4.wav");
	enginesound->PrecacheSound("scientist/sci_pain5.wav");

	enginesound->PrecacheSound("scientist/c1a4_sci_rocket.wav");

	
	
	TalkInit();
	
	BaseClass::Precache();
}

void CNPC_Scientist::ModifyOrAppendCriteria( AI_CriteriaSet& set )
{
	BaseClass::ModifyOrAppendCriteria( set );

	bool predisaster = FBitSet( m_spawnflags, SF_NPC_PREDISASTER ) ? true : false;

	set.AppendCriteria( "disaster", predisaster ? "[disaster::pre]" : "[disaster::post]" );
}

// Init talk data
void CNPC_Scientist::TalkInit()
{
	
	BaseClass::TalkInit();

	// scientist will try to talk to friends in this order:

	m_szFriends[0] = "monster_scientist";
	m_szFriends[1] = "monster_sitting_scientist";
	m_szFriends[2] = "monster_barney";

	// get voice for head
	switch (m_nBody % 3)
	{
	default:
	case HEAD_GLASSES:	GetExpresser()->SetVoicePitch( 105 );	break;	//glasses
	case HEAD_EINSTEIN: GetExpresser()->SetVoicePitch( 100 );	break;	//einstein
	case HEAD_LUTHER:	GetExpresser()->SetVoicePitch( 95 );	break;	//luther
	case HEAD_SLICK:	GetExpresser()->SetVoicePitch( 100 );	break;//slick
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//
//
//-----------------------------------------------------------------------------
void CNPC_Scientist::Spawn( void )
{

	//Select the body first if it's going to be random cause we set his voice pitch in Precache.
	if ( m_nBody == -1 )
		 m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1 );// pick a head, any head
	

	SetRenderColor( 255, 255, 255, 255 );
	
	Precache();

	SetModel( "models/scientist.mdl" );

	SetHullType(HULL_HUMAN);
	SetHullSizeNormal();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );
	m_bloodColor		= BLOOD_COLOR_RED;
	m_fEffects			= 0;
	m_iHealth			= sk_scientist_health.GetFloat();
	m_flFieldOfView		= VIEW_FIELD_WIDE;
	m_NPCState			= NPC_STATE_NONE;

	CapabilitiesClear();
	CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE );
	CapabilitiesAdd( bits_CAP_TURN_HEAD | bits_CAP_ANIMATEDFACE );

	// White hands
	m_nSkin = 0;

	
	// Luther is black, make his hands black
	if ( m_nBody == HEAD_LUTHER )
		 m_nSkin = 1;
	
	NPCInit();

	SetUse( FollowerUse );
}

//-----------------------------------------------------------------------------
// Purpose: 
//
//
// Output : 
//-----------------------------------------------------------------------------
Class_T	CNPC_Scientist::Classify( void )
{
	return	CLASS_HUMAN_PASSIVE;
}

int CNPC_Scientist :: GetSoundInterests ( void )
{
	return	SOUND_WORLD	|
			SOUND_COMBAT	|
			SOUND_DANGER	|
			SOUND_PLAYER;
}

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CNPC_Scientist::HandleAnimEvent( animevent_t *pEvent )
{
	switch( pEvent->event )
	{		
	case SCIENTIST_AE_HEAL:		// Heal my target (if within range)
		Heal();
		break;
	case SCIENTIST_AE_NEEDLEON:
	{
		int oldBody = m_nBody;
		m_nBody = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1;
	}
		break;
	case SCIENTIST_AE_NEEDLEOFF:
	{
		int oldBody = m_nBody;
		m_nBody = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0;
	}
		break;

	default:
		BaseClass::HandleAnimEvent( pEvent );
		break;
	}
}

void CNPC_Scientist::DeclineFollowing( void )
{
	GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 10 );
	//SetSpeechTarget( GetTarget() );

	Speak( SC_POK );
}

void CNPC_Scientist :: Scream( void )
{
	if ( IsOkToSpeak() )
	{
		GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 10 );
		SetSpeechTarget( GetEnemy() );
		Speak( SC_SCREAM );
	}
}

Activity CNPC_Scientist::GetStoppedActivity( void )
{ 
	if ( GetEnemy() != NULL ) 
		return (Activity)ACT_EXCITED;
	
	return BaseClass::GetStoppedActivity();
}

float CNPC_Scientist::MaxYawSpeed( void )
{
	switch( GetActivity() )
	{
	case ACT_TURN_LEFT:
	case ACT_TURN_RIGHT:
		return 160;
		break;
	case ACT_RUN:
		return 160;
		break;
	default:
		return 60;
		break;
	}
}

void CNPC_Scientist :: StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_SAY_HEAL:

		GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 2 );
		SetSpeechTarget( GetTarget() );
		Speak( SC_HEAL );

		TaskComplete();
		break;
	 
	case TASK_SCREAM:
		Scream();
		TaskComplete();
		break;

	case TASK_RANDOM_SCREAM:
		if ( random->RandomFloat( 0, 1 ) < pTask->flTaskData )
			Scream();
		TaskComplete();
		break;

	case TASK_SAY_FEAR:
		if ( IsOkToSpeak() )
		{
			GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + 2 );
			SetSpeechTarget( GetEnemy() );
			if ( GetEnemy() && GetEnemy()->IsPlayer() )
				Speak( SC_PLFEAR );
			else
				Speak( SC_FEAR );
		}
		TaskComplete();
		break;

	case TASK_HEAL:
		SetIdealActivity( ACT_MELEE_ATTACK1 );
		break;

	case TASK_RUN_PATH_SCARED:
		GetNavigator()->SetMovementActivity( ACT_RUN_SCARED );
		break;

	case TASK_MOVE_TO_TARGET_RANGE_SCARED:
		{
			if ( GetTarget() == NULL)
			{
				TaskFail(FAIL_NO_TARGET);
			}
			else if ( (GetTarget()->GetAbsOrigin() - GetAbsOrigin()).Length() < 1 )
			{
				TaskComplete();
			}
		}
		break;

	default:
		BaseClass::StartTask( pTask );
		break;
	}
}

void CNPC_Scientist :: RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_RUN_PATH_SCARED:
		if ( !IsMoving() )
			TaskComplete();
		if ( random->RandomInt(0,31) < 8 )
			Scream();
		break;

	case TASK_MOVE_TO_TARGET_RANGE_SCARED:
		{
			float distance;

			if ( GetTarget() == NULL )
			{
				TaskFail(FAIL_NO_TARGET);
			}
			else
			{
				distance = ( GetNavigator()->GetPath()->ActualGoalPosition() - GetAbsOrigin() ).Length2D();
				// Re-evaluate when you think your finished, or the target has moved too far
				if ( (distance < pTask->flTaskData) || (GetNavigator()->GetPath()->ActualGoalPosition() - GetTarget()->GetAbsOrigin()).Length() > pTask->flTaskData * 0.5 )
				{
					GetNavigator()->GetPath()->ResetGoalPosition(GetTarget()->GetAbsOrigin());
					distance = ( GetNavigator()->GetPath()->ActualGoalPosition() - GetAbsOrigin() ).Length2D();
//					GetNavigator()->GetPath()->Find();
					GetNavigator()->SetGoal( GOALTYPE_TARGETENT );
				}

				// Set the appropriate activity based on an overlapping range
				// overlap the range to prevent oscillation
				// BUGBUG: this is checking linear distance (ie. through walls) and not path distance or even visibility
				if ( distance < pTask->flTaskData )
				{
					TaskComplete();
					GetNavigator()->GetPath()->Clear();		// Stop moving
				}
				else
				{
					if ( distance < 190 && GetNavigator()->GetMovementActivity() != ACT_WALK_SCARED )
						GetNavigator()->SetMovementActivity( ACT_WALK_SCARED );
					else if ( distance >= 270 && GetNavigator()->GetMovementActivity() != ACT_RUN_SCARED )
						GetNavigator()->SetMovementActivity( ACT_RUN_SCARED );
				}
			}
		}
		break;

	case TASK_HEAL:
		if ( IsSequenceFinished() )
		{
			TaskComplete();
		}
		else
		{
			if ( TargetDistance() > 90 )
				TaskComplete();

			if ( GetTarget() )
				 GetMotor()->SetIdealYaw( UTIL_VecToYaw( GetTarget()->GetAbsOrigin() - GetAbsOrigin() ) );

			//GetMotor()->SetYawSpeed( m_YawSpeed );
		}
		break;
	default:
		BaseClass::RunTask( pTask );
		break;
	}
}

int CNPC_Scientist :: OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{

	if ( inputInfo.GetInflictor() && inputInfo.GetInflictor()->GetFlags() & FL_CLIENT )
	{
		Remember( bits_MEMORY_PROVOKED );
		StopFollowing();
	}

	// make sure friends talk about it if player hurts scientist...
	return BaseClass::OnTakeDamage_Alive( inputInfo );
}

void CNPC_Scientist::Event_Killed( const CTakeDamageInfo &info )
{
	SetUse( NULL );	
	BaseClass::Event_Killed( info );
}

bool CNPC_Scientist::CanHeal( void )
{ 
	CBaseEntity *pTarget = GetFollowTarget();

	if ( pTarget == NULL )
		 return false;

	if ( pTarget->IsPlayer() == false )
		 return false;

	if ( (m_flHealTime > gpGlobals->curtime) || (pTarget->m_iHealth > (pTarget->m_iMaxHealth * 0.5)) )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_Scientist::OnUpcomingDoor( AILocalMoveGoal_t *pMoveGoal, CBaseDoor *pDoor, float distClear, AIMoveResult_t *pResult )
{
	// If we can't get through the door, try and open it
	if ( BaseClass::OnUpcomingDoor( 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 );
			}
		}
		return true;
	}

	return false;
}

//=========================================================
// PainSound
//=========================================================
void CNPC_Scientist::PainSound ( void )
{
	if (gpGlobals->curtime < m_flPainTime )
		return;
	
	m_flPainTime = gpGlobals->curtime + random->RandomFloat( 0.5, 0.75 );

	CPASAttenuationFilter filter( this );

	switch (random->RandomInt( 0,4 ) )
	{
		case 0: enginesound->EmitSound( filter, entindex(), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetExpresser()->GetVoicePitch()); break;
		case 1: enginesound->EmitSound( filter, entindex(), CHAN_VOICE, "scientist/sci_pain2.wav", 1, ATTN_NORM, 0, GetExpresser()->GetVoicePitch()); break;
		case 2: enginesound->EmitSound( filter, entindex(), CHAN_VOICE, "scientist/sci_pain3.wav", 1, ATTN_NORM, 0, GetExpresser()->GetVoicePitch()); break;
		case 3: enginesound->EmitSound( filter, entindex(), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, GetExpresser()->GetVoicePitch()); break;
		case 4: enginesound->EmitSound( filter, entindex(), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, GetExpresser()->GetVoicePitch()); break;
	}
}

//=========================================================
// DeathSound 
//=========================================================
void CNPC_Scientist::DeathSound ( void )
{
	PainSound();
}


void CNPC_Scientist::Heal( void )
{
	if ( !CanHeal() )
		  return;

	Vector target = GetFollowTarget()->GetAbsOrigin() - GetAbsOrigin();
	if ( target.Length() > 100 )
		return;

	GetTarget()->TakeHealth( sk_scientist_heal.GetFloat(), DMG_GENERIC );
	// Don't heal again for 1 minute
	m_flHealTime = gpGlobals->curtime + 60;
}

int CNPC_Scientist::TranslateSchedule( int scheduleType )
{
	switch( scheduleType )
	{
	// Hook these to make a looping schedule
	case SCHED_TARGET_FACE:
		{
			int baseType;

			// call base class default so that scientist will talk
			// when 'used' 
			baseType = BaseClass::TranslateSchedule( scheduleType );

			if (baseType == SCHED_IDLE_STAND)
				return SCHED_TARGET_FACE;	// override this for different target face behavior
			else
				return baseType;
		}
		break;

	case SCHED_TARGET_CHASE:
		return SCHED_SCI_FOLLOWTARGET;
		break;

	case SCHED_IDLE_STAND:
		{
			int baseType;
				
			baseType = BaseClass::TranslateSchedule( scheduleType );

			if (baseType == SCHED_IDLE_STAND)
				return SCHED_SCI_IDLESTAND;	// override this for different target face behavior
			else
				return baseType;
		}
		break;
	}

	return BaseClass::TranslateSchedule( scheduleType );
}

Activity CNPC_Scientist::NPC_TranslateActivity( Activity newActivity )
{
	if ( GetFollowTarget() && GetEnemy() )
	{
		CBaseEntity *pEnemy = GetEnemy();

		int relationship = D_NU;

		// Nothing scary, just me and the player
		if ( pEnemy != NULL )
			 relationship = IRelationType( pEnemy );

		if ( relationship == D_HT || relationship == D_FR )
		{
			if ( newActivity == ACT_WALK )
				 return ACT_WALK_SCARED;
			else if ( newActivity == ACT_RUN )
				 return ACT_RUN_SCARED;
		}
	}

	return BaseClass::NPC_TranslateActivity( newActivity );
}

int CNPC_Scientist::SelectSchedule( void )
{
	// so we don't keep calling through the EHANDLE stuff
	CBaseEntity *pEnemy = GetEnemy();

	if ( GetFollowTarget() )
	{
		// so we don't keep calling through the EHANDLE stuff
		CBaseEntity *pEnemy = GetEnemy();

		int relationship = D_NU;

		// Nothing scary, just me and the player
		if ( pEnemy != NULL )
			 relationship = IRelationType( pEnemy );

		if ( relationship != D_HT && relationship != D_FR )
		{
			// If I'm already close enough to my target
			if ( TargetDistance() <= 128 )
			{
				if ( CanHeal() )	// Heal opportunistically
					return SCHED_SCI_HEAL;
			}
		}
	}
	else if ( HasCondition( COND_PLAYER_PUSHING ) && !(GetSpawnFlags() & SF_NPC_PREDISASTER ) )
	{		// Player wants me to move
			return SCHED_HL1TALKER_FOLLOW_MOVE_AWAY;
	}

	if ( BehaviorSelectSchedule() )
	{
		return BaseClass::SelectSchedule();
	}

	
	
	if ( HasCondition( COND_HEAR_DANGER ) && m_NPCState != NPC_STATE_PRONE )
	{
		CSound *pSound;
		pSound = GetBestSound();

		if ( pSound && pSound->IsSoundType(SOUND_DANGER) )
			return SCHED_TAKE_COVER_FROM_BEST_SOUND;
	}

	switch( m_NPCState )
	{

	case NPC_STATE_ALERT:	
	case NPC_STATE_IDLE:

		if ( pEnemy )
		{
			if ( HasCondition( COND_SEE_ENEMY ) )
				m_flFearTime = gpGlobals->curtime;
			else if ( DisregardEnemy( pEnemy ) )		// After 15 seconds of being hidden, return to alert
			{
				SetEnemy( NULL );
				pEnemy = NULL;
			}
		}

		if ( HasCondition( COND_LIGHT_DAMAGE ) ||  HasCondition( COND_HEAVY_DAMAGE ) )
		{
			// flinch if hurt
			return SCHED_SMALL_FLINCH;
		}
	
		// Cower when you hear something scary
		if ( HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_COMBAT ) ) 
		{
			CSound *pSound;
			pSound = GetBestSound();
		
			if ( pSound )
			{
				if ( pSound->IsSoundType(SOUND_DANGER | SOUND_COMBAT) )
				{
					if ( gpGlobals->curtime - m_flFearTime > 3 )	// Only cower every 3 seconds or so
					{
						m_flFearTime = gpGlobals->curtime;		// Update last fear
						return SCHED_SCI_STARTLE;	// This will just duck for a second
					}
				}
			}
		}
		
		if ( GetFollowTarget() )
		{
			if ( !GetFollowTarget()->IsAlive() )
			{
				// UNDONE: Comment about the recently dead player here?
				StopFollowing();
				break;
			}
			
			int relationship = D_NU;

			// Nothing scary, just me and the player
			if ( pEnemy != NULL )
				 relationship = IRelationType( pEnemy );
			
			if ( relationship != D_HT )
			{
				return SCHED_TARGET_FACE;	// Just face and follow.
			}
			else	// UNDONE: When afraid, scientist won't move out of your way.  Keep This?  If not, write move away scared
			{
				if ( HasCondition( COND_NEW_ENEMY ) ) // I just saw something new and scary, react
					return SCHED_SCI_FEAR;					// React to something scary
				return SCHED_SCI_FACETARGETSCARED;	// face and follow, but I'm scared!
			}
		}

		// try to say something about smells
		TrySmellTalk();
		break;


	case NPC_STATE_COMBAT:

		if ( HasCondition( COND_NEW_ENEMY ) )
			return SCHED_SCI_FEAR;					// Point and scream!
		if ( HasCondition( COND_SEE_ENEMY ) )
			return SCHED_SCI_COVER;		// Take Cover
		
		if ( HasCondition( COND_HEAR_COMBAT ) || HasCondition( COND_HEAR_DANGER ) )
			 return SCHED_TAKE_COVER_FROM_BEST_SOUND;	// Cower and panic from the scary sound!
	
		return SCHED_SCI_COVER;			// Run & Cower
		break;
	}

	return BaseClass::SelectSchedule();
}

NPC_STATE CNPC_Scientist::SelectIdealState ( void )
{
	switch ( m_NPCState )
	{
	case NPC_STATE_ALERT:
	case NPC_STATE_IDLE:
		if ( HasCondition( COND_NEW_ENEMY ) )
		{
			if ( GetFollowTarget() && GetEnemy() )
			{
				int relationship = IRelationType( GetEnemy() );
				if ( relationship != D_FR || relationship != D_HT && ( !HasCondition( COND_LIGHT_DAMAGE  ) || !HasCondition( COND_HEAVY_DAMAGE ) ) )
				{
					// Don't go to combat if you're following the player
					m_IdealNPCState = NPC_STATE_ALERT;
					return m_IdealNPCState;
				}
				StopFollowing();
			}
		}
		else if ( HasCondition( COND_LIGHT_DAMAGE  ) || HasCondition( COND_HEAVY_DAMAGE ) ) 
		{
			// Stop following if you take damage
			if ( GetFollowTarget() )
				 StopFollowing();
		}
		break;

	case NPC_STATE_COMBAT:
		{
			CBaseEntity *pEnemy = GetEnemy();
			if ( pEnemy != NULL )
			{
				if ( DisregardEnemy( pEnemy ) )		// After 15 seconds of being hidden, return to alert
				{
					// Strip enemy when going to alert
					m_IdealNPCState = NPC_STATE_ALERT;
					SetEnemy( NULL );
					return m_IdealNPCState;
				}
				// Follow if only scared a little
				if ( GetFollowTarget() )
				{
					m_IdealNPCState = NPC_STATE_ALERT;
					return m_IdealNPCState;
				}

				if ( HasCondition( COND_SEE_ENEMY ) )
				{
					m_flFearTime = gpGlobals->curtime;
					m_IdealNPCState = NPC_STATE_COMBAT;
					return m_IdealNPCState;
				}

			}
		}
		break;
	}

	return BaseClass::SelectIdealState();
}

int CNPC_Scientist::FriendNumber( int arrayNumber )
{
	static int array[3] = { 1, 2, 0 };
	if ( arrayNumber < 3 )
		return array[ arrayNumber ];
	return arrayNumber;
}

float CNPC_Scientist::TargetDistance( void )
{
	// If we lose the player, or he dies, return a really large distance
	if ( GetTarget() == NULL || !GetTarget()->IsAlive() )
		return 1e6;

	return (GetTarget()->GetLocalOrigin() - GetLocalOrigin()).Length();
}

//=========================================================
// Dead Scientist PROP
//=========================================================
class CNPC_DeadScientist : public CAI_BaseNPC
{
	DECLARE_CLASS( CNPC_DeadScientist, CAI_BaseNPC );
public:

	void Spawn( void );
	Class_T	Classify ( void ) { return	CLASS_NONE; }

	bool KeyValue( const char *szKeyName, const char *szValue );
	float MaxYawSpeed ( void ) { return 8.0f; }

	int	m_iPose;// which sequence to display	-- temporary, don't need to save
	int m_iDesiredSequence;
	static char *m_szPoses[7];
};


char *CNPC_DeadScientist::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" };

bool CNPC_DeadScientist::KeyValue( const char *szKeyName, const char *szValue )
{
	if ( FStrEq( szKeyName, "pose" ) )
		m_iPose = atoi( szValue );
	else 
		BaseClass::KeyValue( szKeyName, szValue );

	return true;
}

LINK_ENTITY_TO_CLASS( monster_scientist_dead, CNPC_DeadScientist );

//
// ********** DeadScientist SPAWN **********
//
void CNPC_DeadScientist::Spawn( void )
{
	engine->PrecacheModel("models/scientist.mdl");
	SetModel( "models/scientist.mdl" );
	
	m_fEffects			= 0;
	SetSequence( 0 );
	m_bloodColor		= BLOOD_COLOR_RED;

	SetRenderColor( 255, 255, 255, 255 );

	if ( m_nBody == -1 )
	{// -1 chooses a random head
		m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1);// pick a head, any head
	}
	// Luther is black, make his hands black
	if ( m_nBody == HEAD_LUTHER )
		m_nSkin = 1;
	else
		m_nSkin = 0;

	SetSequence( LookupSequence( m_szPoses[m_iPose] ) );

	if ( GetSequence() == -1)
	{
		Msg ( "Dead scientist with bad pose\n" );
	}

	m_iHealth			= 0.0;//gSkillData.barneyHealth;

	NPCInitDead();

}


//=========================================================
// Sitting Scientist PROP
//=========================================================


LINK_ENTITY_TO_CLASS( monster_sitting_scientist, CNPC_SittingScientist );

//IMPLEMENT_CUSTOM_AI( monster_sitting_scientist, CNPC_SittingScientist );

//IMPLEMENT_SERVERCLASS_ST( CNPC_SittingScientist, DT_NPC_SittingScientist )
//END_SEND_TABLE()


//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CNPC_SittingScientist )
	DEFINE_FIELD( CNPC_SittingScientist, m_iHeadTurn, FIELD_INTEGER ),
	DEFINE_FIELD( CNPC_SittingScientist, m_flResponseDelay, FIELD_FLOAT ),

	DEFINE_THINKFUNC( CNPC_SittingScientist, SittingThink ),
END_DATADESC()


// animation sequence aliases 
typedef enum
{
SITTING_ANIM_sitlookleft,
SITTING_ANIM_sitlookright,
SITTING_ANIM_sitscared,
SITTING_ANIM_sitting2,
SITTING_ANIM_sitting3
} SITTING_ANIM;


//
// ********** Scientist SPAWN **********
//
void CNPC_SittingScientist :: Spawn( )
{
	engine->PrecacheModel("models/scientist.mdl");
	SetModel("models/scientist.mdl");
	Precache();

	InitBoneControllers();

	SetHullType(HULL_HUMAN);
	SetHullSizeNormal();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );
	m_fEffects			= 0;
	m_iHealth			= 50;
	
	m_bloodColor		= BLOOD_COLOR_RED;
	m_flFieldOfView		= VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result )

	m_NPCState			= NPC_STATE_NONE;

	CapabilitiesClear();
	CapabilitiesAdd( bits_CAP_TURN_HEAD );

	m_spawnflags |= SF_NPC_PREDISASTER; // predisaster only!

	if ( m_nBody == -1 )
	{// -1 chooses a random head
		m_nBody = random->RandomInt( 0, NUM_SCIENTIST_HEADS-1 );// pick a head, any head
	}
	// Luther is black, make his hands black
	if ( m_nBody == HEAD_LUTHER )
		 m_nBody = 1;
	
	m_baseSequence = LookupSequence( "sitlookleft" );
	SetSequence( m_baseSequence + random->RandomInt(0,4) );

	UTIL_DropToFloor( this,MASK_SOLID );

	ResetSequenceInfo( );

	NPCInit();
	
	SetThink (SittingThink);
	SetNextThink( gpGlobals->curtime + 0.1f );
}

void CNPC_SittingScientist :: Precache( void )
{
	m_baseSequence = LookupSequence( "sitlookleft" );
	TalkInit();
}


int CNPC_SittingScientist::FriendNumber( int arrayNumber )
{
	static int array[3] = { 2, 1, 0 };
	if ( arrayNumber < 3 )
		return array[ arrayNumber ];
	return arrayNumber;
}

//=========================================================
// sit, do stuff
//=========================================================
void CNPC_SittingScientist :: SittingThink( void )
{
	CBaseEntity *pent;	

	StudioFrameAdvance( );

	// try to greet player
	//FIXMEFIXME
	if ( GetExpresser()->CanSpeakConcept( TLK_HELLO ) )
	{
		pent = FindNamedEntity( "!nearestfriend" );
		if (pent)
		{
			float yaw = VecToYaw(pent->GetAbsOrigin() - GetAbsOrigin()) - GetAbsAngles().y;

			if (yaw > 180) yaw -= 360;
			if (yaw < -180) yaw += 360;
				
			if (yaw > 0)
				SetSequence( m_baseSequence + SITTING_ANIM_sitlookleft );
			else
				SetSequence ( m_baseSequence + SITTING_ANIM_sitlookright );
		
			ResetSequenceInfo( );
			m_flCycle = 0;
			SetBoneController( 0, 0 );

			GetExpresser()->Speak( TLK_HELLO );
		}
	}
	else if ( IsSequenceFinished() )
	{
		int i = random->RandomInt(0,99);
		m_iHeadTurn = 0;
		
		if (m_flResponseDelay && gpGlobals->curtime > m_flResponseDelay)
		{
			// respond to question
			GetExpresser()->Speak( TLK_QUESTION );
			SetSequence( m_baseSequence + SITTING_ANIM_sitscared );
			m_flResponseDelay = 0;
		}
		else if (i < 30)
		{
			SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );

			// turn towards player or nearest friend and speak

			//FIXME
		/*/	if (!FBitSet(m_nSpeak, bit_saidHelloPlayer))
				pent = FindNearestFriend(TRUE);
			else*/
				pent = FindNamedEntity( "!nearestfriend" );

			if (!FIdleSpeak() || !pent)
			{	
				m_iHeadTurn = random->RandomInt(0,8) * 10 - 40;
				SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );
			}
			else
			{
				// only turn head if we spoke
				float yaw = VecToYaw(pent->GetAbsOrigin() - GetAbsOrigin()) - GetAbsAngles().y;

				if (yaw > 180) yaw -= 360;
				if (yaw < -180) yaw += 360;
				
				if (yaw > 0)
					SetSequence( m_baseSequence + SITTING_ANIM_sitlookleft );
				else
					SetSequence( m_baseSequence + SITTING_ANIM_sitlookright );

				//ALERT(at_console, "sitting speak\n");
			}
		}
		else if (i < 60)
		{
			SetSequence( m_baseSequence + SITTING_ANIM_sitting3 );
			m_iHeadTurn = random->RandomInt(0,8) * 10 - 40;
			if ( random->RandomInt(0,99) < 5)
			{
				//ALERT(at_console, "sitting speak2\n");
				FIdleSpeak();
			}
		}
		else if (i < 80)
		{
			SetSequence( m_baseSequence + SITTING_ANIM_sitting2 );
		}
		else if (i < 100)
		{
			SetSequence( m_baseSequence + SITTING_ANIM_sitscared );
		}

		ResetSequenceInfo( );
		m_flCycle = 0;
		SetBoneController( 0, m_iHeadTurn );
	}

	SetNextThink( gpGlobals->curtime + 0.1f );
}

// prepare sitting scientist to answer a question
void CNPC_SittingScientist :: SetAnswerQuestion( CNPCSimpleTalker *pSpeaker )
{
	m_flResponseDelay = gpGlobals->curtime + random->RandomFloat(3, 4);
	SetSpeechTarget( (CNPCSimpleTalker *)pSpeaker );
}


//=========================================================
// FIdleSpeak
// ask question of nearby friend, or make statement
//=========================================================
int CNPC_SittingScientist :: FIdleSpeak ( void )
{ 
	// try to start a conversation, or make statement
	int pitch;
	
	if (!IsOkToSpeak())
		return FALSE;

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

	pitch = GetExpresser()->GetVoicePitch();
		
	// 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 = FindNamedEntity( "!nearestfriend" );

	if (pentFriend && random->RandomInt(0,1))
	{
//		CNPCSimpleTalker *pTalkMonster = (CNPCSimpleTalker *)pentFriend;
		//pTalkMonster->SetAnswerQuestion( this );

		SetSchedule( SCHED_TALKER_IDLE_RESPONSE );
		SetSpeechTarget( this );
		
		Msg( "Asking some question!\n" );
		
		IdleHeadTurn( pentFriend->GetAbsOrigin());
		SENTENCEG_PlayRndSz( edict(), TLK_PQUESTION, 1.0, SNDLVL_TALKING, 0, pitch );
		// set global min delay for next conversation
		GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + random->RandomFloat(4.8, 5.2) );
		return TRUE;
	}

	// otherwise, play an idle statement
	if ( random->RandomInt(0,1))
	{
		Msg( "Making idle statement!\n" );

		SENTENCEG_PlayRndSz( edict(), TLK_PIDLE, 1.0, SNDLVL_TALKING, 0, pitch );
		// set global min delay for next conversation
		GetExpresser()->BlockSpeechUntil( gpGlobals->curtime + random->RandomFloat(4.8, 5.2) );
		return TRUE;
	}

	// never spoke
	GetExpresser()->BlockSpeechUntil( 0 );
	return FALSE;
}

//------------------------------------------------------------------------------
//
// Schedules
//
//------------------------------------------------------------------------------

AI_BEGIN_CUSTOM_NPC( monster_scientist, CNPC_Scientist )

	DECLARE_TASK( TASK_SAY_HEAL )
	DECLARE_TASK( TASK_HEAL )
	DECLARE_TASK( TASK_SAY_FEAR )
	DECLARE_TASK( TASK_RUN_PATH_SCARED )
	DECLARE_TASK( TASK_SCREAM )
	DECLARE_TASK( TASK_RANDOM_SCREAM )
	DECLARE_TASK( TASK_MOVE_TO_TARGET_RANGE_SCARED )
	
	DECLARE_ACTIVITY( ACT_EXCITED )

	//=========================================================
	// > SCHED_SCI_HEAL
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_HEAL,

		"	Tasks"
		"		TASK_GET_PATH_TO_TARGET				0"
		"		TASK_MOVE_TO_TARGET_RANGE			50"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_SCI_FOLLOWTARGET"
		"		TASK_FACE_IDEAL						0"
		"		TASK_SAY_HEAL						0"
		"		TASK_PLAY_SEQUENCE_FACE_TARGET		ACTIVITY:ACT_ARM"
		"		TASK_HEAL							0"
		"		TASK_PLAY_SEQUENCE_FACE_TARGET		ACTIVITY:ACT_DISARM"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_SCI_FOLLOWTARGET
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_FOLLOWTARGET,

		"	Tasks"
//		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_SCI_STOPFOLLOWING"
		"		TASK_GET_PATH_TO_TARGET			0"
		"		TASK_MOVE_TO_TARGET_RANGE		128"
		"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_TARGET_FACE"	
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_COMBAT"
	)

	//=========================================================
	// > SCHED_SCI_STOPFOLLOWING
	//=========================================================
//	DEFINE_SCHEDULE
//	(
//		SCHED_SCI_STOPFOLLOWING,
//
//		"	Tasks"
//		"		TASK_TALKER_CANT_FOLLOW			0"
//		"	"
//		"	Interrupts"
//	)

	//=========================================================
	// > SCHED_SCI_FACETARGET
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_FACETARGET,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_FACE_TARGET			0"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE"
		"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_SCI_FOLLOWTARGET"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
		"		COND_HEAR_COMBAT"
		"		COND_GIVE_WAY"
	)

	//=========================================================
	// > SCHED_SCI_COVER
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_COVER,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE			SCHEDULE:SCHED_SCI_PANIC"
		"		TASK_STOP_MOVING				0"
		"		TASK_FIND_COVER_FROM_ENEMY		0"
		"		TASK_RUN_PATH_SCARED			0"
		"		TASK_TURN_LEFT					179"
		"		TASK_SET_SCHEDULE				SCHEDULE:SCHED_SCI_HIDE"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
	)

	//=========================================================
	// > SCHED_SCI_HIDE
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_HIDE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE		SCHEDULE:SCHED_SCI_PANIC"
		"		TASK_STOP_MOVING			0"
		"		TASK_PLAY_SEQUENCE			ACTIVITY:ACT_CROUCHIDLE"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_CROUCHIDLE"
		"		TASK_WAIT_RANDOM			10"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"
		"		COND_SEE_HATE"
		"		COND_SEE_FEAR"
		"		COND_SEE_DISLIKE"
		"		COND_HEAR_DANGER"
	)

	//=========================================================
	// > SCHED_SCI_IDLESTAND
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_IDLESTAND,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_IDLE"
		"		TASK_WAIT					2"
		"		TASK_TALKER_HEADRESET		0"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_SMELL"
		"		COND_PROVOKED"
		"		COND_HEAR_COMBAT"
		"		COND_GIVE_WAY"
	)

	//=========================================================
	// > SCHED_SCI_PANIC
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_PANIC,

		"	Tasks"
		"		TASK_STOP_MOVING					0"
		"		TASK_FACE_ENEMY						0"
		"		TASK_SCREAM							0"
		"		TASK_PLAY_SEQUENCE_FACE_ENEMY		ACTIVITY:ACT_EXCITED"
		"		TASK_SET_ACTIVITY					ACTIVITY:ACT_IDLE"
		"	"
		"	Interrupts"
	)

	//=========================================================
	// > SCHED_SCI_FOLLOWSCARED
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_FOLLOWSCARED,

		"	Tasks"
		"		TASK_GET_PATH_TO_TARGET				0"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_SCI_FOLLOWTARGET"	
		"		TASK_MOVE_TO_TARGET_RANGE_SCARED	128"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_LIGHT_DAMAGE"
		"		COND_HEAVY_DAMAGE"
		"		COND_HEAR_DANGER"
	)

	//=========================================================
	// > SCHED_SCI_FACETARGETSCARED
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_FACETARGETSCARED,

		"	Tasks"
		"	TASK_FACE_TARGET				0"
		"		TASK_SET_ACTIVITY			ACTIVITY:ACT_CROUCHIDLE"
		"		TASK_SET_SCHEDULE			SCHEDULE:SCHED_SCI_FOLLOWSCARED"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_HEAR_DANGER"
	)

	//=========================================================
	// > SCHED_FEAR
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_FEAR,

		"	Tasks"
		"		TASK_STOP_MOVING			0"
		"		TASK_FACE_ENEMY				0"
		"		TASK_SAY_FEAR				0"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
	)

	//=========================================================
	// > SCHED_SCI_STARTLE
	//=========================================================
	DEFINE_SCHEDULE
	(
		SCHED_SCI_STARTLE,

		"	Tasks"
		"		TASK_SET_FAIL_SCHEDULE				SCHEDULE:SCHED_SCI_PANIC"
		"		TASK_RANDOM_SCREAM					0.3"
		"		TASK_STOP_MOVING					0"
		"		TASK_PLAY_SEQUENCE_FACE_ENEMY		ACTIVITY:ACT_CROUCH"
		"		TASK_RANDOM_SCREAM					0.1"
		"		TASK_PLAY_SEQUENCE_FACE_ENEMY		ACTIVITY:ACT_CROUCHIDLE"
		"		TASK_WAIT_RANDOM					1"
		"	"
		"	Interrupts"
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"
		"		COND_SEE_HATE"
		"		COND_SEE_FEAR"
		"		COND_SEE_DISLIKE"
	)

AI_END_CUSTOM_NPC()
