// log_test.c: Interface between MW4 Game-App and zStats logging library.
// ----------------------------------------------------------------------
// J. Keimig
// jkeimig@netgamesusa.com
// 05 Jan 00	// Sample
// 09 Oct 00	// Near ZBR for MW4
//

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include "MW4Headers.hpp"
#include "MW4Shell.hpp"
#include "MWApplication.hpp"
#include "MWMission.hpp"
#include "MWPlayer.hpp"
#include "mwmission.hpp"
#include "bucket.hpp"
#if !defined(NO_MR)
#include "nglog.hpp"
#include "nglog_mw4.hpp"
#endif // !defined(NO_MR)
#include "GameTypes.h"
#include <GameOS/Network.hpp>
#include <Buildnum/buildnum.h>

#if !defined(NO_LOG)
// mdm - this is the key name for the NGWS status property
const char NGWS_STATUS_KEY[] = "ngWS";

#define VA_BUFF 4
#define BUF_MAX 50

#define UPDATE_INTERVAL	12.0	// Minimum time interval between score updates.

typedef int (*PFNGetngLogAPI)(nglog_import_t *, nglog_export_t *);

char ngWS_status[32];			// External ngWS status (example for external public server info)
nglog_library_t *nglog = NULL;	// ngLog library object
PROCESS_INFORMATION pi = {0};	// Single process info var to track ngWS status


// Example of server configuration variables that can
// be adjusted by the server operator.
int  z_style;
int  z_flush;
int  z_buffer;
int  z_id;
int  zS_exec;
int  zWS_enable;		// Server setting if ngWS is to be enabled.
char z_path[1024];
char z_logname[1024];
char zS_cfg[1024];
char zS_logdir[1024];


// Private local
int  game_started = 0;	// For ordering... guarantees an entry will be placed first
int  game_finished = 0;	// For filtering different end-game states
int  log_bufcnt = 0;	// Number of buffered entries.
char log_buf[BUF_MAX][1024];	// Buffering for ordering.

double score_update = 0.0;	// Periodic score updates.

int  va_index = 0;			// Current va_buf pointer
char va_buf[VA_BUFF][16384];// Handle nested calls for va

// Watermark info
#define H_OFFSET 7
#define H_LEN    10*3+8
unsigned char h_refs[] = { 251, 204, 207, 204, 219, 204, 199, /**/ 201, 192, 242,
						   244, 231, 135, 240, 151, 213, 203, 227, 244, 144,
						   231, 144, 146, 231, 202, 150, 228, 129, 151, 146,
						   151, 148, 144, 137, 196, 151, 245, 247, 232, 213,
						   231, 211, 206, 151, 233, /**/ 152, 155, 155, 155, 154,
						     6,   7, 199,   5, 251,  97,   0, 101,  41,  97,
						     0,  42,  41,  99,   7, 202, 181,  32,  73, 255,
						   199, 206, 254, 250, 137, 218, 204, 202, 219, 204,
						   221, 137, 218, 221, 219, 192, 199, 206, 152, 152,
						   243, 134,  28,   6, 111, 222, 198,   8,  54, 134
						};

//
// void MW4log_setStatusString( const char * status ):
// ---------------------------------------------------
// Set the ngWS status string.  This is the value for the 
// ngWorldStats property that GUN/GameSpy will display.
//
void MW4log_setStatusString( const char * status )
{
    gosASSERT( strlen(status) < 32 );

    strncpy(ngWS_status, status, 32);

    // this advertises the property to any Game List servers that are available.
    gos_NetSetAdvertItem( 0, NGWS_STATUS_KEY, status );
}


//
// void MW4log_init(void):
// -----------------------
// Start up the logging interface, if needed.
//
void MW4log_init(void)
{
	if(!MWApplication::GetInstance() || !MWApplication::GetInstance()->serverFlag || !MWApplication::GetInstance()->networkingFlag) {
		return;
	}

	if(nglog && z_style != STYLE_NGSTATS) {
		z_style = STYLE_APPEND;
	}

	// Load the logging library
	if(!nglog && !MW4log_load_zLogLib("ZlogLib.dll")) {
//		printf("Error loading ngLog library\n");
		return;
	}

	log_bufcnt = 0;

	// Specify logging options.
//	MW4log_options_init();

	// Start the log
	MW4log_start_logging();

	return;
}


