//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:		barnacle - stationary ceiling mounted 'fishing' monster	
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//=============================================================================//

#include "cbase.h"
#include "hl1_npc_barnacle.h"
#include "npcevent.h"
#include "gib.h"
#include "ai_default.h"
#include "activitylist.h"
#include "hl2_player.h"
#include "vstdlib/random.h"
#include "physics_saverestore.h"
#include "vcollide_parse.h"
#include "engine/IEngineSound.h"

ConVar	sk_barnacle_health( "sk_barnacle_health","25");

//-----------------------------------------------------------------------------
// Private activities.
//-----------------------------------------------------------------------------
static int ACT_EAT = 0;

//-----------------------------------------------------------------------------
// Interactions
//-----------------------------------------------------------------------------
int	g_interactionBarnacleVictimDangle	= 0;
int	g_interactionBarnacleVictimReleased	= 0;
int	g_interactionBarnacleVictimGrab		= 0;

LINK_ENTITY_TO_CLASS( monster_barnacle, CNPC_Barnacle );
IMPLEMENT_CUSTOM_AI( monster_barnacle, CNPC_Barnacle );

//-----------------------------------------------------------------------------
// Purpose: Initialize the custom schedules
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_Barnacle::InitCustomSchedules(void) 
{
	INIT_CUSTOM_AI(CNPC_Barnacle);

	ADD_CUSTOM_ACTIVITY(CNPC_Barnacle, ACT_EAT);

	g_interactionBarnacleVictimDangle	= CBaseCombatCharacter::GetInteractionID();
	g_interactionBarnacleVictimReleased	= CBaseCombatCharacter::GetInteractionID();
	g_interactionBarnacleVictimGrab		= CBaseCombatCharacter::GetInteractionID();	
}


