//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "c_ai_basenpc.h" #include "c_te_particlesystem.h" #include "fx.h" #include "fx_sparks.h" #include "c_tracer.h" #include "clientsideeffects.h" #include "iefx.h" #include "dlight.h" #include "bone_setup.h" #include "c_rope.h" #include "fx_line.h" #include "c_sprite.h" #include "view.h" #include "view_scene.h" #include "materialsystem/imaterialvar.h" #include "simple_keys.h" #include "fx_envelope.h" #include "iclientvehicle.h" #include "engine/ivdebugoverlay.h" #include "particles_localspace.h" #include "dlight.h" #include "iefx.h" #include "c_te_effect_dispatch.h" #include "tier0/vprof.h" #include "clienteffectprecachesystem.h" #include #include "fx_water.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define STRIDER_MSG_BIG_SHOT 1 #define STRIDER_MSG_STREAKS 2 #define STRIDER_MSG_DEAD 3 #define STOMP_IK_SLOT 11 const int NUM_STRIDER_IK_TARGETS = 6; const float STRIDERFX_BIG_SHOT_TIME = 1.25f; const float STRIDERFX_END_ALL_TIME = 4.0f; class C_StriderFX : public C_EnvelopeFX { public: typedef C_EnvelopeFX BaseClass; C_StriderFX(); ~C_StriderFX() { EffectShutdown(); } void Update( C_BaseEntity *pOwner, const Vector &targetPos ); // Returns the bounds relative to the origin (render bounds) virtual void GetRenderBounds( Vector& mins, Vector& maxs ) { ClearBounds( mins, maxs ); AddPointToBounds( m_worldPosition, mins, maxs ); AddPointToBounds( m_targetPosition, mins, maxs ); mins -= GetRenderOrigin(); maxs -= GetRenderOrigin(); } virtual void EffectInit( int entityIndex, int attachment ) { m_limitHitTime = 0; BaseClass::EffectInit( entityIndex, attachment ); } virtual void EffectShutdown( void ) { m_limitHitTime = 0; BaseClass::EffectShutdown(); } virtual int DrawModel( int flags ); virtual void LimitTime( float tmax ) { float dt = tmax - m_t; if ( dt < 0 ) { dt = 0; } m_limitHitTime = gpGlobals->curtime + dt; BaseClass::LimitTime( tmax ); } C_BaseEntity *m_pOwner; Vector m_targetPosition; Vector m_beamEndPosition; pixelvis_handle_t m_queryHandleGun; pixelvis_handle_t m_queryHandleBeamEnd; float m_limitHitTime; }; //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class C_Strider : public C_AI_BaseNPC { DECLARE_CLASS( C_Strider, C_AI_BaseNPC ); public: DECLARE_CLIENTCLASS(); DECLARE_INTERPOLATION(); C_Strider(); virtual ~C_Strider(); // model specific virtual void ReceiveMessage( int classID, bf_read &msg ); virtual void CalculateIKLocks( float currentTime ) { // NOTE: All strider IK is solved on the server, enable this to do it client-side //BaseClass::CalculateIKLocks( currentTime ); if ( m_pIk && m_pIk->m_target.Count() ) { Assert(m_pIk->m_target.Count() > STOMP_IK_SLOT); // HACKHACK: Hardcoded 11??? Not a cleaner way to do this CIKTarget &target = m_pIk->m_target[STOMP_IK_SLOT]; target.SetPos( m_vecHitPos ); // target.latched.pos = m_vecHitPos; for ( int i = 0; i < NUM_STRIDER_IK_TARGETS; i++ ) { CIKTarget &target = m_pIk->m_target[i]; target.SetPos( m_vecIKTarget[i] ); #if 0 debugoverlay->AddBoxOverlay( m_vecIKTarget[i], Vector( -2, -2, -2 ), Vector( 2, 2, 2), QAngle( 0, 0, 0 ), (int)255*m_pIk->m_target[i].est.latched, 0, 0, 0, 0 ); #endif } } } virtual void OnDataChanged( DataUpdateType_t updateType ); virtual void GetRenderBounds( Vector& theMins, Vector& theMaxs ); virtual void ClientThink(); private: C_Strider( const C_Strider & ); C_StriderFX m_cannonFX; Vector m_vecHitPos; Vector m_vecIKTarget[NUM_STRIDER_IK_TARGETS]; CInterpolatedVar< Vector > m_iv_vecHitPos; CInterpolatedVarArray< Vector, NUM_STRIDER_IK_TARGETS > m_iv_vecIKTarget; Vector m_vecRenderMins; Vector m_vecRenderMaxs; float m_flNextRopeCutTime; }; IMPLEMENT_CLIENTCLASS_DT(C_Strider, DT_NPC_Strider, CNPC_Strider) RecvPropVector(RECVINFO(m_vecHitPos)), RecvPropVector(RECVINFO(m_vecIKTarget[0])), RecvPropVector(RECVINFO(m_vecIKTarget[1])), RecvPropVector(RECVINFO(m_vecIKTarget[2])), RecvPropVector(RECVINFO(m_vecIKTarget[3])), RecvPropVector(RECVINFO(m_vecIKTarget[4])), RecvPropVector(RECVINFO(m_vecIKTarget[5])), END_RECV_TABLE() C_StriderFX::C_StriderFX() { m_pOwner = NULL; m_active = false; } void C_StriderFX::Update( C_BaseEntity *pOwner, const Vector &targetPos ) { BaseClass::Update(); m_pOwner = pOwner; if ( m_active ) { m_targetPosition = targetPos; } } // --on gun // warpy sprite bit // darkening sprite // glowy blue flare sprite // bubble warpy sprite // after glow sprite // --on line of sight // narrow beam // wide beam // --on impact point // sparkly white bits // sparkly white streaks // pale blue particle steam enum { STRIDERFX_WARP_SCALE = 0, STRIDERFX_DARKNESS, STRIDERFX_FLARE_COLOR, STRIDERFX_FLARE_SIZE, STRIDERFX_BUBBLE_SIZE, STRIDERFX_BUBBLE_REFRACT, STRIDERFX_NARROW_BEAM_COLOR, STRIDERFX_NARROW_BEAM_SIZE, STRIDERFX_WIDE_BEAM_COLOR, STRIDERFX_WIDE_BEAM_SIZE, STRIDERFX_AFTERGLOW_COLOR, STRIDERFX_WIDE_BEAM_LENGTH, STRIDERFX_SPARK_COUNT, STRIDERFX_STREAK_COUNT, STRIDERFX_STEAM_COUNT, // must be last STRIDERFX_PARAMETERS, }; class CStriderFXEnvelope { public: CStriderFXEnvelope(); void AddKey( int parameterIndex, const CSimpleKeyInterp &key ) { Assert( parameterIndex >= 0 && parameterIndex < STRIDERFX_PARAMETERS ); if ( parameterIndex >= 0 && parameterIndex < STRIDERFX_PARAMETERS ) { m_parameters[parameterIndex].Insert( key ); } } CSimpleKeyList m_parameters[STRIDERFX_PARAMETERS]; }; // NOTE: Beam widths are half-widths or radii, so this is a beam that represents a cylinder with 2" radius const float NARROW_BEAM_WIDTH = 2; const float WIDE_BEAM_WIDTH = 16; const float FLARE_SIZE = 128; const float DARK_SIZE = 64; const float AFTERGLOW_SIZE = 64; const float WARP_SIZE = 512; const float WARP_REFRACT = 0.075f; const float WARP_BUBBLE_SIZE = 256; const float WARP_BUBBLE_REFRACT = 1.0f; CStriderFXEnvelope::CStriderFXEnvelope() { AddKey( STRIDERFX_WARP_SCALE, CSimpleKeyInterp( 0, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_WARP_SCALE, CSimpleKeyInterp( 1.25, KEY_ACCELERATE, 1 ) ); AddKey( STRIDERFX_WARP_SCALE, CSimpleKeyInterp( 1.25, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_WARP_SCALE, CSimpleKeyInterp( 1.3, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 0.5, KEY_SPLINE, 1 ) ); AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 1.0, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 1.25, KEY_SPLINE, 0 ) ); AddKey( STRIDERFX_DARKNESS, CSimpleKeyInterp( 2.0, KEY_SPLINE, 0 ) ); AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 0, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 0.5, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 1.25, KEY_ACCELERATE, 1 ) ); AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 1.5, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_FLARE_COLOR, CSimpleKeyInterp( 2.0, KEY_SPLINE, 0 ) ); AddKey( STRIDERFX_FLARE_SIZE, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_FLARE_SIZE, CSimpleKeyInterp( 1.0, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_FLARE_SIZE, CSimpleKeyInterp( 2.0, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_BUBBLE_SIZE, CSimpleKeyInterp( 1.3, KEY_LINEAR, 0.5 ) ); AddKey( STRIDERFX_BUBBLE_SIZE, CSimpleKeyInterp( 2.0, KEY_DECELERATE, 2 ) ); AddKey( STRIDERFX_BUBBLE_REFRACT, CSimpleKeyInterp( 1.3, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_BUBBLE_REFRACT, CSimpleKeyInterp( 2.0, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 1.25, KEY_ACCELERATE, 1.0 ) ); AddKey( STRIDERFX_NARROW_BEAM_COLOR, CSimpleKeyInterp( 1.5, KEY_SPLINE, 0 ) ); AddKey( STRIDERFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 0.0, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 0.5, KEY_ACCELERATE, 1 ) ); AddKey( STRIDERFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 1.25, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_NARROW_BEAM_SIZE, CSimpleKeyInterp( 1.5, KEY_DECELERATE, 2 ) ); AddKey( STRIDERFX_WIDE_BEAM_COLOR, CSimpleKeyInterp( 1.25, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_WIDE_BEAM_COLOR, CSimpleKeyInterp( 1.5, KEY_SPLINE, 1 ) ); AddKey( STRIDERFX_WIDE_BEAM_COLOR, CSimpleKeyInterp( 1.75, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_WIDE_BEAM_COLOR, CSimpleKeyInterp( 2.1, KEY_SPLINE, 0 ) ); AddKey( STRIDERFX_WIDE_BEAM_SIZE, CSimpleKeyInterp( 1.25, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_WIDE_BEAM_SIZE, CSimpleKeyInterp( 2.1, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 1.0, KEY_LINEAR, 0 ) ); AddKey( STRIDERFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 1.25, KEY_SPLINE, 1 ) ); AddKey( STRIDERFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 3.0, KEY_LINEAR, 1 ) ); AddKey( STRIDERFX_AFTERGLOW_COLOR, CSimpleKeyInterp( 3.5, KEY_ACCELERATE, 0 ) ); AddKey( STRIDERFX_WIDE_BEAM_LENGTH, CSimpleKeyInterp( 1.25, KEY_LINEAR, 1.0 ) ); AddKey( STRIDERFX_WIDE_BEAM_LENGTH, CSimpleKeyInterp( 1.5, KEY_ACCELERATE, 0.0 ) ); AddKey( STRIDERFX_WIDE_BEAM_LENGTH, CSimpleKeyInterp( 2.1, KEY_LINEAR, 0 ) ); //AddKey( STRIDERFX_SPARK_COUNT, //AddKey( STRIDERFX_STREAK_COUNT, //AddKey( STRIDERFX_STEAM_COUNT, } CStriderFXEnvelope g_StriderCannonEnvelope; void ScaleColor( color32 &out, const color32 &in, float scale ) { out.r = (byte)(int)((float)in.r * scale); out.g = (byte)(int)((float)in.g * scale); out.b = (byte)(int)((float)in.b * scale); out.a = (byte)(int)((float)in.a * scale); } void DrawSpriteTangentSpace( const Vector &vecOrigin, float flWidth, float flHeight, color32 color ) { unsigned char pColor[4] = { color.r, color.g, color.b, color.a }; // Generate half-widths flWidth *= 0.5f; flHeight *= 0.5f; // Compute direction vectors for the sprite Vector fwd, right( 1, 0, 0 ), up( 0, 1, 0 ); VectorSubtract( CurrentViewOrigin(), vecOrigin, fwd ); float flDist = VectorNormalize( fwd ); if (flDist >= 1e-3) { CrossProduct( CurrentViewUp(), fwd, right ); flDist = VectorNormalize( right ); if (flDist >= 1e-3) { CrossProduct( fwd, right, up ); } else { // In this case, fwd == g_vecVUp, it's right above or // below us in screen space CrossProduct( fwd, CurrentViewRight(), up ); VectorNormalize( up ); CrossProduct( up, fwd, right ); } } Vector left = -right; Vector down = -up; Vector back = -fwd; CMeshBuilder meshBuilder; Vector point; CMatRenderContextPtr pRenderContext( materials ); IMesh* pMesh = pRenderContext->GetDynamicMesh( ); meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); meshBuilder.Color4ubv (pColor); meshBuilder.TexCoord2f (0, 0, 1); VectorMA (vecOrigin, -flHeight, up, point); VectorMA (point, -flWidth, right, point); meshBuilder.TangentS3fv( left.Base() ); meshBuilder.TangentT3fv( down.Base() ); meshBuilder.Normal3fv( back.Base() ); meshBuilder.Position3fv (point.Base()); meshBuilder.AdvanceVertex(); meshBuilder.Color4ubv (pColor); meshBuilder.TexCoord2f (0, 0, 0); VectorMA (vecOrigin, flHeight, up, point); VectorMA (point, -flWidth, right, point); meshBuilder.TangentS3fv( left.Base() ); meshBuilder.TangentT3fv( down.Base() ); meshBuilder.Normal3fv( back.Base() ); meshBuilder.Position3fv (point.Base()); meshBuilder.AdvanceVertex(); meshBuilder.Color4ubv (pColor); meshBuilder.TexCoord2f (0, 1, 0); VectorMA (vecOrigin, flHeight, up, point); VectorMA (point, flWidth, right, point); meshBuilder.TangentS3fv( left.Base() ); meshBuilder.TangentT3fv( down.Base() ); meshBuilder.Normal3fv( back.Base() ); meshBuilder.Position3fv (point.Base()); meshBuilder.AdvanceVertex(); meshBuilder.Color4ubv (pColor); meshBuilder.TexCoord2f (0, 1, 1); VectorMA (vecOrigin, -flHeight, up, point); VectorMA (point, flWidth, right, point); meshBuilder.TangentS3fv( left.Base() ); meshBuilder.TangentT3fv( down.Base() ); meshBuilder.Normal3fv( back.Base() ); meshBuilder.Position3fv (point.Base()); meshBuilder.AdvanceVertex(); meshBuilder.End(); pMesh->Draw(); } void Strider_DrawSprite( const Vector &vecOrigin, float size, const color32 &color ) { DrawSpriteTangentSpace( vecOrigin, size, size, color ); } void Strider_DrawLine( const Vector &start, const Vector &end, float width, IMaterial *pMaterial, const color32 &color ) { FX_DrawLineFade( start, end, width, pMaterial, color, 8.0f ); } int C_StriderFX::DrawModel( int ) { static color32 white = {255,255,255,255}; Vector params[STRIDERFX_PARAMETERS]; bool hasParam[STRIDERFX_PARAMETERS]; if ( !m_active ) return 1; C_BaseEntity *ent = cl_entitylist->GetEnt( m_entityIndex ); if ( ent ) { QAngle angles; ent->GetAttachment( m_attachment, m_worldPosition, angles ); } // This forces time to drive from the main clock instead of being integrated per-draw below // that way the effect moves on even when culled for visibility if ( m_limitHitTime > 0 && m_tMax > 0 ) { float dt = m_limitHitTime - gpGlobals->curtime; if ( dt < 0 ) { dt = 0; } // if the clock needs to move, update it. if ( m_tMax - dt > m_t ) { m_t = m_tMax - dt; m_beamEndPosition = m_worldPosition; } } else { // don't have enough info to derive the time, integrate current frame time m_t += gpGlobals->frametime; if ( m_tMax > 0 ) { m_t = clamp( m_t, 0, m_tMax ); m_beamEndPosition = m_worldPosition; } } float t = m_t; bool hasAny = false; memset( hasParam, 0, sizeof(hasParam) ); for ( int i = 0; i < STRIDERFX_PARAMETERS; i++ ) { hasParam[i] = g_StriderCannonEnvelope.m_parameters[i].Interp( params[i], t ); hasAny = hasAny || hasParam[i]; } pixelvis_queryparams_t gunParams; gunParams.Init(m_worldPosition, 4.0f); float gunFractionVisible = PixelVisibility_FractionVisible( gunParams, &m_queryHandleGun ); bool gunVisible = gunFractionVisible > 0.0f ? true : false; // draw the narrow beam if ( hasParam[STRIDERFX_NARROW_BEAM_COLOR] && hasParam[STRIDERFX_NARROW_BEAM_SIZE] ) { IMaterial *pMat = materials->FindMaterial( "sprites/bluelaser1", TEXTURE_GROUP_CLIENT_EFFECTS ); float width = NARROW_BEAM_WIDTH * params[STRIDERFX_NARROW_BEAM_SIZE].x; color32 color; float bright = params[STRIDERFX_NARROW_BEAM_COLOR].x; ScaleColor( color, white, bright ); Strider_DrawLine( m_beamEndPosition, m_targetPosition, width, pMat, color ); } // draw the wide beam if ( hasParam[STRIDERFX_WIDE_BEAM_COLOR] && hasParam[STRIDERFX_WIDE_BEAM_SIZE] ) { IMaterial *pMat = materials->FindMaterial( "effects/blueblacklargebeam", TEXTURE_GROUP_CLIENT_EFFECTS ); float width = WIDE_BEAM_WIDTH * params[STRIDERFX_WIDE_BEAM_SIZE].x; color32 color; float bright = params[STRIDERFX_WIDE_BEAM_COLOR].x; ScaleColor( color, white, bright ); Vector wideBeamEnd = m_beamEndPosition; if ( hasParam[STRIDERFX_WIDE_BEAM_LENGTH] ) { float amt = params[STRIDERFX_WIDE_BEAM_LENGTH].x; wideBeamEnd = m_beamEndPosition * amt + m_targetPosition * (1-amt); } Strider_DrawLine( wideBeamEnd, m_targetPosition, width, pMat, color ); } // after glow sprite bool updated = false; CMatRenderContextPtr pRenderContext( materials ); // warpy sprite bit if ( hasParam[STRIDERFX_WARP_SCALE] && !hasParam[STRIDERFX_BUBBLE_SIZE] && gunVisible ) { if ( !updated ) { updated = true; pRenderContext->Flush(); UpdateRefractTexture(); } IMaterial *pMat = materials->FindMaterial( "effects/strider_pinch_dudv", TEXTURE_GROUP_CLIENT_EFFECTS ); float size = WARP_SIZE; float refract = params[STRIDERFX_WARP_SCALE].x * WARP_REFRACT * gunFractionVisible; pRenderContext->Bind( pMat, (IClientRenderable*)this ); IMaterialVar *pVar = pMat->FindVar( "$refractamount", NULL ); pVar->SetFloatValue( refract ); Strider_DrawSprite( m_worldPosition, size, white ); } // darkening sprite // glowy blue flare sprite if ( hasParam[STRIDERFX_FLARE_COLOR] && hasParam[STRIDERFX_FLARE_SIZE] && hasParam[STRIDERFX_DARKNESS] && gunVisible ) { IMaterial *pMat = materials->FindMaterial( "effects/blueblackflash", TEXTURE_GROUP_CLIENT_EFFECTS ); float size = FLARE_SIZE * params[STRIDERFX_FLARE_SIZE].x; color32 color; float bright = params[STRIDERFX_FLARE_COLOR].x * gunFractionVisible; ScaleColor( color, white, bright ); color.a = (int)(255 * params[STRIDERFX_DARKNESS].x); pRenderContext->Bind( pMat, (IClientRenderable*)this ); Strider_DrawSprite( m_worldPosition, size, color ); } // bubble warpy sprite if ( hasParam[STRIDERFX_BUBBLE_SIZE] ) { Vector wideBeamEnd = m_beamEndPosition; if ( hasParam[STRIDERFX_WIDE_BEAM_LENGTH] ) { float amt = params[STRIDERFX_WIDE_BEAM_LENGTH].x; wideBeamEnd = m_beamEndPosition * amt + m_targetPosition * (1-amt); } pixelvis_queryparams_t endParams; endParams.Init(wideBeamEnd, 4.0f, 0.001f); float endFractionVisible = PixelVisibility_FractionVisible( endParams, &m_queryHandleBeamEnd ); bool endVisible = endFractionVisible > 0.0f ? true : false; if ( endVisible ) { if ( !updated ) { updated = true; pRenderContext->Flush(); UpdateRefractTexture(); } IMaterial *pMat = materials->FindMaterial( "effects/strider_bulge_dudv", TEXTURE_GROUP_CLIENT_EFFECTS ); float refract = endFractionVisible * WARP_BUBBLE_REFRACT * params[STRIDERFX_BUBBLE_REFRACT].x; float size = WARP_BUBBLE_SIZE * params[STRIDERFX_BUBBLE_SIZE].x; IMaterialVar *pVar = pMat->FindVar( "$refractamount", NULL ); pVar->SetFloatValue( refract ); pRenderContext->Bind( pMat, (IClientRenderable*)this ); Strider_DrawSprite( wideBeamEnd, size, white ); } } else { // call this to have the check ready on the first frame pixelvis_queryparams_t endParams; endParams.Init(m_beamEndPosition, 4.0f, 0.001f); PixelVisibility_FractionVisible( endParams, &m_queryHandleBeamEnd ); } if ( hasParam[STRIDERFX_AFTERGLOW_COLOR] && gunVisible ) { IMaterial *pMat = materials->FindMaterial( "effects/blueblackflash", TEXTURE_GROUP_CLIENT_EFFECTS ); float size = AFTERGLOW_SIZE;// * params[STRIDERFX_FLARE_SIZE].x; color32 color; float bright = params[STRIDERFX_AFTERGLOW_COLOR].x * gunFractionVisible; ScaleColor( color, white, bright ); pRenderContext->Bind( pMat, (IClientRenderable*)this ); Strider_DrawSprite( m_worldPosition, size, color ); dlight_t *dl = effects->CL_AllocDlight( m_entityIndex ); dl->origin = m_worldPosition; dl->color.r = 40; dl->color.g = 60; dl->color.b = 255; dl->color.exponent = 5; dl->radius = bright * 128; dl->die = gpGlobals->curtime + 0.001; } if ( m_t >= STRIDERFX_END_ALL_TIME && !hasAny ) { EffectShutdown(); } return 1; } //----------------------------------------------------------------------------- // Purpose: Strider class implementation //----------------------------------------------------------------------------- C_Strider::C_Strider() : m_iv_vecHitPos("C_Strider::m_iv_vecHitPos"), m_iv_vecIKTarget("C_Strider::m_iv_vecIKTarget") { AddVar( &m_vecHitPos, &m_iv_vecHitPos, LATCH_ANIMATION_VAR ); memset(m_vecIKTarget, 0, sizeof(m_vecIKTarget)); AddVar( &m_vecIKTarget, &m_iv_vecIKTarget, LATCH_ANIMATION_VAR ); m_flNextRopeCutTime = 0; } C_Strider::~C_Strider() { } void C_Strider::ReceiveMessage( int classID, bf_read &msg ) { if ( classID != GetClientClass()->m_ClassID ) { // message is for subclass BaseClass::ReceiveMessage( classID, msg ); return; } int messageType = msg.ReadByte(); switch( messageType ) { case STRIDER_MSG_STREAKS: { Vector pos; msg.ReadBitVec3Coord( pos ); m_cannonFX.SetRenderOrigin( pos ); m_cannonFX.EffectInit( entindex(), LookupAttachment( "BigGun" ) ); m_cannonFX.LimitTime( STRIDERFX_BIG_SHOT_TIME ); } break; case STRIDER_MSG_BIG_SHOT: { Vector tmp; msg.ReadBitVec3Coord( tmp ); m_cannonFX.SetTime( STRIDERFX_BIG_SHOT_TIME ); m_cannonFX.LimitTime( STRIDERFX_END_ALL_TIME ); } break; case STRIDER_MSG_DEAD: { m_cannonFX.EffectShutdown(); } break; } } void C_Strider::OnDataChanged( DataUpdateType_t updateType ) { if ( updateType == DATA_UPDATE_CREATED ) { // We need to have our render bounds defined or shadow creation won't work correctly ClientThink(); ClientThinkList()->SetNextClientThink( GetClientHandle(), CLIENT_THINK_ALWAYS ); } BaseClass::OnDataChanged( updateType ); m_cannonFX.Update( this, m_vecHitPos ); } //----------------------------------------------------------------------------- // Purpose: Recompute my rendering box //----------------------------------------------------------------------------- void C_Strider::ClientThink() { // The reason why this is here, as opposed to in SetObjectCollisionBox, // is because of IK. The code below recomputes bones so as to get at the hitboxes, // which causes IK to trigger, which causes raycasts against the other entities to occur, // which is illegal to do while in the Relink phase. ComputeEntitySpaceHitboxSurroundingBox( &m_vecRenderMins, &m_vecRenderMaxs ); // UNDONE: Disabled this until we can get closer to a final map and tune #if 0 // Cut ropes. if ( gpGlobals->curtime >= m_flNextRopeCutTime ) { // Blow the bbox out a little. Vector vExtendedMins = vecMins - Vector( 50, 50, 50 ); Vector vExtendedMaxs = vecMaxs + Vector( 50, 50, 50 ); C_RopeKeyframe *ropes[512]; int nRopes = C_RopeKeyframe::GetRopesIntersectingAABB( ropes, ARRAYSIZE( ropes ), GetAbsOrigin() + vExtendedMins, GetAbsOrigin() + vExtendedMaxs ); for ( int i=0; i < nRopes; i++ ) { C_RopeKeyframe *pRope = ropes[i]; if ( pRope->GetEndEntity() ) { Vector vPos; if ( pRope->GetEndPointPos( 1, vPos ) ) { // Detach the endpoint. pRope->SetEndEntity( NULL ); // Make some spark effect here.. g_pEffects->Sparks( vPos ); } } } m_flNextRopeCutTime = gpGlobals->curtime + 0.5; } #endif // True argument because the origin may have stayed the same, but the size is expected to always change g_pClientShadowMgr->AddToDirtyShadowList( this, true ); } //----------------------------------------------------------------------------- // Purpose: Recompute my rendering box //----------------------------------------------------------------------------- void C_Strider::GetRenderBounds( Vector& theMins, Vector& theMaxs ) { theMins = m_vecRenderMins; theMaxs = m_vecRenderMaxs; } //----------------------------------------------------------------------------- // Strider muzzle flashes //----------------------------------------------------------------------------- void MuzzleFlash_Strider( ClientEntityHandle_t hEntity, int attachmentIndex ) { VPROF_BUDGET( "MuzzleFlash_Strider", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); // If the client hasn't seen this entity yet, bail. matrix3x4_t matAttachment; if ( !FX_GetAttachmentTransform( hEntity, attachmentIndex, matAttachment ) ) return; CSmartPtr pSimple = CLocalSpaceEmitter::Create( "MuzzleFlash_Strider", hEntity, attachmentIndex ); SimpleParticle *pParticle; Vector forward(1,0,0), offset; //NOTENOTE: All coords are in local space float flScale = random->RandomFloat( 3.0f, 4.0f ); float burstSpeed = random->RandomFloat( 400.0f, 600.0f ); #define FRONT_LENGTH 12 // Front flash for ( int i = 1; i < FRONT_LENGTH; i++ ) { offset = (forward * (i*2.0f*flScale)); pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/combinemuzzle%d", random->RandomInt(1,2) ) ), offset ); if ( pParticle == NULL ) return; pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = 0.1f; pParticle->m_vecVelocity = forward * burstSpeed; pParticle->m_uchColor[0] = 255; pParticle->m_uchColor[1] = 255; pParticle->m_uchColor[2] = 255; pParticle->m_uchStartAlpha = 255.0f; pParticle->m_uchEndAlpha = 0; pParticle->m_uchStartSize = ( (random->RandomFloat( 6.0f, 8.0f ) * (FRONT_LENGTH-(i))/(FRONT_LENGTH*0.75f)) * flScale ); pParticle->m_uchEndSize = pParticle->m_uchStartSize; pParticle->m_flRoll = random->RandomInt( 0, 360 ); pParticle->m_flRollDelta = 0.0f; } Vector right(0,1,0), up(0,0,1); Vector dir = right - up; #define SIDE_LENGTH 8 burstSpeed = random->RandomFloat( 400.0f, 600.0f ); // Diagonal flash for ( int i = 1; i < SIDE_LENGTH; i++ ) { offset = (dir * (i*flScale)); pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/combinemuzzle%d", random->RandomInt(1,2) ) ), offset ); if ( pParticle == NULL ) return; pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = 0.2f; pParticle->m_vecVelocity = dir * burstSpeed * 0.25f; pParticle->m_uchColor[0] = 255; pParticle->m_uchColor[1] = 255; pParticle->m_uchColor[2] = 255; pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 0; pParticle->m_uchStartSize = ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale ); pParticle->m_uchEndSize = pParticle->m_uchStartSize; pParticle->m_flRoll = random->RandomInt( 0, 360 ); pParticle->m_flRollDelta = 0.0f; } dir = right + up; burstSpeed = random->RandomFloat( 400.0f, 600.0f ); // Diagonal flash for ( int i = 1; i < SIDE_LENGTH; i++ ) { offset = (-dir * (i*flScale)); pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/combinemuzzle%d", random->RandomInt(1,2) ) ), offset ); if ( pParticle == NULL ) return; pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = 0.2f; pParticle->m_vecVelocity = dir * -burstSpeed * 0.25f; pParticle->m_uchColor[0] = 255; pParticle->m_uchColor[1] = 255; pParticle->m_uchColor[2] = 255; pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 0; pParticle->m_uchStartSize = ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale ); pParticle->m_uchEndSize = pParticle->m_uchStartSize; pParticle->m_flRoll = random->RandomInt( 0, 360 ); pParticle->m_flRollDelta = 0.0f; } dir = up; burstSpeed = random->RandomFloat( 400.0f, 600.0f ); // Top flash for ( int i = 1; i < SIDE_LENGTH; i++ ) { offset = (dir * (i*flScale)); pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( VarArgs( "effects/combinemuzzle%d", random->RandomInt(1,2) ) ), offset ); if ( pParticle == NULL ) return; pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = 0.2f; pParticle->m_vecVelocity = dir * burstSpeed * 0.25f; pParticle->m_uchColor[0] = 255; pParticle->m_uchColor[1] = 255; pParticle->m_uchColor[2] = 255; pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 0; pParticle->m_uchStartSize = ( (random->RandomFloat( 2.0f, 4.0f ) * (SIDE_LENGTH-(i))/(SIDE_LENGTH*0.5f)) * flScale ); pParticle->m_uchEndSize = pParticle->m_uchStartSize; pParticle->m_flRoll = random->RandomInt( 0, 360 ); pParticle->m_flRollDelta = 0.0f; } pParticle = (SimpleParticle *) pSimple->AddParticle( sizeof( SimpleParticle ), pSimple->GetPMaterial( "effects/strider_muzzle" ), vec3_origin ); if ( pParticle == NULL ) return; pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = random->RandomFloat( 0.3f, 0.4f ); pParticle->m_vecVelocity.Init(); pParticle->m_uchColor[0] = 255; pParticle->m_uchColor[1] = 255; pParticle->m_uchColor[2] = 255; pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 0; pParticle->m_uchStartSize = flScale * random->RandomFloat( 12.0f, 16.0f ); pParticle->m_uchEndSize = 0.0f; pParticle->m_flRoll = random->RandomInt( 0, 360 ); pParticle->m_flRollDelta = 0.0f; Vector origin; MatrixGetColumn( matAttachment, 3, &origin ); int entityIndex = ClientEntityList().HandleToEntIndex( hEntity ); if ( entityIndex >= 0 ) { dlight_t *el = effects->CL_AllocElight( LIGHT_INDEX_MUZZLEFLASH + entityIndex ); el->origin = origin; el->color.r = 64; el->color.g = 128; el->color.b = 255; el->color.exponent = 5; el->radius = random->RandomInt( 100, 150 ); el->decay = el->radius / 0.05f; el->die = gpGlobals->curtime + 0.1f; } } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void StriderMuzzleFlashCallback( const CEffectData &data ) { MuzzleFlash_Strider( data.m_hEntity, data.m_nAttachmentIndex ); } DECLARE_CLIENT_EFFECT( "StriderMuzzleFlash", StriderMuzzleFlashCallback ); #define BLOOD_MIN_SPEED 64.0f*2.0f #define BLOOD_MAX_SPEED 256.0f*8.0f //----------------------------------------------------------------------------- // Purpose: // Input : &origin - // &normal - // scale - //----------------------------------------------------------------------------- void StriderBlood( const Vector &origin, const Vector &normal, float scale ) { VPROF_BUDGET( "StriderBlood", VPROF_BUDGETGROUP_PARTICLE_RENDERING ); //Find area ambient light color and use it to tint smoke Vector worldLight = WorldGetLightForPoint( origin, true ); Vector tint; float luminosity; UTIL_GetNormalizedColorTintAndLuminosity( worldLight, &tint, &luminosity ); // We only take a portion of the tint tint = (tint * 0.25f)+(Vector(0.75f,0.75f,0.75f)); // Rescale to a character range luminosity = MAX( 200, luminosity*255 ); CSmartPtr pSimple = CSplashParticle::Create( "splish" ); pSimple->SetSortOrigin( origin ); int i; float flScale = scale / 8.0f; PMaterialHandle hMaterial = ParticleMgr()->GetPMaterial( "effects/slime1" ); float length = 0.2f; Vector vForward, vRight, vUp; Vector offDir; TrailParticle *tParticle; CSmartPtr sparkEmitter = CTrailParticles::Create( "splash" ); if ( !sparkEmitter ) return; sparkEmitter->SetSortOrigin( origin ); sparkEmitter->m_ParticleCollision.SetGravity( 600.0f ); sparkEmitter->SetFlag( bitsPARTICLE_TRAIL_VELOCITY_DAMPEN ); sparkEmitter->SetVelocityDampen( 2.0f ); //Dump out drops Vector offset; for ( i = 0; i < 64; i++ ) { offset = origin; offset[0] += random->RandomFloat( -8.0f, 8.0f ) * flScale; offset[1] += random->RandomFloat( -8.0f, 8.0f ) * flScale; offset[2] += random->RandomFloat( -8.0f, 8.0f ) * flScale; tParticle = (TrailParticle *) sparkEmitter->AddParticle( sizeof(TrailParticle), hMaterial, offset ); if ( tParticle == NULL ) break; tParticle->m_flLifetime = 0.0f; tParticle->m_flDieTime = 1.0f; offDir = normal + RandomVector( -1.0f, 1.0f ); tParticle->m_vecVelocity = offDir * random->RandomFloat( BLOOD_MIN_SPEED * flScale * 2.0f, BLOOD_MAX_SPEED * flScale * 2.0f ); tParticle->m_vecVelocity[2] += random->RandomFloat( 8.0f, 32.0f ) * flScale; tParticle->m_flWidth = random->RandomFloat( 20.0f, 26.0f ) * flScale; tParticle->m_flLength = random->RandomFloat( length*0.5f, length ) * flScale; int nColor = random->RandomInt( luminosity*0.75f, luminosity ); tParticle->m_color.r = nColor * tint.x; tParticle->m_color.g = nColor * tint.y; tParticle->m_color.b = nColor * tint.z; tParticle->m_color.a = 255; } } //----------------------------------------------------------------------------- // Purpose: // Input : &data - //----------------------------------------------------------------------------- void StriderBloodCallback( const CEffectData &data ) { StriderBlood( data.m_vOrigin, data.m_vNormal, data.m_flScale ); } DECLARE_CLIENT_EFFECT( "StriderBlood", StriderBloodCallback );