You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2913 lines
68 KiB
2913 lines
68 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
|
|
#include "cbase.h" |
|
#include <ctype.h> |
|
#include "sentence.h" |
|
#include "hud_closecaption.h" |
|
#include "tier1/strtools.h" |
|
#include <vgui_controls/Controls.h> |
|
#include <vgui/IVGui.h> |
|
#include <vgui/ISurface.h> |
|
#include <vgui/IScheme.h> |
|
#include <vgui/ILocalize.h> |
|
#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 * sizeof(ucs2) ]; |
|
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 ); |
|
|
|
SetProportional( true ); |
|
|
|
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", true ); |
|
|
|
if ( IsPC() ) |
|
{ |
|
m_hFonts[CCFONT_BOLD] = pScheme->GetFont( "CloseCaption_Bold", true ); |
|
m_hFonts[CCFONT_ITALIC] = pScheme->GetFont( "CloseCaption_Italic", true ); |
|
m_hFonts[CCFONT_ITALICBOLD] = pScheme->GetFont( "CloseCaption_BoldItalic", true ); |
|
} |
|
else |
|
{ |
|
m_hFonts[CCFONT_SMALL] = pScheme->GetFont( "CloseCaption_Small", true ); |
|
} |
|
|
|
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[ caption->fileindex ].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 << 1 ); |
|
#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; |
|
} |
|
}
|
|
|