Force team switch & keep stats

I have an auto balance function I wrote for my CTF server, which keeps the team sizes balanced. However, right now I’m switching the player’s team via:

Cbuf_ExecuteText( EXEC_NOW, va( “forceteam %d red\n”, i ) );

This causes the player’s score to be wiped. I’m looking for the player to keep his score, if he’s switching to the other team due to auto balancing.

Is it possible to overwrite the [PERS_TEAM] value with say TEAM_RED? I know this is possible somehow, as a few other active CTF servers keep the player’s score when he’s switched.

So, basically how I’ve got this working is copying the old persistent stats for the player over after I’ve switched their team. However, their position on the scoreboard is put at the bottom of their team (players who have a score of 0 will be placed above the switched player on the scoreboard). My question is would I have to calculate the client’s new position on the scoreboard and update it to their PERS_RANK?

For anyone in the future reading this and looking to see how I did it:

static void SV_SwitchTeam_f(void) {

char 			*newteam;
int             i, score, captures, hits, rank, spawn_count, killed, impressive, excellent, defend, assist, gauntlet;
client_t        *cl;
playerState_t	*ps;
static cvar_t 	*gametype;

cl = SV_GetPlayerByHandle();
i = cl - svs.clients;
ps = SV_GameClientNum(i);
gametype = Cvar_Get ("g_gametype", "0", CVAR_SERVERINFO);

if (!com_sv_running->integer) {
    Com_Printf("Server is not running\n");

if (Cmd_Argc() != 2 ) {
    Com_Printf("Usage: switchteam <id>\n");

if (!cl) {
	Com_Printf("Error: invalid client.>\n");

if( gametype->value != 3 && gametype->value != 4 ){
    Com_Printf("Error: gametype needs to be CTF or TDM.\n");

newteam 	= (ps->persistant[PERS_TEAM] == 1) ? "blue" : "red";

score 		= ps->persistant[PERS_SCORE];
captures 	= ps->persistant[PERS_CAPTURES];
hits 		= ps->persistant[PERS_HITS];
rank 		= ps->persistant[PERS_RANK];
spawn_count = ps->persistant[PERS_SPAWN_COUNT];
killed 		= ps->persistant[PERS_KILLED];

impressive 	= ps->persistant[PERS_IMPRESSIVE_COUNT];
excellent 	= ps->persistant[PERS_EXCELLENT_COUNT];
defend 		= ps->persistant[PERS_DEFEND_COUNT];
assist 		= ps->persistant[PERS_ASSIST_COUNT];
gauntlet 	= ps->persistant[PERS_GAUNTLET_FRAG_COUNT];

if( ps->persistant[PERS_TEAM] == 1 || ps->persistant[PERS_TEAM] == 2 ) {

	Cbuf_ExecuteText( EXEC_NOW, va( "forceteam %i %s\n", i, newteam ) );

	ps->persistant[PERS_SCORE] 					= score;				
	ps->persistant[PERS_CAPTURES] 				= captures;				// captures
	ps->persistant[PERS_HITS] 					= hits;					// total points damage inflicted so damage beeps can sound on change
	//ps->persistant[PERS_RANK] 				= rank;					// player rank or team rank
	ps->persistant[PERS_SPAWN_COUNT] 			= spawn_count;			// incremented every respawn
	ps->persistant[PERS_KILLED] 				= killed;				// count of the number of times you died

	// player awards tracking
	ps->persistant[PERS_IMPRESSIVE_COUNT] 		= impressive;			// two railgun hits in a row
	ps->persistant[PERS_EXCELLENT_COUNT] 		= excellent;			// two successive kills in a short amount of time
	ps->persistant[PERS_DEFEND_COUNT]			= defend;				// defend awards
	ps->persistant[PERS_ASSIST_COUNT] 			= assist;				// assist awards
	ps->persistant[PERS_GAUNTLET_FRAG_COUNT] 	= gauntlet; 			// kills with the guantlet



When you post code use the following so it’s formatted correctly. The ` are backticks on the same key as ~ in US QWERTY layout. cpp means C++ highlighting.

// the code

As an ugly hack I would recommend running forceteam and then intercepting

G_LogPrintf( "ClientBegin: %i\n", clientNum );

which is called after clearing the playerstate but before calling CalulateRanks(). So in sv_game.c G_PRINT check for “ClientBegin:” and override the score there if needed.

Note: This only works when running dedicated server, see G_LogPrintf.

I wasn’t sure how to format the code. I should have asked. I apologize for my messy posts. I will use it correctly in the future.

I’ll play around with the ClientBegin today. Thank you as always!