/***
*
*	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
*	
*	This product contains software technology licensed from Id 
*	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc. 
*	All Rights Reserved.
*
*   This source code contains proprietary and confidential information of
*   Valve LLC and its suppliers.  Access to this code is restricted to
*   persons who have executed a written SDK license with Valve.  Any access,
*   use or distribution of this code by or to any unlicensed person is illegal.
*
****/

#include	"extdll.h"
#include	"util.h"
#include	"cbase.h"
#include	"monsters.h"
#include	"schedule.h"
#include	"talkmonster.h"
#include	"defaultai.h"
#include	"scripted.h"
#include	"soundent.h"
#include	"animation.h"

//=========================================================
// Talking monster base class
// Used for scientists and barneys
//=========================================================
float CTalkMonster::g_talkWaitTime = 0;		// time delay until it's ok to speak: used so that two NPCs don't talk at once

// NOTE: m_voicePitch & m_szGrp should be fixed up by precache each save/restore

TYPEDESCRIPTION	CTalkMonster::m_SaveData[] =
{
	DEFINE_FIELD( CTalkMonster, m_bitsSaid, FIELD_INTEGER ),
	DEFINE_FIELD( CTalkMonster, m_nSpeak, FIELD_INTEGER ),

	// Recalc'ed in Precache()
	//DEFINE_FIELD( CTalkMonster, m_voicePitch, FIELD_INTEGER ),
	//DEFINE_FIELD( CTalkMonster, m_szGrp, FIELD_??? ),
	DEFINE_FIELD( CTalkMonster, m_useTime, FIELD_TIME ),
	DEFINE_FIELD( CTalkMonster, m_iszUse, FIELD_STRING ),
	DEFINE_FIELD( CTalkMonster, m_iszUnUse, FIELD_STRING ),
	DEFINE_FIELD( CTalkMonster, m_flLastSaidSmelled, FIELD_TIME ),
	DEFINE_FIELD( CTalkMonster, m_flStopTalkTime, FIELD_TIME ),
	DEFINE_FIELD( CTalkMonster, m_hTalkTarget, FIELD_EHANDLE ),
};

IMPLEMENT_SAVERESTORE( CTalkMonster, CBaseMonster )

// array of friend names
const char *CTalkMonster::m_szFriends[TLK_CFRIENDS] =
{
	"monster_barney",
	"monster_scientist",
	"monster_sitting_scientist",
};

//=========================================================
// AI Schedules Specific to talking monsters
//=========================================================
Task_t tlIdleResponse[] =
{
	{ TASK_SET_ACTIVITY, (float)ACT_IDLE },	// Stop and listen
	{ TASK_WAIT, 0.5f },	// Wait until sure it's me they are talking to
	{ TASK_TLK_EYECONTACT, 0.0f },	// Wait until speaker is done
	{ TASK_TLK_RESPOND, 0.0f },	// Wait and then say my response
	{ TASK_TLK_IDEALYAW, 0.0f },	// look at who I'm talking to
	{ TASK_FACE_IDEAL, 0.0f }, 
	{ TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },
	{ TASK_TLK_EYECONTACT, 0.0f },	// Wait until speaker is done
};

Schedule_t slIdleResponse[] =
{
	{
		tlIdleResponse,
		ARRAYSIZE( tlIdleResponse ),
		bits_COND_NEW_ENEMY |
		bits_COND_LIGHT_DAMAGE |
		bits_COND_HEAVY_DAMAGE,
		0,
		"Idle Response"
	},
};

Task_t tlIdleSpeak[] =
{
	{ TASK_TLK_SPEAK, 0.0f },// question or remark
	{ TASK_TLK_IDEALYAW, 0.0f },// look at who I'm talking to
	{ TASK_FACE_IDEAL, 0.0f },
	{ TASK_SET_ACTIVITY, (float)ACT_SIGNAL3	},
	{ TASK_TLK_EYECONTACT, 0.0f },
	{ TASK_WAIT_RANDOM, 0.5f },
};

Schedule_t slIdleSpeak[] =
{
	{
		tlIdleSpeak,
		ARRAYSIZE( tlIdleSpeak ),
		bits_COND_NEW_ENEMY |
		bits_COND_CLIENT_PUSH |
		bits_COND_LIGHT_DAMAGE |
		bits_COND_HEAVY_DAMAGE,
		0,
		"Idle Speak"
	},
};

