//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "c_AI_BaseNPC.h"
#include "engine/IVDebugOverlay.h"

#if defined( HL2_DLL ) || defined( HL2_EPISODIC )
#include "c_basehlplayer.h"
#endif

#include "death_pose.h"

// includes for fog of war
#include "hl2wars/c_rts_globalresource.h"
#include "hl2wars/c_hl2wars_player.h"
#include "hl2wars/hl2wars_convars.h"
#include "tier0/vprof.h"

#include "hl2wars/fx_quad2.h"

#include "model_types.h"

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

#define PING_MAX_TIME	2.0

IMPLEMENT_CLIENTCLASS_DT( C_AI_BaseNPC, DT_AI_BaseNPC, CAI_BaseNPC )
	RecvPropInt( RECVINFO( m_lifeState ) ),
	RecvPropBool( RECVINFO( m_bPerformAvoidance ) ),
	RecvPropBool( RECVINFO( m_bIsMoving ) ),
	RecvPropBool( RECVINFO( m_bFadeCorpse ) ),
	RecvPropInt( RECVINFO ( m_iDeathPose) ),
	RecvPropInt( RECVINFO( m_iDeathFrame) ),
	RecvPropInt( RECVINFO( m_iSpeedModRadius ) ),
	RecvPropInt( RECVINFO( m_iSpeedModSpeed ) ),
	RecvPropInt( RECVINFO( m_bSpeedModActive ) ),
	RecvPropBool( RECVINFO( m_bImportanRagdoll ) ),
	RecvPropFloat( RECVINFO( m_flTimePingEffect ) ),

	// rts data
	//RecvPropBool( RECVINFO( m_bCleanUp ) ),
	RecvPropInt( RECVINFO(m_iBehavior) ),
	RecvPropInt( RECVINFO(m_iUnitIndex) ),
	RecvPropInt		(RECVINFO(m_iHealth)),
	RecvPropInt		(RECVINFO(m_iMaxHealth)),
	RecvPropArray3( RECVINFO_ARRAY(m_fAbilityRechargeTimes), RecvPropFloat( RECVINFO( m_fAbilityRechargeTimes[0] ) ) ),
	RecvPropArray3( RECVINFO_ARRAY(m_hFowObjs), RecvPropEHandle( RECVINFO( m_hFowObjs[0] ) ) ),
	RecvPropInt		(RECVINFO(m_iFowObjsPointer)),
END_RECV_TABLE()

extern ConVar cl_npc_speedmod_intime;

bool NPC_IsImportantNPC( C_BaseAnimating *pAnimating )
{
	C_AI_BaseNPC *pBaseNPC = dynamic_cast < C_AI_BaseNPC* > ( pAnimating );

	if ( pBaseNPC == NULL )
		return false;

	return pBaseNPC->ImportantRagdoll();
}

C_AI_BaseNPC::C_AI_BaseNPC()
{
	m_vecOldPos.Init();		// init just in case. If the fog of war is turned off, it wouldn't matter.

	// testing
	//m_hSelectedEffect = NULL;

	m_ProjectTextureUnlitHandle = CLIENTSHADOW_INVALID_HANDLE;
	m_fSelectionCircleDist = 50.0f;
	m_bForceCheapSelectionCircle = false;
	m_pCheapSelectionCircle = NULL;

	// init the material reference for the selection circle
	m_SelectionCircleShadow.Init("decals/selection_circle", TEXTURE_GROUP_DECAL);
}

//-----------------------------------------------------------------------------
// Makes ragdolls ignore npcclip brushes
//-----------------------------------------------------------------------------
unsigned int C_AI_BaseNPC::PhysicsSolidMaskForEntity( void ) const 
{
	// This allows ragdolls to move through npcclip brushes
	if ( !IsRagdoll() )
	{
		return MASK_NPCSOLID; 
	}
	return MASK_SOLID;
}


