//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: The downtrodden citizens of City 17.
//
//=============================================================================//

#include "cbase.h"

#include "hl2wars/npc/hlw_npc_combine.h"
#include "hl2wars/rts_globalresource.h"
#include "hl2wars/rts_point_order.h"
#include "npcevent.h"
#include "grenade_frag.h"
#include "basegrenade_shared.h"
#include "weapon_sniperrifle.h"

#include "ai_navigator.h"
#include "ai_tacticalservices.h"
#include "ai_hint.h"
#include "ai_interactions.h"
#include "ai_squadslot.h"
#include "ai_squad.h"
#include "ai_route.h"

#include "nav_area.h"

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

#define COMBINE_GRENADE_TIMER		3.5

#define COMBINE_MIN_CROUCH_DISTANCE		256.0

//=========================================================
// Combines's Anim Events Go Here
//=========================================================
#define COMBINE_AE_RELOAD			( 2 )
#define COMBINE_AE_KICK				( 3 )
#define COMBINE_AE_AIM				( 4 )
#define COMBINE_AE_GREN_TOSS		( 7 )
#define COMBINE_AE_GREN_LAUNCH		( 8 )
#define COMBINE_AE_GREN_DROP		( 9 )
#define COMBINE_AE_CAUGHT_ENEMY		( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad.


extern int COMBINE_AE_BEGIN_ALTFIRE;
extern int COMBINE_AE_ALTFIRE;

//---------------------------------------------------------

LINK_ENTITY_TO_CLASS( hlw_npc_combine_s, CHLW_NPC_Combine );

//---------------------------------------------------------

