This commit is contained in:
FluorescentCIAAfricanAmerican
2020-04-22 12:56:21 -04:00
commit 3bf9df6b27
15370 changed files with 5489726 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// stdafx.cpp : source file that includes just the standard includes
// tagbuild.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file
+26
View File
@@ -0,0 +1,26 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// stdafx.h : include file for standard system include files,
// or project specific include files that are used frequently, but
// are changed infrequently
//
#if !defined(AFX_STDAFX_H__BC019BF6_D3B1_4EA1_AA0E_044EEC919C6D__INCLUDED_)
#define AFX_STDAFX_H__BC019BF6_D3B1_4EA1_AA0E_044EEC919C6D__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
// TODO: reference additional headers your program requires here
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_STDAFX_H__BC019BF6_D3B1_4EA1_AA0E_044EEC919C6D__INCLUDED_)
@@ -0,0 +1,34 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================
#ifndef BASE_GAMESTATS_PARSE_H
#define BASE_GAMESTATS_PARSE_H
#ifdef _WIN32
#pragma once
#endif
enum CustomDataReturnCode
{
CUSTOMDATA_NONE = 0,
CUSTOMDATA_FAILED,
CUSTOMDATA_SUCCESS
};
class IMySQL;
struct ParseContext_t
{
char const *file;
char const *gamename;
bool describeonly;
IMySQL *mysql;
int skipcount;
bool bCustomDirectoryNotMade;
};
void ProcessPerfData( IMySQL *pSQL, time_t fileTime, char const *pGameName, char const *pPerfString );
#endif // BASE_GAMESTATS_PARSE_H
+45
View File
@@ -0,0 +1,45 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: The cs game stats header
//
// $NoKeywords: $
//=============================================================================//
#define CS_STATS_BLOB_VERSION 3
extern const char *pValidStatLevels[];
#define CS_NUM_LEVELS 18
#define WEAPON_MAX 34
int GetCSLevelIndex( const char *pLevelName );
typedef struct
{
char szGameName[8];
byte iVersion;
char szMapName[32];
char ipAddr[4];
short port;
int serverid;
} gamestats_header_t;
typedef struct
{
gamestats_header_t header;
short iMinutesPlayed;
short iTerroristVictories[CS_NUM_LEVELS];
short iCounterTVictories[CS_NUM_LEVELS];
short iBlackMarketPurchases[WEAPON_MAX];
short iAutoBuyPurchases;
short iReBuyPurchases;
short iAutoBuyM4A1Purchases;
short iAutoBuyAK47Purchases;
short iAutoBuyFamasPurchases;
short iAutoBuyGalilPurchases;
short iAutoBuyVestHelmPurchases;
short iAutoBuyVestPurchases;
} cs_gamestats_t;
@@ -0,0 +1,191 @@
create table weapons
(
WeaponID INT,
PRIMARY KEY( WeaponID ),
Count INT,
) TYPE=MyISAM;
create table maps
(
MapName CHAR(16),
PRIMARY KEY( MapName ),
TerroristWins INT,
CTWins INT,
) TYPE=MyISAM;
create table autobuy
(
Purchase CHAR(16),
PRIMARY KEY( Purchase ),
Count INT,
) TYPE=MyISAM;
create table purchases_pricing
(
snapshot CHAR(16),
PRIMARY KEY( snapshot ),
dt2 DATETIME,
weapon_id INT,
purchases INT,
current_price INT,
projected_price INT,
) TYPE=MyISAM;
create table snapshot_counter
(
counter INT,
PRIMARY KEY( counter ),
) TYPE=MyISAM;
create table weapon_info
(
WeaponID INT,
PRIMARY KEY( WeaponID ),
side ENUM('Terrorists', 'Counter-Terrorists', 'Both'),
fullname CHAR( 32 ),
default_price INT,
current_price INT,
) TYPE=MyISAM;
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 1, 3, "228 COMPACT", 600, 600 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 2, 3, "9X19MM SIDEARM", 400, 400 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 16, 3, "K&M .45 TACTICAL", 500, 500 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 25, 3, "NIGHT HAWK .50C", 650, 650 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 11, 2, "ES FIVE-SEVEN", 750, 750 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 10, 1, ".40 DUAL ELITES", 800, 800 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 20, 3, "LEONE 12 GAUGE SUPER", 1700, 1700 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 5, 3, "LEONE YG1265 AUTO SHOTGUN", 3000, 3000 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 22, 2, "SCHMIDT MACHINE PISTOL", 1250, 1250 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 18, 3, "K&M SUB-MACHINE GUN", 1500, 1500 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 12, 3, "K&M UMP45", 1700, 1700 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 29, 3, "ES C90", 2350, 2350 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 7, 1, "INGRAM MAC-10", 1400, 1400 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 15, 2, "CLARION 5.56", 2250, 2250 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 3, 3, "SCHMIDT SCOUT", 2750, 2750 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 21, 2, "MAVERICK M4A1 CARBINE", 3100, 3100 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 8, 2, "BULLPUP", 3500, 3500 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 13, 1, "KRIEG 550 COMMANDO", 4200, 4200 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 26, 1, "KRIEG 552", 3500, 3500 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 17, 3, "MAGNUM SNIPER RIFLE", 4750, 4750 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 14, 1, "IDF DEFENDER", 2000, 2000 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 27, 1, "CV-47", 2500, 2500 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 23, 2, "D3/AU-1", 5000, 5000 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 19, 3, "M249", 5750, 5750 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 31, 3, "KEVLAR", 650, 650 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 32, 3, "KEVLAR+HELMET", 1000, 1000 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 24, 3, "FLASHBANG", 200, 200 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 4, 3, "HE GRENADE", 300, 300 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 9, 3, "SMOKE GRENADE", 300, 300 );
Insert into weapon_info ( WeaponID, side, fullname, default_price, current_price ) values ( 33, 3, "NIGHTVISION", 1250, 1250 );
# update weapons set Count=Count+450 where WeaponID = 0;
# Insert into weapons (WeaponID,Count) values ( 1, 0 );
# update maps set TerroristWins=0, CTWins=0 where MapName = "cs_assault";
# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_assault", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_compound", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_havana", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_italy", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_militia", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "cs_office", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_aztec", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_cbble", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_chateau", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_dust2", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_dust", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_inferno", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_nuke", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_piranesi", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_port", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_prodigy", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_tides", 0, 0 );
# Insert into maps (MapName,TerroristWins, CTWins) values ( "de_train", 0, 0 );
//Autobuy
Insert into autobuy (Purchase, Count) values ( "Auto-Buy", 0 );
Insert into autobuy (Purchase, Count) values ( "Re-Buy", 0 );
Insert into autobuy (Purchase, Count) values ( "Auto-Buy: M4A1", 0 );
Insert into autobuy (Purchase, Count) values ( "Auto-Buy: AK47", 0 );
Insert into autobuy (Purchase, Count) values ( "Auto-Buy: Famas", 0 );
Insert into autobuy (Purchase, Count) values ( "Auto-Buy: Galil", 0 );
Insert into autobuy (Purchase, Count) values ( "Auto-Buy: Suit", 0 );
Insert into autobuy (Purchase, Count) values ( "Auto-Buy: Kev", 0 );
update autobuy set Count=0 where Purchase = "Auto-Buy";
update autobuy set Count=0 where Purchase = "Re-Buy";
update autobuy set Count=0 where Purchase = "Auto-Buy: M4A1";
update autobuy set Count=0 where Purchase = "Auto-Buy: AK47";
update autobuy set Count=0 where Purchase = "Auto-Buy: Famas";
update autobuy set Count=0 where Purchase = "Auto-Buy: Galil";
update autobuy set Count=0 where Purchase = "Auto-Buy: Suit";
update autobuy set Count=0 where Purchase = "Auto-Buy: Kev";
//Copy and Paste into the SQL terminal to reset everything
update weapons set Count=0 where WeaponID = 1;
update weapons set Count=0 where WeaponID = 2;
update weapons set Count=0 where WeaponID = 3;
update weapons set Count=0 where WeaponID = 4;
update weapons set Count=0 where WeaponID = 5;
update weapons set Count=0 where WeaponID = 6;
update weapons set Count=0 where WeaponID = 7;
update weapons set Count=0 where WeaponID = 8;
update weapons set Count=0 where WeaponID = 9;
update weapons set Count=0 where WeaponID = 10;
update weapons set Count=0 where WeaponID = 11;
update weapons set Count=0 where WeaponID = 12;
update weapons set Count=0 where WeaponID = 13;
update weapons set Count=0 where WeaponID = 14;
update weapons set Count=0 where WeaponID = 15;
update weapons set Count=0 where WeaponID = 16;
update weapons set Count=0 where WeaponID = 17;
update weapons set Count=0 where WeaponID = 18;
update weapons set Count=0 where WeaponID = 19;
update weapons set Count=0 where WeaponID = 20;
update weapons set Count=0 where WeaponID = 21;
update weapons set Count=0 where WeaponID = 22;
update weapons set Count=0 where WeaponID = 23;
update weapons set Count=0 where WeaponID = 24;
update weapons set Count=0 where WeaponID = 25;
update weapons set Count=0 where WeaponID = 26;
update weapons set Count=0 where WeaponID = 27;
update weapons set Count=0 where WeaponID = 28;
update weapons set Count=0 where WeaponID = 29;
update weapons set Count=0 where WeaponID = 30;
update weapons set Count=0 where WeaponID = 31;
update weapons set Count=0 where WeaponID = 32;
update weapons set Count=0 where WeaponID = 33;
update maps set TerroristWins=0, CTWins=0 where MapName = "cs_assault";
update maps set TerroristWins=0, CTWins=0 where MapName = "cs_compound";
update maps set TerroristWins=0, CTWins=0 where MapName = "cs_havana";
update maps set TerroristWins=0, CTWins=0 where MapName = "cs_italy";
update maps set TerroristWins=0, CTWins=0 where MapName = "cs_militia";
update maps set TerroristWins=0, CTWins=0 where MapName = "cs_office";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_aztec";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_cbble";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_chateau";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_dust2";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_dust";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_inferno";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_nuke";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_piranesi";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_port";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_prodigy";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_tides";
update maps set TerroristWins=0, CTWins=0 where MapName = "de_train";
@@ -0,0 +1,235 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
//
//
#include "stdafx.h"
#include <stdio.h>
#include <process.h>
#include <string.h>
#include <windows.h>
#include <sys/stat.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 "tier2/tier2.h"
#include "filesystem.h"
#include "cbase.h"
#include "gamestats.h"
#include "cs_gamestats.h"
#include "base_gamestats_parse.h"
extern CUtlDict< int, unsigned short > g_mapOrder;
const char *pValidStatLevels[] =
{
"cs_assault",
"cs_compound",
"cs_havana",
"cs_italy",
"cs_militia",
"cs_office",
"de_aztec",
"de_cbble",
"de_chateau",
"de_dust2",
"de_dust",
"de_inferno",
"de_nuke",
"de_piranesi",
"de_port",
"de_prodigy",
"de_tides",
"de_train",
};
static const char * s_WeaponAliasInfo[] =
{
"none", // WEAPON_NONE
"p228", // WEAPON_P228
"glock", // WEAPON_GLOCK // old glock
"scout", // WEAPON_SCOUT
"hegren", // WEAPON_HEGRENADE
"xm1014", // WEAPON_XM1014 // auto shotgun
"c4", // WEAPON_C4
"mac10", // WEAPON_MAC10 // T only
"aug", // WEAPON_AUG
"sgren", // WEAPON_SMOKEGRENADE
"elite", // WEAPON_ELITE
"fiveseven",// WEAPON_FIVESEVEN
"ump45", // WEAPON_UMP45
"sg550", // WEAPON_SG550 // auto-sniper
"galil", // WEAPON_GALIL
"famas", // WEAPON_FAMAS // CT cheap m4a1
"usp", // WEAPON_USP
"awp", // WEAPON_AWP
"mp5navy", // WEAPON_MP5N
"m249", // WEAPON_M249 // big machinegun
"m3", // WEAPON_M3 // cheap shotgun
"m4a1", // WEAPON_M4A1
"tmp", // WEAPON_TMP
"g3sg1", // WEAPON_G3SG1 // T auto-sniper
"flash", // WEAPON_FLASHBANG
"deagle", // WEAPON_DEAGLE
"sg552", // WEAPON_SG552 // T aug equivalent
"ak47", // WEAPON_AK47
"knife", // WEAPON_KNIFE
"p90", // WEAPON_P90
"shield", // WEAPON_SHIELDGUN
"kevlar",
"assaultsuit",
"nightvision",
NULL, // WEAPON_NONE
};
void DescribeData( cs_gamestats_t &stats )
{
Msg( " Blob version: %d\n", stats.header.iVersion );
Msg( " Server Uptime: %d\n", stats.iMinutesPlayed );
for ( int i = 0; i < CS_NUM_LEVELS; i++ )
{
Msg( "%s - Terrorists Wins: %d | Counter-Terrorists Wins: %d\n", pValidStatLevels[i], stats.iTerroristVictories[i], stats.iCounterTVictories[i] );
}
for ( int i = 0; i < WEAPON_MAX; i++ )
{
Msg( "%s was purchased %d time(s)\n", s_WeaponAliasInfo[i], stats.iBlackMarketPurchases[i] );
}
char q[ 512 ];
Q_snprintf( q, sizeof( q ), "Auto-Buy = %d\n", stats.iAutoBuyPurchases );
Msg( q );
Q_snprintf( q, sizeof( q ), "Re-Buy = %d\n", stats.iReBuyPurchases );
Msg( q );
Q_snprintf( q, sizeof( q ), "Auto-Buy: M4A1 = %d\n", stats.iAutoBuyM4A1Purchases );
Msg( q );
Q_snprintf( q, sizeof( q ), "Auto-Buy: AK47 = %d\n", stats.iAutoBuyAK47Purchases );
Msg( q );
Q_snprintf( q, sizeof( q ), "Auto-Buy: Famas = %d\n", stats.iAutoBuyFamasPurchases );
Msg( q );
Q_snprintf( q, sizeof( q ), "Auto-Buy: Galil = %d\n", stats.iAutoBuyGalilPurchases );
Msg( q );
Q_snprintf( q, sizeof( q ), "Auto-Buy: Suit = %d\n", stats.iAutoBuyVestHelmPurchases );
Msg( q );
Q_snprintf( q, sizeof( q ), "Auto-Buy: Kev = %d\n", stats.iAutoBuyVestPurchases );
Msg( q );
}
int CS_ParseCustomGameStatsData( ParseContext_t *ctx )
{
if ( g_pFullFileSystem == NULL )
return CUSTOMDATA_FAILED;
FileHandle_t FileHandle = g_pFullFileSystem->Open( ctx->file, "rb" );
if ( !FileHandle )
{
return CUSTOMDATA_FAILED;
}
if ( ctx->mysql == NULL && ctx->describeonly == false )
return CUSTOMDATA_FAILED;
char q[ 512 ];
cs_gamestats_t stats;
g_pFullFileSystem->Read( &stats, sizeof( cs_gamestats_t ), FileHandle );
if ( Q_stricmp( stats.header.szGameName, "cstrike" ) )
return CUSTOMDATA_FAILED;
if ( stats.header.iVersion != CS_STATS_BLOB_VERSION )
{
Msg( "Error: Incorrect Blob Version! Got: %d - Expected: %d\n", stats.header.iVersion, CS_STATS_BLOB_VERSION );
return CUSTOMDATA_FAILED;
}
if ( ctx->describeonly == true )
{
DescribeData( stats );
return CUSTOMDATA_SUCCESS;
}
//Do maps first
for ( int i = 0; i < CS_NUM_LEVELS; i++ )
{
Q_snprintf( q, sizeof( q ), "update maps set TerroristWins=TerroristWins+%d, CTWins=CTWins+%d where MapName = \"%s\";", stats.iTerroristVictories[i], stats.iCounterTVictories[i], pValidStatLevels[i] );
int retcode = ctx->mysql->Execute( q );
if ( retcode != 0 )
{
printf( "Query:\n %s\n failed\n", q );
return CUSTOMDATA_FAILED;
}
}
//Now do all weapons
for ( int i = 0; i < WEAPON_MAX; i++ )
{
int iWeaponID = i;
//HACKHACK: Fix up incorrect data for the smoke grenades.
if ( i == 0 && stats.iBlackMarketPurchases[i] != 0 )
{
iWeaponID = 9;
}
Q_snprintf( q, sizeof( q ), "update weapons set Count=Count+%d where WeaponID = %d;", stats.iBlackMarketPurchases[i], iWeaponID );
int retcode = ctx->mysql->Execute( q );
if ( retcode != 0 )
{
printf( "Query:\n %s\n failed\n", q );
return CUSTOMDATA_FAILED;
}
}
Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy\";", stats.iAutoBuyPurchases );
ctx->mysql->Execute( q );
Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Re-Buy\";", stats.iReBuyPurchases );
ctx->mysql->Execute( q );
Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: M4A1\";", stats.iAutoBuyM4A1Purchases );
ctx->mysql->Execute( q );
Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: AK47\";", stats.iAutoBuyAK47Purchases );
ctx->mysql->Execute( q );
Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: Famas\";", stats.iAutoBuyFamasPurchases );
ctx->mysql->Execute( q );
Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: Galil\";", stats.iAutoBuyGalilPurchases );
ctx->mysql->Execute( q );
Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: Suit\";", stats.iAutoBuyVestHelmPurchases );
ctx->mysql->Execute( q );
Q_snprintf( q, sizeof( q ), "update autobuy set Count=Count+%d where Purchase = \"Auto-Buy: Kev\";", stats.iAutoBuyVestPurchases );
ctx->mysql->Execute( q );
return CUSTOMDATA_SUCCESS;
}
@@ -0,0 +1,47 @@
# create database gamestats;
# use gamestats;
create table ep1
(
UserID CHAR(16),
PRIMARY KEY( UserID ),
LastUpdate DATETIME,
KEY( LastUpdate ),
Version TINYINT,
Count INT,
Seconds INT,
HDR INT,
Captions INT,
Commentary INT,
Easy INT,
Medium INT,
Hard INT,
nonsteam TINYINT,
cybercafe TINYINT,
hl2_chapter TINYINT,
SecondsToCompleteGame INT, # Non-zero if user has completed game
HighestMap CHAR(16),
DXLevel INT,
Deaths INT,
) TYPE=MyISAM;
create table ep1_maps
(
UserID CHAR(16),
MapName CHAR(16),
PRIMARY KEY( UserID,MapName ),
LastUpdate DATETIME,
KEY( LastUpdate ),
Version TINYINT,
Count INT,
Seconds INT,
HDR INT,
Captions INT,
Commentary INT,
Easy INT,
Medium INT,
Hard INT,
nonsteam TINYINT,
cybercafe TINYINT,
Deaths INT,
) TYPE=MyISAM;
+168
View File
@@ -0,0 +1,168 @@
create database gamestats_ep2;
use gamestats_ep2;
create table ep2
(
UserID CHAR(16),
PRIMARY KEY( UserID ),
LastUpdate DATETIME,
KEY( LastUpdate ),
Version TINYINT,
Tag CHAR(8),
KEY( Tag ),
Count INT,
Seconds INT,
HDR INT,
Captions INT,
Commentary INT,
Easy INT,
Medium INT,
Hard INT,
nonsteam TINYINT,
cybercafe TINYINT,
hl2_chapter TINYINT,
SecondsToCompleteGame INT, # Non-zero if user has completed game
HighestMap CHAR(20),
DXLevel INT,
Deaths INT
) TYPE=MyISAM;
create table ep2_maps
(
UserID CHAR(16),
MapName CHAR(20),
PRIMARY KEY( UserID,MapName ),
LastUpdate DATETIME,
KEY( LastUpdate ),
Version TINYINT,
Tag CHAR(8),
KEY( Tag ),
Count INT,
Seconds INT,
HDR INT,
Captions INT,
Commentary INT,
Easy INT,
Medium INT,
Hard INT,
nonsteam TINYINT,
cybercafe TINYINT,
Deaths INT
) TYPE=MyISAM;
create table ep2_entities
(
UserID CHAR(16),
Tag CHAR(8),
KEY( Tag ),
MapName CHAR(20),
MapVersion INT,
KEY( MapVersion ),
Entity CHAR(32),
PRIMARY KEY( UserID,Tag,MapName,Entity ),
LastUpdate DATETIME,
KEY( LastUpdate ),
BodyCount INT,
KilledPlayer INT
) TYPE=MyISAM;
create table ep2_deaths
(
UserID CHAR(16),
Tag CHAR(8),
KEY( Tag ),
MapName CHAR(20),
MapVersion INT,
KEY( MapVersion ),
LastUpdate DATETIME,
KEY( LastUpdate ),
DeathIndex INT,
X SMALLINT,
Y SMALLINT,
Z SMALLINT,
PRIMARY KEY ( UserID, Tag, MapName, X, Y, Z )
) TYPE=MyISAM;
create table ep2_weapons
(
UserID CHAR(16),
Tag CHAR(8),
KEY( Tag ),
MapName CHAR(20),
MapVersion INT,
KEY( MapVersion ),
Weapon CHAR(32),
PRIMARY KEY( UserID,Tag, MapName,Weapon ),
LastUpdate DATETIME,
KEY( LastUpdate ),
Shots INT,
Hits INT,
Damage DOUBLE
) TYPE=MyISAM;
create table ep2_saves
(
UserID CHAR(16),
Tag CHAR(8),
KEY( Tag ),
MapName CHAR(20),
MapVersion INT,
KEY( MapVersion ),
LastUpdate DATETIME,
KEY( LastUpdate ),
FIRSTDEATH INT, # index into ep2_deaths
NUMDEATHS INT,
X SMALLINT,
Y SMALLINT,
Z SMALLINT,
HEALTH SMALLINT,
SAVETYPE TINYINT, # 0 unknown, 1 autosave, 2 user save (quick or other)
PRIMARY KEY( UseriD, Tag, MapName, FirstDeath, NumDeaths )
) TYPE=MyISAM;
create table ep2_counters
(
UserID CHAR(16),
Tag CHAR(8),
KEY( Tag ),
MapName CHAR(20),
MapVersion INT,
KEY( MapVersion ),
PRIMARY KEY( UserID,Tag,MapName ),
LastUpdate DATETIME,
KEY( LastUpdate ),
CRATESSMASHED INT,
OBJECTSPUNTED INT,
VEHICULARHOMICIDES INT,
DISTANCE_INVEHICLE BIGINT,
DISTANCE_ONFOOT BIGINT,
DISTANCE_ONFOOTSPRINTING BIGINT,
FALLINGDEATHS INT,
VEHICLE_OVERTURNED INT,
LOADGAME_STILLALIVE INT,
LOADS INT,
SAVES INT,
GODMODES INT,
NOCLIPS INT,
DAMAGETAKEN DOUBLE
) TYPE=MyISAM;
create table ep2_generic
(
UserID CHAR(16),
Tag CHAR(8),
KEY( Tag ),
MapName CHAR(20),
MapVersion INT,
KEY( MapVersion ),
StatName CHAR(16),
PRIMARY KEY( UserID,Tag, MapName,StatName ),
LastUpdate DATETIME,
KEY( LastUpdate ),
Count INT,
Value DOUBLE,
X SMALLINT,
Y SMALLINT,
Z SMALLINT
) TYPE=MyISAM;
@@ -0,0 +1,606 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
#include "stdafx.h"
#include <stdio.h>
#include <process.h>
#include <string.h>
#include <windows.h>
#include <sys/stat.h>
#include <time.h>
#include <string>
#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 "tier2/tier2.h"
#include "filesystem.h"
#include "cbase.h"
#include "gamestats.h"
#include "episodic/ep2_gamestats.h"
#include "base_gamestats_parse.h"
void v_escape_string( std::string& s );
// EP2 custom data blob parsing stuff
static Ep2LevelStats_t *g_pCurrentMap;
static CUtlDict<Ep2LevelStats_t, unsigned short> g_dictMapStats;
extern CUtlDict< int, unsigned short > g_mapOrder;
void DescribeData( BasicGameStats_t &stats, const char *szStatsFileUserID, int iStatsFileVersion );
void InsertData( CUtlDict< int, unsigned short >& mapOrder, IMySQL *sql, BasicGameStats_t &gs, const char *szStatsFileUserID, int iStatsFileVersion, const char *gamename, char const *tag = NULL );
struct CounterInfo_t
{
int type;
char const *counterName;
};
static CounterInfo_t g_IntCounterNames[ ] =
{
{ Ep2LevelStats_t::COUNTER_CRATESSMASHED, "Crates Smashed" },
{ Ep2LevelStats_t::COUNTER_OBJECTSPUNTED, "Objects Punted" },
{ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES, "Vehicular Homicides" },
{ Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE, "Distance in Vehicles (inches)" },
{ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT, "Distance on Foot (inches)" },
{ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING, "Distance on Foot Sprint (inches)" },
{ Ep2LevelStats_t::COUNTER_FALLINGDEATHS, "Falling Death" },
{ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED, "Flipped Vehicles" },
{ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE, "Loadgame while player alive" },
{ Ep2LevelStats_t::COUNTER_LOADS, "Number of saves (w/autosaves)" },
{ Ep2LevelStats_t::COUNTER_SAVES, "Number of game loads" },
{ Ep2LevelStats_t::COUNTER_GODMODES, "Times entered God mode" },
{ Ep2LevelStats_t::COUNTER_NOCLIPS, "Times entered NoClip mode" },
};
static CounterInfo_t g_FloatCounterNames[ ] =
{
{ Ep2LevelStats_t::COUNTER_DAMAGETAKEN, "Damage Taken" },
};
char const *SaveType( int type )
{
switch ( type )
{
default:
case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_UNKNOWN:
break;
case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_AUTOSAVE:
return "Auto Save";
case Ep2LevelStats_t::SaveGameInfoRecord2_t::TYPE_USERSAVE:
return "User Save";
}
return "Unknown";
}
#define INCHES_TO_MILES ( 1.0 / ( 5280.0 * 12.0 ) )
#define INCHES_TO_FEET ( 1.0 / 12.0 )
void DescribeEp2Stats()
{
int i;
int iMap;
for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
{
// Get the current map.
Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
Msg( "map version[ %d ], user tag[ %s ]\n", pCurrentMap->m_Tag.m_nMapVersion, pCurrentMap->m_Tag.m_szTagText );
Msg( " --- %s ------\n %d deaths\n", pCurrentMap->m_Header.m_szMapName, pCurrentMap->m_aPlayerDeaths.Count() );
for ( i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); ++i )
{
Msg( " @ ( %d, %d, %d )\n",
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
}
Msg( " -- Weapon Stats --\n" );
CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
{
const Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
double acc = 0.0f;
if ( lump.m_nShots > 0 )
{
acc = 100.0f * (double)lump.m_nHits / (double)lump.m_nShots;
}
Msg( " %32.32s: shots %5d hits %5d damage %8.2f [accuracy %8.2f %%]\n", wdict.GetElementName( i ), lump.m_nShots, lump.m_nHits, lump.m_flDamageInflicted, acc );
}
Msg( " -- NPC Stats -- \n" );
CUtlDict< Ep2LevelStats_t::EntityDeathsLump_t, int > &dict = pCurrentMap->m_dictEntityDeaths;
for ( i = dict.First(); i != dict.InvalidIndex(); i = dict.Next( i ) )
{
const Ep2LevelStats_t::EntityDeathsLump_t &lump = dict[ i ];
Msg( " %32.32s: bodycount %5d killedplayer %3d\n", dict.GetElementName( i ), lump.m_nBodyCount, lump.m_nKilledPlayer );
}
for ( i = 0; i < Ep2LevelStats_t::NUM_INTCOUNTER_TYPES; ++i )
{
if ( i == Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ||
i == Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ||
i == Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING )
{
Msg( " %32.32s: %8I64u [%8.3f feet] [%8.3f miles]\n", g_IntCounterNames[ i ].counterName, pCurrentMap->m_IntCounters[ i ], INCHES_TO_FEET * (double)pCurrentMap->m_IntCounters[ i ], INCHES_TO_MILES * (double)pCurrentMap->m_IntCounters[ i ] );
}
else
{
Msg( " %32.32s: %8I64u\n", g_IntCounterNames[ i ].counterName, pCurrentMap->m_IntCounters[ i ] );
}
}
for ( i = 0; i < Ep2LevelStats_t::NUM_FLOATCOUNTER_TYPES; ++i )
{
Msg( " %32.32s: %8.2f\n", g_FloatCounterNames[ i ].counterName, pCurrentMap->m_FloatCounters[ i ] );
}
Ep2LevelStats_t::SaveGameInfo_t *sg = &pCurrentMap->m_SaveGameInfo;
Msg( " -- Save Game --\n" );
time_t t = (time_t)sg->m_nCurrentSaveFileTime;
struct tm *timestamp;
timestamp = localtime( &t );
if ( t == (time_t)0 )
{
Msg( " No save file\n" );
}
else
{
Msg( " Current save %s file time %s\n", sg->m_sCurrentSaveFile.String(), asctime( timestamp ) );
}
for ( i = 0; i < sg->m_Records.Count(); ++i )
{
const Ep2LevelStats_t::SaveGameInfoRecord2_t &rec = sg->m_Records[ i ];
Msg( " %3d deaths, saved at (%5d %5d %5d) with health %3d %s\n",
rec.m_nNumDeaths,
(int)rec.m_nSavePos[ 0 ],
(int)rec.m_nSavePos[ 1 ],
(int)rec.m_nSavePos[ 2 ],
(int)rec.m_nSaveHealth,
SaveType( (int)rec.m_SaveType ) );
}
Msg( " -- Generic Stats --\n" );
CUtlDict< Ep2LevelStats_t::GenericStatsLump_t, int > &gdict = pCurrentMap->m_dictGeneric;
for ( i = gdict.First(); i != gdict.InvalidIndex(); i = gdict.Next( i ) )
{
const Ep2LevelStats_t::GenericStatsLump_t &lump = gdict[ i ];
Msg( " %32.32s: count %u total %f @[%d, %d, %d]\n", gdict.GetElementName( i ), lump.m_unCount, lump.m_flCurrentValue, lump.m_Pos[ 0 ], lump.m_Pos[ 1 ], lump.m_Pos[ 2 ] );
}
Msg( "\n" );
}
}
void InsertCustomData( CUtlDict< int, unsigned short >& mapOrder, IMySQL *sql, const char *szStatsFileUserID, int iStatsFileVersion, const char *gamename )
{
if ( !sql )
return;
char q[ 1024 ];
char counternames[ 512 ];
std::string userid;
userid = szStatsFileUserID;
v_escape_string( userid );
// Deal with the maps
int i;
int iMap;
for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
{
// Get the current map.
Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
char const *pszMapName = g_dictMapStats.GetElementName( iMap );
std::string mapname;
mapname = pszMapName;
v_escape_string( mapname );
std::string tag;
tag = pCurrentMap->m_Tag.m_szTagText;
v_escape_string( tag );
int mapversion = pCurrentMap->m_Tag.m_nMapVersion;
#if 1
int slot = mapOrder.Find( pszMapName );
if ( slot == mapOrder.InvalidIndex() )
{
if ( Q_stricmp( pszMapName, "devtest" ) )
continue;
}
#endif
// Deal with deaths
for ( i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); ++i )
{
Q_snprintf( q, sizeof( q ), "REPLACE into %s_deaths (UserID,Tag,LastUpdate,MapName,MapVersion,DeathIndex,X,Y,Z) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,%d,%d,%d,%d);",
gamename,
userid.c_str(),
tag.c_str(),
mapname.c_str(),
mapversion,
i,
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
}
// Deal with weapon data
CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
{
const Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
std::string wname = wdict.GetElementName( i );
v_escape_string( wname );
Q_snprintf( q, sizeof( q ), "REPLACE into %s_weapons (UserID,Tag,LastUpdate,MapName,MapVersion,Weapon,Shots,Hits,Damage) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,\"%-.32s\",%d,%d,%g);",
gamename,
userid.c_str(),
tag.c_str(),
mapname.c_str(),
mapversion,
wname.c_str(),
lump.m_nShots,
lump.m_nHits,
lump.m_flDamageInflicted
);
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
}
CUtlDict< Ep2LevelStats_t::EntityDeathsLump_t, int > &dict = pCurrentMap->m_dictEntityDeaths;
for ( i = dict.First(); i != dict.InvalidIndex(); i = dict.Next( i ) )
{
const Ep2LevelStats_t::EntityDeathsLump_t &lump = dict[ i ];
std::string ename = dict.GetElementName( i );
v_escape_string( ename );
Q_snprintf( q, sizeof( q ), "REPLACE into %s_entities (UserID,Tag,LastUpdate,MapName,MapVersion,Entity,BodyCount,KilledPlayer) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,\"%-.32s\",%d,%d);",
gamename,
userid.c_str(),
tag.c_str(),
mapname.c_str(),
mapversion,
ename.c_str(),
lump.m_nBodyCount,
lump.m_nKilledPlayer
);
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
}
// Counters
Q_snprintf( counternames, sizeof( counternames ),
"CRATESSMASHED,"\
"OBJECTSPUNTED,"\
"VEHICULARHOMICIDES,"\
"DISTANCE_INVEHICLE,"\
"DISTANCE_ONFOOT,"\
"DISTANCE_ONFOOTSPRINTING,"\
"FALLINGDEATHS,"\
"VEHICLE_OVERTURNED,"\
"LOADGAME_STILLALIVE,"\
"LOADS,"\
"SAVES,"\
"GODMODES,"\
"NOCLIPS,"\
"DAMAGETAKEN" );
Q_snprintf( q, sizeof( q ), "REPLACE into %s_counters (UserID,Tag,LastUpdate,MapName,MapVersion,%s) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,"\
"%u,"\
"%u,"\
"%u,"\
"%I64u,"\
"%I64u,"\
"%I64u,"\
"%u,"\
"%u,"\
"%u,"\
"%u,"\
"%u,"\
"%u,"\
"%u,"\
"%f);",
gamename,
counternames,
userid.c_str(),
tag.c_str(),
mapname.c_str(),
mapversion,
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_CRATESSMASHED ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_OBJECTSPUNTED ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICULARHOMICIDES ],
pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_INVEHICLE ],
pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOT ],
pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_DISTANCE_ONFOOTSPRINTING ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_FALLINGDEATHS ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_VEHICLE_OVERTURNED ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADGAME_STILLALIVE ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_LOADS ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_SAVES ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_GODMODES ],
(uint32)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ],
(double)pCurrentMap->m_FloatCounters[ Ep2LevelStats_t::COUNTER_DAMAGETAKEN ]
);
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
Ep2LevelStats_t::SaveGameInfo_t *sg = &pCurrentMap->m_SaveGameInfo;
/*
Msg( " -- Save Game --\n" );
time_t t = (time_t)sg->m_nCurrentSaveFileTime;
struct tm *timestamp;
timestamp = localtime( &t );
if ( t == (time_t)0 )
{
Msg( " No save file\n" );
}
else
{
Msg( " Current save %s file time %s\n", sg->m_sCurrentSaveFile.String(), asctime( timestamp ) );
}
*/
for ( i = 0; i < sg->m_Records.Count(); ++i )
{
const Ep2LevelStats_t::SaveGameInfoRecord2_t &rec = sg->m_Records[ i ];
Q_snprintf( q, sizeof( q ), "REPLACE into %s_saves (UserID,Tag,LastUpdate,MapName,MapVersion,FirstDeath,NumDeaths,X,Y,Z,Health,SaveType) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,%d,%d,%d,%d,%d,%d,%d);",
gamename,
userid.c_str(),
tag.c_str(),
mapname.c_str(),
mapversion,
rec.m_nFirstDeathIndex,
rec.m_nNumDeaths,
(int)rec.m_nSavePos[ 0 ],
(int)rec.m_nSavePos[ 1 ],
(int)rec.m_nSavePos[ 2 ],
(int)rec.m_nSaveHealth,
(int)rec.m_SaveType
);
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
}
// Deal with generic stats data
CUtlDict< Ep2LevelStats_t::GenericStatsLump_t, int > &gdict = pCurrentMap->m_dictGeneric;
for ( i = gdict.First(); i != gdict.InvalidIndex(); i = gdict.Next( i ) )
{
const Ep2LevelStats_t::GenericStatsLump_t &lump = gdict[ i ];
std::string statname = gdict.GetElementName( i );
v_escape_string( statname );
Q_snprintf( q, sizeof( q ), "REPLACE into %s_generic (UserID,Tag,LastUpdate,MapName,MapVersion,StatName,Count,Value,X,Y,Z) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,\"%-.16s\",%d,%f,%d,%d,%d);",
gamename,
userid.c_str(),
tag.c_str(),
mapname.c_str(),
mapversion,
statname.c_str(),
lump.m_unCount,
lump.m_flCurrentValue,
lump.m_Pos[ 0 ],
lump.m_Pos[ 1 ],
lump.m_Pos[ 2 ]
);
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *szMapName -
// Output : Ep2LevelStats_t
//-----------------------------------------------------------------------------
Ep2LevelStats_t *FindOrAddMapStats( const char *szMapName )
{
int iMap = g_dictMapStats.Find( szMapName );
if( iMap == g_dictMapStats.InvalidIndex() )
{
iMap = g_dictMapStats.Insert( szMapName );
}
return &g_dictMapStats[iMap];
}
int GetMaxLumpCount()
{
return EP2_MAX_LUMP_COUNT;
}
void LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer, char *tag, size_t tagsize )
{
tag[ 0 ] = 0;
Ep2LevelStats_t::LoadData( g_dictMapStats, LoadBuffer );
if ( g_dictMapStats.Count() > 0 )
{
Q_strncpy( tag, g_dictMapStats[ g_dictMapStats.First() ].m_Tag.m_szTagText, tagsize );
}
int i;
int iMap;
for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
{
// Get the current map.
Ep2LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
CUtlDict< Ep2LevelStats_t::WeaponLump_t, int > &wdict = pCurrentMap->m_dictWeapons;
for ( i = wdict.First(); i != wdict.InvalidIndex(); i = wdict.Next( i ) )
{
Ep2LevelStats_t::WeaponLump_t &lump = wdict[ i ];
// Bogus #, some kind of damage scaling?
if ( lump.m_flDamageInflicted > 50000 )
{
lump.m_flDamageInflicted = 0.0f;
lump.m_nHits = 0;
lump.m_nShots = 0;
}
}
if ( (int64)pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] < 0 )
{
pCurrentMap->m_IntCounters[ Ep2LevelStats_t::COUNTER_NOCLIPS ] = 0;
}
}
}
bool Ep2_ParseCurrentUserID( char const *pchDataFile, char *pchUserID, size_t bufsize, time_t &modifiedtime )
{
FILE *fp = fopen( pchDataFile, "rb" );
if ( fp )
{
CUtlBuffer statsBuffer;
struct _stat sb;
_stat( pchDataFile, &sb );
// Msg( "Processing %s\n", ctx->file );
int nBytesToRead = min( sb.st_size, sizeof( short ) + 16 );
statsBuffer.Clear();
statsBuffer.EnsureCapacity( nBytesToRead );
fread( statsBuffer.Base(), nBytesToRead, 1, fp );
statsBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesToRead );
fclose( fp );
// Skip the version
statsBuffer.GetShort();
statsBuffer.Get( pchUserID, 16 );
pchUserID[ bufsize - 1 ] = 0;
modifiedtime = sb.st_mtime;
return statsBuffer.TellPut() == statsBuffer.TellGet();
}
return false;
}
int Ep2_ParseCustomGameStatsData( ParseContext_t *ctx )
{
g_dictMapStats.Purge();
FILE *fp = fopen( ctx->file, "rb" );
if ( fp )
{
CUtlBuffer statsBuffer;
struct _stat sb;
_stat( ctx->file, &sb );
// Msg( "Processing %s\n", ctx->file );
statsBuffer.Clear();
statsBuffer.EnsureCapacity( sb.st_size );
fread( statsBuffer.Base(), sb.st_size, 1, fp );
statsBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, sb.st_size );
fclose( fp );
char shortname[ 128 ];
Q_FileBase( ctx->file, shortname, sizeof( shortname ) );
char szCurrentStatsFileUserID[17];
int iCurrentStatsFileVersion;
iCurrentStatsFileVersion = statsBuffer.GetShort();
statsBuffer.Get( szCurrentStatsFileUserID, 16 );
szCurrentStatsFileUserID[ sizeof( szCurrentStatsFileUserID ) - 1 ] = 0;
bool valid = true;
BasicGameStats_t stats;
char tag[ 32 ] = { 0 };
unsigned int iCheckIfStandardDataSaved = statsBuffer.GetUnsignedInt();
if( iCheckIfStandardDataSaved != GAMESTATS_STANDARD_NOT_SAVED )
{
//standard data was saved, rewind so the stats can read in time to completion
statsBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, -((int)sizeof( unsigned int )) );
valid = stats.ParseFromBuffer( statsBuffer, iCurrentStatsFileVersion );
if ( ctx->describeonly )
{
DescribeData( stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion );
}
}
//check for custom data
bool bHasCustomData = (valid && (statsBuffer.TellPut() != statsBuffer.TellGet()));
if( bHasCustomData )
{
LoadCustomDataFromBuffer( statsBuffer, tag, sizeof( tag ) );
if( ctx->describeonly )
{
DescribeEp2Stats();
}
else
{
InsertCustomData( g_mapOrder, ctx->mysql, szCurrentStatsFileUserID, iCurrentStatsFileVersion, ctx->gamename );
}
}
// Do base data after custom since we need the custom to parse out the "tag" used for this user
if ( valid )
{
if ( !ctx->describeonly )
{
InsertData( g_mapOrder, ctx->mysql, stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion, ctx->gamename, tag );
}
}
else
{
++ctx->skipcount;
}
}
return CUSTOMDATA_SUCCESS;
}
@@ -0,0 +1,47 @@
# create database gamestats;
# use gamestats;
create table portal
(
UserID CHAR(16),
PRIMARY KEY( UserID ),
LastUpdate DATETIME,
KEY( LastUpdate ),
Version TINYINT,
Count INT,
Seconds INT,
HDR INT,
Captions INT,
Commentary INT,
Easy INT,
Medium INT,
Hard INT,
nonsteam TINYINT,
cybercafe TINYINT,
hl2_chapter TINYINT,
SecondsToCompleteGame INT, # Non-zero if user has completed game
HighestMap CHAR(16),
DXLevel INT,
Deaths INT,
) TYPE=MyISAM;
create table portal_maps
(
UserID CHAR(16),
MapName CHAR(16),
PRIMARY KEY( UserID,MapName ),
LastUpdate DATETIME,
KEY( LastUpdate ),
Version TINYINT,
Count INT,
Seconds INT,
HDR INT,
Captions INT,
Commentary INT,
Easy INT,
Medium INT,
Hard INT,
nonsteam TINYINT,
cybercafe TINYINT,
Deaths INT,
) TYPE=MyISAM;
@@ -0,0 +1,982 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// tagbuild.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#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 "base_gamestats_parse.h"
#include "cbase.h"
#include "gamestats.h"
#include "tier0/icommandline.h"
// roll-our-own symbol table class. Note we don't use CUtlSymbolTable because that and related classes have short int deeply baked in as index type, so can
// only hold 64K entries. We sometimes need to process more than 64K files at a time.
struct AnalysisData
{
AnalysisData()
{
symbols.SetLessFunc( CaselessStringLessThanIgnoreSlashes );
}
~AnalysisData()
{
int i = symbols.FirstInorder();
while ( i != symbols.InvalidIndex() )
{
const char *symbol = symbols[i];
if ( symbol )
{
delete symbol;
}
i = symbols.NextInorder( i );
}
}
CUtlRBTree<const char*,int> symbols;
};
static AnalysisData g_Analysis;
static bool describeonly = false;
typedef int (*DataParseFunc)( ParseContext_t * );
typedef void (*PostImportFunc) ( IMySQL *sql );
typedef bool (*ParseCurrentUserIDFunc)( char const *pchDataFile, char *pchUserID, size_t bufsize, time_t &modifiedtime );
extern int CS_ParseCustomGameStatsData( ParseContext_t *ctx );
extern int Ep2_ParseCustomGameStatsData( ParseContext_t *ctx );
extern int TF_ParseCustomGameStatsData( ParseContext_t *ctx );
extern void TF_PostImport( IMySQL *sql );
int Default_ParseCustomGameStatsData( ParseContext_t *ctx );
extern bool Ep2_ParseCurrentUserID( char const *pchDataFile, char *pchUserID, size_t bufsize, time_t &modifiedtime );
struct DataParser_t
{
char const *pchGameName;
DataParseFunc pfnParseFunc;
PostImportFunc pfnPostImport;
ParseCurrentUserIDFunc pfnParseUserID;
};
static DataParser_t g_ParseFuncs[] =
{
{ "cstrike", CS_ParseCustomGameStatsData, NULL },
{ "tf", TF_ParseCustomGameStatsData, TF_PostImport },
// { "dods", Default_ParseCustomGameStatsData, NULL },
// { "portal", Default_ParseCustomGameStatsData, NULL },
{ "ep1", Default_ParseCustomGameStatsData, NULL }, // Just a STUB
{ "ep2", Ep2_ParseCustomGameStatsData, NULL, Ep2_ParseCurrentUserID }
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void printusage( void )
{
printf( "processgamestats:\n" );
printf( "processgamestats game dbhost user password dbname rootdir\n" );
printf( "processgamestats game datafile [describe only]\n\n" );
printf( "valid gamenames:\n" );
for ( int i = 0 ; i < ARRAYSIZE( g_ParseFuncs ); ++i )
{
printf( " %s\n", g_ParseFuncs[ i ].pchGameName );
}
// Exit app
exit( 1 );
}
void BuildFileList_R( CUtlVector< int >& files, char const *dir, char const *extension )
{
WIN32_FIND_DATA wfd;
char directory[ 256 ];
char filename[ 256 ];
HANDLE ff;
sprintf( directory, "%s\\*.*", dir );
if ( ( ff = FindFirstFile( directory, &wfd ) ) == INVALID_HANDLE_VALUE )
return;
int extlen = strlen( extension );
do
{
if ( wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
{
if ( wfd.cFileName[ 0 ] == '.' )
continue;
// Recurse down directory
sprintf( filename, "%s\\%s", dir, wfd.cFileName );
BuildFileList_R( files, filename, extension );
}
else
{
int len = strlen( wfd.cFileName );
if ( len > extlen )
{
if ( !stricmp( &wfd.cFileName[ len - extlen ], extension ) )
{
char filename[ MAX_PATH ];
Q_snprintf( filename, sizeof( filename ), "%s\\%s", dir, wfd.cFileName );
_strlwr( filename );
Q_FixSlashes( filename );
char *symbol = strdup( filename );
int sym = g_Analysis.symbols.Insert( symbol );
files.AddToTail( sym );
}
}
}
} while ( FindNextFile( ff, &wfd ) );
}
void BuildFileList( CUtlVector< int >& files, char const *rootdir, char const *extension )
{
files.RemoveAll();
BuildFileList_R( files, rootdir, extension );
}
void DescribeData( BasicGameStats_t &stats, const char *szStatsFileUserID, int iStatsFileVersion )
{
double averageSession = 0.0f;
if ( stats.m_Summary.m_nCount > 0 )
{
averageSession = (double)stats.m_Summary.m_nSeconds / (double)stats.m_Summary.m_nCount;
}
Msg( "---------------------------------------------------------------------------\n" );
Msg( "%16.16s : %s\n", "User", szStatsFileUserID );
Msg( " %16.16s: %8d\n", "Blob version", iStatsFileVersion );
Msg( " %16.16s: %8d sessions\n", "Played", stats.m_Summary.m_nCount );
Msg( " %16.16s: %8d seconds\n", "Total Time", stats.m_Summary.m_nSeconds );
Msg( " %16.16s: %8.2f seconds\n", "Avg Session", averageSession );
Msg( " %16.16s: %8d\n", "Commentary", stats.m_Summary.m_nCommentary );
Msg( " %16.16s: %8d\n", "HDR", stats.m_Summary.m_nHDR );
Msg( " %16.16s: %8d\n", "Captions", stats.m_Summary.m_nCaptions );
Msg( " %16.16s: %8d\n", "Easy", stats.m_Summary.m_nSkill[ 0 ] );
Msg( " %16.16s: %8d\n", "Medium", stats.m_Summary.m_nSkill[ 1 ] );
Msg( " %16.16s: %8d\n", "Hard", stats.m_Summary.m_nSkill[ 2 ] );
Msg( " %16.16s: %8d seconds\n", "Completion time ", stats.m_nSecondsToCompleteGame );
Msg( " %16.16s: %8d\n", "Number of deaths", stats.m_Summary.m_nDeaths );
Msg( " -- Maps played --\n" );
for ( int i = stats.m_MapTotals.First(); i != stats.m_MapTotals.InvalidIndex(); i = stats.m_MapTotals.Next( i ) )
{
char const *mapname = stats.m_MapTotals.GetElementName( i );
BasicGameStatsRecord_t &rec = stats.m_MapTotals[ i ];
Msg( " %16.16s: %5d seconds in %3d sessions (%4d deaths)\n", mapname, rec.m_nSeconds, rec.m_nCount, rec.m_nDeaths );
}
}
#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 InsertData( CUtlDict< int, unsigned short >& mapOrder, IMySQL *sql, BasicGameStats_t &gs, const char *szStatsFileUserID, int iStatsFileVersion, const char *gamename, char const *tag = NULL )
{
if ( !sql )
return;
char q[ 512 ];
std::string userid;
userid = szStatsFileUserID;
v_escape_string( userid );
int farthestPlayed = -1;
std::string highestmap;
int namelen = 20;
if ( !Q_stricmp( gamename, "ep1" ) )
{
namelen = 16;
}
char finalname[ 64 ];
std::string finaltag;
finaltag = tag ? tag : "";
v_escape_string( finaltag );
// Deal with the maps
for ( int i = gs.m_MapTotals.First(); i != gs.m_MapTotals.InvalidIndex(); i = gs.m_MapTotals.Next( i ) )
{
char const *pszMapName = gs.m_MapTotals.GetElementName( i );
std::string mapname;
mapname = pszMapName;
v_escape_string( mapname );
Q_strncpy( finalname, mapname.c_str(), namelen );
int slot = mapOrder.Find( pszMapName );
if ( slot != mapOrder.InvalidIndex() )
{
int order = mapOrder[ slot ];
if ( order > farthestPlayed )
{
farthestPlayed = order;
}
}
else
{
if ( Q_stricmp( pszMapName, "devtest" ) )
continue;
}
BasicGameStatsRecord_t& rec = gs.m_MapTotals[ i ];
if ( tag )
{
Q_snprintf( q, sizeof( q ), "REPLACE into %s_maps (UserID,LastUpdate,Version,MapName,Tag,Count,Seconds,HDR,Captions,Commentary,Easy,Medium,Hard,nonsteam,cybercafe,Deaths) values (\"%s\",Now(),%d,\"%s\",\"%s\",%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d);",
gamename,
userid.c_str(),
iStatsFileVersion,
finalname,
finaltag.c_str(),
rec.m_nCount,
rec.m_nSeconds,
rec.m_nHDR,
rec.m_nCaptions,
rec.m_nCommentary,
rec.m_nSkill[ 0 ],
rec.m_nSkill[ 1 ],
rec.m_nSkill[ 2 ],
rec.m_bSteam ? 0 : 1,
rec.m_bCyberCafe ? 1 : 0,
rec.m_nDeaths );
}
else
{
Q_snprintf( q, sizeof( q ), "REPLACE into %s_maps (UserID,LastUpdate,Version,MapName,Count,Seconds,HDR,Captions,Commentary,Easy,Medium,Hard,nonsteam,cybercafe,Deaths) values (\"%s\",Now(),%d,\"%s\",%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d);",
gamename,
userid.c_str(),
iStatsFileVersion,
finalname,
rec.m_nCount,
rec.m_nSeconds,
rec.m_nHDR,
rec.m_nCaptions,
rec.m_nCommentary,
rec.m_nSkill[ 0 ],
rec.m_nSkill[ 1 ],
rec.m_nSkill[ 2 ],
rec.m_bSteam ? 0 : 1,
rec.m_bCyberCafe ? 1 : 0,
rec.m_nDeaths );
}
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
}
if ( farthestPlayed != -1 )
{
highestmap = mapOrder.GetElementName( farthestPlayed );
}
v_escape_string( highestmap );
Q_strncpy( finalname, highestmap.c_str(), namelen );
if ( tag )
{
Q_snprintf( q, sizeof( q ), "REPLACE into %s (UserID,LastUpdate,Version,Tag,Count,Seconds,HDR,Captions,Commentary,Easy,Medium,Hard,SecondsToCompleteGame,HighestMap,nonsteam,cybercafe,hl2_chapter,dxlevel,Deaths) values (\"%s\",Now(),%d,\"%s\",%d,%d,%d,%d,%d,%d,%d,%d,%d,\"%s\",%d,%d,%d,%d,%d);",
gamename,
userid.c_str(),
iStatsFileVersion,
finaltag.c_str(),
gs.m_Summary.m_nCount,
gs.m_Summary.m_nSeconds,
gs.m_Summary.m_nHDR,
gs.m_Summary.m_nCaptions,
gs.m_Summary.m_nCommentary,
gs.m_Summary.m_nSkill[ 0 ],
gs.m_Summary.m_nSkill[ 1 ],
gs.m_Summary.m_nSkill[ 2 ],
gs.m_nSecondsToCompleteGame,
finalname,
gs.m_bSteam ? 0 : 1,
gs.m_bCyberCafe ? 1 : 0,
gs.m_nHL2ChaptureUnlocked,
gs.m_nDXLevel,
gs.m_Summary.m_nDeaths );
}
else
{
Q_snprintf( q, sizeof( q ), "REPLACE into %s (UserID,LastUpdate,Version,Count,Seconds,HDR,Captions,Commentary,Easy,Medium,Hard,SecondsToCompleteGame,HighestMap,nonsteam,cybercafe,hl2_chapter,dxlevel,Deaths) values (\"%s\",Now(),%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,\"%s\",%d,%d,%d,%d,%d);",
gamename,
userid.c_str(),
iStatsFileVersion,
gs.m_Summary.m_nCount,
gs.m_Summary.m_nSeconds,
gs.m_Summary.m_nHDR,
gs.m_Summary.m_nCaptions,
gs.m_Summary.m_nCommentary,
gs.m_Summary.m_nSkill[ 0 ],
gs.m_Summary.m_nSkill[ 1 ],
gs.m_Summary.m_nSkill[ 2 ],
gs.m_nSecondsToCompleteGame,
finalname,
gs.m_bSteam ? 0 : 1,
gs.m_bCyberCafe ? 1 : 0,
gs.m_nHL2ChaptureUnlocked,
gs.m_nDXLevel,
gs.m_Summary.m_nDeaths );
}
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
}
CUtlDict< int, unsigned short > g_mapOrder;
void BuildMapList( void )
{
void *buffer = NULL;
char *pFileList;
FILE * pFile;
pFile = fopen ("maplist.txt", "r");
int i = 0;
if ( pFile )
{
long lSize;
// obtain file size.
fseek (pFile , 0 , SEEK_END);
lSize = ftell (pFile);
rewind (pFile);
// allocate memory to contain the whole file.
buffer = (char*) malloc (lSize);
if ( buffer != NULL )
{
// copy the file into the buffer.
fread (buffer,1,lSize,pFile);
pFileList = (char*)buffer;
char szToken[1024];
while ( 1 )
{
pFileList = ParseFile( pFileList, szToken, false );
if ( pFileList == NULL )
break;
g_mapOrder.Insert( szToken, i );
i++;
}
}
fclose( pFile );
free( buffer );
}
else
{
Msg( "Couldn't load maplist.txt for mod!!!\n" );
}
}
int Default_ParseCustomGameStatsData( ParseContext_t *ctx )
{
FILE *fp = fopen( ctx->file, "rb" );
if ( fp )
{
CUtlBuffer statsBuffer;
struct _stat sb;
_stat( ctx->file, &sb );
statsBuffer.Clear();
statsBuffer.EnsureCapacity( sb.st_size );
fread( statsBuffer.Base(), sb.st_size, 1, fp );
statsBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, sb.st_size );
fclose( fp );
char shortname[ 128 ];
Q_FileBase( ctx->file, shortname, sizeof( shortname ) );
char szCurrentStatsFileUserID[17];
int iCurrentStatsFileVersion;
iCurrentStatsFileVersion = statsBuffer.GetShort();
statsBuffer.Get( szCurrentStatsFileUserID, 16 );
szCurrentStatsFileUserID[ sizeof( szCurrentStatsFileUserID ) - 1 ] = 0;
bool valid = true;
unsigned int iCheckIfStandardDataSaved = statsBuffer.GetUnsignedInt();
if( iCheckIfStandardDataSaved != GAMESTATS_STANDARD_NOT_SAVED )
{
//standard data was saved, rewind so the stats can read in time to completion
statsBuffer.SeekGet( CUtlBuffer::SEEK_CURRENT, -((int)sizeof( unsigned int )) );
BasicGameStats_t stats;
valid = stats.ParseFromBuffer( statsBuffer, iCurrentStatsFileVersion );
if ( describeonly )
{
DescribeData( stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion );
}
else
{
if ( valid )
{
InsertData( g_mapOrder, ctx->mysql, stats, szCurrentStatsFileUserID, iCurrentStatsFileVersion, ctx->gamename );
}
else
{
++ctx->skipcount;
}
}
}
//check for custom data
bool bHasCustomData = (valid && (statsBuffer.TellPut() != statsBuffer.TellGet()));
if( bHasCustomData )
{
if( describeonly )
{
//separate out the custom data and store it off for processing by other applications,
//since they only wanted to 'describe' the data, just use a local temp and overwrite it each time
const char *szCustomDataOutputFileName = "customdata_temp.dat";
Msg( "\n\nFound custom data, dumping to %s\n", szCustomDataOutputFileName );
FILE *pCustomDataOutput = fopen( szCustomDataOutputFileName, "wb+" );
if( pCustomDataOutput )
{
int iGetPosition = statsBuffer.TellGet();
fwrite( (((unsigned char *)statsBuffer.Base()) + iGetPosition), statsBuffer.TellPut() - iGetPosition, 1, pCustomDataOutput );
fclose( pCustomDataOutput );
}
}
else
{
//separate out the custom data and store it off for processing by other applications,
//assume we will have multiple input stats files from the same user, so store custom data under their userid name and overwrite old data to avoid bloat
if( ctx->bCustomDirectoryNotMade )
{
CreateDirectory( "customdatadumps", NULL );
ctx->bCustomDirectoryNotMade = false;
}
char szCustomDataOutputFileName[256];
Q_snprintf( szCustomDataOutputFileName, sizeof( szCustomDataOutputFileName ), "customdatadumps/%s.dat", szCurrentStatsFileUserID );
FILE *pCustomDataOutput = fopen( szCustomDataOutputFileName, "wb+" );
if( pCustomDataOutput )
{
int iGetPosition = statsBuffer.TellGet();
fwrite( (((unsigned char *)statsBuffer.Base()) + iGetPosition), statsBuffer.TellPut() - iGetPosition, 1, pCustomDataOutput );
fclose( pCustomDataOutput );
}
}
}
}
return CUSTOMDATA_SUCCESS;
}
int main(int argc, char* argv[])
{
CommandLine()->CreateCmdLine( argc, argv );
ParseContext_t ctx;
if ( argc < 7 && argc != 3 )
{
printusage();
}
describeonly = argc == 3;
int gameArg = 1;
int hostArg = 2;
int usernameArg = 3;
int pwArg = 4;
int dbArg = 5;
int dirArg = 6;
if ( describeonly )
{
dirArg = 2;
}
InitDefaultFileSystem();
BuildMapList();
const char *gamename = argv[ gameArg ];
DataParseFunc parseFunc = NULL;
PostImportFunc postImportFunc = NULL;
ParseCurrentUserIDFunc parseUserIDFunc = NULL;
for ( int i = 0 ; i < ARRAYSIZE( g_ParseFuncs ); ++i )
{
if ( !Q_stricmp( g_ParseFuncs[ i ].pchGameName, gamename ) )
{
parseFunc = g_ParseFuncs[ i ].pfnParseFunc;
postImportFunc = g_ParseFuncs[ i ].pfnPostImport;
parseUserIDFunc = g_ParseFuncs[ i ].pfnParseUserID;
break;
}
}
if ( !parseFunc )
{
printf( "Invalid game name '%s'\n", gamename );
printusage();
}
bool batchMode = true;
CUtlVector< int > files;
if ( describeonly || Q_stristr( argv[ dirArg ], ".dat" ) )
{
char filename[ MAX_PATH ];
Q_snprintf( filename, sizeof( filename ), "%s", argv[ dirArg ] );
_strlwr( filename );
Q_FixSlashes( filename );
char *symbol = strdup( filename );
int sym = g_Analysis.symbols.Insert( symbol );
files.AddToTail( sym );
batchMode = false;
}
else
{
Msg( "Building file list\n" );
BuildFileList( files, argv[ dirArg ], "dat" );
}
if ( !files.Count() )
{
printf( "No files to operate upon\n" );
exit( -1 );
}
int c = files.Count();
// Cull list of files by looking for most recent version of user's stats and only keeping around those files
if ( parseUserIDFunc )
{
struct CUserIDFileMapping
{
CUserIDFileMapping() :
filename( UTL_INVAL_SYMBOL ), filemodifiedtime( 0 ), modcount( 1 )
{
userid[ 0 ] = 0;
}
char userid[ 17 ];
CUtlSymbol filename;
time_t filemodifiedtime;
int modcount;
static bool Less( const CUserIDFileMapping &lhs, const CUserIDFileMapping &rhs )
{
return Q_stricmp( lhs.userid, rhs.userid ) < 0;
}
};
CUtlRBTree< CUserIDFileMapping, int > userIDToFileMap( 0, 0, CUserIDFileMapping::Less );
int nDiscards = 0;
int nSkips =0;
int nMaxMod = 1;
for ( int i = 0; i < c; ++i )
{
char const *fn = g_Analysis.symbols.Element( files[ i ] );
CUserIDFileMapping search;
search.filename = files[ i ];
if ( (*parseUserIDFunc)( fn, search.userid, sizeof( search.userid ), search.filemodifiedtime ) )
{
// Find map index
int idx = userIDToFileMap.Find( search );
if ( idx == userIDToFileMap.InvalidIndex() )
{
userIDToFileMap.Insert( search );
}
else
{
CUserIDFileMapping &update = userIDToFileMap[ idx ];
if ( search.filemodifiedtime > update.filemodifiedtime )
{
update.filename = files[ i ];
update.filemodifiedtime = search.filemodifiedtime;
update.modcount++;
if ( update.modcount > nMaxMod )
{
nMaxMod = update.modcount;
}
}
++nDiscards;
}
}
else
{
++nSkips;
}
if ( i > 0 && !( i % 100 ) )
{
printf( "Parsing user ID's: [%-6.6d/%-6.6d] %.2f %% complete\n", i, c, 100.0f * (float)i/(float)c );
}
}
Msg( "discarded %d of %d, remainder %d [%d skipped] max mod %d\n", nDiscards, c, userIDToFileMap.Count(), nSkips, nMaxMod );
// Now re-write files and count with pared down listing
files.Purge();
for( int i = userIDToFileMap.FirstInorder(); i != userIDToFileMap.InvalidIndex(); i = userIDToFileMap.NextInorder( i ) )
{
files.AddToTail( userIDToFileMap[ i ].filename );
}
c = files.Count();
}
bool bTrySql = !describeonly;
bool bSqlOkay = false;
CSysModule *sql = NULL;
CreateInterfaceFn factory = NULL;
IMySQL *mysql = NULL;
if ( bTrySql )
{
// Now connect to steamweb and update the engineaccess table
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( argv[ dbArg ], argv[ hostArg ], argv[ usernameArg ], argv[ pwArg ] ) )
{
bSqlOkay = true;
if ( batchMode )
{
Msg( "Successfully connected to database %s on host %s, user %s\n",
argv[ dbArg ], argv[ hostArg ], argv[ usernameArg ] );
}
}
else
{
Msg( "mysql->InitMySQL( %s, %s, %s, [password]) failed\n",
argv[ dbArg ], argv[ hostArg ], argv[ usernameArg ] );
}
}
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" );
}
}
ctx.gamename = gamename;
ctx.describeonly = describeonly;
ctx.mysql = mysql;
ctx.skipcount = 0;
ctx.bCustomDirectoryNotMade = true;
if ( bSqlOkay || describeonly )
{
for ( int i = 0; i < c; ++i )
{
char const *fn = g_Analysis.symbols.Element( files[ i ] );
ctx.file = fn;
int iCustomData = (*parseFunc)( &ctx );
if ( iCustomData == CUSTOMDATA_SUCCESS )
{
if ( i > 0 && !( i % 100 ) )
{
printf( "Processing: [%-6.6d/%-6.6d] %.2f %% complete\n", i, c, 100.0f * (float)i/(float)c );
}
}
}
if ( ctx.skipcount > 0 )
{
printf( "Skipped %d samples which appear to be malformed or contain bogus data\n", ctx.skipcount );
}
// if this game has a post-import function to call after all the files have been imported, call it now
if ( bSqlOkay && postImportFunc )
{
postImportFunc( mysql );
}
}
if ( bSqlOkay )
{
if ( mysql )
{
mysql->Release();
mysql = NULL;
}
if ( sql )
{
Sys_UnloadModule( sql );
sql = NULL;
}
}
return 0;
}
static void OverWriteCharsWeHate( char *pStr )
{
while( *pStr )
{
switch( *pStr )
{
case '\n':
case '\r':
case '\\':
case '\"':
case '\'':
case '\032':
case ';':
*pStr = ' ';
}
pStr++;
}
}
void InsertKeyDataIntoTable( IMySQL *pSQL, time_t fileTime, char const *pTableName, char const *pPerfData, char const *pKeyWhiteList[], int nNumFields )
{
char szDate[128]="Now()";
if ( fileTime > 0 )
{
tm * pTm = localtime( &fileTime );
Q_snprintf( szDate, ARRAYSIZE( szDate ), "'%04d-%02d-%02d %02d:%02d:%02d'",
pTm->tm_year + 1900, pTm->tm_mon + 1, pTm->tm_mday, pTm->tm_hour, pTm->tm_min, pTm->tm_sec );
}
// we don't need to worry about semicolons embedded in string fields because we supressed them
// on the client. if some malicious person inserts them, the mandled field names will fail the
// whitelist check, causing the record to be ignored.
CUtlVector<char *> tokens;
// split into tokens at non-quoated spaces or ;'s
for(;;)
{
char const *pStr = pPerfData;
if ( pStr[0] == 0 )
break;
while( pStr[0] && ( pStr[0] != ' ' ) && ( pStr[0] != ';' ) )
{
if ( pStr[0]=='"')
{
// skip to end quote
char const *pEq = strchr( pStr + 1, '\"' );
if ( ! pEq )
{
printf(" close quote with no open quote\n" );
return;
}
pStr = pEq;
}
pStr++;
}
// got a field
int nlen = pStr - pPerfData;
if ( nlen > 2 )
{
char *pToken = new char[ nlen + 1 ];
memcpy( pToken, pPerfData, nlen );
pToken[nlen] = 0;
tokens.AddToTail( pToken );
}
if ( pStr[0] )
pStr++;
pPerfData = pStr;
}
bool bBadData = false;
char fieldNameBuffer[1024];
char fieldValueBuffer[2048];
strcpy( fieldNameBuffer, "(CreationTimeStamp, " );
Q_snprintf( fieldValueBuffer, ARRAYSIZE( fieldValueBuffer), "( %s,", szDate );
for( int i = 0; i < tokens.Count(); i++ )
{
char *pKVData = tokens[i];
char *pEqualsSign = strchr( pKVData, '=' );
if (! pEqualsSign )
{
bBadData = true;
break;
}
*pEqualsSign = 0; // *semicolon->null
// check that the field is in the white list
bool bFoundIt = false;
for( int nCheck = 0; nCheck < nNumFields; nCheck++ )
if ( strcmp( pKVData, pKeyWhiteList[nCheck] ) == 0 )
{
bFoundIt = true;
break;
}
V_strncat( fieldNameBuffer, pKVData, sizeof( fieldNameBuffer ) );
if ( i != tokens.Count() -1 )
V_strncat( fieldNameBuffer, ",", sizeof( fieldNameBuffer ) );
else
V_strncat( fieldNameBuffer, ")", sizeof( fieldNameBuffer ) );
char *pValue = pEqualsSign + 1;
OverWriteCharsWeHate( pValue );
if ( ( strlen( pValue ) < 1 ) || (! bFoundIt ) )
{
bBadData = true;
break;
}
// kill lead + trail space
if ( pValue[0] == ' ' )
pValue++;
if ( pValue[strlen(pValue) - 1 ] == ' ' )
pValue[strlen( pValue ) - 1 ] =0;
V_strncat( fieldValueBuffer, "'", sizeof( fieldValueBuffer ) );
V_strncat( fieldValueBuffer, pValue, sizeof( fieldValueBuffer ) );
if ( i != tokens.Count() -1 )
V_strncat( fieldValueBuffer, "',", sizeof( fieldValueBuffer ) );
else
V_strncat( fieldValueBuffer, "')", sizeof( fieldValueBuffer ) );
}
if (! bBadData )
{
char sqlCommandBuffer[1024 + sizeof( fieldNameBuffer ) + sizeof( fieldValueBuffer ) ];
sprintf( sqlCommandBuffer, "insert into %s %s values %s;", pTableName, fieldNameBuffer, fieldValueBuffer );
// printf("cmd %s\n", sqlCommandBuffer);
int retcode = pSQL->Execute( sqlCommandBuffer );
if ( retcode != 0 )
{
printf( "command %s failed\n", sqlCommandBuffer );
}
}
tokens.PurgeAndDeleteElements();
}
char const *s_PerfKeyList[] = {
"AvgFps",
"MinFps",
"MaxFps",
"CPUID",
"CPUGhz",
"NumCores",
"GPUDrv",
"GPUVendor",
"GPUDeviceID",
"GPUDriverVersion",
"DxLvl",
"Width",
"Height",
"MapName",
"TotalLevelTime",
"NumLevels"
};
void ProcessPerfData( IMySQL *pSQL, time_t fileTime, char const *pTableName, char const *pPerfData )
{
InsertKeyDataIntoTable( pSQL, fileTime, pTableName, pPerfData, s_PerfKeyList, ARRAYSIZE( s_PerfKeyList) );
}
@@ -0,0 +1,78 @@
//-----------------------------------------------------------------------------
// PROCESSGAMESTATS.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;$SRCDIR\game\shared\tf;$SRCDIR\game\shared\econ;$SRCDIR\gcsdk\steamextra"
$EnableC++Exceptions "Yes (/EHsc)"
$PreprocessorDefinitions "$BASE;GAME_DLL;TF_DLL;NO_STEAM"
}
}
$Project "Processgamestats"
{
$Folder "Source Files"
{
$File "$SRCDIR\public\filesystem_helpers.cpp"
$File "$SRCDIR\public\filesystem_helpers.h"
$File "$SRCDIR\game\server\GameStats_BasicStatsFunctions.cpp"
$File "processgamestats.cpp"
$Folder "Custom Stats"
{
$File "base_gamestats_parse.h"
$File "cs_gamestats.h"
$File "cstrike_gamestats_parse.cpp"
$File "$SRCDIR\game\server\episodic\ep2_gamestats.h"
$File "ep2_gamestats_parse.cpp"
$File "$SRCDIR\game\server\tf\tf_gamestats.h"
$File "tf_gamestats_parse.cpp"
$File "$SRCDIR\game\shared\tf\tf_gamestats_shared.cpp"
$File "$SRCDIR\game\shared\tf\tf_gamestats_shared.h"
}
}
$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\game\shared\gamestats.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 "Database Setup"
{
$File "cstrike_gamestats.db"
$File "portal_gamestats.db"
$File "ep1_gamestats.db"
$File "ep2_gamestats.db"
$File "tf_gamestats.db"
}
$Folder "Link Libraries"
{
$Lib tier2
}
}
+227
View File
@@ -0,0 +1,227 @@
create database if not exists gamestats_tf;
use gamestats_tf;
create table if not exists tf_mapdata
(
MapName VARCHAR(64)
);
alter table tf_mapdata add INDEX(MapName);
alter table tf_mapdata add column ServerID VARCHAR(16);
alter table tf_mapdata add column TimeSubmitted DATETIME;
alter table tf_mapdata add INDEX(TimeSubmitted);
alter table tf_mapdata add column RoundsPlayed BIGINT;
alter table tf_mapdata add column TotalTime BIGINT;
alter table tf_mapdata add column BlueWins BIGINT;
alter table tf_mapdata add column RedWins BIGINT;
alter table tf_mapdata add column Stalemates BIGINT;
create table if not exists tf_mapdata_rollup
(
MapName VARCHAR(64)
);
alter table tf_mapdata_rollup add INDEX(MapName);
alter table tf_mapdata_rollup add column TimeSubmitted DATETIME;
alter table tf_mapdata_rollup add INDEX(TimeSubmitted);
alter table tf_mapdata_rollup add column RoundsPlayed BIGINT;
alter table tf_mapdata_rollup add column TotalTime BIGINT;
alter table tf_mapdata_rollup add column BlueWins BIGINT;
alter table tf_mapdata_rollup add column RedWins BIGINT;
alter table tf_mapdata_rollup add column Stalemates BIGINT;
create table if not exists tf_classdata
(
MapName VARCHAR(64)
);
alter table tf_classdata add INDEX(MapName);
alter table tf_classdata add column ServerID VARCHAR(16);
alter table tf_classdata add column TimeSubmitted DATETIME;
alter table tf_classdata add INDEX(TimeSubmitted);
alter table tf_classdata add column Class TINYINT;
alter table tf_classdata add column Spawns BIGINT;
alter table tf_classdata add column TotalTime BIGINT;
alter table tf_classdata add column Score BIGINT;
alter table tf_classdata add column Kills BIGINT;
alter table tf_classdata add column Deaths BIGINT;
alter table tf_classdata add column Assists BIGINT;
alter table tf_classdata add column Captures BIGINT;
create table if not exists tf_classdata_rollup
(
MapName VARCHAR(64)
);
alter table tf_classdata_rollup add INDEX(MapName);
alter table tf_classdata_rollup add column TimeSubmitted DATETIME;
alter table tf_classdata_rollup add INDEX(TimeSubmitted);
alter table tf_classdata_rollup add column Class TINYINT;
alter table tf_classdata_rollup add column Spawns BIGINT;
alter table tf_classdata_rollup add column TotalTime BIGINT;
alter table tf_classdata_rollup add column Score BIGINT;
alter table tf_classdata_rollup add column Kills BIGINT;
alter table tf_classdata_rollup add column Deaths BIGINT;
alter table tf_classdata_rollup add column Assists BIGINT;
alter table tf_classdata_rollup add column Captures BIGINT;
create table if not exists tf_weapondata
(
WeaponID TINYINT
);
alter table tf_weapondata add INDEX(WeaponID);
alter table tf_weapondata add column ServerID VARCHAR(16);
alter table tf_weapondata add column MapName VARCHAR(64);
alter table tf_weapondata add INDEX(MapName);
alter table tf_weapondata add column TimeSubmitted DATETIME;
alter table tf_weapondata add INDEX(TimeSubmitted);
alter table tf_weapondata add column ShotsFired BIGINT;
alter table tf_weapondata add column ShotsFiredCrit BIGINT;
alter table tf_weapondata add column ShotsHit BIGINT;
alter table tf_weapondata add column DamageTotal BIGINT;
alter table tf_weapondata add column HitsWithKnownDistance BIGINT;
alter table tf_weapondata add column DistanceTotal BIGINT;
create table if not exists tf_weapondata_rollup
(
WeaponID TINYINT
);
alter table tf_weapondata_rollup add INDEX(WeaponID);
alter table tf_weapondata_rollup add column MapName VARCHAR(64);
alter table tf_weapondata_rollup add INDEX(MapName);
alter table tf_weapondata_rollup add column TimeSubmitted DATETIME;
alter table tf_weapondata_rollup add INDEX(TimeSubmitted);
alter table tf_weapondata_rollup add column ShotsFired BIGINT;
alter table tf_weapondata_rollup add column ShotsFiredCrit BIGINT;
alter table tf_weapondata_rollup add column ShotsHit BIGINT;
alter table tf_weapondata_rollup add column DamageTotal BIGINT;
alter table tf_weapondata_rollup add column HitsWithKnownDistance BIGINT;
alter table tf_weapondata_rollup add column DistanceTotal BIGINT;
create table if not exists tf_deaths
(
UserID CHAR(16),
Tag CHAR(8),
KEY( Tag ),
MapName CHAR(20),
MapVersion INT,
KEY( MapVersion ),
LastUpdate DATETIME,
KEY( LastUpdate ),
DeathIndex INT,
X SMALLINT,
Y SMALLINT,
Z SMALLINT,
PRIMARY KEY ( UserID, Tag, MapName, X, Y, Z )
) TYPE=MyISAM;
create table if not exists tf_perfdata
(
CreationTimeStamp DATETIME,
AvgFps FLOAT,
MinFps FLOAT,
MaxFps FLOAT,
CPUID VARCHAR(64),
CPUGhz FLOAT,
NumCores INT,
GPUDrv VARCHAR(64),
GPUVendor INT,
GPUDeviceID INT,
GPUDriverVersion VARCHAR(25),
DxLvl INT,
Width INT,
Height INT,
MapName VARCHAR(64),
TotalLevelTime INT,
NumLevels SMALLINT,
INDEX(CreationTimeStamp),
INDEX(AvgFps),
INDEX(GPUDrv),
INDEX(Width),
INDEX(MapName)
);
create table if not exists tf_perfdata_rollup_fps
(
CreationTimeStamp DATETIME,
NumEntries BIGINT,
AvgFps FLOAT,
INDEX(CreationTimeStamp),
);
create table if not exists tf_perfdata_rollup_fpsdist
(
CreationTimeStamp DATETIME,
NumEntries BIGINT,
BaseFps FLOAT,
GPUDrv VARCHAR(64),
INDEX(CreationTimeStamp),
INDEX(GPUDrv)
);
create table if not exists tf_perfdata_rollup_fpsmap
(
CreationTimeStamp DATETIME,
NumEntries BIGINT,
AvgFps FLOAT,
MapName VARCHAR(64),
INDEX(CreationTimeStamp),
INDEX(MapName),
);
create table if not exists tf_perfdata_rollup_fpscard
(
CreationTimeStamp DATETIME,
NumEntries BIGINT,
AvgFps FLOAT,
GPUDrv VARCHAR(64),
INDEX(CreationTimeStamp),
INDEX(GPUDrv),
);
create table if not exists tf_perfdata_rollup_fpswidth
(
CreationTimeStamp DATETIME,
NumEntries BIGINT,
AvgFps FLOAT,
Width INT,
INDEX(CreationTimeStamp),
);
create table if not exists tf_perfdata_rollup_fpscardwidth
(
CreationTimeStamp DATETIME,
NumEntries BIGINT,
AvgFps FLOAT,
Width INT,
GPUDrv VARCHAR(64),
INDEX(CreationTimeStamp),
INDEX(GPUDrv),
);
create table if not exists tf_perfdata_rollup_cpudist
(
CreationTimeStamp DATETIME,
NumEntries BIGINT,
AvgFps FLOAT,
CPUID VARCHAR(64),
NumCores INT,
BaseCPUGhz FLOAT,
GPUVendor INT,
INDEX(CreationTimeStamp),
);
create table if not exists tf_perfdata_rollup_playtime
(
CreationTimeStamp DATETIME,
AvgSession FLOAT,
AvgLevels FLOAT,
INDEX(CreationTimeStamp),
);
create table if not exists tf_perfdata_rollup_playtimedist
(
CreationTimeStamp DATETIME,
BaseSession FLOAT,
NumEntries INT,
INDEX(CreationTimeStamp),
);
@@ -0,0 +1,480 @@
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
//
//
#include "stdafx.h"
#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 "tier2/tier2.h"
#include "filesystem.h"
#include "cbase.h"
#include "gamestats.h"
class CBaseObject;
#include "tf/tf_gamestats.h"
#include "base_gamestats_parse.h"
#include "tier0/icommandline.h"
#include <string>
static TFReportedStats_t g_reportedStats;
extern void v_escape_string (std::string& s);
//-----------------------------------------------------------------------------
// Weapons.
//-----------------------------------------------------------------------------
static const char *s_aWeaponNames[] =
{
"NONE",
"BAT",
"BOTTLE",
"FIREAXE",
"CLUB",
"CROWBAR",
"KNIFE",
"MEDIKIT",
"PIPE",
"SHOVEL",
"WRENCH",
"BONESAW",
"SHOTGUN_PRIMARY",
"SHOTGUN_SECONDARY",
"SNIPERRIFLE",
"MINIGUN",
"SMG",
"SYRINGEGUN_MEDIC",
"TRANQ",
"ROCKETLAUNCHER",
"GRENADELAUNCHER",
"PIPEBOMBLAUNCHER",
"FLAMETHROWER",
"GRENADE_NORMAL",
"GRENADE_CONCUSSION",
"GRENADE_NAIL",
"GRENADE_MIRV",
"GRENADE_MIRV_DEMOMAN",
"GRENADE_NAPALM",
"GRENADE_GAS",
"GRENADE_EMP",
"GRENADE_CALTROP",
"GRENADE_PIPEBOMB",
"GRENADE_SMOKE_BOMB",
"GRENADE_HEAL",
"PISTOL",
"REVOLVER",
"NAILGUN",
"PDA",
"PDA_DEMOMAN",
"PDA_ENGINEER",
"PDA_SPY",
"BUILDER",
"MEDIGUN",
"FLAG",
"GRENADE_MIRVBOMB",
"FLAMETHROWER_ROCKET",
"GRENADE_DEMOMAN",
"SENTRY_BULLET",
"SENTRY_ROCKET",
"DISPENSER",
"INVIS",
};
//-----------------------------------------------------------------------------
// Classes
//-----------------------------------------------------------------------------
static const char *s_aClassNames[] =
{
"UNDEFINED",
"SCOUT",
"SNIPER",
"SOLDIER",
"DEMOMAN",
"MEDIC",
"HEAVYWEAPONS",
"PYRO",
"SPY",
"ENGINEER",
"CIVILIAN",
};
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static bool LoadCustomDataFromBuffer( CUtlBuffer &LoadBuffer )
{
g_reportedStats.Clear();
return g_reportedStats.LoadCustomDataFromBuffer( LoadBuffer );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
static const char *ClassIdToAlias( int iClass )
{
if ( ( iClass >= ARRAYSIZE( s_aClassNames ) ) || ( iClass < 0 ) )
return NULL;
return s_aClassNames[iClass];
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
const char *WeaponIdToAlias( int iWeapon )
{
if ( ( iWeapon >= ARRAYSIZE( s_aWeaponNames ) ) || ( iWeapon < 0 ) )
return NULL;
return s_aWeaponNames[iWeapon];
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void DescribeTF2Stats()
{
#if 0 // not up to date w/latest stats code.
int iMap;
for ( iMap = g_dictMapStats.First(); iMap != g_dictMapStats.InvalidIndex(); iMap = g_dictMapStats.Next( iMap ) )
{
// Get the current map.
TF_Gamestats_LevelStats_t *pCurrentMap = &g_dictMapStats[iMap];
Msg( " --- %s ------\n %d deaths\n %.2f seconds total playtime\n", pCurrentMap->m_Header.m_szMapName,
pCurrentMap->m_aPlayerDeaths.Count(), pCurrentMap->m_Header.m_flTime );
for( int i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); i++ )
{
Msg( " %s killed %s with %s at (%d,%d,%d), distance %d\n",
ClassIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iAttackClass ),
ClassIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iTargetClass ),
WeaponIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iWeapon ),
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ],
pCurrentMap->m_aPlayerDeaths[ i ].iDistance );
}
Msg( "\n---------------------------------\n\n %d damage records\n", pCurrentMap->m_aPlayerDamage.Count() );
for( int i = 0; i < pCurrentMap->m_aPlayerDamage.Count(); i++ )
{
Msg( " %.2f : %s at (%d,%d,%d) caused %d damage to %s with %s at (%d,%d,%d)%s%s\n",
pCurrentMap->m_aPlayerDamage[ i ].fTime,
ClassIdToAlias( pCurrentMap->m_aPlayerDamage[ i ].iAttackClass ),
pCurrentMap->m_aPlayerDamage[ i ].nAttackerPosition[ 0 ],
pCurrentMap->m_aPlayerDamage[ i ].nAttackerPosition[ 1 ],
pCurrentMap->m_aPlayerDamage[ i ].nAttackerPosition[ 2 ],
pCurrentMap->m_aPlayerDamage[ i ].iDamage,
ClassIdToAlias( pCurrentMap->m_aPlayerDamage[ i ].iTargetClass ),
WeaponIdToAlias( pCurrentMap->m_aPlayerDamage[ i ].iWeapon ),
pCurrentMap->m_aPlayerDamage[ i ].nTargetPosition[ 0 ],
pCurrentMap->m_aPlayerDamage[ i ].nTargetPosition[ 1 ],
pCurrentMap->m_aPlayerDamage[ i ].nTargetPosition[ 2 ],
pCurrentMap->m_aPlayerDamage[ i ].iCrit ? ", CRIT!" : "",
pCurrentMap->m_aPlayerDamage[ i ].iKill ? ", KILL" : "" );
}
Msg( "\n" );
}
#endif
}
extern CUtlDict< int, unsigned short > g_mapOrder;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void InsertTF2Data( bool bDeathOnly, char const *szStatsFileUserID, time_t fileTime, IMySQL *sql, int iStatsFileVersion )
{
if ( !sql )
return;
std::string userid;
userid = szStatsFileUserID;
v_escape_string( userid );
char szDate[128]="Now()";
if ( fileTime > 0 )
{
tm * pTm = localtime( &fileTime );
Q_snprintf( szDate, ARRAYSIZE( szDate ), "'%04d-%02d-%02d %02d:%02d:%02d'",
pTm->tm_year + 1900, pTm->tm_mon + 1, pTm->tm_mday, pTm->tm_hour, pTm->tm_min, pTm->tm_sec );
}
char q[4096];
{
int iMap;
for ( iMap = g_reportedStats.m_dictMapStats.First(); iMap != g_reportedStats.m_dictMapStats.InvalidIndex(); iMap = g_reportedStats.m_dictMapStats.Next( iMap ) )
{
// Get the current map.
TF_Gamestats_LevelStats_t *pCurrentMap = &g_reportedStats.m_dictMapStats[iMap];
#if 1
int slot = g_mapOrder.Find( pCurrentMap->m_Header.m_szMapName );
if ( slot == g_mapOrder.InvalidIndex() )
{
if ( Q_stricmp( pCurrentMap->m_Header.m_szMapName, "devtest" ) )
continue;
}
#endif
std::string mapname;
mapname = pCurrentMap->m_Header.m_szMapName;
v_escape_string( mapname );
std::string tag;
tag = ""; // pCurrentMap->m_Tag.m_szTagText;
v_escape_string( tag );
int mapversion = 0;
/*
for( int i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); i++ )
{
Msg( " %s killed %s with %s at (%d,%d,%d), distance %d\n",
ClassIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iAttackClass ),
ClassIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iTargetClass ),
WeaponIdToAlias( pCurrentMap->m_aPlayerDeaths[ i ].iWeapon ),
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ],
pCurrentMap->m_aPlayerDeaths[ i ].iDistance );
}
*/
// Deal with deaths
for ( int i = 0; i < pCurrentMap->m_aPlayerDeaths.Count(); ++i )
{
Q_snprintf( q, sizeof( q ), "REPLACE into %s_deaths (UserID,Tag,LastUpdate,MapName,MapVersion,DeathIndex,X,Y,Z) values (\"%s\",\"%s\",Now(),\"%-.20s\",%d,%d,%d,%d,%d);",
"tf",
userid.c_str(),
tag.c_str(),
mapname.c_str(),
mapversion,
i,
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 0 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 1 ],
pCurrentMap->m_aPlayerDeaths[ i ].nPosition[ 2 ] );
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
printf( "Query %s failed\n", q );
return;
}
}
}
if ( bDeathOnly )
return;
}
int iMap;
for ( iMap = g_reportedStats.m_dictMapStats.First(); iMap != g_reportedStats.m_dictMapStats.InvalidIndex();
iMap = g_reportedStats.m_dictMapStats.Next( iMap ) )
{
// Get the current map.
TF_Gamestats_LevelStats_t *pCurrentMap = &g_reportedStats.m_dictMapStats[iMap];
std::string mapname = pCurrentMap->m_Header.m_szMapName;
v_escape_string( mapname );
// insert map data into database
Q_snprintf( q, sizeof( q ), "INSERT into tf_mapdata (ServerID,TimeSubmitted,MapName,RoundsPlayed,TotalTime,BlueWins,RedWins,Stalemates,BlueSuddenDeathWins,RedSuddenDeathWins) "
"values (\"%s\",%s,\"%s\",%d,%d,%d,%d,%d,%d,%d);",
szStatsFileUserID,szDate, mapname.c_str(), pCurrentMap->m_Header.m_iRoundsPlayed, pCurrentMap->m_Header.m_iTotalTime, pCurrentMap->m_Header.m_iBlueWins,
pCurrentMap->m_Header.m_iRedWins, pCurrentMap->m_Header.m_iStalemates, pCurrentMap->m_Header.m_iBlueSuddenDeathWins, pCurrentMap->m_Header.m_iRedSuddenDeathWins );
// Msg( "%s\n", q );
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
Msg( "Query %s failed\n", q );
return;
}
// insert the class data
for ( int i = TF_CLASS_UNDEFINED + 1; i < TF_CLASS_CIVILIAN; i++ )
{
TF_Gamestats_ClassStats_t &classStats = pCurrentMap->m_aClassStats[i];
if ( 0 == classStats.iSpawns ) // skip any classes that have had no spawns
continue;
Q_snprintf( q, sizeof( q ), "INSERT into tf_classdata (ServerID,MapName,TimeSubmitted,Class,Spawns,TotalTime,Score,Kills,Deaths,Assists,Captures) "
"values (\"%s\",\"%s\",%s,%d,%d,%d,%d,%d,%d,%d,%d);",
szStatsFileUserID, mapname.c_str(), szDate, i, classStats.iSpawns, classStats.iTotalTime, classStats.iScore, classStats.iKills, classStats.iDeaths,
classStats.iAssists, classStats.iCaptures );
// Msg( "%s\n", q );
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
Msg( "Query %s failed\n", q );
return;
}
}
// insert the weapon data
for ( int i = TF_WEAPON_NONE+1; i < TF_WEAPON_COUNT; i++ )
{
TF_Gamestats_WeaponStats_t &weaponStats = pCurrentMap->m_aWeaponStats[i];
// skip any weapons that have no shots fired
if ( 0 == weaponStats.iShotsFired )
continue;
Q_snprintf( q, sizeof( q ), "INSERT into tf_weapondata (ServerID, MapName, TimeSubmitted,WeaponID,ShotsFired,ShotsFiredCrit,ShotsHit,DamageTotal,"
"HitsWithKnownDistance,DistanceTotal) "
"values (\"%s\",\"%s\",%s,%d,%d,%d,%d,%d,%d,%llu)",
szStatsFileUserID, mapname.c_str(), szDate, i, weaponStats.iShotsFired, weaponStats.iCritShotsFired, weaponStats.iHits, weaponStats.iTotalDamage,
weaponStats.iHitsWithKnownDistance, weaponStats.iTotalDistance );
// Msg( "%s\n", q );
int retcode = sql->Execute( q );
if ( retcode != 0 )
{
Msg( "Query %s failed\n", q );
return;
}
}
}
}
bool g_bDeathOnly = false;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int TF_ParseCustomGameStatsData( ParseContext_t *ctx )
{
FILE *fp = fopen( ctx->file, "rb" );
if ( !fp )
return CUSTOMDATA_FAILED;
CUtlBuffer statsBuffer;
struct _stat sb;
_stat( ctx->file, &sb );
statsBuffer.Clear();
statsBuffer.EnsureCapacity( sb.st_size );
fread( statsBuffer.Base(), sb.st_size, 1, fp );
statsBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, sb.st_size );
fclose( fp );
if ( memcmp( statsBuffer.Base(), "\"gamestats\"", 11 ) == 0 )
{
Msg( "Got new-style file format that we don't support, skipping\n" );
return CUSTOMDATA_SUCCESS;
}
if ( memcmp( statsBuffer.Base(), "PERFDATA:", 9 ) == 0 )
{
ProcessPerfData( ctx->mysql, sb.st_mtime, "tf_perfdata", ( ( char * ) statsBuffer.Base() ) + 9 );
return CUSTOMDATA_SUCCESS;
}
char shortname[ 128 ];
Q_FileBase( ctx->file, shortname, sizeof( shortname ) );
char szCurrentStatsFileUserID[17];
int iCurrentStatsFileVersion;
iCurrentStatsFileVersion = statsBuffer.GetShort();
statsBuffer.Get( szCurrentStatsFileUserID, 16 );
szCurrentStatsFileUserID[ sizeof( szCurrentStatsFileUserID ) - 1 ] = 0;
unsigned int iCheckIfStandardDataSaved = statsBuffer.GetUnsignedInt();
if( iCheckIfStandardDataSaved != GAMESTATS_STANDARD_NOT_SAVED )
{
// we don't care about the standard data, so why is it here?
return CUSTOMDATA_FAILED;
}
// check for custom data
bool bHasCustomData = (statsBuffer.TellPut() != statsBuffer.TellGet());
if( !bHasCustomData )
{
// where's our data?
return CUSTOMDATA_FAILED;
}
if ( !LoadCustomDataFromBuffer( statsBuffer ) )
return CUSTOMDATA_FAILED;
if( ctx->describeonly )
{
DescribeTF2Stats();
}
else
{
if ( CommandLine()->CheckParm( "-deathsonly" ) )
{
g_bDeathOnly = true;
}
InsertTF2Data( g_bDeathOnly, szCurrentStatsFileUserID, sb.st_mtime, ctx->mysql, iCurrentStatsFileVersion );
}
return CUSTOMDATA_SUCCESS;
}
//-----------------------------------------------------------------------------
// Purpose: Called after all new files have been parsed & imported
//-----------------------------------------------------------------------------
void TF_PostImport( IMySQL *sql )
{
#if 0 // now handled by PHP script
if ( g_bDeathOnly )
return;
// All the new data files have been imported to SQL. Now, do a rollup of the raw data into the rollup tables.
// delete existing rollup for class data
int retcode = sql->Execute( "delete from tf_classdata_rollup" );
if ( 0 != retcode )
{
Msg( "Failed to delete from tf_classdata_rollup\n" );
return;
}
// create new rollup for class data
retcode = sql->Execute( "insert into tf_classdata_rollup (class,spawns,totaltime,score,kills,deaths,assists,captures) "
"select class,sum(spawns),sum(totaltime),sum(score),sum(kills),sum(deaths),sum(assists),sum(captures) from tf_classdata group by class;" );
if ( 0 != retcode )
{
Msg( "Failed to create class data rollup\n" );
return;
}
// delete existing rollup for map data
retcode = sql->Execute( "delete from tf_mapdata_rollup" );
if ( 0 != retcode )
{
Msg( "Failed to delete from tf_mapdata_rollup\n" );
return;
}
// create new rollup for map data
retcode = sql->Execute( "insert into tf_mapdata_rollup (mapname,roundsplayed,totaltime,bluewins,redwins,stalemates) "
"select mapname,sum(roundsplayed),sum(totaltime),sum(bluewins),sum(redwins),sum(stalemates) from tf_mapdata group by mapname;" );
if ( 0 != retcode )
{
Msg( "Failed to create map data rollup\n" );
return;
}
#endif
}