Team Fortress 2 Source Code as on 22/4/2020
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.

635 lines
15 KiB

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