Commits

Torr_Samaho committed 456ad27

Clients can now control how often the server sends them updated player positions. The is done with the new CVAR cl_ticsperupdate (default value 3, allowed values 1-3). The lower the number, the smoother the movement of the other player appears on a client but the more network traffic is used by the position updates.

SVN r3292 (latestzdoom)

Comments (0)

Files changed (9)

docs/Skulltag Version History.txt

 +	- Added AnnouncerSound(str entry, int flags) function to allow custom announcer events. [Blzut3]
 +	- Considerably improved client side prediction when the player is standing/jumping/moving/landing on another actor, e.g. a bridge thing. [Torr Samaho]
 +	- Considerably improved client side prediction after respawning and teleporting. [Torr Samaho]
++	- Clients can now control how often the server sends them updated player positions. The is done with the new CVAR cl_ticsperupdate (default value 3, allowed values 1-3). The lower the number, the smoother the movement of the other player appears on a client but the more network traffic is used by the position updates. [Torr Samaho]
 -	- Fixed: After a "changemap" map change on a server with join password and a team based gamemode, players who were on a team on the previous map were turned into spectators but kept their team. [Torr Samaho]
 -	- Fixed a crash that could happen when pressing the "show medals" key during intermission if the player was spying another player before the start of the intermission. [Torr Samaho]
 -	- Fixed: When the gamemode is automatically changed upon entering a new map after a "changemap" map change offline, players possibly stay on a team even though the new game mode doesn't have teams or are on no team even though the new game mode uses teams. [Torr Samaho]

src/cl_commands.cpp

 		NETWORK_WriteByte( &CLIENT_GetLocalBuffer( )->ByteStream, players[consoleplayer].userinfo.bUnlagged );
 	if ( ulFlags & USERINFO_RESPAWNONFIRE )
 		NETWORK_WriteByte( &CLIENT_GetLocalBuffer( )->ByteStream, players[consoleplayer].userinfo.bRespawnonfire );
+	if ( ulFlags & USERINFO_TICSPERUPDATE )
+		NETWORK_WriteByte( &CLIENT_GetLocalBuffer( )->ByteStream, players[consoleplayer].userinfo.ulTicsPerUpdate );
 	if (( PlayerClasses.Size( ) > 1 ) && ( ulFlags & USERINFO_PLAYERCLASS ))
 	{
 		if ( players[consoleplayer].userinfo.PlayerClass == -1 )
 	NETWORK_WriteByte( &g_ByteStream, players[consoleplayer].userinfo.lHandicap );
 	NETWORK_WriteByte( &g_ByteStream, players[consoleplayer].userinfo.bUnlagged );
 	NETWORK_WriteByte( &g_ByteStream, players[consoleplayer].userinfo.bRespawnonfire );
+	NETWORK_WriteByte( &g_ByteStream, players[consoleplayer].userinfo.ulTicsPerUpdate );
 	NETWORK_WriteString( &g_ByteStream, PlayerClasses[players[consoleplayer].CurrentPlayerClass].Type->Meta.GetMetaString( APMETA_DisplayName ));
 }
 
 	players[consoleplayer].userinfo.lHandicap = NETWORK_ReadByte( &g_ByteStream );
 	players[consoleplayer].userinfo.bUnlagged = NETWORK_ReadByte( &g_ByteStream );
 	players[consoleplayer].userinfo.bRespawnonfire = NETWORK_ReadByte( &g_ByteStream );
+	players[consoleplayer].userinfo.ulTicsPerUpdate = NETWORK_ReadByte( &g_ByteStream );
 	players[consoleplayer].userinfo.PlayerClass = D_PlayerClassToInt( NETWORK_ReadString( &g_ByteStream ));
 
 	R_BuildPlayerTranslation( consoleplayer );
 	LONG		lSkin;
 	bool		bUnlagged;
 	bool		bRespawnonfire;
+	ULONG		ulTicsPerUpdate;
 
 	// Read in the player whose userinfo is being sent to us.
 	ulPlayer = NETWORK_ReadByte( pByteStream );
 	if ( ulFlags & USERINFO_RESPAWNONFIRE )
 		bRespawnonfire = NETWORK_ReadByte( pByteStream );
 
