Jump to content

Home

Q3 Engine Game Bugs / JA bugs


Recommended Posts

I'm in the process of intergrating all the bugfixes into OJP and I notice that I can't replicate the following bug. Ensiform, if there maybe additional conditions for this bug to occur? Thanks!

2. bot_minplayers removerandom bot bug where it kicks spectators watching them instead of the bot:

 

change the following in g_bot.c:

 

trap_SendConsoleCommand( EXEC_INSERT, va("kick \"%s\"\n", netname) );

 

to

 

trap_SendConsoleCommand( EXEC_INSERT, va("clientkick \"%d\"\n", cl->ps.clientNum));

 

Edit: Also, Ensiform, would you mind adding additional position information for those bugs mentioned in the starting post? I'd feel more comfortable if I could see exactly where the code is supposed to go. Maybe just show the line of code that preceeds the patched code? Thanks!

Link to post
Share on other sites
  • Replies 229
  • Created
  • Last Reply

Top Posters In This Topic

Also, the bug fix for item 1 in Enisform's first post doesn't fully work. It looks like currentOrigin doesn't work for spectators so you gotta do a spectator check and then return values based on that. As such, the proper fix should be....

 

g_cmd.c

void Cmd_Where_f( gentity_t *ent ) {
trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) );
}

 

to

 

g_cmd.c

void Cmd_Where_f( gentity_t *ent ) {
if(ent->client && ent->client->sess.sessionTeam != TEAM_SPECTATOR )
{//active players use currentOrigin
	trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->r.currentOrigin ) ) );
}
else
{
	trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) );
}
}

Link to post
Share on other sites

Alternate fix for a dead body's color didn't match the player's color in team games. The fix offered by Enisform doesn't conform to the way team colors are normally forced on the player. In addition, I think that Ensiform's method might cause problems with dead body colors in siege.

 

g_client.c:

 

SetupGameGhoul2Model()

Change

if ( g_gametype.integer >= GT_TEAM && g_gametype.integer != GT_SIEGE && !g_trueJedi.integer )
{
BG_ValidateSkinForTeam( truncModelName, skin, ent->client->sess.sessionTeam, NULL );
}

to

if ( g_gametype.integer >= GT_TEAM && g_gametype.integer != GT_SIEGE && !g_trueJedi.integer )
{
//Also adjust customRGBA for team colors.
float colorOverride[3];

colorOverride[0] = colorOverride[1] = colorOverride[2] = 0.0f;

BG_ValidateSkinForTeam( truncModelName, skin, ent->client->sess.sessionTeam, colorOverride);
if (colorOverride[0] != 0.0f ||
	colorOverride[1] != 0.0f ||
	colorOverride[2] != 0.0f)
{
	ent->client->ps.customRGBA[0] = colorOverride[0]*255.0f;
	ent->client->ps.customRGBA[1] = colorOverride[1]*255.0f;
	ent->client->ps.customRGBA[2] = colorOverride[2]*255.0f;
}
}

 

ClientUserinfoChanged()

After

client->ps.customRGBA[3]=255;

Add

if ( g_gametype.integer >= GT_TEAM && g_gametype.integer != GT_SIEGE && !g_trueJedi.integer )
{
char skin[MAX_QPATH];
float colorOverride[3];

colorOverride[0] = colorOverride[1] = colorOverride[2] = 0.0f;

BG_ValidateSkinForTeam( model, skin, client->sess.sessionTeam, colorOverride);
if (colorOverride[0] != 0.0f ||
	colorOverride[1] != 0.0f ||
	colorOverride[2] != 0.0f)
{
	client->ps.customRGBA[0] = colorOverride[0]*255.0f;
	client->ps.customRGBA[1] = colorOverride[1]*255.0f;
	client->ps.customRGBA[2] = colorOverride[2]*255.0f;
}
}

ClientSpawn()

After

client->ps.customRGBA[3]=255;

Add

//update our customRGBA for team colors. 
if ( g_gametype.integer >= GT_TEAM && g_gametype.integer != GT_SIEGE && !g_trueJedi.integer )
{
char skin[MAX_QPATH];
char model[MAX_QPATH];
float colorOverride[3];

colorOverride[0] = colorOverride[1] = colorOverride[2] = 0.0f;
Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) );

