Jump to content

Home

Q3 Engine Game Bugs / JA bugs


Recommended Posts

  • 3 weeks later...
  • Replies 229
  • Created
  • Last Reply

Top Posters In This Topic

It appears any cvar that is part of userinfo is susceptible to being too large and thus making the userinfo string bigger than 1024 (which would likely cause the IP string and others too may not then be retrieved with trap_GetUserinfo(...) as its not there because it was chopped off).

 

This can be a hazard because then ban checks cannot be performed. >.<

 

Fix: Well Luigi has a Windows only patch for it but I haven't heard it tested with q3 and its not supported by Linux.

 

Other possibilities: Enlarge the buffers in ClientConnect/ClientUserinfoChanged and check that its actual length is not greater than 1024. Check that there is indeed \ip\ in the string (You wouldn't want to also check for the value I guess because the value is lost after first connect.)

Link to post
Share on other sites

Info_ValueForKey function calls are quite expensive as they must parse the entire string every time it is called.

 

Improvement: Use a string hashing method and compare the tokens using Info_NextPair. An example of this can be seen from lucel in the NoQuarter ET mod source.

Link to post
Share on other sites
in w_force.c & g_client.c, look for:

Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) );

replace with:

if (!(ent->r.svFlags & SVF_BOT))
{
	char *s;
	s = Info_ValueForKey (userinfo, "forcepowers");
	FR_NormalizeForcePowers(s, strlen(s));
	strcpy( forcePowers, s );
}
else
{
	Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) );
}

Why did you add it in both places?

You only need it in one, did it just in case? I believe if you do it in userinfo it prevents it from being changed to it and in w_force.c it changes to it but it doesn't process it. (i think)

I'm only curious.

Link to post
Share on other sites

bg_misc.c\BG_LegalizedForcePowers is also a good place to put it but the other way is kind of easier in my opinion. :)

 

Strange... I just tried this force crash 'patch' in w.force.c first and it worked, it didn't crash. I try it in g_client.c\ClientUserInfoChanged and it doesn't prevent the crash. I did everything exactly right but instead of putting it in both places I put it only in userinfo and it isn't working, rawr!!

What would be the cause of this?

Link to post
Share on other sites

