Пример #1
0
// Nico, delayed map change check function (thread)
void *G_delayed_map_change_watcher(void *arg) {
	int count    = 0;
	int limit    = 10; // Nico, in seconds
	int timeLeft = 0;

	// Nico, silent GCC
	(void)arg;

	while (!level.delayedMapChange.disabledWatcher) {
		if (level.time && level.delayedMapChange.timeChange) {
			// There is a delayed change

			if (level.time >= level.delayedMapChange.timeChange) {
				// Nico, do we have to wait for some threads to finish their work?
				while (activeThreadsCounter > 0 && count < limit) {
					threadingAllowed = qfalse;
					G_DPrintf("%s: waiting for %d thread%s before changing map\n", GAME_VERSION, activeThreadsCounter, activeThreadsCounter > 1 ? "s" : "");
					my_sleep(1000); // Nico, sleep for 1sec
					count++;
				}

				if (count >= limit) {
					G_Error("%s: warning, threads waiting timeout reached (threads: %d)", GAME_VERSION, activeThreadsCounter);
				}
				G_DPrintf("%s: changing map now!\n", GAME_VERSION);
				trap_SendConsoleCommand(EXEC_APPEND, va("map %s\n", level.delayedMapChange.passedVote));
				break;
			} else {
				timeLeft = (level.delayedMapChange.timeChange - level.time) / 1000;

				// Print incomming map change every 5 secs
				if (timeLeft % 5 == 0) {
					G_DPrintf("%s: map change to %s in %d sec%s\n", GAME_VERSION, level.delayedMapChange.passedVote, timeLeft, timeLeft > 1 ? "s" : "");
				}

				// Announce incomming map to players
				switch (timeLeft) {
				case 600:
					AP(va("cpm \"%s^w: map will be changed to ^Z%s ^win ^D10 ^wmins\n\"", GAME_VERSION_COLORED, level.delayedMapChange.passedVote));
					break;
				case 300:
					AP(va("cpm \"%s^w: map will be changed to ^Z%s ^win ^D5 ^wmins\n\"", GAME_VERSION_COLORED, level.delayedMapChange.passedVote));
					break;
				case 120:
					AP(va("cpm \"%s^w: map will be changed to ^Z%s ^win ^D2 ^wmins\n\"", GAME_VERSION_COLORED, level.delayedMapChange.passedVote));
					break;
				case 60:
					AP(va("cpm \"%s^w: map will be changed to ^Z%s ^win ^D1 ^wmin\n\"", GAME_VERSION_COLORED, level.delayedMapChange.passedVote));
					break;
				case 30:
					AP(va("cpm \"%s^w: map will be changed to ^Z%s ^win ^D30 ^wsecs\n\"", GAME_VERSION_COLORED, level.delayedMapChange.passedVote));
					break;
				}
			}
		}
		my_sleep(999);
	}
	return NULL;
}
Пример #2
0
/**
 * Record handler
 */
