//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:		357 - hand gun
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "NPCEvent.h"
#include "basehlcombatweapon.h"
#include "basecombatcharacter.h"
#include "AI_BaseNPC.h"
#include "player.h"
#include "gamerules.h"
#include "in_buttons.h"
#include "soundent.h"
#include "game.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "te_effect_dispatch.h"
#include "beam_flags.h"
#include "ieffects.h"
#include "sprite.h"
#include "explode.h"
#include "ammodef.h"

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

#ifndef CLIENT_DLL
ConVar weapon_proto1_rate_of_fire( "weapon_proto1_rate_of_fire", "5" );
ConVar weapon_proto1_mass_limit( "weapon_proto1_mass_limit", "250" );
ConVar weapon_proto1_alt_fire_range( "weapon_proto1_alt_fire_range", "250" );
ConVar weapon_proto1_ping_delay( "weapon_proto1_ping_delay", "1" );
ConVar weapon_proto1_ping_radius( "weapon_proto1_ping_radius", "1200" );
ConVar weapon_proto1_penetrate_depth( "weapon_proto1_penetrate_depth", "24" );
ConVar weapon_proto1_exit_blast_radius( "weapon_proto1_exit_blast_radius", "100" );
ConVar weapon_proto1_echo_sound_duration( "weapon_proto1_echo_sound_duration", "0.25" );
#endif

#define PENETRATING_BOLT_MODEL "models/crossbow_bolt.mdl"

// The sound that is heard when the ping detects an object.
#define SONAR_ECHO_SOUND	"Weapon_Proto1.Echo"

// The sound the player hears when activating a ping
#define SONAR_PING_SOUND "Weapon_Proto1.Ping"

// The sound played by a physics object that is being consumed
#define CONSUME_OBJECT_SOUND "Weapon_AR2.NPC_Double"

//-----------------------------------------------------------------------------
// CWeaponProto1
//-----------------------------------------------------------------------------
class CWeaponProto1 : public CBaseHLCombatWeapon
{
	DECLARE_CLASS( CWeaponProto1, CBaseHLCombatWeapon );
public:

	CWeaponProto1( void );

	void	Precache( void );
	void	ItemPostFrame( void );
	void	PrimaryAttack( void );
	void	SecondaryAttack( void );
	void	Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator );
	void	Equip( CBaseCombatCharacter *pOwner );

	void	FireHitscanBolt();
	bool	CanHitscanBoltReachTarget();

	void	FirePenetratingBolt();
	bool	CanPenetratingBoltReachTarget( CBaseEntity *pTarget );
	bool	CanConsumeObject( CBaseEntity *pObject );
	void	ConsumeObject( CBaseEntity *pObject );
	void	SendPing();
	void	DoPingEffect();
	void	DoPingSearch();
	void	PingEntity( CBaseEntity *pEntity );

	CBaseEntity *DetectObject();

	float	m_flTimeNextPing;
	int		m_iPingSpriteTexture;

	int		m_iEchoSoundsToDo;
	float	m_flTimeNextEchoSound;
	float	m_flEchoSoundDelay;

	DECLARE_SERVERCLASS();
	DECLARE_DATADESC();
};

LINK_ENTITY_TO_CLASS( weapon_proto1, CWeaponProto1 );

PRECACHE_WEAPON_REGISTER( weapon_proto1 );

IMPLEMENT_SERVERCLASS_ST( CWeaponProto1, DT_WeaponProto1 )
END_SEND_TABLE()

