//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Combine gun turret that emerges from a trapdoor in the ground.
//
//=============================================================================//
#include "cbase.h"
#include "ai_basenpc.h"
#include "ai_default.h"
#include "ai_task.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_senses.h"
#include "ai_memory.h"
#include "soundent.h"
#include "game.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "npcevent.h"
#include "IEffects.h"
#include "ammodef.h"
#include "smoke_trail.h"
#include "beam_shared.h"
#include "explode.h"
#include "te_effect_dispatch.h"

#define GROUNDTURRET_BEAM_SPRITE "materials/effects/bluelaser2.vmt"

#define GROUNDTURRET_VIEWCONE		60.0f // (degrees)
#define GROUNDTURRET_RETIRE_TIME	7.0f

ConVar ai_newgroundturret ( "ai_newgroundturret", "0" );


// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//=========================================================
//=========================================================
class CNPC_GroundTurret : public CAI_BaseNPC
{
public:
	DECLARE_CLASS( CNPC_GroundTurret, CAI_BaseNPC );
	DECLARE_DATADESC();

	void	Precache( void );
	void	Spawn( void );
	bool	CreateVPhysics( void );
	void	PrescheduleThink();
	Class_T Classify( void );

	void PostNPCInit();

	// Damage & Death
	int OnTakeDamage_Alive( const CTakeDamageInfo &info );
	void Event_Killed( const CTakeDamageInfo &info );
	void DeathEffects();
	bool CanBecomeRagdoll( void ) { return false; }
	void DeathSound( void );

	// Combat
	void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType );
	Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget )
	{
		return VECTOR_CONE_5DEGREES;
	}


	// Sensing 
	void GatherConditions();
	Vector EyePosition();
	bool FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker );
	bool QuerySeeEntity( CBaseEntity *pEntity);

	
	bool IsOpeningOrClosing() { return (GetAbsVelocity().z != 0.0f); }
	bool IsEnabled()
	{
		if( ai_newgroundturret.GetBool() )
		{
			return true;
		}

		return m_bEnabled;
	}
	bool IsOpen();

	// Tasks & Schedules
	void StartTask( const Task_t *pTask );
	void RunTask( const Task_t *pTask );
	virtual int SelectSchedule( void );
	virtual int TranslateSchedule( int scheduleType );

	// Activities & Animation
	Activity	NPC_TranslateActivity( Activity eNewActivity );
	void		Shoot();
	void		Scan();
	void		ProjectBeam( const Vector &vecStart, const Vector &vecDir, int width, int brightness, float duration );

	// Local
	void SetActive( bool bActive ) {}

	// Inputs
	void InputEnable( inputdata_t &inputdata );
	void InputDisable( inputdata_t &inputdata );

	// Outputs
	COutputEvent	m_OnAreaClear;

	DEFINE_CUSTOM_AI;

private:
	//-----------------------------------------------------
	// Conditions, Schedules, Tasks
	//-----------------------------------------------------
	enum 
	{
		SCHED_GROUND_TURRET_IDLE = BaseClass::NEXT_SCHEDULE,
		SCHED_GROUND_TURRET_ATTACK,

		TASK_GROUNDTURRET_SCAN = BaseClass::NEXT_TASK,
	};

	int			m_iAmmoType;
	SmokeTrail	*m_pSmoke;

	bool		m_bEnabled;

	float		m_flTimeNextShoot;
	float		m_flTimeLastSawEnemy;
	Vector		m_vecSpread;
	bool		m_bHasExploded;
	int			m_iDeathSparks;
	float		m_flSensingDist;
	bool		m_bSeeEnemy;
	float		m_flTimeNextPing;
	
	Vector		m_vecClosedPos;

	Vector		m_vecLightOffset;
};

LINK_ENTITY_TO_CLASS( npc_turret_ground, CNPC_GroundTurret );