static void *recordHandler(void *data) {
	int            code;
	struct query_s *queryStruct = (struct query_s *)data;
	gentity_t      *ent         = queryStruct->ent;
	int            timerunNum;

	code = API_query(queryStruct->cmd, queryStruct->result, queryStruct->query, sizeof (queryStruct->query));

	timerunNum = ent->client->sess.currentTimerunNum;

	switch (code) {
	case 1001: // PB
		if (ent->client->sess.timerunCheckpointWereLoaded[timerunNum]) {
			memcpy(ent->client->sess.timerunBestCheckpointTimes[timerunNum], ent->client->sess.timerunCheckpointTimes, sizeof (ent->client->sess.timerunCheckpointTimes));
		}
		AP(va("print \"%s^w: %s\n\"", GAME_VERSION_COLORED, queryStruct->result));

		// Nico, keep this demo if autodemo is enabled
		if (ent->client->pers.autoDemo) {
			saveDemo(ent);
		}
		break;

	case 1002: // SB
	case 1003: // SB but player was already rec holder
	case 1004: // SB was tied
		if (ent->client->sess.timerunCheckpointWereLoaded[timerunNum]) {
			memcpy(ent->client->sess.timerunBestCheckpointTimes[timerunNum], ent->client->sess.timerunCheckpointTimes, sizeof (ent->client->sess.timerunCheckpointTimes));
		}
		AP(va("bp \"^w%s\n\"", queryStruct->result));

		// Nico, keep this demo if autodemo is enabled
		if (ent->client->pers.autoDemo) {
			saveDemo(ent);
		}
		break;

	case 1005: // Slow time
		CP(va("print \"%s^w: %s\n\"", GAME_VERSION_COLORED, queryStruct->result));

		// Nico, check player keepDemo setting to see if we keep this one or not
		if (ent->client->pers.autoDemo && ent->client->pers.keepAllDemos) {
			saveDemo(ent);
		}
		break;

	default: // Error
		CP(va("print \"%s^w: error: %s\n\"", GAME_VERSION_COLORED, queryStruct->result));
		break;
	}

	free(queryStruct->result);
	free(queryStruct);

	// Decrease global active thread counter
	activeThreadsCounter--;
	G_DPrintf("%s: decreasing threads counter to %d\n", GAME_VERSION, activeThreadsCounter);

	return NULL;
}
Пример #3
0
/*QUAKED trigger_multiple (.5 .5 .5) ? AXIS_ONLY ALLIED_ONLY NOBOT BOTONLY SOLDIERONLY LTONLY MEDICONLY ENGINEERONLY COVERTOPSONLY
"wait" : Seconds between triggerings, 0.5 default, -1 = one time only.
"random"	wait variance, default is 0
Variable sized repeatable trigger.  Must be targeted at one or more entities.
so, the basic time between firing is a random time between
(wait - random) and (wait + random)
*/
void SP_trigger_multiple(gentity_t *ent) {
	gentity_t *target = NULL;

	G_SpawnFloat("wait", "0.5", &ent->wait);

	ent->touch   = Touch_Multi;
	ent->use     = Use_Multi;
	ent->s.eType = ET_TRIGGER_MULTIPLE;

	InitTrigger(ent);

	// Nico, override wait -1 or wait 9999 on trigger_multiple where target is start timer
	// Note, this test is in case the start/stop timer or checkpoint entity was defined before the trigger multiple
	if (g_forceTimerReset.integer) {
		target = G_FindByTargetname(NULL, ent->target);
		if (target &&
			ent->wait != 0.5 &&
			(!Q_stricmp(target->classname, "target_startTimer")
		    || !Q_stricmp(target->classname, "target_stopTimer")
		    || !Q_stricmp(target->classname, "target_checkpoint"))) {
			G_DPrintf("%s: SP_trigger_multiple linked to %s, wait found = %f, overrided to 0.5\n", GAME_VERSION, target->classname, ent->wait);
			ent->wait = 0.5;
		}
	}

#ifdef VISIBLE_TRIGGERS
	ent->r.svFlags &= ~SVF_NOCLIENT;
#endif

	trap_LinkEntity(ent);
}
Пример #4
0
void SP_target_checkpoint(gentity_t *ent) {
	char *t         = NULL;
	int  timerunNum = 0;

	// Nico, used to look for parent
	gentity_t *parent = NULL;

	// Nico, override wait -1 or wait 9999 on timer check entities
	if (g_forceTimerReset.integer) {
		parent = G_FindByTarget(NULL, ent->targetname);
		if (parent && parent->wait != 0.5 && !Q_stricmp(parent->classname, "trigger_multiple")) {
			G_DPrintf("%s: SP_target_checkpoint, wait found = %f, overrided to 0.5\n", GAME_VERSION, parent->wait);
			G_SpawnFloat("wait", "0.5", &parent->wait);
		}
	}

	G_SpawnString("name", "default", &t);
	ent->timerunName = G_NewString(t);
	// create a timerun with this name if it doesn't exit yet
	timerunNum = GetTimerunNum(ent->timerunName);

	if (level.numCheckpoints[timerunNum] >= MAX_TIMERUN_CHECKPOINTS) {
		G_Error("Exceeded maximum number of 'target_checkpoint' entities in '%s' timerun (max %i)\n", ent->timerunName, MAX_TIMERUN_CHECKPOINTS);
		return;
	}

	ent->count = level.numCheckpoints[timerunNum];
	ent->use   = target_checkpoint_use;

	level.numCheckpoints[timerunNum]++;

	level.isTimerun = qtrue;
}
Пример #5
0
/*
==============
G_Script_ParseSpawnbot

  Parses "Spawnbot" command, precaching a custom character if specified
==============
*/
void G_Script_ParseSpawnbot(char **ppScript, char params[], int paramsize)
{
	qboolean parsingCharacter = qfalse;
	char     *token;

	token = COM_ParseExt(ppScript, qfalse);
	while (token[0])
	{
		// if we are currently parsing a spawnbot command, check the parms for
		// a custom character, which we will need to precache on the client

		// did we just see a '/character' parm?
		if (parsingCharacter)
		{

			parsingCharacter = qfalse;

			G_CharacterIndex(token);

			if (!BG_FindCharacter(token))
			{
				bg_character_t *character = BG_FindFreeCharacter(token);

				Q_strncpyz(character->characterFile, token, sizeof(character->characterFile));

				if (!G_RegisterCharacter(token, character))
				{
					G_Error("ERROR: G_Script_ParseSpawnbot: failed to load character file '%s'\n", token);
				}
			}

#ifdef DEBUG
			G_DPrintf("precached character %s\n", token);
#endif
		}
		else if (!Q_stricmp(token, "/character"))
		{
			parsingCharacter = qtrue;
		}

		if (strlen(params))          // add a space between each param
		{
			Q_strcat(params, paramsize, " ");
		}

		if (strrchr(token, ' '))      // need to wrap this param in quotes since it has more than one word
		{
			Q_strcat(params, paramsize, "\"");
		}

		Q_strcat(params, paramsize, token);

		if (strrchr(token, ' '))      // need to wrap this param in quotes since it has more than one word
		{
			Q_strcat(params, paramsize, "\"");
		}

		token = COM_ParseExt(ppScript, qfalse);
	}
}
Пример #6
0
/*
===============
G_LoadBots
===============
*/
static void G_LoadBots( void ) {
	vmCvar_t	botsFile;
	int			numdirs;
	char		filename[128];
	char		dirlist[1024];
	char*		dirptr;
	int			i;
	int			dirlen;

	if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) {
		return;
	}

	g_numBots = 0;

	trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM );
	if( *botsFile.string ) {
		G_LoadBotsFromFile(botsFile.string);
	}
	else {
		G_LoadBotsFromFile("scripts/bots.txt");
	}

	// get all bots from .bot files
	numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 );
	dirptr  = dirlist;
	for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
		dirlen = strlen(dirptr);
		strcpy(filename, "scripts/");
		strcat(filename, dirptr);
		G_LoadBotsFromFile(filename);
	}
	G_DPrintf("%i bots parsed\n", g_numBots);
}
Пример #7
0
/*
===============
G_LoadArenas
===============
*/
static void G_LoadArenas( void ) {
	int			numdirs;
	vmCvar_t	arenasFile;
	char		filename[128];
	char		dirlist[1024];
	char*		dirptr;
	int			i, n;
	int			dirlen;

	g_numArenas = 0;

	trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM );
	if( *arenasFile.string ) {
		G_LoadArenasFromFile(arenasFile.string);
	}
	else {
		G_LoadArenasFromFile("scripts/arenas.txt");
	}

	// get all arenas from .arena files
	numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 );
	dirptr  = dirlist;
	for (i = 0; i < numdirs; i++, dirptr += dirlen+1) {
		dirlen = strlen(dirptr);
		strcpy(filename, "scripts/");
		strcat(filename, dirptr);
		G_LoadArenasFromFile(filename);
	}
	G_DPrintf("%i arenas parsed\n", g_numArenas);
	
	for( n = 0; n < g_numArenas; n++ ) {
		Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) );
	}
}
Пример #8
0
void SP_target_stoptimer(gentity_t *ent) {
	char *t = NULL;

	// Nico, used to look for parent
	gentity_t *parent = NULL;

	// Nico, override wait -1 or wait 9999 on stop timer entities
	if (g_forceTimerReset.integer) {
		parent = G_FindByTarget(NULL, ent->targetname);
		if (parent && parent->wait != 0.5 && !Q_stricmp(parent->classname, "trigger_multiple")) {
			G_DPrintf("%s: SP_target_stoptimer, wait found = %f, overrided to 0.5\n", GAME_VERSION, parent->wait);
			G_SpawnFloat("wait", "0.5", &parent->wait);
		}
	}

	G_SpawnString("name", "default", &t);
	ent->timerunName = G_NewString(t);
	// create a timerun with this name if it doesn't exit yet
	GetTimerunNum(ent->timerunName);

	G_SpawnInt("mincheckpoints", "0", &ent->count);

	ent->use = target_stoptimer_use;

	level.isTimerun = qtrue;
}
Пример #9
0
/*
==============
alarmbox_updateparts
==============
*/
void alarmbox_updateparts(gentity_t *ent, qboolean matestoo) {
	gentity_t *t, *mate;
	qboolean  alarming = (ent->s.frame == 1);

	// update teammates
	if (matestoo) {
		for (mate = ent->teammaster; mate; mate = mate->teamchain) {
			if (mate == ent) {
				continue;
			}

			if (!(mate->active)) {     // don't update dead alarm boxes, they stay dead
				continue;
			}

			if (!(ent->active)) {     // destroyed, so just turn teammates off
				mate->s.frame = 0;
			} else {
				mate->s.frame = ent->s.frame;
			}

			alarmbox_updateparts(mate, qfalse);
		}
	}

	// update lights
	if (!ent->target) {
		return;
	}

	t = NULL;
	while ((t = G_FindByTargetname(t, ent->target)) != NULL) {
		if (t == ent) {
			G_DPrintf("WARNING: Entity used itself.\n");
		} else {
			// give the dlight the sound
			if (!Q_stricmp(t->classname, "dlight")) {
				t->soundLoop = ent->soundLoop;

				if (alarming) {
					if (!(t->r.linked)) {
						G_UseEntity(t, ent, 0);
					}
				} else {
					if (t->r.linked) {
						G_UseEntity(t, ent, 0);
					}
				}
			}
			// alarmbox can tell script_trigger about activation
			// (but don't trigger if dying, only activation)
			else if (!Q_stricmp(t->classname, "target_script_trigger") && ent->active) {   // not dead
				G_UseEntity(t, ent, 0);
			}
		}
	}
}
Пример #10
0
/*
===============
G_CallSpawn

Finds the spawn function for the entity and calls it,
returning qfalse if not found
===============
*/
qboolean G_CallSpawn(gentity_t *ent) {
	spawn_t *s;
	gitem_t *item;

	if (!ent->classname) {
		G_DPrintf("G_CallSpawn: NULL classname\n");
		return qfalse;
	}

	// check item spawn functions
	for (item = bg_itemlist + 1 ; item->classname ; ++item) {
		if (!strcmp(item->classname, ent->classname)) {
			// found it
			G_SpawnItem(ent, item);

			G_Script_ScriptParse(ent);
			G_Script_ScriptEvent(ent, "spawn", "");
			return qtrue;
		}
	}

	// check normal spawn functions
	for (s = spawns ; s->name ; ++s) {
		if (!strcmp(s->name, ent->classname)) {
			// found it
			s->spawn(ent);

			// RF, entity scripting
			if (ent->scriptName) {
				G_Script_ScriptParse(ent);
				G_Script_ScriptEvent(ent, "spawn", "");
			}

			return qtrue;
		}
	}
	G_DPrintf("%s doesn't have a spawn function\n", ent->classname);
	return qfalse;
}
Пример #11
0
void Team_ReturnFlagSound(gentity_t *ent, int team) {
	// play powerup spawn sound to all clients
	gentity_t *pm;

	if (ent == NULL) {
		G_DPrintf("Warning:  NULL passed to Team_ReturnFlagSound\n");
		return;
	}

	pm                = G_PopupMessage(PM_OBJECTIVE);
	pm->s.effect3Time = G_StringIndex(ent->message);
	pm->s.effect2Time = team;
	pm->s.density     = 1; // 1 = returned
}
Пример #12
0
/**
 * Get checkpoints handler
 */