//
// void MW4log_options_init(void):
// -------------------------------
// Sets default "server" logging parameters.
//
void MW4log_options_init(void)
{
	// External tweak variables (i.e. through server config)
	z_style		= STYLE_NONE;
	z_flush		= FLUSH_NOW;
	z_buffer	= 10;
	z_id		= 27910;
	zS_exec		= 0;
	zWS_enable	= 1;
	strcpy(z_path, "Stats");
	strcpy(z_logname, "MW4_zlog.log");
	strcpy(zS_cfg, "zStatsMW4.cfg");
	strcpy(zS_logdir, "logs");

	// Set external info based on initial settings
	if(zWS_enable) {
        MW4log_setStatusString("ENABLED");
	} else {
        MW4log_setStatusString("DISABLED");
	}
//	printf("*INITIAL* ngWS status: %s\n", ngWS_status);

	return;
}


//
// int MW4log_load_zLogLib(char *libname):
// ----------------------------------------
// Attempts to load and initialize logging library.
//
int MW4log_load_zLogLib(char *libname)
{
	int i, len;
	char *tmp;
	char libpath[MAX_PATH*2], buf[MAX_PATH*3];
	nglog_import_t imp;
	PFNGetngLogAPI GetngLogAPI = NULL;
	HANDLE ngloglibhandle = NULL;


	len = GetCurrentDirectory(MAX_PATH, libpath);
	tmp = libpath;
	while(*tmp)	{
		*tmp ++= (char)tolower((int)*tmp);
	}

	sprintf(buf, "%s%s%s", libpath, MW4_BINARY_PATH, libname);

	// Try to load from the hardcoded path.  If no-go, then try the local directory.
	ngloglibhandle = LoadLibrary(buf);
	if(!ngloglibhandle) {
		strcpy(libpath, _pgmptr);
		tmp = libpath;
		while(*tmp)	{
			*tmp++ = (char)tolower((int)*tmp);
		}

		i = strlen(libpath)-1;
		while(libpath[i] != '\\') {
			i--;
		}
		libpath[i] = 0;
		sprintf(buf, "%s\\%s", libpath, libname);

		ngloglibhandle = LoadLibrary(buf);
		if(!ngloglibhandle) {
			printf("LibError: Couldn't load \"%s\"\n", libname);
			return(0);
		}
	}

	GetngLogAPI = (PFNGetngLogAPI) GetProcAddress((struct HINSTANCE__ *)ngloglibhandle, "GetngLogAPI");
	if(GetngLogAPI == NULL) {
		FreeLibrary((struct HINSTANCE__ *)ngloglibhandle);
//		printf("LibError: Couldn't find GetngLogAPI in \"%s\"\n", libname);
		return(0);
	} else {
		nglog = (nglog_library_t *) calloc(1, sizeof(nglog_library_t));
		nglog->fn = (nglog_export_t *) calloc(1, sizeof(nglog_export_t));

		strncpy(nglog->path, libname, sizeof(nglog->path) - 1);
		nglog->path[MAX_PATH - 1] = 0;
		nglog->handle = ngloglibhandle;
		imp.MsgDump = MW4log_msgDump;
		imp.TimeDump = MW4log_timeDump;

		// Set all the dynamic bindings
		if(!(GetngLogAPI)(&imp, nglog->fn)) {
//			printf("LibError: Error in getting API\n");
			return(0);
		}

//		printf("%s *INITIALIZED*\n", nglog->fn->LibVersion());
	}

	return(1);
}


//
// void MW4log_unload_zLogLib(void):
// ----------------------------------
// Unloads and frees resources due to logging.
//
void MW4log_unload_zLogLib(void)
{
	if(!nglog) {
		return;
	}

	FreeLibrary((struct HINSTANCE__ *)nglog->handle);

	free(nglog->fn);
	free(nglog);

	nglog = NULL;

	return;
}


