source-engine/game/server/EnvBeam.cpp

763 lines
18 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements visual effects entities: sprites, beams, bubbles, etc.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "beam_shared.h"
#include "ndebugoverlay.h"
#include "filters.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
// Keeps us from doing strcmps in the tracefilter.
string_t g_iszPhysicsPropClassname;
enum Touch_t
{
touch_none = 0,
touch_player_only,
touch_npc_only,
touch_player_or_npc,
touch_player_or_npc_or_physicsprop,
};
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 );
bool PassesTouchFilters(CBaseEntity *pOther);
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 );
const char *GetDecalName( void ){ return STRING( m_iszDecal );}
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;
string_t m_iFilterName;
EHANDLE m_hFilter;
string_t m_iszDecal;
COutputEvent m_OnTouchedByEntity;
};
LINK_ENTITY_TO_CLASS( env_beam, CEnvBeam );
BEGIN_DATADESC( CEnvBeam )
DEFINE_FIELD( m_active, FIELD_INTEGER ),
DEFINE_FIELD( m_spriteTexture, FIELD_INTEGER ),
DEFINE_KEYFIELD( m_iszStartEntity, FIELD_STRING, "LightningStart" ),
DEFINE_KEYFIELD( m_iszEndEntity, FIELD_STRING, "LightningEnd" ),
DEFINE_KEYFIELD( m_life, FIELD_FLOAT, "life" ),
DEFINE_KEYFIELD( m_boltWidth, FIELD_FLOAT, "BoltWidth" ),
DEFINE_KEYFIELD( m_noiseAmplitude, FIELD_FLOAT, "NoiseAmplitude" ),
DEFINE_KEYFIELD( m_speed, FIELD_INTEGER, "TextureScroll" ),
DEFINE_KEYFIELD( m_restrike, FIELD_FLOAT, "StrikeTime" ),
DEFINE_KEYFIELD( m_iszSpriteName, FIELD_STRING, "texture" ),
DEFINE_KEYFIELD( m_frameStart, FIELD_INTEGER, "framestart" ),
DEFINE_KEYFIELD( m_radius, FIELD_FLOAT, "Radius" ),
DEFINE_KEYFIELD( m_TouchType, FIELD_INTEGER, "TouchType" ),
DEFINE_KEYFIELD( m_iFilterName, FIELD_STRING, "filtername" ),
DEFINE_KEYFIELD( m_iszDecal, FIELD_STRING, "decalname" ),
DEFINE_FIELD( m_hFilter, FIELD_EHANDLE ),
// Function Pointers
DEFINE_FUNCTION( StrikeThink ),
DEFINE_FUNCTION( UpdateThink ),
// Input functions
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
DEFINE_INPUTFUNC( FIELD_VOID, "StrikeOnce", InputStrikeOnce ),
DEFINE_OUTPUT( 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);
// Check for tapering
if ( HasSpawnFlags( SF_BEAM_TAPEROUT ) )
{
SetWidth( m_boltWidth );
SetEndWidth( 0 );
}
else
{
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) )
{
AddEffects( 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 )
{
if ( !Q_stristr( STRING(m_iszSpriteName), ".vmt" ) )
{
// HACK/YWB: This was almost always the laserbeam.spr, so alloc'ing the name a second time with the proper extension isn't going to
// kill us on memrory.
//Warning( "Level Design Error: %s (%i:%s) Sprite name (%s) missing .vmt extension!\n",
// STRING( m_iClassname ), entindex(), GetEntityName(), STRING(m_iszSpriteName) );
char fixedname[ 512 ];
Q_strncpy( fixedname, STRING( m_iszSpriteName ), sizeof( fixedname ) );
Q_SetExtension( fixedname, ".vmt", sizeof( fixedname ) );
m_iszSpriteName = AllocPooledString( fixedname );
}
g_iszPhysicsPropClassname = AllocPooledString( "prop_physics" );
m_spriteTexture = PrecacheModel( STRING(m_iszSpriteName) );
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEnvBeam::Activate( void )
{
// Get a handle to my filter entity if there is one
if (m_iFilterName != NULL_STRING)
{
m_hFilter = dynamic_cast<CBaseFilter *>(gEntList.FindEntityByName( NULL, m_iFilterName ));
}
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() )
{
RemoveEffects( 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() )
{
AddEffects( 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 )
return;
m_speed = clamp( (int) m_speed, 0, (int) MAX_BEAM_SCROLLSPEED );
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;
}
};
class CTraceFilterPlayersNPCsPhysicsProps : public ITraceFilter
{
public:
bool ShouldHitEntity( IHandleEntity *pServerEntity, int contentsMask )
{
CBaseEntity *pEntity = EntityFromEntityHandle( pServerEntity );
if ( pEntity )
{
if ( pEntity->IsPlayer() || pEntity->MyNPCPointer() || pEntity->m_iClassname == g_iszPhysicsPropClassname )
return true;
}
return false;
}
virtual TraceType_t GetTraceType() const
{
return TRACE_ENTITIES_ONLY;
}
};
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CEnvBeam::PassesTouchFilters(CBaseEntity *pOther)
{
bool fPassedSoFar = false;
// Touched some player or NPC!
if( m_TouchType != touch_npc_only )
{
if( pOther->IsPlayer() )
{
fPassedSoFar = true;
}
}
if( m_TouchType != touch_player_only )
{
if( pOther->IsNPC() )
{
fPassedSoFar = true;
}
}
if( m_TouchType == touch_player_or_npc_or_physicsprop )
{
if( pOther->m_iClassname == g_iszPhysicsPropClassname )
{
fPassedSoFar = true;
}
}
if( fPassedSoFar )
{
CBaseFilter* pFilter = (CBaseFilter*)(m_hFilter.Get());
return (!pFilter) ? true : pFilter->PassesFilter( this, pOther );
}
return false;
}
//-----------------------------------------------------------------------------
// 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() );
if( m_TouchType == touch_player_or_npc_or_physicsprop )
{
CTraceFilterPlayersNPCsPhysicsProps traceFilter;
enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr );
}
else
{
CTraceFilterPlayersNPCs traceFilter;
enginetrace->TraceRay( ray, MASK_SHOT, &traceFilter, &tr );
}
if( tr.fraction != 1.0 && PassesTouchFilters( 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 );
CBaseEntity *pEnd = gEntList.FindEntityByName( NULL, m_iszEndEntity );
if (( pStart == NULL ) || ( pEnd == NULL ))
{
return;
}
m_nNumBeamEnts = 2;
m_speed = clamp( (int) m_speed, 0, (int) MAX_BEAM_SCROLLSPEED );
// 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 ) )
{
DevMsg("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( m_speed );
if ( m_spawnflags & SF_BEAM_SHADEIN )
{
SetBeamFlags( FBEAM_SHADEIN );
}
else if ( m_spawnflags & SF_BEAM_SHADEOUT )
{
SetBeamFlags( FBEAM_SHADEOUT );
}
}