| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961 | /* * CVCMIServer.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 <boost/asio.hpp>#include "../lib/filesystem/Filesystem.h"#include "../lib/mapping/CCampaignHandler.h"#include "../lib/CThreadHelper.h"#include "../lib/serializer/Connection.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/CCreatureHandler.h"#include "zlib.h"#include "CVCMIServer.h"#include "../lib/StartInfo.h"#include "../lib/mapping/CMap.h"#include "../lib/rmg/CMapGenOptions.h"#ifdef VCMI_ANDROID#include "lib/CAndroidVMHelper.h"#else#include "../lib/Interprocess.h"#endif#include "../lib/VCMI_Lib.h"#include "../lib/VCMIDirs.h"#include "CGameHandler.h"#include "../lib/mapping/CMapInfo.h"#include "../lib/GameConstants.h"#include "../lib/logging/CBasicLogConfigurator.h"#include "../lib/CConfigHandler.h"#include "../lib/ScopeGuard.h"#include "../lib/serializer/CMemorySerializer.h"#include "../lib/UnlockGuard.h"// for applier#include "../lib/registerTypes/RegisterTypes.h"// UUID generation#include <boost/uuid/uuid.hpp>#include <boost/uuid/uuid_io.hpp>#include <boost/uuid/uuid_generators.hpp>#include "../lib/CGameState.h"#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)#include <execinfo.h>#endiftemplate<typename T> class CApplyOnServer;class CBaseForServerApply{public:	virtual bool applyOnServerBefore(CVCMIServer * srv, void * pack) const =0;	virtual void applyOnServerAfter(CVCMIServer * srv, void * pack) const =0;	virtual ~CBaseForServerApply() {}	template<typename U> static CBaseForServerApply * getApplier(const U * t = nullptr)	{		return new CApplyOnServer<U>();	}};template <typename T> class CApplyOnServer : public CBaseForServerApply{public:	bool applyOnServerBefore(CVCMIServer * srv, void * pack) const override	{		T * ptr = static_cast<T *>(pack);		if(ptr->checkClientPermissions(srv))		{			boost::unique_lock<boost::mutex> stateLock(srv->stateMutex);			return ptr->applyOnServer(srv);		}		else			return false;	}	void applyOnServerAfter(CVCMIServer * srv, void * pack) const override	{		T * ptr = static_cast<T *>(pack);		ptr->applyOnServerAfterAnnounce(srv);	}};template <>class CApplyOnServer<CPack> : public CBaseForServerApply{public:	bool applyOnServerBefore(CVCMIServer * srv, void * pack) const override	{		logGlobal->error("Cannot apply plain CPack!");		assert(0);		return false;	}	void applyOnServerAfter(CVCMIServer * srv, void * pack) const override	{		logGlobal->error("Cannot apply plain CPack!");		assert(0);	}};std::string NAME_AFFIX = "server";std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')';CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts)	: port(3030), io(std::make_shared<boost::asio::io_service>()), state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false){	uuid = boost::uuids::to_string(boost::uuids::random_generator()());	logNetwork->trace("CVCMIServer created! UUID: %s", uuid);	applier = std::make_shared<CApplier<CBaseForServerApply>>();	registerTypesLobbyPacks(*applier);	if(cmdLineOptions.count("port"))		port = cmdLineOptions["port"].as<ui16>();	logNetwork->info("Port %d will be used", port);	try	{		acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));	}	catch(...)	{		logNetwork->info("Port %d is busy, trying to use random port instead", port);		if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("enable-shm"))		{			logNetwork->error("Cant pass port number to client without shared memory!", port);			exit(0);		}		acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));		port = acceptor->local_endpoint().port();	}	logNetwork->info("Listening for connections at port %d", port);}CVCMIServer::~CVCMIServer(){	announceQueue.clear();	if(announceLobbyThread)		announceLobbyThread->join();}void CVCMIServer::run(){	if(!restartGameplay)	{		this->announceLobbyThread = vstd::make_unique<boost::thread>(&CVCMIServer::threadAnnounceLobby, this);#ifndef VCMI_ANDROID		if(cmdLineOptions.count("enable-shm"))		{			std::string sharedMemoryName = "vcmi_memory";			if(cmdLineOptions.count("enable-shm-uuid") && cmdLineOptions.count("uuid"))			{				sharedMemoryName += "_" + cmdLineOptions["uuid"].as<std::string>();			}			shm = std::make_shared<SharedMemory>(sharedMemoryName);		}#endif		startAsyncAccept();		#ifndef VCMI_ANDROID		if(shm)		{			shm->sr->setToReadyAndNotify(port);		}#else		CAndroidVMHelper vmHelper;		vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");		vmHelper.Detach();#endif	}	while(state == EServerState::LOBBY || state == EServerState::GAMEPLAY_STARTING)		boost::this_thread::sleep(boost::posix_time::milliseconds(50));	logNetwork->info("Thread handling connections ended");	if(state == EServerState::GAMEPLAY)	{		gh->run(si->mode == StartInfo::LOAD_GAME);	}}void CVCMIServer::threadAnnounceLobby(){	while(state != EServerState::SHUTDOWN)	{		{			boost::unique_lock<boost::recursive_mutex> myLock(mx);			while(!announceQueue.empty())			{				announcePack(std::move(announceQueue.front()));				announceQueue.pop_front();			}			if(state != EServerState::LOBBY)			{				if(acceptor)					acceptor->close();			}			if(acceptor)			{				acceptor->get_io_service().reset();				acceptor->get_io_service().poll();			}		}		boost::this_thread::sleep(boost::posix_time::milliseconds(50));	}}void CVCMIServer::prepareToStartGame(){	if(state == EServerState::GAMEPLAY)	{		restartGameplay = true;		state = EServerState::LOBBY;		// FIXME: dirry hack to make sure old CGameHandler::run is finished		boost::this_thread::sleep(boost::posix_time::milliseconds(1000));	}	state = EServerState::GAMEPLAY_STARTING;	gh = std::make_shared<CGameHandler>(this);	switch(si->mode)	{	case StartInfo::CAMPAIGN:		logNetwork->info("Preparing to start new campaign");		si->campState->currentMap = boost::make_optional(campaignMap);		si->campState->chosenCampaignBonuses[campaignMap] = campaignBonus;		gh->init(si.get());		break;	case StartInfo::NEW_GAME:		logNetwork->info("Preparing to start new game");		gh->init(si.get());		break;	case StartInfo::LOAD_GAME:		logNetwork->info("Preparing to start loaded game");		gh->load(si->mapname);		break;	default:		logNetwork->error("Wrong mode in StartInfo!");		assert(0);		break;	}}void CVCMIServer::startGameImmidiately(){	for(auto c : connections)		c->enterGameplayConnectionMode(gh->gs);	state = EServerState::GAMEPLAY;}void CVCMIServer::startAsyncAccept(){	assert(!upcomingConnection);	assert(acceptor);	upcomingConnection = std::make_shared<TSocket>(acceptor->get_io_service());	acceptor->async_accept(*upcomingConnection, std::bind(&CVCMIServer::connectionAccepted, this, _1));}void CVCMIServer::connectionAccepted(const boost::system::error_code & ec){	if(ec)	{		if(state != EServerState::SHUTDOWN)			logNetwork->info("Something wrong during accepting: %s", ec.message());		return;	}	try	{		logNetwork->info("We got a new connection! :)");		auto c = std::make_shared<CConnection>(upcomingConnection, NAME, uuid);		upcomingConnection.reset();		connections.insert(c);		c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);	}	catch(std::exception & e)	{		upcomingConnection.reset();		logNetwork->info("I guess it was just my imagination!");	}	startAsyncAccept();}void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c){	setThreadName("CVCMIServer::handleConnection");	c->enterLobbyConnectionMode();	try	{		while(c->connected)		{			CPack * pack = c->retrievePack();			if(auto lobbyPack = dynamic_ptr_cast<CPackForLobby>(pack))			{				handleReceivedPack(std::unique_ptr<CPackForLobby>(lobbyPack));			}			else if(auto serverPack = dynamic_ptr_cast<CPackForServer>(pack))			{				gh->handleReceivedPack(serverPack);			}		}	}	catch(boost::system::system_error & e)	{		if(state != EServerState::LOBBY)			gh->handleClientDisconnection(c);	}	catch(const std::exception & e)	{		boost::unique_lock<boost::recursive_mutex> queueLock(mx);		logNetwork->error("%s dies... \nWhat happened: %s", c->toString(), e.what());	}	catch(...)	{		state = EServerState::SHUTDOWN;		handleException();		throw;	}	boost::unique_lock<boost::recursive_mutex> queueLock(mx);//	if(state != ENDING_AND_STARTING_GAME)	if(c->connected)	{		auto lcd = vstd::make_unique<LobbyClientDisconnected>();		lcd->c = c;		lcd->clientId = c->connectionID;		handleReceivedPack(std::move(lcd));	}	logNetwork->info("Thread listening for %s ended", c->toString());	c->handler.reset();}void CVCMIServer::handleReceivedPack(std::unique_ptr<CPackForLobby> pack){	CBaseForServerApply * apply = applier->getApplier(typeList.getTypeID(pack.get()));	if(apply->applyOnServerBefore(this, pack.get()))		addToAnnounceQueue(std::move(pack));}void CVCMIServer::announcePack(std::unique_ptr<CPackForLobby> pack){	for(auto c : connections)	{		// FIXME: we need to avoid senting something to client that not yet get answer for LobbyClientConnected		// Until UUID set we only pass LobbyClientConnected to this client		if(c->uuid == uuid && !dynamic_cast<LobbyClientConnected *>(pack.get()))			continue;		c->sendPack(pack.get());	}	applier->getApplier(typeList.getTypeID(pack.get()))->applyOnServerAfter(this, pack.get());}void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName){	logNetwork->info("%s says: %s", playerName, txt);	auto cm = vstd::make_unique<LobbyChatMessage>();	cm->playerName = playerName;	cm->message = txt;	addToAnnounceQueue(std::move(cm));}void CVCMIServer::addToAnnounceQueue(std::unique_ptr<CPackForLobby> pack){	boost::unique_lock<boost::recursive_mutex> queueLock(mx);	announceQueue.push_back(std::move(pack));}bool CVCMIServer::passHost(int toConnectionId){	for(auto c : connections)	{		if(isClientHost(c->connectionID))			continue;		if(c->connectionID != toConnectionId)			continue;		hostClient = c;		hostClientId = c->connectionID;		announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId));		return true;	}	return false;}void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, StartInfo::EMode mode){	c->connectionID = currentClientId++;	if(!hostClient)	{		hostClient = c;		hostClientId = c->connectionID;		si->mode = mode;	}	logNetwork->info("Connection with client %d established. UUID: %s", c->connectionID, c->uuid);	for(auto & name : names)	{		logNetwork->info("Client %d player: %s", c->connectionID, name);		ui8 id = currentPlayerId++;		ClientPlayer cp;		cp.connection = c->connectionID;		cp.name = name;		playerNames.insert(std::make_pair(id, cp));		announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % id % c->connectionID));		//put new player in first slot with AI		for(auto & elem : si->playerInfos)		{			if(elem.second.isControlledByAI() && !elem.second.compOnly)			{				setPlayerConnectedId(elem.second, id);				break;			}		}	}}void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c){	connections -= c;	for(auto & pair : playerNames)	{		if(pair.second.connection != c->connectionID)			continue;		int id = pair.first;		announceTxt(boost::str(boost::format("%s (pid %d cid %d) left the game") % id % playerNames[id].name % c->connectionID));		playerNames.erase(id);		// Reset in-game players client used back to AI		if(PlayerSettings * s = si->getPlayersSettings(id))		{			setPlayerConnectedId(*s, PlayerSettings::PLAYER_AI);		}	}}void CVCMIServer::setPlayerConnectedId(PlayerSettings & pset, ui8 player) const{	if(vstd::contains(playerNames, player))		pset.name = playerNames.find(player)->second.name;	else		pset.name = VLC->generaltexth->allTexts[468]; //Computer	pset.connectedPlayerIDs.clear();	if(player != PlayerSettings::PLAYER_AI)		pset.connectedPlayerIDs.insert(player);}void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpts){	mi = mapInfo;	if(!mi)		return;	auto namesIt = playerNames.cbegin();	si->playerInfos.clear();	if(mi->scenarioOptionsOfSave)	{		si = CMemorySerializer::deepCopy(*mi->scenarioOptionsOfSave);		si->mode = StartInfo::LOAD_GAME;		if(si->campState)			campaignMap = si->campState->currentMap.get();		for(auto & ps : si->playerInfos)		{			if(!ps.second.compOnly && ps.second.connectedPlayerIDs.size() && namesIt != playerNames.cend())			{				setPlayerConnectedId(ps.second, namesIt++->first);			}			else			{				setPlayerConnectedId(ps.second, PlayerSettings::PLAYER_AI);			}		}	}	else if(si->mode == StartInfo::NEW_GAME || si->mode == StartInfo::CAMPAIGN)	{		if(mi->campaignHeader)			return;		for(int i = 0; i < mi->mapHeader->players.size(); i++)		{			const PlayerInfo & pinfo = mi->mapHeader->players[i];			//neither computer nor human can play - no player			if(!(pinfo.canHumanPlay || pinfo.canComputerPlay))				continue;			PlayerSettings & pset = si->playerInfos[PlayerColor(i)];			pset.color = PlayerColor(i);			if(pinfo.canHumanPlay && namesIt != playerNames.cend())			{				setPlayerConnectedId(pset, namesIt++->first);			}			else			{				setPlayerConnectedId(pset, PlayerSettings::PLAYER_AI);				if(!pinfo.canHumanPlay)				{					pset.compOnly = true;				}			}			pset.castle = pinfo.defaultCastle();			pset.hero = pinfo.defaultHero();			if(pset.hero != PlayerSettings::RANDOM && pinfo.hasCustomMainHero())			{				pset.hero = pinfo.mainCustomHeroId;				pset.heroName = pinfo.mainCustomHeroName;				pset.heroPortrait = pinfo.mainCustomHeroPortrait;			}			pset.handicap = PlayerSettings::NO_HANDICAP;		}		if(mi->isRandomMap && mapGenOpts)			si->mapGenOptions = std::shared_ptr<CMapGenOptions>(mapGenOpts);		else			si->mapGenOptions.reset();	}	si->mapname = mi->fileURI;}void CVCMIServer::updateAndPropagateLobbyState(){	boost::unique_lock<boost::mutex> stateLock(stateMutex);	// Update player settings for RMG	// TODO: find appropriate location for this code	if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)	{		for(const auto & psetPair : si->playerInfos)		{			const auto & pset = psetPair.second;			si->mapGenOptions->setStartingTownForPlayer(pset.color, pset.castle);			if(pset.isControlledByHuman())			{				si->mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::HUMAN);			}		}	}	auto lus = vstd::make_unique<LobbyUpdateState>();	lus->state = *this;	addToAnnounceQueue(std::move(lus));}void CVCMIServer::setPlayer(PlayerColor clickedColor){	struct PlayerToRestore	{		PlayerColor color;		int id;		void reset() { id = -1; color = PlayerColor::CANNOT_DETERMINE; }		PlayerToRestore(){ reset(); }	} playerToRestore;	PlayerSettings & clicked = si->playerInfos[clickedColor];	//identify clicked player	int clickedNameID = 0; //number of player - zero means AI, assume it initially	if(clicked.isControlledByHuman())		clickedNameID = *(clicked.connectedPlayerIDs.begin()); //if not AI - set appropiate ID	if(clickedNameID > 0 && playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place	{		PlayerSettings & restPos = si->playerInfos[playerToRestore.color];		setPlayerConnectedId(restPos, playerToRestore.id);		playerToRestore.reset();	}	int newPlayer; //which player will take clicked position	//who will be put here?	if(!clickedNameID) //AI player clicked -> if possible replace computer with unallocated player	{		newPlayer = getIdOfFirstUnallocatedPlayer();		if(!newPlayer) //no "free" player -> get just first one			newPlayer = playerNames.begin()->first;	}	else //human clicked -> take next	{		auto i = playerNames.find(clickedNameID); //clicked one		i++; //player AFTER clicked one		if(i != playerNames.end())			newPlayer = i->first;		else			newPlayer = 0; //AI if we scrolled through all players	}	setPlayerConnectedId(clicked, newPlayer); //put player	//if that player was somewhere else, we need to replace him with computer	if(newPlayer) //not AI	{		for(auto i = si->playerInfos.begin(); i != si->playerInfos.end(); i++)		{			int curNameID = *(i->second.connectedPlayerIDs.begin());			if(i->first != clickedColor && curNameID == newPlayer)			{				assert(i->second.connectedPlayerIDs.size());				playerToRestore.color = i->first;				playerToRestore.id = newPlayer;				setPlayerConnectedId(i->second, PlayerSettings::PLAYER_AI); //set computer				break;			}		}	}}void CVCMIServer::optionNextCastle(PlayerColor player, int dir){	PlayerSettings & s = si->playerInfos[player];	si16 & cur = s.castle;	auto & allowed = getPlayerInfo(player.getNum()).allowedFactions;	const bool allowRandomTown = getPlayerInfo(player.getNum()).isFactionRandom;	if(cur == PlayerSettings::NONE) //no change		return;	if(cur == PlayerSettings::RANDOM) //first/last available	{		if(dir > 0)			cur = *allowed.begin(); //id of first town		else			cur = *allowed.rbegin(); //id of last town	}	else // next/previous available	{		if((cur == *allowed.begin() && dir < 0) || (cur == *allowed.rbegin() && dir > 0))		{			if(allowRandomTown)			{				cur = PlayerSettings::RANDOM;			}			else			{				if(dir > 0)					cur = *allowed.begin();				else					cur = *allowed.rbegin();			}		}		else		{			assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range			auto iter = allowed.find(cur);			std::advance(iter, dir);			cur = *iter;		}	}	if(s.hero >= 0 && !getPlayerInfo(player.getNum()).hasCustomMainHero()) // remove hero unless it set to fixed one in map editor	{		s.hero = PlayerSettings::RANDOM;	}	if(cur < 0 && s.bonus == PlayerSettings::RESOURCE)		s.bonus = PlayerSettings::RANDOM;}void CVCMIServer::setCampaignMap(int mapId){	campaignMap = mapId;	si->difficulty = si->campState->camp->scenarios[mapId].difficulty;	campaignBonus = -1;	updateStartInfoOnMapChange(si->campState->getMapInfo(mapId));}void CVCMIServer::setCampaignBonus(int bonusId){	campaignBonus = bonusId;	const CCampaignScenario & scenario = si->campState->camp->scenarios[campaignMap];	const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;	if(bonDescs[bonusId].type == CScenarioTravel::STravelBonus::HERO)	{		for(auto & elem : si->playerInfos)		{			if(elem.first == PlayerColor(bonDescs[bonusId].info1))				setPlayerConnectedId(elem.second, 1);			else				setPlayerConnectedId(elem.second, PlayerSettings::PLAYER_AI);		}	}}void CVCMIServer::optionNextHero(PlayerColor player, int dir){	PlayerSettings & s = si->playerInfos[player];	if(s.castle < 0 || s.hero == PlayerSettings::NONE)		return;	if(s.hero == PlayerSettings::RANDOM) // first/last available	{		int max = VLC->heroh->heroes.size(),			min = 0;		s.hero = nextAllowedHero(player, min, max, 0, dir);	}	else	{		if(dir > 0)			s.hero = nextAllowedHero(player, s.hero, VLC->heroh->heroes.size(), 1, dir);		else			s.hero = nextAllowedHero(player, -1, s.hero, 1, dir); // min needs to be -1 -- hero at index 0 would be skipped otherwise	}}int CVCMIServer::nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir){	if(dir > 0)	{		for(int i = min + incl; i <= max - incl; i++)			if(canUseThisHero(player, i))				return i;	}	else	{		for(int i = max - incl; i >= min + incl; i--)			if(canUseThisHero(player, i))				return i;	}	return -1;}void CVCMIServer::optionNextBonus(PlayerColor player, int dir){	PlayerSettings & s = si->playerInfos[player];	PlayerSettings::Ebonus & ret = s.bonus = static_cast<PlayerSettings::Ebonus>(static_cast<int>(s.bonus) + dir);	if(s.hero == PlayerSettings::NONE &&		!getPlayerInfo(player.getNum()).heroesNames.size() &&		ret == PlayerSettings::ARTIFACT) //no hero - can't be artifact	{		if(dir < 0)			ret = PlayerSettings::RANDOM;		else			ret = PlayerSettings::GOLD;	}	if(ret > PlayerSettings::RESOURCE)		ret = PlayerSettings::RANDOM;	if(ret < PlayerSettings::RANDOM)		ret = PlayerSettings::RESOURCE;	if(s.castle == PlayerSettings::RANDOM && ret == PlayerSettings::RESOURCE) //random castle - can't be resource	{		if(dir < 0)			ret = PlayerSettings::GOLD;		else			ret = PlayerSettings::RANDOM;	}}bool CVCMIServer::canUseThisHero(PlayerColor player, int ID){	return VLC->heroh->heroes.size() > ID		&& si->playerInfos[player].castle == VLC->heroh->heroes[ID]->heroClass->faction		&& !vstd::contains(getUsedHeroes(), ID)		&& mi->mapHeader->allowedHeroes[ID];}std::vector<int> CVCMIServer::getUsedHeroes(){	std::vector<int> heroIds;	for(auto & p : si->playerInfos)	{		const auto & heroes = getPlayerInfo(p.first.getNum()).heroesNames;		for(auto & hero : heroes)			if(hero.heroId >= 0) //in VCMI map format heroId = -1 means random hero				heroIds.push_back(hero.heroId);		if(p.second.hero != PlayerSettings::RANDOM)			heroIds.push_back(p.second.hero);	}	return heroIds;}ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const{	for(auto i = playerNames.cbegin(); i != playerNames.cend(); i++)	{		if(!si->getPlayersSettings(i->first))			return i->first;	}	return 0;}#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)void handleLinuxSignal(int sig){	const int STACKTRACE_SIZE = 100;	void * buffer[STACKTRACE_SIZE];	int ptrCount = backtrace(buffer, STACKTRACE_SIZE);	char * * strings;	logGlobal->error("Error: signal %d :", sig);	strings = backtrace_symbols(buffer, ptrCount);	if(strings == nullptr)	{		logGlobal->error("There are no symbols.");	}	else	{		for(int i = 0; i < ptrCount; ++i)		{			logGlobal->error(strings[i]);		}		free(strings);	}	_exit(EXIT_FAILURE);}#endifstatic void handleCommandOptions(int argc, char * argv[], boost::program_options::variables_map & options){	namespace po = boost::program_options;	po::options_description opts("Allowed options");	opts.add_options()	("help,h", "display help and exit")	("version,v", "display version information and exit")	("run-by-client", "indicate that server launched by client on same machine")	("uuid", po::value<std::string>(), "")	("enable-shm-uuid", "use UUID for shared memory identifier")	("enable-shm", "enable usage of shared memory")	("port", po::value<ui16>(), "port at which server will listen to connections from client");	if(argc > 1)	{		try		{			po::store(po::parse_command_line(argc, argv, opts), options);		}		catch(std::exception & e)		{			std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;		}	}	po::notify(options);	if(options.count("help"))	{		auto time = std::time(0);		printf("%s - A Heroes of Might and Magic 3 clone\n", GameConstants::VCMI_VERSION.c_str());		printf("Copyright (C) 2007-%d VCMI dev team - see AUTHORS file\n", std::localtime(&time)->tm_year + 1900);		printf("This is free software; see the source for copying conditions. There is NO\n");		printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");		printf("\n");		std::cout << opts;		exit(0);	}	if(options.count("version"))	{		printf("%s\n", GameConstants::VCMI_VERSION.c_str());		std::cout << VCMIDirs::get().genHelpString();		exit(0);	}}int main(int argc, char * argv[]){#ifndef VCMI_ANDROID	// Correct working dir executable folder (not bundle folder) so we can use executable relative paths	boost::filesystem::current_path(boost::filesystem::system_complete(argv[0]).parent_path());#endif	// Installs a sig sev segmentation violation handler	// to log stacktrace#if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)	signal(SIGSEGV, handleLinuxSignal);#endif	console = new CConsoleHandler();	CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console);	logConfig.configureDefault();	logGlobal->info(NAME);	boost::program_options::variables_map opts;	handleCommandOptions(argc, argv, opts);	preinitDLL(console);	settings.init();	logConfig.configure();	loadDLLClasses();	srand((ui32)time(nullptr));	try	{		boost::asio::io_service io_service;		CVCMIServer server(opts);		try		{			while(server.state != EServerState::SHUTDOWN)			{				server.run();			}			io_service.run();		}		catch(boost::system::system_error & e) //for boost errors just log, not crash - probably client shut down connection		{			logNetwork->error(e.what());			server.state = EServerState::SHUTDOWN;		}		catch(...)		{			handleException();		}	}	catch(boost::system::system_error & e)	{		logNetwork->error(e.what());		//catch any startup errors (e.g. can't access port) errors		//and return non-zero status so client can detect error		throw;	}#ifdef VCMI_ANDROID	CAndroidVMHelper envHelper;	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");#endif	vstd::clear_pointer(VLC);	return 0;}#ifdef VCMI_ANDROIDvoid CVCMIServer::create(){	const char * foo[1] = {"android-server"};	main(1, const_cast<char **>(foo));}#endif
 |