//
// void MW4log_start_logging(void):
// --------------------------------
// Gets logging started with proper headers and logfile formats.
//
void MW4log_start_logging(void)
{
	int i;
	const char *hostname, *gametype;
	char *mapname;
	char *info[] = {0, 0, 0, 0, 0,
					0, 0, 0, 0, 0,
					0, 0, 0, 0, 0,
					0, 0, 0, 0, 0,
					0, 0, 0, 0};	// 23 info lines + 1 terminator
	nglog_base base_info;

	if(!MWApplication::GetInstance()) {
		return;
	}

	NetMissionParameters::MWNetMissionParameters *params = MWApplication::GetInstance()->GetLocalNetParams();


	// Sanity check
	if(!params) {
		STOP(("MissionParams == NULL"));
	}

	// Initialize the log
	base_info.style = z_style;
	base_info.flush = z_flush;
	base_info.buffer = z_buffer;
//	base_info.worldstats = 1;	// Hard-coded for beta
	zWS_enable = params->m_reportStats;
	base_info.worldstats = zWS_enable;
	base_info.id = z_id;

	// "path" should be a full path definition
	strncpy(base_info.path, z_path, sizeof(base_info.path) - 1);
	base_info.path[sizeof(base_info.path) - 1] = 0;
	strncpy(base_info.logname, z_logname, sizeof(base_info.logname) - 1);
	base_info.logname[sizeof(base_info.logname) - 1] = 0;

	// ngStats setup info
	base_info.ngS_exec = 0;	// Call ngStats after every log close
	strncpy(base_info.ngS_cfg, zS_cfg, sizeof(base_info.ngS_cfg) - 1);
	base_info.ngS_cfg[sizeof(base_info.ngS_cfg) - 1] = 0;
	strncpy(base_info.ngS_logdir, zS_logdir, sizeof(base_info.ngS_logdir) - 1);
	base_info.ngS_logdir[sizeof(base_info.ngS_logdir) - 1] = 0;

	// ngStats and ngWorldStats name identification
	strcpy(base_info.ngS_name, "MechWarrior4");
	strcpy(base_info.ngWS_name, "MW4");

	// Hash input
	base_info.h_start = H_OFFSET;
	base_info.h_len   = H_LEN;
	base_info.h_refs  = h_refs;
	// Initialize required base header fields
	base_info.game_author = GAME_AUTHOR;
	base_info.game_name = GAME_NAME;
	base_info.game_url = GAME_AUTHOR_URL;
	base_info.game_version = VERSION;
//	base_info.game_version = GAME_VERSION;

	if(Network::GetInstance()) {
		hostname = Network::GetInstance()->GetGameName();
	} else {
		hostname = NULL;
	}
	if(MWApplication::GetInstance()) {
		mapname = (char *) MWApplication::GetInstance()->m_currentMissionName;
	} else {
		mapname = NULL;
	}
	if(MW4Shell::Instance) {
		gametype = MW4Shell::Instance->GetRuleName(params->m_ruleType);
	} else {
		gametype = NULL;
	}

	// Custom info for log header
// MSL 5.05 Add Option
	info[0]  = strdup(va("server_address\t%s", MW4log_hostAddr()));
	info[1]  = strdup(va("server_port\t%d", 1234567));
	info[2]  = strdup(va("timelimit\t%d", params->m_gameLength));
	info[3]  = strdup(va("fraglimit\t%d", params->m_killLimit));
	info[4]  = strdup(va("fraglimitNumber\t%d", params->m_killLimitNumber));
	info[5]  = strdup(va("respawnlimit\t%d", params->m_respawnLimit));
	info[6]  = strdup(va("respawnlimitNumber\t%d", params->m_respawnLimitNumber));
	info[7]  = strdup(va("splashdamage\t%d", params->m_splashOn));
	info[8]  = strdup(va("weaponjam\t%d", params->m_weaponjamOn));
	info[9]  = strdup(va("ammobayfire\t%d", params->m_ammobayfireOn));
// MSL 5.05 Advance Mode
	info[10] = strdup(va("advancemode\t%d", params->m_advancemodeOn));
// MSL 5.05 Armor Mode
	info[11] = strdup(va("armormode\t%d", params->m_armormodeOn));
	info[12] = strdup(va("splashdamagePercentage\t%d", params->m_splashPercentage));
	info[13] = strdup(va("unlimitedammo\t%d", params->m_unlimitedAmmo));
	info[14] = strdup(va("mechheat\t%d", params->m_heatOn));
	info[15] = strdup(va("autoaim\t%d", params->m_allowAutoAim));
	info[16] = strdup(va("stockmechs\t%d", params->m_onlyStockMech));
	info[17] = strdup(va("invulnerabledrop\t%d", params->m_invulnerableDrop));
	info[18] = strdup(va("forcerespawn\t%d", params->m_forceRespawn));
	info[19] = strdup(va("radarmode\t%d", params->m_radarMode));
	info[20] = strdup(va("maxclients\t%d", params->m_maxPlayers));
	info[21] = strdup(va("hostname\t%s", ((hostname) ? hostname : "UNKNOWN_HOST")));
	info[22] = strdup(va("cpu\t%s", params->m_serverCPU));
	info[23] = strdup(va("map\t%d\t%s", params->m_mapID, ((mapname) ? mapname : "UNKNOWN_MAP")));
	info[24] = strdup(va("gametype\t%s", ((gametype) ? gametype : "UNKNOWN_GAMETYPE")));
	info[25] = 0;	// Make sure final string is null

	// We're all set, start up the log
	strncpy(z_logname, nglog->fn->logStart(&base_info, info), sizeof(z_logname) - 1);
	z_logname[sizeof(z_logname) - 1] = 0;

	// Clear out strdup allocations
// MSL 5.05 Add Option
	for(i=0; i<25; i++) {
		if(info[i]) {
			free(info[i]);
		}
	}

	return;
}


