CVCMIServer.cpp 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199
  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. #include "CVCMIServer.h"
  12. #include "CGameHandler.h"
  13. #include "GlobalLobbyProcessor.h"
  14. #include "LobbyNetPackVisitors.h"
  15. #include "processors/PlayerMessageProcessor.h"
  16. #include "../lib/CThreadHelper.h"
  17. #include "../lib/GameLibrary.h"
  18. #include "../lib/campaign/CampaignState.h"
  19. #include "../lib/entities/hero/CHeroHandler.h"
  20. #include "../lib/entities/hero/CHeroClass.h"
  21. #include "../lib/entities/ResourceTypeHandler.h"
  22. #include "../lib/gameState/CGameState.h"
  23. #include "../lib/mapping/CMapInfo.h"
  24. #include "../lib/mapping/CMapHeader.h"
  25. #include "../lib/modding/ModIncompatibility.h"
  26. #include "../lib/rmg/CMapGenOptions.h"
  27. #include "../lib/serializer/CMemorySerializer.h"
  28. #include "../lib/serializer/GameConnection.h"
  29. #include "../lib/texts/CGeneralTextHandler.h"
  30. // UUID generation
  31. #include <boost/uuid/uuid.hpp>
  32. #include <boost/uuid/uuid_io.hpp>
  33. #include <boost/uuid/uuid_generators.hpp>
  34. #include <boost/program_options.hpp>
  35. class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
  36. {
  37. private:
  38. CVCMIServer & handler;
  39. std::shared_ptr<CGameHandler> gh;
  40. std::shared_ptr<GameConnection> connection;
  41. public:
  42. CVCMIServerPackVisitor(CVCMIServer & handler, const std::shared_ptr<CGameHandler> & gh, const std::shared_ptr<GameConnection> & connection)
  43. : handler(handler)
  44. , gh(gh)
  45. , connection(connection)
  46. {
  47. }
  48. bool callTyped() override { return false; }
  49. void visitForLobby(CPackForLobby & packForLobby) override
  50. {
  51. handler.handleReceivedPack(connection, packForLobby);
  52. }
  53. void visitForServer(CPackForServer & serverPack) override
  54. {
  55. if (gh)
  56. gh->handleReceivedPack(connection->connectionID, serverPack);
  57. else
  58. logNetwork->error("Received pack for game server while in lobby!");
  59. }
  60. void visitForClient(CPackForClient & clientPack) override
  61. {
  62. }
  63. };
  64. CVCMIServer::CVCMIServer(uint16_t port, bool runByClient)
  65. : currentClientId(GameConnectionID::FIRST_CONNECTION)
  66. , currentPlayerId(PlayerConnectionID::FIRST_HUMAN)
  67. , port(port)
  68. , runByClient(runByClient)
  69. {
  70. uuid = boost::uuids::to_string(boost::uuids::random_generator()());
  71. logNetwork->trace("CVCMIServer created! UUID: %s", uuid);
  72. networkHandler = INetworkHandler::createHandler();
  73. if(state == EServerState::LOBBY)
  74. startDiscoveryListener();
  75. }
  76. CVCMIServer::~CVCMIServer()
  77. {
  78. stopDiscoveryListener();
  79. }
  80. uint16_t CVCMIServer::prepare(bool connectToLobby, bool listenForConnections) {
  81. if(connectToLobby) {
  82. lobbyProcessor = std::make_unique<GlobalLobbyProcessor>(*this);
  83. return 0;
  84. } else {
  85. return startAcceptingIncomingConnections(listenForConnections);
  86. }
  87. }
  88. uint16_t CVCMIServer::startAcceptingIncomingConnections(bool listenForConnections)
  89. {
  90. networkServer = networkHandler->createServerTCP(*this);
  91. port
  92. ? logNetwork->info("Port %d will be used", port)
  93. : logNetwork->info("Randomly assigned port will be used");
  94. // config port may be 0 => srvport will contain the OS-assigned port value
  95. if (listenForConnections)
  96. {
  97. auto srvport = networkServer->start(port);
  98. logNetwork->info("Listening for connections at port %d", srvport);
  99. return srvport;
  100. }
  101. else
  102. return 0;
  103. }
  104. void CVCMIServer::onNewConnection(const std::shared_ptr<INetworkConnection> & connection)
  105. {
  106. if(getState() == EServerState::LOBBY)
  107. {
  108. activeConnections.push_back(std::make_shared<GameConnection>(connection));
  109. activeConnections.back()->enterLobbyConnectionMode();
  110. }
  111. else
  112. {
  113. // TODO: reconnection support
  114. connection->close();
  115. }
  116. }
  117. void CVCMIServer::onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message)
  118. {
  119. std::shared_ptr<GameConnection> c = findConnection(connection);
  120. if (c == nullptr)
  121. throw std::out_of_range("Unknown connection received in CVCMIServer::findConnection");
  122. auto pack = c->retrievePack(message);
  123. CVCMIServerPackVisitor visitor(*this, this->gh, c);
  124. pack->visit(visitor);
  125. }
  126. void CVCMIServer::setState(EServerState value)
  127. {
  128. if (value == EServerState::SHUTDOWN && state == EServerState::SHUTDOWN)
  129. logGlobal->warn("Attempt to shutdown already shutdown server!");
  130. // do not attempt to restart dying server
  131. assert(state != EServerState::SHUTDOWN || state == value);
  132. if(state != EServerState::LOBBY && value == EServerState::LOBBY && discoveryListener)
  133. startDiscoveryListener();
  134. if(state == EServerState::LOBBY && value != EServerState::LOBBY && discoveryListener)
  135. stopDiscoveryListener();
  136. state = value;
  137. if (state == EServerState::SHUTDOWN)
  138. networkHandler->stop();
  139. }
  140. void CVCMIServer::startDiscoveryListener()
  141. {
  142. if(!discoveryListener)
  143. discoveryListener = getNetworkHandler().createServerDiscoveryListener(*this);
  144. discoveryListener->start();
  145. }
  146. void CVCMIServer::stopDiscoveryListener()
  147. {
  148. if(discoveryListener)
  149. {
  150. discoveryListener->stop();
  151. discoveryListener.reset();
  152. }
  153. }
  154. EServerState CVCMIServer::getState() const
  155. {
  156. return state;
  157. }
  158. bool CVCMIServer::isInLobby() const
  159. {
  160. return getState() == EServerState::LOBBY;
  161. }
  162. std::shared_ptr<GameConnection> CVCMIServer::findConnection(const std::shared_ptr<INetworkConnection> & netConnection)
  163. {
  164. for(const auto & gameConnection : activeConnections)
  165. {
  166. if (gameConnection->isMyConnection(netConnection))
  167. return gameConnection;
  168. }
  169. return nullptr;
  170. }
  171. bool CVCMIServer::wasStartedByClient() const
  172. {
  173. return runByClient;
  174. }
  175. void CVCMIServer::run()
  176. {
  177. networkHandler->run();
  178. }
  179. void CVCMIServer::onTimer()
  180. {
  181. // we might receive onTimer call after transitioning from GAMEPLAY to LOBBY state, e.g. on game restart
  182. if (getState() != EServerState::GAMEPLAY)
  183. return;
  184. static const auto serverUpdateInterval = std::chrono::milliseconds(100);
  185. auto timeNow = std::chrono::steady_clock::now();
  186. auto timePassedBefore = lastTimerUpdateTime - gameplayStartTime;
  187. auto timePassedNow = timeNow - gameplayStartTime;
  188. lastTimerUpdateTime = timeNow;
  189. auto msPassedBefore = std::chrono::duration_cast<std::chrono::milliseconds>(timePassedBefore);
  190. auto msPassedNow = std::chrono::duration_cast<std::chrono::milliseconds>(timePassedNow);
  191. auto msDelta = msPassedNow - msPassedBefore;
  192. if (msDelta.count())
  193. gh->tick(msDelta.count());
  194. networkHandler->createTimer(*this, serverUpdateInterval);
  195. }
  196. void CVCMIServer::prepareToRestart()
  197. {
  198. if(getState() != EServerState::GAMEPLAY)
  199. {
  200. assert(0);
  201. return;
  202. }
  203. setState(EServerState::LOBBY);
  204. if (si->campState)
  205. {
  206. assert(si->campState->currentScenario().has_value());
  207. campaignMap = si->campState->currentScenario().value_or(CampaignScenarioID(0));
  208. campaignBonus = si->campState->getBonusID(campaignMap).value_or(-1);
  209. }
  210. for(auto activeConnection : activeConnections)
  211. activeConnection->enterLobbyConnectionMode();
  212. gh = nullptr;
  213. }
  214. bool CVCMIServer::prepareToStartGame()
  215. {
  216. Load::ProgressAccumulator progressTracking;
  217. Load::Progress current(1);
  218. progressTracking.include(current);
  219. auto progressTrackingThread = std::thread([this, &progressTracking]()
  220. {
  221. setThreadName("progressTrackingThread");
  222. auto currentProgress = std::numeric_limits<Load::Type>::max();
  223. while(!progressTracking.finished())
  224. {
  225. if(progressTracking.get() != currentProgress)
  226. {
  227. //FIXME: UNGUARDED MULTITHREADED ACCESS!!!
  228. currentProgress = progressTracking.get();
  229. LobbyLoadProgress loadProgress;
  230. loadProgress.progress = currentProgress;
  231. announcePack(loadProgress);
  232. }
  233. std::this_thread::sleep_for(std::chrono::milliseconds(50));
  234. }
  235. //send final progress
  236. LobbyLoadProgress loadProgress;
  237. loadProgress.progress = std::numeric_limits<Load::Type>::max();
  238. announcePack(loadProgress);
  239. });
  240. auto newGH = std::make_shared<CGameHandler>(*this);
  241. bool started = false;
  242. try
  243. {
  244. switch(si->mode)
  245. {
  246. case EStartMode::CAMPAIGN:
  247. logNetwork->info("Preparing to start new campaign");
  248. si->startTime = std::time(nullptr);
  249. si->fileURI = mi->fileURI;
  250. si->campState->setCurrentMap(campaignMap);
  251. si->campState->setCurrentMapBonus(campaignBonus);
  252. newGH->init(si.get(), progressTracking); // may throw
  253. started = true;
  254. break;
  255. case EStartMode::NEW_GAME:
  256. logNetwork->info("Preparing to start new game");
  257. si->startTime = std::time(nullptr);
  258. si->fileURI = mi->fileURI;
  259. newGH->init(si.get(), progressTracking); // may throw
  260. started = true;
  261. break;
  262. case EStartMode::LOAD_GAME:
  263. logNetwork->info("Preparing to start loaded game");
  264. if(loadSavedGame(*newGH, *si))
  265. started = true;
  266. break;
  267. default:
  268. logNetwork->error("Wrong mode in StartInfo!");
  269. assert(0);
  270. break;
  271. }
  272. }
  273. catch(const ModIncompatibility & e)
  274. {
  275. logGlobal->error("Failed to launch game: %s", e.what());
  276. announceMessage(e.getFullErrorMsg());
  277. }
  278. catch(const IdentifierResolutionException & e)
  279. {
  280. logGlobal->error("Failed to launch game: %s", e.what());
  281. MetaString errorMsg;
  282. errorMsg.appendTextID("vcmi.server.errors.campOrMapFile.unknownEntity");
  283. errorMsg.replaceRawString(e.identifierName);
  284. announceMessage(errorMsg);
  285. }
  286. catch(const std::exception & e)
  287. {
  288. logGlobal->error("Failed to launch game: %s", e.what());
  289. auto str = MetaString::createFromTextID("vcmi.broadcast.failedLoadGame");
  290. str.appendRawString(":\n");
  291. str.appendRawString(e.what());
  292. announceMessage(str);
  293. }
  294. current.finish();
  295. progressTrackingThread.join();
  296. if (!started)
  297. return false;
  298. gh = std::move(newGH);
  299. if(lobbyProcessor)
  300. lobbyProcessor->sendGameStarted();
  301. return true;
  302. }
  303. void CVCMIServer::startGameImmediately()
  304. {
  305. for(auto activeConnection : activeConnections)
  306. activeConnection->setCallback(gh->gameInfo());
  307. for(auto activeConnection : activeConnections)
  308. {
  309. auto players = getAllClientPlayers(activeConnection->connectionID);
  310. std::stringstream sbuffer;
  311. sbuffer << "Connection " << static_cast<int>(activeConnection->connectionID) << " will handle " << players.size() << " player: ";
  312. for (PlayerColor color : players)
  313. sbuffer << color << " ";
  314. logGlobal->info(sbuffer.str());
  315. }
  316. gh->start(si->mode == EStartMode::LOAD_GAME);
  317. setState(EServerState::GAMEPLAY);
  318. lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now();
  319. onTimer();
  320. multiplayerWelcomeMessage();
  321. }
  322. void CVCMIServer::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
  323. {
  324. logNetwork->error("Network error receiving a pack. Connection has been closed");
  325. std::shared_ptr<GameConnection> c = findConnection(connection);
  326. // player may have already disconnected via clientDisconnected call
  327. if (c)
  328. {
  329. LobbyClientDisconnected lcd;
  330. lcd.clientId = c->connectionID;
  331. handleReceivedPack(c, lcd);
  332. }
  333. }
  334. void CVCMIServer::handleReceivedPack(std::shared_ptr<GameConnection> connection, CPackForLobby & pack)
  335. {
  336. ClientPermissionsCheckerNetPackVisitor checker(*this, connection);
  337. pack.visit(checker);
  338. if(checker.getResult())
  339. {
  340. ApplyOnServerNetPackVisitor applier(*this, connection);
  341. pack.visit(applier);
  342. if (applier.getResult())
  343. announcePack(pack);
  344. }
  345. }
  346. void CVCMIServer::announcePack(CPackForLobby & pack)
  347. {
  348. for(auto activeConnection : activeConnections)
  349. {
  350. // FIXME: we need to avoid sending something to client that not yet get answer for LobbyClientConnected
  351. // Until UUID set we only pass LobbyClientConnected to this client
  352. //if(c->uuid == uuid && !dynamic_cast<LobbyClientConnected *>(pack.get()))
  353. // continue;
  354. activeConnection->sendPack(pack);
  355. }
  356. ApplyOnServerAfterAnnounceNetPackVisitor applier(*this);
  357. pack.visit(applier);
  358. }
  359. void CVCMIServer::announceMessage(const MetaString & txt)
  360. {
  361. logNetwork->info("Show message: %s", txt.toString());
  362. LobbyShowMessage cm;
  363. cm.message = txt;
  364. announcePack(cm);
  365. }
  366. void CVCMIServer::announceMessage(const std::string & txt)
  367. {
  368. MetaString str;
  369. str.appendRawString(txt);
  370. announceMessage(str);
  371. }
  372. void CVCMIServer::announceTxt(const MetaString & txt, const std::string & playerName)
  373. {
  374. logNetwork->info("%s says: %s", playerName, txt.toString());
  375. LobbyChatMessage cm;
  376. cm.playerName = playerName;
  377. cm.message = txt;
  378. announcePack(cm);
  379. }
  380. void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName)
  381. {
  382. MetaString str;
  383. str.appendRawString(txt);
  384. announceTxt(str, playerName);
  385. }
  386. bool CVCMIServer::passHost(GameConnectionID toConnectionId)
  387. {
  388. for(auto activeConnection : activeConnections)
  389. {
  390. if(isClientHost(activeConnection->connectionID))
  391. continue;
  392. if(activeConnection->connectionID != toConnectionId)
  393. continue;
  394. hostClientId = activeConnection->connectionID;
  395. announceTxt(boost::str(boost::format("Pass host to connection %d") % static_cast<int>(toConnectionId)));
  396. return true;
  397. }
  398. return false;
  399. }
  400. void CVCMIServer::clientConnected(std::shared_ptr<GameConnection> c, std::vector<std::string> & names, const std::string & uuid, EStartMode mode)
  401. {
  402. assert(getState() == EServerState::LOBBY);
  403. c->connectionID = currentClientId;
  404. currentClientId = vstd::next(currentClientId, 1);
  405. c->uuid = uuid;
  406. if(hostClientId == GameConnectionID::INVALID)
  407. {
  408. hostClientId = c->connectionID;
  409. si->mode = mode;
  410. }
  411. logNetwork->info("Connection with client %d established. UUID: %s", static_cast<int>(c->connectionID), c->uuid);
  412. for(auto & name : names)
  413. {
  414. logNetwork->info("Client %d player: %s", static_cast<int>(c->connectionID), name);
  415. PlayerConnectionID id = currentPlayerId;
  416. currentPlayerId = vstd::next(currentPlayerId, 1);
  417. ClientPlayer cp;
  418. cp.connection = c->connectionID;
  419. cp.name = name;
  420. playerNames.try_emplace(id, cp);
  421. announceTxt(boost::str(boost::format("%s (pid %d cid %d) joins the game") % name % static_cast<int>(id) % static_cast<int>(c->connectionID)));
  422. //put new player in first slot with AI
  423. for(auto & elem : si->playerInfos)
  424. {
  425. if(elem.second.isControlledByAI() && !elem.second.compOnly)
  426. {
  427. setPlayerConnectedId(elem.second, id);
  428. break;
  429. }
  430. }
  431. }
  432. }
  433. void CVCMIServer::clientDisconnected(std::shared_ptr<GameConnection> connection)
  434. {
  435. assert(vstd::contains(activeConnections, connection));
  436. logGlobal->trace("Received disconnection request");
  437. vstd::erase(activeConnections, connection);
  438. if(activeConnections.empty() || hostClientId == connection->connectionID)
  439. {
  440. setState(EServerState::SHUTDOWN);
  441. return;
  442. }
  443. if(gh && getState() == EServerState::GAMEPLAY)
  444. {
  445. gh->handleClientDisconnection(connection->connectionID);
  446. }
  447. }
  448. void CVCMIServer::setPlayerConnectedId(PlayerSettings & pset, PlayerConnectionID player) const
  449. {
  450. if(vstd::contains(playerNames, player))
  451. pset.name = playerNames.find(player)->second.name;
  452. else
  453. pset.name = LIBRARY->generaltexth->allTexts[468]; //Computer
  454. logGlobal->debug("Player color %d will be controlled from connection %d", pset.color, static_cast<int>(player));
  455. pset.connectedPlayerIDs.clear();
  456. if(player != PlayerConnectionID::PLAYER_AI)
  457. pset.connectedPlayerIDs.insert(player);
  458. }
  459. void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpts)
  460. {
  461. mi = mapInfo;
  462. if(!mi)
  463. return;
  464. auto namesIt = playerNames.cbegin();
  465. si->playerInfos.clear();
  466. if(mi->scenarioOptionsOfSave)
  467. {
  468. si = CMemorySerializer::deepCopy(*mi->scenarioOptionsOfSave);
  469. si->mode = EStartMode::LOAD_GAME;
  470. if(si->campState)
  471. campaignMap = si->campState->currentScenario().value();
  472. for(auto & ps : si->playerInfos)
  473. {
  474. if(!ps.second.compOnly && ps.second.connectedPlayerIDs.size() && namesIt != playerNames.cend())
  475. {
  476. setPlayerConnectedId(ps.second, namesIt++->first);
  477. }
  478. else
  479. {
  480. setPlayerConnectedId(ps.second, PlayerConnectionID::PLAYER_AI);
  481. }
  482. }
  483. }
  484. else if(si->mode == EStartMode::NEW_GAME || si->mode == EStartMode::CAMPAIGN)
  485. {
  486. if(mi->campaign)
  487. return;
  488. for(int i = 0; i < mi->mapHeader->players.size(); i++)
  489. {
  490. const PlayerInfo & pinfo = mi->mapHeader->players[i];
  491. //neither computer nor human can play - no player
  492. if(!(pinfo.canHumanPlay || pinfo.canComputerPlay))
  493. continue;
  494. PlayerSettings & pset = si->playerInfos[PlayerColor(i)];
  495. pset.color = PlayerColor(i);
  496. if(pinfo.canHumanPlay && namesIt != playerNames.cend())
  497. {
  498. setPlayerConnectedId(pset, namesIt++->first);
  499. }
  500. else
  501. {
  502. setPlayerConnectedId(pset, PlayerConnectionID::PLAYER_AI);
  503. if(!pinfo.canHumanPlay)
  504. {
  505. pset.compOnly = true;
  506. }
  507. }
  508. pset.castle = pinfo.defaultCastle();
  509. pset.hero = pinfo.defaultHero();
  510. if(pset.hero != HeroTypeID::RANDOM && pinfo.hasCustomMainHero())
  511. {
  512. pset.hero = pinfo.mainCustomHeroId;
  513. pset.heroNameTextId = pinfo.mainCustomHeroNameTextId;
  514. pset.heroPortrait = pinfo.mainCustomHeroPortrait;
  515. }
  516. }
  517. if(mi->isRandomMap && mapGenOpts)
  518. si->mapGenOptions = std::shared_ptr<CMapGenOptions>(mapGenOpts);
  519. else
  520. si->mapGenOptions.reset();
  521. }
  522. if (lobbyProcessor)
  523. {
  524. std::string roomDescription;
  525. if (si->mapGenOptions)
  526. {
  527. if (si->mapGenOptions->getMapTemplate())
  528. roomDescription = si->mapGenOptions->getMapTemplate()->getName();
  529. // else - no template selected.
  530. // TODO: handle this somehow?
  531. }
  532. else
  533. roomDescription = mi->getNameTranslated();
  534. lobbyProcessor->sendChangeRoomDescription(roomDescription);
  535. }
  536. si->mapname = mi->fileURI;
  537. }
  538. void CVCMIServer::updateAndPropagateLobbyState()
  539. {
  540. // Update player settings for RMG
  541. // TODO: find appropriate location for this code
  542. if(si->mapGenOptions && si->mode == EStartMode::NEW_GAME)
  543. {
  544. for(const auto & psetPair : si->playerInfos)
  545. {
  546. const auto & pset = psetPair.second;
  547. si->mapGenOptions->setStartingTownForPlayer(pset.color, pset.castle);
  548. si->mapGenOptions->setStartingHeroForPlayer(pset.color, pset.hero);
  549. if(pset.isControlledByHuman())
  550. {
  551. si->mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::HUMAN);
  552. }
  553. else
  554. {
  555. si->mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::AI);
  556. }
  557. }
  558. }
  559. LobbyUpdateState lus;
  560. lus.state = *static_cast<LobbyState*>(this);
  561. announcePack(lus);
  562. }
  563. void CVCMIServer::setPlayer(PlayerColor clickedColor)
  564. {
  565. struct PlayerToRestore
  566. {
  567. PlayerColor color;
  568. PlayerConnectionID id;
  569. void reset() { id = PlayerConnectionID::INVALID; color = PlayerColor::CANNOT_DETERMINE; }
  570. PlayerToRestore(){ reset(); }
  571. };
  572. PlayerToRestore playerToRestore;
  573. PlayerSettings & clicked = si->playerInfos[clickedColor];
  574. //identify clicked player
  575. PlayerConnectionID clickedNameID = PlayerConnectionID::PLAYER_AI;
  576. if(clicked.isControlledByHuman())
  577. clickedNameID = *(clicked.connectedPlayerIDs.begin()); //if not AI - set appropriate ID
  578. if(clickedNameID > PlayerConnectionID::PLAYER_AI && playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place
  579. {
  580. PlayerSettings & restPos = si->playerInfos[playerToRestore.color];
  581. setPlayerConnectedId(restPos, playerToRestore.id);
  582. playerToRestore.reset();
  583. }
  584. PlayerConnectionID newPlayer; //which player will take clicked position
  585. //who will be put here?
  586. if(clickedNameID == PlayerConnectionID::PLAYER_AI) //AI player clicked -> if possible replace computer with unallocated player
  587. {
  588. newPlayer = getIdOfFirstUnallocatedPlayer();
  589. if(newPlayer == PlayerConnectionID::PLAYER_AI) //no "free" player -> get just first one
  590. newPlayer = playerNames.begin()->first;
  591. }
  592. else //human clicked -> take next
  593. {
  594. auto i = playerNames.find(clickedNameID); //clicked one
  595. i++; //player AFTER clicked one
  596. if(i != playerNames.end())
  597. newPlayer = i->first;
  598. else
  599. newPlayer = PlayerConnectionID::PLAYER_AI; //AI if we scrolled through all players
  600. }
  601. setPlayerConnectedId(clicked, newPlayer); //put player
  602. //if that player was somewhere else, we need to replace him with computer
  603. if(newPlayer != PlayerConnectionID::PLAYER_AI) //not AI
  604. {
  605. for(auto i = si->playerInfos.begin(); i != si->playerInfos.end(); i++)
  606. {
  607. PlayerConnectionID curNameID = *(i->second.connectedPlayerIDs.begin());
  608. if(i->first != clickedColor && curNameID == newPlayer)
  609. {
  610. assert(i->second.connectedPlayerIDs.size());
  611. playerToRestore.color = i->first;
  612. playerToRestore.id = newPlayer;
  613. setPlayerConnectedId(i->second, PlayerConnectionID::PLAYER_AI); //set computer
  614. break;
  615. }
  616. }
  617. }
  618. }
  619. void CVCMIServer::setPlayerName(PlayerColor color, const std::string & name)
  620. {
  621. if(color == PlayerColor::CANNOT_DETERMINE)
  622. return;
  623. PlayerSettings & player = si->playerInfos.at(color);
  624. if(!player.isControlledByHuman())
  625. return;
  626. if(player.connectedPlayerIDs.empty())
  627. return;
  628. PlayerConnectionID nameID = *(player.connectedPlayerIDs.begin()); //if not AI - set appropriate ID
  629. playerNames[nameID].name = name;
  630. setPlayerConnectedId(player, nameID);
  631. }
  632. void CVCMIServer::setPlayerHandicap(PlayerColor color, Handicap handicap)
  633. {
  634. if(color == PlayerColor::CANNOT_DETERMINE)
  635. return;
  636. si->playerInfos[color].handicap = handicap;
  637. int humanPlayer = 0;
  638. for (const auto & pi : si->playerInfos)
  639. if(pi.second.isControlledByHuman())
  640. humanPlayer++;
  641. if(humanPlayer < 2) // Singleplayer
  642. return;
  643. MetaString str;
  644. str.appendTextID("vcmi.lobby.handicap");
  645. str.appendRawString(" ");
  646. str.appendName(color);
  647. str.appendRawString(":");
  648. if(handicap.startBonus.empty() && handicap.percentIncome == 100 && handicap.percentGrowth == 100)
  649. {
  650. str.appendRawString(" ");
  651. str.appendTextID("core.genrltxt.523");
  652. announceTxt(str);
  653. return;
  654. }
  655. for(auto & res : LIBRARY->resourceTypeHandler->getAllObjects())
  656. if(handicap.startBonus[res] != 0)
  657. {
  658. str.appendRawString(" ");
  659. str.appendName(res);
  660. str.appendRawString(":");
  661. str.appendRawString(std::to_string(handicap.startBonus[res]));
  662. }
  663. if(handicap.percentIncome != 100)
  664. {
  665. str.appendRawString(" ");
  666. str.appendTextID("core.jktext.32");
  667. str.appendRawString(":");
  668. str.appendRawString(std::to_string(handicap.percentIncome) + "%");
  669. }
  670. if(handicap.percentGrowth != 100)
  671. {
  672. str.appendRawString(" ");
  673. str.appendTextID("core.genrltxt.194");
  674. str.appendRawString(":");
  675. str.appendRawString(std::to_string(handicap.percentGrowth) + "%");
  676. }
  677. announceTxt(str);
  678. }
  679. void CVCMIServer::optionNextCastle(PlayerColor player, int dir)
  680. {
  681. PlayerSettings & s = si->playerInfos[player];
  682. FactionID & cur = s.castle;
  683. auto & allowed = getPlayerInfo(player).allowedFactions;
  684. const bool allowRandomTown = getPlayerInfo(player).isFactionRandom;
  685. if(cur == FactionID::NONE) //no change
  686. return;
  687. if(cur == FactionID::RANDOM) //first/last available
  688. {
  689. if(dir > 0)
  690. cur = *allowed.begin(); //id of first town
  691. else
  692. cur = *allowed.rbegin(); //id of last town
  693. }
  694. else // next/previous available
  695. {
  696. if((cur == *allowed.begin() && dir < 0) || (cur == *allowed.rbegin() && dir > 0))
  697. {
  698. if(allowRandomTown)
  699. {
  700. cur = FactionID::RANDOM;
  701. }
  702. else
  703. {
  704. if(dir > 0)
  705. cur = *allowed.begin();
  706. else
  707. cur = *allowed.rbegin();
  708. }
  709. }
  710. else
  711. {
  712. assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range
  713. auto iter = allowed.find(cur);
  714. std::advance(iter, dir);
  715. cur = *iter;
  716. }
  717. }
  718. if(s.hero.isValid() && !getPlayerInfo(player).hasCustomMainHero()) // remove hero unless it set to fixed one in map editor
  719. {
  720. s.hero = HeroTypeID::RANDOM;
  721. }
  722. if(!cur.isValid() && s.bonus == PlayerStartingBonus::RESOURCE)
  723. s.bonus = PlayerStartingBonus::RANDOM;
  724. }
  725. void CVCMIServer::optionSetCastle(PlayerColor player, FactionID id)
  726. {
  727. PlayerSettings & s = si->playerInfos[player];
  728. FactionID & cur = s.castle;
  729. auto & allowed = getPlayerInfo(player).allowedFactions;
  730. if(cur == FactionID::NONE) //no change
  731. return;
  732. if(allowed.find(id) == allowed.end() && id != FactionID::RANDOM) // valid id
  733. return;
  734. cur = static_cast<FactionID>(id);
  735. if(s.hero.isValid() && !getPlayerInfo(player).hasCustomMainHero()) // remove hero unless it set to fixed one in map editor
  736. {
  737. s.hero = HeroTypeID::RANDOM;
  738. }
  739. if(!cur.isValid() && s.bonus == PlayerStartingBonus::RESOURCE)
  740. s.bonus = PlayerStartingBonus::RANDOM;
  741. }
  742. void CVCMIServer::setCampaignMap(CampaignScenarioID mapId)
  743. {
  744. campaignMap = mapId;
  745. si->difficulty = si->campState->scenario(mapId).difficulty;
  746. campaignBonus = -1;
  747. updateStartInfoOnMapChange(si->campState->getMapInfo(mapId));
  748. }
  749. void CVCMIServer::setCampaignBonus(int bonusId)
  750. {
  751. campaignBonus = bonusId;
  752. const CampaignScenario & scenario = si->campState->scenario(campaignMap);
  753. const CampaignBonus & bonus = scenario.travelOptions.bonusesToChoose.at(bonusId);
  754. if(bonus.getType() == CampaignBonusType::HERO || bonus.getType() == CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO)
  755. {
  756. PlayerColor startingPlayer = bonus.getType() == CampaignBonusType::HERO ?
  757. bonus.getValue<CampaignBonusStartingHero>().startingPlayer :
  758. bonus.getValue<CampaignBonusHeroesFromScenario>().startingPlayer;
  759. for(auto & elem : si->playerInfos)
  760. {
  761. if(elem.first == startingPlayer)
  762. setPlayerConnectedId(elem.second, PlayerConnectionID::FIRST_HUMAN);
  763. else
  764. setPlayerConnectedId(elem.second, PlayerConnectionID::PLAYER_AI);
  765. }
  766. }
  767. }
  768. void CVCMIServer::optionNextHero(PlayerColor player, int dir)
  769. {
  770. PlayerSettings & s = si->playerInfos[player];
  771. if(!s.castle.isValid() || s.hero == HeroTypeID::NONE)
  772. return;
  773. if(s.hero == HeroTypeID::RANDOM) // first/last available
  774. {
  775. if (dir > 0)
  776. s.hero = nextAllowedHero(player, HeroTypeID(-1), dir);
  777. else
  778. s.hero = nextAllowedHero(player, HeroTypeID(LIBRARY->heroh->size()), dir);
  779. }
  780. else
  781. {
  782. s.hero = nextAllowedHero(player, s.hero, dir);
  783. }
  784. }
  785. void CVCMIServer::optionSetHero(PlayerColor player, HeroTypeID id)
  786. {
  787. PlayerSettings & s = si->playerInfos[player];
  788. if(!s.castle.isValid() || s.hero == HeroTypeID::NONE)
  789. return;
  790. if(id == HeroTypeID::RANDOM)
  791. {
  792. s.hero = HeroTypeID::RANDOM;
  793. }
  794. if(canUseThisHero(player, id))
  795. s.hero = static_cast<HeroTypeID>(id);
  796. }
  797. HeroTypeID CVCMIServer::nextAllowedHero(PlayerColor player, HeroTypeID initial, int direction)
  798. {
  799. HeroTypeID first(initial.getNum() + direction);
  800. if(direction > 0)
  801. {
  802. for (auto i = first; i.getNum() < LIBRARY->heroh->size(); ++i)
  803. if(canUseThisHero(player, i))
  804. return i;
  805. }
  806. else
  807. {
  808. for (auto i = first; i.getNum() >= 0; --i)
  809. if(canUseThisHero(player, i))
  810. return i;
  811. }
  812. return HeroTypeID::RANDOM;
  813. }
  814. void CVCMIServer::optionNextBonus(PlayerColor player, int dir)
  815. {
  816. PlayerSettings & s = si->playerInfos[player];
  817. s.bonus = static_cast<PlayerStartingBonus>(static_cast<int>(s.bonus) + dir);
  818. if(s.hero == HeroTypeID::NONE &&
  819. getPlayerInfo(player).heroesNames.empty() &&
  820. s.bonus == PlayerStartingBonus::ARTIFACT) //no hero - can't be artifact
  821. {
  822. if(dir < 0)
  823. s.bonus = PlayerStartingBonus::RANDOM;
  824. else
  825. s.bonus = PlayerStartingBonus::GOLD;
  826. }
  827. if(s.bonus > PlayerStartingBonus::RESOURCE)
  828. s.bonus = PlayerStartingBonus::RANDOM;
  829. if(s.bonus < PlayerStartingBonus::RANDOM)
  830. s.bonus = PlayerStartingBonus::RESOURCE;
  831. if(s.castle == FactionID::RANDOM && s.bonus == PlayerStartingBonus::RESOURCE) //random castle - can't be resource
  832. {
  833. if(dir < 0)
  834. s.bonus = PlayerStartingBonus::GOLD;
  835. else
  836. s.bonus = PlayerStartingBonus::RANDOM;
  837. }
  838. }
  839. void CVCMIServer::optionSetBonus(PlayerColor player, PlayerStartingBonus id)
  840. {
  841. PlayerSettings & s = si->playerInfos[player];
  842. if(s.hero == HeroTypeID::NONE &&
  843. !getPlayerInfo(player).heroesNames.size() &&
  844. id == PlayerStartingBonus::ARTIFACT) //no hero - can't be artifact
  845. return;
  846. if(id > PlayerStartingBonus::RESOURCE)
  847. return;
  848. if(id < PlayerStartingBonus::RANDOM)
  849. return;
  850. if(s.castle == FactionID::RANDOM && id == PlayerStartingBonus::RESOURCE) //random castle - can't be resource
  851. return;
  852. s.bonus = id;
  853. }
  854. bool CVCMIServer::canUseThisHero(PlayerColor player, HeroTypeID ID)
  855. {
  856. if (!ID.hasValue())
  857. return false;
  858. if (ID.getNum() >= LIBRARY->heroh->size())
  859. return false;
  860. if (si->playerInfos[player].castle != ID.toHeroType()->heroClass->faction)
  861. return false;
  862. if (vstd::contains(getUsedHeroes(), ID))
  863. return false;
  864. if (!mi->mapHeader->allowedHeroes.count(ID))
  865. return false;
  866. for (const auto & disposedHero : mi->mapHeader->disposedHeroes)
  867. if (disposedHero.heroId == ID && !disposedHero.players.count(player))
  868. return false;
  869. return true;
  870. }
  871. std::vector<HeroTypeID> CVCMIServer::getUsedHeroes()
  872. {
  873. std::vector<HeroTypeID> heroIds;
  874. for(const auto & p : si->playerInfos)
  875. {
  876. const auto & heroes = getPlayerInfo(p.first).heroesNames;
  877. for(const auto & hero : heroes)
  878. if(hero.heroId.hasValue())
  879. heroIds.push_back(hero.heroId);
  880. if(p.second.hero != HeroTypeID::RANDOM)
  881. heroIds.push_back(p.second.hero);
  882. }
  883. return heroIds;
  884. }
  885. PlayerConnectionID CVCMIServer::getIdOfFirstUnallocatedPlayer() const
  886. {
  887. for(auto i = playerNames.cbegin(); i != playerNames.cend(); i++)
  888. {
  889. if(!si->getPlayersSettings(i->first))
  890. return i->first;
  891. }
  892. return PlayerConnectionID::PLAYER_AI;
  893. }
  894. void CVCMIServer::multiplayerWelcomeMessage()
  895. {
  896. int humanPlayer = 0;
  897. for (const auto & pi : si->playerInfos)
  898. if(pi.second.isControlledByHuman())
  899. humanPlayer++;
  900. if(humanPlayer < 2 || mi->mapHeader->battleOnly) // Singleplayer or Battle only mode
  901. return;
  902. gh->playerMessages->broadcastSystemMessage(MetaString::createFromTextID("vcmi.broadcast.command"));
  903. for (const auto & pi : si->playerInfos)
  904. if(!pi.second.handicap.startBonus.empty() || pi.second.handicap.percentIncome != 100 || pi.second.handicap.percentGrowth != 100)
  905. {
  906. MetaString str;
  907. str.appendTextID("vcmi.lobby.handicap");
  908. str.appendRawString(" ");
  909. str.appendName(pi.first);
  910. str.appendRawString(":");
  911. for(auto & res : LIBRARY->resourceTypeHandler->getAllObjects())
  912. if(pi.second.handicap.startBonus[res] != 0)
  913. {
  914. str.appendRawString(" ");
  915. str.appendName(res);
  916. str.appendRawString(":");
  917. str.appendRawString(std::to_string(pi.second.handicap.startBonus[res]));
  918. }
  919. if(pi.second.handicap.percentIncome != 100)
  920. {
  921. str.appendRawString(" ");
  922. str.appendTextID("core.jktext.32");
  923. str.appendRawString(":");
  924. str.appendRawString(std::to_string(pi.second.handicap.percentIncome) + "%");
  925. }
  926. if(pi.second.handicap.percentGrowth != 100)
  927. {
  928. str.appendRawString(" ");
  929. str.appendTextID("core.genrltxt.194");
  930. str.appendRawString(":");
  931. str.appendRawString(std::to_string(pi.second.handicap.percentGrowth) + "%");
  932. }
  933. gh->playerMessages->broadcastSystemMessage(str);
  934. }
  935. std::vector<std::string> optionIds;
  936. if(si->extraOptionsInfo.cheatsAllowed)
  937. optionIds.emplace_back("vcmi.optionsTab.cheatAllowed.hover");
  938. if(si->extraOptionsInfo.unlimitedReplay)
  939. optionIds.emplace_back("vcmi.optionsTab.unlimitedReplay.hover");
  940. if(!optionIds.size()) // No settings to publish
  941. return;
  942. MetaString str;
  943. str.appendTextID("vcmi.optionsTab.extraOptions.hover");
  944. str.appendRawString(": ");
  945. for(int i = 0; i < optionIds.size(); i++)
  946. {
  947. str.appendTextID(optionIds[i]);
  948. if(i < optionIds.size() - 1)
  949. str.appendRawString(", ");
  950. }
  951. gh->playerMessages->broadcastSystemMessage(str);
  952. }
  953. INetworkHandler & CVCMIServer::getNetworkHandler()
  954. {
  955. return *networkHandler;
  956. }
  957. INetworkServer & CVCMIServer::getNetworkServer()
  958. {
  959. return *networkServer;
  960. }
  961. bool CVCMIServer::loadSavedGame(CGameHandler & handler, const StartInfo & info)
  962. {
  963. try
  964. {
  965. handler.load(info);
  966. }
  967. catch(const ModIncompatibility & e)
  968. {
  969. logGlobal->error("Failed to load game: %s", e.what());
  970. announceMessage(e.getFullErrorMsg());
  971. return false;
  972. }
  973. catch(const IdentifierResolutionException & e)
  974. {
  975. logGlobal->error("Failed to load game: %s", e.what());
  976. MetaString errorMsg;
  977. errorMsg.appendTextID("vcmi.server.errors.saveFile.unknownEntity");
  978. errorMsg.replaceRawString(e.identifierName);
  979. announceMessage(errorMsg);
  980. return false;
  981. }
  982. catch(const std::exception & e)
  983. {
  984. logGlobal->error("Failed to load game: %s", e.what());
  985. auto str = MetaString::createFromTextID("vcmi.broadcast.failedLoadGame");
  986. str.appendRawString(": ");
  987. str.appendRawString(e.what());
  988. announceMessage(str);
  989. return false;
  990. }
  991. return true;
  992. }
  993. bool CVCMIServer::isPlayerHost(const PlayerColor & color) const
  994. {
  995. return LobbyInfo::isPlayerHost(color);
  996. }
  997. bool CVCMIServer::hasPlayerAt(PlayerColor player, GameConnectionID connectionID) const
  998. {
  999. return vstd::contains(getAllClientPlayers(connectionID), player);
  1000. }
  1001. bool CVCMIServer::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
  1002. {
  1003. for (const auto & c : activeConnections)
  1004. if (hasPlayerAt(left, c->connectionID) && hasPlayerAt(right, c->connectionID))
  1005. return true;
  1006. return false;
  1007. }
  1008. void CVCMIServer::applyPack(CPackForClient & pack)
  1009. {
  1010. logNetwork->trace("\tSending to all clients: %s", typeid(pack).name());
  1011. for (const auto & c : activeConnections)
  1012. c->sendPack(pack);
  1013. gh->gs->apply(pack);
  1014. logNetwork->trace("\tApplied on gameState(): %s", typeid(pack).name());
  1015. }
  1016. void CVCMIServer::sendPack(CPackForClient & pack, GameConnectionID connectionID)
  1017. {
  1018. for (const auto & c : activeConnections)
  1019. if (c->connectionID == connectionID)
  1020. c->sendPack(pack);
  1021. }