diff options
author | Michael Levin <risujin@fastmail.fm> | 2009-10-03 11:25:01 +0000 |
---|---|---|
committer | Tim Angus <tim@ngus.net> | 2013-01-03 00:14:58 +0000 |
commit | 5189226d1e8df1e56240d51a32d0db82924320d7 (patch) | |
tree | fccc195f973841dfa5dad503421e01774b2c9f22 | |
parent | 4b1bd2babe3c5b37648b987d16225d9f111205ce (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.c | 41 | ||||
-rw-r--r-- | src/cgame/cg_local.h | 5 | ||||
-rw-r--r-- | src/cgame/cg_main.c | 3 | ||||
-rw-r--r-- | src/cgame/cg_servercmds.c | 3 | ||||
-rw-r--r-- | src/cgame/cg_tutorial.c | 11 | ||||
-rw-r--r-- | src/cgame/cg_view.c | 3 | ||||
-rw-r--r-- | src/game/bg_public.h | 3 | ||||
-rw-r--r-- | src/game/g_active.c | 158 | ||||
-rw-r--r-- | src/game/g_client.c | 32 | ||||
-rw-r--r-- | src/game/g_cmds.c | 84 | ||||
-rw-r--r-- | src/game/g_combat.c | 3 | ||||
-rw-r--r-- | src/game/g_local.h | 2 | ||||
-rw-r--r-- | src/game/g_main.c | 4 | ||||
-rw-r--r-- | ui/ingame_options.menu | 20 |
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 |