BG_ValidateSkinForTeam( model, skin, savedSess.sessionTeam, colorOverride);
if (colorOverride[0] != 0.0f ||
	colorOverride[1] != 0.0f ||
	colorOverride[2] != 0.0f)
{
	client->ps.customRGBA[0] = colorOverride[0]*255.0f;
	client->ps.customRGBA[1] = colorOverride[1]*255.0f;
	client->ps.customRGBA[2] = colorOverride[2]*255.0f;
}
}

 

Edit: Fixed typos in first and second code sections.

Link to post
Share on other sites
Also, the bug fix for item 1 in Enisform's first post doesn't fully work. It looks like currentOrigin doesn't work for spectators so you gotta do a spectator check and then return values based on that. As such, the proper fix should be....

 

g_cmd.c

void Cmd_Where_f( gentity_t *ent ) {
trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) );
}

 

to

 

g_cmd.c

void Cmd_Where_f( gentity_t *ent ) {
if(ent->client && ent->client->sess.sessionTeam != TEAM_SPECTATOR )
{//active players use currentOrigin
	trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->r.currentOrigin ) ) );
}
else
{
	trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) );
}
}

 

fix? spectators don't really have an origin. try playing a map with location entities in them, it doesn't update and is not supposed to.

Link to post
Share on other sites

uhm... put some bots in the game from bot_minplayers, spectate them so that removerandombot will kick them however it will (try) to kick you. if you are localhost it will just say 'cannot kick localhost', also don't use ps.clientnum try something more like:

 

	int idnum;

 

replace the top of the for loop with this:

 

	for( i = 0; i < level.numConnectedClients; i++ ) {
	idnum = level.sortedClients[i];

 

then use idnum instead of cl->ps.clientNum.

 

then just get rid of the netname part in G_RemoveRandomBot.

 

this is what my function looks like:

 

/*
===============
G_RemoveRandomBot
===============
*/
int G_RemoveRandomBot( int team ) {
int i,idnum;
gentity_t	*cl_ent;

for ( i=0 ; i< g_maxclients.integer ; i++ ) {
	idnum = level.sortedSlots[i];
	cl_ent = g_entities + idnum;
	if ( cl_ent->client->pers.connected != CON_CONNECTED ) {
		continue;
	}
	if ( !(cl_ent->r.svFlags & SVF_BOT) ) {
		continue;
	}
	if ( cl_ent->client->ps.powerups[PW_BLUEFLAG] ) {
		continue;
	}
	if ( cl_ent->client->ps.powerups[PW_REDFLAG] ) {
		continue;
	}
	if ( cl_ent->client->ps.powerups[PW_NEUTRALFLAG] ) {
		continue;
	}
	//[bugFix9]
	if ( cl_ent->client->sess.sessionTeam == TEAM_SPECTATOR 
		&& cl_ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
	{//this entity is actually following another entity so the ps data is for a
		//different entity.  Bots never spectate like this so, skip this player.
		continue;
	}
	//[/bugFix9]
	if (g_gametype.integer == GT_SIEGE)
	{
		if ( team >= 0 && cl_ent->client->sess.siegeDesiredTeam != team ) {
			continue;
		}
	}
	else
	{
		if ( team >= 0 && cl_ent->client->sess.sessionTeam != team ) {
			continue;
		}
	}

	trap_SendConsoleCommand( EXEC_INSERT, va2("clientkick \"%d\"\n", idnum));
	return qtrue;
}
return qfalse;
}

 

sortedSlots is basically just sortedClients except it sorts by clientnumber instead of score, and va2 is just va but improved/tweaked. my func also skips removing bots that carry flags.

Link to post
Share on other sites
fix? spectators don't really have an origin. try playing a map with location entities in them, it doesn't update and is not supposed to.

The original intent of the "where" command appears to have been for using spectator mode to determine positions on the map. That's why it doesn't work for active players. But on the flip side, currentOrigin isn't updated for spectators!

Link to post
Share on other sites

¿? hmm you mean in the trap call? well technically, i just looked at re.RemapShader in the engine and only the first parameter really needs it and the last object is really just a float as far as i can see.

 

See (this is the fixed version in the engine using a fail-safe checker for COM_StripExtension):

 

tr_shader.c:

 

void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) {
char		strippedName[MAX_QPATH];
int			hash;
shader_t	*sh, *sh2;
qhandle_t	h;

sh = R_FindShaderByName( shaderName );
if (sh == NULL || sh == tr.defaultShader) {
	h = RE_RegisterShaderLightMap(shaderName, 0);
	sh = R_GetShaderByHandle(h);
}
if (sh == NULL || sh == tr.defaultShader) {
	ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName );
	return;
}

sh2 = R_FindShaderByName( newShaderName );
if (sh2 == NULL || sh2 == tr.defaultShader) {
	h = RE_RegisterShaderLightMap(newShaderName, 0);
	sh2 = R_GetShaderByHandle(h);
}

if (sh2 == NULL || sh2 == tr.defaultShader) {
	ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName );
	return;
}

