diff --git a/DXMainClient/DXGUI/Multiplayer/CnCNet/TunnelListBox.cs b/DXMainClient/DXGUI/Multiplayer/CnCNet/TunnelListBox.cs index a7cc08303..8678d2f0c 100644 --- a/DXMainClient/DXGUI/Multiplayer/CnCNet/TunnelListBox.cs +++ b/DXMainClient/DXGUI/Multiplayer/CnCNet/TunnelListBox.cs @@ -30,7 +30,7 @@ public TunnelListBox(WindowManager windowManager, TunnelHandler tunnelHandler) : PanelBackgroundDrawMode = PanelBackgroundImageDrawMode.STRETCHED; BackgroundTexture = AssetLoader.CreateTexture(new Color(0, 0, 0, 128), 1, 1); AddColumn("Name", 230); - AddColumn("Official", 70); + AddColumn("Status", 70); AddColumn("Ping", 76); AddColumn("Players", 90); AllowRightClickUnselect = false; @@ -67,14 +67,14 @@ private void TunnelHandler_TunnelsRefreshed(object sender, EventArgs e) { ClearItems(); - int tunnelIndex = 0; + lowestTunnelRating = int.MaxValue; foreach (CnCNetTunnel tunnel in tunnelHandler.Tunnels) { List info = new List(); info.Add(tunnel.Name); - info.Add(Conversions.BooleanToString(tunnel.Official, BooleanStringStyle.YESNO)); + info.Add(tunnel.Official ? "Official" : (tunnel.Recommended) ? "Verified" : "Regular"); if (tunnel.PingInMs < 0) info.Add("Unknown"); else @@ -82,38 +82,29 @@ private void TunnelHandler_TunnelsRefreshed(object sender, EventArgs e) info.Add(tunnel.Clients + " / " + tunnel.MaxClients); AddItem(info, true); - - if ((tunnel.Official || tunnel.Recommended) && tunnel.PingInMs > -1) - { - int rating = GetTunnelRating(tunnel); - if (rating < lowestTunnelRating) - { - bestTunnelIndex = tunnelIndex; - lowestTunnelRating = rating; - } - } - - tunnelIndex++; } if (tunnelHandler.Tunnels.Count > 0) { - if (!isManuallySelectedTunnel) - { - SelectedIndex = bestTunnelIndex; - isManuallySelectedTunnel = false; - } - else + if (isManuallySelectedTunnel) { int manuallySelectedIndex = tunnelHandler.Tunnels.FindIndex(t => t.Address == manuallySelectedTunnelAddress); if (manuallySelectedIndex == -1) { - SelectedIndex = bestTunnelIndex; + SelectedIndex = -1; isManuallySelectedTunnel = false; } else - SelectedIndex = manuallySelectedIndex; + { + CnCNetTunnel tunnel = tunnelHandler.Tunnels[manuallySelectedIndex]; + + if (tunnel.Clients >= tunnel.MaxClients) + { + SelectedIndex = -1; + isManuallySelectedTunnel = false; + } + } } } @@ -128,34 +119,21 @@ private void TunnelHandler_TunnelPinged(int tunnelIndex) if (tunnel.PingInMs == -1) lbItem.Text = "Unknown"; else - { lbItem.Text = tunnel.PingInMs + " ms"; - int rating = GetTunnelRating(tunnel); - if (isManuallySelectedTunnel) - return; + if (tunnel.Rating < lowestTunnelRating) + { + bestTunnelIndex = tunnelIndex; + lowestTunnelRating = tunnel.Rating; - if ((tunnel.Recommended || tunnel.Official) && rating < lowestTunnelRating) + if (!isManuallySelectedTunnel || tunnel.Clients >= tunnel.MaxClients) { - bestTunnelIndex = tunnelIndex; - lowestTunnelRating = rating; SelectedIndex = tunnelIndex; - } + isManuallySelectedTunnel = false; + } } } - private int GetTunnelRating(CnCNetTunnel tunnel) - { - double usageRatio = (double)tunnel.Clients / tunnel.MaxClients; - - if (usageRatio == 0) - usageRatio = 0.1; - - usageRatio *= 100.0; - - return Convert.ToInt32(Math.Pow(tunnel.PingInMs, 2.0) * usageRatio); - } - private void TunnelListBox_SelectedIndexChanged(object sender, EventArgs e) { if (!IsValidIndexSelected()) diff --git a/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs b/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs index 743a78be5..547afbc65 100644 --- a/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs +++ b/DXMainClient/DXGUI/Multiplayer/GameLobby/CnCNetGameLobby.cs @@ -41,7 +41,7 @@ public class CnCNetGameLobby : MultiplayerGameLobby public CnCNetGameLobby(WindowManager windowManager, string iniName, TopBar topBar, List GameModes, CnCNetManager connectionManager, - TunnelHandler tunnelHandler, GameCollection gameCollection, CnCNetUserData cncnetUserData, MapLoader mapLoader, DiscordHandler discordHandler) : + TunnelHandler tunnelHandler, GameCollection gameCollection, CnCNetUserData cncnetUserData, MapLoader mapLoader, DiscordHandler discordHandler) : base(windowManager, iniName, topBar, GameModes, mapLoader, discordHandler) { this.connectionManager = connectionManager; @@ -175,7 +175,7 @@ public override void Initialize() private void GameBroadcastTimer_TimeElapsed(object sender, EventArgs e) => BroadcastGame(); - public void SetUp(Channel channel, bool isHost, int playerLimit, + public void SetUp(Channel channel, bool isHost, int playerLimit, CnCNetTunnel tunnel, string hostName, bool isCustomPassword) { this.channel = channel; @@ -581,10 +581,19 @@ protected override void HostLaunchGame() List playerPorts = tunnelHandler.CurrentTunnel.GetPlayerPortInfo(Players.Count); + if (playerPorts.Count < Players.Count && tunnelHandler.Tunnels.Any()) + { + Logger.Log($"Picking a new tunnel server because '{tunnelHandler.CurrentTunnel.Name}' was full"); + tunnelHandler.CurrentTunnel = tunnelHandler.Tunnels.Aggregate((i1, i2) => i1.Rating < i2.Rating ? i1 : i2); + playerPorts = tunnelHandler.CurrentTunnel.GetPlayerPortInfo(Players.Count); + } + + Logger.Log($"Launching Game using tunnel server '{tunnelHandler.CurrentTunnel.Name}'"); + if (playerPorts.Count < Players.Count) { ShowTunnelSelectionWindow("An error occured while contacting " + - "the CnCNet tunnel server." + Environment.NewLine + + "the CnCNet tunnel server." + Environment.NewLine + "Try picking a different tunnel server:"); AddNotice("An error occured while contacting the specified CnCNet " + "tunnel server. Please try using a different tunnel server ", ERROR_MESSAGE_COLOR); @@ -635,7 +644,7 @@ protected override void RequestReadyStatus() { if (Map == null || GameMode == null) { - AddNotice("The game host needs to select a different map or " + + AddNotice("The game host needs to select a different map or " + "you will be unable to participate in the match."); return; } @@ -697,8 +706,8 @@ private void HandleOptionsRequest(string playerName, int options) if (team < 0 || team > 4) return; - if (side != pInfo.SideId - || start != pInfo.StartingLocation + if (side != pInfo.SideId + || start != pInfo.StartingLocation || team != pInfo.TeamId) { ClearReadyStatuses(); @@ -806,7 +815,7 @@ private void ApplyPlayerOptions(string sender, string message) // If we can't find the player from the channel user list, // ignore the player - // They've either left the channel or got kicked before the + // They've either left the channel or got kicked before the // player options message reached us if (channel.Users.Find(pName) == null) { @@ -890,7 +899,7 @@ protected override void OnGameOptionChanged() // Let's pack the booleans into bytes List byteList = Conversions.BoolArrayIntoBytes(optionValues).ToList(); - + while (byteList.Count % 4 != 0) byteList.Add(0); @@ -1165,7 +1174,7 @@ protected override void GameProcessExited() } /// - /// Handles the "START" (game start) command sent by the game host. + /// Handles the "START" (game start) command sent by the game host. /// private void NonHostLaunchGame(string sender, string message) { @@ -1381,7 +1390,7 @@ private void CheaterNotification(string sender, string cheaterName) if (sender != hostName) return; - AddNotice("Player " + cheaterName + " has different files compared to the game host. Either " + + AddNotice("Player " + cheaterName + " has different files compared to the game host. Either " + cheaterName + " or the game host could be cheating.", Color.Red); } @@ -1464,7 +1473,7 @@ protected override void BanPlayer(int playerIndex) } } - private void HandleCheatDetectedMessage(string sender) => + private void HandleCheatDetectedMessage(string sender) => AddNotice(sender + " has modified game files during the client session. They are likely attempting to cheat!", Color.Red); private void HandleTunnelServerChangeMessage(string sender, string tunnelAddressAndPort) @@ -1504,7 +1513,7 @@ private void HandleTunnelServerChange(CnCNetTunnel tunnel) #region CnCNet map sharing - private void MapSharer_MapDownloadFailed(object sender, SHA1EventArgs e) + private void MapSharer_MapDownloadFailed(object sender, SHA1EventArgs e) => WindowManager.AddCallback(new Action(MapSharer_HandleMapDownloadFailed), e); private void MapSharer_HandleMapDownloadFailed(SHA1EventArgs e) @@ -1615,7 +1624,7 @@ private void HandleMapUploadRequest(string sender, string mapSHA1) { Logger.Log("HandleMapUploadRequest: Map is official, so skip request"); - AddNotice(string.Format("{0} doesn't have the map '{1}' on their local installation. " + + AddNotice(string.Format("{0} doesn't have the map '{1}' on their local installation. " + "The map needs to be changed or {0} is unable to participate in the match.", sender, map.Name)); diff --git a/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetTunnel.cs b/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetTunnel.cs index 60a3c72ac..002497792 100644 --- a/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetTunnel.cs +++ b/DXMainClient/Domain/Multiplayer/CnCNet/CnCNetTunnel.cs @@ -18,7 +18,7 @@ public class CnCNetTunnel public CnCNetTunnel() { } /// - /// Parses a formatted string that contains the tunnel server's + /// Parses a formatted string that contains the tunnel server's /// information into a CnCNetTunnel instance. /// /// The string that contains the tunnel server's information. @@ -34,7 +34,7 @@ public static CnCNetTunnel Parse(string str) string address = parts[0]; string[] detailedAddress = address.Split(new char[] { ':' }); - + tunnel.Address = detailedAddress[0]; tunnel.Port = int.Parse(detailedAddress[1]); tunnel.Country = parts[1]; @@ -85,6 +85,59 @@ public static CnCNetTunnel Parse(string str) public double Distance { get; private set; } public int PingInMs { get; set; } = -1; + /// + /// Rating Factor: + /// The latency in milleseconds that ping times are rounded off to. + /// + private const int LATENCY_PRECISION = 70; + + /// + /// Rating Factor: + /// The importance of latency compared to client count. + /// + private const int LATENCY_WEIGHT = 100000; + + /// + /// Rating Factor: + /// The value of client count compared to latency + /// + private const int CLIENTS_WEIGHT = 1; + + /// + /// Rating Factor: + /// Base Rating for untrusted servers and unpingable servers. + /// + private const int UNTRUSTED_RATING = int.MaxValue - 100000; + + /// + /// Rating Factor: + /// The amount of free client slots needed for the tunnel to be eligible for autoselection + /// + private const int CLIENTS_HEADROOM = 24; + + public int Rating + { + get + { + if (Clients + CLIENTS_HEADROOM >= MaxClients) + return int.MaxValue; + + if (Official || Recommended) + { + if (PingInMs <= -1) + return UNTRUSTED_RATING + Clients; + + int latency = 1 + PingInMs / LATENCY_PRECISION; + + return (latency * LATENCY_WEIGHT) + (Clients * CLIENTS_WEIGHT); + } + else + { + return UNTRUSTED_RATING + Clients; + } + } + } + /// /// Gets a list of player ports to use from a specific tunnel server. ///