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.

3302 lines
77 KiB

  1. //========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include <ctype.h>
  9. #include <vstdlib/vstrtools.h>
  10. #ifdef _PS3
  11. #include <wctype.h>
  12. int wcsnlen( wchar_t const *wsz, int nMaxLen )
  13. {
  14. int nLen = 0;
  15. while ( nMaxLen -- > 0 )
  16. {
  17. if ( *( wsz ++ ) )
  18. ++ nLen;
  19. else
  20. break;
  21. }
  22. return nLen;
  23. }
  24. #endif
  25. #include "sentence.h"
  26. #include "hud_closecaption.h"
  27. #include "tier1/strtools.h"
  28. #include <vgui_controls/Controls.h>
  29. #include <vgui/IVGui.h>
  30. #include <vgui/ISurface.h>
  31. #include <vgui/IScheme.h>
  32. #include <vgui/ILocalize.h>
  33. #include "iclientmode.h"
  34. #include "hud_macros.h"
  35. #include "checksum_crc.h"
  36. #include "filesystem.h"
  37. #include "datacache/idatacache.h"
  38. #include "SoundEmitterSystem/isoundemittersystembase.h"
  39. #include "tier3/tier3.h"
  40. #include "characterset.h"
  41. #include "gamerules.h"
  42. // memdbgon must be the last include file in a .cpp file!!!
  43. #include "tier0/memdbgon.h"
  44. #define CC_INSET 12
  45. extern ISoundEmitterSystemBase *soundemitterbase;
  46. static bool GetDefaultSubtitlesState()
  47. {
  48. return XBX_IsLocalized() && !XBX_IsAudioLocalized();
  49. }
  50. // Marked as FCVAR_USERINFO so that the server can cull CC messages before networking them down to us!!!
  51. ConVar closecaption( "closecaption", GetDefaultSubtitlesState() ? "1" : "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE, "Enable close captioning." );
  52. extern ConVar cc_lang;
  53. static ConVar cc_linger_time( "cc_linger_time", "1.0", FCVAR_ARCHIVE, "Close caption linger time." );
  54. static ConVar cc_predisplay_time( "cc_predisplay_time", "0.25", FCVAR_ARCHIVE, "Close caption delay before showing caption." );
  55. static ConVar cc_captiontrace( "cc_captiontrace", "1", 0, "Show missing closecaptions (0 = no, 1 = devconsole, 2 = show in hud)" );
  56. static ConVar cc_subtitles( "cc_subtitles", GetDefaultSubtitlesState() ? "1" : "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_GAMECONSOLE, "If set, don't show sound effect captions, just voice overs (i.e., won't help hearing impaired players)." );
  57. ConVar english( "english", "1", FCVAR_HIDDEN, "If set to 1, running the english language set of assets." );
  58. #define MAX_CAPTION_CHARACTERS 10200
  59. #define CAPTION_PAN_FADE_TIME 0.5 // The time it takes for a line to fade while panning over a large entry
  60. #define CAPTION_PAN_SLIDE_TIME 0.5 // The time it takes for a line to slide on while panning over a large entry
  61. // A work unit is a pre-processed chunk of CC text to display
  62. // Any state changes (font/color/etc) cause a new work unit to be precomputed
  63. // Moving onto a new line also causes a new Work Unit
  64. // The width and height are stored so that layout can be quickly recomputed each frame
  65. class CCloseCaptionWorkUnit
  66. {
  67. public:
  68. CCloseCaptionWorkUnit();
  69. ~CCloseCaptionWorkUnit();
  70. void SetWidth( int w );
  71. int GetWidth() const;
  72. void SetHeight( int h );
  73. int GetHeight() const;
  74. void SetPos( int x, int y );
  75. void GetPos( int& x, int &y ) const;
  76. void SetFadeStart( float flTime );
  77. float GetFadeStart( void ) const;
  78. void SetBold( bool bold );
  79. bool GetBold() const;
  80. void SetItalic( bool ital );
  81. bool GetItalic() const;
  82. void SetStream( const wchar_t *stream );
  83. const wchar_t *GetStream() const;
  84. void SetColor( Color& clr );
  85. Color GetColor() const;
  86. vgui::HFont GetFont() const
  87. {
  88. return m_hFont;
  89. }
  90. void SetFont( vgui::HFont fnt )
  91. {
  92. m_hFont = fnt;
  93. }
  94. void Dump()
  95. {
  96. char buf[ 2048 ];
  97. g_pVGuiLocalize->ConvertUnicodeToANSI( GetStream(), buf, sizeof( buf ) );
  98. Msg( "x = %i, y = %i, w = %i h = %i text %s\n", m_nX, m_nY, m_nWidth, m_nHeight, buf );
  99. }
  100. private:
  101. int m_nX;
  102. int m_nY;
  103. int m_nWidth;
  104. int m_nHeight;
  105. float m_flFadeStartTime;
  106. bool m_bBold;
  107. bool m_bItalic;
  108. wchar_t *m_pszStream;
  109. vgui::HFont m_hFont;
  110. Color m_Color;
  111. };
  112. CCloseCaptionWorkUnit::CCloseCaptionWorkUnit() :
  113. m_nWidth(0),
  114. m_nHeight(0),
  115. m_bBold(false),
  116. m_bItalic(false),
  117. m_pszStream(0),
  118. m_Color( Color( 255, 255, 255, 255 ) ),
  119. m_hFont( 0 ),
  120. m_flFadeStartTime(0)
  121. {
  122. }
  123. CCloseCaptionWorkUnit::~CCloseCaptionWorkUnit()
  124. {
  125. delete[] m_pszStream;
  126. m_pszStream = NULL;
  127. }
  128. void CCloseCaptionWorkUnit::SetWidth( int w )
  129. {
  130. m_nWidth = w;
  131. }
  132. int CCloseCaptionWorkUnit::GetWidth() const
  133. {
  134. return m_nWidth;
  135. }
  136. void CCloseCaptionWorkUnit::SetHeight( int h )
  137. {
  138. m_nHeight = h;
  139. }
  140. int CCloseCaptionWorkUnit::GetHeight() const
  141. {
  142. return m_nHeight;
  143. }
  144. void CCloseCaptionWorkUnit::SetPos( int x, int y )
  145. {
  146. m_nX = x;
  147. m_nY = y;
  148. }
  149. void CCloseCaptionWorkUnit::GetPos( int& x, int &y ) const
  150. {
  151. x = m_nX;
  152. y = m_nY;
  153. }
  154. void CCloseCaptionWorkUnit::SetFadeStart( float flTime )
  155. {
  156. m_flFadeStartTime = flTime;
  157. }
  158. float CCloseCaptionWorkUnit::GetFadeStart( void ) const
  159. {
  160. return m_flFadeStartTime;
  161. }
  162. void CCloseCaptionWorkUnit::SetBold( bool bold )
  163. {
  164. m_bBold = bold;
  165. }
  166. bool CCloseCaptionWorkUnit::GetBold() const
  167. {
  168. return m_bBold;
  169. }
  170. void CCloseCaptionWorkUnit::SetItalic( bool ital )
  171. {
  172. m_bItalic = ital;
  173. }
  174. bool CCloseCaptionWorkUnit::GetItalic() const
  175. {
  176. return m_bItalic;
  177. }
  178. void CCloseCaptionWorkUnit::SetStream( const wchar_t *stream )
  179. {
  180. delete[] m_pszStream;
  181. m_pszStream = NULL;
  182. #ifdef WIN32
  183. int len = wcsnlen( stream, ( MAX_CAPTION_CHARACTERS - 1 ) );
  184. #else
  185. int len = wcslen( stream );
  186. #endif
  187. Assert( len < ( MAX_CAPTION_CHARACTERS - 1 ) );
  188. m_pszStream = new wchar_t[ len + 1 ];
  189. wcsncpy( m_pszStream, stream, len );
  190. m_pszStream[ len ] = L'\0';
  191. }
  192. const wchar_t *CCloseCaptionWorkUnit::GetStream() const
  193. {
  194. return m_pszStream ? m_pszStream : L"";
  195. }
  196. void CCloseCaptionWorkUnit::SetColor( Color& clr )
  197. {
  198. m_Color = clr;
  199. }
  200. Color CCloseCaptionWorkUnit::GetColor() const
  201. {
  202. return m_Color;
  203. }
  204. //-----------------------------------------------------------------------------
  205. // Purpose:
  206. //-----------------------------------------------------------------------------
  207. class CCloseCaptionItem
  208. {
  209. public:
  210. CCloseCaptionItem(
  211. const wchar_t *stream,
  212. float timetolive,
  213. float addedtime,
  214. float predisplay,
  215. bool valid,
  216. bool fromplayer,
  217. bool bSFXEntry,
  218. bool bLowPriorityEntry
  219. ) :
  220. m_flTimeToLive( 0.0f ),
  221. m_flAddedTime( addedtime ),
  222. m_bValid( false ),
  223. m_nTotalWidth( 0 ),
  224. m_nTotalHeight( 0 ),
  225. m_bSizeComputed( false ),
  226. m_bFromPlayer( fromplayer ),
  227. m_bSFXEntry( bSFXEntry ),
  228. m_bLowPriorityEntry( bLowPriorityEntry )
  229. {
  230. SetStream( stream );
  231. SetTimeToLive( timetolive );
  232. SetInitialLifeSpan( timetolive );
  233. SetPreDisplayTime( cc_predisplay_time.GetFloat() + predisplay );
  234. m_bValid = valid;
  235. }
  236. CCloseCaptionItem( const CCloseCaptionItem& src )
  237. {
  238. SetStream( src.m_szStream );
  239. m_flTimeToLive = src.m_flTimeToLive;
  240. m_bValid = src.m_bValid;
  241. m_bFromPlayer = src.m_bFromPlayer;
  242. m_flAddedTime = src.m_flAddedTime;
  243. }
  244. ~CCloseCaptionItem( void )
  245. {
  246. while ( m_Work.Count() > 0 )
  247. {
  248. CCloseCaptionWorkUnit *unit = m_Work[ 0 ];
  249. m_Work.Remove( 0 );
  250. delete unit;
  251. }
  252. }
  253. void SetStream( const wchar_t *stream)
  254. {
  255. wcsncpy( m_szStream, stream, sizeof( m_szStream ) / sizeof( wchar_t ) );
  256. }
  257. const wchar_t *GetStream() const
  258. {
  259. return m_szStream;
  260. }
  261. void SetTimeToLive( float ttl )
  262. {
  263. m_flTimeToLive = ttl;
  264. }
  265. float GetTimeToLive( void ) const
  266. {
  267. return m_flTimeToLive;
  268. }
  269. void SetInitialLifeSpan( float t )
  270. {
  271. m_flInitialLifeSpan = t;
  272. }
  273. float GetInitialLifeSpan() const
  274. {
  275. return m_flInitialLifeSpan;
  276. }
  277. bool IsValid() const
  278. {
  279. return m_bValid;
  280. }
  281. void SetHeight( int h )
  282. {
  283. m_nTotalHeight = h;
  284. }
  285. int GetHeight() const
  286. {
  287. return m_nTotalHeight;
  288. }
  289. void SetWidth( int w )
  290. {
  291. m_nTotalWidth = w;
  292. }
  293. int GetWidth() const
  294. {
  295. return m_nTotalWidth;
  296. }
  297. void AddWork( CCloseCaptionWorkUnit *unit )
  298. {
  299. m_Work.AddToTail( unit );
  300. }
  301. int GetNumWorkUnits() const
  302. {
  303. return m_Work.Count();
  304. }
  305. CCloseCaptionWorkUnit *GetWorkUnit( int index )
  306. {
  307. Assert( index >= 0 && index < m_Work.Count() );
  308. return m_Work[ index ];
  309. }
  310. void SetSizeComputed( bool computed )
  311. {
  312. m_bSizeComputed = computed;
  313. }
  314. bool GetSizeComputed() const
  315. {
  316. return m_bSizeComputed;
  317. }
  318. void SetPreDisplayTime( float t )
  319. {
  320. m_flPreDisplayTime = t;
  321. }
  322. float GetPreDisplayTime() const
  323. {
  324. return m_flPreDisplayTime;
  325. }
  326. float GetAlpha( float fadeintimehidden, float fadeintime, float fadeouttime )
  327. {
  328. float time_since_start = m_flInitialLifeSpan - m_flTimeToLive;
  329. float time_until_end = m_flTimeToLive;
  330. float totalfadeintime = fadeintimehidden + fadeintime;
  331. if ( totalfadeintime > 0.001f &&
  332. time_since_start < totalfadeintime )
  333. {
  334. if ( time_since_start >= fadeintimehidden )
  335. {
  336. float f = 1.0f;
  337. if ( fadeintime > 0.001f )
  338. {
  339. f = ( time_since_start - fadeintimehidden ) / fadeintime;
  340. }
  341. f = clamp( f, 0.0f, 1.0f );
  342. return f;
  343. }
  344. return 0.0f;
  345. }
  346. if ( fadeouttime > 0.001f &&
  347. time_until_end < fadeouttime )
  348. {
  349. float f = time_until_end / fadeouttime;
  350. f = clamp( f, 0.0f, 1.0f );
  351. return f;
  352. }
  353. return 1.0f;
  354. }
  355. float GetAddedTime() const
  356. {
  357. return m_flAddedTime;
  358. }
  359. void SetAddedTime( float addt )
  360. {
  361. m_flAddedTime = addt;
  362. }
  363. bool IsFromPlayer() const
  364. {
  365. return m_bFromPlayer;
  366. }
  367. bool IsSFXEntry() const
  368. {
  369. return m_bSFXEntry;
  370. }
  371. bool IsLowPriorityEntry() const
  372. {
  373. return m_bLowPriorityEntry;
  374. }
  375. private:
  376. wchar_t m_szStream[ MAX_CAPTION_CHARACTERS ];
  377. float m_flPreDisplayTime;
  378. float m_flTimeToLive;
  379. float m_flInitialLifeSpan;
  380. float m_flAddedTime;
  381. bool m_bValid;
  382. int m_nTotalWidth;
  383. int m_nTotalHeight;
  384. bool m_bSizeComputed;
  385. bool m_bFromPlayer;
  386. bool m_bSFXEntry;
  387. bool m_bLowPriorityEntry;
  388. CUtlVector< CCloseCaptionWorkUnit * > m_Work;
  389. };
  390. struct VisibleStreamItem
  391. {
  392. int height;
  393. int width;
  394. CCloseCaptionItem *item;
  395. };
  396. //-----------------------------------------------------------------------------
  397. // Purpose: The only resource manager parameter we currently care about is the name
  398. // of the .vcd to cache into memory
  399. //-----------------------------------------------------------------------------
  400. struct asynccaptionparams_t
  401. {
  402. const char *dbfile;
  403. int fileindex;
  404. int blocktoload;
  405. int blockoffset;
  406. int blocksize;
  407. };
  408. // 16K of cache for close caption data
  409. #define MAX_ASYNCCAPTION_MEMORY_CACHE (int)( 64.0 * 1024.0f )
  410. void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus );
  411. struct AsyncCaptionData_t
  412. {
  413. int m_nBlockNum;
  414. byte *m_pBlockData;
  415. int m_nFileIndex;
  416. int m_nBlockSize;
  417. bool m_bLoadPending : 1;
  418. bool m_bLoadCompleted : 1;
  419. FSAsyncControl_t m_hAsyncControl;
  420. AsyncCaptionData_t() :
  421. m_nBlockNum( -1 ),
  422. m_pBlockData( 0 ),
  423. m_nFileIndex( -1 ),
  424. m_nBlockSize( 0 ),
  425. m_bLoadPending( false ),
  426. m_bLoadCompleted( false ),
  427. m_hAsyncControl( NULL )
  428. {
  429. }
  430. // APIS required by CDataManager
  431. void DestroyResource()
  432. {
  433. if ( m_bLoadPending && !m_bLoadCompleted )
  434. {
  435. filesystem->AsyncFinish( m_hAsyncControl, true );
  436. }
  437. filesystem->AsyncRelease( m_hAsyncControl );
  438. WipeData();
  439. delete this;
  440. }
  441. void ReleaseData()
  442. {
  443. filesystem->AsyncRelease( m_hAsyncControl );
  444. m_hAsyncControl = 0;
  445. WipeData();
  446. m_bLoadCompleted = false;
  447. Assert( !m_bLoadPending );
  448. }
  449. void WipeData()
  450. {
  451. delete[] m_pBlockData;
  452. m_pBlockData = NULL;
  453. }
  454. AsyncCaptionData_t *GetData()
  455. {
  456. return this;
  457. }
  458. unsigned int Size()
  459. {
  460. return sizeof( *this ) + m_nBlockSize;
  461. }
  462. void AsyncLoad( const char *fileName, int blockOffset )
  463. {
  464. // Already pending
  465. Assert ( !m_hAsyncControl );
  466. // async load the file
  467. FileAsyncRequest_t fileRequest;
  468. fileRequest.pContext = (void *)this;
  469. fileRequest.pfnCallback = ::CaptionAsyncLoaderCallback;
  470. fileRequest.pData = m_pBlockData;
  471. fileRequest.pszFilename = fileName;
  472. fileRequest.nOffset = blockOffset;
  473. fileRequest.flags = 0;
  474. fileRequest.nBytes = m_nBlockSize;
  475. fileRequest.priority = -1;
  476. fileRequest.pszPathID = "GAME";
  477. // queue for async load
  478. MEM_ALLOC_CREDIT();
  479. filesystem->AsyncRead( fileRequest, &m_hAsyncControl );
  480. }
  481. // you must implement these static functions for the ResourceManager
  482. // -----------------------------------------------------------
  483. static AsyncCaptionData_t *CreateResource( const asynccaptionparams_t &params )
  484. {
  485. AsyncCaptionData_t *data = new AsyncCaptionData_t;
  486. data->m_nBlockNum = params.blocktoload;
  487. data->m_nFileIndex = params.fileindex;
  488. data->m_nBlockSize = params.blocksize;
  489. data->m_pBlockData = new byte[ data->m_nBlockSize ];
  490. return data;
  491. }
  492. static unsigned int EstimatedSize( const asynccaptionparams_t &params )
  493. {
  494. // The block size is assumed to be 4K
  495. return ( sizeof( AsyncCaptionData_t ) + params.blocksize );
  496. }
  497. };
  498. //-----------------------------------------------------------------------------
  499. // Purpose: This manages the instanced scene memory handles. We essentially grow a handle list by scene filename where
  500. // the handle is a pointer to a AsyncCaptionData_t defined above. If the resource manager uncaches the handle, we reload the
  501. // .vcd from disk. Precaching a .vcd calls into FindOrAddBlock which moves the .vcd to the head of the LRU if it's in memory
  502. // or it reloads it from disk otherwise.
  503. //-----------------------------------------------------------------------------
  504. class CAsyncCaptionResourceManager : public CAutoGameSystem, public CManagedDataCacheClient< AsyncCaptionData_t, asynccaptionparams_t >
  505. {
  506. public:
  507. CAsyncCaptionResourceManager() : CAutoGameSystem( "CAsyncCaptionResourceManager" )
  508. {
  509. }
  510. void SetDbInfo( const CUtlVector< AsyncCaption_t > & info )
  511. {
  512. m_Db = info;
  513. }
  514. virtual bool Init()
  515. {
  516. CCacheClientBaseClass::Init( g_pDataCache, "Captions", MAX_ASYNCCAPTION_MEMORY_CACHE );
  517. return true;
  518. }
  519. virtual void Shutdown()
  520. {
  521. Clear();
  522. CCacheClientBaseClass::Shutdown();
  523. }
  524. //-----------------------------------------------------------------------------
  525. // Purpose: Spew a cache summary to the console
  526. //-----------------------------------------------------------------------------
  527. void SpewMemoryUsage()
  528. {
  529. GetCacheSection()->OutputReport();
  530. DataCacheStatus_t status;
  531. DataCacheLimits_t limits;
  532. GetCacheSection()->GetStatus( &status, &limits );
  533. int bytesUsed, bytesTotal;
  534. float percent;
  535. bytesUsed = status.nBytes;
  536. bytesTotal = limits.nMaxBytes;
  537. percent = 100.0f * (float)bytesUsed / (float)bytesTotal;
  538. int count = 0;
  539. for ( int i = 0; i < m_Db.Count(); ++i )
  540. {
  541. count += m_Db[ i ].m_RequestedBlocks.Count();
  542. }
  543. DevMsg( "CAsyncCaptionResourceManager: %i blocks total %s, %.2f %% of capacity\n", count, Q_pretifymem( bytesUsed, 2 ), percent );
  544. }
  545. virtual void LevelInitPostEntity()
  546. {
  547. }
  548. void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus )
  549. {
  550. // get our preserved data
  551. AsyncCaptionData_t *pData = ( AsyncCaptionData_t * )request.pContext;
  552. Assert( pData );
  553. // mark as completed in single atomic operation
  554. pData->m_bLoadCompleted = true;
  555. }
  556. int ComputeBlockOffset( int fileIndex, int blockNum )
  557. {
  558. return m_Db[ fileIndex ].m_Header.dataoffset + blockNum * m_Db[ fileIndex ].m_Header.blocksize;
  559. }
  560. void GetBlockInfo( int fileIndex, int blockNum, bool& entry, bool& pending, bool& loaded )
  561. {
  562. pending = false;
  563. loaded = false;
  564. AsyncCaption_t::BlockInfo_t search;
  565. search.fileindex = fileIndex;
  566. search.blocknum = blockNum;
  567. CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ fileIndex ].m_RequestedBlocks;
  568. int idx = requested.Find( search );
  569. if ( idx == requested.InvalidIndex() )
  570. {
  571. entry = false;
  572. return;
  573. }
  574. entry = true;
  575. DataCacheHandle_t handle = requested[ idx ].handle;
  576. AsyncCaptionData_t *pCaptionData = CacheLock( handle );
  577. if ( pCaptionData )
  578. {
  579. if ( pCaptionData->m_bLoadPending )
  580. {
  581. pending = true;
  582. }
  583. else if ( pCaptionData->m_bLoadCompleted )
  584. {
  585. loaded = true;
  586. }
  587. CacheUnlock( handle );
  588. }
  589. }
  590. // Either commences async loading or polls for async loading once per frame to wait for it to complete...
  591. void PollForAsyncLoading( CHudCloseCaption *hudCloseCaption, int dbFileIndex, int blockNum )
  592. {
  593. const char *dbname = m_Db[ dbFileIndex ].m_DataBaseFile.String();
  594. CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ dbFileIndex ].m_RequestedBlocks;
  595. int idx = FindOrAddBlock( dbFileIndex, blockNum );
  596. if ( idx == requested.InvalidIndex() )
  597. {
  598. Assert( 0 );
  599. return;
  600. }
  601. DataCacheHandle_t handle = requested[ idx ].handle;
  602. AsyncCaptionData_t *pCaptionData = CacheLock( handle );
  603. if ( !pCaptionData )
  604. {
  605. // Try and reload it
  606. char fn[ 256 ];
  607. Q_strncpy( fn, dbname, sizeof( fn ) );
  608. Q_FixSlashes( fn );
  609. Q_strlower( fn );
  610. asynccaptionparams_t params;
  611. params.dbfile = fn;
  612. params.blocktoload = blockNum;
  613. params.blocksize = m_Db[ dbFileIndex ].m_Header.blocksize;
  614. params.blockoffset = ComputeBlockOffset( dbFileIndex, blockNum );
  615. params.fileindex = dbFileIndex;
  616. handle = requested[ idx ].handle = CacheCreate( params );
  617. pCaptionData = CacheLock( handle );
  618. if ( !pCaptionData )
  619. {
  620. Assert( pCaptionData );
  621. return;
  622. }
  623. }
  624. if ( pCaptionData->m_bLoadCompleted )
  625. {
  626. pCaptionData->m_bLoadPending = false;
  627. // Copy in data at this point
  628. Assert( hudCloseCaption );
  629. if ( hudCloseCaption )
  630. {
  631. hudCloseCaption->OnFinishAsyncLoad( requested[ idx ].fileindex, requested[ idx ].blocknum, pCaptionData );
  632. }
  633. // This finalizes the load (unlocks the handle)
  634. GetCacheSection()->BreakLock( handle );
  635. return;
  636. }
  637. if ( pCaptionData->m_bLoadPending )
  638. {
  639. CacheUnlock( handle );
  640. return;
  641. }
  642. // Commence load (locks handle for entire async load) (unlocked above)
  643. pCaptionData->m_bLoadPending = true;
  644. pCaptionData->AsyncLoad( dbname, ComputeBlockOffset( dbFileIndex, blockNum ) );
  645. }
  646. //-----------------------------------------------------------------------------
  647. // Purpose: Touch the cache or load the scene into the cache for the first time
  648. // Input : *filename -
  649. //-----------------------------------------------------------------------------
  650. int FindOrAddBlock( int dbFileIndex, int blockNum )
  651. {
  652. const char *dbname = m_Db[ dbFileIndex ].m_DataBaseFile.String();
  653. CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ dbFileIndex ].m_RequestedBlocks;
  654. AsyncCaption_t::BlockInfo_t search;
  655. search.blocknum = blockNum;
  656. search.fileindex = dbFileIndex;
  657. int idx = requested.Find( search );
  658. if ( idx != requested.InvalidIndex() )
  659. {
  660. // Move it to head of LRU
  661. CacheTouch( requested[ idx ].handle );
  662. return idx;
  663. }
  664. char fn[ 256 ];
  665. Q_strncpy( fn, dbname, sizeof( fn ) );
  666. Q_FixSlashes( fn );
  667. Q_strlower( fn );
  668. asynccaptionparams_t params;
  669. params.dbfile = fn;
  670. params.blocktoload = blockNum;
  671. params.blockoffset = ComputeBlockOffset( dbFileIndex, blockNum );
  672. params.blocksize = m_Db[ dbFileIndex ].m_Header.blocksize;
  673. params.fileindex = dbFileIndex;
  674. memhandle_t handle = CacheCreate( params );
  675. AsyncCaption_t::BlockInfo_t info;
  676. info.fileindex = dbFileIndex;
  677. info.blocknum = blockNum;
  678. info.handle = handle;
  679. // Add scene filename to dictionary
  680. idx = requested.Insert( info );
  681. return idx;
  682. }
  683. void Flush()
  684. {
  685. CacheFlush();
  686. }
  687. void Clear()
  688. {
  689. for ( int file = 0; file < m_Db.Count(); ++file )
  690. {
  691. CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ file ].m_RequestedBlocks;
  692. int c = requested.Count();
  693. for ( int i = 0; i < c; ++i )
  694. {
  695. memhandle_t dat = requested[ i ].handle;
  696. CacheRemove( dat );
  697. }
  698. requested.RemoveAll();
  699. }
  700. }
  701. private:
  702. CUtlVector< AsyncCaption_t > m_Db;
  703. };
  704. CAsyncCaptionResourceManager g_AsyncCaptionResourceManager;
  705. void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus )
  706. {
  707. g_AsyncCaptionResourceManager.CaptionAsyncLoaderCallback( request, numReadBytes, asyncStatus );
  708. }
  709. DECLARE_HUDELEMENT_FLAGS( CHudCloseCaption, HUDELEMENT_SS_FULLSCREEN_ONLY );
  710. DECLARE_HUD_MESSAGE( CHudCloseCaption, CloseCaption );
  711. DECLARE_HUD_MESSAGE( CHudCloseCaption, CloseCaptionDirect );
  712. CHudCloseCaption::CHudCloseCaption( const char *pElementName )
  713. : CHudElement( pElementName ),
  714. vgui::Panel( NULL, "HudCloseCaption" ),
  715. m_CloseCaptionRepeats( 0, 0, CaptionTokenLessFunc ),
  716. m_CurrentLanguage( UTL_INVAL_SYMBOL ),
  717. m_bPaintDebugInfo( false ),
  718. m_ColorMap( k_eDictCompareTypeCaseInsensitive )
  719. {
  720. vgui::Panel *pParent = GetFullscreenClientMode()->GetViewport();
  721. SetParent( pParent );
  722. SetProportional( true );
  723. m_nGoalHeight = 0;
  724. m_nCurrentHeight = 0;
  725. m_flGoalAlpha = 1.0f;
  726. m_flCurrentAlpha = 1.0f;
  727. m_flGoalHeightStartTime = 0;
  728. m_flGoalHeightFinishTime = 0;
  729. m_bLocked = false;
  730. m_bVisibleDueToDirect = false;
  731. SetPaintBorderEnabled( false );
  732. SetPaintBackgroundEnabled( false );
  733. vgui::ivgui()->AddTickSignal( GetVPanel(), 0 );
  734. if ( !IsGameConsole() )
  735. {
  736. g_pVGuiLocalize->AddFile( "resource/closecaption_%language%.txt", "GAME", true );
  737. }
  738. HOOK_HUD_MESSAGE( CHudCloseCaption, CloseCaption );
  739. HOOK_HUD_MESSAGE( CHudCloseCaption, CloseCaptionDirect );
  740. char uilanguage[ 64 ];
  741. engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
  742. if ( !Q_stricmp( uilanguage, "english" ) )
  743. {
  744. english.SetValue( 1 );
  745. }
  746. else
  747. {
  748. english.SetValue( 0 );
  749. }
  750. InitCaptionDictionary( uilanguage );
  751. LoadColorMap( "resource/captioning_colors.txt" );
  752. m_bLevelShutDown = false;
  753. m_bUseAsianWordWrapping = false;
  754. }
  755. //-----------------------------------------------------------------------------
  756. // Purpose:
  757. //-----------------------------------------------------------------------------
  758. CHudCloseCaption::~CHudCloseCaption()
  759. {
  760. m_CloseCaptionRepeats.RemoveAll();
  761. ClearAsyncWork();
  762. }
  763. //-----------------------------------------------------------------------------
  764. // Purpose:
  765. // Input : *newmap -
  766. //-----------------------------------------------------------------------------
  767. void CHudCloseCaption::LevelInit( void )
  768. {
  769. CreateFonts();
  770. // Reset repeat counters per level
  771. m_CloseCaptionRepeats.RemoveAll();
  772. // Wipe any stale pending work items...
  773. ClearAsyncWork();
  774. }
  775. static ConVar cc_minvisibleitems( "cc_minvisibleitems", "1", 0, "Minimum number of caption items to show." );
  776. void CHudCloseCaption::TogglePaintDebug()
  777. {
  778. m_bPaintDebugInfo = !m_bPaintDebugInfo;
  779. }
  780. //-----------------------------------------------------------------------------
  781. // Purpose:
  782. //-----------------------------------------------------------------------------
  783. void CHudCloseCaption::Paint( void )
  784. {
  785. int w, h;
  786. GetSize( w, h );
  787. if ( m_bPaintDebugInfo )
  788. {
  789. int blockWide = 350;
  790. int startx = 50;
  791. int y = 0;
  792. int size = 8;
  793. int sizewithgap = size + 1;
  794. for ( int a = 0; a < m_AsyncCaptions.Count(); ++a )
  795. {
  796. int x = startx;
  797. int c = m_AsyncCaptions[ a ].m_Header.numblocks;
  798. for ( int i = 0 ; i < c; ++i )
  799. {
  800. bool entry, pending, loaded;
  801. g_AsyncCaptionResourceManager.GetBlockInfo( a, i, entry, pending, loaded );
  802. if ( !entry )
  803. {
  804. vgui::surface()->DrawSetColor( Color( 0, 0, 0, 127 ) );
  805. }
  806. else if ( pending )
  807. {
  808. vgui::surface()->DrawSetColor( Color( 0, 0, 255, 127 ) );
  809. }
  810. else if ( loaded )
  811. {
  812. vgui::surface()->DrawSetColor( Color( 0, 255, 0, 127 ) );
  813. }
  814. else
  815. {
  816. vgui::surface()->DrawSetColor( Color( 255, 255, 0, 127 ) );
  817. }
  818. vgui::surface()->DrawFilledRect( x, y, x + size, y + size );
  819. x += sizewithgap;
  820. if ( x >= startx + blockWide )
  821. {
  822. x = startx;
  823. y += sizewithgap;
  824. }
  825. }
  826. y += sizewithgap;
  827. }
  828. }
  829. wrect_t rcOutput;
  830. rcOutput.left = 0;
  831. rcOutput.right = w;
  832. rcOutput.bottom = h;
  833. rcOutput.top = m_nTopOffset;
  834. wrect_t rcText = rcOutput;
  835. int avail_width = rcText.right - rcText.left - 2 * CC_INSET;
  836. int avail_height = rcText.bottom - rcText.top - 2 * CC_INSET;
  837. int totalheight = 0;
  838. int i;
  839. CUtlVector< VisibleStreamItem > visibleitems;
  840. int c = m_Items.Count();
  841. int maxwidth = 0;
  842. for ( i = 0; i < c; i++ )
  843. {
  844. CCloseCaptionItem *item = m_Items[ i ];
  845. // Not ready for display yet.
  846. if ( item->GetPreDisplayTime() > 0.0f )
  847. {
  848. continue;
  849. }
  850. if ( !item->GetSizeComputed() )
  851. {
  852. ComputeStreamWork( avail_width, item );
  853. }
  854. int itemwidth = item->GetWidth();
  855. int itemheight = item->GetHeight();
  856. totalheight += itemheight;
  857. if ( itemwidth > maxwidth )
  858. {
  859. maxwidth = itemwidth;
  860. }
  861. VisibleStreamItem si;
  862. si.height = itemheight;
  863. si.width = itemwidth;
  864. si.item = item;
  865. visibleitems.AddToTail( si );
  866. int iSizeCheck = 0;
  867. // Pop the oldest SFX items off the stack first if we're out of space
  868. while ( totalheight > avail_height &&
  869. visibleitems.Count() > cc_minvisibleitems.GetInt() &&
  870. iSizeCheck <= visibleitems.Count() - 1 )
  871. {
  872. VisibleStreamItem & pop = visibleitems[ iSizeCheck ];
  873. if ( pop.item->IsSFXEntry() )
  874. {
  875. totalheight -= pop.height;
  876. // And make it die right away...
  877. pop.item->SetTimeToLive( 0.0f );
  878. visibleitems.Remove( iSizeCheck );
  879. continue;
  880. }
  881. iSizeCheck++;
  882. }
  883. iSizeCheck = 0;
  884. // Pop the oldest low priority items off the stack next if we're still out of space
  885. while ( totalheight > avail_height &&
  886. visibleitems.Count() > cc_minvisibleitems.GetInt() &&
  887. iSizeCheck <= visibleitems.Count() - 1 )
  888. {
  889. VisibleStreamItem & pop = visibleitems[ iSizeCheck ];
  890. if ( pop.item->IsLowPriorityEntry() )
  891. {
  892. totalheight -= pop.height;
  893. // And make it die right away...
  894. pop.item->SetTimeToLive( 0.0f );
  895. visibleitems.Remove( iSizeCheck );
  896. continue;
  897. }
  898. iSizeCheck++;
  899. }
  900. // Start popping really old items off the stack if we're still out of space
  901. while ( itemheight <= avail_height &&
  902. totalheight > avail_height &&
  903. visibleitems.Count() > cc_minvisibleitems.GetInt() )
  904. {
  905. VisibleStreamItem & pop = visibleitems[ 0 ];
  906. totalheight -= pop.height;
  907. // And make it die right away...
  908. pop.item->SetTimeToLive( 0.0f );
  909. visibleitems.Remove( 0 );
  910. }
  911. }
  912. float desiredAlpha = visibleitems.Count() >= 1 ? 1.0f : 0.0f;
  913. // Always return at least one line height for drawing the surrounding box
  914. totalheight = MAX( totalheight, m_nLineHeight );
  915. // Trigger box growing
  916. if ( totalheight != m_nGoalHeight )
  917. {
  918. m_nGoalHeight = totalheight;
  919. m_flGoalHeightStartTime = gpGlobals->curtime;
  920. m_flGoalHeightFinishTime = gpGlobals->curtime + m_flGrowTime;
  921. }
  922. if ( desiredAlpha != m_flGoalAlpha )
  923. {
  924. m_flGoalAlpha = desiredAlpha;
  925. m_flGoalHeightStartTime = gpGlobals->curtime;
  926. m_flGoalHeightFinishTime = gpGlobals->curtime + m_flGrowTime;
  927. }
  928. // If shrunk to zero and faded out, nothing left to do
  929. if ( !visibleitems.Count() &&
  930. m_nGoalHeight == m_nCurrentHeight &&
  931. m_flGoalAlpha == m_flCurrentAlpha )
  932. {
  933. m_flGoalHeightStartTime = 0;
  934. m_flGoalHeightFinishTime = 0;
  935. return;
  936. }
  937. bool growingDown = false;
  938. // Continue growth?
  939. if ( m_flGoalHeightFinishTime &&
  940. m_flGoalHeightStartTime &&
  941. m_flGoalHeightFinishTime > m_flGoalHeightStartTime )
  942. {
  943. float togo = m_nGoalHeight - m_nCurrentHeight;
  944. float alphatogo = m_flGoalAlpha - m_flCurrentAlpha;
  945. growingDown = togo < 0.0f ? true : false;
  946. float dt = m_flGoalHeightFinishTime - m_flGoalHeightStartTime;
  947. float frac = ( gpGlobals->curtime - m_flGoalHeightStartTime ) / dt;
  948. frac = clamp( frac, 0.0f, 1.0f );
  949. int newHeight = m_nCurrentHeight + (int)( frac * togo );
  950. m_nCurrentHeight = newHeight;
  951. float newAlpha = m_flCurrentAlpha + frac * alphatogo;
  952. m_flCurrentAlpha = clamp( newAlpha, 0.0f, 1.0f );
  953. }
  954. else
  955. {
  956. m_nCurrentHeight = m_nGoalHeight;
  957. m_flCurrentAlpha = m_flGoalAlpha;
  958. }
  959. rcText.top = rcText.bottom - m_nCurrentHeight - 2 * CC_INSET;
  960. Color bgColor = GetBgColor();
  961. bgColor[3] = m_flBackgroundAlpha;
  962. DrawBox( rcText.left, MAX(rcText.top,0), rcText.right - rcText.left, rcText.bottom - MAX(rcText.top,0), bgColor, m_flCurrentAlpha );
  963. if ( !visibleitems.Count() )
  964. {
  965. return;
  966. }
  967. rcText.left += CC_INSET;
  968. rcText.right -= CC_INSET;
  969. int textHeight = m_nCurrentHeight;
  970. if ( growingDown )
  971. {
  972. // If growing downward, keep the text locked to the bottom of the window instead of anchored to the top
  973. textHeight = totalheight;
  974. }
  975. rcText.top = rcText.bottom - textHeight - CC_INSET;
  976. // Now draw them
  977. c = visibleitems.Count();
  978. for ( i = 0; i < c; i++ )
  979. {
  980. VisibleStreamItem *si = &visibleitems[ i ];
  981. // If the oldest/top item was created with additional time, we can remove that now
  982. if ( i == 0 )
  983. {
  984. if ( si->item->GetAddedTime() > 0.0f )
  985. {
  986. float ttl = si->item->GetTimeToLive();
  987. ttl -= si->item->GetAddedTime();
  988. ttl = MAX( 0.0f, ttl );
  989. si->item->SetTimeToLive( ttl );
  990. si->item->SetAddedTime( 0.0f );
  991. }
  992. }
  993. int height = si->height;
  994. CCloseCaptionItem *item = si->item;
  995. int iFadeLine = -1;
  996. float flFadeLineAlpha = 1.0;
  997. // If the height is greater than the total height of the element,
  998. // we need to slowly pan over this item.
  999. if ( height > avail_height )
  1000. {
  1001. // Figure out how many lines we'll need to move to see the whole caption
  1002. int units = item->GetNumWorkUnits();
  1003. int extraheight = (height - avail_height);
  1004. for ( int j = 0 ; j < units; j++ )
  1005. {
  1006. CCloseCaptionWorkUnit *wu = item->GetWorkUnit( j );
  1007. extraheight -= wu->GetHeight();
  1008. if ( extraheight <= 0 )
  1009. {
  1010. units = j+2; // Add an extra line since we want to scroll over the lifetime of the audio and the last scroll is at the end time
  1011. break;
  1012. }
  1013. }
  1014. // Figure out the delta between each point where we move the line
  1015. float flMoveDelta = item->GetInitialLifeSpan() / (float)units;
  1016. float flCurMove = item->GetInitialLifeSpan() - item->GetTimeToLive();
  1017. int iHeightToMove = 0;
  1018. int iLinesToMove = clamp( floor( flCurMove / flMoveDelta ), 0, units );
  1019. if ( iLinesToMove )
  1020. {
  1021. int iCurrentLineHeight = 0;
  1022. for ( int j = 0 ; j < iLinesToMove; j++ )
  1023. {
  1024. iHeightToMove = iCurrentLineHeight;
  1025. CCloseCaptionWorkUnit *wu = item->GetWorkUnit( j );
  1026. iCurrentLineHeight += wu->GetHeight();
  1027. }
  1028. // Slide to the desired distance, once the fade is done
  1029. float flTimePostMove = flCurMove - (flMoveDelta * iLinesToMove);
  1030. if ( flTimePostMove < CAPTION_PAN_FADE_TIME )
  1031. {
  1032. iFadeLine = iLinesToMove-1;
  1033. // It's time to fade out the top line. If it hasn't started fading yet, start it.
  1034. CCloseCaptionWorkUnit *wu = item->GetWorkUnit(iFadeLine);
  1035. if ( wu->GetFadeStart() == 0 )
  1036. {
  1037. wu->SetFadeStart( gpGlobals->curtime );
  1038. }
  1039. // Fade out quickly
  1040. float flFadeTime = (gpGlobals->curtime - wu->GetFadeStart()) / CAPTION_PAN_FADE_TIME;
  1041. flFadeLineAlpha = clamp( 1.0 - flFadeTime, 0, 1 );
  1042. }
  1043. else if ( flTimePostMove < (CAPTION_PAN_FADE_TIME+CAPTION_PAN_SLIDE_TIME) )
  1044. {
  1045. flTimePostMove -= CAPTION_PAN_FADE_TIME;
  1046. float flSlideTime = clamp( flTimePostMove / 0.25, 0, 1 );
  1047. iHeightToMove += ceil((iCurrentLineHeight - iHeightToMove) * flSlideTime);
  1048. iFadeLine = iLinesToMove-1;
  1049. flFadeLineAlpha = 0.0f;
  1050. }
  1051. else
  1052. {
  1053. iHeightToMove = iCurrentLineHeight;
  1054. }
  1055. }
  1056. // Minor adjustment to center the caption text within the window.
  1057. rcText.top = -iHeightToMove + 2;
  1058. }
  1059. rcText.bottom = rcText.top + height;
  1060. wrect_t rcOut = rcText;
  1061. rcOut.right = rcOut.left + si->width + 6;
  1062. DrawStream( rcOut, rcOutput, item, iFadeLine, flFadeLineAlpha );
  1063. rcText.top += height;
  1064. rcText.bottom += height;
  1065. if ( rcText.top >= rcOutput.bottom )
  1066. break;
  1067. }
  1068. }
  1069. void CHudCloseCaption::OnTick( void )
  1070. {
  1071. // See if any async work has completed
  1072. ProcessAsyncWork();
  1073. float dt = gpGlobals->frametime;
  1074. int c = m_Items.Count();
  1075. int i;
  1076. if ( m_bVisibleDueToDirect )
  1077. {
  1078. SetVisible( true );
  1079. if ( !c )
  1080. {
  1081. // Don't clear our force visible if we're waiting for the caption to load
  1082. if ( m_AsyncWork.Count() == 0 )
  1083. {
  1084. m_bVisibleDueToDirect = false;
  1085. }
  1086. }
  1087. }
  1088. else
  1089. {
  1090. SetVisible( closecaption.GetBool() );
  1091. }
  1092. // Pass one decay all timers
  1093. for ( i = 0 ; i < c ; ++i )
  1094. {
  1095. CCloseCaptionItem *item = m_Items[ i ];
  1096. float predisplay = item->GetPreDisplayTime();
  1097. if ( predisplay > 0.0f )
  1098. {
  1099. predisplay -= dt;
  1100. predisplay = MAX( 0.0f, predisplay );
  1101. item->SetPreDisplayTime( predisplay );
  1102. }
  1103. else
  1104. {
  1105. // remove time from actual playback
  1106. float ttl = item->GetTimeToLive();
  1107. ttl -= dt;
  1108. ttl = MAX( 0.0f, ttl );
  1109. item->SetTimeToLive( ttl );
  1110. }
  1111. }
  1112. // Pass two, remove from head until we get to first item with time remaining
  1113. bool foundfirstnondeletion = false;
  1114. for ( i = 0 ; i < c ; ++i )
  1115. {
  1116. CCloseCaptionItem *item = m_Items[ i ];
  1117. // Skip items not yet showing...
  1118. float predisplay = item->GetPreDisplayTime();
  1119. if ( predisplay > 0.0f )
  1120. {
  1121. continue;
  1122. }
  1123. float ttl = item->GetTimeToLive();
  1124. if ( ttl > 0.0f )
  1125. {
  1126. foundfirstnondeletion = true;
  1127. continue;
  1128. }
  1129. // Skip the remainder of the items after we find the first/oldest active item
  1130. if ( foundfirstnondeletion )
  1131. {
  1132. continue;
  1133. }
  1134. delete item;
  1135. m_Items.Remove( i );
  1136. --i;
  1137. --c;
  1138. }
  1139. }
  1140. void CHudCloseCaption::ApplySchemeSettings( vgui::IScheme *pScheme )
  1141. {
  1142. BaseClass::ApplySchemeSettings( pScheme );
  1143. // must get/reget fonts when scheme changes due to video resolution changes
  1144. CreateFonts();
  1145. SetUseAsianWordWrapping();
  1146. }
  1147. void CHudCloseCaption::Reset( void )
  1148. {
  1149. if ( m_bLevelShutDown || !g_pGameRules || !g_pGameRules->IsMultiplayer() )
  1150. {
  1151. m_Items.PurgeAndDeleteElements();
  1152. ClearAsyncWork();
  1153. Unlock();
  1154. }
  1155. }
  1156. bool CHudCloseCaption::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ) const
  1157. {
  1158. const wchar_t *in = *ppIn;
  1159. const wchar_t *oldin = in;
  1160. if ( *in != L'<' )
  1161. {
  1162. *ppIn += ( oldin - in );
  1163. return false;
  1164. }
  1165. args[ 0 ] = 0;
  1166. cmd[ 0 ]= 0;
  1167. wchar_t *out = cmd;
  1168. in++;
  1169. while ( *in != L'\0' && *in != L':' && *in != L'>' && !V_isspace( *in ) )
  1170. {
  1171. *out++ = *in++;
  1172. }
  1173. *out = L'\0';
  1174. if ( *in != L':' )
  1175. {
  1176. *ppIn += ( in - oldin );
  1177. return true;
  1178. }
  1179. in++;
  1180. out = args;
  1181. while ( *in != L'\0' && *in != L'>' )
  1182. {
  1183. *out++ = *in++;
  1184. }
  1185. *out = L'\0';
  1186. //if ( *in == L'>' )
  1187. // in++;
  1188. *ppIn += ( in - oldin );
  1189. return true;
  1190. }
  1191. //-----------------------------------------------------------------------------
  1192. // Purpose:
  1193. // Input : *stream -
  1194. // *findcmd -
  1195. // value -
  1196. // Output : Returns true on success, false on failure.
  1197. //-----------------------------------------------------------------------------
  1198. bool CHudCloseCaption::GetFloatCommandValue( const wchar_t *stream, const wchar_t *findcmd, float& value ) const
  1199. {
  1200. const wchar_t *curpos = stream;
  1201. for ( ; curpos && *curpos != L'\0'; ++curpos )
  1202. {
  1203. wchar_t cmd[ 256 ];
  1204. wchar_t args[ 256 ];
  1205. if ( SplitCommand( &curpos, cmd, args ) )
  1206. {
  1207. if ( !wcscmp( cmd, findcmd ) )
  1208. {
  1209. value = (float)wcstod( args, NULL );
  1210. return true;
  1211. }
  1212. continue;
  1213. }
  1214. }
  1215. return false;
  1216. }
  1217. bool CHudCloseCaption::StreamHasCommand( const wchar_t *stream, const wchar_t *findcmd ) const
  1218. {
  1219. const wchar_t *curpos = stream;
  1220. for ( ; curpos && *curpos != L'\0'; ++curpos )
  1221. {
  1222. wchar_t cmd[ 256 ];
  1223. wchar_t args[ 256 ];
  1224. if ( SplitCommand( &curpos, cmd, args ) )
  1225. {
  1226. if ( !wcscmp( cmd, findcmd ) )
  1227. {
  1228. return true;
  1229. }
  1230. continue;
  1231. }
  1232. }
  1233. return false;
  1234. }
  1235. //-----------------------------------------------------------------------------
  1236. // Purpose: It's blank or only comprised of whitespace/space characters...
  1237. // Input : *stream -
  1238. // Output : static bool
  1239. //-----------------------------------------------------------------------------
  1240. static bool IsAllSpaces( const wchar_t *stream )
  1241. {
  1242. const wchar_t *p = stream;
  1243. while ( *p != L'\0' )
  1244. {
  1245. if ( !iswspace( *p ) )
  1246. return false;
  1247. p++;
  1248. }
  1249. return true;
  1250. }
  1251. bool CHudCloseCaption::StreamHasCommand( const wchar_t *stream, const wchar_t *search )
  1252. {
  1253. for ( const wchar_t *curpos = stream; curpos && *curpos != L'\0'; ++curpos )
  1254. {
  1255. wchar_t cmd[ 256 ];
  1256. wchar_t args[ 256 ];
  1257. if ( SplitCommand( &curpos, cmd, args ) )
  1258. {
  1259. if ( !wcscmp( cmd, search ) )
  1260. {
  1261. return true;
  1262. }
  1263. }
  1264. }
  1265. return false;
  1266. }
  1267. void CHudCloseCaption::Process( const wchar_t *stream, float duration, bool fromplayer, bool direct )
  1268. {
  1269. if ( !direct )
  1270. {
  1271. if ( !closecaption.GetBool() )
  1272. return;
  1273. // If we're locked, ignore all closecaption commands
  1274. if ( m_bLocked )
  1275. return;
  1276. }
  1277. // Nothing to do...
  1278. if ( IsAllSpaces( stream) )
  1279. {
  1280. return;
  1281. }
  1282. bool bSFXEntry = StreamHasCommand( stream, L"sfx" );
  1283. bool bLowPriorityEntry = StreamHasCommand( stream, L"low" );
  1284. // If subtitling, don't show sfx captions at all
  1285. if ( cc_subtitles.GetBool() && bSFXEntry )
  1286. {
  1287. return;
  1288. }
  1289. bool valid = true;
  1290. if ( !wcsncmp( stream, L"!!!", wcslen( L"!!!" ) ) )
  1291. {
  1292. // It's in the text file, but hasn't been translated...
  1293. valid = false;
  1294. }
  1295. if ( !wcsncmp( stream, L"-->", wcslen( L"-->" ) ) )
  1296. {
  1297. // It's in the text file, but hasn't been translated...
  1298. valid = false;
  1299. if ( cc_captiontrace.GetInt() < 2 )
  1300. {
  1301. if ( cc_captiontrace.GetInt() == 1 )
  1302. {
  1303. Msg( "Missing caption for '%S'\n", stream );
  1304. }
  1305. return;
  1306. }
  1307. }
  1308. float lifespan = duration + cc_linger_time.GetFloat();
  1309. float addedlife = 0.0f;
  1310. if ( m_Items.Count() > 0 )
  1311. {
  1312. // Get the remaining life span of the last item
  1313. CCloseCaptionItem *final = m_Items[ m_Items.Count() - 1 ];
  1314. float prevlife = final->GetTimeToLive();
  1315. if ( prevlife > lifespan )
  1316. {
  1317. addedlife = prevlife - lifespan;
  1318. }
  1319. lifespan = MAX( lifespan, prevlife );
  1320. }
  1321. float delay = 0.0f;
  1322. float override_duration = 0.0f;
  1323. wchar_t phrase[ MAX_CAPTION_CHARACTERS ];
  1324. wchar_t *out = phrase;
  1325. for ( const wchar_t *curpos = stream; curpos && *curpos != L'\0'; ++curpos )
  1326. {
  1327. wchar_t cmd[ 256 ];
  1328. wchar_t args[ 256 ];
  1329. const wchar_t *prevpos = curpos;
  1330. if ( SplitCommand( &curpos, cmd, args ) )
  1331. {
  1332. if ( !wcscmp( cmd, L"delay" ) )
  1333. {
  1334. // End current phrase
  1335. *out = L'\0';
  1336. if ( phrase[ 0 ] != L'\0' )
  1337. {
  1338. CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer, bSFXEntry, bLowPriorityEntry );
  1339. m_Items.AddToTail( item );
  1340. if ( StreamHasCommand( phrase, L"sfx" ) )
  1341. {
  1342. // SFX show up instantly.
  1343. item->SetPreDisplayTime( 0.0f );
  1344. }
  1345. if ( GetFloatCommandValue( phrase, L"len", override_duration ) )
  1346. {
  1347. item->SetTimeToLive( override_duration );
  1348. }
  1349. }
  1350. // Start new phrase
  1351. out = phrase;
  1352. // Delay must be positive
  1353. delay = MAX( 0.0f, (float)wcstod( args, NULL ) );
  1354. continue;
  1355. }
  1356. int copychars = curpos - prevpos;
  1357. while ( --copychars >= 0 )
  1358. {
  1359. *out++ = *prevpos++;
  1360. }
  1361. }
  1362. *out++ = *curpos;
  1363. }
  1364. // End final phrase, if any
  1365. *out = L'\0';
  1366. if ( phrase[ 0 ] != L'\0' )
  1367. {
  1368. CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer, bSFXEntry, bLowPriorityEntry );
  1369. m_Items.AddToTail( item );
  1370. if ( StreamHasCommand( phrase, L"sfx" ) )
  1371. {
  1372. // SFX show up instantly.
  1373. item->SetPreDisplayTime( 0.0f );
  1374. }
  1375. if ( GetFloatCommandValue( phrase, L"len", override_duration ) )
  1376. {
  1377. item->SetTimeToLive( override_duration );
  1378. item->SetInitialLifeSpan( override_duration );
  1379. }
  1380. }
  1381. }
  1382. void CHudCloseCaption::CreateFonts( void )
  1383. {
  1384. vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( vgui::scheme()->GetScheme( "basemodui_scheme" ) );
  1385. if ( !IsGameConsole() )
  1386. {
  1387. m_hFonts[CCFONT_NORMAL] = pScheme->GetFont( "CloseCaption_Normal", true );
  1388. m_hFonts[CCFONT_BOLD] = pScheme->GetFont( "CloseCaption_Bold", true );
  1389. m_hFonts[CCFONT_ITALIC] = pScheme->GetFont( "CloseCaption_Italic", true );
  1390. m_hFonts[CCFONT_ITALICBOLD] = pScheme->GetFont( "CloseCaption_BoldItalic", true );
  1391. m_nLineHeight = MAX( 6, vgui::surface()->GetFontTall( m_hFonts[ CCFONT_NORMAL ] ) );
  1392. }
  1393. else
  1394. {
  1395. m_hFonts[CCFONT_CONSOLE] = pScheme->GetFont( "CloseCaption_Console", true );
  1396. m_nLineHeight = MAX( 6, vgui::surface()->GetFontTall( m_hFonts[ CCFONT_CONSOLE ] ) );
  1397. }
  1398. }
  1399. struct WorkUnitParams
  1400. {
  1401. WorkUnitParams()
  1402. {
  1403. Q_memset( stream, 0, sizeof( stream ) );
  1404. out = stream;
  1405. x = 0;
  1406. y = 0;
  1407. width = 0;
  1408. bold = italic = false;
  1409. clr = Color( 255, 255, 255, 255 );
  1410. newline = false;
  1411. font = 0;
  1412. }
  1413. ~WorkUnitParams()
  1414. {
  1415. }
  1416. void Finalize( int lineheight )
  1417. {
  1418. *out = L'\0';
  1419. }
  1420. void Next( int lineheight )
  1421. {
  1422. // Restart output
  1423. Q_memset( stream, 0, sizeof( stream ) );
  1424. out = stream;
  1425. x += width;
  1426. width = 0;
  1427. // Leave bold, italic and color alone!!!
  1428. if ( newline )
  1429. {
  1430. newline = false;
  1431. x = 0;
  1432. y += lineheight;
  1433. }
  1434. }
  1435. int GetFontNumber()
  1436. {
  1437. return CHudCloseCaption::GetFontNumber( bold, italic );
  1438. }
  1439. wchar_t stream[ MAX_CAPTION_CHARACTERS ];
  1440. wchar_t *out;
  1441. int x;
  1442. int y;
  1443. int width;
  1444. bool bold;
  1445. bool italic;
  1446. Color clr;
  1447. bool newline;
  1448. vgui::HFont font;
  1449. };
  1450. void CHudCloseCaption::AddWorkUnit( CCloseCaptionItem *item,
  1451. WorkUnitParams& params )
  1452. {
  1453. params.Finalize( vgui::surface()->GetFontTall( params.font ) );
  1454. if ( params.stream[ 0 ] != L'\0' )
  1455. {
  1456. CCloseCaptionWorkUnit *wu = new CCloseCaptionWorkUnit();
  1457. wu->SetStream( params.stream );
  1458. wu->SetColor( params.clr );
  1459. wu->SetBold( params.bold );
  1460. wu->SetItalic( params.italic );
  1461. wu->SetWidth( params.width );
  1462. wu->SetHeight( vgui::surface()->GetFontTall( params.font ) );
  1463. wu->SetPos( params.x, params.y );
  1464. wu->SetFont( params.font );
  1465. wu->SetFadeStart( 0 );
  1466. int curheight = item->GetHeight();
  1467. int curwidth = item->GetWidth();
  1468. curheight = MAX( curheight, params.y + wu->GetHeight() );
  1469. curwidth = MAX( curwidth, params.x + params.width );
  1470. item->SetHeight( curheight );
  1471. item->SetWidth( curwidth );
  1472. // Add it
  1473. item->AddWork( wu );
  1474. params.Next( vgui::surface()->GetFontTall( params.font ) );
  1475. }
  1476. }
  1477. void CHudCloseCaption::ComputeStreamWork( int available_width, CCloseCaptionItem *item )
  1478. {
  1479. // Start with a clean param block
  1480. WorkUnitParams params;
  1481. const wchar_t *curpos = item->GetStream();
  1482. CUtlVector< Color > colorStack;
  1483. // Need to distinguish wspace breaks from Asian word wrapping breaks
  1484. bool most_recent_break_was_wspace = true;
  1485. const wchar_t *most_recent_space = NULL;
  1486. int most_recent_space_w = -1;
  1487. for ( ; curpos && *curpos != L'\0'; ++curpos )
  1488. {
  1489. wchar_t cmd[ 256 ];
  1490. wchar_t args[ 256 ];
  1491. if ( SplitCommand( &curpos, cmd, args ) )
  1492. {
  1493. if ( !wcscmp( cmd, L"cr" ) )
  1494. {
  1495. params.newline = true;
  1496. AddWorkUnit( item, params);
  1497. }
  1498. else if ( !wcscmp( cmd, L"clr" ) )
  1499. {
  1500. AddWorkUnit( item, params );
  1501. if ( args[0] == 0 && colorStack.Count()>= 2)
  1502. {
  1503. colorStack.Remove( colorStack.Count() - 1 );
  1504. params.clr = colorStack[ colorStack.Count() - 1 ];
  1505. }
  1506. else
  1507. {
  1508. int r, g, b;
  1509. Color newcolor;
  1510. bool bColorValid = false;
  1511. if ( 3 == swscanf( args, L"%i,%i,%i", &r, &g, &b ) )
  1512. {
  1513. newcolor = Color( r, g, b, 255 );
  1514. bColorValid = true;
  1515. }
  1516. if ( bColorValid )
  1517. {
  1518. // lookup the alternate color using the color map
  1519. if ( curpos[0] == L'>' )
  1520. {
  1521. // identify a possible announcer tag
  1522. wchar_t announcerName[128];
  1523. const wchar_t *pColon = wcsstr( curpos+1, L":" );
  1524. if ( pColon )
  1525. {
  1526. // parse away any <???> commands after the color
  1527. const wchar_t *pStart = curpos + 1;
  1528. while ( pStart[0] == L'<' )
  1529. {
  1530. pStart = wcsstr( pStart, L">" );
  1531. if ( !pStart )
  1532. {
  1533. // should have found matched >
  1534. // go back to original position
  1535. pStart = curpos + 1;
  1536. break;
  1537. }
  1538. pStart++;
  1539. }
  1540. int nLength = MIN( sizeof( announcerName ), ( pColon - pStart + 1 ) * 2 );
  1541. V_wcsncpy( announcerName, pStart, nLength );
  1542. Color tagColor;
  1543. bool bFound = FindColorForTag( announcerName, tagColor );
  1544. if ( bFound )
  1545. {
  1546. newcolor = tagColor;
  1547. }
  1548. }
  1549. }
  1550. colorStack.AddToTail( newcolor );
  1551. params.clr = colorStack[ colorStack.Count() - 1 ];
  1552. }
  1553. }
  1554. }
  1555. else if ( !wcscmp( cmd, L"playerclr" ) )
  1556. {
  1557. AddWorkUnit( item, params );
  1558. if ( args[0] == 0 && colorStack.Count()>= 2)
  1559. {
  1560. colorStack.Remove( colorStack.Count() - 1 );
  1561. params.clr = colorStack[ colorStack.Count() - 1 ];
  1562. }
  1563. else
  1564. {
  1565. // player and npc color selector
  1566. // e.g.,. 255,255,255:200,200,200
  1567. int pr, pg, pb, nr, ng, nb;
  1568. Color newcolor;
  1569. if ( 6 == swscanf( args, L"%i,%i,%i:%i,%i,%i", &pr, &pg, &pb, &nr, &ng, &nb ) )
  1570. {
  1571. newcolor = item->IsFromPlayer() ? Color( pr, pg, pb, 255 ) : Color( nr, ng, nb, 255 );
  1572. colorStack.AddToTail( newcolor );
  1573. params.clr = colorStack[ colorStack.Count() - 1 ];
  1574. }
  1575. }
  1576. }
  1577. else if ( !wcscmp( cmd, L"I" ) )
  1578. {
  1579. AddWorkUnit( item, params );
  1580. params.italic = !params.italic;
  1581. }
  1582. else if ( !wcscmp( cmd, L"B" ) )
  1583. {
  1584. AddWorkUnit( item, params );
  1585. params.bold = !params.bold;
  1586. }
  1587. continue;
  1588. }
  1589. int font;
  1590. if ( !IsGameConsole() )
  1591. {
  1592. font = params.GetFontNumber();
  1593. }
  1594. else
  1595. {
  1596. // consoles cannot support the varied fonts
  1597. font = CCFONT_CONSOLE;
  1598. }
  1599. vgui::HFont useF = m_hFonts[font];
  1600. params.font = useF;
  1601. int w, h;
  1602. wchar_t sz[2];
  1603. sz[ 0 ] = *curpos;
  1604. sz[ 1 ] = L'\0';
  1605. vgui::surface()->GetTextSize( useF, sz, w, h );
  1606. if ( ( params.x + params.width ) + w > available_width )
  1607. {
  1608. if ( most_recent_space && curpos >= most_recent_space + 1 )
  1609. {
  1610. // Roll back to previous space character if there is one...
  1611. int goback = curpos - most_recent_space - 1;
  1612. params.out -= ( goback + ( most_recent_break_was_wspace ? 1 : 0 ) ); // Don't drop the character before the wrap if it's not whitespace!
  1613. params.width = most_recent_space_w;
  1614. wchar_t *extra = new wchar_t[ goback + 1 ];
  1615. wcsncpy( extra, most_recent_space + 1, goback );
  1616. extra[ goback ] = L'\0';
  1617. params.newline = true;
  1618. AddWorkUnit( item, params );
  1619. wcsncpy( params.out, extra, goback );
  1620. params.out += goback;
  1621. int textw, texth;
  1622. vgui::surface()->GetTextSize( useF, extra, textw, texth );
  1623. params.width = textw;
  1624. delete[] extra;
  1625. most_recent_space = NULL;
  1626. most_recent_space_w = -1;
  1627. }
  1628. else
  1629. {
  1630. params.newline = true;
  1631. AddWorkUnit( item, params );
  1632. }
  1633. }
  1634. *params.out++ = *curpos;
  1635. params.width += w;
  1636. if ( isbreakablewspace( *curpos ) )
  1637. {
  1638. most_recent_break_was_wspace = true;
  1639. most_recent_space = curpos;
  1640. most_recent_space_w = params.width;
  1641. }
  1642. else if ( m_bUseAsianWordWrapping && AsianWordWrap::CanBreakAfter( curpos ) )
  1643. {
  1644. most_recent_break_was_wspace = false;
  1645. most_recent_space = curpos;
  1646. most_recent_space_w = params.width;
  1647. }
  1648. }
  1649. // Add the final unit.
  1650. params.newline = true;
  1651. AddWorkUnit( item, params );
  1652. item->SetSizeComputed( true );
  1653. // DumpWork( item );
  1654. }
  1655. void CHudCloseCaption:: DumpWork( CCloseCaptionItem *item )
  1656. {
  1657. int c = item->GetNumWorkUnits();
  1658. for ( int i = 0 ; i < c; ++i )
  1659. {
  1660. CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i );
  1661. wu->Dump();
  1662. }
  1663. }
  1664. void CHudCloseCaption::DrawStream( wrect_t &rcText, wrect_t &rcWindow, CCloseCaptionItem *item, int iFadeLine, float flFadeLineAlpha )
  1665. {
  1666. int c = item->GetNumWorkUnits();
  1667. wrect_t rcOut;
  1668. float alpha = item->GetAlpha( m_flItemHiddenTime, m_flItemFadeInTime, m_flItemFadeOutTime );
  1669. for ( int i = 0 ; i < c; ++i )
  1670. {
  1671. int x = 0;
  1672. int y = 0;
  1673. CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i );
  1674. vgui::HFont useF = wu->GetFont();
  1675. wu->GetPos( x, y );
  1676. rcOut.left = rcText.left + x + 3;
  1677. rcOut.right = rcOut.left + wu->GetWidth();
  1678. rcOut.top = rcText.top + y;
  1679. rcOut.bottom = rcOut.top + wu->GetHeight();
  1680. // Adjust alpha to handle fade in/out at the top & bottom of the element.
  1681. // Used for single commentary entries that are too big to fit into the element.
  1682. float flLineAlpha = alpha;
  1683. if ( i == iFadeLine )
  1684. {
  1685. flLineAlpha *= flFadeLineAlpha;
  1686. }
  1687. else if ( rcOut.top < rcWindow.top )
  1688. {
  1689. // We're off the top of the element, so don't draw
  1690. continue;
  1691. }
  1692. else if ( rcOut.bottom > rcWindow.bottom )
  1693. {
  1694. continue;
  1695. }
  1696. else if ( rcOut.top > rcWindow.bottom )
  1697. {
  1698. float flFadeHeight = (float)wu->GetHeight() * 0.25;
  1699. float flDist = (float)(rcOut.top - rcWindow.bottom) / flFadeHeight;
  1700. flDist = Bias( flDist, 0.2 );
  1701. if ( flDist > 1 )
  1702. continue;
  1703. flLineAlpha *= 1.0 - flDist;
  1704. }
  1705. Color useColor = wu->GetColor();
  1706. useColor[ 3 ] *= flLineAlpha;
  1707. if ( !item->IsValid() )
  1708. {
  1709. useColor = Color( 255, 255, 255, 255 * flLineAlpha );
  1710. rcOut.right += 2;
  1711. vgui::surface()->DrawSetColor( Color( 100, 100, 40, 255 * flLineAlpha ) );
  1712. vgui::surface()->DrawFilledRect( rcOut.left, rcOut.top, rcOut.right, rcOut.bottom );
  1713. }
  1714. vgui::surface()->DrawSetTextFont( useF );
  1715. vgui::surface()->DrawSetTextPos( rcOut.left, rcOut.top );
  1716. vgui::surface()->DrawSetTextColor( useColor );
  1717. #ifdef WIN32
  1718. int len = wcsnlen( wu->GetStream(), MAX_CAPTION_CHARACTERS ) ;
  1719. #else
  1720. int len = wcslen( wu->GetStream() ) ;
  1721. #endif
  1722. vgui::surface()->DrawPrintText( wu->GetStream(), len );
  1723. }
  1724. }
  1725. bool CHudCloseCaption::GetNoRepeatValue( const wchar_t *caption, float &retval )
  1726. {
  1727. retval = 0.0f;
  1728. const wchar_t *curpos = caption;
  1729. for ( ; curpos && *curpos != L'\0'; ++curpos )
  1730. {
  1731. wchar_t cmd[ 256 ];
  1732. wchar_t args[ 256 ];
  1733. if ( SplitCommand( &curpos, cmd, args ) )
  1734. {
  1735. if ( !wcscmp( cmd, L"norepeat" ) )
  1736. {
  1737. retval = (float)wcstod( args, NULL );
  1738. return true;
  1739. }
  1740. continue;
  1741. }
  1742. }
  1743. return false;
  1744. }
  1745. bool CHudCloseCaption::CaptionTokenLessFunc( const CaptionRepeat &lhs, const CaptionRepeat &rhs )
  1746. {
  1747. return ( lhs.m_nTokenIndex < rhs.m_nTokenIndex );
  1748. }
  1749. static bool CaptionTrace( const char *token )
  1750. {
  1751. static CUtlSymbolTable s_MissingCloseCaptions;
  1752. // Make sure we only show the message once
  1753. if ( UTL_INVAL_SYMBOL == s_MissingCloseCaptions.Find( token ) )
  1754. {
  1755. s_MissingCloseCaptions.AddString( token );
  1756. return true;
  1757. }
  1758. return false;
  1759. }
  1760. static ConVar cc_sentencecaptionnorepeat( "cc_sentencecaptionnorepeat", "4", 0, "How often a sentence can repeat." );
  1761. int CRCString( const char *str )
  1762. {
  1763. int len = Q_strlen( str );
  1764. CRC32_t crc;
  1765. CRC32_Init( &crc );
  1766. CRC32_ProcessBuffer( &crc, str, len );
  1767. CRC32_Final( &crc );
  1768. return ( int )crc;
  1769. }
  1770. class CAsyncCaption
  1771. {
  1772. public:
  1773. CAsyncCaption() :
  1774. m_flDuration( 0.0f ),
  1775. m_bIsStream( false ),
  1776. m_bFromPlayer( false )
  1777. {
  1778. }
  1779. ~CAsyncCaption()
  1780. {
  1781. int c = m_Tokens.Count();
  1782. for ( int i = 0; i < c; ++i )
  1783. {
  1784. delete m_Tokens[ i ];
  1785. }
  1786. m_Tokens.Purge();
  1787. }
  1788. void StartRequesting( CHudCloseCaption *hudCloseCaption, CUtlVector< AsyncCaption_t >& directories )
  1789. {
  1790. // Issue pending async requests for each token in string
  1791. int c = m_Tokens.Count();
  1792. for ( int i = 0; i < c; ++i )
  1793. {
  1794. caption_t *caption = m_Tokens[ i ];
  1795. Assert( !caption->stream );
  1796. Assert( caption->dirindex >= 0 );
  1797. CaptionLookup_t& entry = directories[ caption->fileindex ].m_CaptionDirectory[ caption->dirindex ];
  1798. // Request this block, and if it's there, it'll call OnDataLoaded immediately
  1799. g_AsyncCaptionResourceManager.PollForAsyncLoading( hudCloseCaption, caption->fileindex, entry.blockNum );
  1800. }
  1801. }
  1802. void OnDataArrived( CUtlVector< AsyncCaption_t >& directories, int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData )
  1803. {
  1804. int c = m_Tokens.Count();
  1805. for ( int i = 0; i < c; ++i )
  1806. {
  1807. caption_t *caption = m_Tokens[ i ];
  1808. if ( !caption || caption->stream != NULL || caption->fileindex != nFileIndex )
  1809. continue;
  1810. // Lookup the data
  1811. CaptionLookup_t &entry = directories[ nFileIndex ].m_CaptionDirectory[ caption->dirindex ];
  1812. if ( entry.blockNum != nBlockNum )
  1813. continue;
  1814. const wchar_t *pIn = ( const wchar_t *)&pData->m_pBlockData[ entry.offset ];
  1815. caption->stream = new wchar_t[ entry.length >> 1 ];
  1816. memcpy( (void *)caption->stream, pIn, entry.length );
  1817. }
  1818. }
  1819. void ProcessAsyncWork( CHudCloseCaption *hudCloseCaption, CUtlVector< AsyncCaption_t >& directories )
  1820. {
  1821. int c = m_Tokens.Count();
  1822. for ( int i = 0; i < c; ++i )
  1823. {
  1824. caption_t *caption = m_Tokens[ i ];
  1825. if ( caption->stream != NULL )
  1826. continue;
  1827. CaptionLookup_t& entry = directories[ caption->fileindex].m_CaptionDirectory[ caption->dirindex ];
  1828. // Request this block, and if it's there, it'll call OnDataLoaded immediately
  1829. g_AsyncCaptionResourceManager.PollForAsyncLoading( hudCloseCaption, caption->fileindex, entry.blockNum );
  1830. }
  1831. }
  1832. bool GetStream( OUT_Z_BYTECAP(bufSizeInBytes) wchar_t *buf, int bufSizeInBytes )
  1833. {
  1834. buf[ 0 ] = L'\0';
  1835. int c = m_Tokens.Count();
  1836. for ( int i = 0; i < c; ++i )
  1837. {
  1838. caption_t *caption = m_Tokens[ i ];
  1839. if ( !caption || caption->stream == NULL )
  1840. {
  1841. return false;
  1842. }
  1843. }
  1844. unsigned int curlen = 0;
  1845. unsigned int maxlen = bufSizeInBytes / sizeof( wchar_t );
  1846. // Compose full stream from tokens
  1847. for ( int i = 0; i < c; ++i )
  1848. {
  1849. caption_t *caption = m_Tokens[ i ];
  1850. #ifdef WIN32
  1851. unsigned int len = wcsnlen( caption->stream, maxlen ) + 1;
  1852. #else
  1853. unsigned int len = wcslen( caption->stream ) + 1;
  1854. #endif
  1855. Assert( len < maxlen );
  1856. if ( curlen + len >= maxlen )
  1857. break;
  1858. wcscat( buf, caption->stream );
  1859. if ( i < c - 1 )
  1860. {
  1861. wcscat( buf, L" " );
  1862. }
  1863. curlen += len;
  1864. }
  1865. return true;
  1866. }
  1867. bool IsStream() const
  1868. {
  1869. return m_bIsStream;
  1870. }
  1871. void SetIsStream( bool state )
  1872. {
  1873. m_bIsStream = state;
  1874. }
  1875. void AddRandomToken( CUtlVector< AsyncCaption_t >& directories )
  1876. {
  1877. int dc = directories.Count();
  1878. int fileindex = RandomInt( 0, dc - 1 );
  1879. int c = directories[ fileindex ].m_CaptionDirectory.Count();
  1880. int idx = RandomInt( 0, c - 1 );
  1881. caption_t *caption = new caption_t;
  1882. char foo[ 32 ];
  1883. Q_snprintf( foo, sizeof( foo ), "%d", idx );
  1884. caption->token = strdup( foo );
  1885. CaptionLookup_t help;
  1886. help.SetHash( foo );
  1887. caption->hash = help.hash;
  1888. caption->dirindex = idx;
  1889. caption->stream = NULL;
  1890. caption->fileindex = fileindex;
  1891. m_Tokens.AddToTail( caption );
  1892. }
  1893. bool AddTokenByHash
  1894. (
  1895. CUtlVector< AsyncCaption_t >& directories,
  1896. unsigned int hash,
  1897. char const *pchToken
  1898. )
  1899. {
  1900. CaptionLookup_t search;
  1901. search.hash = hash;
  1902. int idx = -1;
  1903. int i;
  1904. int dc = directories.Count();
  1905. for ( i = 0; i < dc; ++i )
  1906. {
  1907. idx = directories[ i ].m_CaptionDirectory.Find( search );
  1908. if ( idx == directories[ i ].m_CaptionDirectory.InvalidIndex() )
  1909. continue;
  1910. break;
  1911. }
  1912. if ( i >= dc || idx == -1 )
  1913. {
  1914. AssertMsgOnce( *pchToken, "Should never fail to find a caption by hash, since server side has searched for hash!!!" );
  1915. return false;
  1916. }
  1917. caption_t *caption = new caption_t;
  1918. caption->token = strdup( pchToken );
  1919. caption->hash = hash;
  1920. caption->dirindex = idx;
  1921. caption->stream = NULL;
  1922. caption->fileindex = i;
  1923. m_Tokens.AddToTail( caption );
  1924. return true;
  1925. }
  1926. bool AddToken
  1927. (
  1928. CUtlVector< AsyncCaption_t >& directories,
  1929. const char *token
  1930. )
  1931. {
  1932. CaptionLookup_t search;
  1933. search.SetHash( token );
  1934. return AddTokenByHash( directories, search.hash, token );
  1935. }
  1936. int Count() const
  1937. {
  1938. return m_Tokens.Count();
  1939. }
  1940. const char *GetToken( int index )
  1941. {
  1942. return m_Tokens[ index ]->token;
  1943. }
  1944. void GetOriginalStream( char *buf, size_t bufsize )
  1945. {
  1946. buf[ 0 ] = 0;
  1947. int c = Count();
  1948. for ( int i = 0 ; i < c; ++i )
  1949. {
  1950. Q_strncat( buf, GetToken( i ), bufsize, COPY_ALL_CHARACTERS );
  1951. if ( i != c - 1 )
  1952. {
  1953. Q_strncat( buf, " ", bufsize, COPY_ALL_CHARACTERS );
  1954. }
  1955. }
  1956. }
  1957. void SetDuration( float t )
  1958. {
  1959. m_flDuration = t;
  1960. }
  1961. float GetDuration()
  1962. {
  1963. return m_flDuration;
  1964. }
  1965. bool IsFromPlayer()
  1966. {
  1967. return m_bFromPlayer;
  1968. }
  1969. void SetFromPlayer( bool state )
  1970. {
  1971. m_bFromPlayer = state;
  1972. }
  1973. bool IsDirect()
  1974. {
  1975. return m_bDirect;
  1976. }
  1977. void SetDirect( bool state )
  1978. {
  1979. m_bDirect = state;
  1980. }
  1981. unsigned int GetHash() const
  1982. {
  1983. if ( m_bIsStream )
  1984. return 0u;
  1985. if ( m_Tokens.Count() == 0 )
  1986. return 0u;
  1987. return m_Tokens[ 0 ]->hash;
  1988. }
  1989. private:
  1990. float m_flDuration;
  1991. bool m_bIsStream : 1;
  1992. bool m_bFromPlayer : 1;
  1993. bool m_bDirect : 1;
  1994. struct caption_t
  1995. {
  1996. caption_t() :
  1997. token( 0 ),
  1998. hash( 0u ),
  1999. dirindex( -1 ),
  2000. fileindex( -1 ),
  2001. stream( 0 )
  2002. {
  2003. }
  2004. ~caption_t()
  2005. {
  2006. free( token );
  2007. delete[] stream;
  2008. }
  2009. void SetStream( const wchar_t *in )
  2010. {
  2011. delete[] stream;
  2012. stream = 0;
  2013. if ( !in )
  2014. return;
  2015. #ifdef WIN32
  2016. int len = wcsnlen( in, ( MAX_CAPTION_CHARACTERS - 1 ) );
  2017. #else
  2018. int len = wcslen( in );
  2019. #endif
  2020. Assert( len < ( MAX_CAPTION_CHARACTERS - 1 ) );
  2021. stream = new wchar_t[ len + 1 ];
  2022. wcsncpy( stream, in, len + 1 );
  2023. }
  2024. char *token;
  2025. unsigned int hash;
  2026. int dirindex;
  2027. int fileindex;
  2028. wchar_t *stream;
  2029. };
  2030. CUtlVector< caption_t * > m_Tokens;
  2031. };
  2032. void CHudCloseCaption::ProcessAsyncWork()
  2033. {
  2034. int i;
  2035. for( i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); i = m_AsyncWork.Next( i ) )
  2036. {
  2037. // check for data arrival
  2038. CAsyncCaption *item = m_AsyncWork[ i ];
  2039. Assert( item );
  2040. if ( item )
  2041. {
  2042. item->ProcessAsyncWork( this, m_AsyncCaptions );
  2043. }
  2044. }
  2045. // Now operate on any new data which arrived
  2046. for( i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); )
  2047. {
  2048. int n = m_AsyncWork.Next( i );
  2049. CAsyncCaption *item = m_AsyncWork[ i ];
  2050. wchar_t stream[ MAX_CAPTION_CHARACTERS ];
  2051. // If we get to the first item with pending async work, stop processing
  2052. if ( !item || !item->GetStream( stream, sizeof( stream ) ) )
  2053. {
  2054. break;
  2055. }
  2056. if ( stream[ 0 ] != L'\0' )
  2057. {
  2058. char original[ 512 ];
  2059. item->GetOriginalStream( original, sizeof( original ) );
  2060. // Process it now
  2061. if ( item->IsStream() )
  2062. {
  2063. _ProcessSentenceCaptionStream( item->Count(), original, stream );
  2064. }
  2065. else
  2066. {
  2067. #if defined( POSIX ) && !defined( PLATFORM_PS3 )
  2068. wchar_t localStream[ MAX_CAPTION_CHARACTERS ];
  2069. // we persist to disk as ucs2 so convert back to real unicode here
  2070. V_UCS2ToUnicode( (ucs2 *)stream, localStream, sizeof(localStream) );
  2071. _ProcessCaption( localStream, item->GetHash(), item->GetDuration(), item->IsFromPlayer(), item->IsDirect() );
  2072. #else
  2073. _ProcessCaption( stream, item->GetHash(), item->GetDuration(), item->IsFromPlayer(), item->IsDirect() );
  2074. #endif
  2075. }
  2076. }
  2077. m_AsyncWork.Remove( i );
  2078. delete item;
  2079. i = n;
  2080. }
  2081. }
  2082. void CHudCloseCaption::ClearAsyncWork()
  2083. {
  2084. for ( int i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); i = m_AsyncWork.Next( i ) )
  2085. {
  2086. CAsyncCaption *item = m_AsyncWork[ i ];
  2087. delete item;
  2088. }
  2089. m_AsyncWork.Purge();
  2090. }
  2091. extern void Hack_FixEscapeChars( char *str );
  2092. void CHudCloseCaption::ProcessCaptionDirect( const char *tokenname, float duration, bool fromplayer /* = false */ )
  2093. {
  2094. m_bVisibleDueToDirect = true;
  2095. char token[ 512 ];
  2096. Q_strncpy( token, tokenname, sizeof( token ) );
  2097. if ( Q_strstr( token, "\\" ) )
  2098. {
  2099. Hack_FixEscapeChars( token );
  2100. }
  2101. ProcessCaption( token, duration, fromplayer, true );
  2102. }
  2103. void CHudCloseCaption::PlayRandomCaption()
  2104. {
  2105. if ( !closecaption.GetBool() )
  2106. return;
  2107. CAsyncCaption *async = new CAsyncCaption;
  2108. async->SetIsStream( false );
  2109. async->AddRandomToken( m_AsyncCaptions );
  2110. async->SetDuration( RandomFloat( 1.0f, 3.0f ) );
  2111. async->SetFromPlayer( RandomInt( 0, 1 ) == 0 ? true : false );
  2112. async->StartRequesting( this, m_AsyncCaptions );
  2113. m_AsyncWork.AddToTail( async );
  2114. }
  2115. bool CHudCloseCaption::AddAsyncWork( char const *tokenstream, bool bIsStream, float duration, bool fromplayer, bool direct /*=false*/ )
  2116. {
  2117. if ( !closecaption.GetBool() && !direct )
  2118. return false;
  2119. bool bret = true;
  2120. CAsyncCaption *async = new CAsyncCaption();
  2121. async->SetIsStream( bIsStream );
  2122. async->SetDirect( direct );
  2123. if ( !bIsStream )
  2124. {
  2125. bret = async->AddToken
  2126. (
  2127. m_AsyncCaptions,
  2128. tokenstream
  2129. );
  2130. }
  2131. else
  2132. {
  2133. // The first token from the stream is the name of the sentence
  2134. char tokenname[ 512 ];
  2135. tokenname[ 0 ] = 0;
  2136. const char *p = tokenstream;
  2137. p = nexttoken( tokenname, p, ' ' );
  2138. // p points to reset of sentence tokens, build up a unicode string from them...
  2139. while ( p && Q_strlen( tokenname ) > 0 )
  2140. {
  2141. p = nexttoken( tokenname, p, ' ' );
  2142. if ( Q_strlen( tokenname ) == 0 )
  2143. break;
  2144. async->AddToken
  2145. (
  2146. m_AsyncCaptions,
  2147. tokenname
  2148. );
  2149. }
  2150. }
  2151. m_AsyncWork.AddToTail( async );
  2152. async->SetDuration( duration );
  2153. async->SetFromPlayer( fromplayer );
  2154. // Do this last as the block might be resident already and this will finish immediately...
  2155. async->StartRequesting( this, m_AsyncCaptions );
  2156. return bret;
  2157. }
  2158. bool CHudCloseCaption::AddAsyncWorkByHash( unsigned int hash, float duration, bool fromplayer, bool direct /*=false*/ )
  2159. {
  2160. if ( !closecaption.GetBool() && !direct )
  2161. return false;
  2162. bool bret = true;
  2163. CAsyncCaption *async = new CAsyncCaption();
  2164. async->SetIsStream( false );
  2165. async->SetDirect( direct );
  2166. bret = async->AddTokenByHash
  2167. (
  2168. m_AsyncCaptions,
  2169. hash,
  2170. ""
  2171. );
  2172. m_AsyncWork.AddToTail( async );
  2173. async->SetDuration( duration );
  2174. async->SetFromPlayer( fromplayer );
  2175. // Do this last as the block might be resident already and this will finish immediately...
  2176. async->StartRequesting( this, m_AsyncCaptions );
  2177. return bret;
  2178. }
  2179. void CHudCloseCaption::ProcessSentenceCaptionStream( const char *tokenstream )
  2180. {
  2181. float interval = cc_sentencecaptionnorepeat.GetFloat();
  2182. interval = clamp( interval, 0.1f, 60.0f );
  2183. // The first token from the stream is the name of the sentence
  2184. char tokenname[ 512 ];
  2185. tokenname[ 0 ] = 0;
  2186. const char *p = tokenstream;
  2187. p = nexttoken( tokenname, p, ' ' );
  2188. if ( Q_strlen( tokenname ) > 0 )
  2189. {
  2190. // Use it to check for "norepeat" rules
  2191. CaptionRepeat entry;
  2192. entry.m_nTokenIndex = CRCString( tokenname );
  2193. int idx = m_CloseCaptionRepeats.Find( entry );
  2194. if ( m_CloseCaptionRepeats.InvalidIndex() == idx )
  2195. {
  2196. entry.m_flLastEmitTime = gpGlobals->curtime;
  2197. entry.m_nLastEmitTick = gpGlobals->tickcount;
  2198. entry.m_flInterval = interval;
  2199. m_CloseCaptionRepeats.Insert( entry );
  2200. }
  2201. else
  2202. {
  2203. CaptionRepeat &entry = m_CloseCaptionRepeats[ idx ];
  2204. if ( gpGlobals->curtime < ( entry.m_flLastEmitTime + entry.m_flInterval ) )
  2205. {
  2206. return;
  2207. }
  2208. entry.m_flLastEmitTime = gpGlobals->curtime;
  2209. entry.m_nLastEmitTick = gpGlobals->tickcount;
  2210. }
  2211. }
  2212. AddAsyncWork( tokenstream, true, 0.0f, false );
  2213. }
  2214. void CHudCloseCaption::_ProcessSentenceCaptionStream( int wordCount, const char *tokenstream, const wchar_t *caption_full )
  2215. {
  2216. if ( caption_full[ 0 ] != L'\0' )
  2217. {
  2218. Process( caption_full, ( wordCount + 1 ) * 0.75f, false /*never from player!*/ );
  2219. }
  2220. }
  2221. bool CHudCloseCaption::ProcessCaption( const char *tokenname, float duration, bool fromplayer /* = false */, bool direct /* = false */ )
  2222. {
  2223. return AddAsyncWork( tokenname, false, duration, fromplayer, direct );
  2224. }
  2225. bool CHudCloseCaption::ProcessCaptionByHash( unsigned int hash, float duration, bool fromplayer, bool direct /* = false */ )
  2226. {
  2227. return AddAsyncWorkByHash( hash, duration, fromplayer, direct );
  2228. }
  2229. void CHudCloseCaption::_ProcessCaption( const wchar_t *caption, unsigned int hash, float duration, bool fromplayer, bool direct )
  2230. {
  2231. // Get the string for the token
  2232. float interval = 0.0f;
  2233. bool hasnorepeat = GetNoRepeatValue( caption, interval );
  2234. CaptionRepeat entry;
  2235. entry.m_nTokenIndex = hash;
  2236. int idx = m_CloseCaptionRepeats.Find( entry );
  2237. if ( m_CloseCaptionRepeats.InvalidIndex() == idx )
  2238. {
  2239. entry.m_flLastEmitTime = gpGlobals->curtime;
  2240. entry.m_nLastEmitTick = gpGlobals->tickcount;
  2241. entry.m_flInterval = interval;
  2242. m_CloseCaptionRepeats.Insert( entry );
  2243. }
  2244. else
  2245. {
  2246. CaptionRepeat &entry = m_CloseCaptionRepeats[ idx ];
  2247. // Interval of 0.0 means just don't double emit on same tick #
  2248. if ( entry.m_flInterval <= 0.0f )
  2249. {
  2250. if ( gpGlobals->tickcount <= entry.m_nLastEmitTick )
  2251. {
  2252. return;
  2253. }
  2254. }
  2255. else if ( hasnorepeat )
  2256. {
  2257. if ( gpGlobals->curtime < ( entry.m_flLastEmitTime + entry.m_flInterval ) )
  2258. {
  2259. return;
  2260. }
  2261. }
  2262. entry.m_flLastEmitTime = gpGlobals->curtime;
  2263. entry.m_nLastEmitTick = gpGlobals->tickcount;
  2264. }
  2265. Process( caption, duration, fromplayer, direct );
  2266. }
  2267. //-----------------------------------------------------------------------------
  2268. // Purpose:
  2269. // Input : *pszName -
  2270. // iSize -
  2271. // *pbuf -
  2272. //-----------------------------------------------------------------------------
  2273. bool CHudCloseCaption::MsgFunc_CloseCaption(const CCSUsrMsg_CloseCaption &msg)
  2274. {
  2275. unsigned int hash;
  2276. hash = msg.hash();
  2277. float duration = msg.duration() * 0.1f;
  2278. bool fromplayer = msg.from_player();
  2279. ProcessCaptionByHash( hash, duration, fromplayer );
  2280. return true;
  2281. }
  2282. //-----------------------------------------------------------------------------
  2283. // Purpose:
  2284. // Input : *pszName -
  2285. // iSize -
  2286. // *pbuf -
  2287. //-----------------------------------------------------------------------------
  2288. bool CHudCloseCaption::MsgFunc_CloseCaptionDirect(const CCSUsrMsg_CloseCaptionDirect &msg)
  2289. {
  2290. unsigned int hash;
  2291. hash = msg.hash();
  2292. float duration = msg.duration() * 0.1f;
  2293. bool fromplayer = msg.from_player();
  2294. m_bVisibleDueToDirect = true;
  2295. ProcessCaptionByHash( hash, duration, fromplayer, true );
  2296. return true;
  2297. }
  2298. int CHudCloseCaption::GetFontNumber( bool bold, bool italic )
  2299. {
  2300. if ( IsGameConsole() )
  2301. {
  2302. return CHudCloseCaption::CCFONT_CONSOLE;
  2303. }
  2304. if ( bold || italic )
  2305. {
  2306. if( bold && italic )
  2307. {
  2308. return CHudCloseCaption::CCFONT_ITALICBOLD;
  2309. }
  2310. if ( bold )
  2311. {
  2312. return CHudCloseCaption::CCFONT_BOLD;
  2313. }
  2314. if ( italic )
  2315. {
  2316. return CHudCloseCaption::CCFONT_ITALIC;
  2317. }
  2318. }
  2319. return CHudCloseCaption::CCFONT_NORMAL;
  2320. }
  2321. void CHudCloseCaption::Flush()
  2322. {
  2323. g_AsyncCaptionResourceManager.Flush();
  2324. }
  2325. void CHudCloseCaption::InitCaptionDictionary( const char *language, bool bForce )
  2326. {
  2327. if ( !bForce && m_CurrentLanguage.IsValid() && !Q_stricmp( m_CurrentLanguage.String(), language ) )
  2328. return;
  2329. if ( !language && bForce && m_CurrentLanguage.IsValid() )
  2330. {
  2331. // use existing language
  2332. language = m_CurrentLanguage.String();
  2333. }
  2334. else
  2335. {
  2336. m_CurrentLanguage = language;
  2337. }
  2338. // Make sure we've finished all out async work before rebuilding the dictionary
  2339. const float flMaxAsyncProcessTime = 0.5f;
  2340. float flStartProcessTime = Plat_FloatTime();
  2341. while ( m_AsyncWork.Count() > 0 )
  2342. {
  2343. ProcessAsyncWork();
  2344. if ( Plat_FloatTime() - flStartProcessTime >= flMaxAsyncProcessTime )
  2345. {
  2346. DevWarning( "Could not finish async caption work after %f seconds of processing before caption dictionary init!\n", flMaxAsyncProcessTime );
  2347. m_AsyncWork.RemoveAll();
  2348. break;
  2349. }
  2350. }
  2351. m_AsyncCaptions.Purge();
  2352. g_AsyncCaptionResourceManager.Clear();
  2353. bool bAdded = AddFileToCaptionDictionary( VarArgs( "resource/closecaption_%s.dat", language ) );
  2354. if ( !bAdded && V_stricmp( language, "english" ) )
  2355. {
  2356. // non-english can fallback to english
  2357. AddFileToCaptionDictionary( "resource/closecaption_english.dat" );
  2358. }
  2359. bAdded = AddFileToCaptionDictionary( VarArgs( "resource/subtitles_%s.dat", language ) );
  2360. if ( !bAdded && V_stricmp( language, "english" ) )
  2361. {
  2362. // non-english can fallback to english
  2363. AddFileToCaptionDictionary( "resource/subtitles_english.dat" );
  2364. }
  2365. g_AsyncCaptionResourceManager.SetDbInfo( m_AsyncCaptions );
  2366. }
  2367. bool CHudCloseCaption::AddFileToCaptionDictionary( const char *filename )
  2368. {
  2369. int searchPathLen = filesystem->GetSearchPath( "GAME", true, NULL, 0 );
  2370. char *searchPaths = (char *)stackalloc( searchPathLen + 1 );
  2371. filesystem->GetSearchPath( "GAME", true, searchPaths, searchPathLen );
  2372. bool bAddedCaptions = false;
  2373. for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) )
  2374. {
  2375. if ( IsGameConsole() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) && !V_stristr( path, ".zip" ) )
  2376. {
  2377. // only want zip paths
  2378. continue;
  2379. }
  2380. if ( IsPS3() && !V_stristr( path, ".zip" ) )
  2381. {
  2382. // PS3 cannot convert dvddev subtitles files, must be in the zip
  2383. Warning( "Client: skipped dvddev caption file: %s\n", filename );
  2384. continue;
  2385. }
  2386. char fullpath[MAX_PATH];
  2387. Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", path, filename );
  2388. Q_FixSlashes( fullpath );
  2389. Q_strlower( fullpath );
  2390. if ( IsGameConsole() )
  2391. {
  2392. char fullpath360[MAX_PATH];
  2393. UpdateOrCreateCaptionFile( fullpath, fullpath360, sizeof( fullpath360 ) );
  2394. Q_strncpy( fullpath, fullpath360, sizeof( fullpath ) );
  2395. }
  2396. int idx = m_AsyncCaptions.AddToTail();
  2397. AsyncCaption_t& entry = m_AsyncCaptions[ idx ];
  2398. if ( !entry.LoadFromFile( fullpath ) )
  2399. {
  2400. m_AsyncCaptions.Remove( idx );
  2401. }
  2402. else
  2403. {
  2404. DevMsg( "Client: added caption file: %s\n", fullpath );
  2405. bAddedCaptions = true;
  2406. }
  2407. }
  2408. return bAddedCaptions;
  2409. }
  2410. void CHudCloseCaption::OnFinishAsyncLoad( int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData )
  2411. {
  2412. // Fill in data for all users of pData->m_nBlockNum
  2413. FOR_EACH_LL( m_AsyncWork, i )
  2414. {
  2415. CAsyncCaption *item = m_AsyncWork[ i ];
  2416. if ( item )
  2417. {
  2418. item->OnDataArrived( m_AsyncCaptions, nFileIndex, nBlockNum, pData );
  2419. }
  2420. }
  2421. }
  2422. //-----------------------------------------------------------------------------
  2423. // Purpose:
  2424. //-----------------------------------------------------------------------------
  2425. void CHudCloseCaption::Lock( void )
  2426. {
  2427. m_bLocked = true;
  2428. }
  2429. //-----------------------------------------------------------------------------
  2430. // Purpose:
  2431. //-----------------------------------------------------------------------------
  2432. void CHudCloseCaption::Unlock( void )
  2433. {
  2434. m_bLocked = false;
  2435. }
  2436. static int EmitCaptionCompletion( const char *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] )
  2437. {
  2438. int current = 0;
  2439. if ( !g_pVGuiLocalize || IsGameConsole() )
  2440. return current;
  2441. const char *cmdname = "cc_emit";
  2442. char *substring = NULL;
  2443. int substringLen = 0;
  2444. if ( Q_strstr( partial, cmdname ) && strlen(partial) > strlen(cmdname) + 1 )
  2445. {
  2446. substring = (char *)partial + strlen( cmdname ) + 1;
  2447. substringLen = strlen(substring);
  2448. }
  2449. vgui::StringIndex_t i = g_pVGuiLocalize->GetFirstStringIndex();
  2450. while ( i != vgui::INVALID_STRING_INDEX &&
  2451. current < COMMAND_COMPLETION_MAXITEMS )
  2452. {
  2453. const char *ccname = g_pVGuiLocalize->GetNameByIndex( i );
  2454. if ( ccname )
  2455. {
  2456. if ( !substring || !Q_strncasecmp( ccname, substring, substringLen ) )
  2457. {
  2458. Q_snprintf( commands[ current ], sizeof( commands[ current ] ), "%s %s", cmdname, ccname );
  2459. current++;
  2460. }
  2461. }
  2462. i = g_pVGuiLocalize->GetNextStringIndex( i );
  2463. }
  2464. return current;
  2465. }
  2466. CON_COMMAND_F_COMPLETION( cc_emit, "Emits a closed caption", 0, EmitCaptionCompletion )
  2467. {
  2468. if ( args.ArgC() != 2 )
  2469. {
  2470. Msg( "usage: cc_emit tokenname\n" );
  2471. return;
  2472. }
  2473. CHudCloseCaption *hudCloseCaption = GET_FULLSCREEN_HUDELEMENT( CHudCloseCaption );
  2474. if ( hudCloseCaption )
  2475. {
  2476. hudCloseCaption->ProcessCaption( args[1], 5.0f );
  2477. }
  2478. }
  2479. CON_COMMAND( cc_random, "Emits a random caption" )
  2480. {
  2481. int count = 1;
  2482. if ( args.ArgC() == 2 )
  2483. {
  2484. count = MAX( 1, atoi( args[ 1 ] ) );
  2485. }
  2486. CHudCloseCaption *hudCloseCaption = GET_FULLSCREEN_HUDELEMENT( CHudCloseCaption );
  2487. if ( hudCloseCaption )
  2488. {
  2489. for ( int i = 0; i < count; ++i )
  2490. {
  2491. hudCloseCaption->PlayRandomCaption();
  2492. }
  2493. }
  2494. }
  2495. CON_COMMAND( cc_flush, "Flushes async'd captions." )
  2496. {
  2497. CHudCloseCaption *hudCloseCaption = GET_FULLSCREEN_HUDELEMENT( CHudCloseCaption );
  2498. if ( hudCloseCaption )
  2499. {
  2500. hudCloseCaption->Flush();
  2501. }
  2502. }
  2503. CON_COMMAND( cc_showblocks, "Toggles showing which blocks are pending/loaded async." )
  2504. {
  2505. CHudCloseCaption *hudCloseCaption = GET_FULLSCREEN_HUDELEMENT( CHudCloseCaption );
  2506. if ( hudCloseCaption )
  2507. {
  2508. hudCloseCaption->TogglePaintDebug();
  2509. }
  2510. }
  2511. void OnCaptionLanguageChanged( IConVar *pConVar, const char *pOldString, float flOldValue )
  2512. {
  2513. if ( !g_pVGuiLocalize )
  2514. return;
  2515. ConVarRef var( pConVar );
  2516. char fn[ 512 ];
  2517. Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", var.GetString() );
  2518. // Re-adding the file, even if it's "english" will overwrite the tokens as needed
  2519. if ( !IsGameConsole() )
  2520. {
  2521. g_pVGuiLocalize->AddFile( "resource/closecaption_%language%.txt", "GAME", true );
  2522. }
  2523. char uilanguage[ 64 ];
  2524. if (engine)
  2525. {
  2526. engine->GetUILanguage( uilanguage, sizeof( uilanguage ) );
  2527. }
  2528. else
  2529. {
  2530. Msg( "Unable to get default ui language, using \'english\'\n" );
  2531. Q_strcpy( uilanguage, "english" );
  2532. }
  2533. CHudCloseCaption *hudCloseCaption = GET_FULLSCREEN_HUDELEMENT( CHudCloseCaption );
  2534. if ( !hudCloseCaption )
  2535. return;
  2536. // If it's not the default, load the language on top of the user's default language
  2537. if ( Q_strlen( var.GetString() ) > 0 && Q_stricmp( var.GetString(), uilanguage ) )
  2538. {
  2539. if ( !IsGameConsole() )
  2540. {
  2541. if ( g_pFullFileSystem->FileExists( fn ) )
  2542. {
  2543. g_pVGuiLocalize->AddFile( fn, "GAME", true );
  2544. }
  2545. else
  2546. {
  2547. char fallback[ 512 ];
  2548. Q_snprintf( fallback, sizeof( fallback ), "resource/closecaption_%s.txt", uilanguage );
  2549. Msg( "%s not found\n", fn );
  2550. Msg( "%s will be used\n", fallback );
  2551. }
  2552. }
  2553. hudCloseCaption->InitCaptionDictionary( var.GetString() );
  2554. }
  2555. else
  2556. {
  2557. hudCloseCaption->InitCaptionDictionary( uilanguage );
  2558. }
  2559. DevMsg( "cc_lang = %s\n", var.GetString() );
  2560. }
  2561. ConVar cc_lang( "cc_lang", "", FCVAR_ARCHIVE, "Current close caption language (emtpy = use game UI language)", OnCaptionLanguageChanged );
  2562. #if defined( _GAMECONSOLE )
  2563. // internal issued command evented by DLC mount, not meant for users
  2564. CON_COMMAND_F( cc_reload, "", 0 )
  2565. {
  2566. CHudCloseCaption *hudCloseCaption = GET_FULLSCREEN_HUDELEMENT( CHudCloseCaption );
  2567. if ( hudCloseCaption )
  2568. {
  2569. // minimal changes, TU DLC hack
  2570. // Clears a private store that otherwise prevents the cc's from a rull re-init, the language hasn't changed, but the underlying data has
  2571. // due to new search path mounts.
  2572. hudCloseCaption->ClearCurrentLanguage();
  2573. OnCaptionLanguageChanged( &cc_lang, cc_lang.GetString(), cc_lang.GetFloat() );
  2574. }
  2575. }
  2576. #endif
  2577. CON_COMMAND( cc_findsound, "Searches for soundname which emits specified text." )
  2578. {
  2579. if ( args.ArgC() != 2 )
  2580. {
  2581. Msg( "usage: cc_findsound 'substring'\n" );
  2582. return;
  2583. }
  2584. CHudCloseCaption *hudCloseCaption = GET_FULLSCREEN_HUDELEMENT( CHudCloseCaption );
  2585. if ( hudCloseCaption )
  2586. {
  2587. hudCloseCaption->FindSound( args.Arg( 1 ) );
  2588. }
  2589. }
  2590. void CHudCloseCaption::FindSound( char const *pchANSI )
  2591. {
  2592. // Now do the searching
  2593. ucs2 stream[ 1024 ];
  2594. char streamANSI[ 1024 ];
  2595. for ( int i = 0 ; i < m_AsyncCaptions.Count(); ++i )
  2596. {
  2597. AsyncCaption_t &data = m_AsyncCaptions[ i ];
  2598. byte *block = new byte[ data.m_Header.blocksize ];
  2599. int nLoadedBlock = -1;
  2600. Q_memset( block, 0, data.m_Header.blocksize );
  2601. CaptionDictionary_t &dict = data.m_CaptionDirectory;
  2602. for ( int j = 0; j < dict.Count(); ++j )
  2603. {
  2604. CaptionLookup_t &lu = dict[ j ];
  2605. int blockNum = lu.blockNum;
  2606. const char *dbname = data.m_DataBaseFile.String();
  2607. // Try and reload it
  2608. char fn[ 256 ];
  2609. Q_strncpy( fn, dbname, sizeof( fn ) );
  2610. Q_FixSlashes( fn );
  2611. Q_strlower( fn );
  2612. asynccaptionparams_t params;
  2613. params.dbfile = fn;
  2614. params.blocktoload = blockNum;
  2615. params.blocksize = data.m_Header.blocksize;
  2616. params.blockoffset = data.m_Header.dataoffset + blockNum *data.m_Header.blocksize;
  2617. params.fileindex = i;
  2618. if ( blockNum != nLoadedBlock )
  2619. {
  2620. nLoadedBlock = blockNum;
  2621. FileHandle_t fh = filesystem->Open( fn, "rb" );
  2622. filesystem->Seek( fh, params.blockoffset, FILESYSTEM_SEEK_CURRENT );
  2623. filesystem->Read( block, data.m_Header.blocksize, fh );
  2624. filesystem->Close( fh );
  2625. }
  2626. // Now we have the data
  2627. const ucs2 *pIn = ( const ucs2 *)&block[ lu.offset ];
  2628. Q_memcpy( (void *)stream, pIn, MIN( lu.length, sizeof( stream ) ) );
  2629. // Now search for search text
  2630. V_UCS2ToUTF8( stream, streamANSI, sizeof( streamANSI ) );
  2631. streamANSI[ sizeof( streamANSI ) - 1 ] = 0;
  2632. if ( Q_stristr( streamANSI, pchANSI ) )
  2633. {
  2634. CaptionLookup_t search;
  2635. Msg( "found '%s' in %s\n", streamANSI, fn );
  2636. // Now find the sounds that will hash to this
  2637. for ( int k = soundemitterbase->First(); k != soundemitterbase->InvalidIndex(); k = soundemitterbase->Next( k ) )
  2638. {
  2639. char const *pchSoundName = soundemitterbase->GetSoundName( k );
  2640. // Hash it
  2641. search.SetHash( pchSoundName );
  2642. if ( search.hash == lu.hash )
  2643. {
  2644. Msg( " '%s' matches\n", pchSoundName );
  2645. }
  2646. }
  2647. if ( IsPC() )
  2648. {
  2649. for ( LocalizeStringIndex_t r = g_pVGuiLocalize->GetFirstStringIndex(); r != vgui::INVALID_STRING_INDEX; r = g_pVGuiLocalize->GetNextStringIndex( r ) )
  2650. {
  2651. const char *strName = g_pVGuiLocalize->GetNameByIndex( r );
  2652. search.SetHash( strName );
  2653. if ( search.hash == lu.hash )
  2654. {
  2655. Msg( " '%s' localization matches\n", strName );
  2656. }
  2657. }
  2658. }
  2659. }
  2660. }
  2661. delete[] block;
  2662. }
  2663. }
  2664. void CHudCloseCaption::ClearCurrentLanguage()
  2665. {
  2666. m_CurrentLanguage = UTL_INVAL_SYMBOL;
  2667. }
  2668. void CHudCloseCaption::LoadColorMap( const char *pFilename )
  2669. {
  2670. CUtlBuffer colorMapBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER );
  2671. if ( !g_pFullFileSystem->ReadFile( pFilename, "MOD", colorMapBuffer ) )
  2672. return;
  2673. characterset_t breakSet;
  2674. CharacterSetBuild( &breakSet, "" );
  2675. for ( ;; )
  2676. {
  2677. char tagToken[MAX_PATH];
  2678. int nTokenSize = colorMapBuffer.ParseToken( &breakSet, tagToken, sizeof( tagToken ) );
  2679. if ( nTokenSize <= 0 )
  2680. {
  2681. break;
  2682. }
  2683. char colorToken[MAX_PATH];
  2684. nTokenSize = colorMapBuffer.ParseToken( &breakSet, colorToken, sizeof( colorToken ) );
  2685. if ( nTokenSize <= 0 )
  2686. {
  2687. break;
  2688. }
  2689. if ( !StringHasPrefix( colorToken, "<clr:" ) )
  2690. {
  2691. // unrecognized color format
  2692. continue;
  2693. }
  2694. int r, g, b;
  2695. if ( sscanf( colorToken + V_strlen( "<clr:" ), "%i,%i,%i", &r, &g, &b ) != 3 )
  2696. {
  2697. // bad color format
  2698. continue;
  2699. }
  2700. Color tagColor = Color( r, g, b, 255 );
  2701. int iIndex = m_ColorMap.Find( tagToken );
  2702. if ( iIndex == m_ColorMap.InvalidIndex() )
  2703. {
  2704. iIndex = m_ColorMap.Insert( tagToken );
  2705. }
  2706. m_ColorMap[iIndex] = tagColor;
  2707. }
  2708. }
  2709. bool CHudCloseCaption::FindColorForTag( wchar_t *pTag, Color &tagColor )
  2710. {
  2711. char buf[128];
  2712. g_pVGuiLocalize->ConvertUnicodeToANSI( pTag, buf, sizeof( buf ) );
  2713. int iIndex = m_ColorMap.Find( buf );
  2714. if ( iIndex != m_ColorMap.InvalidIndex() )
  2715. {
  2716. tagColor = m_ColorMap[iIndex];
  2717. return true;
  2718. }
  2719. // not found
  2720. return false;
  2721. }
  2722. void CHudCloseCaption::SetUseAsianWordWrapping()
  2723. {
  2724. static bool bCheckForAsianLanguage = false;
  2725. static bool bIsAsianLanguage = false;
  2726. if ( !bCheckForAsianLanguage )
  2727. {
  2728. bCheckForAsianLanguage = true;
  2729. const char *pLanguage = vgui::scheme()->GetLanguage();
  2730. if ( pLanguage )
  2731. {
  2732. if ( !V_stricmp( pLanguage, "japanese" ) ||
  2733. !V_stricmp( pLanguage, "schinese" ) ||
  2734. !V_stricmp( pLanguage, "tchinese" ) )
  2735. {
  2736. bIsAsianLanguage = true;
  2737. }
  2738. }
  2739. }
  2740. m_bUseAsianWordWrapping = bIsAsianLanguage;
  2741. }