//
// void MW4log_ngWS_update(void):
// ------------------------------
// Updates current ngWS status capability.
//
void MW4log_ngWS_update(void)
{
	int stat;


	// ngWS was never invoked from the beginning
	if(pi.dwThreadId == -1111) {
//		printf("No exit_code, error during ngWS init\n");
        MW4log_setStatusString("INACTIVE");

	// ngWS logging *is* enabled.. check exit codes.
	} else {
		stat = nglog->fn->statusNGWS(&pi);
		switch(stat) {
			case EXIT_PROCERR:
				MW4log_setStatusString("INACTIVE");
				break;
			case EXIT_NGWSERR:
				MW4log_setStatusString("INACTIVE");
				break;
			case EXIT_RUNNING:
				break;	// Maintain current state.
			case EXIT_OKAY:
				MW4log_setStatusString("ENABLED");
				break;
			default:
				MW4log_setStatusString("ENABLED");
				break;
		}
	}

	return;
}


//
// void MW4log_msgDump(int msg_type, char *format, ...):
// -----------------------------------------------------
// Handles messages passed from the ngLogLibrary
//
void MW4log_msgDump(int msg_type, char *format, ...)
{
	char buf[4096];
	va_list argptr;


	va_start(argptr, format);
	vsprintf(buf, format, argptr);
	va_end(argptr);

	// Print "info" to stdout.
	if(msg_type == MSG_INFO) {
//		nglog->fn->gameString((float)gos_GetElapsedTime(), va("(%s) INFO: %s", nglog->path, buf));
//		fprintf(stdout, "(%s) INFO: %s", nglog->path, buf);
//		fflush(stdout);
	} else {	// MSG_ERROR, MSG_CRITICAL
		// Print "errors" to stderr.
//		nglog->fn->gameString((float)gos_GetElapsedTime(), va("** (%s) ERROR: %s", nglog->path, buf));
//		fprintf(stderr, "** (%s) ERROR: %s", nglog->path, buf);
//		fflush(stderr);
	}

	return;
}


//
// char *MW4log_timeDump(void):
// ---------------------------
// Returns current gametime for MW4.
//
char *MW4log_timeDump(void)
{
	MWMission *mission = (MWMission*)MWMission::GetInstance();


	if(!mission) {
		return("0.0000");
	} else {
		return(va("%.4f", mission->GetMissionTime()));
	}
}


//
// char *va(char *format, ...):
// -------------------------------
// Does a varargs printf into a rotating static buffer.
//
char *va(char *format, ...)
{
	char *tmp;
	va_list argptr;


	tmp = va_buf[va_index];

	va_start(argptr, format);
	vsprintf(tmp, format, argptr);
	va_end(argptr);

	va_index = (va_index + 1) % VA_BUFF;

	return(tmp);
}



