//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============
//
// Purpose: Implements visual effects entities: sprites, beams, bubbles, etc.
//
// $NoKeywords: $
//=============================================================================

#include "cbase.h"
#include "beam_shared.h"
#include "customentity.h"
#include "ndebugoverlay.h"

enum Touch_t
{
	touch_none = 0,
	touch_player_only,
	touch_npc_only,
	touch_player_or_npc,
};

class CEnvBeam : public CBeam
{
public:
	DECLARE_CLASS( CEnvBeam, CBeam );

	void	Spawn( void );
	void	Precache( void );
	void	Activate( void );

	void	StrikeThink( void );
	void	UpdateThink( void );
	void	RandomArea( void );
	void	RandomPoint( const Vector &vecSrc );
	void	Zap( const Vector &vecSrc, const Vector &vecDest );

	void	Strike( void );

	void InputTurnOn( inputdata_t &inputdata );
	void InputTurnOff( inputdata_t &inputdata );
	void InputToggle( inputdata_t &inputdata );
	void InputStrikeOnce( inputdata_t &inputdata );

	void TurnOn( void );
	void TurnOff( void );
	void Toggle( void );
	
	inline bool ServerSide( void )
	{
		if ( m_life == 0 && !HasSpawnFlags(SF_BEAM_RING) )
			return true;
		return false;
	}

	DECLARE_DATADESC();

	void	BeamUpdateVars( void );

	int		m_active;
	int		m_spriteTexture;

	string_t m_iszStartEntity;
	string_t m_iszEndEntity;
	float	m_life;
	float	m_boltWidth;
	float	m_noiseAmplitude;
	int		m_speed;
	float	m_restrike;
	string_t m_iszSpriteName;
	int		m_frameStart;

	float	m_radius;

	Touch_t	m_TouchType;

	COutputEvent	m_OnTouchedByEntity;
};

LINK_ENTITY_TO_CLASS( env_beam, CEnvBeam );

BEGIN_DATADESC( CEnvBeam )

	DEFINE_FIELD( CEnvBeam, m_active, FIELD_INTEGER ),
	DEFINE_FIELD( CEnvBeam, m_spriteTexture, FIELD_INTEGER ),

	DEFINE_KEYFIELD( CEnvBeam, m_iszStartEntity, FIELD_STRING, "LightningStart" ),
	DEFINE_KEYFIELD( CEnvBeam, m_iszEndEntity, FIELD_STRING, "LightningEnd" ),
	DEFINE_KEYFIELD( CEnvBeam, m_life, FIELD_FLOAT, "life" ),
	DEFINE_KEYFIELD( CEnvBeam, m_boltWidth, FIELD_FLOAT, "BoltWidth" ),
	DEFINE_KEYFIELD( CEnvBeam, m_noiseAmplitude, FIELD_FLOAT, "NoiseAmplitude" ),
	DEFINE_KEYFIELD( CEnvBeam, m_speed, FIELD_INTEGER, "TextureScroll" ),
	DEFINE_KEYFIELD( CEnvBeam, m_restrike, FIELD_FLOAT, "StrikeTime" ),
	DEFINE_KEYFIELD( CEnvBeam, m_iszSpriteName, FIELD_STRING, "texture" ),
	DEFINE_KEYFIELD( CEnvBeam, m_frameStart, FIELD_INTEGER, "framestart" ),
	DEFINE_KEYFIELD( CEnvBeam, m_radius, FIELD_FLOAT, "Radius" ),
	DEFINE_KEYFIELD( CEnvBeam, m_TouchType, FIELD_INTEGER, "TouchType" ),

	// Function Pointers
	DEFINE_FUNCTION( CEnvBeam, StrikeThink ),
	DEFINE_FUNCTION( CEnvBeam, UpdateThink ),

	// Input functions
	DEFINE_INPUTFUNC( CEnvBeam, FIELD_VOID, "TurnOn", InputTurnOn ),
	DEFINE_INPUTFUNC( CEnvBeam, FIELD_VOID, "TurnOff", InputTurnOff ),
	DEFINE_INPUTFUNC( CEnvBeam, FIELD_VOID, "Toggle", InputToggle ),
	DEFINE_INPUTFUNC( CEnvBeam, FIELD_VOID, "StrikeOnce", InputStrikeOnce ),

	DEFINE_OUTPUT( CEnvBeam, m_OnTouchedByEntity, "OnTouchedByEntity" ),

END_DATADESC()



