summaryrefslogtreecommitdiff
path: root/game/client/tf/vgui/tf_playermodelpanel.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/tf/vgui/tf_playermodelpanel.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/client/tf/vgui/tf_playermodelpanel.cpp')
-rw-r--r--game/client/tf/vgui/tf_playermodelpanel.cpp2893
1 files changed, 2893 insertions, 0 deletions
diff --git a/game/client/tf/vgui/tf_playermodelpanel.cpp b/game/client/tf/vgui/tf_playermodelpanel.cpp
new file mode 100644
index 0000000..cb49453
--- /dev/null
+++ b/game/client/tf/vgui/tf_playermodelpanel.cpp
@@ -0,0 +1,2893 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "tf_shareddefs.h"
+#include "tf_playermodelpanel.h"
+#include "tf_classdata.h"
+#include "tf_item_inventory.h"
+#include "vgui/IVGui.h"
+#include "game_item_schema.h"
+#include "econ_item_system.h"
+#include "animation.h"
+#include "choreoscene.h"
+#include "choreoevent.h"
+#include "choreoactor.h"
+#include "choreochannel.h"
+#include "scenefilecache/ISceneFileCache.h"
+#include "c_sceneentity.h"
+#include "c_baseflex.h"
+#include "sentence.h"
+#include "engine/IEngineSound.h"
+#include "c_tf_player.h"
+#include "tier2/renderutils.h"
+#include "bone_setup.h"
+#include "halloween/tf_weapon_spellbook.h"
+#include "matsys_controls/matsyscontrols.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+DECLARE_BUILD_FACTORY( CTFPlayerModelPanel );
+
+char g_szSceneTmpName[256];
+
+static bool IsTauntItem( GameItemDefinition_t *pItemDef, const int iTeam, const int iClass, const char **ppSequence = NULL, const char **ppRequiredItem = NULL, const char **ppScene = NULL )
+{
+ CTFTauntInfo *pTauntData = pItemDef->GetTauntData();
+ if ( pTauntData )
+ {
+ if ( ppScene )
+ {
+ int iTauntIndex = RandomInt( 0, pTauntData->GetIntroSceneCount( iClass ) - 1 );
+ *ppScene = pTauntData->GetIntroScene( iClass, iTauntIndex );
+ }
+
+ if ( ppRequiredItem )
+ {
+ *ppRequiredItem = pTauntData->GetProp( iClass );
+ }
+
+ return true;
+ }
+
+ for ( int i=0; i<pItemDef->GetNumAnimations( iTeam ); ++i )
+ {
+ animation_on_wearable_t* pAnim = pItemDef->GetAnimationData( iTeam, i );
+ if ( pAnim && pAnim->pszActivity && !Q_stricmp( pAnim->pszActivity, "taunt_concept" ) )
+ {
+ // If we have a scene, use it first
+ const char *pszScene = pAnim->pszScene;
+ if ( pszScene && (iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS) )
+ {
+ if ( ppScene )
+ {
+ Q_snprintf( g_szSceneTmpName, sizeof(g_szSceneTmpName), "scenes/player/%s/low/%s", g_aPlayerClassNames_NonLocalized[iClass], pszScene );
+ *ppScene = g_szSceneTmpName;
+ }
+ }
+
+ const char *pszSequence = pAnim->pszSequence;
+ if ( pszSequence )
+ {
+ if ( ppSequence )
+ {
+ *ppSequence = pszSequence;
+ }
+ if ( ppRequiredItem )
+ {
+ *ppRequiredItem = pAnim->pszRequiredItem;
+ }
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerModelPanel::CTFPlayerModelPanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName ),
+ m_LocalToGlobal( 0, 0, FlexSettingLessFunc )
+{
+ m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
+ m_iCurrentSlotIndex = -1;
+
+ m_nBody = 0;
+ m_pHeldItem = NULL;
+ m_iTeam = TF_TEAM_RED;
+ m_bZoomedToHead = false;
+ m_pszVCD = NULL;
+ m_pszWeaponEntityRequired = NULL;
+ m_bLoopVCD = true;
+ m_bVCDFileNameOnly = true;
+
+ InitPhonemeMappings();
+
+ m_pScene = NULL;
+ ClearScene();
+ memset( m_flexWeight, 0, sizeof( m_flexWeight ) );
+
+ SetIgnoreDoubleClick( true );
+
+ for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
+ {
+ m_aParticleSystems[i] = NULL;
+ }
+
+ m_bPlaySparks = false;
+ m_pszEyeGlowParticleName[0] = '\0';
+ m_bDrawActionSlotEffects = false;
+ m_bDrawTauntParticles = false;
+ m_bIsRobot = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CTFPlayerModelPanel::~CTFPlayerModelPanel( void )
+{
+ m_vecItemsLoaded.PurgeAndDeleteElements();
+ m_ItemsToCarry.PurgeAndDeleteElements();
+
+ for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[i] );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ApplySettings( KeyValues *inResourceData )
+{
+ BaseClass::ApplySettings( inResourceData );
+
+ m_angPlayerOrg = m_angPlayer;
+
+ static ConVarRef cl_hud_minmode( "cl_hud_minmode", true );
+ if ( cl_hud_minmode.IsValid() && cl_hud_minmode.GetBool() )
+ {
+ inResourceData->ProcessResolutionKeys( "_minmode" );
+ }
+
+ // custom class data
+ m_customClassData.Purge();
+ KeyValues *pCustomData = inResourceData->FindKey( "customclassdata" );
+ if ( pCustomData )
+ {
+ for ( KeyValues *pData = pCustomData->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
+ {
+ CustomClassData_t data;
+ data.m_flFOV = pData->GetFloat( "fov" );
+ data.m_vPosition.Init( pData->GetFloat( "origin_x" ), pData->GetFloat( "origin_y" ), pData->GetFloat( "origin_z" ) );
+ data.m_vAngles.Init( pData->GetFloat( "angles_x" ), pData->GetFloat( "angles_y" ), pData->GetFloat( "angles_z" ) );
+ m_customClassData.AddToTail( data );
+ }
+
+ Assert( m_customClassData.Count() == TF_LAST_NORMAL_CLASS );
+ }
+
+ // always allow particle for this panel
+ m_bUseParticle = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetToPlayerClass( int iClass, bool bIsRobot, bool bForceRefresh /*= false*/ )
+{
+ if ( m_bIsRobot != bIsRobot )
+ {
+ bForceRefresh = true;
+ }
+ m_bIsRobot = bIsRobot;
+
+ if ( m_iCurrentClassIndex == iClass && !bForceRefresh )
+ return;
+
+ if ( m_bZoomedToHead )
+ {
+ ToggleZoom();
+ }
+
+ m_iCurrentClassIndex = iClass;
+ ClearScene();
+
+ if ( IsValidTFPlayerClass( m_iCurrentClassIndex ) )
+ {
+ if ( bIsRobot )
+ {
+ SetMDL( g_szPlayerRobotModels[ m_iCurrentClassIndex ] );
+ }
+ else
+ {
+ TFPlayerClassData_t *pData = GetPlayerClassData( m_iCurrentClassIndex );
+ SetMDL( pData->GetModelName() );
+ }
+
+ HoldFirstValidItem();
+
+ // set custom class data
+ if ( m_customClassData.IsValidIndex( m_iCurrentClassIndex ) )
+ {
+ SetCameraFOV( m_customClassData[m_iCurrentClassIndex].m_flFOV );
+ m_vecPlayerPos = m_customClassData[m_iCurrentClassIndex].m_vPosition;
+ m_angPlayer = m_customClassData[m_iCurrentClassIndex].m_vAngles;
+ }
+ else
+ {
+ m_angPlayer = m_angPlayerOrg;
+ }
+ }
+ else
+ {
+ SetMDL( MDLHANDLE_INVALID );
+ RemoveAdditionalModels();
+ }
+
+ InitPhonemeMappings();
+
+ SetTeam( TF_TEAM_RED );
+
+ m_nBody = 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::HoldFirstValidItem( void )
+{
+ RemoveAdditionalModels();
+
+ if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
+ return;
+
+ int iDesiredSlot = -1;
+
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ bool bIsTauntItem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
+ if ( !bIsTauntItem )
+ {
+ if ( pItem->GetStaticData()->IsAWearable() )
+ continue;
+ if ( pItem->GetAnimationSlot() == -2 )
+ continue;
+ }
+
+ // Found a weapon. Wield it.
+ iDesiredSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
+ break;
+ }
+
+ if ( iDesiredSlot != -1 )
+ {
+ UpdateHeldItem( iDesiredSlot );
+ return;
+ }
+
+ // If we didn't find a weapon to wield, we wield the class's base primary weapon
+ CEconItemView *pItem = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, LOADOUT_POSITION_PRIMARY );
+ if ( !pItem || !pItem->IsValid() )
+ {
+ // Some classes only have secondary weapons. Fall back to that.
+ pItem = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, LOADOUT_POSITION_SECONDARY );
+ }
+
+ if ( pItem && pItem->IsValid() )
+ {
+ SwitchHeldItemTo( pItem );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::HoldItemInSlot( int iSlot )
+{
+ if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
+ return false;
+
+ return UpdateHeldItem( iSlot );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::HoldItem( int iItemNumber )
+{
+ if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
+ return false;
+
+ if ( iItemNumber >= m_ItemsToCarry.Count() )
+ return false;
+
+ CEconItemView *pItem = m_ItemsToCarry[iItemNumber];
+
+ bool bIsTauntitem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
+
+ // Ignore requests to equip wearables, because they're always equipped
+ // Also ignore requests to equip non-wearables that are never actively equipped
+ if ( bIsTauntitem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) )
+ {
+ SwitchHeldItemTo( pItem );
+ return true;
+ }
+
+ // If we were trying to switch to a new item, and it's not valid, stick to our current
+ if ( pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ) != m_iCurrentSlotIndex )
+ {
+ UpdateHeldItem( m_iCurrentSlotIndex );
+ return false;
+ }
+
+ // We were trying to stay on the current weapon, and it's not valid. Find anything.
+ HoldFirstValidItem();
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::UpdateHeldItem( int iDesiredSlot )
+{
+ m_pHeldItem = NULL;
+
+ CEconItemView *pItem = GetItemInSlot( iDesiredSlot );
+ if ( pItem )
+ {
+ bool bIsTauntItem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
+ // Ignore requests to equip wearables, because they're always equipped
+ // Also ignore requests to equip non-wearables that are never actively equipped
+ if ( bIsTauntItem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) )
+ {
+ SwitchHeldItemTo( pItem );
+ m_pHeldItem = pItem;
+ return true;
+ }
+ }
+
+ // If we were trying to switch to a new item, and it's not valid, stick to our current
+ if ( iDesiredSlot != m_iCurrentSlotIndex )
+ {
+ UpdateHeldItem( m_iCurrentSlotIndex );
+ return false;
+ }
+
+ // We were trying to stay on the current weapon, and it's not valid. Find anything.
+ HoldFirstValidItem();
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ClearScene( void )
+{
+ if ( m_pScene )
+ {
+ delete m_pScene;
+ }
+
+ m_pScene = NULL;
+ m_flSceneTime = 0;
+ m_flSceneEndTime = 0;
+ m_flLastTickTime = 0;
+ m_bLoopScene = true;
+ //memset( m_flexWeight, 0, sizeof( m_flexWeight ) );
+}
+
+extern CChoreoStringPool g_ChoreoStringPool;
+CChoreoScene *LoadSceneForModel( const char *filename, IChoreoEventCallback *pCallback, float *flSceneEndTime )
+{
+ char loadfile[ 512 ];
+ V_strcpy_safe( loadfile, filename );
+ V_SetExtension( loadfile, ".vcd", sizeof( loadfile ) );
+ V_FixSlashes( loadfile );
+
+ char *pBuffer = NULL;
+ size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile );
+ if ( bufsize <= 0 )
+ return NULL;
+
+ pBuffer = new char[ bufsize ];
+ if ( !scenefilecache->GetSceneData( filename, (byte *)pBuffer, bufsize ) )
+ {
+ delete[] pBuffer;
+ return NULL;
+ }
+
+ CChoreoScene *pScene;
+ if ( IsBufferBinaryVCD( pBuffer, bufsize ) )
+ {
+ pScene = new CChoreoScene( pCallback );
+ CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY );
+ if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) )
+ {
+ Warning( "Unable to restore binary scene '%s'\n", loadfile );
+ delete pScene;
+ pScene = NULL;
+ }
+ else
+ {
+ pScene->SetPrintFunc( Scene_Printf );
+ pScene->SetEventCallbackInterface( pCallback );
+ }
+ }
+ else
+ {
+ g_TokenProcessor.SetBuffer( pBuffer );
+ pScene = ChoreoLoadScene( loadfile, pCallback, &g_TokenProcessor, Scene_Printf );
+ }
+
+ delete[] pBuffer;
+
+ if ( flSceneEndTime != NULL )
+ {
+ // find the scene length
+ // The scene is as long as the end point for the last event unless one of the events is a loop
+ *flSceneEndTime = 0.0f;
+ bool bSetEndTime = false;
+ for ( int i = 0; i < pScene->GetNumEvents(); i++ )
+ {
+ CChoreoEvent *pEvent = pScene->GetEvent( i );
+ if ( pEvent->GetType() == CChoreoEvent::LOOP )
+ {
+ *flSceneEndTime = -1.0f;
+ bSetEndTime = false;
+ break;
+ }
+
+ if ( pEvent->GetEndTime() > *flSceneEndTime )
+ {
+ *flSceneEndTime = pEvent->GetEndTime();
+ bSetEndTime = true;
+ }
+ }
+
+ if ( bSetEndTime )
+ {
+ *flSceneEndTime += 0.1f; // give time for lerp to idle pose
+ }
+ }
+
+ return pScene;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::PlayVCD( const char *pszVCD, const char *pszWeaponEntityRequired /*= NULL*/, bool bLoopVCD /*= true*/, bool bFileNameOnly /*= true*/ )
+{
+ m_pszVCD = pszVCD;
+ m_pszWeaponEntityRequired = pszWeaponEntityRequired;
+ m_bLoopVCD = bLoopVCD;
+ m_bVCDFileNameOnly = bFileNameOnly;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::FireEvent( const char *pszEventName, const char *pszEventOptions )
+{
+ //Plat_DebugString( CFmtStr( "********* ANIM EVENT: %s\n", pszEventName ) );
+
+ if ( V_strcmp( pszEventName, "AE_WPN_HIDE" ) == 0 )
+ {
+ int nWeaponIndex = GetMergeMDLIndex( static_cast<IClientRenderable*>(m_pHeldItem) );
+ if ( nWeaponIndex >= 0 )
+ {
+ m_aMergeMDLs[nWeaponIndex].m_bDisabled = true;
+ }
+ }
+ else if ( V_strcmp( pszEventName, "AE_WPN_UNHIDE" ) == 0 )
+ {
+ int nWeaponIndex = GetMergeMDLIndex( static_cast<IClientRenderable*>(m_pHeldItem) );
+ if ( nWeaponIndex >= 0 )
+ {
+ m_aMergeMDLs[nWeaponIndex].m_bDisabled = false;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SwitchHeldItemTo( CEconItemView *pItem )
+{
+ m_nBody = 0;
+
+ ClearScene();
+
+ // Clear out visible items, and re-equip out wearables
+ RemoveAdditionalModels();
+ EquipAllWearables( pItem );
+
+ // Then equip the held item
+ EquipItem( pItem );
+ m_iCurrentSlotIndex = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
+ m_pHeldItem = pItem;
+
+ m_StatTrackModel.m_bDisabled = true;
+ m_StatTrackModel.m_MDL.SetMDL( MDLHANDLE_INVALID );
+ CAttribute_String attrModule;
+ static CSchemaAttributeDefHandle pAttr_module( "weapon_uses_stattrak_module" );
+ if ( m_pHeldItem->FindAttribute( pAttr_module, &attrModule ) && attrModule.has_value() )
+ {
+ // Allow for already strange items
+ bool bIsStrange = false;
+ if ( m_pHeldItem->GetQuality() == AE_STRANGE )
+ {
+ bIsStrange = true;
+ }
+
+ if ( !bIsStrange )
+ {
+ // Go over the attributes of the item, if it has any strange attributes the item is strange and don't apply
+ for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
+ {
+ if ( m_pHeldItem->FindAttribute( GetKillEaterAttr_Score( i ) ) )
+ {
+ bIsStrange = true;
+ break;
+ }
+ }
+ }
+
+ if ( bIsStrange )
+ {
+ static CSchemaAttributeDefHandle pAttr_moduleScale( "weapon_stattrak_module_scale" );
+ // Does it have a stat track module
+ m_flStatTrackScale = 1.0f;
+ uint32 unFloatAsUint32 = 1;
+ if ( m_pHeldItem->FindAttribute( pAttr_moduleScale, &unFloatAsUint32 ) )
+ {
+ m_flStatTrackScale = (float&)unFloatAsUint32;
+ }
+
+ MDLHandle_t hStatTrackMDL = mdlcache->FindMDL( "models/weapons/c_models/stattrack.mdl" );
+ if ( mdlcache->IsErrorModel( hStatTrackMDL ) )
+ {
+ hStatTrackMDL = MDLHANDLE_INVALID;
+ }
+ m_StatTrackModel.m_MDL.SetMDL( hStatTrackMDL );
+ mdlcache->Release( hStatTrackMDL ); // counterbalance addref from within FindMDL
+
+ m_StatTrackModel.m_MDL.m_pProxyData = static_cast<IClientRenderable*>(pItem);
+ m_StatTrackModel.m_bDisabled = false;
+ m_StatTrackModel.m_MDL.m_nSequence = ACT_IDLE;
+ SetIdentityMatrix( m_StatTrackModel.m_MDLToWorld );
+ }
+ }
+
+ SetSequenceLayers( NULL, 0 );
+
+ // See if our VCD is overridden
+ if ( m_pszVCD )
+ {
+ // Make sure we're holding the weapon, if it's required
+ bool bCanRunScene = true;
+ if ( m_pszWeaponEntityRequired && *m_pszWeaponEntityRequired )
+ {
+ bCanRunScene = false;
+
+ if ( pItem && pItem->IsValid() )
+ {
+ const char *pszClassName = pItem->GetStaticData()->GetItemClass();
+ if ( pszClassName && *pszClassName )
+ {
+ bCanRunScene = V_stricmp( pszClassName, m_pszWeaponEntityRequired ) == 0;
+ }
+ }
+ }
+
+ if ( bCanRunScene )
+ {
+ if ( m_bVCDFileNameOnly )
+ {
+ // auto complete relative path for the vcd file
+ V_sprintf_safe( g_szSceneTmpName, "scenes/player/%s/low/%s", g_aPlayerClassNames_NonLocalized[m_iCurrentClassIndex], m_pszVCD );
+ }
+ else
+ {
+ // m_pszVCD should be a valid relative path
+ V_strcpy_safe( g_szSceneTmpName, m_pszVCD );
+ }
+
+ m_pScene = LoadSceneForModel( g_szSceneTmpName, this, &m_flSceneEndTime );
+ m_bLoopScene = m_bLoopVCD;
+
+ return;
+ }
+ }
+
+ const char *pScene = NULL;
+ const char *pSequence = NULL;
+ const char *pRequiredItem = NULL;
+ bool bRemoveTauntParticles = true;
+
+ if ( IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex, &pSequence, &pRequiredItem, &pScene ) )
+ {
+ MDLCACHE_CRITICAL_SECTION();
+
+ if ( pScene )
+ {
+ m_pScene = LoadSceneForModel( pScene, this, &m_flSceneEndTime );
+
+ // load custom prop for taunt
+ const char *pszProp = pItem->GetStaticData()->GetTauntData()->GetProp( m_iCurrentClassIndex );
+ if ( pszProp )
+ {
+ LoadAndAttachAdditionalModel( pszProp, pItem );
+ }
+
+ // force taunt to equip certain slot
+ static CSchemaAttributeDefHandle pAttrDef_TauntForceWeaponSlot( "taunt force weapon slot" );
+ const char* pszTauntForceWeaponSlotName = NULL;
+ if ( FindAttribute_UnsafeBitwiseCast<CAttribute_String>( pItem, pAttrDef_TauntForceWeaponSlot, &pszTauntForceWeaponSlotName ) )
+ {
+ int iForceWeaponSlot = StringFieldToInt( pszTauntForceWeaponSlotName, GetItemSchema()->GetWeaponTypeSubstrings() );
+ EquipRequiredLoadoutSlot( iForceWeaponSlot );
+ }
+ }
+ else
+ {
+ ClearScene();
+
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ int iSequence = LookupSequence( &studioHdr, pSequence );
+ if ( iSequence >= 0 )
+ {
+ // does a weapon need to be equipped?
+ loadout_positions_t requiredLoadoutItem = LOADOUT_POSITION_INVALID;
+ if ( pRequiredItem )
+ {
+ requiredLoadoutItem = (loadout_positions_t)StringFieldToInt( pRequiredItem, ItemSystem()->GetItemSchema()->GetLoadoutStrings( pItem->GetItemDefinition()->GetEquipType() ) );
+ }
+
+ EquipRequiredLoadoutSlot( requiredLoadoutItem );
+
+ // finally, set the sequence layers
+ MDLSquenceLayer_t tmpSequenceLayers[1];
+ tmpSequenceLayers[0].m_nSequenceIndex = iSequence;
+ tmpSequenceLayers[0].m_flWeight = 1.0;
+ tmpSequenceLayers[0].m_bNoLoop = false;
+ tmpSequenceLayers[0].m_flCycleBeganAt = m_RootMDL.m_MDL.m_flTime;
+ SetSequenceLayers( tmpSequenceLayers, 1 );
+ }
+ }
+
+ // Taunt Particles
+ static CSchemaAttributeDefHandle pAttrDef_OnTauntAttachParticleIndex( "on taunt attach particle index" );
+ uint32 unUnusualEffectIndex = 0;
+ if ( pItem->FindAttribute( pAttrDef_OnTauntAttachParticleIndex, &unUnusualEffectIndex ) && unUnusualEffectIndex > 0 )
+ {
+ const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( unUnusualEffectIndex );
+ if ( pParticleSystem )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[ SYSTEM_TAUNT ] );
+ m_aParticleSystems[ SYSTEM_TAUNT ] = CreateParticleData( pParticleSystem->pszSystemName );
+ m_flTauntParticleRefireTime = gpGlobals->curtime + pParticleSystem->fRefireTime;
+ m_flTauntParticleRefireRate = pParticleSystem->fRefireTime;
+ m_bDrawTauntParticles = true;
+ bRemoveTauntParticles = false;
+ }
+ }
+ }
+
+ // Clear out taunt particles
+ if ( bRemoveTauntParticles && m_aParticleSystems[SYSTEM_TAUNT] )
+ {
+ m_bDrawTauntParticles = false;
+ SafeDeleteParticleData( &m_aParticleSystems[SYSTEM_TAUNT] );
+ }
+
+ // Check if it has a PoseParameter Attributes (r_hand_grip)
+ float flPose = 0;
+ static CSchemaAttributeDefHandle pAttrDef_RightHandPose( "righthand pose parameter" );
+ uint32 iValue = 0;
+ if ( pItem->FindAttribute( pAttrDef_RightHandPose, &iValue ) )
+ {
+ flPose = (float&)iValue;
+ }
+ SetPoseParameterByName( "r_hand_grip", flPose );
+
+ // Check for hand particles (spell book)
+ // always nuke
+ if ( m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[ SYSTEM_ACTIONSLOT ] );
+ }
+ m_bDrawActionSlotEffects = false;
+ if ( pItem->GetStaticData()->GetItemClass() )
+ {
+ m_bDrawActionSlotEffects = FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_weapon_spellbook" );
+ }
+
+ // update eyeglows
+ m_bUpdateEyeGlows = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EquipRequiredLoadoutSlot( int iRequiredLoadoutSlot )
+{
+ if ( iRequiredLoadoutSlot != LOADOUT_POSITION_INVALID )
+ {
+ int iDesiredSlot = -1;
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ if ( pItem->GetStaticData()->IsAWearable() )
+ continue;
+ if ( pItem->GetAnimationSlot() == -2 )
+ continue;
+
+ // Found a weapon. Wield it.
+ if ( iRequiredLoadoutSlot == pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ) )
+ {
+ iDesiredSlot = i;
+ break;
+ }
+ }
+
+ if ( iDesiredSlot >= 0 )
+ {
+ EquipItem( m_ItemsToCarry[iDesiredSlot] );
+ }
+ else
+ {
+ // If we didn't find a weapon in the appropriate slot, get the base item
+ CEconItemView *pWeapon = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, iRequiredLoadoutSlot );
+ if ( pWeapon && pWeapon->IsValid() )
+ {
+ EquipItem( pWeapon );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateWeaponBodygroups( bool bModifyDeployedOnlyBodygroups )
+{
+ for ( int i=0; i<MAX_WEAPON_SLOTS; i++ )
+ {
+ CEconItemView *pItem = GetItemInSlot( i );
+ if ( !pItem )
+ continue;
+
+ if ( pItem->GetStaticData()->GetHideBodyGroupsDeployedOnly() != bModifyDeployedOnlyBodygroups )
+ continue;
+
+ if ( !(m_pHeldItem == pItem || !pItem->GetStaticData()->GetHideBodyGroupsDeployedOnly()) )
+ continue;
+
+ UpdateHiddenBodyGroups( pItem );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateHiddenBodyGroups( CEconItemView* pItem )
+{
+ MDLCACHE_CRITICAL_SECTION();
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ int iNumBodyGroups = pItem->GetStaticData()->GetNumModifiedBodyGroups( 0 );
+ for ( int i=0; i<iNumBodyGroups; ++i )
+ {
+ int iState = 0;
+ const char *pszBodyGroup = pItem->GetStaticData()->GetModifiedBodyGroup( 0, i, iState );
+ int iBodyGroup = FindBodygroupByName( &studioHdr, pszBodyGroup );
+
+ if ( iBodyGroup == -1 )
+ continue;
+
+ ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, iState );
+ SetBody( m_nBody );
+ }
+
+ // Handle style-based bodygroups
+ const CEconItemDefinition *pItemDef = pItem->GetItemDefinition();
+ const CEconStyleInfo *pStyle = pItemDef ? pItemDef->GetStyleInfo( pItem->GetStyle() ) : NULL;
+ if ( pStyle )
+ {
+ FOR_EACH_VEC( pStyle->GetAdditionalHideBodygroups(), i )
+ {
+ int iBodyGroup = FindBodygroupByName( &studioHdr, pStyle->GetAdditionalHideBodygroups()[i] );
+
+ if ( iBodyGroup == -1 )
+ continue;
+
+ ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, 1 ); // force state to '1' here to mean hidden
+ SetBody( m_nBody );
+ }
+ }
+
+ // Handle world model bodygroup overrides
+ int iBodyOverride = pItem->GetStaticData()->GetWorldmodelBodygroupOverride( m_iTeam );
+ int iBodyStateOverride = pItem->GetStaticData()->GetWorldmodelBodygroupStateOverride( m_iTeam );
+ if ( iBodyOverride > -1 && iBodyStateOverride > -1 )
+ {
+ ::SetBodygroup( &studioHdr, m_nBody, iBodyOverride, iBodyStateOverride );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CEconItemView *CTFPlayerModelPanel::GetItemInSlot( int iSlot )
+{
+ CEconItemView *pOwnedItemInSlot = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, iSlot );
+
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
+ if ( iSlot == iLoadoutSlot )
+ return pItem;
+
+ // GetLoadoutSlot will not work for misc2, taunt2-8 because it will always return misc/taunt
+ if ( pOwnedItemInSlot && pOwnedItemInSlot->GetItemDefIndex() == pItem->GetItemDefIndex() )
+ return pItem;
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EquipAllWearables( CEconItemView *pHeldItem )
+{
+ // First, reset all our bodygroups
+ MDLCACHE_CRITICAL_SECTION();
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ const CEconItemSchema::BodygroupStateMap_t& mapBodygroupState = GetItemSchema()->GetDefaultBodygroupStateMap();
+
+ FOR_EACH_MAP_FAST( mapBodygroupState, i )
+ {
+ const char *pszBodygroupName = mapBodygroupState.Key(i);
+ int iBodyGroup = FindBodygroupByName( &studioHdr, pszBodygroupName );
+ if ( iBodyGroup > -1 )
+ {
+ int iState = mapBodygroupState[i];
+ ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, iState );
+ }
+ }
+
+ SetBody( m_nBody );
+
+ UpdateWeaponBodygroups( false );
+
+ // Now equip each of our wearables
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ // If it's a wearable item, we put it on.
+ if ( pItem->GetStaticData()->IsAWearable() )
+ {
+ EquipItem( pItem );
+ }
+
+ // Then see if there's an extra wearable we need to attach for this item
+ const char *pszAttached = pItem->GetExtraWearableModel();
+ if ( pszAttached && pszAttached[ 0 ] )
+ {
+ const char *pszViewModelAttached = pItem->GetExtraWearableViewModel();
+ if ( pHeldItem == pItem || pszViewModelAttached == NULL || pszViewModelAttached[ 0 ] == '\0' || pszViewModelAttached[ 0 ] == '?' )
+ {
+ LoadAndAttachAdditionalModel( pszAttached, pItem );
+ }
+ }
+ }
+
+ UpdateWeaponBodygroups( true );
+
+ SetBody( m_nBody );
+
+ UpdatePreviewVisuals();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EquipItem( CEconItemView *pItem )
+{
+ if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
+ return;
+
+ const GameItemDefinition_t *pItemDef = pItem->GetItemDefinition();
+ Assert( pItemDef );
+
+ // Change team number so skins composite correctly
+ pItem->SetTeamNumber( m_iTeam );
+
+ // Non wearables can modify the animation
+ if ( !pItemDef->IsAWearable() )
+ {
+ int iAnimSlot = pItem->GetAnimationSlot();
+
+ // Ignore items that don't want to control player animation
+ if ( iAnimSlot == -2 )
+ return;
+
+ if ( iAnimSlot == -1 )
+ {
+ iAnimSlot = pItemDef->GetLoadoutSlot( m_iCurrentClassIndex );
+ }
+
+ const CUtlVector<const char *>& vecWeaponTypeSubstrings = GetItemSchema()->GetWeaponTypeSubstrings();
+ if ( vecWeaponTypeSubstrings.IsValidIndex( iAnimSlot ) )
+ {
+ int iAnim = FindAnimByName( vecWeaponTypeSubstrings[iAnimSlot] );
+ SetModelAnim( iAnim );
+ }
+ }
+
+ // Attach the models for the item
+ const char *pszAttached = pItem->GetWorldDisplayModel();
+ if ( !pszAttached )
+ {
+ pszAttached = pItem->GetPlayerDisplayModel( m_iCurrentClassIndex, m_iTeam );
+ }
+
+ if ( pszAttached && pszAttached[0] )
+ {
+ LoadAndAttachAdditionalModel( pszAttached, pItem );
+
+ int iTeam = pItemDef->GetBestVisualTeamData( m_iTeam );
+ // Set attached models if viewable third-person.
+ {
+ const int iNumAttachedModels = pItemDef->GetNumAttachedModels( iTeam );
+ for ( int i = 0; i < iNumAttachedModels; ++i )
+ {
+ attachedmodel_t *pModel = pItemDef->GetAttachedModelData( iTeam, i );
+
+ if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) )
+ continue;
+
+ if ( !pModel->m_pszModelName )
+ {
+ Warning( "econ item definition '%s' attachment (team %d idx %d) has no model\n", pItemDef->GetDefinitionName(), iTeam, i );
+ continue;
+ }
+
+ LoadAndAttachAdditionalModel( pModel->m_pszModelName, pItem );
+ }
+ }
+
+ // Festive
+ // Set attached models if viewable third-person.
+ static CSchemaAttributeDefHandle pAttr_is_festivized( "is_festivized" );
+ if ( pAttr_is_festivized && pItem->FindAttribute( pAttr_is_festivized ) )
+ {
+ const int iNumAttachedModels = pItemDef->GetNumAttachedModelsFestivized( iTeam );
+ for ( int i = 0; i < iNumAttachedModels; ++i )
+ {
+ attachedmodel_t *pModel = pItemDef->GetAttachedModelDataFestivized( iTeam, i );
+
+ if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) )
+ continue;
+
+ if ( !pModel->m_pszModelName )
+ {
+ Warning( "econ item definition '%s' attachment (team %d idx %d) has no model\n", pItemDef->GetDefinitionName(), iTeam, i );
+ continue;
+ }
+
+ LoadAndAttachAdditionalModel( pModel->m_pszModelName, pItem );
+ }
+ }
+ }
+
+ // Hide any item associated groups.
+ UpdateHiddenBodyGroups( pItem );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CTFPlayerModelPanel::AddCarriedItem( CEconItemView *pItem )
+{
+ CEconItemView *pNewItem = new CEconItemView;
+ *pNewItem = *pItem;
+ int iIdx = m_ItemsToCarry.AddToTail( pNewItem );
+
+ // This is a terrible hack. If we have team paint, we need an entity to find out what team
+ // we're on, but in this panel we don't have one. Instead, we force a flag all the way through
+ // the system on the CEconItemView so that the low-level paint code can pull from it if necessary.
+ if ( GetTeam() == TF_TEAM_BLUE )
+ {
+ pNewItem->SetClientItemFlags( kEconItemFlagClient_ForceBlueTeam );
+ }
+
+ return iIdx;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ClearCarriedItems( void )
+{
+ RemoveAdditionalModels();
+ m_ItemsToCarry.PurgeAndDeleteElements();
+ m_pHeldItem = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::RemoveAdditionalModels( void )
+{
+ ClearMergeMDLs();
+
+ // Unregister for all callbacks
+ modelinfo->UnregisterModelLoadCallback( -1, this );
+ m_vecDynamicAssetsLoaded.Purge();
+ m_vecItemsLoaded.PurgeAndDeleteElements();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::LoadAndAttachAdditionalModel( const char *pMDLName, CEconItemView *pItem )
+{
+ int nModelIndex = -1;
+
+ if ( pItem->GetStaticData()->IsContentStreamable() )
+ {
+ // Get the client-only dynamic model index. The auto-addref
+ // of vecDynamicAssetsLoaded will actually trigger the load.
+ nModelIndex = modelinfo->RegisterDynamicModel( pMDLName, true );
+ // Dynamic models never fail to register in this engine.
+ Assert( nModelIndex != -1 );
+ }
+ else
+ {
+ // Is the (non-streamable) model already precached? If so, use it.
+ nModelIndex = modelinfo->GetModelIndex( pMDLName );
+ }
+
+ if ( nModelIndex == -1 )
+ {
+ MDLHandle_t hMDL = vgui::MDLCache()->FindMDL( pMDLName );
+ Assert( hMDL != MDLHANDLE_INVALID );
+ if ( hMDL != MDLHANDLE_INVALID )
+ {
+ // Model not loaded, not dynamic. Hard load and exit out.
+ SetMergeMDL( hMDL, static_cast<IClientRenderable*>(pItem), pItem->GetSkin( m_iTeam ) );
+ }
+ m_MergeMDL = hMDL;
+ return;
+ }
+
+ CEconItemView *pClone = new CEconItemView;
+ *pClone = *pItem;
+ m_vecDynamicAssetsLoaded[ m_vecDynamicAssetsLoaded.AddToTail() ] = nModelIndex;
+ m_vecItemsLoaded.AddToTail( pClone );
+
+ // callback triggers immediately if not dynamic
+ modelinfo->RegisterModelLoadCallback( nModelIndex, this, true );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+static void SetMDLSkinForTeam( CMDL *pMDL, const CEconItemView *pItem, int iTeam )
+{
+ Assert( pItem );
+
+ if ( !pMDL )
+ return;
+
+ // Ask the item for a skin...
+ int nSkin = pItem->GetSkin( iTeam );
+
+ if ( nSkin == -1 )
+ {
+ // ... if not, use the team skin.
+ nSkin = iTeam == TF_TEAM_RED ? 0 : 1;
+ }
+
+ pMDL->m_nSkin = nSkin;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::OnModelLoadComplete( const model_t *pModel )
+{
+ CEconItemView *pItem = NULL;
+ FOR_EACH_VEC_BACK( m_vecDynamicAssetsLoaded, i )
+ {
+ if ( modelinfo->GetModel( m_vecDynamicAssetsLoaded[ i ] ) == pModel )
+ {
+ pItem = GetPreviewItem( m_vecItemsLoaded[ i ] );
+ break;
+ }
+ }
+
+ Assert( pItem );
+ if ( pItem )
+ {
+ MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel );
+ Assert( hMDL != MDLHANDLE_INVALID );
+ if ( hMDL != MDLHANDLE_INVALID )
+ {
+ SetMergeMDL( hMDL, static_cast<IClientRenderable*>(pItem) );
+
+ int nBody = 0;
+ if ( pItem->GetStaticData()->UsesPerClassBodygroups( m_iTeam ) )
+ {
+ CMDL *pMDL = GetMergeMDL(hMDL);
+ if ( pMDL )
+ {
+ // Classes start at 1, bodygroups at 0, so we shift them all back 1.
+ MDLCACHE_CRITICAL_SECTION();
+ CStudioHdr sHDR( pMDL->GetStudioHdr(), g_pMDLCache );
+ ::SetBodygroup( &sHDR, nBody, 1, m_iCurrentClassIndex-1 );
+ pMDL->m_nBody = nBody;
+ }
+ }
+
+ // Set the custom skin.
+ SetMDLSkinForTeam( GetMergeMDL( hMDL ), pItem, m_iTeam );
+ }
+ }
+}
+
+void CTFPlayerModelPanel::SetTeam( int iTeam )
+{
+ m_iTeam = iTeam;
+
+ UpdatePreviewVisuals();
+}
+
+void CTFPlayerModelPanel::UpdatePreviewVisuals()
+{
+ // Assume skin will be chosen based only on the preview team
+ int iSkin = m_iTeam == TF_TEAM_RED ? 0 : 1;
+
+ // Check if any of the items we're carrying should override this
+ static CSchemaAttributeDefHandle pAttrDef_PlayerSkinOverride( "player skin override" );
+ Assert( pAttrDef_PlayerSkinOverride );
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ if ( !pItem )
+ continue;
+ float fSkinOverride = 0.0f;
+ if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrDef_PlayerSkinOverride, &fSkinOverride ) && fSkinOverride == 1.0f )
+ {
+ C_TFPlayer::AdjustSkinIndexForZombie( m_iCurrentClassIndex, iSkin );
+ break;
+ }
+ Assert( fSkinOverride == 0.0f );
+ }
+
+ // Set the player model skin.
+ SetSkin( iSkin );
+
+ // Set the weapon's skin.
+ if ( m_MergeMDL && m_pHeldItem )
+ {
+ SetMDLSkinForTeam( GetMergeMDL( m_MergeMDL ), GetPreviewItem( m_pHeldItem ), m_iTeam );
+ }
+
+ // Set the skin for all other equipped items (wearables, etc).
+ for ( int i=0; i<m_vecDynamicAssetsLoaded.Count(); i++ )
+ {
+ const model_t *pModel = modelinfo->GetModel( m_vecDynamicAssetsLoaded[i] );
+ if ( pModel )
+ {
+ MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel );
+
+ // We're iterating over a list of the dynamic assets that we've completed streaming in, but
+ // we want to set the style based on the "preview item" definition if possible.
+ SetMDLSkinForTeam( GetMergeMDL( hMDL ), GetPreviewItem( m_vecItemsLoaded[i] ), m_iTeam );
+ }
+ }
+}
+
+CEconItemView *CTFPlayerModelPanel::GetPreviewItem( CEconItemView *pMatchItem )
+{
+ Assert( pMatchItem );
+ if ( !pMatchItem )
+ return NULL;
+
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ if ( *pMatchItem == *pItem )
+ return pItem;
+ }
+
+ return pMatchItem;
+}
+
+int ClassZoomZ[] =
+{
+ 0,
+ 20, // TF_CLASS_SCOUT,
+ 25, // TF_CLASS_SNIPER,
+ 20, // TF_CLASS_SOLDIER,
+ 22, // TF_CLASS_DEMOMAN,
+ 30, // TF_CLASS_MEDIC,
+ 30, // TF_CLASS_HEAVYWEAPONS,
+ 22, // TF_CLASS_PYRO,
+ 27, // TF_CLASS_SPY,
+ 20, // TF_CLASS_ENGINEER,
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ToggleZoom()
+{
+ m_bZoomedToHead = !m_bZoomedToHead;
+
+ // NOTE: GetZoomOffset() relies on m_bZoomedToHead being up to date
+ m_vecPlayerPos += GetZoomOffset();
+
+ SetModelAnglesAndPosition( m_angPlayer, m_vecPlayerPos );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CTFPlayerModelPanel::GetZoomOffset()
+{
+ const Vector vecOffset( 100, 0, ClassZoomZ[m_iCurrentClassIndex] );
+ return m_bZoomedToHead ? -vecOffset : vecOffset;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::PrePaint3D( IMatRenderContext *pRenderContext )
+{
+ if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER )
+ {
+ modelrender->ForcedMaterialOverride( *g_PlayerPreviewEffect.GetInvulnMaterialRef() );
+ }
+
+ BaseClass::PrePaint3D( pRenderContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::PostPaint3D( IMatRenderContext *pRenderContext )
+{
+ if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER )
+ {
+ modelrender->ForcedMaterialOverride( NULL );
+ }
+
+ static bool bAlternate = false;
+ Vector vColor = bAlternate ? m_vEyeGlowColor1 : m_vEyeGlowColor2;
+ bAlternate = !bAlternate;
+ // Eye glows
+ if ( m_aParticleSystems[ SYSTEM_EYEGLOW_RIGHT ] )
+ {
+ m_aParticleSystems[ SYSTEM_EYEGLOW_RIGHT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+ if ( m_aParticleSystems[ SYSTEM_EYESPARK_RIGHT ] )
+ {
+ m_aParticleSystems[ SYSTEM_EYESPARK_RIGHT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+
+ if ( m_aParticleSystems[ SYSTEM_EYEGLOW_LEFT ] )
+ {
+ m_aParticleSystems[ SYSTEM_EYEGLOW_LEFT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+ if ( m_aParticleSystems[ SYSTEM_EYESPARK_LEFT ])
+ {
+ m_aParticleSystems[ SYSTEM_EYESPARK_LEFT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
+ }
+
+ m_bUpdateEyeGlows = false;
+ m_bPlaySparks = false;
+
+ // remove all particles that are not up-to-date before simulating the updated ones in the base
+ for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
+ {
+ if ( m_aParticleSystems[i] && !m_aParticleSystems[i]->m_bIsUpdateToDate )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[i] );
+ }
+ }
+
+ BaseClass::PostPaint3D( pRenderContext );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose : Called by base Mdlpanel when a merged mdl has been drawn
+// For TF we use this as a way to render effects on top of model as appropriate (ie Unusual effects)
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::RenderingRootModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix )
+{
+ if ( !m_bUseParticle )
+ return;
+
+ // Eye Glows
+ UpdateEyeGlows( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, true );
+ UpdateEyeGlows( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, false );
+
+ // Right hand
+ UpdateActionSlotEffects( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix );
+
+ // Taunt Effects
+ UpdateTauntEffects( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix );
+}
+
+CEconItemView *CTFPlayerModelPanel::GetLoadoutItemFromMDLHandle( loadout_positions_t iPosition, MDLHandle_t mdlHandle )
+{
+ // Check if we have a particle hat, if not ignore
+ CEconItemView *pEconItem = NULL;
+
+ // Find this item
+ FOR_EACH_VEC( m_ItemsToCarry, i )
+ {
+ CEconItemView *pItem = m_ItemsToCarry[i];
+ int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
+ if ( ( IsMiscSlot( iLoadoutSlot ) && IsMiscSlot( iPosition ) ) ||
+ ( IsValidPickupWeaponSlot( iLoadoutSlot ) && iLoadoutSlot == iPosition ) )
+ {
+ const char * pDisplayModel = pItem->GetPlayerDisplayModel( m_iCurrentClassIndex, m_iTeam );
+ if ( pDisplayModel )
+ {
+ MDLHandle_t hMDLFindResult = vgui::MDLCache()->FindMDL( pDisplayModel );
+ // compare the model to make sure that this is the same item
+ if ( hMDLFindResult == mdlHandle )
+ {
+ pEconItem = pItem;
+ vgui::MDLCache()->Release(hMDLFindResult); // counterbalance addref from within FindMDL
+ break;
+ }
+ vgui::MDLCache()->Release(hMDLFindResult);
+ }
+ }
+ }
+
+ return pEconItem;
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::RenderingMergedModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix )
+{
+ if ( !m_bUseParticle )
+ return;
+
+ static struct MergeModelSlot_t
+ {
+ loadout_positions_t iPosition;
+ modelpanel_particle_system_t iSystem;
+ } s_mergeModelSlot[] =
+ {
+ { LOADOUT_POSITION_HEAD, SYSTEM_HEAD },
+ { LOADOUT_POSITION_MISC, SYSTEM_MISC1 },
+ { LOADOUT_POSITION_MISC2, SYSTEM_MISC2 },
+ { LOADOUT_POSITION_PRIMARY, SYSTEM_WEAPON },
+ { LOADOUT_POSITION_SECONDARY, SYSTEM_WEAPON },
+ { LOADOUT_POSITION_MELEE, SYSTEM_WEAPON },
+ };
+
+ modelpanel_particle_system_t iSystem = SYSTEM_HEAD;
+ loadout_positions_t iPosition = LOADOUT_POSITION_INVALID;
+ CEconItemView *pEconItem = NULL;
+ int count = ARRAYSIZE( s_mergeModelSlot );
+ for ( int i=0; i<count; ++i )
+ {
+ // find the item for this model
+ pEconItem = GetLoadoutItemFromMDLHandle( s_mergeModelSlot[i].iPosition, mdlHandle );
+ if ( pEconItem )
+ {
+ iPosition = s_mergeModelSlot[i].iPosition;
+ iSystem = s_mergeModelSlot[i].iSystem;
+
+ // this is horrible but this fixes multiple unusual cosmetics with same default loadout to update their particles
+ if ( m_aParticleSystems[ iSystem ] && m_aParticleSystems[ iSystem ]->m_bIsUpdateToDate )
+ continue;
+
+ break;
+ }
+ }
+
+ // couldn't find matching item for this model, do nothing
+ if ( !pEconItem )
+ return;
+
+ // Unusual Particles
+ // Update Misc Particles 1 by 1, Unfortunately the equip location is generic (MISC_SLOT) and not the specific slot
+ // so we have to test each slot individually
+ UpdateCosmeticParticles( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, iSystem, pEconItem );
+
+ if ( m_iCurrentSlotIndex == iPosition )
+ {
+ RenderStatTrack( pStudioHdr, pWorldMatrix );
+ }
+}
+
+IMaterial* CTFPlayerModelPanel::GetOverrideMaterial( MDLHandle_t mdlHandle )
+{
+ loadout_positions_t s_iPosition[] = {
+ LOADOUT_POSITION_HEAD,
+ LOADOUT_POSITION_MISC,
+ LOADOUT_POSITION_MISC2,
+ LOADOUT_POSITION_PRIMARY,
+ LOADOUT_POSITION_SECONDARY,
+ LOADOUT_POSITION_MELEE
+ };
+
+ int count = ARRAYSIZE( s_iPosition );
+ for ( int i = 0; i < count; ++i )
+ {
+ CEconItemView *pEconItem = GetLoadoutItemFromMDLHandle( s_iPosition[ i ], mdlHandle );
+ if ( pEconItem )
+ return pEconItem->GetMaterialOverride( m_iTeam );
+ }
+
+ return NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::RenderStatTrack( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix )
+{
+ // Draw the merge MDLs.
+ if ( !m_StatTrackModel.m_bDisabled )
+ {
+ matrix3x4_t matMergeBoneToWorld[MAXSTUDIOBONES];
+
+ // Get the merge studio header.
+ studiohdr_t *pStatTrackStudioHdr = m_StatTrackModel.m_MDL.GetStudioHdr();
+ matrix3x4_t *pMergeBoneToWorld = &matMergeBoneToWorld[0];
+
+ // If we have a valid mesh, bonemerge it. If we have an invalid mesh we can't bonemerge because
+ // it'll crash trying to pull data from the missing header.
+ if ( pStatTrackStudioHdr != NULL )
+ {
+ CStudioHdr mergeHdr( pStatTrackStudioHdr, g_pMDLCache );
+ m_StatTrackModel.m_MDL.SetupBonesWithBoneMerge( &mergeHdr, pMergeBoneToWorld, pStudioHdr, pWorldMatrix, m_StatTrackModel.m_MDLToWorld );
+ for ( int i=0; i<mergeHdr.numbones(); ++i )
+ {
+ MatrixScaleBy( m_flStatTrackScale, pMergeBoneToWorld[i] );
+ }
+ m_StatTrackModel.m_MDL.Draw( m_StatTrackModel.m_MDLToWorld, pMergeBoneToWorld );
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::UpdateCosmeticParticles(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix,
+ modelpanel_particle_system_t iSystem,
+ CEconItemView *pEconItem
+)
+{
+ if ( m_aParticleSystems[ iSystem ] && m_aParticleSystems[ iSystem ]->m_bIsUpdateToDate )
+ return false;
+
+ attachedparticlesystem_t *pParticleSystem = NULL;
+
+ // do community_sparkle effect if this is a community item?
+ const int iQualityParticleType = pEconItem->GetQualityParticleType();
+ if ( iQualityParticleType > 0 )
+ {
+ pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iQualityParticleType );
+ }
+
+ if ( !pParticleSystem )
+ {
+ // does this hat even have a particle effect
+ static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
+ uint32 iValue = 0;
+ if ( !pEconItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) )
+ {
+ return false;
+ }
+
+ const float& value_as_float = (float&)iValue;
+ pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( value_as_float );
+ }
+
+ // failed to find any particle effect
+ if ( !pParticleSystem )
+ {
+ return false;
+ }
+
+ // Team Color
+ if ( GetTeam() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" ))
+ {
+ static char pBlue[256];
+ V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
+ pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
+ if ( !pParticleSystem )
+ {
+ return false;
+ }
+ }
+
+ // if this thing has a bip_head or prp_helmet (aka a hat)
+ int iBone = Studio_BoneIndexByName( pStudioHdr, "bip_head" );
+ if ( iBone < 0 )
+ {
+ iBone = Studio_BoneIndexByName( pStudioHdr, "prp_helmet" );
+ if ( iBone < 0 )
+ {
+ iBone = Studio_BoneIndexByName( pStudioHdr, "prp_hat" );
+ }
+ }
+
+ // default to root
+ if ( iBone < 0 )
+ {
+ iBone = 0;
+ }
+
+ // Get Use Head Origin
+ CUtlVector< int > vecAttachments;
+ static CSchemaAttributeDefHandle pAttrDef_UseHead( "particle effect use head origin" );
+ uint32 iUseHead = 0;
+ if ( !pEconItem->FindAttribute( pAttrDef_UseHead, &iUseHead ) || !iUseHead == 0 )
+ {
+ // not using head? try searching for attachment points
+ for ( int i=0; i<ARRAYSIZE( pParticleSystem->pszControlPoints ); ++i )
+ {
+ const char *pszAttachmentName = pParticleSystem->pszControlPoints[i];
+ if ( pszAttachmentName && pszAttachmentName[0] )
+ {
+ int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttachmentName );
+ if ( iAttachment < 0 )
+ continue;
+
+ vecAttachments.AddToTail( iAttachment );
+ }
+ }
+ }
+
+ static char pszFullname[256];
+ const char* pszSystemName = pParticleSystem->pszSystemName;
+ // Weapon Remap for a Base Effect to be used on a specific weapon
+ if ( pParticleSystem->bUseSuffixName && pEconItem && pEconItem->GetItemDefinition()->GetParticleSuffix() )
+ {
+ V_strcpy_safe( pszFullname, pParticleSystem->pszSystemName );
+ V_strcat_safe( pszFullname, "_" );
+ V_strcat_safe( pszFullname, pEconItem->GetItemDefinition()->GetParticleSuffix() );
+ pszSystemName = pszFullname;
+ }
+
+ // Update the Particles and render them
+ if ( m_aParticleSystems[ iSystem ] )
+ {
+ // Check if its a new particle system
+ if ( V_strcmp( m_aParticleSystems[ iSystem ]->m_pParticleSystem->GetName(), pszSystemName ) )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[ iSystem ] );
+ m_aParticleSystems[ iSystem ] = CreateParticleData( pszSystemName );
+ }
+ }
+ else
+ {
+ // create
+ m_aParticleSystems[ iSystem ] = CreateParticleData( pszSystemName );
+ }
+
+ // Particle system does not exist
+ if ( !m_aParticleSystems[ iSystem ] )
+ return false;
+
+ // Get offset if it exists (and if we're using head offset)
+ static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
+ uint32 iOffset = 0;
+ Vector vecParticleOffset( 0, 0, 0 );
+ if ( iUseHead > 0 && pEconItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) )
+ {
+ vecParticleOffset.z = (float&)iOffset;
+ }
+
+ m_aParticleSystems[ iSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, iBone, vecParticleOffset );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateEyeGlows(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix,
+ bool bIsRightEye
+) {
+ float flOffset = 0;
+ modelpanel_particle_system_t eyeSystem = bIsRightEye ? SYSTEM_EYEGLOW_RIGHT : SYSTEM_EYEGLOW_LEFT;
+ modelpanel_particle_system_t sparkSystem = bIsRightEye ? SYSTEM_EYESPARK_RIGHT : SYSTEM_EYESPARK_LEFT;
+ const char* pszAttach = bIsRightEye ? "eyeglow_R" : "eyeglow_L";
+
+ // is this a model we care about?
+ int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttach );
+ if ( iAttachment == INVALID_PARTICLE_ATTACHMENT || iAttachment == -1 )
+ return;
+
+ if ( m_bUpdateEyeGlows )
+ {
+ const char* pszGlowEffectName = m_pszEyeGlowParticleName;
+
+ // kill old effects
+ SafeDeleteParticleData( &m_aParticleSystems[ eyeSystem ] );
+
+ if ( !bIsRightEye && GetPlayerClass() == TF_CLASS_DEMOMAN )
+ {
+ // demo man has a green eyeglow for eyelander if applicable
+ C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
+ if ( pPlayer )
+ {
+ int iDecaps = pPlayer->m_Shared.GetDecapitations();
+ pszGlowEffectName = pPlayer->GetDemomanEyeEffectName( iDecaps );
+ }
+ }
+
+ if ( pszGlowEffectName && pszGlowEffectName[0] != '\0' )
+ {
+ m_aParticleSystems[ eyeSystem ] = CreateParticleData( pszGlowEffectName );
+ }
+ }
+
+ if ( m_bPlaySparks && !m_vEyeGlowColor1.IsZero() )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[ sparkSystem ] );
+
+ // Generate an eye spark as well not for demo
+ m_aParticleSystems[ sparkSystem ] = CreateParticleData( "killstreak_t0_lvl1_flash" );
+ }
+
+ // Tick Update on position
+ if ( m_aParticleSystems[ eyeSystem ] || m_aParticleSystems[ sparkSystem ] )
+ {
+ // Figure out where our attach point is
+ matrix3x4_t matAttachToWorld;
+
+ CUtlVector< int > vecAttachments;
+ vecAttachments.AddToTail( iAttachment );
+
+ // Update control points which is updating the position of the particles
+ Vector vecForward;
+ MatrixGetColumn( matAttachToWorld, 0, vecForward );
+
+ Vector vecParticleOffset = vecForward * flOffset;
+ if ( m_aParticleSystems[ eyeSystem ] )
+ {
+ m_aParticleSystems[ eyeSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset );
+ }
+
+ if ( m_aParticleSystems[ sparkSystem ] )
+ {
+ m_aParticleSystems[ sparkSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateActionSlotEffects(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix
+) {
+ // is this a model we care about?
+ int iAttachment = Studio_FindAttachment( pStudioHdr, "effect_hand_R" );
+ if ( iAttachment == INVALID_PARTICLE_ATTACHMENT || iAttachment == -1 )
+ return;
+
+ if ( !m_bDrawActionSlotEffects )
+ return;
+
+ if ( !m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
+ {
+ m_aParticleSystems[ SYSTEM_ACTIONSLOT ] = CreateParticleData( CTFSpellBook::GetHandEffect( m_pHeldItem, 0 ) );
+ }
+
+ if ( !m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
+ return;
+
+ CUtlVector< int > vecAttachments;
+ vecAttachments.AddToTail( iAttachment );
+
+ m_aParticleSystems[ SYSTEM_ACTIONSLOT ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments );
+}
+
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::UpdateTauntEffects(
+ IMatRenderContext *pRenderContext,
+ CStudioHdr *pStudioHdr,
+ MDLHandle_t mdlHandle,
+ matrix3x4_t *pWorldMatrix
+ ) {
+ if ( !m_bDrawTauntParticles )
+ return;
+
+ if ( !m_aParticleSystems[SYSTEM_TAUNT] )
+ return;
+
+ // Check if refire is needed
+ if ( m_flTauntParticleRefireRate > 0 && m_flTauntParticleRefireTime < gpGlobals->curtime )
+ {
+ m_flTauntParticleRefireTime = gpGlobals->curtime + m_flTauntParticleRefireRate;
+
+ // safe off current particle name
+ CUtlString strParticleName = m_aParticleSystems[SYSTEM_TAUNT]->m_pParticleSystem->GetName();
+
+ // remove old particle
+ SafeDeleteParticleData( &m_aParticleSystems[SYSTEM_TAUNT] );
+
+ // create new particle
+ m_aParticleSystems[SYSTEM_TAUNT] = CreateParticleData( strParticleName.String() );
+ }
+
+ matrix3x4_t matAttachToWorld;
+ SetIdentityMatrix( matAttachToWorld );
+
+ CUtlVector< int > vecAttachments;
+ m_aParticleSystems[SYSTEM_TAUNT]->UpdateControlPoints( pStudioHdr, &matAttachToWorld, vecAttachments, 0, m_vecPlayerPos );
+}
+
+//-----------------------------------------------------------------------------
+// Called Externally
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetEyeGlowEffect( const char *pEffectName, Vector vColor1, Vector vColor2, bool bForceUpdate, bool bPlaySparks )
+{
+ m_vEyeGlowColor1 = vColor1;
+ m_vEyeGlowColor2 = vColor2;
+ m_bPlaySparks = bPlaySparks;
+
+ if ( bForceUpdate )
+ {
+ m_bUpdateEyeGlows = true;
+ }
+
+ if ( !pEffectName )
+ {
+ if ( m_pszEyeGlowParticleName[0] != '\0' )
+ {
+ m_bUpdateEyeGlows = true;
+ }
+ m_pszEyeGlowParticleName[0] = '\0';
+ }
+ else if ( !FStrEq( m_pszEyeGlowParticleName, pEffectName) )
+ {
+ V_strcpy_safe( m_pszEyeGlowParticleName, pEffectName );
+ m_bUpdateEyeGlows = true;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: clear all particles
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::InvalidateParticleEffects()
+{
+ for ( int i=0; i<ARRAYSIZE(m_aParticleSystems); ++i )
+ {
+ if ( m_aParticleSystems[i] )
+ {
+ SafeDeleteParticleData( &m_aParticleSystems[i] );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
+{
+ if ( !event || !event->GetActive() )
+ return;
+
+ CChoreoActor *actor = event->GetActor();
+ if ( actor && !actor->GetActive() )
+ return;
+
+ CChoreoChannel *channel = event->GetChannel();
+ if ( channel && !channel->GetActive() )
+ return;
+
+ //Msg( "Got STARTEVENT at %.2f\n", currenttime );
+ //Msg( "%8.4f: start %s\n", currenttime, event->GetDescription() );
+
+ switch ( event->GetType() )
+ {
+ case CChoreoEvent::SEQUENCE:
+ ProcessSequence( scene, event );
+ break;
+
+ case CChoreoEvent::SPEAK:
+ {
+ if ( m_bDisableSpeakEvent )
+ return;
+
+ // FIXME: dB hack. soundlevel needs to be moved into inside of wav?
+ soundlevel_t iSoundlevel = SNDLVL_TALKING;
+ if ( event->GetParameters2() )
+ {
+ iSoundlevel = (soundlevel_t)atoi( event->GetParameters2() );
+ if ( iSoundlevel == SNDLVL_NONE )
+ {
+ iSoundlevel = SNDLVL_TALKING;
+ }
+ }
+
+ float time_in_past = currenttime - event->GetStartTime() ;
+ float soundtime = gpGlobals->curtime - time_in_past;
+
+ EmitSound_t es;
+ es.m_nChannel = CHAN_VOICE;
+ es.m_flVolume = 1;
+ es.m_SoundLevel = iSoundlevel;
+ es.m_flSoundTime = soundtime;
+ es.m_bEmitCloseCaption = false;
+ es.m_pSoundName = event->GetParameters();
+
+ C_RecipientFilter filter;
+ C_BaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, es );
+ }
+ break;
+
+ case CChoreoEvent::STOPPOINT:
+ {
+ // Nothing, this is a symbolic event for keeping the vcd alive for ramping out after the last true event
+ //ClearScene();
+ }
+ break;
+
+ case CChoreoEvent::LOOP:
+ ProcessLoop( scene, event );
+ break;
+
+ // Not supported in TF2's model previews
+ case CChoreoEvent::SUBSCENE:
+ case CChoreoEvent::SECTION:
+ {
+ Assert(0);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
+{
+ if ( !event || !event->GetActive() )
+ return;
+
+ CChoreoActor *actor = event->GetActor();
+ if ( actor && !actor->GetActive() )
+ return;
+
+ CChoreoChannel *channel = event->GetChannel();
+ if ( channel && !channel->GetActive() )
+ return;
+
+ //Msg( "Got ENDEVENT at %.2f\n", currenttime );
+ //Msg( "%8.4f: end %s %i\n", currenttime, event->GetDescription(), event->GetType() );
+
+ switch ( event->GetType() )
+ {
+ case CChoreoEvent::SUBSCENE:
+ {
+ // Not supported in TF2's model previews
+ Assert(0);
+ }
+ break;
+ case CChoreoEvent::SPEAK:
+ {
+ }
+ break;
+ case CChoreoEvent::STOPPOINT:
+ {
+ //SetSequenceLayers( NULL, 0 );
+ //ClearScene();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
+{
+ if ( !event || !event->GetActive() )
+ return;
+
+ CChoreoActor *actor = event->GetActor();
+ if ( actor && !actor->GetActive() )
+ return;
+
+ CChoreoChannel *channel = event->GetChannel();
+ if ( channel && !channel->GetActive() )
+ return;
+
+ //Msg("PROCESSEVENT at %.2f\n", currenttime );
+
+ switch( event->GetType() )
+ {
+ case CChoreoEvent::EXPRESSION:
+ if ( !m_bShouldRunFlexEvents )
+ {
+ ProcessFlexSettingSceneEvent( scene, event );
+ }
+ break;
+
+ case CChoreoEvent::FLEXANIMATION:
+ if ( m_bShouldRunFlexEvents )
+ {
+ ProcessFlexAnimation( scene, event );
+ }
+ break;
+
+ case CChoreoEvent::SEQUENCE:
+ case CChoreoEvent::SPEAK:
+ case CChoreoEvent::STOPPOINT:
+ // Nothing
+ break;
+
+ // Not supported in TF2's model previews
+ case CChoreoEvent::LOOKAT:
+ case CChoreoEvent::FACE:
+ case CChoreoEvent::SUBSCENE:
+ case CChoreoEvent::MOVETO:
+ case CChoreoEvent::INTERRUPT:
+ case CChoreoEvent::PERMIT_RESPONSES:
+ case CChoreoEvent::GESTURE:
+ Assert(0);
+ break;
+
+ default:
+ break;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Apply a sequence
+// Input : *event -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessSequence( CChoreoScene *scene, CChoreoEvent *event )
+{
+ Assert( event->GetType() == CChoreoEvent::SEQUENCE );
+
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ if ( !event->GetActor() )
+ return;
+
+ int iSequence = LookupSequence( &studioHdr, event->GetParameters() );
+ if (iSequence < 0)
+ return;
+
+ // making sure the mdl has correct playback rate
+ mstudioseqdesc_t &seqdesc = studioHdr.pSeqdesc( iSequence );
+ mstudioanimdesc_t &animdesc = studioHdr.pAnimdesc( studioHdr.iRelativeAnim( iSequence, seqdesc.anim(0,0) ) );
+ m_RootMDL.m_MDL.m_flPlaybackRate = animdesc.fps;
+
+ MDLSquenceLayer_t tmpSequenceLayers[1];
+ tmpSequenceLayers[0].m_nSequenceIndex = iSequence;
+ tmpSequenceLayers[0].m_flWeight = 1.0;
+ tmpSequenceLayers[0].m_bNoLoop = true;
+ tmpSequenceLayers[0].m_flCycleBeganAt = m_RootMDL.m_MDL.m_flTime;
+ SetSequenceLayers( tmpSequenceLayers, 1 );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *scene -
+// *event -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessLoop( CChoreoScene *scene, CChoreoEvent *event )
+{
+ Assert( event->GetType() == CChoreoEvent::LOOP );
+
+ float backtime = (float)atof( event->GetParameters() );
+
+ bool process = true;
+ int counter = event->GetLoopCount();
+ if ( counter != -1 )
+ {
+ int remaining = event->GetNumLoopsRemaining();
+ if ( remaining <= 0 )
+ {
+ process = false;
+ }
+ else
+ {
+ event->SetNumLoopsRemaining( --remaining );
+ }
+ }
+
+ if ( !process )
+ return;
+
+ //Msg("LOOP: %.2f (%.2f)\n", m_flSceneTime, scene->GetTime() );
+
+ float flPrevTime = m_flSceneTime;
+ scene->LoopToTime( backtime );
+ m_flSceneTime = backtime;
+
+ //Msg(" -> %.2f (%.2f)\n", m_flSceneTime, scene->GetTime() );
+
+ float flDelta = flPrevTime - backtime;
+
+ //Msg(" -> Delta %.2f\n", flDelta );
+
+ // If we're running noloop sequences, we need to push out their begin time, so they keep playing
+ for ( int i = 0; i < m_nNumSequenceLayers; i++ )
+ {
+ if ( m_SequenceLayers[i].m_bNoLoop )
+ {
+ m_SequenceLayers[i].m_flCycleBeganAt += flDelta;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+LocalFlexController_t CTFPlayerModelPanel::GetNumFlexControllers( void )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ return studioHdr.numflexcontrollers();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFPlayerModelPanel::GetFlexDescFacs( int iFlexDesc )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexdesc_t *pflexdesc = studioHdr.pFlexdesc( iFlexDesc );
+
+ return pflexdesc->pszFACS( );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFPlayerModelPanel::GetFlexControllerName( LocalFlexController_t iFlexController )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( iFlexController );
+
+ return pflexcontroller->pszName( );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+const char *CTFPlayerModelPanel::GetFlexControllerType( LocalFlexController_t iFlexController )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( iFlexController );
+
+ return pflexcontroller->pszType( );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+LocalFlexController_t CTFPlayerModelPanel::FindFlexController( const char *szName )
+{
+ for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
+ {
+ if (stricmp( GetFlexControllerName( i ), szName ) == 0)
+ {
+ return i;
+ }
+ }
+
+ // AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) );
+ return LocalFlexController_t(-1);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetFlexWeight( LocalFlexController_t index, float value )
+{
+ if (index >= 0 && index < GetNumFlexControllers())
+ {
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( index );
+
+ if (pflexcontroller->max != pflexcontroller->min)
+ {
+ value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min);
+ value = clamp( value, 0.0f, 1.0f );
+ }
+
+ m_flexWeight[ index ] = value;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+float CTFPlayerModelPanel::GetFlexWeight( LocalFlexController_t index )
+{
+ if (index >= 0 && index < GetNumFlexControllers())
+ {
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+
+ mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( index );
+
+ if (pflexcontroller->max != pflexcontroller->min)
+ {
+ return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min;
+ }
+
+ return m_flexWeight[index];
+ }
+ return 0.0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: During paint, apply the flex weights to the model
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetupFlexWeights( void )
+{
+ if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
+ return;
+
+ // initialize the models local to global flex controller mappings
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ if (studioHdr.pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1)
+ {
+ for ( LocalFlexController_t i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++)
+ {
+ int j = C_BaseFlex::AddGlobalFlexController( studioHdr.pFlexcontroller( i )->pszName() );
+ studioHdr.pFlexcontroller( i )->localToGlobal = j;
+ }
+ }
+
+ int iControllers = GetNumFlexControllers();
+ for ( int j = 0; j < iControllers; j++ )
+ {
+ m_RootMDL.m_MDL.m_pFlexControls[j] = 0;
+ }
+
+ LocalFlexController_t i;
+
+ // Decay to neutral
+ for ( i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
+ {
+ SetFlexWeight( i, GetFlexWeight( i ) * 0.95 );
+ }
+
+ // Run scene
+ if ( m_pScene )
+ {
+ m_bShouldRunFlexEvents = true;
+ m_pScene->Think( m_flSceneTime );
+ }
+
+ // get the networked flexweights and convert them from 0..1 to real dynamic range
+ for (i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++)
+ {
+ mstudioflexcontroller_t *pflex = studioHdr.pFlexcontroller( i );
+
+ m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] = m_flexWeight[i];
+ // rescale
+ m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] = m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min;
+ }
+
+ if ( m_pScene )
+ {
+ m_bShouldRunFlexEvents = false;
+ m_pScene->Think( m_flSceneTime );
+ }
+
+ ProcessVisemes( m_PhonemeClasses );
+
+ if ( m_pScene )
+ {
+ // Advance time
+ if ( m_flLastTickTime < FLT_EPSILON )
+ {
+ m_flLastTickTime = m_RootMDL.m_MDL.m_flTime - 0.1;
+ }
+
+ m_flSceneTime += (m_RootMDL.m_MDL.m_flTime - m_flLastTickTime);
+ m_flLastTickTime = m_RootMDL.m_MDL.m_flTime;
+
+ if ( m_flSceneEndTime > FLT_EPSILON && m_flSceneTime > m_flSceneEndTime )
+ {
+ bool bLoopScene = m_bLoopScene;
+ char filename[MAX_PATH];
+ V_strcpy_safe( filename, m_pScene->GetFilename() );
+
+ SetSequenceLayers( NULL, 0 );
+ ClearScene();
+
+ if ( bLoopScene )
+ {
+ m_pScene = LoadSceneForModel( filename, this, &m_flSceneEndTime );
+ }
+ else
+ {
+ m_pszVCD = NULL;
+ }
+ }
+ }
+}
+
+extern CFlexSceneFileManager g_FlexSceneFileManager;
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessExpression( CChoreoScene *scene, CChoreoEvent *event )
+{
+ // Flexanimations have to have an end time!!!
+ if ( !event->HasEndTime() )
+ return;
+
+ // Look up the actual strings
+ const char *scenefile = event->GetParameters();
+ const char *name = event->GetParameters2();
+
+ // Have to find both strings
+ if ( scenefile && name )
+ {
+ // Find the scene file
+ const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true );
+ if ( pExpHdr )
+ {
+ float scenetime = scene->GetTime();
+
+ float flIntensity = event->GetIntensity( scenetime );
+
+ int i;
+ const flexsetting_t *pSetting = NULL;
+
+ // Find the named setting in the base
+ for ( i = 0; i < pExpHdr->numflexsettings; i++ )
+ {
+ pSetting = pExpHdr->pSetting( i );
+ if ( !pSetting )
+ continue;
+
+ if ( !V_stricmp( pSetting->pszName(), name ) )
+ break;
+ }
+
+ if ( i>=pExpHdr->numflexsettings )
+ return;
+
+ flexweight_t *pWeights = NULL;
+ int truecount = pSetting->psetting( (byte *)pExpHdr, 0, &pWeights );
+ if ( !pWeights )
+ return;
+
+ for (i = 0; i < truecount; i++, pWeights++)
+ {
+ int j = FlexControllerLocalToGlobal( pExpHdr, pWeights->key );
+
+ float s = clamp( pWeights->influence * flIntensity, 0.0f, 1.0f );
+ m_RootMDL.m_MDL.m_pFlexControls[ j ] = m_RootMDL.m_MDL.m_pFlexControls[j] * (1.0f - s) + pWeights->weight * s;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessFlexSettingSceneEvent( CChoreoScene *scene, CChoreoEvent *event )
+{
+ // Flexanimations have to have an end time!!!
+ if ( !event->HasEndTime() )
+ return;
+
+ VPROF( "C_BaseFlex::ProcessFlexSettingSceneEvent" );
+
+ // Look up the actual strings
+ const char *scenefile = event->GetParameters();
+ const char *name = event->GetParameters2();
+
+ // Have to find both strings
+ if ( scenefile && name )
+ {
+ // Find the scene file
+ const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true );
+ if ( pExpHdr )
+ {
+ float scenetime = scene->GetTime();
+
+ float scale = event->GetIntensity( scenetime );
+
+ // Add the named expression
+ AddFlexSetting( name, scale, pExpHdr );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *expr -
+// scale -
+// *pSettinghdr -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::AddFlexSetting( const char *expr, float scale, const flexsettinghdr_t *pSettinghdr )
+{
+ int i;
+ const flexsetting_t *pSetting = NULL;
+
+ // Find the named setting in the base
+ for ( i = 0; i < pSettinghdr->numflexsettings; i++ )
+ {
+ pSetting = pSettinghdr->pSetting( i );
+ if ( !pSetting )
+ continue;
+
+ const char *name = pSetting->pszName();
+
+ if ( !V_stricmp( name, expr ) )
+ break;
+ }
+
+ if ( i>=pSettinghdr->numflexsettings )
+ {
+ return;
+ }
+
+ flexweight_t *pWeights = NULL;
+ int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights );
+ if ( !pWeights )
+ return;
+
+ for (i = 0; i < truecount; i++, pWeights++)
+ {
+ // Translate to local flex controller
+ // this is translating from the settings's local index to the models local index
+ int index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key );
+
+ // blend scaled weighting in to total (post networking g_flexweight!!!!)
+ float s = clamp( scale * pWeights->influence, 0.0f, 1.0f );
+ m_RootMDL.m_MDL.m_pFlexControls[index] = m_RootMDL.m_MDL.m_pFlexControls[index] * (1.0f - s) + pWeights->weight * s;
+
+ for ( int iMergeMDL=0; iMergeMDL<m_aMergeMDLs.Count(); ++iMergeMDL )
+ {
+ m_aMergeMDLs[iMergeMDL].m_MDL.m_pFlexControls[index] = m_aMergeMDLs[iMergeMDL].m_MDL.m_pFlexControls[index] * (1.0f - s) + pWeights->weight * s;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Apply flexanimation to actor's face
+// Input : *event -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessFlexAnimation( CChoreoScene *scene, CChoreoEvent *event )
+{
+ Assert( event->GetType() == CChoreoEvent::FLEXANIMATION );
+
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ CStudioHdr *hdr = &studioHdr;
+ if ( !hdr )
+ return;
+
+ if ( !event->GetTrackLookupSet() )
+ {
+ // Create lookup data
+ for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
+ {
+ CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
+ if ( !track )
+ continue;
+
+ if ( track->IsComboType() )
+ {
+ char name[ 512 ];
+ Q_strncpy( name, "right_" ,sizeof(name));
+ Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
+
+ track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 0 );
+
+ Q_strncpy( name, "left_" ,sizeof(name));
+ Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
+
+ track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 1 );
+ }
+ else
+ {
+ track->SetFlexControllerIndex( MAX( FindFlexController( (char *)track->GetFlexControllerName() ), LocalFlexController_t(0)), 0 );
+ }
+ }
+
+ event->SetTrackLookupSet( true );
+ }
+
+ float scenetime = scene->GetTime();
+
+ float weight = event->GetIntensity( scenetime );
+
+ // Iterate animation tracks
+ for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
+ {
+ CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
+ if ( !track )
+ continue;
+
+ // Disabled
+ if ( !track->IsTrackActive() )
+ continue;
+
+ // Map track flex controller to global name
+ if ( track->IsComboType() )
+ {
+ for ( int side = 0; side < 2; side++ )
+ {
+ LocalFlexController_t controller = track->GetRawFlexControllerIndex( side );
+
+ // Get spline intensity for controller
+ float flIntensity = track->GetIntensity( scenetime, side );
+ if ( controller >= LocalFlexController_t(0) )
+ {
+ float orig = GetFlexWeight( controller );
+ float value = orig * (1 - weight) + flIntensity * weight;
+ SetFlexWeight( controller, value );
+ }
+ }
+ }
+ else
+ {
+ LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 );
+
+ // Get spline intensity for controller
+ float flIntensity = track->GetIntensity( scenetime, 0 );
+ if ( controller >= LocalFlexController_t(0) )
+ {
+ float orig = GetFlexWeight( controller );
+ float value = orig * (1 - weight) + flIntensity * weight;
+ SetFlexWeight( controller, value );
+ }
+ }
+ }
+}
+
+extern ConVar g_CV_PhonemeDelay;
+extern ConVar g_CV_PhonemeFilter;
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ProcessVisemes( Emphasized_Phoneme *classes )
+{
+ // Any sounds being played?
+ if ( !MouthInfo().IsActive() )
+ return;
+
+ // Multiple phoneme tracks can overlap, look across all such tracks.
+ for ( int source = 0 ; source < MouthInfo().GetNumVoiceSources(); source++ )
+ {
+ CVoiceData *vd = MouthInfo().GetVoiceSource( source );
+ if ( !vd || vd->ShouldIgnorePhonemes() )
+ continue;
+
+ CSentence *sentence = engine->GetSentence( vd->GetSource() );
+ if ( !sentence )
+ continue;
+
+ float sentence_length = engine->GetSentenceLength( vd->GetSource() );
+ float timesincestart = vd->GetElapsedTime();
+
+ // This sound should be done...why hasn't it been removed yet???
+ if ( timesincestart >= ( sentence_length + 2.0f ) )
+ continue;
+
+ // Adjust actual time
+ float t = timesincestart - g_CV_PhonemeDelay.GetFloat();
+
+ // Get box filter duration
+ float dt = g_CV_PhonemeFilter.GetFloat();
+
+ // Streaming sounds get an additional delay...
+ /*
+ // Tracker 20534: Probably not needed any more with the async sound stuff that
+ // we now have (we don't have a disk i/o hitch on startup which might have been
+ // messing up the startup timing a bit )
+ bool streaming = engine->IsStreaming( vd->m_pAudioSource );
+ if ( streaming )
+ {
+ t -= g_CV_PhonemeDelayStreaming.GetFloat();
+ }
+ */
+
+ // Assume sound has been playing for a while...
+ bool juststarted = false;
+
+ // Get intensity setting for this time (from spline)
+ float emphasis_intensity = sentence->GetIntensity( t, sentence_length );
+
+ // Blend and add visemes together
+ AddVisemesForSentence( classes, emphasis_intensity, sentence, t, dt, juststarted );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted )
+{
+ int pcount = sentence->GetRuntimePhonemeCount();
+ for ( int k = 0; k < pcount; k++ )
+ {
+ const CBasePhonemeTag *phoneme = sentence->GetRuntimePhoneme( k );
+
+ if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime())
+ {
+ bool bCrossfade = true;
+ if (bCrossfade)
+ {
+ if (k < pcount-1)
+ {
+ const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 );
+ // if I have a neighbor
+ if ( next )
+ {
+ // and they're touching
+ if (next->GetStartTime() == phoneme->GetEndTime() )
+ {
+ // no gap, so increase the blend length to the end of the next phoneme, as long as it's not longer than the current phoneme
+ dt = MAX( dt, MIN( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
+ }
+ else
+ {
+ // dead space, so increase the blend length to the start of the next phoneme, as long as it's not longer than the current phoneme
+ dt = MAX( dt, MIN( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
+ }
+ }
+ else
+ {
+ // last phoneme in list, increase the blend length to the length of the current phoneme
+ dt = MAX( dt, phoneme->GetEndTime() - phoneme->GetStartTime() );
+ }
+ }
+ }
+ }
+
+ float t1 = ( phoneme->GetStartTime() - t) / dt;
+ float t2 = ( phoneme->GetEndTime() - t) / dt;
+
+ if (t1 < 1.0 && t2 > 0)
+ {
+ float scale;
+
+ // clamp
+ if (t2 > 1)
+ t2 = 1;
+ if (t1 < 0)
+ t1 = 0;
+
+ // FIXME: simple box filter. Should use something fancier
+ scale = (t2 - t1);
+
+ AddViseme( classes, emphasis_intensity, phoneme->GetPhonemeCode(), scale, juststarted );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *classes -
+// phoneme -
+// scale -
+// newexpression -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ CStudioHdr *hdr = &studioHdr;
+ if ( !hdr )
+ return;
+
+ int type;
+
+ // Setup weights for any emphasis blends
+ bool skip = SetupEmphasisBlend( classes, phoneme );
+
+ phoneme = 230;
+ scale = 1.0;
+
+ // Uh-oh, missing or unknown phoneme???
+ if ( skip )
+ {
+ return;
+ }
+
+ // Compute blend weights
+ ComputeBlendedSetting( classes, emphasis_intensity );
+
+ for ( type = 0; type < NUM_PHONEME_CLASSES; type++ )
+ {
+ Emphasized_Phoneme *info = &classes[ type ];
+ if ( !info->valid || info->amount == 0.0f )
+ continue;
+
+ const flexsettinghdr_t *actual_flexsetting_header = info->base;
+ const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme );
+ if (!pSetting)
+ {
+ continue;
+ }
+
+ flexweight_t *pWeights = NULL;
+
+ int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights );
+ if ( pWeights )
+ {
+ for ( int i = 0; i < truecount; i++)
+ {
+ // Translate to global controller number
+ int j = FlexControllerLocalToGlobal( actual_flexsetting_header, pWeights->key );
+ // Add scaled weighting in
+ if ( pWeights->weight > 0 )
+ {
+ m_RootMDL.m_MDL.m_pFlexControls[j] += info->amount * scale * pWeights->weight;
+ }
+ // Go to next setting
+ pWeights++;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: A lot of the one time setup and also resets amount to 0.0f default
+// for strong/weak/normal tracks
+// Returning true == skip this phoneme
+// Input : *classes -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CTFPlayerModelPanel::SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme )
+{
+ int i;
+
+ bool skip = false;
+
+ for ( i = 0; i < NUM_PHONEME_CLASSES; i++ )
+ {
+ Emphasized_Phoneme *info = &classes[ i ];
+
+ // Assume it's bogus
+ info->valid = false;
+ info->amount = 0.0f;
+
+ // One time setup
+ if ( !info->basechecked )
+ {
+ info->basechecked = true;
+ info->base = (flexsettinghdr_t *)g_FlexSceneFileManager.FindSceneFile( this, info->classname, false );
+ }
+ info->exp = NULL;
+ if ( info->base )
+ {
+ Assert( info->base->id == ('V' << 16) + ('F' << 8) + ('E') );
+ info->exp = info->base->pIndexedSetting( phoneme );
+ }
+
+ if ( info->required && ( !info->base || !info->exp ) )
+ {
+ skip = true;
+ break;
+ }
+
+ if ( info->exp )
+ {
+ info->valid = true;
+ }
+ }
+
+ return skip;
+}
+
+#define STRONG_CROSSFADE_START 0.60f
+#define WEAK_CROSSFADE_START 0.40f
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Here's the formula
+// 0.5 is neutral 100 % of the default setting
+// Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END
+// If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START
+// so we don't get huge numbers
+// Input : *classes -
+// emphasis_intensity -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity )
+{
+ // See which blends are available for the current phoneme
+ bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid;
+ bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid;
+
+ // Better have phonemes in general
+ Assert( classes[ PHONEME_CLASS_NORMAL ].valid );
+
+ if ( emphasis_intensity > STRONG_CROSSFADE_START )
+ {
+ if ( has_strong )
+ {
+ // Blend in some of strong
+ float dist_remaining = 1.0f - emphasis_intensity;
+ float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START );
+
+ classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START;
+ classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac;
+ }
+ else
+ {
+ emphasis_intensity = MIN( emphasis_intensity, STRONG_CROSSFADE_START );
+ classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
+ }
+ }
+ else if ( emphasis_intensity < WEAK_CROSSFADE_START )
+ {
+ if ( has_weak )
+ {
+ // Blend in some weak
+ float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity;
+ float frac = dist_remaining / ( WEAK_CROSSFADE_START );
+
+ classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START;
+ classes[ PHONEME_CLASS_WEAK ].amount = frac;
+ }
+ else
+ {
+ emphasis_intensity = MAX( emphasis_intensity, WEAK_CROSSFADE_START );
+ classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
+ }
+ }
+ else
+ {
+ // Assume 0.5 (neutral) becomes a scaling of 1.0f
+ classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::InitPhonemeMappings( void )
+{
+ CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
+ if ( studioHdr.IsValid() )
+ {
+ char szBasename[MAX_PATH];
+ Q_StripExtension( studioHdr.pszName(), szBasename, sizeof( szBasename ) );
+
+ char szExpressionName[MAX_PATH];
+ Q_snprintf( szExpressionName, sizeof( szExpressionName ), "%s/phonemes/phonemes", szBasename );
+ if ( g_FlexSceneFileManager.FindSceneFile( this, szExpressionName, false ) )
+ {
+ SetupMappings( szExpressionName );
+ return;
+ }
+ }
+
+ SetupMappings( "phonemes" );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::SetupMappings( char const *pchFileRoot )
+{
+ // Fill in phoneme class lookup
+ memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) );
+
+ Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ];
+ Q_snprintf( normal->classname, sizeof( normal->classname ), "%s", pchFileRoot );
+ normal->required = true;
+
+ Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ];
+ Q_snprintf( weak->classname, sizeof( weak->classname ), "%s_weak", pchFileRoot );
+ Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ];
+ Q_snprintf( strong->classname, sizeof( strong->classname ), "%s_strong", pchFileRoot );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but
+// we just do this in memory with an array of integers (could be shorts, I suppose)
+// Input : *pSettinghdr -
+//-----------------------------------------------------------------------------
+void CTFPlayerModelPanel::EnsureTranslations( const flexsettinghdr_t *pSettinghdr )
+{
+ Assert( pSettinghdr );
+
+ FS_LocalToGlobal_t entry( pSettinghdr );
+
+ unsigned short idx = m_LocalToGlobal.Find( entry );
+ if ( idx != m_LocalToGlobal.InvalidIndex() )
+ return;
+
+ entry.SetCount( pSettinghdr->numkeys );
+
+ for ( int i = 0; i < pSettinghdr->numkeys; ++i )
+ {
+ entry.m_Mapping[ i ] = C_BaseFlex::AddGlobalFlexController( pSettinghdr->pLocalName( i ) );
+ }
+
+ m_LocalToGlobal.Insert( entry );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Look up instance specific mapping
+// Input : *pSettinghdr -
+// key -
+// Output : int
+//-----------------------------------------------------------------------------
+int CTFPlayerModelPanel::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key )
+{
+ FS_LocalToGlobal_t entry( pSettinghdr );
+
+ int idx = m_LocalToGlobal.Find( entry );
+ if ( idx == m_LocalToGlobal.InvalidIndex() )
+ {
+ // This should never happen!!!
+ Assert( 0 );
+ Warning( "Unable to find mapping for flexcontroller %i, settings %p on CTFPlayerModelPanel\n", key, pSettinghdr );
+ EnsureTranslations( pSettinghdr );
+ idx = m_LocalToGlobal.Find( entry );
+ if ( idx == m_LocalToGlobal.InvalidIndex() )
+ {
+ Error( "CTFPlayerModelPanel::FlexControllerLocalToGlobal failed!\n" );
+ }
+ }
+
+ FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ];
+ // Validate lookup
+ Assert( result.m_nCount != 0 && key < result.m_nCount );
+ int index = result.m_Mapping[ key ];
+ return index;
+}
+