qboolean BG_LegalizedForcePowers(char *powerOut, int maxRank, qboolean freeSaber, int teamForce, int gametype, int fpDisabled)
{
char powerBuf[128] = {0};
char readBuf[128] = {0};
qboolean maintainsValidity = qtrue;
int powerLen = strlen(powerOut);
int i = 0;
int c = 0;
int allowedPoints = 0;
int usedPoints = 0;
int countDown = 0;

int final_Side;
int final_Powers[NUM_FORCE_POWERS];

//[bugFix36]
//blank out the final_Powers array in case we get garbage in powerOut.
memset(final_Powers, 0, sizeof(final_Powers));
//[/bugFix36]

if (powerLen >= 128)
{ //This should not happen. If it does, this is obviously a bogus string.
	//They can have this string. Because I said so.
	Q_strncpyz(powerBuf, "7-1-032330000000001333", sizeof(powerBuf));
	maintainsValidity = qfalse;
}
else
{
	Q_strncpyz(powerBuf, powerOut, sizeof(powerBuf)); //copy it as the original
}

//first of all, print the max rank into the string as the rank
strcpy(powerOut, va("%i-", maxRank));

while (i < 128 && powerBuf[i] && powerBuf[i] != '-')
{
	i++;
}
i++;
while (i < 128 && powerBuf[i] && powerBuf[i] != '-')
{
	readBuf[c] = powerBuf[i];
	c++;
	i++;
}
readBuf[c] = 0;
i++;
//at this point, readBuf contains the intended side
final_Side = Q_atoi(readBuf);

if (final_Side != FORCE_LIGHTSIDE &&
	final_Side != FORCE_DARKSIDE)
{ //Not a valid side. You will be dark. Because I said so. (this is something that should never actually happen unless you purposely feed in an invalid config)
	final_Side = FORCE_DARKSIDE;
	maintainsValidity = qfalse;
}

if (teamForce)
{ //If we are under force-aligned teams, make sure we're on the right side.
	if (final_Side != teamForce)
	{
		final_Side = teamForce;
		//maintainsValidity = qfalse;
		//Not doing this, for now. Let them join the team with their filtered powers.
	}
}

//Now we have established a valid rank, and a valid side.
//Read the force powers in, and cut them down based on the various rules supplied.
c = 0;
//[bugFix36]
while (i < 128 && powerBuf[i] && powerBuf[i] != '\n' && powerBuf[i] != '\r'  //standard sanity checks
	&& powerBuf[i] >= '0' && powerBuf[i] <= '3' && c < NUM_FORCE_POWERS)
//while (i < 128 && powerBuf[i] && powerBuf[i] != '\n' && c < NUM_FORCE_POWERS)
//[/bugFix36]
{
	readBuf[0] = powerBuf[i];
	readBuf[1] = 0;
	final_Powers[c] = Q_atoi(readBuf);
	c++;
	i++;
}

//final_Powers now contains all the stuff from the string
//Set the maximum allowed points used based on the max rank level, and count the points actually used.
allowedPoints = forceMasteryPoints[maxRank];

i = 0;
while (i < NUM_FORCE_POWERS)
{ //if this power doesn't match the side we're on, then 0 it now.
	if (final_Powers[i] &&
		forcePowerDarkLight[i] &&
		forcePowerDarkLight[i] != final_Side)
	{
		final_Powers[i] = 0;
		//This is only likely to happen with g_forceBasedTeams. Let it slide.
	}

	if ( final_Powers[i] &&
		(fpDisabled & (1 << i)) )
	{ //if this power is disabled on the server via said server option, then we don't get it.
		final_Powers[i] = 0;
	}

	i++;
}

if (gametype < GT_TEAM)
{ //don't bother with team powers then
	final_Powers[FP_TEAM_HEAL] = 0;
	final_Powers[FP_TEAM_FORCE] = 0;
}

usedPoints = 0;
i = 0;
while (i < NUM_FORCE_POWERS) {
	countDown = 0;

	countDown = final_Powers[i];
	//[MBQ3FILLFIX]
	if(countDown > 3) {
		return qfalse; //-1
	}
	//[/MBQ3FILLFIX]

	while (countDown > 0)
	{
		usedPoints += bgForcePowerCost[i][countDown]; //[fp index][fp level]
		//if this is jump, or we have a free saber and it's offense or defense, take the level back down on level 1
		if ( countDown == 1 &&
			((i == FP_LEVITATION) ||
			 (i == FP_SABER_OFFENSE && freeSaber) ||
			 (i == FP_SABER_DEFENSE && freeSaber)) )
		{
			usedPoints -= bgForcePowerCost[i][countDown];
		}
		countDown--;
	}

	i++;
}

if (usedPoints > allowedPoints)
{ //Time to do the fancy stuff. (meaning, slowly cut parts off while taking a guess at what is most or least important in the config)
	int attemptedCycles = 0;
	int powerCycle = 2;
	int minPow = 0;

	if (freeSaber)
	{
		minPow = 1;
	}

	maintainsValidity = qfalse;

	while (usedPoints > allowedPoints)
	{
		c = 0;

		while (c < NUM_FORCE_POWERS && usedPoints > allowedPoints)
		{
			if (final_Powers[c] && final_Powers[c] < powerCycle)
			{ //kill in order of lowest powers, because the higher powers are probably more important
				if (c == FP_SABER_OFFENSE &&
					(final_Powers[FP_SABER_DEFENSE] > minPow || final_Powers[FP_SABERTHROW] > 0))
				{ //if we're on saber attack, only suck it down if we have no def or throw either
					int whichOne = FP_SABERTHROW; //first try throw

					if (!final_Powers[whichOne])
					{
						whichOne = FP_SABER_DEFENSE; //if no throw, drain defense
					}

					while (final_Powers[whichOne] > 0 && usedPoints > allowedPoints)
					{
						if ( final_Powers[whichOne] > 1 ||
							( (whichOne != FP_SABER_OFFENSE || !freeSaber) &&
							  (whichOne != FP_SABER_DEFENSE || !freeSaber) ) )
						{ //don't take attack or defend down on level 1 still, if it's free
							usedPoints -= bgForcePowerCost[whichOne][final_Powers[whichOne]];
							final_Powers[whichOne]--;
						}
						else
						{
							break;
						}
					}
				}
				else
				{
					while (final_Powers[c] > 0 && usedPoints > allowedPoints)
					{
						if ( final_Powers[c] > 1 ||
							((c != FP_LEVITATION) &&
							(c != FP_SABER_OFFENSE || !freeSaber) &&
							(c != FP_SABER_DEFENSE || !freeSaber)) )
						{
							usedPoints -= bgForcePowerCost[c][final_Powers[c]];
							final_Powers[c]--;
						}
						else
						{
							break;
						}
					}
				}
			}

			c++;
		}

		powerCycle++;
		attemptedCycles++;

		if (attemptedCycles > NUM_FORCE_POWERS)
		{ //I think this should be impossible. But just in case.
			break;
		}
	}

	if (usedPoints > allowedPoints)
	{ //Still? Fine then.. we will kill all of your powers, except the freebies.
		i = 0;

		while (i < NUM_FORCE_POWERS)
		{
			final_Powers[i] = 0;
			if (i == FP_LEVITATION ||
				(i == FP_SABER_OFFENSE && freeSaber) ||
				(i == FP_SABER_DEFENSE && freeSaber))
			{
				final_Powers[i] = 1;
			}
			i++;
		}
		usedPoints = 0;
	}
}

if (freeSaber)
{
	if (final_Powers[FP_SABER_OFFENSE] < 1)
	{
		final_Powers[FP_SABER_OFFENSE] = 1;
	}
	if (final_Powers[FP_SABER_DEFENSE] < 1)
	{
		final_Powers[FP_SABER_DEFENSE] = 1;
	}
}
if (final_Powers[FP_LEVITATION] < 1)
{
	final_Powers[FP_LEVITATION] = 1;
}

i = 0;
while (i < NUM_FORCE_POWERS)
{
	if (final_Powers[i] > FORCE_LEVEL_3)
	{
		final_Powers[i] = FORCE_LEVEL_3;
	}
	i++;
}

if (fpDisabled)
{ //If we specifically have attack or def disabled, force them up to level 3. It's the way
  //things work for the case of all powers disabled.
  //If jump is disabled, down-cap it to level 1. Otherwise don't do a thing.
	if (fpDisabled & (1 << FP_LEVITATION))
	{
		final_Powers[FP_LEVITATION] = 1;
	}
	if (fpDisabled & (1 << FP_SABER_OFFENSE))
	{
		final_Powers[FP_SABER_OFFENSE] = 3;
	}
	if (fpDisabled & (1 << FP_SABER_DEFENSE))
	{
		final_Powers[FP_SABER_DEFENSE] = 3;
	}
}

if (final_Powers[FP_SABER_OFFENSE] < 1)
{
	final_Powers[FP_SABER_DEFENSE] = 0;
	final_Powers[FP_SABERTHROW] = 0;
}

//We finally have all the force powers legalized and stored locally.
//Put them all into the string and return the result. We already have
//the rank there, so print the side and the powers now.
Q_strcat(powerOut, 128, va("%i-", final_Side));

i = strlen(powerOut);
c = 0;
while (c < NUM_FORCE_POWERS)
{
	Q_strncpyz(readBuf, va("%i", final_Powers[c]), sizeof(readBuf));
	powerOut[i] = readBuf[0];
	c++;
	i++;
}
powerOut[i] = 0;

return maintainsValidity;
}

 

