|
|
//========= Copyright � 1996-2005, 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 );
protected: // true if the end point vecline was specified in hammer
inline bool HasEndPointHandle() { return !m_vEndPointRelative.IsZero(); }
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;
// endpoint may be optionally specified as a vecline instead of a target entity.
// note: this mechanism seems rather roundabout, because the parent CBeam has
// the m_vecEndPos data member; however, the CEnvBeam is programmed to overwrite it
// each frame with the position of a target entity, so the easiest way to
// implement this behavior was to put this bit of redundant data in the child
// class and have it get written back every frame. If this bothers you,
// please fix it.
Vector m_vEndPointWorld; // this is the point as read from the level spec; however it's not used, because
Vector m_vEndPointRelative; // on spawn, endpoint is transformed into local space here.
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_vEndPointWorld, FIELD_VECTOR, "targetpoint" ), 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_KEYFIELD( m_nClipStyle, FIELD_INTEGER, "ClipStyle" ),
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 a non-targetentity endpoint was specified, transform it into local relative space
// so it can move along with the base
if (!m_vEndPointWorld.IsZero()) { WorldToEntitySpace( m_vEndPointWorld, &m_vEndPointRelative ); } else { m_vEndPointRelative.Zero(); }
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 && !HasEndPointHandle()) { 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 the end entity is missing, we use the Hammer-specified vector offset instead.
bool bEndPointFromEntity = pEnd != NULL;
if ( pStart == NULL || ( !bEndPointFromEntity && !HasEndPointHandle() ) ) return;
Vector vEndPointLocation; if ( bEndPointFromEntity ) { vEndPointLocation = pEnd->GetAbsOrigin() ; } else { EntityToWorldSpace( m_vEndPointRelative, &vEndPointLocation ); }
m_speed = clamp( m_speed, 0, MAX_BEAM_SCROLLSPEED ); bool pointStart = IsStaticPointEntity( pStart ); bool pointEnd = !bEndPointFromEntity || 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 ? &vEndPointLocation : 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 the end entity is missing, we use the Hammer-specified vector offset instead.
bool bEndPointFromEntity = pEnd != NULL;
if (( pStart == NULL ) || ( !bEndPointFromEntity && !HasEndPointHandle() )) { return; }
Vector vEndPointPos; if ( bEndPointFromEntity ) { vEndPointPos = pEnd->GetAbsOrigin(); } else { EntityToWorldSpace( m_vEndPointRelative, &vEndPointPos ); }
m_nNumBeamEnts = 2;
m_speed = clamp( m_speed, 0, 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 ( !bEndPointFromEntity || IsStaticPointEntity( pEnd ) ) { SetAbsEndPos( vEndPointPos ); } 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 ); } }
|