BEGIN_DATADESC( CHLW_NPC_Combine )
END_DATADESC()

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHLW_NPC_Combine::Spawn()
{
	//BaseClass::Spawn();

	Precache();
	SetModel( STRING( GetModelName() ) );

	SetHullType(HULL_HUMAN);
	SetHullSizeNormal();

	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_STANDABLE );
	SetBloodColor( BLOOD_COLOR_RED );
	m_flFieldOfView		= 0.02;
	m_NPCState		= NPC_STATE_NONE;

	m_flNextPainSoundTime	= 0;
	m_flNextAlertSoundTime	= 0;

	CapabilitiesClear();
	CapabilitiesAdd( bits_CAP_ANIMATEDFACE | bits_CAP_TURN_HEAD );
	CapabilitiesAdd( bits_CAP_USE_WEAPONS | bits_CAP_AIM_GUN | bits_CAP_MOVE_SHOOT );
	CapabilitiesAdd( bits_CAP_DUCK | bits_CAP_DOORS_GROUP );
	CapabilitiesAdd( bits_CAP_USE_SHOT_REGULATOR );
	CapabilitiesAdd( bits_CAP_MOVE_GROUND );
	CapabilitiesAdd( bits_CAP_NO_HIT_PLAYER | bits_CAP_NO_HIT_SQUADMATES | bits_CAP_FRIENDLY_DMG_IMMUNE );

	m_flNextLostSoundTime		= 0;

	SetMoveType( MOVETYPE_STEP );

	NPCInit();
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
void CHLW_NPC_Combine::Precache()
{
	const char *pModelName = STRING( GetModelName() );

	if( !Q_stricmp( pModelName, "models/combine_super_soldier.mdl" ) )
	{
		m_fIsElite = true;
	}
	else
	{
		m_fIsElite = false;
	}

	if( !GetModelName() )
	{
		SetModelName( MAKE_STRING( "models/combine_soldier.mdl" ) );
	}

	PrecacheModel( STRING( GetModelName() ) );

	UTIL_PrecacheOther( "item_healthvial" );
	UTIL_PrecacheOther( "weapon_frag" );
	UTIL_PrecacheOther( "item_ammo_ar2_altfire" );

	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
// Purpose: 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CHLW_NPC_Combine::CreateBehaviors()
{
	AddBehavior( &m_PlacableTankBehavior );
	
	return BaseClass::CreateBehaviors();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CHLW_NPC_Combine::PrescheduleThink( void )
{
	BaseClass::PrescheduleThink();

	if( GetCurrentOrder() )
	{
		// We are in cover and need to crouch if we can shoot the enemy or are idling.
		CAI_Hint *pHint =  GetCurrentOrder()->GetHint();
		HidingSpot *pSpot = GetCurrentOrder()->GetHidingSpot();
		if( (pHint && pHint->HintType() == HINT_TACTICAL_COVER_LOW) ||
			pSpot)
		{
			// FIXME: This check is causing a crash on the first encounter with an enemy?
			//if( GetEnemy() && !CouldShootIfCrouching( GetEnemy() ) )
			//{
			//	Stand();
			//}
			//else 
			{
				Crouch();
			}
		}
		else
		{
			Stand();
		}
	}

	// only show the sniper beam if we are in alert of combat state
	CWeaponSniperRifle *pSniper = dynamic_cast<CWeaponSniperRifle*>(GetActiveWeapon());
	if ( pSniper )
	{
		if( GetState() == NPC_STATE_ALERT || GetState() == NPC_STATE_COMBAT )
		{
			if( !pSniper->IsLaserOn() )
				pSniper->LaserOn();

			pSniper->UpdateLaser();

			if( pSniper->IsLaserOn() )
				SetNextThink( gpGlobals->curtime + 0.03 );
		}
		else if( GetState() == NPC_STATE_IDLE )
			pSniper->LaserOff();
		// else don't care
	}
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
int CHLW_NPC_Combine::SelectSchedule( void )
{
	int nSched = SelectFlinchSchedule();
	if ( nSched != SCHED_NONE )
		return nSched;

	// First, check abilities
	if( GetCurrentOrder() )
	{
		if( GetCurrentOrder()->GetOrderType() == ORDER_DO_ABILITY && GetCurrentOrder()->GetAbility() != ABILITY_NONE )
		{
			if( !HasCondition(COND_CAN_DO_ABILITY) ) 
			{
				if( HasCondition(COND_TOO_FAR_FOR_ABILITY) )
				{
					return SCHED_MOVE_TO_ABILITY_RANGE;
				}
				else if( HasCondition(COND_TOO_CLOSE_FOR_ABILITY) )
				{
					return SCHED_STANDOFF_ABILITY_RANGE;
				}
			}

			m_iActiveAbilityButton = GetCurrentOrder()->GetAbilityButton();
			
			if( GetCurrentOrder()->GetAbility() == ABILITY_GRENADE )
			{
				Vector vecTarget = GetCurrentOrder()->WorldSpaceCenter();

				if( GetCurrentOrder()->GetTarget() )
					m_hForcedGrenadeTarget = GetCurrentOrder()->GetTarget();
				else
					m_hForcedGrenadeTarget = GetCurrentOrder();

				if ( IsElite() )
				{
					if ( FVisible( GetCurrentOrder() ) )
					{
						m_vecAltFireTarget = vecTarget;
						return SCHED_COMBINE_AR2_ALTFIRE;
					}
				}
				else
				{
					// If we can, throw a grenade at the target. 
					// Ignore grenade count / distance / etc
					if ( CheckCanThrowGrenade( vecTarget ) )
					{
						return SCHED_COMBINE_FORCED_GRENADE_THROW;
					}
				}

				// Can't throw at the target, so lets try moving to somewhere where I can see it
				if ( !FVisible( GetCurrentOrder() ) )
				{
					return SCHED_COMBINE_MOVE_TO_FORCED_GREN_LOS;
				}			
			}
		}
	}

	if( BehaviorSelectSchedule() )
	{
		return CAI_BaseActor::SelectSchedule();
	}

	// no special cases here, call the base class
	return CAI_BaseActor::SelectSchedule();
}

//-----------------------------------------------------------------------------
// Purpose:
// Input  :
// Output :
//-----------------------------------------------------------------------------
int CHLW_NPC_Combine::TranslateSchedule( int scheduleType ) 
{
	switch( scheduleType )
	{
	case SCHED_RANGE_ATTACK1:
		{
			if ( HasCondition( COND_NO_PRIMARY_AMMO ) || HasCondition( COND_LOW_PRIMARY_AMMO ) )
			{
				return TranslateSchedule( SCHED_HIDE_AND_RELOAD );
			}


			if ( IsCrouching() || ( CrouchIsDesired() && !HasCondition( COND_HEAVY_DAMAGE ) ) )
			{
				// See if we can crouch and shoot
				if (GetEnemy() != NULL)
				{
					float dist = (GetLocalOrigin() - GetEnemy()->GetLocalOrigin()).Length();

					// only crouch if they are relatively far away
					if (dist > COMBINE_MIN_CROUCH_DISTANCE)
					{
						// try crouching
						Crouch();

						Vector targetPos = GetEnemy()->BodyTarget(GetActiveWeapon()->GetLocalOrigin());

						// if we can't see it crouched, stand up
						if (!WeaponLOSCondition(GetLocalOrigin(),targetPos,false))
						{
							Stand();
						}
					}
				}
			}
			else
			{
				// always assume standing
				Stand();
			}

			return SCHED_COMBINE_RANGE_ATTACK1;
		}
	case SCHED_RANGE_ATTACK2:
		{
			// If my weapon can range attack 2 use the weapon
			if (GetActiveWeapon() && GetActiveWeapon()->CapabilitiesGet() & bits_CAP_WEAPON_RANGE_ATTACK2)
			{
				return SCHED_RANGE_ATTACK2;
			}
			// Otherwise use innate attack
			else
			{
				return SCHED_COMBINE_RANGE_ATTACK2;
			}
		}
		// SCHED_COMBAT_FACE:
		// SCHED_COMBINE_WAIT_FACE_ENEMY:
		// SCHED_COMBINE_SWEEP:
		// SCHED_COMBINE_COVER_AND_RELOAD:
		// SCHED_COMBINE_FOUND_ENEMY:
	}

	return CAI_BaseActor::TranslateSchedule( scheduleType );
}

//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CHLW_NPC_Combine::HandleAnimEvent( animevent_t *pEvent )
{
	Vector vecShootDir;
	Vector vecShootOrigin;
	bool handledEvent = false;

	if (pEvent->type & AE_TYPE_NEWEVENTSYSTEM)
	{
		if ( pEvent->event == COMBINE_AE_BEGIN_ALTFIRE )
		{
			EmitSound( "Weapon_CombineGuard.Special1" );
			handledEvent = true;
		}
		else if ( pEvent->event == COMBINE_AE_ALTFIRE )
		{
			if( IsElite() )
			{
				if( !AbilityTakeResources() ) 
					return;

				animevent_t fakeEvent;

				fakeEvent.pSource = this;
				fakeEvent.event = EVENT_WEAPON_AR2_ALTFIRE;
				GetActiveWeapon()->Operator_HandleAnimEvent( &fakeEvent, this );

				AbilityCompleted();
			}

			handledEvent = true;
		}
		else
		{
			BaseClass::HandleAnimEvent( pEvent );
		}
	}
	else
	{
		switch( pEvent->event )
		{
			case COMBINE_AE_AIM:	
				{
					handledEvent = true;
					break;
				}
			case COMBINE_AE_RELOAD:

				// We never actually run out of ammo, just need to refill the clip
				if (GetActiveWeapon())
				{
					GetActiveWeapon()->WeaponSound( RELOAD_NPC );
					GetActiveWeapon()->m_iClip1 = GetActiveWeapon()->GetMaxClip1(); 
					GetActiveWeapon()->m_iClip2 = GetActiveWeapon()->GetMaxClip2();  
				}
				ClearCondition(COND_LOW_PRIMARY_AMMO);
				ClearCondition(COND_NO_PRIMARY_AMMO);
				ClearCondition(COND_NO_SECONDARY_AMMO);
				handledEvent = true;
				break;

			case COMBINE_AE_GREN_TOSS:
			{
				if( !AbilityTakeResources() ) 
					return;

				// can we do this ability? ( ain't it recharging?)
				if( !CanDoAbility( m_iActiveAbilityButton ) )
				{
					return;
				}

				Vector vecSpin;
				vecSpin.x = random->RandomFloat( -1000.0, 1000.0 );
				vecSpin.y = random->RandomFloat( -1000.0, 1000.0 );
				vecSpin.z = random->RandomFloat( -1000.0, 1000.0 );

				Vector vecStart;
				GetAttachment( "lefthand", vecStart );

				if( m_NPCState == NPC_STATE_SCRIPT )
				{
					// Use a fixed velocity for grenades thrown in scripted state.
					// Grenades thrown from a script do not count against grenades remaining for the AI to use.
					Vector forward, up, vecThrow;

					GetVectors( &forward, NULL, &up );
					vecThrow = forward * 750 + up * 175;
					Fraggrenade_Create( vecStart, vec3_angle, vecThrow, vecSpin, this, COMBINE_GRENADE_TIMER, true );
				}
				else
				{
					// Use the Velocity that AI gave us.
					Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vecSpin, this, COMBINE_GRENADE_TIMER, true );
				}

				AbilityCompleted();
			}
			handledEvent = true;
			break;

			case COMBINE_AE_GREN_LAUNCH:
			{
				EmitSound( "NPC_Combine.GrenadeLaunch" );
				
				CBaseEntity *pGrenade = CreateNoSpawn( "npc_contactgrenade", Weapon_ShootPosition(), vec3_angle, this );
				pGrenade->KeyValue( "velocity", m_vecTossVelocity );
				pGrenade->Spawn( );
			}
			handledEvent = true;
			break;

			case COMBINE_AE_GREN_DROP:
			{
				Vector vecStart;
				GetAttachment( "lefthand", vecStart );

				Fraggrenade_Create( vecStart, vec3_angle, m_vecTossVelocity, vec3_origin, this, COMBINE_GRENADE_TIMER, true );
			}
			handledEvent = true;
			break;

			/*case COMBINE_AE_KICK:
			{
				// Does no damage, because damage is applied based upon whether the target can handle the interaction
				CBaseEntity *pHurt = CheckTraceHullAttack( 70, -Vector(16,16,18), Vector(16,16,18), 0, DMG_CLUB );
				CBaseCombatCharacter* pBCC = ToBaseCombatCharacter( pHurt );
   				if (pBCC)
   				{
   					Vector forward, up;
   					AngleVectors( GetLocalAngles(), &forward, NULL, &up );
	   
   					if ( !pBCC->DispatchInteraction( g_interactionCombineBash, NULL, this ) )
   					{
						if ( pBCC->IsPlayer() )
						{
  							pBCC->ViewPunch( QAngle(-12,-7,0) );
  							pHurt->ApplyAbsVelocityImpulse( forward * 100 + up * 50 );
						}
	  
  						CTakeDamageInfo info( this, this, m_nKickDamage, DMG_CLUB );
 						CalculateMeleeDamageForce( &info, forward, pBCC->GetAbsOrigin() );
 						pBCC->TakeDamage( info );
	   
  						EmitSound( "NPC_Combine.WeaponBash" );
  					}
  				}			
			
				m_Sentences.Speak( "COMBINE_KICK" );
				handledEvent = true;
				break;
			}

			case COMBINE_AE_CAUGHT_ENEMY:
				m_Sentences.Speak( "COMBINE_ALERT" );
				handledEvent = true;
				break;*/

			default:
				BaseClass::HandleAnimEvent( pEvent );
				break;
		}
	}

	if( handledEvent )
	{
		m_iLastAnimEventHandled = pEvent->event;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Translate base class activities into combot activites
//-----------------------------------------------------------------------------
Activity CHLW_NPC_Combine::NPC_TranslateActivity( Activity eNewActivity )
{
	//Slaming this back to ACT_COMBINE_BUGBAIT since we don't want ANYTHING to change our activity while we burn.
	//if ( HasCondition( COND_COMBINE_ON_FIRE ) )
		//return BaseClass::NPC_TranslateActivity( ACT_COMBINE_BUGBAIT );

	if( GetCurrentOrder() )
	{
		CAI_Hint *pHint =  GetCurrentOrder()->GetHint();
		if( pHint && pHint->HintType() == HINT_TACTICAL_COVER_LOW )
		{
			switch( pHint->HintType() )
			{
			case HINT_TACTICAL_COVER_LOW:
			case HINT_TACTICAL_COVER_MED:
				{
					if( eNewActivity == ACT_IDLE )
						return ACT_COVER_LOW;
					if( eNewActivity == ACT_WALK )
						return ACT_COVER_LOW;
					break;
				}
			default:
				break;
			}
		}

		HidingSpot *pSpot = GetCurrentOrder()->GetHidingSpot();
		if( pSpot )
		{
			if( eNewActivity == ACT_IDLE )
				return ACT_COVER_LOW;
			if( eNewActivity == ACT_WALK )
				return ACT_COVER_LOW;
		}
	}

	return BaseClass::NPC_TranslateActivity( eNewActivity );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
WeaponProficiency_t CHLW_NPC_Combine::CalcWeaponProficiency( CBaseCombatWeapon *pWeapon )
{
	if( FClassnameIs( pWeapon, "weapon_smg1" ) )
	{
		return WEAPON_PROFICIENCY_PERFECT;
	}

	if( FClassnameIs( pWeapon, "weapon_sniperrifle" ) )
	{
		return WEAPON_PROFICIENCY_PERFECT;
	}

	return BaseClass::CalcWeaponProficiency( pWeapon );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int	CHLW_NPC_Combine::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	CTakeDamageInfo subInfo = info;
	if( GetCurrentOrder() )
	{
		if( IsCrouching() )
		{	
			// reward players for making there units search cover by reducing the damage they receive.
			if( (GetCurrentOrder()->GetHint() && GetCurrentOrder()->GetHint()->GetAbsOrigin().DistTo(GetAbsOrigin()) < 96.0f ) || 
				(GetCurrentOrder()->GetHidingSpot() && GetCurrentOrder()->GetHidingSpot()->GetPosition().DistTo(GetAbsOrigin()) < 96.0f ) )
				subInfo.ScaleDamage(0.6f);
		}
	}

	return BaseClass::OnTakeDamage_Alive(subInfo);
}

//-----------------------------------------------------------------------------
// Purpose: Our unit type depends on our weapons and flags
//-----------------------------------------------------------------------------
void CHLW_NPC_Combine::InitUnitIndex()
{
	const char *m_sUnitType;
	if( IsElite() ) {
		m_sUnitType = "UNIT_COMBINE_ELITE";
		m_iUnitIndex = g_pRTSResource->GetUnitIndex( m_sUnitType );
	}
	else if( HasShotgun() ) {
		m_sUnitType = "UNIT_COMBINE_SG";
		m_iUnitIndex = g_pRTSResource->GetUnitIndex( m_sUnitType );
	}
	else {
		m_sUnitType = "UNIT_COMBINE";
		m_iUnitIndex = g_pRTSResource->GetUnitIndex( m_sUnitType );
	}
}