Task_t	tlIdleSpeakWait[] =
{
	{ TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk
	{ TASK_TLK_SPEAK, 0.0f },// question or remark
	{ TASK_TLK_EYECONTACT, 0.0f },// 
	{ TASK_WAIT, 2.0f },// wait - used when sci is in 'use' mode to keep head turned
};

Schedule_t slIdleSpeakWait[] =
{
	{
		tlIdleSpeakWait,
		ARRAYSIZE( tlIdleSpeakWait ),
		bits_COND_NEW_ENEMY |
		bits_COND_CLIENT_PUSH |
		bits_COND_LIGHT_DAMAGE |
		bits_COND_HEAVY_DAMAGE,
		0,
		"Idle Speak Wait"
	},
};

Task_t tlIdleHello[] =
{
	{ TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk
	{ TASK_TLK_HELLO, 0.0f },// Try to say hello to player
	{ TASK_TLK_EYECONTACT, 0.0f },
	{ TASK_WAIT, 0.5f },// wait a bit
	{ TASK_TLK_HELLO, 0.0f },// Try to say hello to player
	{ TASK_TLK_EYECONTACT, 0.0f },
	{ TASK_WAIT, 0.5f },// wait a bit
	{ TASK_TLK_HELLO, 0.0f },// Try to say hello to player
	{ TASK_TLK_EYECONTACT, 0.0f },
	{ TASK_WAIT, 0.5f },// wait a bit
	{ TASK_TLK_HELLO, 0.0f },// Try to say hello to player
	{ TASK_TLK_EYECONTACT, 0.0f },
	{ TASK_WAIT, 0.5f },// wait a bit
};

Schedule_t slIdleHello[] =
{
	{
		tlIdleHello,
		ARRAYSIZE( tlIdleHello ),
		bits_COND_NEW_ENEMY |
		bits_COND_CLIENT_PUSH |
		bits_COND_LIGHT_DAMAGE |
		bits_COND_HEAVY_DAMAGE |
		bits_COND_HEAR_SOUND |
		bits_COND_PROVOKED,
		bits_SOUND_COMBAT,
		"Idle Hello"
	},
};

Task_t tlIdleStopShooting[] =
{
	{ TASK_TLK_STOPSHOOTING, 0.0f },// tell player to stop shooting friend
	// { TASK_TLK_EYECONTACT, 0.0f },// look at the player
};

Schedule_t slIdleStopShooting[] =
{
	{
		tlIdleStopShooting,
		ARRAYSIZE( tlIdleStopShooting ),
		bits_COND_NEW_ENEMY |
		bits_COND_LIGHT_DAMAGE |
		bits_COND_HEAVY_DAMAGE |
		bits_COND_HEAR_SOUND,
		0,
		"Idle Stop Shooting"
	},
};

Task_t tlMoveAway[] =
{
	{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_MOVE_AWAY_FAIL },
	{ TASK_STORE_LASTPOSITION, 0.0f },
	{ TASK_MOVE_AWAY_PATH, 100.0f },
	{ TASK_WALK_PATH_FOR_UNITS, 100.0f },
	{ TASK_STOP_MOVING, 0.0f },
	{ TASK_FACE_PLAYER, 0.5f },
};

Schedule_t slMoveAway[] =
{
	{
		tlMoveAway,
		ARRAYSIZE( tlMoveAway ),
		0,
		0,
		"MoveAway"
	},
};

Task_t tlMoveAwayFail[] =
{
	{ TASK_STOP_MOVING, 0.0f },
	{ TASK_FACE_PLAYER, 0.5f },
};

Schedule_t slMoveAwayFail[] =
{
	{
		tlMoveAwayFail,
		ARRAYSIZE( tlMoveAwayFail ),
		0,
		0,
		"MoveAwayFail"
	},
};

Task_t tlMoveAwayFollow[] =
{
	{ TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_FACE },
	{ TASK_STORE_LASTPOSITION, (float)0 },
	{ TASK_MOVE_AWAY_PATH, (float)100 },
	{ TASK_WALK_PATH_FOR_UNITS, (float)100 },
	{ TASK_STOP_MOVING, (float)0 },
	{ TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE },
};

Schedule_t slMoveAwayFollow[] =
{
	{
		tlMoveAwayFollow,
		ARRAYSIZE( tlMoveAwayFollow ),
		0,
		0,
		"MoveAwayFollow"
	},
};

Task_t tlTlkIdleWatchClient[] =
{
	{ TASK_STOP_MOVING, 0 },
	{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
	{ TASK_TLK_LOOK_AT_CLIENT, 6.0f },
};

Task_t tlTlkIdleWatchClientStare[] =
{
	{ TASK_STOP_MOVING, 0 },
	{ TASK_SET_ACTIVITY, (float)ACT_IDLE },
	{ TASK_TLK_CLIENT_STARE, 6.0f },
	{ TASK_TLK_STARE, 0.0f },
	{ TASK_TLK_IDEALYAW, 0.0f },// look at who I'm talking to
	{ TASK_FACE_IDEAL, 0.0f }, 
	{ TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },
	{ TASK_TLK_EYECONTACT, 0.0f },
};

Schedule_t slTlkIdleWatchClient[] =
{
	{
		tlTlkIdleWatchClient,
		ARRAYSIZE( tlTlkIdleWatchClient ),
		bits_COND_NEW_ENEMY |
		bits_COND_LIGHT_DAMAGE |
		bits_COND_HEAVY_DAMAGE |
		bits_COND_HEAR_SOUND |
		bits_COND_SMELL |
		bits_COND_CLIENT_PUSH |
		bits_COND_CLIENT_UNSEEN |
		bits_COND_PROVOKED,
		bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code.
		//bits_SOUND_PLAYER |
		//bits_SOUND_WORLD |
		bits_SOUND_DANGER |
		bits_SOUND_MEAT |// scents
		bits_SOUND_CARCASS |
		bits_SOUND_GARBAGE,
		"TlkIdleWatchClient"
	},

	{
		tlTlkIdleWatchClientStare,
		ARRAYSIZE( tlTlkIdleWatchClientStare ),
		bits_COND_NEW_ENEMY |
		bits_COND_LIGHT_DAMAGE |
		bits_COND_HEAVY_DAMAGE |
		bits_COND_HEAR_SOUND |
		bits_COND_SMELL |
		bits_COND_CLIENT_PUSH |
		bits_COND_CLIENT_UNSEEN |
		bits_COND_PROVOKED,
		bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code.
		//bits_SOUND_PLAYER |
		//bits_SOUND_WORLD |
		bits_SOUND_DANGER |
		bits_SOUND_MEAT |// scents
		bits_SOUND_CARCASS |
		bits_SOUND_GARBAGE,
		"TlkIdleWatchClientStare"
	},
};

Task_t	tlTlkIdleEyecontact[] =
{
	{ TASK_TLK_IDEALYAW, 0.0f },// look at who I'm talking to
	{ TASK_FACE_IDEAL, 0.0f }, 
	{ TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },
	{ TASK_TLK_EYECONTACT, 0.0f },// Wait until speaker is done
};

Schedule_t slTlkIdleEyecontact[] =
{
	{
		tlTlkIdleEyecontact,
		ARRAYSIZE( tlTlkIdleEyecontact ), 
		bits_COND_NEW_ENEMY |
		bits_COND_CLIENT_PUSH |
		bits_COND_LIGHT_DAMAGE |
		bits_COND_HEAVY_DAMAGE,
		0,
		"TlkIdleEyecontact"
	},
};

DEFINE_CUSTOM_SCHEDULES( CTalkMonster )
{
	slIdleResponse,
	slIdleSpeak,
	slIdleHello,
	slIdleSpeakWait,
	slIdleStopShooting,
	slMoveAway,
	slMoveAwayFollow,
	slMoveAwayFail,
	slTlkIdleWatchClient,
	&slTlkIdleWatchClient[1],
	slTlkIdleEyecontact,
};

IMPLEMENT_CUSTOM_SCHEDULES( CTalkMonster, CBaseMonster )

void CTalkMonster::SetActivity( Activity newActivity )
{
	if( newActivity == ACT_IDLE && IsTalking() )
		newActivity = ACT_SIGNAL3;

	if( newActivity == ACT_SIGNAL3 && ( LookupActivity( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE ) )
		newActivity = ACT_IDLE;

	CBaseMonster::SetActivity( newActivity );
}

void CTalkMonster::StartTask( Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_TLK_SPEAK:
		// ask question or make statement
		FIdleSpeak();
		TaskComplete();
		break;
	case TASK_TLK_RESPOND:
		// respond to question
		IdleRespond();
		TaskComplete();
		break;
	case TASK_TLK_HELLO:
		// greet player
		FIdleHello();
		TaskComplete();
		break;
	case TASK_TLK_STARE:
		// let the player know I know he's staring at me.
		FIdleStare();
		TaskComplete();
		break;
	case TASK_FACE_PLAYER:
	case TASK_TLK_LOOK_AT_CLIENT:
	case TASK_TLK_CLIENT_STARE:
		// track head to the client for a while.
		m_flWaitFinished = gpGlobals->time + pTask->flData;
		break;
	case TASK_TLK_EYECONTACT:
		break;
	case TASK_TLK_IDEALYAW:
		if( m_hTalkTarget != 0 )
		{
			pev->yaw_speed = 60;
			float yaw = VecToYaw( m_hTalkTarget->pev->origin - pev->origin ) - pev->angles.y;

			if( yaw > 180 )
				yaw -= 360;
			if( yaw < -180 )
				yaw += 360;

			if( yaw < 0 )
			{
				pev->ideal_yaw = Q_min( yaw + 45, 0 ) + pev->angles.y;
			}
			else
			{
				pev->ideal_yaw = Q_max( yaw - 45, 0 ) + pev->angles.y;
			}
		}
		TaskComplete();
		break;
	case TASK_TLK_HEADRESET:
		// reset head position after looking at something
		m_hTalkTarget = NULL;
		TaskComplete();
		break;
	case TASK_TLK_STOPSHOOTING:
		// tell player to stop shooting
		PlaySentence( m_szGrp[TLK_NOSHOOT], RANDOM_FLOAT( 2.8, 3.2 ), VOL_NORM, ATTN_NORM );
		TaskComplete();
		break;
	case TASK_CANT_FOLLOW:
		StopFollowing( FALSE );
		PlaySentence( m_szGrp[TLK_STOP], RANDOM_FLOAT( 2, 2.5 ), VOL_NORM, ATTN_NORM );
		TaskComplete();
		break;
	case TASK_WALK_PATH_FOR_UNITS:
		m_movementActivity = ACT_WALK;
		break;
	case TASK_MOVE_AWAY_PATH:
		{
			Vector dir = pev->angles;
			dir.y = pev->ideal_yaw + 180;
			Vector move;

			UTIL_MakeVectorsPrivate( dir, move, NULL, NULL );
			dir = pev->origin + move * pTask->flData;
			if( MoveToLocation( ACT_WALK, 2, dir ) )
			{
				TaskComplete();
			}
			else if( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) )
			{
				// then try for plain ole cover
				m_flMoveWaitFinished = gpGlobals->time + 2.0f;
				TaskComplete();
			}
			else
			{
				// nowhere to go?
				TaskFail();
			}
		}
		break;
	case TASK_PLAY_SCRIPT:
		m_hTalkTarget = NULL;
		CBaseMonster::StartTask( pTask );
		break;
	default:
		CBaseMonster::StartTask( pTask );
	}
}

void CTalkMonster::RunTask( Task_t *pTask )
{
	edict_t *pPlayer;

	switch( pTask->iTask )
	{
	case TASK_TLK_CLIENT_STARE:
	case TASK_TLK_LOOK_AT_CLIENT:
		// track head to the client for a while.
		if( m_MonsterState == MONSTERSTATE_IDLE &&
			 !IsMoving() &&
			 !IsTalking() )
		{
			// Get edict for one player
			pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 );

			if( pPlayer )
			{
				IdleHeadTurn( pPlayer->v.origin );
			}
		}
		else
		{
			// started moving or talking
			TaskFail();
			return;
		}

		if( pTask->iTask == TASK_TLK_CLIENT_STARE )
		{
			if( pPlayer )
			{
				// fail out if the player looks away or moves away.
				if( ( pPlayer->v.origin - pev->origin ).Length2D() > TLK_STARE_DIST )
				{
					// player moved away.
					TaskFail();
				}

				UTIL_MakeVectors( pPlayer->v.angles );
				if( UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) < m_flFieldOfView )
				{
					// player looked away
					TaskFail();
				}
			}
		}

		if( gpGlobals->time > m_flWaitFinished )
		{
			TaskComplete();
		}
		break;
	case TASK_FACE_PLAYER:
		{
			// Get edict for one player
			pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 );

			if( pPlayer )
			{
				MakeIdealYaw( pPlayer->v.origin );
				ChangeYaw( pev->yaw_speed );
				IdleHeadTurn( pPlayer->v.origin );
				if( gpGlobals->time > m_flWaitFinished && FlYawDiff() < 10 )
				{
					TaskComplete();
				}
			}
			else
			{
				TaskFail();
			}
		}
		break;
	case TASK_TLK_EYECONTACT:
		if( !IsMoving() && IsTalking() && m_hTalkTarget != 0 )
		{
			// ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time );
			IdleHeadTurn( m_hTalkTarget->pev->origin );
		}
		else
		{
			TaskComplete();
		}
		break;
	case TASK_WALK_PATH_FOR_UNITS:
		{
			float distance;

			distance = ( m_vecLastPosition - pev->origin ).Length2D();

			// Walk path until far enough away
			if( distance > pTask->flData || MovementIsComplete() )
			{
				TaskComplete();
				RouteClear();		// Stop moving
			}
		}
		break;
	case TASK_WAIT_FOR_MOVEMENT:
		if( IsTalking() && m_hTalkTarget != 0 )
		{
			// ALERT( at_console, "walking, talking\n" );
			IdleHeadTurn( m_hTalkTarget->pev->origin );
		}
		else
		{
			IdleHeadTurn( pev->origin );
			// override so that during walk, a scientist may talk and greet player
			FIdleHello();
			if( RANDOM_LONG( 0, m_nSpeak * 20 ) == 0)
			{
				FIdleSpeak();
			}
		}

		CBaseMonster::RunTask( pTask );
		if( TaskIsComplete() )
			IdleHeadTurn( pev->origin );
		break;
	default:
		if( IsTalking() && m_hTalkTarget != 0 )
		{
			IdleHeadTurn( m_hTalkTarget->pev->origin );
		}
		else
		{
			SetBoneController( 0, 0 );
		}
		CBaseMonster::RunTask( pTask );
	}
}

