//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================
#include "cbase.h"
#include "basetfplayer_shared.h"
#include "in_buttons.h"
#include "tf_shareddefs.h"
#include "basegrenade_shared.h"
#include "basetfcombatweapon_shared.h"
#include "weapon_limpetmine.h"
#include "IEffects.h"
#include "engine/IEngineSound.h"
#include "weapon_grenade_rocket.h"
#include "beam_shared.h"
#include "tf_gamerules.h"

#if defined( CLIENT_DLL )
#else
#include "grenade_limpetmine.h"
#include "tf_obj_sentrygun.h"
#include "ai_network.h"
#include "tf_team.h"
#endif

// Damage CVars
ConVar	weapon_laserdesignator_range( "weapon_laserdesignator_range","2048", FCVAR_REPLICATED, "Laser Designator maximum range" );
ConVar	weapon_limpetmine_max_deployed( "weapon_limpetmine_max_deployed","0", FCVAR_REPLICATED, "Maximum number of Limpet Mines that can be deployed at a single time" );
ConVar	weapon_limpetmine_max_distance_off_trace( "weapon_limpetmine_max_distance_off_trace","25", FCVAR_REPLICATED, "Maximum distance off of the trace that a limpet can be." );

IMPLEMENT_NETWORKCLASS_ALIASED( WeaponLimpetmine, DT_WeaponLimpetmine )

BEGIN_NETWORK_TABLE( CWeaponLimpetmine, DT_WeaponLimpetmine )
#if !defined( CLIENT_DLL )
	SendPropInt( SENDINFO( m_iDeployedLimpets ), 5, SPROP_UNSIGNED ),
	SendPropEHandle( SENDINFO( m_hDesignatedEntity ) ),
#else
	RecvPropInt( RECVINFO( m_iDeployedLimpets ) ),
	RecvPropEHandle( RECVINFO(m_hDesignatedEntity) ),
#endif
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CWeaponLimpetmine  )

	DEFINE_PRED_FIELD( CWeaponLimpetmine, m_iDeployedLimpets, FIELD_INTEGER, FTYPEDESC_INSENDTABLE ),
	DEFINE_PRED_FIELD( CWeaponLimpetmine, m_hDesignatedEntity, FIELD_EHANDLE, FTYPEDESC_INSENDTABLE ),

END_PREDICTION_DATA()