void C_AI_BaseNPC::ClientThink( void )
{
	BaseClass::ClientThink();

#ifdef HL2_DLL
	C_BaseHLPlayer *pPlayer = dynamic_cast<C_BaseHLPlayer*>( C_BasePlayer::GetLocalPlayer() );

	if ( ShouldModifyPlayerSpeed() == true )
	{
		if ( pPlayer )
		{
			float flDist = (GetAbsOrigin() - pPlayer->GetAbsOrigin()).LengthSqr();

			if ( flDist <= GetSpeedModifyRadius() )
			{
				if ( pPlayer->m_hClosestNPC )
				{
					if ( pPlayer->m_hClosestNPC != this )
					{
						float flDistOther = (pPlayer->m_hClosestNPC->GetAbsOrigin() - pPlayer->GetAbsOrigin()).Length();

						//If I'm closer than the other NPC then replace it with myself.
						if ( flDist < flDistOther )
						{
							pPlayer->m_hClosestNPC = this;
							pPlayer->m_flSpeedModTime = gpGlobals->curtime + cl_npc_speedmod_intime.GetFloat();
						}
					}
				}
				else
				{
					pPlayer->m_hClosestNPC = this;
					pPlayer->m_flSpeedModTime = gpGlobals->curtime + cl_npc_speedmod_intime.GetFloat();
				}
			}
		}
	}
#endif // HL2_DLL

#ifdef HL2_EPISODIC
	C_BaseHLPlayer *pPlayer = dynamic_cast<C_BaseHLPlayer*>( C_BasePlayer::GetLocalPlayer() );

	if ( pPlayer && m_flTimePingEffect > gpGlobals->curtime )
	{
		float fPingEffectTime = m_flTimePingEffect - gpGlobals->curtime;
		
		if ( fPingEffectTime > 0.0f )
		{
			Vector vRight, vUp;
			Vector vMins, vMaxs;

			float fFade;

			if( fPingEffectTime <= 1.0f )
			{
				fFade = 1.0f - (1.0f - fPingEffectTime);
			}
			else
			{
				fFade = 1.0f;
			}

			GetRenderBounds( vMins, vMaxs );
			AngleVectors (pPlayer->GetAbsAngles(), NULL, &vRight, &vUp );
			Vector p1 = GetAbsOrigin() + vRight * vMins.x + vUp * vMins.z;
			Vector p2 = GetAbsOrigin() + vRight * vMaxs.x + vUp * vMins.z;
			Vector p3 = GetAbsOrigin() + vUp * vMaxs.z;

			int r = 0 * fFade;
			int g = 255 * fFade;
			int b = 0 * fFade;

			debugoverlay->AddLineOverlay( p1, p2, r, g, b, true, 0.05f );
			debugoverlay->AddLineOverlay( p2, p3, r, g, b, true, 0.05f );
			debugoverlay->AddLineOverlay( p3, p1, r, g, b, true, 0.05f );
		}
	}
#endif
}

void C_AI_BaseNPC::OnDataChanged( DataUpdateType_t type )
{
	BaseClass::OnDataChanged( type );

	if ( ( ShouldModifyPlayerSpeed() == true ) || ( m_flTimePingEffect > gpGlobals->curtime ) )
	{
		SetNextClientThink( CLIENT_THINK_ALWAYS );
	}

	// update fow ( shouldn't this be moved to a different place?)
	if( sv_fogofwar.GetBool() )
	{
		if( m_fNextUpdateFOW < gpGlobals->curtime )
		{
			UpdateFOW( IsVisibleForPlayer() );
			m_fNextUpdateFOW = gpGlobals->curtime + 0.2f;	// don't update too much, it is pretty intensive
		}
	}
	else 
	{
		if( !IsVisibleFOW() )
		{
			SetVisibleFOW( true );
			
			if( GetActiveWeapon() )
			{
				GetActiveWeapon()->SetVisibleFOW( true );
			}
		}
	}

	// change team detection
	if( GetTeamRTSID() != m_iOldTeam )
	{
		g_RTSR->ChangeTeamUnit( m_iOldTeam, GetTeamRTSID(), this );

		m_iOldTeam = GetTeamRTSID();
	}
}