void CTalkMonster::Killed( entvars_t *pevAttacker, int iGib )
{
	// If a client killed me (unless I was already Barnacle'd), make everyone else mad/afraid of him
	if( ( pevAttacker->flags & FL_CLIENT) && m_MonsterState != MONSTERSTATE_PRONE )
	{
		AlertFriends();
		LimitFollowers( CBaseEntity::Instance( pevAttacker ), 0 );
	}

	m_hTargetEnt = 0;
	// Don't finish that sentence
	StopTalking();
	SetUse( NULL );
	CBaseMonster::Killed( pevAttacker, iGib );
}

CBaseEntity *CTalkMonster::EnumFriends( CBaseEntity *pPrevious, int listNumber, BOOL bTrace )
{
	CBaseEntity *pFriend = pPrevious;
	const char *pszFriend;
	TraceResult tr;
	Vector vecCheck;

	pszFriend = m_szFriends[FriendNumber( listNumber )];
	while( ( pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend ) ) )
	{
		if( pFriend == this || !pFriend->IsAlive() )
			// don't talk to self or dead people
			continue;
		if( bTrace )
		{
			vecCheck = pFriend->pev->origin;
			vecCheck.z = pFriend->pev->absmax.z;

			UTIL_TraceLine( pev->origin, vecCheck, ignore_monsters, ENT( pev ), &tr );
		}
		else
			tr.flFraction = 1.0f;

		if( tr.flFraction == 1.0f )
		{
			return pFriend;
		}
	}

	return NULL;
}

