summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cgame/cg_draw.c31
-rw-r--r--src/game/g_active.c31
-rw-r--r--src/game/g_client.c11
-rw-r--r--src/game/g_cmds.c255
-rw-r--r--src/game/g_local.h2
-rw-r--r--src/game/g_main.c4
6 files changed, 298 insertions, 36 deletions
diff --git a/src/cgame/cg_draw.c b/src/cgame/cg_draw.c
index fa046dbb..2158379c 100644
--- a/src/cgame/cg_draw.c
+++ b/src/cgame/cg_draw.c
@@ -2844,6 +2844,36 @@ static void CG_DrawIntermission( void )
cg.scoreBoardShowing = CG_DrawScoreboard( );
}
+#define FOLLOWING_STRING "following "
+
+/*
+=================
+CG_DrawFollow
+=================
+*/
+static qboolean CG_DrawFollow( void )
+{
+ float w;
+ vec4_t color;
+ char buffer[ MAX_STRING_CHARS ];
+
+ if( !( cg.snap->ps.pm_flags & PMF_FOLLOW ) )
+ return qfalse;
+
+ color[ 0 ] = 1;
+ color[ 1 ] = 1;
+ color[ 2 ] = 1;
+ color[ 3 ] = 1;
+
+ strcpy( buffer, FOLLOWING_STRING );
+ strcat( buffer, cgs.clientinfo[ cg.snap->ps.clientNum ].name );
+
+ w = CG_Text_Width( buffer, 0.7f, 0 );
+ CG_Text_Paint( 320 - w / 2, 400, 0.7f, color, buffer, 0, 0, ITEM_TEXTSTYLE_SHADOWED );
+
+ return qtrue;
+}
+
//==================================================================================
#define SPECTATOR_STRING "SPECTATOR"
@@ -2901,6 +2931,7 @@ static void CG_Draw2D( void )
CG_DrawVote( );
CG_DrawTeamVote( );
+ CG_DrawFollow( );
// don't draw center string if scoreboard is up
cg.scoreBoardShowing = CG_DrawScoreboard( );
diff --git a/src/game/g_active.c b/src/game/g_active.c
index cad4a2cf..7ef3c810 100644
--- a/src/game/g_active.c
+++ b/src/game/g_active.c
@@ -317,6 +317,9 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
client = ent->client;
+ client->oldbuttons = client->buttons;
+ client->buttons = ucmd->buttons;
+
if( client->sess.spectatorState != SPECTATOR_FOLLOW )
{
if( client->sess.spectatorState == SPECTATOR_LOCKED )
@@ -342,23 +345,23 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd )
G_TouchTriggers( ent );
trap_UnlinkEntity( ent );
- }
-
- client->oldbuttons = client->buttons;
- client->buttons = ucmd->buttons;
-
- if( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) )
- {
- if( client->pers.classSelection == PCL_NONE )
+
+ if( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) )
{
- 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->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 );
+ }
}
}
+
+ if( ( client->buttons & BUTTON_ATTACK2 ) && !( client->oldbuttons & BUTTON_ATTACK2 ) )
+ Cmd_FollowCycle_f( ent, 1 );
}
diff --git a/src/game/g_client.c b/src/game/g_client.c
index 4645780c..35b68f1a 100644
--- a/src/game/g_client.c
+++ b/src/game/g_client.c
@@ -1497,6 +1497,17 @@ void ClientDisconnect( int clientNum )
if( !ent->client )
return;
+ // stop any following clients
+ 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 == clientNum )
+ {
+ G_StopFollowing( &g_entities[ i ] );
+ }
+ }
+
// send effect if they were completely connected
if( ent->client->pers.connected == CON_CONNECTED &&
ent->client->sess.sessionTeam != TEAM_SPECTATOR )
diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c
index d9d9922c..1145bad1 100644
--- a/src/game/g_cmds.c
+++ b/src/game/g_cmds.c
@@ -18,6 +18,90 @@
/*
==================
+G_SanitiseName
+
+Remove case and control characters from a player name
+==================
+*/
+void G_SanitiseName( char *in, char *out )
+{
+ while( *in )
+ {
+ if( *in == 27 )
+ {
+ in += 2; // skip color code
+ continue;
+ }
+
+ if( *in < 32 )
+ {
+ in++;
+ continue;
+ }
+
+ *out++ = tolower( *in++ );
+ }
+
+ *out = 0;
+}
+
+/*
+==================
+G_ClientNumberFromString
+
+Returns a player number for either a number or name string
+Returns -1 if invalid
+==================
+*/
+int G_ClientNumberFromString( gentity_t *to, char *s )
+{
+ gclient_t *cl;
+ int idnum;
+ char s2[ MAX_STRING_CHARS ];
+ char n2[ MAX_STRING_CHARS ];
+
+ // numeric values are just slot numbers
+ if( s[ 0 ] >= '0' && s[ 0 ] <= '9' )
+ {
+ idnum = atoi( s );
+
+ if( idnum < 0 || idnum >= level.maxclients )
+ {
+ trap_SendServerCommand( to - g_entities, va( "print \"Bad client slot: %i\n\"", idnum ) );
+ return -1;
+ }
+
+ cl = &level.clients[ idnum ];
+
+ if( cl->pers.connected != CON_CONNECTED )
+ {
+ trap_SendServerCommand( to - g_entities, va( "print \"Client %i is not active\n\"", idnum ) );
+ return -1;
+ }
+
+ return idnum;
+ }
+
+ // check for a name match
+ G_SanitiseName( s, s2 );
+
+ for( idnum = 0, cl = level.clients; idnum < level.maxclients; idnum++, cl++ )
+ {
+ if( cl->pers.connected != CON_CONNECTED )
+ continue;
+
+ G_SanitiseName( cl->pers.netname, n2 );
+
+ if( !strcmp( n2, s2 ) )
+ return idnum;
+ }
+
+ trap_SendServerCommand( to - g_entities, va( "print \"User %s is not on the server\n\"", s ) );
+ return -1;
+}
+
+/*
+==================
ScoreboardMessage
==================
@@ -336,17 +420,36 @@ void Cmd_Kill_f( gentity_t *ent )
/*
=================
-Cmd_Team_f
+G_ChangeTeam
=================
*/
-void Cmd_Team_f( gentity_t *ent )
+void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam )
{
- int oldTeam;
- char s[MAX_TOKEN_CHARS];
+ pTeam_t oldTeam = ent->client->pers.teamSelection;
- //TA: rip out the q3a team system :)
+ ent->client->pers.teamSelection = newTeam;
- oldTeam = ent->client->pers.teamSelection;
+ if( oldTeam != newTeam )
+ {
+ level.bankCredits[ ent->client->ps.clientNum ] = 0;
+ ent->client->ps.persistant[ PERS_CREDIT ] = 0;
+ ent->client->pers.classSelection = PCL_NONE;
+ ClientSpawn( ent, NULL, NULL, NULL );
+ }
+
+ //update ClientInfo
+ ClientUserinfoChanged( ent->client->ps.clientNum );
+}
+
+/*
+=================
+Cmd_Team_f
+=================
+*/
+void Cmd_Team_f( gentity_t *ent )
+{
+ pTeam_t team;
+ char s[ MAX_TOKEN_CHARS ];
trap_Argv( 1, s, sizeof( s ) );
@@ -357,7 +460,7 @@ void Cmd_Team_f( gentity_t *ent )
}
if( !Q_stricmp( s, "spectate" ) )
- ent->client->pers.teamSelection = PTE_NONE;
+ team = PTE_NONE;
else if( !Q_stricmp( s, "aliens" ) )
{
if( g_teamForceBalance.integer && level.numAlienClients > level.numHumanClients )
@@ -367,7 +470,7 @@ void Cmd_Team_f( gentity_t *ent )
return;
}
- ent->client->pers.teamSelection = PTE_ALIENS;
+ team = PTE_ALIENS;
}
else if( !Q_stricmp( s, "humans" ) )
{
@@ -378,28 +481,19 @@ void Cmd_Team_f( gentity_t *ent )
return;
}
- ent->client->pers.teamSelection = PTE_HUMANS;
+ team = PTE_HUMANS;
}
else if( !Q_stricmp( s, "auto" ) )
{
if( level.numHumanClients > level.numAlienClients )
- ent->client->pers.teamSelection = PTE_ALIENS;
+ team = PTE_ALIENS;
else if( level.numHumanClients < level.numAlienClients )
- ent->client->pers.teamSelection = PTE_HUMANS;
+ team = PTE_HUMANS;
else
- ent->client->pers.teamSelection = PTE_ALIENS + ( rand( ) % 2 );
+ team = PTE_ALIENS + ( rand( ) % 2 );
}
- if( oldTeam != ent->client->pers.teamSelection )
- {
- level.bankCredits[ ent->client->ps.clientNum ] = 0;
- ent->client->ps.persistant[ PERS_CREDIT ] = 0;
- ent->client->pers.classSelection = PCL_NONE;
- ClientSpawn( ent, NULL, NULL, NULL );
- }
-
- //update ClientInfo
- ClientUserinfoChanged( ent->client->ps.clientNum );
+ G_ChangeTeam( ent, team );
//FIXME: put some team change broadcast code here.
}
@@ -1715,6 +1809,117 @@ void Cmd_Reload_f( gentity_t *ent )
/*
=================
+G_StopFollowing
+
+If the client being followed leaves the game, or you just want to drop
+to free floating spectator mode
+=================
+*/
+void G_StopFollowing( gentity_t *ent )
+{
+ ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR;
+ ent->client->sess.sessionTeam = TEAM_SPECTATOR;
+ ent->client->sess.spectatorState = SPECTATOR_FREE;
+ ent->client->ps.pm_flags &= ~PMF_FOLLOW;
+ ent->r.svFlags &= ~SVF_BOT;
+ ent->client->ps.clientNum = ent - g_entities;
+}
+
+/*
+=================
+Cmd_Follow_f
+=================
+*/
+void Cmd_Follow_f( gentity_t *ent )
+{
+ int i;
+ char arg[ MAX_TOKEN_CHARS ];
+
+ if( trap_Argc( ) != 2 )
+ {
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+
+ return;
+ }
+
+ trap_Argv( 1, arg, sizeof( arg ) );
+ i = G_ClientNumberFromString( ent, arg );
+
+ if( i == -1 )
+ return;
+
+ // can't follow self
+ if( &level.clients[ i ] == ent->client )
+ return;
+
+ // can't follow another spectator
+ if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR )
+ return;
+
+ // first set them to spectator
+ if( ent->client->sess.sessionTeam != TEAM_SPECTATOR )
+ G_ChangeTeam( ent, PTE_NONE );
+
+ ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
+ ent->client->sess.spectatorClient = i;
+}
+
+/*
+=================
+Cmd_FollowCycle_f
+=================
+*/
+void Cmd_FollowCycle_f( gentity_t *ent, int dir )
+{
+ int clientnum;
+ int original;
+
+ // first set them to spectator
+ if( ent->client->sess.spectatorState == SPECTATOR_NOT )
+ G_ChangeTeam( ent, PTE_NONE );
+
+ if( dir != 1 && dir != -1 )
+ G_Error( "Cmd_FollowCycle_f: bad dir %i", dir );
+
+ clientnum = ent->client->sess.spectatorClient;
+ original = clientnum;
+
+ do
+ {
+ clientnum += dir;
+
+ if( clientnum >= level.maxclients )
+ clientnum = 0;
+
+ if( clientnum < 0 )
+ clientnum = level.maxclients - 1;
+
+ // can't follow self
+ if( &level.clients[ clientnum ] == ent->client )
+ continue;
+
+ // can only follow connected clients
+ if( level.clients[ clientnum ].pers.connected != CON_CONNECTED )
+ continue;
+
+ // can't follow another spectator
+ if( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR )
+ continue;
+
+ // this is good, we can use it
+ ent->client->sess.spectatorClient = clientnum;
+ ent->client->sess.spectatorState = SPECTATOR_FOLLOW;
+ return;
+
+ } while( clientnum != original );
+
+ // leave it where it was
+}
+
+
+/*
+=================
Cmd_Test_f
=================
*/
@@ -1826,6 +2031,12 @@ void ClientCommand( int clientNum )
Cmd_Vote_f( ent );
else if( Q_stricmp( cmd, "callteamvote" ) == 0 )
Cmd_CallTeamVote_f( ent );
+ else if( Q_stricmp( cmd, "follow" ) == 0 )
+ Cmd_Follow_f( ent );
+ else if( Q_stricmp (cmd, "follownext") == 0)
+ Cmd_FollowCycle_f( ent, 1 );
+ else if( Q_stricmp( cmd, "followprev" ) == 0 )
+ Cmd_FollowCycle_f( ent, -1 );
else if( Q_stricmp( cmd, "teamvote" ) == 0 )
Cmd_TeamVote_f( ent );
else if( Q_stricmp( cmd, "setviewpos" ) == 0 )
diff --git a/src/game/g_local.h b/src/game/g_local.h
index 926cecb0..99f6ebf4 100644
--- a/src/game/g_local.h
+++ b/src/game/g_local.h
@@ -564,6 +564,8 @@ char *G_NewString( const char *string );
// g_cmds.c
//
void Cmd_Score_f( gentity_t *ent );
+void G_StopFollowing( gentity_t *ent );
+void Cmd_FollowCycle_f( gentity_t *ent, int dir );
//
// g_physics.c
diff --git a/src/game/g_main.c b/src/game/g_main.c
index eb81594d..37cd9a01 100644
--- a/src/game/g_main.c
+++ b/src/game/g_main.c
@@ -1153,6 +1153,10 @@ If a new client connects, this will be called after the spawn function.
*/
void MoveClientToIntermission( gentity_t *ent )
{
+ // take out of follow mode if needed
+ if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW )
+ G_StopFollowing( ent );
+
// move to the spot
VectorCopy( level.intermission_origin, ent->s.origin );
VectorCopy( level.intermission_origin, ent->client->ps.origin );