//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC( CNPC_GroundTurret )
	DEFINE_FIELD( m_iAmmoType,		FIELD_INTEGER ),
	DEFINE_FIELD( m_pSmoke,			FIELD_CLASSPTR ),
	DEFINE_FIELD( m_vecSpread,		FIELD_VECTOR ),
	DEFINE_FIELD( m_bEnabled,		FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flTimeNextShoot, FIELD_TIME ),
	DEFINE_FIELD( m_flTimeLastSawEnemy, FIELD_TIME ),
	DEFINE_FIELD( m_iDeathSparks,	FIELD_INTEGER ),
	DEFINE_FIELD( m_bHasExploded,	FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flSensingDist,	FIELD_FLOAT ),
	DEFINE_FIELD( m_flTimeNextPing, FIELD_TIME ),
	DEFINE_FIELD( m_bSeeEnemy,		FIELD_BOOLEAN ),
	DEFINE_FIELD( m_vecClosedPos,	FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( m_vecLightOffset,	FIELD_POSITION_VECTOR ),

	DEFINE_THINKFUNC( DeathEffects ),

	DEFINE_OUTPUT( m_OnAreaClear, "OnAreaClear" ),

	DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
	DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_GroundTurret::Precache( void )
{
	PrecacheModel( GROUNDTURRET_BEAM_SPRITE );
	PrecacheModel( "models/combine_turrets/ground_turret.mdl" );

	PrecacheScriptSound( "NPC_CeilingTurret.Deploy" );
	PrecacheScriptSound( "NPC_FloorTurret.ShotSounds" );
	PrecacheScriptSound( "NPC_FloorTurret.Die" );
	PrecacheScriptSound( "NPC_FloorTurret.Ping" );
	PrecacheScriptSound( "DoSpark" );

	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_GroundTurret::Spawn( void )
{
	Precache();

	UTIL_SetModel( this, "models/combine_turrets/ground_turret.mdl" );

	SetNavType( NAV_FLY );
	SetSolid( SOLID_VPHYSICS );

	SetBloodColor( DONT_BLEED );
	m_iHealth			= 125;
	m_flFieldOfView		= cos( ((GROUNDTURRET_VIEWCONE / 2.0f) * M_PI / 180.0f) );
	m_NPCState			= NPC_STATE_NONE;

	m_vecSpread.x = 0.5;
	m_vecSpread.y = 0.5;
	m_vecSpread.z = 0.5;

	CapabilitiesClear();

	AddEFlags( EFL_NO_DISSOLVE );

	NPCInit();

	CapabilitiesAdd( bits_CAP_SIMPLE_RADIUS_DAMAGE );

	m_iAmmoType = GetAmmoDef()->Index( "PISTOL" );

	m_pSmoke = NULL;

	m_bHasExploded = false;
	m_bEnabled = false;

	if( ai_newgroundturret.GetBool() )
	{
		m_flSensingDist = 384;
		SetDistLook( m_flSensingDist );
	}
	else
	{
		m_flSensingDist = 2048;
	}

	if( !GetParent() )
	{
		DevMsg("ERROR! npc_ground_turret with no parent!\n");
		UTIL_Remove(this);
		return;
	}

	m_flTimeNextShoot = gpGlobals->curtime;
	m_flTimeNextPing = gpGlobals->curtime;

	m_vecClosedPos = GetAbsOrigin();

	StudioFrameAdvance();

	Vector vecPos;

	GetAttachment( "eyes", vecPos );
	SetViewOffset( vecPos - GetAbsOrigin() );

	GetAttachment( "light", vecPos );
	m_vecLightOffset = vecPos - GetAbsOrigin();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CNPC_GroundTurret::CreateVPhysics( void )
{
	//Spawn our physics hull
	if ( !VPhysicsInitStatic() )
	{
		DevMsg( "npc_turret_ground unable to spawn physics object!\n" );
	}

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_GroundTurret::PrescheduleThink()
{
	if( UTIL_FindClientInPVS(edict()) )
	{
		SetNextThink( gpGlobals->curtime + 0.03f );
	}
	else
	{
		SetNextThink( gpGlobals->curtime + 0.1f );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : 
//-----------------------------------------------------------------------------
Class_T	CNPC_GroundTurret::Classify( void )
{
	if( !IsOpen() )
	{
		// NPC's should disregard me if I'm closed.
		return CLASS_NONE;
	}
	else
	{
		return CLASS_COMBINE;
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_GroundTurret::PostNPCInit()
{
	BaseClass::PostNPCInit();
}

//---------------------------------------------------------
//---------------------------------------------------------
int CNPC_GroundTurret::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	if( !info.GetInflictor() )
	{
		return 0;
	}

	// Only take damage from self (kill input from my bullseye) or missiles.
	if( info.GetInflictor() != this && info.GetInflictor()->Classify() != CLASS_MISSILE )
	{
		return 0;
	}

	CTakeDamageInfo infoCopy = info;

	if( info.GetInflictor() == this )
	{
		// Taking damage from myself, make sure it's fatal.
		infoCopy.SetDamage( GetHealth() );
		infoCopy.SetDamageType( DMG_REMOVENORAGDOLL | DMG_GENERIC );
	}

	return BaseClass::OnTakeDamage_Alive( infoCopy );
}

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_GroundTurret::Event_Killed( const CTakeDamageInfo &info )
{
	BaseClass::Event_Killed( info );

	if ( m_pSmoke != NULL )
		return;

	m_pSmoke = SmokeTrail::CreateSmokeTrail();
	
	if ( m_pSmoke )
	{
		m_pSmoke->m_SpawnRate			= 18;
		m_pSmoke->m_ParticleLifetime	= 3.0;
		m_pSmoke->m_StartSize			= 8;
		m_pSmoke->m_EndSize				= 32;
		m_pSmoke->m_SpawnRadius			= 16;
		m_pSmoke->m_MinSpeed			= 8;
		m_pSmoke->m_MaxSpeed			= 32;
		m_pSmoke->m_Opacity 			= 0.6;
		
		m_pSmoke->m_StartColor.Init( 0.25f, 0.25f, 0.25f );
		m_pSmoke->m_EndColor.Init( 0, 0, 0 );
		m_pSmoke->SetLifetime( 30.0f );
		m_pSmoke->FollowEntity( this );
	}

	m_iDeathSparks = random->RandomInt( 6, 12 );

	SetThink( &CNPC_GroundTurret::DeathEffects );
	SetNextThink( gpGlobals->curtime + 1.5f );
}

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_GroundTurret::DeathEffects()
{
	if( !m_bHasExploded )
	{
		//ExplosionCreate( GetAbsOrigin(), QAngle( 0, 0, 1 ), this, 150, 150, false );
		DeathSound();
		m_bHasExploded = true;
		SetNextThink( gpGlobals->curtime + 0.5 );
	}
	else
	{
		// Sparks
		EmitSound( "DoSpark" );
		m_iDeathSparks--;

		if( m_iDeathSparks == 0 )
		{
			SetThink(NULL);
			return;
		}

		SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.5, 2.5 ) );
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_GroundTurret::DeathSound()
{
	EmitSound("NPC_FloorTurret.Die");
}

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_GroundTurret::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType )
{
#if 1
	//BaseClass::MakeTracer( vecTracerSrc, tr, iTracerType );
	UTIL_Tracer( vecTracerSrc, tr.endpos, 0, TRACER_DONT_USE_ATTACHMENT, 5000, true, "AR2Tracer" );
#else
	CBeam *pBeam;
	int	width = 2;

	pBeam = CBeam::BeamCreate( GROUNDTURRET_BEAM_SPRITE, width );
	if ( !pBeam )
		return;
	
	pBeam->SetStartPos( vecTracerSrc );
	pBeam->SetEndPos( tr.endpos );
	pBeam->SetWidth( width );
	pBeam->SetEndWidth( width / 4.0f );

	pBeam->SetBrightness( 100 );
	pBeam->SetColor( 0, 145+random->RandomInt( -16, 16 ), 255 );
	pBeam->RelinkBeam();
	pBeam->LiveForTime( random->RandomFloat( 0.2f, 0.5f ) );
#endif
}

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_GroundTurret::GatherConditions()
{
	if( !IsEnabled() )
	{
		return;
	}

	if( !IsOpen() && !UTIL_FindClientInPVS( edict() ) )
	{
		return;
	}

	// Throw away old enemies so the turret can retire
	AIEnemiesIter_t iter;

	for( AI_EnemyInfo_t *pEMemory = GetEnemies()->GetFirst(&iter); pEMemory != NULL; pEMemory = GetEnemies()->GetNext(&iter) )
	{
		if( pEMemory->timeLastSeen < gpGlobals->curtime - GROUNDTURRET_RETIRE_TIME )
		{
			pEMemory->hEnemy = NULL;
		}
	}

	BaseClass::GatherConditions();

	if( GetEnemy() && HasCondition(COND_SEE_ENEMY) )
	{
		m_flTimeLastSawEnemy = gpGlobals->curtime;
	}
	else
	{
		if( gpGlobals->curtime - m_flTimeLastSawEnemy >= GROUNDTURRET_RETIRE_TIME )
		{
			m_OnAreaClear.FireOutput(this, this);
			m_flTimeLastSawEnemy = FLT_MAX;
			return;
		}
	}

	if( HasCondition( COND_SEE_ENEMY ) )
	{
		m_bSeeEnemy = true;
	}
	else
	{
		m_bSeeEnemy = false;
	}

	if( GetEnemy() && m_bSeeEnemy && IsEnabled() )
	{
		if( m_flTimeNextShoot < gpGlobals->curtime )
		{
			Shoot();
		}
	}
}

//---------------------------------------------------------
//---------------------------------------------------------
Vector CNPC_GroundTurret::EyePosition()
{
	if( ai_newgroundturret.GetBool() )
	{
		return GetAbsOrigin() + Vector( 0, 0, 6 );
	}

	return GetAbsOrigin() + GetViewOffset();
}

//---------------------------------------------------------
//---------------------------------------------------------
bool CNPC_GroundTurret::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
{
	if ( BaseClass::FVisible( pEntity, traceMask, ppBlocker ) )
		return true;
	if ( ( pEntity->GetAbsOrigin().AsVector2D() - GetAbsOrigin().AsVector2D() ).LengthSqr() < Square(10*12) &&
		 FInViewCone( pEntity->GetAbsOrigin() ) &&
		 BaseClass::FVisible( pEntity->GetAbsOrigin() + Vector( 0, 0, 1 ), traceMask, ppBlocker ) )
		return true;
	return false;
}

//---------------------------------------------------------
//---------------------------------------------------------
bool CNPC_GroundTurret::QuerySeeEntity( CBaseEntity *pEntity)
{
	float flDist;

	flDist = (pEntity->GetAbsOrigin() - EyePosition()).Length2DSqr();

	if( flDist <= m_flSensingDist * m_flSensingDist )
	{
		return true;
	}

	return false;
}

//---------------------------------------------------------
//---------------------------------------------------------
bool CNPC_GroundTurret::IsOpen()
{
	// The method is hacky but in the end, this does actually give
	// us a pretty good idea if the turret is open or closed.
	return( fabs(GetAbsOrigin().z - m_vecClosedPos.z ) > 1.0f );
}

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_GroundTurret::StartTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_GROUNDTURRET_SCAN:
		Scan();
		break;

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

//---------------------------------------------------------
//---------------------------------------------------------
void CNPC_GroundTurret::RunTask( const Task_t *pTask )
{
	switch( pTask->iTask )
	{
	case TASK_GROUNDTURRET_SCAN:
		Scan();
		break;

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

//---------------------------------------------------------
//---------------------------------------------------------
int CNPC_GroundTurret::SelectSchedule( void )
{
	return SCHED_GROUND_TURRET_IDLE;
}

//---------------------------------------------------------
//---------------------------------------------------------
int CNPC_GroundTurret::TranslateSchedule( int scheduleType ) 
{
	switch( scheduleType )
	{
	case SCHED_IDLE_STAND:
		return SCHED_GROUND_TURRET_IDLE;
		break;
	}

	return BaseClass::TranslateSchedule( scheduleType );
}

//-----------------------------------------------------------------------------
// Purpose: Override base class activiites
// Input  :
// Output :
//-----------------------------------------------------------------------------
Activity CNPC_GroundTurret::NPC_TranslateActivity( Activity activity )
{
	return ACT_IDLE;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_GroundTurret::Shoot()
{
	FireBulletsInfo_t info;

	Vector vecSrc = EyePosition();
	Vector vecDir;

	GetVectors( &vecDir, NULL, NULL );

	for( int i = 0 ; i < 1 ; i++ )
	{
		info.m_vecSrc = vecSrc;
		
		if( i > 0 || !GetEnemy()->IsPlayer() )
		{
			// Subsequent shots or shots at non-players random
			GetVectors( &info.m_vecDirShooting, NULL, NULL );
			info.m_vecSpread = m_vecSpread;
		}
		else
		{
			// First shot is at the enemy.
			info.m_vecDirShooting = GetActualShootTrajectory( vecSrc );
			info.m_vecSpread = VECTOR_CONE_PRECALCULATED;
		}
		
		info.m_iTracerFreq = 1;
		info.m_iShots = 1;
		info.m_pAttacker = this;
		info.m_flDistance = MAX_COORD_RANGE;
		info.m_iAmmoType = m_iAmmoType;

		FireBullets( info );
	}

	// Do the AR2 muzzle flash
	CEffectData data;
	data.m_nEntIndex = entindex();
	data.m_nAttachmentIndex = LookupAttachment( "eyes" );
	data.m_flScale = 1.0f;
	data.m_fFlags = MUZZLEFLASH_COMBINE;
	DispatchEffect( "MuzzleFlash", data );

	EmitSound( "NPC_FloorTurret.ShotSounds" );

	m_flTimeNextShoot = gpGlobals->curtime + 0.09;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_GroundTurret::ProjectBeam( const Vector &vecStart, const Vector &vecDir, int width, int brightness, float duration )
{
	CBeam *pBeam;
	pBeam = CBeam::BeamCreate( GROUNDTURRET_BEAM_SPRITE, width );
	if ( !pBeam )
		return;

	trace_t tr;
	AI_TraceLine( vecStart, vecStart + vecDir * m_flSensingDist, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
	
	pBeam->SetStartPos( tr.endpos );
	pBeam->SetEndPos( tr.startpos );
	pBeam->SetWidth( width );
	pBeam->SetEndWidth( 0.1 );
	pBeam->SetFadeLength( 16 );

	pBeam->SetBrightness( brightness );
	pBeam->SetColor( 0, 145+random->RandomInt( -16, 16 ), 255 );
	pBeam->RelinkBeam();
	pBeam->LiveForTime( duration );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_GroundTurret::Scan()
{
	if( m_bSeeEnemy )
	{
		// Using a bool for this check because the condition gets wiped out by changing schedules.
		return;
	}

	if( IsOpeningOrClosing() )
	{
		// Moving.
		return;
	}

	if( !IsOpen() )
	{
		// Closed
		return;
	}

	if( !UTIL_FindClientInPVS(edict()) )
	{
		return;
	}

	if( gpGlobals->curtime >= m_flTimeNextPing )
	{
		EmitSound( "NPC_FloorTurret.Ping" );
		m_flTimeNextPing = gpGlobals->curtime + 1.0f;
	}

	QAngle	scanAngle;
	Vector	forward;
	Vector	vecEye = GetAbsOrigin() + m_vecLightOffset;

	// Draw the outer extents
	scanAngle = GetAbsAngles();
	scanAngle.y += (GROUNDTURRET_VIEWCONE / 2.0f);
	AngleVectors( scanAngle, &forward, NULL, NULL );
	ProjectBeam( vecEye, forward, 1, 30, 0.1 );

	scanAngle = GetAbsAngles();
	scanAngle.y -= (GROUNDTURRET_VIEWCONE / 2.0f);
	AngleVectors( scanAngle, &forward, NULL, NULL );
	ProjectBeam( vecEye, forward, 1, 30, 0.1 );

	// Draw a sweeping beam
	scanAngle = GetAbsAngles();
	scanAngle.y += (GROUNDTURRET_VIEWCONE / 2.0f) * sin( gpGlobals->curtime * 3.0f );
	
	AngleVectors( scanAngle, &forward, NULL, NULL );
	ProjectBeam( vecEye, forward, 1, 30, 0.3 );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_GroundTurret::InputEnable( inputdata_t &inputdata )
{
	m_bEnabled = true;

	// Because the turret might not ever ACQUIRE an enemy, we need to arrange to 
	// retire after a few seconds.
	m_flTimeLastSawEnemy = gpGlobals->curtime;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_GroundTurret::InputDisable( inputdata_t &inputdata )
{
	m_bEnabled = false;
}

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

AI_BEGIN_CUSTOM_NPC( npc_groundturret, CNPC_GroundTurret )

	DECLARE_TASK( TASK_GROUNDTURRET_SCAN );

	DEFINE_SCHEDULE
	(
		SCHED_GROUND_TURRET_IDLE,

		"	Tasks "
		"		TASK_SET_ACTIVITY		ACTIVITY:ACT_IDLE"
		"		TASK_GROUNDTURRET_SCAN	0"
		""
		"	Interrupts "
		"		COND_NEW_ENEMY"
		"		COND_SEE_ENEMY"
		"		COND_LOST_ENEMY"
	)

	DEFINE_SCHEDULE
	(
		SCHED_GROUND_TURRET_ATTACK,

		"	Tasks "
		"		TASK_WAIT_INDEFINITE	0"
		""
		"	Interrupts "
		"		COND_NEW_ENEMY"
		"		COND_LOST_ENEMY"
		"		COND_SEE_ENEMY"
	)

AI_END_CUSTOM_NPC()

