Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

638 lines
15 KiB

  1. //========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "quakedef.h"
  9. #include "cdll_int.h"
  10. #include "draw.h"
  11. #include "tmessage.h"
  12. #include "common.h"
  13. #include "characterset.h"
  14. #include "mem_fgets.h"
  15. #include "tier0/icommandline.h"
  16. #include "tier0/platform.h"
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. #define MSGFILE_NAME 0
  20. #define MSGFILE_TEXT 1
  21. #define MAX_MESSAGES 600 // I don't know if this table will balloon like every other feature in Half-Life
  22. // But, for now, I've set this to a reasonable value
  23. // Defined in other files.
  24. static characterset_t g_WhiteSpace;
  25. client_textmessage_t gMessageParms;
  26. client_textmessage_t *gMessageTable = NULL;
  27. int gMessageTableCount = 0;
  28. char gNetworkTextMessageBuffer[MAX_NETMESSAGE][512];
  29. const char *gNetworkMessageNames[MAX_NETMESSAGE] = { NETWORK_MESSAGE1, NETWORK_MESSAGE2, NETWORK_MESSAGE3, NETWORK_MESSAGE4, NETWORK_MESSAGE5, NETWORK_MESSAGE6 };
  30. client_textmessage_t gNetworkTextMessage[MAX_NETMESSAGE] =
  31. {
  32. 0, // effect
  33. 255,255,255,255,
  34. 255,255,255,255,
  35. -1.0f, // x
  36. -1.0f, // y
  37. 0.0f, // fadein
  38. 0.0f, // fadeout
  39. 0.0f, // holdtime
  40. 0.0f, // fxTime,
  41. NULL,//pVGuiSchemeFontName (NULL == default)
  42. NETWORK_MESSAGE1, // pName message name.
  43. gNetworkTextMessageBuffer[0] // pMessage
  44. };
  45. char gDemoMessageBuffer[512];
  46. client_textmessage_t tm_demomessage =
  47. {
  48. 0, // effect
  49. 255,255,255,255,
  50. 255,255,255,255,
  51. -1.0f, // x
  52. -1.0f, // y
  53. 0.0f, // fadein
  54. 0.0f, // fadeout
  55. 0.0f, // holdtime
  56. 0.0f, // fxTime,
  57. NULL,// pVGuiSchemeFontName (NULL == default)
  58. DEMO_MESSAGE, // pName message name.
  59. gDemoMessageBuffer // pMessage
  60. };
  61. static client_textmessage_t orig_demo_message = tm_demomessage;
  62. static void TextMessageParse( byte *pMemFile, int fileSize );
  63. // The string "pText" is assumed to have all whitespace from both ends cut out
  64. int IsComment( char *pText )
  65. {
  66. if ( pText )
  67. {
  68. int length = strlen( pText );
  69. if ( length >= 2 && pText[0] == '/' && pText[1] == '/' )
  70. return 1;
  71. // No text?
  72. if ( length > 0 )
  73. return 0;
  74. }
  75. // No text is a comment too
  76. return 1;
  77. }
  78. // The string "pText" is assumed to have all whitespace from both ends cut out
  79. int IsStartOfText( char *pText )
  80. {
  81. if ( pText )
  82. {
  83. if ( pText[0] == '{' )
  84. return 1;
  85. }
  86. return 0;
  87. }
  88. // The string "pText" is assumed to have all whitespace from both ends cut out
  89. int IsEndOfText( char *pText )
  90. {
  91. if ( pText )
  92. {
  93. if ( pText[0] == '}' )
  94. return 1;
  95. }
  96. return 0;
  97. }
  98. #if 0
  99. int IsWhiteSpace( char space )
  100. {
  101. if ( space == ' ' || space == '\t' || space == '\r' || space == '\n' )
  102. return 1;
  103. return 0;
  104. }
  105. #else
  106. #define IsWhiteSpace(space) IN_CHARACTERSET( g_WhiteSpace, space )
  107. #endif
  108. const char *SkipSpace( const char *pText )
  109. {
  110. if ( pText )
  111. {
  112. int pos = 0;
  113. while ( pText[pos] && IsWhiteSpace( pText[pos] ) )
  114. pos++;
  115. return pText + pos;
  116. }
  117. return NULL;
  118. }
  119. const char *SkipText( const char *pText )
  120. {
  121. if ( pText )
  122. {
  123. int pos = 0;
  124. while ( pText[pos] && !IsWhiteSpace( pText[pos] ) )
  125. pos++;
  126. return pText + pos;
  127. }
  128. return NULL;
  129. }
  130. int ParseFloats( const char *pText, float *pFloat, int count )
  131. {
  132. const char *pTemp = pText;
  133. int index = 0;
  134. while ( pTemp && count > 0 )
  135. {
  136. // Skip current token / float
  137. pTemp = SkipText( pTemp );
  138. // Skip any whitespace in between
  139. pTemp = SkipSpace( pTemp );
  140. if ( pTemp )
  141. {
  142. // Parse a float
  143. pFloat[index] = (float)atof( pTemp );
  144. count--;
  145. index++;
  146. }
  147. }
  148. if ( count == 0 )
  149. return 1;
  150. return 0;
  151. }
  152. int ParseString( char const *pText, char *buf, size_t bufsize )
  153. {
  154. const char *pTemp = pText;
  155. // Skip current token / float
  156. pTemp = SkipText( pTemp );
  157. // Skip any whitespace in between
  158. pTemp = SkipSpace( pTemp );
  159. if ( pTemp )
  160. {
  161. char const *pStart = pTemp;
  162. pTemp = SkipText( pTemp );
  163. int len = MIN( pTemp - pStart + 1, (int)bufsize - 1 );
  164. Q_strncpy( buf, pStart, len );
  165. buf[ len ] = 0;
  166. return 1;
  167. }
  168. return 0;
  169. }
  170. // Trims all whitespace from the front and end of a string
  171. void TrimSpace( const char *source, char *dest )
  172. {
  173. int start, end, length;
  174. start = 0;
  175. end = strlen( source );
  176. while ( source[start] && IsWhiteSpace( source[start] ) )
  177. start++;
  178. end--;
  179. while ( end > 0 && IsWhiteSpace( source[end] ) )
  180. end--;
  181. end++;
  182. length = end - start;
  183. if ( length > 0 )
  184. memcpy( dest, source + start, length );
  185. else
  186. length = 0;
  187. // Terminate the dest string
  188. dest[ length ] = 0;
  189. }
  190. int IsToken( const char *pText, const char *pTokenName )
  191. {
  192. if ( !pText || !pTokenName )
  193. return 0;
  194. if ( StringHasPrefix( pText+1, pTokenName ) )
  195. return 1;
  196. return 0;
  197. }
  198. static char g_pchSkipName[ 64 ];
  199. int ParseDirective( const char *pText )
  200. {
  201. if ( pText && pText[0] == '$' )
  202. {
  203. float tempFloat[8];
  204. if ( IsToken( pText, "position" ) )
  205. {
  206. if ( ParseFloats( pText, tempFloat, 2 ) )
  207. {
  208. gMessageParms.x = tempFloat[0];
  209. gMessageParms.y = tempFloat[1];
  210. }
  211. }
  212. else if ( IsToken( pText, "effect" ) )
  213. {
  214. if ( ParseFloats( pText, tempFloat, 1 ) )
  215. {
  216. gMessageParms.effect = (int)tempFloat[0];
  217. }
  218. }
  219. else if ( IsToken( pText, "fxtime" ) )
  220. {
  221. if ( ParseFloats( pText, tempFloat, 1 ) )
  222. {
  223. gMessageParms.fxtime = tempFloat[0];
  224. }
  225. }
  226. else if ( IsToken( pText, "color2" ) )
  227. {
  228. if ( ParseFloats( pText, tempFloat, 3 ) )
  229. {
  230. gMessageParms.r2 = (int)tempFloat[0];
  231. gMessageParms.g2 = (int)tempFloat[1];
  232. gMessageParms.b2 = (int)tempFloat[2];
  233. }
  234. }
  235. else if ( IsToken( pText, "color" ) )
  236. {
  237. if ( ParseFloats( pText, tempFloat, 3 ) )
  238. {
  239. gMessageParms.r1 = (int)tempFloat[0];
  240. gMessageParms.g1 = (int)tempFloat[1];
  241. gMessageParms.b1 = (int)tempFloat[2];
  242. }
  243. }
  244. else if ( IsToken( pText, "fadein" ) )
  245. {
  246. if ( ParseFloats( pText, tempFloat, 1 ) )
  247. {
  248. gMessageParms.fadein = tempFloat[0];
  249. }
  250. }
  251. else if ( IsToken( pText, "fadeout" ) )
  252. {
  253. if ( ParseFloats( pText, tempFloat, 3 ) )
  254. {
  255. gMessageParms.fadeout = tempFloat[0];
  256. }
  257. }
  258. else if ( IsToken( pText, "holdtime" ) )
  259. {
  260. if ( ParseFloats( pText, tempFloat, 3 ) )
  261. {
  262. gMessageParms.holdtime = tempFloat[0];
  263. }
  264. }
  265. else if ( IsToken( pText, "boxsize" ) )
  266. {
  267. if ( ParseFloats( pText, tempFloat, 1 ) )
  268. {
  269. gMessageParms.bRoundedRectBackdropBox = tempFloat[0] != 0.0f;
  270. gMessageParms.flBoxSize = tempFloat[0];
  271. }
  272. }
  273. else if ( IsToken( pText, "boxcolor" ) )
  274. {
  275. if ( ParseFloats( pText, tempFloat, 4 ) )
  276. {
  277. for ( int i = 0; i < 4; ++i )
  278. {
  279. gMessageParms.boxcolor[ i ] = (byte)(int)tempFloat[ i ];
  280. }
  281. }
  282. }
  283. else if ( IsToken( pText, "clearmessage" ) )
  284. {
  285. if ( ParseString( pText, g_pchSkipName, sizeof( g_pchSkipName ) ) )
  286. {
  287. if ( !g_pchSkipName[ 0 ] || !Q_stricmp( g_pchSkipName, "0" ) )
  288. {
  289. gMessageParms.pClearMessage = NULL;
  290. }
  291. else
  292. {
  293. gMessageParms.pClearMessage = g_pchSkipName;
  294. }
  295. }
  296. }
  297. else
  298. {
  299. ConDMsg("Unknown token: %s\n", pText );
  300. }
  301. return 1;
  302. }
  303. return 0;
  304. }
  305. #define NAME_HEAP_SIZE 16384
  306. void TextMessageParse( byte *pMemFile, int fileSize )
  307. {
  308. char buf[512], trim[512];
  309. char *pCurrentText=0, *pNameHeap;
  310. char currentName[512], nameHeap[ NAME_HEAP_SIZE ];
  311. int lastNamePos;
  312. int mode = MSGFILE_NAME; // Searching for a message name
  313. int lineNumber, filePos, lastLinePos;
  314. int messageCount;
  315. client_textmessage_t textMessages[ MAX_MESSAGES ];
  316. int i, nameHeapSize, textHeapSize, messageSize;
  317. intp nameOffset;
  318. lastNamePos = 0;
  319. lineNumber = 0;
  320. filePos = 0;
  321. lastLinePos = 0;
  322. messageCount = 0;
  323. bool bSpew = CommandLine()->FindParm( "-textmessagedebug" ) ? true : false;
  324. CharacterSetBuild( &g_WhiteSpace, " \r\n\t" );
  325. while( memfgets( pMemFile, fileSize, &filePos, buf, 512 ) != NULL )
  326. {
  327. if(messageCount>=MAX_MESSAGES)
  328. {
  329. Sys_Error("tmessage::TextMessageParse : messageCount>=MAX_MESSAGES");
  330. }
  331. TrimSpace( buf, trim );
  332. switch( mode )
  333. {
  334. case MSGFILE_NAME:
  335. if ( IsComment( trim ) ) // Skip comment lines
  336. break;
  337. if ( ParseDirective( trim ) ) // Is this a directive "$command"?, if so parse it and break
  338. break;
  339. if ( IsStartOfText( trim ) )
  340. {
  341. mode = MSGFILE_TEXT;
  342. pCurrentText = (char*)(pMemFile + filePos);
  343. break;
  344. }
  345. if ( IsEndOfText( trim ) )
  346. {
  347. ConDMsg("Unexpected '}' found, line %d\n", lineNumber );
  348. return;
  349. }
  350. Q_strncpy( currentName, trim, sizeof( currentName ) );
  351. break;
  352. case MSGFILE_TEXT:
  353. if ( IsEndOfText( trim ) )
  354. {
  355. int length = strlen(currentName);
  356. // Save name on name heap
  357. if ( lastNamePos + length > 8192 )
  358. {
  359. ConDMsg("Error parsing file!\n" );
  360. return;
  361. }
  362. Q_strcpy( nameHeap + lastNamePos, currentName );
  363. // Terminate text in-place in the memory file (it's temporary memory that will be deleted)
  364. // If the string starts with #, it's a localization string and we don't
  365. // want the \n (or \r) on the end or the Find() lookup will fail (so subtract 2)
  366. if ( pCurrentText && pCurrentText[0] && pCurrentText[0] == '#' && lastLinePos > 1 &&
  367. ( ( pMemFile[lastLinePos - 2] == '\n' ) || ( pMemFile[lastLinePos - 2] == '\r' ) ) )
  368. {
  369. pMemFile[ lastLinePos - 2 ] = 0;
  370. }
  371. else
  372. {
  373. pMemFile[ lastLinePos - 1 ] = 0;
  374. }
  375. // Save name/text on heap
  376. textMessages[ messageCount ] = gMessageParms;
  377. textMessages[ messageCount ].pName = nameHeap + lastNamePos;
  378. lastNamePos += strlen(currentName) + 1;
  379. if ( gMessageParms.pClearMessage )
  380. {
  381. Q_strncpy( nameHeap + lastNamePos, textMessages[ messageCount ].pClearMessage, Q_strlen( textMessages[ messageCount ].pClearMessage ) + 1 );
  382. textMessages[ messageCount ].pClearMessage = nameHeap + lastNamePos;
  383. lastNamePos += Q_strlen( textMessages[ messageCount ].pClearMessage ) + 1;
  384. }
  385. textMessages[ messageCount ].pMessage = pCurrentText;
  386. if ( bSpew )
  387. {
  388. client_textmessage_t *m = &textMessages[ messageCount ];
  389. Msg( "%d %s\n",
  390. messageCount, m->pName ? m->pName : "(null)" );
  391. Msg( " effect %d, color1(%d,%d,%d,%d), color2(%d,%d,%d,%d)\n",
  392. m->effect, m->r1, m->g1, m->b1, m->a1, m->r2, m->g2, m->b2, m->a2 );
  393. Msg( " pos %f,%f, fadein %f fadeout %f hold %f fxtime %f\n",
  394. m->x, m->y, m->fadein, m->fadeout, m->holdtime, m->fxtime );
  395. Msg( " '%s'\n", m->pMessage ? m->pMessage : "(null)" );
  396. Msg( " box %s, size %f, color(%d,%d,%d,%d)\n",
  397. m->bRoundedRectBackdropBox ? "yes" : "no", m->flBoxSize, m->boxcolor[ 0 ], m->boxcolor[ 1 ], m->boxcolor[ 2 ], m->boxcolor[ 3 ] );
  398. if ( m->pClearMessage )
  399. {
  400. Msg( " will clear '%s'\n", m->pClearMessage );
  401. }
  402. }
  403. messageCount++;
  404. // Reset parser to search for names
  405. mode = MSGFILE_NAME;
  406. break;
  407. }
  408. if ( IsStartOfText( trim ) )
  409. {
  410. ConDMsg("Unexpected '{' found, line %d\n", lineNumber );
  411. return;
  412. }
  413. break;
  414. }
  415. lineNumber++;
  416. lastLinePos = filePos;
  417. if ( messageCount >= MAX_MESSAGES )
  418. {
  419. ConMsg("WARNING: TOO MANY MESSAGES IN TITLES.TXT, MAX IS %d\n", MAX_MESSAGES );
  420. break;
  421. }
  422. }
  423. ConDMsg("Parsed %d text messages\n", messageCount );
  424. nameHeapSize = lastNamePos;
  425. textHeapSize = 0;
  426. for ( i = 0; i < messageCount; i++ )
  427. textHeapSize += strlen( textMessages[i].pMessage ) + 1;
  428. messageSize = (messageCount * sizeof(client_textmessage_t));
  429. // Must malloc because we need to be able to clear it after initialization
  430. gMessageTable = (client_textmessage_t *)malloc( textHeapSize + nameHeapSize + messageSize );
  431. // Copy table over
  432. memcpy( gMessageTable, textMessages, messageSize );
  433. // Copy Name heap
  434. pNameHeap = ((char *)gMessageTable) + messageSize;
  435. memcpy( pNameHeap, nameHeap, nameHeapSize );
  436. nameOffset = pNameHeap - gMessageTable[0].pName;
  437. // Copy text & fixup pointers
  438. pCurrentText = pNameHeap + nameHeapSize;
  439. for ( i = 0; i < messageCount; i++ )
  440. {
  441. gMessageTable[i].pName += nameOffset; // Adjust name pointer (parallel buffer)
  442. if ( gMessageTable[ i ].pClearMessage )
  443. {
  444. gMessageTable[ i ].pClearMessage += nameOffset;
  445. }
  446. Q_strcpy( pCurrentText, gMessageTable[i].pMessage ); // Copy text over
  447. gMessageTable[i].pMessage = pCurrentText;
  448. pCurrentText += strlen( pCurrentText ) + 1;
  449. }
  450. #if _DEBUG
  451. if ( (pCurrentText - (char *)gMessageTable) != (textHeapSize + nameHeapSize + messageSize) )
  452. ConMsg("Overflow text message buffer!!!!!\n");
  453. #endif
  454. gMessageTableCount = messageCount;
  455. }
  456. void TextMessageShutdown( void )
  457. {
  458. // Clear out any old data that's sitting around.
  459. if ( gMessageTable )
  460. {
  461. free( gMessageTable );
  462. gMessageTable = NULL;
  463. }
  464. }
  465. void TextMessageInit( void )
  466. {
  467. int fileSize;
  468. byte *pMemFile;
  469. // Clear out any old data that's sitting around.
  470. if ( gMessageTable )
  471. {
  472. free( gMessageTable );
  473. gMessageTable = NULL;
  474. }
  475. pMemFile = COM_LoadFile( "scripts/titles.txt", 5, &fileSize );
  476. if ( pMemFile )
  477. {
  478. TextMessageParse( pMemFile, fileSize );
  479. free( pMemFile );
  480. }
  481. int i;
  482. for ( i = 0; i < MAX_NETMESSAGE; i++ )
  483. {
  484. gNetworkTextMessage[ i ].pMessage =
  485. gNetworkTextMessageBuffer[ i ];
  486. }
  487. }
  488. void TextMessage_DemoMessage( const char *pszMessage, float fFadeInTime, float fFadeOutTime, float fHoldTime )
  489. {
  490. if ( !pszMessage || !pszMessage[0] )
  491. return;
  492. // Restore
  493. tm_demomessage = orig_demo_message;
  494. Q_strncpy( gDemoMessageBuffer, (char *)pszMessage, sizeof( gDemoMessageBuffer ) );
  495. tm_demomessage.fadein = fFadeInTime;
  496. tm_demomessage.fadeout = fFadeOutTime;
  497. tm_demomessage.holdtime = fHoldTime;
  498. }
  499. //-----------------------------------------------------------------------------
  500. // Purpose:
  501. // Input : *pszMessage -
  502. // *message -
  503. //-----------------------------------------------------------------------------
  504. void TextMessage_DemoMessageFull( const char *pszMessage, client_textmessage_t const *message )
  505. {
  506. Assert( message );
  507. if ( !message )
  508. return;
  509. if ( !pszMessage || !pszMessage[0] )
  510. return;
  511. memcpy( &tm_demomessage, message, sizeof( tm_demomessage ) );
  512. tm_demomessage.pMessage = orig_demo_message.pMessage;
  513. tm_demomessage.pName = orig_demo_message.pName;
  514. Q_strncpy( gDemoMessageBuffer, pszMessage, sizeof( gDemoMessageBuffer ) );
  515. }
  516. client_textmessage_t *TextMessageGet( const char *pName )
  517. {
  518. if (!Q_stricmp( pName, DEMO_MESSAGE ))
  519. return &tm_demomessage;
  520. // HACKHACK -- add 4 "channels" of network text
  521. if (!Q_stricmp( pName, NETWORK_MESSAGE1 ))
  522. return gNetworkTextMessage;
  523. else if (!Q_stricmp( pName, NETWORK_MESSAGE2 ))
  524. return gNetworkTextMessage + 1;
  525. else if (!Q_stricmp( pName, NETWORK_MESSAGE3 ))
  526. return gNetworkTextMessage + 2;
  527. else if (!Q_stricmp( pName, NETWORK_MESSAGE4 ))
  528. return gNetworkTextMessage + 3;
  529. else if (!Q_stricmp( pName, NETWORK_MESSAGE5 ))
  530. return gNetworkTextMessage + 4;
  531. else if (!Q_stricmp( pName, NETWORK_MESSAGE6 ))
  532. return gNetworkTextMessage + 5;
  533. for ( int i = 0; i < gMessageTableCount; i++ )
  534. {
  535. if ( !Q_stricmp( pName, gMessageTable[i].pName ) )
  536. return &gMessageTable[i];
  537. }
  538. return NULL;
  539. }