// remap all the shaders with the given name
// even tho they might have different lightmaps
COM_StripExtension(shaderName, strippedName, sizeof(strippedName));
hash = generateHashValue(strippedName, FILE_HASH_SIZE);
for (sh = hashTable[hash]; sh; sh = sh->next) {
	if (Q_stricmp(sh->name, strippedName) == 0) {
		if (sh != sh2) {
			sh->remappedShader = sh2;
		} else {
			sh->remappedShader = NULL;
		}
	}
}
if (timeOffset) {
	sh2->timeOffset = atof(timeOffset);
}
}

 

old version that exists in jka:

 

void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) {
char		strippedName[MAX_QPATH];
int			hash;
shader_t	*sh, *sh2;
qhandle_t	h;

sh = R_FindShaderByName( shaderName );
if (sh == NULL || sh == tr.defaultShader) {
	h = RE_RegisterShaderLightMap(shaderName, 0);
	sh = R_GetShaderByHandle(h);
}
if (sh == NULL || sh == tr.defaultShader) {
	ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName );
	return;
}

sh2 = R_FindShaderByName( newShaderName );
if (sh2 == NULL || sh2 == tr.defaultShader) {
	h = RE_RegisterShaderLightMap(newShaderName, 0);
	sh2 = R_GetShaderByHandle(h);
}

if (sh2 == NULL || sh2 == tr.defaultShader) {
	ri.Printf( PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName );
	return;
}

// remap all the shaders with the given name
// even tho they might have different lightmaps
COM_StripExtension(shaderName, strippedName);
hash = generateHashValue(shaderName, FILE_HASH_SIZE);
for (sh = hashTable[hash]; sh; sh = sh->next) {
	if (Q_stricmp(sh->name, strippedName) == 0) {
		if (sh != sh2) {
			sh->remappedShader = sh2;
		} else {
			sh->remappedShader = NULL;
		}
	}
}
if (timeOffset) {
	sh2->timeOffset = atof(timeOffset);
}
}

 

note how in the fixed version com_stripextension takes into affect the newsize, and the jka one does not.

 

/*
============
COM_StripExtension
============
*/
void COM_StripExtension( const char *in, char *out ) {
while ( *in && *in != '.' ) {
	*out++ = *in++;
}
*out = 0;
}

 

vs.

 

/*
============
COM_StripExtensionSafe
============
*/
void COM_StripExtensionSafe( const char *in, char *out, int destsize ) {
int             length;

Q_strncpyz(out, in, destsize);

length = strlen(out)-1;
while (length > 0 && out[length] != '.')
{
	length--;
	if (out[length] == '/')
		return;		// no extension
}
if (length)
	out[length] = 0;
}

 

i renamed it because i do not like to modify anything in the q_* files

Link to post
Share on other sites

There's a few typo bugs in the saberMoveData table that result in the wrong animations being played during bottom left/right broken parries, parries, and knockaways.

 

bg_saber.c, saberMoveData[]

Replace

