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.

1315 lines
31 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================
  6. #include <stdio.h>
  7. #include <process.h>
  8. #include <string.h>
  9. #include <windows.h>
  10. #include <sys/stat.h>
  11. #include <time.h>
  12. #include "interface.h"
  13. #include "imysqlwrapper.h"
  14. #include "tier1/utlvector.h"
  15. #include "tier1/utlbuffer.h"
  16. #include "tier1/utlsymbol.h"
  17. #include "tier1/utlstring.h"
  18. #include "tier1/utldict.h"
  19. #include "KeyValues.h"
  20. #include "filesystem_helpers.h"
  21. #include "tier2/tier2.h"
  22. #include "filesystem.h"
  23. #include "interface.h"
  24. #include "vstdlib/random.h"
  25. #include "jpeglib/jpeglib.h"
  26. static char gamename[ 32 ] = "ep2";
  27. extern IFileSystem *g_pFullFileSystem;
  28. struct Image_t
  29. {
  30. byte *data;
  31. int w, h;
  32. };
  33. bool ReadBitmapRGB( const byte *raw, size_t rawlen, Image_t *image )
  34. {
  35. assert( image );
  36. CUtlBuffer buf;
  37. buf.Put( raw, rawlen );
  38. // int i;
  39. BITMAPFILEHEADER bmfh;
  40. BITMAPINFOHEADER bmih;
  41. int cbBmpBits;
  42. byte *pb;
  43. // Read file header
  44. buf.Get( &bmfh, sizeof( bmfh ) );
  45. // Read info header
  46. buf.Get( &bmih, sizeof( bmih ) );
  47. // Bogus info header check
  48. if (!(bmih.biSize == sizeof( bmih ) && bmih.biPlanes == 1))
  49. return false;
  50. // Bogus bit depth? Only 8-bit supported.
  51. if (bmih.biBitCount != 24)
  52. return false;
  53. // Bogus compression? Only non-compressed supported.
  54. if (bmih.biCompression != BI_RGB )
  55. return false;
  56. image->w = bmih.biWidth;
  57. image->h = bmih.biHeight;
  58. // Read bitmap bits (remainder of file)
  59. cbBmpBits = bmfh.bfSize - buf.TellGet();
  60. assert( cbBmpBits == ( image->w * image->h * 3 ) );
  61. int cbTotalbytes = cbBmpBits;
  62. pb = new byte[ cbBmpBits ];
  63. if (pb == 0)
  64. {
  65. return false;
  66. }
  67. buf.Get( pb, cbBmpBits );
  68. byte *start = pb;
  69. image->data = new byte[ cbTotalbytes ];
  70. int bitrueWidth = (bmih.biWidth + 3) & ~3;
  71. byte *dst = image->data;
  72. int x, y;
  73. for ( y = 0; y < image->h; ++y )
  74. {
  75. int rowstart = 3 * ( image->h - 1 - y ) * bitrueWidth;
  76. byte *row = &start[ rowstart ];
  77. for ( x = 0; x < image->w; ++x )
  78. {
  79. #define USE_GRAYSCALE
  80. #if defined( USE_GRAYSCALE )
  81. int val = row[ 0 ] + row[ 1 ] + row[ 2 ];
  82. val /= 3;
  83. *dst++ = val;
  84. *dst++ = val;
  85. *dst++ = val;
  86. row += 3;
  87. #else
  88. *dst++ = *row++;
  89. *dst++ = *row++;
  90. *dst++ = *row++;
  91. #endif
  92. }
  93. }
  94. delete[] start;
  95. return true;
  96. }
  97. void ParseBugs( CUtlDict< CUtlVector< Vector > *, int >& list, char const *filename )
  98. {
  99. FileHandle_t fh = g_pFullFileSystem->Open( filename, "rb" );
  100. if ( FILESYSTEM_INVALID_HANDLE == fh )
  101. return;
  102. size_t size = g_pFullFileSystem->Size( fh );
  103. char *buffer = new char[ size + 1 ];
  104. g_pFullFileSystem->Read( buffer, size, fh );
  105. buffer[ size ] = 0;
  106. g_pFullFileSystem->Close( fh );
  107. char *data = buffer;
  108. while ( 1 )
  109. {
  110. // Now parse out the data
  111. // First find level:
  112. char *p = strstr( data, "level: " );
  113. if ( !p )
  114. break;
  115. char mapname[ 512 ];
  116. char *i = p + 8;
  117. char *o = mapname;
  118. while ( *i && *i != ' ' && *i != '\r' && *i != '\n' )
  119. *o++ = *i++;
  120. *o = 0;
  121. data += 8;
  122. // Now find the position
  123. p = strstr( data, "setpos " );
  124. if ( !p )
  125. break;
  126. char position[ 512 ];
  127. i = p + 7;
  128. o = position;
  129. while ( *i && *i != ';' )
  130. *o++ = *i++;
  131. *o = 0;
  132. data += 7;
  133. Vector pos;
  134. if ( 3 == sscanf( position, "%f %f %f", &pos.x, &pos.y, &pos.z ) )
  135. {
  136. int idx = list.Find( mapname );
  137. if ( idx == list.InvalidIndex() )
  138. {
  139. idx = list.Insert( mapname, new CUtlVector< Vector > );
  140. }
  141. CUtlVector< Vector > *db = list[ idx ];
  142. db->AddToTail( pos );
  143. }
  144. // printf( "map: %s pos %s\n", mapname, position );
  145. }
  146. delete[] buffer;
  147. return;
  148. }
  149. bool WriteBitmap( char const *filename, Image_t *image )
  150. {
  151. FileHandle_t fh = g_pFullFileSystem->Open( filename, "wb" );
  152. if ( FILESYSTEM_INVALID_HANDLE == fh )
  153. return false;
  154. BITMAPFILEHEADER hdr = { 0 };
  155. BITMAPINFOHEADER bi = { 0 };
  156. int w = image->w;
  157. int h = image->h;
  158. int imageSize = image->w * image->h * 3;
  159. // file header
  160. hdr.bfType = 0x4d42; // 'BM'
  161. hdr.bfSize = (DWORD) ( sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + imageSize );
  162. hdr.bfReserved1 = 0;
  163. hdr.bfReserved2 = 0;
  164. hdr.bfOffBits = (DWORD) ( sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) );
  165. g_pFullFileSystem->Write( (LPVOID)&hdr, sizeof(BITMAPFILEHEADER), fh );
  166. // bitmap header
  167. bi.biSize = sizeof(BITMAPINFOHEADER);
  168. bi.biWidth = image->w;
  169. bi.biHeight = image->h;
  170. bi.biPlanes = 1;
  171. bi.biBitCount = 24;
  172. bi.biCompression = BI_RGB;
  173. bi.biSizeImage = 0; //vid.rowbytes * vid.height;
  174. bi.biXPelsPerMeter = 0;
  175. bi.biYPelsPerMeter = 0;
  176. bi.biClrUsed = 0;
  177. bi.biClrImportant = 0;
  178. g_pFullFileSystem->Write( (LPVOID)&bi, sizeof(BITMAPINFOHEADER), fh );
  179. // bitmap bits
  180. byte *hp = new byte[ imageSize ];
  181. Q_memcpy( hp, image->data, imageSize );
  182. byte b;
  183. int i;
  184. // Invert vertically for BMP format
  185. for ( i = 0; i < h / 2; i++ )
  186. {
  187. byte *top = hp + i * w * 3;
  188. byte *bottom = hp + (h - i - 1) * w * 3;
  189. for (int j = 0; j < w * 3; j++)
  190. {
  191. b = *top;
  192. *top = *bottom;
  193. *bottom = b;
  194. top++;
  195. bottom++;
  196. }
  197. }
  198. g_pFullFileSystem->Write( (LPVOID)hp, imageSize, fh );
  199. delete[] hp;
  200. // clean up
  201. g_pFullFileSystem->Close( fh );
  202. return true;
  203. }
  204. //-----------------------------------------------------------------------------
  205. // Purpose: Expanded data destination object for CUtlBuffer output
  206. //-----------------------------------------------------------------------------
  207. struct JPEGDestinationManager_t
  208. {
  209. struct jpeg_destination_mgr pub; // public fields
  210. CUtlBuffer *pBuffer; // target/final buffer
  211. byte *buffer; // start of temp buffer
  212. };
  213. // choose an efficiently bufferaable size
  214. #define OUTPUT_BUF_SIZE 4096
  215. //-----------------------------------------------------------------------------
  216. // Purpose: Initialize destination --- called by jpeg_start_compress
  217. // before any data is actually written.
  218. //-----------------------------------------------------------------------------
  219. METHODDEF(void) init_destination (j_compress_ptr cinfo)
  220. {
  221. JPEGDestinationManager_t *dest = ( JPEGDestinationManager_t *) cinfo->dest;
  222. // Allocate the output buffer --- it will be released when done with image
  223. dest->buffer = (byte *)
  224. (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
  225. OUTPUT_BUF_SIZE * sizeof(byte));
  226. dest->pub.next_output_byte = dest->buffer;
  227. dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
  228. }
  229. //-----------------------------------------------------------------------------
  230. // Purpose: Empty the output buffer --- called whenever buffer fills up.
  231. // Input : boolean -
  232. //-----------------------------------------------------------------------------
  233. METHODDEF(boolean) empty_output_buffer (j_compress_ptr cinfo)
  234. {
  235. JPEGDestinationManager_t *dest = ( JPEGDestinationManager_t * ) cinfo->dest;
  236. CUtlBuffer *buf = dest->pBuffer;
  237. // Add some data
  238. buf->Put( dest->buffer, OUTPUT_BUF_SIZE );
  239. dest->pub.next_output_byte = dest->buffer;
  240. dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
  241. return TRUE;
  242. }
  243. //-----------------------------------------------------------------------------
  244. // Purpose: Terminate destination --- called by jpeg_finish_compress
  245. // after all data has been written. Usually needs to flush buffer.
  246. //
  247. // NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
  248. // application must deal with any cleanup that should happen even
  249. // for error exit.
  250. //-----------------------------------------------------------------------------
  251. METHODDEF(void) term_destination (j_compress_ptr cinfo)
  252. {
  253. JPEGDestinationManager_t *dest = (JPEGDestinationManager_t *) cinfo->dest;
  254. size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer;
  255. CUtlBuffer *buf = dest->pBuffer;
  256. /* Write any data remaining in the buffer */
  257. if (datacount > 0)
  258. {
  259. buf->Put( dest->buffer, datacount );
  260. }
  261. }
  262. //-----------------------------------------------------------------------------
  263. // Purpose: Set up functions for writing data to a CUtlBuffer instead of FILE *
  264. //-----------------------------------------------------------------------------
  265. GLOBAL(void) jpeg_UtlBuffer_dest (j_compress_ptr cinfo, CUtlBuffer *pBuffer )
  266. {
  267. JPEGDestinationManager_t *dest;
  268. /* The destination object is made permanent so that multiple JPEG images
  269. * can be written to the same file without re-executing jpeg_stdio_dest.
  270. * This makes it dangerous to use this manager and a different destination
  271. * manager serially with the same JPEG object, because their private object
  272. * sizes may be different. Caveat programmer.
  273. */
  274. if (cinfo->dest == NULL) { /* first time for this JPEG object? */
  275. cinfo->dest = (struct jpeg_destination_mgr *)
  276. (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
  277. sizeof(JPEGDestinationManager_t));
  278. }
  279. dest = ( JPEGDestinationManager_t * ) cinfo->dest;
  280. dest->pub.init_destination = init_destination;
  281. dest->pub.empty_output_buffer = empty_output_buffer;
  282. dest->pub.term_destination = term_destination;
  283. dest->pBuffer = pBuffer;
  284. }
  285. bool ImageToJPEGBuffer( Image_t *image, CUtlBuffer& buf, int quality )
  286. {
  287. #if !defined( _X360 )
  288. // Validate quality level
  289. quality = clamp( quality, 1, 100 );
  290. int imagew = image->w;
  291. int imageh = image->h;
  292. // Allocate space for bits
  293. uint8 *pImage = new uint8[ imagew * 3 * imageh];
  294. if ( !pImage )
  295. {
  296. Msg( "Unable to allocate %i bytes for image\n", imagew * 3 * imageh );
  297. return false;
  298. }
  299. // Get Bits from the material system
  300. // ReadScreenPixels( 0, 0, GetModeWidth(), GetModeHeight(), pImage, IMAGE_FORMAT_RGB888 );
  301. // Copy data in
  302. for ( int y = 0; y < imageh; ++y )
  303. {
  304. byte *row = (byte *)&pImage[ y * imagew * 3 ];
  305. const byte *pSrc = (const byte *)&image->data[ y * imagew * 3 ];
  306. for ( int x = 0; x < imagew; ++x )
  307. {
  308. // BGR to RGB
  309. row[ 0 ] = pSrc[ 2 ];
  310. row[ 1 ] = pSrc[ 1 ];
  311. row[ 2 ] = pSrc[ 0 ];
  312. pSrc += 3;
  313. row += 3;
  314. }
  315. }
  316. JSAMPROW row_pointer[1]; // pointer to JSAMPLE row[s]
  317. int row_stride; // physical row width in image buffer
  318. // stderr handler
  319. struct jpeg_error_mgr jerr;
  320. // compression data structure
  321. struct jpeg_compress_struct cinfo;
  322. row_stride = imagew * 3; // JSAMPLEs per row in image_buffer
  323. // point at stderr
  324. cinfo.err = jpeg_std_error(&jerr);
  325. // create compressor
  326. jpeg_create_compress(&cinfo);
  327. // Hook CUtlBuffer to compression
  328. jpeg_UtlBuffer_dest(&cinfo, &buf );
  329. // image width and height, in pixels
  330. cinfo.image_width = imagew;
  331. cinfo.image_height = imageh;
  332. // RGB is 3 componnent
  333. cinfo.input_components = 3;
  334. // # of color components per pixel
  335. cinfo.in_color_space = JCS_RGB;
  336. // Apply settings
  337. jpeg_set_defaults(&cinfo);
  338. jpeg_set_quality(&cinfo, quality, TRUE );
  339. // Start compressor
  340. jpeg_start_compress(&cinfo, TRUE);
  341. // Write scanlines
  342. while ( cinfo.next_scanline < cinfo.image_height )
  343. {
  344. row_pointer[ 0 ] = &pImage[ cinfo.next_scanline * row_stride ];
  345. jpeg_write_scanlines( &cinfo, row_pointer, 1 );
  346. }
  347. // Finalize image
  348. jpeg_finish_compress(&cinfo);
  349. // Cleanup
  350. jpeg_destroy_compress(&cinfo);
  351. delete[] pImage;
  352. #else
  353. // not supporting
  354. Assert( 0 );
  355. #endif
  356. return true;
  357. }
  358. bool WriteJPeg( char const *filename, Image_t *image )
  359. {
  360. FileHandle_t fh = g_pFullFileSystem->Open( filename, "wb" );
  361. if ( FILESYSTEM_INVALID_HANDLE == fh )
  362. return false;
  363. CUtlBuffer buf;
  364. ImageToJPEGBuffer( image, buf, 90 );
  365. g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh );
  366. // clean up
  367. g_pFullFileSystem->Close( fh );
  368. return true;
  369. }
  370. #include <string>
  371. //-------------------------------------------------
  372. void v_escape_string (std::string& s)
  373. {
  374. if ( !s.size() )
  375. return;
  376. for ( unsigned int i = 0;i<s.size();i++ )
  377. {
  378. switch (s[i])
  379. {
  380. case '\0': /* Must be escaped for "mysql" */
  381. s[i] = '\\';
  382. s.insert(i+1,"0",1); i++;//lint !e534
  383. break;
  384. case '\n': /* Must be escaped for logs */
  385. s[i] = '\\';
  386. s.insert(i+1,"n",1); i++;//lint !e534
  387. break;
  388. case '\r':
  389. s[i] = '\\';
  390. s.insert(i+1,"r",1); i++;//lint !e534
  391. break;
  392. case '\\':
  393. s[i] = '\\';
  394. s.insert(i+1,"\\",1); i++;//lint !e534
  395. break;
  396. case '\"':
  397. s[i] = '\\';
  398. s.insert(i+1,"\"",1); i++;//lint !e534
  399. break;
  400. case '\'': /* Better safe than sorry */
  401. s[i] = '\\';
  402. s.insert(i+1,"\'",1); i++;//lint !e534
  403. break;
  404. case '\032': /* This gives problems on Win32 */
  405. s[i] = '\\';
  406. s.insert(i+1,"Z",1); i++;//lint !e534
  407. break;
  408. default:
  409. break;
  410. }
  411. }
  412. }
  413. void HSLToRGB( Color &out, float *hsl )
  414. {
  415. float sat[3],ctmp[3];
  416. out[ 3 ] = 255;
  417. while (hsl[ 0 ] < 0)
  418. hsl[ 0 ] += 360;
  419. while (hsl[ 0 ] > 360)
  420. hsl[ 0 ] -= 360;
  421. if (hsl[ 0 ] < 120)
  422. {
  423. sat[ 0 ] = (120 - hsl[ 0 ]) / 60.0;
  424. sat[ 1 ] = hsl[ 0 ] / 60.0;
  425. sat[ 2 ] = 0;
  426. }
  427. else if (hsl[ 0 ] < 240)
  428. {
  429. sat[ 0 ] = 0;
  430. sat[ 1 ] = (240 - hsl[ 0 ]) / 60.0;
  431. sat[ 2 ] = (hsl[ 0 ] - 120) / 60.0;
  432. } else
  433. {
  434. sat[ 0 ] = (hsl[ 0 ] - 240) / 60.0;
  435. sat[ 1 ] = 0;
  436. sat[ 2 ] = (360 - hsl[ 0 ]) / 60.0;
  437. }
  438. sat[ 0 ] = min(sat[ 0 ],1.f);
  439. sat[ 1 ] = min(sat[ 1 ],1.f);
  440. sat[ 2 ] = min(sat[ 2 ],1.f);
  441. ctmp[ 0 ] = 2 * hsl[ 1 ] * sat[ 0 ] + (1 - hsl[ 1 ]);
  442. ctmp[ 1 ] = 2 * hsl[ 1 ] * sat[ 1 ] + (1 - hsl[ 1 ]);
  443. ctmp[ 2 ] = 2 * hsl[ 1 ] * sat[ 2 ] + (1 - hsl[ 1 ]);
  444. if (hsl[ 2 ] < 0.5)
  445. {
  446. out[ 0 ] = 255.0f * hsl[ 2 ] * ctmp[ 0 ];
  447. out[ 1 ] = 255.0f * hsl[ 2 ] * ctmp[ 1 ];
  448. out[ 2 ] = 255.0f * hsl[ 2 ] * ctmp[ 2 ];
  449. }
  450. else
  451. {
  452. out[ 0 ] = 255.0f * ( (1 - hsl[ 2 ]) * ctmp[ 0 ] + 2 * hsl[ 2 ] - 1 );
  453. out[ 1 ] = 255.0f * ( (1 - hsl[ 2 ]) * ctmp[ 1 ] + 2 * hsl[ 2 ] - 1 );
  454. out[ 2 ] = 255.0f * ( (1 - hsl[ 2 ]) * ctmp[ 2 ] + 2 * hsl[ 2 ] - 1 );
  455. }
  456. }
  457. inline void DrawColoredRect( Image_t *image, int x, int y, int w, int h, const Color &rgb, float flAlpha )
  458. {
  459. flAlpha = clamp( flAlpha, 0.0f, 1.0f );
  460. float flOneMinusAlpha = 1.0f - flAlpha;
  461. for ( int xx = x; xx < x + w; ++xx )
  462. {
  463. if ( xx < 0 || xx >= image->w )
  464. continue;
  465. for ( int yy = y; yy < y + h; ++yy )
  466. {
  467. if ( yy < 0 || yy >= image->h )
  468. continue;
  469. int offset = yy * image->w * 3 + xx * 3;
  470. byte *dst = &image->data[ offset ];
  471. for ( int i = 0 ; i < 3; ++i )
  472. {
  473. dst[ i ] = (byte)( flOneMinusAlpha * dst[ i ] + flAlpha * rgb[ i ] );
  474. }
  475. }
  476. }
  477. }
  478. void BuildAggregateStats( IMySQL *mysql, char const *pszMapName, char const *whereClause )
  479. {
  480. char q[ 2048 ];
  481. // Got it!!!
  482. std::string mapname;
  483. mapname = pszMapName;
  484. v_escape_string( mapname );
  485. // Now read all the locations from the sql server
  486. Q_snprintf( q, sizeof( q ), "select Sum(Count), Avg(Count), Avg(Seconds)/60.0, Avg(Deaths) from %s_maps where MapName = \'%s\' %s and Seconds < 15000;", gamename, mapname.c_str(), whereClause );
  487. int retcode = mysql->Execute( q );
  488. if ( retcode != 0 )
  489. {
  490. Msg( "Query %s failed\n", q );
  491. return;
  492. }
  493. if ( mysql->SeekToFirstRow() && mysql->NextRow() )
  494. {
  495. int SumCount = mysql->GetColumnValue_Int( 0 );
  496. if ( SumCount == 0 )
  497. {
  498. Msg( "No data for %s\n", mapname.c_str() );
  499. return;
  500. }
  501. float AverageCount = Q_atof( mysql->GetColumnValue_String( 1 ) );
  502. float AvgTime = Q_atof( mysql->GetColumnValue_String( 2 ) );
  503. float AvgDeaths = Q_atof( mysql->GetColumnValue_String( 3 ) );
  504. Msg( "Sessions %d, average sessions %.2f avg time %.2f (minutes) avgdeaths %.2f\n",
  505. SumCount,
  506. AverageCount,
  507. AvgTime,
  508. AvgDeaths );
  509. }
  510. else
  511. {
  512. Msg( "No data for %s\n", mapname.c_str() );
  513. return;
  514. }
  515. // Now show entity stats
  516. Q_snprintf( q, sizeof( q ), "select Entity, Sum(BodyCount), avg(BodyCount), Sum(KilledPlayer), avg(KilledPlayer) from %s_entities where mapname = \'%s\' %s group by Entity;", gamename, mapname.c_str(), whereClause );
  517. retcode = mysql->Execute( q );
  518. if ( retcode != 0 )
  519. {
  520. Msg( "Query %s failed\n", q );
  521. return;
  522. }
  523. Msg( " Entities\n\n" );
  524. Msg( " %32s %10s %10s %15s %10s\n",
  525. "Entity", "Killed", "Avg", "KilledPlayer", "Avg" );
  526. if ( mysql->SeekToFirstRow() )
  527. {
  528. while ( mysql->NextRow() )
  529. {
  530. Msg( " %32s %10d %10.2f %15d %10.2f\n",
  531. mysql->GetColumnValue_String( 0 ),
  532. mysql->GetColumnValue_Int( 1 ),
  533. Q_atof( mysql->GetColumnValue_String( 2 ) ),
  534. mysql->GetColumnValue_Int( 3 ),
  535. Q_atof( mysql->GetColumnValue_String( 4 ) ) );
  536. }
  537. }
  538. // Now show weapon stats
  539. Q_snprintf( q, sizeof( q ), "select Weapon, Sum(Shots), avg(Shots), sum(Hits), avg(Hits), Sum(Damage), Avg(Damage) from %s_weapons where Damage < 100000 and mapname = \'%s\' %s group by Weapon;", gamename, mapname.c_str(), whereClause );
  540. retcode = mysql->Execute( q );
  541. if ( retcode != 0 )
  542. {
  543. Msg( "Query %s failed\n", q );
  544. return;
  545. }
  546. Msg( " Weapons\n" );
  547. Msg( " %32s %10s %10s %15s %10s %15s %10s\n",
  548. "Weapon", "Shots", "Avg", "Hits", "Avg", "Damage", "Avg" );
  549. if ( mysql->SeekToFirstRow() )
  550. {
  551. while ( mysql->NextRow() )
  552. {
  553. Msg( " %32s %10d %10.2f %15d %10.2f %15d %10.2f\n",
  554. mysql->GetColumnValue_String( 0 ),
  555. mysql->GetColumnValue_Int( 1 ),
  556. Q_atof( mysql->GetColumnValue_String( 2 ) ),
  557. mysql->GetColumnValue_Int( 3 ),
  558. Q_atof( mysql->GetColumnValue_String( 4 ) ),
  559. mysql->GetColumnValue_Int( 5 ),
  560. Q_atof( mysql->GetColumnValue_String( 6 ) ));
  561. }
  562. }
  563. // Now show weapon stats
  564. Q_snprintf( q, sizeof( q ), "select count(*)/count(distinct(userid) ) from %s_saves where mapname = \'%s\' %s;", gamename, mapname.c_str(), whereClause );
  565. retcode = mysql->Execute( q );
  566. if ( retcode != 0 )
  567. {
  568. Msg( "Query %s failed\n", q );
  569. return;
  570. }
  571. Msg( " Saves\n" );
  572. if ( mysql->SeekToFirstRow() )
  573. {
  574. while ( mysql->NextRow() )
  575. {
  576. const char *colVal = mysql->GetColumnValue_String( 0 );
  577. float flVal = 0.0f;
  578. if ( colVal )
  579. flVal = Q_atof( colVal );
  580. Msg( " Average Saves per user: %.2f\n", flVal );
  581. }
  582. }
  583. Msg( " Counters\n" );
  584. static char *counters[] =
  585. {
  586. "CRATESSMASHED",
  587. "OBJECTSPUNTED",
  588. "VEHICULARHOMICIDES",
  589. "DISTANCE_INVEHICLE",
  590. "DISTANCE_ONFOOT",
  591. "DISTANCE_ONFOOTSPRINTING",
  592. "FALLINGDEATHS",
  593. "VEHICLE_OVERTURNED",
  594. "LOADGAME_STILLALIVE",
  595. "LOADS",
  596. "SAVES",
  597. "GODMODES",
  598. "NOCLIPS",
  599. "DAMAGETAKEN",
  600. NULL
  601. };
  602. Q_snprintf( q, sizeof( q ), "select Sum(CRATESSMASHED), Avg(CRATESSMASHED),"\
  603. "Sum(OBJECTSPUNTED), Avg(OBJECTSPUNTED),"\
  604. "Sum(VEHICULARHOMICIDES), Avg(VEHICULARHOMICIDES),"\
  605. "Sum(DISTANCE_INVEHICLE), Avg(DISTANCE_INVEHICLE),"\
  606. "Sum(DISTANCE_ONFOOT), Avg(DISTANCE_ONFOOT),"\
  607. "Sum(DISTANCE_ONFOOTSPRINTING), Avg(DISTANCE_ONFOOTSPRINTING),"\
  608. "Sum(FALLINGDEATHS), Avg(FALLINGDEATHS),"\
  609. "Sum(VEHICLE_OVERTURNED), Avg(VEHICLE_OVERTURNED),"\
  610. "Sum(LOADGAME_STILLALIVE), Avg(LOADGAME_STILLALIVE),"\
  611. "Sum(LOADS), Avg(LOADS),"\
  612. "Sum(SAVES), Avg(SAVES),"\
  613. "Sum(GODMODES), Avg(GODMODES),"\
  614. "Sum(NOCLIPS), Avg(NOCLIPS),"\
  615. "Sum(DAMAGETAKEN), Avg(DAMAGETAKEN) "\
  616. "from %s_counters where mapname = \'%s\' %s;", gamename, mapname.c_str(), whereClause );
  617. retcode = mysql->Execute( q );
  618. if ( retcode != 0 )
  619. {
  620. Msg( "Query %s failed\n", q );
  621. return;
  622. }
  623. int i = 0;
  624. Msg( " %32s %20s %20s\n", "Counter", "Total", "Average" );
  625. #define INCHES_TO_MILES ( 1.0 / ( 5280.0 * 12.0 ) )
  626. if ( mysql->SeekToFirstRow() )
  627. {
  628. while ( mysql->NextRow() )
  629. {
  630. while ( counters[ i ] != NULL )
  631. {
  632. int idx = 2 * i;
  633. char const *raw1 = mysql->GetColumnValue_String( idx );
  634. char const *raw2 = mysql->GetColumnValue_String( idx + 1 );
  635. // Msg( "%s raw %s\n%s\n", counters[ i ], raw1, raw2 );
  636. uint64 sum = _atoi64( raw1 );
  637. double avg = Q_atof( raw2 );
  638. if ( Q_stristr( counters[ i ], "DISTANCE_" ) )
  639. {
  640. Msg( " %32s %20.2f %20.2f [miles]\n",
  641. counters[ i ],
  642. (double)sum * INCHES_TO_MILES,
  643. avg * INCHES_TO_MILES );
  644. }
  645. else
  646. {
  647. Msg( " %32s %20I64u %20.2f\n",
  648. counters[ i ],
  649. sum,
  650. avg );
  651. }
  652. ++i;
  653. }
  654. }
  655. }
  656. Msg( "-----------------------------------------------------------\n" );
  657. // Now show generic stats
  658. Q_snprintf( q, sizeof( q ), "select StatName, Sum(Count), avg(Count), sum(Value), avg(Value) from %s_generic where mapname = \'%s\' %s group by StatName;", gamename, mapname.c_str(), whereClause );
  659. retcode = mysql->Execute( q );
  660. if ( retcode != 0 )
  661. {
  662. Msg( "Query %s failed\n", q );
  663. return;
  664. }
  665. Msg( " Generic\n" );
  666. Msg( " %32s %10s %10s %15s %10s\n",
  667. "Name", "Count", "Avg", "Value", "Avg" );
  668. if ( mysql->SeekToFirstRow() )
  669. {
  670. while ( mysql->NextRow() )
  671. {
  672. Msg( " %32s %10d %10.2f %15d %10.2f\n",
  673. mysql->GetColumnValue_String( 0 ),
  674. mysql->GetColumnValue_Int( 1 ),
  675. Q_atof( mysql->GetColumnValue_String( 2 ) ),
  676. mysql->GetColumnValue_Int( 3 ),
  677. Q_atof( mysql->GetColumnValue_String( 4 ) ) );
  678. }
  679. }
  680. }
  681. #define EP2_DEATHS_VERSION "1.1"
  682. void printusage( int argc, char * argv[] )
  683. {
  684. Msg( "%s:\n"\
  685. " Version = " EP2_DEATHS_VERSION "\n"\
  686. " Date [ " __DATE__ " " __TIME__ " ]\n"\
  687. " Copyright Valve 2007. All rights reserved.\n"\
  688. " -game [ep2 | tf] { which game }\n"\
  689. " -bugs bugtestfile { special bug file culled from PVCSTracker report }\n"\
  690. " -imagedir imagedir { directory for mapinfo.res and image .bmps }\n"\
  691. " -deaths { build death images }\n"\
  692. " -scale imagescalefactor (default 1.0)\n"\
  693. " -h sqldbhost\n"\
  694. " -u sqluser\n"\
  695. " -p sqlpw\n"\
  696. " -db sqldb\n"\
  697. " -stress count { run stress testing for image creation }\n"\
  698. " -where 'additional where clause'\n"\
  699. " -stats { spew per level stats for entities, weapons, etc. }\n", argv[ 0 ] );
  700. }
  701. int main(int argc, char* argv[])
  702. {
  703. int i;
  704. char bugfile[ 512 ] = { 0 };
  705. char pathname[ 512 ] = { 0 };
  706. char whereclause[ 1024 ] = { 0 };
  707. float flScale = 1.0f;
  708. char const *host = "";
  709. char const *user = "";
  710. char const *pw = "";
  711. char const *db = "";
  712. bool bBugSpots = false;
  713. bool bBuildImages = false;
  714. bool bBuildStats = false;
  715. bool bStressTest = false;
  716. int stresstestcount = 1000;
  717. for ( i = 1 ; i < argc ; ++i )
  718. {
  719. if (!stricmp(argv[i],"-bugs"))
  720. {
  721. bBugSpots = true;
  722. Q_strncpy( bugfile, argv[i+1], sizeof( bugfile ) );
  723. Msg( " parsing bugs from '%s'\n", bugfile );
  724. ++i;
  725. }
  726. else if ( !stricmp( argv[ i ], "-game" ))
  727. {
  728. Q_strncpy( gamename, argv[i+1], sizeof( gamename ) );
  729. Msg( " death maps for game '%s'\n", gamename );
  730. ++i;
  731. }
  732. else if (!stricmp(argv[i],"-deaths"))
  733. {
  734. bBuildImages = true;
  735. Msg( " generating death images\n" );
  736. }
  737. else if (!stricmp(argv[i],"-imagedir"))
  738. {
  739. Q_strncpy( pathname, argv[i+1], sizeof( pathname ) );
  740. Msg( " image dir '%s'\n", pathname );
  741. ++i;
  742. }
  743. else if (!stricmp(argv[i],"-where"))
  744. {
  745. Q_snprintf( whereclause, sizeof( whereclause ), " and %s", argv[i+1] );
  746. Msg( " using custom where clause '%s'\n", argv[i+1] );
  747. ++i;
  748. }
  749. else if (!stricmp(argv[i],"-h"))
  750. {
  751. host = argv[ i + 1 ];
  752. Msg( " mysql host '%s'\n", host );
  753. ++i;
  754. }
  755. else if (!stricmp(argv[i],"-u"))
  756. {
  757. user = argv[ i + 1 ];
  758. Msg( " mysql user '%s'\n", user );
  759. ++i;
  760. }
  761. else if (!stricmp(argv[i],"-p"))
  762. {
  763. pw = argv[ i + 1 ];
  764. // Msg( " mysql pw '%s'\n", pw );
  765. ++i;
  766. }
  767. else if (!stricmp(argv[i],"-db"))
  768. {
  769. db = argv[ i + 1 ];
  770. Msg( " mysql db '%s'\n", db );
  771. ++i;
  772. }
  773. else if (!stricmp(argv[i],"-scale"))
  774. {
  775. flScale = max( 0.01f, Q_atof ( argv[ i + 1 ] ) );
  776. Msg( " image scale '%g'\n", flScale );
  777. ++i;
  778. }
  779. else if (!stricmp(argv[i],"-stats"))
  780. {
  781. bBuildStats = true;
  782. Msg( " generating per level stats\n" );
  783. }
  784. else if (!stricmp(argv[i],"-stress"))
  785. {
  786. bStressTest = true;
  787. stresstestcount = Q_atoi( argv[ i + 1 ] );
  788. Msg( " performing stress testing count = %d\n", stresstestcount );
  789. ++i;
  790. }
  791. else
  792. break;
  793. }
  794. if ( i != argc )
  795. {
  796. printusage( argc, argv );
  797. return -1;
  798. }
  799. if ( !pathname[ 0 ] )
  800. {
  801. printusage( argc, argv );
  802. return -1;
  803. }
  804. InitDefaultFileSystem();
  805. CUtlDict< CUtlVector< Vector > *, int > raw;
  806. char fn[ 512 ];
  807. Q_snprintf( fn, sizeof( fn ), "%s/mapinfo.res", pathname );
  808. Q_FixSlashes( fn );
  809. Q_strlower( fn );
  810. KeyValues *kv = new KeyValues( "mapinfo.res" );
  811. if ( !kv->LoadFromFile( g_pFullFileSystem, fn, NULL ) )
  812. {
  813. Msg( "Unable to load mapinfo.res file from image directory [%s]\n",
  814. pathname );
  815. exit( -1 );
  816. }
  817. CSysModule *sql = NULL;
  818. CreateInterfaceFn factory = NULL;
  819. IMySQL *mysql = NULL;
  820. bool bSqlOkay = false;
  821. if ( !bBugSpots )
  822. {
  823. sql = Sys_LoadModule( "mysql_wrapper" );
  824. if ( sql )
  825. {
  826. factory = Sys_GetFactory( sql );
  827. if ( factory )
  828. {
  829. mysql = ( IMySQL * )factory( MYSQL_WRAPPER_VERSION_NAME, NULL );
  830. if ( mysql )
  831. {
  832. if ( mysql->InitMySQL( db, host, user, pw ) )
  833. {
  834. bSqlOkay = true;
  835. Msg( "Successfully connected to database %s on host %s, user %s\n", db, host, user );
  836. }
  837. else
  838. {
  839. Msg( "mysql->InitMySQL( %s, %s, %s, [password]) failed\n", db, host, user );
  840. }
  841. }
  842. else
  843. {
  844. Msg( "Unable to get MYSQL_WRAPPER_VERSION_NAME(%s) from mysql_wrapper\n", MYSQL_WRAPPER_VERSION_NAME );
  845. }
  846. }
  847. else
  848. {
  849. Msg( "Sys_GetFactory on mysql_wrapper failed\n" );
  850. }
  851. }
  852. else
  853. {
  854. Msg( "Sys_LoadModule( mysql_wrapper ) failed\n" );
  855. }
  856. }
  857. else
  858. {
  859. ParseBugs( raw, bugfile );
  860. }
  861. // Build lookup
  862. int numgrades = 200;
  863. float flGradesMinusOne = (float)( numgrades - 1 );
  864. Color *colors = new Color[ numgrades ];
  865. float *fastSquareRoot = new float[ numgrades ];
  866. for ( int iGrades = 0; iGrades < numgrades; ++iGrades )
  867. {
  868. float flValue = (float)iGrades / flGradesMinusOne;
  869. float hsl[ 3 ];
  870. hsl[ 0 ] = 240.0f - ( 1.0f - flValue ) * 240.0f;
  871. hsl[ 1 ] = 1.0f;
  872. hsl[ 2 ] = 0.5f;
  873. HSLToRGB( colors[iGrades], hsl );
  874. fastSquareRoot[iGrades] = sqrt( flValue );
  875. }
  876. Color black( 0, 0, 0, 0 );
  877. for ( KeyValues *map = kv->GetFirstSubKey(); map; map = map->GetNextKey() )
  878. {
  879. char const *pszMapName = map->GetName();
  880. Msg( "Processing %s\n", pszMapName );
  881. if ( bBuildImages )
  882. {
  883. char imagefile[ 512 ];
  884. Q_snprintf( imagefile, sizeof( imagefile ), "%s/%s.bmp", pathname, pszMapName );
  885. Q_FixSlashes( imagefile );
  886. Q_strlower( imagefile );
  887. char outfile[ 512 ];
  888. Q_snprintf( outfile, sizeof( outfile ), "%s/%s_deaths.jpg", pathname, pszMapName );
  889. Q_FixSlashes( outfile );
  890. Q_strlower( outfile );
  891. // Do the processing
  892. FileHandle_t fh = g_pFullFileSystem->Open( imagefile, "rb" );
  893. if ( FILESYSTEM_INVALID_HANDLE == fh )
  894. continue;
  895. int pos_x = map->GetInt( "x", 0 );
  896. int pos_y = map->GetInt( "y", 0 );
  897. float scale = map->GetFloat( "scale", 1.0f );
  898. int size = g_pFullFileSystem->Size( fh );
  899. byte *buf = new byte[ size + 1 ];
  900. g_pFullFileSystem->Read( buf, size, fh );
  901. g_pFullFileSystem->Close( fh );
  902. buf[ size ] = 0;
  903. // Now parse into image
  904. Image_t image = { 0 };
  905. if ( ReadBitmapRGB( buf, size, &image ) )
  906. {
  907. float flMaxValue = 0.0f;
  908. float flRadius = flScale * 256.0f / scale;
  909. int nRadius = ( int )( flRadius );
  910. //float flRadius = 2.0f;
  911. //int nRadius = 2;
  912. float flRadiusSqr = flRadius * flRadius;
  913. CUtlVector< POINT > vecDeaths;
  914. if ( bBugSpots )
  915. {
  916. int idx = raw.Find( pszMapName );
  917. if ( idx != raw.InvalidIndex() )
  918. {
  919. CUtlVector< Vector > *pvDB = raw[ idx ];
  920. for ( int iVec= 0; iVec < pvDB->Count(); ++iVec )
  921. {
  922. int x = (int)pvDB->Element( iVec )[ 0 ];
  923. int y = (int)pvDB->Element( iVec )[ 1 ];
  924. // int z = mysql->GetColumnValue_Int( 2 );
  925. float pixx = (float)( x - pos_x );
  926. float pixy = (float)( y - pos_y );
  927. pixx /= scale;
  928. pixy /= -scale;
  929. POINT death;
  930. death.x = pixx;
  931. death.y = pixy;
  932. vecDeaths.AddToTail( death );
  933. }
  934. }
  935. }
  936. else
  937. {
  938. if ( bStressTest )
  939. {
  940. for ( int iTest = 0 ; iTest < stresstestcount; ++iTest )
  941. {
  942. POINT death;
  943. do
  944. {
  945. death.x = RandomInt( 0, image.w - 1 );
  946. death.y = RandomInt( 0, image.h - 1 );
  947. byte *rgb = &image.data[ 3 * death.y * image.w + 3 * death.x ];
  948. if ( rgb[ 0 ] || rgb[ 1 ] || rgb[ 2 ] )
  949. {
  950. break;
  951. }
  952. } while ( true );
  953. vecDeaths.AddToTail( death );
  954. }
  955. }
  956. else
  957. {
  958. char q[ 512 ];
  959. // Got it!!!
  960. std::string mapname;
  961. mapname = pszMapName;
  962. v_escape_string( mapname );
  963. // Now read all the locations from the sql server
  964. Q_snprintf( q, sizeof( q ), "select x, y from %s_deaths where MapName = \'%s\' %s;", gamename, mapname.c_str(), whereclause );
  965. int retcode = mysql->Execute( q );
  966. if ( retcode != 0 )
  967. {
  968. printf( "Query %s failed\n", q );
  969. return -1;
  970. }
  971. bool bMoreData = mysql->SeekToFirstRow();
  972. while ( bMoreData && mysql->NextRow() )
  973. {
  974. int x = mysql->GetColumnValue_Int( 0 );
  975. int y = mysql->GetColumnValue_Int( 1 );
  976. // int z = mysql->GetColumnValue_Int( 2 );
  977. float pixx = (float)( x - pos_x );
  978. float pixy = (float)( y - pos_y );
  979. pixx /= scale;
  980. pixy /= -scale;
  981. POINT death;
  982. death.x = pixx;
  983. death.y = pixy;
  984. vecDeaths.AddToTail( death );
  985. }
  986. }
  987. }
  988. float *info = new float[ image.h * image.w ];
  989. Q_memset( info, 0, image.h * image.w * sizeof( float ) );
  990. float ooRadiusSqr = 1.0f / flRadiusSqr;
  991. int nSide = nRadius * 2 + 1;
  992. float *contribution = new float[ nSide * nSide ];
  993. Q_memset( contribution, 0, nSide * nSide * sizeof( float ) );
  994. for ( int s = 0; s < nSide; ++s )
  995. {
  996. for ( int t = 0; t < nSide; ++t )
  997. {
  998. int dx = s - nRadius;
  999. int dy = t - nRadius;
  1000. float dSqr = dx * dx + dy * dy;
  1001. if ( dSqr < flRadiusSqr )
  1002. {
  1003. contribution[ t * nSide + s ] = 1.0f - ( dSqr * ooRadiusSqr );
  1004. }
  1005. }
  1006. }
  1007. float st = Plat_FloatTime();
  1008. int c = vecDeaths.Count();
  1009. for ( int iDeath = 0; iDeath < c; ++iDeath )
  1010. {
  1011. POINT &v = vecDeaths[iDeath];
  1012. int xpos = v.x;
  1013. int ypos = v.y;
  1014. for ( int y = max( 0, ypos - nRadius ); y <= min( ypos + nRadius, image.h - 1 ); ++y )
  1015. {
  1016. for ( int x = max( 0, xpos - nRadius ); x <= xpos + nRadius; ++x )
  1017. {
  1018. if ( x >= image.w )
  1019. break;
  1020. float *slot = &info[ y * image.w + x ];
  1021. int dx = x - xpos + nRadius;
  1022. int dy = y - ypos + nRadius;
  1023. // Figure out contrubution
  1024. flScale = contribution[ dy * nSide + dx ];
  1025. if ( flScale <= 0.0f )
  1026. continue;
  1027. *slot += flScale;
  1028. if ( *slot > flMaxValue )
  1029. flMaxValue = *slot;
  1030. }
  1031. }
  1032. }
  1033. if ( flMaxValue >0.0f )
  1034. {
  1035. float flOneOverMax = 1.0f / flMaxValue;
  1036. for ( int y = 0; y < image.h; ++y )
  1037. {
  1038. float *slot = &info[ y * image.w ];
  1039. for ( int x = 0; x < image.w; ++x, ++slot )
  1040. {
  1041. float flValue = *slot * flOneOverMax;
  1042. int colorIndex = clamp( (int)( flValue * flGradesMinusOne + 0.5f ), 0, numgrades - 1 );
  1043. const Color &col = colors[ colorIndex ];
  1044. DrawColoredRect( &image, x, y, 1, 1, black, flValue * flValue );
  1045. float sqroot = fastSquareRoot[ colorIndex ];
  1046. if ( sqroot != 0.0f )
  1047. sqroot = max( sqroot, 0.33f );
  1048. DrawColoredRect( &image, x, y, 1, 1, col, sqroot );
  1049. }
  1050. }
  1051. }
  1052. float et = Plat_FloatTime();
  1053. double msec = 1000.0 * ( et - st );
  1054. double msecperdeath = 0.0;
  1055. if ( vecDeaths.Count() > 0 )
  1056. {
  1057. msecperdeath = 1000.0 * msec / (double)vecDeaths.Count();
  1058. }
  1059. if ( bStressTest )
  1060. {
  1061. Msg( "Processing took %f msec %f msec per thousand deaths [%d deaths]\n", msec, msecperdeath, vecDeaths.Count() );
  1062. }
  1063. delete[] contribution;
  1064. delete[] info;
  1065. // Now write the image back out
  1066. WriteJPeg( outfile, &image );
  1067. }
  1068. delete[] image.data;
  1069. if ( bStressTest )
  1070. break;
  1071. }
  1072. if ( bBuildStats )
  1073. {
  1074. BuildAggregateStats( mysql, pszMapName, whereclause );
  1075. }
  1076. }
  1077. delete[] fastSquareRoot;
  1078. delete[] colors;
  1079. kv->deleteThis();
  1080. if ( bSqlOkay )
  1081. {
  1082. if ( mysql )
  1083. {
  1084. mysql->Release();
  1085. mysql = NULL;
  1086. }
  1087. if ( sql )
  1088. {
  1089. Sys_UnloadModule( sql );
  1090. sql = NULL;
  1091. }
  1092. }
  1093. return 0;
  1094. }