Index: binaries/data/mods/public/gui/session/session.js
===================================================================
--- binaries/data/mods/public/gui/session/session.js (revision 16726)
+++ binaries/data/mods/public/gui/session/session.js (working copy)
@@ -555,10 +555,20 @@
if (battleState)
global.music.setState(global.music.states[battleState]);
}
}
+function onReplayFinished()
+{
+ closeMenu();
+ closeOpenDialogs();
+ pauseGame();
+ var btCaptions = [translateWithContext("replayFinished", "Yes"), translateWithContext("replayFinished", "No")];
+ var btCode = [leaveGame, resumeGame];
+ messageBox(400, 200, translateWithContext("replayFinished", "The replay has finished. Do you want to quit?"), translateWithContext("replayFinished","Confirmation"), 0, btCaptions, btCode);
+}
+
/**
* updates a status bar on the GUI
* nameOfBar: name of the bar
* points: points to show
* maxPoints: max points
Index: binaries/data/mods/public/gui/session/session.xml
===================================================================
--- binaries/data/mods/public/gui/session/session.xml (revision 16726)
+++ binaries/data/mods/public/gui/session/session.xml (working copy)
@@ -20,10 +20,14 @@
onSimulationUpdate();
+
+ onReplayFinished();
+
+
this.hidden = !this.hidden;
Index: source/main.cpp
===================================================================
--- source/main.cpp (revision 16726)
+++ source/main.cpp (working copy)
@@ -440,27 +440,44 @@
return;
// run non-visual simulation replay if requested
if (args.Has("replay"))
{
+ std::string replayFile = args.Get("replay");
+ if (!FileExists(OsPath(replayFile)))
+ {
+ debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str());
+ return;
+ }
Paths paths(args);
g_VFS = CreateVfs(20 * MiB);
g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE);
MountMods(paths, GetMods(args, INIT_MODS));
{
CReplayPlayer replay;
- replay.Load(args.Get("replay"));
+ replay.Load(replayFile);
replay.Replay(args.Has("serializationtest"), args.Has("ooslog"));
}
g_VFS.reset();
CXeromyces::Terminate();
return;
}
+ // If visual replay file does not exist, quit before starting the renderer
+ if (args.Has("replay-visual"))
+ {
+ std::string replayFile = args.Get("replay-visual");
+ if (!FileExists(OsPath(replayFile)))
+ {
+ debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str());
+ return;
+ }
+ }
+
// run in archive-building mode if requested
if (args.Has("archivebuild"))
{
Paths paths(args);
Index: source/network/NetTurnManager.cpp
===================================================================
--- source/network/NetTurnManager.cpp (revision 16726)
+++ source/network/NetTurnManager.cpp (working copy)
@@ -1,6 +1,6 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
@@ -223,30 +223,53 @@
NETTURN_LOG((L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str()));
// Only complain the first time
if (m_HasSyncError)
return;
- m_HasSyncError = true;
bool quick = !TurnNeedsFullHash(turn);
std::string hash;
- bool ok = m_Simulation2.ComputeStateHash(hash, quick);
- ENSURE(ok);
+ ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
OsPath path = psLogDir()/"oos_dump.txt";
std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
m_Simulation2.DumpDebugState(file);
file.close();
+ hash = Hexify(hash);
+ const std::string& expectedHashHex = Hexify(expectedHash);
+
+ DisplayOOSError(turn, hash, expectedHashHex, false, &path);
+}
+
+void CNetTurnManager::DisplayOOSError(u32 turn, std::string& hash, const std::string& expectedHash, const bool isReplay, OsPath* path = NULL)
+{
+ m_HasSyncError = true;
+
std::stringstream msg;
- msg << "Out of sync on turn " << turn << ": expected hash " << Hexify(expectedHash) << "\n\n";
- msg << "Current state: turn " << m_CurrentTurn << ", hash " << Hexify(hash) << "\n\n";
- msg << "Dumping current state to " << utf8_from_wstring(path.string());
+ msg << "Out of sync on turn " << turn << ": expected hash " << expectedHash << "\n";
+
+ if (expectedHash != hash || m_CurrentTurn != turn)
+ msg << "\nCurrent state: turn " << m_CurrentTurn << ", hash " << hash << "\n\n";
+
+ if (isReplay)
+ msg << "\nThe current game state is different from the original game state.\n\n";
+ else
+ {
+ if (expectedHash == hash)
+ msg << "Your game state is identical to the hosts game state.\n\n";
+ else
+ msg << "Your game state is different from the hosts game state.\n\n";
+ }
+
+ if (path)
+ msg << "Dumping current state to " << utf8_from_wstring(OsPath(*path).string());
+
+ LOGERROR("%s", msg.str());
+
if (g_GUI)
g_GUI->DisplayMessageBox(600, 350, L"Sync error", wstring_from_utf8(msg.str()));
- else
- LOGERROR("%s", msg.str());
}
void CNetTurnManager::Interpolate(float simFrameLength, float realFrameLength)
{
// TODO: using m_TurnLength might be a bit dodgy when length changes - maybe
@@ -320,12 +343,11 @@
void CNetTurnManager::QuickSave()
{
TIMER(L"QuickSave");
std::stringstream stream;
- bool ok = m_Simulation2.SerializeState(stream);
- if (!ok)
+ if (!m_Simulation2.SerializeState(stream))
{
LOGERROR("Failed to quicksave game");
return;
}
@@ -348,12 +370,11 @@
LOGERROR("Cannot quickload game - no game was quicksaved");
return;
}
std::stringstream stream(m_QuickSaveState);
- bool ok = m_Simulation2.DeserializeState(stream);
- if (!ok)
+ if (!m_Simulation2.DeserializeState(stream))
{
LOGERROR("Failed to quickload game");
return;
}
@@ -400,12 +421,11 @@
{
bool quick = !TurnNeedsFullHash(turn);
std::string hash;
{
PROFILE3("state hash check");
- bool ok = m_Simulation2.ComputeStateHash(hash, quick);
- ENSURE(ok);
+ ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
}
NETTURN_LOG((L"NotifyFinishedUpdate(%d, %hs)\n", turn, Hexify(hash).c_str()));
m_Replay.Hash(hash, quick);
@@ -450,24 +470,90 @@
{
#if 0 // this hurts performance and is only useful for verifying log replays
std::string hash;
{
PROFILE3("state hash check");
- bool ok = m_Simulation2.ComputeStateHash(hash);
- ENSURE(ok);
+ ENSURE(m_Simulation2.ComputeStateHash(hash));
}
m_Replay.Hash(hash);
#endif
}
void CNetLocalTurnManager::OnSimulationMessage(CSimulationMessage* UNUSED(msg))
{
debug_warn(L"This should never be called");
}
+CNetReplayTurnManager::CNetReplayTurnManager(CSimulation2& simulation, IReplayLogger& replay) :
+ CNetLocalTurnManager(simulation, replay)
+{
+}
+
+void CNetReplayTurnManager::StoreReplayCommand(u32 turn, int player, const std::string& command)
+{
+ m_ReplayCommands[turn][player].push_back(command);
+}
+
+void CNetReplayTurnManager::StoreReplayHash(u32 turn, const std::string& hash, bool quick)
+{
+ m_ReplayHash[turn] = std::make_pair(hash, quick);
+}
+
+void CNetReplayTurnManager::StoreReplayTurnLength(u32 turn, u32 turnLength)
+{
+ m_ReplayTurnLengths[turn] = turnLength;
+
+ // Initialize turn length
+ if (turn == 0)
+ m_TurnLength = m_ReplayTurnLengths[0];
+}
+
+void CNetReplayTurnManager::StoreFinalReplayTurn(u32 turn)
+{
+ m_FinalReplayTurn = turn;
+}
+
+void CNetReplayTurnManager::NotifyFinishedUpdate(u32 turn)
+{
+ if (turn > m_FinalReplayTurn)
+ return;
+
+ debug_printf("Executing turn %d of %d\n", turn, m_FinalReplayTurn);
+ DoTurn(turn);
+
+ // Compare hash if it exists in the replay and if we didn't have an oos already
+ if (m_HasSyncError || m_ReplayHash.find(turn) == m_ReplayHash.end())
+ return;
+
+ std::string expectedHash = m_ReplayHash[turn].first;
+ bool quickHash = m_ReplayHash[turn].second;
+
+ // Compute hash
+ std::string hash;
+ ENSURE(m_Simulation2.ComputeStateHash(hash, quickHash));
+ hash = Hexify(hash);
+
+ if (hash != expectedHash)
+ DisplayOOSError(turn, hash, expectedHash, true);
+}
+void CNetReplayTurnManager::DoTurn(u32 turn)
+{
+ // Save turn length
+ m_TurnLength = m_ReplayTurnLengths[turn];
+ // Simulate commands for that turn
+ for (auto& command : m_ReplayCommands[turn])
+ {
+ for (size_t i = 0; i < command.second.size(); ++i)
+ {
+ JS::RootedValue data(m_Simulation2.GetScriptInterface().GetContext());
+ m_Simulation2.GetScriptInterface().ParseJSON(command.second[i], &data);
+ AddCommand(m_ClientId, command.first, data, m_CurrentTurn + 1);
+ }
+ }
+}
CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) :
m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP)
{
// The first turn we will actually execute is number 2,
Index: source/network/NetTurnManager.h
===================================================================
--- source/network/NetTurnManager.h (revision 16726)
+++ source/network/NetTurnManager.h (working copy)
@@ -1,6 +1,6 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2015 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
@@ -17,13 +17,15 @@
#ifndef INCLUDED_NETTURNMANAGER
#define INCLUDED_NETTURNMANAGER
#include "simulation2/helpers/SimulationCommand.h"
+#include "lib/os_path.h"
#include
#include