BEGIN_DATADESC( CNPC_Barnacle )

	DEFINE_FIELD( m_flAltitude, FIELD_FLOAT ),
	DEFINE_FIELD( m_flKillVictimTime, FIELD_TIME ),
	DEFINE_FIELD( m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something.
	DEFINE_FIELD( m_fLiftingPrey, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_flTongueAdj, FIELD_FLOAT ),
	DEFINE_FIELD( m_flIgnoreTouchesUntil, FIELD_TIME ),

	// Function pointers
	DEFINE_THINKFUNC( BarnacleThink ),
	DEFINE_THINKFUNC( WaitTillDead ),
END_DATADESC()


//=========================================================
// Classify - indicates this monster's place in the 
// relationship table.
//=========================================================
Class_T	CNPC_Barnacle::Classify ( void )
{
	return	CLASS_ALIEN_MONSTER;
}

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//
// Returns number of events handled, 0 if none.
//=========================================================
void CNPC_Barnacle::HandleAnimEvent( animevent_t *pEvent )
{
	switch( pEvent->event )
	{
	case BARNACLE_AE_PUKEGIB:
		CGib::SpawnRandomGibs( this, 1, GIB_HUMAN );	
		break;
	default:
		BaseClass::HandleAnimEvent( pEvent );
		break;
	}
}

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

	SetModel( "models/barnacle.mdl" );
	UTIL_SetSize( this, Vector(-16, -16, -32), Vector(16, 16, 0) );

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetMoveType( MOVETYPE_NONE );
	SetBloodColor( BLOOD_COLOR_GREEN );
	m_iHealth			= sk_barnacle_health.GetFloat();
	m_flFieldOfView		= 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
	m_NPCState			= NPC_STATE_NONE;
	m_flKillVictimTime	= 0;
	m_cGibs				= 0;
	m_fLiftingPrey		= FALSE;
	m_takedamage		= DAMAGE_YES;

	InitBoneControllers();
	InitTonguePosition();

	// set eye position
	SetDefaultEyeOffset();

	SetActivity ( ACT_IDLE );

	SetThink ( &CNPC_Barnacle::BarnacleThink );
	SetNextThink( gpGlobals->curtime + 0.5f );
	//Do not have a shadow
	AddEffects( EF_NOSHADOW );

	m_flIgnoreTouchesUntil = gpGlobals->curtime;
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
int	CNPC_Barnacle::OnTakeDamage_Alive( const CTakeDamageInfo &inputInfo )
{
	CTakeDamageInfo info = inputInfo;
	if ( info.GetDamageType() & DMG_CLUB )
	{
		info.SetDamage( m_iHealth );
	}

	return BaseClass::OnTakeDamage_Alive( info );
}

//-----------------------------------------------------------------------------
// Purpose: Initialize tongue position when first spawned
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CNPC_Barnacle::InitTonguePosition( void )
{
	CBaseEntity *pTouchEnt;
	float flLength;

	pTouchEnt = TongueTouchEnt( &flLength );
	m_flAltitude = flLength;

	Vector origin;
	QAngle angle;

	GetAttachment( "TongueEnd", origin, angle );

	m_flTongueAdj = origin.z - GetAbsOrigin().z;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Barnacle::BarnacleThink ( void )
{
	CBaseEntity *pTouchEnt;
	float flLength;

	SetNextThink( gpGlobals->curtime + 0.1f );

	if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI)
	{
		// AI Disabled, don't do anything
	}
	else if ( GetEnemy() != NULL )
	{
// barnacle has prey.

		if ( !GetEnemy()->IsAlive() )
		{
			// someone (maybe even the barnacle) killed the prey. Reset barnacle.
			m_fLiftingPrey = FALSE;// indicate that we're not lifting prey.
			SetEnemy( NULL );
			return;
		}

		CBaseCombatCharacter* pVictim = GetEnemyCombatCharacterPointer();
		Assert( pVictim );

		if ( m_fLiftingPrey )
		{	

			if ( GetEnemy() != NULL && pVictim->m_lifeState == LIFE_DEAD )
			{
				// crap, someone killed the prey on the way up.
				SetEnemy( NULL );
				m_fLiftingPrey = FALSE;
				return;
			}

	// still pulling prey.
			Vector vecNewEnemyOrigin = GetEnemy()->GetLocalOrigin();
			vecNewEnemyOrigin.x = GetLocalOrigin().x;
			vecNewEnemyOrigin.y = GetLocalOrigin().y;

			// guess as to where their neck is
			// FIXME: remove, ask victim where their neck is
			vecNewEnemyOrigin.x -= 6 * cos(GetEnemy()->GetLocalAngles().y * M_PI/180.0);	
			vecNewEnemyOrigin.y -= 6 * sin(GetEnemy()->GetLocalAngles().y * M_PI/180.0);

			m_flAltitude -= BARNACLE_PULL_SPEED;
			vecNewEnemyOrigin.z += BARNACLE_PULL_SPEED;

			if ( fabs( GetLocalOrigin().z - ( vecNewEnemyOrigin.z + GetEnemy()->GetViewOffset().z ) ) < BARNACLE_BODY_HEIGHT )
			{
		// prey has just been lifted into position ( if the victim origin + eye height + 8 is higher than the bottom of the barnacle, it is assumed that the head is within barnacle's body )
				m_fLiftingPrey = FALSE;

				CPASAttenuationFilter filter( this );
				EmitSound( filter, entindex(), "Barnacle.Bite");

				// Take a while to kill the player
				m_flKillVictimTime = gpGlobals->curtime + 10;
						
				if ( pVictim )
				{
					pVictim->DispatchInteraction( g_interactionBarnacleVictimDangle, NULL, this );
					SetActivity ( (Activity)ACT_EAT );
				}
			}

			CBaseEntity *pEnemy = GetEnemy();

			trace_t trace;
			UTIL_TraceEntity( pEnemy, pEnemy->GetAbsOrigin(), vecNewEnemyOrigin, MASK_SOLID_BRUSHONLY, pEnemy, COLLISION_GROUP_NONE, &trace );

			if( trace.fraction != 1.0 )
			{
				// The victim cannot be moved from their current origin to this new origin. So drop them.
				SetEnemy( NULL );
				m_fLiftingPrey = FALSE;

				if( pEnemy->MyCombatCharacterPointer() )
				{
					pEnemy->MyCombatCharacterPointer()->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
				}

				// Ignore touches long enough to let the victim move away.
				m_flIgnoreTouchesUntil = gpGlobals->curtime + 1.5;

				SetActivity( ACT_IDLE );

				return;
			}

			UTIL_SetOrigin ( GetEnemy(), vecNewEnemyOrigin );
		}
		else
		{
	// prey is lifted fully into feeding position and is dangling there.

			if ( m_flKillVictimTime != -1 && gpGlobals->curtime > m_flKillVictimTime )
			{
				// kill!
				if ( pVictim )
				{
					// DMG_CRUSH added so no physics force is generated
					pVictim->TakeDamage( CTakeDamageInfo( this, this, pVictim->m_iHealth, DMG_SLASH | DMG_ALWAYSGIB | DMG_CRUSH ) );
					m_cGibs = 3;
				}

				return;
			}

			// bite prey every once in a while
			if ( pVictim && ( random->RandomInt( 0, 49 ) == 0 ) )
			{
				CPASAttenuationFilter filter( this );
				EmitSound( filter, entindex(), "Barnacle.Chew" );

				if ( pVictim )
				{
					pVictim->DispatchInteraction( g_interactionBarnacleVictimDangle, NULL, this );
				}
			}
		}
	}
	else
	{
// barnacle has no prey right now, so just idle and check to see if anything is touching the tongue.

		// If idle and no nearby client, don't think so often. Client should be out of PVS and not within 50 feet.
		if ( !UTIL_FindClientInPVS(edict()) )
		{
			CBasePlayer *pPlayer = UTIL_PlayerByIndex(1);

			if( pPlayer )
			{
				Vector vecDist = pPlayer->GetAbsOrigin() - GetAbsOrigin();

				if( vecDist.Length2DSqr() >= Square(600.0f) )
				{
					SetNextThink( gpGlobals->curtime + 1.5f );
				}
			}
		}

		if ( IsActivityFinished() )
		{// this is done so barnacle will fidget.
			SetActivity ( ACT_IDLE );
		}

		if ( m_cGibs && random->RandomInt(0,99) == 1 )
		{
			// cough up a gib.
			CGib::SpawnRandomGibs( this, 1, GIB_HUMAN );
			m_cGibs--;

			CPASAttenuationFilter filter( this );
			EmitSound( filter, entindex(), "Barnacle.Chew" );
		}

		pTouchEnt = TongueTouchEnt( &flLength );

		//NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, flLength ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,0,0, 0, 0.1 );

		if ( pTouchEnt != NULL )
		{
			// tongue is fully extended, and is touching someone.
			CBaseCombatCharacter* pBCC = (CBaseCombatCharacter *)pTouchEnt;

			// FIXME: humans should return neck position
			Vector vecGrabPos = pTouchEnt->GetAbsOrigin();

			if ( pBCC && pBCC->DispatchInteraction( g_interactionBarnacleVictimGrab, &vecGrabPos, this ) )
			{
				CPASAttenuationFilter filter( this );
				EmitSound( filter, entindex(), "Barnacle.Alert" );

				SetSequenceByName ( "attack1" );

				SetEnemy( pTouchEnt );

				pTouchEnt->SetMoveType( MOVETYPE_FLY );
				pTouchEnt->SetAbsVelocity( vec3_origin );
				pTouchEnt->SetBaseVelocity( vec3_origin );
				Vector origin = GetAbsOrigin();
				origin.z = pTouchEnt->GetAbsOrigin().z;
				pTouchEnt->SetLocalOrigin( origin );
				
				m_fLiftingPrey = TRUE;// indicate that we should be lifting prey.
				m_flKillVictimTime = -1;// set this to a bogus time while the victim is lifted.

				m_flAltitude = (GetAbsOrigin().z - vecGrabPos.z);
			}
		}
		else
		{
			// calculate a new length for the tongue to be clear of anything else that moves under it. 
			if ( m_flAltitude < flLength )
			{
				// if tongue is higher than is should be, lower it kind of slowly.
				m_flAltitude += BARNACLE_PULL_SPEED;
			}
			else
			{
				m_flAltitude = flLength;
			}

		}

	}

	// ALERT( at_console, "tounge %f\n", m_flAltitude + m_flTongueAdj );
	//NDebugOverlay::Box( GetAbsOrigin() - Vector( 0, 0, m_flAltitude ), Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255,255,255, 0, 0.1 );

	SetBoneController( 0, -(m_flAltitude + m_flTongueAdj) );
	StudioFrameAdvance();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Barnacle::Event_Killed( const CTakeDamageInfo &info )
{
	AddSolidFlags( FSOLID_NOT_SOLID );
	m_takedamage		= DAMAGE_NO;
	m_lifeState			= LIFE_DEAD;
	if ( GetEnemy() != NULL )
	{
		CBaseCombatCharacter *pVictim = GetEnemyCombatCharacterPointer();

		if ( pVictim )
		{
			pVictim->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
		}
	}

	CGib::SpawnRandomGibs( this, 4, GIB_HUMAN );

	CPASAttenuationFilter filter( this );
	EmitSound( filter, entindex(), "Barnacle.Die" );

	SetActivity ( ACT_DIESIMPLE );
	SetBoneController( 0, 0 );

	StudioFrameAdvance();

	SetNextThink( gpGlobals->curtime + 0.1f );
	SetThink ( &CNPC_Barnacle::WaitTillDead );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CNPC_Barnacle::WaitTillDead ( void )
{
	SetNextThink( gpGlobals->curtime + 0.1f );

	StudioFrameAdvance();
	DispatchAnimEvents ( this );

	if ( IsActivityFinished() )
	{
		// death anim finished. 
		StopAnimation();
		SetThink ( NULL );
	}
}

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

	PrecacheScriptSound( "Barnacle.Bite" );
	PrecacheScriptSound( "Barnacle.Chew" );
	PrecacheScriptSound( "Barnacle.Alert" );
	PrecacheScriptSound( "Barnacle.Die" );

	BaseClass::Precache();
}	

//=========================================================
// TongueTouchEnt - does a trace along the barnacle's tongue
// to see if any entity is touching it. Also stores the length
// of the trace in the int pointer provided.
//=========================================================
#define BARNACLE_CHECK_SPACING	8
CBaseEntity *CNPC_Barnacle::TongueTouchEnt ( float *pflLength )
{
	trace_t		tr;
	float		length;

	// trace once to hit architecture and see if the tongue needs to change position.
	UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() - Vector ( 0 , 0 , 2048 ), 
		MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
	
	length = fabs( GetAbsOrigin().z - tr.endpos.z );
	// Pull it up a tad
	length -= 16;
	if ( pflLength )
	{
		*pflLength = length;
	}

	// Don't try to touch any prey.
	if ( m_flIgnoreTouchesUntil > gpGlobals->curtime )
		return NULL;

	Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 );
	Vector mins = GetAbsOrigin() - delta;
	Vector maxs = GetAbsOrigin() + delta;
	maxs.z = GetAbsOrigin().z;
	
	// Take our current tongue's length or a point higher if we hit a wall 
	// NOTENOTE: (this relieves the need to know if the tongue is currently moving)
	mins.z -= MIN( m_flAltitude, length );

	CBaseEntity *pList[10];
	int count = UTIL_EntitiesInBox( pList, 10, mins, maxs, (FL_CLIENT|FL_NPC) );
	if ( count )
	{
		for ( int i = 0; i < count; i++ )
		{
			CBaseCombatCharacter *pVictim = ToBaseCombatCharacter( pList[ i ] );

			bool bCanHurt = false;

			if ( IRelationType( pList[i] ) == D_HT || IRelationType( pList[i] ) == D_FR )
				 bCanHurt = true;

			if ( pList[i] != this && bCanHurt == true && pVictim->m_lifeState == LIFE_ALIVE )
			{
				return pList[i];
			}
		}
	}

	return NULL;
}