//
// char *MW4log_hostAddr(void):
// ----------------------------
// Returns string of the local host's FIRST IP address in dotted-decimal
// notation.
//
char *MW4log_hostAddr(void)
{
	struct hostent *hptr;
	static char temp[128], lname[256];
	unsigned char *p;


	if(gethostname(lname, sizeof(lname))) {
		sprintf(temp, "ERROR: no name");
		return(temp);
	}
	if((hptr = gethostbyname(lname)) == NULL) {
		sprintf(temp, "ERROR: can't convert name\n");
		return(temp);
	}

	p = (unsigned char *) hptr->h_addr_list[0];
	sprintf(temp, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);

	return(temp);
}


//
// void ngLog_playerIdentifier(unsigned char *name, char *passwd, int buf_len):
// ----------------------------------------------------------------------------
// Spits back the player's unique MD5 identifier.
//
char *MW4log_playerIdentifier(unsigned char *buf, int buf_len)
{
	int i;
	static char tmp[64], tmp2[64];


	tmp[0] = 0;
	for(i=0; i<buf_len; i++) {
		sprintf(tmp2, "%02x", buf[i]);
		strcat(tmp, tmp2);
	}

	return(tmp);
}



//
// void MW4log_generic(char *str):
// ------------------------------------------------
// Dummy log entry -- REMOVE ME!
//
void MW4log_generic(int entry_type, char *str)
{
	char msg[4096];


	if(!MWApplication::GetInstance() || !MWApplication::GetInstance()->serverFlag || !MWApplication::GetInstance()->networkingFlag) {
		return;
	}

	if(!nglog) {
		// Save contents of va, for safety
		strncpy(msg, str, sizeof(msg)-1);
		msg[sizeof(msg) - 1] = 0;

		MW4log_init();
		if(!nglog) {
			return;
		}

		str = msg;
	}

//	nglog->fn->gameString((float)gos_GetElapsedTime(), va("rcvd:\t%s", str));

	// Take care of ordering
	if(!game_started) {
		if(entry_type == MW4GAME_START) {
			nglog->fn->gameString(str);
			MW4log_writeBuf();
			game_started = 1;
			game_finished = 0;
			score_update = UPDATE_INTERVAL;

			return;
		} else {
			if(log_bufcnt < BUF_MAX) {
				strncpy(log_buf[log_bufcnt], str, sizeof(log_buf[log_bufcnt]) - 1);
				log_buf[log_bufcnt][sizeof(log_buf[log_bufcnt]) - 1] = 0;
				log_bufcnt++;
			}

			return;
		}
	}

	// Now, make entries
	nglog->fn->gameString(str);

	return;
}


//
// void MW4log_writeBuf(void):
// ---------------------------
// Drains buffered entries.
//
void MW4log_writeBuf(void)
{
	int i;

	for(i=0; i<log_bufcnt; i++) {
		nglog->fn->gameString(log_buf[i]);
	}

	log_bufcnt = 0;

	return;
}


//
// void MW4log_gameEnd(int entry_type, char *str):
// -----------------------------------------------
// GAME OVER MAN!!!
//
void MW4log_GameEnd(int entry_type, char *str)
{
	static int zWS_called = 0;


	if(!MWApplication::GetInstance() || !MWApplication::GetInstance()->serverFlag || !MWApplication::GetInstance()->networkingFlag) {
		return;
	}

	if(!nglog || game_finished) {
		return;
	}

	// Drain any queued entries (shouldn't happen?)
	MW4log_writeBuf();

	// Update zWS setting, if necessary.
	if(zWS_enable && zWS_called) {
		MW4log_ngWS_update();
	}

	nglog->fn->gameEnd(str, &pi);
	game_started = 0;
	game_finished = 1;
	if(zWS_enable) {
		zWS_called = 1;
	}

	free(nglog->fn);
	nglog->fn = NULL;
	free(nglog);
	nglog = NULL;


	return;
}


//
// void MW4log_playerConnect(char *str, int p_id):
// -----------------------------------------------
// Player connect.
//
void MW4log_playerConnect(int p_id, char *p_name, char *p_info, float p_tonnage, unsigned char *buf, int buf_len)
{
	char msg[1024];


	if(!MWApplication::GetInstance() || !MWApplication::GetInstance()->serverFlag || !MWApplication::GetInstance()->networkingFlag) {
		return;
	}

	if(!nglog) {
		// Save contents of va, for safety
		strcpy(msg, p_info);

		MW4log_init();
		if(!nglog) {
			return;
		}

		p_info = msg;
	}

	nglog->fn->plConnect(p_id, va("%s\t%s\t%.2f", p_name, p_info, p_tonnage), buf, buf_len);

	return;
}