Thats all I use and seems fine.

Link to post
Share on other sites
  • 1 month later...

There seems to be an issue with using the cgs.scores1/2 for the team score as it uses data from a ConfigString which appears to be somewhat unreliable during map changes on the client side. For instance: Server running a map that ends and starts changing to new map but you started connecting while old map was still running and then you get to Awaiting Snapshot and start the new map load... You will notice that one or both scores on mini-scoreboard are not quite like they should be (see scoreboard for real score).

Link to post
Share on other sites
  • 1 year later...

Screw letting the dead rest in peace, I want to bring this part of the forum alive again.

 

Basically, the Q3/JKA memory management is poor - Do not follow their examples!

More at this thread (Old JA+ exploit)

 

"When you assign dynamic memory as a buffer for anything, be sure to free the damn memory when you are done with it!"

Otherwise, if that function is called enough, the memory pool will overflow and crash the server - Not a good thing at all!

For the most part, it's not a problem..but if us mod authors are allocating memory for whatever reason, free it!

Link to post
Share on other sites
Screw letting the dead rest in peace, I want to bring this part of the forum alive again.

 

Basically, the Q3/JKA memory management is poor - Do not follow their examples!

More at this thread (Old JA+ exploit)

 

"When you assign dynamic memory as a buffer for anything, be sure to free the damn memory when you are done with it!"

