CVCMIServer.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. /*
  2. * CVCMIServer.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #if BOOST_VERSION >= 106600
  12. #define BOOST_ASIO_ENABLE_OLD_SERVICES
  13. #endif
  14. #include <boost/asio.hpp>
  15. #include "../lib/filesystem/Filesystem.h"
  16. #include "../lib/mapping/CCampaignHandler.h"
  17. #include "../lib/CThreadHelper.h"
  18. #include "../lib/serializer/Connection.h"
  19. #include "../lib/CModHandler.h"
  20. #include "../lib/CArtHandler.h"
  21. #include "../lib/CGeneralTextHandler.h"
  22. #include "../lib/CHeroHandler.h"
  23. #include "../lib/CTownHandler.h"
  24. #include "../lib/CBuildingHandler.h"
  25. #include "../lib/spells/CSpellHandler.h"
  26. #include "../lib/CCreatureHandler.h"
  27. #include "zlib.h"
  28. #include "CVCMIServer.h"
  29. #include "../lib/StartInfo.h"
  30. #include "../lib/mapping/CMap.h"
  31. #include "../lib/rmg/CMapGenOptions.h"
  32. #ifdef VCMI_ANDROID
  33. #include "lib/CAndroidVMHelper.h"
  34. #else
  35. #include "../lib/Interprocess.h"
  36. #endif
  37. #include "../lib/VCMI_Lib.h"
  38. #include "../lib/VCMIDirs.h"
  39. #include "CGameHandler.h"
  40. #include "../lib/mapping/CMapInfo.h"
  41. #include "../lib/GameConstants.h"
  42. #include "../lib/logging/CBasicLogConfigurator.h"
  43. #include "../lib/CConfigHandler.h"
  44. #include "../lib/ScopeGuard.h"
  45. #include "../lib/UnlockGuard.h"
  46. // for applier
  47. #include "../lib/registerTypes/RegisterTypes.h"
  48. // UUID generation
  49. #include <boost/uuid/uuid.hpp>
  50. #include <boost/uuid/uuid_io.hpp>
  51. #include <boost/uuid/uuid_generators.hpp>
  52. #include "../lib/CGameState.h"
  53. #if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)
  54. #include <execinfo.h>
  55. #endif
  56. template<typename T> class CApplyOnServer;
  57. class CBaseForServerApply
  58. {
  59. public:
  60. virtual bool applyOnServerBefore(CVCMIServer * srv, void * pack) const =0;
  61. virtual void applyOnServerAfter(CVCMIServer * srv, void * pack) const =0;
  62. virtual ~CBaseForServerApply() {}
  63. template<typename U> static CBaseForServerApply * getApplier(const U * t = nullptr)
  64. {
  65. return new CApplyOnServer<U>();
  66. }
  67. };
  68. template <typename T> class CApplyOnServer : public CBaseForServerApply
  69. {
  70. public:
  71. bool applyOnServerBefore(CVCMIServer * srv, void * pack) const override
  72. {
  73. T * ptr = static_cast<T *>(pack);
  74. if(ptr->checkClientPermissions(srv))
  75. {
  76. boost::unique_lock<boost::mutex> stateLock(srv->stateMutex);
  77. return ptr->applyOnServer(srv);
  78. }
  79. else
  80. return false;
  81. }
  82. void applyOnServerAfter(CVCMIServer * srv, void * pack) const override
  83. {
  84. T * ptr = static_cast<T *>(pack);
  85. ptr->applyOnServerAfterAnnounce(srv);
  86. }
  87. };
  88. template <>
  89. class CApplyOnServer<CPack> : public CBaseForServerApply
  90. {
  91. public:
  92. bool applyOnServerBefore(CVCMIServer * srv, void * pack) const override
  93. {
  94. logGlobal->error("Cannot apply plain CPack!");
  95. assert(0);
  96. return false;
  97. }
  98. void applyOnServerAfter(CVCMIServer * srv, void * pack) const override
  99. {
  100. logGlobal->error("Cannot apply plain CPack!");
  101. assert(0);
  102. }
  103. };
  104. std::string NAME_AFFIX = "server";
  105. std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')';
  106. CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts)
  107. : port(3030), io(std::make_shared<boost::asio::io_service>()), state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false)
  108. {
  109. uuid = boost::uuids::to_string(boost::uuids::random_generator()());
  110. logNetwork->trace("CVCMIServer created! UUID: %s", uuid);
  111. applier = std::make_shared<CApplier<CBaseForServerApply>>();
  112. registerTypesLobbyPacks(*applier);
  113. if(cmdLineOptions.count("port"))
  114. port = cmdLineOptions["port"].as<ui16>();
  115. logNetwork->info("Port %d will be used", port);
  116. try
  117. {
  118. acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
  119. }
  120. catch(...)
  121. {
  122. logNetwork->info("Port %d is busy, trying to use random port instead", port);
  123. if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("enable-shm"))
  124. {
  125. logNetwork->error("Cant pass port number to client without shared memory!", port);
  126. exit(0);
  127. }
  128. acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));
  129. port = acceptor->local_endpoint().port();
  130. }
  131. logNetwork->info("Listening for connections at port %d", port);
  132. }
  133. CVCMIServer::~CVCMIServer()
  134. {
  135. for(CPackForLobby * pack : announceQueue)
  136. delete pack;
  137. announceQueue.clear();
  138. }
  139. void CVCMIServer::run()
  140. {
  141. if(!restartGameplay)
  142. {
  143. boost::thread(&CVCMIServer::threadAnnounceLobby, this);
  144. #ifndef VCMI_ANDROID
  145. if(cmdLineOptions.count("enable-shm"))
  146. {
  147. std::string sharedMemoryName = "vcmi_memory";
  148. if(cmdLineOptions.count("enable-shm-uuid") && cmdLineOptions.count("uuid"))
  149. {
  150. sharedMemoryName += "_" + cmdLineOptions["uuid"].as<std::string>();
  151. }
  152. shm = std::make_shared<SharedMemory>(sharedMemoryName);
  153. }
  154. #endif
  155. startAsyncAccept();
  156. if(shm)
  157. {
  158. shm->sr->setToReadyAndNotify(port);
  159. }
  160. }
  161. while(state == EServerState::LOBBY)
  162. boost::this_thread::sleep(boost::posix_time::milliseconds(50));
  163. logNetwork->info("Thread handling connections ended");
  164. if(state == EServerState::GAMEPLAY)
  165. {
  166. gh->run(si->mode == StartInfo::LOAD_GAME);
  167. }
  168. }
  169. void CVCMIServer::threadAnnounceLobby()
  170. {
  171. while(state != EServerState::SHUTDOWN)
  172. {
  173. {
  174. boost::unique_lock<boost::recursive_mutex> myLock(mx);
  175. while(!announceQueue.empty())
  176. {
  177. announcePack(announceQueue.front());
  178. announceQueue.pop_front();
  179. }
  180. if(state != EServerState::LOBBY)
  181. {
  182. if(acceptor)
  183. acceptor->close();
  184. }
  185. if(acceptor)
  186. {
  187. acceptor->get_io_service().reset();
  188. acceptor->get_io_service().poll();
  189. }
  190. }
  191. boost::this_thread::sleep(boost::posix_time::milliseconds(50));
  192. }
  193. }
  194. void CVCMIServer::prepareToStartGame()
  195. {
  196. if(state == EServerState::GAMEPLAY)
  197. {
  198. restartGameplay = true;
  199. state = EServerState::LOBBY;
  200. // FIXME: dirry hack to make sure old CGameHandler::run is finished
  201. boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
  202. }
  203. gh = std::make_shared<CGameHandler>(this);
  204. switch(si->mode)
  205. {
  206. case StartInfo::CAMPAIGN:
  207. logNetwork->info("Preparing to start new campaign");
  208. si->campState->currentMap = boost::make_optional(campaignMap);
  209. si->campState->chosenCampaignBonuses[campaignMap] = campaignBonus;
  210. gh->init(si.get());
  211. break;
  212. case StartInfo::NEW_GAME:
  213. logNetwork->info("Preparing to start new game");
  214. gh->init(si.get());
  215. break;
  216. case StartInfo::LOAD_GAME:
  217. logNetwork->info("Preparing to start loaded game");
  218. gh->load(si->mapname);
  219. break;
  220. default:
  221. logNetwork->error("Wrong mode in StartInfo!");
  222. assert(0);
  223. break;
  224. }
  225. }
  226. void CVCMIServer::startGameImmidiately()
  227. {
  228. for(auto c : connections)
  229. c->enterGameplayConnectionMode(gh->gs);
  230. state = EServerState::GAMEPLAY;
  231. }
  232. void CVCMIServer::startAsyncAccept()
  233. {
  234. assert(!upcomingConnection);
  235. assert(acceptor);
  236. upcomingConnection = std::make_shared<TSocket>(acceptor->get_io_service());
  237. acceptor->async_accept(*upcomingConnection, std::bind(&CVCMIServer::connectionAccepted, this, _1));
  238. }
  239. void CVCMIServer::connectionAccepted(const boost::system::error_code & ec)
  240. {
  241. if(ec)
  242. {
  243. if(state != EServerState::SHUTDOWN)
  244. logNetwork->info("Something wrong during accepting: %s", ec.message());
  245. return;
  246. }
  247. try
  248. {
  249. logNetwork->info("We got a new connection! :)");
  250. auto c = std::make_shared<CConnection>(upcomingConnection, NAME, uuid);
  251. upcomingConnection.reset();
  252. connections.insert(c);
  253. c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
  254. }
  255. catch(std::exception & e)
  256. {
  257. upcomingConnection.reset();
  258. logNetwork->info("I guess it was just my imagination!");
  259. }
  260. startAsyncAccept();
  261. }
  262. void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
  263. {
  264. setThreadName("CVCMIServer::handleConnection");
  265. c->enterLobbyConnectionMode();
  266. try
  267. {
  268. while(c->connected)
  269. {
  270. CPack * pack = c->retrievePack();
  271. if(auto lobbyPack = dynamic_ptr_cast<CPackForLobby>(pack))
  272. {
  273. handleReceivedPack(lobbyPack);
  274. }
  275. else if(auto serverPack = dynamic_ptr_cast<CPackForServer>(pack))
  276. {
  277. gh->handleReceivedPack(serverPack);
  278. }
  279. }
  280. }
  281. catch(boost::system::system_error & e)
  282. {
  283. if(state != EServerState::LOBBY)
  284. gh->handleClientDisconnection(c);
  285. }
  286. catch(const std::exception & e)
  287. {
  288. boost::unique_lock<boost::recursive_mutex> queueLock(mx);
  289. logNetwork->error("%s dies... \nWhat happened: %s", c->toString(), e.what());
  290. }
  291. catch(...)
  292. {
  293. state = EServerState::SHUTDOWN;
  294. handleException();
  295. throw;
  296. }
  297. boost::unique_lock<boost::recursive_mutex> queueLock(mx);
  298. // if(state != ENDING_AND_STARTING_GAME)
  299. {
  300. auto lcd = new LobbyClientDisconnected();
  301. lcd->c = c;
  302. lcd->clientId = c->connectionID;
  303. handleReceivedPack(lcd);
  304. }
  305. logNetwork->info("Thread listening for %s ended", c->toString());
  306. c->handler.reset();
  307. }
  308. void CVCMIServer::handleReceivedPack(CPackForLobby * pack)
  309. {
  310. CBaseForServerApply * apply = applier->getApplier(typeList.getTypeID(pack));
  311. if(apply->applyOnServerBefore(this, pack))
  312. addToAnnounceQueue(pack);
  313. else
  314. delete pack;
  315. }
  316. void CVCMIServer::announcePack(CPackForLobby * pack)
  317. {
  318. for(auto c : connections)
  319. {
  320. // FIXME: we need to avoid senting something to client that not yet get answer for LobbyClientConnected
  321. // Until UUID set we only pass LobbyClientConnected to this client
  322. if(c->uuid == uuid && !dynamic_cast<LobbyClientConnected *>(pack))
  323. continue;
  324. c->sendPack(pack);
  325. }
  326. applier->getApplier(typeList.getTypeID(pack))->applyOnServerAfter(this, pack);
  327. delete pack;
  328. }
  329. void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName)
  330. {
  331. logNetwork->info("%s says: %s", playerName, txt);
  332. auto cm = new LobbyChatMessage();
  333. cm->playerName = playerName;
  334. cm->message = txt;
  335. addToAnnounceQueue(cm);
  336. }
  337. void CVCMIServer::addToAnnounceQueue(CPackForLobby * pack)
  338. {
  339. boost::unique_lock<boost::recursive_mutex> queueLock(mx);
  340. announceQueue.push_back(pack);
  341. }
  342. bool CVCMIServer::passHost(int toConnectionId)
  343. {
  344. for(auto c : connections)
  345. {
  346. if(isClientHost(c->connectionID))
  347. continue;
  348. if(c->connectionID != toConnectionId)
  349. continue;
  350. hostClient = c;
  351. hostClientId = c->connectionID;
  352. announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId));
  353. return true;
  354. }
  355. return false;
  356. }
  357. void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, StartInfo::EMode mode)
  358. {
  359. c->connectionID = currentClientId++;
  360. if(!hostClient)
  361. {
  362. hostClient = c;
  363. hostClientId = c->connectionID;
  364. si->mode = mode;
  365. }
  366. logNetwork->info("Connection with client %d established. UUID: %s", c->connectionID, c->uuid);
  367. for(auto & name : names)
  368. {
  369. logNetwork->info("Client %d player: %s", c->connectionID, name);
  370. ui8 id = currentPlayerId++;
  371. ClientPlayer cp;
  372. cp.connection = c->connectionID;
  373. cp.name = name;
  374. playerNames.insert(std::make_pair(id, cp));
  375. announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % id % c->connectionID));
  376. //put new player in first slot with AI
  377. for(auto & elem : si->playerInfos)
  378. {
  379. if(elem.second.isControlledByAI() && !elem.second.compOnly)
  380. {
  381. setPlayerConnectedId(elem.second, id);
  382. break;
  383. }
  384. }
  385. }
  386. }
  387. void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
  388. {
  389. connections -= c;
  390. for(auto & pair : playerNames)
  391. {
  392. if(pair.second.connection != c->connectionID)
  393. continue;
  394. int id = pair.first;
  395. announceTxt(boost::str(boost::format("%s (pid %d cid %d) left the game") % id % playerNames[id].name % c->connectionID));
  396. playerNames.erase(id);
  397. // Reset in-game players client used back to AI
  398. if(PlayerSettings * s = si->getPlayersSettings(id))
  399. {
  400. setPlayerConnectedId(*s, PlayerSettings::PLAYER_AI);
  401. }
  402. }
  403. }
  404. void CVCMIServer::setPlayerConnectedId(PlayerSettings & pset, ui8 player) const
  405. {
  406. if(vstd::contains(playerNames, player))
  407. pset.name = playerNames.find(player)->second.name;
  408. else
  409. pset.name = VLC->generaltexth->allTexts[468]; //Computer
  410. pset.connectedPlayerIDs.clear();
  411. if(player != PlayerSettings::PLAYER_AI)
  412. pset.connectedPlayerIDs.insert(player);
  413. }
  414. void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpts)
  415. {
  416. mi = mapInfo;
  417. if(!mi)
  418. return;
  419. auto namesIt = playerNames.cbegin();
  420. si->playerInfos.clear();
  421. if(mi->scenarioOptionsOfSave)
  422. {
  423. si = std::shared_ptr<StartInfo>(mi->scenarioOptionsOfSave);
  424. si->mode = StartInfo::LOAD_GAME;
  425. if(si->campState)
  426. campaignMap = si->campState->currentMap.get();
  427. for(auto & ps : si->playerInfos)
  428. {
  429. if(!ps.second.compOnly && ps.second.connectedPlayerIDs.size() && namesIt != playerNames.cend())
  430. {
  431. setPlayerConnectedId(ps.second, namesIt++->first);
  432. }
  433. else
  434. {
  435. setPlayerConnectedId(ps.second, PlayerSettings::PLAYER_AI);
  436. }
  437. }
  438. }
  439. else if(si->mode == StartInfo::NEW_GAME || si->mode == StartInfo::CAMPAIGN)
  440. {
  441. if(mi->campaignHeader)
  442. return;
  443. for(int i = 0; i < mi->mapHeader->players.size(); i++)
  444. {
  445. const PlayerInfo & pinfo = mi->mapHeader->players[i];
  446. //neither computer nor human can play - no player
  447. if(!(pinfo.canHumanPlay || pinfo.canComputerPlay))
  448. continue;
  449. PlayerSettings & pset = si->playerInfos[PlayerColor(i)];
  450. pset.color = PlayerColor(i);
  451. if(pinfo.canHumanPlay && namesIt != playerNames.cend())
  452. {
  453. setPlayerConnectedId(pset, namesIt++->first);
  454. }
  455. else
  456. {
  457. setPlayerConnectedId(pset, PlayerSettings::PLAYER_AI);
  458. if(!pinfo.canHumanPlay)
  459. {
  460. pset.compOnly = true;
  461. }
  462. }
  463. pset.castle = pinfo.defaultCastle();
  464. pset.hero = pinfo.defaultHero();
  465. if(pset.hero != PlayerSettings::RANDOM && pinfo.hasCustomMainHero())
  466. {
  467. pset.hero = pinfo.mainCustomHeroId;
  468. pset.heroName = pinfo.mainCustomHeroName;
  469. pset.heroPortrait = pinfo.mainCustomHeroPortrait;
  470. }
  471. pset.handicap = PlayerSettings::NO_HANDICAP;
  472. }
  473. if(mi->isRandomMap && mapGenOpts)
  474. si->mapGenOptions = std::shared_ptr<CMapGenOptions>(mapGenOpts);
  475. else
  476. si->mapGenOptions.reset();
  477. }
  478. si->mapname = mi->fileURI;
  479. }
  480. void CVCMIServer::updateAndPropagateLobbyState()
  481. {
  482. boost::unique_lock<boost::mutex> stateLock(stateMutex);
  483. // Update player settings for RMG
  484. // TODO: find appropriate location for this code
  485. if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
  486. {
  487. for(const auto & psetPair : si->playerInfos)
  488. {
  489. const auto & pset = psetPair.second;
  490. si->mapGenOptions->setStartingTownForPlayer(pset.color, pset.castle);
  491. if(pset.isControlledByHuman())
  492. {
  493. si->mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::HUMAN);
  494. }
  495. }
  496. }
  497. auto lus = new LobbyUpdateState();
  498. lus->state = *this;
  499. addToAnnounceQueue(lus);
  500. }
  501. void CVCMIServer::setPlayer(PlayerColor clickedColor)
  502. {
  503. struct PlayerToRestore
  504. {
  505. PlayerColor color;
  506. int id;
  507. void reset() { id = -1; color = PlayerColor::CANNOT_DETERMINE; }
  508. PlayerToRestore(){ reset(); }
  509. } playerToRestore;
  510. PlayerSettings & clicked = si->playerInfos[clickedColor];
  511. PlayerSettings * old = nullptr;
  512. //identify clicked player
  513. int clickedNameID = 0; //number of player - zero means AI, assume it initially
  514. if(clicked.isControlledByHuman())
  515. clickedNameID = *(clicked.connectedPlayerIDs.begin()); //if not AI - set appropiate ID
  516. if(clickedNameID > 0 && playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place
  517. {
  518. PlayerSettings & restPos = si->playerInfos[playerToRestore.color];
  519. setPlayerConnectedId(restPos, playerToRestore.id);
  520. playerToRestore.reset();
  521. }
  522. int newPlayer; //which player will take clicked position
  523. //who will be put here?
  524. if(!clickedNameID) //AI player clicked -> if possible replace computer with unallocated player
  525. {
  526. newPlayer = getIdOfFirstUnallocatedPlayer();
  527. if(!newPlayer) //no "free" player -> get just first one
  528. newPlayer = playerNames.begin()->first;
  529. }
  530. else //human clicked -> take next
  531. {
  532. auto i = playerNames.find(clickedNameID); //clicked one
  533. i++; //player AFTER clicked one
  534. if(i != playerNames.end())
  535. newPlayer = i->first;
  536. else
  537. newPlayer = 0; //AI if we scrolled through all players
  538. }
  539. setPlayerConnectedId(clicked, newPlayer); //put player
  540. //if that player was somewhere else, we need to replace him with computer
  541. if(newPlayer) //not AI
  542. {
  543. for(auto i = si->playerInfos.begin(); i != si->playerInfos.end(); i++)
  544. {
  545. int curNameID = *(i->second.connectedPlayerIDs.begin());
  546. if(i->first != clickedColor && curNameID == newPlayer)
  547. {
  548. assert(i->second.connectedPlayerIDs.size());
  549. playerToRestore.color = i->first;
  550. playerToRestore.id = newPlayer;
  551. setPlayerConnectedId(i->second, PlayerSettings::PLAYER_AI); //set computer
  552. old = &i->second;
  553. break;
  554. }
  555. }
  556. }
  557. }
  558. void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
  559. {
  560. PlayerSettings & s = si->playerInfos[player];
  561. si16 & cur = s.castle;
  562. auto & allowed = getPlayerInfo(player.getNum()).allowedFactions;
  563. const bool allowRandomTown = getPlayerInfo(player.getNum()).isFactionRandom;
  564. if(cur == PlayerSettings::NONE) //no change
  565. return;
  566. if(cur == PlayerSettings::RANDOM) //first/last available
  567. {
  568. if(dir > 0)
  569. cur = *allowed.begin(); //id of first town
  570. else
  571. cur = *allowed.rbegin(); //id of last town
  572. }
  573. else // next/previous available
  574. {
  575. if((cur == *allowed.begin() && dir < 0) || (cur == *allowed.rbegin() && dir > 0))
  576. {
  577. if(allowRandomTown)
  578. {
  579. cur = PlayerSettings::RANDOM;
  580. }
  581. else
  582. {
  583. if(dir > 0)
  584. cur = *allowed.begin();
  585. else
  586. cur = *allowed.rbegin();
  587. }
  588. }
  589. else
  590. {
  591. assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range
  592. auto iter = allowed.find(cur);
  593. std::advance(iter, dir);
  594. cur = *iter;
  595. }
  596. }
  597. if(s.hero >= 0 && !getPlayerInfo(player.getNum()).hasCustomMainHero()) // remove hero unless it set to fixed one in map editor
  598. {
  599. s.hero = PlayerSettings::RANDOM;
  600. }
  601. if(cur < 0 && s.bonus == PlayerSettings::RESOURCE)
  602. s.bonus = PlayerSettings::RANDOM;
  603. }
  604. void CVCMIServer::setCampaignMap(int mapId)
  605. {
  606. campaignMap = mapId;
  607. si->difficulty = si->campState->camp->scenarios[mapId].difficulty;
  608. campaignBonus = -1;
  609. updateStartInfoOnMapChange(si->campState->getMapInfo(mapId));
  610. }
  611. void CVCMIServer::setCampaignBonus(int bonusId)
  612. {
  613. campaignBonus = bonusId;
  614. const CCampaignScenario & scenario = si->campState->camp->scenarios[campaignMap];
  615. const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
  616. if(bonDescs[bonusId].type == CScenarioTravel::STravelBonus::HERO)
  617. {
  618. for(auto & elem : si->playerInfos)
  619. {
  620. if(elem.first == PlayerColor(bonDescs[bonusId].info1))
  621. setPlayerConnectedId(elem.second, 1);
  622. else
  623. setPlayerConnectedId(elem.second, PlayerSettings::PLAYER_AI);
  624. }
  625. }
  626. }
  627. void CVCMIServer::optionNextHero(PlayerColor player, int dir)
  628. {
  629. PlayerSettings & s = si->playerInfos[player];
  630. if(s.castle < 0 || s.hero == PlayerSettings::NONE)
  631. return;
  632. if(s.hero == PlayerSettings::RANDOM) // first/last available
  633. {
  634. int max = VLC->heroh->heroes.size(),
  635. min = 0;
  636. s.hero = nextAllowedHero(player, min, max, 0, dir);
  637. }
  638. else
  639. {
  640. if(dir > 0)
  641. s.hero = nextAllowedHero(player, s.hero, VLC->heroh->heroes.size(), 1, dir);
  642. else
  643. s.hero = nextAllowedHero(player, -1, s.hero, 1, dir); // min needs to be -1 -- hero at index 0 would be skipped otherwise
  644. }
  645. }
  646. int CVCMIServer::nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir)
  647. {
  648. if(dir > 0)
  649. {
  650. for(int i = min + incl; i <= max - incl; i++)
  651. if(canUseThisHero(player, i))
  652. return i;
  653. }
  654. else
  655. {
  656. for(int i = max - incl; i >= min + incl; i--)
  657. if(canUseThisHero(player, i))
  658. return i;
  659. }
  660. return -1;
  661. }
  662. void CVCMIServer::optionNextBonus(PlayerColor player, int dir)
  663. {
  664. PlayerSettings & s = si->playerInfos[player];
  665. PlayerSettings::Ebonus & ret = s.bonus = static_cast<PlayerSettings::Ebonus>(static_cast<int>(s.bonus) + dir);
  666. if(s.hero == PlayerSettings::NONE &&
  667. !getPlayerInfo(player.getNum()).heroesNames.size() &&
  668. ret == PlayerSettings::ARTIFACT) //no hero - can't be artifact
  669. {
  670. if(dir < 0)
  671. ret = PlayerSettings::RANDOM;
  672. else
  673. ret = PlayerSettings::GOLD;
  674. }
  675. if(ret > PlayerSettings::RESOURCE)
  676. ret = PlayerSettings::RANDOM;
  677. if(ret < PlayerSettings::RANDOM)
  678. ret = PlayerSettings::RESOURCE;
  679. if(s.castle == PlayerSettings::RANDOM && ret == PlayerSettings::RESOURCE) //random castle - can't be resource
  680. {
  681. if(dir < 0)
  682. ret = PlayerSettings::GOLD;
  683. else
  684. ret = PlayerSettings::RANDOM;
  685. }
  686. }
  687. bool CVCMIServer::canUseThisHero(PlayerColor player, int ID)
  688. {
  689. return VLC->heroh->heroes.size() > ID
  690. && si->playerInfos[player].castle == VLC->heroh->heroes[ID]->heroClass->faction
  691. && !vstd::contains(getUsedHeroes(), ID)
  692. && mi->mapHeader->allowedHeroes[ID];
  693. }
  694. std::vector<int> CVCMIServer::getUsedHeroes()
  695. {
  696. std::vector<int> heroIds;
  697. for(auto & p : si->playerInfos)
  698. {
  699. const auto & heroes = getPlayerInfo(p.first.getNum()).heroesNames;
  700. for(auto & hero : heroes)
  701. if(hero.heroId >= 0) //in VCMI map format heroId = -1 means random hero
  702. heroIds.push_back(hero.heroId);
  703. if(p.second.hero != PlayerSettings::RANDOM)
  704. heroIds.push_back(p.second.hero);
  705. }
  706. return heroIds;
  707. }
  708. ui8 CVCMIServer::getIdOfFirstUnallocatedPlayer() const
  709. {
  710. for(auto i = playerNames.cbegin(); i != playerNames.cend(); i++)
  711. {
  712. if(!si->getPlayersSettings(i->first))
  713. return i->first;
  714. }
  715. return 0;
  716. }
  717. #if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)
  718. void handleLinuxSignal(int sig)
  719. {
  720. const int STACKTRACE_SIZE = 100;
  721. void * buffer[STACKTRACE_SIZE];
  722. int ptrCount = backtrace(buffer, STACKTRACE_SIZE);
  723. char * * strings;
  724. logGlobal->error("Error: signal %d :", sig);
  725. strings = backtrace_symbols(buffer, ptrCount);
  726. if(strings == nullptr)
  727. {
  728. logGlobal->error("There are no symbols.");
  729. }
  730. else
  731. {
  732. for(int i = 0; i < ptrCount; ++i)
  733. {
  734. logGlobal->error(strings[i]);
  735. }
  736. free(strings);
  737. }
  738. _exit(EXIT_FAILURE);
  739. }
  740. #endif
  741. static void handleCommandOptions(int argc, char * argv[], boost::program_options::variables_map & options)
  742. {
  743. namespace po = boost::program_options;
  744. po::options_description opts("Allowed options");
  745. opts.add_options()
  746. ("help,h", "display help and exit")
  747. ("version,v", "display version information and exit")
  748. ("run-by-client", "indicate that server launched by client on same machine")
  749. ("uuid", po::value<std::string>(), "")
  750. ("enable-shm-uuid", "use UUID for shared memory identifier")
  751. ("enable-shm", "enable usage of shared memory")
  752. ("port", po::value<ui16>(), "port at which server will listen to connections from client");
  753. if(argc > 1)
  754. {
  755. try
  756. {
  757. po::store(po::parse_command_line(argc, argv, opts), options);
  758. }
  759. catch(std::exception & e)
  760. {
  761. std::cerr << "Failure during parsing command-line options:\n" << e.what() << std::endl;
  762. }
  763. }
  764. po::notify(options);
  765. if(options.count("help"))
  766. {
  767. auto time = std::time(0);
  768. printf("%s - A Heroes of Might and Magic 3 clone\n", GameConstants::VCMI_VERSION.c_str());
  769. printf("Copyright (C) 2007-%d VCMI dev team - see AUTHORS file\n", std::localtime(&time)->tm_year + 1900);
  770. printf("This is free software; see the source for copying conditions. There is NO\n");
  771. printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
  772. printf("\n");
  773. std::cout << opts;
  774. exit(0);
  775. }
  776. if(options.count("version"))
  777. {
  778. printf("%s\n", GameConstants::VCMI_VERSION.c_str());
  779. std::cout << VCMIDirs::get().genHelpString();
  780. exit(0);
  781. }
  782. }
  783. int main(int argc, char * argv[])
  784. {
  785. #ifndef VCMI_ANDROID
  786. // Correct working dir executable folder (not bundle folder) so we can use executable relative paths
  787. boost::filesystem::current_path(boost::filesystem::system_complete(argv[0]).parent_path());
  788. #endif
  789. // Installs a sig sev segmentation violation handler
  790. // to log stacktrace
  791. #if defined(__GNUC__) && !defined(__MINGW32__) && !defined(VCMI_ANDROID)
  792. signal(SIGSEGV, handleLinuxSignal);
  793. #endif
  794. console = new CConsoleHandler();
  795. CBasicLogConfigurator logConfig(VCMIDirs::get().userCachePath() / "VCMI_Server_log.txt", console);
  796. logConfig.configureDefault();
  797. logGlobal->info(NAME);
  798. boost::program_options::variables_map opts;
  799. handleCommandOptions(argc, argv, opts);
  800. preinitDLL(console);
  801. settings.init();
  802. logConfig.configure();
  803. loadDLLClasses();
  804. srand((ui32)time(nullptr));
  805. try
  806. {
  807. boost::asio::io_service io_service;
  808. CVCMIServer server(opts);
  809. try
  810. {
  811. while(server.state != EServerState::SHUTDOWN)
  812. {
  813. server.run();
  814. }
  815. io_service.run();
  816. }
  817. catch(boost::system::system_error & e) //for boost errors just log, not crash - probably client shut down connection
  818. {
  819. logNetwork->error(e.what());
  820. server.state = EServerState::SHUTDOWN;
  821. }
  822. catch(...)
  823. {
  824. handleException();
  825. }
  826. }
  827. catch(boost::system::system_error & e)
  828. {
  829. logNetwork->error(e.what());
  830. //catch any startup errors (e.g. can't access port) errors
  831. //and return non-zero status so client can detect error
  832. throw;
  833. }
  834. #ifdef VCMI_ANDROID
  835. CAndroidVMHelper envHelper;
  836. envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
  837. #endif
  838. vstd::clear_pointer(VLC);
  839. CResourceHandler::clear();
  840. return 0;
  841. }
  842. #ifdef VCMI_ANDROID
  843. void CVCMIServer::create()
  844. {
  845. const char * foo[1] = {"android-server"};
  846. main(1, const_cast<char **>(foo));
  847. }
  848. #endif