summaryrefslogtreecommitdiff
path: root/devtools/ep2_deathmap
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /devtools/ep2_deathmap
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'devtools/ep2_deathmap')
-rw-r--r--devtools/ep2_deathmap/ep2_deathmap.cpp1315
-rw-r--r--devtools/ep2_deathmap/ep2_deathmap.vpc54
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
+ }
+}