// Broken parries
{"BParry Top",	BOTH_H1_S1_T_,		Q_T,	Q_B,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_UP,
{"BParry UR",	BOTH_H1_S1_TR,		Q_TR,	Q_BL,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_UR,
{"BParry UL",	BOTH_H1_S1_TL,		Q_TL,	Q_BR,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_UL,
{"BParry LR",	BOTH_H1_S1_BL,		Q_BL,	Q_TR,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_LR,
{"BParry Bot",	BOTH_H1_S1_B_,		Q_B,	Q_T,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_LL
{"BParry LL",	BOTH_H1_S1_BR,		Q_BR,	Q_TL,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_LL

// Knockaways
{"Knock Top",	BOTH_K1_S1_T_,		Q_R,	Q_T,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BL2TR,		LS_T1_T__BR,		150	},	// LS_PARRY_UP,
{"Knock UR",	BOTH_K1_S1_TR,		Q_R,	Q_TR,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BL2TR,		LS_T1_TR__R,		150	},	// LS_PARRY_UR,
{"Knock UL",	BOTH_K1_S1_TL,		Q_R,	Q_TL,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BR2TL,		LS_T1_TL__L,		150	},	// LS_PARRY_UL,
{"Knock LR",	BOTH_K1_S1_BL,		Q_R,	Q_BL,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_TL2BR,		LS_T1_BL_TL,		150	},	// LS_PARRY_LR,
{"Knock LL",	BOTH_K1_S1_BR,		Q_R,	Q_BR,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_TR2BL,		LS_T1_BR_TR,		150	},	// LS_PARRY_LL

// Parry
{"Parry Top",	BOTH_P1_S1_T_,		Q_R,	Q_T,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BL2TR,		LS_A_T2B,		150	},	// LS_PARRY_UP,
{"Parry UR",	BOTH_P1_S1_TR,		Q_R,	Q_TL,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BL2TR,		LS_A_TR2BL,		150	},	// LS_PARRY_UR,
{"Parry UL",	BOTH_P1_S1_TL,		Q_R,	Q_TR,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BR2TL,		LS_A_TL2BR,		150	},	// LS_PARRY_UL,
{"Parry LR",	BOTH_P1_S1_BL,		Q_R,	Q_BR,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_TL2BR,		LS_A_BR2TL,		150	},	// LS_PARRY_LR,
{"Parry LL",	BOTH_P1_S1_BR,		Q_R,	Q_BL,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_TR2BL,		LS_A_BL2TR,		150	},	// LS_PARRY_LL

with

// Broken parries
{"BParry Top",	BOTH_H1_S1_T_,		Q_T,	Q_B,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_UP,
{"BParry UR",	BOTH_H1_S1_TR,		Q_TR,	Q_BL,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_UR,
{"BParry UL",	BOTH_H1_S1_TL,		Q_TL,	Q_BR,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_UL,
{"BParry LR",	BOTH_H1_S1_BR,		Q_BL,	Q_TR,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_LR,
{"BParry Bot",	BOTH_H1_S1_B_,		Q_B,	Q_T,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_LR
{"BParry LL",	BOTH_H1_S1_BL,		Q_BR,	Q_TL,	AFLAG_ACTIVE,	50,		BLK_NO,	LS_READY,		LS_READY,		150	},	// LS_PARRY_LL

// Knockaways
{"Knock Top",	BOTH_K1_S1_T_,		Q_R,	Q_T,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BL2TR,		LS_T1_T__BR,		150	},	// LS_PARRY_UP,
{"Knock UR",	BOTH_K1_S1_TR,		Q_R,	Q_TR,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BL2TR,		LS_T1_TR__R,		150	},	// LS_PARRY_UR,
{"Knock UL",	BOTH_K1_S1_TL,		Q_R,	Q_TL,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BR2TL,		LS_T1_TL__L,		150	},	// LS_PARRY_UL,
{"Knock LR",	BOTH_K1_S1_BR,		Q_R,	Q_BL,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_TL2BR,		LS_T1_BL_TL,		150	},	// LS_PARRY_LR,
{"Knock LL",	BOTH_K1_S1_BL,		Q_R,	Q_BR,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_TR2BL,		LS_T1_BR_TR,		150	},	// LS_PARRY_LL

// Parry
{"Parry Top",	BOTH_P1_S1_T_,		Q_R,	Q_T,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BL2TR,		LS_A_T2B,		150	},	// LS_PARRY_UP,
{"Parry UR",	BOTH_P1_S1_TR,		Q_R,	Q_TL,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BL2TR,		LS_A_TR2BL,		150	},	// LS_PARRY_UR,
{"Parry UL",	BOTH_P1_S1_TL,		Q_R,	Q_TR,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_BR2TL,		LS_A_TL2BR,		150	},	// LS_PARRY_UL,
{"Parry LR",	BOTH_P1_S1_BR,		Q_R,	Q_BR,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_TL2BR,		LS_A_BR2TL,		150	},	// LS_PARRY_LR,
{"Parry LL",	BOTH_P1_S1_BL,		Q_R,	Q_BL,	AFLAG_ACTIVE,	50,		BLK_WIDE,	LS_R_TR2BL,		LS_A_BL2TR,		150	},	// LS_PARRY_LL

Link to post
Share on other sites

Replacement for DeathmatchScoreboardMessage that comes from Enemy Territory with some enhancements and such so that it will not go over 1022 limit.

 

Note: if you use unlagged change the ping part to use pers.realPing instead of ps.ping.

 

// G_SendScore_Add
// 
// Add score with clientNum at index i of level.sortedClients[]
// to the string buf.
// 
// returns qtrue if the score was appended to buf, qfalse otherwise.
qboolean G_SendScore_Add(gentity_t *ent, int i, char *buf, int bufsize) 
{
gclient_t *cl;
int ping, scoreFlags=0, accuracy, perfect;
char entry[256];

entry[0] = '\0';

cl = &level.clients[level.sortedClients[i]];

if ( cl->pers.connected == CON_CONNECTING ) {
	ping = -1;
} else {
	ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
}

if( cl->accuracy_shots ) {
	accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots;
} else {
	accuracy = 0;
}
perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0;

Com_sprintf (entry, sizeof(entry),
	" %i %i %i %i %i %i %i %i %i %i %i %i %i %i ", 
	level.sortedClients[i],
	cl->ps.persistant[PERS_SCORE], 
	ping, 
	(level.time - cl->pers.enterTime)/60000,
	scoreFlags,
	g_entities[level.sortedClients[i]].s.powerups, 
	accuracy, 
	cl->ps.persistant[PERS_IMPRESSIVE_COUNT],
	cl->ps.persistant[PERS_EXCELLENT_COUNT],
	cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], 
	cl->ps.persistant[PERS_DEFEND_COUNT], 
	cl->ps.persistant[PERS_ASSIST_COUNT], 
	perfect,
	cl->ps.persistant[PERS_CAPTURES]);

if((strlen(buf) + strlen(entry) + 1) > bufsize) {
	return qfalse;
}
Q_strcat(buf, bufsize, entry);
return qtrue;
}

/*
==================
G_SendScore

==================
*/
void G_SendScore( gentity_t *ent ) {
int i;
int numSorted;
int count;
// tjw: commands over 1022 will crash the client so they're
//      pruned in trap_SendServerCommand()
//      1022 -32 for the startbuffer
char		buffer[990];
char		startbuffer[32];

numSorted = level.numConnectedClients;

if (numSorted > MAX_CLIENTS)
{
	numSorted = MAX_CLIENTS;
}

count = 0;
*buffer = '\0';
*startbuffer = '\0';

Q_strncpyz(startbuffer, va(
	"scores %i %i %i",
	level.numConnectedClients,
	level.teamScores[TEAM_RED],
	level.teamScores[TEAM_BLUE]),
	sizeof(startbuffer));

// tjw: keep adding scores to the scores command until we fill 
//      up the buffer.  
for(i=0 ; i < numSorted ; i++) {
	// tjw: the old version of SendScore() did this.  I removed it
	//      originally because it seemed like an unneccessary hack.
	//      perhaps it is necessary for compat with CG_Argv()?
	if(!G_SendScore_Add(ent, i, buffer, sizeof(buffer))) {
		break;
	}
	count++;
}
if(!count) {
	return;
}
trap_SendServerCommand(ent-g_entities, va(
	"%s%s", startbuffer, buffer));
}

 

Now just replace all instances of DeathmatchScoreboardMessage with G_SendScore.

 

Client-Side fix to allow actually up to MAX_CLIENTS instead of only 20 clients to draw on scoreboard:

 

cg_servercmds.c:

 

/*
=================
CG_ParseScores

=================
*/
static void CG_ParseScores( void ) {
int		i, powerups;

cg.numScores = atoi( CG_Argv( 1 ) );
if ( cg.numScores > MAX_CLIENTS ) {
	cg.numScores = MAX_CLIENTS;
}

cg.teamScores[0] = atoi( CG_Argv( 2 ) );
cg.teamScores[1] = atoi( CG_Argv( 3 ) );

memset( cg.scores, 0, sizeof( cg.scores ) );
for ( i = 0 ; i < cg.numScores ; i++ ) {
	//
	cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) );
	cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) );
	cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) );
	cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) );
	cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) );
	powerups = atoi( CG_Argv( i * 14 + 9 ) );
	cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10));
	cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11));
	cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12));
	cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13));
	cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14));
	cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15));
	cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16));
	cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17));

	if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) {
		cg.scores[i].client = 0;
	}
	cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score;
	cgs.clientinfo[ cg.scores[i].client ].powerups = powerups;

	cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team;
}
}

