//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Clients CBaseObject // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "c_baseobject.h" #include "c_tf_player.h" #include "hud.h" #include "c_tf_team.h" #include "engine/IEngineSound.h" #include "particles_simple.h" #include "functionproxy.h" #include "IEffects.h" #include "model_types.h" #include "particlemgr.h" #include "particle_collision.h" #include "c_tf_weapon_builder.h" #include "ivrenderview.h" #include "ObjectControlPanel.h" #include "engine/ivmodelinfo.h" #include "c_te_effect_dispatch.h" #include "toolframework_client.h" #include "tf_hud_building_status.h" #include "cl_animevent.h" #include "eventlist.h" #include "c_obj_sapper.h" #include "tf_gamerules.h" #include "tf_hud_spectator_extras.h" #include "tf_proxyentity.h" // NVNT for building forces #include "haptics/haptic_utils.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // forward declarations void ToolFramework_RecordMaterialParams( IMaterial *pMaterial ); #define MAX_VISIBLE_BUILDPOINT_DISTANCE (400 * 400) // Remove aliasing of name due to shared code #undef CBaseObject IMPLEMENT_AUTO_LIST( IBaseObjectAutoList ); IMPLEMENT_CLIENTCLASS_DT(C_BaseObject, DT_BaseObject, CBaseObject) RecvPropInt(RECVINFO(m_iHealth)), RecvPropInt(RECVINFO(m_iMaxHealth)), RecvPropInt(RECVINFO(m_bHasSapper)), RecvPropInt(RECVINFO(m_iObjectType)), RecvPropBool(RECVINFO(m_bBuilding)), RecvPropBool(RECVINFO(m_bPlacing)), RecvPropBool(RECVINFO(m_bCarried)), RecvPropBool(RECVINFO(m_bCarryDeploy)), RecvPropBool(RECVINFO(m_bMiniBuilding)), RecvPropFloat(RECVINFO(m_flPercentageConstructed)), RecvPropInt(RECVINFO(m_fObjectFlags)), RecvPropEHandle(RECVINFO(m_hBuiltOnEntity)), RecvPropInt( RECVINFO( m_bDisabled ) ), RecvPropEHandle( RECVINFO( m_hBuilder ) ), RecvPropVector( RECVINFO( m_vecBuildMaxs ) ), RecvPropVector( RECVINFO( m_vecBuildMins ) ), RecvPropInt( RECVINFO( m_iDesiredBuildRotations ) ), RecvPropInt( RECVINFO( m_bServerOverridePlacement ) ), RecvPropInt( RECVINFO(m_iUpgradeLevel) ), RecvPropInt( RECVINFO(m_iUpgradeMetal) ), RecvPropInt( RECVINFO(m_iUpgradeMetalRequired) ), RecvPropInt( RECVINFO(m_iHighestUpgradeLevel) ), RecvPropInt( RECVINFO(m_iObjectMode) ), RecvPropBool( RECVINFO( m_bDisposableBuilding ) ), RecvPropBool( RECVINFO( m_bWasMapPlaced ) ), RecvPropBool( RECVINFO( m_bPlasmaDisable ) ), END_RECV_TABLE() ConVar cl_obj_test_building_damage( "cl_obj_test_building_damage", "-1", FCVAR_CHEAT, "debug building damage", true, -1, true, BUILDING_DAMAGE_LEVEL_CRITICAL ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_BaseObject::C_BaseObject( ) { m_YawPreviewState = YAW_PREVIEW_OFF; m_bBuilding = false; m_bPlacing = false; m_flPercentageConstructed = 0; m_fObjectFlags = 0; m_iOldUpgradeLevel = 0; m_flCurrentBuildRotation = 0; m_damageLevel = BUILDING_DAMAGE_LEVEL_NONE; m_iLastPlacementPosValid = -1; m_iObjectMode = 0; m_bCarryDeploy = false; m_bOldCarryDeploy = false; m_bMiniBuilding = false; m_bDisposableBuilding = false; m_vecBuildForward = vec3_origin; m_flBuildDistance = 0.0f; m_flInvisibilityPercent = 0.f; m_bWasMapPlaced = false; m_hDamageEffects = NULL; m_bPlasmaDisable = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- C_BaseObject::~C_BaseObject( void ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::Spawn( void ) { BaseClass::Spawn(); m_bServerOverridePlacement = true; // assume valid at the start } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::UpdateOnRemove( void ) { StopAnimGeneratedSounds(); DestroyBoneAttachments(); CTFHudSpectatorExtras *pSpectatorExtras = GET_HUDELEMENT( CTFHudSpectatorExtras ); if ( pSpectatorExtras ) { pSpectatorExtras->RemoveEntity( entindex() ); } BaseClass::UpdateOnRemove(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::PreDataUpdate( DataUpdateType_t updateType ) { BaseClass::PreDataUpdate( updateType ); m_iOldHealth = m_iHealth; m_hOldOwner = GetOwner(); m_bWasActive = ShouldBeActive(); m_bWasBuilding = m_bBuilding; m_bOldDisabled = m_bDisabled; m_bWasPlacing = m_bPlacing; m_nObjectOldSequence = GetSequence(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::OnDataChanged( DataUpdateType_t updateType ) { if (updateType == DATA_UPDATE_CREATED) { CreateBuildPoints(); // NVNT if the local player created this send a created effect if(IsOwnedByLocalPlayer() &&haptics) { haptics->ProcessHapticEvent(3, "Game", "Build", GetClassname()); } } BaseClass::OnDataChanged( updateType ); // did we just pick up the object? if ( !m_bWasPlacing && m_bPlacing ) { m_iLastPlacementPosValid = -1; } // Did we just finish building? if ( m_bWasBuilding && !m_bBuilding ) { FinishedBuilding(); } else if ( !m_bWasBuilding && m_bBuilding ) { ResetClientsideFrame(); } // Did we just go active? bool bShouldBeActive = ShouldBeActive(); if ( !m_bWasActive && bShouldBeActive ) { OnGoActive(); } else if ( m_bWasActive && !bShouldBeActive ) { OnGoInactive(); } if ( m_bDisabled != m_bOldDisabled ) { if ( m_bDisabled ) { OnStartDisabled(); } else { OnEndDisabled(); } } if ( !IsBuilding() && m_iHealth != m_iOldHealth ) { // recalc our damage particle state BuildingDamageLevel_t damageLevel = CalculateDamageLevel(); if ( damageLevel != m_damageLevel ) { UpdateDamageEffects( damageLevel ); m_damageLevel = damageLevel; } } if ( m_bCarryDeploy != m_bOldCarryDeploy ) { m_bOldCarryDeploy = m_bCarryDeploy; if ( !m_bCarryDeploy ) { // Update our damage effects when we're done redeploying. UpdateDamageEffects( CalculateDamageLevel() ); } } if ( m_iHealth > m_iOldHealth && m_iHealth == m_iMaxHealth ) { // If we were just fully healed, remove all decals RemoveAllDecals(); } if ( GetOwner() == C_TFPlayer::GetLocalTFPlayer() ) { IGameEvent *event = gameeventmanager->CreateEvent( "building_info_changed" ); if ( event ) { event->SetInt( "building_type", GetType() ); event->SetInt( "object_mode", GetObjectMode() ); gameeventmanager->FireEventClientSide( event ); } } if ( IsPlacing() && GetSequence() != m_nObjectOldSequence ) { // Ignore server sequences while placing OnPlacementStateChanged( m_iLastPlacementPosValid > 0 ); } if ( m_iOldUpgradeLevel != m_iUpgradeLevel ) { UpgradeLevelChanged(); m_iOldUpgradeLevel = m_iUpgradeLevel; } // NVNT building status if(IsOwnedByLocalPlayer()) { if(m_bWasBuilding!=m_bBuilding) { if(m_bBuilding && haptics) { haptics->ProcessHapticEvent(3, "Game", "Building", GetClassname()); } } } } //----------------------------------------------------------------------------- // 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; } } const char* C_BaseObject::GetStatusName() const { return GetObjectInfo( GetType() )->m_AltModes[GetObjectMode()].pszStatusName; } //----------------------------------------------------------------------------- // Purpose: placement state has changed, update the model //----------------------------------------------------------------------------- void C_BaseObject::OnPlacementStateChanged( bool bValidPlacement ) { if ( bValidPlacement ) { // NVNT if the local player placed this send a created effect if(IsOwnedByLocalPlayer()&&haptics) { haptics->ProcessHapticEvent(3, "Game", "Placed", GetClassname()); } SetActivity( ACT_OBJ_PLACING ); } else { SetActivity( ACT_OBJ_IDLE ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::Simulate( void ) { if ( IsPlacing() && !MustBeBuiltOnAttachmentPoint() ) { int iValidPlacement = ( IsPlacementPosValid() && ServerValidPlacement() ) ? 1 : 0; if ( m_iLastPlacementPosValid != iValidPlacement ) { m_iLastPlacementPosValid = iValidPlacement; OnPlacementStateChanged( m_iLastPlacementPosValid > 0 ); } // We figure out our own placement pos, but we still leave it to the server to // do collision with other entities and nobuild triggers, so that sets the // placement animation SetLocalOrigin( m_vecBuildOrigin ); InvalidateBoneCache(); // Clear out our origin and rotation interpolation history // so we don't pop when we teleport in the actual position from the server CInterpolatedVar< Vector > &interpolator = GetOriginInterpolator(); interpolator.ClearHistory(); CInterpolatedVar &rotInterpolator = GetRotationInterpolator(); rotInterpolator.ClearHistory(); } else if ( !IsPlacing() && !IsCarried() && m_iLastPlacementPosValid == 0 ) { // HACK HACK: This sentry has been placed, but was placed on the server before the client updated // from the carry position to see that was a valid placement. // It missed its chance to set the correct activity, so we're doing it now. SetActivity( ACT_OBJ_RUNNING ); // Check if the activity was valid because it might have still been using the older placement model if ( GetActivity() != ACT_INVALID ) { // Remember to retest our placement, but don't keep forcing the running activity m_iLastPlacementPosValid = -1; } } BaseClass::Simulate(); } //----------------------------------------------------------------------------- // Purpose: Return false if the server is telling us we can't place right now // could be due to placing in a nobuild or respawn room //----------------------------------------------------------------------------- bool C_BaseObject::ServerValidPlacement( void ) { return m_bServerOverridePlacement; } bool C_BaseObject::WasLastPlacementPosValid( void ) { if ( MustBeBuiltOnAttachmentPoint() ) { return ( !IsEffectActive(EF_NODRAW) ); } return ( m_iLastPlacementPosValid > 0 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int C_BaseObject::DrawModel( int flags ) { int drawn; // 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); } HighlightBuildPoints( flags ); return drawn; } float C_BaseObject::GetReversesBuildingConstructionSpeed( void ) { if ( HasSapper() ) { C_ObjectSapper *pSapper = dynamic_cast< C_ObjectSapper* >( FirstMoveChild() ); if ( pSapper ) { return pSapper->GetReversesBuildingConstructionSpeed(); } } return 0.0f; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::HighlightBuildPoints( int flags ) { C_TFPlayer *pLocal = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocal ) return; if ( !GetNumBuildPoints() || !InLocalTeam() ) return; C_TFWeaponBuilder *pBuilderWpn = dynamic_cast< C_TFWeaponBuilder * >( 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(); bool bSameTeam = ( pPlacementObj->GetTeamNumber() == GetTeamNumber() ); if ( pPlacementObj->IsHostileUpgrade() && bSameTeam ) { // Don't hilight hostile upgrades on friendly objects return; } else if ( !bSameTeam ) { // Don't hilight upgrades on enemy objects return; } // 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 ); // 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 ); } } } //----------------------------------------------------------------------------- // 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: //----------------------------------------------------------------------------- BuildingDamageLevel_t C_BaseObject::CalculateDamageLevel( void ) { float flPercentHealth = (float)m_iHealth / (float)m_iMaxHealth; BuildingDamageLevel_t damageLevel = BUILDING_DAMAGE_LEVEL_NONE; if ( flPercentHealth < 0.25 ) { damageLevel = BUILDING_DAMAGE_LEVEL_CRITICAL; } else if ( flPercentHealth < 0.45 ) { damageLevel = BUILDING_DAMAGE_LEVEL_HEAVY; } else if ( flPercentHealth < 0.65 ) { damageLevel = BUILDING_DAMAGE_LEVEL_MEDIUM; } else if ( flPercentHealth < 0.85 ) { damageLevel = BUILDING_DAMAGE_LEVEL_LIGHT; } if ( cl_obj_test_building_damage.GetInt() >= 0 ) { damageLevel = (BuildingDamageLevel_t)cl_obj_test_building_damage.GetInt(); } return damageLevel; } /* //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::Release( void ) { // Remove any reticles on this entity C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pPlayer ) { pPlayer->Remove_Target( this ); } BaseClass::Release(); } */ //----------------------------------------------------------------------------- // Ownership: //----------------------------------------------------------------------------- C_TFPlayer *C_BaseObject::GetOwner() { return m_hBuilder; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BaseObject::IsOwnedByLocalPlayer() const { if ( !m_hBuilder ) return false; return ( m_hBuilder == C_TFPlayer::GetLocalTFPlayer() ); } //----------------------------------------------------------------------------- // 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_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); pPlayer->SetSelectedObject( this ); } void C_BaseObject::ResetClientsideFrame( void ) { SetCycle( GetReversesBuildingConstructionSpeed() != 0.0f ? 1.0f : 0.0f ); } //----------------------------------------------------------------------------- // 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) //----------------------------------------------------------------------------- const 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_TFPlayer::GetLocalTFPlayer() ) && 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 ( 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 ); } } //----------------------------------------------------------------------------- // Purpose: Player has waved his crosshair over this entity. Display appropriate hints. //----------------------------------------------------------------------------- void C_BaseObject::DisplayHintTo( C_BasePlayer *pPlayer ) { bool bHintPlayed = false; C_TFPlayer *pTFPlayer = ToTFPlayer(pPlayer); if ( InSameTeam( pPlayer ) ) { // We're looking at a friendly object. if ( HasSapper() ) { bHintPlayed = pPlayer->HintMessage( HINT_OBJECT_HAS_SAPPER, true, true ); } if ( pTFPlayer->IsPlayerClass( TF_CLASS_ENGINEER ) ) { // I'm an engineer. // If I'm looking at a constructing object, let me know I can help build it (but not // if I built it myself, since I've already got that hint from the wrench). if ( !bHintPlayed && IsBuilding() && GetBuilder() != pTFPlayer ) { bHintPlayed = pPlayer->HintMessage( HINT_ENGINEER_USE_WRENCH_ONOTHER, false, true ); } // If it's damaged, I can repair it if ( !bHintPlayed && !IsBuilding() && GetHealth() < GetMaxHealth() ) { bHintPlayed = pPlayer->HintMessage( HINT_ENGINEER_REPAIR_OBJECT, false, true ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::GetGlowEffectColor( float *r, float *g, float *b ) { if ( TFGameRules() ) { TFGameRules()->GetTeamGlowColor( GetTeamNumber(), *r, *g, *b ); } else { *r = 0.76f; *g = 0.76f; *b = 0.76f; } } //----------------------------------------------------------------------------- // Purpose: Does this object have a sapper on it //----------------------------------------------------------------------------- bool C_BaseObject::HasSapper( void ) { return m_bHasSapper; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool C_BaseObject::IsPlasmaDisabled( void ) { return m_bPlasmaDisable; } void C_BaseObject::OnStartDisabled() { } void C_BaseObject::OnEndDisabled() { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::GetTargetIDString( OUT_Z_BYTECAP( iMaxLenInBytes ) wchar_t *sIDString, int iMaxLenInBytes, bool bSpectator ) { Assert( iMaxLenInBytes >= sizeof(sIDString[0]) ); sIDString[0] = '\0'; C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return; if ( pLocalPlayer->InSameDisguisedTeam( this ) || pLocalPlayer->IsPlayerClass( TF_CLASS_SPY ) || bSpectator ) { wchar_t wszBuilderName[ MAX_PLAYER_NAME_LENGTH ]; const char *pszStatusName = GetStatusName(); const wchar_t *wszObjectName = g_pVGuiLocalize->Find( pszStatusName ); bool bHasMode = false; const char *printFormatString = "#TF_playerid_object"; if ( IsMiniBuilding() && !IsDisposableBuilding() ) { printFormatString = "#TF_playerid_object_mini"; } const wchar_t *wszModeName = L""; const CObjectInfo* pObjectInfo = GetObjectInfo( GetType() ); if ( pObjectInfo && (pObjectInfo->m_iNumAltModes > 0) ) { const char *pszModeName = pObjectInfo->m_AltModes[GetObjectMode()].pszModeName; wszModeName = g_pVGuiLocalize->Find( pszModeName ); printFormatString = "TF_playerid_object_mode"; bHasMode = true; } if ( !wszObjectName ) { wszObjectName = L""; } C_BasePlayer *pBuilder = GetOwner(); if ( pBuilder ) { g_pVGuiLocalize->ConvertANSIToUnicode( pBuilder->GetPlayerName(), wszBuilderName, sizeof(wszBuilderName) ); } else { wszBuilderName[0] = '\0'; } // building or live, show health wchar_t * localizedString = g_pVGuiLocalize->Find( printFormatString ); if ( localizedString ) { if ( bHasMode ) { g_pVGuiLocalize->ConstructString( sIDString, iMaxLenInBytes, localizedString, 3, wszObjectName, wszBuilderName, wszModeName ); } else { g_pVGuiLocalize->ConstructString( sIDString, iMaxLenInBytes, localizedString, 2, wszObjectName, wszBuilderName ); } } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::GetTargetIDDataString( OUT_Z_BYTECAP(iMaxLenInBytes) wchar_t *sDataString, int iMaxLenInBytes ) { Assert( iMaxLenInBytes >= sizeof(sDataString[0]) ); sDataString[0] = '\0'; C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return; // Sentryguns have models for each level, so we don't show it in their target ID. bool bShowLevel = ( GetType() != OBJ_SENTRYGUN ); wchar_t wszLevel[32]; if ( bShowLevel ) { _snwprintf( wszLevel, ARRAYSIZE(wszLevel) - 1, L"%d", m_iUpgradeLevel ); wszLevel[ ARRAYSIZE(wszLevel)-1 ] = '\0'; } if ( m_iUpgradeLevel >= 3 ) { if ( bShowLevel ) { g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find("#TF_playerid_object_level"), 1, wszLevel ); } return; } wchar_t wszBuilderName[ MAX_PLAYER_NAME_LENGTH ]; wchar_t wszObjectName[ 32 ]; wchar_t wszUpgradeProgress[ 32 ]; g_pVGuiLocalize->ConvertANSIToUnicode( GetStatusName(), wszObjectName, sizeof(wszObjectName) ); C_BasePlayer *pBuilder = GetOwner(); if ( pBuilder ) { g_pVGuiLocalize->ConvertANSIToUnicode( pBuilder->GetPlayerName(), wszBuilderName, sizeof(wszBuilderName) ); } else { wszBuilderName[0] = '\0'; } // level 1 and 2 show upgrade progress if ( !IsMiniBuilding() && !IsDisposableBuilding() ) { _snwprintf( wszUpgradeProgress, ARRAYSIZE(wszUpgradeProgress) - 1, L"%d / %d", m_iUpgradeMetal, GetUpgradeMetalRequired() ); wszUpgradeProgress[ ARRAYSIZE(wszUpgradeProgress)-1 ] = '\0'; if ( bShowLevel ) { g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find("#TF_playerid_object_upgrading_level"), 2, wszLevel, wszUpgradeProgress ); } else { g_pVGuiLocalize->ConstructString( sDataString, iMaxLenInBytes, g_pVGuiLocalize->Find("#TF_playerid_object_upgrading"), 1, wszUpgradeProgress ); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int C_BaseObject::GetDisplayPriority( void ) { return GetObjectInfo( GetType() )->m_iDisplayPriority; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- const char *C_BaseObject::GetHudStatusIcon( void ) { return GetObjectInfo( GetType() )->m_pHudStatusIcon; } ConVar cl_obj_fake_alert( "cl_obj_fake_alert", "0", 0, "", true, BUILDING_HUD_ALERT_NONE, true, MAX_BUILDING_HUD_ALERT_LEVEL-1 ); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- BuildingHudAlert_t C_BaseObject::GetBuildingAlertLevel( void ) { float flHealthPercent = GetHealth() / GetMaxHealth(); BuildingHudAlert_t alertLevel = BUILDING_HUD_ALERT_NONE; if ( HasSapper() ) { alertLevel = BUILDING_HUD_ALERT_SAPPER; } else if ( !IsBuilding() && flHealthPercent < 0.33 ) { alertLevel = BUILDING_HUD_ALERT_VERY_LOW_HEALTH; } else if ( !IsBuilding() && flHealthPercent < 0.66 ) { alertLevel = BUILDING_HUD_ALERT_LOW_HEALTH; } BuildingHudAlert_t iFakeAlert = (BuildingHudAlert_t)cl_obj_fake_alert.GetInt(); if ( iFakeAlert > BUILDING_HUD_ALERT_NONE && iFakeAlert < MAX_BUILDING_HUD_ALERT_LEVEL ) { alertLevel = iFakeAlert; } return alertLevel; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- ShadowType_t C_BaseObject::ShadowCastType( void ) { if ( GetInvisibilityLevel() == 1.f ) return SHADOWS_NONE; return BaseClass::ShadowCastType(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float C_BaseObject::GetInvisibilityLevel( void ) { #ifdef STAGING_ONLY C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); C_TFPlayer *pOwner = GetOwner(); if ( pLocalPlayer && pLocalPlayer->m_Shared.InCond( TF_COND_STEALTHED_PHASE ) && pLocalPlayer != pOwner ) return 1.f; #endif // STAGING_ONLY return m_flInvisibilityPercent; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void C_BaseObject::SetInvisibilityLevel( float flValue ) { m_flPrevInvisibilityPercent = m_flInvisibilityPercent; m_flInvisibilityPercent = clamp( flValue, 0.f, 1.f ); } //----------------------------------------------------------------------------- // Purpose: find the anim events that may have started sounds, and stop them. //----------------------------------------------------------------------------- void C_BaseObject::StopAnimGeneratedSounds( void ) { MDLCACHE_CRITICAL_SECTION(); CStudioHdr *pStudioHdr = GetModelPtr(); if ( !pStudioHdr ) return; mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( GetSequence() ); if ( seqdesc.numevents == 0 ) return; float flCurrentCycle = GetCycle(); mstudioevent_t *pevent = GetEventIndexForSequence( seqdesc ); for (int i = 0; i < (int)seqdesc.numevents; i++) { if ( pevent[i].cycle < flCurrentCycle ) { if ( pevent[i].event == CL_EVENT_SOUND || pevent[i].event == AE_CL_PLAYSOUND ) { StopSound( entindex(), pevent[i].options ); } } } } //============================================================================================================ // 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; SetFloatResult( m_Factor.GetFloat() ); if ( ToolsEnabled() ) { ToolFramework_RecordMaterialParams( GetMaterial() ); } } 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" ) { } //----------------------------------------------------------------------------- // Purpose: Used for spy invisiblity material //----------------------------------------------------------------------------- class CBuildingInvisProxy : public CBaseInvisMaterialProxy { public: virtual void OnBind( C_BaseEntity *pBaseEntity ) OVERRIDE; }; //----------------------------------------------------------------------------- // Purpose: // Input : //----------------------------------------------------------------------------- void CBuildingInvisProxy::OnBind( C_BaseEntity *pBaseEntity ) { if ( !m_pPercentInvisible ) return; if ( !pBaseEntity->IsBaseObject() ) return; C_BaseObject *pObject = static_cast< C_BaseObject* >( pBaseEntity ); if ( !pObject ) return; CTFPlayer *pOwner = ToTFPlayer( pObject->GetOwner() ); if ( !pOwner ) { m_pPercentInvisible->SetFloatValue( 0.0f ); return; } m_pPercentInvisible->SetFloatValue( pObject->GetInvisibilityLevel() ); } EXPOSE_INTERFACE( CBuildingInvisProxy, IMaterialProxy, "building_invis" IMATERIAL_PROXY_INTERFACE_VERSION );