+	// [BB] Read in the player's respawnonfire setting.
+	if ( ulFlags & USERINFO_TICSPERUPDATE )
+		ulTicsPerUpdate = NETWORK_ReadByte( pByteStream );
+
 	// If this isn't a valid player, break out.
 	// We actually send the player's userinfo before he gets spawned, thus putting him in
 	// the game. Therefore, this call won't work unless the way the server sends the data
 	if ( ulFlags & USERINFO_RESPAWNONFIRE )
 		pPlayer->userinfo.bRespawnonfire = bRespawnonfire;
 
+	if ( ulFlags & USERINFO_TICSPERUPDATE )
+		pPlayer->userinfo.ulTicsPerUpdate = ulTicsPerUpdate;
+
 	// Build translation tables, always gotta do this!
 	R_BuildPlayerTranslation( ulPlayer );
 }

src/d_netinfo.cpp

 CVAR (Bool,		cl_unlagged,			true,		CVAR_USERINFO | CVAR_ARCHIVE);
 // [BB] Let the user decide whether he wants to respawn when pressing fire.
 CVAR (Bool,		cl_respawnonfire,			true,		CVAR_USERINFO | CVAR_ARCHIVE);
+// [BB] Let the user control how often the server sends updated player positions to him.
+CVAR (Int,		cl_ticsperupdate,			3,		CVAR_USERINFO | CVAR_ARCHIVE);
 
 // [BB] Two variables to keep track of client side name changes.
 static	ULONG	g_ulLastNameChangeTime = 0;
 	// [Spleen] The player's unlagged preference.
 	INFO_Unlagged,
 	// [BB]
-	INFO_Respawnonfire
+	INFO_Respawnonfire,
+	// [BB]
+	INFO_Ticsperupdate
 };
 
 const char *GenderNames[3] = { "male", "female", "other" };
 	// [BB]
 	coninfo->bRespawnonfire = cl_respawnonfire;
 
+	// [BB]
+	coninfo->ulTicsPerUpdate = cl_ticsperupdate;
+
 	R_BuildPlayerTranslation (consoleplayer);
 }
 
 	{
 		ulUpdateFlags |= USERINFO_RESPAWNONFIRE;
 	}
+	// [BB]
+	else if ( cvar == &cl_ticsperupdate )
+	{
+		if ( cl_ticsperupdate < 1 )
+		{
+			cl_ticsperupdate = 1;
+			return;
+		}
+		if ( cl_ticsperupdate > 3 )
+		{
+			cl_ticsperupdate = 3;
+			return;
+		}
+
+		ulUpdateFlags |= USERINFO_TICSPERUPDATE;
+	}
 
 	val = cvar->GetGenericRep (CVAR_String);
 	escaped_val = D_EscapeUserInfo(val.String);
 					 "\\playerclass\\%s"
 					 "\\unlagged\\%s" // [Spleen]
 					 "\\respawnonfire\\%s" // [BB]
+					 "\\ticsperupdate\\%d" // [BB]
 					 ,
 					 D_EscapeUserInfo(info->netname).GetChars(),
 					 (double)info->aimdist / (float)ANGLE_1,
 					 // [Spleen] Write the player's unlagged preference.
 					 info->bUnlagged ? "on" : "off",
 					 // [BB]
-					 info->bRespawnonfire ? "on" : "off"
+					 info->bRespawnonfire ? "on" : "off",
+					 // [BB]
+					 info->ulTicsPerUpdate
 				);
 		}
 		else
 				"\\%s"			// playerclass
 				"\\%s"			// [Spleen] unlagged
 				"\\%s"			// [BB] respawnonfire
+				"\\%d"			// [BB] ticsperupdate
 				,
 				D_EscapeUserInfo(info->netname).GetChars(),
 				(double)info->aimdist / (float)ANGLE_1,
 				// [Spleen] Write the player's unlagged preference.
 				info->bUnlagged ? "on" : "off",
 				// [BB]
