MySQL database export (dbexport.h / dbexport.cpp): - New files dbexport.h / dbexport.cpp implement late-bound MySQL export. libmysql.dll is loaded at runtime via LoadLibrary/GetProcAddress so no MySQL SDK is required at compile time; the app runs normally if the DLL is absent. - After each print job, match data is exported to a MySQL server before PrintDlg() is called: one row in 'match', one row per player in 'player_result', one row per attacker/victim pair in 'pvp'. An optional 'event' table records every individual SRecScore entry (off by default). Tables are created automatically (CREATE TABLE IF NOT EXISTS) on first connect. - Config stored in mw4print.ini (app directory), section [MySQLExport]: Enabled, Host, Port, Database, Username, Password, ExportEvents. - Config loaded at startup (OnCreate); DB_LoadConfig() / DB_SaveConfig(). - Connection timeout set to 5 seconds so the app does not hang if the server is unreachable. - libmysql.dll (MySQL Connector/C 32-bit) added to Gameleap/mw4/ so the deploy script copies it to MW4/ alongside mw4print.exe. Database Settings dialog (File > Database Settings... / Ctrl+D): - MFC dialog: enable checkbox, Host/Port/Database/Username/Password fields, Export Events checkbox, Test Connection button with live status label, OK/Cancel. OK persists settings to mw4print.ini immediately. Configurable banner text (File > Banner Setting...): - The 'WWW.MECHJOCK.COM' URL string printed at the bottom of every score sheet is now configurable. Stored as BannerText= in options.ini under [battle tech print] (same section/file as the other print layout params). File > Banner Setting... opens a dialog to edit it; OK saves to options.ini and takes effect on the next print job with no restart needed. Default value is the original MECHJOCK string if the key is absent.
487 lines
19 KiB
C++
487 lines
19 KiB
C++
// dbexport.cpp - MySQL export implementation for mw4print
|
|
//
|
|
// Included directly from ChildView.cpp (same pattern as recscore.cpp).
|
|
//
|
|
|
|
#include "dbexport.h"
|
|
|
|
// ---- Config default values -----------------------------------------------
|
|
|
|
SDBConfig g_dbConfig = {
|
|
false, // bEnabled
|
|
"localhost", // szHost
|
|
3306, // nPort
|
|
"firestorm_stats", // szDatabase
|
|
"mw4", // szUsername
|
|
"", // szPassword
|
|
false // bExportEvents
|
|
};
|
|
|
|
// ---- MySQL late binding --------------------------------------------------
|
|
//
|
|
// We load libmysql.dll at runtime so the exe does not hard-link against it.
|
|
// All function pointers are NULL until DB_LoadMySQL() succeeds.
|
|
|
|
static HMODULE g_hMySQL = NULL;
|
|
|
|
typedef void* MYSQLH; // opaque MYSQL* handle
|
|
typedef unsigned long MYULONG;
|
|
typedef unsigned __int64 MYULLONG;
|
|
|
|
// MYSQL_OPT_CONNECT_TIMEOUT = 0
|
|
#define MY_OPT_CONNECT_TIMEOUT 0
|
|
|
|
typedef MYSQLH (__cdecl *PFN_mysql_init) (MYSQLH);
|
|
typedef MYSQLH (__cdecl *PFN_mysql_real_connect) (MYSQLH, const char*, const char*, const char*, const char*, unsigned int, const char*, unsigned long);
|
|
typedef void (__cdecl *PFN_mysql_options) (MYSQLH, int, const char*);
|
|
typedef int (__cdecl *PFN_mysql_real_query) (MYSQLH, const char*, unsigned long);
|
|
typedef MYULLONG (__cdecl *PFN_mysql_insert_id) (MYSQLH);
|
|
typedef void (__cdecl *PFN_mysql_close) (MYSQLH);
|
|
typedef const char* (__cdecl *PFN_mysql_error) (MYSQLH);
|
|
typedef MYULONG (__cdecl *PFN_mysql_escape_string) (char*, const char*, MYULONG);
|
|
|
|
static PFN_mysql_init pfn_init = NULL;
|
|
static PFN_mysql_real_connect pfn_real_connect = NULL;
|
|
static PFN_mysql_options pfn_options = NULL;
|
|
static PFN_mysql_real_query pfn_real_query = NULL;
|
|
static PFN_mysql_insert_id pfn_insert_id = NULL;
|
|
static PFN_mysql_close pfn_close = NULL;
|
|
static PFN_mysql_error pfn_error = NULL;
|
|
static PFN_mysql_escape_string pfn_escape_string = NULL;
|
|
|
|
static bool DB_LoadMySQL()
|
|
{
|
|
if (g_hMySQL)
|
|
return true;
|
|
|
|
// Try app directory first, then system path.
|
|
char szPath[MAX_PATH];
|
|
sprintf(szPath, "%s\\libmysql.dll", AssetsDirectory1);
|
|
g_hMySQL = LoadLibrary(szPath);
|
|
if (!g_hMySQL)
|
|
g_hMySQL = LoadLibrary("libmysql.dll");
|
|
if (!g_hMySQL)
|
|
return false;
|
|
|
|
#define GETPROC(name, pfn) pfn = (PFN_##name)GetProcAddress(g_hMySQL, #name); if (!pfn) { FreeLibrary(g_hMySQL); g_hMySQL=NULL; return false; }
|
|
GETPROC(mysql_init, pfn_init)
|
|
GETPROC(mysql_real_connect, pfn_real_connect)
|
|
GETPROC(mysql_options, pfn_options)
|
|
GETPROC(mysql_real_query, pfn_real_query)
|
|
GETPROC(mysql_insert_id, pfn_insert_id)
|
|
GETPROC(mysql_close, pfn_close)
|
|
GETPROC(mysql_error, pfn_error)
|
|
GETPROC(mysql_escape_string, pfn_escape_string)
|
|
#undef GETPROC
|
|
|
|
return true;
|
|
}
|
|
|
|
// ---- Helpers -------------------------------------------------------------
|
|
|
|
// Open a connection using current config. Returns handle on success, NULL
|
|
// on failure. Caller must call pfn_close on the returned handle.
|
|
static MYSQLH DB_Connect(char* pszError, int nErrBufSize)
|
|
{
|
|
if (!DB_LoadMySQL()) {
|
|
if (pszError)
|
|
strncpy(pszError, "libmysql.dll not found. Install MySQL Connector/C and place libmysql.dll next to mw4print.exe.", nErrBufSize - 1);
|
|
return NULL;
|
|
}
|
|
|
|
MYSQLH h = pfn_init(NULL);
|
|
if (!h) {
|
|
if (pszError)
|
|
strncpy(pszError, "mysql_init failed (out of memory?)", nErrBufSize - 1);
|
|
return NULL;
|
|
}
|
|
|
|
// Short connect timeout so the app doesn't hang if server is unreachable.
|
|
int nTimeout = 5;
|
|
pfn_options(h, MY_OPT_CONNECT_TIMEOUT, (const char*)&nTimeout);
|
|
|
|
MYSQLH conn = pfn_real_connect(h,
|
|
g_dbConfig.szHost,
|
|
g_dbConfig.szUsername,
|
|
g_dbConfig.szPassword,
|
|
g_dbConfig.szDatabase,
|
|
(unsigned int)g_dbConfig.nPort,
|
|
NULL, 0);
|
|
|
|
if (!conn) {
|
|
if (pszError) {
|
|
strncpy(pszError, pfn_error(h), nErrBufSize - 1);
|
|
pszError[nErrBufSize - 1] = '\0';
|
|
}
|
|
pfn_close(h);
|
|
return NULL;
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
// Execute a query string; return true on success.
|
|
static bool DB_Exec(MYSQLH h, const char* pszSQL)
|
|
{
|
|
return pfn_real_query(h, pszSQL, (unsigned long)strlen(pszSQL)) == 0;
|
|
}
|
|
|
|
// Escape a string for use inside SQL single-quoted literals.
|
|
// Writes into pszOut (must be at least 2*len+1 bytes).
|
|
static void DB_Escape(char* pszOut, const char* pszIn)
|
|
{
|
|
if (pfn_escape_string)
|
|
pfn_escape_string(pszOut, pszIn, (MYULONG)strlen(pszIn));
|
|
else {
|
|
// Fallback: manual escape of ' and backslash
|
|
char* dst = pszOut;
|
|
for (const char* src = pszIn; *src; ++src) {
|
|
if (*src == '\'' || *src == '\\')
|
|
*dst++ = '\\';
|
|
*dst++ = *src;
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
}
|
|
|
|
// ---- CREATE TABLE statements (run on first connect) ----------------------
|
|
|
|
static const char* s_szCreateMatch =
|
|
"CREATE TABLE IF NOT EXISTS `match` ("
|
|
" `match_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,"
|
|
" `match_time` DATETIME NOT NULL,"
|
|
" `server_name` VARCHAR(256) NOT NULL DEFAULT '',"
|
|
" `server_ip` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `map_name` VARCHAR(128) NOT NULL DEFAULT '',"
|
|
" `scenario_name` VARCHAR(128) NOT NULL DEFAULT '',"
|
|
" `game_type` INT NOT NULL DEFAULT 0,"
|
|
" `game_type_name` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `is_team_game` TINYINT(1) NOT NULL DEFAULT 0,"
|
|
" `player_count` INT NOT NULL DEFAULT 0,"
|
|
" `team_count` INT NOT NULL DEFAULT 0,"
|
|
" `team_scores` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `game_length` INT NOT NULL DEFAULT 0,"
|
|
" `kill_limit` INT NOT NULL DEFAULT 0,"
|
|
" `respawn_limit` INT NOT NULL DEFAULT 0,"
|
|
" `friendly_fire` INT NOT NULL DEFAULT 0,"
|
|
" `heat_on` TINYINT(1) NOT NULL DEFAULT 0,"
|
|
" `armor_mode` TINYINT(1) NOT NULL DEFAULT 0,"
|
|
" `unlimited_ammo` TINYINT(1) NOT NULL DEFAULT 0,"
|
|
" `is_night` TINYINT(1) NOT NULL DEFAULT 0,"
|
|
" `min_tonnage` INT NOT NULL DEFAULT 0,"
|
|
" `max_tonnage` INT NOT NULL DEFAULT 0,"
|
|
" PRIMARY KEY (`match_id`)"
|
|
") ENGINE=InnoDB DEFAULT CHARSET=utf8";
|
|
|
|
static const char* s_szCreatePlayerResult =
|
|
"CREATE TABLE IF NOT EXISTS `player_result` ("
|
|
" `result_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,"
|
|
" `match_id` INT UNSIGNED NOT NULL,"
|
|
" `player_name` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `mech_name` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `team` INT NOT NULL DEFAULT 0,"
|
|
" `score` INT NOT NULL DEFAULT 0,"
|
|
" `kills` INT NOT NULL DEFAULT 0,"
|
|
" `deaths` INT NOT NULL DEFAULT 0,"
|
|
" `is_bot` TINYINT(1) NOT NULL DEFAULT 0,"
|
|
" `pilot_decal` INT NOT NULL DEFAULT 0,"
|
|
" PRIMARY KEY (`result_id`),"
|
|
" KEY `match_id` (`match_id`)"
|
|
") ENGINE=InnoDB DEFAULT CHARSET=utf8";
|
|
|
|
static const char* s_szCreatePVP =
|
|
"CREATE TABLE IF NOT EXISTS `pvp` ("
|
|
" `pvp_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,"
|
|
" `match_id` INT UNSIGNED NOT NULL,"
|
|
" `attacker` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `victim` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `kills` INT NOT NULL DEFAULT 0,"
|
|
" `score` INT NOT NULL DEFAULT 0,"
|
|
" PRIMARY KEY (`pvp_id`),"
|
|
" KEY `match_id` (`match_id`)"
|
|
") ENGINE=InnoDB DEFAULT CHARSET=utf8";
|
|
|
|
static const char* s_szCreateEvent =
|
|
"CREATE TABLE IF NOT EXISTS `event` ("
|
|
" `event_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,"
|
|
" `match_id` INT UNSIGNED NOT NULL,"
|
|
" `event_type` INT NOT NULL DEFAULT 0,"
|
|
" `actor` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `target` VARCHAR(64) NOT NULL DEFAULT '',"
|
|
" `weapon` INT NOT NULL DEFAULT 0,"
|
|
" `body_zone` INT NOT NULL DEFAULT 0,"
|
|
" `param` INT NOT NULL DEFAULT 0,"
|
|
" PRIMARY KEY (`event_id`),"
|
|
" KEY `match_id` (`match_id`)"
|
|
") ENGINE=InnoDB DEFAULT CHARSET=utf8";
|
|
|
|
// Map CBucketManager::Bucket_Type int to a game_type name string.
|
|
static const char* GameTypeName(int n)
|
|
{
|
|
switch (n) {
|
|
case 0: return "StockDestruction";
|
|
case 1: return "StockTeamDestruction";
|
|
case 2: return "StockAttrition";
|
|
case 3: return "StockTeamAttrition";
|
|
case 4: return "StockCaptureTheFlag";
|
|
case 5: return "StockKingOfTheHill";
|
|
case 6: return "StockTeamKingOfTheHill";
|
|
case 7: return "StockTerritories";
|
|
case 8: return "StockStealTheBacon";
|
|
case 9: return "StockCaptureBase";
|
|
case 10: return "StockDestroyObjective";
|
|
case 11: return "StockEscort";
|
|
case 12: return "StockMasterTrial";
|
|
case 13: return "StockSiegeAssault";
|
|
case 14: return "StockCampaign";
|
|
case 15: return "CustomDestruction";
|
|
case 16: return "CustomTeamDestruction";
|
|
case 17: return "CustomAttrition";
|
|
case 18: return "CustomTeamAttrition";
|
|
case 19: return "CustomCaptureTheFlag";
|
|
case 20: return "CustomKingOfTheHill";
|
|
case 21: return "CustomTeamKingOfTheHill";
|
|
case 22: return "CustomTerritories";
|
|
case 23: return "CustomStealTheBacon";
|
|
case 24: return "CustomCaptureBase";
|
|
case 25: return "CustomDestroyObjective";
|
|
case 26: return "CustomEscort";
|
|
case 27: return "CustomMasterTrial";
|
|
case 28: return "CustomSiegeAssault";
|
|
case 29: return "CustomCampaign";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
// Resolve a ReplicatorID to a player name by scanning aRSOs[].
|
|
static const char* ResolvePlayerName(
|
|
const ReplicatorID& id,
|
|
PCRecScoreObject aRSOs[],
|
|
int nPlayers)
|
|
{
|
|
for (int i = 0; i < nPlayers; i++) {
|
|
if (aRSOs[i]->m_who == id)
|
|
return aRSOs[i]->GetName();
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// ---- Config I/O ----------------------------------------------------------
|
|
|
|
static void DB_GetINIPath(char* pszOut)
|
|
{
|
|
sprintf(pszOut, "%s\\mw4print.ini", AssetsDirectory1);
|
|
}
|
|
|
|
void DB_LoadConfig()
|
|
{
|
|
char szINI[MAX_PATH];
|
|
DB_GetINIPath(szINI);
|
|
|
|
const char* sec = "MySQLExport";
|
|
g_dbConfig.bEnabled = !!GetPrivateProfileInt(sec, "Enabled", 0, szINI);
|
|
GetPrivateProfileString(sec, "Host", "localhost", g_dbConfig.szHost, sizeof(g_dbConfig.szHost), szINI);
|
|
g_dbConfig.nPort = GetPrivateProfileInt(sec, "Port", 3306, szINI);
|
|
GetPrivateProfileString(sec, "Database", "firestorm_stats", g_dbConfig.szDatabase, sizeof(g_dbConfig.szDatabase), szINI);
|
|
GetPrivateProfileString(sec, "Username", "mw4", g_dbConfig.szUsername, sizeof(g_dbConfig.szUsername), szINI);
|
|
GetPrivateProfileString(sec, "Password", "", g_dbConfig.szPassword, sizeof(g_dbConfig.szPassword), szINI);
|
|
g_dbConfig.bExportEvents = !!GetPrivateProfileInt(sec, "ExportEvents", 0, szINI);
|
|
}
|
|
|
|
void DB_SaveConfig()
|
|
{
|
|
char szINI[MAX_PATH];
|
|
DB_GetINIPath(szINI);
|
|
|
|
const char* sec = "MySQLExport";
|
|
char szBuf[32];
|
|
|
|
WritePrivateProfileString(sec, "Enabled", g_dbConfig.bEnabled ? "1" : "0", szINI);
|
|
WritePrivateProfileString(sec, "Host", g_dbConfig.szHost, szINI);
|
|
_itoa(g_dbConfig.nPort, szBuf, 10);
|
|
WritePrivateProfileString(sec, "Port", szBuf, szINI);
|
|
WritePrivateProfileString(sec, "Database", g_dbConfig.szDatabase, szINI);
|
|
WritePrivateProfileString(sec, "Username", g_dbConfig.szUsername, szINI);
|
|
WritePrivateProfileString(sec, "Password", g_dbConfig.szPassword, szINI);
|
|
WritePrivateProfileString(sec, "ExportEvents", g_dbConfig.bExportEvents ? "1" : "0", szINI);
|
|
}
|
|
|
|
// ---- Test connection -----------------------------------------------------
|
|
|
|
bool DB_TestConnection(char* pszError, int nErrBufSize)
|
|
{
|
|
if (pszError && nErrBufSize > 0)
|
|
pszError[0] = '\0';
|
|
|
|
MYSQLH h = DB_Connect(pszError, nErrBufSize);
|
|
if (!h)
|
|
return false;
|
|
|
|
bool bOK = DB_Exec(h, "SELECT 1");
|
|
if (!bOK && pszError) {
|
|
strncpy(pszError, pfn_error(h), nErrBufSize - 1);
|
|
pszError[nErrBufSize - 1] = '\0';
|
|
}
|
|
pfn_close(h);
|
|
return bOK;
|
|
}
|
|
|
|
// ---- Main export ---------------------------------------------------------
|
|
|
|
bool DB_ExportMatch(
|
|
const CRecScoreFull& rsf,
|
|
PCRecScoreObject aRSOs[],
|
|
int nPlayers,
|
|
const int naTeams[8],
|
|
int nTeamCount,
|
|
const MWNetMissionParameters& params)
|
|
{
|
|
char szError[512];
|
|
MYSQLH h = DB_Connect(szError, sizeof(szError));
|
|
if (!h)
|
|
return false;
|
|
|
|
char szSQL[4096];
|
|
char szEsc[512];
|
|
bool bOK = true;
|
|
|
|
// Ensure tables exist (silent if already present).
|
|
DB_Exec(h, s_szCreateMatch);
|
|
DB_Exec(h, s_szCreatePlayerResult);
|
|
DB_Exec(h, s_szCreatePVP);
|
|
if (g_dbConfig.bExportEvents)
|
|
DB_Exec(h, s_szCreateEvent);
|
|
|
|
// ---- Insert match row ------------------------------------------------
|
|
|
|
// Build team_scores as comma-separated string e.g. "100,80,0,0,0,0,0,0"
|
|
char szTeamScores[64];
|
|
sprintf(szTeamScores, "%d,%d,%d,%d,%d,%d,%d,%d",
|
|
rsf.m_naTeamScores[0], rsf.m_naTeamScores[1],
|
|
rsf.m_naTeamScores[2], rsf.m_naTeamScores[3],
|
|
rsf.m_naTeamScores[4], rsf.m_naTeamScores[5],
|
|
rsf.m_naTeamScores[6], rsf.m_naTeamScores[7]);
|
|
|
|
char szServerNameE[512], szServerIPE[512], szMapE[256], szScenarioE[256], szGameTypeNameE[128];
|
|
DB_Escape(szServerNameE, params.m_serverName);
|
|
DB_Escape(szServerIPE, params.m_serverIP);
|
|
DB_Escape(szMapE, params.m_mapName);
|
|
DB_Escape(szScenarioE, params.m_scenarioName);
|
|
DB_Escape(szGameTypeNameE, GameTypeName(params.m_ruleType));
|
|
|
|
sprintf(szSQL,
|
|
"INSERT INTO `match` "
|
|
"(match_time, server_name, server_ip, map_name, scenario_name, "
|
|
" game_type, game_type_name, is_team_game, player_count, team_count, "
|
|
" team_scores, game_length, kill_limit, respawn_limit, friendly_fire, "
|
|
" heat_on, armor_mode, unlimited_ammo, is_night, min_tonnage, max_tonnage) "
|
|
"VALUES ("
|
|
"'%04d-%02d-%02d %02d:%02d:%02d'," // match_time
|
|
"'%s','%s','%s','%s'," // server_name, server_ip, map_name, scenario_name
|
|
"%d,'%s',%d,%d,%d," // game_type, game_type_name, is_team_game, player_count, team_count
|
|
"'%s'," // team_scores
|
|
"%d,%d,%d,%d," // game_length, kill_limit, respawn_limit, friendly_fire
|
|
"%d,%d,%d,%d,%d,%d" // heat_on, armor_mode, unlimited_ammo, is_night, min_tonnage, max_tonnage
|
|
")",
|
|
rsf.m_SysTime.wYear, rsf.m_SysTime.wMonth, rsf.m_SysTime.wDay,
|
|
rsf.m_SysTime.wHour, rsf.m_SysTime.wMinute, rsf.m_SysTime.wSecond,
|
|
szServerNameE, szServerIPE, szMapE, szScenarioE,
|
|
params.m_ruleType, szGameTypeNameE,
|
|
rsf.m_bTeamGame ? 1 : 0, nPlayers, nTeamCount,
|
|
szTeamScores,
|
|
params.m_gameLength,
|
|
params.m_killLimit ? params.m_killLimitNumber : 0,
|
|
params.m_respawnLimit ? params.m_respawnLimitNumber : 0,
|
|
params.m_friendlyFirePercentage,
|
|
params.m_heatOn ? 1 : 0,
|
|
params.m_armormodeOn ? 1 : 0,
|
|
params.m_unlimitedAmmo ? 1 : 0,
|
|
params.m_isNight ? 1 : 0,
|
|
params.m_minimumTonnage,
|
|
params.m_maximumTonnage);
|
|
|
|
bOK = DB_Exec(h, szSQL);
|
|
if (!bOK) {
|
|
pfn_close(h);
|
|
return false;
|
|
}
|
|
|
|
MYULLONG nMatchID = pfn_insert_id(h);
|
|
|
|
// ---- Insert player_result rows ---------------------------------------
|
|
|
|
for (int i = 0; i < nPlayers && bOK; i++) {
|
|
const CRecScoreObject& rso = *aRSOs[i];
|
|
char szNameE[128], szMechE[128];
|
|
DB_Escape(szNameE, rso.GetName());
|
|
DB_Escape(szMechE, rso.GetMech());
|
|
|
|
sprintf(szSQL,
|
|
"INSERT INTO `player_result` "
|
|
"(match_id, player_name, mech_name, team, score, kills, deaths, is_bot, pilot_decal) "
|
|
"VALUES (%I64u,'%s','%s',%d,%d,%d,%d,%d,%d)",
|
|
nMatchID,
|
|
szNameE, szMechE,
|
|
rso.m_nPilotTeam,
|
|
rso.m_nScore,
|
|
rso.m_nKills,
|
|
rso.m_nDeaths,
|
|
rso.m_bBOT ? 1 : 0,
|
|
rso.m_nPilotDecal);
|
|
|
|
bOK = DB_Exec(h, szSQL);
|
|
}
|
|
|
|
// ---- Insert pvp rows -------------------------------------------------
|
|
|
|
for (int p = 0; p < rsf.m_nPVPs && bOK; p++) {
|
|
const CRecScoreFull::SPVP_Rec& pvp = rsf.m_pPVPs[p];
|
|
const char* pszAttacker = ResolvePlayerName(pvp.m_Inf, aRSOs, nPlayers);
|
|
const char* pszVictim = ResolvePlayerName(pvp.m_Rcv, aRSOs, nPlayers);
|
|
|
|
char szAttE[128], szVicE[128];
|
|
DB_Escape(szAttE, pszAttacker);
|
|
DB_Escape(szVicE, pszVictim);
|
|
|
|
sprintf(szSQL,
|
|
"INSERT INTO `pvp` (match_id, attacker, victim, kills, score) "
|
|
"VALUES (%I64u,'%s','%s',%d,%d)",
|
|
nMatchID, szAttE, szVicE, pvp.m_nKills, pvp.m_nScore);
|
|
|
|
bOK = DB_Exec(h, szSQL);
|
|
}
|
|
|
|
// ---- Insert event rows (optional) ------------------------------------
|
|
|
|
if (bOK && g_dbConfig.bExportEvents) {
|
|
// Walk the CRecScorePack linked list.
|
|
const CRecScorePack* pPack = rsf.m_pScorePackStart;
|
|
while (pPack && bOK) {
|
|
for (int e = 0; e < pPack->m_nCount && bOK; e++) {
|
|
const SRecScore& rs = pPack->m_a[e];
|
|
const char* pszActor = ResolvePlayerName(rs.m_who, aRSOs, nPlayers);
|
|
const char* pszTarget = ResolvePlayerName(rs.m_whom, aRSOs, nPlayers);
|
|
char szActE[128], szTgtE[128];
|
|
DB_Escape(szActE, pszActor);
|
|
DB_Escape(szTgtE, pszTarget);
|
|
|
|
sprintf(szSQL,
|
|
"INSERT INTO `event` (match_id, event_type, actor, target, weapon, body_zone, param) "
|
|
"VALUES (%I64u,%d,'%s','%s',%d,%d,%d)",
|
|
nMatchID,
|
|
rs.m_nType,
|
|
szActE, szTgtE,
|
|
rs.m_nBy,
|
|
rs.m_Where.m_xWhere,
|
|
rs.m_nParam);
|
|
|
|
bOK = DB_Exec(h, szSQL);
|
|
}
|
|
pPack = pPack->m_pNext;
|
|
}
|
|
}
|
|
|
|
pfn_close(h);
|
|
return bOK;
|
|
}
|