Index: binaries/data/mods/public/gui/session/messages.js =================================================================== --- binaries/data/mods/public/gui/session/messages.js +++ binaries/data/mods/public/gui/session/messages.js @@ -39,6 +39,9 @@ "netwarn": msg => { addNetworkWarning(msg); }, + "out-of-sync": msg => { + onNetworkOutOfSync(msg); + }, "players": msg => { handlePlayerAssignmentsMessage(msg); }, @@ -601,6 +604,58 @@ } } +function onNetworkOutOfSync(msg) +{ + let txt = [ + sprintf(translate("Out-of-sync error on turn %(turn)s."), { + "turn": msg.turn + }), + + sprintf(translateWithContext("Out-Of-Sync", "Players: %(players)s"), { + "players": msg.players.join(translate(", ")) + }), + + msg.hash == msg.expectedHash ? + translateWithContext("Out-Of-Sync", "Your game state is identical to the hosts game state.") : + translateWithContext("Out-Of-Sync", "Your game state differs from the hosts game state."), + + "" + ]; + + if (msg.turn > 1 && g_GameAttributes.settings.PlayerData.some(pData => pData && pData.AI)) + txt.push(translateWithContext("Out-Of-Sync", "Rejoining Multiplayer games with AIs is not supported yet!")); + else + { + txt.push( + translateWithContext("Out-Of-Sync", "Ensure all players use the same mods."), + translateWithContext("Out-Of-Sync", 'Click on "Report Bugs" in the main menu to help fix this.'), + sprintf(translateWithContext("Out-Of-Sync", "Replay saved to %(filepath)s"), { + "filepath": escapeText(msg.path_replay) + }), + sprintf(translateWithContext("Out-Of-Sync", "Dumping current state to %(filepath)s"), { + "filepath": escapeText(msg.path_oos_dump) + }) + ); + } + + messageBox( + 600, 280, + txt.join("\n"), + translate("Out Of Sync") + ); +} + +function onReplayOutOfSync() +{ + messageBox( + 500, 140, + translate("Out of synchronization error!") + "\n" + + // Translation: This is shown if replay is out of sync + translateWithContext("Out-Of-Sync", "The current game state is different from the original game state."), + translate("Out Of Sync") + ); +} + function handlePlayerAssignmentsMessage(message) { for (let guid in g_PlayerAssignments) Index: binaries/data/mods/public/gui/session/session.xml =================================================================== --- binaries/data/mods/public/gui/session/session.xml +++ binaries/data/mods/public/gui/session/session.xml @@ -30,6 +30,10 @@ onReplayFinished(); + + onReplayOutOfSync(); + + this.hidden = !this.hidden; Index: source/network/NetClientTurnManager.cpp =================================================================== --- source/network/NetClientTurnManager.cpp +++ source/network/NetClientTurnManager.cpp @@ -122,18 +122,29 @@ hash = Hexify(hash); - std::stringstream msg; - msg << "Out of sync on turn " << turn; - + std::stringstream playerNamesString; + std::vector playerNamesStrings; + playerNamesStrings.reserve(playerNames.size()); for (size_t i = 0; i < playerNames.size(); ++i) - msg << (i == 0 ? "\nPlayers: " : ", ") << utf8_from_wstring(playerNames[i].m_Name); - - msg << "\n\n" << "Your game state is " << (expectedHash == hash ? "identical to" : "different from") << " the hosts game state."; - - msg << "\n\n" << "Dumping current state to " << CStr(path.string8()).EscapeToPrintableASCII(); + { + CStr name = utf8_from_wstring(playerNames[i].m_Name); + playerNamesString << (i == 0 ? "" : ", ") << name; + playerNamesStrings.push_back(name); + } - LOGERROR("%s", msg.str()); + LOGERROR("Out-Of-Sync on turn %d\nPlayers: %s\nDumping state to %s", turn, playerNamesString.str().c_str(), path.string8()); - if (g_GUI) - g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str())); + ScriptInterface& scriptInterface = m_NetClient.GetScriptInterface(); + JSContext* cx = scriptInterface.GetContext(); + JSAutoRequest rq(cx); + + JS::RootedValue msg(cx); + scriptInterface.Eval("({ 'type':'out-of-sync' })", &msg); + scriptInterface.SetProperty(msg, "turn", turn); + scriptInterface.SetProperty(msg, "players", playerNamesStrings); + scriptInterface.SetProperty(msg, "expectedHash", expectedHash); + scriptInterface.SetProperty(msg, "hash", hash); + scriptInterface.SetProperty(msg, "path_oos_dump", path.string8()); + scriptInterface.SetProperty(msg, "path_replay", m_Replay.GetDirectory().string8()); + m_NetClient.PushGuiMessage(msg); } Index: source/simulation2/system/ReplayTurnManager.h =================================================================== --- source/simulation2/system/ReplayTurnManager.h +++ source/simulation2/system/ReplayTurnManager.h @@ -41,8 +41,6 @@ void DoTurn(u32 turn); - void OnSyncError(u32 turn); - // Contains the commands of every player on each turn std::map>> m_ReplayCommands; Index: source/simulation2/system/ReplayTurnManager.cpp =================================================================== --- source/simulation2/system/ReplayTurnManager.cpp +++ source/simulation2/system/ReplayTurnManager.cpp @@ -77,7 +77,11 @@ hash = Hexify(hash); if (hash != expectedHash) - OnSyncError(turn); + { + m_HasSyncError = true; + LOGERROR("Replay out of sync on turn %d", turn); + g_GUI->SendEventToAll("ReplayOutOfSync"); + } } void CReplayTurnManager::DoTurn(u32 turn) @@ -100,16 +104,3 @@ if (turn == m_FinalTurn) g_GUI->SendEventToAll("ReplayFinished"); } - -void CReplayTurnManager::OnSyncError(u32 turn) -{ - m_HasSyncError = true; - - std::stringstream msg; - msg << "Out of sync on turn " << turn << "\n\n" << "The current game state is different from the original game state."; - - LOGERROR("%s", msg.str()); - - if (g_GUI) - g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str())); -}