Otherwise, if that function is called enough, the memory pool will overflow and crash the server - Not a good thing at all!

For the most part, it's not a problem..but if us mod authors are allocating memory for whatever reason, free it!

 

I don't see why it's that poor. It's just lazy modders who are used to languages with memory management systems. Being in C, Q3/JKA figure you are going to clean up your own messes :)

Link to post
Share on other sites

There's a potential infinite loop in G_RadiusDamage

 

Look for..

		if ( dist >= radius ) {
		continue;
	}

 

After it, add..

		if(ent->health <= 0)
		continue;

 

AFAIK this shouldn't be a problem unless you've added an entity that deals out radius damage and can be destroyed itself.

Link to post
Share on other sites

SKIP TO BOTTOM...

Because of the way chat strings are handled and sent out to each client, players can prepend something to the start of their name to make their chat text appear in the alert area (You know, where it says 'x was y by z' in the top-left)

 

Easy way to test this is doing '/name .*Blah' and saying anything.

 

You have a few choices on what you can do..

You can check on every change of their userinfo string, or do something simple and nicer such as this..

 

In G_SayTo (g_cmds.c) just add in this check before the trap_SendServerCommand call..

	for (i=0; i<strlen(name); i++)
{
	if (name[i] == '.')
		continue;
	if (name[i] == '*' && name[i-1] == '.')
		return;
	break;
}

 

That will successfully stop them from saying anything if they're trying to use this (minor) exploit.

Other things you can do is alter 'name' and remove that character so they can chat but it won't appear in the alert area.

You could also warn them, change their name, kick them, silence them...whatever you want.

I suppose you can adapt that check to work in ClientUserinfoChanged (g_client.c) if you want.

 

Happy coding. =]

 

It appears you can also use '/name **Blah' or whatever, so this 'fix' is useless.

I'll patch this another way some day, unless someone would like to try...