LINK_ENTITY_TO_CLASS( weapon_limpetmine, CWeaponLimpetmine );
PRECACHE_WEAPON_REGISTER(weapon_limpetmine);

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CWeaponLimpetmine::CWeaponLimpetmine( void )
{
	SetPredictionEligible( true );

	m_hDesignatedEntity = NULL;
	m_flNextBuzzTime = 0;
	m_iDeployedLimpets = 0;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::Precache( void )
{
	BaseClass::Precache();

#ifndef CLIENT_DLL
	UTIL_PrecacheOther( "grenade_limpetmine" );
#endif
	PrecacheModel( "sprites/laserbeam.vmt" );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::UpdateOnRemove( void )
{
#ifndef CLIENT_DLL
	RemoveDeployedLimpets();

	if ( m_hLaserDesignation )
	{
		UTIL_Remove( m_hLaserDesignation );
	}
#endif

	// Chain at end to mimic destructor unwind order
	BaseClass::UpdateOnRemove();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::CleanupOnActStart( void )
{
#ifndef CLIENT_DLL
	RemoveDeployedLimpets();
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CWeaponLimpetmine::GetFireRate( void )
{	
	return 0.5; 
}

//-----------------------------------------------------------------------------
// Purpose: Limpetmine considers itself as having ammo at all times, because it contains the designator
//-----------------------------------------------------------------------------
bool CWeaponLimpetmine::HasAnyAmmo( void )
{
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Stop thinking and holster
//-----------------------------------------------------------------------------
bool CWeaponLimpetmine::Holster( CBaseCombatWeapon *pSwitchingTo )
{
	StopDesignating();
	return BaseClass::Holster(pSwitchingTo);
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::ItemPostFrame( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( !pPlayer )
		return;

#ifndef CLIENT_DLL
	// If we don't have a laser designator yet, create one
	if ( !m_hLaserDesignation )
	{
		m_hLaserDesignation = CEnvLaserDesignation::Create( pPlayer ); 
	}
#endif

	// Is the player trying to designate?
	if ( pPlayer->m_nButtons & IN_ATTACK )
	{
		if ( m_bDesignating )
		{
#ifndef CLIENT_DLL
			ActivateBeam();
#endif
		}
		else if ( m_flNextPrimaryAttack <= gpGlobals->curtime )
		{
			StartDesignating();
#ifndef CLIENT_DLL
			ActivateBeam();
#endif
		}
	}
	else if ( m_bDesignating )
	{
		StopDesignating();
	}
	else if ( (pPlayer->m_nButtons & IN_ATTACK2) && !m_flStartedLaunchingAt && (m_flNextPrimaryAttack <= gpGlobals->curtime) )
	{
		m_flStartedLaunchingAt = gpGlobals->curtime;

		SendWeaponAnim( ACT_VM_PRIMARYATTACK );
	}
	else if ( (pPlayer->m_afButtonReleased & IN_ATTACK2) && (m_flNextPrimaryAttack <= gpGlobals->curtime) && m_flStartedLaunchingAt )
	{
		m_flNextPrimaryAttack = gpGlobals->curtime;
		PrimaryAttack();
		m_flStartedLaunchingAt = 0;
	}

	UpdateBeamTarget();

	// Always idle
	WeaponIdle();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::PrimaryAttack( void )
{
	CBaseTFPlayer *pPlayer = ToBaseTFPlayer( GetOwner() );
	if ( !pPlayer )
		return;

	// Don't allow anymore limpets to be placed if we've reached the max
	if ( m_iDeployedLimpets >= weapon_limpetmine_max_deployed.GetInt() )
	{
#ifdef CLIENT_DLL
		// We should flash the limpet mine count on the weapon at this point
		CLocalPlayerFilter filter;
		EmitSound( filter, entindex(), "WeaponLimpetmine.Deny" );
		m_flNextBuzzTime = gpGlobals->curtime + 0.5f;	// only buzz every so often.
#endif
		return;
	}

	if ( IsOwnerEMPed() )
		return;

	trace_t tr;
	// Get an aim vector. Don't use GetAimVector() because we don't want autoaiming.
	Vector vecSrc = pPlayer->Weapon_ShootPosition( );
	Vector vecAiming;
	pPlayer->EyeVectors( &vecAiming );

	// Calculate launch velocity (3 seconds for max distance)
	float flThrowTime = min( (gpGlobals->curtime - m_flStartedLaunchingAt), 3.0 );
	float flSpeed = 600 + (300 * flThrowTime);
	vecAiming *= flSpeed;

	WeaponSound( DOUBLE );
#ifndef CLIENT_DLL
	ThrowLimpet( pPlayer, vecSrc, vecAiming );
#endif
	SendWeaponAnim( ACT_VM_SECONDARYATTACK );

	m_flNextPrimaryAttack = gpGlobals->curtime + GetFireRate();
	m_flNextSecondaryAttack = gpGlobals->curtime + GetFireRate();

	pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType );
}

#if !defined( CLIENT_DLL )
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::ThrowLimpet( CBasePlayer *pPlayer, Vector vecSrc, Vector vecAiming )
{
	CLimpetMine *pLimpet = CLimpetMine::Create(vecSrc, vecAiming, pPlayer);
	pLimpet->SetLauncher( this );

	// Increase the limpet count
	m_iDeployedLimpets += 1;
}

//-----------------------------------------------------------------------------
// Purpose: Detonates all deployed limpets for this weapon
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::DetonateDeployedLimpets( void )
{
	CBaseEntity *pEntity = NULL;
	while ((pEntity = gEntList.FindEntityByClassname( pEntity, "grenade_limpetmine" )) != NULL)
	{
		CLimpetMine* pLimpet = (CLimpetMine*)pEntity;
		if ( pLimpet->IsLive() && pLimpet->GetOwner() == GetOwner() )
		{
			pLimpet->Use( GetOwner(), this, USE_ON, 0 );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Quietly removes all deployed limpets for this weapon
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::RemoveDeployedLimpets( void )
{
	CBaseEntity *pEntity = NULL;
	while ((pEntity = gEntList.FindEntityByClassname( pEntity, "grenade_limpetmine" )) != NULL)
	{
		CLimpetMine* pLimpet = (CLimpetMine*)pEntity;
		if ( pLimpet->GetOwner() == GetOwner() )
		{
			pLimpet->Use( GetOwner(), this, USE_SET, 0 );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::ActivateBeam( void )
{
	CBaseTFPlayer *pPlayer = ToBaseTFPlayer( GetOwner() );
	if ( !pPlayer )
		return;

	Assert( m_pBeam );
	if( m_hDesignatedEntity != NULL )
	{
		CBaseEntity* pEntity = m_hDesignatedEntity.Get();

		// If we hit a player, and it's an enemy, tell my sentryguns to attack it
		if ( pEntity->IsPlayer() && !pPlayer->InSameTeam(pEntity) )
		{
			DesignateSentriesToAttack( pEntity );
		}
		else if ( pEntity->GetFlags() & FL_NPC )
		{
			// If it's an enemy NPC, tell my sentryguns to attack it
			if ( !pPlayer->InSameTeam( pEntity ) )
			{
				DesignateSentriesToAttack( pEntity );
			}
		}
		else
		{
			// Is it a sentrygun? If so, tell it to toggle it's sunken state.
			if ( pEntity->Classify() == CLASS_MILITARY )
			{
				CObjectSentrygun *pSentry = dynamic_cast<CObjectSentrygun *>(pEntity);
				if ( pSentry && pSentry->GetBuilder() == pPlayer )
				{
					pSentry->ToggleTurtle();
					return;
				}
				else 
				{
					// If it's an enemy object, tell my sentryguns to attack it
					if ( !pPlayer->InSameTeam( pEntity ) )
					{
						DesignateSentriesToAttack( pEntity );
						return;
					}
				}
			}

			// Is it a limpet mine? If so, detonate it
			CLimpetMine *pLimpet = dynamic_cast<CLimpetMine *>(pEntity);
			if ( pLimpet && pLimpet->IsLive() && pLimpet->GetOwner()->pev == pPlayer->pev )
			{
				pLimpet->Use( pPlayer, this, USE_ON, 0 );
			}

		}
	} 
}

//-----------------------------------------------------------------------------
// Purpose: Return true if the specified entity is a valid one for designation
//-----------------------------------------------------------------------------
bool CWeaponLimpetmine::ValidDesignationTarget( CBaseEntity *pEntity )
{
	if( !pEntity )
		return false;

	// Ignore the world
	if ((!pEntity) || ( pEntity->entindex() == 0 ))
		return false;

	CBaseTFPlayer *pPlayer = ToBaseTFPlayer( GetOwner() );
	Assert(pPlayer);
	if(!pPlayer) 
		return false;

	// Ignore players on my team
	if ( pEntity->IsPlayer() )
	{
		if( pEntity->InSameTeam(GetOwner()) )
			return false;
		else
			return true;
	}

	// can either target my own sentry guns to turtle, or enemy buildings to target for sentries:
	if ( pEntity->Classify() == CLASS_MILITARY )
	{
		// Is it a sentry gun I built?
		if ( dynamic_cast<CObjectSentrygun *>(pEntity) && ((CObjectSentrygun*)pEntity)->GetBuilder() == pPlayer )
		{
			return true;
		}
		// Is it an enemy object?
		else if ( !pPlayer->InSameTeam( pEntity ) )
		{
			return true;
		}
	}

	// My limpet mines are valid
	CLimpetMine *pLimpet = dynamic_cast<CLimpetMine *>(pEntity);
	if ( pLimpet && pLimpet->IsLive() && pLimpet->GetOwner()->pev == GetOwner()->pev )
		return true;

	// NPCs are valid
	if ( pEntity->GetFlags() & FL_NPC )
		return true;

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Set the player's sentryguns to all attack the specified target
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::DesignateSentriesToAttack( CBaseEntity *pEntity )
{
	CBaseTFPlayer *pPlayer = ToBaseTFPlayer( GetOwner() );
	if ( !pPlayer )
		return;

	// Tell all this player's sentryguns
	for ( int i = 0; i < pPlayer->GetObjectCount(); i++ )
	{
		CBaseObject *pObj = pPlayer->GetObject(i);
		if ( !pObj )
			continue;

		if ( pObj->IsSentrygun() )
		{
			CObjectSentrygun *pSentry = static_cast<CObjectSentrygun *>(pObj);
			pSentry->DesignateTarget( pEntity );
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseEntity* CWeaponLimpetmine::GetDesignatedEntity( CBaseTFPlayer *pPlayer )
{
	// Construct the start and end vectors.
	Vector vecSrc = pPlayer->Weapon_ShootPosition( );
	Vector vecAiming;
	pPlayer->EyeVectors( &vecAiming );
	Vector vecEnd = vecSrc + vecAiming * weapon_laserdesignator_range.GetFloat();

	float fBestDistanceSq = FLT_MAX;
	CBaseEntity* pBestEntity = NULL;
	float fMaxDist = weapon_limpetmine_max_distance_off_trace.GetFloat();
	float fMaxDistSq = fMaxDist * fMaxDist;

	// Check all limpets against a cylinder:
	if( CLimpetMine::allLimpets )
	{
		for ( CLimpetMine *pLimpet = CLimpetMine::allLimpets ;pLimpet; pLimpet = pLimpet->nextLimpet)
		{
			// Find the closest limpet to the center of the cylinder:
			if( pLimpet->IsLive() && pLimpet->GetOwner() != NULL && pLimpet->GetOwner()->pev == pPlayer->pev )
			{
				Vector vecNearestPoint = PointOnLineNearestPoint( vecSrc, vecEnd, pLimpet->GetAbsOrigin() );

				float fDistSq = ( pLimpet->GetAbsOrigin() - vecNearestPoint ).LengthSqr();
				
				if (( fDistSq > fMaxDistSq ) || ( fDistSq >= fBestDistanceSq ))
					continue;

				if (TFGameRules()->IsBlockedByEnemyShields( vecSrc, pLimpet->GetAbsOrigin(), GetOwner()->GetTeamNumber() ))
					continue;

				pBestEntity = pLimpet;
				fBestDistanceSq = fDistSq;
			}
		}

		if( pBestEntity )
			return pBestEntity;
	}

	// Check to see if we are designating a combat object:
	for ( int i = 0; i < pPlayer->GetObjectCount(); i++ )
	{
		CBaseObject *pObj = pPlayer->GetObject(i);
		if ( !pObj || !pObj->IsSentrygun() )
			continue;

		Vector vecObjCenter = pObj->WorldSpaceCenter();
		Vector vecNearestPoint = PointOnLineNearestPoint( vecSrc, vecEnd, vecObjCenter );

		float fDistSq = ( vecObjCenter - vecNearestPoint ).LengthSqr();
		
		if (( fDistSq > fMaxDistSq ) || ( fDistSq >= fBestDistanceSq ))
			continue;

		if (TFGameRules()->IsBlockedByEnemyShields( vecSrc, vecObjCenter, GetOwner()->GetTeamNumber() ))
			continue;

		pBestEntity = pObj;
		fBestDistanceSq = fDistSq;
	}

	// Valid or NULL
	return pBestEntity;
}

#endif

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::WeaponIdle( void )
{
	if ( HasWeaponIdleTimeElapsed() )
	{
		if ( m_bDesignating || m_flStartedLaunchingAt )
		{
			SendWeaponAnim( ACT_VM_PRIMARYATTACK );
		}
		else
		{
			SendWeaponAnim( ACT_VM_IDLE );
		}
		SetWeaponIdleTime( gpGlobals->curtime + 0.2 );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Start designating with the laser
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::StartDesignating( void )
{
	m_bDesignating = true;

	// Add myself to the designated target list
	Vector vecOrigin(0,0,0);

	SendWeaponAnim( ACT_VM_PRIMARYATTACK );

#ifndef CLIENT_DLL
	CBaseTFPlayer *pPlayer = ToBaseTFPlayer( GetOwner() );
	if ( !pPlayer )
		return;

	if ( !m_pBeam )
	{
		m_pBeam = CBeam::BeamCreate( "sprites/laserbeam.vmt", 5 );

		m_pBeam->PointEntInit( vec3_origin, this );
		m_pBeam->SetEndAttachment( 1 );
		m_pBeam->SetColor( 255, 32, 32 );
		m_pBeam->SetBrightness( 255 );
		m_pBeam->SetNoise( 0 );
		m_pBeam->SetWidth( 0.5 );
		m_pBeam->SetEndWidth( 0.5 );
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Stop designating with the laser
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::StopDesignating( void )
{
	SendWeaponAnim( ACT_VM_IDLE );

	m_bDesignating = false;

#ifndef CLIENT_DLL
	if ( m_pBeam )
	{
		UTIL_Remove( m_pBeam );
		m_pBeam = NULL;
	}

	if ( m_hLaserDesignation )
	{
		m_hLaserDesignation->SetActive( false );
	}

	// Remove any designated target:
	m_hDesignatedEntity.Set( NULL );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Update the beam position and do designator functions
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::UpdateBeamTarget()
{
	CBaseTFPlayer *pPlayer = ToBaseTFPlayer( GetOwner() );
	if ( !pPlayer )
		return;

	// "Fire" the designator beam
	Vector vecSrc = pPlayer->Weapon_ShootPosition( );
	Vector vecAiming;
	pPlayer->EyeVectors( &vecAiming );
	Vector vecEnd = vecSrc + vecAiming * weapon_laserdesignator_range.GetFloat();

	trace_t tr;
	TFGameRules()->WeaponTraceLine(vecSrc, vecEnd, MASK_SHOT, pPlayer, DMG_PROBE, &tr);

	// Only update our designated target point if we hit something
#ifndef CLIENT_DLL
	if ( tr.fraction != 1.0 && m_bDesignating )
	{
		m_hLaserDesignation->SetActive( true );
		m_hLaserDesignation->SetAbsOrigin( tr.endpos );
	}
	else
	{
		m_hLaserDesignation->SetActive( false );
	}

	// Update beam visual
	if( m_pBeam )
	{
		m_pBeam->SetStartPos( tr.endpos );
		m_pBeam->RelinkBeam();
	}

	CBaseEntity* pEntity= NULL;

	// Perform designator functions
	// Did we hit something?
	//pEntity = CBaseEntity::Instance( tr.u.ent );

	// If we hit a target we don't care about, try and grab the nearest valid target to our crosshair
	//if ( !ValidDesignationTarget( pEntity ) )
	{
		// Get the designated entity, ignoring trace information:
		pEntity = GetDesignatedEntity( pPlayer );
		
		if( !ValidDesignationTarget( pEntity ) )
		{
			m_hDesignatedEntity.Set( NULL );
			return;
		}
	}

	// We have a valid entity, light it up.
	m_hDesignatedEntity.Set( pEntity );
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponLimpetmine::DecrementLimpets( void )
{
#ifndef CLIENT_DLL
	if ( m_iDeployedLimpets )
	{
		m_iDeployedLimpets -= 1;
	}
#endif
}

#ifdef CLIENT_DLL
//-----------------------------------------------------------------------------
// Purpose: Limpet mine's always selectable, because the laser designator can always fire
//-----------------------------------------------------------------------------
bool C_WeaponLimpetmine::CanBeSelected( void )
{
	return true;
}

void C_WeaponLimpetmine::PreDataUpdate( DataUpdateType_t updateType )
{
	BaseClass::PreDataUpdate( updateType );

	m_hOldDesignatedEntity = m_hDesignatedEntity;
}

void C_WeaponLimpetmine::PostDataUpdate( DataUpdateType_t updateType )
{
	BaseClass::PostDataUpdate( updateType );

	if ( GetOwner() == C_BaseTFPlayer::GetLocalPlayer() )
	{
		// Old target isn't valid anymore, so reset it's render properties
		if ( m_hOldDesignatedEntity.Get() && m_hOldDesignatedEntity != m_hDesignatedEntity )
		{
			m_hOldDesignatedEntity->m_nRenderFX = m_eDesignatedEntityOriginalRenderFx;
			m_hOldDesignatedEntity->m_nRenderMode = m_eDesignatedEntityOriginalRenderMode;
		}

		// New target? Set it's render properties
		if ( m_hDesignatedEntity != NULL && m_hOldDesignatedEntity != m_hDesignatedEntity )
		{
			// Store off original info for designated entity
			m_eDesignatedEntityOriginalRenderFx		= m_hDesignatedEntity->m_nRenderFX;
			m_eDesignatedEntityOriginalRenderMode	= m_hDesignatedEntity->m_nRenderMode;

			// Set up alpha blended pulsefast renderer
			m_hDesignatedEntity->m_nRenderFX	= kRenderFxPulseFastWider;
			m_hDesignatedEntity->m_nRenderMode	= kRenderTransAlpha;
		}
	}
}

#endif