static void *checkpointsHandler(void *data) {
	int            code;
	struct query_s *queryStruct = (struct query_s *)data;
	gentity_t      *ent         = queryStruct->ent;

	code = API_query(queryStruct->cmd, queryStruct->result, queryStruct->query, sizeof (queryStruct->query));

	if (code >= 1000) {
		int timerunNum = code - 1000;
		if (queryStruct->result && checkAPIResult(queryStruct->result) && timerunNum >= 0 && timerunNum < MAX_TIMERUNS) {
			char *pch;
			int  i = 0;

			// No error, no timeout

			// Reset client checkpoints
			memset(ent->client->sess.timerunBestCheckpointTimes[timerunNum], 0, sizeof (ent->client->sess.timerunBestCheckpointTimes[timerunNum]));
			ent->client->sess.timerunCheckpointsPassed = 0;

			// Set new checkpoints
			pch = strtok(queryStruct->result, "O");
			while (pch != NULL && i < MAX_TIMERUN_CHECKPOINTS) {
				ent->client->sess.timerunBestCheckpointTimes[timerunNum][i] = atoi(pch);
				pch                                                         = strtok(NULL, "O");
				i++;
			}

			// Mark CP were loaded for this run
			ent->client->sess.timerunCheckpointWereLoaded[timerunNum] = 1;

			CP(va("print \"%s^w: checkpoints loaded for run #%d!\n\"", GAME_VERSION_COLORED, timerunNum));
		} else {
			CP(va("print \"%s^w: error while loading checkpoints\n\"", GAME_VERSION_COLORED));
		}
	} else if (code == 0) {
		clientBigDataPrint(ent, queryStruct->result);
	} else {
		CP(va("print \"%s^w: error while loading checkpoints\n\"", GAME_VERSION_COLORED));
	}

	free(queryStruct->result);
	free(queryStruct);

	// Decrease global active thread counter
	activeThreadsCounter--;
	G_DPrintf("%s: decreasing threads counter to %d\n", GAME_VERSION, activeThreadsCounter);

	return NULL;
}
Пример #13
0
void G_unloadAPI() {
	if (api_module == NULL) {
		G_DPrintf("%s: API module is not loaded (G_unloadAPI).\n", GAME_VERSION);
	} else {

		API_clean();

#if defined _WIN32
		FreeLibrary(api_module);
#else
		dlclose(api_module);
#endif

		G_Printf("%s: API module unloaded!\n", GAME_VERSION);
	}
}
Пример #14
0
/*
===============
SaveRegisteredItems

Write the needed items to a config string
so the client will know which ones to precache
===============
*/
void SaveRegisteredItems( void ) {
	char	string[MAX_ITEMS+1];
	int		i;
	int		count;

	count = 0;
	for ( i = 0 ; i < BG_NumItems() ; i++ ) {
		if ( itemRegistered[i] ) {
			count++;
			string[i] = '1';
		} else {
			string[i] = '0';
		}
	}
	string[ BG_NumItems() ] = 0;

	G_DPrintf( "%i items registered\n", count );
	trap_SetConfigstring(CS_ITEMS, string);
}
Пример #15
0
/**
 * Random map handler
 */
