summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Levin <risujin@fastmail.fm>2009-10-03 11:25:01 +0000
committerTim Angus <tim@ngus.net>2013-01-03 00:14:58 +0000
commit5189226d1e8df1e56240d51a32d0db82924320d7 (patch)
treefccc195f973841dfa5dad503421e01774b2c9f22
parent4b1bd2babe3c5b37648b987d16225d9f111205ce (diff)
* Spawn queue displays ordinals ("You are 2nd in the spawn queue")
* Server does not communicate the number of eggs and telenodes to all clients anymore -- the correct number of spawns is passed to the client when they are in the spawn queue only (puts PERS_UNUSED to use) Sticky and dead spectate (holy fuck that was hard for something so simple sounding): * Big thanks to Lakitu7 for passing along the patch (with contributions from TJW, Undeference, and R1CH) * UI has a new option to enable/disable sticky spectate * Spectators get to see the full dying animation * Dead players can spectate their teammates whether they are in the spawn queue or not I corrected several nasty bugs and recoded a LOT of the patch. Potentially the "spawn without a weapon" thing may have been fixed but maybe not. There are possibly other new bugs so keep an eye out for them.
-rw-r--r--src/cgame/cg_draw.c41
-rw-r--r--src/cgame/cg_local.h5
-rw-r--r--src/cgame/cg_main.c3
-rw-r--r--src/cgame/cg_servercmds.c3
-rw-r--r--src/cgame/cg_tutorial.c11
-rw-r--r--src/cgame/cg_view.c3
-rw-r--r--src/game/bg_public.h3
-rw-r--r--src/game/g_active.c158
-rw-r--r--src/game/g_client.c32
-rw-r--r--src/game/g_cmds.c84
-rw-r--r--src/game/g_combat.c3
-rw-r--r--src/game/g_local.h2
-rw-r--r--src/game/g_main.c4
-rw-r--r--ui/ingame_options.menu20
14 files changed, 239 insertions, 133 deletions
diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c
index bc012338..4720fd0f 100644
--- a/src/cgame/cg_draw.c
+++ b/src/cgame/cg_draw.c
@@ -2730,7 +2730,7 @@ static qboolean CG_DrawFollow( void )
vec4_t color;
char buffer[ MAX_STRING_CHARS ];
- if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ if( cg.snap->ps.clientNum == cg.clientNum )
return qfalse;
color[ 0 ] = 1;
@@ -2756,7 +2756,8 @@ static qboolean CG_DrawQueue( void )
{
float w;
vec4_t color;
- char buffer[ MAX_STRING_CHARS ];
+ int position, remainder;
+ char *ordinal, buffer[ MAX_STRING_CHARS ];
if( !( cg.snap->ps.pm_flags & PMF_QUEUED ) )
return qfalse;
@@ -2766,28 +2767,28 @@ static qboolean CG_DrawQueue( void )
color[ 2 ] = 1;
color[ 3 ] = 1;
- Com_sprintf( buffer, MAX_STRING_CHARS, "You are in position %d of the spawn queue.",
- cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1 );
+ position = cg.snap->ps.persistant[ PERS_QUEUEPOS ] + 1;
+ if( position < 1 )
+ return qfalse;
+ remainder = position % 10;
+ ordinal = "th";
+ if( remainder == 1 )
+ ordinal = "st";
+ else if( remainder == 2 )
+ ordinal = "nd";
+ else if( remainder == 3 )
+ ordinal = "rd";
+ Com_sprintf( buffer, MAX_STRING_CHARS, "You are %d%s in the spawn queue",
+ position, ordinal );
w = UI_Text_Width( buffer, 0.7f, 0 );
UI_Text_Paint( 320 - w / 2, 360, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
- if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
- {
- if( cgs.numAlienSpawns == 1 )
- Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." );
- else
- Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.",
- cgs.numAlienSpawns );
- }
- else if( cg.snap->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
- {
- if( cgs.numHumanSpawns == 1 )
- Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining." );
- else
- Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining.",
- cgs.numHumanSpawns );
- }
+ if( cg.snap->ps.persistant[ PERS_SPAWNS ] == 1 )
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There is 1 spawn remaining" );
+ else
+ Com_sprintf( buffer, MAX_STRING_CHARS, "There are %d spawns remaining",
+ cg.snap->ps.persistant[ PERS_SPAWNS ] );
w = UI_Text_Width( buffer, 0.7f, 0 );
UI_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h
index 71e4a14c..360dac34 100644
--- a/src/cgame/cg_local.h
+++ b/src/cgame/cg_local.h
@@ -1346,9 +1346,6 @@ typedef struct
int alienNextStageThreshold;
int humanNextStageThreshold;
- int numAlienSpawns;
- int numHumanSpawns;
-
//
// locally derived information from gamestate
//
@@ -1508,6 +1505,8 @@ extern vmCvar_t cg_painBlendMax;
extern vmCvar_t cg_painBlendScale;
extern vmCvar_t cg_painBlendZoom;
+extern vmCvar_t cg_stickySpec;
+
extern vmCvar_t ui_currentClass;
extern vmCvar_t ui_carriage;
extern vmCvar_t ui_stages;
diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c
index 0f68f488..8c472321 100644
--- a/src/cgame/cg_main.c
+++ b/src/cgame/cg_main.c
@@ -222,6 +222,8 @@ vmCvar_t cg_painBlendMax;
vmCvar_t cg_painBlendScale;
vmCvar_t cg_painBlendZoom;
+vmCvar_t cg_stickySpec;
+
vmCvar_t ui_currentClass;
vmCvar_t ui_carriage;
vmCvar_t ui_stages;
@@ -319,6 +321,7 @@ static cvarTable_t cvarTable[ ] =
{ &cg_wwSmoothTime, "cg_wwSmoothTime", "300", CVAR_ARCHIVE },
{ &cg_wwFollow, "cg_wwFollow", "1", CVAR_ARCHIVE|CVAR_USERINFO },
{ &cg_wwToggle, "cg_wwToggle", "1", CVAR_ARCHIVE|CVAR_USERINFO },
+ { &cg_stickySpec, "cg_stickySpec", "1", CVAR_ARCHIVE|CVAR_USERINFO },
{ &cg_depthSortParticles, "cg_depthSortParticles", "1", CVAR_ARCHIVE },
{ &cg_bounceParticles, "cg_bounceParticles", "0", CVAR_ARCHIVE },
{ &cg_consoleLatency, "cg_consoleLatency", "3000", CVAR_ARCHIVE },
diff --git a/src/cgame/cg_servercmds.c b/src/cgame/cg_servercmds.c
index afc45780..5c150e97 100644
--- a/src/cgame/cg_servercmds.c
+++ b/src/cgame/cg_servercmds.c
@@ -158,7 +158,6 @@ void CG_SetConfigValues( void )
sscanf( CG_ConfigString( CS_STAGES ), "%d %d %d %d %d %d", &cgs.alienStage, &cgs.humanStage,
&cgs.alienKills, &cgs.humanKills, &cgs.alienNextStageThreshold, &cgs.humanNextStageThreshold );
- sscanf( CG_ConfigString( CS_SPAWNS ), "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) );
cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) );
@@ -292,8 +291,6 @@ static void CG_ConfigStringModified( void )
if( cgs.humanStage != oldHumanStage )
CG_AnnounceHumanStageTransistion( oldHumanStage, cgs.humanStage );
}
- else if( num == CS_SPAWNS )
- sscanf( str, "%d %d", &cgs.numAlienSpawns, &cgs.numHumanSpawns );
else if( num == CS_LEVEL_START_TIME )
cgs.levelStartTime = atoi( str );
else if( num == CS_VOTE_TIME )
diff --git a/src/cgame/cg_tutorial.c b/src/cgame/cg_tutorial.c
index b2abd7a5..04d0ec28 100644
--- a/src/cgame/cg_tutorial.c
+++ b/src/cgame/cg_tutorial.c
@@ -558,9 +558,14 @@ static void CG_SpectatorText( char *text, playerState_t *ps )
{
if( cgs.clientinfo[ cg.clientNum ].team != PTE_NONE )
{
- Q_strcat( text, MAX_TUTORIAL_TEXT,
- va( "Press %s to spawn\n",
- CG_KeyNameForCommand( "+attack" ) ) );
+ if( ps->pm_flags & PMF_QUEUED )
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to leave spawn queue\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
+ else
+ Q_strcat( text, MAX_TUTORIAL_TEXT,
+ va( "Press %s to spawn\n",
+ CG_KeyNameForCommand( "+attack" ) ) );
}
else
{
diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c
index 16a16478..2c718556 100644
--- a/src/cgame/cg_view.c
+++ b/src/cgame/cg_view.c
@@ -1052,7 +1052,8 @@ static int CG_CalcViewValues( void )
ps = &cg.predictedPlayerState;
// intermission view
- if( ps->pm_type == PM_INTERMISSION )
+ if( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_FREEZE ||
+ ps->pm_type == PM_SPECTATOR )
{
VectorCopy( ps->origin, cg.refdef.vieworg );
VectorCopy( ps->viewangles, cg.refdefViewAngles );
diff --git a/src/game/bg_public.h b/src/game/bg_public.h
index c645f915..f78218ac 100644
--- a/src/game/bg_public.h
+++ b/src/game/bg_public.h
@@ -71,7 +71,6 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#define CS_BUILDPOINTS 28
#define CS_STAGES 29
-#define CS_SPAWNS 30
#define CS_MODELS 33
#define CS_SOUNDS (CS_MODELS+MAX_MODELS)
@@ -261,7 +260,7 @@ typedef enum
{
PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!!
PERS_HITS, // total points damage inflicted so damage beeps can sound on change
- PERS_RANK,
+ PERS_SPAWNS, // how many spawns your team has
PERS_TEAM,
PERS_SPAWN_COUNT, // incremented every respawn
PERS_ATTACKER, // clientnum of last damage inflicter
diff --git a/src/game/g_active.c b/src/game/g_active.c
index 47fd446c..9fe75f92 100644
--- a/src/game/g_active.c
+++ b/src/game/g_active.c
@@ -393,43 +393,85 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
{
pmove_t pm;
gclient_t *client;
- qboolean attack1, attack3;
+ int clientNum;
+ qboolean attack1, attack3, following, queued;
client = ent->client;
client->oldbuttons = client->buttons;
client->buttons = ucmd->buttons;
- attack1 = ( ( client->buttons & BUTTON_ATTACK ) &&
- !( client->oldbuttons & BUTTON_ATTACK ) );
- attack3 = ( ( client->buttons & BUTTON_USE_HOLDABLE ) &&
- !( client->oldbuttons & BUTTON_USE_HOLDABLE ) );
+ attack1 = ( client->buttons & BUTTON_ATTACK ) &&
+ !( client->oldbuttons & BUTTON_ATTACK );
+ attack3 = ( client->buttons & BUTTON_USE_HOLDABLE ) &&
+ !( client->oldbuttons & BUTTON_USE_HOLDABLE );
+
+ // We are in following mode only if we are following a non-spectating client
+ following = client->sess.spectatorState != SPECTATOR_FOLLOW;
+ if( following )
+ {
+ clientNum = client->sess.spectatorClient;
+ if( clientNum < 0 || clientNum > level.maxclients ||
+ !g_entities[ clientNum ].client ||
+ !g_entities[ clientNum ].client->sess.sessionTeam == TEAM_SPECTATOR )
+ following = qfalse;
+ }
+
+ // Check to see if we are in the spawn queue
+ queued = qfalse;
+ if( client->pers.teamSelection == PTE_ALIENS )
+ queued = G_SearchSpawnQueue( &level.alienSpawnQueue, ent - g_entities );
+ else if( client->pers.teamSelection == PTE_HUMANS )
+ queued = G_SearchSpawnQueue( &level.humanSpawnQueue, ent - g_entities );
+
+ // Wants to get out of spawn queue
+ if( attack1 && queued )
+ {
+ if( client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+ if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
+ G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
+ else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
+ G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
+ client->pers.classSelection = PCL_NONE;
+ client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
+ client->ps.pm_flags &= ~PMF_QUEUED;
+ queued = qfalse;
+ }
+ else if( attack1 && client->pers.teamSelection != PTE_NONE &&
+ client->pers.classSelection == PCL_NONE )
+ {
+ // Wants to get into spawn queue
+ if( client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+ if( client->pers.teamSelection == PTE_NONE )
+ G_TriggerMenu( client->ps.clientNum, MN_TEAM );
+ else if( client->pers.teamSelection == PTE_ALIENS )
+ G_TriggerMenu( client->ps.clientNum, MN_A_CLASS );
+ else if( client->pers.teamSelection == PTE_HUMANS )
+ G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN );
+ }
- if( client->sess.spectatorState != SPECTATOR_FOLLOW )
+ // We are either not following anyone or following a spectator
+ if( !following )
{
- if( client->sess.spectatorState == SPECTATOR_LOCKED )
+ if( client->sess.spectatorState == SPECTATOR_LOCKED ||
+ client->sess.spectatorState == SPECTATOR_FOLLOW )
client->ps.pm_type = PM_FREEZE;
else
client->ps.pm_type = PM_SPECTATOR;
- // in case the client entered the queue while following a teammate
- if( ( client->pers.teamSelection == PTE_ALIENS &&
- G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) ||
- ( client->pers.teamSelection == PTE_HUMANS &&
- G_SearchSpawnQueue( &level.alienSpawnQueue, ent-g_entities ) ) )
- {
+ if( queued )
client->ps.pm_flags |= PMF_QUEUED;
- }
client->ps.speed = BG_FindSpeedForClass( client->ps.stats[ STAT_PCLASS ] );
-
client->ps.stats[ STAT_STAMINA ] = 0;
client->ps.stats[ STAT_MISC ] = 0;
client->ps.stats[ STAT_BUILDABLE ] = 0;
client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
client->ps.weapon = WP_NONE;
- // set up for pmove
+ // Set up for pmove
memset( &pm, 0, sizeof( pm ) );
pm.ps = &client->ps;
pm.cmd = *ucmd;
@@ -437,67 +479,36 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
pm.trace = trap_Trace;
pm.pointcontents = trap_PointContents;
- // perform a pmove
+ // Perform a pmove
Pmove( &pm );
- // save results of pmove
+ // Save results of pmove
VectorCopy( client->ps.origin, ent->s.origin );
G_TouchTriggers( ent );
trap_UnlinkEntity( ent );
- if( ( attack1 || attack3 ) && ( client->ps.pm_flags & PMF_QUEUED ) )
- {
- if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
- G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
- else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
- G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
-
- client->pers.classSelection = PCL_NONE;
- client->ps.stats[ STAT_PCLASS ] = PCL_NONE;
- }
-
- if( attack1 && client->pers.classSelection == PCL_NONE )
- {
- if( client->pers.teamSelection == PTE_NONE )
- G_TriggerMenu( client->ps.clientNum, MN_TEAM );
- else if( client->pers.teamSelection == PTE_ALIENS )
- G_TriggerMenu( client->ps.clientNum, MN_A_CLASS );
- else if( client->pers.teamSelection == PTE_HUMANS )
- G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN );
- }
-
- //set the queue position for the client side
+ // Set the queue position and spawn count for the client side
if( client->ps.pm_flags & PMF_QUEUED )
{
if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
{
client->ps.persistant[ PERS_QUEUEPOS ] =
G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum );
+ client->ps.persistant[ PERS_SPAWNS ] = level.numAlienSpawns;
}
else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
{
client->ps.persistant[ PERS_QUEUEPOS ] =
G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum );
+ client->ps.persistant[ PERS_SPAWNS ] = level.numHumanSpawns;
}
}
}
- else if( attack1 && ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
- {
- G_StopFollowing( ent );
- client->pers.classSelection = PCL_NONE;
- if( client->pers.teamSelection == PTE_NONE )
- G_TriggerMenu( ent-g_entities, MN_TEAM );
- else if( client->pers.teamSelection == PTE_ALIENS )
- G_TriggerMenu( ent-g_entities, MN_A_CLASS );
- else if( client->pers.teamSelection == PTE_HUMANS )
- G_TriggerMenu( ent-g_entities, MN_H_SPAWN );
- }
-
+
+ // Tertiary fire or use button toggles following mode
if( attack3 )
- {
G_ToggleFollow( ent );
- }
}
@@ -592,11 +603,10 @@ void ClientTimerActions( gentity_t *ent, int msec )
if( stopped || client->ps.pm_type == PM_JETPACK )
client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE;
else if( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST )
- client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE;
+ client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE;
else if( walking || crouched )
client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE;
-
-
+
// Check stamina limits
if( client->ps.stats[ STAT_STAMINA ] > MAX_STAMINA )
client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
@@ -623,6 +633,7 @@ void ClientTimerActions( gentity_t *ent, int msec )
}
else
client->ps.stats[ STAT_MISC ] = 0;
+
}
if( !( ucmd->buttons & BUTTON_ATTACK2 ) || client->charging ||
@@ -645,7 +656,6 @@ void ClientTimerActions( gentity_t *ent, int msec )
//can't charge backwards
if( ucmd->forwardmove < 0 )
client->ps.stats[ STAT_MISC ] = 0;
-
if( client->ps.stats[ STAT_MISC ] > LEVEL4_TRAMPLE_CHARGE_MAX )
client->ps.stats[ STAT_MISC ] = LEVEL4_TRAMPLE_CHARGE_MAX;
}
@@ -838,11 +848,10 @@ void ClientTimerActions( gentity_t *ent, int msec )
if( modifier >= 3.0f )
client->ps.stats[ STAT_STATE ] |= SS_HEALING_3X;
else if( modifier >= 2.0f )
-
client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X;
if( creep || modifier != 1.0f )
client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE;
- else
+ else
client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE;
if( ent->health > 0 &&
@@ -1493,15 +1502,13 @@ void ClientThink_real( gentity_t *ent )
if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) )
timeLeft -= BSUIT_PCLOUD_PROTECTION;
-
if( BG_InventoryContainsUpgrade( UP_HELMET, client->ps.stats ) )
timeLeft -= HELMET_PCLOUD_PROTECTION;
-
if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) )
timeLeft -= LIGHTARMOUR_PCLOUD_PROTECTION;
if( timeLeft <= 0 )
- client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED;
+ client->ps.stats[ STAT_STATE ] &= ~SS_POISONCLOUDED;
}
if( client->ps.stats[ STAT_STATE ] & SS_POISONED &&
@@ -1872,18 +1879,23 @@ void SpectatorClientEndFrame( gentity_t *ent )
if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
{
clientNum = ent->client->sess.spectatorClient;
-
- if( clientNum >= 0 )
+ if( clientNum >= 0 && clientNum < level.maxclients )
{
cl = &level.clients[ clientNum ];
-
- if( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR )
+ if( cl->pers.connected == CON_CONNECTED )
{
- flags = ( cl->ps.eFlags & ~( EF_VOTED | EF_TEAMVOTED ) ) |
- ( ent->client->ps.eFlags & ( EF_VOTED | EF_TEAMVOTED ) );
- ent->client->ps = cl->ps;
- ent->client->ps.pm_flags |= PMF_FOLLOW;
- ent->client->ps.eFlags = flags;
+ if( cl->sess.sessionTeam != TEAM_SPECTATOR )
+ {
+ flags = ( cl->ps.eFlags & ~( EF_VOTED | EF_TEAMVOTED ) ) |
+ ( ent->client->ps.eFlags & ( EF_VOTED | EF_TEAMVOTED ) );
+ ent->client->ps = cl->ps;
+ ent->client->ps.eFlags = flags;
+ ent->client->ps.pm_flags |= PMF_FOLLOW;
+ }
+ else
+ ent->client->ps.pm_flags &= ~PMF_FOLLOW;
+ ent->client->ps.clientNum = clientNum;
+ ent->client->ps.pm_flags &= ~PMF_QUEUED;
}
}
}
@@ -1910,7 +1922,7 @@ void ClientEndFrame( gentity_t *ent )
pers = &ent->client->pers;
- // save a copy of things from playerState in case of SPECTATOR_FOLLOW
+ // save a copy of certain playerState values in case of SPECTATOR_FOLLOW
pers->score = ent->client->ps.persistant[ PERS_SCORE ];
pers->credit = ent->client->ps.persistant[ PERS_CREDIT ];
diff --git a/src/game/g_client.c b/src/game/g_client.c
index 6c9ea759..1a2714cb 100644
--- a/src/game/g_client.c
+++ b/src/game/g_client.c
@@ -739,11 +739,30 @@ respawn
*/
void respawn( gentity_t *ent )
{
+ int i;
+
SpawnCorpse( ent );
// Clients can't respawn - they must go through the class cmd
ent->client->pers.classSelection = PCL_NONE;
ClientSpawn( ent, NULL, NULL, NULL );
+
+ // stop any following clients that don't have sticky spec on
+ for( i = 0; i < level.maxclients; i++ )
+ {
+ if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR &&
+ level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW &&
+ level.clients[ i ].sess.spectatorClient == ent - g_entities )
+ {
+ if( !( level.clients[ i ].pers.stickySpec ) )
+ {
+ if( !G_FollowNewClient( &g_entities[ i ], 1 ) )
+ G_StopFollowing( &g_entities[ i ] );
+ }
+ else
+ G_FollowLockView( &g_entities[ i ] );
+ }
+ }
}
/*
@@ -954,6 +973,10 @@ void ClientUserinfoChanged( int clientNum )
if( !strcmp( s, "localhost" ) )
client->pers.localClient = qtrue;
+ // stickyspec toggle
+ s = Info_ValueForKey( userinfo, "cg_stickySpec" );
+ client->pers.stickySpec = atoi( s ) != 0;
+
// set name
Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) );
s = Info_ValueForKey( userinfo, "name" );
@@ -1319,6 +1342,13 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles
teamLocal = client->pers.teamSelection;
+ //if client is dead and following teammate, stop following before spawning
+ if( client->sess.spectatorClient != -1 )
+ {
+ client->sess.spectatorClient = -1;
+ client->sess.spectatorState = SPECTATOR_FREE;
+ }
+
// only start client if chosen a class and joined a team
if( client->pers.classSelection == PCL_NONE && teamLocal == PTE_NONE )
{
@@ -1330,7 +1360,7 @@ void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles
client->sess.sessionTeam = TEAM_SPECTATOR;
client->sess.spectatorState = SPECTATOR_LOCKED;
}
-
+
if( origin != NULL )
VectorCopy( origin, spawn_origin );
diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c
index 0b1857d0..28431dd8 100644
--- a/src/game/g_cmds.c
+++ b/src/game/g_cmds.c
@@ -251,7 +251,9 @@ void ScoreboardMessage( gentity_t *ent )
else
ping = cl->ps.ping < 999 ? cl->ps.ping : 999;
- if( cl->sess.sessionTeam != TEAM_SPECTATOR )
+ if( cl->sess.sessionTeam != TEAM_SPECTATOR &&
+ ( ent->client->pers.teamSelection == PTE_NONE ||
+ cl->pers.teamSelection == ent->client->pers.teamSelection ) )
{
weapon = cl->ps.weapon;
@@ -528,7 +530,12 @@ void G_LeaveTeam( gentity_t *self )
else if( team == PTE_HUMANS )
G_RemoveFromSpawnQueue( &level.humanSpawnQueue, self->client->ps.clientNum );
else
+ {
+ // might have been following somone so reset
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( self );
return;
+ }
// stop any following clients
G_StopFromFollowing( self );
@@ -2466,7 +2473,7 @@ void G_StopFollowing( gentity_t *ent )
}
else
{
- vec3_t spawn_origin, spawn_angles;
+ vec3_t spawn_origin, spawn_angles;
ent->client->sess.spectatorState = SPECTATOR_LOCKED;
@@ -2481,12 +2488,11 @@ void G_StopFollowing( gentity_t *ent )
}
ent->client->sess.spectatorClient = -1;
ent->client->ps.pm_flags &= ~PMF_FOLLOW;
-
ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING;
+ ent->client->ps.stats[ STAT_VIEWLOCK ] = 0;
ent->client->ps.eFlags &= ~EF_WALLCLIMB;
ent->client->ps.viewangles[ PITCH ] = 0.0f;
-
ent->client->ps.clientNum = ent - g_entities;
CalculateRanks( );
@@ -2494,6 +2500,42 @@ void G_StopFollowing( gentity_t *ent )
/*
=================
+G_FollowLockView
+
+Client is still following a player, but that player has gone to spectator
+mode and cannot be followed for the moment
+=================
+*/
+void G_FollowLockView( gentity_t *ent )
+{
+ vec3_t spawn_origin, spawn_angles;
+ int clientNum;
+
+ ent->client->sess.sessionTeam = TEAM_SPECTATOR;
+ ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
+ ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR;
+ ent->client->ps.pm_flags &= ~PMF_FOLLOW;
+ ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection;
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING;
+ ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBINGCEILING;
+ ent->client->ps.stats[ STAT_VIEWLOCK ] = 0;
+ ent->client->ps.eFlags &= ~EF_WALLCLIMB;
+ ent->client->ps.viewangles[ PITCH ] = 0.0f;
+ clientNum = ent->client->ps.clientNum = ent->client->sess.spectatorClient;
+
+ // Put the view at the team spectator lock position
+ if( level.clients[ clientNum ].pers.teamSelection == PTE_ALIENS )
+ G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles );
+ else if( level.clients[ clientNum ].pers.teamSelection == PTE_HUMANS )
+ G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles );
+
+ G_SetOrigin( ent, spawn_origin );
+ VectorCopy( spawn_origin, ent->client->ps.origin );
+ G_SetClientViewAngle( ent, spawn_angles );
+}
+
+/*
+=================
G_FollowNewClient
This was a really nice, elegant function. Then I fucked it up.
@@ -2544,18 +2586,21 @@ qboolean G_FollowNewClient( gentity_t *ent, int dir )
if( level.clients[ clientnum ].pers.connected != CON_CONNECTED )
continue;
- // can't follow another spectator
- if( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR )
+ // can't follow a spectator
+ if( level.clients[ clientnum ].pers.teamSelection == PTE_NONE )
continue;
-
+
+ // if stickyspec is disabled, can't follow someone in queue either
+ if( !ent->client->pers.stickySpec &&
+ level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR )
+ continue;
+
// can only follow teammates when dead and on a team
- if( ent->client->pers.teamSelection != PTE_NONE &&
- ( level.clients[ clientnum ].pers.teamSelection !=
+ if( ent->client->pers.teamSelection != PTE_NONE &&
+ ( level.clients[ clientnum ].pers.teamSelection !=
ent->client->pers.teamSelection ) )
- {
continue;
- }
-
+
// this is good, we can use it
ent->client->sess.spectatorClient = clientnum;
ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
@@ -2575,7 +2620,7 @@ void G_ToggleFollow( gentity_t *ent )
{
if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
G_StopFollowing( ent );
- else if( ent->client->sess.spectatorState == SPECTATOR_FREE )
+ else
G_FollowNewClient( ent, 1 );
}
@@ -2617,17 +2662,16 @@ void Cmd_Follow_f( gentity_t *ent )
if( &level.clients[ i ] == ent->client )
return;
- // can't follow another spectator
- if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR )
+ // can't follow another spectator if sticky spec is off
+ if( !ent->client->pers.stickySpec &&
+ level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR )
return;
// can only follow teammates when dead and on a team
if( ent->client->pers.teamSelection != PTE_NONE &&
( level.clients[ i ].pers.teamSelection !=
ent->client->pers.teamSelection ) )
- {
return;
- }
ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
ent->client->sess.spectatorClient = i;
@@ -2902,9 +2946,9 @@ commands_t cmds[ ] = {
{ "ptrcverify", 0, Cmd_PTRCVerify_f },
{ "ptrcrestore", 0, Cmd_PTRCRestore_f },
- { "follow", CMD_SPEC, Cmd_Follow_f },
- { "follownext", CMD_SPEC, Cmd_FollowCycle_f },
- { "followprev", CMD_SPEC, Cmd_FollowCycle_f },
+ { "follow", 0, Cmd_Follow_f },
+ { "follownext", 0, Cmd_FollowCycle_f },
+ { "followprev", 0, Cmd_FollowCycle_f },
{ "where", CMD_TEAM, Cmd_Where_f },
{ "teamvote", CMD_TEAM, Cmd_TeamVote_f },
diff --git a/src/game/g_combat.c b/src/game/g_combat.c
index c674d1da..20864852 100644
--- a/src/game/g_combat.c
+++ b/src/game/g_combat.c
@@ -195,9 +195,6 @@ void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int
if( level.intermissiontime )
return;
- // stop any following clients
- G_StopFromFollowing( self );
-
self->client->ps.pm_type = PM_DEAD;
self->suicideTime = 0;
diff --git a/src/game/g_local.h b/src/game/g_local.h
index bfda00b4..0305c3c9 100644
--- a/src/game/g_local.h
+++ b/src/game/g_local.h
@@ -332,6 +332,7 @@ typedef struct
usercmd_t cmd; // we would lose angles if not persistant
qboolean localClient; // true if "ip" info key is "localhost"
qboolean initialSpawn; // the first spawn should be at a cool location
+ qboolean stickySpec; // don't stop spectating a player after they get killed
qboolean pmoveFixed; //
char netname[ MAX_NETNAME ];
int maxHealth; // for handicapping
@@ -688,6 +689,7 @@ char *G_NewString( const char *string );
void Cmd_Score_f( gentity_t *ent );
void G_StopFromFollowing( gentity_t *ent );
void G_StopFollowing( gentity_t *ent );
+void G_FollowLockView( gentity_t *ent );
qboolean G_FollowNewClient( gentity_t *ent, int dir );
void G_ToggleFollow( gentity_t *ent );
void G_MatchOnePlayer( int *plist, int num, char *err, int len );
diff --git a/src/game/g_main.c b/src/game/g_main.c
index 82647557..a1219909 100644
--- a/src/game/g_main.c
+++ b/src/game/g_main.c
@@ -1041,10 +1041,6 @@ void G_CountSpawns( void )
if( ent->s.modelindex == BA_H_SPAWN && ent->health > 0 )
level.numHumanSpawns++;
}
-
- //let the client know how many spawns there are
- trap_SetConfigstring( CS_SPAWNS, va( "%d %d",
- level.numAlienSpawns, level.numHumanSpawns ) );
}
/*
diff --git a/ui/ingame_options.menu b/ui/ingame_options.menu
index ae3e3325..ac08995b 100644
--- a/ui/ingame_options.menu
+++ b/ui/ingame_options.menu
@@ -382,6 +382,26 @@
}
}
+ itemDef
+ {
+ name game
+ group optionsGrp
+ type ITEM_TYPE_YESNO
+ text "Sticky Spectate:"
+ cvar "cg_stickySpec"
+ rect CONTENT_X (CONTENT_Y+(12*ELEM_H)) CONTENT_W ELEM_H
+ textalign ALIGN_RIGHT
+ textvalign VALIGN_CENTER
+ textalignx CONTENT_OFF
+ textscale .25
+ forecolor 1 1 1 1
+ visible MENU_FALSE
+ action
+ {
+ play "sound/misc/menu1.wav";
+ }
+ }
+
//////// CONTROLS
//Controls menu