diff --git a/OpenRA.Game/Network/Session.cs b/OpenRA.Game/Network/Session.cs index ffa6be46dc..a57135b012 100644 --- a/OpenRA.Game/Network/Session.cs +++ b/OpenRA.Game/Network/Session.cs @@ -61,6 +61,7 @@ namespace OpenRA.Network public bool IsAdmin; public bool IsReady { get { return State == ClientState.Ready; } } public bool IsObserver { get { return Slot == null; } } + public int Ping = -1; } public class Slot @@ -82,7 +83,7 @@ namespace OpenRA.Network public string Map; public string[] Ban; public string[] Mods = { "ra" }; // mod names - public int OrderLatency = 3; + public int OrderLatency = 3; // net tick frames (x 120 = ms) public int RandomSeed = 0; public bool FragileAlliances = false; // Allow diplomatic stance changes after game start. public bool AllowCheats = false; diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index e70a62d027..c16408fe1d 100755 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -164,8 +164,7 @@ namespace OpenRA.Network && !orderManager.GameStarted) { orderManager.FramesAhead = orderManager.LobbyInfo.GlobalSettings.OrderLatency; - Game.Debug( - "Order lag is now {0} frames.".F(orderManager.LobbyInfo.GlobalSettings.OrderLatency)); + Game.Debug("Order lag is now {0} frames.".F(orderManager.LobbyInfo.GlobalSettings.OrderLatency)); } Game.SyncLobbyInfo(); break; diff --git a/OpenRA.Game/Server/Connection.cs b/OpenRA.Game/Server/Connection.cs index b9d4b07b80..8ee7061b0e 100644 --- a/OpenRA.Game/Server/Connection.cs +++ b/OpenRA.Game/Server/Connection.cs @@ -12,6 +12,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; +using System.Net.NetworkInformation; +using System.Threading; namespace OpenRA.Server { @@ -22,8 +24,9 @@ namespace OpenRA.Server public ReceiveState State = ReceiveState.Header; public int ExpectLength = 8; public int Frame = 0; - public int MostRecentFrame = 0; + public string RemoteAddress; + public int Latency = -1; /* client data */ public int PlayerIndex; @@ -97,7 +100,36 @@ namespace OpenRA.Server } break; } } - }} + } + + bool hasBeenPinged; + public void Ping() + { + if (!hasBeenPinged) + { + hasBeenPinged = true; + var pingSender = new Ping(); + pingSender.PingCompleted += new PingCompletedEventHandler(pongRecieved); + AutoResetEvent waiter = new AutoResetEvent(false); + pingSender.SendAsync(RemoteAddress, waiter); + } + } + + void pongRecieved(object sender, PingCompletedEventArgs e) + { + if (e.Cancelled || e.Error != null) + Latency = -1; + else + { + PingReply pong = e.Reply; + if (pong != null && pong.Status == IPStatus.Success) + Latency = (int)pong.RoundtripTime; + else + Latency = -1; + } + } + + } public enum ReceiveState { Header, Data }; } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index ac37c9d75f..020950c9b0 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -59,6 +59,8 @@ namespace OpenRA.Server public Map Map; XTimer gameTimeout; + int highestLatency; + protected volatile ServerState pState = new ServerState(); public ServerState State { @@ -215,6 +217,9 @@ namespace OpenRA.Server DispatchOrdersToClient(newConn, 0, 0, new ServerOrder("HandshakeRequest", request.Serialize()).Serialize()); } catch (Exception) { DropClient(newConn); } + + newConn.RemoteAddress = ((IPEndPoint)newConn.socket.RemoteEndPoint).Address.ToString(); + newConn.Ping(); } void ValidateClient(Connection newConn, string data) @@ -266,8 +271,7 @@ namespace OpenRA.Server // Check if IP is banned if (lobbyInfo.GlobalSettings.Ban != null) { - var remote_addr = ((IPEndPoint)newConn.socket.RemoteEndPoint).Address.ToString(); - if (lobbyInfo.GlobalSettings.Ban.Contains(remote_addr)) + if (lobbyInfo.GlobalSettings.Ban.Contains(newConn.RemoteAddress)) { Console.WriteLine("Rejected connection from "+client.Name+"("+newConn.socket.RemoteEndPoint+"); Banned."); Log.Write("server", "Rejected connection from {0}; Banned.", @@ -282,6 +286,8 @@ namespace OpenRA.Server preConns.Remove(newConn); conns.Add(newConn); + client.Ping = newConn.Latency; + // Enforce correct PlayerIndex and Slot client.Index = newConn.PlayerIndex; client.Slot = lobbyInfo.FirstEmptySlot(); @@ -296,22 +302,24 @@ namespace OpenRA.Server OpenRA.Network.Session.Client clientAdmin = lobbyInfo.Clients.Where(c1 => c1.IsAdmin).Single(); - Log.Write("server", "Client {0}: Accepted connection from {1}", - newConn.PlayerIndex, newConn.socket.RemoteEndPoint); + Log.Write("server", "Client {0}: Accepted connection from {1} with {2} ms ping latency.", + newConn.PlayerIndex, newConn.socket.RemoteEndPoint, newConn.Latency); foreach (var t in ServerTraits.WithInterface()) t.ClientJoined(this, newConn); - SyncLobbyInfo(); SendChat(newConn, "has joined the game."); - if ( File.Exists("{0}motd_{1}.txt".F(Platform.SupportDir, lobbyInfo.GlobalSettings.Mods[0])) ) + SetDynamicOrderLag(); + SyncLobbyInfo(); + + if (File.Exists("{0}motd_{1}.txt".F(Platform.SupportDir, lobbyInfo.GlobalSettings.Mods[0]))) { var motd = System.IO.File.ReadAllText("{0}motd_{1}.txt".F(Platform.SupportDir, lobbyInfo.GlobalSettings.Mods[0])); SendChatTo(newConn, motd); } - if ( lobbyInfo.GlobalSettings.Dedicated ) + if (lobbyInfo.GlobalSettings.Dedicated) { if (client.IsAdmin) SendChatTo(newConn, " You are admin now!"); @@ -494,7 +502,10 @@ namespace OpenRA.Server SendChat(toDrop, "Admin left! {0} is a new admin now!".F(lastClient.Name)); } } - + + if (highestLatency == toDrop.Latency) + SetDynamicOrderLag(); + DispatchOrders( toDrop, toDrop.MostRecentFrame, new byte[] { 0xbf } ); if (conns.Count != 0 || lobbyInfo.GlobalSettings.Dedicated) @@ -510,6 +521,23 @@ namespace OpenRA.Server catch { } } + public void SetDynamicOrderLag() + { + foreach (var conn in conns) + { + if (conn.Latency > highestLatency) + highestLatency = conn.Latency; + } + + Log.Write("server", "Measured {0} ms as the highest connection round trip time.".F(highestLatency)); + + lobbyInfo.GlobalSettings.OrderLatency = highestLatency / 120; + if (lobbyInfo.GlobalSettings.OrderLatency < 1) // should never be 0 + lobbyInfo.GlobalSettings.OrderLatency = 1; + + Log.Write("server", "Order lag has been adjusted to {0} frames.".F(lobbyInfo.GlobalSettings.OrderLatency)); + } + public void SyncLobbyInfo() { if (State != ServerState.GameStarted) /* don't do this while the game is running, it breaks things. */ @@ -542,7 +570,7 @@ namespace OpenRA.Server t.GameStarted(this); // Check TimeOut - if ( Settings.TimeOut > 10000 ) + if (Settings.TimeOut > 10000) { gameTimeout = new XTimer(Settings.TimeOut); gameTimeout.Elapsed += (_,e) => diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs index 13798ceb3e..c7be1963f0 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyLogic.cs @@ -478,6 +478,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic template.Get("NAME").GetText = () => client.Name; if (client.IsAdmin) template.Get("NAME").Font = "Bold"; + if (client.Ping > -1) + template.Get("NAME").GetColor = () => LobbyUtils.GetPingColor(client.Ping); + var color = template.Get("COLOR"); color.GetColor = () => client.ColorRamp.GetColor(0); diff --git a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs index fa04fad141..452b522e6e 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/LobbyUtils.cs @@ -26,6 +26,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic if (c.IsAdmin) name.Font = "Bold"; name.Text = c.Name; + if (c.Ping > -1) + name.TextColor = GetPingColor(c.Ping); name.OnEnterKey = () => { name.Text = name.Text.Trim(); @@ -189,5 +191,14 @@ namespace OpenRA.Mods.RA.Widgets.Logic Game.Renderer.Fonts["Bold"].DrawTextWithContrast(client.Name, position + new int2(5, 5), Color.White, Color.Black, 1); } } + + public static Color GetPingColor(int ping) + { + if (ping > 720) // OrderLag > 6 + return Color.Red; + if (ping > 360) // OrderLag > 3 + return Color.Orange; + return Color.LimeGreen; + } } }