Edited by -=*Raz0r*=-
Link to post
Share on other sites
It appears any cvar that is part of userinfo is susceptible to being too large and thus making the userinfo string bigger than 1024 (which would likely cause the IP string and others too may not then be retrieved with trap_GetUserinfo(...) as it's not there because it was chopped off).

 

This can be a hazard because then ban checks cannot be performed. >.<

As mentioned after, we can check that the value does exist, and ban if not.

 

 

Fix: Well Luigi has a Windows only patch for it but I haven't heard it tested with q3 and its not supported by Linux.
I'm remember hearing there's a side-effect to that 'fix'

 

 

Check that there is indeed \ip\ in the string (You wouldn't want to also check for the value I guess because the value is lost after first connect.)
For those wondering how to do this, it's rather simple...

Head over to ClientConnect in g_client.c

 

Declare a variable like so:

char TmpIP[32] = {0};

 

Adapt some code early on in the function so it looks like this:

	// check to see if they are on the banned IP list
value = Info_ValueForKey (userinfo, "ip");
if (!isBot)
	Q_strncpyz( TmpIP, value, sizeof(TmpIP) ); // Used later
if ( G_FilterPacket( value ) ) {
	return "Banned";
}

 

Then after the G_ReadSessionData call, chuck in:

	if (firstTime && !isBot)
{
	if(!TmpIP[0])
	{// No IP sent when connecting, probably an unban hack attempt
		client->pers.connected = CON_DISCONNECTED;
		return "Invalid userinfo detected";
	}
	Q_strncpyz(client->sess.IP, TmpIP, sizeof(client->sess.IP));
}

 

You can then use client->sess.IP anywhere in the gameside code for whatever reason.

 

 

Another way to prevent q3infoboom would be to patch the engine.

I'm not allowed to 'release' the fix, but it involves hooking SV_ConnectionlessPacket and checking lengths..

Edited by -=*Raz0r*=-
Link to post
Share on other sites

5 in a row!

 

Some of you know of the 'JA Haxxor Toolkit' and its features..

Well, one of these features is a multi-lined name (You can also make it look like someone else said something)

 

So, an effective way to combat this? Simple.

Adapt your Info_Validate to look like this...

static const char badChars[] = { '\n', '\r', '\"', ';' };
qboolean Info_Validate( const char *s ) {
int i = 0;
for (i=0; i<sizeof(badChars); i++)
	if ( strchr( s, badChars[i] ) )
		return qfalse;
return qtrue;
}

 

That should effectively remove carriage returns, line breaks, semicolons and quotation marks from any field in the userinfo string (Client names are kept in their userinfo string)

 

EDIT: Silly me, you should also perform this check in the say function (G_Say or something in g_cmds.c)

 

EDIT: I suppose the semi-logical thing would be to remove all instances of those characters in the string, and afterwards check if there are any characters remaining in the string (To prevent a free method of getting a blank name/etc)

Edited by -=*Raz0r*=-
Link to post
Share on other sites
  • 1 year later...

There's a missing "firing" animation for the concussion rifle.

 

In bg_misc.c

 

int WeaponAttackAnim[WP_NUM_WEAPONS] =
{
BOTH_ATTACK1,//WP_NONE, //(shouldn't happen)

BOTH_ATTACK3,//WP_STUN_BATON,
BOTH_ATTACK3,//WP_MELEE,
BOTH_STAND2,//WP_SABER, //(has its own handling)
BOTH_ATTACK2,//WP_BRYAR_PISTOL,
BOTH_ATTACK3,//WP_BLASTER,
BOTH_ATTACK3,//BOTH_ATTACK4,//WP_DISRUPTOR,
BOTH_ATTACK3,//BOTH_ATTACK5,//WP_BOWCASTER,
BOTH_ATTACK3,//BOTH_ATTACK6,//WP_REPEATER,
BOTH_ATTACK3,//BOTH_ATTACK7,//WP_DEMP2,
BOTH_ATTACK3,//BOTH_ATTACK8,//WP_FLECHETTE,
BOTH_ATTACK3,//BOTH_ATTACK9,//WP_ROCKET_LAUNCHER,
BOTH_THERMAL_THROW,//WP_THERMAL,
BOTH_ATTACK3,//BOTH_ATTACK11,//WP_TRIP_MINE,
BOTH_ATTACK3,//BOTH_ATTACK12,//WP_DET_PACK,
BOTH_ATTACK2,//WP_BRYAR_OLD,

//NOT VALID (e.g. should never really be used):
BOTH_STAND1,//WP_EMPLACED_GUN,


BOTH_ATTACK1//WP_TURRET,
};

 

Replace with

 

int WeaponAttackAnim[WP_NUM_WEAPONS] =
{
BOTH_ATTACK1,//WP_NONE, //(shouldn't happen)

BOTH_ATTACK3,//WP_STUN_BATON,
BOTH_ATTACK3,//WP_MELEE,
BOTH_STAND2,//WP_SABER, //(has its own handling)
BOTH_ATTACK2,//WP_BRYAR_PISTOL,
BOTH_ATTACK3,//WP_BLASTER,
BOTH_ATTACK3,//BOTH_ATTACK4,//WP_DISRUPTOR,
BOTH_ATTACK3,//BOTH_ATTACK5,//WP_BOWCASTER,
BOTH_ATTACK3,//BOTH_ATTACK6,//WP_REPEATER,
BOTH_ATTACK3,//BOTH_ATTACK7,//WP_DEMP2,
BOTH_ATTACK3,//BOTH_ATTACK8,//WP_FLECHETTE,
BOTH_ATTACK3,//BOTH_ATTACK9,//WP_ROCKET_LAUNCHER,
BOTH_THERMAL_THROW,//WP_THERMAL,
BOTH_ATTACK3,//BOTH_ATTACK11,//WP_TRIP_MINE,
BOTH_ATTACK3,//BOTH_ATTACK12,//WP_DET_PACK,
BOTH_ATTACK3,//WP_CONCUSSION, //Raz: Fixed bryar pistol animation
BOTH_ATTACK2,//WP_BRYAR_OLD,

//NOT VALID (e.g. should never really be used):
BOTH_STAND1,//WP_EMPLACED_GUN,


BOTH_ATTACK1//WP_TURRET,
};

 

This array is shared by the server and client, so depending on your use case scenario, you may want to override this "fix" to avoid prediction errors (i.e the server "correcting" your animation halfway through the sequence)

 

In my case, I am developing a cross-compatible server-side and client-side mod (separately) as an alternative to JA+

I check the serverinfo for "gamename" inside CG_ParseServerinfo, and work out which mod the server is running.

 

After that, modify PM_Weapon to look like this

 

		#ifndef QAGAME
		//Raz: Hacky fix here
		int weapon = pm->ps->weapon;
		if ( cg.mod != SMOD_JAPP && (pm->ps->weapon == WP_CONCUSSION || pm->ps->weapon == WP_BRYAR_OLD) )
			weapon++;
		PM_StartTorsoAnim( WeaponAttackAnim[weapon] );
	#else
		PM_StartTorsoAnim( WeaponAttackAnim[pm->ps->weapon] );
	#endif

I have not yet written the code to set the "correct" animation depending on the client's mod. Plugin sniffing is an ugly area.

Edited by -=*Raz0r*=-
Link to post
Share on other sites

There is a bug in jamp.exe where connecting to an invalid hostname or IP whilst ingame will shove you out to a black screen, unable to do anything (including open your console). A very ugly situation.

 

The function in question is CL_Connect_f (0x41D990)

The code in question is:

	//Taken from q3
if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) {
	Com_Printf ("Bad server address\n");
	cls.state = CA_DISCONNECTED;
	return;
}

At the very least, it should be setting your connection state to CA_CONNECTING

 

Ideally, you would rewrite this function to have a different code path if you attempt to connect to a bad hostname or IP whilst ingame.

Another solution, is to patch the opcode setting cls.state to CA_DISCONNECTED

 

You will have to unlock the code page with VirtualProtect or mprotect

I suggest writing a wrapper.

 

UnlockMemory( 0x41DACB, 1 );
*(unsigned char *)0x41DACB = (unsigned char)0x03;
LockMemory( 0x41DACB, 1 );

 

At the moment this fix is only for Windows, but it is possible to fix on Mac with the right addresses.

 

With this fix, you will be sent to the "Connecting to someinvalidhostname...1" screen and things will carry on as normal. Not ideal, but it works.

Link to post
Share on other sites

Another bug in jamp.exe where you can't use the Alt + Enter combination to toggle fullscreen. This is intended behaviour, but Raven(?) used the wrong connection state again.

 

The function in question is MainWndProc (0x454880)

The code in question is:

    if ( com_r_fullscreen
     && cl_allowAltEnter
     && (cls_state == CA_DISCONNECTED || cls_state == CA_CONNECTED)
     && cl_allowAltEnter.integer) )
   {
     Cvar_SetValue( "r_fullscreen", (com_r_fullscreen.integer == 0) );
     Cbuf_AddText("vid_restart\n");
   }

 

