//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose:		Tripmine
//
// $NoKeywords: $
//=============================================================================

#include "cbase.h"
#include "NPCEvent.h"
#include "hl1_basecombatweapon_shared.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 "hl1_player.h"
#include "hl1_basegrenade.h"
#include "beam_shared.h"

extern ConVar sk_plr_dmg_tripmine;


//-----------------------------------------------------------------------------
// CWeaponTripMine
//-----------------------------------------------------------------------------


#define TRIPMINE_MODEL "models/w_tripmine.mdl"


class CWeaponTripMine : public CBaseHL1CombatWeapon
{
	DECLARE_CLASS( CWeaponTripMine, CBaseHL1CombatWeapon );
public:

	CWeaponTripMine( void );

	void	Spawn( void );
	void	Precache( void );
	void	Equip( CBaseCombatCharacter *pOwner );
	void	PrimaryAttack( void );
	void	WeaponIdle( void );
	bool	Holster( CBaseCombatWeapon *pSwitchingTo = NULL );

	DECLARE_SERVERCLASS();
	DECLARE_DATADESC();

private:
	bool	m_bJustPlaced;
	int		m_iGroundIndex;
	int		m_iPickedUpIndex;
};

LINK_ENTITY_TO_CLASS( weapon_tripmine, CWeaponTripMine );

PRECACHE_WEAPON_REGISTER( weapon_tripmine );

IMPLEMENT_SERVERCLASS_ST( CWeaponTripMine, DT_WeaponTripMine )
END_SEND_TABLE()

BEGIN_DATADESC( CWeaponTripMine )
END_DATADESC()

//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CWeaponTripMine::CWeaponTripMine( void )
{
	m_bReloadsSingly	= false;
	m_bFiresUnderwater	= true;

	m_bJustPlaced		= false;
}

void CWeaponTripMine::Spawn( void )
{
	BaseClass::Spawn();

	m_iWorldModelIndex = m_iGroundIndex;
	SetModel( TRIPMINE_MODEL );

	SetActivity( ACT_TRIPMINE_GROUND );
	ResetSequenceInfo( );
	m_flPlaybackRate = 0;

	if ( !g_pGameRules->IsDeathmatch() )
	{
		UTIL_SetSize( this, Vector(-16, -16, 0), Vector(16, 16, 28) ); 
	}
}

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

	m_iGroundIndex		= PrecacheModel( TRIPMINE_MODEL );
	m_iPickedUpIndex	= PrecacheModel( GetWorldModel() );

	UTIL_PrecacheOther( "monster_tripmine" );
}

void CWeaponTripMine::Equip( CBaseCombatCharacter *pOwner )
{
	m_iWorldModelIndex = m_iPickedUpIndex;

	BaseClass::Equip( pOwner );
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CWeaponTripMine::PrimaryAttack( void )
{
	CHL1_Player *pPlayer = ToHL1Player( GetOwner() );
	if ( !pPlayer )
	{
		return;
	}

	if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
		return;

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

	trace_t tr;

	UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 128, MASK_SHOT, pPlayer, COLLISION_GROUP_NONE, &tr );

	if ( tr.fraction < 1.0 )
	{
		CBaseEntity *pEntity = tr.m_pEnt;
		if ( pEntity && !( pEntity->GetFlags() & FL_CONVEYOR ) )
		{
			QAngle angles;
			VectorAngles( tr.plane.normal, angles );

			CBaseEntity::Create( "monster_tripmine", tr.endpos + tr.plane.normal * 8, angles, pPlayer );

			pPlayer->RemoveAmmo( 1, m_iPrimaryAmmoType );

			pPlayer->SetAnimation( PLAYER_ATTACK1 );
			SendWeaponAnim( ACT_VM_DRAW );
			
			m_flNextPrimaryAttack = gpGlobals->curtime + 0.3;
			m_flTimeWeaponIdle = gpGlobals->curtime;
			m_bJustPlaced = true;
		}
	}
	else
	{
		m_flTimeWeaponIdle = gpGlobals->curtime + random->RandomFloat( 10, 15 );
	}
}

