//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
#include	"cbase.h"
#include	"hl1_ai_basenpc.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_memory.h"
#include	"ai_route.h"
#include	"ai_motor.h"
#include	"ai_senses.h"
#include	"soundent.h"
#include	"game.h"
#include	"npcevent.h"
#include	"entitylist.h"
#include	"activitylist.h"
#include	"animation.h"
#include	"basecombatweapon.h"
#include	"IEffects.h"
#include	"vstdlib/random.h"
#include	"engine/IEngineSound.h"
#include	"ammodef.h"
#include	"ai_behavior_follow.h"
#include	"ai_navigator.h"
#include	"decals.h"


#define		ROACH_IDLE				0
#define		ROACH_BORED				1
#define		ROACH_SCARED_BY_ENT		2
#define		ROACH_SCARED_BY_LIGHT	3
#define		ROACH_SMELL_FOOD		4
#define		ROACH_EAT				5

//=========================================================
// Monster's Anim Events Go Here
//=========================================================
class CNPC_Roach : public CHL1BaseNPC
{
	DECLARE_CLASS( CNPC_Roach, CHL1BaseNPC );
	
public:

	void Spawn( void );
	void Precache( void );
	float MaxYawSpeed( void );

//	DECLARE_DATADESC();

	void NPCThink ( void );
	void PickNewDest ( int iCondition );
	void Look ( int iDistance );
	void Move ( float flInterval );

	Class_T  Classify( void ) { return CLASS_INSECT; }

	void Touch ( CBaseEntity *pOther );

	void Event_Killed( const CTakeDamageInfo &info );
	int		GetSoundInterests ( void );

	void Eat( float flFullDuration );
	bool ShouldEat( void );

	bool ShouldGib( const CTakeDamageInfo &info ) { return false; }

	float	m_flLastLightLevel;
	float	m_flNextSmellTime;
		
	// UNDONE: These don't necessarily need to be save/restored, but if we add more data, it may
	bool	m_fLightHacked;
	int		m_iMode;

	float m_flHungryTime;
	// -----------------------------
};

LINK_ENTITY_TO_CLASS( monster_cockroach, CNPC_Roach );

//BEGIN_DATADESC( CNPC_Roach )

//	DEFINE_FUNCTION( RoachTouch ),
	
//END_DATADESC()


//=========================================================
// Spawn
//=========================================================
void CNPC_Roach::Spawn()
{
	Precache( );

	SetModel( "models/roach.mdl" );
	UTIL_SetSize( this, Vector( -1, -1, 0 ), Vector( 1, 1, 2 ) );

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );
	m_bloodColor		= BLOOD_COLOR_YELLOW;
	ClearEffects();
	m_iHealth			= 1;
	m_flFieldOfView		= 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
	m_NPCState			= NPC_STATE_NONE;

	SetRenderColor( 255, 255, 255, 255 );

	NPCInit();
	SetActivity ( ACT_IDLE );

	SetViewOffset ( Vector ( 0, 0, 1 ) );// position of the eyes relative to monster's origin.
	m_takedamage		= DAMAGE_YES;
	m_fLightHacked		= FALSE;
	m_flLastLightLevel	= -1;
	m_iMode				= ROACH_IDLE;
	m_flNextSmellTime	= gpGlobals->curtime;

	AddEffects( EF_NOSHADOW );
}

//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_Roach::Precache()
{
	PrecacheModel("models/roach.mdl");

	PrecacheScriptSound( "Roach.Walk" );
	PrecacheScriptSound( "Roach.Die" );
	PrecacheScriptSound( "Roach.Smash" );
}

float CNPC_Roach::MaxYawSpeed( void )
{
	return 120.0f;
}

void CNPC_Roach::Eat( float flFullDuration )
{
	m_flHungryTime = gpGlobals->curtime + flFullDuration;
}

bool CNPC_Roach::ShouldEat( void )
{
	if ( m_flHungryTime > gpGlobals->curtime )
	{
		return false;
	}

	return true;
}