CA_CONNECTED should actually be CA_ACTIVE ("game views should be displayed")

My fix, however, is slightly hacky but will allow the Alt + Enter combination on all connection states.

It still relies on cl_allowAltEnter being 1

 

UnlockMemory( 0x454B5A, 2 );
*(unsigned char *)0x454B5A = (unsigned char)0x90; //NOP opcode, skip over the instruction
*(unsigned char *)0x454B5B = (unsigned char)0x90; //NOP opcode, skip over the instruction
LockMemory( 0x454B5A, 2 );

 

No Mac fix as of yet. I am not sure if this is even applicable for Mac. I don't own one.

Link to post
Share on other sites

A well-known bug, where charged shots cause a dynamic light bug on players.

Easy fix, worth posting.

 

cg_ents.c, CG_EntityEffects

 

Adjust the if statement near the end to match the following:

	// constant light glow
if ( cent->currentState.constantLight && cent->currentState.eType != ET_PLAYER && cent->currentState.eType != ET_BODY ) {

Link to post
Share on other sites

Credit goes to Didz for finding/fixing this.

 

In MP, misc_model_static entities' model bounds loading code is incorrect leading to disappearing cliffs and stuff on maps such as t1_surprise and hoth2.

 

In cg_main.c, search for void CG_CreateModelFromSpawnEnt(cgSpawnEnt_t *ent)

 

About 44 lines after that inside the function, find:

	VectorScaleVector(mins, ent->scale, mins);
VectorScaleVector(maxs, ent->scale, maxs);

 

Replace these lines with:

	//[invalid Model Bounds Fix]
//VectorScaleVector(mins, ent->scale, mins);
//VectorScaleVector(maxs, ent->scale, maxs);
VectorScaleVector(mins, RefEnt->modelScale, mins);
VectorScaleVector(maxs, RefEnt->modelScale, maxs);
//[/invalid Model Bounds Fix]

Link to post
Share on other sites

Credit goes to Xycaleth for finding/fixing this.

 

 

In JKA, players can jump-crouch through some patches, where it's an angle. This fixes that problem.

 

In bg_pmove.c, find:

static void PM_CheckDuck (void)

Above that, add:

static qboolean PM_CanStand ( void )
{
   qboolean canStand = qtrue;
   float x, y;
   trace_t trace;

   const vec3_t lineMins = { -5.0f, -5.0f, -2.5f };
   const vec3_t lineMaxs = { 5.0f, 5.0f, 0.0f };

   for ( x = pm->mins[0] + 5.0f; canStand && x <= (pm->maxs[0] - 5.0f); x += 10.0f )
   {
       for ( y = pm->mins[1] + 5.0f; y <= (pm->maxs[1] - 5.0f); y += 10.0f )
       {
           vec3_t start = { x, y, pm->maxs[2] };
           vec3_t end = { x, y, pm->ps->standheight };

           VectorAdd (start, pm->ps->origin, start);
           VectorAdd (end, pm->ps->origin, end);

           pm->trace (&trace, start, lineMins, lineMaxs, end, pm->ps->clientNum, pm->tracemask);
	    if ( trace.allsolid || trace.fraction < 1.0f )
	    {
		    canStand = qfalse;
		    break;
	    }
       }
   }

   return canStand;
}

 

In the PM_CheckDuck function, find:

		else if (pm->ps->pm_flags & PMF_ROLLING)
	{
		// try to stand up
		pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2;
		pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask );
		if (!trace.allsolid)
			pm->ps->pm_flags &= ~PMF_ROLLING;
	}