void CWeaponTripMine::WeaponIdle( void )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );

	if ( !pPlayer )
	{
		return;
	}

	if ( !HasWeaponIdleTimeElapsed() )
		return;

	if ( m_bJustPlaced )
	{
		m_bJustPlaced = false;

		if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
		{
			if ( !pPlayer->SwitchToNextBestWeapon( pPlayer->GetActiveWeapon() ) )
				Holster();
		}
		else
		{
			SendWeaponAnim( ACT_VM_DRAW );
			SetWeaponIdleTime( gpGlobals->curtime + random->RandomFloat( 10, 15 ) );
		}
	}
	else
	{
		int iAnim;

		if ( random->RandomFloat( 0, 1 ) <= 0.75 )
		{
			iAnim = ACT_VM_IDLE;
		}
		else
		{
			iAnim = ACT_VM_FIDGET;
		}

		SendWeaponAnim( iAnim );
	}
}

bool CWeaponTripMine::Holster( CBaseCombatWeapon *pSwitchingTo )
{
	CBasePlayer *pPlayer = ToBasePlayer( GetOwner() );
	if ( !pPlayer )
	{
		return false;
	}

	if ( !BaseClass::Holster( pSwitchingTo ) )
	{
		return false;
	}

	if ( pPlayer->GetAmmoCount( m_iPrimaryAmmoType ) <= 0 )
	{
		SetThink( &CWeaponTripMine::DestroyItem );
		SetNextThink( gpGlobals->curtime + 0.1 );
	}

	pPlayer->SetNextAttack( gpGlobals->curtime + 0.5 );

	return true;
}


//-----------------------------------------------------------------------------
// CTripmineGrenade
//-----------------------------------------------------------------------------

#define TRIPMINE_BEAM_SPRITE "sprites/laserbeam.vmt"

class CTripmineGrenade : public CHL1BaseGrenade
{
	DECLARE_CLASS( CTripmineGrenade, CHL1BaseGrenade );
public:
	CTripmineGrenade();
	void		Spawn( void );
	void		Precache( void );

	int			OnTakeDamage_Alive( const CTakeDamageInfo &info );
	
	void		WarningThink( void );
	void		PowerupThink( void );
	void		BeamBreakThink( void );
	void		DelayDeathThink( void );
	void		Event_Killed( const CTakeDamageInfo &info );

	DECLARE_DATADESC();

private:
	void			MakeBeam( void );
	void			KillBeam( void );

private:
	float					m_flPowerUp;
	Vector					m_vecDir;
	Vector					m_vecEnd;
	float					m_flBeamLength;

	CHandle<CBaseEntity>	m_hRealOwner;
	CHandle<CBeam>			m_hBeam;

	CHandle<CBaseEntity>	m_hStuckOn;
	Vector					m_posStuckOn;
	QAngle					m_angStuckOn;

	int						m_iLaserModel;
};

LINK_ENTITY_TO_CLASS( monster_tripmine, CTripmineGrenade );

