//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:		The builder bug
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "AI_Task.h"
#include "AI_Default.h"
#include "AI_Schedule.h"
#include "AI_Hull.h"
#include "AI_Hint.h"
#include "AI_Navigator.h"
#include "activitylist.h"
#include "soundent.h"
#include "game.h"
#include "NPCEvent.h"
#include "tf_player.h"
#include "EntityList.h"
#include "ndebugoverlay.h"
#include "shake.h"
#include "monstermaker.h"
#include "decals.h"
#include "vstdlib/random.h"
#include "tf_obj.h"
#include "engine/IEngineSound.h"
#include "IEffects.h"
#include "npc_bug_builder.h"
#include "npc_bug_hole.h"

ConVar	npc_bug_builder_health( "npc_bug_builder_health", "100" );
 
BEGIN_DATADESC( CNPC_Bug_Builder )

	DEFINE_FIELD( m_flIdleDelay,		FIELD_FLOAT ),

END_DATADESC()

LINK_ENTITY_TO_CLASS( npc_bug_builder, CNPC_Bug_Builder );
IMPLEMENT_CUSTOM_AI( npc_bug_builder, CNPC_Bug_Builder );

// Dawdling details
// Max & Min distances for dawdle forward movement
#define DAWDLE_MIN_DIST			64
#define DAWDLE_MAX_DIST			1024

//==================================================
// Bug Conditions
//==================================================
enum BugConditions
{
	COND_BBUG_RETURN_TO_BUGHOLE = LAST_SHARED_CONDITION,
};

//==================================================
// Bug Schedules
//==================================================

enum BugSchedules
{
	SCHED_BBUG_FLEE_ENEMY = LAST_SHARED_SCHEDULE,
	SCHED_BBUG_RETURN_TO_BUGHOLE,
	SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE,
	SCHED_BBUG_DAWDLE,
};

//==================================================
// Bug Tasks
//==================================================

enum BugTasks
{
	TASK_BBUG_GET_PATH_TO_FLEE = LAST_SHARED_TASK,
	TASK_BBUG_GET_PATH_TO_BUGHOLE,
	TASK_BBUG_HOLE_REMOVE,
	TASK_BBUG_GET_PATH_TO_DAWDLE,
	TASK_BBUG_FACE_DAWDLE,
};

//==================================================
// Bug Activities
//==================================================

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CNPC_Bug_Builder::CNPC_Bug_Builder( void )
{
	m_flFieldOfView	= 0.5f;
	m_flIdleDelay	= 0.0f;
}

