| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204 | /* * Client.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */#include "StdInc.h"#include "Client.h"#include <SDL.h>#include <boost/uuid/uuid.hpp>#include <boost/uuid/uuid_io.hpp>#include <boost/uuid/uuid_generators.hpp>#include "CMusicHandler.h"#include "../lib/mapping/CCampaignHandler.h"#include "../CCallback.h"#include "../lib/CConsoleHandler.h"#include "CGameInfo.h"#include "../lib/CGameState.h"#include "CPlayerInterface.h"#include "../lib/StartInfo.h"#include "../lib/battle/BattleInfo.h"#include "../lib/CModHandler.h"#include "../lib/CArtHandler.h"#include "../lib/CGeneralTextHandler.h"#include "../lib/CHeroHandler.h"#include "../lib/CTownHandler.h"#include "../lib/CBuildingHandler.h"#include "../lib/spells/CSpellHandler.h"#include "../lib/serializer/CTypeList.h"#include "../lib/serializer/Connection.h"#include "../lib/serializer/CLoadIntegrityValidator.h"#ifndef VCMI_ANDROID#include "../lib/Interprocess.h"#endif#include "../lib/NetPacks.h"#include "../lib/VCMI_Lib.h"#include "../lib/VCMIDirs.h"#include "../lib/mapping/CMap.h"#include "../lib/mapping/CMapService.h"#include "../lib/JsonNode.h"#include "mapHandler.h"#include "../lib/CConfigHandler.h"#include "CPreGame.h"#include "battle/CBattleInterface.h"#include "../lib/CThreadHelper.h"#include "../lib/CScriptingModule.h"#include "../lib/registerTypes/RegisterTypes.h"#include "gui/CGuiHandler.h"#include "CMT.h"extern std::string NAME;#ifdef VCMI_ANDROID#include "lib/CAndroidVMHelper.h"#endif#ifdef VCMI_ANDROIDstd::atomic_bool androidTestServerReadyFlag;#endifThreadSafeVector<int> CClient::waitingRequest;template <typename T> class CApplyOnCL;class CBaseForCLApply{public:	virtual void applyOnClAfter(CClient *cl, void *pack) const =0;	virtual void applyOnClBefore(CClient *cl, void *pack) const =0;	virtual ~CBaseForCLApply(){}	template<typename U> static CBaseForCLApply *getApplier(const U * t=nullptr)	{		return new CApplyOnCL<U>();	}};template <typename T> class CApplyOnCL : public CBaseForCLApply{public:	void applyOnClAfter(CClient *cl, void *pack) const override	{		T *ptr = static_cast<T*>(pack);		ptr->applyCl(cl);	}	void applyOnClBefore(CClient *cl, void *pack) const override	{		T *ptr = static_cast<T*>(pack);		ptr->applyFirstCl(cl);	}};template <> class CApplyOnCL<CPack> : public CBaseForCLApply{public:	void applyOnClAfter(CClient *cl, void *pack) const override	{		logGlobal->error("Cannot apply on CL plain CPack!");		assert(0);	}	void applyOnClBefore(CClient *cl, void *pack) const override	{		logGlobal->error("Cannot apply on CL plain CPack!");		assert(0);	}};static CApplier<CBaseForCLApply> *applier = nullptr;void CClient::init(){	waitingRequest.clear();	hotSeat = false;	{		TLockGuard _(connectionHandlerMutex);		connectionHandler.reset();	}	pathInfo = nullptr;	applier = new CApplier<CBaseForCLApply>();	registerTypesClientPacks1(*applier);	registerTypesClientPacks2(*applier);	IObjectInterface::cb = this;	serv = nullptr;	gs = nullptr;	erm = nullptr;	terminate = false;}CClient::CClient(){	init();}CClient::CClient(CConnection *con, StartInfo *si){	init();	newGame(con,si);}CClient::~CClient(){	delete applier;}void CClient::waitForMoveAndSend(PlayerColor color){	try	{		setThreadName("CClient::waitForMoveAndSend");		assert(vstd::contains(battleints, color));		BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));		if(ba.actionType != EActionType::CANCEL)		{			logNetwork->trace("Send battle action to server: %s", ba.toString());			MakeAction temp_action(ba);			sendRequest(&temp_action, color);		}	}	catch(boost::thread_interrupted&)	{		logNetwork->debug("Wait for move thread was interrupted and no action will be send. Was a battle ended by spell?");	}	catch(...)	{		handleException();	}}void CClient::run(){	setThreadName("CClient::run");	try	{		while(!terminate)		{			CPack *pack = serv->retreivePack(); //get the package from the server			if (terminate)			{				vstd::clear_pointer(pack);				break;			}			handlePack(pack);		}	}	//catch only asio exceptions	catch (const boost::system::system_error& e)	{		logNetwork->error("Lost connection to server, ending listening thread!");		logNetwork->error(e.what());		if(!terminate) //rethrow (-> boom!) only if closing connection was unexpected		{			logNetwork->error("Something wrong, lost connection while game is still ongoing...");			throw;		}	}}void CClient::save(const std::string & fname){	if(gs->curB)	{		logNetwork->error("Game cannot be saved during battle!");		return;	}	SaveGame save_game(fname);	sendRequest((CPackForClient*)&save_game, PlayerColor::NEUTRAL);}void CClient::endGame(bool closeConnection){	//suggest interfaces to finish their stuff (AI should interrupt any bg working threads)	for (auto& i : playerint)		i.second->finish();	// Game is ending	// Tell the network thread to reach a stable state	if (closeConnection)		stopConnection();	logNetwork->info("Closed connection.");	GH.curInt = nullptr;	{		boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);		logNetwork->info("Ending current game!");		if(GH.topInt())		{			GH.topInt()->deactivate();		}		GH.listInt.clear();		GH.objsToBlit.clear();		GH.statusbar = nullptr;		logNetwork->info("Removed GUI.");		vstd::clear_pointer(const_cast<CGameInfo*>(CGI)->mh);		vstd::clear_pointer(gs);		logNetwork->info("Deleted mapHandler and gameState.");		LOCPLINT = nullptr;	}	playerint.clear();	battleints.clear();	callbacks.clear();	battleCallbacks.clear();	CGKeys::reset();	CGMagi::reset();	CGObelisk::reset();	logNetwork->info("Deleted playerInts.");	logNetwork->info("Client stopped.");}#if 1void CClient::loadGame(const std::string & fname, const bool server, const std::vector<int>& humanplayerindices, const int loadNumPlayers, int player_, const std::string & ipaddr, const ui16 port){	PlayerColor player(player_); //intentional shadowing	logNetwork->info("Loading procedure started!");	CServerHandler sh;	if(server)		sh.startServer();	else		serv = sh.justConnectToServer(ipaddr, port);	CStopWatch tmh;	std::unique_ptr<CLoadFile> loader;	try	{		boost::filesystem::path clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::CLIENT_SAVEGAME));		boost::filesystem::path controlServerSaveName;		if (CResourceHandler::get("local")->existsResource(ResourceID(fname, EResType::SERVER_SAVEGAME)))		{			controlServerSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME));		}		else// create entry for server savegame. Triggered if save was made after launch and not yet present in res handler		{			controlServerSaveName = boost::filesystem::path(clientSaveName).replace_extension(".vsgm1");			CResourceHandler::get("local")->createResource(controlServerSaveName.string(), true);		}		if(clientSaveName.empty())			throw std::runtime_error("Cannot open client part of " + fname);		if(controlServerSaveName.empty() || !boost::filesystem::exists(controlServerSaveName))			throw std::runtime_error("Cannot open server part of " + fname);		{			CLoadIntegrityValidator checkingLoader(clientSaveName, controlServerSaveName, MINIMAL_SERIALIZATION_VERSION);			loadCommonState(checkingLoader);			loader = checkingLoader.decay();		}		logNetwork->info("Loaded common part of save %d ms", tmh.getDiff());		const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();		const_cast<CGameInfo*>(CGI)->mh->map = gs->map;		pathInfo = make_unique<CPathsInfo>(getMapSize());		CGI->mh->init();		logNetwork->info("Initing maphandler: %d ms", tmh.getDiff());	}	catch(std::exception &e)	{		logGlobal->error("Cannot load game %s. Error: %s", fname, e.what());		throw; //obviously we cannot continue here	}/*    if(!server)         player = PlayerColor(player_);*/	std::set<PlayerColor> clientPlayers;	if(server)	serv = sh.connectToServer();    //*loader >> *this;	if(server)	{		tmh.update();		ui8 pom8;		*serv << ui8(3) << ui8(loadNumPlayers); //load game; one client if single-player		*serv << fname;		*serv >> pom8;		if(pom8)			throw std::runtime_error("Server cannot open the savegame!");		else			logNetwork->info("Server opened savegame properly.");	}	if(server)	{		for(auto & elem : gs->scenarioOps->playerInfos)		{			if(!std::count(humanplayerindices.begin(),humanplayerindices.end(),elem.first.getNum()) || elem.first==player)				clientPlayers.insert(elem.first);		}		clientPlayers.insert(PlayerColor::NEUTRAL);	}	else	{		clientPlayers.insert(player);	}	std::cout << "CLIENTPLAYERS:\n";	for(auto x : clientPlayers)		std::cout << x << std::endl;	std::cout << "ENDCLIENTPLAYERS\n";	serialize(loader->serializer, loader->serializer.fileVersion, clientPlayers);	*serv << ui32(clientPlayers.size());	for(auto & elem : clientPlayers)		*serv << ui8(elem.getNum());	serv->addStdVecItems(gs); /*why is this here?*/    //*loader >> *this;	logNetwork->info("Loaded client part of save %d ms", tmh.getDiff());	logNetwork->info("Sent info to server: %d ms", tmh.getDiff());    //*serv << clientPlayers;	serv->enableStackSendingByID();	serv->disableSmartPointerSerialization();// 	logGlobal->trace("Objects:");// 	for(int i = 0; i < gs->map->objects.size(); i++)// 	{// 		auto o = gs->map->objects[i];// 		if(o)// 			logGlobal->trace("\tindex=%5d, id=%5d; address=%5d, pos=%s, name=%s", i, o->id, (int)o.get(), o->pos, o->getHoverText());// 		else// 			logGlobal->trace("\tindex=%5d --- nullptr", i);// 	}}#endifvoid CClient::newGame( CConnection *con, StartInfo *si ){	enum {SINGLE, HOST, GUEST} networkMode = SINGLE;	if (con == nullptr)	{		CServerHandler sh;		serv = sh.connectToServer();	}	else	{		serv = con;		networkMode = con->isHost() ? HOST : GUEST;	}	CConnection &c = *serv;	////////////////////////////////////////////////////	logNetwork->info("\tWill send info to server...");	CStopWatch tmh;	if(networkMode == SINGLE)	{		ui8 pom8;		c << ui8(2) << ui8(1); //new game; one client		c << *si;		c >> pom8;		if(pom8) throw std::runtime_error("Server cannot open the map!");	}	c >> si;	logNetwork->info("\tSending/Getting info to/from the server: %d ms", tmh.getDiff());	c.enableStackSendingByID();	c.disableSmartPointerSerialization();	// Initialize game state	CMapService mapService;	gs = new CGameState();	logNetwork->info("\tCreating gamestate: %i",tmh.getDiff());	gs->init(&mapService, si, settings["general"]["saveRandomMaps"].Bool());	logNetwork->info("Initializing GameState (together): %d ms", tmh.getDiff());	// Now after possible random map gen, we know exact player count.	// Inform server about how many players client handles	std::set<PlayerColor> myPlayers;	for(auto & elem : gs->scenarioOps->playerInfos)	{		if((networkMode == SINGLE)                                                      //single - one client has all player		   || (networkMode != SINGLE && serv->connectionID == elem.second.playerID)      //multi - client has only "its players"		   || (networkMode == HOST && elem.second.playerID == PlayerSettings::PLAYER_AI))//multi - host has all AI players		{			myPlayers.insert(elem.first); //add player		}	}	if(networkMode != GUEST)		myPlayers.insert(PlayerColor::NEUTRAL);	c << myPlayers;	// Init map handler	if(gs->map)	{		if(!settings["session"]["headless"].Bool())		{			const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();			CGI->mh->map = gs->map;			logNetwork->info("Creating mapHandler: %d ms", tmh.getDiff());			CGI->mh->init();		}		pathInfo = make_unique<CPathsInfo>(getMapSize());		logNetwork->info("Initializing mapHandler (together): %d ms", tmh.getDiff());	}	int humanPlayers = 0;	for(auto & elem : gs->scenarioOps->playerInfos)//initializing interfaces for players	{		PlayerColor color = elem.first;		gs->currentPlayer = color;		if(!vstd::contains(myPlayers, color))			continue;		logNetwork->trace("Preparing interface for player %s", color.getStr());		if(elem.second.playerID == PlayerSettings::PLAYER_AI)		{			auto AiToGive = aiNameForPlayer(elem.second, false);			logNetwork->info("Player %s will be lead by %s", color, AiToGive);			installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);		}		else		{			installNewPlayerInterface(std::make_shared<CPlayerInterface>(color), color);			humanPlayers++;		}	}	if(settings["session"]["spectate"].Bool())	{		installNewPlayerInterface(std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR), PlayerColor::SPECTATOR, true);	}	loadNeutralBattleAI();	serv->addStdVecItems(gs);	hotSeat = (humanPlayers > 1);// 	std::vector<FileInfo> scriptModules;// 	CFileUtility::getFilesWithExt(scriptModules, LIB_DIR "/scripting", "." LIB_EXT);// 	for(FileInfo &m : scriptModules)// 	{// 		CScriptingModule * nm = CDynLibHandler::getNewScriptingModule(m.name);// 		privilegedGameEventReceivers.push_back(nm);// 		privilegedBattleEventReceivers.push_back(nm);// 		nm->giveActionCB(this);// 		nm->giveInfoCB(this);// 		nm->init();//// 		erm = nm; //something tells me that there'll at most one module and it'll be ERM// 	}}void CClient::serialize(BinarySerializer & h, const int version){	assert(h.saving);	h & hotSeat;	{		ui8 players = playerint.size();		h & players;		for(auto i = playerint.begin(); i != playerint.end(); i++)		{			LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);			assert(i->first == i->second->playerID);			h & i->first;			h & i->second->dllName;			h & i->second->human;			i->second->saveGame(h, version);		}	}}void CClient::serialize(BinaryDeserializer & h, const int version){	assert(!h.saving);	h & hotSeat;	{		ui8 players = 0; //fix for uninitialized warning		h & players;		for(int i=0; i < players; i++)		{			std::string dllname;			PlayerColor pid;			bool isHuman = false;			h & pid;			h & dllname;			h & isHuman;			LOG_TRACE_PARAMS(logGlobal, "Loading player %s interface", pid);			std::shared_ptr<CGameInterface> nInt;			if(dllname.length())			{				if(pid == PlayerColor::NEUTRAL)				{					installNewBattleInterface(CDynLibHandler::getNewBattleAI(dllname), pid);					//TODO? consider serialization					continue;				}				else				{					assert(!isHuman);					nInt = CDynLibHandler::getNewAI(dllname);				}			}			else			{				assert(isHuman);				nInt = std::make_shared<CPlayerInterface>(pid);			}			nInt->dllName = dllname;			nInt->human = isHuman;			nInt->playerID = pid;			installNewPlayerInterface(nInt, pid);			nInt->loadGame(h, version); //another evil cast, check above		}		if(!vstd::contains(battleints, PlayerColor::NEUTRAL))			loadNeutralBattleAI();	}}void CClient::serialize(BinarySerializer & h, const int version, const std::set<PlayerColor> & playerIDs){	assert(h.saving);	h & hotSeat;	{		ui8 players = playerint.size();		h & players;		for(auto i = playerint.begin(); i != playerint.end(); i++)		{			LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);			assert(i->first == i->second->playerID);			h & i->first;			h & i->second->dllName;			h & i->second->human;			i->second->saveGame(h, version);		}	}}void CClient::serialize(BinaryDeserializer & h, const int version, const std::set<PlayerColor> & playerIDs){	assert(!h.saving);	h & hotSeat;	{		ui8 players = 0; //fix for uninitialized warning		h & players;		for(int i=0; i < players; i++)		{			std::string dllname;			PlayerColor pid;			bool isHuman = false;			h & pid;			h & dllname;			h & isHuman;			LOG_TRACE_PARAMS(logGlobal, "Loading player %s interface", pid);			std::shared_ptr<CGameInterface> nInt;			if(dllname.length())			{				if(pid == PlayerColor::NEUTRAL)				{					if(playerIDs.count(pid))						installNewBattleInterface(CDynLibHandler::getNewBattleAI(dllname), pid);					//TODO? consider serialization					continue;				}				else				{					assert(!isHuman);					nInt = CDynLibHandler::getNewAI(dllname);				}			}			else			{				assert(isHuman);				nInt = std::make_shared<CPlayerInterface>(pid);			}			nInt->dllName = dllname;			nInt->human = isHuman;			nInt->playerID = pid;			nInt->loadGame(h, version);			if(settings["session"]["onlyai"].Bool() && isHuman)			{				removeGUI();				nInt.reset();				dllname = aiNameForPlayer(false);				nInt = CDynLibHandler::getNewAI(dllname);				nInt->dllName = dllname;				nInt->human = false;				nInt->playerID = pid;				installNewPlayerInterface(nInt, pid);				GH.totalRedraw();			}			else			{				if(playerIDs.count(pid))					installNewPlayerInterface(nInt, pid);			}		}		if(settings["session"]["spectate"].Bool())		{			removeGUI();			auto p = std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR);			installNewPlayerInterface(p, PlayerColor::SPECTATOR, true);			GH.curInt = p.get();			LOCPLINT->activateForSpectator();			GH.totalRedraw();		}		if(playerIDs.count(PlayerColor::NEUTRAL))			loadNeutralBattleAI();	}}void CClient::handlePack( CPack * pack ){	if(pack == nullptr)	{		logNetwork->error("Dropping nullptr CPack! You should check whether client and server ABI matches.");		return;	}	CBaseForCLApply *apply = applier->getApplier(typeList.getTypeID(pack)); //find the applier	if(apply)	{		boost::unique_lock<boost::recursive_mutex> guiLock(*CPlayerInterface::pim);		apply->applyOnClBefore(this, pack);		logNetwork->trace("\tMade first apply on cl");		gs->apply(pack);		logNetwork->trace("\tApplied on gs");		apply->applyOnClAfter(this, pack);		logNetwork->trace("\tMade second apply on cl");	}	else	{		logNetwork->error("Message %s cannot be applied, cannot find applier!", typeList.getTypeInfo(pack)->name());	}	delete pack;}void CClient::finishCampaign( std::shared_ptr<CCampaignState> camp ){}void CClient::proposeNextMission(std::shared_ptr<CCampaignState> camp){	GH.pushInt(new CBonusSelection(camp));}void CClient::stopConnection(){	terminate = true;	if(serv)	{		boost::unique_lock<boost::mutex>(*serv->wmx);		if(serv->isHost()) //request closing connection		{			logNetwork->info("Connection has been requested to be closed.");			CloseServer close_server;			sendRequest(&close_server, PlayerColor::NEUTRAL);			logNetwork->info("Sent closing signal to the server");		}		else		{			LeaveGame leave_Game;			sendRequest(&leave_Game, PlayerColor::NEUTRAL);			logNetwork->info("Sent leaving signal to the server");		}	}	{		TLockGuard _(connectionHandlerMutex);		if(connectionHandler)//end connection handler		{			if(connectionHandler->get_id() != boost::this_thread::get_id())				connectionHandler->join();			logNetwork->info("Connection handler thread joined");			connectionHandler.reset();		}	}	if (serv) //and delete connection	{		serv->close();		vstd::clear_pointer(serv);		logNetwork->warn("Our socket has been closed.");	}}void CClient::battleStarted(const BattleInfo * info){	for(auto &battleCb : battleCallbacks)	{		if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })			||  battleCb.first >= PlayerColor::PLAYER_LIMIT)		{			battleCb.second->setBattle(info);		}	}// 	for(ui8 side : info->sides)// 		if(battleCallbacks.count(side))// 			battleCallbacks[side]->setBattle(info);	std::shared_ptr<CPlayerInterface> att, def;	auto &leftSide = info->sides[0], &rightSide = info->sides[1];	//If quick combat is not, do not prepare interfaces for battleint	if(!settings["adventure"]["quickCombat"].Bool())	{		if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)			att = std::dynamic_pointer_cast<CPlayerInterface>( playerint[leftSide.color] );		if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)			def = std::dynamic_pointer_cast<CPlayerInterface>( playerint[rightSide.color] );	}	if(!settings["session"]["headless"].Bool())	{		if(!!att || !!def)		{			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);			auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,				Rect((screen->w - 800)/2,					 (screen->h - 600)/2, 800, 600), att, def);			GH.pushInt(bi);		}		else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())		{			//TODO: This certainly need improvement			auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);			spectratorInt->cb->setBattle(info);			boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);			auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,				Rect((screen->w - 800)/2,					 (screen->h - 600)/2, 800, 600), att, def, spectratorInt);			GH.pushInt(bi);		}	}	auto callBattleStart = [&](PlayerColor color, ui8 side){		if(vstd::contains(battleints, color))			battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side);	};	callBattleStart(leftSide.color, 0);	callBattleStart(rightSide.color, 1);	callBattleStart(PlayerColor::UNFLAGGABLE, 1);	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())		callBattleStart(PlayerColor::SPECTATOR, 1);	if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide].color))	{		boost::thread(&CClient::commenceTacticPhaseForInt, this, battleints[info->sides[info->tacticsSide].color]);	}}void CClient::battleFinished(){	stopAllBattleActions();	for(auto & side : gs->curB->sides)		if(battleCallbacks.count(side.color))			battleCallbacks[side.color]->setBattle(nullptr);	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())		battleCallbacks[PlayerColor::SPECTATOR]->setBattle(nullptr);}void CClient::loadNeutralBattleAI(){	installNewBattleInterface(CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()), PlayerColor::NEUTRAL);}void CClient::commitPackage( CPackForClient *pack ){	CommitPackage cp;	cp.freePack = false;	cp.packToCommit = pack;	sendRequest(&cp, PlayerColor::NEUTRAL);}PlayerColor CClient::getLocalPlayer() const{	if(LOCPLINT)		return LOCPLINT->playerID;	return getCurrentPlayer();}void CClient::commenceTacticPhaseForInt(std::shared_ptr<CBattleGameInterface> battleInt){	setThreadName("CClient::commenceTacticPhaseForInt");	try	{		battleInt->yourTacticPhase(gs->curB->tacticDistance);		if(gs && !!gs->curB && gs->curB->tacticDistance) //while awaiting for end of tactics phase, many things can happen (end of battle... or game)		{			MakeAction ma(BattleAction::makeEndOFTacticPhase(gs->curB->playerToSide(battleInt->playerID).get()));			sendRequest(&ma, battleInt->playerID);		}	}	catch(...)	{		handleException();	}}void CClient::invalidatePaths(){	// turn pathfinding info into invalid. It will be regenerated later	boost::unique_lock<boost::mutex> pathLock(pathInfo->pathMx);	pathInfo->hero = nullptr;}const CPathsInfo * CClient::getPathsInfo(const CGHeroInstance *h){	assert(h);	boost::unique_lock<boost::mutex> pathLock(pathInfo->pathMx);	if (pathInfo->hero != h)	{		gs->calculatePaths(h, *pathInfo.get());	}	return pathInfo.get();}int CClient::sendRequest(const CPack *request, PlayerColor player){	static ui32 requestCounter = 0;	ui32 requestID = requestCounter++;	logNetwork->trace("Sending a request \"%s\". It'll have an ID=%d.", typeid(*request).name(), requestID);	waitingRequest.pushBack(requestID);	serv->sendPackToServer(*request, player, requestID);	if(vstd::contains(playerint, player))		playerint[player]->requestSent(dynamic_cast<const CPackForServer*>(request), requestID);	return requestID;}void CClient::campaignMapFinished( std::shared_ptr<CCampaignState> camp ){	endGame(false);	GH.curInt = CGPreGame::create();	auto & epilogue = camp->camp->scenarios[camp->mapsConquered.back()].epilog;	auto finisher = [=]()	{		if(camp->mapsRemaining.size())			proposeNextMission(camp);		else			finishCampaign(camp);	};	if(epilogue.hasPrologEpilog)	{		GH.pushInt(new CPrologEpilogVideo(epilogue, finisher));	}	else	{		finisher();	}}void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color, bool battlecb){	boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);	if(!color)		privilegedGameEventReceivers.push_back(gameInterface);	playerint[colorUsed] = gameInterface;	logGlobal->trace("\tInitializing the interface for player %s", colorUsed);	auto cb = std::make_shared<CCallback>(gs, color, this);	callbacks[colorUsed] = cb;	battleCallbacks[colorUsed] = cb;	gameInterface->init(cb);	installNewBattleInterface(gameInterface, color, battlecb);}void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback){	boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);	PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);	if(!color)		privilegedBattleEventReceivers.push_back(battleInterface);	battleints[colorUsed] = battleInterface;	if(needCallback)	{		logGlobal->trace("\tInitializing the battle interface for player %s", *color);		auto cbc = std::make_shared<CBattleCallback>(color, this);		battleCallbacks[colorUsed] = cbc;		battleInterface->init(cbc);	}}std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI){	if(ps.name.size())	{		const boost::filesystem::path aiPath = VCMIDirs::get().fullLibraryPath("AI", ps.name);		if (boost::filesystem::exists(aiPath))			return ps.name;	}	return aiNameForPlayer(battleAI);}std::string CClient::aiNameForPlayer(bool battleAI){	const int sensibleAILimit = settings["session"]["oneGoodAI"].Bool() ? 1 : PlayerColor::PLAYER_LIMIT_I;	std::string goodAI = battleAI ? settings["server"]["neutralAI"].String() : settings["server"]["playerAI"].String();	std::string badAI = battleAI ? "StupidAI" : "EmptyAI";	//TODO what about human players	if(battleints.size() >= sensibleAILimit)		return badAI;	return goodAI;}void CClient::startPlayerBattleAction(PlayerColor color){	stopPlayerBattleAction(color);	if(vstd::contains(battleints, color))	{		auto thread = std::make_shared<boost::thread>(std::bind(&CClient::waitForMoveAndSend, this, color));		playerActionThreads[color] = thread;	}}void CClient::stopPlayerBattleAction(PlayerColor color){	if(vstd::contains(playerActionThreads, color))	{		auto thread = playerActionThreads.at(color);		if(thread->joinable())		{			thread->interrupt();			thread->join();		}		playerActionThreads.erase(color);	}}void CClient::stopAllBattleActions(){	while(!playerActionThreads.empty())		stopPlayerBattleAction(playerActionThreads.begin()->first);}void CServerHandler::startServer(){	if(settings["session"]["donotstartserver"].Bool())		return;	th.update();#ifdef VCMI_ANDROID	CAndroidVMHelper envHelper;	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true);#else	serverThread = new boost::thread(&CServerHandler::callServer, this); //runs server executable;#endif	if(verbose)		logNetwork->info("Setting up thread calling server: %d ms", th.getDiff());}void CServerHandler::waitForServer(){	if(settings["session"]["donotstartserver"].Bool())		return;	if(!serverThread)		startServer();	th.update();#ifndef VCMI_ANDROID	if(shared)		shared->sr->waitTillReady();#else	logNetwork->info("waiting for server");	while (!androidTestServerReadyFlag.load())	{		logNetwork->info("still waiting...");		boost::this_thread::sleep(boost::posix_time::milliseconds(1000));	}	logNetwork->info("waiting for server finished...");	androidTestServerReadyFlag = false;#endif	if(verbose)		logNetwork->info("Waiting for server: %d ms", th.getDiff());}CConnection * CServerHandler::connectToServer(){	waitForServer();	th.update(); //put breakpoint here to attach to server before it does something stupid#ifndef VCMI_ANDROID	CConnection *ret = justConnectToServer(settings["server"]["server"].String(), shared ? shared->sr->port : 0);#else	CConnection *ret = justConnectToServer(settings["server"]["server"].String());#endif	if(verbose)		logNetwork->info("\tConnecting to the server: %d ms", th.getDiff());	return ret;}ui16 CServerHandler::getDefaultPort(){	if(settings["session"]["serverport"].Integer())		return settings["session"]["serverport"].Integer();	else		return settings["server"]["port"].Integer();}std::string CServerHandler::getDefaultPortStr(){	return boost::lexical_cast<std::string>(getDefaultPort());}CServerHandler::CServerHandler(bool runServer){	serverThread = nullptr;	shared = nullptr;	verbose = true;	uuid = boost::uuids::to_string(boost::uuids::random_generator()());#ifndef VCMI_ANDROID	if(settings["session"]["donotstartserver"].Bool() || settings["session"]["disable-shm"].Bool())		return;	std::string sharedMemoryName = "vcmi_memory";	if(settings["session"]["enable-shm-uuid"].Bool())	{		//used or automated testing when multiple clients start simultaneously		sharedMemoryName += "_" + uuid;	}	try	{		shared = new SharedMemory(sharedMemoryName, true);	}	catch(...)	{		vstd::clear_pointer(shared);		logNetwork->error("Cannot open interprocess memory. Continue without it...");	}#endif}CServerHandler::~CServerHandler(){	delete shared;	delete serverThread; //detaches, not kills thread}void CServerHandler::callServer(){#ifndef VCMI_ANDROID	setThreadName("CServerHandler::callServer");	const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();	std::string comm = VCMIDirs::get().serverPath().string()		+ " --port=" + getDefaultPortStr()		+ " --run-by-client"		+ " --uuid=" + uuid;	if(shared)	{		comm += " --enable-shm";		if(settings["session"]["enable-shm-uuid"].Bool())			comm += " --enable-shm-uuid";	}	comm += " > \"" + logName + '\"';	int result = std::system(comm.c_str());	if (result == 0)	{		logNetwork->info("Server closed correctly");		serverAlive.setn(false);	}	else	{		logNetwork->error("Error: server failed to close correctly or crashed!");		logNetwork->error("Check %s for more info", logName);		serverAlive.setn(false);		// TODO: make client return to main menu if server actually crashed during game.//		exit(1);// exit in case of error. Othervice without working server VCMI will hang	}#endif}CConnection * CServerHandler::justConnectToServer(const std::string &host, const ui16 port){	CConnection *ret = nullptr;	while(!ret)	{		try		{			logNetwork->info("Establishing connection...");			ret = new CConnection(	host.size() ? host : settings["server"]["server"].String(),									port ? port : getDefaultPort(),									NAME);			ret->connectionID = 1; // TODO: Refactoring for the server so IDs set outside of CConnection		}		catch(...)		{			logNetwork->error("\nCannot establish connection! Retrying within 2 seconds");			SDL_Delay(2000);		}	}	return ret;}#ifdef VCMI_ANDROIDextern "C" JNIEXPORT void JNICALL Java_eu_vcmi_vcmi_NativeMethods_notifyServerReady(JNIEnv * env, jobject cls){	logNetwork->info("Received server ready signal");	androidTestServerReadyFlag.store(true);}extern "C" JNIEXPORT bool JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jobject cls){	logGlobal->info("Received emergency save game request");	if(!LOCPLINT || !LOCPLINT->cb)	{		return false;	}	LOCPLINT->cb->save("Saves/_Android_Autosave");	return true;}#endif
 |