//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Chargeable Plasma & Shield combo
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "basetfplayer_shared.h"
#include "weapon_combatshield.h"
#include "in_buttons.h"
#include "weapon_combat_usedwithshieldbase.h"
#include "plasmaprojectile.h"
#include "in_buttons.h"
#include "tf_shareddefs.h"

#if defined( CLIENT_DLL )

#include "iefx.h"
#include "dlight.h"
#include "clienteffectprecachesystem.h"
#include "beamdraw.h"

#define CWeaponCombatPlasmaRifle C_WeaponCombatPlasmaRifle
#define CWeaponCombatPlasmaRifleHuman C_WeaponCombatPlasmaRifleHuman
#define CWeaponCombatPlasmaRifleAlien C_WeaponCombatPlasmaRifleAlien

#endif

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

#if defined( CLIENT_DLL )

class CChargeBall;
// Precache the effects
CLIENTEFFECT_REGISTER_BEGIN( PrecacheWeaponCombatPlasmaRifle )
CLIENTEFFECT_MATERIAL( "sprites/chargeball_team1" )
CLIENTEFFECT_MATERIAL( "sprites/chargeball_team2" )
CLIENTEFFECT_REGISTER_END()

#endif

// Damage CVars
ConVar	weapon_combat_plasmarifle_damage( "weapon_combat_plasmarifle_damage","10", FCVAR_REPLICATED, "Plasma Rifle maximum damage" );
ConVar	weapon_combat_plasmarifle_range( "weapon_combat_plasmarifle_range","500", FCVAR_REPLICATED, "Plasma Rifle maximum range" );
ConVar	weapon_combat_plasmarifle_radius( "weapon_combat_plasmarifle_radius","90", FCVAR_REPLICATED, "Plasma Rifle explosion radius when charged" );
ConVar	weapon_combat_plasmarifle_ducking_mod( "weapon_combat_plasmarifle_ducking_mod", "0.6f", FCVAR_REPLICATED, "Plasma Rifle ducking speed modifier" );

//-----------------------------------------------------------------------------
// Purpose: Shared viersion of CWeaponCombatPlasmaRifle
//-----------------------------------------------------------------------------
class CWeaponCombatPlasmaRifle : public CWeaponCombatUsedWithShieldBase
{
	DECLARE_CLASS( CWeaponCombatPlasmaRifle, CWeaponCombatUsedWithShieldBase );
public:
	DECLARE_NETWORKCLASS();
	DECLARE_PREDICTABLE();
#if !defined( CLIENT_DLL )
	DECLARE_DATADESC();
#endif

	CWeaponCombatPlasmaRifle( void ) {}

	virtual void	ItemPostFrame( void );
	virtual void	PrimaryAttack( void );
	virtual float 	GetFireRate( void );
	virtual void	Spawn();
	virtual bool	Deploy( void );
	virtual bool	Holster( CBaseCombatWeapon *pSwitchingTo = NULL );
	void			ChargeThink( void );
	virtual float	GetDefaultAnimSpeed( void );

	// All predicted weapons need to implement and return true
	virtual bool			IsPredicted( void ) const
	{ 
		return true;
	}

private:
	CWeaponCombatPlasmaRifle( const CWeaponCombatPlasmaRifle & );
public:

#if defined( CLIENT_DLL )
	virtual bool	ShouldPredict( void )
	{
		if ( GetOwner() && 
			GetOwner() == C_BasePlayer::GetLocalPlayer() )
			return true;

		return BaseClass::ShouldPredict();
	}

	virtual void	OnDataChanged( DataUpdateType_t updateType );
	virtual int		DrawModel( int flags );
	virtual void	ViewModelDrawn( CBaseViewModel *pBaseViewModel );
	virtual void	ClientThink( );
	virtual bool	IsTransparent( );
private:
	// Purpose: Draws the charging effect
	void DrawChargingEffect( float flSize, CBaseAnimating *pAttachedEnt );
	CMaterialReference m_hMaterial;

#endif
private:
	CNetworkVar( float, m_flPower );
	CNetworkVar( bool, m_bCharging );
};

