diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs
index deebab3d5..96fbc7dfe 100644
--- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs
+++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs
@@ -1029,9 +1029,10 @@ namespace Barotrauma.Networking
EndVoteTickBox.Selected = false;
- int seed = inc.ReadInt32();
- string levelSeed = inc.ReadString();
- float levelDifficulty = inc.ReadFloat();
+ int seed = inc.ReadInt32();
+ string levelSeed = inc.ReadString();
+ int levelEqualityCheckVal = inc.ReadInt32();
+ float levelDifficulty = inc.ReadFloat();
byte losMode = inc.ReadByte();
@@ -1100,6 +1101,14 @@ namespace Barotrauma.Networking
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
}
+ if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal)
+ {
+ string errorMsg = " Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ").";
+ DebugConsole.ThrowError(errorMsg, createMessageBox: true);
+ CoroutineManager.StartCoroutine(EndGame(""));
+ yield return CoroutineStatus.Failure;
+ }
+
if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null);
gameStarted = true;
@@ -1414,7 +1423,7 @@ namespace Barotrauma.Networking
break;
case ServerNetObject.ENTITY_EVENT:
case ServerNetObject.ENTITY_EVENT_INITIAL:
- entityEventManager.Read(objHeader, inc, sendingTime, entities);
+ if (!entityEventManager.Read(objHeader, inc, sendingTime, entities)) { break; }
break;
case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc);
diff --git a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs
index a17d20aac..f7451440a 100644
--- a/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs
+++ b/Barotrauma/BarotraumaClient/Source/Networking/NetEntityEvent/ClientEntityEventManager.cs
@@ -114,9 +114,9 @@ namespace Barotrauma.Networking
private UInt16? firstNewID;
///
- /// Read the events from the message, ignoring ones we've already received
+ /// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
///
- public void Read(ServerNetObject type, NetIncomingMessage msg, float sendingTime, List entities)
+ public bool Read(ServerNetObject type, NetIncomingMessage msg, float sendingTime, List entities)
{
UInt16 unreceivedEntityEventCount = 0;
@@ -192,6 +192,7 @@ namespace Barotrauma.Networking
"Received msg " + thisEventID + ", entity " + entityID + " not found",
Microsoft.Xna.Framework.Color.Red);
GameMain.Client.ReportError(ClientNetError.MISSING_ENTITY, eventID: thisEventID, entityID: entityID);
+ return false;
}
msg.Position += msgLength * 8;
@@ -230,6 +231,7 @@ namespace Barotrauma.Networking
}
msg.ReadPadBits();
}
+ return true;
}
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null)
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
index 4c4641540..0bf1f2535 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
@@ -734,9 +734,57 @@ namespace Barotrauma.Networking
fileSender.ReadFileRequest(inc, connectedClient);
}
break;
+ case ClientPacketHeader.ERROR:
+ HandleClientError(inc, connectedClient);
+ break;
}
}
+ private void HandleClientError(NetIncomingMessage inc, Client c)
+ {
+ string errorStr = "Unhandled error report";
+
+ ClientNetError error = (ClientNetError)inc.ReadByte();
+ int levelEqualityCheckVal = inc.ReadInt32();
+ switch (error)
+ {
+ case ClientNetError.MISSING_EVENT:
+ UInt16 expectedID = inc.ReadUInt16();
+ UInt16 receivedID = inc.ReadUInt16();
+ errorStr = "Expecting event id " + expectedID.ToString() + ", received " + receivedID.ToString();
+ break;
+ case ClientNetError.MISSING_ENTITY:
+ UInt16 eventID = inc.ReadUInt16();
+ UInt16 entityID = inc.ReadUInt16();
+ Entity entity = Entity.FindEntityByID(entityID);
+ if (entity == null)
+ {
+ errorStr = "Received an update for an entity that doesn't exist (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ").";
+ }
+ else if (entity is Character character)
+ {
+ errorStr = "Missing character " + character.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ").";
+ }
+ else if (entity is Item item)
+ {
+ errorStr = "Missing item " + item.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ").";
+ }
+ else
+ {
+ errorStr = "Missing entity " + entity.ToString() + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ").";
+ }
+ break;
+ }
+
+ if (Level.Loaded != null && levelEqualityCheckVal != Level.Loaded.EqualityCheckVal)
+ {
+ errorStr += " Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ").";
+ }
+
+ Log(c.Name + " has reported an error: " + errorStr, ServerLog.MessageType.Error);
+ KickClient(c, errorStr);
+ }
+
public override void CreateEntityEvent(INetSerializable entity, object[] extraData = null)
{
if (!(entity is IServerSerializable)) throw new InvalidCastException("entity is not IServerSerializable");
@@ -1759,6 +1807,7 @@ namespace Barotrauma.Networking
msg.Write(seed);
msg.Write(GameMain.GameSession.Level.Seed);
+ msg.Write(GameMain.GameSession.Level.EqualityCheckVal);
msg.Write(serverSettings.SelectedLevelDifficulty);
msg.Write((byte)GameMain.Config.LosMode);