Link to post
Share on other sites

Now for an updated TeamplayInfoMessage.

 

This is going to be slightly longer because we need to add a new sorted client list to level_locals

 

add this to level_locals_t struct (g_local.h):

 

int			sortedSlots[MAX_CLIENTS];		// sorted by clientnum

 

now above CalculateRanks add this function (g_main.c):

 

/*
=============
SortSlots

=============
*/
int QDECL SortSlots( const void *a, const void *b ) {
return *(int *)a - *(int *)b;
}

 

Okay now in CalculateRanks,

 

below this line:

 

level.sortedClients[level.numConnectedClients] = i;

 

add:

 

level.sortedSlots[level.numConnectedClients] = i;

 

now look for this line:

 

qsort( level.sortedClients, level.numConnectedClients, 
	sizeof(level.sortedClients[0]), SortRanks );

 

below it add:

 

qsort( level.sortedSlots, level.numConnectedClients, 
	sizeof(level.sortedSlots[0]), SortSlots );

 

now g_team.c

 

A replacement for TeamplayInfoMessage.

 

// G_SendTeamInfo_Add
// 
// Add teaminfo with clientNum at index i of level.sortedSlots[]
// to the string buf.
// 
// returns qtrue if the info was appended to buf, qfalse otherwise.
qboolean G_SendTeamInfo_Add(gentity_t *ent, gentity_t *player, int idnum, char *buf, int bufsize) 
{
int		h, a;
char	entry[128];

entry[0] = '\0';

h = player->client->ps.stats[sTAT_HEALTH];
a = player->client->ps.stats[sTAT_ARMOR];
if (h < 0) h = 0;
if (a < 0) a = 0;

Com_sprintf (entry, sizeof(entry),
	" %i %i %i %i %i %i", 
	idnum, player->client->pers.teamState.location, h, a, 
	player->client->ps.weapon, player->s.powerups);
if((strlen(buf) + strlen(entry) + 1) > bufsize) {
	return qfalse;
}
Q_strcat(buf, bufsize, entry);
return qtrue;
}

