You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2894 lines
85 KiB
2894 lines
85 KiB
5 years ago
|
//========= 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;
|
||
|
}
|
||
|
|