//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEnvBeam::Spawn( void )
{
	if ( !m_iszSpriteName )
	{
		SetThink( &CEnvBeam::SUB_Remove );
		return;
	}

	BaseClass::Spawn();

	m_noiseAmplitude = min(MAX_BEAM_NOISEAMPLITUDE, m_noiseAmplitude);

	SetWidth( m_boltWidth );
	SetEndWidth( GetWidth() );	// Note: EndWidth is not scaled

	if ( ServerSide() )
	{
		SetThink( &CEnvBeam::UpdateThink );
		SetNextThink( gpGlobals->curtime );
		SetFireTime( gpGlobals->curtime );

		if ( GetEntityName() != NULL_STRING )
		{
			if ( !(m_spawnflags & SF_BEAM_STARTON) )
			{
				m_fEffects |= EF_NODRAW;
				m_active = 0;
				SetNextThink( TICK_NEVER_THINK );
			}
			else
			{
				m_active = 1;
			}
		}
	}
	else
	{
		m_active = 0;
		if ( !GetEntityName() || FBitSet(m_spawnflags, SF_BEAM_STARTON) )
		{
			SetThink( &CEnvBeam::StrikeThink );
			SetNextThink( gpGlobals->curtime + 1.0f );
		}
	}

}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEnvBeam::Precache( void )
{
	m_spriteTexture = engine->PrecacheModel( STRING(m_iszSpriteName) );
	BaseClass::Precache();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEnvBeam::Activate( void )
{
	BaseClass::Activate();

	if ( ServerSide() )
		BeamUpdateVars();
}


//-----------------------------------------------------------------------------
// Purpose: Input handler to turn the lightning on either continually or for
//			interval refiring.
//-----------------------------------------------------------------------------
void CEnvBeam::InputTurnOn( inputdata_t &inputdata )
{
	if ( !m_active )
	{
		TurnOn();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler to turn the lightning off.
//-----------------------------------------------------------------------------
void CEnvBeam::InputTurnOff( inputdata_t &inputdata )
{
	if ( m_active )
	{
		TurnOff();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler to toggle the lightning on/off.
//-----------------------------------------------------------------------------
void CEnvBeam::InputToggle( inputdata_t &inputdata )
{
	if ( m_active )
	{
		TurnOff();
	}
	else
	{
		TurnOn();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Input handler for making the beam strike once. This will not affect
//			any interval refiring that might be going on. If the lifetime is set
//			to zero (infinite) it will turn on and stay on.
//-----------------------------------------------------------------------------
void CEnvBeam::InputStrikeOnce( inputdata_t &inputdata )
{
	Strike();
}


//-----------------------------------------------------------------------------
// Purpose: Turns the lightning on. If it is set for interval refiring, it will
//			begin doing so. If it is set to be continually on, it will do so.
//-----------------------------------------------------------------------------
void CEnvBeam::TurnOn( void )
{
	m_active = 1;

	if ( ServerSide() )
	{
		m_fEffects &= ~EF_NODRAW;
		DoSparks( GetAbsStartPos(), GetAbsEndPos() );

		SetThink( &CEnvBeam::UpdateThink );
		SetNextThink( gpGlobals->curtime );
		SetFireTime( gpGlobals->curtime );
	}
	else
	{
		SetThink( &CEnvBeam::StrikeThink );
		SetNextThink( gpGlobals->curtime );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEnvBeam::TurnOff( void )
{
	m_active = 0;

	if ( ServerSide() )
	{
		m_fEffects |= EF_NODRAW;
	}

	SetNextThink( TICK_NEVER_THINK );
	SetThink( NULL );
}


//-----------------------------------------------------------------------------
// Purpose: Think function for striking at intervals.
//-----------------------------------------------------------------------------
void CEnvBeam::StrikeThink( void )
{
	if ( m_life != 0 )
	{
		if ( m_spawnflags & SF_BEAM_RANDOM )
			SetNextThink( gpGlobals->curtime + m_life + random->RandomFloat( 0, m_restrike ) );
		else
			SetNextThink( gpGlobals->curtime + m_life + m_restrike );
	}
	m_active = 1;

	if (!m_iszEndEntity)
	{
		if (!m_iszStartEntity)
		{
			RandomArea( );
		}
		else
		{
			CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) );
			if (pStart != NULL)
			{
				RandomPoint( pStart->GetAbsOrigin() );
			}
			else
			{
				Msg( "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) );
			}
		}
		return;
	}

	Strike();
}


//-----------------------------------------------------------------------------
// Purpose: Strikes once for its configured lifetime.
//-----------------------------------------------------------------------------
void CEnvBeam::Strike( void )
{
	CBroadcastRecipientFilter filter;

	CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) );
	CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) );

	if ( pStart != NULL && pEnd != NULL )
	{
		int pointStart = IsStaticPointEntity( pStart );
		int pointEnd = IsStaticPointEntity( pEnd );

		if ( pointStart || pointEnd )
		{
			if ( m_spawnflags & SF_BEAM_RING )
			{
				// don't work
				return;
			}

			te->BeamEntPoint( filter, 0.0,
				pointStart ? 0 : pStart->entindex(),
				pointStart ? &pStart->GetAbsOrigin() : NULL,
				pointEnd ? 0 : pEnd->entindex(),
				pointEnd ? &pEnd->GetAbsOrigin() : NULL,
				m_spriteTexture,
				0,	// No halo
				m_frameStart,
				(int)m_flFrameRate,
				m_life,
				m_boltWidth,
				m_boltWidth,	// End width
				0,				// No fade
				m_noiseAmplitude,
				m_clrRender->r,	m_clrRender->g,	m_clrRender->b,	m_clrRender->a,
				m_speed );
		}
		else
		{
			if ( m_spawnflags & SF_BEAM_RING)
			{
				te->BeamRing( filter, 0.0,
					pStart->entindex(), 
					pEnd->entindex(), 
					m_spriteTexture, 
					0,	// No halo
					m_frameStart,
					(int)m_flFrameRate,
					m_life,
					m_boltWidth,
					0,	// No spread
					m_noiseAmplitude,
					m_clrRender->r,
					m_clrRender->g,
					m_clrRender->b,
					m_clrRender->a,
					m_speed );
			}
			else
			{
				te->BeamEnts( filter, 0.0,
					pStart->entindex(), 
					pEnd->entindex(), 
					m_spriteTexture,
					0,	// No halo
					m_frameStart,
					(int)m_flFrameRate,
					m_life,
					m_boltWidth,
					m_boltWidth,	// End width
					0,				// No fade
					m_noiseAmplitude,
					m_clrRender->r,
					m_clrRender->g,
					m_clrRender->b,
					m_clrRender->a,
					m_speed );

			}
		}

		DoSparks( pStart->GetAbsOrigin(), pEnd->GetAbsOrigin() );
		if ( m_flDamage > 0 )
		{
			trace_t tr;
			UTIL_TraceLine( pStart->GetAbsOrigin(), pEnd->GetAbsOrigin(), MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr );
			BeamDamageInstant( &tr, m_flDamage );
		}
	}
}


class CTraceFilterPlayersNPCs : public ITraceFilter
{
public:
	bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
	{
		CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
		if ( pEntity )
		{
			if ( pEntity->IsPlayer() || pEntity->MyNPCPointer() )
				return true;
		}

		return false;
	}
	virtual TraceType_t	GetTraceType() const
	{
		return TRACE_ENTITIES_ONLY;
	}
};

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEnvBeam::UpdateThink( void )
{
	// Apply damage every 1/10th of a second.
	if ( ( m_flDamage > 0 ) && ( gpGlobals->curtime >= m_flFireTime + 0.1 ) )
	{
		trace_t tr;
		UTIL_TraceLine( GetAbsStartPos(), GetAbsEndPos(), MASK_SOLID, NULL, COLLISION_GROUP_NONE, &tr );
		BeamDamage( &tr );
		// BeamDamage calls RelinkBeam, so no need to call it again.
	}
	else
	{
		RelinkBeam();
	}

	if( m_TouchType != touch_none )
	{
		trace_t tr;
		Ray_t ray;
		ray.Init( GetAbsStartPos(), GetAbsEndPos() );
		CTraceFilterPlayersNPCs traceFilter;
		enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr );

		NDebugOverlay::Line( tr.startpos, tr.endpos, 255,0,0, false, 1.0 );

		if( tr.fraction != 1.0 )
		{
			// Touched some player or NPC!
			if( m_TouchType == touch_player_only || m_TouchType == touch_player_or_npc )
			{
				if( tr.m_pEnt->IsPlayer() )
				{
					m_OnTouchedByEntity.FireOutput( tr.m_pEnt, this, 0 );
					return;
				}
			}

			if( m_TouchType == touch_npc_only || m_TouchType == touch_player_or_npc )
			{
				if( (CAI_BaseNPC *)tr.m_pEnt )
				{
					m_OnTouchedByEntity.FireOutput( tr.m_pEnt, this, 0 );
					return;
				}
			}
		}
	}

	SetNextThink( gpGlobals->curtime );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : &vecSrc - 
//			&vecDest - 
//-----------------------------------------------------------------------------
void CEnvBeam::Zap( const Vector &vecSrc, const Vector &vecDest )
{
	CBroadcastRecipientFilter filter;

	te->BeamPoints( filter, 0.0,
		&vecSrc, 
		&vecDest, 
		m_spriteTexture, 
		0,	// No halo
		m_frameStart,
		(int)m_flFrameRate,
		m_life,
		m_boltWidth,
		m_boltWidth,	// End width
		0,				// No fade
		m_noiseAmplitude,
		m_clrRender->r,
		m_clrRender->g,
		m_clrRender->b,
		m_clrRender->a,
		m_speed );

	DoSparks( vecSrc, vecDest );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEnvBeam::RandomArea( void )
{
	int iLoops = 0;

	for (iLoops = 0; iLoops < 10; iLoops++)
	{
		Vector vecSrc = GetAbsOrigin();

		Vector vecDir1 = Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ),random->RandomFloat( -1.0, 1.0 ) );
		VectorNormalize( vecDir1 );
		trace_t	tr1;
		UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr1 );

		if (tr1.fraction == 1.0)
			continue;

		Vector vecDir2;
		do {
			vecDir2 = Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ),random->RandomFloat( -1.0, 1.0 ) );
		} while (DotProduct(vecDir1, vecDir2 ) > 0);
		VectorNormalize( vecDir2 );
		trace_t	tr2;
		UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr2 );

		if (tr2.fraction == 1.0)
			continue;

		if ((tr1.endpos - tr2.endpos).Length() < m_radius * 0.1)
			continue;

		UTIL_TraceLine( tr1.endpos, tr2.endpos, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr2 );

		if (tr2.fraction != 1.0)
			continue;

 		Zap( tr1.endpos, tr2.endpos );

		break;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : vecSrc - 
//-----------------------------------------------------------------------------
void CEnvBeam::RandomPoint( const Vector &vecSrc )
{
	int iLoops = 0;

	for (iLoops = 0; iLoops < 10; iLoops++)
	{
		Vector vecDir1 = Vector( random->RandomFloat( -1.0, 1.0 ), random->RandomFloat( -1.0, 1.0 ),random->RandomFloat( -1.0, 1.0 ) );
		VectorNormalize( vecDir1 );
		trace_t	tr1;
		UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr1 );

		if ((tr1.endpos - vecSrc).Length() < m_radius * 0.1)
			continue;

		if (tr1.fraction == 1.0)
			continue;

		Zap( vecSrc, tr1.endpos );
		break;
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CEnvBeam::BeamUpdateVars( void )
{
	CBaseEntity *pStart = gEntList.FindEntityByName( NULL, m_iszStartEntity, NULL );
	CBaseEntity *pEnd = gEntList.FindEntityByName( NULL, m_iszEndEntity, NULL );

	if (( pStart == NULL ) || ( pEnd == NULL ))
	{
		return;
	}

	m_nNumBeamEnts = 2;

	// NOTE: If the end entity is the beam itself (and the start entity
	// isn't *also* the beam itself, we've got problems. This is a problem
	// because SetAbsStartPos actually sets the entity's origin.
	if ( ( pEnd == this ) && ( pStart != this ) )
	{
		Warning("env_beams cannot have the end entity be the beam itself\n"
			"unless the start entity is also the beam itself!\n" );
		Assert(0);
	}

	SetModelName( m_iszSpriteName );
	SetTexture( m_spriteTexture );

	SetType( BEAM_ENTPOINT );

	if ( IsStaticPointEntity( pStart ) )
	{
		SetAbsStartPos( pStart->GetAbsOrigin() );
	}
	else
	{
		SetStartEntity( pStart );
	}

	if ( IsStaticPointEntity( pEnd ) )
	{
		SetAbsEndPos( pEnd->GetAbsOrigin() );
	}
	else
	{
		SetEndEntity( pEnd );
	}

	RelinkBeam();

	SetWidth( min(MAX_BEAM_WIDTH, m_boltWidth) );
	SetNoise( min(MAX_BEAM_NOISEAMPLITUDE, m_noiseAmplitude) );
	SetFrame( m_frameStart );
	SetScrollRate( min(m_speed, MAX_BEAM_SCROLLSPEED) );
	if ( m_spawnflags & SF_BEAM_SHADEIN )
	{
		SetBeamFlags( FBEAM_SHADEIN );
	}
	else if ( m_spawnflags & SF_BEAM_SHADEOUT )
	{
		SetBeamFlags( FBEAM_SHADEOUT );
	}
}