BEGIN_DATADESC( CTripmineGrenade )
	DEFINE_FIELD( CTripmineGrenade, m_flPowerUp,	FIELD_TIME ),
	DEFINE_FIELD( CTripmineGrenade, m_vecDir,		FIELD_VECTOR ),
	DEFINE_FIELD( CTripmineGrenade, m_vecEnd,		FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( CTripmineGrenade, m_flBeamLength, FIELD_FLOAT ),
	DEFINE_FIELD( CTripmineGrenade, m_hBeam,		FIELD_EHANDLE ),
	DEFINE_FIELD( CTripmineGrenade, m_hRealOwner,	FIELD_EHANDLE ),
	DEFINE_FIELD( CTripmineGrenade, m_hStuckOn,		FIELD_EHANDLE ),
	DEFINE_FIELD( CTripmineGrenade, m_posStuckOn,	FIELD_POSITION_VECTOR ),
	DEFINE_FIELD( CTripmineGrenade, m_angStuckOn,	FIELD_VECTOR ),

	// Function Pointers
	DEFINE_FUNCTION( CTripmineGrenade, WarningThink ),
	DEFINE_FUNCTION( CTripmineGrenade, PowerupThink ),
	DEFINE_FUNCTION( CTripmineGrenade, BeamBreakThink ),
	DEFINE_FUNCTION( CTripmineGrenade, DelayDeathThink ),
END_DATADESC()


CTripmineGrenade::CTripmineGrenade()
{
	m_vecDir.Init();
	m_vecEnd.Init();
}

void CTripmineGrenade :: Spawn( void )
{
	Precache( );
	// motor
	SetMoveType( MOVETYPE_FLY );
	SetSolid( SOLID_BBOX );
	AddSolidFlags( FSOLID_NOT_SOLID );
	SetModel( TRIPMINE_MODEL );

	m_flCycle	= 0;
	SetSequence( SelectWeightedSequence( ACT_TRIPMINE_WORLD ) );
	ResetSequenceInfo();
	m_flPlaybackRate = 0;
	
	UTIL_SetSize( this, Vector( -8, -8, -8), Vector(8, 8, 8) );
	Relink();

	m_flDamage	= sk_plr_dmg_tripmine.GetFloat();
	m_DmgRadius	= m_flDamage * 2.5;

	if ( m_spawnflags & 1 )
	{
		// power up quickly
		m_flPowerUp = gpGlobals->curtime + 1.0;
	}
	else
	{
		// power up in 2.5 seconds
		m_flPowerUp = gpGlobals->curtime + 2.5;
	}
	
	SetThink( PowerupThink );
	SetNextThink( gpGlobals->curtime + 0.2 );

	m_takedamage = DAMAGE_YES;

	m_iHealth = 1;

	if ( GetOwnerEntity() != NULL )
	{
		// play deploy sound
		EmitSound( "TripmineGrenade.Deploy" );
		EmitSound( "TripmineGrenade.Charge" );

		m_hRealOwner = GetOwnerEntity();
	}
	AngleVectors( GetAbsAngles(), &m_vecDir );
	m_vecEnd = GetAbsOrigin() + m_vecDir * MAX_TRACE_LENGTH;
}


void CTripmineGrenade :: Precache( void )
{
	engine->PrecacheModel( TRIPMINE_MODEL ); 
	m_iLaserModel = engine->PrecacheModel( TRIPMINE_BEAM_SPRITE );
}


void CTripmineGrenade :: WarningThink( void  )
{
	// set to power up
	SetThink( PowerupThink );
	SetNextThink( gpGlobals->curtime + 1.0f );
}


void CTripmineGrenade :: PowerupThink( void  )
{
	if ( m_hStuckOn == NULL )
	{
		// find out what we've been stuck on
		CBaseEntity *pOldOwner = GetOwnerEntity();
		SetOwnerEntity( NULL );
		trace_t tr;
		UTIL_TraceLine( GetAbsOrigin() + m_vecDir * 8, GetAbsOrigin() - m_vecDir * 32, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
		if ( tr.startsolid )
		{
			SetOwnerEntity( pOldOwner );
			m_flPowerUp += 0.1;
			SetNextThink( gpGlobals->curtime + 0.1f );
			return;
		}
		if ( tr.fraction < 1.0 )
		{
			SetOwnerEntity( tr.m_pEnt );
			m_hStuckOn		= tr.m_pEnt;
			m_posStuckOn	= m_hStuckOn->GetAbsOrigin();
			m_angStuckOn	= m_hStuckOn->GetAbsAngles();
		}
		else
		{
			StopSound( "TripmineGrenade.Deploy" );
			StopSound( "TripmineGrenade.Charge" );
			SetThink( SUB_Remove );
			SetNextThink( gpGlobals->curtime + 0.1f );
//			ALERT( at_console, "WARNING:Tripmine at %.0f, %.0f, %.0f removed\n", pev->origin.x, pev->origin.y, pev->origin.z );
			KillBeam();
			return;
		}
	}
	else if ( (m_posStuckOn != m_hStuckOn->GetAbsOrigin()) || (m_angStuckOn != m_hStuckOn->GetAbsAngles()) )
	{
		StopSound( "TripmineGrenade.Deploy" );
		StopSound( "TripmineGrenade.Charge" );
		CBaseEntity *pMine = Create( "weapon_tripmine", GetAbsOrigin() + m_vecDir * 24, GetAbsAngles() );
		pMine->AddSpawnFlags( SF_NORESPAWN );

		SetThink( SUB_Remove );
		SetNextThink( gpGlobals->curtime + 0.1f );
		KillBeam();
		return;
	}

	if ( gpGlobals->curtime > m_flPowerUp )
	{
		MakeBeam( );
		RemoveSolidFlags( FSOLID_NOT_SOLID );
		Relink();

		m_bIsLive = true;

		// play enabled sound
		EmitSound( "TripmineGrenade.Activate" );
	}

	SetNextThink( gpGlobals->curtime + 0.1f );
}


void CTripmineGrenade :: KillBeam( void )
{
	if ( m_hBeam )
	{
		UTIL_Remove( m_hBeam );
		m_hBeam = NULL;
	}
}


void CTripmineGrenade :: MakeBeam( void )
{
	trace_t tr;

	UTIL_TraceLine( GetAbsOrigin(), m_vecEnd, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );

	m_flBeamLength = tr.fraction;

	// set to follow laser spot
	SetThink( BeamBreakThink );

	SetNextThink( gpGlobals->curtime + 1.0f );

	Vector vecTmpEnd = GetAbsOrigin() + m_vecDir * MAX_TRACE_LENGTH * m_flBeamLength;

	m_hBeam = CBeam::BeamCreate( TRIPMINE_BEAM_SPRITE, 1.0 );
	m_hBeam->PointEntInit( vecTmpEnd, this );
	m_hBeam->SetColor( 0, 214, 198 );
	m_hBeam->SetScrollRate( 25.5 );
	m_hBeam->SetBrightness( 64 );
}


void CTripmineGrenade :: BeamBreakThink( void  )
{
	bool bBlowup = false;
	trace_t tr;

	// NOT MASK_SHOT because we want only simple hit boxes
	UTIL_TraceLine( GetAbsOrigin(), m_vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );

	// ALERT( at_console, "%f : %f\n", tr.flFraction, m_flBeamLength );

	// respawn detect. 
	if ( !m_hBeam )
	{
		MakeBeam();
		if ( tr.m_pEnt )
		{
			m_hStuckOn = tr.m_pEnt;	// reset stuck on ent too
		}
	}

	CBaseEntity *pEntity = tr.m_pEnt;
	CBaseCombatCharacter *pBCC  = ToBaseCombatCharacter( pEntity );

	if ( pBCC || fabs( m_flBeamLength - tr.fraction ) > 0.001 )
	{
		bBlowup = true;
	}
	else
	{
		if ( m_hStuckOn == NULL )
			bBlowup = true;
		else if ( m_posStuckOn != m_hStuckOn->GetAbsOrigin() )
			bBlowup = true;
		else if ( m_angStuckOn != m_hStuckOn->GetAbsAngles() )
			bBlowup = true;
	}

	if ( bBlowup )
	{
		SetOwnerEntity( m_hRealOwner );
		m_iHealth = 0;
		Event_Killed( CTakeDamageInfo( this, m_hRealOwner, 100, GIB_NORMAL ) );
		return;
	}

	SetNextThink( gpGlobals->curtime + 0.1 );
}

int CTripmineGrenade::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
	if (gpGlobals->curtime < m_flPowerUp && info.GetDamage() < m_iHealth)
	{
		// disable
		// Create( "weapon_tripmine", GetLocalOrigin() + m_vecDir * 24, GetAngles() );
		SetThink( SUB_Remove );
		SetNextThink( gpGlobals->curtime + 0.1f );
		KillBeam();
		return 0;
	}
	return BaseClass::OnTakeDamage_Alive( info );
}

void CTripmineGrenade::Event_Killed( const CTakeDamageInfo &info )
{
	m_takedamage = DAMAGE_NO;

	if ( info.GetAttacker() && ( info.GetAttacker()->GetFlags() & FL_CLIENT ) )
	{
		// some client has destroyed this mine, he'll get credit for any kills
		SetOwnerEntity( info.GetAttacker() );
	}

	SetThink( DelayDeathThink );
	SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1, 0.3 ) );

	StopSound( "TripmineGrenade.Charge" );
}

void CTripmineGrenade::DelayDeathThink( void )
{
	KillBeam();
	trace_t tr;
	UTIL_TraceLine ( GetAbsOrigin() + m_vecDir * 8, GetAbsOrigin() - m_vecDir * 64,  MASK_SOLID, this, COLLISION_GROUP_NONE, & tr);

	Explode( &tr, DMG_BLAST );
}
