//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //============================================================================= #include #include #include #include #include #include #include "interface.h" #include "imysqlwrapper.h" #include "tier1/utlvector.h" #include "tier1/utlbuffer.h" #include "tier1/utlsymbol.h" #include "tier1/utlstring.h" #include "tier1/utldict.h" #include "KeyValues.h" #include "filesystem_helpers.h" #include "tier2/tier2.h" #include "filesystem.h" #include "interface.h" #include "vstdlib/random.h" #include "jpeglib/jpeglib.h" static char gamename[ 32 ] = "ep2"; extern IFileSystem *g_pFullFileSystem; struct Image_t { byte *data; int w, h; }; bool ReadBitmapRGB( const byte *raw, size_t rawlen, Image_t *image ) { assert( image ); CUtlBuffer buf; buf.Put( raw, rawlen ); // int i; BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; int cbBmpBits; byte *pb; // Read file header buf.Get( &bmfh, sizeof( bmfh ) ); // Read info header buf.Get( &bmih, sizeof( bmih ) ); // Bogus info header check if (!(bmih.biSize == sizeof( bmih ) && bmih.biPlanes == 1)) return false; // Bogus bit depth? Only 8-bit supported. if (bmih.biBitCount != 24) return false; // Bogus compression? Only non-compressed supported. if (bmih.biCompression != BI_RGB ) return false; image->w = bmih.biWidth; image->h = bmih.biHeight; // Read bitmap bits (remainder of file) cbBmpBits = bmfh.bfSize - buf.TellGet(); assert( cbBmpBits == ( image->w * image->h * 3 ) ); int cbTotalbytes = cbBmpBits; pb = new byte[ cbBmpBits ]; if (pb == 0) { return false; } buf.Get( pb, cbBmpBits ); byte *start = pb; image->data = new byte[ cbTotalbytes ]; int bitrueWidth = (bmih.biWidth + 3) & ~3; byte *dst = image->data; int x, y; for ( y = 0; y < image->h; ++y ) { int rowstart = 3 * ( image->h - 1 - y ) * bitrueWidth; byte *row = &start[ rowstart ]; for ( x = 0; x < image->w; ++x ) { #define USE_GRAYSCALE #if defined( USE_GRAYSCALE ) int val = row[ 0 ] + row[ 1 ] + row[ 2 ]; val /= 3; *dst++ = val; *dst++ = val; *dst++ = val; row += 3; #else *dst++ = *row++; *dst++ = *row++; *dst++ = *row++; #endif } } delete[] start; return true; } void ParseBugs( CUtlDict< CUtlVector< Vector > *, int >& list, char const *filename ) { FileHandle_t fh = g_pFullFileSystem->Open( filename, "rb" ); if ( FILESYSTEM_INVALID_HANDLE == fh ) return; size_t size = g_pFullFileSystem->Size( fh ); char *buffer = new char[ size + 1 ]; g_pFullFileSystem->Read( buffer, size, fh ); buffer[ size ] = 0; g_pFullFileSystem->Close( fh ); char *data = buffer; while ( 1 ) { // Now parse out the data // First find level: char *p = strstr( data, "level: " ); if ( !p ) break; char mapname[ 512 ]; char *i = p + 8; char *o = mapname; while ( *i && *i != ' ' && *i != '\r' && *i != '\n' ) *o++ = *i++; *o = 0; data += 8; // Now find the position p = strstr( data, "setpos " ); if ( !p ) break; char position[ 512 ]; i = p + 7; o = position; while ( *i && *i != ';' ) *o++ = *i++; *o = 0; data += 7; Vector pos; if ( 3 == sscanf( position, "%f %f %f", &pos.x, &pos.y, &pos.z ) ) { int idx = list.Find( mapname ); if ( idx == list.InvalidIndex() ) { idx = list.Insert( mapname, new CUtlVector< Vector > ); } CUtlVector< Vector > *db = list[ idx ]; db->AddToTail( pos ); } // printf( "map: %s pos %s\n", mapname, position ); } delete[] buffer; return; } bool WriteBitmap( char const *filename, Image_t *image ) { FileHandle_t fh = g_pFullFileSystem->Open( filename, "wb" ); if ( FILESYSTEM_INVALID_HANDLE == fh ) return false; BITMAPFILEHEADER hdr = { 0 }; BITMAPINFOHEADER bi = { 0 }; int w = image->w; int h = image->h; int imageSize = image->w * image->h * 3; // file header hdr.bfType = 0x4d42; // 'BM' hdr.bfSize = (DWORD) ( sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + imageSize ); hdr.bfReserved1 = 0; hdr.bfReserved2 = 0; hdr.bfOffBits = (DWORD) ( sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) ); g_pFullFileSystem->Write( (LPVOID)&hdr, sizeof(BITMAPFILEHEADER), fh ); // bitmap header bi.biSize = sizeof(BITMAPINFOHEADER); bi.biWidth = image->w; bi.biHeight = image->h; bi.biPlanes = 1; bi.biBitCount = 24; bi.biCompression = BI_RGB; bi.biSizeImage = 0; //vid.rowbytes * vid.height; bi.biXPelsPerMeter = 0; bi.biYPelsPerMeter = 0; bi.biClrUsed = 0; bi.biClrImportant = 0; g_pFullFileSystem->Write( (LPVOID)&bi, sizeof(BITMAPINFOHEADER), fh ); // bitmap bits byte *hp = new byte[ imageSize ]; Q_memcpy( hp, image->data, imageSize ); byte b; int i; // Invert vertically for BMP format for ( i = 0; i < h / 2; i++ ) { byte *top = hp + i * w * 3; byte *bottom = hp + (h - i - 1) * w * 3; for (int j = 0; j < w * 3; j++) { b = *top; *top = *bottom; *bottom = b; top++; bottom++; } } g_pFullFileSystem->Write( (LPVOID)hp, imageSize, fh ); delete[] hp; // clean up g_pFullFileSystem->Close( fh ); return true; } //----------------------------------------------------------------------------- // Purpose: Expanded data destination object for CUtlBuffer output //----------------------------------------------------------------------------- struct JPEGDestinationManager_t { struct jpeg_destination_mgr pub; // public fields CUtlBuffer *pBuffer; // target/final buffer byte *buffer; // start of temp buffer }; // choose an efficiently bufferaable size #define OUTPUT_BUF_SIZE 4096 //----------------------------------------------------------------------------- // Purpose: Initialize destination --- called by jpeg_start_compress // before any data is actually written. //----------------------------------------------------------------------------- METHODDEF(void) init_destination (j_compress_ptr cinfo) { JPEGDestinationManager_t *dest = ( JPEGDestinationManager_t *) cinfo->dest; // Allocate the output buffer --- it will be released when done with image dest->buffer = (byte *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, OUTPUT_BUF_SIZE * sizeof(byte)); dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; } //----------------------------------------------------------------------------- // Purpose: Empty the output buffer --- called whenever buffer fills up. // Input : boolean - //----------------------------------------------------------------------------- METHODDEF(boolean) empty_output_buffer (j_compress_ptr cinfo) { JPEGDestinationManager_t *dest = ( JPEGDestinationManager_t * ) cinfo->dest; CUtlBuffer *buf = dest->pBuffer; // Add some data buf->Put( dest->buffer, OUTPUT_BUF_SIZE ); dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = OUTPUT_BUF_SIZE; return TRUE; } //----------------------------------------------------------------------------- // Purpose: Terminate destination --- called by jpeg_finish_compress // after all data has been written. Usually needs to flush buffer. // // NB: *not* called by jpeg_abort or jpeg_destroy; surrounding // application must deal with any cleanup that should happen even // for error exit. //----------------------------------------------------------------------------- METHODDEF(void) term_destination (j_compress_ptr cinfo) { JPEGDestinationManager_t *dest = (JPEGDestinationManager_t *) cinfo->dest; size_t datacount = OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; CUtlBuffer *buf = dest->pBuffer; /* Write any data remaining in the buffer */ if (datacount > 0) { buf->Put( dest->buffer, datacount ); } } //----------------------------------------------------------------------------- // Purpose: Set up functions for writing data to a CUtlBuffer instead of FILE * //----------------------------------------------------------------------------- GLOBAL(void) jpeg_UtlBuffer_dest (j_compress_ptr cinfo, CUtlBuffer *pBuffer ) { JPEGDestinationManager_t *dest; /* The destination object is made permanent so that multiple JPEG images * can be written to the same file without re-executing jpeg_stdio_dest. * This makes it dangerous to use this manager and a different destination * manager serially with the same JPEG object, because their private object * sizes may be different. Caveat programmer. */ if (cinfo->dest == NULL) { /* first time for this JPEG object? */ cinfo->dest = (struct jpeg_destination_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(JPEGDestinationManager_t)); } dest = ( JPEGDestinationManager_t * ) cinfo->dest; dest->pub.init_destination = init_destination; dest->pub.empty_output_buffer = empty_output_buffer; dest->pub.term_destination = term_destination; dest->pBuffer = pBuffer; } bool ImageToJPEGBuffer( Image_t *image, CUtlBuffer& buf, int quality ) { #if !defined( _X360 ) // Validate quality level quality = clamp( quality, 1, 100 ); int imagew = image->w; int imageh = image->h; // Allocate space for bits uint8 *pImage = new uint8[ imagew * 3 * imageh]; if ( !pImage ) { Msg( "Unable to allocate %i bytes for image\n", imagew * 3 * imageh ); return false; } // Get Bits from the material system // ReadScreenPixels( 0, 0, GetModeWidth(), GetModeHeight(), pImage, IMAGE_FORMAT_RGB888 ); // Copy data in for ( int y = 0; y < imageh; ++y ) { byte *row = (byte *)&pImage[ y * imagew * 3 ]; const byte *pSrc = (const byte *)&image->data[ y * imagew * 3 ]; for ( int x = 0; x < imagew; ++x ) { // BGR to RGB row[ 0 ] = pSrc[ 2 ]; row[ 1 ] = pSrc[ 1 ]; row[ 2 ] = pSrc[ 0 ]; pSrc += 3; row += 3; } } JSAMPROW row_pointer[1]; // pointer to JSAMPLE row[s] int row_stride; // physical row width in image buffer // stderr handler struct jpeg_error_mgr jerr; // compression data structure struct jpeg_compress_struct cinfo; row_stride = imagew * 3; // JSAMPLEs per row in image_buffer // point at stderr cinfo.err = jpeg_std_error(&jerr); // create compressor jpeg_create_compress(&cinfo); // Hook CUtlBuffer to compression jpeg_UtlBuffer_dest(&cinfo, &buf ); // image width and height, in pixels cinfo.image_width = imagew; cinfo.image_height = imageh; // RGB is 3 componnent cinfo.input_components = 3; // # of color components per pixel cinfo.in_color_space = JCS_RGB; // Apply settings jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE ); // Start compressor jpeg_start_compress(&cinfo, TRUE); // Write scanlines while ( cinfo.next_scanline < cinfo.image_height ) { row_pointer[ 0 ] = &pImage[ cinfo.next_scanline * row_stride ]; jpeg_write_scanlines( &cinfo, row_pointer, 1 ); } // Finalize image jpeg_finish_compress(&cinfo); // Cleanup jpeg_destroy_compress(&cinfo); delete[] pImage; #else // not supporting Assert( 0 ); #endif return true; } bool WriteJPeg( char const *filename, Image_t *image ) { FileHandle_t fh = g_pFullFileSystem->Open( filename, "wb" ); if ( FILESYSTEM_INVALID_HANDLE == fh ) return false; CUtlBuffer buf; ImageToJPEGBuffer( image, buf, 90 ); g_pFullFileSystem->Write( buf.Base(), buf.TellPut(), fh ); // clean up g_pFullFileSystem->Close( fh ); return true; } #include //------------------------------------------------- void v_escape_string (std::string& s) { if ( !s.size() ) return; for ( unsigned int i = 0;i 360) hsl[ 0 ] -= 360; if (hsl[ 0 ] < 120) { sat[ 0 ] = (120 - hsl[ 0 ]) / 60.0; sat[ 1 ] = hsl[ 0 ] / 60.0; sat[ 2 ] = 0; } else if (hsl[ 0 ] < 240) { sat[ 0 ] = 0; sat[ 1 ] = (240 - hsl[ 0 ]) / 60.0; sat[ 2 ] = (hsl[ 0 ] - 120) / 60.0; } else { sat[ 0 ] = (hsl[ 0 ] - 240) / 60.0; sat[ 1 ] = 0; sat[ 2 ] = (360 - hsl[ 0 ]) / 60.0; } sat[ 0 ] = min(sat[ 0 ],1.f); sat[ 1 ] = min(sat[ 1 ],1.f); sat[ 2 ] = min(sat[ 2 ],1.f); ctmp[ 0 ] = 2 * hsl[ 1 ] * sat[ 0 ] + (1 - hsl[ 1 ]); ctmp[ 1 ] = 2 * hsl[ 1 ] * sat[ 1 ] + (1 - hsl[ 1 ]); ctmp[ 2 ] = 2 * hsl[ 1 ] * sat[ 2 ] + (1 - hsl[ 1 ]); if (hsl[ 2 ] < 0.5) { out[ 0 ] = 255.0f * hsl[ 2 ] * ctmp[ 0 ]; out[ 1 ] = 255.0f * hsl[ 2 ] * ctmp[ 1 ]; out[ 2 ] = 255.0f * hsl[ 2 ] * ctmp[ 2 ]; } else { out[ 0 ] = 255.0f * ( (1 - hsl[ 2 ]) * ctmp[ 0 ] + 2 * hsl[ 2 ] - 1 ); out[ 1 ] = 255.0f * ( (1 - hsl[ 2 ]) * ctmp[ 1 ] + 2 * hsl[ 2 ] - 1 ); out[ 2 ] = 255.0f * ( (1 - hsl[ 2 ]) * ctmp[ 2 ] + 2 * hsl[ 2 ] - 1 ); } } inline void DrawColoredRect( Image_t *image, int x, int y, int w, int h, const Color &rgb, float flAlpha ) { flAlpha = clamp( flAlpha, 0.0f, 1.0f ); float flOneMinusAlpha = 1.0f - flAlpha; for ( int xx = x; xx < x + w; ++xx ) { if ( xx < 0 || xx >= image->w ) continue; for ( int yy = y; yy < y + h; ++yy ) { if ( yy < 0 || yy >= image->h ) continue; int offset = yy * image->w * 3 + xx * 3; byte *dst = &image->data[ offset ]; for ( int i = 0 ; i < 3; ++i ) { dst[ i ] = (byte)( flOneMinusAlpha * dst[ i ] + flAlpha * rgb[ i ] ); } } } } void BuildAggregateStats( IMySQL *mysql, char const *pszMapName, char const *whereClause ) { char q[ 2048 ]; // Got it!!! std::string mapname; mapname = pszMapName; v_escape_string( mapname ); // Now read all the locations from the sql server 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 ); int retcode = mysql->Execute( q ); if ( retcode != 0 ) { Msg( "Query %s failed\n", q ); return; } if ( mysql->SeekToFirstRow() && mysql->NextRow() ) { int SumCount = mysql->GetColumnValue_Int( 0 ); if ( SumCount == 0 ) { Msg( "No data for %s\n", mapname.c_str() ); return; } float AverageCount = Q_atof( mysql->GetColumnValue_String( 1 ) ); float AvgTime = Q_atof( mysql->GetColumnValue_String( 2 ) ); float AvgDeaths = Q_atof( mysql->GetColumnValue_String( 3 ) ); Msg( "Sessions %d, average sessions %.2f avg time %.2f (minutes) avgdeaths %.2f\n", SumCount, AverageCount, AvgTime, AvgDeaths ); } else { Msg( "No data for %s\n", mapname.c_str() ); return; } // Now show entity stats 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 ); retcode = mysql->Execute( q ); if ( retcode != 0 ) { Msg( "Query %s failed\n", q ); return; } Msg( " Entities\n\n" ); Msg( " %32s %10s %10s %15s %10s\n", "Entity", "Killed", "Avg", "KilledPlayer", "Avg" ); if ( mysql->SeekToFirstRow() ) { while ( mysql->NextRow() ) { Msg( " %32s %10d %10.2f %15d %10.2f\n", mysql->GetColumnValue_String( 0 ), mysql->GetColumnValue_Int( 1 ), Q_atof( mysql->GetColumnValue_String( 2 ) ), mysql->GetColumnValue_Int( 3 ), Q_atof( mysql->GetColumnValue_String( 4 ) ) ); } } // Now show weapon stats 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 ); retcode = mysql->Execute( q ); if ( retcode != 0 ) { Msg( "Query %s failed\n", q ); return; } Msg( " Weapons\n" ); Msg( " %32s %10s %10s %15s %10s %15s %10s\n", "Weapon", "Shots", "Avg", "Hits", "Avg", "Damage", "Avg" ); if ( mysql->SeekToFirstRow() ) { while ( mysql->NextRow() ) { Msg( " %32s %10d %10.2f %15d %10.2f %15d %10.2f\n", mysql->GetColumnValue_String( 0 ), mysql->GetColumnValue_Int( 1 ), Q_atof( mysql->GetColumnValue_String( 2 ) ), mysql->GetColumnValue_Int( 3 ), Q_atof( mysql->GetColumnValue_String( 4 ) ), mysql->GetColumnValue_Int( 5 ), Q_atof( mysql->GetColumnValue_String( 6 ) )); } } // Now show weapon stats Q_snprintf( q, sizeof( q ), "select count(*)/count(distinct(userid) ) from %s_saves where mapname = \'%s\' %s;", gamename, mapname.c_str(), whereClause ); retcode = mysql->Execute( q ); if ( retcode != 0 ) { Msg( "Query %s failed\n", q ); return; } Msg( " Saves\n" ); if ( mysql->SeekToFirstRow() ) { while ( mysql->NextRow() ) { const char *colVal = mysql->GetColumnValue_String( 0 ); float flVal = 0.0f; if ( colVal ) flVal = Q_atof( colVal ); Msg( " Average Saves per user: %.2f\n", flVal ); } } Msg( " Counters\n" ); static char *counters[] = { "CRATESSMASHED", "OBJECTSPUNTED", "VEHICULARHOMICIDES", "DISTANCE_INVEHICLE", "DISTANCE_ONFOOT", "DISTANCE_ONFOOTSPRINTING", "FALLINGDEATHS", "VEHICLE_OVERTURNED", "LOADGAME_STILLALIVE", "LOADS", "SAVES", "GODMODES", "NOCLIPS", "DAMAGETAKEN", NULL }; Q_snprintf( q, sizeof( q ), "select Sum(CRATESSMASHED), Avg(CRATESSMASHED),"\ "Sum(OBJECTSPUNTED), Avg(OBJECTSPUNTED),"\ "Sum(VEHICULARHOMICIDES), Avg(VEHICULARHOMICIDES),"\ "Sum(DISTANCE_INVEHICLE), Avg(DISTANCE_INVEHICLE),"\ "Sum(DISTANCE_ONFOOT), Avg(DISTANCE_ONFOOT),"\ "Sum(DISTANCE_ONFOOTSPRINTING), Avg(DISTANCE_ONFOOTSPRINTING),"\ "Sum(FALLINGDEATHS), Avg(FALLINGDEATHS),"\ "Sum(VEHICLE_OVERTURNED), Avg(VEHICLE_OVERTURNED),"\ "Sum(LOADGAME_STILLALIVE), Avg(LOADGAME_STILLALIVE),"\ "Sum(LOADS), Avg(LOADS),"\ "Sum(SAVES), Avg(SAVES),"\ "Sum(GODMODES), Avg(GODMODES),"\ "Sum(NOCLIPS), Avg(NOCLIPS),"\ "Sum(DAMAGETAKEN), Avg(DAMAGETAKEN) "\ "from %s_counters where mapname = \'%s\' %s;", gamename, mapname.c_str(), whereClause ); retcode = mysql->Execute( q ); if ( retcode != 0 ) { Msg( "Query %s failed\n", q ); return; } int i = 0; Msg( " %32s %20s %20s\n", "Counter", "Total", "Average" ); #define INCHES_TO_MILES ( 1.0 / ( 5280.0 * 12.0 ) ) if ( mysql->SeekToFirstRow() ) { while ( mysql->NextRow() ) { while ( counters[ i ] != NULL ) { int idx = 2 * i; char const *raw1 = mysql->GetColumnValue_String( idx ); char const *raw2 = mysql->GetColumnValue_String( idx + 1 ); // Msg( "%s raw %s\n%s\n", counters[ i ], raw1, raw2 ); uint64 sum = _atoi64( raw1 ); double avg = Q_atof( raw2 ); if ( Q_stristr( counters[ i ], "DISTANCE_" ) ) { Msg( " %32s %20.2f %20.2f [miles]\n", counters[ i ], (double)sum * INCHES_TO_MILES, avg * INCHES_TO_MILES ); } else { Msg( " %32s %20I64u %20.2f\n", counters[ i ], sum, avg ); } ++i; } } } Msg( "-----------------------------------------------------------\n" ); // Now show generic stats 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 ); retcode = mysql->Execute( q ); if ( retcode != 0 ) { Msg( "Query %s failed\n", q ); return; } Msg( " Generic\n" ); Msg( " %32s %10s %10s %15s %10s\n", "Name", "Count", "Avg", "Value", "Avg" ); if ( mysql->SeekToFirstRow() ) { while ( mysql->NextRow() ) { Msg( " %32s %10d %10.2f %15d %10.2f\n", mysql->GetColumnValue_String( 0 ), mysql->GetColumnValue_Int( 1 ), Q_atof( mysql->GetColumnValue_String( 2 ) ), mysql->GetColumnValue_Int( 3 ), Q_atof( mysql->GetColumnValue_String( 4 ) ) ); } } } #define EP2_DEATHS_VERSION "1.1" void printusage( int argc, char * argv[] ) { Msg( "%s:\n"\ " Version = " EP2_DEATHS_VERSION "\n"\ " Date [ " __DATE__ " " __TIME__ " ]\n"\ " Copyright Valve 2007. All rights reserved.\n"\ " -game [ep2 | tf] { which game }\n"\ " -bugs bugtestfile { special bug file culled from PVCSTracker report }\n"\ " -imagedir imagedir { directory for mapinfo.res and image .bmps }\n"\ " -deaths { build death images }\n"\ " -scale imagescalefactor (default 1.0)\n"\ " -h sqldbhost\n"\ " -u sqluser\n"\ " -p sqlpw\n"\ " -db sqldb\n"\ " -stress count { run stress testing for image creation }\n"\ " -where 'additional where clause'\n"\ " -stats { spew per level stats for entities, weapons, etc. }\n", argv[ 0 ] ); } int main(int argc, char* argv[]) { int i; char bugfile[ 512 ] = { 0 }; char pathname[ 512 ] = { 0 }; char whereclause[ 1024 ] = { 0 }; float flScale = 1.0f; char const *host = ""; char const *user = ""; char const *pw = ""; char const *db = ""; bool bBugSpots = false; bool bBuildImages = false; bool bBuildStats = false; bool bStressTest = false; int stresstestcount = 1000; for ( i = 1 ; i < argc ; ++i ) { if (!stricmp(argv[i],"-bugs")) { bBugSpots = true; Q_strncpy( bugfile, argv[i+1], sizeof( bugfile ) ); Msg( " parsing bugs from '%s'\n", bugfile ); ++i; } else if ( !stricmp( argv[ i ], "-game" )) { Q_strncpy( gamename, argv[i+1], sizeof( gamename ) ); Msg( " death maps for game '%s'\n", gamename ); ++i; } else if (!stricmp(argv[i],"-deaths")) { bBuildImages = true; Msg( " generating death images\n" ); } else if (!stricmp(argv[i],"-imagedir")) { Q_strncpy( pathname, argv[i+1], sizeof( pathname ) ); Msg( " image dir '%s'\n", pathname ); ++i; } else if (!stricmp(argv[i],"-where")) { Q_snprintf( whereclause, sizeof( whereclause ), " and %s", argv[i+1] ); Msg( " using custom where clause '%s'\n", argv[i+1] ); ++i; } else if (!stricmp(argv[i],"-h")) { host = argv[ i + 1 ]; Msg( " mysql host '%s'\n", host ); ++i; } else if (!stricmp(argv[i],"-u")) { user = argv[ i + 1 ]; Msg( " mysql user '%s'\n", user ); ++i; } else if (!stricmp(argv[i],"-p")) { pw = argv[ i + 1 ]; // Msg( " mysql pw '%s'\n", pw ); ++i; } else if (!stricmp(argv[i],"-db")) { db = argv[ i + 1 ]; Msg( " mysql db '%s'\n", db ); ++i; } else if (!stricmp(argv[i],"-scale")) { flScale = max( 0.01f, Q_atof ( argv[ i + 1 ] ) ); Msg( " image scale '%g'\n", flScale ); ++i; } else if (!stricmp(argv[i],"-stats")) { bBuildStats = true; Msg( " generating per level stats\n" ); } else if (!stricmp(argv[i],"-stress")) { bStressTest = true; stresstestcount = Q_atoi( argv[ i + 1 ] ); Msg( " performing stress testing count = %d\n", stresstestcount ); ++i; } else break; } if ( i != argc ) { printusage( argc, argv ); return -1; } if ( !pathname[ 0 ] ) { printusage( argc, argv ); return -1; } InitDefaultFileSystem(); CUtlDict< CUtlVector< Vector > *, int > raw; char fn[ 512 ]; Q_snprintf( fn, sizeof( fn ), "%s/mapinfo.res", pathname ); Q_FixSlashes( fn ); Q_strlower( fn ); KeyValues *kv = new KeyValues( "mapinfo.res" ); if ( !kv->LoadFromFile( g_pFullFileSystem, fn, NULL ) ) { Msg( "Unable to load mapinfo.res file from image directory [%s]\n", pathname ); exit( -1 ); } CSysModule *sql = NULL; CreateInterfaceFn factory = NULL; IMySQL *mysql = NULL; bool bSqlOkay = false; if ( !bBugSpots ) { sql = Sys_LoadModule( "mysql_wrapper" ); if ( sql ) { factory = Sys_GetFactory( sql ); if ( factory ) { mysql = ( IMySQL * )factory( MYSQL_WRAPPER_VERSION_NAME, NULL ); if ( mysql ) { if ( mysql->InitMySQL( db, host, user, pw ) ) { bSqlOkay = true; Msg( "Successfully connected to database %s on host %s, user %s\n", db, host, user ); } else { Msg( "mysql->InitMySQL( %s, %s, %s, [password]) failed\n", db, host, user ); } } else { Msg( "Unable to get MYSQL_WRAPPER_VERSION_NAME(%s) from mysql_wrapper\n", MYSQL_WRAPPER_VERSION_NAME ); } } else { Msg( "Sys_GetFactory on mysql_wrapper failed\n" ); } } else { Msg( "Sys_LoadModule( mysql_wrapper ) failed\n" ); } } else { ParseBugs( raw, bugfile ); } // Build lookup int numgrades = 200; float flGradesMinusOne = (float)( numgrades - 1 ); Color *colors = new Color[ numgrades ]; float *fastSquareRoot = new float[ numgrades ]; for ( int iGrades = 0; iGrades < numgrades; ++iGrades ) { float flValue = (float)iGrades / flGradesMinusOne; float hsl[ 3 ]; hsl[ 0 ] = 240.0f - ( 1.0f - flValue ) * 240.0f; hsl[ 1 ] = 1.0f; hsl[ 2 ] = 0.5f; HSLToRGB( colors[iGrades], hsl ); fastSquareRoot[iGrades] = sqrt( flValue ); } Color black( 0, 0, 0, 0 ); for ( KeyValues *map = kv->GetFirstSubKey(); map; map = map->GetNextKey() ) { char const *pszMapName = map->GetName(); Msg( "Processing %s\n", pszMapName ); if ( bBuildImages ) { char imagefile[ 512 ]; Q_snprintf( imagefile, sizeof( imagefile ), "%s/%s.bmp", pathname, pszMapName ); Q_FixSlashes( imagefile ); Q_strlower( imagefile ); char outfile[ 512 ]; Q_snprintf( outfile, sizeof( outfile ), "%s/%s_deaths.jpg", pathname, pszMapName ); Q_FixSlashes( outfile ); Q_strlower( outfile ); // Do the processing FileHandle_t fh = g_pFullFileSystem->Open( imagefile, "rb" ); if ( FILESYSTEM_INVALID_HANDLE == fh ) continue; int pos_x = map->GetInt( "x", 0 ); int pos_y = map->GetInt( "y", 0 ); float scale = map->GetFloat( "scale", 1.0f ); int size = g_pFullFileSystem->Size( fh ); byte *buf = new byte[ size + 1 ]; g_pFullFileSystem->Read( buf, size, fh ); g_pFullFileSystem->Close( fh ); buf[ size ] = 0; // Now parse into image Image_t image = { 0 }; if ( ReadBitmapRGB( buf, size, &image ) ) { float flMaxValue = 0.0f; float flRadius = flScale * 256.0f / scale; int nRadius = ( int )( flRadius ); //float flRadius = 2.0f; //int nRadius = 2; float flRadiusSqr = flRadius * flRadius; CUtlVector< POINT > vecDeaths; if ( bBugSpots ) { int idx = raw.Find( pszMapName ); if ( idx != raw.InvalidIndex() ) { CUtlVector< Vector > *pvDB = raw[ idx ]; for ( int iVec= 0; iVec < pvDB->Count(); ++iVec ) { int x = (int)pvDB->Element( iVec )[ 0 ]; int y = (int)pvDB->Element( iVec )[ 1 ]; // int z = mysql->GetColumnValue_Int( 2 ); float pixx = (float)( x - pos_x ); float pixy = (float)( y - pos_y ); pixx /= scale; pixy /= -scale; POINT death; death.x = pixx; death.y = pixy; vecDeaths.AddToTail( death ); } } } else { if ( bStressTest ) { for ( int iTest = 0 ; iTest < stresstestcount; ++iTest ) { POINT death; do { death.x = RandomInt( 0, image.w - 1 ); death.y = RandomInt( 0, image.h - 1 ); byte *rgb = &image.data[ 3 * death.y * image.w + 3 * death.x ]; if ( rgb[ 0 ] || rgb[ 1 ] || rgb[ 2 ] ) { break; } } while ( true ); vecDeaths.AddToTail( death ); } } else { char q[ 512 ]; // Got it!!! std::string mapname; mapname = pszMapName; v_escape_string( mapname ); // Now read all the locations from the sql server Q_snprintf( q, sizeof( q ), "select x, y from %s_deaths where MapName = \'%s\' %s;", gamename, mapname.c_str(), whereclause ); int retcode = mysql->Execute( q ); if ( retcode != 0 ) { printf( "Query %s failed\n", q ); return -1; } bool bMoreData = mysql->SeekToFirstRow(); while ( bMoreData && mysql->NextRow() ) { int x = mysql->GetColumnValue_Int( 0 ); int y = mysql->GetColumnValue_Int( 1 ); // int z = mysql->GetColumnValue_Int( 2 ); float pixx = (float)( x - pos_x ); float pixy = (float)( y - pos_y ); pixx /= scale; pixy /= -scale; POINT death; death.x = pixx; death.y = pixy; vecDeaths.AddToTail( death ); } } } float *info = new float[ image.h * image.w ]; Q_memset( info, 0, image.h * image.w * sizeof( float ) ); float ooRadiusSqr = 1.0f / flRadiusSqr; int nSide = nRadius * 2 + 1; float *contribution = new float[ nSide * nSide ]; Q_memset( contribution, 0, nSide * nSide * sizeof( float ) ); for ( int s = 0; s < nSide; ++s ) { for ( int t = 0; t < nSide; ++t ) { int dx = s - nRadius; int dy = t - nRadius; float dSqr = dx * dx + dy * dy; if ( dSqr < flRadiusSqr ) { contribution[ t * nSide + s ] = 1.0f - ( dSqr * ooRadiusSqr ); } } } float st = Plat_FloatTime(); int c = vecDeaths.Count(); for ( int iDeath = 0; iDeath < c; ++iDeath ) { POINT &v = vecDeaths[iDeath]; int xpos = v.x; int ypos = v.y; for ( int y = max( 0, ypos - nRadius ); y <= min( ypos + nRadius, image.h - 1 ); ++y ) { for ( int x = max( 0, xpos - nRadius ); x <= xpos + nRadius; ++x ) { if ( x >= image.w ) break; float *slot = &info[ y * image.w + x ]; int dx = x - xpos + nRadius; int dy = y - ypos + nRadius; // Figure out contrubution flScale = contribution[ dy * nSide + dx ]; if ( flScale <= 0.0f ) continue; *slot += flScale; if ( *slot > flMaxValue ) flMaxValue = *slot; } } } if ( flMaxValue >0.0f ) { float flOneOverMax = 1.0f / flMaxValue; for ( int y = 0; y < image.h; ++y ) { float *slot = &info[ y * image.w ]; for ( int x = 0; x < image.w; ++x, ++slot ) { float flValue = *slot * flOneOverMax; int colorIndex = clamp( (int)( flValue * flGradesMinusOne + 0.5f ), 0, numgrades - 1 ); const Color &col = colors[ colorIndex ]; DrawColoredRect( &image, x, y, 1, 1, black, flValue * flValue ); float sqroot = fastSquareRoot[ colorIndex ]; if ( sqroot != 0.0f ) sqroot = max( sqroot, 0.33f ); DrawColoredRect( &image, x, y, 1, 1, col, sqroot ); } } } float et = Plat_FloatTime(); double msec = 1000.0 * ( et - st ); double msecperdeath = 0.0; if ( vecDeaths.Count() > 0 ) { msecperdeath = 1000.0 * msec / (double)vecDeaths.Count(); } if ( bStressTest ) { Msg( "Processing took %f msec %f msec per thousand deaths [%d deaths]\n", msec, msecperdeath, vecDeaths.Count() ); } delete[] contribution; delete[] info; // Now write the image back out WriteJPeg( outfile, &image ); } delete[] image.data; if ( bStressTest ) break; } if ( bBuildStats ) { BuildAggregateStats( mysql, pszMapName, whereclause ); } } delete[] fastSquareRoot; delete[] colors; kv->deleteThis(); if ( bSqlOkay ) { if ( mysql ) { mysql->Release(); mysql = NULL; } if ( sql ) { Sys_UnloadModule( sql ); sql = NULL; } } return 0; }