static void *randommapHandler(void *data) {
	int            code;
	struct query_s *queryStruct = (struct query_s *)data;
	gentity_t      *ent         = queryStruct->ent;       // Nico, note: this is NULL is randomMap was asked by server (timelimit)
	fileHandle_t   f;

	code = API_query(queryStruct->cmd, queryStruct->result, queryStruct->query, sizeof (queryStruct->query));

	if (code == 0 && queryStruct->result && checkAPIResult(queryStruct->result)) {
		char mapfile[MAX_QPATH] = { 0 };

		Com_sprintf(mapfile, sizeof (mapfile), "maps/%s.bsp", queryStruct->result);

		trap_FS_FOpenFile(mapfile, &f, FS_READ);

		trap_FS_FCloseFile(f);

		if (!f) {
			AP(va("print \"^1Error:^7 the map ^3%s^7 is not on the server.\n\"", queryStruct->result));
		} else {
			// Check from where the map change come
			// randommap vote or timelimit?
			if (g_timelimit.integer > 0) {
				G_delay_map_change(queryStruct->result, g_timelimit.integer);
			} else {
				// Nico, delay the map change
				G_delay_map_change(queryStruct->result, 0);
			}
		}
	} else {
		CP(va("print \"%s^w: error while getting a random map!\n\"", GAME_VERSION_COLORED));
	}

	free(queryStruct->result);
	free(queryStruct);

	// Decrease global active thread counter
	activeThreadsCounter--;
	G_DPrintf("%s: decreasing threads counter to %d\n", GAME_VERSION, activeThreadsCounter);

	return NULL;
}
Пример #16
0
void InitTrigger(gentity_t *self)
{
	if (!VectorCompare(self->s.angles, vec3_origin))
	{
		G_SetMovedir(self->s.angles, self->movedir);
	}

	if (self->model)
	{
		trap_SetBrushModel(self, self->model);
	}
	else
	{
		// empty models for ETPro mapscripting
		G_DPrintf("^6InitTrigger: trap_SetBrushModel(NULL) skipped for scriptName %s\n", self->scriptName);
	}

	self->r.contents = CONTENTS_TRIGGER;        // replaces the -1 from trap_SetBrushModel
	self->r.svFlags  = SVF_NOCLIENT;
}
Пример #17
0
/**
 * Check API handler
 */
static void *checkAPIHandler(void *data) {
	int            code;
	struct query_s *queryStruct = (struct query_s *)data;

	code = API_query(queryStruct->cmd, queryStruct->result, queryStruct->query, sizeof (queryStruct->query));

	if (code == 0) {
		G_Printf("%s: %s\n", GAME_VERSION, queryStruct->result);
	} else {
		G_Printf("%s: failed to check API (code: %d, result: %s)\n", GAME_VERSION, code, queryStruct->result);
	}

	free(queryStruct->result);
	free(queryStruct);

	// Decrease global active thread counter
	activeThreadsCounter--;
	G_DPrintf("%s: decreasing threads counter to %d\n", GAME_VERSION, activeThreadsCounter);

	return NULL;
}
Пример #18
0
/**
 * Map rank command handler
 */
static void *mapRankHandler(void *data) {
	int            code;
	struct query_s *queryStruct = (struct query_s *)data;
	gentity_t      *ent         = queryStruct->ent;

	code = API_query(queryStruct->cmd, queryStruct->result, queryStruct->query, sizeof (queryStruct->query));

	if (code == 0) {
		clientBigDataPrint(ent, queryStruct->result);
	} else {
		CP(va("print \"%s^w: error while requesting rank\n\"", GAME_VERSION_COLORED));
	}

	free(queryStruct->result);
	free(queryStruct);

	// Decrease global active thread counter
	activeThreadsCounter--;
	G_DPrintf("%s: decreasing threads counter to %d\n", GAME_VERSION, activeThreadsCounter);

	return NULL;
}
Пример #19
0
/**
 * Login handler
 */
static void *loginHandler(void *data) {
	int            code;
	int            len          = 0;
	struct query_s *queryStruct = (struct query_s *)data;
	gentity_t      *ent         = queryStruct->ent;

	code = API_query(queryStruct->cmd, queryStruct->result, queryStruct->query, sizeof (queryStruct->query));

	len = strlen(queryStruct->result);

	if (code == 0) {
		if (len > 0 && ent && ent->client) {
			ent->client->sess.logged = qtrue;
			CP(va("print \"%s^w: you are now logged in!\n\"", GAME_VERSION_COLORED));
			ClientUserinfoChanged(ent->client->ps.clientNum);

			// Notify client that he is now logged in
			trap_SendServerCommand(ent - g_entities, "client_login");

			// Start recording a new temp demo.
			trap_SendServerCommand(ent - g_entities, "tempDemoStart");
		} else {
			CP(va("print \"%s^w: login failed!\n\"", GAME_VERSION_COLORED));
		}
	} else {
		CP(va("print \"%s^w: login failed!\n\"", GAME_VERSION_COLORED));
	}

	free(queryStruct->result);
	free(queryStruct);

	// Decrease global active thread counter
	activeThreadsCounter--;
	G_DPrintf("%s: decreasing threads counter to %d\n", GAME_VERSION, activeThreadsCounter);

	return NULL;
}
Пример #20
0
/*
==============
ClientEndFrame

Called at the end of each server frame for each connected client
A fast client will have multiple ClientThink for each ClientEndFrame,
while a slow client may have multiple ClientEndFrame between ClientThink.
==============
*/
void ClientEndFrame( gentity_t *ent ) {
	int i;

	if ( ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) || ( ent->client->ps.pm_flags & PMF_LIMBO ) ) { // JPW NERVE
		SpectatorClientEndFrame( ent );
		return;
	}

	if ( !ent->aiCharacter ) {
		// turn off any expired powerups
		for ( i = 0 ; i < MAX_POWERUPS ; i++ ) {

			if ( i == PW_FIRE ||             // these aren't dependant on level.time
				 i == PW_ELECTRIC ||
				 i == PW_BREATHER ||
				 i == PW_NOFATIGUE ) {

				continue;
			}

			if ( ent->client->ps.powerups[ i ] < level.time ) {
				ent->client->ps.powerups[ i ] = 0;
			}
		}
	}

	// save network bandwidth
