Means if you try to check any of the other force power values, you get 0 regardless of what it would be on server.
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.)
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.
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.
I think you only need to do proper checks in BG_LegalizedForcePowers.
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?
qboolean BG_LegalizedForcePowers(char *powerOut, int maxRank, qboolean freeSaber, int teamForce, int gametype, int fpDisabled)
{
char powerBuf[128] = ;
char readBuf[128] = ;
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];
//
//blank out the final_Powers array in case we get garbage in powerOut.
memset(final_Powers, 0, sizeof(final_Powers));
//
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;
//
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)
//
{
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];
//
if(countDown > 3) {
return qfalse; //-1
}
//
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.
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).
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 (
http://www.japlus.net/phpBB2/viewtopic.php?p=8432#8432) (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!
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 (
http://www.japlus.net/phpBB2/viewtopic.php?p=8432#8432) (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 :)
Nothing calling G_Alloc is freeing that memory - that's rather poor to me.
I myself just use malloc and free it - and after all, why shouldn't we =]?
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.
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[i]' 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...
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] = ;
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..
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)
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.
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 (
http://msdn.microsoft.com/en-us/library/aa366898(v=vs.85).aspx) or mprotect (
http://linux.die.net/man/2/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.
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.
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 ) {
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]
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.
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;
}
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)
The name field in the profile customisation screen (
http://i499.photobucket.com/albums/rr357/amraz0r/c1062ec0.jpg) 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 );
}
There's an infinite loop in Cmd_FollowCycle_f (g_cmds.c) that can easily be used to attack an unpatched server.
To reproduce the infinite loop:
Join a server (Preferrably FFA?) where there are no in-game players (Spectators are fine)
/team follow1
Click to cycle through clients
Replace it with this version, also containing this (
http://www.lucasforums.com/showthread.php?p=2142829#post2142829) fix
void Cmd_FollowCycle_f( gentity_t *ent, int dir ) {
int clientnum;
int original;
qboolean looped = qfalse;
// if they are playing a tournement game, count as a loss
if ( (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL)
&& ent->client->sess.sessionTeam == TEAM_FREE ) {\
//WTF???
ent->client->sess.losses++;
}
// first set them to spectator
if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) {
SetTeam( ent, "spectator" );
}
if ( dir != 1 && dir != -1 ) {
G_Error( "Cmd_FollowCycle_f: bad dir %i", dir );
}
clientnum = ent->client->sess.spectatorClient;
original = clientnum;
do {
clientnum += dir;
if ( clientnum >= level.maxclients )
{
//Raz: Avoid /team follow1 crash
if ( looped )
{
clientnum = original;
break;
}
else
{
clientnum = 0;
looped = qtrue;
}
}
if ( clientnum < 0 ) {
if ( looped )
{
clientnum = original;
break;
}
else
{
clientnum = level.maxclients - 1;
looped = qtrue;
}
}
// can only follow connected clients
if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) {
continue;
}
// can't follow another spectator
if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) {
continue;
}
//ensiform's fix
// can't follow another spectator
if ( level.clients[ clientnum ].tempSpectate >= level.time ) {
return;
}
// this is good, we can use it
ent->client->sess.spectatorClient = clientnum;
ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
return;
} while ( clientnum != original );
// leave it where it was
}
I'm sure the loop could be written more elegantly, but this will suffice.
Prediction error after spectating somebody who is on an ET_MOVER then switching to roaming mode.
Modify the start of CG_AdjustPositionForMover (cg_ents.c) to match:
void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) {
centity_t *cent;
vec3_t oldOrigin, origin, deltaOrigin;
vec3_t oldAngles, angles, deltaAngles;
if ( cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR )
{//Don't bother if we're a spectator
VectorCopy( in, out );
return;
}
Weird behaviour in UI code causes onOpen {} events to be triggered on the parent item of the item you're closing (via out-of-bounds click)
Example: Ingame menu -> click "About" -> Click "Setup" -> the onOpen event of the "ingame" menu would be fired.
This is only an issue if you have your own menus that use these events. Personally I'm using transitions in all my menus, which were being triggered at the wrong time.
In ui_shared.c -> Menus_HandleOOBClick
Comment out "Menus_Activate(&Menus[i]);" in the for loop.
for (i = 0; i < menuCount; i++) {
if (Menu_OverActiveItem(&Menus[i], DC->cursorx, DC->cursory)) {
Menu_RunCloseScript(menu);
menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE);
// Menus_Activate(&Menus[i]);
Menu_HandleMouseMove(&Menus[i], DC->cursorx, DC->cursory);
Menu_HandleKey(&Menus[i], key, down);
}
}
JA's font width calculation is horrible broken, especially when using abnormal (anything but 1.0) scales.
The issue is in the engine, RE_Font_StrLenPixels uses integer precision for the return value, causing visible data loss.
The way I'm currently working around this is by replacing trap_R_Font_StrLenPixels with:
float trap_R_Font_StrLenPixels(const char *text, const int iFontIndex, const float scale)
{
//Raz: HACK! RE_Font_StrLenPixels only works semi-correctly with 1.0f scale
float width = (float)syscall( CG_R_FONT_STRLENPIXELS, text, iFontIndex, PASSFLOAT(1.0f));
return width * scale;
}
Note I'm using a float return value - you'll have to adjust function prototypes and usage all around the code to use floats for best results.
I also use a large custom font to help with scaling.
This is not a fix, but it does help tremendously when trying to center-align text in both cgame and ui
Theoretically you could replace all text rendering with your own OpenGL code and use the bmfont library. If you wanted to D:
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 (
http://msdn.microsoft.com/en-us/library/aa366898(v=vs.85).aspx) or mprotect (
http://linux.die.net/man/2/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.
This fix is actually wrong.
The problem is raven fail coding in the UI:
(Main menu is never actually loaded because you were in the server last which will tell UI_Init to be (qtrue) on ingameload so it will only load ingame related menus. (This is fine however)
When it goes to try to revert back to main menu with setactivemenu UIMENU_MAIN:
if (uiInfo.inGameLoad)
{
// UI_LoadNonIngame();
}
because of this, it fails to actually "activate" main.