//=========================================================
// MonsterThink, overridden for roaches.
//=========================================================
void CNPC_Roach::NPCThink( void  )
{
	if ( FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
		SetNextThink( gpGlobals->curtime + random->RandomFloat( 1.0f , 1.5f ) );
	else
		SetNextThink( gpGlobals->curtime + 0.1f );// keep monster thinking

	float flInterval = gpGlobals->curtime - GetLastThink();

	StudioFrameAdvance( ); // animate

	if ( !m_fLightHacked )
	{
		// if light value hasn't been collection for the first time yet, 
		// suspend the creature for a second so the world finishes spawning, then we'll collect the light level.
		SetNextThink( gpGlobals->curtime + 1 );
		m_fLightHacked = TRUE;
		return;
	}
	else if ( m_flLastLightLevel < 0 )
	{
		// collect light level for the first time, now that all of the lightmaps in the roach's area have been calculated.
		m_flLastLightLevel = 0;
	}

	switch ( m_iMode )
	{
	case	ROACH_IDLE:
	case	ROACH_EAT:
		{
			// if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random.
			if ( random->RandomInt( 0, 3 ) == 1 )
			{
				Look( 150 );

				if ( HasCondition( COND_SEE_FEAR ) )
				{
					// if see something scary
					//ALERT ( at_aiconsole, "Scared\n" );
					Eat( 30 +  ( random->RandomInt( 0, 14 ) ) );// roach will ignore food for 30 to 45 seconds
					PickNewDest( ROACH_SCARED_BY_ENT );
					SetActivity ( ACT_WALK );
				}
				else if ( random->RandomInt( 0,149 ) == 1 )
				{
					// if roach doesn't see anything, there's still a chance that it will move. (boredom)
					//ALERT ( at_aiconsole, "Bored\n" );
					PickNewDest( ROACH_BORED );
					SetActivity ( ACT_WALK );

					if ( m_iMode == ROACH_EAT )
					{
						// roach will ignore food for 30 to 45 seconds if it got bored while eating. 
						Eat( 30 +  ( random->RandomInt(0,14) ) );
					}
				}
			}
	
			// don't do this stuff if eating!
			if ( m_iMode == ROACH_IDLE )
			{
				if ( ShouldEat() )
				{
					 GetSenses()->Listen();
				}

				if ( 0 > m_flLastLightLevel )
				{
					// someone turned on lights!
					//ALERT ( at_console, "Lights!\n" );
					PickNewDest( ROACH_SCARED_BY_LIGHT );
					SetActivity ( ACT_WALK );
				}
				else if ( HasCondition( COND_SMELL ) )
				{
					CSound *pSound = GetLoudestSoundOfType( ALL_SOUNDS );

					// roach smells food and is just standing around. Go to food unless food isn't on same z-plane.
					if ( pSound && abs( pSound->GetSoundOrigin().z - GetAbsOrigin().z ) <= 3 )
					{
						PickNewDest( ROACH_SMELL_FOOD );
						SetActivity ( ACT_WALK );
					}
				}
			}

			break;
		}
	case	ROACH_SCARED_BY_LIGHT:
		{
			// if roach was scared by light, then stop if we're over a spot at least as dark as where we started!
			if ( 0 <= m_flLastLightLevel )
			{
				SetActivity ( ACT_IDLE );
				m_flLastLightLevel = 0;// make this our new light level.
			}
			break;
		}
	}
	
	if ( GetActivity() != ACT_IDLE )
	{
		Move( flInterval );
	}
}

void CNPC_Roach::PickNewDest ( int iCondition )
{
	Vector	vecNewDir;
	Vector	vecDest;
	float	flDist;

	m_iMode = iCondition;

    GetNavigator()->ClearGoal();

	if ( m_iMode == ROACH_SMELL_FOOD )
	{
		// find the food and go there.
		CSound *pSound = GetLoudestSoundOfType( ALL_SOUNDS );

		if ( pSound )
		{
			GetNavigator()->SetRandomGoal( 3 - random->RandomInt( 0,5 ) );
			return;
		}
	}

	do 
	{
		// picks a random spot, requiring that it be at least 128 units away
		// else, the roach will pick a spot too close to itself and run in 
		// circles. this is a hack but buys me time to work on the real monsters.
		vecNewDir.x = random->RandomInt( -1, 1 );
		vecNewDir.y = random->RandomInt( -1, 1 );
		flDist		= 256 + ( random->RandomInt(0,255) );
		vecDest = GetAbsOrigin() + vecNewDir * flDist;

	} while ( ( vecDest - GetAbsOrigin() ).Length2D() < 128 );

	Vector vecLocation;

	vecLocation.x = vecDest.x;
	vecLocation.y = vecDest.y;
	vecLocation.z = GetAbsOrigin().z;
	
	AI_NavGoal_t goal( GOALTYPE_LOCATION, vecLocation, ACT_WALK );

	GetNavigator()->SetGoal( goal );

	if ( random->RandomInt( 0, 9 ) == 1 )
	{
		// every once in a while, a roach will play a skitter sound when they decide to run
		CPASAttenuationFilter filter( this );
		EmitSound( filter, entindex(), "Roach.Walk" );
	}
}

//=========================================================
// Look - overriden for the roach, which can virtually see 
// 360 degrees.
//=========================================================
void CNPC_Roach::Look ( int iDistance )
{
	CBaseEntity	*pSightEnt = NULL;// the current visible entity that we're dealing with

	// DON'T let visibility information from last frame sit around!
	ClearCondition( COND_SEE_HATE | COND_SEE_DISLIKE |  COND_SEE_ENEMY | COND_SEE_FEAR );

	// don't let monsters outside of the player's PVS act up, or most of the interesting
	// things will happen before the player gets there!
	if ( FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
	{
		return;
	}
	
	// Does sphere also limit itself to PVS?
	// Examine all entities within a reasonable radius
	// !!!PERFORMANCE - let's trivially reject the ent list before radius searching!

	for ( CEntitySphereQuery sphere( GetAbsOrigin(), iDistance ); ( pSightEnt = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
	{
		// only consider ents that can be damaged. !!!temporarily only considering other monsters and clients
		if (  pSightEnt->IsPlayer() || FBitSet ( pSightEnt->GetFlags(), FL_NPC ) )
		{
			if ( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->GetFlags(), FL_NOTARGET ) && pSightEnt->m_iHealth > 0 )
			{
				// don't add the Enemy's relationship to the conditions. We only want to worry about conditions when
				// we see monsters other than the Enemy.
				switch ( IRelationType ( pSightEnt ) )
				{
				case	D_FR:		
					SetCondition( COND_SEE_FEAR );
					break;
				case	D_NU:
					break;
				default:
					Msg ( "%s can't asses %s\n", GetClassname(), pSightEnt->GetClassname() );
					break;
				}
			}
		}
	}
}

//=========================================================
// roach's move function
//=========================================================
void CNPC_Roach::Move ( float flInterval ) 
{
	float		flWaypointDist;
	Vector		vecApex;

	// local move to waypoint.
	flWaypointDist = ( GetNavigator()->GetGoalPos() - GetAbsOrigin() ).Length2D();
	
	GetMotor()->SetIdealYawToTargetAndUpdate( GetNavigator()->GetGoalPos() );

	float speed = 150 * flInterval;

	Vector vToTarget = GetNavigator()->GetGoalPos() - GetAbsOrigin();
	vToTarget.NormalizeInPlace();
	Vector vMovePos = vToTarget * speed;

	if ( random->RandomInt( 0,7 ) == 1 )
	{
		// randomly change direction
		PickNewDest( m_iMode );
	}
	
	if( !WalkMove( vMovePos, MASK_NPCSOLID ) )
	{
		PickNewDest( m_iMode );
	}

	// if the waypoint is closer than step size, then stop after next step (ok for roach to overshoot)
	if ( flWaypointDist <= m_flGroundSpeed * flInterval )
	{
		// take truncated step and stop

		SetActivity ( ACT_IDLE );
		m_flLastLightLevel = 0;// this is roach's new comfortable light level

		if ( m_iMode == ROACH_SMELL_FOOD )
		{
			m_iMode = ROACH_EAT;
		}
		else
		{
			m_iMode = ROACH_IDLE;
		}
	}

	if ( random->RandomInt( 0,149 ) == 1 && m_iMode != ROACH_SCARED_BY_LIGHT && m_iMode != ROACH_SMELL_FOOD )
	{
		// random skitter while moving as long as not on a b-line to get out of light or going to food
		PickNewDest( FALSE );
	}
}

void CNPC_Roach::Touch ( CBaseEntity *pOther )
{
	Vector		vecSpot;
	trace_t		tr;

	if ( pOther->GetAbsVelocity() == vec3_origin || !pOther->IsPlayer() )
	{
		return;
	}

	vecSpot = GetAbsOrigin() + Vector ( 0 , 0 , 8 );//move up a bit, and trace down.
	//UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ),  ignore_monsters, ENT(pev), & tr);

	UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), MASK_ALL, this, COLLISION_GROUP_NONE, &tr);

	// This isn't really blood.  So you don't have to screen it out based on violence levels (UTIL_ShouldShowBlood())
	UTIL_DecalTrace( &tr, "YellowBlood" );

	// DMG_GENERIC because we don't want any physics force generated
	TakeDamage( CTakeDamageInfo( pOther, pOther, m_iHealth, DMG_GENERIC ) );
}

void CNPC_Roach::Event_Killed( const CTakeDamageInfo &info )
{
	RemoveSolidFlags( FSOLID_NOT_SOLID );
	
	CPASAttenuationFilter filter( this );
	
	//random sound
	if ( random->RandomInt( 0,4 ) == 1 )
	{
		EmitSound( filter, entindex(), "Roach.Die" );
	}
	else
	{
		EmitSound( filter, entindex(), "Roach.Smash" );
	}
	
	CSoundEnt::InsertSound ( SOUND_WORLD, GetAbsOrigin(), 128, 1 );

	UTIL_Remove( this );
}

int CNPC_Roach::GetSoundInterests ( void) 
{
	return	SOUND_CARCASS	|
			SOUND_MEAT;
}