//-----------------------------------------------------------------------------
// Purpose: Setup our schedules and tasks, etc.
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::InitCustomSchedules( void ) 
{
	INIT_CUSTOM_AI( CNPC_Bug_Builder );

	// Schedules
	ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_FLEE_ENEMY );
	ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE );
	ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE );
	ADD_CUSTOM_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_DAWDLE );

	// Conditions
	ADD_CUSTOM_CONDITION( CNPC_Bug_Builder, COND_BBUG_RETURN_TO_BUGHOLE );
		
	// Tasks
	ADD_CUSTOM_TASK( CNPC_Bug_Builder,	TASK_BBUG_GET_PATH_TO_FLEE );
	ADD_CUSTOM_TASK( CNPC_Bug_Builder,	TASK_BBUG_GET_PATH_TO_BUGHOLE );
	ADD_CUSTOM_TASK( CNPC_Bug_Builder,	TASK_BBUG_HOLE_REMOVE );
	ADD_CUSTOM_TASK( CNPC_Bug_Builder,	TASK_BBUG_GET_PATH_TO_DAWDLE );
	ADD_CUSTOM_TASK( CNPC_Bug_Builder,	TASK_BBUG_FACE_DAWDLE );

	// Activities
	//ADD_CUSTOM_ACTIVITY( CNPC_Bug_Builder,	ACT_BUG_WARRIOR_DISTRACT );

	AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_FLEE_ENEMY );
	AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE );
	AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE );
	AI_LOAD_SCHEDULE( CNPC_Bug_Builder, SCHED_BBUG_DAWDLE );
}

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

	SetModel( BUG_BUILDER_MODEL );

	SetHullType(HULL_TINY);
	SetHullSizeNormal();
	SetDefaultEyeOffset();
	SetViewOffset( (WorldAlignMins() + WorldAlignMaxs()) * 0.5 );	// See from my center
	SetDistLook( 1024.0 );
	m_flNextDawdle = 0;
	
	SetNavType(NAV_GROUND);
	m_NPCState		= NPC_STATE_NONE;
	SetBloodColor( BLOOD_COLOR_YELLOW );
	m_iHealth		= npc_bug_builder_health.GetFloat();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_STEP );

	CapabilitiesAdd( bits_CAP_MOVE_GROUND );

	NPCInit();

	BaseClass::Spawn();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::Precache( void )
{
	PrecacheModel( BUG_BUILDER_MODEL );

	PrecacheScriptSound( "NPC_Bug_Builder.Idle" );
	PrecacheScriptSound( "NPC_Bug_Builder.Pain" );

	BaseClass::Precache();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : int
//-----------------------------------------------------------------------------
int CNPC_Bug_Builder::SelectSchedule( void )
{
	// If I'm not in idle anymore, don't idle
	if ( m_NPCState != NPC_STATE_IDLE )
	{
		m_flNextDawdle = 0;
	}

	switch ( m_NPCState )
	{
	case NPC_STATE_IDLE:
		{
			// BugHole might be requesting help
			if ( HasCondition( COND_BBUG_RETURN_TO_BUGHOLE ) )
				return SCHED_BBUG_RETURN_TO_BUGHOLE;

			// Setup to dawdle a bit from now
			if ( !m_flNextDawdle )
			{
				m_flNextDawdle = gpGlobals->curtime + random->RandomFloat( 3.0, 5.0 );
			}
			else if ( m_flNextDawdle < gpGlobals->curtime )
			{
				m_flNextDawdle = 0;
				return SCHED_BBUG_DAWDLE;
			}

			// When I take damage, I flee
			if ( HasCondition( COND_LIGHT_DAMAGE | COND_HEAVY_DAMAGE ) )
				return SCHED_BBUG_FLEE_ENEMY;

			// Return to my bughole
			//return SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE;
			break;
		}
	case NPC_STATE_ALERT:
		{
			// BugHole might be requesting help
			if ( HasCondition( COND_BBUG_RETURN_TO_BUGHOLE ) )
				return SCHED_BBUG_RETURN_TO_BUGHOLE;

			// When I take damage, I flee
			if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
				return SCHED_BBUG_FLEE_ENEMY;

			break;
		}
	case NPC_STATE_COMBAT:
		{
			// Did I lose my enemy?
			if ( HasCondition ( COND_LOST_ENEMY ) || HasCondition ( COND_ENEMY_UNREACHABLE ) )
			{
				SetEnemy( NULL );
				SetState(NPC_STATE_IDLE);
				return BaseClass::SelectSchedule();
			}

			// When I take damage, I flee
			if ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
				return SCHED_BBUG_FLEE_ENEMY;
		}
		break;
	}

	return BaseClass::SelectSchedule();
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::StartTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_BBUG_GET_PATH_TO_FLEE:
		{
			// Always tell our bughole that we're under attack
			if ( m_hMyBugHole )
			{
				m_hMyBugHole->IncomingFleeingBug( this );
			}

			// If we have no squad, or we couldn't get a path to our squadmate, move to our bughole
			if ( m_hMyBugHole )
			{
				SetTarget( m_hMyBugHole );
				AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
				if ( GetNavigator()->SetGoal( goal ) )
				{
					TaskComplete();
					return;
				}
			}

			TaskComplete();
		}
		break;

	case TASK_BBUG_GET_PATH_TO_BUGHOLE:
		{
			// Get a path back to my bughole
			// If we have no squad, or we couldn't get a path to our squadmate, look for a bughole
			if ( m_hMyBugHole )
			{
				SetTarget( m_hMyBugHole );
				AI_NavGoal_t goal( GOALTYPE_TARGETENT, vec3_origin, ACT_RUN );
				if ( GetNavigator()->SetGoal( goal ) )
				{
					TaskComplete();
					return;
				}
			}

			TaskFail( "Couldn't get to bughole." );
		}
		break;

	case TASK_BBUG_HOLE_REMOVE:
		{
			TaskComplete();

			// Crawl inside the bughole and remove myself
			AddEffects( EF_NODRAW );
			AddSolidFlags( FSOLID_NOT_SOLID );
			Event_Killed( CTakeDamageInfo( this, this, 200, DMG_CRUSH ) );

			// Tell the bughole
			if ( m_hMyBugHole )
			{
				m_hMyBugHole->BugReturned();
			}
		}
		break;

	case TASK_BBUG_GET_PATH_TO_DAWDLE:
		{
			// Get a dawdle point ahead of us
			Vector vecForward, vecTarget;
			AngleVectors( GetAbsAngles(), &vecForward );
			VectorMA( GetAbsOrigin(), random->RandomFloat( DAWDLE_MIN_DIST, DAWDLE_MAX_DIST ), vecForward, vecTarget );

			// See how far we could move ahead
			trace_t tr;
			UTIL_TraceEntity( this, GetAbsOrigin(), vecTarget, MASK_SOLID, &tr);
			float flDistance = tr.fraction * (vecTarget - GetAbsOrigin()).Length();
			if ( flDistance >= DAWDLE_MIN_DIST )
			{
				AI_NavGoal_t goal( tr.endpos );
				GetNavigator()->SetGoal( goal );
			}

			TaskComplete();
		}
		break;

	case TASK_BBUG_FACE_DAWDLE:
		{
			// Turn a random amount to the right
			float flYaw = GetMotor()->GetIdealYaw();
			flYaw = flYaw + random->RandomFloat( 45, 135 );
			GetMotor()->SetIdealYaw( UTIL_AngleMod(flYaw) );
			SetTurnActivity();
			break;
		}
		break;

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

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pTask - 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::RunTask( const Task_t *pTask )
{
	switch ( pTask->iTask )
	{
	case TASK_BBUG_FACE_DAWDLE:
		{
			GetMotor()->UpdateYaw();
			if ( FacingIdeal() )
			{
				TaskComplete();
			}
			break;
		}

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

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CNPC_Bug_Builder::FValidateHintType(CAI_Hint *pHint)
{
	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pVictim - 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::Event_Killed( const CTakeDamageInfo &info )
{
	BaseClass::Event_Killed( info );

	// Remove myself in a minute
	if ( !ShouldFadeOnDeath() )
	{
		SetThink( SUB_Remove );
		SetNextThink( gpGlobals->curtime + 20 );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pEvent - 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::HandleAnimEvent( animevent_t *pEvent )
{
	BaseClass::HandleAnimEvent( pEvent );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CNPC_Bug_Builder::MaxYawSpeed( void )
{
	return 2.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::IdleSound( void )
{
	EmitSound( "NPC_Bug_Builder.Idle" );
	m_flIdleDelay = gpGlobals->curtime + 4.0f;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::PainSound( const CTakeDamageInfo &info )
{
	EmitSound( "NPC_Bug_Builder.Pain" );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Bug_Builder::ShouldPlayIdleSound( void )
{
	//Only do idles in the right states
	if ( ( m_NPCState != NPC_STATE_IDLE && m_NPCState != NPC_STATE_ALERT ) )
		return false;

	//Gagged monsters don't talk
	if ( HasSpawnFlags( SF_NPC_GAG ) )
		return false;

	//Don't cut off another sound or play again too soon
	if ( m_flIdleDelay > gpGlobals->curtime )
		return false;

	//Randomize it a bit
	if ( random->RandomInt( 0, 20 ) )
		return false;

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::SetBugHole( CMaker_BugHole *pBugHole )
{
	m_hMyBugHole = pBugHole;
}

//-----------------------------------------------------------------------------
// Purpose: BugHole is calling me home to defend it
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::ReturnToBugHole( void )
{
	SetCondition( COND_BBUG_RETURN_TO_BUGHOLE );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Bug_Builder::AlertSound( void )
{
	if ( GetEnemy() )
	{
		//FIXME: We need a better solution for inner-squad alerts!!
		//SOUND_DANGER is designed to frighten NPC's away. Need a different SOUND_ type.
		CSoundEnt::InsertSound( SOUND_DANGER, GetEnemy()->GetAbsOrigin(), 1024, 0.5f, this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Overridden for team handling
//-----------------------------------------------------------------------------
Disposition_t CNPC_Bug_Builder::IRelationType( CBaseEntity *pTarget )
{
	// Builders ignore everything
	return D_NU;
}


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

//=========================================================
// Dawdle around
//=========================================================
AI_DEFINE_SCHEDULE 
(
	SCHED_BBUG_DAWDLE,

	"	Tasks"
	"		TASK_SET_TOLERANCE_DISTANCE		32"
	"		TASK_BBUG_GET_PATH_TO_DAWDLE	0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	"		TASK_BBUG_FACE_DAWDLE			0"
	"	"
	"	Interrupts"
	"		COND_LIGHT_DAMAGE"
	"		COND_HEAVY_DAMAGE"
);

//=========================================================
// Flee from our enemy
//=========================================================
AI_DEFINE_SCHEDULE 
(
	SCHED_BBUG_FLEE_ENEMY,

	"	Tasks"
	"		TASK_SET_TOLERANCE_DISTANCE		128"
	"		TASK_BBUG_GET_PATH_TO_FLEE		0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	"		TASK_TURN_RIGHT					180"
	"	"
	"	Interrupts"
	"		COND_ENEMY_DEAD"
	"		COND_LOST_ENEMY"
);

//=========================================================
// Retreat to a bughole
//=========================================================
AI_DEFINE_SCHEDULE 
(
	SCHED_BBUG_RETURN_TO_BUGHOLE,

	"	Tasks"
	"		TASK_SET_TOLERANCE_DISTANCE		128"
	"		TASK_BBUG_GET_PATH_TO_BUGHOLE	0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	"	"
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_HEAR_COMBAT"
	"		COND_HEAR_DANGER"
);

//=========================================================
// Return to a bughole and remove myself
//=========================================================
AI_DEFINE_SCHEDULE 
(
	SCHED_BBUG_RETURN_TO_BUGHOLE_AND_REMOVE,

	"	Tasks"
	"		TASK_WAIT						5"		// Wait for 5-10 seconds to see if anything happens
	"		TASK_WAIT_RANDOM				5"
	"		TASK_SET_TOLERANCE_DISTANCE		128"
	"		TASK_BBUG_GET_PATH_TO_BUGHOLE	0"
	"		TASK_RUN_PATH					0"
	"		TASK_WAIT_FOR_MOVEMENT			0"
	"		TASK_BBUG_HOLE_REMOVE			0"
	"	"
	"	Interrupts"
	"		COND_NEW_ENEMY"
	"		COND_HEAR_COMBAT"
	"		COND_HEAR_DANGER"
);