BEGIN_DATADESC( CWeaponProto1 )
	DEFINE_FIELD( m_flTimeNextPing, FIELD_TIME ),
	DEFINE_FIELD( m_iEchoSoundsToDo, FIELD_INTEGER ),
	DEFINE_FIELD( m_flTimeNextEchoSound, FIELD_TIME ),
	DEFINE_FIELD( m_flEchoSoundDelay, FIELD_FLOAT ),
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CWeaponProto1::CWeaponProto1( void )
{
	m_bReloadsSingly	= false;
	m_bFiresUnderwater	= false;
	m_iEchoSoundsToDo = 0;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponProto1::Operator_HandleAnimEvent( animevent_t *pEvent, CBaseCombatCharacter *pOperator )
{
	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

	switch( pEvent->event )
	{
		case EVENT_WEAPON_RELOAD:
			{
				CEffectData data;

				// Emit six spent shells
				for ( int i = 0; i < 6; i++ )
				{
					data.m_vOrigin = pOwner->WorldSpaceCenter() + RandomVector( -4, 4 );
					data.m_vAngles = QAngle( 90, random->RandomInt( 0, 360 ), 0 );
					data.m_nEntIndex = entindex();

					DispatchEffect( "ShellEject", data );
				}

				break;
			}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::FireHitscanBolt()
{
	if( !HasAnyAmmo() )
	{
		WeaponSound( EMPTY );
		return;
	}

	CBasePlayer *pOwner = ToBasePlayer( GetOwner() );

	if ( pOwner == NULL )
		return;

	Vector vecAiming	= pOwner->GetAutoaimVector( 0 );
	Vector vecSrc		= pOwner->Weapon_ShootPosition();

	QAngle angAiming;
	VectorAngles( vecAiming, angAiming );

	// We're committed to the shot now. So take the ammo.
	pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType );

	trace_t tr;
	// Trace the initial shot from the weapon
	UTIL_TraceLine( vecSrc, vecSrc + vecAiming * MAX_COORD_INTEGER, MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr );
	NDebugOverlay::Line( vecSrc + Vector( 0, 0, -3 ), tr.endpos, 255, 255, 0, false, 0.1f );

	if( tr.m_pEnt == NULL )
	{
		// Bail out if we hit nothing at all.
		return;
	}

	// Record the entity that the beam struck.
	CBaseEntity *pObjectStruck = tr.m_pEnt;
	
	if( pObjectStruck->MyNPCPointer() && pObjectStruck->IsAlive() )
	{
		CTakeDamageInfo damageInfo( this, pOwner, tr.m_pEnt->GetHealth(), DMG_GENERIC | DMG_DISSOLVE );
		Vector force( 0, 0, 100.0f );
		damageInfo.SetDamageForce( force );
		damageInfo.SetDamagePosition( tr.endpos );
		tr.m_pEnt->TakeDamage( damageInfo );
	}

	// Mark the point where the shot entered an object.
	//NDebugOverlay::Cross3D( tr.endpos, 8, 255, 255, 255, false, 60.0f );

	// Trace the second, punched-through shot. First, we have to locate the exit point.
	// There are two methods for this. If the shot struck the world, we have to trace out of solid.
	// If the shot struck an entity, we simply trace again, ignoring that entity.
	CBaseEntity *pIgnoreEnt = NULL;

	if( pObjectStruck->IsWorld() )
	{
		pIgnoreEnt = pOwner;
		// World method. Trace till you leave solid, then continue.
		
		// Move the trace "cursor" into the surface
		Vector vecStart = tr.endpos + vecAiming * 1.0f;

		// Now see if this solid is thin enough to penetrate.
		UTIL_TraceLine( vecStart, vecStart + vecAiming * weapon_proto1_penetrate_depth.GetFloat(), MASK_SHOT, pIgnoreEnt, COLLISION_GROUP_NONE, &tr );

		if( tr.fractionleftsolid <= 0.0f || tr.fractionleftsolid >= 1.0f )
		{
			// Wall can not be penetrated (too thick)
			return;
		}

		// This is a bit hacky, overwriting this value, but it lets us re-use code below.
		tr.endpos = vecStart + vecAiming * (weapon_proto1_penetrate_depth.GetFloat() * tr.fractionleftsolid);
	}
	else
	{
		// Ignore whatever was struck.
		pIgnoreEnt = tr.m_pEnt;
	}

	//NDebugOverlay::Cross3D( tr.endpos, 8, 255, 255, 255, false, 60.0f );

	// Now just ignore the ent and trace again.
	UTIL_TraceLine( tr.endpos, tr.endpos + vecAiming * MAX_COORD_INTEGER, MASK_SHOT, pIgnoreEnt, COLLISION_GROUP_NONE, &tr );

	if( tr.m_pEnt != NULL )
	{
		NDebugOverlay::Line( tr.startpos, tr.endpos, 255, 0, 255, false, 0.1f );

		if( tr.m_pEnt->MyNPCPointer() && tr.m_pEnt->IsAlive() )
		{
			Vector force( 0, 0, 1 );
			CTakeDamageInfo damageInfo( this, pOwner, tr.m_pEnt->GetHealth(), DMG_GENERIC | DMG_DISSOLVE );
			damageInfo.SetDamageForce( force );
			damageInfo.SetDamagePosition( tr.endpos );
			tr.m_pEnt->TakeDamage( damageInfo );
		}
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::Equip( CBaseCombatCharacter *pOwner )
{
	BaseClass::Equip( pOwner );

	pOwner->GiveAmmo( 3, "ammo_proto1" );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::Precache( void )
{
	BaseClass::Precache();
	m_iPingSpriteTexture = PrecacheModel( "sprites/physbeam.vmt" );

	UTIL_PrecacheOther( "penetrating_bolt" );

	PrecacheSound( SONAR_ECHO_SOUND );
	PrecacheSound( SONAR_PING_SOUND );
	PrecacheSound( CONSUME_OBJECT_SOUND );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::ItemPostFrame( void )
{
	BaseClass::ItemPostFrame();

	CBaseEntity *pObject = DetectObject();

	if( pObject && CanConsumeObject( pObject ) )
	{
		g_pEffects->Sparks( pObject->WorldSpaceCenter() );
	}

	if( m_iEchoSoundsToDo > 0 && gpGlobals->curtime >= m_flTimeNextEchoSound )
	{
		EmitSound( SONAR_ECHO_SOUND );
		m_iEchoSoundsToDo--;

		m_flTimeNextEchoSound = gpGlobals->curtime + m_flEchoSoundDelay;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponProto1::PrimaryAttack( void )
{
	// Only the player fires this way so we can cast
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );

	if ( !pPlayer )
	{
		return;
	}

	SendWeaponAnim( ACT_VM_PRIMARYATTACK );
	pPlayer->SetAnimation( PLAYER_ATTACK1 );

	WeaponSound( SINGLE );
	pPlayer->DoMuzzleFlash();

	//FirePenetratingBolt();
	FireHitscanBolt();

	float flRateOfFireDelay = 1.0f / weapon_proto1_rate_of_fire.GetFloat();
	m_flNextPrimaryAttack = gpGlobals->curtime + flRateOfFireDelay;

	pPlayer->ViewPunch( QAngle( -1, random->RandomFloat( -1, 1 ), 0 ) );

	if ( !HasAnyAmmo() )
	{
		// HEV suit - indicate out of ammo condition
		pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); 
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::SecondaryAttack( void )
{
	m_flNextSecondaryAttack = gpGlobals->curtime + 0.1f;

	// See if we can detect an object that can be eaten
	CBaseEntity *pObject = DetectObject();
	if( pObject )
	{
		if( CanConsumeObject( pObject ) )
		{
			ConsumeObject( pObject );
		}

		return;
	}

	// Otherwise, try to Ping
	if( m_flTimeNextPing > gpGlobals->curtime )
	{
		// Too soon!
		return;
	}

	SendPing();
}

//-----------------------------------------------------------------------------
// Do a bunch of computations and determine whether a bolt fired from this location
// could punch through a wall and harm this target. This function assumes that
// the attacker does NOT have line of sight to the target.
//
// A bolt can reach the target IF:
//	-The target is not protected by a solid that is thicker than 
//	 the weapon's ability to penetrate solids
//
//	-Only one solid separates the attacker from the target.
//-----------------------------------------------------------------------------
bool CWeaponProto1::CanPenetratingBoltReachTarget( CBaseEntity *pTarget )
{
	trace_t tr;
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );

	if( !pPlayer )
	{
		return false;
	}

	UTIL_TraceLine( pTarget->WorldSpaceCenter(), pPlayer->EyePosition(), MASK_SHOT, pTarget, COLLISION_GROUP_NONE, &tr );

	// This target is too far from the wall if his world space center is farther from the wall than the weapon's blast radius.
	// (The explosion that accompanies the bolt's exit from the solid would not reach the target)
	float flDistFromWall = (tr.startpos - tr.endpos).Length();

	if( flDistFromWall > weapon_proto1_exit_blast_radius.GetFloat() )
		return false;
	
	// At this point in the function, we know that the target is close enough to the solid to be harmed by the explosion
	// caused when the bolt exits the solid. Now we just need to check to see if the solid is thin enough for the bolt to
	// pass through. To do this, we trace from the player to the target and measure the distance between the two traces' endpoints.
	Vector vecTargetEndPos = tr.endpos;
	UTIL_TraceLine( pPlayer->EyePosition(), pTarget->WorldSpaceCenter(), MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );
	float flSolidThickness = (tr.endpos - vecTargetEndPos).Length();

	if( flSolidThickness > weapon_proto1_penetrate_depth.GetFloat() )
	{
		// In this case, the endpoints of the target's traceline and the player's traceline are farther apart than the bolt's penetration
		// depth. This either means that there is a single solid between the two that is too thick to penetrate, or that there are 
		// several solids between the two. Either is a failure case. 
		return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CWeaponProto1::CanConsumeObject( CBaseEntity *pObject )
{
	IPhysicsObject *pPhysObject = pObject->VPhysicsGetObject();

	if( !pPhysObject )
	{
		Msg("Object does not have VPhysics\n");
		return false;
	}

	if( pPhysObject->GetMass() > weapon_proto1_mass_limit.GetFloat() )
	{
		Msg("Object too heavy\n");
		return false;
	}

	if( !pObject->ClassMatches( "prop_physics" ) )
	{
		return false;
	}

	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::ConsumeObject( CBaseEntity *pObject )
{
	CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating*>(pObject);

	if( !pAnimating )
	{
		Msg("FAILED to consume %s\n", pObject->GetClassname() );
		return;
	}

	pAnimating->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_CORE );
	pAnimating->EmitSound( CONSUME_OBJECT_SOUND );
		
	Msg("GULP!\n");

	m_flNextSecondaryAttack = gpGlobals->curtime + 1.0f;

	GetOwner()->GiveAmmo( 1, "ammo_proto1" );
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::SendPing()
{
	EmitSound( SONAR_PING_SOUND );

	DoPingEffect();
	DoPingSearch();
	m_flTimeNextPing = gpGlobals->curtime + weapon_proto1_ping_delay.GetFloat();
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::DoPingEffect() 
{
	CBroadcastRecipientFilter filter;

	te->BeamRingPoint( filter, 0, GetAbsOrigin(),	//origin
		8.0f,	//start radius
		weapon_proto1_ping_radius.GetFloat() * 2.0f,		//end radius
		m_iPingSpriteTexture, //texture
		0,			//halo index
		0,			//start frame
		2,			//framerate
		0.5f,		//life
		64,			//width
		0,			//spread
		0,			//amplitude
		255,	//r
		255,	//g
		225,	//b
		128,	//a
		0,		//speed
		FBEAM_FADEOUT
		);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::DoPingSearch() 
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );

	Assert( pPlayer != NULL );

	CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs();
	int nAIs = g_AI_Manager.NumAIs();

	float flMaxDistSqr = Square( weapon_proto1_ping_radius.GetFloat() );
	int iFoundTargets = 0;

	for ( int i = 0; i < nAIs; i++ )
	{
		CAI_BaseNPC *pNPC = ppAIs[ i ];
		// Start rejecting.

		// Dead
		if( !pNPC->IsAlive() )
			continue;

		// Out of range
		if( pNPC->GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ) > flMaxDistSqr )
			continue;

		PingEntity( pNPC );
		iFoundTargets++;
	}

	// Now we set up to make the echo sounds. Store off how many to make, (one per target discovered)
	// when to do the next one, and the time between them. This makes sure
	// that the weapon makes the correct number of echo sounds over the 
	// lifetime of the ping. It's not synchronized with the visual effect,
	// but it still works well. (sjb)
	m_iEchoSoundsToDo = iFoundTargets;
	m_flTimeNextEchoSound = gpGlobals->curtime;
	m_flEchoSoundDelay = weapon_proto1_echo_sound_duration.GetFloat() / ((float)iFoundTargets);
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CWeaponProto1::PingEntity( CBaseEntity *pEntity )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	CAI_BaseNPC *pNPC = pEntity->MyNPCPointer();

	if( !pPlayer || !pNPC )
		return;

	bool bEnemy = pNPC->IRelationType( pPlayer ) != D_LI;

	if( bEnemy )
	{
		pNPC->StartPingEffect();
	}
}

//-----------------------------------------------------------------------------
// See if the proto weapon is pointed at a physics object that can be eaten
//-----------------------------------------------------------------------------
CBaseEntity *CWeaponProto1::DetectObject()
{
	trace_t tr;

	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	Vector vecSrc		= pPlayer->Weapon_ShootPosition();
	Vector vecAiming	= pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT );

	UTIL_TraceLine( vecSrc, vecSrc + vecAiming * weapon_proto1_alt_fire_range.GetFloat(), (MASK_SHOT|CONTENTS_GRATE), pPlayer, COLLISION_GROUP_NONE, &tr );

	if( tr.m_pEnt && tr.m_pEnt->VPhysicsGetObject() != NULL )
	{
		return tr.m_pEnt;
	}

	return NULL;
}
