Sv_maxclients latched

Is there a good reason why sv_maxclients is latched and it’s not allowed to be updated instantly?

/qcommon/cvar.c

Inside of Cvar_Set2, I changed

if (!force)

to

if (!force && strcmp(var_name,"sv_maxclients") != 0)

This prevents it from being latched. But, the problem is, when I issue the command it now immediately crashes the server.

Well, you found the reason. The server crashes when reading/writing past end of svs.clients (possibly snapshot entities array too, I don’t remember). Fun fact, UI’s trap_Cvar_SetValue( “sv_maxclients”, 64 ) bypasses latch and crashes the local server.

I’m not sure if there is a particular reason changing sv_maxclients is not supported though. Maybe to prevent excess memory allocating and freeing and having to handle dropping players in slots above sv_maxclients mid-game.

Thanks for the insight! I’ll cross this one off of my list.

I’m revisiting this topic because I’ve found that the Q3 servers at the CROM community: ctf.cromctf.com, ffa.cromctf.com and freeze.cromctf.com are able to update the maxclients cvar in realtime. If there’s 32 clients and the sv_maxclients is set to 33 and a new client connects, it will immediately change the sv_maxclients value to 34. So there has to be a solution to this question.

I wrote a function that runs every 10 seconds to increase/decrease the maxclients value. It’s not too useful without the ability to update it in realtime. In the SV_ChangeMaxClients() function, would there be anything that I can comment out / do differently to prevent the server from crashing when modifying it in realtime?

void adjustMaxClients(void){

	#define MAXCLIENTS 		48
	#define MINCLIENTS 			32
	#define CLIENTDIFF 			5
	#define CLIENTTHRESHOLD	3

	client_t *cl;
	int i, num = 0, newvalue;

	for (i=0,cl=svs.clients ; i < sv_maxclients->integer ; i++,cl++){
		if(!cl->state){ continue; }
		num++;
	}

	// Do nothing if there's the maximum number of clients connected
	if(sv_maxclients->integer == MAXCLIENTS) {
		return;
	}

	// Example clients connected: 46
	if( num == MAXCLIENTS || num + CLIENTDIFF >= MAXCLIENTS ) {
		newvalue = MAXCLIENTS;
	}

	// Example clients connected: 28
	else if ( num + CLIENTTHRESHOLD <= MINCLIENTS ) {
		newvalue = MINCLIENTS; 
	}

	// Example clients connected: 35
	else {
		newvalue = num + CLIENTDIFF;
	}

	if( sv_maxclients->integer != newvalue ) {
		Cvar_Set( "sv_maxclients", itoa(newvalue) );
	}

}

Replace all references of sv_maxclients->integer with (new variable) svs.maxclients. Once a frame check if sv_maxclients->modified and update svs.clients / svs.maxclients (SV_ChangeMaxClients) and set sv_maxclients->modified to qfalse.

You’ll also need to change svs.snapshotEntities from being allocated on the hunk to zone (because hunk_alloc can’t be freed). In SV_ChangeMaxClients you’ll need to reallocate svs.snapshotEntities and copy the old data into it.

Or you could just set sv_maxclients to the real max and screw with the value sent to players in infoResponse. Though, I honestly don’t understand the point.

Perfect. I think it would be easiest for me to do the latter! Thank you. I’ll post up my code when I’m done for others to reference on how to do it.

In the US 1.32c CTF community, in recent years some of larger servers have been hit with everything from amplification attacks to a nonstop barrage of fake clients connecting (\xff\xff\xff\xff connect ) with different IPs (socks servers). CROM was one of the communities that was hit the hardest by a rival server who I won’t name.

I’ve coded it so no more than 2 clients can connect from the same IP address. I’ve set-up an iptables rate limiting policy to try and combat amplification. Automatically adjusting the maximum amount of players every that can connect every N amount of seconds was just one extra additional layer.

Use Cvar_SetValue if you need to set a number rather than non-portable/non-standard itoa. Or Cvar_Set with va("%d", value)

I don’t think setting the value is enough really. The gamecode caches the value on startup as level.maxclients and doesn’t update it until a map_restart or new game VM load. Plus, you have to reallocate storage space in the clients buffer in the engine.