mirror of
https://github.com/nillerusr/source-engine.git
synced 2026-08-08 01:39:36 +00:00
1
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user