//-----------------------------------------------------------------------------
// Spawn weapon
//-----------------------------------------------------------------------------
void CWeaponCombatPlasmaRifle::Spawn()
{
	BaseClass::Spawn();
	m_flPower = 1;
	m_bCharging = false;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponCombatPlasmaRifle::ItemPostFrame( void )
{
	ChargeThink();

	CBaseTFPlayer *pOwner = ToBaseTFPlayer( GetOwner() );
	if (!pOwner)
		return;

	if ( UsesClipsForAmmo1() )
	{
		CheckReload();
	}

	// Handle charge firing
	if ( GetShieldState() == SS_DOWN && !m_bInReload )
	{
		if ( (pOwner->m_nButtons & IN_ATTACK ) && (m_flNextPrimaryAttack <= gpGlobals->curtime) )
		{
			if ( m_iClip1 > 0 )
			{
				// Fire the plasma shot
				PrimaryAttack();
				m_flPower = 1.0;
			}
			else
			{
				Reload();
			}
		}

		// Reload button (or fire button when we're out of ammo)
		if ( m_flNextPrimaryAttack <= gpGlobals->curtime ) 
		{
			if ( pOwner->m_nButtons & IN_RELOAD ) 
			{
				Reload();
			}
			else if ( !((pOwner->m_nButtons & IN_ATTACK) || (pOwner->m_nButtons & IN_ATTACK2) || (pOwner->m_nButtons & IN_RELOAD)) )
			{
				if ( !m_iClip1 && HasPrimaryAmmo() )
				{
					Reload();
				}
			}
		}
	}

	// Prevent shield post frame if we're not ready to attack, or we're charging
	AllowShieldPostFrame( m_flNextPrimaryAttack <= gpGlobals->curtime || m_bInReload );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponCombatPlasmaRifle::PrimaryAttack( void )
{
	CBaseTFPlayer *pPlayer = (CBaseTFPlayer*)GetOwner();
	if (!pPlayer)
		return;
	
	WeaponSound(SINGLE);

	// Fire the bullets
	Vector vecSrc = pPlayer->Weapon_ShootPosition( );
	Vector vecAiming;
	pPlayer->EyeVectors( &vecAiming );

	PlayAttackAnimation( GetPrimaryAttackActivity() );

	// Shift it down a bit so the firer can see it
	Vector right, forward;
	AngleVectors( pPlayer->EyeAngles() + pPlayer->m_Local.m_vecPunchAngle, &forward, &right, NULL );
	Vector vecStartSpot = vecSrc;

	Vector gunOffset = Vector(0,0,-8) + right * 12 + forward * 16;

	CPowerPlasmaProjectile *pPlasma = CPowerPlasmaProjectile::CreatePredicted( vecStartSpot, vecAiming, gunOffset, DMG_ENERGYBEAM, pPlayer );
	if ( pPlasma )
	{
		pPlasma->SetDamage( m_flPower * weapon_combat_plasmarifle_damage.GetFloat() );
		pPlasma->m_hOwner = pPlayer;
		pPlasma->SetPower( m_flPower );
		// Calculate range based upon charge power
		float flRange = weapon_combat_plasmarifle_range.GetFloat() + RemapVal( m_flPower, 1.0, MAX_RIFLE_POWER, 0, weapon_combat_plasmarifle_range.GetFloat() * 0.75 );
		pPlasma->SetMaxRange( flRange );
		pPlasma->Activate();
	}

	// Go explosive if fully charged
//	if ( m_flPower >= MAX_RIFLE_POWER )
//	{
//		pPlasma->SetExplosive( weapon_combat_plasmarifle_radius.GetFloat() );
//		pPlasma->SetPlasmaType( PLASMATYPE_PLASMABALL_EXPLOSIVE );
//	}

	m_flNextPrimaryAttack = gpGlobals->curtime + GetFireRate();
	m_iClip1 = m_iClip1 - 1;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
float CWeaponCombatPlasmaRifle::GetFireRate( void )
{
	float flFireRate = ( SequenceDuration() * 0.4 ) + SHARED_RANDOMFLOAT( 0.0, 0.035f );

	// Get the player.
	CBaseTFPlayer *pPlayer = ( CBaseTFPlayer* )GetOwner();
	if ( pPlayer )
	{
		// Fire more rapidly when we are ducking.
		if ( pPlayer->GetFlags() & FL_DUCKING )
		{
			flFireRate *= weapon_combat_plasmarifle_ducking_mod.GetFloat();
		}
	}

	return flFireRate;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CWeaponCombatPlasmaRifle::Deploy( void )
{
	if ( BaseClass::Deploy() )
	{
		m_bCharging = true;
		GainedNewTechnology(NULL);
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Our player just died
//-----------------------------------------------------------------------------
bool CWeaponCombatPlasmaRifle::Holster( CBaseCombatWeapon *pSwitchingTo )
{
	if ( BaseClass::Holster(pSwitchingTo) )
	{
		m_bCharging = false;
		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: Match the anim speed to the weapon speed while crouching
//-----------------------------------------------------------------------------
float CWeaponCombatPlasmaRifle::GetDefaultAnimSpeed( void )
{
	if ( GetOwner() && GetOwner()->IsPlayer() )
	{
		if ( GetOwner()->GetFlags() & FL_DUCKING )
			return (1.0 + (1.0 - weapon_combat_plasmarifle_ducking_mod.GetFloat()) );
	}

	return 1.0;
}

//-----------------------------------------------------------------------------
// Purpose: Charge up over time
//-----------------------------------------------------------------------------
void CWeaponCombatPlasmaRifle::ChargeThink( void )
{
	if ( !m_bCharging )
		return;

	if ( IsOwnerEMPed() )
	{
		m_flPower = 1;
	}
	else if ( m_iClip1 > 0 && m_flPower < MAX_RIFLE_POWER )
	{
		m_flPower = MIN( MAX_RIFLE_POWER, m_flPower + (((MAX_RIFLE_POWER-1.0) / RIFLE_CHARGE_TIME) * gpGlobals->frametime ) );
	}
}

#if defined( CLIENT_DLL )
//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CWeaponCombatPlasmaRifle::OnDataChanged( DataUpdateType_t updateType )
{
	SetPredictionEligible( true );

	BaseClass::OnDataChanged( updateType );

	if (updateType == DATA_UPDATE_CREATED)
	{
		if ( GetTeamNumber() == 1 )
			m_hMaterial.Init( "sprites/chargeball_team1", TEXTURE_GROUP_CLIENT_EFFECTS );
		else 
			m_hMaterial.Init( "sprites/chargeball_team2", TEXTURE_GROUP_CLIENT_EFFECTS );
	}

	if (WeaponState() == WEAPON_IS_ACTIVE)
	{
		// Start thinking so we can manipulate the light
		SetNextClientThink( CLIENT_THINK_ALWAYS );
	}
	else
	{
		SetNextClientThink( CLIENT_THINK_NEVER );
	}
}


//-----------------------------------------------------------------------------
// Deal with dynamic lighting
//-----------------------------------------------------------------------------
void CWeaponCombatPlasmaRifle::ClientThink( )
{
	BaseClass::ClientThink();

	if (!inv_demo.GetInt())
	{
		C_BaseTFPlayer *pPlayer = (C_BaseTFPlayer *)GetOwner();
		if ( !pPlayer || (pPlayer->GetHealth() <= 0) || !IsDormant() )
		{
			SetNextClientThink( CLIENT_THINK_NEVER );
			return;
		}

		// FIXME: dl->origin should be based on the attachment point
		dlight_t *dl = effects->CL_AllocDlight( entindex() );
		dl->origin = GetRenderOrigin();
		if (GetTeamNumber() == 1)
		{
			dl->color.r = 40;
			dl->color.g = 60;
			dl->color.b = 250;
		}
		else
		{
			dl->color.r = 250;
			dl->color.g = 60;
			dl->color.b = 40;
		}
		dl->color.exponent = 7;
		dl->radius = 20 * m_flPower + 10;
		dl->die = gpGlobals->curtime + 0.01;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Draws the charging effect
//-----------------------------------------------------------------------------
void CWeaponCombatPlasmaRifle::DrawChargingEffect( float flSize, CBaseAnimating *pAttachedEnt )
{
	if (!pAttachedEnt)
		return;

	Vector vecMuzzleOrigin, vecBarrelOrigin;
	QAngle angMuzzleAngles, angBarrelAngles;
	int iMuzzle = pAttachedEnt->LookupAttachment( "muzzle" );
	//int iBarrel = pAttachedEnt->LookupAttachment( "barrel" );

	if ( pAttachedEnt->GetAttachment( iMuzzle, vecMuzzleOrigin, angMuzzleAngles ) )
	{
		// View model attachments are modified so you can place entities at the attachment
		// point and when they are rendered (with a different FOV than the view model itself uses)
		// they will render in the right spot when the view model is drawn.
		// 
		// In this case though, we are rendering at the same time as the view model, so we want
		// the attachment point before the correction has been applied.
		pAttachedEnt->UncorrectViewModelAttachment( vecMuzzleOrigin );

		// If I'm fully charged, put funky effects on the ball
		materials->Bind( m_hMaterial, this );
		
		if ( m_flPower >= MAX_RIFLE_POWER )
		{
			float frac = fmod( gpGlobals->curtime, 1.0 );
			frac *= 2 * M_PI;
			frac = sin( frac );
			flSize += (frac * 2) - 1.5;
			int colorFade = 190 + (int)( frac * 32.0f );

			color32 color = { 0, 0, 0, 255 };
			if ( GetTeamNumber() == 1 )
			{
				color.r = colorFade;
				color.g = colorFade;
			}
			else 
			{
				color.g = colorFade;
			}
			DrawSprite( vecMuzzleOrigin, flSize, flSize, color );
		}
		else
		{
			color32 color = { 255, 255, 255, 255 };
			DrawSprite( vecMuzzleOrigin, flSize, flSize, color );
		}
	}
}


//-----------------------------------------------------------------------------
// We're transparent because we draw a transparent charging effect
//-----------------------------------------------------------------------------
bool CWeaponCombatPlasmaRifle::IsTransparent( )
{
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: Draws the model
//-----------------------------------------------------------------------------
int CWeaponCombatPlasmaRifle::DrawModel( int flags )
{
	int retval = BaseClass::DrawModel( flags );
	if (retval == 0)
		return 0;

	if (IsCarrierAlive())
	{
		// FIXME: Maybe do some client-side simulation on the size?
		// It may get jerky otherwise

		// Draw the charging effect
		float flSize = 10 * m_flPower + 5;

		DrawChargingEffect( flSize, this );
	}
	return retval;
}


//-----------------------------------------------------------------------------
// Purpose: Draws the model
//-----------------------------------------------------------------------------
void CWeaponCombatPlasmaRifle::ViewModelDrawn( CBaseViewModel *pBaseViewModel )
{
	// Draw the charging effect
	float flSize = 4 * m_flPower + 1;

	if ( m_iClip1 > 0 )
	{
		DrawChargingEffect( flSize, pBaseViewModel );
	}
}
#endif


IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCombatPlasmaRifle, DT_WeaponCombatPlasmaRifle )

BEGIN_NETWORK_TABLE( CWeaponCombatPlasmaRifle, DT_WeaponCombatPlasmaRifle )
#if !defined( CLIENT_DLL )
	SendPropFloat( SENDINFO( m_flPower ), 14, SPROP_ROUNDUP, 1.0f, MAX_RIFLE_POWER ),
	SendPropInt( SENDINFO( m_bCharging ), 1, SPROP_UNSIGNED ),
#else
	RecvPropFloat( RECVINFO( m_flPower ) ),
	RecvPropInt( RECVINFO( m_bCharging ) ),
#endif
END_NETWORK_TABLE()

LINK_ENTITY_TO_CLASS( weapon_combat_plasmarifle_base, CWeaponCombatPlasmaRifle );

BEGIN_PREDICTION_DATA( CWeaponCombatPlasmaRifle  )

	DEFINE_PRED_FIELD_TOL( m_flPower, FIELD_FLOAT, FTYPEDESC_INSENDTABLE, 0.05f ),
	DEFINE_PRED_FIELD( m_bCharging, FIELD_BOOLEAN, FTYPEDESC_INSENDTABLE ),

END_PREDICTION_DATA()

#if !defined( CLIENT_DLL )

BEGIN_DATADESC( CWeaponCombatPlasmaRifle )

	// Function Pointers
	DEFINE_FUNCTION( ChargeThink ),

END_DATADESC()

// PRECACHE_WEAPON_REGISTER(weapon_combat_plasmarifle_base);
#endif

//-----------------------------------------------------------------------------
// Purpose: Need to do different art on client vs server
//-----------------------------------------------------------------------------
class CWeaponCombatPlasmaRifleHuman : public CWeaponCombatPlasmaRifle
{
	DECLARE_CLASS( CWeaponCombatPlasmaRifleHuman, CWeaponCombatPlasmaRifle );
public:
	DECLARE_NETWORKCLASS();
	DECLARE_PREDICTABLE();

	CWeaponCombatPlasmaRifleHuman( void ) {}

private:
	CWeaponCombatPlasmaRifleHuman( const CWeaponCombatPlasmaRifleHuman & );
};

//-----------------------------------------------------------------------------
// Purpose: Need to do different art on client vs server
//-----------------------------------------------------------------------------
class CWeaponCombatPlasmaRifleAlien : public CWeaponCombatPlasmaRifle
{
	DECLARE_CLASS( CWeaponCombatPlasmaRifleAlien, CWeaponCombatPlasmaRifle );
public:
	DECLARE_NETWORKCLASS();
	DECLARE_PREDICTABLE();

	CWeaponCombatPlasmaRifleAlien( void ) {}

private:
	CWeaponCombatPlasmaRifleAlien( const CWeaponCombatPlasmaRifleAlien & );
};

IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCombatPlasmaRifleHuman, DT_WeaponCombatPlasmaRifleHuman )

BEGIN_NETWORK_TABLE( CWeaponCombatPlasmaRifleHuman, DT_WeaponCombatPlasmaRifleHuman )
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CWeaponCombatPlasmaRifleHuman )
END_PREDICTION_DATA()

IMPLEMENT_NETWORKCLASS_ALIASED( WeaponCombatPlasmaRifleAlien, DT_WeaponCombatPlasmaRifleAlien )

BEGIN_NETWORK_TABLE( CWeaponCombatPlasmaRifleAlien, DT_WeaponCombatPlasmaRifleAlien )
END_NETWORK_TABLE()

BEGIN_PREDICTION_DATA( CWeaponCombatPlasmaRifleAlien )
END_PREDICTION_DATA()

LINK_ENTITY_TO_CLASS( weapon_combat_plasmarifle, CWeaponCombatPlasmaRifleHuman );
LINK_ENTITY_TO_CLASS( weapon_combat_plasmarifle_alien, CWeaponCombatPlasmaRifleAlien );

PRECACHE_WEAPON_REGISTER(weapon_combat_plasmarifle);
PRECACHE_WEAPON_REGISTER(weapon_combat_plasmarifle_alien);