diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/tf/vgui/tf_playermodelpanel.cpp | |
| download | archived-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.cpp | 2893 |
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; +} + |