Counter Strike : Global Offensive Source Code
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.

673 lines
16 KiB

  1. //====== Copyright c 1996-2007, Valve Corporation, All rights reserved. =======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "tier0/dbg.h"
  9. #include "tier0/platform.h"
  10. #include "mathlib/mathlib.h"
  11. #include "tier0/tslist.h"
  12. #include "tier1/utlmap.h"
  13. #include "tier1/convar.h"
  14. #include "bone_setup.h"
  15. #include "con_nprint.h"
  16. #include "cdll_int.h"
  17. #include "globalvars_base.h"
  18. #include "posedebugger.h"
  19. #include "iclientnetworkable.h"
  20. // memdbgon must be the last include file in a .cpp file!!!
  21. #include "tier0/memdbgon.h"
  22. extern IVEngineClient *engine;
  23. extern CGlobalVarsBase *gpGlobals;
  24. static ConVar ui_posedebug_fade_in_time( "ui_posedebug_fade_in_time", "0.2",
  25. FCVAR_CHEAT | FCVAR_DONTRECORD,
  26. "Time during which a new pose activity layer is shown in green in +posedebug UI" );
  27. static ConVar ui_posedebug_fade_out_time( "ui_posedebug_fade_out_time", "0.8",
  28. FCVAR_CHEAT | FCVAR_DONTRECORD,
  29. "Time to keep a no longer active pose activity layer in red until removing it from +posedebug UI" );
  30. //////////////////////////////////////////////////////////////////////////
  31. //
  32. // CPoseDebuggerStub : IPoseDebugger
  33. // empty interface implementation
  34. //
  35. //////////////////////////////////////////////////////////////////////////
  36. class CPoseDebuggerStub : public IPoseDebugger
  37. {
  38. public:
  39. virtual void StartBlending( IClientNetworkable *pEntity, const CStudioHdr *pStudioHdr ) { }
  40. virtual void AccumulatePose(
  41. const CStudioHdr *pStudioHdr,
  42. CIKContext *pIKContext,
  43. Vector pos[],
  44. Quaternion q[],
  45. int sequence,
  46. float cycle,
  47. const float poseParameter[],
  48. int boneMask,
  49. float flWeight,
  50. float flTime
  51. ) { }
  52. };
  53. static CPoseDebuggerStub s_PoseDebuggerStub;
  54. IPoseDebugger *g_pPoseDebugger = &s_PoseDebuggerStub;
  55. //////////////////////////////////////////////////////////////////////////
  56. //
  57. // CPoseDebuggerImpl : IPoseDebugger
  58. // Purpose: Main implementation of the pose debugger
  59. // Declaration
  60. //
  61. //////////////////////////////////////////////////////////////////////////
  62. class ModelPoseDebugInfo
  63. {
  64. public:
  65. ModelPoseDebugInfo() : m_iEntNum( 0 ), m_iCurrentText( 0 ) { }
  66. public:
  67. // Entity number
  68. int m_iEntNum;
  69. // Currently processed text
  70. int m_iCurrentText;
  71. // Info Text Flags
  72. enum InfoTextFlags
  73. {
  74. F_SEEN_THIS_FRAME = 1 << 0,
  75. F_SEEN_LAST_FRAME = 1 << 1,
  76. };
  77. struct InfoText
  78. {
  79. InfoText() { memset( this, 0, sizeof( *this ) ); }
  80. // Flags
  81. uint32 m_uiFlags;
  82. // Time seen
  83. float m_flTimeToLive, m_flTimeAlive;
  84. // Activity/label
  85. int m_iActivity;
  86. char m_chActivity[100];
  87. char m_chLabel[100];
  88. // Text
  89. char m_chTextLines[4][256];
  90. enum
  91. {
  92. MAX_TEXT_LINES = 4
  93. };
  94. };
  95. CCopyableUtlVector< InfoText > m_arrTxt;
  96. public:
  97. // Add an info text
  98. void AddInfoText( InfoText *x, ModelPoseDebugInfo *pOld );
  99. // Lookup an info text
  100. InfoText *LookupInfoText( InfoText *x );
  101. // Print pending info text
  102. void PrintPendingInfoText( int &rnPosPrint );
  103. };
  104. void ModelPoseDebugInfo::AddInfoText( InfoText *x, ModelPoseDebugInfo *pOld )
  105. {
  106. if ( x )
  107. {
  108. // Try to set the proper flags on the info text
  109. x->m_uiFlags &= ~F_SEEN_LAST_FRAME;
  110. x->m_uiFlags |= F_SEEN_THIS_FRAME;
  111. }
  112. // If we have smth to compare against
  113. if ( pOld )
  114. {
  115. // Search for the same activity/label in the other model pose debug info
  116. ModelPoseDebugInfo &o = *pOld;
  117. int k = o.m_iCurrentText;
  118. if ( x )
  119. {
  120. for ( ; k < o.m_arrTxt.Count(); ++ k )
  121. {
  122. InfoText &txt = o.m_arrTxt[k];
  123. if ( ( txt.m_uiFlags & F_SEEN_THIS_FRAME ) &&
  124. !stricmp( x->m_chActivity, txt.m_chActivity ) &&
  125. !stricmp( x->m_chLabel, txt.m_chLabel ) &&
  126. ( x->m_iActivity == txt.m_iActivity ) )
  127. {
  128. x->m_flTimeAlive = txt.m_flTimeAlive;
  129. break;
  130. }
  131. }
  132. }
  133. else
  134. {
  135. k = o.m_arrTxt.Count();
  136. }
  137. // Range of finished activities
  138. int iFinishedRange[2] = { o.m_iCurrentText, k };
  139. // Check whether this is a new message
  140. if ( k == o.m_arrTxt.Count() )
  141. {
  142. if ( !x )
  143. {
  144. o.m_iCurrentText = k;
  145. }
  146. else
  147. {
  148. // Don't update the current when insertion happens and don't have finished commands
  149. iFinishedRange[1] = iFinishedRange[0];
  150. }
  151. }
  152. else
  153. {
  154. o.m_iCurrentText = k + 1;
  155. if ( x )
  156. {
  157. x->m_uiFlags |= F_SEEN_LAST_FRAME;
  158. x->m_flTimeAlive += gpGlobals->frametime;
  159. }
  160. }
  161. // Everything before finished
  162. for ( int iFinished = iFinishedRange[0]; iFinished < iFinishedRange[1]; ++ iFinished )
  163. {
  164. InfoText &txtFinished = o.m_arrTxt[ iFinished ];
  165. if ( txtFinished.m_uiFlags & F_SEEN_THIS_FRAME )
  166. txtFinished.m_uiFlags |= F_SEEN_LAST_FRAME;
  167. txtFinished.m_uiFlags &= ~F_SEEN_THIS_FRAME;
  168. txtFinished.m_flTimeToLive -= gpGlobals->frametime;
  169. txtFinished.m_flTimeAlive += gpGlobals->frametime;
  170. if ( txtFinished.m_flTimeToLive >= 0.0f )
  171. m_arrTxt.AddToTail( txtFinished );
  172. }
  173. }
  174. if ( x )
  175. {
  176. // Now add it to the array
  177. x->m_flTimeToLive = ui_posedebug_fade_out_time.GetFloat();
  178. m_arrTxt.AddToTail( *x );
  179. }
  180. }
  181. ModelPoseDebugInfo::InfoText * ModelPoseDebugInfo::LookupInfoText( InfoText *x )
  182. {
  183. int k = m_iCurrentText;
  184. if ( x )
  185. {
  186. for ( ; k < m_arrTxt.Count(); ++ k )
  187. {
  188. InfoText &txt = m_arrTxt[k];
  189. if ( ( txt.m_uiFlags & F_SEEN_THIS_FRAME ) &&
  190. !stricmp( x->m_chActivity, txt.m_chActivity ) &&
  191. !stricmp( x->m_chLabel, txt.m_chLabel ) &&
  192. ( x->m_iActivity == txt.m_iActivity ) )
  193. {
  194. return &txt;
  195. }
  196. }
  197. }
  198. return NULL;
  199. }
  200. void ModelPoseDebugInfo::PrintPendingInfoText( int &rnPosPrint )
  201. {
  202. con_nprint_s nxPrn = { 0 };
  203. nxPrn.time_to_live = -1;
  204. nxPrn.color[0] = 1.0f, nxPrn.color[1] = 1.0f, nxPrn.color[2] = 1.0f;
  205. nxPrn.fixed_width_font = true;
  206. float const flFadeInTime = ui_posedebug_fade_in_time.GetFloat();
  207. float const flFadeOutTime = ui_posedebug_fade_out_time.GetFloat();
  208. // Now print all the accumulated spew
  209. for ( int k = m_iCurrentText; k < m_arrTxt.Count(); ++ k )
  210. {
  211. InfoText &prntxt = m_arrTxt[k];
  212. switch( prntxt.m_uiFlags & ( F_SEEN_LAST_FRAME | F_SEEN_THIS_FRAME ) )
  213. {
  214. case ( F_SEEN_LAST_FRAME | F_SEEN_THIS_FRAME ) :
  215. nxPrn.color[0] = 1.f;
  216. nxPrn.color[1] = 1.f;
  217. nxPrn.color[2] = 1.f;
  218. if ( prntxt.m_flTimeAlive > flFadeInTime )
  219. break;
  220. else
  221. NULL; // Fall-through to keep showing in green
  222. case F_SEEN_THIS_FRAME :
  223. if ( flFadeInTime > 0.f )
  224. {
  225. nxPrn.color[0] = 1.f * prntxt.m_flTimeAlive / flFadeInTime;
  226. nxPrn.color[1] = 1.f;
  227. nxPrn.color[2] = 1.f * prntxt.m_flTimeAlive / flFadeInTime;
  228. }
  229. else
  230. {
  231. nxPrn.color[0] = ( prntxt.m_flTimeAlive > 0.0f ) ? 1.f : 0.f;
  232. nxPrn.color[1] = 1.f;
  233. nxPrn.color[2] = ( prntxt.m_flTimeAlive > 0.0f ) ? 1.f : 0.f;
  234. }
  235. break;
  236. case F_SEEN_LAST_FRAME :
  237. case 0:
  238. if ( flFadeOutTime > 0.f )
  239. nxPrn.color[0] = 1.f * prntxt.m_flTimeToLive / flFadeOutTime;
  240. else
  241. nxPrn.color[0] = ( prntxt.m_flTimeToLive > 0.0f ) ? 1.f : 0.f;
  242. nxPrn.color[1] = 0.f;
  243. nxPrn.color[2] = 0.f;
  244. break;
  245. }
  246. nxPrn.index = ( rnPosPrint += 1 );
  247. engine->Con_NXPrintf( &nxPrn, "%s", prntxt.m_chTextLines[0] );
  248. for ( int iLine = 1; iLine < ModelPoseDebugInfo::InfoText::MAX_TEXT_LINES; ++ iLine)
  249. {
  250. if ( !prntxt.m_chTextLines[iLine][0] )
  251. break;
  252. nxPrn.index = ( rnPosPrint += 1 );
  253. engine->Con_NXPrintf( &nxPrn, "%s", prntxt.m_chTextLines[iLine] );
  254. }
  255. }
  256. m_iCurrentText = m_arrTxt.Count();
  257. }
  258. class CPoseDebuggerImpl : public IPoseDebugger
  259. {
  260. public:
  261. CPoseDebuggerImpl();
  262. ~CPoseDebuggerImpl();
  263. public:
  264. void ShowAllModels( bool bShow );
  265. void ShowModel( int iEntNum, bool bShow );
  266. bool IsModelShown( int iEntNum ) const;
  267. public:
  268. virtual void StartBlending( IClientNetworkable *pEntity, const CStudioHdr *pStudioHdr );
  269. virtual void AccumulatePose(
  270. const CStudioHdr *pStudioHdr,
  271. CIKContext *pIKContext,
  272. Vector pos[],
  273. Quaternion q[],
  274. int sequence,
  275. float cycle,
  276. const float poseParameter[],
  277. int boneMask,
  278. float flWeight,
  279. float flTime
  280. );
  281. protected:
  282. typedef CUtlMap< CStudioHdr const *, ModelPoseDebugInfo > MapModel;
  283. MapModel m_mapModel, m_mapModelOld;
  284. int m_nPosPrint;
  285. CBitVec< MAX_EDICTS > m_uiMaskShowModels;
  286. CStudioHdr const *m_pLastModel;
  287. };
  288. static CPoseDebuggerImpl s_PoseDebuggerImpl;
  289. //////////////////////////////////////////////////////////////////////////
  290. //
  291. // CPoseDebuggerImpl
  292. // Implementation
  293. //
  294. //////////////////////////////////////////////////////////////////////////
  295. CPoseDebuggerImpl::CPoseDebuggerImpl() :
  296. m_mapModel( DefLessFunc( CStudioHdr const * ) ),
  297. m_mapModelOld( DefLessFunc( CStudioHdr const * ) ),
  298. m_nPosPrint( 0 ),
  299. m_pLastModel( NULL )
  300. {
  301. m_uiMaskShowModels.SetAll();
  302. }
  303. CPoseDebuggerImpl::~CPoseDebuggerImpl()
  304. {
  305. // g_pPoseDebugger = &s_PoseDebuggerStub;
  306. }
  307. void CPoseDebuggerImpl::ShowAllModels( bool bShow )
  308. {
  309. bShow ? m_uiMaskShowModels.SetAll() : m_uiMaskShowModels.ClearAll();
  310. }
  311. void CPoseDebuggerImpl::ShowModel( int iEntNum, bool bShow )
  312. {
  313. Assert( iEntNum < MAX_EDICTS );
  314. if ( iEntNum < MAX_EDICTS )
  315. m_uiMaskShowModels.Set( iEntNum, bShow );
  316. }
  317. bool CPoseDebuggerImpl::IsModelShown( int iEntNum ) const
  318. {
  319. Assert( iEntNum < MAX_EDICTS );
  320. if ( iEntNum >= 0 && iEntNum < MAX_EDICTS )
  321. return m_uiMaskShowModels.IsBitSet( iEntNum );
  322. else
  323. return false;
  324. }
  325. void CPoseDebuggerImpl::StartBlending( IClientNetworkable *pEntity, const CStudioHdr *pStudioHdr )
  326. {
  327. // virtualmodel_t const *pVMdl = pStudioHdr->GetVirtualModel();
  328. // if ( !pVMdl )
  329. // return;
  330. if ( !ThreadInMainThread() )
  331. {
  332. ExecuteOnce( "Turn of threading when using pose debugger\n" );
  333. return;
  334. }
  335. // If we are starting a new model then finalize the previous one
  336. if ( pStudioHdr != m_pLastModel && m_pLastModel )
  337. {
  338. MapModel::IndexType_t idx = m_mapModel.Find( m_pLastModel );
  339. if ( idx != m_mapModel.InvalidIndex() )
  340. {
  341. ModelPoseDebugInfo &mpi = m_mapModel.Element( idx );
  342. ModelPoseDebugInfo *pMpiOld = NULL;
  343. MapModel::IndexType_t idxMapModelOld = m_mapModelOld.Find( m_pLastModel );
  344. if ( idxMapModelOld != m_mapModelOld.InvalidIndex() )
  345. {
  346. pMpiOld = &m_mapModelOld.Element( idxMapModelOld );
  347. }
  348. mpi.AddInfoText( NULL, pMpiOld );
  349. mpi.PrintPendingInfoText( m_nPosPrint );
  350. }
  351. }
  352. m_pLastModel = pStudioHdr;
  353. // Go ahead with the new model
  354. studiohdr_t const *pRMdl = pStudioHdr->GetRenderHdr();
  355. if ( !pRMdl ||
  356. !pRMdl->numincludemodels )
  357. return;
  358. // Entity number
  359. int iEntNum = pEntity->entindex();
  360. if ( !IsModelShown( iEntNum ) )
  361. return;
  362. // Check if we saw the model
  363. if ( m_mapModel.Find( pStudioHdr ) != m_mapModel.InvalidIndex() )
  364. {
  365. // Initialize the printing position
  366. m_nPosPrint = 9;
  367. // Swap the maps
  368. m_mapModelOld.RemoveAll();
  369. m_mapModelOld.Swap( m_mapModel );
  370. // Zero out the text on the old map
  371. for ( int k = m_mapModelOld.FirstInorder();
  372. k != m_mapModelOld.InvalidIndex();
  373. k = m_mapModelOld.NextInorder( k ) )
  374. {
  375. ModelPoseDebugInfo &mpi = m_mapModelOld[k];
  376. mpi.m_iCurrentText = 0;
  377. }
  378. }
  379. else
  380. {
  381. // Next model
  382. m_nPosPrint += 3;
  383. }
  384. ModelPoseDebugInfo mpi;
  385. mpi.m_iEntNum = iEntNum;
  386. m_mapModel.Insert( pStudioHdr, mpi );
  387. con_nprint_s nxPrn = { 0 };
  388. nxPrn.index = m_nPosPrint;
  389. nxPrn.time_to_live = -1;
  390. nxPrn.color[0] = 0.9f, nxPrn.color[1] = 1.0f, nxPrn.color[2] = 0.9f;
  391. nxPrn.fixed_width_font = false;
  392. engine->Con_NXPrintf( &nxPrn, "[ %2d ] Model: %s", iEntNum, pRMdl->pszName() );
  393. m_nPosPrint += 3;
  394. }
  395. void CPoseDebuggerImpl::AccumulatePose( const CStudioHdr *pStudioHdr, CIKContext *pIKContext,
  396. Vector pos[], Quaternion q[], int sequence, float cycle,
  397. const float poseParameter[], int boneMask,
  398. float flWeight, float flTime )
  399. {
  400. // virtualmodel_t const *pVMdl = pStudioHdr->GetVirtualModel();
  401. // if ( !pVMdl )
  402. // return;
  403. if ( !ThreadInMainThread() )
  404. {
  405. return;
  406. }
  407. studiohdr_t const *pRMdl = pStudioHdr->GetRenderHdr();
  408. if ( !pRMdl ||
  409. !pRMdl->numincludemodels )
  410. return;
  411. MapModel::IndexType_t idxMapModel = m_mapModel.Find( pStudioHdr );
  412. if ( idxMapModel == m_mapModel.InvalidIndex() )
  413. return;
  414. ModelPoseDebugInfo &mpi = m_mapModel.Element( idxMapModel );
  415. if ( !IsModelShown( mpi.m_iEntNum ) )
  416. return;
  417. ModelPoseDebugInfo *pMpiOld = NULL;
  418. MapModel::IndexType_t idxMapModelOld = m_mapModelOld.Find( pStudioHdr );
  419. if ( idxMapModelOld != m_mapModelOld.InvalidIndex() )
  420. {
  421. pMpiOld = &m_mapModelOld.Element( idxMapModelOld );
  422. }
  423. //
  424. // Actual processing
  425. //
  426. mstudioseqdesc_t &seqdesc = ((CStudioHdr *)pStudioHdr)->pSeqdesc( sequence );
  427. if ( sequence >= pStudioHdr->GetNumSeq() )
  428. {
  429. sequence = 0;
  430. seqdesc = ((CStudioHdr *)pStudioHdr)->pSeqdesc( sequence );
  431. }
  432. enum
  433. {
  434. widthActivity = 35,
  435. widthLayer = 35,
  436. widthIks = 60,
  437. widthPercent = 6,
  438. };
  439. // Prepare the text
  440. char chBuffer[256];
  441. ModelPoseDebugInfo::InfoText txt;
  442. int numLines = 0;
  443. txt.m_iActivity = seqdesc.activity;
  444. Q_snprintf( txt.m_chActivity, ARRAYSIZE( txt.m_chActivity ), "%s", seqdesc.pszActivityName() );
  445. Q_snprintf( txt.m_chLabel, ARRAYSIZE( txt.m_chLabel ), "%s", seqdesc.pszLabel() );
  446. if ( !txt.m_chActivity[0] )
  447. {
  448. // Try to find the last seen activity and re-use it
  449. for ( int iLast = mpi.m_arrTxt.Count(); iLast --> 0; )
  450. {
  451. ModelPoseDebugInfo::InfoText &lastSeenTxt = mpi.m_arrTxt[iLast];
  452. if ( lastSeenTxt.m_uiFlags & ModelPoseDebugInfo::F_SEEN_THIS_FRAME &&
  453. lastSeenTxt.m_chActivity[0] )
  454. {
  455. Q_snprintf( txt.m_chActivity, ARRAYSIZE( txt.m_chActivity ), "%s", lastSeenTxt.m_chActivity );
  456. break;
  457. }
  458. }
  459. }
  460. // The layer information
  461. ModelPoseDebugInfo::InfoText *pOldTxt = pMpiOld ? pMpiOld->LookupInfoText( &txt ) : NULL;
  462. Q_snprintf( txt.m_chTextLines[numLines],
  463. ARRAYSIZE( txt.m_chTextLines[numLines] ),
  464. "%-*s %-*s %*.2f %*.1f/%-*d %*.0f%% ",
  465. widthActivity,
  466. seqdesc.pszActivityName(),
  467. widthLayer,
  468. seqdesc.pszLabel(),
  469. 7,
  470. pOldTxt ? pOldTxt->m_flTimeAlive : 0.f,
  471. 5,
  472. cycle * ( ((CStudioHdr *)pStudioHdr)->pAnimdesc( seqdesc.anim( 0, 0 ) ).numframes - 1 ),
  473. 3,
  474. ((CStudioHdr *)pStudioHdr)->pAnimdesc( seqdesc.anim( 0, 0 ) ).numframes,
  475. widthPercent,
  476. flWeight * 100.0f
  477. );
  478. ++ numLines;
  479. if ( seqdesc.numiklocks )
  480. {
  481. Q_snprintf( chBuffer,
  482. ARRAYSIZE( chBuffer ),
  483. "iklocks : %-2d : ",
  484. seqdesc.numiklocks );
  485. for ( int k = 0; k < seqdesc.numiklocks; ++ k )
  486. {
  487. mstudioiklock_t *plock = seqdesc.pIKLock( k );
  488. mstudioikchain_t *pchain = pStudioHdr->pIKChain( plock->chain );
  489. Q_snprintf( chBuffer + strlen( chBuffer ), ARRAYSIZE( chBuffer ) - strlen( chBuffer ), "%s ", pchain->pszName() );
  490. // plock->flPosWeight;
  491. // plock->flLocalQWeight;
  492. }
  493. Q_snprintf( txt.m_chTextLines[numLines],
  494. ARRAYSIZE( txt.m_chTextLines[numLines] ),
  495. "%-*s",
  496. widthIks,
  497. chBuffer
  498. );
  499. ++ numLines;
  500. }
  501. if ( seqdesc.numikrules )
  502. {
  503. Q_snprintf( chBuffer, ARRAYSIZE( chBuffer ), "ikrules : %-2d",
  504. seqdesc.numikrules );
  505. Q_snprintf( txt.m_chTextLines[numLines],
  506. ARRAYSIZE( txt.m_chTextLines[numLines] ),
  507. "%-*s",
  508. widthIks,
  509. chBuffer
  510. );
  511. ++ numLines;
  512. }
  513. // Now add the accumulated text into the container
  514. mpi.AddInfoText( &txt, pMpiOld );
  515. mpi.PrintPendingInfoText( m_nPosPrint );
  516. }
  517. #ifdef _DEBUG
  518. //////////////////////////////////////////////////////////////////////////
  519. //
  520. // Con-commands
  521. //
  522. //////////////////////////////////////////////////////////////////////////
  523. static void IN_PoseDebuggerStart( const CCommand &args )
  524. {
  525. if ( args.ArgC() <= 1 )
  526. {
  527. // No args, enable all
  528. s_PoseDebuggerImpl.ShowAllModels( true );
  529. }
  530. else
  531. {
  532. // If explicitly showing the pose debugger when it was disabled
  533. if ( g_pPoseDebugger != &s_PoseDebuggerImpl )
  534. {
  535. s_PoseDebuggerImpl.ShowAllModels( false );
  536. }
  537. // Show only specific models
  538. for ( int k = 1; k < args.ArgC(); ++ k )
  539. {
  540. int iEntNum = atoi( args.Arg( k ) );
  541. s_PoseDebuggerImpl.ShowModel( iEntNum, true );
  542. }
  543. }
  544. g_pPoseDebugger = &s_PoseDebuggerImpl;
  545. }
  546. static void IN_PoseDebuggerEnd( const CCommand &args )
  547. {
  548. if ( args.ArgC() <= 1 )
  549. {
  550. // No args, disable all
  551. s_PoseDebuggerImpl.ShowAllModels( false );
  552. // Set the stub pointer
  553. g_pPoseDebugger = &s_PoseDebuggerStub;
  554. }
  555. else
  556. {
  557. // Hide only specific models
  558. for ( int k = 1; k < args.ArgC(); ++ k )
  559. {
  560. int iEntNum = atoi( args.Arg( k ) );
  561. s_PoseDebuggerImpl.ShowModel( iEntNum, false );
  562. }
  563. }
  564. }
  565. static ConCommand posedebuggerstart( "+posedebug", IN_PoseDebuggerStart, "Turn on pose debugger or add ents to pose debugger UI", FCVAR_CHEAT );
  566. static ConCommand posedebuggerend ( "-posedebug", IN_PoseDebuggerEnd, "Turn off pose debugger or hide ents from pose debugger UI", FCVAR_CHEAT );
  567. #endif