void CTalkMonster::AlertFriends( void )
{
	CBaseEntity *pFriend = NULL;
	int i;

	// for each friend in this bsp...
	for( i = 0; i < TLK_CFRIENDS; i++ )
	{
		while( ( pFriend = EnumFriends( pFriend, i, TRUE ) ) )
		{
			CBaseMonster *pMonster = pFriend->MyMonsterPointer();
			if( pMonster->IsAlive() )
			{
				// don't provoke a friend that's playing a death animation. They're a goner
				pMonster->m_afMemory |= bits_MEMORY_PROVOKED;
			}
		}
	}
}

void CTalkMonster::ShutUpFriends( void )
{
	CBaseEntity *pFriend = NULL;
	int i;

	// for each friend in this bsp...
	for( i = 0; i < TLK_CFRIENDS; i++ )
	{
		while( ( pFriend = EnumFriends( pFriend, i, TRUE ) ) )
		{
			CBaseMonster *pMonster = pFriend->MyMonsterPointer();
			if( pMonster )
			{
				pMonster->SentenceStop();
			}
		}
	}
}

// UNDONE: Keep a follow time in each follower, make a list of followers in this function and do LRU
// UNDONE: Check this in Restore to keep restored monsters from joining a full list of followers
void CTalkMonster::LimitFollowers( CBaseEntity *pPlayer, int maxFollowers )
{
	CBaseEntity *pFriend = NULL;
	int i, count;

	count = 0;

	// for each friend in this bsp...
	for( i = 0; i < TLK_CFRIENDS; i++ )
	{
		while( ( pFriend = EnumFriends( pFriend, i, FALSE ) ) )
		{
			CBaseMonster *pMonster = pFriend->MyMonsterPointer();
			if( pMonster )
			{
				if( pMonster->m_hTargetEnt == pPlayer )
				{
					count++;
					if( count > maxFollowers )
						pMonster->StopFollowing( TRUE );
				}
			}
		}
	}
}

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

	return ( m_hTargetEnt->pev->origin - pev->origin ).Length();
}

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CTalkMonster::HandleAnimEvent( MonsterEvent_t *pEvent )
{
	switch( pEvent->event )
	{
	case SCRIPT_EVENT_SENTENCE_RND1:		// Play a named sentence group 25% of the time
		if( RANDOM_LONG( 0, 99 ) < 75 )
			break;
		// fall through...
	case SCRIPT_EVENT_SENTENCE:				// Play a named sentence group
		ShutUpFriends();
		PlaySentence( pEvent->options, RANDOM_FLOAT( 2.8f, 3.4f ), VOL_NORM, ATTN_IDLE );
		//ALERT( at_console, "script event speak\n" );
		break;
	default:
		CBaseMonster::HandleAnimEvent( pEvent );
		break;
	}
}

