diff --git a/OpenRA.Game/GameInformation.cs b/OpenRA.Game/GameInformation.cs
index 0ec409877d..6bca091ef6 100644
--- a/OpenRA.Game/GameInformation.cs
+++ b/OpenRA.Game/GameInformation.cs
@@ -179,6 +179,9 @@ namespace OpenRA
/// The time when this player won or lost the game.
public DateTime OutcomeTimestampUtc;
+ /// The frame at which this player disconnected.
+ public int DisconnectFrame;
+
#endregion
}
}
diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs
index 82b7fe8a65..08188f794a 100644
--- a/OpenRA.Game/Network/UnitOrders.cs
+++ b/OpenRA.Game/Network/UnitOrders.cs
@@ -39,7 +39,13 @@ namespace OpenRA.Network
{
var client = orderManager.LobbyInfo.ClientWithIndex(clientId);
if (client != null)
+ {
client.State = Session.ClientState.Disconnected;
+ var player = world?.FindPlayerByClient(client);
+ if (player != null)
+ world.OnPlayerDisconnected(player);
+ }
+
break;
}
diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs
index 80e70467ae..705bea821c 100644
--- a/OpenRA.Game/Server/Server.cs
+++ b/OpenRA.Game/Server/Server.cs
@@ -1044,6 +1044,12 @@ namespace OpenRA.Server
// Send disconnected order, even if still in the lobby
DispatchOrdersToClients(toDrop, 0, Order.FromTargetString("Disconnected", "", true).Serialize());
+ if (gameInfo != null && !dropClient.IsObserver)
+ {
+ var disconnectedPlayer = gameInfo.Players.First(p => p.ClientIndex == toDrop.PlayerIndex);
+ disconnectedPlayer.DisconnectFrame = toDrop.MostRecentFrame;
+ }
+
LobbyInfo.Clients.RemoveAll(c => c.Index == toDrop.PlayerIndex);
LobbyInfo.ClientPings.RemoveAll(p => p.Index == toDrop.PlayerIndex);
diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs
index 3a26388609..8f8f68ad58 100644
--- a/OpenRA.Game/World.cs
+++ b/OpenRA.Game/World.cs
@@ -552,6 +552,15 @@ namespace OpenRA
}
}
+ public void OnPlayerDisconnected(Player player)
+ {
+ var pi = gameInfo.GetPlayer(player);
+ if (pi == null)
+ return;
+
+ pi.DisconnectFrame = OrderManager.NetFrameNumber;
+ }
+
public void RequestGameSave(string filename)
{
// Allow traits to save arbitrary data that will be passed back via IGameSaveTraitData.ResolveTraitData