/*
==================
G_SendTeamInfo

==================
*/
void G_SendTeamInfo( gentity_t *ent ) {
int			i, idnum;
int			numSorted;
int			count;
gentity_t	*player = NULL;
// tjw: commands over 1022 will crash the client so they're
//      pruned in trap_SendServerCommand()
//      1022 -32 for the startbuffer
char		buffer[990];
char		startbuffer[32];

if ( !ent->client->pers.teamInfo )
	return;

numSorted = level.numConnectedClients;

if (numSorted > MAX_CLIENTS)
{
	numSorted = MAX_CLIENTS;
}

count = 0;
*buffer = '\0';
*startbuffer = '\0';

// tjw: keep adding teaminfos to the tinfo command until we fill 
//      up the buffer.  
for(i=0 ; i < numSorted && count < TEAM_MAXOVERLAY; i++) {
	// tjw: the old version of SendScore() did this.  I removed it
	//      originally because it seemed like an unneccessary hack.
	//      perhaps it is necessary for compat with CG_Argv()?
	idnum = level.sortedSlots[i];
	player = g_entities + idnum;
	if ( !player->inuse || player->client->sess.sessionTeam != ent->client->sess.sessionTeam )
		continue;
	if(!G_SendTeamInfo_Add(ent, player, idnum, buffer, sizeof(buffer))) {
		break;
	}
	count++;
}
if(!count) {
	return;
}
Q_strncpyz(startbuffer, va(
	"tinfo %i",
	count),
	sizeof(startbuffer));

trap_SendServerCommand(ent-g_entities, va(
	"%s%s", startbuffer, buffer));
}

 