-				info->bRespawnonfire ? "on" : "off"
+				info->bRespawnonfire ? "on" : "off",
+				// [BB]
+				info->ulTicsPerUpdate
 			);
 		}
 	}
 					info->bRespawnonfire = false;
 				break;
 
+			// [BB]
+			case INFO_Ticsperupdate:
+				info->ulTicsPerUpdate = clamp ( atoi (value), 1, 3 );
+				break;
+
 			default:
 				break;
 			}
 		Printf ("Unlagged:       %s\n", ui->bUnlagged ? "on" : "off");
 		// [BB]
 		Printf ("Respawnonfire:  %s\n", ui->bRespawnonfire ? "on" : "off");
+		// [BB]
+		Printf ("Ticsperupdate:  %d\n", ui->ulTicsPerUpdate);
 	}
 }
 
 
 	// [BB] Let the user decide whether he wants to respawn when pressing fire.
 	bool		bRespawnonfire;
+
+	// [BB] Let the user decide how often he wants the player positions to be updated.
+	ULONG		ulTicsPerUpdate;
 };
 
 FArchive &operator<< (FArchive &arc, userinfo_t &info);
 #define	USERINFO_PLAYERCLASS		128
 #define	USERINFO_UNLAGGED			256
 #define	USERINFO_RESPAWNONFIRE			512
+#define	USERINFO_TICSPERUPDATE			1024
 
 #define	USERINFO_ALL				( USERINFO_NAME | USERINFO_GENDER | USERINFO_COLOR | \
 									USERINFO_AIMDISTANCE | USERINFO_SKIN | USERINFO_RAILCOLOR | \
-									USERINFO_HANDICAP | USERINFO_PLAYERCLASS | USERINFO_UNLAGGED | USERINFO_RESPAWNONFIRE )
+									USERINFO_HANDICAP | USERINFO_PLAYERCLASS | USERINFO_UNLAGGED | USERINFO_RESPAWNONFIRE | USERINFO_TICSPERUPDATE )
 
 // [BB]: Some optimization. For some actors that are sent in bunches, to reduce the size,
 // just send some key letter that identifies the actor, instead of the full name.

src/sv_commands.cpp

 
 		if ( ulUserInfoFlags & USERINFO_RESPAWNONFIRE )
 			NETWORK_WriteByte( &SERVER_GetClient( ulIdx )->PacketBuffer.ByteStream, players[ulPlayer].userinfo.bRespawnonfire );
+
+		if ( ulUserInfoFlags & USERINFO_TICSPERUPDATE )
+			NETWORK_WriteByte( &SERVER_GetClient( ulIdx )->PacketBuffer.ByteStream, players[ulPlayer].userinfo.ulTicsPerUpdate );
 	}
 }
 
 	if ( ulFlags & USERINFO_RESPAWNONFIRE )
 		pPlayer->userinfo.bRespawnonfire = NETWORK_ReadByte( pByteStream );
 