#if 0
	if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) {
		// FIXME: this must change eventually for non-sync demo recording
		VectorClear( ent->client->ps.viewangles );
	}
#endif

	//
	// If the end of unit layout is displayed, don't give
	// the player any normal movement attributes
	//
	if ( level.intermissiontime ) {
		return;
	}

	// burn from lava, etc
	P_WorldEffects( ent );

	// apply all the damage taken this frame
	P_DamageFeedback( ent );

	// add the EF_CONNECTION flag if we haven't gotten commands recently
	if ( level.time - ent->client->lastCmdTime > 1000 ) {
		ent->s.eFlags |= EF_CONNECTION;
	} else {
		ent->s.eFlags &= ~EF_CONNECTION;
	}

	ent->client->ps.stats[STAT_HEALTH] = ent->health;   // FIXME: get rid of ent->health...

	G_SetClientSound( ent );

	// set the latest infor

	// Ridah, fixes jittery zombie movement
	if ( g_smoothClients.integer ) {
		BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, ( ( ent->r.svFlags & SVF_CASTAI ) == 0 ) );
	} else {
		BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, ( ( ent->r.svFlags & SVF_CASTAI ) == 0 ) );
	}

	//SendPendingPredictableEvents( &ent->client->ps );

	// DHM - Nerve :: If it's been a couple frames since being revived, and props_frame_state
	//					wasn't reset, go ahead and reset it
	if ( ent->props_frame_state >= 0 && ( ( level.time - ent->s.effect3Time ) > 100 ) ) {
		ent->props_frame_state = -1;
	}

	if ( ent->health > 0 && StuckInClient( ent ) ) {
		G_DPrintf( "%s is stuck in a client.\n", ent->client->pers.netname );
		ent->r.contents = CONTENTS_CORPSE;
	}

	if ( g_gametype.integer >= GT_WOLF && ent->health > 0 && ent->r.contents == CONTENTS_CORPSE ) {
		WolfReviveBbox( ent );
	}

	// DHM - Nerve :: Reset 'count2' for flamethrower
	if ( !( ent->client->buttons & BUTTON_ATTACK ) ) {
		ent->count2 = 0;
	}
	// dhm
}
Пример #21
0
/*
================
G_SetEntState

  sets the entstate of an entity.
================
*/
void G_SetEntState( gentity_t *ent, entState_t state ) {
	if ( ent->entstate == state ) {
		G_DPrintf( "entity %i already in desired state [%i]\n", ent->s.number, state );
		return;
	}

	switch ( state ) {
	case STATE_DEFAULT:             if ( ent->entstate == STATE_UNDERCONSTRUCTION ) {
			ent->clipmask = ent->realClipmask;
			ent->r.contents = ent->realContents;
			if ( !ent->realNonSolidBModel ) {
				ent->s.eFlags &= ~EF_NONSOLID_BMODEL;
			}
	}

		ent->entstate = STATE_DEFAULT;
		ent->s.powerups = STATE_DEFAULT;

		if ( ent->s.eType == ET_WOLF_OBJECTIVE ) {
			char cs[MAX_STRING_CHARS];
			trap_GetConfigstring( ent->count, cs, sizeof( cs ) );
			ent->count2 &= ~256;
			Info_SetValueForKey( cs, "t", va( "%i", ent->count2 ) );
			trap_SetConfigstring( ent->count, cs );
		}

		if ( ent->s.eType != ET_COMMANDMAP_MARKER ) {
			trap_LinkEntity( ent );
		}

		// deal with any entities in the solid
		{
			int listedEntities, e;
			int entityList[MAX_GENTITIES];
			gentity_t *check, *block;

			listedEntities = trap_EntitiesInBox( ent->r.absmin, ent->r.absmax, entityList, MAX_GENTITIES );

			for ( e = 0; e < listedEntities; e++ ) {
				check = &g_entities[entityList[e]];

				// ignore everything but items, players and missiles (grenades too)
				if ( check->s.eType != ET_MISSILE && check->s.eType != ET_ITEM && check->s.eType != ET_PLAYER && !check->physicsObject ) {
					continue;
				}

				if ( ( block = G_TestEntityPosition( check ) ) == NULL ) {
					continue;
				}

				if ( block != ent ) {
					// the entity is blocked by another entity - that block this should take care of this itself
					continue;
				}

				if ( check->client || check->s.eType == ET_CORPSE ) {
					// gibs anything player like
					G_Damage( check, ent, ent, NULL, NULL, 9999, DAMAGE_NO_PROTECTION, MOD_CRUSH_CONSTRUCTIONDEATH_NOATTACKER );
				} else if ( check->s.eType == ET_ITEM && check->item->giType == IT_TEAM ) {
					// see if it's a critical entity, one that we can't just simply kill (basically flags)
					Team_DroppedFlagThink( check );
				} else {
					// remove the landmine from both teamlists
					if ( check->s.eType == ET_MISSILE && check->methodOfDeath == MOD_LANDMINE ) {
						mapEntityData_t *mEnt;

						if ( ( mEnt = G_FindMapEntityData( &mapEntityData[0], check - g_entities ) ) != NULL ) {
							G_FreeMapEntityData( &mapEntityData[0], mEnt );
						}

						if ( ( mEnt = G_FindMapEntityData( &mapEntityData[1], check - g_entities ) ) != NULL ) {
							G_FreeMapEntityData( &mapEntityData[1], mEnt );
						}
					}

					// just get rid of it
					G_TempEntity( check->s.origin, EV_ITEM_POP );
					G_FreeEntity( check );
				}
			}
		}

		// if this is an mg42, then we should try and calculate mg42 spots again
		BotCalculateMg42Spots();

		break;
	case STATE_UNDERCONSTRUCTION:   ent->entstate = STATE_UNDERCONSTRUCTION;
		ent->s.powerups = STATE_UNDERCONSTRUCTION;
		ent->realClipmask = ent->clipmask;
		if ( ent->s.eType != ET_CONSTRUCTIBLE ) {                           // don't make nonsolid as we want to make them partially solid for staged construction
			ent->clipmask = 0;
		}
		ent->realContents = ent->r.contents;
		if ( ent->s.eType != ET_CONSTRUCTIBLE ) {
			ent->r.contents = 0;
		}
		if ( ent->s.eFlags & EF_NONSOLID_BMODEL ) {
			ent->realNonSolidBModel = qtrue;
		} else
		if ( ent->s.eType != ET_CONSTRUCTIBLE ) {
			ent->s.eFlags |= EF_NONSOLID_BMODEL;
		}

		if ( !Q_stricmp( ent->classname, "misc_mg42" ) ) {
			// stop using the mg42
			mg42_stopusing( ent );
		}

		if ( ent->s.eType == ET_COMMANDMAP_MARKER ) {
			mapEntityData_t *mEnt;

			if ( ( mEnt = G_FindMapEntityData( &mapEntityData[0], ent - g_entities ) ) != NULL ) {
				G_FreeMapEntityData( &mapEntityData[0], mEnt );
			}
			if ( ( mEnt = G_FindMapEntityData( &mapEntityData[1], ent - g_entities ) ) != NULL ) {
				G_FreeMapEntityData( &mapEntityData[1], mEnt );
			}
		}

		trap_LinkEntity( ent );
		break;
	case STATE_INVISIBLE:           if ( ent->entstate == STATE_UNDERCONSTRUCTION ) {
			ent->clipmask = ent->realClipmask;
			ent->r.contents = ent->realContents;
			if ( !ent->realNonSolidBModel ) {
				ent->s.eFlags &= ~EF_NONSOLID_BMODEL;
			}
	}

		ent->entstate = STATE_INVISIBLE;
		ent->s.powerups = STATE_INVISIBLE;

		if ( !Q_stricmp( ent->classname, "misc_mg42" ) ) {
			mg42_stopusing( ent );
		} else if ( ent->s.eType == ET_WOLF_OBJECTIVE ) {
			char cs[MAX_STRING_CHARS];
			trap_GetConfigstring( ent->count, cs, sizeof( cs ) );
			ent->count2 |= 256;
			Info_SetValueForKey( cs, "t", va( "%i", ent->count2 ) );
			trap_SetConfigstring( ent->count, cs );
		}

		if ( ent->s.eType == ET_COMMANDMAP_MARKER ) {
			mapEntityData_t *mEnt;

			if ( ( mEnt = G_FindMapEntityData( &mapEntityData[0], ent - g_entities ) ) != NULL ) {
				G_FreeMapEntityData( &mapEntityData[0], mEnt );
			}
			if ( ( mEnt = G_FindMapEntityData( &mapEntityData[1], ent - g_entities ) ) != NULL ) {
				G_FreeMapEntityData( &mapEntityData[1], mEnt );
			}
		}

		trap_UnlinkEntity( ent );
		break;
	}
}
Пример #22
0
/**
 * Get config handler
 */