Now just replace any instances of TeamplayInfoMessage with G_SendTeamInfo.

Link to post
Share on other sites

Okay, here is a good vsnprintf that works (maybe not for mac but you will have to check) for jka since you dont use qvm:

 

q_shared.c (somewhere above Com_sprintf preferably):

 

(comes from idStr::vsnPrintf from Str.c and Str.h from D3/Q4 1.3 SDK)

 

/*
============
Q_vsnprintf

vsnprintf portability:

C99 standard: vsnprintf returns the number of characters (excluding the trailing
'\0') which would have been written to the final string if enough space had been available
snprintf and vsnprintf do not write more than size bytes (including the trailing '\0')

win32: _vsnprintf returns the number of characters written, not including the terminating null character,
or a negative value if an output error occurs. If the number of characters to write exceeds count, then count 
characters are written and -1 is returned and no trailing '\0' is added.

Q_vsnprintf: always appends a trailing '\0', returns number of characters written (not including terminal \0)
or returns -1 on failure or if the buffer would be overflowed.
============
*/
int Q_vsnprintf( char *dest, int size, const char *fmt, va_list argptr ) {
int ret;

#ifdef _WIN32
ret = _vsnprintf( dest, size-1, fmt, argptr );
#else
ret = vsnprintf( dest, size, fmt, argptr );
#endif
dest[size-1] = '\0';
if ( ret < 0 || ret >= size ) {
	return -1;
}
return ret;
}

 

q_shared.h

 

look for this code:

 

// strlen that discounts Quake color sequences
int Q_PrintStrlen( const char *string );
// removes color sequences from string
char *Q_CleanStr( char *string );

 

add this below it:

 

int Q_vsnprintf( char *dest, int size, const char *fmt, va_list argptr );

 

Use this for many things like, G_Printf, G_LogPrintf, G_Error, CG_Printf, and CG_Error that use vsprintf. However, leave va, and if you wish to use a cleaner Com_sprintf try something like this:

 

(comes from Enemy Territory)

 

void QDECL Com_sprintf( char *dest, int size, const char *fmt, ...) {
int		ret;
va_list		argptr;

va_start (argptr,fmt);
ret = Q_vsnprintf (dest, size, fmt, argptr);
va_end (argptr);
if (ret == -1) {
	Com_Printf ("Com_sprintf: overflow of %i bytes buffer\n", size);
}
}

 

My updated G_LogPrintf with the time fix also:

 

/*
=================
G_LogPrintf

Print to the logfile with a time stamp if it is open
=================
*/
void QDECL G_LogPrintf( const char *fmt, ... ) {
va_list		argptr;
char		string[1024];
int			mins, seconds, msec, l;

msec = level.time;

seconds = msec / 1000;
mins = seconds / 60;
seconds %= 60;
msec %= 1000;

Com_sprintf( string, sizeof(string), "%i:%02i ", mins, seconds );

l = strlen( string );

va_start( argptr, fmt );
Q_vsnprintf( string + l, sizeof( string ) - l, fmt, argptr );
va_end( argptr );

if ( g_dedicated.integer ) {
	G_Printf( "%s", string + l );
}

if ( !level.logFile ) {
	return;
}

trap_FS_Write( string, strlen( string ), level.logFile );
}

Link to post
Share on other sites
  • 2 weeks later...

possibly giving ammo to incorrect index in cg_predict.c.

 

Scroll to bottom of CG_TouchItem in cg_predict.c

 

replace these lines:

 

