there seems to be a bug in interpolation on playback of demos recorded from a localhost server (created through the create game ui option). if you watch one of these demos, especially in lower timescale, the motion seems jerky compared to normal remote-server demos. i've found this fix for it:
cg_snapshot.c function CG_ReadNextSnapshot
find the line
// FIXME: why would trap_GetSnapshot return a snapshot with the same server time
and change it to
// FIXME: why would trap_GetSnapshot return a snapshot with the same server time
if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) {
// because we're playing back demos taken by local servers apparently :O
if ( cg.demoPlayback ) continue;
//continue;
} :twogun:
setting forcepowers to something illegal can cause a crash after forcechanged and respawn.
Mod List Wraps strange sometimes. I will post screenshot when I get a chance.
npc command uses Com_Printf instead of sending a servercommand of print to the specified client thus when trying for syntax and not being the server while using /npc cmd it will only show up for the server.
For whatever reason, Raven chose to use strcpy and strncpy in a lot of places instead of Q_strncpyz. This can cause buffer overflows and overruns. There are only a few places i can think of that should be left. Ususally when the dest is a char *something, however a few can be still length checked only when you don't really know what length should be. Also, bg_vehicleLoad and q_shared really should be left alone.
Thanks to hex on this one also.
speed and rage don't work together to give you more speed like it did in jk2.
cause: raven used an if speed else if rage else ragerecovery
fix: revert it back (use a cvar though)
bg_pmove.c "BG_AdjustClientSpeed"
look for and replace this code:
if (ps->fd.forcePowersActive & (1 << FP_SPEED))
{
ps->speed *= 1.7;
}
else if (ps->fd.forcePowersActive & (1 << FP_RAGE))
{
ps->speed *= 1.3;
}
else if (ps->fd.forceRageRecoveryTime > svTime)
{
ps->speed *= 0.75;
}
with:
if (ps->fd.forcePowersActive & (1 << FP_SPEED))
{
ps->speed *= 1.7;
}
if (ps->fd.forcePowersActive & (1 << FP_RAGE))
{
ps->speed *= 1.3;
}
else if (ps->fd.forceRageRecoveryTime > svTime)
{
ps->speed *= 0.75;
}
The client-side will be needed for this for best performance.
If you haven't respawned into temp spectator mode and in siege, you can still use your holdable items because they are not cleared :doh:
g_combat.c "player_die"
look for these lines:
// remove powerups
memset( self->client->ps.powerups, 0, sizeof(self->client->ps.powerups) );
below it add:
self->client->ps.stats[STAT_HOLDABLE_ITEMS] = 0;
self->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
setting forcepowers to something illegal can cause a crash after forcechanged and respawn.
This is caused by a malformed forcepower string. To fix this we need to take any garbage the client sends us and turn it into a real forcepower string. A real forcepower string looks like #-#-##################. To do this we try to read anything the client gives us and then we fill in the blanks.
Add the following somewhere:
void FR_NormalizeForcePowers(char *powerOut, int powerLen)
{
char powerBuf[128];
char readBuf[2];
int finalPowers[21] = ;
int i, c;
if (powerLen >= 128 || powerLen < 1)
{ //This should not happen. If it does, this is obviously a bogus string.
//They can have this string. Because I said so.
strcpy(powerBuf, "7-1-032330000000001333");
}
else
{
Q_strncpyz(powerBuf, powerOut, sizeof(powerBuf)); //copy it as the original
}
c = 0;
i = 0;
while (i < powerLen && i < 128 && powerBuf[i] && powerBuf[i] != '\n' && powerBuf != '\0' && c < NUM_FORCE_POWERS+2)
{
if (powerBuf[i] != '-')
{
readBuf[0] = powerBuf[i];
readBuf[1] = 0;
finalPowers[c] = atoi(readBuf);
c++;
}
i++;
}
strcpy(powerOut, va("%i-%i-%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i%i\0",
finalPowers[0], finalPowers[1], finalPowers[2],
finalPowers[3], finalPowers[4], finalPowers[5],
finalPowers[6], finalPowers[7], finalPowers[8],
finalPowers[9], finalPowers[10], finalPowers[11],
finalPowers[12], finalPowers[13], finalPowers[14],
finalPowers[15], finalPowers[16], finalPowers[17],
finalPowers[18], finalPowers[19], finalPowers[20]));
}
In g_client.c, look for:
Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) );
replace with:
if (!(ent->r.svFlags & SVF_BOT))
{ // clients could have a bad forcepower string
char *n;
n = Info_ValueForKey (userinfo, "forcepowers");
FR_NormalizeForcePowers(n, strlen(n));
strcpy( forcePowers, n );
}
else
{
Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) );
}
in w_force.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 ) );
}
**EDIT** Fixed to correct bot name issues.
This works for that except for when certain bots like the basejka bots do not have a funname defined or if a player is /name "" it will set their name to 0-0-000000000000000000
Hmm, I see.. Interesting side effect.
That may require some modification to how names are checked, and bots names are stored. I will update the fix as soon as I have some time to run tests.
Or, it could be *s being null so the forcepower info is getting placed in there.
I fixed it so it doesn't mess up bot names. I couldn't reproduce the client name being set to "" bug.
K, applied the fix. i will test the blank name thing again to see.
Also i would suggest using Q_strncpyz instead of strcpy in these situations:
strcpy(powerBuf, "7-1-032330000000001333"); => Q_strncpyz(powerBuf, "7-1-032330000000001333", sizeof(powerBuf));
strcpy(powerBuf, powerOut); //copy it as the original => Q_strncpyz(powerBuf, powerOut, sizeof(powerBuf)); //copy it as the original
strcpy( forcePowers, n ); => Q_strncpyz( forcePowers, n, sizeof(forcePowers) );
strcpy( forcePowers, s ); => Q_strncpyz( forcePowers, s, sizeof(forcePowers) );
I just overloaded my strcpy functions to use a safe bounds checking version.
Issue with charging weapons max charge time not being checked if you hold the use item key. Working on a stable fix.
Here's a really old bug fix that CerburuS told me about ages ago. However, I never actually tried it so your mileage may vary. I'll give this a shot when I have time as well.
just wanted to let you know that there's a bug in basejka which disables Areaportals in Siege permanently if that Areaportal is "open" when the round is being restarted (i.e. the Areaportal will not work at all in the next round, causing bad FPS for the players).
In basejka Siege it's obviously not as much of an issue as it is in MBII since there's only one round restart per map at max (opposed to 20+ in MBII). Thought you might be interested nonetheless...
I've written a relatively simple fix for this problems - could forward to you if you want. It'd need a small modification for basejka though since I'm using a MBII-specific value to determine whether the server is currently in round transition.
Well, area portals should be used quite frequently in Siege maps - I imagine that siege_desert for example uses quite a few. The only one I know for sure is located at the Command Center doors at siege_hoth. They should be easy to detect via /r_showtris 1 though.
//fix for self-deactivating areaportals in Siege
if ( ent->s.eType == ET_MOVER && g_gametype.integer == GT_SIEGE && level.MBIntermission)
{
if ( !Q_stricmp("func_door", ent->classname) && ent->moverState != MOVER_POS1 )
{
SetMoverState( ent, MOVER_POS1, level.time );
if ( ent->teammaster == ent || !ent->teammaster )
{
trap_AdjustAreaPortalState( ent, qfalse );
}
//stop the looping sound
ent->s.loopSound = 0;
ent->s.loopIsSoundset = qfalse; }
continue;
}
This is the code I inserted in g_main.c (around line 4169 in the MB version). I'd just send you the file but since my comp just got fried 2 hours ago, this would take a while ;) Basically you should be able to insert it anywhere in the G_RunFrame function.
Basically it closes any door that is not closed (i.e. opening, closing, or open) and activates the areaportal again. According to our beta testers, it works pretty well so far. If you can think of a better way to do this though, just let me know :)
The only thing that will need adapting is the level.MBintermission value I'm using since that one's MB specific.
Hope it's at least of some help.
EDIT: Ensiform has come up with code to fix this bug for basejka. Look down in this thread for it.
There is a bug with saber blocking not working at all if you die while your saber is knocked out of your hands. I am currently working on a stable fix.
For those not able to understand how the areaportal fix works with non-MB2 code open up g_main.c and look for the following piece of code:
if ( ent->s.eType == ET_MOVER ) {
G_RunMover( ent );
continue;
}
below it add:
//fix for self-deactivating areaportals in Siege
if ( ent->s.eType == ET_MOVER && g_gametype.integer == GT_SIEGE && level.intermissiontime)
{
if ( !Q_stricmp("func_door", ent->classname) && ent->moverState != MOVER_POS1 )
{
SetMoverState( ent, MOVER_POS1, level.time );
if ( ent->teammaster == ent || !ent->teammaster )
{
trap_AdjustAreaPortalState( ent, qfalse );
}
//stop the looping sound
ent->s.loopSound = 0;
ent->s.loopIsSoundset = qfalse;
}
continue;
}
The following appears to fix the bug with saber collision after you die when your saber was knocked away.
In g_combat.c, function player_die
Find:
self->client->ps.saberEntityNum = self->client->saberStoredIndex; //in case we died while our saber was knocked away.
After, add:
if (self->client->ps.weapon == WP_SABER && self->client->saberKnockedTime)
{
gentity_t *saberEnt = &g_entities[self->client->ps.saberEntityNum];
//G_Printf("DEBUG: Running saber cleanup for %s\n", self->client->pers.netname);
self->client->saberKnockedTime = 0;
saberReactivate(saberEnt, self);
saberEnt->r.contents = CONTENTS_LIGHTSABER;
saberEnt->think = saberBackToOwner;
saberEnt->nextthink = level.time;
G_RunObject(saberEnt);
}
nice find ;)
btw, don't forget to extern saberReactivate and saberBackToOwner above player_die.
i'd say leave the changes that raven made to speed + rage intact... it would way unbalance darkside =S
well if someone chooses to add it they can make a toggle then like hex and i have. :nervou
no, a toggle for jk2 style speed+rage stacking or jka not allow stacking.
note: the weapon charge thing with use holdable doesnt seem to be fixed yet so ignore that for right now.
I suggest you delete the post or update it so people will know if reading from the start of the thread.
One really should not be able to return their flag from inside a vehicle since you cannot capture or pickup the enemy flag :sithm:
Fixed the bug with using alt fire and then hitting use holdable to hold your charge.
in bg_pmove.c in the function PmoveSingle:
after:
pm = pmove;
add:
if (pm->cmd.buttons & BUTTON_ATTACK && pm->cmd.buttons & BUTTON_USE_HOLDABLE)
{ pm->cmd.buttons &= ~BUTTON_ATTACK;
pm->cmd.buttons &= ~BUTTON_USE_HOLDABLE;}
if (pm->cmd.buttons & BUTTON_ALT_ATTACK && pm->cmd.buttons & BUTTON_USE_HOLDABLE)
{ pm->cmd.buttons &= ~BUTTON_ALT_ATTACK;
pm->cmd.buttons &= ~BUTTON_USE_HOLDABLE;}
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!
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 ) ) );
}
}
Alternate fix for a dead body's color didn't match the player's color in team games. The fix offered by Enisform (
http://www.lucasforums.com/showpost.php?p=1698122&postcount=12) 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;Addif ( 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.
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.
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;
}
//
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;
}
//
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.
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!
So, "where" is the server side equivilient. I suppose they're a little different due to the view position not being the same as the model origin.
Say, for the "remapShader" exploit fix, why isn't the third argument protected as well? Is it not possible to overflow that one?
ї? 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
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
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;
}
}
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.
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 );
}
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;
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".
That's strange, I thought my fix made it so that the mover just instantly kills anything blocking it if it gets jammed...
Well I just looked and that specific mover itself is not set to be a crusher or do damage so it really wouldn't do much to players.
Point taken. Go ahead and impliment it for OJP if you want. :)
Will do :)
I may implement my other fix for the detpacks/trips/thermals and a person changing teams or leaving server if that is alright with you.
Sure, go ahead. You don't need pre-approval for minor bugfixes. I just mainly want to know about things that majorly rewrite gameplay/code beforehand. :)
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.
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 :)
This is an interesting theory, and I really didn't think mine was going to fix it in the first place, though it does force spectators off that vehicle it still isn't right. Good find Soh Raun.
Nice find Sol Raun. Welcome to the community.