Replace with:

		else if (pm->ps->pm_flags & PMF_ROLLING)
	{
           if ( PM_CanStand() )
           {
               pm->maxs[2] = pm->ps->standheight;
               pm->ps->pm_flags &= ~PMF_ROLLING;
           }
	}

Find:

		else
	{	// stand up if possible 
		if (pm->ps->pm_flags & PMF_DUCKED)
		{
			// try to stand up
			pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2;
			pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask );
			if (!trace.allsolid)
				pm->ps->pm_flags &= ~PMF_DUCKED;
		}
	}

Replace with:

		else
	{	// stand up if possible 
		if (pm->ps->pm_flags & PMF_DUCKED)
		{
               if ( PM_CanStand() )
            {
	            pm->maxs[2] = pm->ps->standheight;
	            pm->ps->pm_flags &= ~PMF_DUCKED;
            }
		}
	}

 

And...that should be it.

Link to post
Share on other sites
  • 1 month later...

ITEM_TYPE_EDITFIELD elements will leave insert/overstrike mode on in various occasions.

 

ui_shared.c -> Item_TextField_HandleKey

 

Replace

		if ( key == A_ENTER || key == A_KP_ENTER || key == A_ESCAPE)  {
		return qfalse;
	}

With

		if ( key == A_ENTER || key == A_KP_ENTER || key == A_ESCAPE || (key == A_MOUSE1 && !Rect_ContainsPoint(&item->window.rect, DC->cursorx, DC->cursory) ))  {
		DC->setOverstrikeMode( qfalse );
		return qfalse;
	}

Link to post
Share on other sites

ITEM_TYPE_LISTBOX elements (Server browser, hilt selection, etc) can't be scrolled.

Whether you consider this a bug or not is totally your call. I like to scroll through lists :D

 

ui_shared.c -> Item_ListBox_HandleKey

 

