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);