+	// [BB]
+	if ( ulFlags & USERINFO_TICSPERUPDATE )
+		pPlayer->userinfo.ulTicsPerUpdate = NETWORK_ReadByte( pByteStream );
+
 	// If this is a Hexen game, read in the player's class.
 	if ( ulFlags & USERINFO_PLAYERCLASS )
 		strncpy( szClass, NETWORK_ReadString( pByteStream ), 63 );
 //
 void SERVER_WriteCommands( void )
 {
-	AActor		*pActor;
-	ULONG		ulIdx;
-	
 	// Ping clients and stuff.
 	SERVER_SendHeartBeat( );
 
-	// Don't need to update origin every tic. The server sends origin and velocity of a 
-	// player and the client always knows origin on the next tic.
-	if (( gametic % 3 ) == 0 )
-	{
+	for ( ULONG ulIdx = 0; ulIdx < MAXPLAYERS; ++ulIdx )
+	{
+		// [BB] Only clients need to be informed about player movement.
+		if ( SERVER_IsValidClient( ulIdx ) == false )
+			continue;
+
+		// Don't need to update origin every tic. 
+		// [BB] The client decides how often he wants to be updated.
+		// The server sends origin and velocity of a 
+		// player and the client always knows origin on the next tic.
+		if (( gametic % players[ulIdx].userinfo.ulTicsPerUpdate ) != 0 )
+			continue;
+
+		// [BB] You can be watching through the eyes of someone, even if you are not a spectator.
+		// If this player is watching through the eyes of another player, send this
+		// player some extra info about that player to make for a better watching
+		// experience.
+		if (( g_aClients[ulIdx].ulDisplayPlayer != ulIdx ) &&
+			( g_aClients[ulIdx].ulDisplayPlayer <= MAXPLAYERS ) &&
+			( players[g_aClients[ulIdx].ulDisplayPlayer].mo != NULL ))
+		{
+			SERVERCOMMANDS_UpdatePlayerExtraData( ulIdx, g_aClients[ulIdx].ulDisplayPlayer );
+		}
+
 		// See if any players need to be updated to clients.
-		for ( ulIdx = 0; ulIdx < MAXPLAYERS; ulIdx++ )
+		for ( ULONG ulPlayer = 0; ulPlayer < MAXPLAYERS; ulPlayer++ )
 		{
-			if ( playeringame[ulIdx] == false )
+			if ( ( playeringame[ulPlayer] == false ) || players[ulPlayer].bSpectating )
 				continue;
 
-			// [BB] You can be watching through the eyes of someone, even if you are not a spectator.
-			// If this player is watching through the eyes of another player, send this
-			// player some extra info about that player to make for a better watching
-			// experience.
-			if (( g_aClients[ulIdx].ulDisplayPlayer != ulIdx ) &&
-				( g_aClients[ulIdx].ulDisplayPlayer <= MAXPLAYERS ) &&
-				( players[g_aClients[ulIdx].ulDisplayPlayer].mo != NULL ))
+			// [BB] The consoleplayer on a client has to be moved differently.
+			if ( ulPlayer == ulIdx )
+				continue;
+
+			SERVERCOMMANDS_MovePlayer( ulPlayer, ulIdx, SVCF_ONLYTHISCLIENT );
+		}
+
+		// Spectators can move around freely, without us telling it what to do (lag-less).
+		if ( players[ulIdx].bSpectating ) 
+		{
+			// Don't send this to bots.
+			if ((( gametic % ( players[ulIdx].userinfo.ulTicsPerUpdate * TICRATE )) == 0 ) && ( players[ulIdx].bIsBot == false ) ) 
 			{
-				SERVERCOMMANDS_UpdatePlayerExtraData( ulIdx, g_aClients[ulIdx].ulDisplayPlayer );
+				// Just send them one byte to let them know they're still alive.
+				SERVERCOMMANDS_Nothing( ulIdx );
 			}
 
-			// Spectators can move around freely, without us telling it what to do (lag-less).
-			if ( players[ulIdx].bSpectating ) 
-			{
-				// Don't send this to bots.
-				if ((( gametic % ( 3 * TICRATE )) == 0 ) && ( players[ulIdx].bIsBot == false ) ) 
-				{
-					// Just send them one byte to let them know they're still alive.
-					SERVERCOMMANDS_Nothing( ulIdx );
-				}
-
-				continue;
-			}
-
-			pActor = players[ulIdx].mo;
-			if ( pActor == NULL )
-				continue;
-
-			SERVERCOMMANDS_MovePlayer( ulIdx, ulIdx, SVCF_SKIPTHISCLIENT );
-			SERVERCOMMANDS_MoveLocalPlayer( ulIdx );
+			continue;
 		}
+
+		SERVERCOMMANDS_MoveLocalPlayer( ulIdx );
 	}
 
 	// Once every four seconds, update each player's ping.
 	if (( gametic % ( 4 * TICRATE )) == 0 )
 	{
-		for ( ulIdx = 0; ulIdx < MAXPLAYERS; ulIdx++ )
+		for ( ULONG ulIdx = 0; ulIdx < MAXPLAYERS; ulIdx++ )
 		{
 			if ( SERVER_IsValidClient( ulIdx ) == false )
 				continue;