//
// void MW4log_writeAllStats(void):
// --------------------------------
// Dumps all player stats.
//
void MW4log_writeAllStats(void)
{
	int i;
	MWApplication *app = MWApplication::GetInstance();


	if(!app) {
		return;
	}

	// Log player stats
	for(i=1; i<Maximum_Players; i++) {
		if(!app->servedConnectionData[i].clientConnected ||
		  !app->servedConnectionData[i].clientPlayer ||
		  !app->servedConnectionData[i].clientPlayer->vehicle) {
			continue;
		}
		
		MW4log_writePlayerStats(i, 0);
	}

	// Log lancemate (bot) stats
	for(i=0; i<Maximum_Lancemates; i++) {
		if(!app->lancemateConnectionData[i].lancemateMech ||
		  !app->lancemateConnectionData[i].lancemateConnected) {
			continue;
		}

		MW4log_writeBotStats(i, 0);
	}

	// Log team scores
/*	if(MWApplication::GetInstance()->GetLocalNetParams()->m_teamAllowed) {	
		MWMission *mission = Cast_Object(MWMission*, Mission::GetInstance());
		stlport::vector<CBucket*> team_buckets;

		if(!mission || !mission->m_BucketManager) {
			return;
		}

		for(int j=0; j<8; j++) {
			mission->m_BucketManager->FillTeamBuckets(i,team_buckets);
			for(stlport::vector<CBucket*>::iterator k=team_buckets.begin(); k!=team_buckets.end(); k++) {
				if((*k)->Tracked() == true) {
					MW4log_generic(MW4P_DSTATS, va("Team_Score\t%d\t%d", i, (*k)->Value()));
				}
			}
			team_buckets.clear();
		}
	}
*/
	return;
}


//
// void MW4log_checkCurrentStats(void):
// ------------------------------------
// Logs players' updated stats, if needed.
//
void MW4log_checkCurrentStats(void)
{
	int i;
	MWApplication *app = MWApplication::GetInstance();
	MWMission *mission = (MWMission*)MWMission::GetInstance();


	if(mission && app && app->GetLocalNetParams()) {
		int gt = app->GetLocalNetParams()->m_ruleType;


		if((gt != StockAttrition && gt != StockTeamAttrition) || !mission ||
		  !mission->GetMissionTime() || mission->GetMissionTime() <= score_update) {
			return;
		}

		score_update = mission->GetMissionTime() + UPDATE_INTERVAL;

		// Log player stats
		for(i=1; i<Maximum_Players; i++) {
			if(!app->servedConnectionData[i].clientConnected ||
			  !app->servedConnectionData[i].clientPlayer ||
			  !app->servedConnectionData[i].clientPlayer->vehicle ||
			  !app->servedConnectionData[i].stats_update) {
				continue;
			}
			
			MW4log_writePlayerStats(i, 1);
			app->servedConnectionData[i].stats_update = 0;
		}

		// Log lancemate (bot) stats
		for(i=0; i<Maximum_Lancemates; i++) {
			if(!app->lancemateConnectionData[i].lancemateMech ||
			  !app->lancemateConnectionData[i].lancemateConnected ||
			  !app->lancemateConnectionData[i].stats_update) {
				continue;
			}

			MW4log_writeBotStats(i, 1);
			app->lancemateConnectionData[i].stats_update = 0;
		}
	}

	return;
}


