//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Clients CBaseObject // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "c_baseobject.h" #include "c_basetfplayer.h" #include "hud.h" #include "c_tfteam.h" #include "engine/IEngineSound.h" #include "particles_simple.h" #include "functionproxy.h" #include "IEffects.h" #include "c_hint_events.h" #include "model_types.h" #include "particlemgr.h" #include "particle_collision.h" #include "env_objecteffects.h" #include "basetfvehicle.h" #include "c_weapon_builder.h" #include "ivrenderview.h" #include "ObjectControlPanel.h" #include "engine/ivmodelinfo.h" #include "c_te_effect_dispatch.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define MAX_VISIBLE_BUILDPOINT_DISTANCE (400 * 400) // Remove aliasing of name due to shared code #undef CBaseObject IMPLEMENT_CLIENTCLASS_DT(C_BaseObject, DT_BaseObject, CBaseObject) RecvPropInt(RECVINFO(m_iHealth)), RecvPropInt(RECVINFO(m_iMaxHealth)), RecvPropInt(RECVINFO(m_bHasSapper)), RecvPropInt(RECVINFO(m_iObjectType)), RecvPropInt(RECVINFO(m_bBuilding)), RecvPropInt(RECVINFO(m_bPlacing)), RecvPropFloat(RECVINFO(m_flPercentageConstructed)), RecvPropInt(RECVINFO(m_fObjectFlags)), RecvPropInt(RECVINFO(m_bDeteriorating)), RecvPropEHandle(RECVINFO(m_hBuiltOnEntity)), RecvPropInt(RECVINFO( m_takedamage ) ), RecvPropInt( RECVINFO( m_bDisabled ) ), RecvPropEHandle( RECVINFO( m_hBuilder ) ), END_RECV_TABLE() //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_BaseObject::C_BaseObject( ) { m_flDamageFlash = 0; m_YawPreviewState = YAW_PREVIEW_OFF; m_bBuilding = false; m_bPlacing = false; m_flPercentageConstructed = 0; m_flNextEffect = 0; m_bOldSapper = m_bHasSapper = false; m_fObjectFlags = 0; m_bDeteriorating = false; m_ThermalMaterial.Init("player/thermal/thermal",TEXTURE_GROUP_CLIENT_EFFECTS); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_BaseObject::~C_BaseObject( void ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::PreDataUpdate( DataUpdateType_t updateType ) { BaseClass::PreDataUpdate( updateType ); m_iOldHealth = m_iHealth; m_bOldSapper = m_bHasSapper; m_hOldOwner = GetOwner(); m_bWasActive = ShouldBeActive(); m_bWasBuilding = m_bBuilding; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::OnDataChanged( DataUpdateType_t updateType ) { if (updateType == DATA_UPDATE_CREATED) { if ( !IS_MINIMAP_PANEL_DEFINED( ) && !(m_fObjectFlags & OF_SUPPRESS_APPEAR_ON_MINIMAP) ) { CONSTRUCT_MINIMAP_PANEL( "minimap_object", MINIMAP_OBJECTS ); } CreateBuildPoints(); } BaseClass::OnDataChanged( updateType ); // Did we just finish building? if ( m_bWasBuilding && !m_bBuilding ) { FinishedBuilding(); } // Did we just go active? bool bShouldBeActive = ShouldBeActive(); if ( !m_bWasActive && bShouldBeActive ) { OnGoActive(); } else if ( m_bWasActive && !bShouldBeActive ) { OnGoInactive(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::SetDormant( bool bDormant ) { BaseClass::SetDormant( bDormant ); //ENTITY_PANEL_ACTIVATE( "analyzed_object", !bDormant ); } #define TF_OBJ_BODYGROUPTURNON 1 #define TF_OBJ_BODYGROUPTURNOFF 0 //----------------------------------------------------------------------------- // Purpose: // Input : origin - // angles - // event - // *options - //----------------------------------------------------------------------------- void C_BaseObject::FireEvent( const Vector& origin, const QAngle& angles, int event, const char *options ) { switch ( event ) { default: { BaseClass::FireEvent( origin, angles, event, options ); } break; case TF_OBJ_PLAYBUILDSOUND: { EmitSound( options ); } break; case TF_OBJ_ENABLEBODYGROUP: { int index = FindBodygroupByName( options ); if ( index >= 0 ) { SetBodygroup( index, TF_OBJ_BODYGROUPTURNON ); } } break; case TF_OBJ_DISABLEBODYGROUP: { int index = FindBodygroupByName( options ); if ( index >= 0 ) { SetBodygroup( index, TF_OBJ_BODYGROUPTURNOFF ); } } break; case TF_OBJ_ENABLEALLBODYGROUPS: case TF_OBJ_DISABLEALLBODYGROUPS: { // Start at 1, because body 0 is the main .mdl body... // Is this the way we want to do this? int count = GetNumBodyGroups(); for ( int i = 1; i < count; i++ ) { int subpartcount = GetBodygroupCount( i ); if ( subpartcount == 2 ) { SetBodygroup( i, ( event == TF_OBJ_ENABLEALLBODYGROUPS ) ? TF_OBJ_BODYGROUPTURNON : TF_OBJ_BODYGROUPTURNOFF ); } else { DevMsg( "TF_OBJ_ENABLE/DISABLEBODY GROUP: %s has a group with %i subparts, should be exactly 2\n", GetClassname(), subpartcount ); } } } break; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BaseObject::OffsetObjectOrigin( Vector& origin ) { if ( !m_bBuilding ) return false; if ( inv_demo.GetBool() ) return false; Vector vecWorldMins, vecWorldMaxs; CollisionProp()->WorldSpaceAABB( &vecWorldMins, &vecWorldMaxs ); float flSize = vecWorldMaxs.z - vecWorldMins.z; origin.z -= (flSize * (1 - m_flPercentageConstructed)); // If we're building, fake sliding the object out of the ground return true; } const char* C_BaseObject::GetStatusName() const { return GetObjectInfo( GetType() )->m_pStatusName; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int C_BaseObject::DrawModel( int flags ) { Vector vRealOrigin = GetLocalOrigin(); Vector vOrigin = vRealOrigin; bool needOriginReset = OffsetObjectOrigin( vOrigin ); if ( needOriginReset ) { SetLocalOrigin( vOrigin ); InvalidateBoneCache(); } int drawn; C_BaseTFPlayer *pLocal = C_BaseTFPlayer::GetLocalPlayer(); if ( pLocal && pLocal->IsUsingThermalVision() ) { modelrender->ForcedMaterialOverride( m_ThermalMaterial ); drawn = BaseClass::DrawModel(flags); modelrender->ForcedMaterialOverride( NULL ); } else { // If we're a brush-built, map-defined object chain up to baseentity draw if ( modelinfo->GetModelType( GetModel() ) == mod_brush ) { drawn = CBaseEntity::DrawModel(flags); } else { drawn = BaseClass::DrawModel(flags); } } // Restore faked origin if ( needOriginReset ) { SetLocalOrigin( vRealOrigin ); } // If we were drawn, draw building effects if we're building, or damage effects if we're damaged if ( drawn && (m_flNextEffect < gpGlobals->curtime) ) { // Haxory LOD C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); if ( (GetAbsOrigin() - pPlayer->GetAbsOrigin()).LengthSqr() < lod_effect_distance.GetFloat() ) { if ( IsBuilding() ) { DrawBuildEffects(); } if ( !m_bPlacing && !m_bBuilding ) { if ( !HasPowerup( POWERUP_EMP ) ) { DrawRunningEffects(); } if ( GetHealth() < GetMaxHealth() ) { DrawDamageEffects(); } } } } HighlightBuildPoints( flags ); return drawn; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::HighlightBuildPoints( int flags ) { C_BaseTFPlayer *pLocal = C_BaseTFPlayer::GetLocalPlayer(); if ( !pLocal ) return; if ( !GetNumBuildPoints() || !InLocalTeam() ) return; C_WeaponBuilder *pBuilderWpn = dynamic_cast< C_WeaponBuilder * >( pLocal->GetActiveWeaponForSelection() ); if ( !pBuilderWpn ) return; if ( !pBuilderWpn->IsPlacingObject() ) return; C_BaseObject *pPlacementObj = pBuilderWpn->GetPlacementModel(); if ( !pPlacementObj || pPlacementObj == this ) return; // Near enough? if ( (GetAbsOrigin() - pLocal->GetAbsOrigin()).LengthSqr() < MAX_VISIBLE_BUILDPOINT_DISTANCE ) { bool bRestoreModel = false; Vector vecPrevAbsOrigin = pPlacementObj->GetAbsOrigin(); QAngle vecPrevAbsAngles = pPlacementObj->GetAbsAngles(); Vector orgColor; render->GetColorModulation( orgColor.Base() ); float orgBlend = render->GetBlend(); // Any empty buildpoints? for ( int i = 0; i < GetNumBuildPoints(); i++ ) { // Can this object build on this point? if ( CanBuildObjectOnBuildPoint( i, pPlacementObj->GetType() ) ) { Vector vecBPOrigin; QAngle vecBPAngles; if ( GetBuildPoint(i, vecBPOrigin, vecBPAngles) ) { pPlacementObj->InvalidateBoneCaches(); Vector color( 0, 255, 0 ); render->SetColorModulation( color.Base() ); float frac = fmod( gpGlobals->curtime, 3 ); frac *= 2 * M_PI; frac = cos( frac ); render->SetBlend( (175 + (int)( frac * 75.0f )) / 255.0 ); // HACK: Fixup angles on the HL2 model we're using if ( !strcmp( modelinfo->GetModelName( pPlacementObj->GetModel() ), "models/items/HealthKit.mdl" ) ) { vecBPAngles.x += 90; } // FIXME: This truly sucks! The bone cache should use // render location for this computation instead of directly accessing AbsAngles // Necessary for bone cache computations to work pPlacementObj->SetAbsOrigin( vecBPOrigin ); pPlacementObj->SetAbsAngles( vecBPAngles ); modelrender->DrawModel( flags, pPlacementObj, pPlacementObj->GetModelInstance(), pPlacementObj->index, pPlacementObj->GetModel(), vecBPOrigin, vecBPAngles, pPlacementObj->m_nSkin, pPlacementObj->m_nBody, pPlacementObj->m_nHitboxSet ); bRestoreModel = true; } } } if ( bRestoreModel ) { pPlacementObj->SetAbsOrigin(vecPrevAbsOrigin); pPlacementObj->SetAbsAngles(vecPrevAbsAngles); pPlacementObj->InvalidateBoneCaches(); render->SetColorModulation( orgColor.Base() ); render->SetBlend( orgBlend ); } } } //----------------------------------------------------------------------------- // Exit points for mounted vehicles.... //----------------------------------------------------------------------------- void C_BaseObject::GetExitPoint( CBaseEntity *pPlayer, int nBuildPoint, Vector *pAbsPosition, QAngle *pAbsAngles ) { Assert(0); } //----------------------------------------------------------------------------- // Purpose: Overridden to allow for brush-built map defined objects //----------------------------------------------------------------------------- bool C_BaseObject::IsIdentityBrush( void ) { return false; } //----------------------------------------------------------------------------- // Builder preview... //----------------------------------------------------------------------------- void C_BaseObject::ActivateYawPreview( bool enable ) { m_YawPreviewState = enable ? YAW_PREVIEW_ON : YAW_PREVIEW_WAITING_FOR_UPDATE; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::PreviewYaw( float yaw ) { m_fYawPreview = yaw; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BaseObject::IsPreviewingYaw() const { return m_YawPreviewState != YAW_PREVIEW_OFF; } //----------------------------------------------------------------------------- // Purpose: This is called to get the initial builder yaw... //----------------------------------------------------------------------------- float C_BaseObject::GetInitialBuilderYaw() { return GetAbsAngles().y; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::PostDataUpdate( DataUpdateType_t updateType ) { BaseClass::PostDataUpdate( updateType ); bool bNewEntity = (updateType == DATA_UPDATE_CREATED); if ( bNewEntity ) { m_flAttackTime = -1000; } // Determine if we're under attack if ( !bNewEntity ) { if ( m_iHealth < m_iOldHealth ) { // Deteriorating objects don't play sounds if ( !IsDeteriorating() ) { m_flAttackTime = gpGlobals->curtime; } } else if ( m_iHealth > m_iOldHealth && m_iHealth == m_iMaxHealth ) { // If we were just fully healed, remove all decals RemoveAllDecals(); } } if ( m_bHasSapper ) { // Play a specific sound for a sapper... if ( m_bOldSapper != m_bHasSapper ) { // Don't create these for dragonsteeth if ( InLocalTeam() && GetType() != OBJ_DRAGONSTEETH ) { // Play a sound. CLocalPlayerFilter filter; EmitSound( filter, SOUND_FROM_LOCAL_PLAYER, "BaseObject.SapperDestroyingTeamBuilding" ); MinimapCreateTempTrace( "minimap_under_attack", MINIMAP_PERSONAL_ORDERS, GetAbsOrigin() ); } } } // Notify the hint system of the object being built. if ( bNewEntity && GetOwner() && ( GetOwner() == C_BasePlayer::GetLocalPlayer() ) ) { C_HintEvent_ObjectBuiltByLocalPlayer event( this ); GlobalHintEvent( &event ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::Release( void ) { // Remove any reticles on this entity C_BaseTFPlayer *pPlayer = C_BaseTFPlayer::GetLocalPlayer(); if ( pPlayer ) { pPlayer->Remove_Target( this ); } BaseClass::Release(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BaseObject::IsUnderAttack( ) { // It's under attack for the 3 seconds after the last attack time return (gpGlobals->curtime - m_flAttackTime) < 5.0f; } //----------------------------------------------------------------------------- // Ownership: //----------------------------------------------------------------------------- C_BaseTFPlayer *C_BaseObject::GetOwner() { return m_hBuilder; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BaseObject::IsOwnedByLocalPlayer() const { if ( !m_hBuilder ) return false; return ( m_hBuilder == C_BaseTFPlayer::GetLocalPlayer() ); } //----------------------------------------------------------------------------- // Purpose: Add entity to visibile entities list //----------------------------------------------------------------------------- void C_BaseObject::AddEntity( void ) { // If set to invisible, skip. Do this before resetting the entity pointer so it has // valid data to decide whether it's visible. if ( !ShouldDraw() ) { return; } // Update the entity position UpdatePosition(); // Yaw preview if (m_YawPreviewState != YAW_PREVIEW_OFF) { // This piece of code makes it so we keep using the preview // until we get a network update which matches the update value if (m_YawPreviewState == YAW_PREVIEW_WAITING_FOR_UPDATE) { if (fmod( fabs(GetLocalAngles().y - m_fYawPreview), 360.0f) < 1.0f) { m_YawPreviewState = YAW_PREVIEW_OFF; } } if (GetLocalOrigin().y != m_fYawPreview) { SetLocalAnglesDim( Y_INDEX, m_fYawPreview ); InvalidateBoneCache(); } } // Create flashlight effects, etc. CreateLightEffects(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::Select( void ) { C_BaseTFPlayer *pPlayer = C_BaseTFPlayer::GetLocalPlayer(); pPlayer->SetSelectedObject( this ); } //----------------------------------------------------------------------------- // Sends client commands back to the server: //----------------------------------------------------------------------------- void C_BaseObject::SendClientCommand( const char *pCmd ) { char szbuf[128]; Q_snprintf( szbuf, sizeof( szbuf ), "objcmd %d %s", entindex(), pCmd ); engine->ClientCmd(szbuf); } //----------------------------------------------------------------------------- // Purpose: Get a text description for the object target //----------------------------------------------------------------------------- const char *C_BaseObject::GetTargetDescription( void ) const { return GetStatusName(); } //----------------------------------------------------------------------------- // Purpose: Get a text description for the object target (more verbose) //----------------------------------------------------------------------------- char *C_BaseObject::GetIDString( void ) { m_szIDString[0] = 0; RecalculateIDString(); return m_szIDString; } //----------------------------------------------------------------------------- // It's a valid ID target when it's building //----------------------------------------------------------------------------- bool C_BaseObject::IsValidIDTarget( void ) { return InSameTeam( C_BaseTFPlayer::GetLocalPlayer() ) && m_bBuilding; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::RecalculateIDString( void ) { // Subclasses may have filled this out with a string if ( !m_szIDString[0] ) { Q_strncpy( m_szIDString, GetTargetDescription(), sizeof(m_szIDString) ); } // Have I taken damage? if ( m_iHealth < m_iMaxHealth ) { char szHealth[ MAX_ID_STRING ]; if ( IsDeteriorating() ) { Q_snprintf( szHealth, sizeof(szHealth), "\nBUILDER LOST, DETERIORATING... %.0f percent", ceil(((float)m_iHealth / (float)m_iMaxHealth) * 100) ); } else if ( m_bBuilding ) { Q_snprintf( szHealth, sizeof(szHealth), "\nConstruction at %.0f percent\nHealth at %.0f percent", (m_flPercentageConstructed * 100), ceil(((float)m_iHealth / (float)m_iMaxHealth) * 100) ); } else { Q_snprintf( szHealth, sizeof(szHealth), "\nHealth at %.0f percent", ceil(((float)m_iHealth / (float)m_iMaxHealth) * 100) ); } Q_strncat( m_szIDString, szHealth, sizeof(m_szIDString), COPY_ALL_CHARACTERS ); } if ( m_bHasSapper ) { Q_strncat( m_szIDString, "\nUse it to remove the attached enemy object", sizeof(m_szIDString), COPY_ALL_CHARACTERS ); } // If it's deteriorating, and I can buy it, tell me C_BaseTFPlayer *pLocalPlayer = C_BaseTFPlayer::GetLocalPlayer(); if ( IsDeteriorating() && pLocalPlayer && ClassCanBuild( pLocalPlayer->PlayerClass(), GetType() ) ) { char szBuy[ MAX_ID_STRING ]; int iCost = CalculateObjectCost( GetType(), pLocalPlayer->GetNumObjects( GetType() ), pLocalPlayer->GetTeamNumber() ); Q_snprintf( szBuy, sizeof(szBuy), "\nBUY THIS OBJECT FOR %d RESOURCES", iCost ); Q_strncat( m_szIDString, szBuy, sizeof(m_szIDString), COPY_ALL_CHARACTERS ); } } //----------------------------------------------------------------------------- // Purpose: Effects created when the object's running //----------------------------------------------------------------------------- void C_BaseObject::DrawRunningEffects( void ) { if ( !GetMaxHealth() ) return; // Get the overall damage percentage float flDamaged = 1.0 - ((float)GetHealth() / (float)GetMaxHealth()); // Damage attachment points int iSmokeAttachment, iSparkAttachment; Vector vecSmoke, vecSpark, vecSmokeDir, dir; QAngle angSmoke, angSpark; // Look for damage points iSmokeAttachment = LookupRandomAttachment( "r_smoke" ); // Get the points if ( GetAttachment( iSmokeAttachment, vecSmoke, angSmoke ) ) { AngleVectors( angSmoke, &vecSmokeDir); float r, g, b; r = g = b = random->RandomFloat( 16, 92 ); // Smoke CSmartPtr pSmokeEmitter = CObjectSmokeParticles::Create( "DrawRunningEffects 1" ); pSmokeEmitter->SetSortOrigin( vecSmoke ); ObjectSmokeParticle *pParticle = (ObjectSmokeParticle *) pSmokeEmitter->AddParticle( sizeof(ObjectSmokeParticle), g_Mat_DustPuff[1], vecSmoke ); if ( pParticle ) { pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = random->RandomFloat( 2.0f, 3.0f ); pParticle->m_uchStartSize = random->RandomFloat( 2, 3 ); pParticle->m_uchEndSize = random->RandomFloat( 5, 10 ); dir[0] = vecSmokeDir[0] + random->RandomFloat( -0.1f, 0.1f ); dir[1] = vecSmokeDir[1] + random->RandomFloat( -0.1f, 0.1f ); dir[2] = vecSmokeDir[2] + random->RandomFloat( -0.1f, 0.1f ); pParticle->m_vecVelocity = dir * random->RandomFloat( 30.0f, 40.0f ); pParticle->m_uchStartAlpha = random->RandomFloat( 128,255 ); pParticle->m_uchEndAlpha = 0; pParticle->m_flRoll = random->RandomFloat( 180, 360 ); pParticle->m_flRollDelta = random->RandomFloat( -1, 1 ); pParticle->m_uchColor[0] = r; pParticle->m_uchColor[1] = g; pParticle->m_uchColor[2] = b; pParticle->m_vecAcceleration = Vector(0,0,10); } } // Sparks for ( float flSparks = flDamaged - 0.3; flSparks > 0; flSparks -= 0.3 ) { // Get random spark attachment point iSparkAttachment = LookupRandomAttachment( "r_spark" ); if ( GetAttachment( iSparkAttachment, vecSpark, angSpark ) ) { g_pEffects->Sparks( vecSpark ); } } m_flNextEffect = gpGlobals->curtime + random->RandomFloat( 0.05, 0.1 ); } //----------------------------------------------------------------------------- // Purpose: Effects created while the object's building itself //----------------------------------------------------------------------------- void C_BaseObject::DrawBuildEffects( void ) { m_flNextEffect = gpGlobals->curtime + 10; } //----------------------------------------------------------------------------- // Purpose: Effects created when the object's damaged //----------------------------------------------------------------------------- void C_BaseObject::DrawDamageEffects( void ) { if ( !GetMaxHealth() ) return; // Get the overall damage percentage float flDamaged = 1.0 - ((float)GetHealth() / (float)GetMaxHealth()); // Damage attachment points int iSmokeAttachment, iFireAttachment, iSparkAttachment; Vector vecSmoke, vecFire, vecSpark, vecSmokeDir, dir; QAngle angSmoke, angFire, angSpark; // HACK: Calculate a random origin // This can go away when we require all objects to have damage attachment points Vector vecOrigin; CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &vecOrigin ); // Look for damage points iSmokeAttachment = LookupRandomAttachment( "d_smoke" ); iFireAttachment = LookupRandomAttachment( "d_fire" ); // Get the points, and if we can't find 'em, use the random origin if ( GetAttachment( iSmokeAttachment, vecSmoke, angSmoke ) ) { AngleVectors( angSmoke, &vecSmokeDir ); } else { vecSmoke = vecOrigin; vecSmokeDir = Vector(0,0,1); } if ( !GetAttachment( iFireAttachment, vecFire, angFire ) ) { vecFire = vecOrigin; angFire = QAngle(0,0,0); } float r, g, b; r = g = b = random->RandomFloat( 16, 92 ); // Smoke CSmartPtr pSmokeEmitter = CSimpleEmitter::Create( "DrawDamageEffects 1" ); pSmokeEmitter->SetSortOrigin( vecSmoke ); SimpleParticle *pParticle = (SimpleParticle *) pSmokeEmitter->AddParticle( sizeof(SimpleParticle), g_Mat_DustPuff[1], vecSmoke ); if ( pParticle ) { pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = random->RandomFloat( 2.0f, 3.0f ); pParticle->m_uchStartSize = MAX( 1, 20 * flDamaged ); pParticle->m_uchEndSize = MAX( 10, 80 * flDamaged ); dir[0] = vecSmokeDir[0] + random->RandomFloat( -0.2f, 0.2f ); dir[1] = vecSmokeDir[1] + random->RandomFloat( -0.2f, 0.2f ); dir[2] = vecSmokeDir[2] + random->RandomFloat( -0.2f, 0.2f ); pParticle->m_vecVelocity = dir * random->RandomFloat( 60.0f, 80.0f ); pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 0; pParticle->m_flRoll = random->RandomFloat( 180, 360 ); pParticle->m_flRollDelta = random->RandomFloat( -1, 1 ); pParticle->m_uchColor[0] = r; pParticle->m_uchColor[1] = g; pParticle->m_uchColor[2] = b; } // If we're really hurt, start burning if ( flDamaged > 0.25 ) { CSmartPtr pFireEmitter = CObjectFireParticles::Create( "DrawDamageEffects 1" ); pFireEmitter->SetSortOrigin( vecFire ); PMaterialHandle hSphereMaterial = pFireEmitter->GetPMaterial( "sprites/floorflame" ); ObjectFireParticle *pParticle = (ObjectFireParticle *) pFireEmitter->AddParticle( sizeof(ObjectFireParticle), hSphereMaterial, vecFire ); if ( pParticle ) { pParticle->m_hParent = this; pParticle->m_iAttachmentPoint = iFireAttachment; pParticle->m_flLifetime = 0.0f; pParticle->m_flDieTime = 1.0; pParticle->m_uchStartSize = MAX( 5, 30 * (flDamaged - 0.25) ); pParticle->m_uchEndSize = pParticle->m_uchStartSize; pParticle->m_vecVelocity = Vector(0,0,1); pParticle->m_uchStartAlpha = 255; pParticle->m_uchEndAlpha = 255; pParticle->m_flRoll = 0; pParticle->m_flRollDelta = 0; } } // Sparks for ( float flSparks = flDamaged - 0.3; flSparks > 0; flSparks -= 0.3 ) { // Get random spark attachment point iSparkAttachment = LookupRandomAttachment( "d_spark" ); if ( !GetAttachment( iSparkAttachment, vecSpark, angSpark ) ) { vecSpark = vecOrigin; angSpark = QAngle(0,0,0); } g_pEffects->Sparks( vecSpark ); } m_flNextEffect = gpGlobals->curtime + random->RandomFloat( 0.2, 0.5 ); } //============================================================================================================ // POWER PROXY //============================================================================================================ class CObjectPowerProxy : public CResultProxy { public: bool Init( IMaterial *pMaterial, KeyValues *pKeyValues ); void OnBind( void *pC_BaseEntity ); private: CFloatInput m_Factor; }; bool CObjectPowerProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues ) { if (!CResultProxy::Init( pMaterial, pKeyValues )) return false; if (!m_Factor.Init( pMaterial, pKeyValues, "scale", 1 )) return false; return true; } void CObjectPowerProxy::OnBind( void *pRenderable ) { // Find the view angle between the player and this entity.... IClientRenderable *pRend = (IClientRenderable *)pRenderable; C_BaseEntity *pEntity = pRend->GetIClientUnknown()->GetBaseEntity(); C_BaseObject *pObject = dynamic_cast(pEntity); if (!pObject) return; int iPowered = pObject->IsPowered(); SetFloatResult( iPowered * m_Factor.GetFloat() ); } EXPOSE_INTERFACE( CObjectPowerProxy, IMaterialProxy, "ObjectPower" IMATERIAL_PROXY_INTERFACE_VERSION ); //----------------------------------------------------------------------------- // Control screen //----------------------------------------------------------------------------- class CBasicControlPanel : public CObjectControlPanel { DECLARE_CLASS( CBasicControlPanel, CObjectControlPanel ); public: CBasicControlPanel( vgui::Panel *parent, const char *panelName ); }; DECLARE_VGUI_SCREEN_FACTORY( CBasicControlPanel, "basic_control_panel" ); //----------------------------------------------------------------------------- // Constructor: //----------------------------------------------------------------------------- CBasicControlPanel::CBasicControlPanel( vgui::Panel *parent, const char *panelName ) : BaseClass( parent, "CBasicControlPanel" ) { }