void C_AI_BaseNPC::GetRagdollInitBoneArrays( matrix3x4_t *pDeltaBones0, matrix3x4_t *pDeltaBones1, matrix3x4_t *pCurrentBones, float boneDt )
{
	ForceSetupBonesAtTime( pDeltaBones0, gpGlobals->curtime - boneDt );
	GetRagdollCurSequenceWithDeathPose( this, pDeltaBones1, gpGlobals->curtime, m_iDeathPose, m_iDeathFrame );
	float ragdollCreateTime = PhysGetSyncCreateTime();
	if ( ragdollCreateTime != gpGlobals->curtime )
	{
		// The next simulation frame begins before the end of this frame
		// so initialize the ragdoll at that time so that it will reach the current
		// position at curtime.  Otherwise the ragdoll will simulate forward from curtime
		// and pop into the future a bit at this point of transition
		ForceSetupBonesAtTime( pCurrentBones, ragdollCreateTime );
	}
	else
	{
		SetupBones( pCurrentBones, MAXSTUDIOBONES, BONE_USED_BY_ANYTHING, gpGlobals->curtime );
	}
}

// --- rts functions
const char *C_AI_BaseNPC::GetUnitType( void ) 
{ 
	return g_RTSR->GetUnitType(m_iUnitIndex); 
}

//--------------------------------------------------------------------
// Purpose: 
//--------------------------------------------------------------------
void C_AI_BaseNPC::UpdateOnRemove( void )
{
	BaseClass::UpdateOnRemove();

	// shutdown selection circle
	DestroySelectionCircle();
	m_SelectionCircleShadow.Shutdown();

	if( m_pCheapSelectionCircle )
	{
		m_pCheapSelectionCircle->DeActivate();
		m_pCheapSelectionCircle = NULL;
	}

	// avoid a crash on exit
	if( !g_RTSR )
		return;

	g_RTSR->RemoveUnit( GetTeamRTSID(), this );

	// read the latest part of the fog we cleared
	if( sv_fogofwar.GetBool() )
	{
		changefogofwar_t update;
		update.delay = gpGlobals->curtime + 3.5f;
		update.position = m_vecOldPos;
		update.radius = g_RTSR->GetUnitViewDistance( GetUnitIndex() );
		update.type = FOW_ADDREMOVE;
		g_RTSR->m_utlFogInfo.AddToTail( update );
	}
}

//--------------------------------------------------------------------
// Purpose: Blink :)
//--------------------------------------------------------------------
void C_AI_BaseNPC::Blink( float blink_time )
{
	m_bIsBlinking = true;
	m_fBlinkTimeOut = gpGlobals->curtime + blink_time;
}

//--------------------------------------------------------------------
//--------------------------------------------------------------------
// Purpose:
//--------------------------------------------------------------------
bool C_AI_BaseNPC::IsVisibleForPlayer()
{
	VPROF_BUDGET( "C_AI_BaseNPC::IsVisibleForPlayer", VPROF_BUDGETGROUP_FOGOFWAR );

	C_HL2Wars_Player* pPlayer = C_HL2Wars_Player::GetLocalHL2WarsPlayer();
	if( !pPlayer ) 
		return true;

	if( pPlayer->GetTeamRTSID() == GetTeamRTSID() )
	{
		return true;
	}

	return g_RTSR->IsUnitVisible( this );
}

//--------------------------------------------------------------------
// Purpose: Override this in the baseclass if more needs to be hidden
//--------------------------------------------------------------------
void C_AI_BaseNPC::UpdateFOW( bool visible )
{
	if( visible )
	{
		SetVisibleFOW( true );
			
		if( GetActiveWeapon() )
		{
			GetActiveWeapon()->SetVisibleFOW( true );
		}

		for( int i = 0; i < m_iFowObjsPointer; i++ )
		{
			if( m_hFowObjs[i] )
				m_hFowObjs[i].Get()->SetVisibleFOW( true );
		}
	}
	else
	{
		SetVisibleFOW( false );

		if( GetActiveWeapon() )
		{
			GetActiveWeapon()->SetVisibleFOW( false );
		}

		for( int i = 0; i < m_iFowObjsPointer; i++ )
		{
			if( m_hFowObjs[i] )
				m_hFowObjs[i].Get()->SetVisibleFOW( false );
		}
	}
}