static void *getConfigHandler(void *data) {
	int            code;
	struct query_s *queryStruct = (struct query_s *)data;
	int            config_strictSaveLoad;
	int            config_physics;
	int            config_disableDrowning;
	int            config_holdDoorsOpen;
	int            config_enableMapEntities;
	int            config_script_size;

	code = API_query(queryStruct->cmd, queryStruct->result, queryStruct->query, sizeof (queryStruct->query));

	if (code != 0) {
		G_Error("%s: error #1 while getting config from API!\n", GAME_VERSION);
	}

	code = sscanf(queryStruct->result, "%10d %10d %10d %10d %10d %10d %*s", // Nico, last field is ignored for the moment
	              &config_strictSaveLoad,
	              &config_physics,
	              &config_disableDrowning,
	              &config_holdDoorsOpen,
	              &config_enableMapEntities,
	              &config_script_size);

	if (code != 6) {
		G_Error("%s: error #2 while getting config from API!\n", GAME_VERSION);
	}

	if (config_script_size != 0) {
		int len;

		level.useAPImapscript = qtrue;

		level.scriptEntity = G_Alloc(config_script_size + 1);
		code               = sscanf(queryStruct->result, "%*d %*d %*d %*d %*d %*d %512000c", level.scriptEntity);

		if (code != 1) {
			G_Error("%s: error #3 while getting config from API!\n", GAME_VERSION);
		}
		*(level.scriptEntity + config_script_size) = '\0';

		// Nico, check script len
		len = strlen(level.scriptEntity);
		if (len != config_script_size) {
			G_Error("%s: error #4 while getting config from API (%d != %d)!\n", GAME_VERSION, config_script_size, len);
		}

		// Nico, do the same as G_Script_ScriptLoad do when loading from a local file
		trap_Cvar_Set("g_scriptName", "");
		G_Script_EventStringInit();
	}

	// Nico, check cvars from API
	if (config_strictSaveLoad != g_strictSaveLoad.integer) {
		G_Printf("%s: updating g_strictSaveLoad from %d to %d\n", GAME_VERSION, g_strictSaveLoad.integer, config_strictSaveLoad);
		trap_Cvar_Set("g_strictSaveLoad", va("%d", config_strictSaveLoad));
	}
	if (config_physics != physics.integer) {
		G_DPrintf("%s: updating physics from %d to %d\n", GAME_VERSION, physics.integer, config_physics);
		trap_Cvar_Set("physics", va("%d", config_physics));
	}
	if (config_disableDrowning != g_disableDrowning.integer) {
		G_DPrintf("%s: updating g_disableDrowning from %d to %d\n", GAME_VERSION, g_disableDrowning.integer, config_disableDrowning);
		// Nico, update the cvar
		g_disableDrowning.integer = config_disableDrowning;
		g_disableDrowning.modificationCount++;
		trap_Cvar_Set("g_disableDrowning", va("%d", config_disableDrowning));
	}
	if (config_holdDoorsOpen != g_holdDoorsOpen.integer) {
		G_DPrintf("%s: updating g_holdDoorsOpen from %d to %d\n", GAME_VERSION, g_holdDoorsOpen.integer, config_holdDoorsOpen);
		// Nico, update this way to make sure the cvar value is updated earlier enough when map elements get spawned
		g_holdDoorsOpen.integer = config_holdDoorsOpen;
		g_holdDoorsOpen.modificationCount++;
		trap_Cvar_Set("g_holdDoorsOpen", va("%d", config_holdDoorsOpen));
	}

	if (config_enableMapEntities != g_enableMapEntities.integer) {
		G_Printf("%s: updating g_enableMapEntities from %d to %d\n", GAME_VERSION, g_enableMapEntities.integer, config_enableMapEntities);
		// Nico, update this way to make sure the cvar value is updated earlier enough when map elements get spawned
		g_enableMapEntities.integer = config_enableMapEntities;
		g_enableMapEntities.modificationCount++;
		trap_Cvar_Set("g_enableMapEntities", va("%d", config_enableMapEntities));
	}

	free(queryStruct->result);
	free(queryStruct);

	return NULL;
}
Пример #23
0
/**
 * Asynchronous (using pthreads) API call function
 *
 * command: must be a command in apiCommands
 * result: pointer to an *already allocated* buffer for storing result
 * ent: entity who made the request
 * count: number of variadic arguments
 */
