Index: src/win32/win_shared.c =================================================================== --- src/win32/win_shared.c (revision 809) +++ src/win32/win_shared.c (working copy) @@ -286,7 +286,20 @@ } char *Sys_DefaultHomePath(void) { - return NULL; + TCHAR szPath[MAX_PATH]; + static char path[MAX_QPATH]; + + if( Q_stricmp( Cvar_VariableString( "arch" ), "winnt" ) ) + return NULL; + if( !SHGetFolderPath( NULL, CSIDL_APPDATA, NULL, 0, szPath ) ) + return NULL; + Q_strncpyz( path, szPath, sizeof(path) ); + Q_strcat( path, sizeof(path), "\\Quake3" ); + if (mkdir(path)) { + if (errno != EEXIST) + Sys_Error("Unable to create directory \"%s\", error is %s(%d)\n", path, strerror(errno), errno); + } + return path; } char *Sys_DefaultInstallPath(void) Index: src/win32/win_local.h =================================================================== --- src/win32/win_local.h (revision 809) +++ src/win32/win_local.h (working copy) @@ -38,6 +38,7 @@ #include #include #include +#include void IN_MouseEvent (int mstate); Index: src/server/sv_main.c =================================================================== --- src/server/sv_main.c (revision 809) +++ src/server/sv_main.c (working copy) @@ -129,6 +129,10 @@ // return; // } + // we don't need to send commands to a client until they enter the game + if( client->state < CS_PRIMED ) + return; + client->reliableSequence++; // if we would be losing an old command that hasn't been acknowledged, // we must drop the connection @@ -186,9 +190,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/tremulous.h =================================================================== --- src/game/tremulous.h (revision 809) +++ src/game/tremulous.h (working copy) @@ -85,7 +85,7 @@ #define LEVEL3_CLAW_REPEAT 700 #define LEVEL3_CLAW_U_REPEAT 600 #define LEVEL3_POUNCE_DMG ADM(100) -#define LEVEL3_POUNCE_RANGE 72.0f +#define LEVEL3_POUNCE_RANGE 96.0f #define LEVEL3_POUNCE_WIDTH 16.0f #define LEVEL3_POUNCE_SPEED 700 #define LEVEL3_POUNCE_UPG_SPEED 800 Index: src/game/g_svcmds.c =================================================================== --- src/game/g_svcmds.c (revision 809) +++ 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 809) +++ src/game/g_local.h (working copy) @@ -622,6 +622,7 @@ qboolean uncondAlienWin; qboolean uncondHumanWin; + qboolean teamLocked[ TEAM_NUM_TEAMS ]; } level_locals_t; // @@ -650,6 +651,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 @@ -1110,6 +1112,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 809) +++ src/game/g_combat.c (working copy) @@ -138,6 +138,7 @@ char *killerName, *obit; float totalDamage = 0.0f; gentity_t *player; + qboolean tk = qfalse; if( self->client->ps.pm_type == PM_DEAD ) @@ -166,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 = ""; } @@ -199,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; @@ -1018,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 ) { Index: src/game/g_buildable.c =================================================================== --- src/game/g_buildable.c (revision 809) +++ src/game/g_buildable.c (working copy) @@ -23,6 +23,9 @@ #include "g_local.h" +// from g_combat.c +extern char *modNames[ ]; + /* ================ G_setBuildableAnim @@ -633,12 +636,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 ] ); } } @@ -845,6 +863,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 ] ); + } } /* @@ -1257,6 +1291,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 ] ); + } } @@ -2198,12 +2248,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 809) +++ src/game/g_main.c (working copy) @@ -121,6 +121,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 @@ -226,6 +230,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} }; Index: src/game/g_admin.c =================================================================== --- src/game/g_admin.c (revision 809) +++ src/game/g_admin.c (working copy) @@ -50,8 +50,8 @@ {"ban", G_admin_ban, "b", "ban a player by IP and GUID with an optional expiration time and reason." - "time is seconds or suffix with 'w' - weeks, 'd' - days, 'h' - hours, or " - "'m' - minutes", + " time is seconds or suffix with 'w' - weeks, 'd' - days, 'h' - hours, " + "or 'm' - minutes", "[^3name|slot#|IP^7] (^5time^7) (^5reason^7)" }, @@ -79,6 +79,11 @@ "display a list of players, their client numbers and their levels", "" }, + + {"lock", G_admin_lock, "K", + "lock a team to prevent anyone from joining it", + "[^3a|h^7]" + }, {"mute", G_admin_mute, "m", "mute a player", @@ -142,6 +147,11 @@ "unbans a player specified by the slot as seen in showbans", "[^3ban slot#^7]" }, + + {"unlock", G_admin_unlock, "K", + "unlock a locked team", + "[^3a|h^7]" + }, {"unmute", G_admin_mute, "m", "unmute a muted player", @@ -285,6 +295,8 @@ for( i = 0; i < MAX_ADMIN_ADMINS && g_admin_admins[ i ]; i++ ) { + if( g_admin_admins[ i ]->level < 1 ) + continue; G_SanitiseName( g_admin_admins[ i ]->name, testName ); if( !Q_stricmp( name2, testName ) && Q_stricmp( ent->client->pers.guid, g_admin_admins[ i ]->guid ) ) @@ -2303,7 +2315,7 @@ ADMBP( va( "^3!help: ^7help for '!%s':\n", g_admin_cmds[ i ].keyword ) ); ADMBP( va( " ^3Funtion: ^7%s\n", g_admin_cmds[ i ].function ) ); - ADMBP( va( " ^3Syntax: ^7%s %s\n", g_admin_cmds[ i ].keyword, + ADMBP( va( " ^3Syntax: ^7!%s %s\n", g_admin_cmds[ i ].keyword, g_admin_cmds[ i ].syntax ) ); ADMBP( va( " ^3Flag: ^7'%c'\n", g_admin_cmds[ i ].flag[ 0 ] ) ); ADMBP_end(); @@ -2324,7 +2336,7 @@ ADMBP( va( "^3!help: ^7help for '%s':\n", g_admin_commands[ i ]->command ) ); ADMBP( va( " ^3Description: ^7%s\n", g_admin_commands[ i ]->desc ) ); - ADMBP( va( " ^3Syntax: ^7%s\n", g_admin_commands[ i ]->command ) ); + ADMBP( va( " ^3Syntax: ^7!%s\n", g_admin_commands[ i ]->command ) ); ADMBP_end(); return qtrue; } @@ -2600,6 +2612,72 @@ return qtrue; } +qboolean G_admin_lock( gentity_t *ent, int skiparg ) +{ + char teamName[2] = {""}; + pTeam_t team; + + if( G_SayArgc() < 1 + skiparg ) + { + ADMP( "^3!lock: ^7usage: !lock [a|h]\n" ); + return qfalse; + } + G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) ); + if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' ) + team = PTE_ALIENS; + else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' ) + team = PTE_HUMANS; + else + { + ADMP( va( "^3!lock: ^7invalid team\"%c\"\n", teamName[0] ) ); + return qfalse; + } + if( level.teamLocked[ team ] ) + { + ADMP( va( "^3!lock: ^7%s team is already locked\n", + ( team == PTE_ALIENS ) ? "Alien" : "Human" ) ); + return qfalse; + } + level.teamLocked[ team ] = qtrue; + AP( va( "print \"^3!lock: ^7%s team has been locked by %s\n\"", + ( team == PTE_ALIENS ) ? "Alien" : "Human", + ( ent ) ? ent->client->pers.netname : "console" ) ); + return qtrue; +} + +qboolean G_admin_unlock( gentity_t *ent, int skiparg ) +{ + char teamName[2] = {""}; + pTeam_t team; + + if( G_SayArgc() < 1 + skiparg ) + { + ADMP( "^3!unlock: ^7usage: !unlock [a|h]\n" ); + return qfalse; + } + G_SayArgv( 1 + skiparg, teamName, sizeof( teamName ) ); + if( teamName[ 0 ] == 'a' || teamName[ 0 ] == 'A' ) + team = PTE_ALIENS; + else if( teamName[ 0 ] == 'h' || teamName[ 0 ] == 'H' ) + team = PTE_HUMANS; + else + { + ADMP( va( "^3!unlock: ^7invalid team\"%c\"\n", teamName[0] ) ); + return qfalse; + } + if( !level.teamLocked[ team ] ) + { + ADMP( va( "^3!lock: ^7%s team is not locked\n", + ( team == PTE_ALIENS ) ? "Alien" : "Human" ) ); + return qfalse; + } + level.teamLocked[ team ] = qfalse; + AP( va( "print \"^3!unlock: ^7%s team has been unlocked by %s\n\"", + ( team == PTE_ALIENS ) ? "Alien" : "Human", + ( ent ) ? ent->client->pers.netname : "console" ) ); + return qtrue; +} + /* * This function facilitates the TP define. ADMP() is similar to CP except that * it prints the message to the server console if ent is not defined. Index: src/game/bg_pmove.c =================================================================== --- src/game/bg_pmove.c (revision 809) +++ 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 809) +++ src/game/g_weapon.c (working copy) @@ -1323,7 +1323,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_admin.h =================================================================== --- src/game/g_admin.h (revision 809) +++ src/game/g_admin.h (working copy) @@ -44,7 +44,7 @@ /* * 1 - cannot be vote kicked, vote muted * 2 - cannot be censored or flood protected TODO - * 3 - UNUSED + * 3 - never loses credits for changing teams * 4 - can see team chat as a spectator * 5 - can switch teams any time, regardless of balance * 6 - does not need to specify a reason for a kick/ban @@ -58,7 +58,7 @@ */ #define ADMF_IMMUNITY '1' #define ADMF_NOCENSORFLOOD '2' /* TODO */ - +#define ADMF_TEAMCHANGEFREE '3' #define ADMF_SPEC_ALLCHAT '4' #define ADMF_FORCETEAMCHANGE '5' #define ADMF_UNACCOUNTABLE '6' @@ -161,6 +161,8 @@ qboolean G_admin_restart( gentity_t *ent, int skiparg ); qboolean G_admin_nextmap( gentity_t *ent, int skiparg ); qboolean G_admin_namelog( gentity_t *ent, int skiparg ); +qboolean G_admin_lock( gentity_t *ent, int skiparg ); +qboolean G_admin_unlock( gentity_t *ent, int skiparg ); void G_admin_print( gentity_t *ent, char *m ); void G_admin_buffer_print( gentity_t *ent, char *m ); Index: src/game/g_cmds.c =================================================================== --- src/game/g_cmds.c (revision 809) +++ src/game/g_cmds.c (working copy) @@ -597,12 +597,26 @@ else if( oldTeam == PTE_HUMANS ) G_RemoveFromSpawnQueue( &level.humanSpawnQueue, ent->client->ps.clientNum ); - // 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 || oldTeam == PTE_ALIENS ) - && ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) + 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; + } + } + else if( ( oldTeam == PTE_HUMANS || oldTeam == PTE_ALIENS ) + && ( level.time - ent->client->pers.teamChangeTime ) > 60000 ) + { + // 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 ] *= @@ -654,6 +668,19 @@ team = PTE_NONE; else if( !Q_stricmp( s, "aliens" ) ) { + if( level.teamLocked[ PTE_ALIENS ] ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"Alien team has been ^1LOCKED\n\"", s ) ); + return; + } + else if( level.teamLocked[ PTE_HUMANS ] ) + { + // if only one team has been locked, let people join the other + // regardless of balance + force = qtrue; + } + if( !force && g_teamForceBalance.integer && ( ( level.numAlienClients > level.numHumanClients ) || ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && @@ -667,6 +694,19 @@ } else if( !Q_stricmp( s, "humans" ) ) { + if( level.teamLocked[ PTE_HUMANS ] ) + { + trap_SendServerCommand( ent-g_entities, + va( "print \"Human team has been ^1LOCKED\n\"", s ) ); + return; + } + else if( level.teamLocked[ PTE_ALIENS ] ) + { + // if only one team has been locked, let people join the other + // regardless of balance + force = qtrue; + } + if( !force && g_teamForceBalance.integer && ( ( level.numHumanClients > level.numAlienClients ) || ( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && @@ -680,12 +720,19 @@ } else if( !Q_stricmp( s, "auto" ) ) { - if( level.numHumanClients > level.numAlienClients ) + if( level.teamLocked[ PTE_HUMANS ] && level.teamLocked[ PTE_ALIENS ] ) + team = PTE_NONE; + else if( level.numHumanClients > level.numAlienClients ) team = PTE_ALIENS; else if( level.numHumanClients < level.numAlienClients ) team = PTE_HUMANS; else team = PTE_ALIENS + ( rand( ) % 2 ); + + if( team == PTE_ALIENS && level.teamLocked[ PTE_ALIENS ] ) + team = PTE_HUMANS; + else if( team == PTE_HUMANS && level.teamLocked[ PTE_HUMANS ] ) + team = PTE_ALIENS; } else { @@ -772,14 +819,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 ); @@ -810,10 +857,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++ ) { @@ -836,12 +879,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; @@ -880,7 +939,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 @@ -1057,6 +1117,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++; @@ -1238,6 +1300,7 @@ break; } + if( i >= level.maxclients ) clientNum = -1; } @@ -1295,6 +1358,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; @@ -1680,6 +1745,16 @@ return; } + 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 ); else @@ -1848,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; } } @@ -2569,6 +2645,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 ) { @@ -2761,3 +2843,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/cvar.c =================================================================== --- src/qcommon/cvar.c (revision 809) +++ src/qcommon/cvar.c (working copy) @@ -318,7 +318,11 @@ value = var->resetString; } - if (!strcmp(value,var->string)) { + if((var->flags & CVAR_LATCH) && var->latchedString) { + if(!strcmp(value,var->latchedString)) + return var; + } + else if (!strcmp(value,var->string)) { return var; } // note what types of cvars have been modified (userinfo, archive, serverinfo, systeminfo) Index: misc/manual.lyx =================================================================== --- misc/manual.lyx (revision 809) +++ misc/manual.lyx (working copy) @@ -7480,7 +7480,7 @@ \begin_inset Tabular - + @@ -7570,7 +7570,7 @@ \layout Standard -!cancelvote/!passvote +!cancelvote \end_inset @@ -7652,6 +7652,24 @@ \layout Standard +K +\end_inset + + +\begin_inset Text + +\layout Standard + +!lock/!unlock +\end_inset + + + + +\begin_inset Text + +\layout Standard + m \end_inset @@ -7706,6 +7724,24 @@ \layout Standard +V +\end_inset + + +\begin_inset Text + +\layout Standard + +!passvote +\end_inset + + + + +\begin_inset Text + +\layout Standard + p \end_inset @@ -7856,7 +7892,7 @@ \begin_inset Tabular - + @@ -7902,6 +7938,24 @@ \layout Standard +3 +\end_inset + + +\begin_inset Text + +\layout Standard + +never loses credits/evo for switching teams +\end_inset + + + + +\begin_inset Text + +\layout Standard + 4 \end_inset