// monsters derived from ctalkmonster should call this in precache()
void CTalkMonster::TalkInit( void )
{
	// every new talking monster must reset this global, otherwise
	// when a level is loaded, nobody will talk (time is reset to 0)
	CTalkMonster::g_talkWaitTime = 0;

	m_voicePitch = 100;
}	
//=========================================================
// FindNearestFriend
// Scan for nearest, visible friend. If fPlayer is true, look for
// nearest player
//=========================================================
CBaseEntity *CTalkMonster::FindNearestFriend( BOOL fPlayer )
{
	CBaseEntity *pFriend = NULL;
	CBaseEntity *pNearest = NULL;
	float range = 10000000.0;
	TraceResult tr;
	Vector vecStart = pev->origin;
	Vector vecCheck;
	int i;
	const char *pszFriend;
	int cfriends;

	vecStart.z = pev->absmax.z;

	if( fPlayer )
		cfriends = 1;
	else
		cfriends = TLK_CFRIENDS;

	// for each type of friend...
	for( i = cfriends-1; i > -1; i-- )
	{
		if( fPlayer )
			pszFriend = "player";
		else
			pszFriend = m_szFriends[FriendNumber( i )];

		if( !pszFriend )
			continue;

		// for each friend in this bsp...
		while( ( pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend ) ) )
		{
			if( pFriend == this || !pFriend->IsAlive() )
				// don't talk to self or dead people
				continue;

			CBaseMonster *pMonster = pFriend->MyMonsterPointer();

			// If not a monster for some reason, or in a script, or prone
			if( !pMonster || pMonster->m_MonsterState == MONSTERSTATE_SCRIPT || pMonster->m_MonsterState == MONSTERSTATE_PRONE )
				continue;

			vecCheck = pFriend->pev->origin;
			vecCheck.z = pFriend->pev->absmax.z;

			// if closer than previous friend, and in range, see if he's visible
			if( range > ( vecStart - vecCheck ).Length())
			{
				UTIL_TraceLine( vecStart, vecCheck, ignore_monsters, ENT( pev ), &tr );

				if( tr.flFraction == 1.0f )
				{
					// visible and in range, this is the new nearest scientist
					if( ( vecStart - vecCheck ).Length() < TALKRANGE_MIN )
					{
						pNearest = pFriend;
						range = ( vecStart - vecCheck ).Length();
					}
				}
			}
		}
	}
	return pNearest;
}

int CTalkMonster::GetVoicePitch( void )
{
	return m_voicePitch + RANDOM_LONG( 0, 3 );
}

void CTalkMonster::Touch( CBaseEntity *pOther )
{
	// Did the player touch me?
	if( pOther->IsPlayer() )
	{
		// Ignore if pissed at player
		if( m_afMemory & bits_MEMORY_PROVOKED )
			return;

		// Stay put during speech
		if( IsTalking() )
			return;

		// Heuristic for determining if the player is pushing me away
		float speed = fabs( pOther->pev->velocity.x ) + fabs( pOther->pev->velocity.y );
		if( speed > 50.0f )
		{
			SetConditions( bits_COND_CLIENT_PUSH );
			MakeIdealYaw( pOther->pev->origin );
		}
	}
}

//=========================================================
// IdleRespond
// Respond to a previous question
//=========================================================
void CTalkMonster::IdleRespond( void )
{
	//int pitch = GetVoicePitch();

	// play response
	PlaySentence( m_szGrp[TLK_ANSWER], RANDOM_FLOAT( 2.8f, 3.2f ), VOL_NORM, ATTN_IDLE );
}

int CTalkMonster::FOkToSpeak( void )
{
	// if in the grip of a barnacle, don't speak
	if( m_MonsterState == MONSTERSTATE_PRONE || m_IdealMonsterState == MONSTERSTATE_PRONE )
	{
		return FALSE;
	}

	// if not alive, certainly don't speak
	if( pev->deadflag != DEAD_NO )
	{
		return FALSE;
	}

	// if someone else is talking, don't speak
	if( gpGlobals->time <= CTalkMonster::g_talkWaitTime )
		return FALSE;

	if( pev->spawnflags & SF_MONSTER_GAG )
		return FALSE;

	if( m_MonsterState == MONSTERSTATE_PRONE )
		return FALSE;

	// if player is not in pvs, don't speak
	if( !IsAlive() || FNullEnt(FIND_CLIENT_IN_PVS( edict() ) ) )
		return FALSE;

	// don't talk if you're in combat
	if( m_hEnemy != 0 && FVisible( m_hEnemy ) )
		return FALSE;

	return TRUE;
}

int CTalkMonster::CanPlaySentence( BOOL fDisregardState ) 
{ 
	if( fDisregardState )
		return CBaseMonster::CanPlaySentence( fDisregardState );
	return FOkToSpeak(); 
}

