Index: src/server/sv_client.c =================================================================== --- src/server/sv_client.c (revision 823) +++ src/server/sv_client.c (working copy) @@ -460,7 +460,11 @@ sharedEntity_t *ent; Com_DPrintf( "Going from CS_PRIMED to CS_ACTIVE for %s\n", client->name ); + client->state = CS_ACTIVE; + // resend all configstrings using the cs commands since these are + // no longer sent when the client is CS_PRIMED + SV_UpdateConfigstrings( client ); // set up the entity for the client clientNum = client - svs.clients; Index: src/server/server.h =================================================================== --- src/server/server.h (revision 823) +++ src/server/server.h (working copy) @@ -169,6 +169,7 @@ netchan_buffer_t **netchan_end_queue; int oldServerTime; + qboolean csupdated[MAX_CONFIGSTRINGS+1]; } client_t; //============================================================================= @@ -227,6 +228,8 @@ extern cvar_t *sv_rconPassword; extern cvar_t *sv_privatePassword; extern cvar_t *sv_allowDownload; +extern cvar_t *sv_wwwDownload; +extern cvar_t *sv_wwwBaseURL; extern cvar_t *sv_maxclients; extern cvar_t *sv_privateClients; @@ -272,6 +275,7 @@ // void SV_SetConfigstring( int index, const char *val ); void SV_GetConfigstring( int index, char *buffer, int bufferSize ); +void SV_UpdateConfigstrings( client_t *client ); void SV_SetUserinfo( int index, const char *val ); void SV_GetUserinfo( int index, char *buffer, int bufferSize ); Index: src/server/sv_init.c =================================================================== --- src/server/sv_init.c (revision 823) +++ src/server/sv_init.c (working copy) @@ -23,15 +23,62 @@ #include "server.h" + /* =============== +SV_SendConfigstring +=============== +*/ +static void SV_SendConfigString(client_t *client, int index) +{ + int maxChunkSize = MAX_STRING_CHARS - 24; + int len; + + if(!sv.configstrings[index][0]) + return; + + len = strlen(sv.configstrings[index]); + + if( len >= maxChunkSize ) { + int sent = 0; + int remaining = len; + char *cmd; + char buf[MAX_STRING_CHARS]; + + while (remaining > 0 ) { + if ( sent == 0 ) { + cmd = "bcs0"; + } + else if( remaining < maxChunkSize ) { + cmd = "bcs2"; + } + else { + cmd = "bcs1"; + } + Q_strncpyz( buf, &sv.configstrings[index][sent], + maxChunkSize ); + + SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, + index, buf ); + + sent += (maxChunkSize - 1); + remaining -= (maxChunkSize - 1); + } + } else { + // standard cs, just send it + SV_SendServerCommand( client, "cs %i \"%s\"\n", index, + sv.configstrings[index] ); + } +} + +/* +=============== SV_SetConfigstring =============== */ void SV_SetConfigstring (int index, const char *val) { int len, i; - int maxChunkSize = MAX_STRING_CHARS - 24; client_t *client; if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { @@ -57,48 +104,41 @@ // send the data to all relevent clients for (i = 0, client = svs.clients; i < sv_maxclients->integer ; i++, client++) { - if ( client->state < CS_PRIMED ) { + if ( client->state < CS_ACTIVE ) { + client->csupdated[index] = qtrue; continue; } // do not always send server info to all clients if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { continue; } + len = strlen( val ); - if( len >= maxChunkSize ) { - int sent = 0; - int remaining = len; - char *cmd; - char buf[MAX_STRING_CHARS]; + SV_SendConfigString(client, index); + } + } +} - while (remaining > 0 ) { - if ( sent == 0 ) { - cmd = "bcs0"; - } - else if( remaining < maxChunkSize ) { - cmd = "bcs2"; - } - else { - cmd = "bcs1"; - } - Q_strncpyz( buf, &val[sent], maxChunkSize ); +void SV_UpdateConfigstrings(client_t *client) +{ + int index; - SV_SendServerCommand( client, "%s %i \"%s\"\n", cmd, index, buf ); + for( index = 0; index <= MAX_CONFIGSTRINGS; index++ ) { + // if the CS hasn't changed since we went to CS_PRIMED, ignore + if(!client->csupdated[index]) + continue; - sent += (maxChunkSize - 1); - remaining -= (maxChunkSize - 1); - } - } else { - // standard cs, just send it - SV_SendServerCommand( client, "cs %i \"%s\"\n", index, val ); - } + // do not always send server info to all clients + if ( index == CS_SERVERINFO && client->gentity && + (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { + continue; } + SV_SendConfigString(client, index); } } - /* =============== SV_GetConfigstring @@ -561,6 +601,10 @@ sv_zombietime = Cvar_Get ("sv_zombietime", "2", CVAR_TEMP ); sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO); + sv_wwwDownload = Cvar_Get ("sv_wwwDownload", "1", + CVAR_SYSTEMINFO|CVAR_ARCHIVE); + sv_wwwBaseURL = Cvar_Get ("sv_wwwBaseURL", "", + CVAR_SYSTEMINFO|CVAR_ARCHIVE); sv_master[0] = Cvar_Get ("sv_master1", MASTER_SERVER_NAME, 0 ); sv_master[1] = Cvar_Get ("sv_master2", "", CVAR_ARCHIVE ); sv_master[2] = Cvar_Get ("sv_master3", "", CVAR_ARCHIVE ); Index: src/server/sv_main.c =================================================================== --- src/server/sv_main.c (revision 823) +++ src/server/sv_main.c (working copy) @@ -33,6 +33,8 @@ cvar_t *sv_rconPassword; // password for remote server commands cvar_t *sv_privatePassword; // password for the privateClient slots cvar_t *sv_allowDownload; +cvar_t *sv_wwwBaseURL; +cvar_t *sv_wwwDownload; cvar_t *sv_maxclients; cvar_t *sv_privateClients; // number of clients reserved for password @@ -129,6 +131,9 @@ // return; // } + if( client->state < CS_ACTIVE ) + return; + client->reliableSequence++; // if we would be losing an old command that hasn't been acknowledged, // we must drop the connection @@ -186,9 +191,6 @@ // send the data to all relevent clients for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) { - if ( client->state < CS_PRIMED ) { - continue; - } SV_AddServerCommand( client, (char *)message ); } } Index: src/game/bg_misc.c =================================================================== --- src/game/bg_misc.c (revision 823) +++ src/game/bg_misc.c (working copy) @@ -67,7 +67,8 @@ qfalse, //qboolean creepTest; ASPAWN_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qfalse, //qboolean replacable; }, { BA_A_BARRICADE, //int buildNum; @@ -102,7 +103,8 @@ qtrue, //qboolean creepTest; BARRICADE_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qfalse, //qboolean replaceable; }, { BA_A_BOOSTER, //int buildNum; @@ -137,7 +139,8 @@ qtrue, //qboolean creepTest; BOOSTER_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qtrue, //qboolean replacable; }, { BA_A_ACIDTUBE, //int buildNum; @@ -172,7 +175,8 @@ qtrue, //qboolean creepTest; ACIDTUBE_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qfalse, //qboolean replacable; }, { BA_A_HIVE, //int buildNum; @@ -207,7 +211,8 @@ qtrue, //qboolean creepTest; HIVE_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qfalse, //qboolean replacable; }, { BA_A_TRAPPER, //int buildNum; @@ -242,7 +247,8 @@ qtrue, //qboolean creepTest; TRAPPER_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qfalse, //qboolean replacable; }, { BA_A_OVERMIND, //int buildNum; @@ -277,7 +283,8 @@ qfalse, //qboolean creepTest; OVERMIND_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qtrue //qboolean reactorTest; + qtrue, //qboolean reactorTest; + qtrue, //qboolean replacable; }, { BA_A_HOVEL, //int buildNum; @@ -312,7 +319,8 @@ qtrue, //qboolean creepTest; HOVEL_CREEPSIZE, //int creepSize; qfalse, //qboolean dccTest; - qtrue //qboolean reactorTest; + qtrue, //qboolean reactorTest; + qfalse, //qboolean replacable; }, { BA_H_SPAWN, //int buildNum; @@ -347,7 +355,8 @@ qfalse, //qboolean creepTest; 0, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qfalse, //qboolean replacable; }, { BA_H_MEDISTAT, //int buildNum; @@ -382,7 +391,8 @@ qfalse, //qboolean creepTest; 0, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qtrue, //qboolean replacable; }, { BA_H_MGTURRET, //int buildNum; @@ -419,7 +429,8 @@ qfalse, //qboolean creepTest; 0, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qfalse, //qboolean replacable; }, { BA_H_TESLAGEN, //int buildNum; @@ -454,7 +465,8 @@ qfalse, //qboolean creepTest; 0, //int creepSize; qtrue, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qfalse, //qboolean replacable; }, { BA_H_DCC, //int buildNum; @@ -489,7 +501,8 @@ qfalse, //qboolean creepTest; 0, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qtrue, //qboolean replacable; }, { BA_H_ARMOURY, //int buildNum; @@ -524,7 +537,8 @@ qfalse, //qboolean creepTest; 0, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qtrue, //qboolean replacable; }, { BA_H_REACTOR, //int buildNum; @@ -559,7 +573,8 @@ qfalse, //qboolean creepTest; 0, //int creepSize; qfalse, //qboolean dccTest; - qtrue //qboolean reactorTest; + qtrue, //qboolean reactorTest; + qtrue, //qboolean replacable; }, { BA_H_REPEATER, //int buildNum; @@ -594,7 +609,8 @@ qfalse, //qboolean creepTest; 0, //int creepSize; qfalse, //qboolean dccTest; - qfalse //qboolean reactorTest; + qfalse, //qboolean reactorTest; + qtrue, //qboolean replacable; } }; @@ -1290,6 +1306,25 @@ /* ============== +BG_FindReplaceableTestForBuildable +============== +*/ +qboolean BG_FindReplaceableTestForBuildable( int bclass ) +{ + int i; + + for( i = 0; i < bg_numBuildables; i++ ) + { + if( bg_buildableList[ i ].buildNum == bclass ) + { + return bg_buildableList[ i ].replaceable; + } + } + return qfalse; +} + +/* +============== BG_FindOverrideForBuildable ============== */ Index: src/game/tremulous.h =================================================================== --- src/game/tremulous.h (revision 823) +++ src/game/tremulous.h (working copy) @@ -306,8 +306,8 @@ #define ALIENSENSE_RANGE 1000.0f -#define ALIEN_POISON_TIME 10000 -#define ALIEN_POISON_DMG 30 +#define ALIEN_POISON_TIME 5000 +#define ALIEN_POISON_DMG 5 #define ALIEN_POISON_DIVIDER (1.0f/1.32f) //about 1.0/(time`th root of damage) #define ALIEN_SPAWN_REPEAT_TIME 10000 @@ -427,9 +427,11 @@ */ #define LIGHTARMOUR_PRICE 70 +#define LIGHTARMOUR_POISON_PROTECTION 1 #define HELMET_PRICE 90 #define HELMET_RANGE 1000.0f +#define HELMET_POISON_PROTECTION 2 #define MEDKIT_PRICE 0 @@ -443,6 +445,7 @@ #define JETPACK_DISABLE_CHANCE 0.3f #define BSUIT_PRICE 400 +#define BSUIT_POISON_PROTECTION 4 #define MGCLIP_PRICE 0 @@ -450,7 +453,7 @@ #define GAS_PRICE 0 -#define MEDKIT_POISON_IMMUNITY_TIME 30000 +#define MEDKIT_POISON_IMMUNITY_TIME 0 #define MEDKIT_STARTUP_TIME 4000 #define MEDKIT_STARTUP_SPEED 5 @@ -588,3 +591,8 @@ #define DAMAGE_FRACTION_FOR_KILL 0.5f //how much damage players (versus structures) need to //do to increment the stage kill counters + +// g_suddenDeathMode settings +#define SDMODE_BP 0 +#define SDMODE_NO_BUILD 1 +#define SDMODE_SELECTIVE 2 Index: src/game/g_svcmds.c =================================================================== --- src/game/g_svcmds.c (revision 823) +++ src/game/g_svcmds.c (working copy) @@ -600,6 +600,11 @@ G_Printf( "cp: %s\n", ConcatArgs( 1 ) ); return qtrue; } + else if( !Q_stricmp( cmd, "m" ) ) + { + G_PrivateMessage( NULL ); + return qtrue; + } G_Printf( "unknown command: %s\n", cmd ); return qtrue; Index: src/game/g_local.h =================================================================== --- src/game/g_local.h (revision 823) +++ src/game/g_local.h (working copy) @@ -347,6 +347,14 @@ int adminLevel; } clientPersistant_t; +#define MAX_UNLAGGED_MARKERS 10 +typedef struct unlagged_s { + vec3_t origin; + vec3_t mins; + vec3_t maxs; + qboolean used; +} unlagged_t; + // this structure is cleared on each ClientSpawn(), // except for 'client->pers' and 'client->sess' struct gclient_s @@ -438,6 +446,12 @@ #define RAM_FRAMES 1 // number of frames to wait before retriggering int retriggerArmouryMenu; // frame number to retrigger the armoury menu + + unlagged_t unlaggedHist[ MAX_UNLAGGED_MARKERS ]; + unlagged_t unlaggedBackup; + unlagged_t unlaggedCalc; + int unlaggedTime; + }; @@ -609,6 +623,9 @@ pTeam_t lastWin; + int suddenDeathABuildPoints; + int suddenDeathHBuildPoints; + qboolean suddenDeath; timeWarning_t suddenDeathWarning; timeWarning_t timelimitWarning; @@ -624,6 +641,9 @@ qboolean uncondHumanWin; qboolean alienTeamLocked; qboolean humanTeamLocked; + + int unlaggedIndex; + int unlaggedTimes[ MAX_UNLAGGED_MARKERS ]; } level_locals_t; // @@ -652,6 +672,7 @@ void G_DecolorString( char *in, char *out ); void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ); void G_SanitiseName( char *in, char *out ); +void G_PrivateMessage( gentity_t *ent ); // // g_physics.c @@ -908,6 +929,11 @@ // // g_active.c // +void G_UnlaggedStore( void ); +void G_UnlaggedClear( gentity_t *ent ); +void G_UnlaggedCalc( int time, gentity_t *skipEnt ); +void G_UnlaggedOn( vec3_t muzzle, float range ); +void G_UnlaggedOff( void ); void ClientThink( int clientNum ); void ClientEndFrame( gentity_t *ent ); void G_RunClient( gentity_t *ent ); @@ -1045,6 +1071,7 @@ extern vmCvar_t g_timelimit; extern vmCvar_t g_suddenDeathTime; +extern vmCvar_t g_suddenDeathMode; extern vmCvar_t g_friendlyFire; extern vmCvar_t g_friendlyFireHumans; extern vmCvar_t g_friendlyFireAliens; @@ -1094,6 +1121,8 @@ extern vmCvar_t g_alienStage2Threshold; extern vmCvar_t g_alienStage3Threshold; +extern vmCvar_t g_unlagged; + extern vmCvar_t g_disabledEquipment; extern vmCvar_t g_disabledClasses; extern vmCvar_t g_disabledBuildables; @@ -1112,6 +1141,9 @@ extern vmCvar_t g_adminNameProtect; extern vmCvar_t g_adminTempBan; +extern vmCvar_t g_privateMessages; +extern vmCvar_t g_dretchPunt; + void trap_Printf( const char *fmt ); void trap_Error( const char *fmt ); int trap_Milliseconds( void ); Index: src/game/g_combat.c =================================================================== --- src/game/g_combat.c (revision 823) +++ src/game/g_combat.c (working copy) @@ -41,9 +41,6 @@ if( !ent->client ) return; - if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) - return; - // no scoring during pre-match warmup if( level.warmupTime ) return; @@ -141,6 +138,7 @@ char *killerName, *obit; float totalDamage = 0.0f; gentity_t *player; + qboolean tk = qfalse; if( self->client->ps.pm_type == PM_DEAD ) @@ -169,7 +167,11 @@ killer = attacker->s.number; if( attacker->client ) + { killerName = attacker->client->pers.netname; + tk = ( attacker != self && attacker->client->ps.stats[ STAT_PTEAM ] + == self->client->ps.stats[ STAT_PTEAM ] ); + } else killerName = ""; } @@ -202,11 +204,24 @@ BG_DeactivateUpgrade( i, self->client->ps.stats ); // broadcast the death event to everyone - ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); - ent->s.eventParm = meansOfDeath; - ent->s.otherEntityNum = self->s.number; - ent->s.otherEntityNum2 = killer; - ent->r.svFlags = SVF_BROADCAST; // send to everyone + if( !tk ) + { + ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); + ent->s.eventParm = meansOfDeath; + ent->s.otherEntityNum = self->s.number; + ent->s.otherEntityNum2 = killer; + ent->r.svFlags = SVF_BROADCAST; // send to everyone + } + else + { + // tjw: obviously this is a hack and belongs in the client, but + // this works as a temporary fix. + trap_SendServerCommand( -1, + va( "print \"%s^7 was killed by ^1TEAMMATE^7 %s\n\"", + self->client->pers.netname, attacker->client->pers.netname ) ); + trap_SendServerCommand( attacker - g_entities, + va( "cp \"You killed ^1TEAMMATE^7 %s\"", self->client->pers.netname ) ); + } self->enemy = attacker; @@ -1021,8 +1036,20 @@ // if the attacker was on the same team if( targ != attacker && OnSameTeam( targ, attacker ) ) { - if( !g_friendlyFire.integer ) + if( g_dretchPunt.integer && + targ->client->ps.stats[ STAT_PCLASS ] == PCL_ALIEN_LEVEL0 ) { + vec3_t dir, push; + + VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, dir ); + VectorNormalizeFast( dir ); + VectorScale( dir, ( damage * 10.0f ), push ); + push[2] = 64.0f; + VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity ); + return; + } + else if( !g_friendlyFire.integer ) + { if( !g_friendlyFireHumans.integer && targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { @@ -1097,8 +1124,8 @@ //if boosted poison every attack if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) { - if( !( targ->client->ps.stats[ STAT_STATE ] & SS_POISONED ) && - !BG_InventoryContainsUpgrade( UP_BATTLESUIT, targ->client->ps.stats ) && + if( targ->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && + !( targ->client->ps.stats[ STAT_STATE ] & SS_POISONED ) && mod != MOD_LEVEL2_ZAP && targ->client->poisonImmunityTime < level.time ) { Index: src/game/g_active.c =================================================================== --- src/game/g_active.c (revision 823) +++ src/game/g_active.c (working copy) @@ -217,6 +217,13 @@ other = &g_entities[ pm->touchents[ i ] ]; + // see G_UnlaggedDetectCollisions(), this is the inverse of that. + // if our movement is blocked by another player's real position, + // don't use the unlagged position for them because they are + // blocking or server-side Pmove() from reaching it + if( other->client && other->client->unlaggedCalc.used ) + other->client->unlaggedCalc.used = qfalse; + //charge attack if( ent->client->ps.weapon == WP_ALEVEL4 && ent->client->ps.stats[ STAT_MISC ] > 0 && @@ -683,22 +690,17 @@ //client is poisoned if( client->ps.stats[ STAT_STATE ] & SS_POISONED ) { - int i; - int seconds = ( ( level.time - client->lastPoisonTime ) / 1000 ) + 1; - int damage = ALIEN_POISON_DMG, damage2 = 0; + int damage = ALIEN_POISON_DMG; + + if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) + damage -= BSUIT_POISON_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_HELMET, client->ps.stats ) ) + damage -= HELMET_POISON_PROTECTION; + if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) + damage -= LIGHTARMOUR_POISON_PROTECTION; - for( i = 0; i < seconds; i++ ) - { - if( i == seconds - 1 ) - damage2 = damage; - - damage *= ALIEN_POISON_DIVIDER; - } - - damage = damage2 - damage; - - G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, NULL, - damage, 0, MOD_POISON ); + G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, + 0, damage, 0, MOD_POISON ); } //replenish alien health @@ -901,6 +903,288 @@ /* ============== + G_UnlaggedStore + + Called on every server frame. Stores position data for the client at that + into client->unlaggedHist[] and the time into level.unlaggedTimes[]. + This data is used by G_UnlaggedCalc() +============== +*/ +void G_UnlaggedStore( void ) +{ + int i = 0; + gentity_t *ent; + unlagged_t *save; + + if( !g_unlagged.integer ) + return; + level.unlaggedIndex++; + if( level.unlaggedIndex >= MAX_UNLAGGED_MARKERS ) + level.unlaggedIndex = 0; + + level.unlaggedTimes[ level.unlaggedIndex ] = level.time; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + save = &ent->client->unlaggedHist[ level.unlaggedIndex ]; + save->used = qfalse; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + VectorCopy( ent->r.mins, save->mins ); + VectorCopy( ent->r.maxs, save->maxs ); + VectorCopy( ent->s.pos.trBase, save->origin ); + save->used = qtrue; + } +} + +/* +============== + G_UnlaggedClear + + Mark all unlaggedHist[] markers for this client invalid. Useful for + preventing teleporting and death. +============== +*/ +void G_UnlaggedClear( gentity_t *ent ) +{ + int i; + + for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) + ent->client->unlaggedHist[ i ].used = qfalse; +} + +/* +============== + G_UnlaggedCalc + + Loops through all active clients and calculates their predicted position + for time then stores it in client->unlaggedCalc +============== +*/ +void G_UnlaggedCalc( int time, gentity_t *rewindEnt ) +{ + int i = 0; + gentity_t *ent; + int startIndex = level.unlaggedIndex; + int stopIndex = -1; + int frameMsec = 0; + float lerp = 0.5f; + + if( !g_unlagged.integer ) + return; + + // clear any calculated values from a previous run + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + ent->client->unlaggedCalc.used = qfalse; + } + + for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) + { + if( level.unlaggedTimes[ startIndex ] <= time ) + break; + stopIndex = startIndex; + if( --startIndex < 0 ) + startIndex = MAX_UNLAGGED_MARKERS - 1; + } + if( i == MAX_UNLAGGED_MARKERS ) + { + // if we searched all markers and the oldest one still isn't old enough + // just use the oldest marker with no lerping + lerp = 0.0f; + } + + // client is on the current frame, no need for unlagged + if( stopIndex == -1 ) + return; + + // lerp between two markers + frameMsec = level.unlaggedTimes[ stopIndex ] - + level.unlaggedTimes[ startIndex ]; + if( frameMsec > 0 ) + { + lerp = ( float )( time - level.unlaggedTimes[ startIndex ] ) + / ( float )frameMsec; + } + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( ent->s.number == rewindEnt->s.number ) + continue; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( ent->client->pers.connected != CON_CONNECTED ) + continue; + if( !ent->client->unlaggedHist[ startIndex ].used ) + continue; + if( !ent->client->unlaggedHist[ stopIndex ].used ) + continue; + + // between two unlagged markers + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins, + ent->client->unlaggedHist[ stopIndex ].mins, + ent->client->unlaggedCalc.mins ); + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs, + ent->client->unlaggedHist[ stopIndex ].maxs, + ent->client->unlaggedCalc.maxs ); + VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin, + ent->client->unlaggedHist[ stopIndex ].origin, + ent->client->unlaggedCalc.origin ); + + ent->client->unlaggedCalc.used = qtrue; + } +} + +/* +============== + G_UnlaggedOff + + Reverses the changes made to all active clients by G_UnlaggedOn() +============== +*/ +void G_UnlaggedOff( void ) +{ + int i = 0; + gentity_t *ent; + + if( !g_unlagged.integer ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + if( !ent->client->unlaggedBackup.used ) + continue; + VectorCopy( ent->client->unlaggedBackup.mins, ent->r.mins ); + VectorCopy( ent->client->unlaggedBackup.maxs, ent->r.maxs ); + VectorCopy( ent->client->unlaggedBackup.origin, ent->r.currentOrigin ); + ent->client->unlaggedBackup.used = qfalse; + trap_LinkEntity( ent ); + } +} + +/* +============== + G_UnlaggedOn + + Called after G_UnlaggedCalc() to apply the calculated values to all active + clients. Once finished tracing, G_UnlaggedOff() must be called to restore + the clients' position data + + As an optimization, all clients that have an unlagged position that is + not touchable at "range" from "muzzle" will be ignored. This is required + to prevent a huge amount of trap_LinkEntity() calls per user cmd. +============== +*/ + +void G_UnlaggedOn( vec3_t muzzle, float range ) +{ + int i = 0; + gentity_t *ent; + unlagged_t *calc; + + if( !g_unlagged.integer ) + return; + + for( i = 0; i < level.maxclients; i++ ) + { + ent = &g_entities[ i ]; + calc = &ent->client->unlaggedCalc; + + if( !calc->used ) + continue; + if( ent->client->unlaggedBackup.used ) + continue; + if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) + continue; + if( VectorCompare( ent->r.currentOrigin, calc->origin ) ) + continue; + if( muzzle ) + { + float r1 = Distance( calc->origin, calc->maxs ); + float r2 = Distance( calc->origin, calc->mins ); + float maxRadius = ( r1 > r2 ) ? r1 : r2; + + if( Distance( muzzle, calc->origin ) > range + maxRadius ) + continue; + } + + // create a backup of the real positions + VectorCopy( ent->r.mins, ent->client->unlaggedBackup.mins ); + VectorCopy( ent->r.maxs, ent->client->unlaggedBackup.maxs ); + VectorCopy( ent->r.currentOrigin, ent->client->unlaggedBackup.origin ); + ent->client->unlaggedBackup.used = qtrue; + + // move the client to the calculated unlagged position + VectorCopy( calc->mins, ent->r.mins ); + VectorCopy( calc->maxs, ent->r.maxs ); + VectorCopy( calc->origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } +} +/* +============== + G_UnlaggedDetectCollisions + + cgame prediction will predict a client's own position all the way up to + the current time, but only updates other player's positions up to the + postition sent in the most recent snapshot. + + This allows player X to essentially "move through" the position of player Y + when player X's cmd is processed with Pmove() on the server. This is because + player Y was clipping player X's Pmove() on his client, but when the same + cmd is processed with Pmove on the server it is not clipped. + + Long story short (too late): don't use unlagged positions for players who + were blocking this player X's client-side Pmove(). This makes the assumption + that if player X's movement was blocked in the client he's going to still + be up against player Y when the Pmove() is run on the server with the + same cmd. + + NOTE: this must be called after Pmove() and G_UnlaggedCalc() +============== +*/ +static void G_UnlaggedDetectCollisions( gentity_t *ent ) +{ + unlagged_t *calc; + trace_t tr; + float r1, r2; + float range; + + if( !g_unlagged.integer ) + return; + + calc = &ent->client->unlaggedCalc; + + // if the client isn't moving, this is not necessary + if( VectorCompare( ent->client->oldOrigin, ent->client->ps.origin ) ) + return; + + range = Distance( ent->client->oldOrigin, ent->client->ps.origin ); + + // increase the range by the player's largest possible radius since it's + // the players bounding box that collides, not their origin + r1 = Distance( calc->origin, calc->mins ); + r2 = Distance( calc->origin, calc->maxs ); + range += ( r1 > r2 ) ? r1 : r2; + + G_UnlaggedOn( ent->client->oldOrigin, range ); + + trap_Trace(&tr, ent->client->oldOrigin, ent->r.mins, ent->r.maxs, + ent->client->ps.origin, ent->s.number, MASK_PLAYERSOLID ); + if( tr.entityNum >= 0 && tr.entityNum < MAX_CLIENTS ) + g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse; + + G_UnlaggedOff( ); +} + +/* +============== ClientThink This will be called once for each client frame, which will @@ -949,6 +1233,9 @@ if( msec > 200 ) msec = 200; + client->unlaggedTime = ucmd->serverTime; + client->unlaggedTime += ( level.time - level.previousTime ); + if( pmove_msec.integer < 8 ) trap_Cvar_Set( "pmove_msec", "8" ); else if( pmove_msec.integer > 33 ) @@ -986,6 +1273,9 @@ if( !ClientInactivityTimer( client ) ) return; + // calculate where ent is currently seeing all the other active clients + G_UnlaggedCalc( ent->client->unlaggedTime, ent ); + if( client->noclip ) client->ps.pm_type = PM_NOCLIP; else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) @@ -1161,6 +1451,8 @@ Pmove( &pm ); + G_UnlaggedDetectCollisions( ent ); + // save results of pmove if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; @@ -1186,6 +1478,9 @@ ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; + // touch other objects + ClientImpacts( ent, &pm ); + // execute client events ClientEvents( ent, oldEventSequence ); @@ -1196,9 +1491,6 @@ VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); VectorCopy( ent->client->ps.origin, ent->s.origin ); - // touch other objects - ClientImpacts( ent, &pm ); - // save results of triggers and client events if( ent->client->ps.eventSequence != oldEventSequence ) ent->eventTime = level.time; @@ -1225,6 +1517,7 @@ //prevent lerping client->ps.eFlags ^= EF_TELEPORT_BIT; client->ps.eFlags &= ~EF_NODRAW; + G_UnlaggedClear( ent ); //client leaves hovel client->ps.stats[ STAT_STATE ] &= ~SS_HOVELING; Index: src/game/g_buildable.c =================================================================== --- src/game/g_buildable.c (revision 823) +++ src/game/g_buildable.c (working copy) @@ -23,6 +23,9 @@ #include "g_local.h" +// from g_combat.c +extern char *modNames[ ]; + /* ================ G_setBuildableAnim @@ -628,12 +631,27 @@ self->s.eFlags &= ~EF_FIRING; //prevent any firing effects - if( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( attacker && attacker->client ) { - if( self->s.modelindex == BA_A_OVERMIND ) - G_AddCreditToClient( attacker->client, OVERMIND_VALUE, qtrue ); - else if( self->s.modelindex == BA_A_SPAWN ) - G_AddCreditToClient( attacker->client, ASPAWN_VALUE, qtrue ); + if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + { + if( self->s.modelindex == BA_A_OVERMIND ) + G_AddCreditToClient( attacker->client, OVERMIND_VALUE, qtrue ); + else if( self->s.modelindex == BA_A_SPAWN ) + G_AddCreditToClient( attacker->client, ASPAWN_VALUE, qtrue ); + } + else + { + G_TeamCommand( PTE_ALIENS, + va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", + BG_FindHumanNameForBuildable( self->s.modelindex ), + attacker->client->pers.netname ) ); + } + G_LogPrintf("Decon: %i %i %i: %s^7 destroyed %s by %s\n", + attacker->client->ps.clientNum, self->s.modelindex, mod, + attacker->client->pers.netname, + BG_FindNameForBuildable( self->s.modelindex ), + modNames[ mod ] ); } } @@ -840,6 +858,22 @@ self->nextthink = level.time + 5000; else self->nextthink = level.time; //blast immediately + + if( attacker && attacker->client ) + { + if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + G_TeamCommand( PTE_ALIENS, + va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", + BG_FindHumanNameForBuildable( self->s.modelindex ), + attacker->client->pers.netname ) ); + } + G_LogPrintf("Decon: %i %i %i: %s^7 destroyed %s by %s\n", + attacker->client->ps.clientNum, self->s.modelindex, mod, + attacker->client->pers.netname, + BG_FindNameForBuildable( self->s.modelindex ), + modNames[ mod ] ); + } } /* @@ -1156,6 +1190,7 @@ //prevent lerping activator->client->ps.eFlags ^= EF_TELEPORT_BIT; activator->client->ps.eFlags |= EF_NODRAW; + G_UnlaggedClear( activator ); activator->client->ps.stats[ STAT_STATE ] |= SS_HOVELING; activator->client->hovel = self; @@ -1241,6 +1276,7 @@ //prevent lerping builder->client->ps.eFlags ^= EF_TELEPORT_BIT; builder->client->ps.eFlags &= ~EF_NODRAW; + G_UnlaggedClear( builder ); G_SetOrigin( builder, newOrigin ); VectorCopy( newOrigin, builder->client->ps.origin ); @@ -1252,6 +1288,22 @@ self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink + + if( attacker && attacker->client ) + { + if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + G_TeamCommand( PTE_ALIENS, + va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", + BG_FindHumanNameForBuildable( self->s.modelindex ), + attacker->client->pers.netname ) ); + } + G_LogPrintf("Decon: %i %i %i: %s^7 destroyed %s by %s\n", + attacker->client->ps.clientNum, self->s.modelindex, mod, + attacker->client->pers.netname, + BG_FindNameForBuildable( self->s.modelindex ), + modNames[ mod ] ); + } } @@ -2193,12 +2245,27 @@ self->nextthink = level.time; //blast immediately } - if( attacker && attacker->client && attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + if( attacker && attacker->client ) { - if( self->s.modelindex == BA_H_REACTOR ) - G_AddCreditToClient( attacker->client, REACTOR_VALUE, qtrue ); - else if( self->s.modelindex == BA_H_SPAWN ) - G_AddCreditToClient( attacker->client, HSPAWN_VALUE, qtrue ); + if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) + { + if( self->s.modelindex == BA_H_REACTOR ) + G_AddCreditToClient( attacker->client, REACTOR_VALUE, qtrue ); + else if( self->s.modelindex == BA_H_SPAWN ) + G_AddCreditToClient( attacker->client, HSPAWN_VALUE, qtrue ); + } + else + { + G_TeamCommand( PTE_HUMANS, + va( "print \"%s ^3DESTROYED^7 by teammate %s^7\n\"", + BG_FindHumanNameForBuildable( self->s.modelindex ), + attacker->client->pers.netname ) ); + } + G_LogPrintf("Decon: %i %i %i: %s^7 destroyed %s by %s\n", + attacker->client->ps.clientNum, self->s.modelindex, mod, + attacker->client->pers.netname, + BG_FindNameForBuildable( self->s.modelindex ), + modNames[ mod ] ); } } Index: src/game/g_main.c =================================================================== --- src/game/g_main.c (revision 823) +++ src/game/g_main.c (working copy) @@ -42,6 +42,7 @@ vmCvar_t g_fraglimit; vmCvar_t g_timelimit; vmCvar_t g_suddenDeathTime; +vmCvar_t g_suddenDeathMode; vmCvar_t g_capturelimit; vmCvar_t g_friendlyFire; vmCvar_t g_friendlyFireAliens; @@ -103,6 +104,8 @@ vmCvar_t g_alienStage2Threshold; vmCvar_t g_alienStage3Threshold; +vmCvar_t g_unlagged; + vmCvar_t g_disabledEquipment; vmCvar_t g_disabledClasses; vmCvar_t g_disabledBuildables; @@ -121,6 +124,10 @@ vmCvar_t g_adminNameProtect; vmCvar_t g_adminTempBan; +vmCvar_t g_privateMessages; + +vmCvar_t g_dretchPunt; + static cvarTable_t gameCvarTable[ ] = { // don't override the cheat state set by the system @@ -141,6 +148,7 @@ // change anytime vars { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, { &g_suddenDeathTime, "g_suddenDeathTime", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, + { &g_suddenDeathMode, "g_suddenDeathMode", "0", CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse }, @@ -206,6 +214,8 @@ { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse }, { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse }, { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse }, + + { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse }, { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM, 0, qfalse }, { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM, 0, qfalse }, @@ -226,6 +236,10 @@ { &g_adminNameProtect, "g_adminNameProtect", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_adminTempBan, "g_adminTempBan", "120", CVAR_ARCHIVE, 0, qfalse }, + { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, + + { &g_dretchPunt, "g_dretchPunt", "0", CVAR_ARCHIVE, 0, qfalse }, + { &g_rankings, "g_rankings", "0", 0, 0, qfalse} }; @@ -985,14 +999,32 @@ if( !level.warmupTime && ( level.time - level.startTime >= g_suddenDeathTime.integer * 60000 ) ) { - localHTP = 0; - localATP = 0; - - //warn about sudden death - if( level.time - level.startTime >= g_suddenDeathTime.integer * 60000 && - level.suddenDeathWarning < TW_PASSED ) + // begin sudden death + if( level.suddenDeathWarning < TW_PASSED ) { trap_SendServerCommand( -1, "cp \"Sudden Death!\"" ); + localHTP = 0; + localATP = 0; + if( g_suddenDeathMode.integer == SDMODE_SELECTIVE ) { + for( i = 1, ent = g_entities + i; i < level.num_entities; i++, ent++ ) + { + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( BG_FindReplaceableTestForBuildable( ent->s.modelindex ) ) + { + int t = BG_FindTeamForBuildable( ent->s.modelindex ); + + if( t == BIT_HUMANS ) + localHTP += BG_FindBuildPointsForBuildable( ent->s.modelindex ); + else if( t == BIT_ALIENS ) + localATP += BG_FindBuildPointsForBuildable( ent->s.modelindex ); + } + } + } + level.suddenDeathHBuildPoints = localHTP; + level.suddenDeathABuildPoints = localATP; + level.suddenDeath = qtrue; level.suddenDeathWarning = TW_PASSED; } } @@ -1007,6 +1039,12 @@ } } } + + if( level.suddenDeath ) + { + localHTP = level.suddenDeathHBuildPoints; + localATP = level.suddenDeathABuildPoints; + } else { localHTP = g_humanBuildPoints.integer; @@ -1037,17 +1075,19 @@ if( buildable == BA_A_OVERMIND && ent->spawned && ent->health > 0 ) level.overmindPresent = qtrue; - if( BG_FindTeamForBuildable( buildable ) == BIT_HUMANS ) + if( !level.suddenDeath || BG_FindReplaceableTestForBuildable( buildable ) ) { - level.humanBuildPoints -= BG_FindBuildPointsForBuildable( buildable ); - - if( ent->powered ) - level.humanBuildPointsPowered -= BG_FindBuildPointsForBuildable( buildable ); + if( BG_FindTeamForBuildable( buildable ) == BIT_HUMANS ) + { + level.humanBuildPoints -= BG_FindBuildPointsForBuildable( buildable ); + if( ent->powered ) + level.humanBuildPointsPowered -= BG_FindBuildPointsForBuildable( buildable ); + } + else + { + level.alienBuildPoints -= BG_FindBuildPointsForBuildable( buildable ); + } } - else - { - level.alienBuildPoints -= BG_FindBuildPointsForBuildable( buildable ); - } } } @@ -2110,12 +2150,6 @@ if( !ent->r.linked && ent->neverFree ) continue; - if( ent->s.eType == ET_MISSILE ) - { - G_RunMissile( ent ); - continue; - } - if( ent->s.eType == ET_BUILDABLE ) { G_BuildableThink( ent, msec ); @@ -2155,6 +2189,25 @@ ClientEndFrame( ent ); } + // save position information for all active clients + G_UnlaggedStore( ); + + // for missle impacts, move every active client one server frame time back + // to compensate for built-in 50ms lag + G_UnlaggedCalc( level.previousTime, NULL ); + G_UnlaggedOn( NULL, 0.0f ); + for( i = MAX_CLIENTS; i < level.num_entities ; i++) + { + ent = &g_entities[ i ]; + if( !ent->inuse ) + continue; + if( ent->freeAfterEvent ) + continue; + if( ent->s.eType == ET_MISSILE ) + G_RunMissile( ent ); + } + G_UnlaggedOff( ); + end = trap_Milliseconds(); //TA: Index: src/game/bg_pmove.c =================================================================== --- src/game/bg_pmove.c (revision 823) +++ src/game/bg_pmove.c (working copy) @@ -510,12 +510,19 @@ pm->ps->weapon != WP_ALEVEL3_UPG ) return qfalse; - if( pm->cmd.buttons & BUTTON_ATTACK2 ) + // we were pouncing, but we've landed + if( pm->ps->groundEntityNum != ENTITYNUM_NONE + && ( pm->ps->pm_flags & PMF_CHARGE ) ) { + pm->ps->weaponTime += LEVEL3_POUNCE_TIME; pm->ps->pm_flags &= ~PMF_CHARGE; - return qfalse; } + // we're building up for a pounce + if( pm->cmd.buttons & BUTTON_ATTACK2 ) + return qfalse; + + // already a pounce in progress if( pm->ps->pm_flags & PMF_CHARGE ) return qfalse; @@ -2705,7 +2712,15 @@ return; } - // make weapon function + + // no bite during pounce + if( ( pm->ps->weapon == WP_ALEVEL3 || pm->ps->weapon == WP_ALEVEL3_UPG ) + && ( pm->cmd.buttons & BUTTON_ATTACK ) + && ( pm->ps->pm_flags & PMF_CHARGE ) ) + { + return; + } + if( pm->ps->weaponTime > 0 ) pm->ps->weaponTime -= pml.msec; Index: src/game/g_weapon.c =================================================================== --- src/game/g_weapon.c (revision 823) +++ src/game/g_weapon.c (working copy) @@ -179,7 +179,10 @@ VectorMA( muzzle, range, forward, end ); + G_UnlaggedOn( muzzle, range ); trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); + if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -223,7 +226,16 @@ VectorMA( end, r, right, end ); VectorMA( end, u, up, end ); - trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + // don't use unlagged if this is not a client (e.g. turret) + if( ent->client ) + { + G_UnlaggedOn( muzzle, 8192 * 16 ); + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); + } + else + trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -308,8 +320,9 @@ SnapVector( tent->s.origin2 ); tent->s.eventParm = rand() & 255; // seed for spread pattern tent->s.otherEntityNum = ent->s.number; - + G_UnlaggedOn( muzzle, 8192 * 16 ); ShotgunPattern( tent->s.pos.trBase, tent->s.origin2, tent->s.eventParm, ent ); + G_UnlaggedOff(); } /* @@ -329,7 +342,10 @@ VectorMA( muzzle, 8192 * 16, forward, end ); + G_UnlaggedOn( muzzle, 8192 * 16 ); trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); + if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -482,7 +498,10 @@ VectorMA( muzzle, 8192 * 16, forward, end ); + G_UnlaggedOn( muzzle, 8192 * 16 ); trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); + if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -534,7 +553,10 @@ VectorMA( muzzle, PAINSAW_RANGE, forward, end ); + G_UnlaggedOn( muzzle, PAINSAW_RANGE ); trap_Trace( &tr, muzzle, NULL, NULL, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); + if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -805,7 +827,9 @@ VectorMA( muzzle, LEVEL0_BITE_RANGE, forward, end ); + G_UnlaggedOn( muzzle, LEVEL0_BITE_RANGE ); trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); if( tr.surfaceFlags & SURF_NOIMPACT ) return qfalse; @@ -939,6 +963,7 @@ VectorAdd( ent->client->ps.origin, range, maxs ); VectorSubtract( ent->client->ps.origin, range, mins ); + G_UnlaggedOn( ent->client->ps.origin, LEVEL1_PCLOUD_RANGE ); num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); for( i = 0; i < num; i++ ) { @@ -967,6 +992,7 @@ } } } + G_UnlaggedOff( ); } @@ -1231,7 +1257,9 @@ VectorMA( muzzle, LEVEL2_AREAZAP_RANGE, forward, end ); + G_UnlaggedOn( muzzle, LEVEL2_AREAZAP_RANGE ); trap_Trace( &tr, muzzle, mins, maxs, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); if( tr.surfaceFlags & SURF_NOIMPACT ) return; @@ -1291,7 +1319,9 @@ VectorMA( muzzle, LEVEL3_POUNCE_RANGE, forward, end ); + G_UnlaggedOn( muzzle, LEVEL3_POUNCE_RANGE ); trap_Trace( &tr, ent->s.origin, mins, maxs, end, ent->s.number, MASK_SHOT ); + G_UnlaggedOff( ); //miss if( tr.fraction >= 1.0 ) @@ -1323,7 +1353,6 @@ G_Damage( traceEnt, ent, ent, forward, tr.endpos, damage, DAMAGE_NO_KNOCKBACK|DAMAGE_NO_LOCDAMAGE, MOD_LEVEL3_POUNCE ); - ent->client->ps.weaponTime += LEVEL3_POUNCE_TIME; ent->client->allowedToPounce = qfalse; return qtrue; Index: src/game/g_misc.c =================================================================== --- src/game/g_misc.c (revision 823) +++ src/game/g_misc.c (working copy) @@ -86,6 +86,7 @@ // toggle the teleport bit so the client knows to not lerp player->client->ps.eFlags ^= EF_TELEPORT_BIT; + G_UnlaggedClear( player ); // set angles SetClientViewAngle( player, angles ); Index: src/game/g_client.c =================================================================== --- src/game/g_client.c (revision 823) +++ src/game/g_client.c (working copy) @@ -89,9 +89,6 @@ if( !client ) return; - if( client->sess.sessionTeam == TEAM_SPECTATOR ) - return; - //if we're already at the max and trying to add credit then stop if( cap ) { @@ -952,8 +949,8 @@ char model[ MAX_QPATH ]; char buffer[ MAX_QPATH ]; char filename[ MAX_QPATH ]; - char oldname[ MAX_STRING_CHARS ]; - char newname[ MAX_STRING_CHARS ]; + char oldname[ MAX_NAME_LENGTH ]; + char newname[ MAX_NAME_LENGTH ]; char err[ MAX_STRING_CHARS ]; qboolean revertName = qfalse; gclient_t *client; @@ -1415,6 +1412,7 @@ // toggle the teleport bit so the client knows to not lerp flags = ent->client->ps.eFlags & ( EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED ); flags ^= EF_TELEPORT_BIT; + G_UnlaggedClear( ent ); // clear everything but the persistant data Index: src/game/bg_public.h =================================================================== --- src/game/bg_public.h (revision 823) +++ src/game/bg_public.h (working copy) @@ -1017,6 +1017,7 @@ qboolean dccTest; qboolean reactorTest; + qboolean replaceable; } buildableAttributes_t; typedef struct @@ -1142,6 +1143,7 @@ int BG_FindCreepSizeForBuildable( int bclass ); int BG_FindDCCTestForBuildable( int bclass ); int BG_FindUniqueTestForBuildable( int bclass ); +qboolean BG_FindReplaceableTestForBuildable( int bclass ); void BG_InitBuildableOverrides( void ); int BG_FindClassNumForName( char *name ); Index: src/game/g_cmds.c =================================================================== --- src/game/g_cmds.c (revision 823) +++ src/game/g_cmds.c (working copy) @@ -587,57 +587,50 @@ { pTeam_t oldTeam = ent->client->pers.teamSelection; + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) + { + G_Printf("stop following me!!!!!\n"); + G_Printf("score before %d\n", ent->client->ps.persistant[ PERS_SCORE ]); + G_StopFollowing( ent ); + G_Printf("score after %d\n", ent->client->ps.persistant[ PERS_SCORE ]); + } + else G_Printf("you're not following anybody\n"); + ent->client->pers.teamSelection = newTeam; - if( oldTeam != newTeam ) + if( oldTeam == newTeam ) + return; + + if( oldTeam == PTE_ALIENS ) + G_RemoveFromSpawnQueue( &level.alienSpawnQueue, ent->client->ps.clientNum ); + else if( oldTeam == PTE_HUMANS ) + G_RemoveFromSpawnQueue( &level.humanSpawnQueue, ent->client->ps.clientNum ); + + if( G_admin_permission( ent, ADMF_TEAMCHANGEFREE ) || + ( ( oldTeam == PTE_HUMANS || oldTeam == PTE_ALIENS ) + && ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) ) { - //if the client is in a queue make sure they are removed from it before changing + // always save in human credtis if( oldTeam == PTE_ALIENS ) - G_RemoveFromSpawnQueue( &level.alienSpawnQueue, ent->client->ps.clientNum ); - else if( oldTeam == PTE_HUMANS ) - G_RemoveFromSpawnQueue( &level.humanSpawnQueue, ent->client->ps.clientNum ); - - if( G_admin_permission( ent, ADMF_TEAMCHANGEFREE ) ) { - // always save in human credtis - if( oldTeam == PTE_ALIENS ) - { - ent->client->ps.persistant[ PERS_CREDIT ] *= - (float)FREEKILL_HUMAN / FREEKILL_ALIEN; - } - if( newTeam == PTE_ALIENS ) - { - ent->client->ps.persistant[ PERS_CREDIT ] *= - (float)FREEKILL_ALIEN / FREEKILL_HUMAN; - } + ent->client->ps.persistant[ PERS_CREDIT ] *= + (float)FREEKILL_HUMAN / FREEKILL_ALIEN; } - else if( ( oldTeam == PTE_HUMANS || oldTeam == PTE_ALIENS ) - && ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) + if( newTeam == PTE_ALIENS ) { - // Tranfer credits and kills as long as this player has been on the - // same team for at least 1 minute. This is done to provide - // a penalty for switching teams for reconnaissance. - if( oldTeam == PTE_HUMANS ) - { - ent->client->ps.persistant[ PERS_CREDIT ] *= - (float)FREEKILL_ALIEN / FREEKILL_HUMAN; - } - else if( oldTeam == PTE_ALIENS ) - { - ent->client->ps.persistant[ PERS_CREDIT ] *= - (float)FREEKILL_HUMAN / FREEKILL_ALIEN; - } + ent->client->ps.persistant[ PERS_CREDIT ] *= + (float)FREEKILL_ALIEN / FREEKILL_HUMAN; } - else - { - ent->client->ps.persistant[ PERS_CREDIT ] = 0; - ent->client->ps.persistant[ PERS_SCORE ] = 0; - } - - ent->client->pers.classSelection = PCL_NONE; - ClientSpawn( ent, NULL, NULL, NULL ); } + else + { + ent->client->ps.persistant[ PERS_CREDIT ] = 0; + ent->client->ps.persistant[ PERS_SCORE ] = 0; + } + ent->client->pers.classSelection = PCL_NONE; + ClientSpawn( ent, NULL, NULL, NULL ); + ent->client->pers.joinedATeam = qtrue; ent->client->pers.teamChangeTime = level.time; @@ -819,14 +812,14 @@ { default: case SAY_ALL: - G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); + G_LogPrintf( "say: %s^7: %s\n", ent->client->pers.netname, chatText ); Com_sprintf( name, sizeof( name ), "%s%s%c%c"EC": ", prefix, ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); color = COLOR_GREEN; break; case SAY_TEAM: - G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); + G_LogPrintf( "sayteam: %s^7: %s\n", ent->client->pers.netname, chatText ); if( Team_GetLocationMsg( ent, location, sizeof( location ) ) ) Com_sprintf( name, sizeof( name ), EC"(%s%c%c"EC") (%s)"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); @@ -857,10 +850,6 @@ return; } - // echo the text to the console - if( g_dedicated.integer ) - G_Printf( "%s%s\n", name, text); - // send it to all the apropriate clients for( j = 0; j < level.maxclients; j++ ) { @@ -883,12 +872,28 @@ static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { char *p; + char *args; if( ent->client->pers.muted ) { return; } + // support parsing /m out of say text since some people have a hard + // time figuring out what the console is. + if( g_privateMessages.integer ) + { + args = G_SayConcatArgs(0); + if( !Q_stricmpn( args, "say /m ", 7 ) || + !Q_stricmpn( args, "say_team /m ", 12 ) || + !Q_stricmpn( args, "say /mt ", 8 ) || + !Q_stricmpn( args, "say_team /mt ", 13 ) ) + { + G_PrivateMessage( ent ); + return; + } + } + if( trap_Argc( ) < 2 && !arg0 ) return; @@ -927,7 +932,8 @@ p = ConcatArgs( 2 ); - G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); + G_LogPrintf( "tell: %s^7 to %s^7: %s\n", ent->client->pers.netname, + target->client->pers.netname, p ); G_Say( ent, target, SAY_TELL, p ); // don't tell to the player self if it was already directed to this player // also don't send the chat back to a bot @@ -978,7 +984,7 @@ return; } - if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + if( ent->client->pers.teamSelection == PTE_NONE ) { trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator\n\"" ); return; @@ -1104,6 +1110,8 @@ trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " called a vote\n\"", ent->client->pers.netname ) ); + G_Printf( "'%s' called a vote for '%s'\n", ent->client->pers.netname, + level.voteString ) ; ent->client->pers.voteCount++; @@ -1342,6 +1350,8 @@ trap_SendServerCommand( i, va("print \"%s " S_COLOR_WHITE "called a team vote\n\"", ent->client->pers.netname ) ); } + G_Printf( "'%s' called a teamvote for '%s'\n", ent->client->pers.netname, + level.teamVoteString[ cs_offset ] ) ; // start the voting, the caller autoamtically votes yes level.teamVoteTime[ cs_offset ] = level.time; @@ -1714,10 +1724,16 @@ return; // Don't allow destruction of buildables that cannot be rebuilt - if( g_suddenDeathTime.integer && ( level.time - level.startTime >= - g_suddenDeathTime.integer * 60000 ) && - BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) + if( level.suddenDeath && traceEnt->health > 0 && + ( ( g_suddenDeathMode.integer == SDMODE_SELECTIVE && + !BG_FindReplaceableTestForBuildable( traceEnt->s.modelindex ) ) || + ( g_suddenDeathMode.integer == SDMODE_BP && + BG_FindBuildPointsForBuildable( traceEnt->s.modelindex ) ) || + g_suddenDeathMode.integer == SDMODE_NO_BUILD ) ) { + trap_SendServerCommand( ent-g_entities, + "print \"During Sudden Death you can only decon buildings that " + "can be rebuilt\n\"" ); return; } @@ -1726,6 +1742,18 @@ G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); return; } + if( traceEnt->health > 0 ) + { + G_TeamCommand( ent->client->pers.teamSelection, + va( "print \"%s ^3DECONSTRUCTED^7 by %s^7\n\"", + BG_FindHumanNameForBuildable( traceEnt->s.modelindex ), + ent->client->pers.netname ) ); + } + G_LogPrintf("Decon: %i %i 0: %s^7 deconstructed %s\n", + ent->client->ps.clientNum, + traceEnt->s.modelindex, + ent->client->pers.netname, + BG_FindNameForBuildable( traceEnt->s.modelindex ) ); if( !deconstruct && CheatsOk( ent ) ) G_Damage( traceEnt, ent, ent, forward, tr.endpos, 10000, 0, MOD_SUICIDE ); @@ -1895,10 +1923,11 @@ if( buyingEnergyAmmo ) { //no armoury nearby - if( ( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) && - !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) ) ) + if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) && + !G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) && + !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) { - trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a reactor or repeater\n\"" ) ); + trap_SendServerCommand( ent-g_entities, va( "print \"You must be near a reactor, repeater, or armoury\n\"" ) ); return; } } @@ -2219,6 +2248,23 @@ trap_Argv( 1, s, sizeof( s ) ); buildable = BG_FindBuildNumForName( s ); + + if( level.suddenDeath ) + { + if( g_suddenDeathMode.integer == SDMODE_SELECTIVE && + !BG_FindReplaceableTestForBuildable( buildable ) ) + { + trap_SendServerCommand( ent-g_entities, + "print \"Only essential buildings can be rebuilt in Sudden Death\n\"" ); + return; + } + else if( g_suddenDeathMode.integer == SDMODE_NO_BUILD ) + { + trap_SendServerCommand( ent-g_entities, + "print \"Building is not allowed during Sudden Death\n\"" ); + } + } + team = ent->client->ps.stats[ STAT_PTEAM ]; if( buildable != BA_NONE && @@ -2326,6 +2372,7 @@ */ void G_StopFollowing( gentity_t *ent ) { + ent->client->ps = level.clients[ ent - g_entities ].ps; ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; ent->client->sess.sessionTeam = TEAM_SPECTATOR; ent->client->sess.spectatorState = SPECTATOR_FREE; @@ -2616,6 +2663,12 @@ Cmd_Tell_f( ent ); return; } + + if( !Q_stricmp( cmd, "m" ) || !Q_stricmp( cmd, "mt" ) ) + { + G_PrivateMessage( ent ); + return; + } if( Q_stricmp( cmd, "score" ) == 0 ) { @@ -2808,3 +2861,105 @@ } *out = '\0'; } + + +void G_PrivateMessage( gentity_t *ent ) +{ + int pids[ MAX_CLIENTS ]; + char name[ MAX_NAME_LENGTH ]; + char netname[ MAX_NAME_LENGTH ]; + char cmd[ 12 ]; + char str[ MAX_STRING_CHARS ]; + char *msg; + char color; + int pcount, count = 0; + int i; + int skipargs = 0; + qboolean teamonly = qfalse; + gentity_t *tmpent; + + if( !g_privateMessages.integer && ent ) + return; + + G_SayArgv( 0, cmd, sizeof( cmd ) ); + if( !Q_stricmp( cmd, "say" ) || !Q_stricmp( cmd, "say_team" ) ) + { + skipargs = 1; + G_SayArgv( 1, cmd, sizeof( cmd ) ); + } + if( G_SayArgc( ) < 3+skipargs ) + { + ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) ); + return; + } + + if( !Q_stricmp( cmd, "mt" ) || !Q_stricmp( cmd, "/mt" ) ) + teamonly = qtrue; + + G_SayArgv( 1+skipargs, name, sizeof( name ) ); + msg = G_SayConcatArgs( 2+skipargs ); + pcount = G_ClientNumbersFromString( name, pids ); + + if( ent ) + { + Q_strncpyz( netname, ent->client->pers.netname, sizeof( netname ) ); + if( teamonly ) + { + for( i=0; i < pcount; i++ ) + { + if( !OnSameTeam( ent, &g_entities[ pids[ i ] ] ) ) + continue; + pids[ count ] = pids[ i ]; + count++; + } + pcount = count; + } + } + else + { + Q_strncpyz( netname, "console", sizeof( name ) ); + } + + color = teamonly ? COLOR_CYAN : COLOR_YELLOW; + + Q_strncpyz( str, + va( "^%csent to %i player%s: ^7", color, pcount, + ( pcount == 1 ) ? "" : "s" ), + sizeof( str ) ); + + for( i=0; i < pcount; i++ ) + { + tmpent = &g_entities[ pids[ i ] ]; + + if( i > 0 ) + Q_strcat( str, sizeof( str ), "^7, " ); + Q_strcat( str, sizeof( str ), tmpent->client->pers.netname ); + CPx( pids[ i ], va( + "chat \"%s^%c -> ^7%s^7: (%d recipients): ^%c%s^7\" %i", + netname, + color, + name, + pcount, + color, + msg, + ent ? ent-g_entities : -1 ) ); + CPx( pids[ i ], va("cp \"^%cprivate message from ^7%s^7\"", + color, + netname ) ); + } + + if( !pcount ) + ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n", + name ) ); + else + { + ADMP( va( "^%cPrivate message: ^7%s\n", color, msg ) ); + ADMP(va("%s\n", str)); + + if( teamonly ) + G_LogPrintf( "tprivmsg: %s^7: %s^7: %s\n", netname, name, msg ); + else + G_LogPrintf( "privmsg: %s^7: %s^7: %s\n", netname, name, msg ); + } +} + Index: src/qcommon/q_shared.h =================================================================== --- src/qcommon/q_shared.h (revision 823) +++ src/qcommon/q_shared.h (working copy) @@ -423,6 +423,9 @@ #define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) #define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) #define VectorMA(v, s, b, o) ((o)[0]=(v)[0]+(b)[0]*(s),(o)[1]=(v)[1]+(b)[1]*(s),(o)[2]=(v)[2]+(b)[2]*(s)) +#define VectorLerp( f, s, e, r ) ((r)[0]=(s)[0]+(f)*((e)[0]-(s)[0]),\ + (r)[1]=(s)[1]+(f)*((e)[1]-(s)[1]),\ + (r)[2]=(s)[2]+(f)*((e)[2]-(s)[2])) #else