Bug found in weapons stats transmitted to clients


([RW]FRED) #1

Hi All, I don’t know if some players didn’t never remarks that the weapons statistics are sometime wrong, and also ur kills stats too so mangled. After looking what append in the code, I find it!

Well in the string sent to clients, a 32bits field is used to indicate which weapons have stats. But ET have more than 32 weapons. I have modified that and i provide u the new version of stats subroutine.
It can support 128 weapons… To find what is modified and to understand more, search in all files the term “weaponmask”

Regards.
<PRE>
cg_servercmds.c

// Cached stats
void CG_parseWeaponStatsGS_cmd(void)
{
clientInfo_t* ci;
gameStats_t* gs = &cgs.gamestats;
int i;
int iArg = 1;
int nClientID;
int nRounds;
unsigned int weaponMask[4];
int skillMask;
int xp = 0;

nClientID		= atoi(CG_Argv(iArg++));
nRounds			= atoi(CG_Argv(iArg++));
weaponMask[0]	= atoi(CG_Argv(iArg++));
weaponMask[1]	= atoi(CG_Argv(iArg++));
weaponMask[2]	= atoi(CG_Argv(iArg++));
weaponMask[3]	= atoi(CG_Argv(iArg++));

gs-&gt;cWeapons = 0;
gs-&gt;cSkills = 0;
gs-&gt;fHasStats = qfalse;

gs-&gt;nClientID = nClientID;
gs-&gt;nRounds = nRounds;

ci = &cgs.clientinfo[nClientID];

// Q_strncpyz(strName, ci->name, sizeof(strName));
// BG_cleanName(cgs.clientinfo[gs->nClientID].name, strName, sizeof(strName), qfalse);

if ((weaponMask[0] + weaponMask[1] + weaponMask[2] + weaponMask[3]))
{
	char strName[MAX_STRING_CHARS];

	for (i = WS_KNIFE; i &lt; WS_MAX; i++)
	{
		if (weaponMask[i/32] & (1 &lt;&lt; (i%32)))
		{
			int nHits		= atoi(CG_Argv(iArg++));
			int nShots		= atoi(CG_Argv(iArg++));
			int nKills		= atoi(CG_Argv(iArg++));
			int nDeaths		= atoi(CG_Argv(iArg++));
			int nHeadshots	= atoi(CG_Argv(iArg++));

			Q_strncpyz(strName, va("%-12s  ", aWeaponInfo[i].pszName), sizeof(strName));
			if(nShots &gt; 0 || nHits &gt; 0) {
				Q_strcat(strName, sizeof(strName), va("%5.1f %4d/%-4d ",
													((nShots == 0) ? 0.0 : (float)(nHits*100.0/(float)nShots)),
													nHits, nShots));
			} else {
				Q_strcat(strName, sizeof(strName), va("                "));
			}

			Q_strncpyz(gs-&gt;strWS[gs-&gt;cWeapons++],
					   va("%s%5d %6d%s", strName, nKills, nDeaths, ((aWeaponInfo[i].fHasHeadShots) ? va(" %9d", nHeadshots) : "")),
					   sizeof(gs-&gt;strWS[0]));

			if(nShots &gt; 0 || nHits &gt; 0 || nKills &gt; 0 || nDeaths) {
				gs-&gt;fHasStats = qtrue;
			}
		}
	}

	if(gs-&gt;fHasStats) {
		int dmg_given = atoi(CG_Argv(iArg++));
		int dmg_rcvd  = atoi(CG_Argv(iArg++));
		int team_dmg  = atoi(CG_Argv(iArg++));

		Q_strncpyz(gs-&gt;strExtra[0], va("Damage Given: %-6d  Team Damage: %d", dmg_given, team_dmg), sizeof(gs-&gt;strExtra[0]));
		Q_strncpyz(gs-&gt;strExtra[1], va("Damage Recvd: %d", dmg_rcvd), sizeof(gs-&gt;strExtra[0]));
	}
}

// Derive XP from individual skill XP
skillMask = atoi(CG_Argv(iArg++));
for(i=SK_BATTLE_SENSE; i&lt;SK_NUM_SKILLS; i++) {
	if(skillMask & (1 &lt;&lt; i)) {
		ci-&gt;skillpoints[i] = atoi(CG_Argv(iArg++));
		xp += ci-&gt;skillpoints[i];
	}
}

Q_strncpyz(gs-&gt;strRank, va("%-13s %d", ((ci-&gt;team == TEAM_AXIS) ? rankNames_Axis : rankNames_Allies)[ci-&gt;rank], xp), sizeof(gs-&gt;strRank));

if(skillMask != 0) {
	char *str;

	for(i=SK_BATTLE_SENSE; i&lt;SK_NUM_SKILLS; i++) {

		if((skillMask & (1 &lt;&lt; i)) == 0) {
			continue;
		}

		if(ci-&gt;skill[i] &lt; NUM_SKILL_LEVELS - 1) {
			str = va("%4d/%-4d", ci-&gt;skillpoints[i], skillLevels[ci-&gt;skill[i]+1]);
		} else {
			str = va("%d", ci-&gt;skillpoints[i]);
		}

		if(cgs.gametype == GT_WOLF_CAMPAIGN) {
			Q_strncpyz(gs-&gt;strSkillz[gs-&gt;cSkills++], va("%-15s %3d %s %12d", skillNames[i], ci-&gt;skill[i], str, ci-&gt;medals[i]), sizeof(gs-&gt;strSkillz[0]));
		} else {
			Q_strncpyz(gs-&gt;strSkillz[gs-&gt;cSkills++], va("%-15s %3d %s", skillNames[i], ci-&gt;skill[i], str),	sizeof(gs-&gt;strSkillz[0]));
		}
	}
}

}