//=========================================================
// FIdleStare
//=========================================================
int CTalkMonster::FIdleStare( void )
{
	if( !FOkToSpeak() )
		return FALSE;

	PlaySentence( m_szGrp[TLK_STARE], RANDOM_FLOAT(5, 7.5), VOL_NORM, ATTN_IDLE );

	m_hTalkTarget = FindNearestFriend( TRUE );
	return TRUE;
}

//=========================================================
// IdleHello
// Try to greet player first time he's seen
//=========================================================
int CTalkMonster::FIdleHello( void )
{
	if( !FOkToSpeak() )
		return FALSE;

	// if this is first time scientist has seen player, greet him
	if( !FBitSet( m_bitsSaid, bit_saidHelloPlayer ) )
	{
		// get a player
		CBaseEntity *pPlayer = FindNearestFriend( TRUE );

		if( pPlayer )
		{
			if( FInViewCone( pPlayer ) && FVisible( pPlayer ) )
			{
				m_hTalkTarget = pPlayer;

				if( FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER ) )
					PlaySentence( m_szGrp[TLK_PHELLO], RANDOM_FLOAT( 3.0f, 3.5f ), VOL_NORM,  ATTN_IDLE );
				else
					PlaySentence( m_szGrp[TLK_HELLO], RANDOM_FLOAT( 3.0f, 3.5f ), VOL_NORM,  ATTN_IDLE );

				SetBits( m_bitsSaid, bit_saidHelloPlayer );

				return TRUE;
			}
		}
	}
	return FALSE;
}

// turn head towards supplied origin
void CTalkMonster::IdleHeadTurn( Vector &vecFriend )
{
	// turn head in desired direction only if ent has a turnable head
	if( m_afCapability & bits_CAP_TURN_HEAD )
	{
		float yaw = VecToYaw( vecFriend - pev->origin ) - pev->angles.y;

		if( yaw > 180 )
			yaw -= 360;
		if( yaw < -180 )
			yaw += 360;

		// turn towards vector
		SetBoneController( 0, yaw );
	}
}

//=========================================================
// FIdleSpeak
// ask question of nearby friend, or make statement
//=========================================================
int CTalkMonster::FIdleSpeak( void )
{ 
	// try to start a conversation, or make statement
	//int pitch;
	const char *szIdleGroup;
	const char *szQuestionGroup;
	float duration;

	if( !FOkToSpeak() )
		return FALSE;

	// set idle groups based on pre/post disaster
	if( FBitSet( pev->spawnflags, SF_MONSTER_PREDISASTER ) )
	{
		szIdleGroup = m_szGrp[TLK_PIDLE];
		szQuestionGroup = m_szGrp[TLK_PQUESTION];
		// set global min delay for next conversation
		duration = RANDOM_FLOAT( 4.8f, 5.2f );
	}
	else
	{
		szIdleGroup = m_szGrp[TLK_IDLE];
		szQuestionGroup = m_szGrp[TLK_QUESTION];
		// set global min delay for next conversation
		duration = RANDOM_FLOAT( 2.8f, 3.2f );
	}

	//pitch = GetVoicePitch();

	// player using this entity is alive and wounded?
	CBaseEntity *pTarget = m_hTargetEnt;

	if( pTarget != NULL )
	{
		if( pTarget->IsPlayer() )
		{
			if( pTarget->IsAlive() )
			{
				m_hTalkTarget = m_hTargetEnt;
				if( !FBitSet(m_bitsSaid, bit_saidDamageHeavy ) && 
					( m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 8 ) )
				{
					//EMIT_SOUND_DYN(ENT( pev ), CHAN_VOICE, m_szGrp[TLK_PLHURT3], 1.0, ATTN_IDLE, 0, pitch );
					PlaySentence( m_szGrp[TLK_PLHURT3], duration, VOL_NORM, ATTN_IDLE );
					SetBits( m_bitsSaid, bit_saidDamageHeavy );
					return TRUE;
				}
				else if( !FBitSet( m_bitsSaid, bit_saidDamageMedium ) && 
					( m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 4 ) )
				{
					//EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT2], 1.0, ATTN_IDLE, 0, pitch );
					PlaySentence( m_szGrp[TLK_PLHURT2], duration, VOL_NORM, ATTN_IDLE );
					SetBits( m_bitsSaid, bit_saidDamageMedium );
					return TRUE;
				}
				else if( !FBitSet( m_bitsSaid, bit_saidDamageLight) &&
					( m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 2 ) )
				{
					//EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, m_szGrp[TLK_PLHURT1], 1.0, ATTN_IDLE, 0, pitch );
					PlaySentence( m_szGrp[TLK_PLHURT1], duration, VOL_NORM, ATTN_IDLE );
					SetBits( m_bitsSaid, bit_saidDamageLight );
					return TRUE;
				}
			}
			else
			{
				//!!!KELLY - here's a cool spot to have the talkmonster talk about the dead player if we want.
				// "Oh dear, Gordon Freeman is dead!" -Scientist
				// "Damn, I can't do this without you." -Barney
			}
		}
	}

	// if there is a friend nearby to speak to, play sentence, set friend's response time, return
	CBaseEntity *pFriend = FindNearestFriend( FALSE );

	if( pFriend && !( pFriend->IsMoving() ) && ( RANDOM_LONG( 0, 99 ) < 75 ) )
	{
		PlaySentence( szQuestionGroup, duration, VOL_NORM, ATTN_IDLE );
		//SENTENCEG_PlayRndSz( ENT( pev ), szQuestionGroup, 1.0, ATTN_IDLE, 0, pitch );

		// force friend to answer
		CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend;
		m_hTalkTarget = pFriend;
		pTalkMonster->SetAnswerQuestion( this ); // UNDONE: This is EVIL!!!
		pTalkMonster->m_flStopTalkTime = m_flStopTalkTime;

		m_nSpeak++;
		return TRUE;
	}

	// otherwise, play an idle statement, try to face client when making a statement.
	if( RANDOM_LONG( 0, 1 ) )
	{
		//SENTENCEG_PlayRndSz( ENT( pev ), szIdleGroup, 1.0, ATTN_IDLE, 0, pitch );
		pFriend = FindNearestFriend( TRUE );

		if( pFriend )
		{
			m_hTalkTarget = pFriend;
			PlaySentence( szIdleGroup, duration, VOL_NORM, ATTN_IDLE );
			m_nSpeak++;
			return TRUE;
		}
	}

	// didn't speak
	Talk( 0 );
	CTalkMonster::g_talkWaitTime = 0;
	return FALSE;
}

void CTalkMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener )
{
	if( !bConcurrent )
		ShutUpFriends();

	ClearConditions( bits_COND_CLIENT_PUSH );	// Forget about moving!  I've got something to say!
	m_useTime = gpGlobals->time + duration;
	PlaySentence( pszSentence, duration, volume, attenuation );

	m_hTalkTarget = pListener;
}

void CTalkMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation )
{
	if( !pszSentence )
		return;

	Talk( duration );

	CTalkMonster::g_talkWaitTime = gpGlobals->time + duration + 2.0f;
	if( pszSentence[0] == '!' )
		EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, GetVoicePitch() );
	else
		SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, GetVoicePitch() );

	// If you say anything, don't greet the player - you may have already spoken to them
	SetBits( m_bitsSaid, bit_saidHelloPlayer );
}

//=========================================================
// Talk - set a timer that tells us when the monster is done
// talking.
//=========================================================
void CTalkMonster::Talk( float flDuration )
{
	if( flDuration <= 0 )
	{
		// no duration :( 
		m_flStopTalkTime = gpGlobals->time + 3.0f;
	}
	else
	{
		m_flStopTalkTime = gpGlobals->time + flDuration;
	}
}

// Prepare this talking monster to answer question
void CTalkMonster::SetAnswerQuestion( CTalkMonster *pSpeaker )
{
	if( !m_pCine )
		ChangeSchedule( slIdleResponse );
	m_hTalkTarget = (CBaseMonster *)pSpeaker;
}

int CTalkMonster::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
{
	if( IsAlive() )
	{
		// if player damaged this entity, have other friends talk about it
		if( pevAttacker && m_MonsterState != MONSTERSTATE_PRONE && FBitSet( pevAttacker->flags, FL_CLIENT ) )
		{
			CBaseEntity *pFriend = FindNearestFriend( FALSE );

			if( pFriend && pFriend->IsAlive() )
			{
				// only if not dead or dying!
				CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend;
				pTalkMonster->ChangeSchedule( slIdleStopShooting );
			}
		}
	}
	return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType );
}

Schedule_t *CTalkMonster::GetScheduleOfType( int Type )
{
	switch( Type )
	{
	case SCHED_MOVE_AWAY:
		return slMoveAway;
	case SCHED_MOVE_AWAY_FOLLOW:
		return slMoveAwayFollow;
	case SCHED_MOVE_AWAY_FAIL:
		return slMoveAwayFail;
	case SCHED_TARGET_FACE:
		// speak during 'use'
		if( RANDOM_LONG( 0, 99 ) < 2 )
			//ALERT( at_console, "target chase speak\n" );
			return slIdleSpeakWait;
		else
			return slIdleStand;
	case SCHED_IDLE_STAND:
		{	
			// if never seen player, try to greet him
			if( !FBitSet( m_bitsSaid, bit_saidHelloPlayer ) )
			{
				return slIdleHello;
			}

			// sustained light wounds?
			if( !FBitSet( m_bitsSaid, bit_saidWoundLight ) && ( pev->health <= ( pev->max_health * 0.75f ) ) )
			{
				//SENTENCEG_PlayRndSz( ENT( pev ), m_szGrp[TLK_WOUND], 1.0, ATTN_IDLE, 0, GetVoicePitch() );
				//CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT( 2.8f, 3.2f );
				PlaySentence( m_szGrp[TLK_WOUND], RANDOM_FLOAT( 2.8f, 3.2f ), VOL_NORM, ATTN_IDLE );
				SetBits( m_bitsSaid, bit_saidWoundLight );
				return slIdleStand;
			}
			// sustained heavy wounds?
			else if( !FBitSet( m_bitsSaid, bit_saidWoundHeavy ) && ( pev->health <= ( pev->max_health * 0.5f ) ) )
			{
				//SENTENCEG_PlayRndSz( ENT( pev ), m_szGrp[TLK_MORTAL], 1.0, ATTN_IDLE, 0, GetVoicePitch() );
				//CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT( 2.8f, 3.2f );
				PlaySentence( m_szGrp[TLK_MORTAL], RANDOM_FLOAT( 2.8f, 3.2f ), VOL_NORM, ATTN_IDLE );
				SetBits( m_bitsSaid, bit_saidWoundHeavy );
				return slIdleStand;
			}

			// talk about world
			if( FOkToSpeak() && RANDOM_LONG( 0, m_nSpeak * 2 ) == 0 )
			{
				//ALERT ( at_console, "standing idle speak\n" );
				return slIdleSpeak;
			}
			
			if( !IsTalking() && HasConditions( bits_COND_SEE_CLIENT ) && RANDOM_LONG( 0, 6 ) == 0 )
			{
				edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 );

				if( pPlayer )
				{
					// watch the client.
					UTIL_MakeVectors( pPlayer->v.angles );
					if( ( pPlayer->v.origin - pev->origin ).Length2D() < TLK_STARE_DIST && 
						UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) >= m_flFieldOfView )
					{
						// go into the special STARE schedule if the player is close, and looking at me too.
						return &slTlkIdleWatchClient[1];
					}

					return slTlkIdleWatchClient;
				}
			}
			else
			{
				if( IsTalking() )
					// look at who we're talking to
					return slTlkIdleEyecontact;
				else
					// regular standing idle
					return slIdleStand;
			}

			// NOTE - caller must first CTalkMonster::GetScheduleOfType, 
			// then check result and decide what to return ie: if sci gets back
			// slIdleStand, return slIdleSciStand
		}
		break;
	}

	return CBaseMonster::GetScheduleOfType( Type );
}