//
// void MW4log_writePlayerStats(int p_id, int stat_summary):
// ---------------------------------------------------------
// Dumps stats for a single player.
//
void MW4log_writePlayerStats(int p_id, int stat_summary)
{
	int i, c_gvn, c_rcv;
	double d_gvn, d_rcv;
	char tmp[256], d_stats[30000];
	MWApplication *app = MWApplication::GetInstance();
	MWMission *mission;
	Check_Object (MWMission::GetInstance());
	mission = Cast_Object (MWMission *,MWMission::GetInstance());


	// Psycho-analysis is required
	if(!app || !mission) {
		return;
	}

	d_gvn = 0.0;
	d_rcv = 0.0;
	c_gvn = 0;
	c_rcv = 0;


	// Per-weapon summary
	d_stats[0] = 0;
	for(i=FirstWeaponID; i<ServedConnectionData::NumWeaponsTracked; i++) {
		if(app->servedConnectionData[p_id].dmg_given[i] ||
		   app->servedConnectionData[p_id].dmg_rcvd[i]  ||
		   app->servedConnectionData[p_id].wep_atts[i]  ||
		   app->servedConnectionData[p_id].wep_hits[i]) {
			if(d_stats[0]) {
				strcat(d_stats, "\t");
			}

			sprintf(tmp, "%d:%d:%d:%.2f:%.2f", i,
											   app->servedConnectionData[p_id].wep_hits[i],
											   app->servedConnectionData[p_id].wep_atts[i],
											   app->servedConnectionData[p_id].dmg_given[i],
											   app->servedConnectionData[p_id].dmg_rcvd[i]);

			if((strlen(tmp) + strlen(d_stats)) < (sizeof(d_stats) - 2)) {
				strcat(d_stats, tmp);
			} else {
				break;
			}
		}
	}

	d_stats[sizeof(d_stats) - 1] = 0;

	if(!stat_summary) {
		if(d_stats[0]) {
			MW4log_generic(MW4P_DSTATS, va("PW\t%d\t%s", p_id, d_stats));
		} else {
			MW4log_generic(MW4P_DSTATS, va("PW\t%d\tNONE", p_id));
		}
	}


	// Per-player interaction summary
	d_stats[0] = 0;
	for(i=0; i<Maximum_Players+Maximum_Lancemates; i++) {
		if(app->servedConnectionData[p_id].pdmg_given[i] ||
		   app->servedConnectionData[p_id].pdmg_rcvd[i]  ||
		   app->servedConnectionData[p_id].comp_inf[i]  ||
		   app->servedConnectionData[p_id].comp_rcv[i]) {

			if(d_stats[0]) {
				strcat(d_stats, "\t");
			}

			sprintf(tmp, "%d:%.2f:%.2f:%d:%d", i,
											   app->servedConnectionData[p_id].pdmg_given[i],
											   app->servedConnectionData[p_id].pdmg_rcvd[i],
											   app->servedConnectionData[p_id].comp_inf[i],
											   app->servedConnectionData[p_id].comp_rcv[i]);

			d_gvn += app->servedConnectionData[p_id].pdmg_given[i];
			d_rcv += app->servedConnectionData[p_id].pdmg_rcvd[i];
			c_gvn += app->servedConnectionData[p_id].comp_inf[i];
			c_rcv += app->servedConnectionData[p_id].comp_rcv[i];

			if((strlen(tmp) + strlen(d_stats)) < (sizeof(d_stats) - 2)) {
				strcat(d_stats, tmp);
			} else {
				break;
			}
		}
	}

	d_stats[sizeof(d_stats) - 1] = 0;

	if(d_stats[0]) {
		MW4log_generic(MW4P_DSTATS, va("PD\t%d\t%s", p_id, d_stats));
	} else {
		MW4log_generic(MW4P_DSTATS, va("PD\t%d\tNONE", p_id));
	}

	if(stat_summary) {
		MW4log_generic(MW4P_DSTATS, va("PSS\t%d\t%.2f:%.2f:%d:%d", p_id, d_gvn, d_rcv, c_gvn, c_rcv));
	}

	if (app->servedConnectionData[p_id].clientPlayer && app->servedConnectionData[p_id].clientPlayer->vehicle)
	{

		ReplicatorID rid = app->servedConnectionData[p_id].clientPlayer->vehicle->GetReplicatorID();
		MW4log_generic(MW4P_DSTATS, va("PS\t%d\t%d\t%d\t%d\t%d", p_id,
										mission->m_BucketManager->GetCustomBucketValue(rid),
										app->servedConnectionData[i].effectivePing,
										0.0f,
										app->servedConnectionData[i].packetLoss));
	}
	


	return;
}



