--- /dev/null
+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 = "<non-client>";
+ }
+@@ -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
+