summaryrefslogtreecommitdiff
path: root/game/client/tf2/c_baseobject.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/tf2/c_baseobject.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'game/client/tf2/c_baseobject.cpp')
-rw-r--r--game/client/tf2/c_baseobject.cpp921
1 files changed, 921 insertions, 0 deletions
diff --git a/game/client/tf2/c_baseobject.cpp b/game/client/tf2/c_baseobject.cpp
new file mode 100644
index 0000000..491dba9
--- /dev/null
+++ b/game/client/tf2/c_baseobject.cpp
@@ -0,0 +1,921 @@
+//========= 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<CObjectSmokeParticles> 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<CSimpleEmitter> 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<CObjectFireParticles> 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<C_BaseObject*>(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" )
+{
+}