//
// void MW4log_writeBotStats(int p_id, int stat_summary):
// ------------------------------------------------------
// Dumps stats for a single bot.
//
void MW4log_writeBotStats(int p_id, int stat_summary)
{
	int i, pid;
	double d_gvn, d_rcv;
	int c_gvn, c_rcv;
	char tmp[256], d_stats[30000];
	MWMission *mission;
	Check_Object (MWMission::GetInstance());
	mission = Cast_Object (MWMission *,MWMission::GetInstance());


	d_gvn = 0.0;
	d_rcv = 0.0;
	c_gvn = 0;
	c_rcv = 0;

	MWApplication *app = MWApplication::GetInstance();
	pid = p_id+Maximum_Players;

	// Per-weapon summary
	d_stats[0] = 0;
	for(i=FirstWeaponID; i<ServedConnectionData::NumWeaponsTracked; i++) {
		if(app->lancemateConnectionData[p_id].dmg_given[i] ||
		   app->lancemateConnectionData[p_id].dmg_rcvd[i]  ||
		   app->lancemateConnectionData[p_id].wep_atts[i]  ||
		   app->lancemateConnectionData[p_id].wep_hits[i]) {
			if(d_stats[0]) {
				strcat(d_stats, "\t");
			}

			sprintf(tmp, "%d:%d:%d:%.2f:%.2f", i,
											   app->lancemateConnectionData[p_id].wep_hits[i],
											   app->lancemateConnectionData[p_id].wep_atts[i],
											   app->lancemateConnectionData[p_id].dmg_given[i],
											   app->lancemateConnectionData[p_id].dmg_rcvd[i]);

			if((strlen(tmp) + strlen(d_stats)) < (sizeof(d_stats) - 2)) {
				strcat(d_stats, tmp);
			} else {
				break;
			}
		}
	}

	d_stats[sizeof(d_stats) - 1] = 0;

	if(!stat_summary) {
		if(d_stats[0]) {
			MW4log_generic(MW4P_DSTATS, va("PW\t%d\t%s", pid, d_stats));
		} else {
			MW4log_generic(MW4P_DSTATS, va("PW\t%d\tNONE", pid));
		}
	}


	// Per-player interaction summary
	d_stats[0] = 0;
	for(i=0; i<Maximum_Players+Maximum_Lancemates; i++) {
		if(app->lancemateConnectionData[p_id].pdmg_given[i] ||
		   app->lancemateConnectionData[p_id].pdmg_rcvd[i]  ||
		   app->lancemateConnectionData[p_id].comp_inf[i]  ||
		   app->lancemateConnectionData[p_id].comp_rcv[i]) {

			if(d_stats[0]) {
				strcat(d_stats, "\t");
			}

			sprintf(tmp, "%d:%.2f:%.2f:%d:%d", i,
											   app->lancemateConnectionData[p_id].pdmg_given[i],
											   app->lancemateConnectionData[p_id].pdmg_rcvd[i],
											   app->lancemateConnectionData[p_id].comp_inf[i],
											   app->lancemateConnectionData[p_id].comp_rcv[i]);

			d_gvn += app->lancemateConnectionData[p_id].pdmg_given[i];
			d_rcv += app->lancemateConnectionData[p_id].pdmg_rcvd[i];
			c_gvn += app->lancemateConnectionData[p_id].comp_inf[i];
			c_rcv += app->lancemateConnectionData[p_id].comp_rcv[i];

			if((strlen(tmp) + strlen(d_stats)) < (sizeof(d_stats) - 2)) {
				strcat(d_stats, tmp);
			} else {
				break;
			}
		}
	}

	d_stats[sizeof(d_stats) - 1] = 0;

	if(d_stats[0]) {
		MW4log_generic(MW4P_DSTATS, va("PD\t%d\t%s", pid, d_stats));
	} else {
		MW4log_generic(MW4P_DSTATS, va("PD\t%d\tNONE", pid));
	}


	if(stat_summary) {
		MW4log_generic(MW4P_DSTATS, va("PSS\t%d\t%.2f:%.2f:%d:%d", pid, d_gvn, d_rcv, c_gvn, c_rcv));
	}

	ReplicatorID rid = app->lancemateConnectionData[p_id].lancemateMech->GetReplicatorID();

	MW4log_generic(MW4P_DSTATS, va("PS\t%d\t%d\t0\t0\t0", pid,
									mission->m_BucketManager->GetCustomBucketValue(rid)));

	return;
}
#endif // !defined(NO_LOG)
