diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /devtools/ep2_deathmap | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'devtools/ep2_deathmap')
| -rw-r--r-- | devtools/ep2_deathmap/ep2_deathmap.cpp | 1315 | ||||
| -rw-r--r-- | devtools/ep2_deathmap/ep2_deathmap.vpc | 54 |
2 files changed, 1369 insertions, 0 deletions
diff --git a/devtools/ep2_deathmap/ep2_deathmap.cpp b/devtools/ep2_deathmap/ep2_deathmap.cpp new file mode 100644 index 0000000..0dbe85a --- /dev/null +++ b/devtools/ep2_deathmap/ep2_deathmap.cpp @@ -0,0 +1,1315 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include <stdio.h> +#include <process.h> +#include <string.h> +#include <windows.h> +#include <sys/stat.h> +#include <time.h> + + +#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 <string> +//------------------------------------------------- +void v_escape_string (std::string& s) +{ + if ( !s.size() ) + return; + for ( unsigned int i = 0;i<s.size();i++ ) + { + switch (s[i]) + { + case '\0': /* Must be escaped for "mysql" */ + s[i] = '\\'; + s.insert(i+1,"0",1); i++;//lint !e534 + break; + case '\n': /* Must be escaped for logs */ + s[i] = '\\'; + s.insert(i+1,"n",1); i++;//lint !e534 + break; + case '\r': + s[i] = '\\'; + s.insert(i+1,"r",1); i++;//lint !e534 + break; + case '\\': + s[i] = '\\'; + s.insert(i+1,"\\",1); i++;//lint !e534 + break; + case '\"': + s[i] = '\\'; + s.insert(i+1,"\"",1); i++;//lint !e534 + break; + case '\'': /* Better safe than sorry */ + s[i] = '\\'; + s.insert(i+1,"\'",1); i++;//lint !e534 + break; + case '\032': /* This gives problems on Win32 */ + s[i] = '\\'; + s.insert(i+1,"Z",1); i++;//lint !e534 + break; + default: + break; + } + } +} + +void HSLToRGB( Color &out, float *hsl ) +{ + float sat[3],ctmp[3]; + out[ 3 ] = 255; + + while (hsl[ 0 ] < 0) + hsl[ 0 ] += 360; + while (hsl[ 0 ] > 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; +} diff --git a/devtools/ep2_deathmap/ep2_deathmap.vpc b/devtools/ep2_deathmap/ep2_deathmap.vpc new file mode 100644 index 0000000..b66f955 --- /dev/null +++ b/devtools/ep2_deathmap/ep2_deathmap.vpc @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------------- +// EP2_DEATHMAP.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;$SRCDIR\utils\vmpi;$SRCDIR\game\shared;$SRCDIR\game\server" + $EnableC++Exceptions "Yes (/EHsc)" + } +} + +$Project "ep2_deathmap" +{ + $Folder "Source Files" + { + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "ep2_deathmap.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\tier0\icommandline.h" + $File "$SRCDIR\utils\vmpi\imysqlwrapper.h" + $File "$SRCDIR\public\tier0\memdbgoff.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + } + + $Folder "Link Libraries" + { + $Lib tier2 + $Lib $LIBCOMMON/jpeglib + } +} |