You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1315 lines
31 KiB
1315 lines
31 KiB
//========= 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; |
|
}
|
|
|