After

			if ( key == A_CURSOR_DOWN || key == A_KP_2 ) 
		{
			if (!listPtr->notselectable) {
				listPtr->cursorPos++;
				if (listPtr->cursorPos < listPtr->startPos) {
					listPtr->startPos = listPtr->cursorPos;
//JLF
#ifndef _XBOX
					return qfalse;
#endif
				}
				if (listPtr->cursorPos >= count) {
					listPtr->cursorPos = count-1;
					return qfalse;
				}
				if (listPtr->cursorPos >= listPtr->startPos + viewmax) {
					listPtr->startPos = listPtr->cursorPos - viewmax + 1;
				}
				item->cursorPos = listPtr->cursorPos;
				DC->feederSelection(item->special, item->cursorPos, NULL);
			}
			else {
				listPtr->startPos++;
				if (listPtr->startPos > max)
					listPtr->startPos = max;
			}
			return qtrue;
		}

Add

			if ( key == A_MWHEELUP ) 
		{
			listPtr->startPos -= ((int)item->special == FEEDER_Q3HEADS) ? viewmax : 1;
			if (listPtr->startPos < 0)
			{
				listPtr->startPos = 0;
				Display_MouseMove(NULL, DC->cursorx, DC->cursory);
				return qfalse;
			}
			Display_MouseMove(NULL, DC->cursorx, DC->cursory);
			return qtrue;
		}
		if ( key == A_MWHEELDOWN ) 
		{
			listPtr->startPos += ((int)item->special == FEEDER_Q3HEADS) ? viewmax : 1;
			if (listPtr->startPos > max)
			{
				listPtr->startPos = max;
				Display_MouseMove(NULL, DC->cursorx, DC->cursory);
				return qfalse;
			}
			Display_MouseMove(NULL, DC->cursorx, DC->cursory);
			return qtrue;
		}

 

 

Updated: Fixed for FEEDER_Q3HEADS to skip an entire row (16th October 2011)

Updated: return qfalse if there's no more to scroll, to prevent the sound from playing (10th November 2011)

Updated: Forcefully update the mouse position when scrolling so the proper listbox entry has focus (12th November 2011)

Edited by -=*Raz0r*=-
Link to post
Share on other sites

The name field in the profile customisation screen will not allow more than 26 characters, despite the actual limit being 36 characters.

Furthermore, overflowing this then changing your name results in some...odd behaviour.

 

The first part of this fix is in the ui/jamp/ingame_player.menu

Adjust this part

		itemDef 
	{
		name 				namefield
		type 				ITEM_TYPE_EDITFIELD
		style 				0
		text 				@MENUS_NAME1
		cvar 				"ui_Name"
		maxchars 			26

To match

		itemDef 
	{
		name 				namefield
		type 				ITEM_TYPE_EDITFIELD
		style 				0
		text 				@MENUS_NAME1
		cvar 				"ui_Name"
		maxchars 			35

36-1 characters to account for the null-terminator, if I am correct.

 

 

ui_main.c -> UI_Update

 

Replace

 	if (Q_stricmp(name, "ui_SetName") == 0) {
	trap_Cvar_Set( "name", UI_Cvar_VariableString("ui_Name"));
	} else if (Q_stricmp(name, "ui_setRate") == 0) {

With

 	if ( !Q_stricmp( name, "ui_SetName" ) )
{
	char buf[36] = { 0 };
	Q_strncpyz( buf, UI_Cvar_VariableString( "ui_Name" ), sizeof( buf ) );
	trap_Cvar_Set( "name", buf );
	}
else if (Q_stricmp(name, "ui_setRate") == 0) {

 

Replace

	else if (Q_stricmp(name, "ui_GetName") == 0) 
{
	trap_Cvar_Set( "ui_Name", UI_Cvar_VariableString("name"));
}

With

	else if ( !Q_stricmp( name, "ui_GetName" ) ) 
{
	char buf[36] = { 0 };
	Q_strncpyz( buf, UI_Cvar_VariableString( "name" ), sizeof( buf ) );
	trap_Cvar_Set( "ui_Name", buf );
}

 

 

ui_main.c -> _UI_Init

 

Replace

	trap_Cvar_Register(NULL, "ui_name", UI_Cvar_VariableString("name"), CVAR_INTERNAL );	//get this now, jic the menus change again trying to setName before getName

With

	{
	char buf[36] = { 0 };
	Q_strncpyz( buf, UI_Cvar_VariableString( "name" ), sizeof( buf ) );
	trap_Cvar_Register( NULL, "ui_Name", buf, CVAR_INTERNAL );
}

Link to post
Share on other sites

×
×
  • Create New...