qboolean G_AsyncAPICall(char *command, char *result, gentity_t *ent, int count, ...) {
	struct query_s *queryStruct;
	pthread_t      thread;
	pthread_attr_t attr;
	int            returnCode = 0;
	void           *(*handler)(void *) = NULL;
	va_list        ap;
	int            i = 0;

	if (api_module == NULL || API_query == NULL) {
		LDE("%s\n", "API module is not loaded");
		return qfalse;
	}

	// Check if thread limit is reached
	if (activeThreadsCounter >= THREADS_MAX) {
		LDE("threads limit (%d) reached: %d\n", THREADS_MAX, activeThreadsCounter);
		return qfalse;
	}

	// Check if we are allowed to start a new thread
	if (!threadingAllowed) {
		LDE("%s\n", "starting new threads is forbidden");
		return qfalse;
	}

	// Check number of arguments in ... (=count)
	if (count <= 0) {
		LDE("invalid argument count %d\n", count);
		return qfalse;
	}

	queryStruct = malloc(sizeof (struct query_s));

	if (queryStruct == NULL) {
		LDE("%s\n", "failed to allocate memory");

		return qfalse;
	}

	va_start(ap, count);

	// Init query buffer
	memset(queryStruct->query, 0, QUERY_MAX_SIZE);

	for (i = 0; i < count; ++i) {
		char *arg;

		arg = va_arg(ap, char *);

		if (!arg) {
			LDE("empty argument %d with command '%s'\n", i, command);
			free(queryStruct);
			va_end(ap);
			return qfalse;
		}

		Q_strcat(queryStruct->query, QUERY_MAX_SIZE, arg);

		// No trailing /
		if (i + 1 < count) {
			Q_strcat(queryStruct->query, QUERY_MAX_SIZE, CHAR_SEPARATOR);
		}
	}

	va_end(ap);

	Q_strncpyz(queryStruct->cmd, command, sizeof (queryStruct->cmd));
	queryStruct->result = result;
	queryStruct->ent    = ent;

	// Get the command handler
	handler = getHandlerForCommand(command);

	if (!handler) {
		LDE("no handler for command '%s'\n", command);
		free(queryStruct);
		return qfalse;
	}

	LDI("asynchronous API call with command: '%s', query '%s'\n", command, queryStruct->query);

	// Create threads as detached as they will never be joined
	if (pthread_attr_init(&attr)) {
		LDE("%s\n", "error in pthread_attr_init");
		free(queryStruct);
		return qfalse;
	}
	if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
		LDE("%s\n", "error in pthread_attr_setdetachstate");
		free(queryStruct);
		return qfalse;
	}

	returnCode = pthread_create(&thread, &attr, handler, (void *)queryStruct);

	if (returnCode) {
		LDE("error in pthread_create, return value is %d\n", returnCode);
		free(queryStruct);
		return qfalse;
	}

	// Nico, increase active threads counter
	activeThreadsCounter++;
	G_DPrintf("%s: increasing threads counter to %d\n", GAME_VERSION, activeThreadsCounter);

	if (pthread_attr_destroy(&attr)) {
		LDE("%s\n", "error in pthread_attr_destroy");
		// Nico, note: I don't free querystruct because it's used in the thread
		return qfalse;
	}

	return qtrue;
}
Пример #24
0
/*
===========
ClientUserInfoChanged

Called from ClientConnect when the player first connects and
directly by the server system when the player updates a userinfo variable.

The game can override any of the settings and call trap_SetUserinfo
if desired.
============
*/
void ClientUserinfoChanged(int clientNum) {
	gentity_t *ent;
	char      *s;
	char      oldname[MAX_STRING_CHARS];
	char      userinfo[MAX_INFO_STRING];
	gclient_t *client;
	char      *ip   = NULL; // Nico, used to store client ip
	char      *rate   = NULL; // Nico, used to store client rate
	char      *snaps   = NULL; // Nico, used to store client snaps
	char      *name = NULL; // Nico, used to store client name
	char      oldAuthToken[MAX_QPATH]; // Nico, used to see if auth token was changed

	ent    = g_entities + clientNum;
	client = ent->client;

	client->ps.clientNum = clientNum;

	// Nico, flood protection
	if (ClientIsFlooding(ent)) {
		G_LogPrintf("Dropping client %d: flooded userinfo\n", clientNum);
		trap_DropClient(clientNum, "^1You were kicked because of flooded userinfo", 0);
		return;
	}

	trap_GetUserinfo(clientNum, userinfo, sizeof (userinfo));

	// Nico, perform security checks on userinfo string
	if (!checkUserinfoString(clientNum, userinfo)) {
		return;
	}

	if (g_developer.integer || *g_log.string || g_dedicated.integer) {
		G_Printf("Userinfo: %s\n", userinfo);
	}

	// check for local client
	ip = Info_ValueForKey(userinfo, "ip");
	Q_strncpyz(client->pers.ip, ip, IP_MAX_LENGTH);
	if (ip && !strcmp(ip, "localhost")) {
		client->pers.localClient = qtrue;
		level.fLocalHost         = qtrue;
		client->sess.referee     = RL_REFEREE;
	}

	// Nico, store rate and snaps
	rate = Info_ValueForKey(userinfo, "rate");
	client->pers.rate = atoi(rate);
	snaps = Info_ValueForKey(userinfo, "snaps");
	client->pers.snaps = atoi(snaps);

	// Nico, backup old auth token
	Q_strncpyz(oldAuthToken, client->pers.authToken, sizeof (oldAuthToken));

	s = Info_ValueForKey(userinfo, "cg_uinfo");
	sscanf(s, "%i %i %i %i %s %i %i %i %i %i %i %i %i %i",
	       &client->pers.clientFlags,
	       &client->pers.clientTimeNudge,
	       &client->pers.clientMaxPackets,

	       // Nico, max FPS
	       &client->pers.maxFPS,

	       // Nico, auth Token
	       (char *)&client->pers.authToken,

	       // Nico, load view angles on load
	       &client->pers.loadViewAngles,

		   // Nico, load weapon on load
	       &client->pers.loadWeapon,

	       // Nico, load position when player dies
	       &client->pers.autoLoad,

	       // Nico, cgaz
	       &client->pers.cgaz,

	       // Nico, hideme
	       &client->pers.hideme,

	       // Nico, client auto demo record setting
	       &client->pers.autoDemo,

	       // Nico, automatically load checkpoints
	       &client->pers.autoLoadCheckpoints,

	       // Nico, persistant specLock
	       (int *)&client->sess.specLocked,

	       // Nico, keep all demos
	       &client->pers.keepAllDemos

	       );

	// Nico, check if auth token was changed
	if (oldAuthToken[0] != '\0' &&
		Q_stricmp(oldAuthToken, client->pers.authToken) &&
		client->sess.logged) {
		// Nico, auth token was changed => logout player if he was logged in
		CP("cp \"You are no longer logged in!\n\"");
		G_LogPrintf("ClientUserinfoChanged: authToken changed for client %d, forcing logout\n", clientNum);
		ent->client->sess.logged = qfalse;
	}

	client->pers.autoActivate      = (client->pers.clientFlags & CGF_AUTOACTIVATE) ? PICKUP_TOUCH : PICKUP_ACTIVATE;
	client->pers.predictItemPickup = ((client->pers.clientFlags & CGF_PREDICTITEMS) != 0);

	if (client->pers.clientFlags & CGF_AUTORELOAD) {
		client->pers.bAutoReloadAux = qtrue;
		client->pmext.bAutoReload   = qtrue;
	} else {
		client->pers.bAutoReloadAux = qfalse;
		client->pmext.bAutoReload   = qfalse;
	}

	// Nico, pmove_fixed
	client->pers.pmoveFixed = client->pers.clientFlags & CGF_PMOVEFIXED;

	// Nico, autologin
	client->pers.autoLogin = client->pers.clientFlags & CGF_AUTOLOGIN;

	//
	// Nico, name handling
	//

	// Backup old name
	Q_strncpyz(oldname, client->pers.netname, sizeof (oldname));

	// Get new name from userinfo string
	name = Info_ValueForKey(userinfo, "name");

	// Clean the new name
	ClientCleanName(name, client->pers.netname, sizeof (client->pers.netname));

	// Check it's valid
	if (CheckName(client->pers.netname) != qtrue) {
		// Invalid name, restore old name
		Q_strncpyz(client->pers.netname, oldname, sizeof (client->pers.netname));
		Info_SetValueForKey(userinfo, "name", oldname);
		trap_SetUserinfo(clientNum, userinfo);
		CPx(clientNum, "print \"^1Invalid name, name change refused\n\"");
		G_LogPrintf("Client %d name change refused\n", clientNum);
	} else {
		// Name is valid
		// Now, check if name was changed or not
		if (client->pers.connected == CON_CONNECTED && strcmp(oldname, client->pers.netname) != 0) {
			// Name was changed
			// Now, check name changes limit
			if (g_maxNameChanges.integer > -1 && client->pers.nameChanges >= g_maxNameChanges.integer) {
				// Nico, limit reached, forbid name change
				Q_strncpyz(client->pers.netname, oldname, sizeof (client->pers.netname));
				Info_SetValueForKey(userinfo, "name", oldname);
				trap_SetUserinfo(clientNum, userinfo);
				CPx(clientNum, "print \"^1You had too many namechanges\n\"");
				G_LogPrintf("Client %d name change refused\n", clientNum);
				return;
			}
			client->pers.nameChanges++;
			trap_SendServerCommand(-1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, client->pers.netname));
		}
	}

	//
	// Nico, end of name handling
	//

	client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;

	// To communicate it to cgame
	client->ps.stats[STAT_PLAYER_CLASS] = client->sess.playerType;
	// Gordon: Not needed any more as it's in clientinfo?

	// send over a subset of the userinfo keys so other clients can
	// print scoreboards, display models, and play custom sounds

	s = va("n\\%s\\t\\%i\\c\\%i\\w\\%i\\lw\\%i\\sw\\%i\\mu\\%i\\ref\\%i\\pm\\%i\\l\\%i\\h\\%i\\cc\\%i",
	       client->pers.netname,
	       client->sess.sessionTeam,
	       client->sess.playerType,
	       client->sess.playerWeapon,
	       client->sess.latchPlayerWeapon,
	       client->sess.latchPlayerWeapon2,
	       client->sess.muted ? 1 : 0,
	       client->sess.referee,
	       client->pers.pmoveFixed ? 1 : 0, // Nico, pmove_fixed
	       client->sess.logged ? 1 : 0, // Nico, login status
	       client->pers.hideme, // Nico, hideme
		   client->sess.countryCode// Nico, country code (GeoIP)
	       );

	trap_GetConfigstring(CS_PLAYERS + clientNum, oldname, sizeof (oldname));

	trap_SetConfigstring(CS_PLAYERS + clientNum, s);

	if (!Q_stricmp(oldname, s)) {
		return;
	}

	G_LogPrintf("ClientUserinfoChanged: %i %s\n", clientNum, s);
	G_DPrintf("ClientUserinfoChanged: %i :: %s\n", clientNum, s);
}