Browse Source

Logging battle activites. Replaying battles with client.
Added an AI for answer validation (it returns junk action packets).
Minor fixes.

Michał W. Urbańczyk 14 years ago
parent
commit
94e7fa5b3c

+ 163 - 0
AI/MadAI/MadAI.vcxproj

@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="RD|Win32">
+      <Configuration>RD</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="RD|x64">
+      <Configuration>RD</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}</ProjectGuid>
+    <RootNamespace>StupidAI</RootNamespace>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>MultiByte</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global.props" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global.props" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global.props" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\..\VCMI_global.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <OutDir>$(SolutionDir)\AI\</OutDir>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <OutDir>$(SolutionDir)\AI\</OutDir>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
+    <OutDir>$(SolutionDir)$(Configuration)\bin\AI\</OutDir>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
+    <OutDir>$(SolutionDir)$(Configuration)\bin\AI\</OutDir>
+    <IncludePath>$(IncludePath)</IncludePath>
+    <LibraryPath>$(LibraryPath)</LibraryPath>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+      <MinimalRebuild>false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <OutputFile>$(OutDir)$(TargetName).dll</OutputFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <OutputFile>$(OutDir)StupidAI.dll</OutputFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <OutputFile>$(OutDir)StupidAI.dll</OutputFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
+    </ClCompile>
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <AdditionalDependencies>VCMI_lib.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(OutDir)..;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+      <OutputFile>$(OutDir)StupidAI.dll</OutputFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="main.cpp" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 43 - 0
AI/MadAI/main.cpp

@@ -0,0 +1,43 @@
+#include <cstring>
+#include "../../AI_Base.h"
+
+
+const char *g_cszAiName = "Mad AI";
+
+
+
+class CMadAI : public CBattleGameInterface
+{
+	CBattleCallback *cb;
+	virtual void init(CBattleCallback * CB) 
+	{
+		cb = CB;
+	}
+
+	virtual BattleAction activeStack(const CStack * stack) 
+	{
+		srand(time(NULL));
+		BattleAction ba;
+		ba.actionType = rand() % 14;
+		ba.additionalInfo = rand() % BFIELD_SIZE + 5;
+		ba.side = rand() % 7;
+		ba.destinationTile = rand() % BFIELD_SIZE + 5;
+		ba.stackNumber = rand() % 500;
+		return ba;
+	}
+};
+
+extern "C" DLL_F_EXPORT void GetAiName(char* name)
+{
+	strcpy_s(name, strlen(g_cszAiName) + 1, g_cszAiName);
+}
+
+extern "C" DLL_F_EXPORT CBattleGameInterface* GetNewBattleAI()
+{
+	return new CMadAI();
+}
+
+extern "C" DLL_F_EXPORT void ReleaseBattleAI(CBattleGameInterface* i)
+{
+	delete (CMadAI*)i;
+}

+ 0 - 11
AI/StupidAI/main.cpp

@@ -8,22 +8,11 @@
 
 const char *g_cszAiName = "Stupid AI 0.1";
 
-extern "C" DLL_F_EXPORT int GetGlobalAiVersion()
-{
-	return AI_INTERFACE_VER;
-}
-
 extern "C" DLL_F_EXPORT void GetAiName(char* name)
 {
 	strcpy_s(name, strlen(g_cszAiName) + 1, g_cszAiName);
 }
 