void C_AI_BaseNPC::CreateSelectionCircle()
{
	// create the selection circle
	const Vector color = g_RTSR->GetColor255( GetTeamRTSID() );
	if( m_bForceCheapSelectionCircle )
	{
		// get size of quad
		Vector origin = GetAbsOrigin();
		Vector mins, maxs; 
		ICollideable* pCol = GetCollideable(); 
		mins = pCol->OBBMins();
		maxs = pCol->OBBMaxs();
		float size = (maxs - mins).Length2D() + 10.0f; 

		// get position of quad	
		const QAngle normalAngle = GetAbsAngles();	    
		Vector normal, stub, stub2;       
		AngleVectors(normalAngle, &stub, &stub2, &normal);	    
		origin[2] += 1.0f; // let it fly just a little bit above the ground		

		//IMaterial *pMaterial = materials->FindMaterial( QUAD_TO_SELECT_NEW, NULL, false );
		//materials->Bind( pMaterial );

		//Msg("%f %f %f \n", color[0], color[1], color[2]);
		m_pCheapSelectionCircle = FX_AddQuad2(origin, normal, size, 1.0f,    
			0.0f, 0.0f, color, this, QUAD_TO_SELECT, 0, true); 
	}
	else
	{
		if (m_ProjectTextureUnlitHandle == CLIENTSHADOW_INVALID_HANDLE)
		{
			//int flags = SHADOW_FLAGS_SHADOW;
			int flags = SHADOW_FLAGS_SHADOW|SHADOW_FLAGS_PROJECTTEXTUREUNLIT;

			Vector mins, maxs; 
			ICollideable* pCol = GetCollideable(); 
			mins = pCol->OBBMins();
			maxs = pCol->OBBMaxs();
			//maxs.z += -1*mins.z;
			//mins.z += -1*mins.z;

			ProjectTextureUnlitInfo_t info;
			info.m_PTUnlitShadow = &m_SelectionCircleShadow;
			info.m_PTUnlitMins = mins;
			info.m_PTUnlitMaxs = maxs;
			info.m_cR = color.x;
			info.m_cG = color.y;
			info.m_cB = color.z;
			info.m_cA = 255;
			info.m_fPTShadowDistance = m_fSelectionCircleDist;
			info.m_vOrigin = GetRenderOrigin();
			info.m_Angle = GetRenderAngles();

			m_Info = info;
			//m_ProjectTextureUnlitHandle = g_pClientShadowMgr->CreatePTUnlit(info, GetClientHandle(), flags);
			m_ProjectTextureUnlitHandle = g_pClientShadowMgr->CreatePTUnlit(info, INVALID_CLIENTENTITY_HANDLE, flags);
		}
	}
}

void C_AI_BaseNPC::DestroySelectionCircle()
{
	// destroy the selection circle
	if (m_ProjectTextureUnlitHandle != CLIENTSHADOW_INVALID_HANDLE)
	{
		g_pClientShadowMgr->DestroyPTUnlit(m_ProjectTextureUnlitHandle);
		m_ProjectTextureUnlitHandle = CLIENTSHADOW_INVALID_HANDLE;
	}

	if( m_pCheapSelectionCircle )
	{
		m_pCheapSelectionCircle->DeActivate();
		m_pCheapSelectionCircle = NULL;
	}
}

int C_AI_BaseNPC::DrawModel( int flags )
{
	if( m_bIsBlinking )
	{
		flags |= STUDIO_ITEM_BLINK;
		if( m_fBlinkTimeOut < gpGlobals->curtime )
			m_bIsBlinking = false;
	}

	return BaseClass::DrawModel(flags);
}

void C_AI_BaseNPC::Simulate( void )
{
	// update our selection circle, if we have one
	if ( m_ProjectTextureUnlitHandle != CLIENTSHADOW_INVALID_HANDLE )
	{
		m_Info.m_vOrigin = GetRenderOrigin();
		m_Info.m_Angle = GetRenderAngles();
		g_pClientShadowMgr->UpdatePTUnlitState( m_ProjectTextureUnlitHandle, m_Info, true );
	}
	

	BaseClass::Simulate();
}




