//========= 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 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; iGetNumAnimations( 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(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(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(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( 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; iGetStaticData()->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; iGetStaticData()->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& 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(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(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( 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; iGetModel( 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; im_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; im_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; ipszControlPoints ); ++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; iGetActive() ) 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; iMergeMDLweight * 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; }