-extern "C" DLL_F_EXPORT char* GetAiNameS()
-{
-	// need to be defined
-	return NULL;
-}
-
 extern "C" DLL_F_EXPORT CBattleGameInterface* GetNewBattleAI()
 {
 	return new CStupidAI();

+ 4 - 2
Odpalarka/main.cpp

@@ -17,8 +17,10 @@ int main(int argc, const char **)
 #else
 		"./vcmiserver"
 #endif
-		;
-	boost::thread t(boost::bind(std::system, (servername + " b1.json StupidAI StupidAI").c_str()));
+	;
+
+	std::string serverCommand = servername + " b1.json StupidAI StupidAI";
+	boost::thread t(boost::bind(std::system, serverCommand.c_str()));
 	boost::thread tt(boost::bind(std::system, runnername.c_str()));
 	boost::thread ttt(boost::bind(std::system, runnername.c_str()));
 	if(argc == 2)

+ 1 - 1
StartInfo.h

@@ -53,7 +53,7 @@ struct PlayerSettings
 /// Struct which describes the difficulty, the turn time,.. of a heroes match.
 struct StartInfo
 {
-	enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, DUEL, INVALID = 255};
+	enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, DUEL, DUEL_REPLAY, INVALID = 255};
 
 	ui8 mode; //uses EMode enum
 	ui8 difficulty; //0=easy; 4=impossible

+ 13 - 0
VCMI_VS10.sln

@@ -26,6 +26,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VCMI_client", "client\VCMI_client.vcxproj", "{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MadAI", "AI\MadAI\MadAI.vcxproj", "{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Win32 = Debug|Win32
@@ -111,6 +113,17 @@ Global
 		{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|Win32.ActiveCfg = RD|x64
 		{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|x64.ActiveCfg = RD|x64
 		{8355EBA8-65C2-44A4-BC2D-78053E1BF2D6}.Release|x64.Build.0 = RD|x64
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Debug|Win32.ActiveCfg = Debug|Win32
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Debug|Win32.Build.0 = Debug|Win32
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Debug|x64.ActiveCfg = Debug|x64
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Debug|x64.Build.0 = Debug|x64
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.RD|Win32.ActiveCfg = RD|Win32
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.RD|Win32.Build.0 = RD|Win32
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.RD|x64.ActiveCfg = RD|x64
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.RD|x64.Build.0 = RD|x64
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Release|Win32.ActiveCfg = RD|x64
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Release|x64.ActiveCfg = RD|x64
+		{DF931F3D-6DD2-4D1F-ADE9-F0098B5AE3F0}.Release|x64.Build.0 = RD|x64
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 30 - 3
client/CMT.cpp

@@ -185,6 +185,7 @@ static void prog_help(const char *progname)
 	printf("  -v, --version     display version information and exit\n");
 }
 
+CLoadFile *replayLoader;
 
 #ifdef _WIN32
 int _tmain(int argc, _TCHAR* argv[])
@@ -197,7 +198,8 @@ int main(int argc, char** argv)
 	opts.add_options()
 		("help,h", "display help and exit")
 		("version,v", "display version information and exit")
-		("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only")
+		("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only)")
+		("replay,r", "replays a recorded battle, use together with -b");
 		("nointro,i", "skips intro movies");
 
 	po::variables_map vm;
@@ -284,7 +286,27 @@ int main(int argc, char** argv)
 	else
 	{
 		StartInfo *si = new StartInfo();
-		si->mode = StartInfo::DUEL;
+		if(vm.count("replay"))
+		{
+			si->mode = StartInfo::DUEL_REPLAY;
+			replayLoader = new CLoadFile(vm["battle"].as<std::string>());
+			replayLoader->smartPointerSerialization = false;
+			if(!replayLoader->sfile)
+			{
+				tlog1 << "Cannot find file with recorded battle (" << si->mapname << ")!\n";
+				exit(1);
+			}
+
+			std::string bname, ai1, ai2;
+			ui8 magic;
+			*replayLoader >> bname >> ai1 >> ai2 >> magic;
+			assert(magic == '$');
+
+			si->mapname = bname;
+			tlog0 << "Replaying battle between " <<ai1 << " and "  << ai2 << " on " << bname << std::endl;
+		}
+		else
+			si->mode = StartInfo::DUEL;
 		startGame(si);
 	}
 	mainGUIThread = new boost::thread(&CGuiHandler::run, boost::ref(GH));
@@ -718,6 +740,7 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/)
 		client->newGame(serv, options);
 		break;
 	case StartInfo::DUEL:
+	case StartInfo::DUEL_REPLAY:
 		client->newDuel(serv, options);
 		break;
 	case StartInfo::LOAD_GAME:
@@ -727,7 +750,11 @@ void startGame(StartInfo * options, CConnection *serv/* = NULL*/)
 		break;
 	}
 
-	client->connectionHandler = new boost::thread(&CClient::run, client);
+	if(client->serv)
+		client->connectionHandler = new boost::thread(&CClient::run, client);
+	else
+		client->connectionHandler = new boost::thread(&CClient::runReplay, client, replayLoader);
+
 }
 
 void requestChangingResolution()

