From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/client/hud_closecaption.cpp | 5820 +++++++++++++++---------------- 1 file changed, 2910 insertions(+), 2910 deletions(-) (limited to 'mp/src/game/client/hud_closecaption.cpp') diff --git a/mp/src/game/client/hud_closecaption.cpp b/mp/src/game/client/hud_closecaption.cpp index 7df2b4c2..f72b9fc1 100644 --- a/mp/src/game/client/hud_closecaption.cpp +++ b/mp/src/game/client/hud_closecaption.cpp @@ -1,2910 +1,2910 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//=============================================================================// - - -#include "cbase.h" -#include -#include "sentence.h" -#include "hud_closecaption.h" -#include "tier1/strtools.h" -#include -#include -#include -#include -#include -#include "iclientmode.h" -#include "hud_macros.h" -#include "checksum_crc.h" -#include "filesystem.h" -#include "datacache/idatacache.h" -#include "SoundEmitterSystem/isoundemittersystembase.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#define CC_INSET 12 - -extern ISoundEmitterSystemBase *soundemitterbase; - -// Marked as FCVAR_USERINFO so that the server can cull CC messages before networking them down to us!!! -ConVar closecaption( "closecaption", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX | FCVAR_USERINFO, "Enable close captioning." ); -extern ConVar cc_lang; -static ConVar cc_linger_time( "cc_linger_time", "1.0", FCVAR_ARCHIVE, "Close caption linger time." ); -static ConVar cc_predisplay_time( "cc_predisplay_time", "0.25", FCVAR_ARCHIVE, "Close caption delay before showing caption." ); -static ConVar cc_captiontrace( "cc_captiontrace", "1", 0, "Show missing closecaptions (0 = no, 1 = devconsole, 2 = show in hud)" ); -static ConVar cc_subtitles( "cc_subtitles", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "If set, don't show sound effect captions, just voice overs (i.e., won't help hearing impaired players)." ); -ConVar english( "english", "1", FCVAR_USERINFO, "If set to 1, running the english language set of assets." ); -static ConVar cc_smallfontlength( "cc_smallfontlength", "300", 0, "If text stream is this long, force usage of small font size." ); - -#define MAX_CAPTION_CHARACTERS 4096 - -#define CAPTION_PAN_FADE_TIME 0.5 // The time it takes for a line to fade while panning over a large entry -#define CAPTION_PAN_SLIDE_TIME 0.5 // The time it takes for a line to slide on while panning over a large entry - - -// A work unit is a pre-processed chunk of CC text to display -// Any state changes (font/color/etc) cause a new work unit to be precomputed -// Moving onto a new line also causes a new Work Unit -// The width and height are stored so that layout can be quickly recomputed each frame -class CCloseCaptionWorkUnit -{ -public: - CCloseCaptionWorkUnit(); - ~CCloseCaptionWorkUnit(); - - void SetWidth( int w ); - int GetWidth() const; - - void SetHeight( int h ); - int GetHeight() const; - - void SetPos( int x, int y ); - void GetPos( int& x, int &y ) const; - - void SetFadeStart( float flTime ); - float GetFadeStart( void ) const; - - void SetBold( bool bold ); - bool GetBold() const; - - void SetItalic( bool ital ); - bool GetItalic() const; - - void SetStream( const wchar_t *stream ); - const wchar_t *GetStream() const; - - void SetColor( Color& clr ); - Color GetColor() const; - - vgui::HFont GetFont() const - { - return m_hFont; - } - - void SetFont( vgui::HFont fnt ) - { - m_hFont = fnt; - } - - void Dump() - { - char buf[ 2048 ]; - g_pVGuiLocalize->ConvertUnicodeToANSI( GetStream(), buf, sizeof( buf ) ); - - Msg( "x = %i, y = %i, w = %i h = %i text %s\n", m_nX, m_nY, m_nWidth, m_nHeight, buf ); - } - -private: - - int m_nX; - int m_nY; - int m_nWidth; - int m_nHeight; - float m_flFadeStartTime; - - bool m_bBold; - bool m_bItalic; - wchar_t *m_pszStream; - vgui::HFont m_hFont; - Color m_Color; -}; - -CCloseCaptionWorkUnit::CCloseCaptionWorkUnit() : - m_nWidth(0), - m_nHeight(0), - m_bBold(false), - m_bItalic(false), - m_pszStream(0), - m_Color( Color( 255, 255, 255, 255 ) ), - m_hFont( 0 ), - m_flFadeStartTime(0) -{ -} - -CCloseCaptionWorkUnit::~CCloseCaptionWorkUnit() -{ - delete[] m_pszStream; - m_pszStream = NULL; -} - -void CCloseCaptionWorkUnit::SetWidth( int w ) -{ - m_nWidth = w; -} - -int CCloseCaptionWorkUnit::GetWidth() const -{ - return m_nWidth; -} - -void CCloseCaptionWorkUnit::SetHeight( int h ) -{ - m_nHeight = h; -} - -int CCloseCaptionWorkUnit::GetHeight() const -{ - return m_nHeight; -} - -void CCloseCaptionWorkUnit::SetPos( int x, int y ) -{ - m_nX = x; - m_nY = y; -} - -void CCloseCaptionWorkUnit::GetPos( int& x, int &y ) const -{ - x = m_nX; - y = m_nY; -} - -void CCloseCaptionWorkUnit::SetFadeStart( float flTime ) -{ - m_flFadeStartTime = flTime; -} - -float CCloseCaptionWorkUnit::GetFadeStart( void ) const -{ - return m_flFadeStartTime; -} - -void CCloseCaptionWorkUnit::SetBold( bool bold ) -{ - m_bBold = bold; -} - -bool CCloseCaptionWorkUnit::GetBold() const -{ - return m_bBold; -} - -void CCloseCaptionWorkUnit::SetItalic( bool ital ) -{ - m_bItalic = ital; -} - -bool CCloseCaptionWorkUnit::GetItalic() const -{ - return m_bItalic; -} - -void CCloseCaptionWorkUnit::SetStream( const wchar_t *stream ) -{ - delete[] m_pszStream; - m_pszStream = NULL; - - int len = wcslen( stream ); - Assert( len < 4096 ); - m_pszStream = new wchar_t[ len + 1 ]; - wcsncpy( m_pszStream, stream, len ); - m_pszStream[ len ] = L'\0'; -} - -const wchar_t *CCloseCaptionWorkUnit::GetStream() const -{ - return m_pszStream ? m_pszStream : L""; -} - -void CCloseCaptionWorkUnit::SetColor( Color& clr ) -{ - m_Color = clr; -} - -Color CCloseCaptionWorkUnit::GetColor() const -{ - return m_Color; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -class CCloseCaptionItem -{ -public: - CCloseCaptionItem( - const wchar_t *stream, - float timetolive, - float addedtime, - float predisplay, - bool valid, - bool fromplayer - ) : - m_flTimeToLive( 0.0f ), - m_flAddedTime( addedtime ), - m_bValid( false ), - m_nTotalWidth( 0 ), - m_nTotalHeight( 0 ), - m_bSizeComputed( false ), - m_bFromPlayer( fromplayer ) - - { - SetStream( stream ); - SetTimeToLive( timetolive ); - SetInitialLifeSpan( timetolive ); - SetPreDisplayTime( cc_predisplay_time.GetFloat() + predisplay ); - m_bValid = valid; - } - - CCloseCaptionItem( const CCloseCaptionItem& src ) - { - SetStream( src.m_szStream ); - m_flTimeToLive = src.m_flTimeToLive; - m_bValid = src.m_bValid; - m_bFromPlayer = src.m_bFromPlayer; - m_flAddedTime = src.m_flAddedTime; - } - - ~CCloseCaptionItem( void ) - { - while ( m_Work.Count() > 0 ) - { - CCloseCaptionWorkUnit *unit = m_Work[ 0 ]; - m_Work.Remove( 0 ); - delete unit; - } - - } - - void SetStream( const wchar_t *stream) - { - wcsncpy( m_szStream, stream, sizeof( m_szStream ) / sizeof( wchar_t ) ); - } - - const wchar_t *GetStream() const - { - return m_szStream; - } - - void SetTimeToLive( float ttl ) - { - m_flTimeToLive = ttl; - } - - float GetTimeToLive( void ) const - { - return m_flTimeToLive; - } - - void SetInitialLifeSpan( float t ) - { - m_flInitialLifeSpan = t; - } - - float GetInitialLifeSpan() const - { - return m_flInitialLifeSpan; - } - - bool IsValid() const - { - return m_bValid; - } - - void SetHeight( int h ) - { - m_nTotalHeight = h; - } - int GetHeight() const - { - return m_nTotalHeight; - } - void SetWidth( int w ) - { - m_nTotalWidth = w; - } - int GetWidth() const - { - return m_nTotalWidth; - } - - void AddWork( CCloseCaptionWorkUnit *unit ) - { - m_Work.AddToTail( unit ); - } - - int GetNumWorkUnits() const - { - return m_Work.Count(); - } - - CCloseCaptionWorkUnit *GetWorkUnit( int index ) - { - Assert( index >= 0 && index < m_Work.Count() ); - - return m_Work[ index ]; - } - - void SetSizeComputed( bool computed ) - { - m_bSizeComputed = computed; - } - - bool GetSizeComputed() const - { - return m_bSizeComputed; - } - - void SetPreDisplayTime( float t ) - { - m_flPreDisplayTime = t; - } - - float GetPreDisplayTime() const - { - return m_flPreDisplayTime; - } - - float GetAlpha( float fadeintimehidden, float fadeintime, float fadeouttime ) - { - float time_since_start = m_flInitialLifeSpan - m_flTimeToLive; - float time_until_end = m_flTimeToLive; - - float totalfadeintime = fadeintimehidden + fadeintime; - - if ( totalfadeintime > 0.001f && - time_since_start < totalfadeintime ) - { - if ( time_since_start >= fadeintimehidden ) - { - float f = 1.0f; - if ( fadeintime > 0.001f ) - { - f = ( time_since_start - fadeintimehidden ) / fadeintime; - } - f = clamp( f, 0.0f, 1.0f ); - return f; - } - - return 0.0f; - } - - if ( fadeouttime > 0.001f && - time_until_end < fadeouttime ) - { - float f = time_until_end / fadeouttime; - f = clamp( f, 0.0f, 1.0f ); - return f; - } - - return 1.0f; - } - - float GetAddedTime() const - { - return m_flAddedTime; - } - - void SetAddedTime( float addt ) - { - m_flAddedTime = addt; - } - - bool IsFromPlayer() const - { - return m_bFromPlayer; - } - -private: - wchar_t m_szStream[ MAX_CAPTION_CHARACTERS ]; - - float m_flPreDisplayTime; - float m_flTimeToLive; - float m_flInitialLifeSpan; - float m_flAddedTime; - bool m_bValid; - int m_nTotalWidth; - int m_nTotalHeight; - - bool m_bSizeComputed; - bool m_bFromPlayer; - - CUtlVector< CCloseCaptionWorkUnit * > m_Work; - -}; - -struct VisibleStreamItem -{ - int height; - int width; - CCloseCaptionItem *item; -}; - -//----------------------------------------------------------------------------- -// Purpose: The only resource manager parameter we currently care about is the name -// of the .vcd to cache into memory -//----------------------------------------------------------------------------- -struct asynccaptionparams_t -{ - const char *dbfile; - int fileindex; - int blocktoload; - int blockoffset; - int blocksize; -}; - -// 16K of cache for close caption data -#define MAX_ASYNCCAPTION_MEMORY_CACHE (int)( 64.0 * 1024.0f ) - -void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ); - -struct AsyncCaptionData_t -{ - int m_nBlockNum; - byte *m_pBlockData; - int m_nFileIndex; - int m_nBlockSize; - - bool m_bLoadPending : 1; - bool m_bLoadCompleted : 1; - - FSAsyncControl_t m_hAsyncControl; - - AsyncCaptionData_t() : - m_nBlockNum( -1 ), - m_pBlockData( 0 ), - m_nFileIndex( -1 ), - m_nBlockSize( 0 ), - m_bLoadPending( false ), - m_bLoadCompleted( false ), - m_hAsyncControl( NULL ) - { - } - - // APIS required by CDataManager - void DestroyResource() - { - if ( m_bLoadPending && !m_bLoadCompleted ) - { - filesystem->AsyncFinish( m_hAsyncControl, true ); - } - filesystem->AsyncRelease( m_hAsyncControl ); - - WipeData(); - delete this; - } - - void ReleaseData() - { - filesystem->AsyncRelease( m_hAsyncControl ); - m_hAsyncControl = 0; - WipeData(); - m_bLoadCompleted = false; - Assert( !m_bLoadPending ); - } - - void WipeData() - { - delete[] m_pBlockData; - m_pBlockData = NULL; - } - - AsyncCaptionData_t *GetData() - { - return this; - } - unsigned int Size() - { - return sizeof( *this ) + m_nBlockSize; - } - - void AsyncLoad( const char *fileName, int blockOffset ) - { - // Already pending - Assert ( !m_hAsyncControl ); - - // async load the file - FileAsyncRequest_t fileRequest; - fileRequest.pContext = (void *)this; - fileRequest.pfnCallback = ::CaptionAsyncLoaderCallback; - fileRequest.pData = m_pBlockData; - fileRequest.pszFilename = fileName; - fileRequest.nOffset = blockOffset; - fileRequest.flags = 0; - fileRequest.nBytes = m_nBlockSize; - fileRequest.priority = -1; - fileRequest.pszPathID = "GAME"; - - // queue for async load - MEM_ALLOC_CREDIT(); - filesystem->AsyncRead( fileRequest, &m_hAsyncControl ); - } - - // you must implement these static functions for the ResourceManager - // ----------------------------------------------------------- - static AsyncCaptionData_t *CreateResource( const asynccaptionparams_t ¶ms ) - { - AsyncCaptionData_t *data = new AsyncCaptionData_t; - data->m_nBlockNum = params.blocktoload; - data->m_nFileIndex = params.fileindex; - data->m_nBlockSize = params.blocksize; - data->m_pBlockData = new byte[ data->m_nBlockSize ]; - return data; - } - - static unsigned int EstimatedSize( const asynccaptionparams_t ¶ms ) - { - // The block size is assumed to be 4K - return ( sizeof( AsyncCaptionData_t ) + params.blocksize ); - } -}; - -//----------------------------------------------------------------------------- -// Purpose: This manages the instanced scene memory handles. We essentially grow a handle list by scene filename where -// the handle is a pointer to a AsyncCaptionData_t defined above. If the resource manager uncaches the handle, we reload the -// .vcd from disk. Precaching a .vcd calls into FindOrAddBlock which moves the .vcd to the head of the LRU if it's in memory -// or it reloads it from disk otherwise. -//----------------------------------------------------------------------------- -class CAsyncCaptionResourceManager : public CAutoGameSystem, public CManagedDataCacheClient< AsyncCaptionData_t, asynccaptionparams_t > -{ -public: - CAsyncCaptionResourceManager() : CAutoGameSystem( "CAsyncCaptionResourceManager" ) - { - } - - void SetDbInfo( const CUtlVector< AsyncCaption_t > & info ) - { - m_Db = info; - } - - virtual bool Init() - { - CCacheClientBaseClass::Init( datacache, "Captions", MAX_ASYNCCAPTION_MEMORY_CACHE ); - return true; - } - virtual void Shutdown() - { - Clear(); - CCacheClientBaseClass::Shutdown(); - } - - //----------------------------------------------------------------------------- - // Purpose: Spew a cache summary to the console - //----------------------------------------------------------------------------- - void SpewMemoryUsage() - { - GetCacheSection()->OutputReport(); - - DataCacheStatus_t status; - DataCacheLimits_t limits; - GetCacheSection()->GetStatus( &status, &limits ); - int bytesUsed = status.nBytes; - int bytesTotal = limits.nMaxBytes; - - float percent = 100.0f * (float)bytesUsed / (float)bytesTotal; - - int count = 0; - for ( int i = 0; i < m_Db.Count(); ++i ) - { - count += m_Db[ i ].m_RequestedBlocks.Count(); - } - - DevMsg( "CAsyncCaptionResourceManager: %i blocks total %s, %.2f %% of capacity\n", count, Q_pretifymem( bytesUsed, 2 ), percent ); - } - - virtual void LevelInitPostEntity() - { - } - - void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ) - { - // get our preserved data - AsyncCaptionData_t *pData = ( AsyncCaptionData_t * )request.pContext; - - Assert( pData ); - - // mark as completed in single atomic operation - pData->m_bLoadCompleted = true; - } - - int ComputeBlockOffset( int fileIndex, int blockNum ) - { - return m_Db[ fileIndex ].m_Header.dataoffset + blockNum * m_Db[ fileIndex ].m_Header.blocksize; - } - - void GetBlockInfo( int fileIndex, int blockNum, bool& entry, bool& pending, bool& loaded ) - { - pending = false; - loaded = false; - AsyncCaption_t::BlockInfo_t search; - search.fileindex = fileIndex; - search.blocknum = blockNum; - - CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ fileIndex ].m_RequestedBlocks; - - int idx = requested.Find( search ); - if ( idx == requested.InvalidIndex() ) - { - entry = false; - return; - } - entry = true; - - DataCacheHandle_t handle = requested[ idx ].handle; - AsyncCaptionData_t *pCaptionData = CacheLock( handle ); - if ( pCaptionData ) - { - if ( pCaptionData->m_bLoadPending ) - { - pending = true; - } - else if ( pCaptionData->m_bLoadCompleted ) - { - loaded = true; - } - CacheUnlock( handle ); - } - } - - // Either commences async loading or polls for async loading once per frame to wait for it to complete... - void PollForAsyncLoading( CHudCloseCaption *hudCloseCaption, int dbFileIndex, int blockNum ) - { - const char *dbname = m_Db[ dbFileIndex ].m_DataBaseFile.String(); - - CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ dbFileIndex ].m_RequestedBlocks; - - int idx = FindOrAddBlock( dbFileIndex, blockNum ); - if ( idx == requested.InvalidIndex() ) - { - Assert( 0 ); - return; - } - - DataCacheHandle_t handle = requested[ idx ].handle; - - AsyncCaptionData_t *pCaptionData = CacheLock( handle ); - if ( !pCaptionData ) - { - // Try and reload it - char fn[ 256 ]; - Q_strncpy( fn, dbname, sizeof( fn ) ); - Q_FixSlashes( fn ); - - asynccaptionparams_t params; - params.dbfile = fn; - params.blocktoload = blockNum; - params.blocksize = m_Db[ dbFileIndex ].m_Header.blocksize; - params.blockoffset = ComputeBlockOffset( dbFileIndex, blockNum ); - params.fileindex = dbFileIndex; - - handle = requested[ idx ].handle = CacheCreate( params ); - pCaptionData = CacheLock( handle ); - if ( !pCaptionData ) - { - Assert( pCaptionData ); - return; - } - } - - if ( pCaptionData->m_bLoadCompleted ) - { - pCaptionData->m_bLoadPending = false; - // Copy in data at this point - Assert( hudCloseCaption ); - if ( hudCloseCaption ) - { - hudCloseCaption->OnFinishAsyncLoad( requested[ idx ].fileindex, requested[ idx ].blocknum, pCaptionData ); - } - - // This finalizes the load (unlocks the handle) - GetCacheSection()->BreakLock( handle ); - return; - } - - if ( pCaptionData->m_bLoadPending ) - { - CacheUnlock( handle ); - return; - } - - // Commence load (locks handle for entire async load) (unlocked above) - pCaptionData->m_bLoadPending = true; - pCaptionData->AsyncLoad( dbname, ComputeBlockOffset( dbFileIndex, blockNum ) ); - } - - //----------------------------------------------------------------------------- - // Purpose: Touch the cache or load the scene into the cache for the first time - // Input : *filename - - //----------------------------------------------------------------------------- - int FindOrAddBlock( int dbFileIndex, int blockNum ) - { - const char *dbname = m_Db[ dbFileIndex ].m_DataBaseFile.String(); - - CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ dbFileIndex ].m_RequestedBlocks; - - AsyncCaption_t::BlockInfo_t search; - search.blocknum = blockNum; - search.fileindex = dbFileIndex; - - int idx = requested.Find( search ); - if ( idx != requested.InvalidIndex() ) - { - // Move it to head of LRU - CacheTouch( requested[ idx ].handle ); - return idx; - } - - char fn[ 256 ]; - Q_strncpy( fn, dbname, sizeof( fn ) ); - Q_FixSlashes( fn ); - - asynccaptionparams_t params; - params.dbfile = fn; - params.blocktoload = blockNum; - params.blockoffset = ComputeBlockOffset( dbFileIndex, blockNum ); - params.blocksize = m_Db[ dbFileIndex ].m_Header.blocksize; - params.fileindex = dbFileIndex; - - memhandle_t handle = CacheCreate( params ); - - AsyncCaption_t::BlockInfo_t info; - info.fileindex = dbFileIndex; - info.blocknum = blockNum; - info.handle = handle; - - // Add scene filename to dictionary - idx = requested.Insert( info ); - return idx; - } - - void Flush() - { - CacheFlush(); - } - - void Clear() - { - for ( int file = 0; file < m_Db.Count(); ++file ) - { - CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ file ].m_RequestedBlocks; - - int c = requested.Count(); - for ( int i = 0; i < c; ++i ) - { - memhandle_t dat = requested[ i ].handle; - CacheRemove( dat ); - } - - requested.RemoveAll(); - } - } - -private: - - CUtlVector< AsyncCaption_t > m_Db; -}; - -CAsyncCaptionResourceManager g_AsyncCaptionResourceManager; - -void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ) -{ - g_AsyncCaptionResourceManager.CaptionAsyncLoaderCallback( request, numReadBytes, asyncStatus ); -} - -DECLARE_HUDELEMENT( CHudCloseCaption ); - -DECLARE_HUD_MESSAGE( CHudCloseCaption, CloseCaption ); - -CHudCloseCaption::CHudCloseCaption( const char *pElementName ) - : CHudElement( pElementName ), - vgui::Panel( NULL, "HudCloseCaption" ), - m_CloseCaptionRepeats( 0, 0, CaptionTokenLessFunc ), - m_CurrentLanguage( UTL_INVAL_SYMBOL ), - m_bPaintDebugInfo( false ) -{ - vgui::Panel *pParent = g_pClientMode->GetViewport(); - SetParent( pParent ); - - m_nGoalHeight = 0; - m_nCurrentHeight = 0; - m_flGoalAlpha = 1.0f; - m_flCurrentAlpha = 1.0f; - - m_flGoalHeightStartTime = 0; - m_flGoalHeightFinishTime = 0; - - m_bLocked = false; - m_bVisibleDueToDirect = false; - - SetPaintBorderEnabled( false ); - SetPaintBackgroundEnabled( false ); - - vgui::ivgui()->AddTickSignal( GetVPanel(), 0 ); - - if ( !IsX360() ) - { - g_pVGuiLocalize->AddFile( "resource/closecaption_%language%.txt", "GAME", true ); - } - - HOOK_HUD_MESSAGE( CHudCloseCaption, CloseCaption ); - - char uilanguage[ 64 ]; - engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); - - if ( !Q_stricmp( uilanguage, "english" ) ) - { - english.SetValue( 1 ); - } - else - { - english.SetValue( 0 ); - } - - char dbfile [ 512 ]; - Q_snprintf( dbfile, sizeof( dbfile ), "resource/closecaption_%s.dat", uilanguage ); - InitCaptionDictionary( dbfile ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CHudCloseCaption::~CHudCloseCaption() -{ - m_CloseCaptionRepeats.RemoveAll(); - - ClearAsyncWork(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *newmap - -//----------------------------------------------------------------------------- -void CHudCloseCaption::LevelInit( void ) -{ - CreateFonts(); - // Reset repeat counters per level - m_CloseCaptionRepeats.RemoveAll(); - - // Wipe any stale pending work items... - ClearAsyncWork(); -} - -static ConVar cc_minvisibleitems( "cc_minvisibleitems", "1", 0, "Minimum number of caption items to show." ); - -void CHudCloseCaption::TogglePaintDebug() -{ - m_bPaintDebugInfo = !m_bPaintDebugInfo; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHudCloseCaption::Paint( void ) -{ - int w, h; - GetSize( w, h ); - - if ( m_bPaintDebugInfo ) - { - int blockWide = 350; - int startx = 50; - - int y = 0; - int size = 8; - int sizewithgap = size + 1; - - for ( int a = 0; a < m_AsyncCaptions.Count(); ++a ) - { - int x = startx; - - int c = m_AsyncCaptions[ a ].m_Header.numblocks; - for ( int i = 0 ; i < c; ++i ) - { - bool entry, pending, loaded; - g_AsyncCaptionResourceManager.GetBlockInfo( a, i, entry, pending, loaded ); - - if ( !entry ) - { - vgui::surface()->DrawSetColor( Color( 0, 0, 0, 127 ) ); - } - else if ( pending ) - { - vgui::surface()->DrawSetColor( Color( 0, 0, 255, 127 ) ); - } - else if ( loaded ) - { - vgui::surface()->DrawSetColor( Color( 0, 255, 0, 127 ) ); - } - else - { - vgui::surface()->DrawSetColor( Color( 255, 255, 0, 127 ) ); - } - - vgui::surface()->DrawFilledRect( x, y, x + size, y + size ); - x += sizewithgap; - if ( x >= startx + blockWide ) - { - x = startx; - y += sizewithgap; - } - } - - y += sizewithgap; - } - } - - wrect_t rcOutput; - rcOutput.left = 0; - rcOutput.right = w; - rcOutput.bottom = h; - rcOutput.top = m_nTopOffset; - - wrect_t rcText = rcOutput; - - int avail_width = rcText.right - rcText.left - 2 * CC_INSET; - int avail_height = rcText.bottom - rcText.top - 2 * CC_INSET; - - int totalheight = 0; - int i; - CUtlVector< VisibleStreamItem > visibleitems; - int c = m_Items.Count(); - int maxwidth = 0; - - for ( i = 0; i < c; i++ ) - { - CCloseCaptionItem *item = m_Items[ i ]; - - // Not ready for display yet. - if ( item->GetPreDisplayTime() > 0.0f ) - { - continue; - } - - if ( !item->GetSizeComputed() ) - { - ComputeStreamWork( avail_width, item ); - } - - int itemwidth = item->GetWidth(); - int itemheight = item->GetHeight(); - - totalheight += itemheight; - if ( itemwidth > maxwidth ) - { - maxwidth = itemwidth; - } - - VisibleStreamItem si; - si.height = itemheight; - si.width = itemwidth; - si.item = item; - - visibleitems.AddToTail( si ); - - // Start popping really old items off the stack if we run out of space - while ( itemheight <= avail_height && - totalheight > avail_height && - visibleitems.Count() > cc_minvisibleitems.GetInt() ) - { - VisibleStreamItem & pop = visibleitems[ 0 ]; - totalheight -= pop.height; - - // And make it die right away... - pop.item->SetTimeToLive( 0.0f ); - - visibleitems.Remove( 0 ); - } - } - - float desiredAlpha = visibleitems.Count() >= 1 ? 1.0f : 0.0f; - - // Always return at least one line height for drawing the surrounding box - totalheight = MAX( totalheight, m_nLineHeight ); - - // Trigger box growing - if ( totalheight != m_nGoalHeight ) - { - m_nGoalHeight = totalheight; - m_flGoalHeightStartTime = gpGlobals->curtime; - m_flGoalHeightFinishTime = gpGlobals->curtime + m_flGrowTime; - } - if ( desiredAlpha != m_flGoalAlpha ) - { - m_flGoalAlpha = desiredAlpha; - m_flGoalHeightStartTime = gpGlobals->curtime; - m_flGoalHeightFinishTime = gpGlobals->curtime + m_flGrowTime; - } - - // If shrunk to zero and faded out, nothing left to do - if ( !visibleitems.Count() && - m_nGoalHeight == m_nCurrentHeight && - m_flGoalAlpha == m_flCurrentAlpha ) - { - m_flGoalHeightStartTime = 0; - m_flGoalHeightFinishTime = 0; - return; - } - - bool growingDown = false; - - // Continue growth? - if ( m_flGoalHeightFinishTime && - m_flGoalHeightStartTime && - m_flGoalHeightFinishTime > m_flGoalHeightStartTime ) - { - float togo = m_nGoalHeight - m_nCurrentHeight; - float alphatogo = m_flGoalAlpha - m_flCurrentAlpha; - - growingDown = togo < 0.0f ? true : false; - - float dt = m_flGoalHeightFinishTime - m_flGoalHeightStartTime; - float frac = ( gpGlobals->curtime - m_flGoalHeightStartTime ) / dt; - frac = clamp( frac, 0.0f, 1.0f ); - int newHeight = m_nCurrentHeight + (int)( frac * togo ); - m_nCurrentHeight = newHeight; - float newAlpha = m_flCurrentAlpha + frac * alphatogo; - m_flCurrentAlpha = clamp( newAlpha, 0.0f, 1.0f ); - } - else - { - m_nCurrentHeight = m_nGoalHeight; - m_flCurrentAlpha = m_flGoalAlpha; - } - - rcText.top = rcText.bottom - m_nCurrentHeight - 2 * CC_INSET; - - Color bgColor = GetBgColor(); - bgColor[3] = m_flBackgroundAlpha; - DrawBox( rcText.left, MAX(rcText.top,0), rcText.right - rcText.left, rcText.bottom - MAX(rcText.top,0), bgColor, m_flCurrentAlpha ); - - if ( !visibleitems.Count() ) - { - return; - } - - rcText.left += CC_INSET; - rcText.right -= CC_INSET; - - int textHeight = m_nCurrentHeight; - if ( growingDown ) - { - // If growing downward, keep the text locked to the bottom of the window instead of anchored to the top - textHeight = totalheight; - } - - rcText.top = rcText.bottom - textHeight - CC_INSET; - - // Now draw them - c = visibleitems.Count(); - for ( i = 0; i < c; i++ ) - { - VisibleStreamItem *si = &visibleitems[ i ]; - - // If the oldest/top item was created with additional time, we can remove that now - if ( i == 0 ) - { - if ( si->item->GetAddedTime() > 0.0f ) - { - float ttl = si->item->GetTimeToLive(); - ttl -= si->item->GetAddedTime(); - ttl = MAX( 0.0f, ttl ); - si->item->SetTimeToLive( ttl ); - si->item->SetAddedTime( 0.0f ); - } - } - - int height = si->height; - CCloseCaptionItem *item = si->item; - - int iFadeLine = -1; - float flFadeLineAlpha = 1.0; - - // If the height is greater than the total height of the element, - // we need to slowly pan over this item. - if ( height > avail_height ) - { - // Figure out how many lines we'll need to move to see the whole caption - int units = item->GetNumWorkUnits(); - int iTotalMove = 0; - for ( int j = 0 ; j < units; j++ ) - { - CCloseCaptionWorkUnit *wu = item->GetWorkUnit( j ); - iTotalMove += wu->GetHeight(); - if ( iTotalMove >= (height - avail_height) ) - { - units = j+1; - break; - } - } - - // Figure out the delta between each point where we move the line - float flMoveDelta = item->GetInitialLifeSpan() / (float)units; - float flCurMove = item->GetInitialLifeSpan() - item->GetTimeToLive(); - int iHeightToMove = 0; - - int iLinesToMove = clamp( Floor2Int( flCurMove / flMoveDelta ), 0, units ); - if ( iLinesToMove ) - { - int iCurrentLineHeight = 0; - for ( int j = 0 ; j < iLinesToMove; j++ ) - { - iHeightToMove = iCurrentLineHeight; - - CCloseCaptionWorkUnit *wu = item->GetWorkUnit( j ); - iCurrentLineHeight += wu->GetHeight(); - } - - // Slide to the desired distance, once the fade is done - float flTimePostMove = flCurMove - (flMoveDelta * iLinesToMove); - if ( flTimePostMove < CAPTION_PAN_FADE_TIME ) - { - iFadeLine = iLinesToMove-1; - - // It's time to fade out the top line. If it hasn't started fading yet, start it. - CCloseCaptionWorkUnit *wu = item->GetWorkUnit(iFadeLine); - if ( wu->GetFadeStart() == 0 ) - { - wu->SetFadeStart( gpGlobals->curtime ); - } - - // Fade out quickly - float flFadeTime = (gpGlobals->curtime - wu->GetFadeStart()) / CAPTION_PAN_FADE_TIME; - flFadeLineAlpha = clamp( 1.0f - flFadeTime, 0.f, 1.f ); - } - else if ( flTimePostMove < (CAPTION_PAN_FADE_TIME+CAPTION_PAN_SLIDE_TIME) ) - { - flTimePostMove -= CAPTION_PAN_FADE_TIME; - float flSlideTime = clamp( flTimePostMove / 0.25f, 0.f, 1.f ); - iHeightToMove += ceil((iCurrentLineHeight - iHeightToMove) * flSlideTime); - } - else - { - iHeightToMove = iCurrentLineHeight; - } - } - - // Minor adjustment to center the caption text within the window. - rcText.top = -iHeightToMove + 2; - } - - rcText.bottom = rcText.top + height; - - wrect_t rcOut = rcText; - - rcOut.right = rcOut.left + si->width + 6; - - DrawStream( rcOut, rcOutput, item, iFadeLine, flFadeLineAlpha ); - - rcText.top += height; - rcText.bottom += height; - - if ( rcText.top >= rcOutput.bottom ) - break; - } -} - -void CHudCloseCaption::OnTick( void ) -{ - // See if any async work has completed - ProcessAsyncWork(); - - - float dt = gpGlobals->frametime; - - int c = m_Items.Count(); - int i; - - if ( m_bVisibleDueToDirect ) - { - SetVisible( true ); - if ( !c ) - { - // Don't clear our force visible if we're waiting for the caption to load - if ( m_AsyncWork.Count() == 0 ) - { - m_bVisibleDueToDirect = false; - } - } - } - else - { - SetVisible( closecaption.GetBool() ); - } - - // Pass one decay all timers - for ( i = 0 ; i < c ; ++i ) - { - CCloseCaptionItem *item = m_Items[ i ]; - - float predisplay = item->GetPreDisplayTime(); - if ( predisplay > 0.0f ) - { - predisplay -= dt; - predisplay = MAX( 0.0f, predisplay ); - item->SetPreDisplayTime( predisplay ); - } - else - { - // remove time from actual playback - float ttl = item->GetTimeToLive(); - ttl -= dt; - ttl = MAX( 0.0f, ttl ); - item->SetTimeToLive( ttl ); - } - } - - // Pass two, remove from head until we get to first item with time remaining - bool foundfirstnondeletion = false; - for ( i = 0 ; i < c ; ++i ) - { - CCloseCaptionItem *item = m_Items[ i ]; - - // Skip items not yet showing... - float predisplay = item->GetPreDisplayTime(); - if ( predisplay > 0.0f ) - { - continue; - } - - float ttl = item->GetTimeToLive(); - if ( ttl > 0.0f ) - { - foundfirstnondeletion = true; - continue; - } - - // Skip the remainder of the items after we find the first/oldest active item - if ( foundfirstnondeletion ) - { - continue; - } - - delete item; - m_Items.Remove( i ); - --i; - --c; - } -} - -void CHudCloseCaption::Reset( void ) -{ - while ( m_Items.Count() > 0 ) - { - CCloseCaptionItem *i = m_Items[ 0 ]; - delete i; - m_Items.Remove( 0 ); - } - - ClearAsyncWork(); - Unlock(); -} - -bool CHudCloseCaption::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ) const -{ - const wchar_t *in = *ppIn; - const wchar_t *oldin = in; - - if ( in[0] != L'<' ) - { - *ppIn += ( oldin - in ); - return false; - } - - args[ 0 ] = 0; - cmd[ 0 ]= 0; - wchar_t *out = cmd; - in++; - while ( *in != L'\0' && *in != L':' && *in != L'>' && !isspace( *in ) ) - { - *out++ = *in++; - } - *out = L'\0'; - - if ( *in != L':' ) - { - *ppIn += ( in - oldin ); - return true; - } - - in++; - out = args; - while ( *in != L'\0' && *in != L'>' ) - { - *out++ = *in++; - } - *out = L'\0'; - - //if ( *in == L'>' ) - // in++; - - *ppIn += ( in - oldin ); - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *stream - -// *findcmd - -// value - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CHudCloseCaption::GetFloatCommandValue( const wchar_t *stream, const wchar_t *findcmd, float& value ) const -{ - const wchar_t *curpos = stream; - - for ( ; curpos && *curpos != L'\0'; ++curpos ) - { - wchar_t cmd[ 256 ]; - wchar_t args[ 256 ]; - - if ( SplitCommand( &curpos, cmd, args ) ) - { - if ( !wcscmp( cmd, findcmd ) ) - { - value = (float)wcstod( args, NULL ); - return true; - } - continue; - } - } - - return false; -} - - -bool CHudCloseCaption::StreamHasCommand( const wchar_t *stream, const wchar_t *findcmd ) const -{ - const wchar_t *curpos = stream; - - for ( ; curpos && *curpos != L'\0'; ++curpos ) - { - wchar_t cmd[ 256 ]; - wchar_t args[ 256 ]; - - if ( SplitCommand( &curpos, cmd, args ) ) - { - if ( !wcscmp( cmd, findcmd ) ) - { - return true; - } - continue; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -// Purpose: It's blank or only comprised of whitespace/space characters... -// Input : *stream - -// Output : static bool -//----------------------------------------------------------------------------- -static bool IsAllSpaces( const wchar_t *stream ) -{ - const wchar_t *p = stream; - while ( *p != L'\0' ) - { - if ( !iswspace( *p ) ) - return false; - - p++; - } - - return true; -} - -bool CHudCloseCaption::StreamHasCommand( const wchar_t *stream, const wchar_t *search ) -{ - for ( const wchar_t *curpos = stream; curpos && *curpos != L'\0'; ++curpos ) - { - wchar_t cmd[ 256 ]; - wchar_t args[ 256 ]; - - if ( SplitCommand( &curpos, cmd, args ) ) - { - if ( !wcscmp( cmd, search ) ) - { - return true; - } - } - } - return false; -} - -void CHudCloseCaption::Process( const wchar_t *stream, float duration, const char *tokenstream, bool fromplayer, bool direct ) -{ - if ( !direct ) - { - if ( !closecaption.GetBool() ) - { - Reset(); - return; - } - - // If we're locked, ignore all closecaption commands - if ( m_bLocked ) - return; - } - - // Nothing to do... - if ( IsAllSpaces( stream) ) - { - return; - } - - // If subtitling, don't show sfx captions at all - if ( cc_subtitles.GetBool() && StreamHasCommand( stream, L"sfx" ) ) - { - return; - } - - bool valid = true; - if ( !wcsncmp( stream, L"!!!", wcslen( L"!!!" ) ) ) - { - // It's in the text file, but hasn't been translated... - valid = false; - } - - if ( !wcsncmp( stream, L"-->", wcslen( L"-->" ) ) ) - { - // It's in the text file, but hasn't been translated... - valid = false; - - if ( cc_captiontrace.GetInt() < 2 ) - { - if ( cc_captiontrace.GetInt() == 1 ) - { - Msg( "Missing caption for '%s'\n", tokenstream ); - } - - return; - } - } - - float lifespan = duration + cc_linger_time.GetFloat(); - - float addedlife = 0.0f; - - if ( m_Items.Count() > 0 ) - { - // Get the remaining life span of the last item - CCloseCaptionItem *final = m_Items[ m_Items.Count() - 1 ]; - float prevlife = final->GetTimeToLive(); - - if ( prevlife > lifespan ) - { - addedlife = prevlife - lifespan; - } - - lifespan = MAX( lifespan, prevlife ); - } - - float delay = 0.0f; - float override_duration = 0.0f; - - wchar_t phrase[ MAX_CAPTION_CHARACTERS ]; - wchar_t *out = phrase; - - for ( const wchar_t *curpos = stream; curpos && *curpos != L'\0'; ++curpos ) - { - wchar_t cmd[ 256 ]; - wchar_t args[ 256 ]; - - const wchar_t *prevpos = curpos; - - if ( SplitCommand( &curpos, cmd, args ) ) - { - if ( !wcscmp( cmd, L"delay" ) ) - { - - // End current phrase - *out = L'\0'; - - if ( wcslen( phrase ) > 0 ) - { - CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer ); - m_Items.AddToTail( item ); - if ( StreamHasCommand( phrase, L"sfx" ) ) - { - // SFX show up instantly. - item->SetPreDisplayTime( 0.0f ); - } - - if ( GetFloatCommandValue( phrase, L"len", override_duration ) ) - { - item->SetTimeToLive( override_duration ); - } - } - - // Start new phrase - out = phrase; - - // Delay must be positive - delay = MAX( 0.0f, (float)wcstod( args, NULL ) ); - - continue; - } - - int copychars = curpos - prevpos; - while ( --copychars >= 0 ) - { - *out++ = *prevpos++; - } - } - - *out++ = *curpos; - } - - // End final phrase, if any - *out = L'\0'; - if ( wcslen( phrase ) > 0 ) - { - CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer ); - m_Items.AddToTail( item ); - - if ( StreamHasCommand( phrase, L"sfx" ) ) - { - // SFX show up instantly. - item->SetPreDisplayTime( 0.0f ); - } - - if ( GetFloatCommandValue( phrase, L"len", override_duration ) ) - { - item->SetTimeToLive( override_duration ); - item->SetInitialLifeSpan( override_duration ); - } - } -} - -void CHudCloseCaption::CreateFonts( void ) -{ - vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); - - m_hFonts[CCFONT_NORMAL] = pScheme->GetFont( "CloseCaption_Normal" ); - - if ( IsPC() ) - { - m_hFonts[CCFONT_BOLD] = pScheme->GetFont( "CloseCaption_Bold" ); - m_hFonts[CCFONT_ITALIC] = pScheme->GetFont( "CloseCaption_Italic" ); - m_hFonts[CCFONT_ITALICBOLD] = pScheme->GetFont( "CloseCaption_BoldItalic" ); - } - else - { - m_hFonts[CCFONT_SMALL] = pScheme->GetFont( "CloseCaption_Small" ); - } - - m_nLineHeight = MAX( 6, vgui::surface()->GetFontTall( m_hFonts[ CCFONT_NORMAL ] ) ); -} - -struct WorkUnitParams -{ - WorkUnitParams() - { - Q_memset( stream, 0, sizeof( stream ) ); - out = stream; - x = 0; - y = 0; - width = 0; - bold = italic = false; - clr = Color( 255, 255, 255, 255 ); - newline = false; - font = 0; - } - - ~WorkUnitParams() - { - } - - void Finalize( int lineheight ) - { - *out = L'\0'; - } - - void Next( int lineheight ) - { - // Restart output - Q_memset( stream, 0, sizeof( stream ) ); - out = stream; - - x += width; - - width = 0; - // Leave bold, italic and color alone!!! - if ( newline ) - { - newline = false; - x = 0; - y += lineheight; - } - } - - int GetFontNumber() - { - return CHudCloseCaption::GetFontNumber( bold, italic ); - } - - wchar_t stream[ MAX_CAPTION_CHARACTERS ]; - wchar_t *out; - - int x; - int y; - int width; - bool bold; - bool italic; - Color clr; - bool newline; - vgui::HFont font; -}; - -void CHudCloseCaption::AddWorkUnit( CCloseCaptionItem *item, - WorkUnitParams& params ) -{ - params.Finalize( vgui::surface()->GetFontTall( params.font ) ); - -#ifdef WIN32 - if ( wcslen( params.stream ) > 0 ) -#else - // params.stream is still in ucs2 format here so just do a basic zero compare for length or just space - if ( ((uint16 *)params.stream)[0] != 0 && ((uint16 *)params.stream)[0] != 32 ) -#endif - { - CCloseCaptionWorkUnit *wu = new CCloseCaptionWorkUnit(); - - wu->SetStream( params.stream ); - wu->SetColor( params.clr ); - wu->SetBold( params.bold ); - wu->SetItalic( params.italic ); - wu->SetWidth( params.width ); - wu->SetHeight( vgui::surface()->GetFontTall( params.font ) ); - wu->SetPos( params.x, params.y ); - wu->SetFont( params.font ); - wu->SetFadeStart( 0 ); - - int curheight = item->GetHeight(); - int curwidth = item->GetWidth(); - - curheight = MAX( curheight, params.y + wu->GetHeight() ); - curwidth = MAX( curwidth, params.x + params.width ); - - item->SetHeight( curheight ); - item->SetWidth( curwidth ); - - // Add it - item->AddWork( wu ); - - params.Next( vgui::surface()->GetFontTall( params.font ) ); - } -} - -void CHudCloseCaption::ComputeStreamWork( int available_width, CCloseCaptionItem *item ) -{ - // Start with a clean param block - WorkUnitParams params; - - const wchar_t *curpos = item->GetStream(); - int streamlen = wcslen( curpos ); - CUtlVector< Color > colorStack; - - const wchar_t *most_recent_space = NULL; - int most_recent_space_w = -1; - - for ( ; curpos && *curpos != L'\0'; ++curpos ) - { - wchar_t cmd[ 256 ]; - wchar_t args[ 256 ]; - - if ( SplitCommand( &curpos, cmd, args ) ) - { - if ( !wcscmp( cmd, L"cr" ) ) - { - params.newline = true; - AddWorkUnit( item, params); - } - else if ( !wcscmp( cmd, L"clr" ) ) - { - AddWorkUnit( item, params ); - - if ( args[0] == 0 && colorStack.Count()>= 2) - { - colorStack.Remove( colorStack.Count() - 1 ); - params.clr = colorStack[ colorStack.Count() - 1 ]; - } - else - { - int r = 0, g = 0, b = 0; - Color newcolor; - if ( 3 == swscanf( args, L"%i,%i,%i", &r, &g, &b ) ) - { - newcolor = Color( r, g, b, 255 ); - colorStack.AddToTail( newcolor ); - params.clr = colorStack[ colorStack.Count() - 1 ]; - } - } - } - else if ( !wcscmp( cmd, L"playerclr" ) ) - { - AddWorkUnit( item, params ); - - if ( args[0] == 0 && colorStack.Count()>= 2) - { - colorStack.Remove( colorStack.Count() - 1 ); - params.clr = colorStack[ colorStack.Count() - 1 ]; - } - else - { - // player and npc color selector - // e.g.,. 255,255,255:200,200,200 - int pr = 0, pg = 0, pb = 0, nr = 0, ng = 0, nb = 0; - Color newcolor; - if ( 6 == swscanf( args, L"%i,%i,%i:%i,%i,%i", &pr, &pg, &pb, &nr, &ng, &nb ) ) - { - newcolor = item->IsFromPlayer() ? Color( pr, pg, pb, 255 ) : Color( nr, ng, nb, 255 ); - colorStack.AddToTail( newcolor ); - params.clr = colorStack[ colorStack.Count() - 1 ]; - } - } - } - else if ( !wcscmp( cmd, L"I" ) ) - { - AddWorkUnit( item, params ); - params.italic = !params.italic; - } - else if ( !wcscmp( cmd, L"B" ) ) - { - AddWorkUnit( item, params ); - params.bold = !params.bold; - } - - continue; - } - - int font; - if ( IsPC() ) - { - font = params.GetFontNumber(); - } - else - { - font = streamlen >= cc_smallfontlength.GetInt() ? CCFONT_SMALL : CCFONT_NORMAL; - } - vgui::HFont useF = m_hFonts[font]; - params.font = useF; - - int w, h; - - wchar_t sz[2]; - sz[ 0 ] = *curpos; - sz[ 1 ] = L'\0'; - vgui::surface()->GetTextSize( useF, sz, w, h ); - - if ( ( params.x + params.width ) + w > available_width ) - { - if ( most_recent_space && curpos >= most_recent_space + 1 ) - { - // Roll back to previous space character if there is one... - int goback = curpos - most_recent_space - 1; - params.out -= ( goback + 1 ); - params.width = most_recent_space_w; - - wchar_t *extra = new wchar_t[ goback + 1 ]; - wcsncpy( extra, most_recent_space + 1, goback ); - extra[ goback ] = L'\0'; - - params.newline = true; - AddWorkUnit( item, params ); - - wcsncpy( params.out, extra, goback ); - params.out += goback; - int textw, texth; - vgui::surface()->GetTextSize( useF, extra, textw, texth ); - - params.width = textw; - - delete[] extra; - - most_recent_space = NULL; - most_recent_space_w = -1; - } - else - { - params.newline = true; - AddWorkUnit( item, params ); - } - } - *params.out++ = *curpos; - params.width += w; - - if ( iswspace( *curpos ) ) - { - most_recent_space = curpos; - most_recent_space_w = params.width; - } - } - - // Add the final unit. - params.newline = true; - AddWorkUnit( item, params ); - - item->SetSizeComputed( true ); - - // DumpWork( item ); -} - -void CHudCloseCaption:: DumpWork( CCloseCaptionItem *item ) -{ - int c = item->GetNumWorkUnits(); - for ( int i = 0 ; i < c; ++i ) - { - CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i ); - wu->Dump(); - } -} - -void CHudCloseCaption::DrawStream( wrect_t &rcText, wrect_t &rcWindow, CCloseCaptionItem *item, int iFadeLine, float flFadeLineAlpha ) -{ - int c = item->GetNumWorkUnits(); - - wrect_t rcOut; - - float alpha = item->GetAlpha( m_flItemHiddenTime, m_flItemFadeInTime, m_flItemFadeOutTime ); - - for ( int i = 0 ; i < c; ++i ) - { - int x = 0; - int y = 0; - - CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i ); - - vgui::HFont useF = wu->GetFont(); - - wu->GetPos( x, y ); - - rcOut.left = rcText.left + x + 3; - rcOut.right = rcOut.left + wu->GetWidth(); - rcOut.top = rcText.top + y; - rcOut.bottom = rcOut.top + wu->GetHeight(); - - // Adjust alpha to handle fade in/out at the top & bottom of the element. - // Used for single commentary entries that are too big to fit into the element. - float flLineAlpha = alpha; - if ( i == iFadeLine ) - { - flLineAlpha *= flFadeLineAlpha; - } - else if ( rcOut.top < rcWindow.top ) - { - // We're off the top of the element, so don't draw - continue; - } - else if ( rcOut.top > rcWindow.bottom ) - { - float flFadeHeight = (float)wu->GetHeight() * 0.25; - float flDist = (float)(rcOut.top - rcWindow.bottom) / flFadeHeight; - flDist = Bias( flDist, 0.2 ); - if ( flDist > 1 ) - continue; - - flLineAlpha *= 1.0 - flDist; - } - - Color useColor = wu->GetColor(); - - useColor[ 3 ] *= flLineAlpha; - - if ( !item->IsValid() ) - { - useColor = Color( 255, 255, 255, 255 * flLineAlpha ); - rcOut.right += 2; - vgui::surface()->DrawSetColor( Color( 100, 100, 40, 255 * flLineAlpha ) ); - vgui::surface()->DrawFilledRect( rcOut.left, rcOut.top, rcOut.right, rcOut.bottom ); - } - - vgui::surface()->DrawSetTextFont( useF ); - vgui::surface()->DrawSetTextPos( rcOut.left, rcOut.top ); - vgui::surface()->DrawSetTextColor( useColor ); - vgui::surface()->DrawPrintText( wu->GetStream(), wcslen( wu->GetStream() ) ); - } -} - -bool CHudCloseCaption::GetNoRepeatValue( const wchar_t *caption, float &retval ) -{ - retval = 0.0f; - const wchar_t *curpos = caption; - - for ( ; curpos && *curpos != L'\0'; ++curpos ) - { - wchar_t cmd[ 256 ]; - wchar_t args[ 256 ]; - - if ( SplitCommand( &curpos, cmd, args ) ) - { - if ( !wcscmp( cmd, L"norepeat" ) ) - { - retval = (float)wcstod( args, NULL ); - return true; - } - continue; - } - } - return false; -} - -bool CHudCloseCaption::CaptionTokenLessFunc( const CaptionRepeat &lhs, const CaptionRepeat &rhs ) -{ - return ( lhs.m_nTokenIndex < rhs.m_nTokenIndex ); -} - -static bool CaptionTrace( const char *token ) -{ - static CUtlSymbolTable s_MissingCloseCaptions; - - // Make sure we only show the message once - if ( UTL_INVAL_SYMBOL == s_MissingCloseCaptions.Find( token ) ) - { - s_MissingCloseCaptions.AddString( token ); - return true; - } - - return false; -} - -static ConVar cc_sentencecaptionnorepeat( "cc_sentencecaptionnorepeat", "4", 0, "How often a sentence can repeat." ); - -int CRCString( const char *str ) -{ - int len = Q_strlen( str ); - CRC32_t crc; - CRC32_Init( &crc ); - CRC32_ProcessBuffer( &crc, str, len ); - CRC32_Final( &crc ); - - return ( int )crc; -} - -class CAsyncCaption -{ -public: - CAsyncCaption() : - m_flDuration( 0.0f ), - m_bIsStream( false ), - m_bFromPlayer( false ) - { - } - - ~CAsyncCaption() - { - int c = m_Tokens.Count(); - for ( int i = 0; i < c; ++i ) - { - delete m_Tokens[ i ]; - } - m_Tokens.Purge(); - } - - void StartRequesting( CHudCloseCaption *hudCloseCaption, CUtlVector< AsyncCaption_t >& directories ) - { - // Issue pending async requests for each token in string - int c = m_Tokens.Count(); - for ( int i = 0; i < c; ++i ) - { - caption_t *caption = m_Tokens[ i ]; - Assert( !caption->stream ); - Assert( caption->dirindex >= 0 ); - - CaptionLookup_t& entry = directories[ caption->fileindex ].m_CaptionDirectory[ caption->dirindex ]; - - // Request this block, and if it's there, it'll call OnDataLoaded immediately - g_AsyncCaptionResourceManager.PollForAsyncLoading( hudCloseCaption, caption->fileindex, entry.blockNum ); - } - } - - void OnDataArrived( CUtlVector< AsyncCaption_t >& directories, int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData ) - { - int c = m_Tokens.Count(); - for ( int i = 0; i < c; ++i ) - { - caption_t *caption = m_Tokens[ i ]; - if ( caption->stream != NULL ) - continue; - - // Lookup the data - CaptionLookup_t &entry = directories[ nFileIndex ].m_CaptionDirectory[ caption->dirindex ]; - if ( entry.blockNum != nBlockNum ) - continue; - -#ifdef WIN32 - const wchar_t *pIn = ( const wchar_t *)&pData->m_pBlockData[ entry.offset ]; - caption->stream = new wchar_t[ entry.length >> 1 ]; - memcpy( (void *)caption->stream, pIn, entry.length ); -#else - // we persist to disk as ucs2 so convert back to real unicode here - caption->stream = new wchar_t[ entry.length ]; - V_UCS2ToUnicode( (ucs2 *)&pData->m_pBlockData[ entry.offset ], caption->stream, entry.length*sizeof(wchar_t) ); -#endif - } - } - - void ProcessAsyncWork( CHudCloseCaption *hudCloseCaption, CUtlVector< AsyncCaption_t >& directories ) - { - int c = m_Tokens.Count(); - for ( int i = 0; i < c; ++i ) - { - caption_t *caption = m_Tokens[ i ]; - if ( caption->stream != NULL ) - continue; - - CaptionLookup_t& entry = directories[ caption->fileindex].m_CaptionDirectory[ caption->dirindex ]; - - // Request this block, and if it's there, it'll call OnDataLoaded immediately - g_AsyncCaptionResourceManager.PollForAsyncLoading( hudCloseCaption, caption->fileindex, entry.blockNum ); - } - } - - bool GetStream( OUT_Z_BYTECAP(bufSizeInBytes) wchar_t *buf, int bufSizeInBytes ) - { - Assert( bufSizeInBytes >= sizeof(buf[0]) ); - buf[ 0 ] = L'\0'; - - int c = m_Tokens.Count(); - for ( int i = 0; i < c; ++i ) - { - caption_t *caption = m_Tokens[ i ]; - if ( caption->stream == NULL ) - { - return false; - } - } - - unsigned int curlen = 0; - unsigned int maxlen = bufSizeInBytes / sizeof( wchar_t ); - - // Compose full stream from tokens - for ( int i = 0; i < c; ++i ) - { - caption_t *caption = m_Tokens[ i ]; - int len = wcslen( caption->stream ) + 1; - if ( curlen + len >= maxlen ) - break; - - wcscat( buf, caption->stream ); - if ( i < c - 1 ) - { - wcscat( buf, L" " ); - } - - curlen += len; - } - - return true; - } - - bool IsStream() const - { - return m_bIsStream; - } - - void SetIsStream( bool state ) - { - m_bIsStream = state; - } - - void AddRandomToken( CUtlVector< AsyncCaption_t >& directories ) - { - int dc = directories.Count(); - int fileindex = RandomInt( 0, dc - 1 ); - - int c = directories[ fileindex ].m_CaptionDirectory.Count(); - int idx = RandomInt( 0, c - 1 ); - - caption_t *caption = new caption_t; - char foo[ 32 ]; - Q_snprintf( foo, sizeof( foo ), "%d", idx ); - caption->token = strdup( foo ); - caption->dirindex = idx; - caption->stream = NULL; - caption->fileindex = fileindex; - - m_Tokens.AddToTail( caption ); - } - - bool AddToken - ( - CUtlVector< AsyncCaption_t >& directories, - const char *token - ) - { - CaptionLookup_t search; - search.SetHash( token ); - - int idx = -1; - int i; - int dc = directories.Count(); - for ( i = 0; i < dc; ++i ) - { - idx = directories[ i ].m_CaptionDirectory.Find( search ); - if ( idx == directories[ i ].m_CaptionDirectory.InvalidIndex() ) - continue; - - break; - } - - if ( i >= dc || idx == -1 ) - return false; - - caption_t *caption = new caption_t; - caption->token = strdup( token ); - caption->dirindex = idx; - caption->stream = NULL; - caption->fileindex = i; - - m_Tokens.AddToTail( caption ); - return true; - } - - int Count() const - { - return m_Tokens.Count(); - } - - const char *GetToken( int index ) - { - return m_Tokens[ index ]->token; - } - - void GetOriginalStream( char *buf, size_t bufsize ) - { - buf[ 0 ] = 0; - int c = Count(); - for ( int i = 0 ; i < c; ++i ) - { - Q_strncat( buf, GetToken( i ), bufsize, COPY_ALL_CHARACTERS ); - if ( i != c - 1 ) - { - Q_strncat( buf, " ", bufsize, COPY_ALL_CHARACTERS ); - } - } - } - - void SetDuration( float t ) - { - m_flDuration = t; - } - - float GetDuration() - { - return m_flDuration; - } - - bool IsFromPlayer() - { - return m_bFromPlayer; - } - - void SetFromPlayer( bool state ) - { - m_bFromPlayer = state; - } - - bool IsDirect() - { - return m_bDirect; - } - - void SetDirect( bool state ) - { - m_bDirect = state; - } -private: - float m_flDuration; - bool m_bIsStream : 1; - bool m_bFromPlayer : 1; - bool m_bDirect : 1; - - struct caption_t - { - caption_t() : - token( 0 ), - dirindex( -1 ), - fileindex( -1 ), - stream( 0 ) - { - } - - ~caption_t() - { - free( token ); - delete[] stream; - } - - void SetStream( const wchar_t *in ) - { - delete[] stream; - stream = 0; - if ( !in ) - return; - - int len = wcslen( in ); - stream = new wchar_t[ len + 1 ]; - wcsncpy( stream, in, len + 1 ); - } - - char *token; - int dirindex; - int fileindex; - wchar_t *stream; - }; - - CUtlVector< caption_t * > m_Tokens; -}; - -void CHudCloseCaption::ProcessAsyncWork() -{ - int i; - for( i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); i = m_AsyncWork.Next( i ) ) - { - // check for data arrival - CAsyncCaption *item = m_AsyncWork[ i ]; - item->ProcessAsyncWork( this, m_AsyncCaptions ); - } - // Now operate on any new data which arrived - for( i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); ) - { - int n = m_AsyncWork.Next( i ); - - CAsyncCaption *item = m_AsyncWork[ i ]; - wchar_t stream[ MAX_CAPTION_CHARACTERS ]; - - // If we get to the first item with pending async work, stop processing - if ( !item->GetStream( stream, sizeof( stream ) ) ) - { - break; - } - - if ( stream[ 0 ] != L'\0' ) - { - char original[ 512 ]; - item->GetOriginalStream( original, sizeof( original ) ); - - // Process it now - if ( item->IsStream() ) - { - _ProcessSentenceCaptionStream( item->Count(), original, stream ); - } - else - { - _ProcessCaption( stream, original, item->GetDuration(), item->IsFromPlayer(), item->IsDirect() ); - } - } - - m_AsyncWork.Remove( i ); - delete item; - - i = n; - } -} - -void CHudCloseCaption::ClearAsyncWork() -{ - for ( int i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); i = m_AsyncWork.Next( i ) ) - { - CAsyncCaption *item = m_AsyncWork[ i ]; - delete item; - } - m_AsyncWork.Purge(); -} - -extern void Hack_FixEscapeChars( char *str ); - -void CHudCloseCaption::ProcessCaptionDirect( const char *tokenname, float duration, bool fromplayer /* = false */ ) -{ - m_bVisibleDueToDirect = true; - - char token[ 512 ]; - Q_strncpy( token, tokenname, sizeof( token ) ); - if ( Q_strstr( token, "\\" ) ) - { - Hack_FixEscapeChars( token ); - } - - ProcessCaption( token, duration, fromplayer, true ); -} - -void CHudCloseCaption::PlayRandomCaption() -{ - CAsyncCaption *async = new CAsyncCaption; - async->SetIsStream( false ); - async->AddRandomToken( m_AsyncCaptions ); - async->SetDuration( RandomFloat( 1.0f, 3.0f ) ); - async->SetFromPlayer( RandomInt( 0, 1 ) == 0 ? true : false ); - async->StartRequesting( this, m_AsyncCaptions ); - m_AsyncWork.AddToTail( async ); -} - -bool CHudCloseCaption::AddAsyncWork( const char *tokenstream, bool bIsStream, float duration, bool fromplayer, bool direct /* = false */ ) -{ - bool bret = true; - - CAsyncCaption *async = new CAsyncCaption(); - async->SetIsStream( bIsStream ); - async->SetDirect( direct ); - if ( !bIsStream ) - { - bret = async->AddToken - ( - m_AsyncCaptions, - tokenstream - ); - } - else - { - // The first token from the stream is the name of the sentence - char tokenname[ 512 ]; - tokenname[ 0 ] = 0; - const char *p = tokenstream; - p = nexttoken( tokenname, p, ' ' ); - // p points to reset of sentence tokens, build up a unicode string from them... - while ( p && Q_strlen( tokenname ) > 0 ) - { - p = nexttoken( tokenname, p, ' ' ); - - if ( Q_strlen( tokenname ) == 0 ) - break; - - async->AddToken - ( - m_AsyncCaptions, - tokenname - ); - } - } - - m_AsyncWork.AddToTail( async ); - - async->SetDuration( duration ); - async->SetFromPlayer( fromplayer ); - // Do this last as the block might be resident already and this will finish immediately... - async->StartRequesting( this, m_AsyncCaptions ); - return bret; -} - - -void CHudCloseCaption::ProcessSentenceCaptionStream( const char *tokenstream ) -{ - float interval = cc_sentencecaptionnorepeat.GetFloat(); - interval = clamp( interval, 0.1f, 60.0f ); - - // The first token from the stream is the name of the sentence - char tokenname[ 512 ]; - - tokenname[ 0 ] = 0; - - const char *p = tokenstream; - - p = nexttoken( tokenname, p, ' ' ); - - if ( Q_strlen( tokenname ) > 0 ) - { - // Use it to check for "norepeat" rules - CaptionRepeat entry; - entry.m_nTokenIndex = CRCString( tokenname ); - - int idx = m_CloseCaptionRepeats.Find( entry ); - if ( m_CloseCaptionRepeats.InvalidIndex() == idx ) - { - entry.m_flLastEmitTime = gpGlobals->curtime; - entry.m_nLastEmitTick = gpGlobals->tickcount; - entry.m_flInterval = interval; - m_CloseCaptionRepeats.Insert( entry ); - } - else - { - CaptionRepeat &entry = m_CloseCaptionRepeats[ idx ]; - if ( gpGlobals->curtime < ( entry.m_flLastEmitTime + entry.m_flInterval ) ) - { - return; - } - - entry.m_flLastEmitTime = gpGlobals->curtime; - entry.m_nLastEmitTick = gpGlobals->tickcount; - } - } - - AddAsyncWork( tokenstream, true, 0.0f, false ); -} - -void CHudCloseCaption::_ProcessSentenceCaptionStream( int wordCount, const char *tokenstream, const wchar_t *caption_full ) -{ - if ( wcslen( caption_full ) > 0 ) - { - Process( caption_full, ( wordCount + 1 ) * 0.75f, tokenstream, false /*never from player!*/ ); - } -} - -bool CHudCloseCaption::ProcessCaption( const char *tokenname, float duration, bool fromplayer /* = false */, bool direct /* = false */ ) -{ - return AddAsyncWork( tokenname, false, duration, fromplayer, direct ); -} - -void CHudCloseCaption::_ProcessCaption( const wchar_t *caption, const char *tokenname, float duration, bool fromplayer, bool direct ) -{ - // Get the string for the token - float interval = 0.0f; - bool hasnorepeat = GetNoRepeatValue( caption, interval ); - - CaptionRepeat entry; - entry.m_nTokenIndex = CRCString( tokenname ); - - int idx = m_CloseCaptionRepeats.Find( entry ); - if ( m_CloseCaptionRepeats.InvalidIndex() == idx ) - { - entry.m_flLastEmitTime = gpGlobals->curtime; - entry.m_nLastEmitTick = gpGlobals->tickcount; - entry.m_flInterval = interval; - m_CloseCaptionRepeats.Insert( entry ); - } - else - { - CaptionRepeat &entry = m_CloseCaptionRepeats[ idx ]; - - // Interval of 0.0 means just don't double emit on same tick # - if ( entry.m_flInterval <= 0.0f ) - { - if ( gpGlobals->tickcount <= entry.m_nLastEmitTick ) - { - return; - } - } - else if ( hasnorepeat ) - { - if ( gpGlobals->curtime < ( entry.m_flLastEmitTime + entry.m_flInterval ) ) - { - return; - } - } - - entry.m_flLastEmitTime = gpGlobals->curtime; - entry.m_nLastEmitTick = gpGlobals->tickcount; - } - - Process( caption, duration, tokenname, fromplayer, direct ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pszName - -// iSize - -// *pbuf - -//----------------------------------------------------------------------------- -void CHudCloseCaption::MsgFunc_CloseCaption(bf_read &msg) -{ - char tokenname[ 512 ]; - msg.ReadString( tokenname, sizeof( tokenname ) ); - float duration = msg.ReadShort() * 0.1f; - byte flagbyte = msg.ReadByte(); - bool warnonmissing = flagbyte & CLOSE_CAPTION_WARNIFMISSING ? true : false; - bool fromplayer = flagbyte & CLOSE_CAPTION_FROMPLAYER ? true : false; - bool bIsMale = flagbyte & CLOSE_CAPTION_GENDER_MALE ? true : false; - bool bIsFemale = flagbyte & CLOSE_CAPTION_GENDER_FEMALE ? true : false; - - if ( warnonmissing && !IsX360() ) - { - wchar_t *pcheck = g_pVGuiLocalize->Find( tokenname ); - if ( !pcheck ) - { - Warning( "No caption found for '%s'\n", tokenname ); - } - } - - char szTestName[ 512 ]; - if ( bIsMale || bIsFemale ) - { - Q_snprintf( szTestName, sizeof( szTestName ), "%s_%s", tokenname, bIsMale ? "male" : "female" ); - // If the gender-ified version exists, use it, otherwise fall through and pass the non-gender string to the cc system for processing - if ( ProcessCaption( szTestName , duration, fromplayer ) ) - { - return; - } - } - - ProcessCaption( tokenname, duration, fromplayer ); -} - -int CHudCloseCaption::GetFontNumber( bool bold, bool italic ) -{ - if ( IsPC() && ( bold || italic ) ) - { - if( bold && italic ) - { - return CHudCloseCaption::CCFONT_ITALICBOLD; - } - - if ( bold ) - { - return CHudCloseCaption::CCFONT_BOLD; - } - - if ( italic ) - { - return CHudCloseCaption::CCFONT_ITALIC; - } - } - - return CHudCloseCaption::CCFONT_NORMAL; -} - -void CHudCloseCaption::Flush() -{ - g_AsyncCaptionResourceManager.Flush(); -} - -void CHudCloseCaption::InitCaptionDictionary( const char *dbfile ) -{ - if ( m_CurrentLanguage.IsValid() && !Q_stricmp( m_CurrentLanguage.String(), dbfile ) ) - return; - - m_CurrentLanguage = dbfile; - - m_AsyncCaptions.Purge(); - - g_AsyncCaptionResourceManager.Clear(); - - char searchPaths[4096]; - filesystem->GetSearchPath( "GAME", true, searchPaths, sizeof( searchPaths ) ); - - for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) ) - { - if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) && !V_stristr( path, ".zip" ) ) - { - // only want zip paths - continue; - } - - char fullpath[MAX_PATH]; - Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", path, dbfile ); - Q_FixSlashes( fullpath ); - - if ( IsX360() ) - { - char fullpath360[MAX_PATH]; - UpdateOrCreateCaptionFile( fullpath, fullpath360, sizeof( fullpath360 ) ); - Q_strncpy( fullpath, fullpath360, sizeof( fullpath ) ); - } - - FileHandle_t fh = filesystem->Open( fullpath, "rb" ); - if ( FILESYSTEM_INVALID_HANDLE != fh ) - { - MEM_ALLOC_CREDIT(); - - CUtlBuffer dirbuffer; - - AsyncCaption_t& entry = m_AsyncCaptions[ m_AsyncCaptions.AddToTail() ]; - - // Read the header - filesystem->Read( &entry.m_Header, sizeof( entry.m_Header ), fh ); - if ( entry.m_Header.magic != COMPILED_CAPTION_FILEID ) - Error( "Invalid file id for %s\n", fullpath ); - if ( entry.m_Header.version != COMPILED_CAPTION_VERSION ) - Error( "Invalid file version for %s\n", fullpath ); - if ( entry.m_Header.directorysize < 0 || entry.m_Header.directorysize > 64 * 1024 ) - Error( "Invalid directory size %d for %s\n", entry.m_Header.directorysize, fullpath ); - //if ( entry.m_Header.blocksize != MAX_BLOCK_SIZE ) - // Error( "Invalid block size %d, expecting %d for %s\n", entry.m_Header.blocksize, MAX_BLOCK_SIZE, fullpath ); - - int directoryBytes = entry.m_Header.directorysize * sizeof( CaptionLookup_t ); - entry.m_CaptionDirectory.EnsureCapacity( entry.m_Header.directorysize ); - dirbuffer.EnsureCapacity( directoryBytes ); - - filesystem->Read( dirbuffer.Base(), directoryBytes, fh ); - filesystem->Close( fh ); - - entry.m_CaptionDirectory.CopyArray( (const CaptionLookup_t *)dirbuffer.PeekGet(), entry.m_Header.directorysize ); - entry.m_CaptionDirectory.RedoSort( true ); - - entry.m_DataBaseFile = fullpath; - } - } - - g_AsyncCaptionResourceManager.SetDbInfo( m_AsyncCaptions ); -} - -void CHudCloseCaption::OnFinishAsyncLoad( int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData ) -{ - // Fill in data for all users of pData->m_nBlockNum - FOR_EACH_LL( m_AsyncWork, i ) - { - CAsyncCaption *item = m_AsyncWork[ i ]; - item->OnDataArrived( m_AsyncCaptions, nFileIndex, nBlockNum, pData ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHudCloseCaption::Lock( void ) -{ - if ( !IsXbox() ) - m_bLocked = true; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CHudCloseCaption::Unlock( void ) -{ - m_bLocked = false; -} - -static int EmitCaptionCompletion( const char *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) -{ - int current = 0; - if ( !g_pVGuiLocalize || IsX360() ) - return current; - - const char *cmdname = "cc_emit"; - char *substring = NULL; - int substringLen = 0; - if ( Q_strstr( partial, cmdname ) && strlen(partial) > strlen(cmdname) + 1 ) - { - substring = (char *)partial + strlen( cmdname ) + 1; - substringLen = strlen(substring); - } - - StringIndex_t i = g_pVGuiLocalize->GetFirstStringIndex(); - - while ( i != INVALID_LOCALIZE_STRING_INDEX && - current < COMMAND_COMPLETION_MAXITEMS ) - { - const char *ccname = g_pVGuiLocalize->GetNameByIndex( i ); - if ( ccname ) - { - if ( !substring || !Q_strncasecmp( ccname, substring, substringLen ) ) - { - Q_snprintf( commands[ current ], sizeof( commands[ current ] ), "%s %s", cmdname, ccname ); - current++; - } - } - i = g_pVGuiLocalize->GetNextStringIndex( i ); - } - - return current; -} - -CON_COMMAND_F_COMPLETION( cc_emit, "Emits a closed caption", 0, EmitCaptionCompletion ) -{ - if ( args.ArgC() != 2 ) - { - Msg( "usage: cc_emit tokenname\n" ); - return; - } - - CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); - if ( hudCloseCaption ) - { - hudCloseCaption->ProcessCaption( args[1], 5.0f ); - } -} - -CON_COMMAND( cc_random, "Emits a random caption" ) -{ - int count = 1; - if ( args.ArgC() == 2 ) - { - count = MAX( 1, atoi( args[ 1 ] ) ); - } - CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); - if ( hudCloseCaption ) - { - for ( int i = 0; i < count; ++i ) - { - hudCloseCaption->PlayRandomCaption(); - } - } -} - - -CON_COMMAND( cc_flush, "Flushes async'd captions." ) -{ - CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); - if ( hudCloseCaption ) - { - hudCloseCaption->Flush(); - } -} - -CON_COMMAND( cc_showblocks, "Toggles showing which blocks are pending/loaded async." ) -{ - CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); - if ( hudCloseCaption ) - { - hudCloseCaption->TogglePaintDebug(); - } -} - -void OnCaptionLanguageChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) -{ - if ( !g_pVGuiLocalize ) - return; - - ConVarRef var( pConVar ); - - char fn[ 512 ]; - Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", var.GetString() ); - - // Re-adding the file, even if it's "english" will overwrite the tokens as needed - if ( !IsX360() ) - { - g_pVGuiLocalize->AddFile( "resource/closecaption_%language%.txt", "GAME", true ); - } - - char uilanguage[ 64 ]; - engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); - - CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); - - // If it's not the default, load the language on top of the user's default language - if ( Q_strlen( var.GetString() ) > 0 && Q_stricmp( var.GetString(), uilanguage ) ) - { - if ( !IsX360() ) - { - if ( g_pFullFileSystem->FileExists( fn ) ) - { - g_pVGuiLocalize->AddFile( fn, "GAME", true ); - } - else - { - char fallback[ 512 ]; - Q_snprintf( fallback, sizeof( fallback ), "resource/closecaption_%s.txt", uilanguage ); - - Msg( "%s not found\n", fn ); - Msg( "%s will be used\n", fallback ); - } - } - - if ( hudCloseCaption ) - { - char dbfile [ 512 ]; - Q_snprintf( dbfile, sizeof( dbfile ), "resource/closecaption_%s.dat", var.GetString() ); - hudCloseCaption->InitCaptionDictionary( dbfile ); - } - } - else - { - if ( hudCloseCaption ) - { - char dbfile [ 512 ]; - Q_snprintf( dbfile, sizeof( dbfile ), "resource/closecaption_%s.dat", uilanguage ); - hudCloseCaption->InitCaptionDictionary( dbfile ); - } - } - DevMsg( "cc_lang = %s\n", var.GetString() ); -} - - - -ConVar cc_lang( "cc_lang", "", FCVAR_ARCHIVE, "Current close caption language (emtpy = use game UI language)", OnCaptionLanguageChanged ); - -CON_COMMAND( cc_findsound, "Searches for soundname which emits specified text." ) -{ - if ( args.ArgC() != 2 ) - { - Msg( "usage: cc_findsound 'substring'\n" ); - return; - } - - CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); - if ( hudCloseCaption ) - { - hudCloseCaption->FindSound( args.Arg( 1 ) ); - } -} - -void CHudCloseCaption::FindSound( char const *pchANSI ) -{ - // Now do the searching - ucs2 stream[ 1024 ]; - char streamANSI[ 1024 ]; - - for ( int i = 0 ; i < m_AsyncCaptions.Count(); ++i ) - { - AsyncCaption_t &data = m_AsyncCaptions[ i ]; - - byte *block = new byte[ data.m_Header.blocksize ]; - - int nLoadedBlock = -1; - - Q_memset( block, 0, data.m_Header.blocksize ); - CaptionDictionary_t &dict = data.m_CaptionDirectory; - for ( int j = 0; j < dict.Count(); ++j ) - { - CaptionLookup_t &lu = dict[ j ]; - - int blockNum = lu.blockNum; - - const char *dbname = data.m_DataBaseFile.String(); - - // Try and reload it - char fn[ 256 ]; - Q_strncpy( fn, dbname, sizeof( fn ) ); - Q_FixSlashes( fn ); - - asynccaptionparams_t params; - params.dbfile = fn; - params.blocktoload = blockNum; - params.blocksize = data.m_Header.blocksize; - params.blockoffset = data.m_Header.dataoffset + blockNum *data.m_Header.blocksize; - params.fileindex = i; - - if ( blockNum != nLoadedBlock ) - { - nLoadedBlock = blockNum; - - FileHandle_t fh = filesystem->Open( fn, "rb" ); - filesystem->Seek( fh, params.blockoffset, FILESYSTEM_SEEK_CURRENT ); - filesystem->Read( block, data.m_Header.blocksize, fh ); - filesystem->Close( fh ); - } - - // Now we have the data - const ucs2 *pIn = ( const ucs2 *)&block[ lu.offset ]; - Q_memcpy( (void *)stream, pIn, MIN( lu.length, sizeof( stream ) ) ); - - // Now search for search text - V_UCS2ToUTF8( stream, streamANSI, sizeof( streamANSI ) ); - streamANSI[ sizeof( streamANSI ) - 1 ] = 0; - - if ( Q_stristr( streamANSI, pchANSI ) ) - { - CaptionLookup_t search; - - Msg( "found '%s' in %s\n", streamANSI, fn ); - - // Now find the sounds that will hash to this - for ( int k = soundemitterbase->First(); k != soundemitterbase->InvalidIndex(); k = soundemitterbase->Next( k ) ) - { - char const *pchSoundName = soundemitterbase->GetSoundName( k ); - - // Hash it - - search.SetHash( pchSoundName ); - - if ( search.hash == lu.hash ) - { - Msg( " '%s' matches\n", pchSoundName ); - } - } - - if ( IsPC() ) - { - for ( int r = g_pVGuiLocalize->GetFirstStringIndex(); r != INVALID_LOCALIZE_STRING_INDEX; r = g_pVGuiLocalize->GetNextStringIndex( r ) ) - { - const char *strName = g_pVGuiLocalize->GetNameByIndex( r ); - - search.SetHash( strName ); - - if ( search.hash == lu.hash ) - { - Msg( " '%s' localization matches\n", strName ); - } - } - } - } - } - - delete[] block; - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "cbase.h" +#include +#include "sentence.h" +#include "hud_closecaption.h" +#include "tier1/strtools.h" +#include +#include +#include +#include +#include +#include "iclientmode.h" +#include "hud_macros.h" +#include "checksum_crc.h" +#include "filesystem.h" +#include "datacache/idatacache.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#define CC_INSET 12 + +extern ISoundEmitterSystemBase *soundemitterbase; + +// Marked as FCVAR_USERINFO so that the server can cull CC messages before networking them down to us!!! +ConVar closecaption( "closecaption", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX | FCVAR_USERINFO, "Enable close captioning." ); +extern ConVar cc_lang; +static ConVar cc_linger_time( "cc_linger_time", "1.0", FCVAR_ARCHIVE, "Close caption linger time." ); +static ConVar cc_predisplay_time( "cc_predisplay_time", "0.25", FCVAR_ARCHIVE, "Close caption delay before showing caption." ); +static ConVar cc_captiontrace( "cc_captiontrace", "1", 0, "Show missing closecaptions (0 = no, 1 = devconsole, 2 = show in hud)" ); +static ConVar cc_subtitles( "cc_subtitles", "0", FCVAR_ARCHIVE | FCVAR_ARCHIVE_XBOX, "If set, don't show sound effect captions, just voice overs (i.e., won't help hearing impaired players)." ); +ConVar english( "english", "1", FCVAR_USERINFO, "If set to 1, running the english language set of assets." ); +static ConVar cc_smallfontlength( "cc_smallfontlength", "300", 0, "If text stream is this long, force usage of small font size." ); + +#define MAX_CAPTION_CHARACTERS 4096 + +#define CAPTION_PAN_FADE_TIME 0.5 // The time it takes for a line to fade while panning over a large entry +#define CAPTION_PAN_SLIDE_TIME 0.5 // The time it takes for a line to slide on while panning over a large entry + + +// A work unit is a pre-processed chunk of CC text to display +// Any state changes (font/color/etc) cause a new work unit to be precomputed +// Moving onto a new line also causes a new Work Unit +// The width and height are stored so that layout can be quickly recomputed each frame +class CCloseCaptionWorkUnit +{ +public: + CCloseCaptionWorkUnit(); + ~CCloseCaptionWorkUnit(); + + void SetWidth( int w ); + int GetWidth() const; + + void SetHeight( int h ); + int GetHeight() const; + + void SetPos( int x, int y ); + void GetPos( int& x, int &y ) const; + + void SetFadeStart( float flTime ); + float GetFadeStart( void ) const; + + void SetBold( bool bold ); + bool GetBold() const; + + void SetItalic( bool ital ); + bool GetItalic() const; + + void SetStream( const wchar_t *stream ); + const wchar_t *GetStream() const; + + void SetColor( Color& clr ); + Color GetColor() const; + + vgui::HFont GetFont() const + { + return m_hFont; + } + + void SetFont( vgui::HFont fnt ) + { + m_hFont = fnt; + } + + void Dump() + { + char buf[ 2048 ]; + g_pVGuiLocalize->ConvertUnicodeToANSI( GetStream(), buf, sizeof( buf ) ); + + Msg( "x = %i, y = %i, w = %i h = %i text %s\n", m_nX, m_nY, m_nWidth, m_nHeight, buf ); + } + +private: + + int m_nX; + int m_nY; + int m_nWidth; + int m_nHeight; + float m_flFadeStartTime; + + bool m_bBold; + bool m_bItalic; + wchar_t *m_pszStream; + vgui::HFont m_hFont; + Color m_Color; +}; + +CCloseCaptionWorkUnit::CCloseCaptionWorkUnit() : + m_nWidth(0), + m_nHeight(0), + m_bBold(false), + m_bItalic(false), + m_pszStream(0), + m_Color( Color( 255, 255, 255, 255 ) ), + m_hFont( 0 ), + m_flFadeStartTime(0) +{ +} + +CCloseCaptionWorkUnit::~CCloseCaptionWorkUnit() +{ + delete[] m_pszStream; + m_pszStream = NULL; +} + +void CCloseCaptionWorkUnit::SetWidth( int w ) +{ + m_nWidth = w; +} + +int CCloseCaptionWorkUnit::GetWidth() const +{ + return m_nWidth; +} + +void CCloseCaptionWorkUnit::SetHeight( int h ) +{ + m_nHeight = h; +} + +int CCloseCaptionWorkUnit::GetHeight() const +{ + return m_nHeight; +} + +void CCloseCaptionWorkUnit::SetPos( int x, int y ) +{ + m_nX = x; + m_nY = y; +} + +void CCloseCaptionWorkUnit::GetPos( int& x, int &y ) const +{ + x = m_nX; + y = m_nY; +} + +void CCloseCaptionWorkUnit::SetFadeStart( float flTime ) +{ + m_flFadeStartTime = flTime; +} + +float CCloseCaptionWorkUnit::GetFadeStart( void ) const +{ + return m_flFadeStartTime; +} + +void CCloseCaptionWorkUnit::SetBold( bool bold ) +{ + m_bBold = bold; +} + +bool CCloseCaptionWorkUnit::GetBold() const +{ + return m_bBold; +} + +void CCloseCaptionWorkUnit::SetItalic( bool ital ) +{ + m_bItalic = ital; +} + +bool CCloseCaptionWorkUnit::GetItalic() const +{ + return m_bItalic; +} + +void CCloseCaptionWorkUnit::SetStream( const wchar_t *stream ) +{ + delete[] m_pszStream; + m_pszStream = NULL; + + int len = wcslen( stream ); + Assert( len < 4096 ); + m_pszStream = new wchar_t[ len + 1 ]; + wcsncpy( m_pszStream, stream, len ); + m_pszStream[ len ] = L'\0'; +} + +const wchar_t *CCloseCaptionWorkUnit::GetStream() const +{ + return m_pszStream ? m_pszStream : L""; +} + +void CCloseCaptionWorkUnit::SetColor( Color& clr ) +{ + m_Color = clr; +} + +Color CCloseCaptionWorkUnit::GetColor() const +{ + return m_Color; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CCloseCaptionItem +{ +public: + CCloseCaptionItem( + const wchar_t *stream, + float timetolive, + float addedtime, + float predisplay, + bool valid, + bool fromplayer + ) : + m_flTimeToLive( 0.0f ), + m_flAddedTime( addedtime ), + m_bValid( false ), + m_nTotalWidth( 0 ), + m_nTotalHeight( 0 ), + m_bSizeComputed( false ), + m_bFromPlayer( fromplayer ) + + { + SetStream( stream ); + SetTimeToLive( timetolive ); + SetInitialLifeSpan( timetolive ); + SetPreDisplayTime( cc_predisplay_time.GetFloat() + predisplay ); + m_bValid = valid; + } + + CCloseCaptionItem( const CCloseCaptionItem& src ) + { + SetStream( src.m_szStream ); + m_flTimeToLive = src.m_flTimeToLive; + m_bValid = src.m_bValid; + m_bFromPlayer = src.m_bFromPlayer; + m_flAddedTime = src.m_flAddedTime; + } + + ~CCloseCaptionItem( void ) + { + while ( m_Work.Count() > 0 ) + { + CCloseCaptionWorkUnit *unit = m_Work[ 0 ]; + m_Work.Remove( 0 ); + delete unit; + } + + } + + void SetStream( const wchar_t *stream) + { + wcsncpy( m_szStream, stream, sizeof( m_szStream ) / sizeof( wchar_t ) ); + } + + const wchar_t *GetStream() const + { + return m_szStream; + } + + void SetTimeToLive( float ttl ) + { + m_flTimeToLive = ttl; + } + + float GetTimeToLive( void ) const + { + return m_flTimeToLive; + } + + void SetInitialLifeSpan( float t ) + { + m_flInitialLifeSpan = t; + } + + float GetInitialLifeSpan() const + { + return m_flInitialLifeSpan; + } + + bool IsValid() const + { + return m_bValid; + } + + void SetHeight( int h ) + { + m_nTotalHeight = h; + } + int GetHeight() const + { + return m_nTotalHeight; + } + void SetWidth( int w ) + { + m_nTotalWidth = w; + } + int GetWidth() const + { + return m_nTotalWidth; + } + + void AddWork( CCloseCaptionWorkUnit *unit ) + { + m_Work.AddToTail( unit ); + } + + int GetNumWorkUnits() const + { + return m_Work.Count(); + } + + CCloseCaptionWorkUnit *GetWorkUnit( int index ) + { + Assert( index >= 0 && index < m_Work.Count() ); + + return m_Work[ index ]; + } + + void SetSizeComputed( bool computed ) + { + m_bSizeComputed = computed; + } + + bool GetSizeComputed() const + { + return m_bSizeComputed; + } + + void SetPreDisplayTime( float t ) + { + m_flPreDisplayTime = t; + } + + float GetPreDisplayTime() const + { + return m_flPreDisplayTime; + } + + float GetAlpha( float fadeintimehidden, float fadeintime, float fadeouttime ) + { + float time_since_start = m_flInitialLifeSpan - m_flTimeToLive; + float time_until_end = m_flTimeToLive; + + float totalfadeintime = fadeintimehidden + fadeintime; + + if ( totalfadeintime > 0.001f && + time_since_start < totalfadeintime ) + { + if ( time_since_start >= fadeintimehidden ) + { + float f = 1.0f; + if ( fadeintime > 0.001f ) + { + f = ( time_since_start - fadeintimehidden ) / fadeintime; + } + f = clamp( f, 0.0f, 1.0f ); + return f; + } + + return 0.0f; + } + + if ( fadeouttime > 0.001f && + time_until_end < fadeouttime ) + { + float f = time_until_end / fadeouttime; + f = clamp( f, 0.0f, 1.0f ); + return f; + } + + return 1.0f; + } + + float GetAddedTime() const + { + return m_flAddedTime; + } + + void SetAddedTime( float addt ) + { + m_flAddedTime = addt; + } + + bool IsFromPlayer() const + { + return m_bFromPlayer; + } + +private: + wchar_t m_szStream[ MAX_CAPTION_CHARACTERS ]; + + float m_flPreDisplayTime; + float m_flTimeToLive; + float m_flInitialLifeSpan; + float m_flAddedTime; + bool m_bValid; + int m_nTotalWidth; + int m_nTotalHeight; + + bool m_bSizeComputed; + bool m_bFromPlayer; + + CUtlVector< CCloseCaptionWorkUnit * > m_Work; + +}; + +struct VisibleStreamItem +{ + int height; + int width; + CCloseCaptionItem *item; +}; + +//----------------------------------------------------------------------------- +// Purpose: The only resource manager parameter we currently care about is the name +// of the .vcd to cache into memory +//----------------------------------------------------------------------------- +struct asynccaptionparams_t +{ + const char *dbfile; + int fileindex; + int blocktoload; + int blockoffset; + int blocksize; +}; + +// 16K of cache for close caption data +#define MAX_ASYNCCAPTION_MEMORY_CACHE (int)( 64.0 * 1024.0f ) + +void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ); + +struct AsyncCaptionData_t +{ + int m_nBlockNum; + byte *m_pBlockData; + int m_nFileIndex; + int m_nBlockSize; + + bool m_bLoadPending : 1; + bool m_bLoadCompleted : 1; + + FSAsyncControl_t m_hAsyncControl; + + AsyncCaptionData_t() : + m_nBlockNum( -1 ), + m_pBlockData( 0 ), + m_nFileIndex( -1 ), + m_nBlockSize( 0 ), + m_bLoadPending( false ), + m_bLoadCompleted( false ), + m_hAsyncControl( NULL ) + { + } + + // APIS required by CDataManager + void DestroyResource() + { + if ( m_bLoadPending && !m_bLoadCompleted ) + { + filesystem->AsyncFinish( m_hAsyncControl, true ); + } + filesystem->AsyncRelease( m_hAsyncControl ); + + WipeData(); + delete this; + } + + void ReleaseData() + { + filesystem->AsyncRelease( m_hAsyncControl ); + m_hAsyncControl = 0; + WipeData(); + m_bLoadCompleted = false; + Assert( !m_bLoadPending ); + } + + void WipeData() + { + delete[] m_pBlockData; + m_pBlockData = NULL; + } + + AsyncCaptionData_t *GetData() + { + return this; + } + unsigned int Size() + { + return sizeof( *this ) + m_nBlockSize; + } + + void AsyncLoad( const char *fileName, int blockOffset ) + { + // Already pending + Assert ( !m_hAsyncControl ); + + // async load the file + FileAsyncRequest_t fileRequest; + fileRequest.pContext = (void *)this; + fileRequest.pfnCallback = ::CaptionAsyncLoaderCallback; + fileRequest.pData = m_pBlockData; + fileRequest.pszFilename = fileName; + fileRequest.nOffset = blockOffset; + fileRequest.flags = 0; + fileRequest.nBytes = m_nBlockSize; + fileRequest.priority = -1; + fileRequest.pszPathID = "GAME"; + + // queue for async load + MEM_ALLOC_CREDIT(); + filesystem->AsyncRead( fileRequest, &m_hAsyncControl ); + } + + // you must implement these static functions for the ResourceManager + // ----------------------------------------------------------- + static AsyncCaptionData_t *CreateResource( const asynccaptionparams_t ¶ms ) + { + AsyncCaptionData_t *data = new AsyncCaptionData_t; + data->m_nBlockNum = params.blocktoload; + data->m_nFileIndex = params.fileindex; + data->m_nBlockSize = params.blocksize; + data->m_pBlockData = new byte[ data->m_nBlockSize ]; + return data; + } + + static unsigned int EstimatedSize( const asynccaptionparams_t ¶ms ) + { + // The block size is assumed to be 4K + return ( sizeof( AsyncCaptionData_t ) + params.blocksize ); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: This manages the instanced scene memory handles. We essentially grow a handle list by scene filename where +// the handle is a pointer to a AsyncCaptionData_t defined above. If the resource manager uncaches the handle, we reload the +// .vcd from disk. Precaching a .vcd calls into FindOrAddBlock which moves the .vcd to the head of the LRU if it's in memory +// or it reloads it from disk otherwise. +//----------------------------------------------------------------------------- +class CAsyncCaptionResourceManager : public CAutoGameSystem, public CManagedDataCacheClient< AsyncCaptionData_t, asynccaptionparams_t > +{ +public: + CAsyncCaptionResourceManager() : CAutoGameSystem( "CAsyncCaptionResourceManager" ) + { + } + + void SetDbInfo( const CUtlVector< AsyncCaption_t > & info ) + { + m_Db = info; + } + + virtual bool Init() + { + CCacheClientBaseClass::Init( datacache, "Captions", MAX_ASYNCCAPTION_MEMORY_CACHE ); + return true; + } + virtual void Shutdown() + { + Clear(); + CCacheClientBaseClass::Shutdown(); + } + + //----------------------------------------------------------------------------- + // Purpose: Spew a cache summary to the console + //----------------------------------------------------------------------------- + void SpewMemoryUsage() + { + GetCacheSection()->OutputReport(); + + DataCacheStatus_t status; + DataCacheLimits_t limits; + GetCacheSection()->GetStatus( &status, &limits ); + int bytesUsed = status.nBytes; + int bytesTotal = limits.nMaxBytes; + + float percent = 100.0f * (float)bytesUsed / (float)bytesTotal; + + int count = 0; + for ( int i = 0; i < m_Db.Count(); ++i ) + { + count += m_Db[ i ].m_RequestedBlocks.Count(); + } + + DevMsg( "CAsyncCaptionResourceManager: %i blocks total %s, %.2f %% of capacity\n", count, Q_pretifymem( bytesUsed, 2 ), percent ); + } + + virtual void LevelInitPostEntity() + { + } + + void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ) + { + // get our preserved data + AsyncCaptionData_t *pData = ( AsyncCaptionData_t * )request.pContext; + + Assert( pData ); + + // mark as completed in single atomic operation + pData->m_bLoadCompleted = true; + } + + int ComputeBlockOffset( int fileIndex, int blockNum ) + { + return m_Db[ fileIndex ].m_Header.dataoffset + blockNum * m_Db[ fileIndex ].m_Header.blocksize; + } + + void GetBlockInfo( int fileIndex, int blockNum, bool& entry, bool& pending, bool& loaded ) + { + pending = false; + loaded = false; + AsyncCaption_t::BlockInfo_t search; + search.fileindex = fileIndex; + search.blocknum = blockNum; + + CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ fileIndex ].m_RequestedBlocks; + + int idx = requested.Find( search ); + if ( idx == requested.InvalidIndex() ) + { + entry = false; + return; + } + entry = true; + + DataCacheHandle_t handle = requested[ idx ].handle; + AsyncCaptionData_t *pCaptionData = CacheLock( handle ); + if ( pCaptionData ) + { + if ( pCaptionData->m_bLoadPending ) + { + pending = true; + } + else if ( pCaptionData->m_bLoadCompleted ) + { + loaded = true; + } + CacheUnlock( handle ); + } + } + + // Either commences async loading or polls for async loading once per frame to wait for it to complete... + void PollForAsyncLoading( CHudCloseCaption *hudCloseCaption, int dbFileIndex, int blockNum ) + { + const char *dbname = m_Db[ dbFileIndex ].m_DataBaseFile.String(); + + CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ dbFileIndex ].m_RequestedBlocks; + + int idx = FindOrAddBlock( dbFileIndex, blockNum ); + if ( idx == requested.InvalidIndex() ) + { + Assert( 0 ); + return; + } + + DataCacheHandle_t handle = requested[ idx ].handle; + + AsyncCaptionData_t *pCaptionData = CacheLock( handle ); + if ( !pCaptionData ) + { + // Try and reload it + char fn[ 256 ]; + Q_strncpy( fn, dbname, sizeof( fn ) ); + Q_FixSlashes( fn ); + + asynccaptionparams_t params; + params.dbfile = fn; + params.blocktoload = blockNum; + params.blocksize = m_Db[ dbFileIndex ].m_Header.blocksize; + params.blockoffset = ComputeBlockOffset( dbFileIndex, blockNum ); + params.fileindex = dbFileIndex; + + handle = requested[ idx ].handle = CacheCreate( params ); + pCaptionData = CacheLock( handle ); + if ( !pCaptionData ) + { + Assert( pCaptionData ); + return; + } + } + + if ( pCaptionData->m_bLoadCompleted ) + { + pCaptionData->m_bLoadPending = false; + // Copy in data at this point + Assert( hudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->OnFinishAsyncLoad( requested[ idx ].fileindex, requested[ idx ].blocknum, pCaptionData ); + } + + // This finalizes the load (unlocks the handle) + GetCacheSection()->BreakLock( handle ); + return; + } + + if ( pCaptionData->m_bLoadPending ) + { + CacheUnlock( handle ); + return; + } + + // Commence load (locks handle for entire async load) (unlocked above) + pCaptionData->m_bLoadPending = true; + pCaptionData->AsyncLoad( dbname, ComputeBlockOffset( dbFileIndex, blockNum ) ); + } + + //----------------------------------------------------------------------------- + // Purpose: Touch the cache or load the scene into the cache for the first time + // Input : *filename - + //----------------------------------------------------------------------------- + int FindOrAddBlock( int dbFileIndex, int blockNum ) + { + const char *dbname = m_Db[ dbFileIndex ].m_DataBaseFile.String(); + + CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ dbFileIndex ].m_RequestedBlocks; + + AsyncCaption_t::BlockInfo_t search; + search.blocknum = blockNum; + search.fileindex = dbFileIndex; + + int idx = requested.Find( search ); + if ( idx != requested.InvalidIndex() ) + { + // Move it to head of LRU + CacheTouch( requested[ idx ].handle ); + return idx; + } + + char fn[ 256 ]; + Q_strncpy( fn, dbname, sizeof( fn ) ); + Q_FixSlashes( fn ); + + asynccaptionparams_t params; + params.dbfile = fn; + params.blocktoload = blockNum; + params.blockoffset = ComputeBlockOffset( dbFileIndex, blockNum ); + params.blocksize = m_Db[ dbFileIndex ].m_Header.blocksize; + params.fileindex = dbFileIndex; + + memhandle_t handle = CacheCreate( params ); + + AsyncCaption_t::BlockInfo_t info; + info.fileindex = dbFileIndex; + info.blocknum = blockNum; + info.handle = handle; + + // Add scene filename to dictionary + idx = requested.Insert( info ); + return idx; + } + + void Flush() + { + CacheFlush(); + } + + void Clear() + { + for ( int file = 0; file < m_Db.Count(); ++file ) + { + CUtlRBTree< AsyncCaption_t::BlockInfo_t, unsigned short >& requested = m_Db[ file ].m_RequestedBlocks; + + int c = requested.Count(); + for ( int i = 0; i < c; ++i ) + { + memhandle_t dat = requested[ i ].handle; + CacheRemove( dat ); + } + + requested.RemoveAll(); + } + } + +private: + + CUtlVector< AsyncCaption_t > m_Db; +}; + +CAsyncCaptionResourceManager g_AsyncCaptionResourceManager; + +void CaptionAsyncLoaderCallback( const FileAsyncRequest_t &request, int numReadBytes, FSAsyncStatus_t asyncStatus ) +{ + g_AsyncCaptionResourceManager.CaptionAsyncLoaderCallback( request, numReadBytes, asyncStatus ); +} + +DECLARE_HUDELEMENT( CHudCloseCaption ); + +DECLARE_HUD_MESSAGE( CHudCloseCaption, CloseCaption ); + +CHudCloseCaption::CHudCloseCaption( const char *pElementName ) + : CHudElement( pElementName ), + vgui::Panel( NULL, "HudCloseCaption" ), + m_CloseCaptionRepeats( 0, 0, CaptionTokenLessFunc ), + m_CurrentLanguage( UTL_INVAL_SYMBOL ), + m_bPaintDebugInfo( false ) +{ + vgui::Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + m_nGoalHeight = 0; + m_nCurrentHeight = 0; + m_flGoalAlpha = 1.0f; + m_flCurrentAlpha = 1.0f; + + m_flGoalHeightStartTime = 0; + m_flGoalHeightFinishTime = 0; + + m_bLocked = false; + m_bVisibleDueToDirect = false; + + SetPaintBorderEnabled( false ); + SetPaintBackgroundEnabled( false ); + + vgui::ivgui()->AddTickSignal( GetVPanel(), 0 ); + + if ( !IsX360() ) + { + g_pVGuiLocalize->AddFile( "resource/closecaption_%language%.txt", "GAME", true ); + } + + HOOK_HUD_MESSAGE( CHudCloseCaption, CloseCaption ); + + char uilanguage[ 64 ]; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + + if ( !Q_stricmp( uilanguage, "english" ) ) + { + english.SetValue( 1 ); + } + else + { + english.SetValue( 0 ); + } + + char dbfile [ 512 ]; + Q_snprintf( dbfile, sizeof( dbfile ), "resource/closecaption_%s.dat", uilanguage ); + InitCaptionDictionary( dbfile ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CHudCloseCaption::~CHudCloseCaption() +{ + m_CloseCaptionRepeats.RemoveAll(); + + ClearAsyncWork(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *newmap - +//----------------------------------------------------------------------------- +void CHudCloseCaption::LevelInit( void ) +{ + CreateFonts(); + // Reset repeat counters per level + m_CloseCaptionRepeats.RemoveAll(); + + // Wipe any stale pending work items... + ClearAsyncWork(); +} + +static ConVar cc_minvisibleitems( "cc_minvisibleitems", "1", 0, "Minimum number of caption items to show." ); + +void CHudCloseCaption::TogglePaintDebug() +{ + m_bPaintDebugInfo = !m_bPaintDebugInfo; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCloseCaption::Paint( void ) +{ + int w, h; + GetSize( w, h ); + + if ( m_bPaintDebugInfo ) + { + int blockWide = 350; + int startx = 50; + + int y = 0; + int size = 8; + int sizewithgap = size + 1; + + for ( int a = 0; a < m_AsyncCaptions.Count(); ++a ) + { + int x = startx; + + int c = m_AsyncCaptions[ a ].m_Header.numblocks; + for ( int i = 0 ; i < c; ++i ) + { + bool entry, pending, loaded; + g_AsyncCaptionResourceManager.GetBlockInfo( a, i, entry, pending, loaded ); + + if ( !entry ) + { + vgui::surface()->DrawSetColor( Color( 0, 0, 0, 127 ) ); + } + else if ( pending ) + { + vgui::surface()->DrawSetColor( Color( 0, 0, 255, 127 ) ); + } + else if ( loaded ) + { + vgui::surface()->DrawSetColor( Color( 0, 255, 0, 127 ) ); + } + else + { + vgui::surface()->DrawSetColor( Color( 255, 255, 0, 127 ) ); + } + + vgui::surface()->DrawFilledRect( x, y, x + size, y + size ); + x += sizewithgap; + if ( x >= startx + blockWide ) + { + x = startx; + y += sizewithgap; + } + } + + y += sizewithgap; + } + } + + wrect_t rcOutput; + rcOutput.left = 0; + rcOutput.right = w; + rcOutput.bottom = h; + rcOutput.top = m_nTopOffset; + + wrect_t rcText = rcOutput; + + int avail_width = rcText.right - rcText.left - 2 * CC_INSET; + int avail_height = rcText.bottom - rcText.top - 2 * CC_INSET; + + int totalheight = 0; + int i; + CUtlVector< VisibleStreamItem > visibleitems; + int c = m_Items.Count(); + int maxwidth = 0; + + for ( i = 0; i < c; i++ ) + { + CCloseCaptionItem *item = m_Items[ i ]; + + // Not ready for display yet. + if ( item->GetPreDisplayTime() > 0.0f ) + { + continue; + } + + if ( !item->GetSizeComputed() ) + { + ComputeStreamWork( avail_width, item ); + } + + int itemwidth = item->GetWidth(); + int itemheight = item->GetHeight(); + + totalheight += itemheight; + if ( itemwidth > maxwidth ) + { + maxwidth = itemwidth; + } + + VisibleStreamItem si; + si.height = itemheight; + si.width = itemwidth; + si.item = item; + + visibleitems.AddToTail( si ); + + // Start popping really old items off the stack if we run out of space + while ( itemheight <= avail_height && + totalheight > avail_height && + visibleitems.Count() > cc_minvisibleitems.GetInt() ) + { + VisibleStreamItem & pop = visibleitems[ 0 ]; + totalheight -= pop.height; + + // And make it die right away... + pop.item->SetTimeToLive( 0.0f ); + + visibleitems.Remove( 0 ); + } + } + + float desiredAlpha = visibleitems.Count() >= 1 ? 1.0f : 0.0f; + + // Always return at least one line height for drawing the surrounding box + totalheight = MAX( totalheight, m_nLineHeight ); + + // Trigger box growing + if ( totalheight != m_nGoalHeight ) + { + m_nGoalHeight = totalheight; + m_flGoalHeightStartTime = gpGlobals->curtime; + m_flGoalHeightFinishTime = gpGlobals->curtime + m_flGrowTime; + } + if ( desiredAlpha != m_flGoalAlpha ) + { + m_flGoalAlpha = desiredAlpha; + m_flGoalHeightStartTime = gpGlobals->curtime; + m_flGoalHeightFinishTime = gpGlobals->curtime + m_flGrowTime; + } + + // If shrunk to zero and faded out, nothing left to do + if ( !visibleitems.Count() && + m_nGoalHeight == m_nCurrentHeight && + m_flGoalAlpha == m_flCurrentAlpha ) + { + m_flGoalHeightStartTime = 0; + m_flGoalHeightFinishTime = 0; + return; + } + + bool growingDown = false; + + // Continue growth? + if ( m_flGoalHeightFinishTime && + m_flGoalHeightStartTime && + m_flGoalHeightFinishTime > m_flGoalHeightStartTime ) + { + float togo = m_nGoalHeight - m_nCurrentHeight; + float alphatogo = m_flGoalAlpha - m_flCurrentAlpha; + + growingDown = togo < 0.0f ? true : false; + + float dt = m_flGoalHeightFinishTime - m_flGoalHeightStartTime; + float frac = ( gpGlobals->curtime - m_flGoalHeightStartTime ) / dt; + frac = clamp( frac, 0.0f, 1.0f ); + int newHeight = m_nCurrentHeight + (int)( frac * togo ); + m_nCurrentHeight = newHeight; + float newAlpha = m_flCurrentAlpha + frac * alphatogo; + m_flCurrentAlpha = clamp( newAlpha, 0.0f, 1.0f ); + } + else + { + m_nCurrentHeight = m_nGoalHeight; + m_flCurrentAlpha = m_flGoalAlpha; + } + + rcText.top = rcText.bottom - m_nCurrentHeight - 2 * CC_INSET; + + Color bgColor = GetBgColor(); + bgColor[3] = m_flBackgroundAlpha; + DrawBox( rcText.left, MAX(rcText.top,0), rcText.right - rcText.left, rcText.bottom - MAX(rcText.top,0), bgColor, m_flCurrentAlpha ); + + if ( !visibleitems.Count() ) + { + return; + } + + rcText.left += CC_INSET; + rcText.right -= CC_INSET; + + int textHeight = m_nCurrentHeight; + if ( growingDown ) + { + // If growing downward, keep the text locked to the bottom of the window instead of anchored to the top + textHeight = totalheight; + } + + rcText.top = rcText.bottom - textHeight - CC_INSET; + + // Now draw them + c = visibleitems.Count(); + for ( i = 0; i < c; i++ ) + { + VisibleStreamItem *si = &visibleitems[ i ]; + + // If the oldest/top item was created with additional time, we can remove that now + if ( i == 0 ) + { + if ( si->item->GetAddedTime() > 0.0f ) + { + float ttl = si->item->GetTimeToLive(); + ttl -= si->item->GetAddedTime(); + ttl = MAX( 0.0f, ttl ); + si->item->SetTimeToLive( ttl ); + si->item->SetAddedTime( 0.0f ); + } + } + + int height = si->height; + CCloseCaptionItem *item = si->item; + + int iFadeLine = -1; + float flFadeLineAlpha = 1.0; + + // If the height is greater than the total height of the element, + // we need to slowly pan over this item. + if ( height > avail_height ) + { + // Figure out how many lines we'll need to move to see the whole caption + int units = item->GetNumWorkUnits(); + int iTotalMove = 0; + for ( int j = 0 ; j < units; j++ ) + { + CCloseCaptionWorkUnit *wu = item->GetWorkUnit( j ); + iTotalMove += wu->GetHeight(); + if ( iTotalMove >= (height - avail_height) ) + { + units = j+1; + break; + } + } + + // Figure out the delta between each point where we move the line + float flMoveDelta = item->GetInitialLifeSpan() / (float)units; + float flCurMove = item->GetInitialLifeSpan() - item->GetTimeToLive(); + int iHeightToMove = 0; + + int iLinesToMove = clamp( Floor2Int( flCurMove / flMoveDelta ), 0, units ); + if ( iLinesToMove ) + { + int iCurrentLineHeight = 0; + for ( int j = 0 ; j < iLinesToMove; j++ ) + { + iHeightToMove = iCurrentLineHeight; + + CCloseCaptionWorkUnit *wu = item->GetWorkUnit( j ); + iCurrentLineHeight += wu->GetHeight(); + } + + // Slide to the desired distance, once the fade is done + float flTimePostMove = flCurMove - (flMoveDelta * iLinesToMove); + if ( flTimePostMove < CAPTION_PAN_FADE_TIME ) + { + iFadeLine = iLinesToMove-1; + + // It's time to fade out the top line. If it hasn't started fading yet, start it. + CCloseCaptionWorkUnit *wu = item->GetWorkUnit(iFadeLine); + if ( wu->GetFadeStart() == 0 ) + { + wu->SetFadeStart( gpGlobals->curtime ); + } + + // Fade out quickly + float flFadeTime = (gpGlobals->curtime - wu->GetFadeStart()) / CAPTION_PAN_FADE_TIME; + flFadeLineAlpha = clamp( 1.0f - flFadeTime, 0.f, 1.f ); + } + else if ( flTimePostMove < (CAPTION_PAN_FADE_TIME+CAPTION_PAN_SLIDE_TIME) ) + { + flTimePostMove -= CAPTION_PAN_FADE_TIME; + float flSlideTime = clamp( flTimePostMove / 0.25f, 0.f, 1.f ); + iHeightToMove += ceil((iCurrentLineHeight - iHeightToMove) * flSlideTime); + } + else + { + iHeightToMove = iCurrentLineHeight; + } + } + + // Minor adjustment to center the caption text within the window. + rcText.top = -iHeightToMove + 2; + } + + rcText.bottom = rcText.top + height; + + wrect_t rcOut = rcText; + + rcOut.right = rcOut.left + si->width + 6; + + DrawStream( rcOut, rcOutput, item, iFadeLine, flFadeLineAlpha ); + + rcText.top += height; + rcText.bottom += height; + + if ( rcText.top >= rcOutput.bottom ) + break; + } +} + +void CHudCloseCaption::OnTick( void ) +{ + // See if any async work has completed + ProcessAsyncWork(); + + + float dt = gpGlobals->frametime; + + int c = m_Items.Count(); + int i; + + if ( m_bVisibleDueToDirect ) + { + SetVisible( true ); + if ( !c ) + { + // Don't clear our force visible if we're waiting for the caption to load + if ( m_AsyncWork.Count() == 0 ) + { + m_bVisibleDueToDirect = false; + } + } + } + else + { + SetVisible( closecaption.GetBool() ); + } + + // Pass one decay all timers + for ( i = 0 ; i < c ; ++i ) + { + CCloseCaptionItem *item = m_Items[ i ]; + + float predisplay = item->GetPreDisplayTime(); + if ( predisplay > 0.0f ) + { + predisplay -= dt; + predisplay = MAX( 0.0f, predisplay ); + item->SetPreDisplayTime( predisplay ); + } + else + { + // remove time from actual playback + float ttl = item->GetTimeToLive(); + ttl -= dt; + ttl = MAX( 0.0f, ttl ); + item->SetTimeToLive( ttl ); + } + } + + // Pass two, remove from head until we get to first item with time remaining + bool foundfirstnondeletion = false; + for ( i = 0 ; i < c ; ++i ) + { + CCloseCaptionItem *item = m_Items[ i ]; + + // Skip items not yet showing... + float predisplay = item->GetPreDisplayTime(); + if ( predisplay > 0.0f ) + { + continue; + } + + float ttl = item->GetTimeToLive(); + if ( ttl > 0.0f ) + { + foundfirstnondeletion = true; + continue; + } + + // Skip the remainder of the items after we find the first/oldest active item + if ( foundfirstnondeletion ) + { + continue; + } + + delete item; + m_Items.Remove( i ); + --i; + --c; + } +} + +void CHudCloseCaption::Reset( void ) +{ + while ( m_Items.Count() > 0 ) + { + CCloseCaptionItem *i = m_Items[ 0 ]; + delete i; + m_Items.Remove( 0 ); + } + + ClearAsyncWork(); + Unlock(); +} + +bool CHudCloseCaption::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ) const +{ + const wchar_t *in = *ppIn; + const wchar_t *oldin = in; + + if ( in[0] != L'<' ) + { + *ppIn += ( oldin - in ); + return false; + } + + args[ 0 ] = 0; + cmd[ 0 ]= 0; + wchar_t *out = cmd; + in++; + while ( *in != L'\0' && *in != L':' && *in != L'>' && !isspace( *in ) ) + { + *out++ = *in++; + } + *out = L'\0'; + + if ( *in != L':' ) + { + *ppIn += ( in - oldin ); + return true; + } + + in++; + out = args; + while ( *in != L'\0' && *in != L'>' ) + { + *out++ = *in++; + } + *out = L'\0'; + + //if ( *in == L'>' ) + // in++; + + *ppIn += ( in - oldin ); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *stream - +// *findcmd - +// value - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CHudCloseCaption::GetFloatCommandValue( const wchar_t *stream, const wchar_t *findcmd, float& value ) const +{ + const wchar_t *curpos = stream; + + for ( ; curpos && *curpos != L'\0'; ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + if ( !wcscmp( cmd, findcmd ) ) + { + value = (float)wcstod( args, NULL ); + return true; + } + continue; + } + } + + return false; +} + + +bool CHudCloseCaption::StreamHasCommand( const wchar_t *stream, const wchar_t *findcmd ) const +{ + const wchar_t *curpos = stream; + + for ( ; curpos && *curpos != L'\0'; ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + if ( !wcscmp( cmd, findcmd ) ) + { + return true; + } + continue; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: It's blank or only comprised of whitespace/space characters... +// Input : *stream - +// Output : static bool +//----------------------------------------------------------------------------- +static bool IsAllSpaces( const wchar_t *stream ) +{ + const wchar_t *p = stream; + while ( *p != L'\0' ) + { + if ( !iswspace( *p ) ) + return false; + + p++; + } + + return true; +} + +bool CHudCloseCaption::StreamHasCommand( const wchar_t *stream, const wchar_t *search ) +{ + for ( const wchar_t *curpos = stream; curpos && *curpos != L'\0'; ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + if ( !wcscmp( cmd, search ) ) + { + return true; + } + } + } + return false; +} + +void CHudCloseCaption::Process( const wchar_t *stream, float duration, const char *tokenstream, bool fromplayer, bool direct ) +{ + if ( !direct ) + { + if ( !closecaption.GetBool() ) + { + Reset(); + return; + } + + // If we're locked, ignore all closecaption commands + if ( m_bLocked ) + return; + } + + // Nothing to do... + if ( IsAllSpaces( stream) ) + { + return; + } + + // If subtitling, don't show sfx captions at all + if ( cc_subtitles.GetBool() && StreamHasCommand( stream, L"sfx" ) ) + { + return; + } + + bool valid = true; + if ( !wcsncmp( stream, L"!!!", wcslen( L"!!!" ) ) ) + { + // It's in the text file, but hasn't been translated... + valid = false; + } + + if ( !wcsncmp( stream, L"-->", wcslen( L"-->" ) ) ) + { + // It's in the text file, but hasn't been translated... + valid = false; + + if ( cc_captiontrace.GetInt() < 2 ) + { + if ( cc_captiontrace.GetInt() == 1 ) + { + Msg( "Missing caption for '%s'\n", tokenstream ); + } + + return; + } + } + + float lifespan = duration + cc_linger_time.GetFloat(); + + float addedlife = 0.0f; + + if ( m_Items.Count() > 0 ) + { + // Get the remaining life span of the last item + CCloseCaptionItem *final = m_Items[ m_Items.Count() - 1 ]; + float prevlife = final->GetTimeToLive(); + + if ( prevlife > lifespan ) + { + addedlife = prevlife - lifespan; + } + + lifespan = MAX( lifespan, prevlife ); + } + + float delay = 0.0f; + float override_duration = 0.0f; + + wchar_t phrase[ MAX_CAPTION_CHARACTERS ]; + wchar_t *out = phrase; + + for ( const wchar_t *curpos = stream; curpos && *curpos != L'\0'; ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + const wchar_t *prevpos = curpos; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + if ( !wcscmp( cmd, L"delay" ) ) + { + + // End current phrase + *out = L'\0'; + + if ( wcslen( phrase ) > 0 ) + { + CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer ); + m_Items.AddToTail( item ); + if ( StreamHasCommand( phrase, L"sfx" ) ) + { + // SFX show up instantly. + item->SetPreDisplayTime( 0.0f ); + } + + if ( GetFloatCommandValue( phrase, L"len", override_duration ) ) + { + item->SetTimeToLive( override_duration ); + } + } + + // Start new phrase + out = phrase; + + // Delay must be positive + delay = MAX( 0.0f, (float)wcstod( args, NULL ) ); + + continue; + } + + int copychars = curpos - prevpos; + while ( --copychars >= 0 ) + { + *out++ = *prevpos++; + } + } + + *out++ = *curpos; + } + + // End final phrase, if any + *out = L'\0'; + if ( wcslen( phrase ) > 0 ) + { + CCloseCaptionItem *item = new CCloseCaptionItem( phrase, lifespan, addedlife, delay, valid, fromplayer ); + m_Items.AddToTail( item ); + + if ( StreamHasCommand( phrase, L"sfx" ) ) + { + // SFX show up instantly. + item->SetPreDisplayTime( 0.0f ); + } + + if ( GetFloatCommandValue( phrase, L"len", override_duration ) ) + { + item->SetTimeToLive( override_duration ); + item->SetInitialLifeSpan( override_duration ); + } + } +} + +void CHudCloseCaption::CreateFonts( void ) +{ + vgui::IScheme *pScheme = vgui::scheme()->GetIScheme( GetScheme() ); + + m_hFonts[CCFONT_NORMAL] = pScheme->GetFont( "CloseCaption_Normal" ); + + if ( IsPC() ) + { + m_hFonts[CCFONT_BOLD] = pScheme->GetFont( "CloseCaption_Bold" ); + m_hFonts[CCFONT_ITALIC] = pScheme->GetFont( "CloseCaption_Italic" ); + m_hFonts[CCFONT_ITALICBOLD] = pScheme->GetFont( "CloseCaption_BoldItalic" ); + } + else + { + m_hFonts[CCFONT_SMALL] = pScheme->GetFont( "CloseCaption_Small" ); + } + + m_nLineHeight = MAX( 6, vgui::surface()->GetFontTall( m_hFonts[ CCFONT_NORMAL ] ) ); +} + +struct WorkUnitParams +{ + WorkUnitParams() + { + Q_memset( stream, 0, sizeof( stream ) ); + out = stream; + x = 0; + y = 0; + width = 0; + bold = italic = false; + clr = Color( 255, 255, 255, 255 ); + newline = false; + font = 0; + } + + ~WorkUnitParams() + { + } + + void Finalize( int lineheight ) + { + *out = L'\0'; + } + + void Next( int lineheight ) + { + // Restart output + Q_memset( stream, 0, sizeof( stream ) ); + out = stream; + + x += width; + + width = 0; + // Leave bold, italic and color alone!!! + if ( newline ) + { + newline = false; + x = 0; + y += lineheight; + } + } + + int GetFontNumber() + { + return CHudCloseCaption::GetFontNumber( bold, italic ); + } + + wchar_t stream[ MAX_CAPTION_CHARACTERS ]; + wchar_t *out; + + int x; + int y; + int width; + bool bold; + bool italic; + Color clr; + bool newline; + vgui::HFont font; +}; + +void CHudCloseCaption::AddWorkUnit( CCloseCaptionItem *item, + WorkUnitParams& params ) +{ + params.Finalize( vgui::surface()->GetFontTall( params.font ) ); + +#ifdef WIN32 + if ( wcslen( params.stream ) > 0 ) +#else + // params.stream is still in ucs2 format here so just do a basic zero compare for length or just space + if ( ((uint16 *)params.stream)[0] != 0 && ((uint16 *)params.stream)[0] != 32 ) +#endif + { + CCloseCaptionWorkUnit *wu = new CCloseCaptionWorkUnit(); + + wu->SetStream( params.stream ); + wu->SetColor( params.clr ); + wu->SetBold( params.bold ); + wu->SetItalic( params.italic ); + wu->SetWidth( params.width ); + wu->SetHeight( vgui::surface()->GetFontTall( params.font ) ); + wu->SetPos( params.x, params.y ); + wu->SetFont( params.font ); + wu->SetFadeStart( 0 ); + + int curheight = item->GetHeight(); + int curwidth = item->GetWidth(); + + curheight = MAX( curheight, params.y + wu->GetHeight() ); + curwidth = MAX( curwidth, params.x + params.width ); + + item->SetHeight( curheight ); + item->SetWidth( curwidth ); + + // Add it + item->AddWork( wu ); + + params.Next( vgui::surface()->GetFontTall( params.font ) ); + } +} + +void CHudCloseCaption::ComputeStreamWork( int available_width, CCloseCaptionItem *item ) +{ + // Start with a clean param block + WorkUnitParams params; + + const wchar_t *curpos = item->GetStream(); + int streamlen = wcslen( curpos ); + CUtlVector< Color > colorStack; + + const wchar_t *most_recent_space = NULL; + int most_recent_space_w = -1; + + for ( ; curpos && *curpos != L'\0'; ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + if ( !wcscmp( cmd, L"cr" ) ) + { + params.newline = true; + AddWorkUnit( item, params); + } + else if ( !wcscmp( cmd, L"clr" ) ) + { + AddWorkUnit( item, params ); + + if ( args[0] == 0 && colorStack.Count()>= 2) + { + colorStack.Remove( colorStack.Count() - 1 ); + params.clr = colorStack[ colorStack.Count() - 1 ]; + } + else + { + int r = 0, g = 0, b = 0; + Color newcolor; + if ( 3 == swscanf( args, L"%i,%i,%i", &r, &g, &b ) ) + { + newcolor = Color( r, g, b, 255 ); + colorStack.AddToTail( newcolor ); + params.clr = colorStack[ colorStack.Count() - 1 ]; + } + } + } + else if ( !wcscmp( cmd, L"playerclr" ) ) + { + AddWorkUnit( item, params ); + + if ( args[0] == 0 && colorStack.Count()>= 2) + { + colorStack.Remove( colorStack.Count() - 1 ); + params.clr = colorStack[ colorStack.Count() - 1 ]; + } + else + { + // player and npc color selector + // e.g.,. 255,255,255:200,200,200 + int pr = 0, pg = 0, pb = 0, nr = 0, ng = 0, nb = 0; + Color newcolor; + if ( 6 == swscanf( args, L"%i,%i,%i:%i,%i,%i", &pr, &pg, &pb, &nr, &ng, &nb ) ) + { + newcolor = item->IsFromPlayer() ? Color( pr, pg, pb, 255 ) : Color( nr, ng, nb, 255 ); + colorStack.AddToTail( newcolor ); + params.clr = colorStack[ colorStack.Count() - 1 ]; + } + } + } + else if ( !wcscmp( cmd, L"I" ) ) + { + AddWorkUnit( item, params ); + params.italic = !params.italic; + } + else if ( !wcscmp( cmd, L"B" ) ) + { + AddWorkUnit( item, params ); + params.bold = !params.bold; + } + + continue; + } + + int font; + if ( IsPC() ) + { + font = params.GetFontNumber(); + } + else + { + font = streamlen >= cc_smallfontlength.GetInt() ? CCFONT_SMALL : CCFONT_NORMAL; + } + vgui::HFont useF = m_hFonts[font]; + params.font = useF; + + int w, h; + + wchar_t sz[2]; + sz[ 0 ] = *curpos; + sz[ 1 ] = L'\0'; + vgui::surface()->GetTextSize( useF, sz, w, h ); + + if ( ( params.x + params.width ) + w > available_width ) + { + if ( most_recent_space && curpos >= most_recent_space + 1 ) + { + // Roll back to previous space character if there is one... + int goback = curpos - most_recent_space - 1; + params.out -= ( goback + 1 ); + params.width = most_recent_space_w; + + wchar_t *extra = new wchar_t[ goback + 1 ]; + wcsncpy( extra, most_recent_space + 1, goback ); + extra[ goback ] = L'\0'; + + params.newline = true; + AddWorkUnit( item, params ); + + wcsncpy( params.out, extra, goback ); + params.out += goback; + int textw, texth; + vgui::surface()->GetTextSize( useF, extra, textw, texth ); + + params.width = textw; + + delete[] extra; + + most_recent_space = NULL; + most_recent_space_w = -1; + } + else + { + params.newline = true; + AddWorkUnit( item, params ); + } + } + *params.out++ = *curpos; + params.width += w; + + if ( iswspace( *curpos ) ) + { + most_recent_space = curpos; + most_recent_space_w = params.width; + } + } + + // Add the final unit. + params.newline = true; + AddWorkUnit( item, params ); + + item->SetSizeComputed( true ); + + // DumpWork( item ); +} + +void CHudCloseCaption:: DumpWork( CCloseCaptionItem *item ) +{ + int c = item->GetNumWorkUnits(); + for ( int i = 0 ; i < c; ++i ) + { + CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i ); + wu->Dump(); + } +} + +void CHudCloseCaption::DrawStream( wrect_t &rcText, wrect_t &rcWindow, CCloseCaptionItem *item, int iFadeLine, float flFadeLineAlpha ) +{ + int c = item->GetNumWorkUnits(); + + wrect_t rcOut; + + float alpha = item->GetAlpha( m_flItemHiddenTime, m_flItemFadeInTime, m_flItemFadeOutTime ); + + for ( int i = 0 ; i < c; ++i ) + { + int x = 0; + int y = 0; + + CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i ); + + vgui::HFont useF = wu->GetFont(); + + wu->GetPos( x, y ); + + rcOut.left = rcText.left + x + 3; + rcOut.right = rcOut.left + wu->GetWidth(); + rcOut.top = rcText.top + y; + rcOut.bottom = rcOut.top + wu->GetHeight(); + + // Adjust alpha to handle fade in/out at the top & bottom of the element. + // Used for single commentary entries that are too big to fit into the element. + float flLineAlpha = alpha; + if ( i == iFadeLine ) + { + flLineAlpha *= flFadeLineAlpha; + } + else if ( rcOut.top < rcWindow.top ) + { + // We're off the top of the element, so don't draw + continue; + } + else if ( rcOut.top > rcWindow.bottom ) + { + float flFadeHeight = (float)wu->GetHeight() * 0.25; + float flDist = (float)(rcOut.top - rcWindow.bottom) / flFadeHeight; + flDist = Bias( flDist, 0.2 ); + if ( flDist > 1 ) + continue; + + flLineAlpha *= 1.0 - flDist; + } + + Color useColor = wu->GetColor(); + + useColor[ 3 ] *= flLineAlpha; + + if ( !item->IsValid() ) + { + useColor = Color( 255, 255, 255, 255 * flLineAlpha ); + rcOut.right += 2; + vgui::surface()->DrawSetColor( Color( 100, 100, 40, 255 * flLineAlpha ) ); + vgui::surface()->DrawFilledRect( rcOut.left, rcOut.top, rcOut.right, rcOut.bottom ); + } + + vgui::surface()->DrawSetTextFont( useF ); + vgui::surface()->DrawSetTextPos( rcOut.left, rcOut.top ); + vgui::surface()->DrawSetTextColor( useColor ); + vgui::surface()->DrawPrintText( wu->GetStream(), wcslen( wu->GetStream() ) ); + } +} + +bool CHudCloseCaption::GetNoRepeatValue( const wchar_t *caption, float &retval ) +{ + retval = 0.0f; + const wchar_t *curpos = caption; + + for ( ; curpos && *curpos != L'\0'; ++curpos ) + { + wchar_t cmd[ 256 ]; + wchar_t args[ 256 ]; + + if ( SplitCommand( &curpos, cmd, args ) ) + { + if ( !wcscmp( cmd, L"norepeat" ) ) + { + retval = (float)wcstod( args, NULL ); + return true; + } + continue; + } + } + return false; +} + +bool CHudCloseCaption::CaptionTokenLessFunc( const CaptionRepeat &lhs, const CaptionRepeat &rhs ) +{ + return ( lhs.m_nTokenIndex < rhs.m_nTokenIndex ); +} + +static bool CaptionTrace( const char *token ) +{ + static CUtlSymbolTable s_MissingCloseCaptions; + + // Make sure we only show the message once + if ( UTL_INVAL_SYMBOL == s_MissingCloseCaptions.Find( token ) ) + { + s_MissingCloseCaptions.AddString( token ); + return true; + } + + return false; +} + +static ConVar cc_sentencecaptionnorepeat( "cc_sentencecaptionnorepeat", "4", 0, "How often a sentence can repeat." ); + +int CRCString( const char *str ) +{ + int len = Q_strlen( str ); + CRC32_t crc; + CRC32_Init( &crc ); + CRC32_ProcessBuffer( &crc, str, len ); + CRC32_Final( &crc ); + + return ( int )crc; +} + +class CAsyncCaption +{ +public: + CAsyncCaption() : + m_flDuration( 0.0f ), + m_bIsStream( false ), + m_bFromPlayer( false ) + { + } + + ~CAsyncCaption() + { + int c = m_Tokens.Count(); + for ( int i = 0; i < c; ++i ) + { + delete m_Tokens[ i ]; + } + m_Tokens.Purge(); + } + + void StartRequesting( CHudCloseCaption *hudCloseCaption, CUtlVector< AsyncCaption_t >& directories ) + { + // Issue pending async requests for each token in string + int c = m_Tokens.Count(); + for ( int i = 0; i < c; ++i ) + { + caption_t *caption = m_Tokens[ i ]; + Assert( !caption->stream ); + Assert( caption->dirindex >= 0 ); + + CaptionLookup_t& entry = directories[ caption->fileindex ].m_CaptionDirectory[ caption->dirindex ]; + + // Request this block, and if it's there, it'll call OnDataLoaded immediately + g_AsyncCaptionResourceManager.PollForAsyncLoading( hudCloseCaption, caption->fileindex, entry.blockNum ); + } + } + + void OnDataArrived( CUtlVector< AsyncCaption_t >& directories, int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData ) + { + int c = m_Tokens.Count(); + for ( int i = 0; i < c; ++i ) + { + caption_t *caption = m_Tokens[ i ]; + if ( caption->stream != NULL ) + continue; + + // Lookup the data + CaptionLookup_t &entry = directories[ nFileIndex ].m_CaptionDirectory[ caption->dirindex ]; + if ( entry.blockNum != nBlockNum ) + continue; + +#ifdef WIN32 + const wchar_t *pIn = ( const wchar_t *)&pData->m_pBlockData[ entry.offset ]; + caption->stream = new wchar_t[ entry.length >> 1 ]; + memcpy( (void *)caption->stream, pIn, entry.length ); +#else + // we persist to disk as ucs2 so convert back to real unicode here + caption->stream = new wchar_t[ entry.length ]; + V_UCS2ToUnicode( (ucs2 *)&pData->m_pBlockData[ entry.offset ], caption->stream, entry.length*sizeof(wchar_t) ); +#endif + } + } + + void ProcessAsyncWork( CHudCloseCaption *hudCloseCaption, CUtlVector< AsyncCaption_t >& directories ) + { + int c = m_Tokens.Count(); + for ( int i = 0; i < c; ++i ) + { + caption_t *caption = m_Tokens[ i ]; + if ( caption->stream != NULL ) + continue; + + CaptionLookup_t& entry = directories[ caption->fileindex].m_CaptionDirectory[ caption->dirindex ]; + + // Request this block, and if it's there, it'll call OnDataLoaded immediately + g_AsyncCaptionResourceManager.PollForAsyncLoading( hudCloseCaption, caption->fileindex, entry.blockNum ); + } + } + + bool GetStream( OUT_Z_BYTECAP(bufSizeInBytes) wchar_t *buf, int bufSizeInBytes ) + { + Assert( bufSizeInBytes >= sizeof(buf[0]) ); + buf[ 0 ] = L'\0'; + + int c = m_Tokens.Count(); + for ( int i = 0; i < c; ++i ) + { + caption_t *caption = m_Tokens[ i ]; + if ( caption->stream == NULL ) + { + return false; + } + } + + unsigned int curlen = 0; + unsigned int maxlen = bufSizeInBytes / sizeof( wchar_t ); + + // Compose full stream from tokens + for ( int i = 0; i < c; ++i ) + { + caption_t *caption = m_Tokens[ i ]; + int len = wcslen( caption->stream ) + 1; + if ( curlen + len >= maxlen ) + break; + + wcscat( buf, caption->stream ); + if ( i < c - 1 ) + { + wcscat( buf, L" " ); + } + + curlen += len; + } + + return true; + } + + bool IsStream() const + { + return m_bIsStream; + } + + void SetIsStream( bool state ) + { + m_bIsStream = state; + } + + void AddRandomToken( CUtlVector< AsyncCaption_t >& directories ) + { + int dc = directories.Count(); + int fileindex = RandomInt( 0, dc - 1 ); + + int c = directories[ fileindex ].m_CaptionDirectory.Count(); + int idx = RandomInt( 0, c - 1 ); + + caption_t *caption = new caption_t; + char foo[ 32 ]; + Q_snprintf( foo, sizeof( foo ), "%d", idx ); + caption->token = strdup( foo ); + caption->dirindex = idx; + caption->stream = NULL; + caption->fileindex = fileindex; + + m_Tokens.AddToTail( caption ); + } + + bool AddToken + ( + CUtlVector< AsyncCaption_t >& directories, + const char *token + ) + { + CaptionLookup_t search; + search.SetHash( token ); + + int idx = -1; + int i; + int dc = directories.Count(); + for ( i = 0; i < dc; ++i ) + { + idx = directories[ i ].m_CaptionDirectory.Find( search ); + if ( idx == directories[ i ].m_CaptionDirectory.InvalidIndex() ) + continue; + + break; + } + + if ( i >= dc || idx == -1 ) + return false; + + caption_t *caption = new caption_t; + caption->token = strdup( token ); + caption->dirindex = idx; + caption->stream = NULL; + caption->fileindex = i; + + m_Tokens.AddToTail( caption ); + return true; + } + + int Count() const + { + return m_Tokens.Count(); + } + + const char *GetToken( int index ) + { + return m_Tokens[ index ]->token; + } + + void GetOriginalStream( char *buf, size_t bufsize ) + { + buf[ 0 ] = 0; + int c = Count(); + for ( int i = 0 ; i < c; ++i ) + { + Q_strncat( buf, GetToken( i ), bufsize, COPY_ALL_CHARACTERS ); + if ( i != c - 1 ) + { + Q_strncat( buf, " ", bufsize, COPY_ALL_CHARACTERS ); + } + } + } + + void SetDuration( float t ) + { + m_flDuration = t; + } + + float GetDuration() + { + return m_flDuration; + } + + bool IsFromPlayer() + { + return m_bFromPlayer; + } + + void SetFromPlayer( bool state ) + { + m_bFromPlayer = state; + } + + bool IsDirect() + { + return m_bDirect; + } + + void SetDirect( bool state ) + { + m_bDirect = state; + } +private: + float m_flDuration; + bool m_bIsStream : 1; + bool m_bFromPlayer : 1; + bool m_bDirect : 1; + + struct caption_t + { + caption_t() : + token( 0 ), + dirindex( -1 ), + fileindex( -1 ), + stream( 0 ) + { + } + + ~caption_t() + { + free( token ); + delete[] stream; + } + + void SetStream( const wchar_t *in ) + { + delete[] stream; + stream = 0; + if ( !in ) + return; + + int len = wcslen( in ); + stream = new wchar_t[ len + 1 ]; + wcsncpy( stream, in, len + 1 ); + } + + char *token; + int dirindex; + int fileindex; + wchar_t *stream; + }; + + CUtlVector< caption_t * > m_Tokens; +}; + +void CHudCloseCaption::ProcessAsyncWork() +{ + int i; + for( i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); i = m_AsyncWork.Next( i ) ) + { + // check for data arrival + CAsyncCaption *item = m_AsyncWork[ i ]; + item->ProcessAsyncWork( this, m_AsyncCaptions ); + } + // Now operate on any new data which arrived + for( i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); ) + { + int n = m_AsyncWork.Next( i ); + + CAsyncCaption *item = m_AsyncWork[ i ]; + wchar_t stream[ MAX_CAPTION_CHARACTERS ]; + + // If we get to the first item with pending async work, stop processing + if ( !item->GetStream( stream, sizeof( stream ) ) ) + { + break; + } + + if ( stream[ 0 ] != L'\0' ) + { + char original[ 512 ]; + item->GetOriginalStream( original, sizeof( original ) ); + + // Process it now + if ( item->IsStream() ) + { + _ProcessSentenceCaptionStream( item->Count(), original, stream ); + } + else + { + _ProcessCaption( stream, original, item->GetDuration(), item->IsFromPlayer(), item->IsDirect() ); + } + } + + m_AsyncWork.Remove( i ); + delete item; + + i = n; + } +} + +void CHudCloseCaption::ClearAsyncWork() +{ + for ( int i = m_AsyncWork.Head(); i != m_AsyncWork.InvalidIndex(); i = m_AsyncWork.Next( i ) ) + { + CAsyncCaption *item = m_AsyncWork[ i ]; + delete item; + } + m_AsyncWork.Purge(); +} + +extern void Hack_FixEscapeChars( char *str ); + +void CHudCloseCaption::ProcessCaptionDirect( const char *tokenname, float duration, bool fromplayer /* = false */ ) +{ + m_bVisibleDueToDirect = true; + + char token[ 512 ]; + Q_strncpy( token, tokenname, sizeof( token ) ); + if ( Q_strstr( token, "\\" ) ) + { + Hack_FixEscapeChars( token ); + } + + ProcessCaption( token, duration, fromplayer, true ); +} + +void CHudCloseCaption::PlayRandomCaption() +{ + CAsyncCaption *async = new CAsyncCaption; + async->SetIsStream( false ); + async->AddRandomToken( m_AsyncCaptions ); + async->SetDuration( RandomFloat( 1.0f, 3.0f ) ); + async->SetFromPlayer( RandomInt( 0, 1 ) == 0 ? true : false ); + async->StartRequesting( this, m_AsyncCaptions ); + m_AsyncWork.AddToTail( async ); +} + +bool CHudCloseCaption::AddAsyncWork( const char *tokenstream, bool bIsStream, float duration, bool fromplayer, bool direct /* = false */ ) +{ + bool bret = true; + + CAsyncCaption *async = new CAsyncCaption(); + async->SetIsStream( bIsStream ); + async->SetDirect( direct ); + if ( !bIsStream ) + { + bret = async->AddToken + ( + m_AsyncCaptions, + tokenstream + ); + } + else + { + // The first token from the stream is the name of the sentence + char tokenname[ 512 ]; + tokenname[ 0 ] = 0; + const char *p = tokenstream; + p = nexttoken( tokenname, p, ' ' ); + // p points to reset of sentence tokens, build up a unicode string from them... + while ( p && Q_strlen( tokenname ) > 0 ) + { + p = nexttoken( tokenname, p, ' ' ); + + if ( Q_strlen( tokenname ) == 0 ) + break; + + async->AddToken + ( + m_AsyncCaptions, + tokenname + ); + } + } + + m_AsyncWork.AddToTail( async ); + + async->SetDuration( duration ); + async->SetFromPlayer( fromplayer ); + // Do this last as the block might be resident already and this will finish immediately... + async->StartRequesting( this, m_AsyncCaptions ); + return bret; +} + + +void CHudCloseCaption::ProcessSentenceCaptionStream( const char *tokenstream ) +{ + float interval = cc_sentencecaptionnorepeat.GetFloat(); + interval = clamp( interval, 0.1f, 60.0f ); + + // The first token from the stream is the name of the sentence + char tokenname[ 512 ]; + + tokenname[ 0 ] = 0; + + const char *p = tokenstream; + + p = nexttoken( tokenname, p, ' ' ); + + if ( Q_strlen( tokenname ) > 0 ) + { + // Use it to check for "norepeat" rules + CaptionRepeat entry; + entry.m_nTokenIndex = CRCString( tokenname ); + + int idx = m_CloseCaptionRepeats.Find( entry ); + if ( m_CloseCaptionRepeats.InvalidIndex() == idx ) + { + entry.m_flLastEmitTime = gpGlobals->curtime; + entry.m_nLastEmitTick = gpGlobals->tickcount; + entry.m_flInterval = interval; + m_CloseCaptionRepeats.Insert( entry ); + } + else + { + CaptionRepeat &entry = m_CloseCaptionRepeats[ idx ]; + if ( gpGlobals->curtime < ( entry.m_flLastEmitTime + entry.m_flInterval ) ) + { + return; + } + + entry.m_flLastEmitTime = gpGlobals->curtime; + entry.m_nLastEmitTick = gpGlobals->tickcount; + } + } + + AddAsyncWork( tokenstream, true, 0.0f, false ); +} + +void CHudCloseCaption::_ProcessSentenceCaptionStream( int wordCount, const char *tokenstream, const wchar_t *caption_full ) +{ + if ( wcslen( caption_full ) > 0 ) + { + Process( caption_full, ( wordCount + 1 ) * 0.75f, tokenstream, false /*never from player!*/ ); + } +} + +bool CHudCloseCaption::ProcessCaption( const char *tokenname, float duration, bool fromplayer /* = false */, bool direct /* = false */ ) +{ + return AddAsyncWork( tokenname, false, duration, fromplayer, direct ); +} + +void CHudCloseCaption::_ProcessCaption( const wchar_t *caption, const char *tokenname, float duration, bool fromplayer, bool direct ) +{ + // Get the string for the token + float interval = 0.0f; + bool hasnorepeat = GetNoRepeatValue( caption, interval ); + + CaptionRepeat entry; + entry.m_nTokenIndex = CRCString( tokenname ); + + int idx = m_CloseCaptionRepeats.Find( entry ); + if ( m_CloseCaptionRepeats.InvalidIndex() == idx ) + { + entry.m_flLastEmitTime = gpGlobals->curtime; + entry.m_nLastEmitTick = gpGlobals->tickcount; + entry.m_flInterval = interval; + m_CloseCaptionRepeats.Insert( entry ); + } + else + { + CaptionRepeat &entry = m_CloseCaptionRepeats[ idx ]; + + // Interval of 0.0 means just don't double emit on same tick # + if ( entry.m_flInterval <= 0.0f ) + { + if ( gpGlobals->tickcount <= entry.m_nLastEmitTick ) + { + return; + } + } + else if ( hasnorepeat ) + { + if ( gpGlobals->curtime < ( entry.m_flLastEmitTime + entry.m_flInterval ) ) + { + return; + } + } + + entry.m_flLastEmitTime = gpGlobals->curtime; + entry.m_nLastEmitTick = gpGlobals->tickcount; + } + + Process( caption, duration, tokenname, fromplayer, direct ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pszName - +// iSize - +// *pbuf - +//----------------------------------------------------------------------------- +void CHudCloseCaption::MsgFunc_CloseCaption(bf_read &msg) +{ + char tokenname[ 512 ]; + msg.ReadString( tokenname, sizeof( tokenname ) ); + float duration = msg.ReadShort() * 0.1f; + byte flagbyte = msg.ReadByte(); + bool warnonmissing = flagbyte & CLOSE_CAPTION_WARNIFMISSING ? true : false; + bool fromplayer = flagbyte & CLOSE_CAPTION_FROMPLAYER ? true : false; + bool bIsMale = flagbyte & CLOSE_CAPTION_GENDER_MALE ? true : false; + bool bIsFemale = flagbyte & CLOSE_CAPTION_GENDER_FEMALE ? true : false; + + if ( warnonmissing && !IsX360() ) + { + wchar_t *pcheck = g_pVGuiLocalize->Find( tokenname ); + if ( !pcheck ) + { + Warning( "No caption found for '%s'\n", tokenname ); + } + } + + char szTestName[ 512 ]; + if ( bIsMale || bIsFemale ) + { + Q_snprintf( szTestName, sizeof( szTestName ), "%s_%s", tokenname, bIsMale ? "male" : "female" ); + // If the gender-ified version exists, use it, otherwise fall through and pass the non-gender string to the cc system for processing + if ( ProcessCaption( szTestName , duration, fromplayer ) ) + { + return; + } + } + + ProcessCaption( tokenname, duration, fromplayer ); +} + +int CHudCloseCaption::GetFontNumber( bool bold, bool italic ) +{ + if ( IsPC() && ( bold || italic ) ) + { + if( bold && italic ) + { + return CHudCloseCaption::CCFONT_ITALICBOLD; + } + + if ( bold ) + { + return CHudCloseCaption::CCFONT_BOLD; + } + + if ( italic ) + { + return CHudCloseCaption::CCFONT_ITALIC; + } + } + + return CHudCloseCaption::CCFONT_NORMAL; +} + +void CHudCloseCaption::Flush() +{ + g_AsyncCaptionResourceManager.Flush(); +} + +void CHudCloseCaption::InitCaptionDictionary( const char *dbfile ) +{ + if ( m_CurrentLanguage.IsValid() && !Q_stricmp( m_CurrentLanguage.String(), dbfile ) ) + return; + + m_CurrentLanguage = dbfile; + + m_AsyncCaptions.Purge(); + + g_AsyncCaptionResourceManager.Clear(); + + char searchPaths[4096]; + filesystem->GetSearchPath( "GAME", true, searchPaths, sizeof( searchPaths ) ); + + for ( char *path = strtok( searchPaths, ";" ); path; path = strtok( NULL, ";" ) ) + { + if ( IsX360() && ( filesystem->GetDVDMode() == DVDMODE_STRICT ) && !V_stristr( path, ".zip" ) ) + { + // only want zip paths + continue; + } + + char fullpath[MAX_PATH]; + Q_snprintf( fullpath, sizeof( fullpath ), "%s%s", path, dbfile ); + Q_FixSlashes( fullpath ); + + if ( IsX360() ) + { + char fullpath360[MAX_PATH]; + UpdateOrCreateCaptionFile( fullpath, fullpath360, sizeof( fullpath360 ) ); + Q_strncpy( fullpath, fullpath360, sizeof( fullpath ) ); + } + + FileHandle_t fh = filesystem->Open( fullpath, "rb" ); + if ( FILESYSTEM_INVALID_HANDLE != fh ) + { + MEM_ALLOC_CREDIT(); + + CUtlBuffer dirbuffer; + + AsyncCaption_t& entry = m_AsyncCaptions[ m_AsyncCaptions.AddToTail() ]; + + // Read the header + filesystem->Read( &entry.m_Header, sizeof( entry.m_Header ), fh ); + if ( entry.m_Header.magic != COMPILED_CAPTION_FILEID ) + Error( "Invalid file id for %s\n", fullpath ); + if ( entry.m_Header.version != COMPILED_CAPTION_VERSION ) + Error( "Invalid file version for %s\n", fullpath ); + if ( entry.m_Header.directorysize < 0 || entry.m_Header.directorysize > 64 * 1024 ) + Error( "Invalid directory size %d for %s\n", entry.m_Header.directorysize, fullpath ); + //if ( entry.m_Header.blocksize != MAX_BLOCK_SIZE ) + // Error( "Invalid block size %d, expecting %d for %s\n", entry.m_Header.blocksize, MAX_BLOCK_SIZE, fullpath ); + + int directoryBytes = entry.m_Header.directorysize * sizeof( CaptionLookup_t ); + entry.m_CaptionDirectory.EnsureCapacity( entry.m_Header.directorysize ); + dirbuffer.EnsureCapacity( directoryBytes ); + + filesystem->Read( dirbuffer.Base(), directoryBytes, fh ); + filesystem->Close( fh ); + + entry.m_CaptionDirectory.CopyArray( (const CaptionLookup_t *)dirbuffer.PeekGet(), entry.m_Header.directorysize ); + entry.m_CaptionDirectory.RedoSort( true ); + + entry.m_DataBaseFile = fullpath; + } + } + + g_AsyncCaptionResourceManager.SetDbInfo( m_AsyncCaptions ); +} + +void CHudCloseCaption::OnFinishAsyncLoad( int nFileIndex, int nBlockNum, AsyncCaptionData_t *pData ) +{ + // Fill in data for all users of pData->m_nBlockNum + FOR_EACH_LL( m_AsyncWork, i ) + { + CAsyncCaption *item = m_AsyncWork[ i ]; + item->OnDataArrived( m_AsyncCaptions, nFileIndex, nBlockNum, pData ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCloseCaption::Lock( void ) +{ + if ( !IsXbox() ) + m_bLocked = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHudCloseCaption::Unlock( void ) +{ + m_bLocked = false; +} + +static int EmitCaptionCompletion( const char *partial, char commands[ COMMAND_COMPLETION_MAXITEMS ][ COMMAND_COMPLETION_ITEM_LENGTH ] ) +{ + int current = 0; + if ( !g_pVGuiLocalize || IsX360() ) + return current; + + const char *cmdname = "cc_emit"; + char *substring = NULL; + int substringLen = 0; + if ( Q_strstr( partial, cmdname ) && strlen(partial) > strlen(cmdname) + 1 ) + { + substring = (char *)partial + strlen( cmdname ) + 1; + substringLen = strlen(substring); + } + + StringIndex_t i = g_pVGuiLocalize->GetFirstStringIndex(); + + while ( i != INVALID_LOCALIZE_STRING_INDEX && + current < COMMAND_COMPLETION_MAXITEMS ) + { + const char *ccname = g_pVGuiLocalize->GetNameByIndex( i ); + if ( ccname ) + { + if ( !substring || !Q_strncasecmp( ccname, substring, substringLen ) ) + { + Q_snprintf( commands[ current ], sizeof( commands[ current ] ), "%s %s", cmdname, ccname ); + current++; + } + } + i = g_pVGuiLocalize->GetNextStringIndex( i ); + } + + return current; +} + +CON_COMMAND_F_COMPLETION( cc_emit, "Emits a closed caption", 0, EmitCaptionCompletion ) +{ + if ( args.ArgC() != 2 ) + { + Msg( "usage: cc_emit tokenname\n" ); + return; + } + + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->ProcessCaption( args[1], 5.0f ); + } +} + +CON_COMMAND( cc_random, "Emits a random caption" ) +{ + int count = 1; + if ( args.ArgC() == 2 ) + { + count = MAX( 1, atoi( args[ 1 ] ) ); + } + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + for ( int i = 0; i < count; ++i ) + { + hudCloseCaption->PlayRandomCaption(); + } + } +} + + +CON_COMMAND( cc_flush, "Flushes async'd captions." ) +{ + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->Flush(); + } +} + +CON_COMMAND( cc_showblocks, "Toggles showing which blocks are pending/loaded async." ) +{ + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->TogglePaintDebug(); + } +} + +void OnCaptionLanguageChanged( IConVar *pConVar, const char *pOldString, float flOldValue ) +{ + if ( !g_pVGuiLocalize ) + return; + + ConVarRef var( pConVar ); + + char fn[ 512 ]; + Q_snprintf( fn, sizeof( fn ), "resource/closecaption_%s.txt", var.GetString() ); + + // Re-adding the file, even if it's "english" will overwrite the tokens as needed + if ( !IsX360() ) + { + g_pVGuiLocalize->AddFile( "resource/closecaption_%language%.txt", "GAME", true ); + } + + char uilanguage[ 64 ]; + engine->GetUILanguage( uilanguage, sizeof( uilanguage ) ); + + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + + // If it's not the default, load the language on top of the user's default language + if ( Q_strlen( var.GetString() ) > 0 && Q_stricmp( var.GetString(), uilanguage ) ) + { + if ( !IsX360() ) + { + if ( g_pFullFileSystem->FileExists( fn ) ) + { + g_pVGuiLocalize->AddFile( fn, "GAME", true ); + } + else + { + char fallback[ 512 ]; + Q_snprintf( fallback, sizeof( fallback ), "resource/closecaption_%s.txt", uilanguage ); + + Msg( "%s not found\n", fn ); + Msg( "%s will be used\n", fallback ); + } + } + + if ( hudCloseCaption ) + { + char dbfile [ 512 ]; + Q_snprintf( dbfile, sizeof( dbfile ), "resource/closecaption_%s.dat", var.GetString() ); + hudCloseCaption->InitCaptionDictionary( dbfile ); + } + } + else + { + if ( hudCloseCaption ) + { + char dbfile [ 512 ]; + Q_snprintf( dbfile, sizeof( dbfile ), "resource/closecaption_%s.dat", uilanguage ); + hudCloseCaption->InitCaptionDictionary( dbfile ); + } + } + DevMsg( "cc_lang = %s\n", var.GetString() ); +} + + + +ConVar cc_lang( "cc_lang", "", FCVAR_ARCHIVE, "Current close caption language (emtpy = use game UI language)", OnCaptionLanguageChanged ); + +CON_COMMAND( cc_findsound, "Searches for soundname which emits specified text." ) +{ + if ( args.ArgC() != 2 ) + { + Msg( "usage: cc_findsound 'substring'\n" ); + return; + } + + CHudCloseCaption *hudCloseCaption = GET_HUDELEMENT( CHudCloseCaption ); + if ( hudCloseCaption ) + { + hudCloseCaption->FindSound( args.Arg( 1 ) ); + } +} + +void CHudCloseCaption::FindSound( char const *pchANSI ) +{ + // Now do the searching + ucs2 stream[ 1024 ]; + char streamANSI[ 1024 ]; + + for ( int i = 0 ; i < m_AsyncCaptions.Count(); ++i ) + { + AsyncCaption_t &data = m_AsyncCaptions[ i ]; + + byte *block = new byte[ data.m_Header.blocksize ]; + + int nLoadedBlock = -1; + + Q_memset( block, 0, data.m_Header.blocksize ); + CaptionDictionary_t &dict = data.m_CaptionDirectory; + for ( int j = 0; j < dict.Count(); ++j ) + { + CaptionLookup_t &lu = dict[ j ]; + + int blockNum = lu.blockNum; + + const char *dbname = data.m_DataBaseFile.String(); + + // Try and reload it + char fn[ 256 ]; + Q_strncpy( fn, dbname, sizeof( fn ) ); + Q_FixSlashes( fn ); + + asynccaptionparams_t params; + params.dbfile = fn; + params.blocktoload = blockNum; + params.blocksize = data.m_Header.blocksize; + params.blockoffset = data.m_Header.dataoffset + blockNum *data.m_Header.blocksize; + params.fileindex = i; + + if ( blockNum != nLoadedBlock ) + { + nLoadedBlock = blockNum; + + FileHandle_t fh = filesystem->Open( fn, "rb" ); + filesystem->Seek( fh, params.blockoffset, FILESYSTEM_SEEK_CURRENT ); + filesystem->Read( block, data.m_Header.blocksize, fh ); + filesystem->Close( fh ); + } + + // Now we have the data + const ucs2 *pIn = ( const ucs2 *)&block[ lu.offset ]; + Q_memcpy( (void *)stream, pIn, MIN( lu.length, sizeof( stream ) ) ); + + // Now search for search text + V_UCS2ToUTF8( stream, streamANSI, sizeof( streamANSI ) ); + streamANSI[ sizeof( streamANSI ) - 1 ] = 0; + + if ( Q_stristr( streamANSI, pchANSI ) ) + { + CaptionLookup_t search; + + Msg( "found '%s' in %s\n", streamANSI, fn ); + + // Now find the sounds that will hash to this + for ( int k = soundemitterbase->First(); k != soundemitterbase->InvalidIndex(); k = soundemitterbase->Next( k ) ) + { + char const *pchSoundName = soundemitterbase->GetSoundName( k ); + + // Hash it + + search.SetHash( pchSoundName ); + + if ( search.hash == lu.hash ) + { + Msg( " '%s' matches\n", pchSoundName ); + } + } + + if ( IsPC() ) + { + for ( int r = g_pVGuiLocalize->GetFirstStringIndex(); r != INVALID_LOCALIZE_STRING_INDEX; r = g_pVGuiLocalize->GetNextStringIndex( r ) ) + { + const char *strName = g_pVGuiLocalize->GetNameByIndex( r ); + + search.SetHash( strName ); + + if ( search.hash == lu.hash ) + { + Msg( " '%s' localization matches\n", strName ); + } + } + } + } + } + + delete[] block; + } +} -- cgit v1.2.3