//=========================================================
// IsTalking - am I saying a sentence right now?
//=========================================================
BOOL CTalkMonster::IsTalking( void )
{
	if( m_flStopTalkTime > gpGlobals->time )
	{
		return TRUE;
	}

	return FALSE;
}

//=========================================================
// If there's a player around, watch him.
//=========================================================
void CTalkMonster::PrescheduleThink( void )
{
	if( !HasConditions( bits_COND_SEE_CLIENT ) )
	{
		SetConditions( bits_COND_CLIENT_UNSEEN );
	}
}

// try to smell something
void CTalkMonster::TrySmellTalk( void )
{
	if( !FOkToSpeak() )
		return;

	// clear smell bits periodically
	if( gpGlobals->time > m_flLastSaidSmelled )
	{
		//ALERT( at_aiconsole, "Clear smell bits\n" );
		ClearBits( m_bitsSaid, bit_saidSmelled );
	}

	// smelled something?
	if( !FBitSet( m_bitsSaid, bit_saidSmelled ) && HasConditions( bits_COND_SMELL ) )
	{
		PlaySentence( m_szGrp[TLK_SMELL], RANDOM_FLOAT( 2.8f, 3.2f ), VOL_NORM, ATTN_IDLE );
		m_flLastSaidSmelled = gpGlobals->time + 60.0f;// don't talk about the stinky for a while.
		SetBits( m_bitsSaid, bit_saidSmelled );
	}
}

int CTalkMonster::IRelationship( CBaseEntity *pTarget )
{
	if( pTarget->IsPlayer() )
		if( m_afMemory & bits_MEMORY_PROVOKED )
			return R_HT;
	return CBaseMonster::IRelationship( pTarget );
}

void CTalkMonster::StopFollowing( BOOL clearSchedule )
{
	if( IsFollowing() )
	{
		if( !( m_afMemory & bits_MEMORY_PROVOKED ) )
		{
			PlaySentence( m_szGrp[TLK_UNUSE], RANDOM_FLOAT( 2.8f, 3.2f ), VOL_NORM, ATTN_IDLE );
			m_hTalkTarget = m_hTargetEnt;
		}

		if( m_movementGoal == MOVEGOAL_TARGETENT )
			RouteClear(); // Stop him from walking toward the player
		m_hTargetEnt = 0;
		if( clearSchedule )
			ClearSchedule();
		if( m_hEnemy != 0 )
			m_IdealMonsterState = MONSTERSTATE_COMBAT;
	}
}

void CTalkMonster::StartFollowing( CBaseEntity *pLeader )
{
	if( m_pCine )
		m_pCine->CancelScript();

	if( m_hEnemy != 0 )
		m_IdealMonsterState = MONSTERSTATE_ALERT;

	m_hTargetEnt = pLeader;
	PlaySentence( m_szGrp[TLK_USE], RANDOM_FLOAT( 2.8f, 3.2f ), VOL_NORM, ATTN_IDLE );
	m_hTalkTarget = m_hTargetEnt;
	ClearConditions( bits_COND_CLIENT_PUSH );
	ClearSchedule();
}

BOOL CTalkMonster::CanFollow( void )
{
	if( m_MonsterState == MONSTERSTATE_SCRIPT )
	{
		if( !m_pCine )
			return FALSE;
		if( !m_pCine->CanInterrupt() )
			return FALSE;
	}

	if( !IsAlive() )
		return FALSE;

	return !IsFollowing();
}

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

	if( pCaller != NULL && pCaller->IsPlayer() )
	{
		// Pre-disaster followers can't be used
		if( pev->spawnflags & SF_MONSTER_PREDISASTER )
		{
			DeclineFollowing();
		}
		else if( CanFollow() )
		{
			LimitFollowers( pCaller, 1 );

			if( m_afMemory & bits_MEMORY_PROVOKED )
				ALERT( at_console, "I'm not following you, you evil person!\n" );
			else
			{
				StartFollowing( pCaller );
				SetBits( m_bitsSaid, bit_saidHelloPlayer );	// Don't say hi after you've started following
			}
		}
		else
		{
			StopFollowing( TRUE );
		}
	}
}

void CTalkMonster::KeyValue( KeyValueData *pkvd )
{
	if( FStrEq( pkvd->szKeyName, "UseSentence" ) )
	{
		m_iszUse = ALLOC_STRING( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else if( FStrEq( pkvd->szKeyName, "UnUseSentence" ) )
	{
		m_iszUnUse = ALLOC_STRING( pkvd->szValue );
		pkvd->fHandled = TRUE;
	}
	else 
		CBaseMonster::KeyValue( pkvd );
}

void CTalkMonster::Precache( void )
{
	if( m_iszUse )
		m_szGrp[TLK_USE] = STRING( m_iszUse );
	if( m_iszUnUse )
		m_szGrp[TLK_UNUSE] = STRING( m_iszUnUse );
}