+ 109 - 25
client/Client.cpp

@@ -39,6 +39,7 @@
 
 #define NOT_LIB
 #include "../lib/RegisterTypes.cpp"
+#include <fstream>
 
 extern std::string NAME;
 namespace intpr = boost::interprocess;
@@ -418,37 +419,46 @@ void CClient::newGame( CConnection *con, StartInfo *si )
 
 void CClient::newDuel(CConnection *con, StartInfo *si)
 {
-	serv = con;
-	if(!serv)
+	if(si->mode == StartInfo::DUEL)
 	{
-		std::string host = "127.0.0.1";
-		std::string port = "3030";
-
-		int i = 3;
-		while(!serv)
+		serv = con;
+		if(!serv)
 		{
-			try
-			{
-				tlog0 << "Establishing connection...\n";
-				serv = new CConnection(host, port, "DLL host");
-			}
-			catch(...)
+			std::string host = "127.0.0.1";
+			std::string port = "3030";
+
+			int i = 3;
+			while(!serv)
 			{
-				tlog1 << "\nCannot establish connection! Retrying within 2 seconds" << std::endl;
-				boost::this_thread::sleep(boost::posix_time::seconds(2));
-				if(!--i)
-					exit(0);
+				try
+				{
+					tlog0 << "Establishing connection...\n";
+					serv = new CConnection(host, port, "DLL host");
+				}
+				catch(...)
+				{
+					tlog1 << "\nCannot establish connection! Retrying within 2 seconds" << std::endl;
+					boost::this_thread::sleep(boost::posix_time::seconds(2));
+					if(!--i)
+						exit(0);
+				}
 			}
 		}
-	}
 
 
-	ui8 color;
-	std::string battleAIName;
-	*serv >> *si >> battleAIName >> color;
-	assert(si->mode == StartInfo::DUEL);
-	assert(color > 1); //we are NOT participants
-	//tlog0 << format("Server wants us to be %s in battle %s as side %d") % battleAIName % si.mapname % (int)color;
+		ui8 color;
+		std::string battleAIName;
+		*serv >> *si >> battleAIName >> color;
+		assert(si->mode == StartInfo::DUEL);
+		assert(color > 1); //we are NOT participants
+		//tlog0 << format("Server wants us to be %s in battle %s as side %d") % battleAIName % si.mapname % (int)color;
+
+
+	}
+	else
+	{
+		si->mode = StartInfo::DUEL;
+	}
 
 	gs = new CGameState();
 	const_cast<CGameInfo*>(CGI)->state = gs;
@@ -463,7 +473,8 @@ void CClient::newDuel(CConnection *con, StartInfo *si)
 	p->init(new CCallback(gs, -1, this));
 	battleStarted(gs->curB);
 
-	serv->addStdVecItems(const_cast<CGameInfo*>(CGI)->state);
+	if(serv)
+		serv->addStdVecItems(const_cast<CGameInfo*>(CGI)->state);
 }
 
 template <typename Handler>
@@ -669,6 +680,79 @@ void CClient::invalidatePaths(const CGHeroInstance *h /*= NULL*/)
 		pathInfo->isValid = false;
 }
 
