Team Fortress 2 Source Code as on 22/4/2020
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.

2893 lines
85 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "tf_shareddefs.h"
  9. #include "tf_playermodelpanel.h"
  10. #include "tf_classdata.h"
  11. #include "tf_item_inventory.h"
  12. #include "vgui/IVGui.h"
  13. #include "game_item_schema.h"
  14. #include "econ_item_system.h"
  15. #include "animation.h"
  16. #include "choreoscene.h"
  17. #include "choreoevent.h"
  18. #include "choreoactor.h"
  19. #include "choreochannel.h"
  20. #include "scenefilecache/ISceneFileCache.h"
  21. #include "c_sceneentity.h"
  22. #include "c_baseflex.h"
  23. #include "sentence.h"
  24. #include "engine/IEngineSound.h"
  25. #include "c_tf_player.h"
  26. #include "tier2/renderutils.h"
  27. #include "bone_setup.h"
  28. #include "halloween/tf_weapon_spellbook.h"
  29. #include "matsys_controls/matsyscontrols.h"
  30. // memdbgon must be the last include file in a .cpp file!!!
  31. #include <tier0/memdbgon.h>
  32. DECLARE_BUILD_FACTORY( CTFPlayerModelPanel );
  33. char g_szSceneTmpName[256];
  34. static bool IsTauntItem( GameItemDefinition_t *pItemDef, const int iTeam, const int iClass, const char **ppSequence = NULL, const char **ppRequiredItem = NULL, const char **ppScene = NULL )
  35. {
  36. CTFTauntInfo *pTauntData = pItemDef->GetTauntData();
  37. if ( pTauntData )
  38. {
  39. if ( ppScene )
  40. {
  41. int iTauntIndex = RandomInt( 0, pTauntData->GetIntroSceneCount( iClass ) - 1 );
  42. *ppScene = pTauntData->GetIntroScene( iClass, iTauntIndex );
  43. }
  44. if ( ppRequiredItem )
  45. {
  46. *ppRequiredItem = pTauntData->GetProp( iClass );
  47. }
  48. return true;
  49. }
  50. for ( int i=0; i<pItemDef->GetNumAnimations( iTeam ); ++i )
  51. {
  52. animation_on_wearable_t* pAnim = pItemDef->GetAnimationData( iTeam, i );
  53. if ( pAnim && pAnim->pszActivity && !Q_stricmp( pAnim->pszActivity, "taunt_concept" ) )
  54. {
  55. // If we have a scene, use it first
  56. const char *pszScene = pAnim->pszScene;
  57. if ( pszScene && (iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS) )
  58. {
  59. if ( ppScene )
  60. {
  61. Q_snprintf( g_szSceneTmpName, sizeof(g_szSceneTmpName), "scenes/player/%s/low/%s", g_aPlayerClassNames_NonLocalized[iClass], pszScene );
  62. *ppScene = g_szSceneTmpName;
  63. }
  64. }
  65. const char *pszSequence = pAnim->pszSequence;
  66. if ( pszSequence )
  67. {
  68. if ( ppSequence )
  69. {
  70. *ppSequence = pszSequence;
  71. }
  72. if ( ppRequiredItem )
  73. {
  74. *ppRequiredItem = pAnim->pszRequiredItem;
  75. }
  76. }
  77. return true;
  78. }
  79. }
  80. return false;
  81. }
  82. //-----------------------------------------------------------------------------
  83. // Purpose:
  84. //-----------------------------------------------------------------------------
  85. CTFPlayerModelPanel::CTFPlayerModelPanel( vgui::Panel *pParent, const char *pName ) : BaseClass( pParent, pName ),
  86. m_LocalToGlobal( 0, 0, FlexSettingLessFunc )
  87. {
  88. m_iCurrentClassIndex = TF_CLASS_UNDEFINED;
  89. m_iCurrentSlotIndex = -1;
  90. m_nBody = 0;
  91. m_pHeldItem = NULL;
  92. m_iTeam = TF_TEAM_RED;
  93. m_bZoomedToHead = false;
  94. m_pszVCD = NULL;
  95. m_pszWeaponEntityRequired = NULL;
  96. m_bLoopVCD = true;
  97. m_bVCDFileNameOnly = true;
  98. InitPhonemeMappings();
  99. m_pScene = NULL;
  100. ClearScene();
  101. memset( m_flexWeight, 0, sizeof( m_flexWeight ) );
  102. SetIgnoreDoubleClick( true );
  103. for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
  104. {
  105. m_aParticleSystems[i] = NULL;
  106. }
  107. m_bPlaySparks = false;
  108. m_pszEyeGlowParticleName[0] = '\0';
  109. m_bDrawActionSlotEffects = false;
  110. m_bDrawTauntParticles = false;
  111. m_bIsRobot = false;
  112. }
  113. //-----------------------------------------------------------------------------
  114. // Purpose:
  115. //-----------------------------------------------------------------------------
  116. CTFPlayerModelPanel::~CTFPlayerModelPanel( void )
  117. {
  118. m_vecItemsLoaded.PurgeAndDeleteElements();
  119. m_ItemsToCarry.PurgeAndDeleteElements();
  120. for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
  121. {
  122. SafeDeleteParticleData( &m_aParticleSystems[i] );
  123. }
  124. }
  125. //-----------------------------------------------------------------------------
  126. // Purpose:
  127. //-----------------------------------------------------------------------------
  128. void CTFPlayerModelPanel::ApplySettings( KeyValues *inResourceData )
  129. {
  130. BaseClass::ApplySettings( inResourceData );
  131. m_angPlayerOrg = m_angPlayer;
  132. static ConVarRef cl_hud_minmode( "cl_hud_minmode", true );
  133. if ( cl_hud_minmode.IsValid() && cl_hud_minmode.GetBool() )
  134. {
  135. inResourceData->ProcessResolutionKeys( "_minmode" );
  136. }
  137. // custom class data
  138. m_customClassData.Purge();
  139. KeyValues *pCustomData = inResourceData->FindKey( "customclassdata" );
  140. if ( pCustomData )
  141. {
  142. for ( KeyValues *pData = pCustomData->GetFirstSubKey(); pData != NULL; pData = pData->GetNextKey() )
  143. {
  144. CustomClassData_t data;
  145. data.m_flFOV = pData->GetFloat( "fov" );
  146. data.m_vPosition.Init( pData->GetFloat( "origin_x" ), pData->GetFloat( "origin_y" ), pData->GetFloat( "origin_z" ) );
  147. data.m_vAngles.Init( pData->GetFloat( "angles_x" ), pData->GetFloat( "angles_y" ), pData->GetFloat( "angles_z" ) );
  148. m_customClassData.AddToTail( data );
  149. }
  150. Assert( m_customClassData.Count() == TF_LAST_NORMAL_CLASS );
  151. }
  152. // always allow particle for this panel
  153. m_bUseParticle = true;
  154. }
  155. //-----------------------------------------------------------------------------
  156. // Purpose:
  157. //-----------------------------------------------------------------------------
  158. void CTFPlayerModelPanel::SetToPlayerClass( int iClass, bool bIsRobot, bool bForceRefresh /*= false*/ )
  159. {
  160. if ( m_bIsRobot != bIsRobot )
  161. {
  162. bForceRefresh = true;
  163. }
  164. m_bIsRobot = bIsRobot;
  165. if ( m_iCurrentClassIndex == iClass && !bForceRefresh )
  166. return;
  167. if ( m_bZoomedToHead )
  168. {
  169. ToggleZoom();
  170. }
  171. m_iCurrentClassIndex = iClass;
  172. ClearScene();
  173. if ( IsValidTFPlayerClass( m_iCurrentClassIndex ) )
  174. {
  175. if ( bIsRobot )
  176. {
  177. SetMDL( g_szPlayerRobotModels[ m_iCurrentClassIndex ] );
  178. }
  179. else
  180. {
  181. TFPlayerClassData_t *pData = GetPlayerClassData( m_iCurrentClassIndex );
  182. SetMDL( pData->GetModelName() );
  183. }
  184. HoldFirstValidItem();
  185. // set custom class data
  186. if ( m_customClassData.IsValidIndex( m_iCurrentClassIndex ) )
  187. {
  188. SetCameraFOV( m_customClassData[m_iCurrentClassIndex].m_flFOV );
  189. m_vecPlayerPos = m_customClassData[m_iCurrentClassIndex].m_vPosition;
  190. m_angPlayer = m_customClassData[m_iCurrentClassIndex].m_vAngles;
  191. }
  192. else
  193. {
  194. m_angPlayer = m_angPlayerOrg;
  195. }
  196. }
  197. else
  198. {
  199. SetMDL( MDLHANDLE_INVALID );
  200. RemoveAdditionalModels();
  201. }
  202. InitPhonemeMappings();
  203. SetTeam( TF_TEAM_RED );
  204. m_nBody = 0;
  205. }
  206. //-----------------------------------------------------------------------------
  207. // Purpose:
  208. //-----------------------------------------------------------------------------
  209. void CTFPlayerModelPanel::HoldFirstValidItem( void )
  210. {
  211. RemoveAdditionalModels();
  212. if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
  213. return;
  214. int iDesiredSlot = -1;
  215. FOR_EACH_VEC( m_ItemsToCarry, i )
  216. {
  217. CEconItemView *pItem = m_ItemsToCarry[i];
  218. bool bIsTauntItem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
  219. if ( !bIsTauntItem )
  220. {
  221. if ( pItem->GetStaticData()->IsAWearable() )
  222. continue;
  223. if ( pItem->GetAnimationSlot() == -2 )
  224. continue;
  225. }
  226. // Found a weapon. Wield it.
  227. iDesiredSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
  228. break;
  229. }
  230. if ( iDesiredSlot != -1 )
  231. {
  232. UpdateHeldItem( iDesiredSlot );
  233. return;
  234. }
  235. // If we didn't find a weapon to wield, we wield the class's base primary weapon
  236. CEconItemView *pItem = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, LOADOUT_POSITION_PRIMARY );
  237. if ( !pItem || !pItem->IsValid() )
  238. {
  239. // Some classes only have secondary weapons. Fall back to that.
  240. pItem = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, LOADOUT_POSITION_SECONDARY );
  241. }
  242. if ( pItem && pItem->IsValid() )
  243. {
  244. SwitchHeldItemTo( pItem );
  245. }
  246. }
  247. //-----------------------------------------------------------------------------
  248. // Purpose:
  249. //-----------------------------------------------------------------------------
  250. bool CTFPlayerModelPanel::HoldItemInSlot( int iSlot )
  251. {
  252. if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
  253. return false;
  254. return UpdateHeldItem( iSlot );
  255. }
  256. //-----------------------------------------------------------------------------
  257. // Purpose:
  258. //-----------------------------------------------------------------------------
  259. bool CTFPlayerModelPanel::HoldItem( int iItemNumber )
  260. {
  261. if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
  262. return false;
  263. if ( iItemNumber >= m_ItemsToCarry.Count() )
  264. return false;
  265. CEconItemView *pItem = m_ItemsToCarry[iItemNumber];
  266. bool bIsTauntitem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
  267. // Ignore requests to equip wearables, because they're always equipped
  268. // Also ignore requests to equip non-wearables that are never actively equipped
  269. if ( bIsTauntitem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) )
  270. {
  271. SwitchHeldItemTo( pItem );
  272. return true;
  273. }
  274. // If we were trying to switch to a new item, and it's not valid, stick to our current
  275. if ( pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ) != m_iCurrentSlotIndex )
  276. {
  277. UpdateHeldItem( m_iCurrentSlotIndex );
  278. return false;
  279. }
  280. // We were trying to stay on the current weapon, and it's not valid. Find anything.
  281. HoldFirstValidItem();
  282. return false;
  283. }
  284. //-----------------------------------------------------------------------------
  285. // Purpose:
  286. //-----------------------------------------------------------------------------
  287. bool CTFPlayerModelPanel::UpdateHeldItem( int iDesiredSlot )
  288. {
  289. m_pHeldItem = NULL;
  290. CEconItemView *pItem = GetItemInSlot( iDesiredSlot );
  291. if ( pItem )
  292. {
  293. bool bIsTauntItem = IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex );
  294. // Ignore requests to equip wearables, because they're always equipped
  295. // Also ignore requests to equip non-wearables that are never actively equipped
  296. if ( bIsTauntItem || ( !pItem->GetStaticData()->IsAWearable() && pItem->GetAnimationSlot() != -2 ) )
  297. {
  298. SwitchHeldItemTo( pItem );
  299. m_pHeldItem = pItem;
  300. return true;
  301. }
  302. }
  303. // If we were trying to switch to a new item, and it's not valid, stick to our current
  304. if ( iDesiredSlot != m_iCurrentSlotIndex )
  305. {
  306. UpdateHeldItem( m_iCurrentSlotIndex );
  307. return false;
  308. }
  309. // We were trying to stay on the current weapon, and it's not valid. Find anything.
  310. HoldFirstValidItem();
  311. return false;
  312. }
  313. //-----------------------------------------------------------------------------
  314. // Purpose:
  315. //-----------------------------------------------------------------------------
  316. void CTFPlayerModelPanel::ClearScene( void )
  317. {
  318. if ( m_pScene )
  319. {
  320. delete m_pScene;
  321. }
  322. m_pScene = NULL;
  323. m_flSceneTime = 0;
  324. m_flSceneEndTime = 0;
  325. m_flLastTickTime = 0;
  326. m_bLoopScene = true;
  327. //memset( m_flexWeight, 0, sizeof( m_flexWeight ) );
  328. }
  329. extern CChoreoStringPool g_ChoreoStringPool;
  330. CChoreoScene *LoadSceneForModel( const char *filename, IChoreoEventCallback *pCallback, float *flSceneEndTime )
  331. {
  332. char loadfile[ 512 ];
  333. V_strcpy_safe( loadfile, filename );
  334. V_SetExtension( loadfile, ".vcd", sizeof( loadfile ) );
  335. V_FixSlashes( loadfile );
  336. char *pBuffer = NULL;
  337. size_t bufsize = scenefilecache->GetSceneBufferSize( loadfile );
  338. if ( bufsize <= 0 )
  339. return NULL;
  340. pBuffer = new char[ bufsize ];
  341. if ( !scenefilecache->GetSceneData( filename, (byte *)pBuffer, bufsize ) )
  342. {
  343. delete[] pBuffer;
  344. return NULL;
  345. }
  346. CChoreoScene *pScene;
  347. if ( IsBufferBinaryVCD( pBuffer, bufsize ) )
  348. {
  349. pScene = new CChoreoScene( pCallback );
  350. CUtlBuffer buf( pBuffer, bufsize, CUtlBuffer::READ_ONLY );
  351. if ( !pScene->RestoreFromBinaryBuffer( buf, loadfile, &g_ChoreoStringPool ) )
  352. {
  353. Warning( "Unable to restore binary scene '%s'\n", loadfile );
  354. delete pScene;
  355. pScene = NULL;
  356. }
  357. else
  358. {
  359. pScene->SetPrintFunc( Scene_Printf );
  360. pScene->SetEventCallbackInterface( pCallback );
  361. }
  362. }
  363. else
  364. {
  365. g_TokenProcessor.SetBuffer( pBuffer );
  366. pScene = ChoreoLoadScene( loadfile, pCallback, &g_TokenProcessor, Scene_Printf );
  367. }
  368. delete[] pBuffer;
  369. if ( flSceneEndTime != NULL )
  370. {
  371. // find the scene length
  372. // The scene is as long as the end point for the last event unless one of the events is a loop
  373. *flSceneEndTime = 0.0f;
  374. bool bSetEndTime = false;
  375. for ( int i = 0; i < pScene->GetNumEvents(); i++ )
  376. {
  377. CChoreoEvent *pEvent = pScene->GetEvent( i );
  378. if ( pEvent->GetType() == CChoreoEvent::LOOP )
  379. {
  380. *flSceneEndTime = -1.0f;
  381. bSetEndTime = false;
  382. break;
  383. }
  384. if ( pEvent->GetEndTime() > *flSceneEndTime )
  385. {
  386. *flSceneEndTime = pEvent->GetEndTime();
  387. bSetEndTime = true;
  388. }
  389. }
  390. if ( bSetEndTime )
  391. {
  392. *flSceneEndTime += 0.1f; // give time for lerp to idle pose
  393. }
  394. }
  395. return pScene;
  396. }
  397. //-----------------------------------------------------------------------------
  398. // Purpose:
  399. //-----------------------------------------------------------------------------
  400. void CTFPlayerModelPanel::PlayVCD( const char *pszVCD, const char *pszWeaponEntityRequired /*= NULL*/, bool bLoopVCD /*= true*/, bool bFileNameOnly /*= true*/ )
  401. {
  402. m_pszVCD = pszVCD;
  403. m_pszWeaponEntityRequired = pszWeaponEntityRequired;
  404. m_bLoopVCD = bLoopVCD;
  405. m_bVCDFileNameOnly = bFileNameOnly;
  406. }
  407. //-----------------------------------------------------------------------------
  408. // Purpose:
  409. //-----------------------------------------------------------------------------
  410. void CTFPlayerModelPanel::FireEvent( const char *pszEventName, const char *pszEventOptions )
  411. {
  412. //Plat_DebugString( CFmtStr( "********* ANIM EVENT: %s\n", pszEventName ) );
  413. if ( V_strcmp( pszEventName, "AE_WPN_HIDE" ) == 0 )
  414. {
  415. int nWeaponIndex = GetMergeMDLIndex( static_cast<IClientRenderable*>(m_pHeldItem) );
  416. if ( nWeaponIndex >= 0 )
  417. {
  418. m_aMergeMDLs[nWeaponIndex].m_bDisabled = true;
  419. }
  420. }
  421. else if ( V_strcmp( pszEventName, "AE_WPN_UNHIDE" ) == 0 )
  422. {
  423. int nWeaponIndex = GetMergeMDLIndex( static_cast<IClientRenderable*>(m_pHeldItem) );
  424. if ( nWeaponIndex >= 0 )
  425. {
  426. m_aMergeMDLs[nWeaponIndex].m_bDisabled = false;
  427. }
  428. }
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Purpose:
  432. //-----------------------------------------------------------------------------
  433. void CTFPlayerModelPanel::SwitchHeldItemTo( CEconItemView *pItem )
  434. {
  435. m_nBody = 0;
  436. ClearScene();
  437. // Clear out visible items, and re-equip out wearables
  438. RemoveAdditionalModels();
  439. EquipAllWearables( pItem );
  440. // Then equip the held item
  441. EquipItem( pItem );
  442. m_iCurrentSlotIndex = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
  443. m_pHeldItem = pItem;
  444. m_StatTrackModel.m_bDisabled = true;
  445. m_StatTrackModel.m_MDL.SetMDL( MDLHANDLE_INVALID );
  446. CAttribute_String attrModule;
  447. static CSchemaAttributeDefHandle pAttr_module( "weapon_uses_stattrak_module" );
  448. if ( m_pHeldItem->FindAttribute( pAttr_module, &attrModule ) && attrModule.has_value() )
  449. {
  450. // Allow for already strange items
  451. bool bIsStrange = false;
  452. if ( m_pHeldItem->GetQuality() == AE_STRANGE )
  453. {
  454. bIsStrange = true;
  455. }
  456. if ( !bIsStrange )
  457. {
  458. // Go over the attributes of the item, if it has any strange attributes the item is strange and don't apply
  459. for ( int i = 0; i < GetKillEaterAttrCount(); i++ )
  460. {
  461. if ( m_pHeldItem->FindAttribute( GetKillEaterAttr_Score( i ) ) )
  462. {
  463. bIsStrange = true;
  464. break;
  465. }
  466. }
  467. }
  468. if ( bIsStrange )
  469. {
  470. static CSchemaAttributeDefHandle pAttr_moduleScale( "weapon_stattrak_module_scale" );
  471. // Does it have a stat track module
  472. m_flStatTrackScale = 1.0f;
  473. uint32 unFloatAsUint32 = 1;
  474. if ( m_pHeldItem->FindAttribute( pAttr_moduleScale, &unFloatAsUint32 ) )
  475. {
  476. m_flStatTrackScale = (float&)unFloatAsUint32;
  477. }
  478. MDLHandle_t hStatTrackMDL = mdlcache->FindMDL( "models/weapons/c_models/stattrack.mdl" );
  479. if ( mdlcache->IsErrorModel( hStatTrackMDL ) )
  480. {
  481. hStatTrackMDL = MDLHANDLE_INVALID;
  482. }
  483. m_StatTrackModel.m_MDL.SetMDL( hStatTrackMDL );
  484. mdlcache->Release( hStatTrackMDL ); // counterbalance addref from within FindMDL
  485. m_StatTrackModel.m_MDL.m_pProxyData = static_cast<IClientRenderable*>(pItem);
  486. m_StatTrackModel.m_bDisabled = false;
  487. m_StatTrackModel.m_MDL.m_nSequence = ACT_IDLE;
  488. SetIdentityMatrix( m_StatTrackModel.m_MDLToWorld );
  489. }
  490. }
  491. SetSequenceLayers( NULL, 0 );
  492. // See if our VCD is overridden
  493. if ( m_pszVCD )
  494. {
  495. // Make sure we're holding the weapon, if it's required
  496. bool bCanRunScene = true;
  497. if ( m_pszWeaponEntityRequired && *m_pszWeaponEntityRequired )
  498. {
  499. bCanRunScene = false;
  500. if ( pItem && pItem->IsValid() )
  501. {
  502. const char *pszClassName = pItem->GetStaticData()->GetItemClass();
  503. if ( pszClassName && *pszClassName )
  504. {
  505. bCanRunScene = V_stricmp( pszClassName, m_pszWeaponEntityRequired ) == 0;
  506. }
  507. }
  508. }
  509. if ( bCanRunScene )
  510. {
  511. if ( m_bVCDFileNameOnly )
  512. {
  513. // auto complete relative path for the vcd file
  514. V_sprintf_safe( g_szSceneTmpName, "scenes/player/%s/low/%s", g_aPlayerClassNames_NonLocalized[m_iCurrentClassIndex], m_pszVCD );
  515. }
  516. else
  517. {
  518. // m_pszVCD should be a valid relative path
  519. V_strcpy_safe( g_szSceneTmpName, m_pszVCD );
  520. }
  521. m_pScene = LoadSceneForModel( g_szSceneTmpName, this, &m_flSceneEndTime );
  522. m_bLoopScene = m_bLoopVCD;
  523. return;
  524. }
  525. }
  526. const char *pScene = NULL;
  527. const char *pSequence = NULL;
  528. const char *pRequiredItem = NULL;
  529. bool bRemoveTauntParticles = true;
  530. if ( IsTauntItem( pItem->GetStaticData(), GetTeam(), m_iCurrentClassIndex, &pSequence, &pRequiredItem, &pScene ) )
  531. {
  532. MDLCACHE_CRITICAL_SECTION();
  533. if ( pScene )
  534. {
  535. m_pScene = LoadSceneForModel( pScene, this, &m_flSceneEndTime );
  536. // load custom prop for taunt
  537. const char *pszProp = pItem->GetStaticData()->GetTauntData()->GetProp( m_iCurrentClassIndex );
  538. if ( pszProp )
  539. {
  540. LoadAndAttachAdditionalModel( pszProp, pItem );
  541. }
  542. // force taunt to equip certain slot
  543. static CSchemaAttributeDefHandle pAttrDef_TauntForceWeaponSlot( "taunt force weapon slot" );
  544. const char* pszTauntForceWeaponSlotName = NULL;
  545. if ( FindAttribute_UnsafeBitwiseCast<CAttribute_String>( pItem, pAttrDef_TauntForceWeaponSlot, &pszTauntForceWeaponSlotName ) )
  546. {
  547. int iForceWeaponSlot = StringFieldToInt( pszTauntForceWeaponSlotName, GetItemSchema()->GetWeaponTypeSubstrings() );
  548. EquipRequiredLoadoutSlot( iForceWeaponSlot );
  549. }
  550. }
  551. else
  552. {
  553. ClearScene();
  554. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  555. int iSequence = LookupSequence( &studioHdr, pSequence );
  556. if ( iSequence >= 0 )
  557. {
  558. // does a weapon need to be equipped?
  559. loadout_positions_t requiredLoadoutItem = LOADOUT_POSITION_INVALID;
  560. if ( pRequiredItem )
  561. {
  562. requiredLoadoutItem = (loadout_positions_t)StringFieldToInt( pRequiredItem, ItemSystem()->GetItemSchema()->GetLoadoutStrings( pItem->GetItemDefinition()->GetEquipType() ) );
  563. }
  564. EquipRequiredLoadoutSlot( requiredLoadoutItem );
  565. // finally, set the sequence layers
  566. MDLSquenceLayer_t tmpSequenceLayers[1];
  567. tmpSequenceLayers[0].m_nSequenceIndex = iSequence;
  568. tmpSequenceLayers[0].m_flWeight = 1.0;
  569. tmpSequenceLayers[0].m_bNoLoop = false;
  570. tmpSequenceLayers[0].m_flCycleBeganAt = m_RootMDL.m_MDL.m_flTime;
  571. SetSequenceLayers( tmpSequenceLayers, 1 );
  572. }
  573. }
  574. // Taunt Particles
  575. static CSchemaAttributeDefHandle pAttrDef_OnTauntAttachParticleIndex( "on taunt attach particle index" );
  576. uint32 unUnusualEffectIndex = 0;
  577. if ( pItem->FindAttribute( pAttrDef_OnTauntAttachParticleIndex, &unUnusualEffectIndex ) && unUnusualEffectIndex > 0 )
  578. {
  579. const attachedparticlesystem_t *pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( unUnusualEffectIndex );
  580. if ( pParticleSystem )
  581. {
  582. SafeDeleteParticleData( &m_aParticleSystems[ SYSTEM_TAUNT ] );
  583. m_aParticleSystems[ SYSTEM_TAUNT ] = CreateParticleData( pParticleSystem->pszSystemName );
  584. m_flTauntParticleRefireTime = gpGlobals->curtime + pParticleSystem->fRefireTime;
  585. m_flTauntParticleRefireRate = pParticleSystem->fRefireTime;
  586. m_bDrawTauntParticles = true;
  587. bRemoveTauntParticles = false;
  588. }
  589. }
  590. }
  591. // Clear out taunt particles
  592. if ( bRemoveTauntParticles && m_aParticleSystems[SYSTEM_TAUNT] )
  593. {
  594. m_bDrawTauntParticles = false;
  595. SafeDeleteParticleData( &m_aParticleSystems[SYSTEM_TAUNT] );
  596. }
  597. // Check if it has a PoseParameter Attributes (r_hand_grip)
  598. float flPose = 0;
  599. static CSchemaAttributeDefHandle pAttrDef_RightHandPose( "righthand pose parameter" );
  600. uint32 iValue = 0;
  601. if ( pItem->FindAttribute( pAttrDef_RightHandPose, &iValue ) )
  602. {
  603. flPose = (float&)iValue;
  604. }
  605. SetPoseParameterByName( "r_hand_grip", flPose );
  606. // Check for hand particles (spell book)
  607. // always nuke
  608. if ( m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
  609. {
  610. SafeDeleteParticleData( &m_aParticleSystems[ SYSTEM_ACTIONSLOT ] );
  611. }
  612. m_bDrawActionSlotEffects = false;
  613. if ( pItem->GetStaticData()->GetItemClass() )
  614. {
  615. m_bDrawActionSlotEffects = FStrEq( pItem->GetStaticData()->GetItemClass(), "tf_weapon_spellbook" );
  616. }
  617. // update eyeglows
  618. m_bUpdateEyeGlows = true;
  619. }
  620. //-----------------------------------------------------------------------------
  621. // Purpose:
  622. //-----------------------------------------------------------------------------
  623. void CTFPlayerModelPanel::EquipRequiredLoadoutSlot( int iRequiredLoadoutSlot )
  624. {
  625. if ( iRequiredLoadoutSlot != LOADOUT_POSITION_INVALID )
  626. {
  627. int iDesiredSlot = -1;
  628. FOR_EACH_VEC( m_ItemsToCarry, i )
  629. {
  630. CEconItemView *pItem = m_ItemsToCarry[i];
  631. if ( pItem->GetStaticData()->IsAWearable() )
  632. continue;
  633. if ( pItem->GetAnimationSlot() == -2 )
  634. continue;
  635. // Found a weapon. Wield it.
  636. if ( iRequiredLoadoutSlot == pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex ) )
  637. {
  638. iDesiredSlot = i;
  639. break;
  640. }
  641. }
  642. if ( iDesiredSlot >= 0 )
  643. {
  644. EquipItem( m_ItemsToCarry[iDesiredSlot] );
  645. }
  646. else
  647. {
  648. // If we didn't find a weapon in the appropriate slot, get the base item
  649. CEconItemView *pWeapon = TFInventoryManager()->GetBaseItemForClass( m_iCurrentClassIndex, iRequiredLoadoutSlot );
  650. if ( pWeapon && pWeapon->IsValid() )
  651. {
  652. EquipItem( pWeapon );
  653. }
  654. }
  655. }
  656. }
  657. //-----------------------------------------------------------------------------
  658. // Purpose:
  659. //-----------------------------------------------------------------------------
  660. void CTFPlayerModelPanel::UpdateWeaponBodygroups( bool bModifyDeployedOnlyBodygroups )
  661. {
  662. for ( int i=0; i<MAX_WEAPON_SLOTS; i++ )
  663. {
  664. CEconItemView *pItem = GetItemInSlot( i );
  665. if ( !pItem )
  666. continue;
  667. if ( pItem->GetStaticData()->GetHideBodyGroupsDeployedOnly() != bModifyDeployedOnlyBodygroups )
  668. continue;
  669. if ( !(m_pHeldItem == pItem || !pItem->GetStaticData()->GetHideBodyGroupsDeployedOnly()) )
  670. continue;
  671. UpdateHiddenBodyGroups( pItem );
  672. }
  673. }
  674. //-----------------------------------------------------------------------------
  675. // Purpose:
  676. //-----------------------------------------------------------------------------
  677. void CTFPlayerModelPanel::UpdateHiddenBodyGroups( CEconItemView* pItem )
  678. {
  679. MDLCACHE_CRITICAL_SECTION();
  680. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  681. int iNumBodyGroups = pItem->GetStaticData()->GetNumModifiedBodyGroups( 0 );
  682. for ( int i=0; i<iNumBodyGroups; ++i )
  683. {
  684. int iState = 0;
  685. const char *pszBodyGroup = pItem->GetStaticData()->GetModifiedBodyGroup( 0, i, iState );
  686. int iBodyGroup = FindBodygroupByName( &studioHdr, pszBodyGroup );
  687. if ( iBodyGroup == -1 )
  688. continue;
  689. ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, iState );
  690. SetBody( m_nBody );
  691. }
  692. // Handle style-based bodygroups
  693. const CEconItemDefinition *pItemDef = pItem->GetItemDefinition();
  694. const CEconStyleInfo *pStyle = pItemDef ? pItemDef->GetStyleInfo( pItem->GetStyle() ) : NULL;
  695. if ( pStyle )
  696. {
  697. FOR_EACH_VEC( pStyle->GetAdditionalHideBodygroups(), i )
  698. {
  699. int iBodyGroup = FindBodygroupByName( &studioHdr, pStyle->GetAdditionalHideBodygroups()[i] );
  700. if ( iBodyGroup == -1 )
  701. continue;
  702. ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, 1 ); // force state to '1' here to mean hidden
  703. SetBody( m_nBody );
  704. }
  705. }
  706. // Handle world model bodygroup overrides
  707. int iBodyOverride = pItem->GetStaticData()->GetWorldmodelBodygroupOverride( m_iTeam );
  708. int iBodyStateOverride = pItem->GetStaticData()->GetWorldmodelBodygroupStateOverride( m_iTeam );
  709. if ( iBodyOverride > -1 && iBodyStateOverride > -1 )
  710. {
  711. ::SetBodygroup( &studioHdr, m_nBody, iBodyOverride, iBodyStateOverride );
  712. }
  713. }
  714. //-----------------------------------------------------------------------------
  715. // Purpose:
  716. //-----------------------------------------------------------------------------
  717. CEconItemView *CTFPlayerModelPanel::GetItemInSlot( int iSlot )
  718. {
  719. CEconItemView *pOwnedItemInSlot = TFInventoryManager()->GetItemInLoadoutForClass( m_iCurrentClassIndex, iSlot );
  720. FOR_EACH_VEC( m_ItemsToCarry, i )
  721. {
  722. CEconItemView *pItem = m_ItemsToCarry[i];
  723. int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
  724. if ( iSlot == iLoadoutSlot )
  725. return pItem;
  726. // GetLoadoutSlot will not work for misc2, taunt2-8 because it will always return misc/taunt
  727. if ( pOwnedItemInSlot && pOwnedItemInSlot->GetItemDefIndex() == pItem->GetItemDefIndex() )
  728. return pItem;
  729. }
  730. return NULL;
  731. }
  732. //-----------------------------------------------------------------------------
  733. // Purpose:
  734. //-----------------------------------------------------------------------------
  735. void CTFPlayerModelPanel::EquipAllWearables( CEconItemView *pHeldItem )
  736. {
  737. // First, reset all our bodygroups
  738. MDLCACHE_CRITICAL_SECTION();
  739. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  740. const CEconItemSchema::BodygroupStateMap_t& mapBodygroupState = GetItemSchema()->GetDefaultBodygroupStateMap();
  741. FOR_EACH_MAP_FAST( mapBodygroupState, i )
  742. {
  743. const char *pszBodygroupName = mapBodygroupState.Key(i);
  744. int iBodyGroup = FindBodygroupByName( &studioHdr, pszBodygroupName );
  745. if ( iBodyGroup > -1 )
  746. {
  747. int iState = mapBodygroupState[i];
  748. ::SetBodygroup( &studioHdr, m_nBody, iBodyGroup, iState );
  749. }
  750. }
  751. SetBody( m_nBody );
  752. UpdateWeaponBodygroups( false );
  753. // Now equip each of our wearables
  754. FOR_EACH_VEC( m_ItemsToCarry, i )
  755. {
  756. CEconItemView *pItem = m_ItemsToCarry[i];
  757. // If it's a wearable item, we put it on.
  758. if ( pItem->GetStaticData()->IsAWearable() )
  759. {
  760. EquipItem( pItem );
  761. }
  762. // Then see if there's an extra wearable we need to attach for this item
  763. const char *pszAttached = pItem->GetExtraWearableModel();
  764. if ( pszAttached && pszAttached[ 0 ] )
  765. {
  766. const char *pszViewModelAttached = pItem->GetExtraWearableViewModel();
  767. if ( pHeldItem == pItem || pszViewModelAttached == NULL || pszViewModelAttached[ 0 ] == '\0' || pszViewModelAttached[ 0 ] == '?' )
  768. {
  769. LoadAndAttachAdditionalModel( pszAttached, pItem );
  770. }
  771. }
  772. }
  773. UpdateWeaponBodygroups( true );
  774. SetBody( m_nBody );
  775. UpdatePreviewVisuals();
  776. }
  777. //-----------------------------------------------------------------------------
  778. // Purpose:
  779. //-----------------------------------------------------------------------------
  780. void CTFPlayerModelPanel::EquipItem( CEconItemView *pItem )
  781. {
  782. if ( m_iCurrentClassIndex == TF_CLASS_UNDEFINED )
  783. return;
  784. const GameItemDefinition_t *pItemDef = pItem->GetItemDefinition();
  785. Assert( pItemDef );
  786. // Change team number so skins composite correctly
  787. pItem->SetTeamNumber( m_iTeam );
  788. // Non wearables can modify the animation
  789. if ( !pItemDef->IsAWearable() )
  790. {
  791. int iAnimSlot = pItem->GetAnimationSlot();
  792. // Ignore items that don't want to control player animation
  793. if ( iAnimSlot == -2 )
  794. return;
  795. if ( iAnimSlot == -1 )
  796. {
  797. iAnimSlot = pItemDef->GetLoadoutSlot( m_iCurrentClassIndex );
  798. }
  799. const CUtlVector<const char *>& vecWeaponTypeSubstrings = GetItemSchema()->GetWeaponTypeSubstrings();
  800. if ( vecWeaponTypeSubstrings.IsValidIndex( iAnimSlot ) )
  801. {
  802. int iAnim = FindAnimByName( vecWeaponTypeSubstrings[iAnimSlot] );
  803. SetModelAnim( iAnim );
  804. }
  805. }
  806. // Attach the models for the item
  807. const char *pszAttached = pItem->GetWorldDisplayModel();
  808. if ( !pszAttached )
  809. {
  810. pszAttached = pItem->GetPlayerDisplayModel( m_iCurrentClassIndex, m_iTeam );
  811. }
  812. if ( pszAttached && pszAttached[0] )
  813. {
  814. LoadAndAttachAdditionalModel( pszAttached, pItem );
  815. int iTeam = pItemDef->GetBestVisualTeamData( m_iTeam );
  816. // Set attached models if viewable third-person.
  817. {
  818. const int iNumAttachedModels = pItemDef->GetNumAttachedModels( iTeam );
  819. for ( int i = 0; i < iNumAttachedModels; ++i )
  820. {
  821. attachedmodel_t *pModel = pItemDef->GetAttachedModelData( iTeam, i );
  822. if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) )
  823. continue;
  824. if ( !pModel->m_pszModelName )
  825. {
  826. Warning( "econ item definition '%s' attachment (team %d idx %d) has no model\n", pItemDef->GetDefinitionName(), iTeam, i );
  827. continue;
  828. }
  829. LoadAndAttachAdditionalModel( pModel->m_pszModelName, pItem );
  830. }
  831. }
  832. // Festive
  833. // Set attached models if viewable third-person.
  834. static CSchemaAttributeDefHandle pAttr_is_festivized( "is_festivized" );
  835. if ( pAttr_is_festivized && pItem->FindAttribute( pAttr_is_festivized ) )
  836. {
  837. const int iNumAttachedModels = pItemDef->GetNumAttachedModelsFestivized( iTeam );
  838. for ( int i = 0; i < iNumAttachedModels; ++i )
  839. {
  840. attachedmodel_t *pModel = pItemDef->GetAttachedModelDataFestivized( iTeam, i );
  841. if ( !( pModel->m_iModelDisplayFlags & kAttachedModelDisplayFlag_WorldModel ) )
  842. continue;
  843. if ( !pModel->m_pszModelName )
  844. {
  845. Warning( "econ item definition '%s' attachment (team %d idx %d) has no model\n", pItemDef->GetDefinitionName(), iTeam, i );
  846. continue;
  847. }
  848. LoadAndAttachAdditionalModel( pModel->m_pszModelName, pItem );
  849. }
  850. }
  851. }
  852. // Hide any item associated groups.
  853. UpdateHiddenBodyGroups( pItem );
  854. }
  855. //-----------------------------------------------------------------------------
  856. // Purpose:
  857. //-----------------------------------------------------------------------------
  858. int CTFPlayerModelPanel::AddCarriedItem( CEconItemView *pItem )
  859. {
  860. CEconItemView *pNewItem = new CEconItemView;
  861. *pNewItem = *pItem;
  862. int iIdx = m_ItemsToCarry.AddToTail( pNewItem );
  863. // This is a terrible hack. If we have team paint, we need an entity to find out what team
  864. // we're on, but in this panel we don't have one. Instead, we force a flag all the way through
  865. // the system on the CEconItemView so that the low-level paint code can pull from it if necessary.
  866. if ( GetTeam() == TF_TEAM_BLUE )
  867. {
  868. pNewItem->SetClientItemFlags( kEconItemFlagClient_ForceBlueTeam );
  869. }
  870. return iIdx;
  871. }
  872. //-----------------------------------------------------------------------------
  873. // Purpose:
  874. //-----------------------------------------------------------------------------
  875. void CTFPlayerModelPanel::ClearCarriedItems( void )
  876. {
  877. RemoveAdditionalModels();
  878. m_ItemsToCarry.PurgeAndDeleteElements();
  879. m_pHeldItem = NULL;
  880. }
  881. //-----------------------------------------------------------------------------
  882. // Purpose:
  883. //-----------------------------------------------------------------------------
  884. void CTFPlayerModelPanel::RemoveAdditionalModels( void )
  885. {
  886. ClearMergeMDLs();
  887. // Unregister for all callbacks
  888. modelinfo->UnregisterModelLoadCallback( -1, this );
  889. m_vecDynamicAssetsLoaded.Purge();
  890. m_vecItemsLoaded.PurgeAndDeleteElements();
  891. }
  892. //-----------------------------------------------------------------------------
  893. // Purpose:
  894. //-----------------------------------------------------------------------------
  895. void CTFPlayerModelPanel::LoadAndAttachAdditionalModel( const char *pMDLName, CEconItemView *pItem )
  896. {
  897. int nModelIndex = -1;
  898. if ( pItem->GetStaticData()->IsContentStreamable() )
  899. {
  900. // Get the client-only dynamic model index. The auto-addref
  901. // of vecDynamicAssetsLoaded will actually trigger the load.
  902. nModelIndex = modelinfo->RegisterDynamicModel( pMDLName, true );
  903. // Dynamic models never fail to register in this engine.
  904. Assert( nModelIndex != -1 );
  905. }
  906. else
  907. {
  908. // Is the (non-streamable) model already precached? If so, use it.
  909. nModelIndex = modelinfo->GetModelIndex( pMDLName );
  910. }
  911. if ( nModelIndex == -1 )
  912. {
  913. MDLHandle_t hMDL = vgui::MDLCache()->FindMDL( pMDLName );
  914. Assert( hMDL != MDLHANDLE_INVALID );
  915. if ( hMDL != MDLHANDLE_INVALID )
  916. {
  917. // Model not loaded, not dynamic. Hard load and exit out.
  918. SetMergeMDL( hMDL, static_cast<IClientRenderable*>(pItem), pItem->GetSkin( m_iTeam ) );
  919. }
  920. m_MergeMDL = hMDL;
  921. return;
  922. }
  923. CEconItemView *pClone = new CEconItemView;
  924. *pClone = *pItem;
  925. m_vecDynamicAssetsLoaded[ m_vecDynamicAssetsLoaded.AddToTail() ] = nModelIndex;
  926. m_vecItemsLoaded.AddToTail( pClone );
  927. // callback triggers immediately if not dynamic
  928. modelinfo->RegisterModelLoadCallback( nModelIndex, this, true );
  929. }
  930. //-----------------------------------------------------------------------------
  931. // Purpose:
  932. //-----------------------------------------------------------------------------
  933. static void SetMDLSkinForTeam( CMDL *pMDL, const CEconItemView *pItem, int iTeam )
  934. {
  935. Assert( pItem );
  936. if ( !pMDL )
  937. return;
  938. // Ask the item for a skin...
  939. int nSkin = pItem->GetSkin( iTeam );
  940. if ( nSkin == -1 )
  941. {
  942. // ... if not, use the team skin.
  943. nSkin = iTeam == TF_TEAM_RED ? 0 : 1;
  944. }
  945. pMDL->m_nSkin = nSkin;
  946. }
  947. //-----------------------------------------------------------------------------
  948. // Purpose:
  949. //-----------------------------------------------------------------------------
  950. void CTFPlayerModelPanel::OnModelLoadComplete( const model_t *pModel )
  951. {
  952. CEconItemView *pItem = NULL;
  953. FOR_EACH_VEC_BACK( m_vecDynamicAssetsLoaded, i )
  954. {
  955. if ( modelinfo->GetModel( m_vecDynamicAssetsLoaded[ i ] ) == pModel )
  956. {
  957. pItem = GetPreviewItem( m_vecItemsLoaded[ i ] );
  958. break;
  959. }
  960. }
  961. Assert( pItem );
  962. if ( pItem )
  963. {
  964. MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel );
  965. Assert( hMDL != MDLHANDLE_INVALID );
  966. if ( hMDL != MDLHANDLE_INVALID )
  967. {
  968. SetMergeMDL( hMDL, static_cast<IClientRenderable*>(pItem) );
  969. int nBody = 0;
  970. if ( pItem->GetStaticData()->UsesPerClassBodygroups( m_iTeam ) )
  971. {
  972. CMDL *pMDL = GetMergeMDL(hMDL);
  973. if ( pMDL )
  974. {
  975. // Classes start at 1, bodygroups at 0, so we shift them all back 1.
  976. MDLCACHE_CRITICAL_SECTION();
  977. CStudioHdr sHDR( pMDL->GetStudioHdr(), g_pMDLCache );
  978. ::SetBodygroup( &sHDR, nBody, 1, m_iCurrentClassIndex-1 );
  979. pMDL->m_nBody = nBody;
  980. }
  981. }
  982. // Set the custom skin.
  983. SetMDLSkinForTeam( GetMergeMDL( hMDL ), pItem, m_iTeam );
  984. }
  985. }
  986. }
  987. void CTFPlayerModelPanel::SetTeam( int iTeam )
  988. {
  989. m_iTeam = iTeam;
  990. UpdatePreviewVisuals();
  991. }
  992. void CTFPlayerModelPanel::UpdatePreviewVisuals()
  993. {
  994. // Assume skin will be chosen based only on the preview team
  995. int iSkin = m_iTeam == TF_TEAM_RED ? 0 : 1;
  996. // Check if any of the items we're carrying should override this
  997. static CSchemaAttributeDefHandle pAttrDef_PlayerSkinOverride( "player skin override" );
  998. Assert( pAttrDef_PlayerSkinOverride );
  999. FOR_EACH_VEC( m_ItemsToCarry, i )
  1000. {
  1001. CEconItemView *pItem = m_ItemsToCarry[i];
  1002. if ( !pItem )
  1003. continue;
  1004. float fSkinOverride = 0.0f;
  1005. if ( FindAttribute_UnsafeBitwiseCast<attrib_value_t>( pItem, pAttrDef_PlayerSkinOverride, &fSkinOverride ) && fSkinOverride == 1.0f )
  1006. {
  1007. C_TFPlayer::AdjustSkinIndexForZombie( m_iCurrentClassIndex, iSkin );
  1008. break;
  1009. }
  1010. Assert( fSkinOverride == 0.0f );
  1011. }
  1012. // Set the player model skin.
  1013. SetSkin( iSkin );
  1014. // Set the weapon's skin.
  1015. if ( m_MergeMDL && m_pHeldItem )
  1016. {
  1017. SetMDLSkinForTeam( GetMergeMDL( m_MergeMDL ), GetPreviewItem( m_pHeldItem ), m_iTeam );
  1018. }
  1019. // Set the skin for all other equipped items (wearables, etc).
  1020. for ( int i=0; i<m_vecDynamicAssetsLoaded.Count(); i++ )
  1021. {
  1022. const model_t *pModel = modelinfo->GetModel( m_vecDynamicAssetsLoaded[i] );
  1023. if ( pModel )
  1024. {
  1025. MDLHandle_t hMDL = modelinfo->GetCacheHandle( pModel );
  1026. // We're iterating over a list of the dynamic assets that we've completed streaming in, but
  1027. // we want to set the style based on the "preview item" definition if possible.
  1028. SetMDLSkinForTeam( GetMergeMDL( hMDL ), GetPreviewItem( m_vecItemsLoaded[i] ), m_iTeam );
  1029. }
  1030. }
  1031. }
  1032. CEconItemView *CTFPlayerModelPanel::GetPreviewItem( CEconItemView *pMatchItem )
  1033. {
  1034. Assert( pMatchItem );
  1035. if ( !pMatchItem )
  1036. return NULL;
  1037. FOR_EACH_VEC( m_ItemsToCarry, i )
  1038. {
  1039. CEconItemView *pItem = m_ItemsToCarry[i];
  1040. if ( *pMatchItem == *pItem )
  1041. return pItem;
  1042. }
  1043. return pMatchItem;
  1044. }
  1045. int ClassZoomZ[] =
  1046. {
  1047. 0,
  1048. 20, // TF_CLASS_SCOUT,
  1049. 25, // TF_CLASS_SNIPER,
  1050. 20, // TF_CLASS_SOLDIER,
  1051. 22, // TF_CLASS_DEMOMAN,
  1052. 30, // TF_CLASS_MEDIC,
  1053. 30, // TF_CLASS_HEAVYWEAPONS,
  1054. 22, // TF_CLASS_PYRO,
  1055. 27, // TF_CLASS_SPY,
  1056. 20, // TF_CLASS_ENGINEER,
  1057. };
  1058. //-----------------------------------------------------------------------------
  1059. // Purpose:
  1060. //-----------------------------------------------------------------------------
  1061. void CTFPlayerModelPanel::ToggleZoom()
  1062. {
  1063. m_bZoomedToHead = !m_bZoomedToHead;
  1064. // NOTE: GetZoomOffset() relies on m_bZoomedToHead being up to date
  1065. m_vecPlayerPos += GetZoomOffset();
  1066. SetModelAnglesAndPosition( m_angPlayer, m_vecPlayerPos );
  1067. }
  1068. //-----------------------------------------------------------------------------
  1069. // Purpose:
  1070. //-----------------------------------------------------------------------------
  1071. Vector CTFPlayerModelPanel::GetZoomOffset()
  1072. {
  1073. const Vector vecOffset( 100, 0, ClassZoomZ[m_iCurrentClassIndex] );
  1074. return m_bZoomedToHead ? -vecOffset : vecOffset;
  1075. }
  1076. //-----------------------------------------------------------------------------
  1077. // Purpose:
  1078. //-----------------------------------------------------------------------------
  1079. void CTFPlayerModelPanel::PrePaint3D( IMatRenderContext *pRenderContext )
  1080. {
  1081. if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER )
  1082. {
  1083. modelrender->ForcedMaterialOverride( *g_PlayerPreviewEffect.GetInvulnMaterialRef() );
  1084. }
  1085. BaseClass::PrePaint3D( pRenderContext );
  1086. }
  1087. //-----------------------------------------------------------------------------
  1088. // Purpose:
  1089. //-----------------------------------------------------------------------------
  1090. void CTFPlayerModelPanel::PostPaint3D( IMatRenderContext *pRenderContext )
  1091. {
  1092. if ( g_PlayerPreviewEffect.GetEffect() == C_TFPlayerPreviewEffect::PREVIEW_EFFECT_UBER )
  1093. {
  1094. modelrender->ForcedMaterialOverride( NULL );
  1095. }
  1096. static bool bAlternate = false;
  1097. Vector vColor = bAlternate ? m_vEyeGlowColor1 : m_vEyeGlowColor2;
  1098. bAlternate = !bAlternate;
  1099. // Eye glows
  1100. if ( m_aParticleSystems[ SYSTEM_EYEGLOW_RIGHT ] )
  1101. {
  1102. m_aParticleSystems[ SYSTEM_EYEGLOW_RIGHT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
  1103. }
  1104. if ( m_aParticleSystems[ SYSTEM_EYESPARK_RIGHT ] )
  1105. {
  1106. m_aParticleSystems[ SYSTEM_EYESPARK_RIGHT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
  1107. }
  1108. if ( m_aParticleSystems[ SYSTEM_EYEGLOW_LEFT ] )
  1109. {
  1110. m_aParticleSystems[ SYSTEM_EYEGLOW_LEFT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
  1111. }
  1112. if ( m_aParticleSystems[ SYSTEM_EYESPARK_LEFT ])
  1113. {
  1114. m_aParticleSystems[ SYSTEM_EYESPARK_LEFT ]->m_pParticleSystem->SetControlPoint( CUSTOM_COLOR_CP1, vColor );
  1115. }
  1116. m_bUpdateEyeGlows = false;
  1117. m_bPlaySparks = false;
  1118. // remove all particles that are not up-to-date before simulating the updated ones in the base
  1119. for ( int i = 0; i < ARRAYSIZE( m_aParticleSystems ); i++ )
  1120. {
  1121. if ( m_aParticleSystems[i] && !m_aParticleSystems[i]->m_bIsUpdateToDate )
  1122. {
  1123. SafeDeleteParticleData( &m_aParticleSystems[i] );
  1124. }
  1125. }
  1126. BaseClass::PostPaint3D( pRenderContext );
  1127. }
  1128. //-----------------------------------------------------------------------------
  1129. // Purpose : Called by base Mdlpanel when a merged mdl has been drawn
  1130. // For TF we use this as a way to render effects on top of model as appropriate (ie Unusual effects)
  1131. //-----------------------------------------------------------------------------
  1132. void CTFPlayerModelPanel::RenderingRootModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix )
  1133. {
  1134. if ( !m_bUseParticle )
  1135. return;
  1136. // Eye Glows
  1137. UpdateEyeGlows( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, true );
  1138. UpdateEyeGlows( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, false );
  1139. // Right hand
  1140. UpdateActionSlotEffects( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix );
  1141. // Taunt Effects
  1142. UpdateTauntEffects( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix );
  1143. }
  1144. CEconItemView *CTFPlayerModelPanel::GetLoadoutItemFromMDLHandle( loadout_positions_t iPosition, MDLHandle_t mdlHandle )
  1145. {
  1146. // Check if we have a particle hat, if not ignore
  1147. CEconItemView *pEconItem = NULL;
  1148. // Find this item
  1149. FOR_EACH_VEC( m_ItemsToCarry, i )
  1150. {
  1151. CEconItemView *pItem = m_ItemsToCarry[i];
  1152. int iLoadoutSlot = pItem->GetStaticData()->GetLoadoutSlot( m_iCurrentClassIndex );
  1153. if ( ( IsMiscSlot( iLoadoutSlot ) && IsMiscSlot( iPosition ) ) ||
  1154. ( IsValidPickupWeaponSlot( iLoadoutSlot ) && iLoadoutSlot == iPosition ) )
  1155. {
  1156. const char * pDisplayModel = pItem->GetPlayerDisplayModel( m_iCurrentClassIndex, m_iTeam );
  1157. if ( pDisplayModel )
  1158. {
  1159. MDLHandle_t hMDLFindResult = vgui::MDLCache()->FindMDL( pDisplayModel );
  1160. // compare the model to make sure that this is the same item
  1161. if ( hMDLFindResult == mdlHandle )
  1162. {
  1163. pEconItem = pItem;
  1164. vgui::MDLCache()->Release(hMDLFindResult); // counterbalance addref from within FindMDL
  1165. break;
  1166. }
  1167. vgui::MDLCache()->Release(hMDLFindResult);
  1168. }
  1169. }
  1170. }
  1171. return pEconItem;
  1172. }
  1173. //-----------------------------------------------------------------------------
  1174. void CTFPlayerModelPanel::RenderingMergedModel( IMatRenderContext *pRenderContext, CStudioHdr *pStudioHdr, MDLHandle_t mdlHandle, matrix3x4_t *pWorldMatrix )
  1175. {
  1176. if ( !m_bUseParticle )
  1177. return;
  1178. static struct MergeModelSlot_t
  1179. {
  1180. loadout_positions_t iPosition;
  1181. modelpanel_particle_system_t iSystem;
  1182. } s_mergeModelSlot[] =
  1183. {
  1184. { LOADOUT_POSITION_HEAD, SYSTEM_HEAD },
  1185. { LOADOUT_POSITION_MISC, SYSTEM_MISC1 },
  1186. { LOADOUT_POSITION_MISC2, SYSTEM_MISC2 },
  1187. { LOADOUT_POSITION_PRIMARY, SYSTEM_WEAPON },
  1188. { LOADOUT_POSITION_SECONDARY, SYSTEM_WEAPON },
  1189. { LOADOUT_POSITION_MELEE, SYSTEM_WEAPON },
  1190. };
  1191. modelpanel_particle_system_t iSystem = SYSTEM_HEAD;
  1192. loadout_positions_t iPosition = LOADOUT_POSITION_INVALID;
  1193. CEconItemView *pEconItem = NULL;
  1194. int count = ARRAYSIZE( s_mergeModelSlot );
  1195. for ( int i=0; i<count; ++i )
  1196. {
  1197. // find the item for this model
  1198. pEconItem = GetLoadoutItemFromMDLHandle( s_mergeModelSlot[i].iPosition, mdlHandle );
  1199. if ( pEconItem )
  1200. {
  1201. iPosition = s_mergeModelSlot[i].iPosition;
  1202. iSystem = s_mergeModelSlot[i].iSystem;
  1203. // this is horrible but this fixes multiple unusual cosmetics with same default loadout to update their particles
  1204. if ( m_aParticleSystems[ iSystem ] && m_aParticleSystems[ iSystem ]->m_bIsUpdateToDate )
  1205. continue;
  1206. break;
  1207. }
  1208. }
  1209. // couldn't find matching item for this model, do nothing
  1210. if ( !pEconItem )
  1211. return;
  1212. // Unusual Particles
  1213. // Update Misc Particles 1 by 1, Unfortunately the equip location is generic (MISC_SLOT) and not the specific slot
  1214. // so we have to test each slot individually
  1215. UpdateCosmeticParticles( pRenderContext, pStudioHdr, mdlHandle, pWorldMatrix, iSystem, pEconItem );
  1216. if ( m_iCurrentSlotIndex == iPosition )
  1217. {
  1218. RenderStatTrack( pStudioHdr, pWorldMatrix );
  1219. }
  1220. }
  1221. IMaterial* CTFPlayerModelPanel::GetOverrideMaterial( MDLHandle_t mdlHandle )
  1222. {
  1223. loadout_positions_t s_iPosition[] = {
  1224. LOADOUT_POSITION_HEAD,
  1225. LOADOUT_POSITION_MISC,
  1226. LOADOUT_POSITION_MISC2,
  1227. LOADOUT_POSITION_PRIMARY,
  1228. LOADOUT_POSITION_SECONDARY,
  1229. LOADOUT_POSITION_MELEE
  1230. };
  1231. int count = ARRAYSIZE( s_iPosition );
  1232. for ( int i = 0; i < count; ++i )
  1233. {
  1234. CEconItemView *pEconItem = GetLoadoutItemFromMDLHandle( s_iPosition[ i ], mdlHandle );
  1235. if ( pEconItem )
  1236. return pEconItem->GetMaterialOverride( m_iTeam );
  1237. }
  1238. return NULL;
  1239. }
  1240. //-----------------------------------------------------------------------------
  1241. // Purpose:
  1242. //-----------------------------------------------------------------------------
  1243. bool CTFPlayerModelPanel::RenderStatTrack( CStudioHdr *pStudioHdr, matrix3x4_t *pWorldMatrix )
  1244. {
  1245. // Draw the merge MDLs.
  1246. if ( !m_StatTrackModel.m_bDisabled )
  1247. {
  1248. matrix3x4_t matMergeBoneToWorld[MAXSTUDIOBONES];
  1249. // Get the merge studio header.
  1250. studiohdr_t *pStatTrackStudioHdr = m_StatTrackModel.m_MDL.GetStudioHdr();
  1251. matrix3x4_t *pMergeBoneToWorld = &matMergeBoneToWorld[0];
  1252. // If we have a valid mesh, bonemerge it. If we have an invalid mesh we can't bonemerge because
  1253. // it'll crash trying to pull data from the missing header.
  1254. if ( pStatTrackStudioHdr != NULL )
  1255. {
  1256. CStudioHdr mergeHdr( pStatTrackStudioHdr, g_pMDLCache );
  1257. m_StatTrackModel.m_MDL.SetupBonesWithBoneMerge( &mergeHdr, pMergeBoneToWorld, pStudioHdr, pWorldMatrix, m_StatTrackModel.m_MDLToWorld );
  1258. for ( int i=0; i<mergeHdr.numbones(); ++i )
  1259. {
  1260. MatrixScaleBy( m_flStatTrackScale, pMergeBoneToWorld[i] );
  1261. }
  1262. m_StatTrackModel.m_MDL.Draw( m_StatTrackModel.m_MDLToWorld, pMergeBoneToWorld );
  1263. }
  1264. return true;
  1265. }
  1266. return false;
  1267. }
  1268. //-----------------------------------------------------------------------------
  1269. bool CTFPlayerModelPanel::UpdateCosmeticParticles(
  1270. IMatRenderContext *pRenderContext,
  1271. CStudioHdr *pStudioHdr,
  1272. MDLHandle_t mdlHandle,
  1273. matrix3x4_t *pWorldMatrix,
  1274. modelpanel_particle_system_t iSystem,
  1275. CEconItemView *pEconItem
  1276. )
  1277. {
  1278. if ( m_aParticleSystems[ iSystem ] && m_aParticleSystems[ iSystem ]->m_bIsUpdateToDate )
  1279. return false;
  1280. attachedparticlesystem_t *pParticleSystem = NULL;
  1281. // do community_sparkle effect if this is a community item?
  1282. const int iQualityParticleType = pEconItem->GetQualityParticleType();
  1283. if ( iQualityParticleType > 0 )
  1284. {
  1285. pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( iQualityParticleType );
  1286. }
  1287. if ( !pParticleSystem )
  1288. {
  1289. // does this hat even have a particle effect
  1290. static CSchemaAttributeDefHandle pAttrDef_AttachParticleEffect( "attach particle effect" );
  1291. uint32 iValue = 0;
  1292. if ( !pEconItem->FindAttribute( pAttrDef_AttachParticleEffect, &iValue ) )
  1293. {
  1294. return false;
  1295. }
  1296. const float& value_as_float = (float&)iValue;
  1297. pParticleSystem = GetItemSchema()->GetAttributeControlledParticleSystem( value_as_float );
  1298. }
  1299. // failed to find any particle effect
  1300. if ( !pParticleSystem )
  1301. {
  1302. return false;
  1303. }
  1304. // Team Color
  1305. if ( GetTeam() == TF_TEAM_BLUE && V_stristr( pParticleSystem->pszSystemName, "_teamcolor_red" ))
  1306. {
  1307. static char pBlue[256];
  1308. V_StrSubst( pParticleSystem->pszSystemName, "_teamcolor_red", "_teamcolor_blue", pBlue, 256 );
  1309. pParticleSystem = GetItemSchema()->FindAttributeControlledParticleSystem( pBlue );
  1310. if ( !pParticleSystem )
  1311. {
  1312. return false;
  1313. }
  1314. }
  1315. // if this thing has a bip_head or prp_helmet (aka a hat)
  1316. int iBone = Studio_BoneIndexByName( pStudioHdr, "bip_head" );
  1317. if ( iBone < 0 )
  1318. {
  1319. iBone = Studio_BoneIndexByName( pStudioHdr, "prp_helmet" );
  1320. if ( iBone < 0 )
  1321. {
  1322. iBone = Studio_BoneIndexByName( pStudioHdr, "prp_hat" );
  1323. }
  1324. }
  1325. // default to root
  1326. if ( iBone < 0 )
  1327. {
  1328. iBone = 0;
  1329. }
  1330. // Get Use Head Origin
  1331. CUtlVector< int > vecAttachments;
  1332. static CSchemaAttributeDefHandle pAttrDef_UseHead( "particle effect use head origin" );
  1333. uint32 iUseHead = 0;
  1334. if ( !pEconItem->FindAttribute( pAttrDef_UseHead, &iUseHead ) || !iUseHead == 0 )
  1335. {
  1336. // not using head? try searching for attachment points
  1337. for ( int i=0; i<ARRAYSIZE( pParticleSystem->pszControlPoints ); ++i )
  1338. {
  1339. const char *pszAttachmentName = pParticleSystem->pszControlPoints[i];
  1340. if ( pszAttachmentName && pszAttachmentName[0] )
  1341. {
  1342. int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttachmentName );
  1343. if ( iAttachment < 0 )
  1344. continue;
  1345. vecAttachments.AddToTail( iAttachment );
  1346. }
  1347. }
  1348. }
  1349. static char pszFullname[256];
  1350. const char* pszSystemName = pParticleSystem->pszSystemName;
  1351. // Weapon Remap for a Base Effect to be used on a specific weapon
  1352. if ( pParticleSystem->bUseSuffixName && pEconItem && pEconItem->GetItemDefinition()->GetParticleSuffix() )
  1353. {
  1354. V_strcpy_safe( pszFullname, pParticleSystem->pszSystemName );
  1355. V_strcat_safe( pszFullname, "_" );
  1356. V_strcat_safe( pszFullname, pEconItem->GetItemDefinition()->GetParticleSuffix() );
  1357. pszSystemName = pszFullname;
  1358. }
  1359. // Update the Particles and render them
  1360. if ( m_aParticleSystems[ iSystem ] )
  1361. {
  1362. // Check if its a new particle system
  1363. if ( V_strcmp( m_aParticleSystems[ iSystem ]->m_pParticleSystem->GetName(), pszSystemName ) )
  1364. {
  1365. SafeDeleteParticleData( &m_aParticleSystems[ iSystem ] );
  1366. m_aParticleSystems[ iSystem ] = CreateParticleData( pszSystemName );
  1367. }
  1368. }
  1369. else
  1370. {
  1371. // create
  1372. m_aParticleSystems[ iSystem ] = CreateParticleData( pszSystemName );
  1373. }
  1374. // Particle system does not exist
  1375. if ( !m_aParticleSystems[ iSystem ] )
  1376. return false;
  1377. // Get offset if it exists (and if we're using head offset)
  1378. static CSchemaAttributeDefHandle pAttrDef_VerticalOffset( "particle effect vertical offset" );
  1379. uint32 iOffset = 0;
  1380. Vector vecParticleOffset( 0, 0, 0 );
  1381. if ( iUseHead > 0 && pEconItem->FindAttribute( pAttrDef_VerticalOffset, &iOffset ) )
  1382. {
  1383. vecParticleOffset.z = (float&)iOffset;
  1384. }
  1385. m_aParticleSystems[ iSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, iBone, vecParticleOffset );
  1386. return true;
  1387. }
  1388. //-----------------------------------------------------------------------------
  1389. void CTFPlayerModelPanel::UpdateEyeGlows(
  1390. IMatRenderContext *pRenderContext,
  1391. CStudioHdr *pStudioHdr,
  1392. MDLHandle_t mdlHandle,
  1393. matrix3x4_t *pWorldMatrix,
  1394. bool bIsRightEye
  1395. ) {
  1396. float flOffset = 0;
  1397. modelpanel_particle_system_t eyeSystem = bIsRightEye ? SYSTEM_EYEGLOW_RIGHT : SYSTEM_EYEGLOW_LEFT;
  1398. modelpanel_particle_system_t sparkSystem = bIsRightEye ? SYSTEM_EYESPARK_RIGHT : SYSTEM_EYESPARK_LEFT;
  1399. const char* pszAttach = bIsRightEye ? "eyeglow_R" : "eyeglow_L";
  1400. // is this a model we care about?
  1401. int iAttachment = Studio_FindAttachment( pStudioHdr, pszAttach );
  1402. if ( iAttachment == INVALID_PARTICLE_ATTACHMENT || iAttachment == -1 )
  1403. return;
  1404. if ( m_bUpdateEyeGlows )
  1405. {
  1406. const char* pszGlowEffectName = m_pszEyeGlowParticleName;
  1407. // kill old effects
  1408. SafeDeleteParticleData( &m_aParticleSystems[ eyeSystem ] );
  1409. if ( !bIsRightEye && GetPlayerClass() == TF_CLASS_DEMOMAN )
  1410. {
  1411. // demo man has a green eyeglow for eyelander if applicable
  1412. C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer();
  1413. if ( pPlayer )
  1414. {
  1415. int iDecaps = pPlayer->m_Shared.GetDecapitations();
  1416. pszGlowEffectName = pPlayer->GetDemomanEyeEffectName( iDecaps );
  1417. }
  1418. }
  1419. if ( pszGlowEffectName && pszGlowEffectName[0] != '\0' )
  1420. {
  1421. m_aParticleSystems[ eyeSystem ] = CreateParticleData( pszGlowEffectName );
  1422. }
  1423. }
  1424. if ( m_bPlaySparks && !m_vEyeGlowColor1.IsZero() )
  1425. {
  1426. SafeDeleteParticleData( &m_aParticleSystems[ sparkSystem ] );
  1427. // Generate an eye spark as well not for demo
  1428. m_aParticleSystems[ sparkSystem ] = CreateParticleData( "killstreak_t0_lvl1_flash" );
  1429. }
  1430. // Tick Update on position
  1431. if ( m_aParticleSystems[ eyeSystem ] || m_aParticleSystems[ sparkSystem ] )
  1432. {
  1433. // Figure out where our attach point is
  1434. matrix3x4_t matAttachToWorld;
  1435. CUtlVector< int > vecAttachments;
  1436. vecAttachments.AddToTail( iAttachment );
  1437. // Update control points which is updating the position of the particles
  1438. Vector vecForward;
  1439. MatrixGetColumn( matAttachToWorld, 0, vecForward );
  1440. Vector vecParticleOffset = vecForward * flOffset;
  1441. if ( m_aParticleSystems[ eyeSystem ] )
  1442. {
  1443. m_aParticleSystems[ eyeSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset );
  1444. }
  1445. if ( m_aParticleSystems[ sparkSystem ] )
  1446. {
  1447. m_aParticleSystems[ sparkSystem ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments, 0, vecParticleOffset );
  1448. }
  1449. }
  1450. }
  1451. //-----------------------------------------------------------------------------
  1452. void CTFPlayerModelPanel::UpdateActionSlotEffects(
  1453. IMatRenderContext *pRenderContext,
  1454. CStudioHdr *pStudioHdr,
  1455. MDLHandle_t mdlHandle,
  1456. matrix3x4_t *pWorldMatrix
  1457. ) {
  1458. // is this a model we care about?
  1459. int iAttachment = Studio_FindAttachment( pStudioHdr, "effect_hand_R" );
  1460. if ( iAttachment == INVALID_PARTICLE_ATTACHMENT || iAttachment == -1 )
  1461. return;
  1462. if ( !m_bDrawActionSlotEffects )
  1463. return;
  1464. if ( !m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
  1465. {
  1466. m_aParticleSystems[ SYSTEM_ACTIONSLOT ] = CreateParticleData( CTFSpellBook::GetHandEffect( m_pHeldItem, 0 ) );
  1467. }
  1468. if ( !m_aParticleSystems[ SYSTEM_ACTIONSLOT ] )
  1469. return;
  1470. CUtlVector< int > vecAttachments;
  1471. vecAttachments.AddToTail( iAttachment );
  1472. m_aParticleSystems[ SYSTEM_ACTIONSLOT ]->UpdateControlPoints( pStudioHdr, pWorldMatrix, vecAttachments );
  1473. }
  1474. //-----------------------------------------------------------------------------
  1475. void CTFPlayerModelPanel::UpdateTauntEffects(
  1476. IMatRenderContext *pRenderContext,
  1477. CStudioHdr *pStudioHdr,
  1478. MDLHandle_t mdlHandle,
  1479. matrix3x4_t *pWorldMatrix
  1480. ) {
  1481. if ( !m_bDrawTauntParticles )
  1482. return;
  1483. if ( !m_aParticleSystems[SYSTEM_TAUNT] )
  1484. return;
  1485. // Check if refire is needed
  1486. if ( m_flTauntParticleRefireRate > 0 && m_flTauntParticleRefireTime < gpGlobals->curtime )
  1487. {
  1488. m_flTauntParticleRefireTime = gpGlobals->curtime + m_flTauntParticleRefireRate;
  1489. // safe off current particle name
  1490. CUtlString strParticleName = m_aParticleSystems[SYSTEM_TAUNT]->m_pParticleSystem->GetName();
  1491. // remove old particle
  1492. SafeDeleteParticleData( &m_aParticleSystems[SYSTEM_TAUNT] );
  1493. // create new particle
  1494. m_aParticleSystems[SYSTEM_TAUNT] = CreateParticleData( strParticleName.String() );
  1495. }
  1496. matrix3x4_t matAttachToWorld;
  1497. SetIdentityMatrix( matAttachToWorld );
  1498. CUtlVector< int > vecAttachments;
  1499. m_aParticleSystems[SYSTEM_TAUNT]->UpdateControlPoints( pStudioHdr, &matAttachToWorld, vecAttachments, 0, m_vecPlayerPos );
  1500. }
  1501. //-----------------------------------------------------------------------------
  1502. // Called Externally
  1503. //-----------------------------------------------------------------------------
  1504. void CTFPlayerModelPanel::SetEyeGlowEffect( const char *pEffectName, Vector vColor1, Vector vColor2, bool bForceUpdate, bool bPlaySparks )
  1505. {
  1506. m_vEyeGlowColor1 = vColor1;
  1507. m_vEyeGlowColor2 = vColor2;
  1508. m_bPlaySparks = bPlaySparks;
  1509. if ( bForceUpdate )
  1510. {
  1511. m_bUpdateEyeGlows = true;
  1512. }
  1513. if ( !pEffectName )
  1514. {
  1515. if ( m_pszEyeGlowParticleName[0] != '\0' )
  1516. {
  1517. m_bUpdateEyeGlows = true;
  1518. }
  1519. m_pszEyeGlowParticleName[0] = '\0';
  1520. }
  1521. else if ( !FStrEq( m_pszEyeGlowParticleName, pEffectName) )
  1522. {
  1523. V_strcpy_safe( m_pszEyeGlowParticleName, pEffectName );
  1524. m_bUpdateEyeGlows = true;
  1525. }
  1526. }
  1527. //-----------------------------------------------------------------------------
  1528. // Purpose: clear all particles
  1529. //-----------------------------------------------------------------------------
  1530. void CTFPlayerModelPanel::InvalidateParticleEffects()
  1531. {
  1532. for ( int i=0; i<ARRAYSIZE(m_aParticleSystems); ++i )
  1533. {
  1534. if ( m_aParticleSystems[i] )
  1535. {
  1536. SafeDeleteParticleData( &m_aParticleSystems[i] );
  1537. }
  1538. }
  1539. }
  1540. //-----------------------------------------------------------------------------
  1541. // Purpose:
  1542. //-----------------------------------------------------------------------------
  1543. void CTFPlayerModelPanel::StartEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
  1544. {
  1545. if ( !event || !event->GetActive() )
  1546. return;
  1547. CChoreoActor *actor = event->GetActor();
  1548. if ( actor && !actor->GetActive() )
  1549. return;
  1550. CChoreoChannel *channel = event->GetChannel();
  1551. if ( channel && !channel->GetActive() )
  1552. return;
  1553. //Msg( "Got STARTEVENT at %.2f\n", currenttime );
  1554. //Msg( "%8.4f: start %s\n", currenttime, event->GetDescription() );
  1555. switch ( event->GetType() )
  1556. {
  1557. case CChoreoEvent::SEQUENCE:
  1558. ProcessSequence( scene, event );
  1559. break;
  1560. case CChoreoEvent::SPEAK:
  1561. {
  1562. if ( m_bDisableSpeakEvent )
  1563. return;
  1564. // FIXME: dB hack. soundlevel needs to be moved into inside of wav?
  1565. soundlevel_t iSoundlevel = SNDLVL_TALKING;
  1566. if ( event->GetParameters2() )
  1567. {
  1568. iSoundlevel = (soundlevel_t)atoi( event->GetParameters2() );
  1569. if ( iSoundlevel == SNDLVL_NONE )
  1570. {
  1571. iSoundlevel = SNDLVL_TALKING;
  1572. }
  1573. }
  1574. float time_in_past = currenttime - event->GetStartTime() ;
  1575. float soundtime = gpGlobals->curtime - time_in_past;
  1576. EmitSound_t es;
  1577. es.m_nChannel = CHAN_VOICE;
  1578. es.m_flVolume = 1;
  1579. es.m_SoundLevel = iSoundlevel;
  1580. es.m_flSoundTime = soundtime;
  1581. es.m_bEmitCloseCaption = false;
  1582. es.m_pSoundName = event->GetParameters();
  1583. C_RecipientFilter filter;
  1584. C_BaseEntity::EmitSound( filter, SOUND_FROM_UI_PANEL, es );
  1585. }
  1586. break;
  1587. case CChoreoEvent::STOPPOINT:
  1588. {
  1589. // Nothing, this is a symbolic event for keeping the vcd alive for ramping out after the last true event
  1590. //ClearScene();
  1591. }
  1592. break;
  1593. case CChoreoEvent::LOOP:
  1594. ProcessLoop( scene, event );
  1595. break;
  1596. // Not supported in TF2's model previews
  1597. case CChoreoEvent::SUBSCENE:
  1598. case CChoreoEvent::SECTION:
  1599. {
  1600. Assert(0);
  1601. }
  1602. break;
  1603. default:
  1604. break;
  1605. }
  1606. }
  1607. //-----------------------------------------------------------------------------
  1608. // Purpose:
  1609. //-----------------------------------------------------------------------------
  1610. void CTFPlayerModelPanel::EndEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
  1611. {
  1612. if ( !event || !event->GetActive() )
  1613. return;
  1614. CChoreoActor *actor = event->GetActor();
  1615. if ( actor && !actor->GetActive() )
  1616. return;
  1617. CChoreoChannel *channel = event->GetChannel();
  1618. if ( channel && !channel->GetActive() )
  1619. return;
  1620. //Msg( "Got ENDEVENT at %.2f\n", currenttime );
  1621. //Msg( "%8.4f: end %s %i\n", currenttime, event->GetDescription(), event->GetType() );
  1622. switch ( event->GetType() )
  1623. {
  1624. case CChoreoEvent::SUBSCENE:
  1625. {
  1626. // Not supported in TF2's model previews
  1627. Assert(0);
  1628. }
  1629. break;
  1630. case CChoreoEvent::SPEAK:
  1631. {
  1632. }
  1633. break;
  1634. case CChoreoEvent::STOPPOINT:
  1635. {
  1636. //SetSequenceLayers( NULL, 0 );
  1637. //ClearScene();
  1638. }
  1639. break;
  1640. default:
  1641. break;
  1642. }
  1643. }
  1644. //-----------------------------------------------------------------------------
  1645. // Purpose:
  1646. //-----------------------------------------------------------------------------
  1647. void CTFPlayerModelPanel::ProcessEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
  1648. {
  1649. if ( !event || !event->GetActive() )
  1650. return;
  1651. CChoreoActor *actor = event->GetActor();
  1652. if ( actor && !actor->GetActive() )
  1653. return;
  1654. CChoreoChannel *channel = event->GetChannel();
  1655. if ( channel && !channel->GetActive() )
  1656. return;
  1657. //Msg("PROCESSEVENT at %.2f\n", currenttime );
  1658. switch( event->GetType() )
  1659. {
  1660. case CChoreoEvent::EXPRESSION:
  1661. if ( !m_bShouldRunFlexEvents )
  1662. {
  1663. ProcessFlexSettingSceneEvent( scene, event );
  1664. }
  1665. break;
  1666. case CChoreoEvent::FLEXANIMATION:
  1667. if ( m_bShouldRunFlexEvents )
  1668. {
  1669. ProcessFlexAnimation( scene, event );
  1670. }
  1671. break;
  1672. case CChoreoEvent::SEQUENCE:
  1673. case CChoreoEvent::SPEAK:
  1674. case CChoreoEvent::STOPPOINT:
  1675. // Nothing
  1676. break;
  1677. // Not supported in TF2's model previews
  1678. case CChoreoEvent::LOOKAT:
  1679. case CChoreoEvent::FACE:
  1680. case CChoreoEvent::SUBSCENE:
  1681. case CChoreoEvent::MOVETO:
  1682. case CChoreoEvent::INTERRUPT:
  1683. case CChoreoEvent::PERMIT_RESPONSES:
  1684. case CChoreoEvent::GESTURE:
  1685. Assert(0);
  1686. break;
  1687. default:
  1688. break;
  1689. }
  1690. }
  1691. //-----------------------------------------------------------------------------
  1692. // Purpose:
  1693. //-----------------------------------------------------------------------------
  1694. bool CTFPlayerModelPanel::CheckEvent( float currenttime, CChoreoScene *scene, CChoreoEvent *event )
  1695. {
  1696. return true;
  1697. }
  1698. //-----------------------------------------------------------------------------
  1699. // Purpose: Apply a sequence
  1700. // Input : *event -
  1701. //-----------------------------------------------------------------------------
  1702. void CTFPlayerModelPanel::ProcessSequence( CChoreoScene *scene, CChoreoEvent *event )
  1703. {
  1704. Assert( event->GetType() == CChoreoEvent::SEQUENCE );
  1705. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  1706. if ( !event->GetActor() )
  1707. return;
  1708. int iSequence = LookupSequence( &studioHdr, event->GetParameters() );
  1709. if (iSequence < 0)
  1710. return;
  1711. // making sure the mdl has correct playback rate
  1712. mstudioseqdesc_t &seqdesc = studioHdr.pSeqdesc( iSequence );
  1713. mstudioanimdesc_t &animdesc = studioHdr.pAnimdesc( studioHdr.iRelativeAnim( iSequence, seqdesc.anim(0,0) ) );
  1714. m_RootMDL.m_MDL.m_flPlaybackRate = animdesc.fps;
  1715. MDLSquenceLayer_t tmpSequenceLayers[1];
  1716. tmpSequenceLayers[0].m_nSequenceIndex = iSequence;
  1717. tmpSequenceLayers[0].m_flWeight = 1.0;
  1718. tmpSequenceLayers[0].m_bNoLoop = true;
  1719. tmpSequenceLayers[0].m_flCycleBeganAt = m_RootMDL.m_MDL.m_flTime;
  1720. SetSequenceLayers( tmpSequenceLayers, 1 );
  1721. }
  1722. //-----------------------------------------------------------------------------
  1723. // Purpose:
  1724. // Input : *scene -
  1725. // *event -
  1726. //-----------------------------------------------------------------------------
  1727. void CTFPlayerModelPanel::ProcessLoop( CChoreoScene *scene, CChoreoEvent *event )
  1728. {
  1729. Assert( event->GetType() == CChoreoEvent::LOOP );
  1730. float backtime = (float)atof( event->GetParameters() );
  1731. bool process = true;
  1732. int counter = event->GetLoopCount();
  1733. if ( counter != -1 )
  1734. {
  1735. int remaining = event->GetNumLoopsRemaining();
  1736. if ( remaining <= 0 )
  1737. {
  1738. process = false;
  1739. }
  1740. else
  1741. {
  1742. event->SetNumLoopsRemaining( --remaining );
  1743. }
  1744. }
  1745. if ( !process )
  1746. return;
  1747. //Msg("LOOP: %.2f (%.2f)\n", m_flSceneTime, scene->GetTime() );
  1748. float flPrevTime = m_flSceneTime;
  1749. scene->LoopToTime( backtime );
  1750. m_flSceneTime = backtime;
  1751. //Msg(" -> %.2f (%.2f)\n", m_flSceneTime, scene->GetTime() );
  1752. float flDelta = flPrevTime - backtime;
  1753. //Msg(" -> Delta %.2f\n", flDelta );
  1754. // If we're running noloop sequences, we need to push out their begin time, so they keep playing
  1755. for ( int i = 0; i < m_nNumSequenceLayers; i++ )
  1756. {
  1757. if ( m_SequenceLayers[i].m_bNoLoop )
  1758. {
  1759. m_SequenceLayers[i].m_flCycleBeganAt += flDelta;
  1760. }
  1761. }
  1762. }
  1763. //-----------------------------------------------------------------------------
  1764. // Purpose:
  1765. //-----------------------------------------------------------------------------
  1766. LocalFlexController_t CTFPlayerModelPanel::GetNumFlexControllers( void )
  1767. {
  1768. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  1769. return studioHdr.numflexcontrollers();
  1770. }
  1771. //-----------------------------------------------------------------------------
  1772. // Purpose:
  1773. //-----------------------------------------------------------------------------
  1774. const char *CTFPlayerModelPanel::GetFlexDescFacs( int iFlexDesc )
  1775. {
  1776. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  1777. mstudioflexdesc_t *pflexdesc = studioHdr.pFlexdesc( iFlexDesc );
  1778. return pflexdesc->pszFACS( );
  1779. }
  1780. //-----------------------------------------------------------------------------
  1781. // Purpose:
  1782. //-----------------------------------------------------------------------------
  1783. const char *CTFPlayerModelPanel::GetFlexControllerName( LocalFlexController_t iFlexController )
  1784. {
  1785. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  1786. mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( iFlexController );
  1787. return pflexcontroller->pszName( );
  1788. }
  1789. //-----------------------------------------------------------------------------
  1790. // Purpose:
  1791. //-----------------------------------------------------------------------------
  1792. const char *CTFPlayerModelPanel::GetFlexControllerType( LocalFlexController_t iFlexController )
  1793. {
  1794. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  1795. mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( iFlexController );
  1796. return pflexcontroller->pszType( );
  1797. }
  1798. //-----------------------------------------------------------------------------
  1799. // Purpose:
  1800. //-----------------------------------------------------------------------------
  1801. LocalFlexController_t CTFPlayerModelPanel::FindFlexController( const char *szName )
  1802. {
  1803. for (LocalFlexController_t i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
  1804. {
  1805. if (stricmp( GetFlexControllerName( i ), szName ) == 0)
  1806. {
  1807. return i;
  1808. }
  1809. }
  1810. // AssertMsg( 0, UTIL_VarArgs( "flexcontroller %s couldn't be mapped!!!\n", szName ) );
  1811. return LocalFlexController_t(-1);
  1812. }
  1813. //-----------------------------------------------------------------------------
  1814. // Purpose:
  1815. //-----------------------------------------------------------------------------
  1816. void CTFPlayerModelPanel::SetFlexWeight( LocalFlexController_t index, float value )
  1817. {
  1818. if (index >= 0 && index < GetNumFlexControllers())
  1819. {
  1820. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  1821. mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( index );
  1822. if (pflexcontroller->max != pflexcontroller->min)
  1823. {
  1824. value = (value - pflexcontroller->min) / (pflexcontroller->max - pflexcontroller->min);
  1825. value = clamp( value, 0.0f, 1.0f );
  1826. }
  1827. m_flexWeight[ index ] = value;
  1828. }
  1829. }
  1830. //-----------------------------------------------------------------------------
  1831. // Purpose:
  1832. //-----------------------------------------------------------------------------
  1833. float CTFPlayerModelPanel::GetFlexWeight( LocalFlexController_t index )
  1834. {
  1835. if (index >= 0 && index < GetNumFlexControllers())
  1836. {
  1837. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  1838. mstudioflexcontroller_t *pflexcontroller = studioHdr.pFlexcontroller( index );
  1839. if (pflexcontroller->max != pflexcontroller->min)
  1840. {
  1841. return m_flexWeight[index] * (pflexcontroller->max - pflexcontroller->min) + pflexcontroller->min;
  1842. }
  1843. return m_flexWeight[index];
  1844. }
  1845. return 0.0;
  1846. }
  1847. //-----------------------------------------------------------------------------
  1848. // Purpose: During paint, apply the flex weights to the model
  1849. //-----------------------------------------------------------------------------
  1850. void CTFPlayerModelPanel::SetupFlexWeights( void )
  1851. {
  1852. if ( m_RootMDL.m_MDL.GetMDL() == MDLHANDLE_INVALID )
  1853. return;
  1854. // initialize the models local to global flex controller mappings
  1855. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  1856. if (studioHdr.pFlexcontroller( LocalFlexController_t(0) )->localToGlobal == -1)
  1857. {
  1858. for ( LocalFlexController_t i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++)
  1859. {
  1860. int j = C_BaseFlex::AddGlobalFlexController( studioHdr.pFlexcontroller( i )->pszName() );
  1861. studioHdr.pFlexcontroller( i )->localToGlobal = j;
  1862. }
  1863. }
  1864. int iControllers = GetNumFlexControllers();
  1865. for ( int j = 0; j < iControllers; j++ )
  1866. {
  1867. m_RootMDL.m_MDL.m_pFlexControls[j] = 0;
  1868. }
  1869. LocalFlexController_t i;
  1870. // Decay to neutral
  1871. for ( i = LocalFlexController_t(0); i < GetNumFlexControllers(); i++)
  1872. {
  1873. SetFlexWeight( i, GetFlexWeight( i ) * 0.95 );
  1874. }
  1875. // Run scene
  1876. if ( m_pScene )
  1877. {
  1878. m_bShouldRunFlexEvents = true;
  1879. m_pScene->Think( m_flSceneTime );
  1880. }
  1881. // get the networked flexweights and convert them from 0..1 to real dynamic range
  1882. for (i = LocalFlexController_t(0); i < studioHdr.numflexcontrollers(); i++)
  1883. {
  1884. mstudioflexcontroller_t *pflex = studioHdr.pFlexcontroller( i );
  1885. m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] = m_flexWeight[i];
  1886. // rescale
  1887. m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] = m_RootMDL.m_MDL.m_pFlexControls[pflex->localToGlobal] * (pflex->max - pflex->min) + pflex->min;
  1888. }
  1889. if ( m_pScene )
  1890. {
  1891. m_bShouldRunFlexEvents = false;
  1892. m_pScene->Think( m_flSceneTime );
  1893. }
  1894. ProcessVisemes( m_PhonemeClasses );
  1895. if ( m_pScene )
  1896. {
  1897. // Advance time
  1898. if ( m_flLastTickTime < FLT_EPSILON )
  1899. {
  1900. m_flLastTickTime = m_RootMDL.m_MDL.m_flTime - 0.1;
  1901. }
  1902. m_flSceneTime += (m_RootMDL.m_MDL.m_flTime - m_flLastTickTime);
  1903. m_flLastTickTime = m_RootMDL.m_MDL.m_flTime;
  1904. if ( m_flSceneEndTime > FLT_EPSILON && m_flSceneTime > m_flSceneEndTime )
  1905. {
  1906. bool bLoopScene = m_bLoopScene;
  1907. char filename[MAX_PATH];
  1908. V_strcpy_safe( filename, m_pScene->GetFilename() );
  1909. SetSequenceLayers( NULL, 0 );
  1910. ClearScene();
  1911. if ( bLoopScene )
  1912. {
  1913. m_pScene = LoadSceneForModel( filename, this, &m_flSceneEndTime );
  1914. }
  1915. else
  1916. {
  1917. m_pszVCD = NULL;
  1918. }
  1919. }
  1920. }
  1921. }
  1922. extern CFlexSceneFileManager g_FlexSceneFileManager;
  1923. //-----------------------------------------------------------------------------
  1924. // Purpose:
  1925. //-----------------------------------------------------------------------------
  1926. void CTFPlayerModelPanel::ProcessExpression( CChoreoScene *scene, CChoreoEvent *event )
  1927. {
  1928. // Flexanimations have to have an end time!!!
  1929. if ( !event->HasEndTime() )
  1930. return;
  1931. // Look up the actual strings
  1932. const char *scenefile = event->GetParameters();
  1933. const char *name = event->GetParameters2();
  1934. // Have to find both strings
  1935. if ( scenefile && name )
  1936. {
  1937. // Find the scene file
  1938. const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true );
  1939. if ( pExpHdr )
  1940. {
  1941. float scenetime = scene->GetTime();
  1942. float flIntensity = event->GetIntensity( scenetime );
  1943. int i;
  1944. const flexsetting_t *pSetting = NULL;
  1945. // Find the named setting in the base
  1946. for ( i = 0; i < pExpHdr->numflexsettings; i++ )
  1947. {
  1948. pSetting = pExpHdr->pSetting( i );
  1949. if ( !pSetting )
  1950. continue;
  1951. if ( !V_stricmp( pSetting->pszName(), name ) )
  1952. break;
  1953. }
  1954. if ( i>=pExpHdr->numflexsettings )
  1955. return;
  1956. flexweight_t *pWeights = NULL;
  1957. int truecount = pSetting->psetting( (byte *)pExpHdr, 0, &pWeights );
  1958. if ( !pWeights )
  1959. return;
  1960. for (i = 0; i < truecount; i++, pWeights++)
  1961. {
  1962. int j = FlexControllerLocalToGlobal( pExpHdr, pWeights->key );
  1963. float s = clamp( pWeights->influence * flIntensity, 0.0f, 1.0f );
  1964. m_RootMDL.m_MDL.m_pFlexControls[ j ] = m_RootMDL.m_MDL.m_pFlexControls[j] * (1.0f - s) + pWeights->weight * s;
  1965. }
  1966. }
  1967. }
  1968. }
  1969. //-----------------------------------------------------------------------------
  1970. // Purpose:
  1971. //-----------------------------------------------------------------------------
  1972. void CTFPlayerModelPanel::ProcessFlexSettingSceneEvent( CChoreoScene *scene, CChoreoEvent *event )
  1973. {
  1974. // Flexanimations have to have an end time!!!
  1975. if ( !event->HasEndTime() )
  1976. return;
  1977. VPROF( "C_BaseFlex::ProcessFlexSettingSceneEvent" );
  1978. // Look up the actual strings
  1979. const char *scenefile = event->GetParameters();
  1980. const char *name = event->GetParameters2();
  1981. // Have to find both strings
  1982. if ( scenefile && name )
  1983. {
  1984. // Find the scene file
  1985. const flexsettinghdr_t *pExpHdr = ( const flexsettinghdr_t * )g_FlexSceneFileManager.FindSceneFile( this, scenefile, true );
  1986. if ( pExpHdr )
  1987. {
  1988. float scenetime = scene->GetTime();
  1989. float scale = event->GetIntensity( scenetime );
  1990. // Add the named expression
  1991. AddFlexSetting( name, scale, pExpHdr );
  1992. }
  1993. }
  1994. }
  1995. //-----------------------------------------------------------------------------
  1996. // Purpose:
  1997. // Input : *expr -
  1998. // scale -
  1999. // *pSettinghdr -
  2000. //-----------------------------------------------------------------------------
  2001. void CTFPlayerModelPanel::AddFlexSetting( const char *expr, float scale, const flexsettinghdr_t *pSettinghdr )
  2002. {
  2003. int i;
  2004. const flexsetting_t *pSetting = NULL;
  2005. // Find the named setting in the base
  2006. for ( i = 0; i < pSettinghdr->numflexsettings; i++ )
  2007. {
  2008. pSetting = pSettinghdr->pSetting( i );
  2009. if ( !pSetting )
  2010. continue;
  2011. const char *name = pSetting->pszName();
  2012. if ( !V_stricmp( name, expr ) )
  2013. break;
  2014. }
  2015. if ( i>=pSettinghdr->numflexsettings )
  2016. {
  2017. return;
  2018. }
  2019. flexweight_t *pWeights = NULL;
  2020. int truecount = pSetting->psetting( (byte *)pSettinghdr, 0, &pWeights );
  2021. if ( !pWeights )
  2022. return;
  2023. for (i = 0; i < truecount; i++, pWeights++)
  2024. {
  2025. // Translate to local flex controller
  2026. // this is translating from the settings's local index to the models local index
  2027. int index = FlexControllerLocalToGlobal( pSettinghdr, pWeights->key );
  2028. // blend scaled weighting in to total (post networking g_flexweight!!!!)
  2029. float s = clamp( scale * pWeights->influence, 0.0f, 1.0f );
  2030. m_RootMDL.m_MDL.m_pFlexControls[index] = m_RootMDL.m_MDL.m_pFlexControls[index] * (1.0f - s) + pWeights->weight * s;
  2031. for ( int iMergeMDL=0; iMergeMDL<m_aMergeMDLs.Count(); ++iMergeMDL )
  2032. {
  2033. m_aMergeMDLs[iMergeMDL].m_MDL.m_pFlexControls[index] = m_aMergeMDLs[iMergeMDL].m_MDL.m_pFlexControls[index] * (1.0f - s) + pWeights->weight * s;
  2034. }
  2035. }
  2036. }
  2037. //-----------------------------------------------------------------------------
  2038. // Purpose: Apply flexanimation to actor's face
  2039. // Input : *event -
  2040. // Output : Returns true on success, false on failure.
  2041. //-----------------------------------------------------------------------------
  2042. void CTFPlayerModelPanel::ProcessFlexAnimation( CChoreoScene *scene, CChoreoEvent *event )
  2043. {
  2044. Assert( event->GetType() == CChoreoEvent::FLEXANIMATION );
  2045. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  2046. CStudioHdr *hdr = &studioHdr;
  2047. if ( !hdr )
  2048. return;
  2049. if ( !event->GetTrackLookupSet() )
  2050. {
  2051. // Create lookup data
  2052. for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
  2053. {
  2054. CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
  2055. if ( !track )
  2056. continue;
  2057. if ( track->IsComboType() )
  2058. {
  2059. char name[ 512 ];
  2060. Q_strncpy( name, "right_" ,sizeof(name));
  2061. Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
  2062. track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 0 );
  2063. Q_strncpy( name, "left_" ,sizeof(name));
  2064. Q_strncat( name, track->GetFlexControllerName(),sizeof(name), COPY_ALL_CHARACTERS );
  2065. track->SetFlexControllerIndex( MAX( FindFlexController( name ), LocalFlexController_t(0) ), 0, 1 );
  2066. }
  2067. else
  2068. {
  2069. track->SetFlexControllerIndex( MAX( FindFlexController( (char *)track->GetFlexControllerName() ), LocalFlexController_t(0)), 0 );
  2070. }
  2071. }
  2072. event->SetTrackLookupSet( true );
  2073. }
  2074. float scenetime = scene->GetTime();
  2075. float weight = event->GetIntensity( scenetime );
  2076. // Iterate animation tracks
  2077. for ( int i = 0; i < event->GetNumFlexAnimationTracks(); i++ )
  2078. {
  2079. CFlexAnimationTrack *track = event->GetFlexAnimationTrack( i );
  2080. if ( !track )
  2081. continue;
  2082. // Disabled
  2083. if ( !track->IsTrackActive() )
  2084. continue;
  2085. // Map track flex controller to global name
  2086. if ( track->IsComboType() )
  2087. {
  2088. for ( int side = 0; side < 2; side++ )
  2089. {
  2090. LocalFlexController_t controller = track->GetRawFlexControllerIndex( side );
  2091. // Get spline intensity for controller
  2092. float flIntensity = track->GetIntensity( scenetime, side );
  2093. if ( controller >= LocalFlexController_t(0) )
  2094. {
  2095. float orig = GetFlexWeight( controller );
  2096. float value = orig * (1 - weight) + flIntensity * weight;
  2097. SetFlexWeight( controller, value );
  2098. }
  2099. }
  2100. }
  2101. else
  2102. {
  2103. LocalFlexController_t controller = track->GetRawFlexControllerIndex( 0 );
  2104. // Get spline intensity for controller
  2105. float flIntensity = track->GetIntensity( scenetime, 0 );
  2106. if ( controller >= LocalFlexController_t(0) )
  2107. {
  2108. float orig = GetFlexWeight( controller );
  2109. float value = orig * (1 - weight) + flIntensity * weight;
  2110. SetFlexWeight( controller, value );
  2111. }
  2112. }
  2113. }
  2114. }
  2115. extern ConVar g_CV_PhonemeDelay;
  2116. extern ConVar g_CV_PhonemeFilter;
  2117. //-----------------------------------------------------------------------------
  2118. // Purpose:
  2119. //-----------------------------------------------------------------------------
  2120. void CTFPlayerModelPanel::ProcessVisemes( Emphasized_Phoneme *classes )
  2121. {
  2122. // Any sounds being played?
  2123. if ( !MouthInfo().IsActive() )
  2124. return;
  2125. // Multiple phoneme tracks can overlap, look across all such tracks.
  2126. for ( int source = 0 ; source < MouthInfo().GetNumVoiceSources(); source++ )
  2127. {
  2128. CVoiceData *vd = MouthInfo().GetVoiceSource( source );
  2129. if ( !vd || vd->ShouldIgnorePhonemes() )
  2130. continue;
  2131. CSentence *sentence = engine->GetSentence( vd->GetSource() );
  2132. if ( !sentence )
  2133. continue;
  2134. float sentence_length = engine->GetSentenceLength( vd->GetSource() );
  2135. float timesincestart = vd->GetElapsedTime();
  2136. // This sound should be done...why hasn't it been removed yet???
  2137. if ( timesincestart >= ( sentence_length + 2.0f ) )
  2138. continue;
  2139. // Adjust actual time
  2140. float t = timesincestart - g_CV_PhonemeDelay.GetFloat();
  2141. // Get box filter duration
  2142. float dt = g_CV_PhonemeFilter.GetFloat();
  2143. // Streaming sounds get an additional delay...
  2144. /*
  2145. // Tracker 20534: Probably not needed any more with the async sound stuff that
  2146. // we now have (we don't have a disk i/o hitch on startup which might have been
  2147. // messing up the startup timing a bit )
  2148. bool streaming = engine->IsStreaming( vd->m_pAudioSource );
  2149. if ( streaming )
  2150. {
  2151. t -= g_CV_PhonemeDelayStreaming.GetFloat();
  2152. }
  2153. */
  2154. // Assume sound has been playing for a while...
  2155. bool juststarted = false;
  2156. // Get intensity setting for this time (from spline)
  2157. float emphasis_intensity = sentence->GetIntensity( t, sentence_length );
  2158. // Blend and add visemes together
  2159. AddVisemesForSentence( classes, emphasis_intensity, sentence, t, dt, juststarted );
  2160. }
  2161. }
  2162. //-----------------------------------------------------------------------------
  2163. // Purpose:
  2164. //-----------------------------------------------------------------------------
  2165. void CTFPlayerModelPanel::AddVisemesForSentence( Emphasized_Phoneme *classes, float emphasis_intensity, CSentence *sentence, float t, float dt, bool juststarted )
  2166. {
  2167. int pcount = sentence->GetRuntimePhonemeCount();
  2168. for ( int k = 0; k < pcount; k++ )
  2169. {
  2170. const CBasePhonemeTag *phoneme = sentence->GetRuntimePhoneme( k );
  2171. if (t > phoneme->GetStartTime() && t < phoneme->GetEndTime())
  2172. {
  2173. bool bCrossfade = true;
  2174. if (bCrossfade)
  2175. {
  2176. if (k < pcount-1)
  2177. {
  2178. const CBasePhonemeTag *next = sentence->GetRuntimePhoneme( k + 1 );
  2179. // if I have a neighbor
  2180. if ( next )
  2181. {
  2182. // and they're touching
  2183. if (next->GetStartTime() == phoneme->GetEndTime() )
  2184. {
  2185. // 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
  2186. dt = MAX( dt, MIN( next->GetEndTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
  2187. }
  2188. else
  2189. {
  2190. // 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
  2191. dt = MAX( dt, MIN( next->GetStartTime() - t, phoneme->GetEndTime() - phoneme->GetStartTime() ) );
  2192. }
  2193. }
  2194. else
  2195. {
  2196. // last phoneme in list, increase the blend length to the length of the current phoneme
  2197. dt = MAX( dt, phoneme->GetEndTime() - phoneme->GetStartTime() );
  2198. }
  2199. }
  2200. }
  2201. }
  2202. float t1 = ( phoneme->GetStartTime() - t) / dt;
  2203. float t2 = ( phoneme->GetEndTime() - t) / dt;
  2204. if (t1 < 1.0 && t2 > 0)
  2205. {
  2206. float scale;
  2207. // clamp
  2208. if (t2 > 1)
  2209. t2 = 1;
  2210. if (t1 < 0)
  2211. t1 = 0;
  2212. // FIXME: simple box filter. Should use something fancier
  2213. scale = (t2 - t1);
  2214. AddViseme( classes, emphasis_intensity, phoneme->GetPhonemeCode(), scale, juststarted );
  2215. }
  2216. }
  2217. }
  2218. //-----------------------------------------------------------------------------
  2219. // Purpose:
  2220. // Input : *classes -
  2221. // phoneme -
  2222. // scale -
  2223. // newexpression -
  2224. //-----------------------------------------------------------------------------
  2225. void CTFPlayerModelPanel::AddViseme( Emphasized_Phoneme *classes, float emphasis_intensity, int phoneme, float scale, bool newexpression )
  2226. {
  2227. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  2228. CStudioHdr *hdr = &studioHdr;
  2229. if ( !hdr )
  2230. return;
  2231. int type;
  2232. // Setup weights for any emphasis blends
  2233. bool skip = SetupEmphasisBlend( classes, phoneme );
  2234. phoneme = 230;
  2235. scale = 1.0;
  2236. // Uh-oh, missing or unknown phoneme???
  2237. if ( skip )
  2238. {
  2239. return;
  2240. }
  2241. // Compute blend weights
  2242. ComputeBlendedSetting( classes, emphasis_intensity );
  2243. for ( type = 0; type < NUM_PHONEME_CLASSES; type++ )
  2244. {
  2245. Emphasized_Phoneme *info = &classes[ type ];
  2246. if ( !info->valid || info->amount == 0.0f )
  2247. continue;
  2248. const flexsettinghdr_t *actual_flexsetting_header = info->base;
  2249. const flexsetting_t *pSetting = actual_flexsetting_header->pIndexedSetting( phoneme );
  2250. if (!pSetting)
  2251. {
  2252. continue;
  2253. }
  2254. flexweight_t *pWeights = NULL;
  2255. int truecount = pSetting->psetting( (byte *)actual_flexsetting_header, 0, &pWeights );
  2256. if ( pWeights )
  2257. {
  2258. for ( int i = 0; i < truecount; i++)
  2259. {
  2260. // Translate to global controller number
  2261. int j = FlexControllerLocalToGlobal( actual_flexsetting_header, pWeights->key );
  2262. // Add scaled weighting in
  2263. if ( pWeights->weight > 0 )
  2264. {
  2265. m_RootMDL.m_MDL.m_pFlexControls[j] += info->amount * scale * pWeights->weight;
  2266. }
  2267. // Go to next setting
  2268. pWeights++;
  2269. }
  2270. }
  2271. }
  2272. }
  2273. //-----------------------------------------------------------------------------
  2274. // Purpose: A lot of the one time setup and also resets amount to 0.0f default
  2275. // for strong/weak/normal tracks
  2276. // Returning true == skip this phoneme
  2277. // Input : *classes -
  2278. // Output : Returns true on success, false on failure.
  2279. //-----------------------------------------------------------------------------
  2280. bool CTFPlayerModelPanel::SetupEmphasisBlend( Emphasized_Phoneme *classes, int phoneme )
  2281. {
  2282. int i;
  2283. bool skip = false;
  2284. for ( i = 0; i < NUM_PHONEME_CLASSES; i++ )
  2285. {
  2286. Emphasized_Phoneme *info = &classes[ i ];
  2287. // Assume it's bogus
  2288. info->valid = false;
  2289. info->amount = 0.0f;
  2290. // One time setup
  2291. if ( !info->basechecked )
  2292. {
  2293. info->basechecked = true;
  2294. info->base = (flexsettinghdr_t *)g_FlexSceneFileManager.FindSceneFile( this, info->classname, false );
  2295. }
  2296. info->exp = NULL;
  2297. if ( info->base )
  2298. {
  2299. Assert( info->base->id == ('V' << 16) + ('F' << 8) + ('E') );
  2300. info->exp = info->base->pIndexedSetting( phoneme );
  2301. }
  2302. if ( info->required && ( !info->base || !info->exp ) )
  2303. {
  2304. skip = true;
  2305. break;
  2306. }
  2307. if ( info->exp )
  2308. {
  2309. info->valid = true;
  2310. }
  2311. }
  2312. return skip;
  2313. }
  2314. #define STRONG_CROSSFADE_START 0.60f
  2315. #define WEAK_CROSSFADE_START 0.40f
  2316. //-----------------------------------------------------------------------------
  2317. // Purpose:
  2318. // Here's the formula
  2319. // 0.5 is neutral 100 % of the default setting
  2320. // Crossfade starts at STRONG_CROSSFADE_START and is full at STRONG_CROSSFADE_END
  2321. // If there isn't a strong then the intensity of the underlying phoneme is fixed at 2 x STRONG_CROSSFADE_START
  2322. // so we don't get huge numbers
  2323. // Input : *classes -
  2324. // emphasis_intensity -
  2325. //-----------------------------------------------------------------------------
  2326. void CTFPlayerModelPanel::ComputeBlendedSetting( Emphasized_Phoneme *classes, float emphasis_intensity )
  2327. {
  2328. // See which blends are available for the current phoneme
  2329. bool has_weak = classes[ PHONEME_CLASS_WEAK ].valid;
  2330. bool has_strong = classes[ PHONEME_CLASS_STRONG ].valid;
  2331. // Better have phonemes in general
  2332. Assert( classes[ PHONEME_CLASS_NORMAL ].valid );
  2333. if ( emphasis_intensity > STRONG_CROSSFADE_START )
  2334. {
  2335. if ( has_strong )
  2336. {
  2337. // Blend in some of strong
  2338. float dist_remaining = 1.0f - emphasis_intensity;
  2339. float frac = dist_remaining / ( 1.0f - STRONG_CROSSFADE_START );
  2340. classes[ PHONEME_CLASS_NORMAL ].amount = (frac) * 2.0f * STRONG_CROSSFADE_START;
  2341. classes[ PHONEME_CLASS_STRONG ].amount = 1.0f - frac;
  2342. }
  2343. else
  2344. {
  2345. emphasis_intensity = MIN( emphasis_intensity, STRONG_CROSSFADE_START );
  2346. classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
  2347. }
  2348. }
  2349. else if ( emphasis_intensity < WEAK_CROSSFADE_START )
  2350. {
  2351. if ( has_weak )
  2352. {
  2353. // Blend in some weak
  2354. float dist_remaining = WEAK_CROSSFADE_START - emphasis_intensity;
  2355. float frac = dist_remaining / ( WEAK_CROSSFADE_START );
  2356. classes[ PHONEME_CLASS_NORMAL ].amount = (1.0f - frac) * 2.0f * WEAK_CROSSFADE_START;
  2357. classes[ PHONEME_CLASS_WEAK ].amount = frac;
  2358. }
  2359. else
  2360. {
  2361. emphasis_intensity = MAX( emphasis_intensity, WEAK_CROSSFADE_START );
  2362. classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
  2363. }
  2364. }
  2365. else
  2366. {
  2367. // Assume 0.5 (neutral) becomes a scaling of 1.0f
  2368. classes[ PHONEME_CLASS_NORMAL ].amount = 2.0f * emphasis_intensity;
  2369. }
  2370. }
  2371. //-----------------------------------------------------------------------------
  2372. // Purpose:
  2373. //-----------------------------------------------------------------------------
  2374. void CTFPlayerModelPanel::InitPhonemeMappings( void )
  2375. {
  2376. CStudioHdr studioHdr( GetStudioHdr(), g_pMDLCache );
  2377. if ( studioHdr.IsValid() )
  2378. {
  2379. char szBasename[MAX_PATH];
  2380. Q_StripExtension( studioHdr.pszName(), szBasename, sizeof( szBasename ) );
  2381. char szExpressionName[MAX_PATH];
  2382. Q_snprintf( szExpressionName, sizeof( szExpressionName ), "%s/phonemes/phonemes", szBasename );
  2383. if ( g_FlexSceneFileManager.FindSceneFile( this, szExpressionName, false ) )
  2384. {
  2385. SetupMappings( szExpressionName );
  2386. return;
  2387. }
  2388. }
  2389. SetupMappings( "phonemes" );
  2390. }
  2391. //-----------------------------------------------------------------------------
  2392. // Purpose:
  2393. //-----------------------------------------------------------------------------
  2394. void CTFPlayerModelPanel::SetupMappings( char const *pchFileRoot )
  2395. {
  2396. // Fill in phoneme class lookup
  2397. memset( m_PhonemeClasses, 0, sizeof( m_PhonemeClasses ) );
  2398. Emphasized_Phoneme *normal = &m_PhonemeClasses[ PHONEME_CLASS_NORMAL ];
  2399. Q_snprintf( normal->classname, sizeof( normal->classname ), "%s", pchFileRoot );
  2400. normal->required = true;
  2401. Emphasized_Phoneme *weak = &m_PhonemeClasses[ PHONEME_CLASS_WEAK ];
  2402. Q_snprintf( weak->classname, sizeof( weak->classname ), "%s_weak", pchFileRoot );
  2403. Emphasized_Phoneme *strong = &m_PhonemeClasses[ PHONEME_CLASS_STRONG ];
  2404. Q_snprintf( strong->classname, sizeof( strong->classname ), "%s_strong", pchFileRoot );
  2405. }
  2406. //-----------------------------------------------------------------------------
  2407. // Purpose: Since everyone shared a pSettinghdr now, we need to set up the localtoglobal mapping per entity, but
  2408. // we just do this in memory with an array of integers (could be shorts, I suppose)
  2409. // Input : *pSettinghdr -
  2410. //-----------------------------------------------------------------------------
  2411. void CTFPlayerModelPanel::EnsureTranslations( const flexsettinghdr_t *pSettinghdr )
  2412. {
  2413. Assert( pSettinghdr );
  2414. FS_LocalToGlobal_t entry( pSettinghdr );
  2415. unsigned short idx = m_LocalToGlobal.Find( entry );
  2416. if ( idx != m_LocalToGlobal.InvalidIndex() )
  2417. return;
  2418. entry.SetCount( pSettinghdr->numkeys );
  2419. for ( int i = 0; i < pSettinghdr->numkeys; ++i )
  2420. {
  2421. entry.m_Mapping[ i ] = C_BaseFlex::AddGlobalFlexController( pSettinghdr->pLocalName( i ) );
  2422. }
  2423. m_LocalToGlobal.Insert( entry );
  2424. }
  2425. //-----------------------------------------------------------------------------
  2426. // Purpose: Look up instance specific mapping
  2427. // Input : *pSettinghdr -
  2428. // key -
  2429. // Output : int
  2430. //-----------------------------------------------------------------------------
  2431. int CTFPlayerModelPanel::FlexControllerLocalToGlobal( const flexsettinghdr_t *pSettinghdr, int key )
  2432. {
  2433. FS_LocalToGlobal_t entry( pSettinghdr );
  2434. int idx = m_LocalToGlobal.Find( entry );
  2435. if ( idx == m_LocalToGlobal.InvalidIndex() )
  2436. {
  2437. // This should never happen!!!
  2438. Assert( 0 );
  2439. Warning( "Unable to find mapping for flexcontroller %i, settings %p on CTFPlayerModelPanel\n", key, pSettinghdr );
  2440. EnsureTranslations( pSettinghdr );
  2441. idx = m_LocalToGlobal.Find( entry );
  2442. if ( idx == m_LocalToGlobal.InvalidIndex() )
  2443. {
  2444. Error( "CTFPlayerModelPanel::FlexControllerLocalToGlobal failed!\n" );
  2445. }
  2446. }
  2447. FS_LocalToGlobal_t& result = m_LocalToGlobal[ idx ];
  2448. // Validate lookup
  2449. Assert( result.m_nCount != 0 && key < result.m_nCount );
  2450. int index = result.m_Mapping[ key ];
  2451. return index;
  2452. }