if ( !cg.predictedPlayerState.ammo[ item->giTag ] ) {
cg.predictedPlayerState.ammo[ item->giTag ] = 1;

with:

 

if ( !cg.predictedPlayerState.ammo[ weaponData[item->giTag].ammoIndex ] ) {
cg.predictedPlayerState.ammo[ weaponData[item->giTag].ammoIndex ] = 1;

Link to post
Share on other sites
  • 2 months later...

I've just found out that Slider's fix posted on a previous page in this thread about detpacks and siege objectives when a player changes teams/disconnects does not entirely work. I've tested it and it still seems to cause them to damage the objectives.

 

Better solution: Remove all detpacks, trip mines, and thermals from the player who switches teams or disconnects/reconnects.

 

Also: BugFix13 is only part of the problem with the siege bridge lame, a properly placed player or other objects can still block the bridge and make it go back in. Fix: in SP_func_door make ent->spawnflags be 5 (start_open and crusher) for that mover and set ent->damage to 9999 if it is on mp/siege_hoth and is func_door and has an ent->target of "droptheclip".

Link to post
Share on other sites
  • 3 weeks later...

In ai_main.c, replace

 

if (InFieldOfVision(bs->viewangles, 100, e_ang_vec))
{ //Our enemy has his saber holstered and has challenged us to a duel, so challenge him back
if (!bs->cur_ps.saberHolstered)
{
    Cmd_ToggleSaber_f(&g_entities[bs->client]);
}
else
{
            if (bs->currentEnemy->client->ps.duelIndex == bs->client &&
         bs->currentEnemy->client->ps.duelTime > level.time &&
                !bs->cur_ps.duelInProgress)
     {
	 Cmd_EngageDuel_f(&g_entities[bs->client]);
     }
}

 

with:

 

if (InFieldOfVision(bs->viewangles, 100, e_ang_vec) && !bs->cur_ps.duelInProgress && 
   !bs->currentEnemy->client->ps.fd.forcePowersActive )
{ //Our enemy has his saber holstered and has challenged us to a duel, so challenge him back
if (!bs->cur_ps.saberHolstered && !bs->cur_ps.saberInFlight )
{
    Cmd_ToggleSaber_f(&g_entities[bs->client]);
}
else
{
    if (bs->currentEnemy->client->ps.duelIndex == bs->client &&
	bs->currentEnemy->client->ps.duelTime > level.time &&
	!bs->cur_ps.duelInProgress)
    {
	Cmd_EngageDuel_f(&g_entities[bs->client]);
    }
}

 

This will prevent people from laming the bots when they have their guard down.

Link to post
Share on other sites
  • 3 weeks later...

This is my first post here, but I've been reading this forum for a while and this thread was very useful to me while programming my mod, so it's time for me to contribute too :)

 

I have finally fixed the "speeder lag" bug ensiform talked about earlier in this thread (well, I think this is the same bug - and if it is, the code he provided didn't fix it). Here is exactly when it happens: you're for example the client 1, and you're a spectator following the client 0. The client 0 fires an event (like attacking, or jumping on a vehicle..) while you're watching him. You stop following him. He climbs on a vehicle, and this is when the bug happen: your view begins to shake/lag horribly when this player is in view (or almost) and on the vehicle. As soon as he gets off, your view comes back to normal.

 

The problem is client-side, and here is the reason: while you are following someone, you receive his events, which calls CG_CheckPlayerstateEvents with the address of cg.snap->ps in the first argument. So the "cent->playerState = ps;" line of this function replaces the cent's (here, client 0) playerState ADDRESS (which should always be in the cgSendPSPool array) with the ADDRESS of the snapshot's playerState. Now you can imagine what happens in CG_Player when the client 0 is attached to the vehicle: his playerState->origin is changed, which also changes the cg.snap->ps.origin, and your view is moved when it shouldn't. It's shaky simply because the cg.snap is alternatively in cg.activeSnapshots[0] and in cg.activeSnapshots[1], so sometimes the snapshot is not modified and the origin is correct.

 

To make it short: in cg_playerState.c in CG_CheckPlayerstateEvents, comment the line 242:

 

void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) {

...
//JLF ADDED to hopefully mark events as player event
		//cent->playerState = ps;
...
}

I think it's ok to simply comment it, because it seems completely stupid, and the comment above (//JLF ADDED to hopefully mark events as player event) shows it was probably meant to be used on Xbox only (the JLF comments are often related to Xbox stuff). I didn't notice anything weird after commenting it, anyway.

 

Sorry for the long explanation for a so simple bugfix (1 line to comment, but it took me hours to find it), but I don't think it was obvious it was related to the bug described :)

Link to post
Share on other sites

×
×
  • Create New...