+std::string typeName(CPack * pack)
+{
+	try
+	{
+		return typeid(*pack).name();
+	}
+	catch(...)
+	{
+		return "unknown type";
+		//tlog1 << "\Unknown type!\t" << e.what() << std::endl;
+	}
+}
+
+void CClient::runReplay(CLoadFile *f)
+{
+	setThreadName(-1, "CClient::runReplay");
+	try
+	{
+		f->sfile->exceptions(std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
+		int i = 0;
+		std::vector<CPack *> hlp;
+		while(f->sfile)
+		{
+			i = hlp.size();
+			tlog5 << i;
+			ui8 magic;
+			std::pair<ui8, CPack *> para;
+			para.first = 254;
+			para.second = NULL;
+
+			try
+			{
+				*f >> para >> magic;
+			}
+			catch(...)
+			{
+				break;
+			}
+			if(magic != '*')
+				throw std::runtime_error("Bad magic byte!");
+			assert(para.second);
+
+			if(para.first != 255)
+			{
+				tlog5 << "\tIgnoring message from player " << (int)para.first 
+					<< " (" << typeName(para.second) << ")" << std::endl;
+			}
+			else
+			{
+				tlog5 << "\tRead message of type " << typeName(para.second) << std::endl;
+				hlp.push_back(para.second);
+			}
+		}
+
+		i = 0;
+		while(!terminate && i < hlp.size())
+		{
+			tlog1 << i << "\tHandling pack of type " << typeName(hlp[i]) << std::endl;
+			handlePack(hlp[i++]);
+		}
+	} 
+	catch (const std::exception& e)
+	{	
+		tlog3 << "Failure when replaying from file, ending reading thread!\n";
+		tlog1 << e.what() << std::endl;
+		if(!terminate) //rethrow (-> boom!) only if closing connection was unexpected
+		{
+			tlog1 << "Something wrong, failed reading while game is still ongoing...\n";
+			throw;
+		}
+	}
+}
+
 template void CClient::serialize( CISer<CLoadFile> &h, const int version );
 template void CClient::serialize( COSer<CSaveFile> &h, const int version );
 

+ 2 - 0
client/Client.h

@@ -31,6 +31,7 @@ class CClient;
 class CScriptingModule;
 struct CPathsInfo;
 namespace boost { class thread; }
+class CLoadFile;
 
 void processCommand(const std::string &message, CClient *&client);
 
@@ -96,6 +97,7 @@ public:
 	void save(const std::string & fname);
 	void loadGame(const std::string & fname);
 	void run();
+	void runReplay(CLoadFile *f);
 	void finishCampaign( CCampaignState * camp );
 	void proposeNextMission( CCampaignState * camp );
 	void invalidatePaths(const CGHeroInstance *h = NULL); //invalidates paths for hero h or for any hero if h is NULL => they'll got recalculated when the next query comes

+ 1 - 1
client/NetPacksClient.cpp

@@ -655,7 +655,7 @@ void BattleResultsApplied::applyCl( CClient *cl )
 	INTERFACE_CALL_IF_PRESENT(player1, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(player2, battleResultsApplied);
 	INTERFACE_CALL_IF_PRESENT(254, battleResultsApplied);
-	if(GS(cl)->initialOpts->mode == StartInfo::DUEL)
+	if(GS(cl)->initialOpts->mode == StartInfo::DUEL && cl->serv)
 	{
 		cl->terminate = true;
 		CloseServer cs;

+ 25 - 0
server/CGameHandler.cpp

@@ -471,6 +471,10 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance *hero1, const CGHer
 			casualtiesPoints = c->AIValue * i->second;
 		}
 		tlog0 << boost::format("Total casualties points: %d\n") % casualtiesPoints;
+
+		//battle ai1 ai2 winner_side winner_casualties
+		std::ofstream resultsList("results.txt", std::fstream::out | std::fstream::app);
+		resultsList << boost::format("\n%s\t%s\t%s\t%d\t%d") % gs->scenarioOps->mapname % ais[0] % ais[1] % (int)battleResult.data->winner % casualtiesPoints;
 	}
 
 	sendAndApply(&resultsApplied);
@@ -640,6 +644,7 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
 		while(1)//server should never shut connection first //was: while(!end2)
 		{
 			pack = c.retreivePack();
+			receivedPack(c.connectionID, pack);
 			int packType = typeList.getTypeID(pack); //get the id of type
 			if(packType == typeList.getTypeID<CloseServer>())
 			{
@@ -1929,6 +1934,7 @@ void CGameHandler::ask( Query * sel, ui8 player, const CFunctionList<void(ui32)>
 
 void CGameHandler::sendToAllClients( CPackForClient * info )
 {
+	broadcastedPack(info);
 	tlog5 << "Sending to all clients a package of type " << typeid(*info).name() << std::endl;
 	for(std::set<CConnection*>::iterator i=conns.begin(); i!=conns.end();i++)
 	{
@@ -5247,6 +5253,25 @@ void CGameHandler::spawnWanderingMonsters(int creatureID)
 	}
 }
 
+static boost::mutex logMx;
+void CGameHandler::receivedPack(ui8 connectionNr, CPack *pack)
+{
+	if(gameLog)
+	{
+		boost::unique_lock<boost::mutex> lock(logMx);
+		*gameLog << connectionNr << pack << ui8('*');
+	}
+}
+
+void CGameHandler::broadcastedPack(CPack *pack)
+{
+	if(gameLog)
+	{
+		boost::unique_lock<boost::mutex> lock(logMx);
+		*gameLog << ui8(255) << pack << ui8('*');
+	}
+}
+
 CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
 {
 	int color = army->tempOwner;

+ 5 - 0
server/CGameHandler.h

@@ -103,6 +103,11 @@ public:
 	std::map<ui32, boost::function<void()> > garrisonCallbacks; //query id => callback - for garrison dialogs
 	std::map<ui32, std::pair<si32,si32> > allowedExchanges;
 
+	std::string ais[2];
+	CSaveFile *gameLog;
+	void receivedPack(ui8 connectionNr, CPack *pack);
+	void broadcastedPack(CPack *pack);
+
 	bool isAllowedExchange(int id1, int id2);
 	bool isAllowedArrangePack(const ArrangeStacks *pack);
 	void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);

+ 11 - 2
server/CVCMIServer.cpp

@@ -534,11 +534,14 @@ void CVCMIServer::startDuel(const std::string &battle, const std::string &leftAI
 	tlog0 << "Preparing gh!\n";
 	CGameHandler *gh = new CGameHandler();
 	gh->init(&si,std::time(NULL));
+	gh->ais[0] = leftAI;
+	gh->ais[1] = rightAI;
 
 	BOOST_FOREACH(CConnection *c, conns)
 	{
 		ui8 player = gh->conns.size();
 		tlog0 << boost::format("Preparing connection %d!\n") % (int)player;
+		c->connectionID = player;
 		c->addStdVecItems(gh->gs, VLC);
 		gh->connections[player] = c;
 		gh->conns.insert(c);
@@ -555,18 +558,24 @@ void CVCMIServer::startDuel(const std::string &battle, const std::string &leftAI
 	*gh->connections[1] << rightAI << ui8(1);
 	*gh->connections[2] << std::string() << ui8(254);
 
+	std::string logFName = "duel_log.vdat";
+	tlog0 << "Logging battle activities (for replay possibility) in " << logFName << std::endl;
+	gh->gameLog = new CSaveFile(logFName);
+	gh->gameLog->smartPointerSerialization = false;
+	*gh->gameLog << battle << leftAI << rightAI << ui8('$');
 
 	tlog0 << "Starting battle!\n";
 	gh->runBattle();
 	tlog0 << "Battle over!\n";
-	delNull(gh);
-	tlog0 << "Removed gh!\n";
 	tlog0 << "Waiting for connections to close\n";
 	BOOST_FOREACH(boost::thread *t, threads)
 	{
 		t->join();
 		delNull(t);
 	}
+	tlog0 << "Removing gh\n";
+	delNull(gh);
+	tlog0 << "Removed gh!\n";
 
 	tlog0 << "Dying...\n";
 	exit(0);