// Client-side stat presentation
void CG_parseWeaponStats_cmd(void (txt_dump)(char ))
{
clientInfo_t
ci;
qboolean fFull = (txt_dump != CG_printWindow);
qboolean fHasStats = qfalse;
char strName[MAX_STRING_CHARS];
int atts;
int deaths;
int dmg_given;
int dmg_rcvd;
int hits;
int kills;
int team_dmg;
int headshots;
unsigned int i;
unsigned int iArg = 1;
unsigned int nClient;
unsigned int nRounds;
unsigned int dwSkillPointMask, xp = 0;
unsigned int dwWeaponMask[4];

nClient = atoi(CG_Argv(iArg++));
nRounds = atoi(CG_Argv(iArg++));
dwWeaponMask[0] = atoi(CG_Argv(iArg++));
dwWeaponMask[1] = atoi(CG_Argv(iArg++));
dwWeaponMask[2] = atoi(CG_Argv(iArg++));
dwWeaponMask[3] = atoi(CG_Argv(iArg++));

ci = &cgs.clientinfo[nClient];

Q_strncpyz(strName, ci-&gt;name, sizeof(strName));
BG_cleanName(cgs.clientinfo[nClient].name, strName, sizeof(strName), qfalse);
txt_dump(va("^7Overall stats for: ^3%s ^7(^2%d^7 Round%s)

“, strName, nRounds, ((nRounds != 1) ? “s” : “”)));
// txt_dump(va(”^7Overall stats for: ^3%s

", strName));

if(fFull) {
	txt_dump(     "Weapon     Acrcy Hits/Atts Kills Deaths Headshots

");
txt_dump( "-------------------------------------------------
");
} else {
txt_dump( "Weapon Acrcy Hits/Atts Kll Dth HS
");
//txt_dump( "-------------------------------------
");
txt_dump( "
");
}

if(dwWeaponMask[0] + dwWeaponMask[1] + dwWeaponMask[2] + dwWeaponMask[3] == 0) {
	txt_dump("^3No weapon info available.

");
} else {
for(i=WS_KNIFE; i<WS_MAX; i++) {
if(dwWeaponMask[i/32] & (1 << (i%32))) {
hits = atoi(CG_Argv(iArg++));
atts = atoi(CG_Argv(iArg++));
kills = atoi(CG_Argv(iArg++));
deaths = atoi(CG_Argv(iArg++));
headshots = atoi(CG_Argv(iArg++));

			Q_strncpyz(strName, va("^3%-9s: ", aWeaponInfo[i].pszName), sizeof(strName));
			if(atts &gt; 0 || hits &gt; 0) {
				fHasStats = qtrue;
				Q_strcat(strName, sizeof(strName), va("^7%5.1f ^5%4d/%-4d ",
													((atts == 0) ? 0.0 : (float)(hits*100.0/(float)atts)),
													hits, atts));
			} else {
				Q_strcat(strName, sizeof(strName), va("                "));
				if(kills &gt; 0 || deaths &gt; 0) fHasStats = qtrue;
			}

			if(fFull)
				txt_dump(va("%s^2%5d ^1%6d%s

“, strName, kills, deaths, ((aWeaponInfo[i].fHasHeadShots) ? va(” ^3%9d", headshots) : “”)));
else
txt_dump(va("%s^2%3d ^1%3d%s
“, strName, kills, deaths, ((aWeaponInfo[i].fHasHeadShots) ? va(” ^3%2d", headshots) : “”)));
}
}

	if(fHasStats) {
		dmg_given = atoi(CG_Argv(iArg++));
		dmg_rcvd  = atoi(CG_Argv(iArg++));
		team_dmg  = atoi(CG_Argv(iArg++));

		if(!fFull) {
			txt_dump("

");
}

		txt_dump(va("

^3Damage Given: ^7%-6d ^3Team Damage: ^7%d
", dmg_given, team_dmg));
txt_dump(va( "^3Damage Recvd: ^7%d
", dmg_rcvd));
}
}

if(!fFull) {
	txt_dump("

");
}

// Derive XP from individual skill XP
dwSkillPointMask = atoi(CG_Argv(iArg++));
for(i=SK_BATTLE_SENSE; i&lt;SK_NUM_SKILLS; i++) {
	if(dwSkillPointMask & (1 &lt;&lt; i)) {
		ci-&gt;skillpoints[i] = atoi(CG_Argv(iArg++));
		xp += ci-&gt;skillpoints[i];
	}
}

txt_dump(va("

^2Rank: ^7%s (%d XP)
", ((ci->team == TEAM_AXIS) ? rankNames_Axis : rankNames_Allies)[ci->rank], xp));

if(!fFull) {
	txt_dump("

");
}

// Medals only in campaign mode
txt_dump(    va("Skills         Level/Points%s

“, ((cgs.gametype == GT_WOLF_CAMPAIGN) ? " Medals” : “”)));
if(fFull) {
txt_dump(va("---------------------------%s
“, ((cgs.gametype == GT_WOLF_CAMPAIGN) ? “--------” : “”)));
} else {
txt_dump(”
");
}

if(dwSkillPointMask == 0) {
	txt_dump("^3No skills acquired!

");
} else {
char *str;

	for(i=SK_BATTLE_SENSE; i&lt;SK_NUM_SKILLS; i++) {

		if((dwSkillPointMask & (1 &lt;&lt; i)) == 0) {
			continue;
		}

		if(ci-&gt;skill[i] &lt; NUM_SKILL_LEVELS - 1) {
			str = va("%d (%d/%d)", ci-&gt;skill[i], ci-&gt;skillpoints[i], skillLevels[ci-&gt;skill[i]+1]);
		} else {
			str = va("%d (%d)", ci-&gt;skill[i], ci-&gt;skillpoints[i]);
		}

		if(cgs.gametype == GT_WOLF_CAMPAIGN) {
			txt_dump(va("%-14s ^3%-12s  ^2%6d

“, skillNames[i], str, ci->medals[i]));
} else {
txt_dump(va(”%-14s ^3%-12s
", skillNames[i], str));
}
}
}
}

g_match.c

// Generates weapon stat info for given ent
char *G_createStats(gentity_t *refEnt)
{
unsigned int i;
unsigned int dwSkillPointMask = 0;
char strWeapInfo[MAX_STRING_CHARS] = {0};
char strSkillInfo[MAX_STRING_CHARS] = {0};
unsigned int dwWeaponMask[4];

if(!refEnt)
	return(NULL);

memset(dwWeaponMask, 0, sizeof(dwWeaponMask));

// Add weapon stats as necessary
for(i=WS_KNIFE; i&lt;WS_MAX; i++) {
	if(refEnt-&gt;client-&gt;sess.aWeaponStats[i].atts || refEnt-&gt;client-&gt;sess.aWeaponStats[i].hits ||
	  refEnt-&gt;client-&gt;sess.aWeaponStats[i].deaths) {
		dwWeaponMask[i/32] |= (1 &lt;&lt; (i % 32));
		Q_strcat(strWeapInfo, sizeof(strWeapInfo), va(" %d %d %d %d %d",
				refEnt-&gt;client-&gt;sess.aWeaponStats[i].hits, refEnt-&gt;client-&gt;sess.aWeaponStats[i].atts,
				refEnt-&gt;client-&gt;sess.aWeaponStats[i].kills, refEnt-&gt;client-&gt;sess.aWeaponStats[i].deaths,
				refEnt-&gt;client-&gt;sess.aWeaponStats[i].headshots));
	}
}

// Additional info
Q_strcat(strWeapInfo, sizeof(strWeapInfo), va(" %d %d %d",
												refEnt-&gt;client-&gt;sess.damage_given,
												refEnt-&gt;client-&gt;sess.damage_received,
												refEnt-&gt;client-&gt;sess.team_damage));

// Add skillpoints as necessary
for(i=SK_BATTLE_SENSE; i&lt;SK_NUM_SKILLS; i++) {
	if(refEnt-&gt;client-&gt;sess.skillpoints[i] &gt; 0) {
		dwSkillPointMask |= (1 &lt;&lt; i);
		Q_strcat(strSkillInfo, sizeof(strSkillInfo), va(" %d", (int)refEnt-&gt;client-&gt;sess.skillpoints[i]));
	}
}

return va
	(
		"%d %d %d %d %d %d%s %d%s",
		refEnt - g_entities,
		refEnt-&gt;client-&gt;sess.rounds,
		dwWeaponMask[0],
		dwWeaponMask[1],
		dwWeaponMask[2],
		dwWeaponMask[3],
		strWeapInfo,
		dwSkillPointMask,
		strSkillInfo
	);

}

// Parses weapon stat info for given ent
// —> The given string must be space delimited and contain only integers
void G_parseStats(char pszStatsInfo)
{
gclient_t
cl;
const char* tmp = pszStatsInfo;
unsigned int i, dwWeaponMask[4], dwClientID = atoi(pszStatsInfo);

if (dwClientID &lt; 0 || dwClientID &gt; MAX_CLIENTS)
	return;

cl = &level.clients[dwClientID];

#define GETVAL(x) if((tmp = strchr(tmp, ’ ')) == NULL) return; x = atoi(++tmp);

GETVAL(cl-&gt;sess.rounds);
GETVAL(dwWeaponMask[0]);
GETVAL(dwWeaponMask[1]);
GETVAL(dwWeaponMask[2]);
GETVAL(dwWeaponMask[3]);

for (i = WS_KNIFE; i &lt; WS_MAX; i++)
{
	if (dwWeaponMask[i/32] & (1 &lt;&lt; (i%32)))
	{
		GETVAL(cl-&gt;sess.aWeaponStats[i].hits);
		GETVAL(cl-&gt;sess.aWeaponStats[i].atts);
		GETVAL(cl-&gt;sess.aWeaponStats[i].kills);
		GETVAL(cl-&gt;sess.aWeaponStats[i].deaths);
		GETVAL(cl-&gt;sess.aWeaponStats[i].headshots);
	}
}

GETVAL(cl-&gt;sess.damage_given);
GETVAL(cl-&gt;sess.damage_received);
GETVAL(cl-&gt;sess.team_damage);

}
</PRE>


(Salteh) #2

nice!
But could you please

code tag

it? :banana:
:smiley:


(Domipheus) #3

christ on a bike


(Domipheus) #4

actually, why dont u just offer it for dl instead os having it all here :slight_smile:


([RW]FRED) #5

Because no room available and the submit preview has rendered more nice than the final post :frowning:


(sniser) #6

Because no room available and the submit preview has rendered more nice than the final post :-([/quote]
You can still edit your post… put it in between code tags and remove the blank lines?


([RW]FRED) #7

Because no room available and the submit preview has rendered more nice than the final post :-([/quote]
You can still edit your post… put it in between code tags and remove the blank lines?[/quote]I’d tried but this stupid IE put 0d0a as carriage return when I past the content from an external editor with a single 0d as carriage return :bash:


(SCDS_reyalP) #8

I haven’t yet really read this, and don’t promise that I didn’t delete anything important, but here it is. Oh, and unified diffs > *.


cg_servercmds.c
// Cached stats
void CG_parseWeaponStatsGS_cmd(void)
{

	clientInfo_t*	ci;
	gameStats_t*	gs = &cgs.gamestats;
	int				i;
	int				iArg = 1;
	int				nClientID;
	int				nRounds;
	unsigned int	weaponMask[4];
	int				skillMask;
	int				xp = 0;


	nClientID		= atoi(CG_Argv(iArg++));
	nRounds			= atoi(CG_Argv(iArg++));
	weaponMask[0]	= atoi(CG_Argv(iArg++));
	weaponMask[1]	= atoi(CG_Argv(iArg++));
	weaponMask[2]	= atoi(CG_Argv(iArg++));
	weaponMask[3]	= atoi(CG_Argv(iArg++));

	gs->cWeapons = 0;
	gs->cSkills = 0;
	gs->fHasStats = qfalse;
	gs->nClientID = nClientID;
	gs->nRounds = nRounds;

	ci = &cgs.clientinfo[nClientID];

//	Q_strncpyz(strName, ci->name, sizeof(strName));
//	BG_cleanName(cgs.clientinfo[gs->nClientID].name, strName, sizeof(strName), qfalse);

	if ((weaponMask[0] + weaponMask[1] + weaponMask[2] + weaponMask[3]))
	{
		char strName[MAX_STRING_CHARS];

		for (i = WS_KNIFE; i < WS_MAX; i++)
		{

			if (weaponMask[i/32] & (1 << (i%32)))
			{
				int nHits		= atoi(CG_Argv(iArg++));
				int nShots		= atoi(CG_Argv(iArg++));
				int nKills		= atoi(CG_Argv(iArg++));
				int nDeaths		= atoi(CG_Argv(iArg++));
				int nHeadshots	= atoi(CG_Argv(iArg++));

				Q_strncpyz(strName, va("%-12s  ", aWeaponInfo[i].pszName), sizeof(strName));

				if(nShots > 0 || nHits > 0) {
					Q_strcat(strName, sizeof(strName), va("%5.1f %4d/%-4d ",
														((nShots == 0) ? 0.0 : (float)(nHits*100.0/(float)nShots)),
														nHits, nShots));
				} else {
					Q_strcat(strName, sizeof(strName), va("                "));
				}


				Q_strncpyz(gs->strWS[gs->cWeapons++],
						   va("%s%5d %6d%s", strName, nKills, nDeaths, ((aWeaponInfo[i].fHasHeadShots) ? va(" %9d", nHeadshots) : "")),
						   sizeof(gs->strWS[0]));

				if(nShots > 0 || nHits > 0 || nKills > 0 || nDeaths) {
					gs->fHasStats = qtrue;
				}
			}
		}

		if(gs->fHasStats) {
			int dmg_given = atoi(CG_Argv(iArg++));
			int dmg_rcvd  = atoi(CG_Argv(iArg++));
			int team_dmg  = atoi(CG_Argv(iArg++));

			Q_strncpyz(gs->strExtra[0], va("Damage Given: %-6d  Team Damage: %d", dmg_given, team_dmg), sizeof(gs->strExtra[0]));
			Q_strncpyz(gs->strExtra[1], va("Damage Recvd: %d", dmg_rcvd), sizeof(gs->strExtra[0]));
		}
	}

	// Derive XP from individual skill XP
	skillMask = atoi(CG_Argv(iArg++));
	for(i=SK_BATTLE_SENSE; i<SK_NUM_SKILLS; i++) {
		if(skillMask & (1 << i)) {
			ci->skillpoints[i] = atoi(CG_Argv(iArg++));
			xp += ci->skillpoints[i];
		}
	}

	Q_strncpyz(gs->strRank, va("%-13s %d", ((ci->team == TEAM_AXIS) ? rankNames_Axis : rankNames_Allies)[ci->rank], xp), sizeof(gs->strRank));

	if(skillMask != 0) {
		char *str;

		for(i=SK_BATTLE_SENSE; i<SK_NUM_SKILLS; i++) {
			if((skillMask & (1 << i)) == 0) {
				continue;
			}

			if(ci->skill[i] < NUM_SKILL_LEVELS - 1) {
				str = va("%4d/%-4d", ci->skillpoints[i], skillLevels[ci->skill[i]+1]);
			} else {
				str = va("%d", ci->skillpoints[i]);
			}

			if(cgs.gametype == GT_WOLF_CAMPAIGN) {
				Q_strncpyz(gs->strSkillz[gs->cSkills++], va("%-15s %3d %s %12d", skillNames[i], ci->skill[i], str, ci->medals[i]), sizeof(gs->strSkillz[0]));
			} else {
				Q_strncpyz(gs->strSkillz[gs->cSkills++], va("%-15s %3d %s", skillNames[i], ci->skill[i], str),	sizeof(gs->strSkillz[0]));
			}
		}
	}
}


// Client-side stat presentation
void CG_parseWeaponStats_cmd(void (txt_dump)(char *))
{
	clientInfo_t*	ci;
	qboolean		fFull = (txt_dump != CG_printWindow);
	qboolean		fHasStats = qfalse;
	char			strName[MAX_STRING_CHARS];
	int				atts;
	int				deaths;
	int				dmg_given;
	int				dmg_rcvd;
	int				hits;
	int				kills;
	int				team_dmg;
	int				headshots;
	unsigned int	i;
	unsigned int	iArg = 1;
	unsigned int	nClient;
	unsigned int	nRounds;
	unsigned int	dwSkillPointMask, xp = 0;
	unsigned int	dwWeaponMask[4];

	nClient = atoi(CG_Argv(iArg++));
	nRounds = atoi(CG_Argv(iArg++));
	dwWeaponMask[0] = atoi(CG_Argv(iArg++));
	dwWeaponMask[1] = atoi(CG_Argv(iArg++));
	dwWeaponMask[2] = atoi(CG_Argv(iArg++));
	dwWeaponMask[3] = atoi(CG_Argv(iArg++));

	ci = &cgs.clientinfo[nClient];

	Q_strncpyz(strName, ci->name, sizeof(strName));
	BG_cleanName(cgs.clientinfo[nClient].name, strName, sizeof(strName), qfalse);
	txt_dump(va("^7Overall stats for: ^3%s ^7(^2%d^7 Round%s)

", strName, nRounds, ((nRounds != 1) ? "s" : "")));
//	txt_dump(va("^7Overall stats for: ^3%s

", strName));

	if(fFull) {
		txt_dump(     "Weapon     Acrcy Hits/Atts Kills Deaths Headshots
");
		txt_dump(     "-------------------------------------------------
");
	} else {
		txt_dump(     "Weapon     Acrcy Hits/Atts Kll Dth HS
");
		//txt_dump(     "-------------------------------------
");
		txt_dump(     "
");
	}

	if(dwWeaponMask[0] + dwWeaponMask[1] + dwWeaponMask[2] + dwWeaponMask[3] == 0) {
		txt_dump("^3No weapon info available.
");
	} else {
		for(i=WS_KNIFE; i<WS_MAX; i++) {
			if(dwWeaponMask[i/32] & (1 << (i%32))) {
				hits = atoi(CG_Argv(iArg++));
				atts = atoi(CG_Argv(iArg++));
				kills = atoi(CG_Argv(iArg++));
				deaths = atoi(CG_Argv(iArg++));
				headshots = atoi(CG_Argv(iArg++));

				Q_strncpyz(strName, va("^3%-9s: ", aWeaponInfo[i].pszName), sizeof(strName));
				if(atts > 0 || hits > 0) {
					fHasStats = qtrue;
					Q_strcat(strName, sizeof(strName), va("^7%5.1f ^5%4d/%-4d ",
														((atts == 0) ? 0.0 : (float)(hits*100.0/(float)atts)),
														hits, atts));
				} else {
					Q_strcat(strName, sizeof(strName), va("                "));
					if(kills > 0 || deaths > 0) fHasStats = qtrue;
				}
				
				if(fFull)
					txt_dump(va("%s^2%5d ^1%6d%s
", strName, kills, deaths, ((aWeaponInfo[i].fHasHeadShots) ? va(" ^3%9d", headshots) : "")));
				else
					txt_dump(va("%s^2%3d ^1%3d%s
", strName, kills, deaths, ((aWeaponInfo[i].fHasHeadShots) ? va(" ^3%2d", headshots) : "")));
			}
		}

		if(fHasStats) {
			dmg_given = atoi(CG_Argv(iArg++));
			dmg_rcvd  = atoi(CG_Argv(iArg++));
			team_dmg  = atoi(CG_Argv(iArg++));

			if(!fFull) {
				txt_dump("

");
			}

			txt_dump(va("
^3Damage Given: ^7%-6d  ^3Team Damage: ^7%d
", dmg_given, team_dmg));
			txt_dump(va(  "^3Damage Recvd: ^7%d
", dmg_rcvd));
		}
	}

	if(!fFull) {
		txt_dump("


");
	}

	// Derive XP from individual skill XP
	dwSkillPointMask = atoi(CG_Argv(iArg++));
	for(i=SK_BATTLE_SENSE; i<SK_NUM_SKILLS; i++) {
		if(dwSkillPointMask & (1 << i)) {
			ci->skillpoints[i] = atoi(CG_Argv(iArg++));
			xp += ci->skillpoints[i];
		}
	}

	txt_dump(va("
^2Rank: ^7%s (%d XP)
", ((ci->team == TEAM_AXIS) ? rankNames_Axis : rankNames_Allies)[ci->rank], xp));

	if(!fFull) {
		txt_dump("


");
	}

	// Medals only in campaign mode
	txt_dump(    va("Skills         Level/Points%s
", ((cgs.gametype == GT_WOLF_CAMPAIGN) ? "  Medals" : "")));
	if(fFull) {
		txt_dump(va("---------------------------%s
", ((cgs.gametype == GT_WOLF_CAMPAIGN) ? "--------" : "")));
	} else {
		txt_dump("
");
	}

	if(dwSkillPointMask == 0) {
		txt_dump("^3No skills acquired!
");
	} else {
		char *str;
		for(i=SK_BATTLE_SENSE; i<SK_NUM_SKILLS; i++) {
			if((dwSkillPointMask & (1 << i)) == 0) {
				continue;
			}

			if(ci->skill[i] < NUM_SKILL_LEVELS - 1) {
				str = va("%d (%d/%d)", ci->skill[i], ci->skillpoints[i], skillLevels[ci->skill[i]+1]);
			} else {
				str = va("%d (%d)", ci->skill[i], ci->skillpoints[i]);
			}

			if(cgs.gametype == GT_WOLF_CAMPAIGN) {
				txt_dump(va("%-14s ^3%-12s  ^2%6d
", skillNames[i], str, ci->medals[i]));
			} else {
				txt_dump(va("%-14s ^3%-12s
", skillNames[i], str));
			}
		}
	}
}

g_match.c
// Generates weapon stat info for given ent
char *G_createStats(gentity_t *refEnt)
{
	unsigned int	i;
	unsigned int	dwSkillPointMask = 0;
	char			strWeapInfo[MAX_STRING_CHARS] = {0};
	char			strSkillInfo[MAX_STRING_CHARS] = {0};
	unsigned int	dwWeaponMask[4];

	if(!refEnt)
		return(NULL);

	memset(dwWeaponMask, 0, sizeof(dwWeaponMask));

	// Add weapon stats as necessary
	for(i=WS_KNIFE; i<WS_MAX; i++) {
		if(refEnt->client->sess.aWeaponStats[i].atts || refEnt->client->sess.aWeaponStats[i].hits ||
		  refEnt->client->sess.aWeaponStats[i].deaths) {
			dwWeaponMask[i/32] |= (1 << (i % 32));
			Q_strcat(strWeapInfo, sizeof(strWeapInfo), va(" %d %d %d %d %d",
					refEnt->client->sess.aWeaponStats[i].hits, refEnt->client->sess.aWeaponStats[i].atts,
					refEnt->client->sess.aWeaponStats[i].kills, refEnt->client->sess.aWeaponStats[i].deaths,
					refEnt->client->sess.aWeaponStats[i].headshots));
		}
	}

	// Additional info
	Q_strcat(strWeapInfo, sizeof(strWeapInfo), va(" %d %d %d",
													refEnt->client->sess.damage_given,
													refEnt->client->sess.damage_received,
													refEnt->client->sess.team_damage));

	// Add skillpoints as necessary
	for(i=SK_BATTLE_SENSE; i<SK_NUM_SKILLS; i++) {
		if(refEnt->client->sess.skillpoints[i] > 0) {
			dwSkillPointMask |= (1 << i);
			Q_strcat(strSkillInfo, sizeof(strSkillInfo), va(" %d", (int)refEnt->client->sess.skillpoints[i]));
		}
	}

	return va
		(
			"%d %d %d %d %d %d%s %d%s",
			refEnt - g_entities,
			refEnt->client->sess.rounds,
			dwWeaponMask[0],
			dwWeaponMask[1],
			dwWeaponMask[2],
			dwWeaponMask[3],
			strWeapInfo,
			dwSkillPointMask,
			strSkillInfo
		);
}

// Parses weapon stat info for given ent
//	---> The given string must be space delimited and contain only integers
void G_parseStats(char *pszStatsInfo)
{
	gclient_t*	cl;
	const char*	tmp = pszStatsInfo;
	unsigned int i, dwWeaponMask[4], dwClientID = atoi(pszStatsInfo);

	if (dwClientID < 0 || dwClientID > MAX_CLIENTS)
		return;

	cl = &level.clients[dwClientID];

#define GETVAL(x) if((tmp = strchr(tmp, ' ')) == NULL) return; x = atoi(++tmp);

	GETVAL(cl->sess.rounds);
	GETVAL(dwWeaponMask[0]);
	GETVAL(dwWeaponMask[1]);
	GETVAL(dwWeaponMask[2]);
	GETVAL(dwWeaponMask[3]);

	for (i = WS_KNIFE; i < WS_MAX; i++)
	{
		if (dwWeaponMask[i/32] & (1 << (i%32)))
		{
			GETVAL(cl->sess.aWeaponStats[i].hits);
			GETVAL(cl->sess.aWeaponStats[i].atts);
			GETVAL(cl->sess.aWeaponStats[i].kills);
			GETVAL(cl->sess.aWeaponStats[i].deaths);
			GETVAL(cl->sess.aWeaponStats[i].headshots);
		}
	}

	GETVAL(cl->sess.damage_given);
	GETVAL(cl->sess.damage_received);
	GETVAL(cl->sess.team_damage);
}


(Rain) #9

I’ve never seen the weapon stats get mangled, but there are only 22 weapons for which stats get tracked (they’re split off into WS_* and converted using aWeapMod.)
What sort of mangling have you seen?


(Salteh) #10

tank mg shots not being counted… or having 5 kills and 0 hits with grenades…
things like that


(Rain) #11

Tank MG shots/kills aren’t counted because of a different bug… (MOD_BROWNING and MOD_MG42 aren’t listed in aWeapMOD.)
The 5 kills/0 hits thing (well, 1 hit, anyway) may be because of the ‘Special hack for intentional gibbage’ bit in g_match.c (G_addStats.)


(Salteh) #12

0 hits :slight_smile:
5 kills :o

mangled :smiley:


(Rain) #13

If you (or someone else) has a demo showing the mangled stats (preferably the whole round, so I can see when they should’ve been added), I’m interested.
:moo:


(Salteh) #14

aaah
It’s not 5 kills… 0 hits… sorry! My long-term memory must’ve been corrupted :o
but 4 hits, 0 shots :frowning:
yeye, happened a long time ago…
No demo… but I found the screenshot…
:banana:


(Deprave) #15

Yea everyone knows the stats can be a bugfest at intermission

nice post


(Ancalagon) #16

I haven’t tested it, but doesn’t the source say that the maximum number of multiplayer weapons in the game is 64?


(Tavington) #17

I’ve noticed at the end of a round someone’s kill tally can be far higher than the actual number of kills they made. Does this code fix the problem?


(bacon) #18

That can happen in a variety of ways. The most common is when you lag while priming near the “boom” time.
It’s an advantage to those who want a higher accuracy :smiley: