Client.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. /*
  2. * Client.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 "Global.h"
  12. #include "Client.h"
  13. #include "CGameInfo.h"
  14. #include "CPlayerInterface.h"
  15. #include "CServerHandler.h"
  16. #include "ClientNetPackVisitors.h"
  17. #include "adventureMap/AdventureMapInterface.h"
  18. #include "battle/BattleInterface.h"
  19. #include "gui/CGuiHandler.h"
  20. #include "gui/WindowHandler.h"
  21. #include "mapView/mapHandler.h"
  22. #include "../CCallback.h"
  23. #include "../lib/CConfigHandler.h"
  24. #include "../lib/gameState/CGameState.h"
  25. #include "../lib/CPlayerState.h"
  26. #include "../lib/CThreadHelper.h"
  27. #include "../lib/VCMIDirs.h"
  28. #include "../lib/UnlockGuard.h"
  29. #include "../lib/battle/BattleInfo.h"
  30. #include "../lib/serializer/BinaryDeserializer.h"
  31. #include "../lib/serializer/BinarySerializer.h"
  32. #include "../lib/serializer/Connection.h"
  33. #include "../lib/mapping/CMapService.h"
  34. #include "../lib/pathfinder/CGPathNode.h"
  35. #include "../lib/filesystem/Filesystem.h"
  36. #include "../lib/registerTypes/RegisterTypesClientPacks.h"
  37. #include <memory>
  38. #include <vcmi/events/EventBus.h>
  39. #if SCRIPTING_ENABLED
  40. #include "../lib/ScriptHandler.h"
  41. #endif
  42. #ifdef VCMI_ANDROID
  43. #include "lib/CAndroidVMHelper.h"
  44. #endif
  45. ThreadSafeVector<int> CClient::waitingRequest;
  46. template<typename T> class CApplyOnCL;
  47. class CBaseForCLApply
  48. {
  49. public:
  50. virtual void applyOnClAfter(CClient * cl, CPack * pack) const =0;
  51. virtual void applyOnClBefore(CClient * cl, CPack * pack) const =0;
  52. virtual ~CBaseForCLApply(){}
  53. template<typename U> static CBaseForCLApply * getApplier(const U * t = nullptr)
  54. {
  55. return new CApplyOnCL<U>();
  56. }
  57. };
  58. template<typename T> class CApplyOnCL : public CBaseForCLApply
  59. {
  60. public:
  61. void applyOnClAfter(CClient * cl, CPack * pack) const override
  62. {
  63. T * ptr = static_cast<T *>(pack);
  64. ApplyClientNetPackVisitor visitor(*cl, *cl->gameState());
  65. ptr->visit(visitor);
  66. }
  67. void applyOnClBefore(CClient * cl, CPack * pack) const override
  68. {
  69. T * ptr = static_cast<T *>(pack);
  70. ApplyFirstClientNetPackVisitor visitor(*cl, *cl->gameState());
  71. ptr->visit(visitor);
  72. }
  73. };
  74. template<> class CApplyOnCL<CPack>: public CBaseForCLApply
  75. {
  76. public:
  77. void applyOnClAfter(CClient * cl, CPack * pack) const override
  78. {
  79. logGlobal->error("Cannot apply on CL plain CPack!");
  80. assert(0);
  81. }
  82. void applyOnClBefore(CClient * cl, CPack * pack) const override
  83. {
  84. logGlobal->error("Cannot apply on CL plain CPack!");
  85. assert(0);
  86. }
  87. };
  88. CPlayerEnvironment::CPlayerEnvironment(PlayerColor player_, CClient * cl_, std::shared_ptr<CCallback> mainCallback_)
  89. : player(player_),
  90. cl(cl_),
  91. mainCallback(mainCallback_)
  92. {
  93. }
  94. const Services * CPlayerEnvironment::services() const
  95. {
  96. return VLC;
  97. }
  98. vstd::CLoggerBase * CPlayerEnvironment::logger() const
  99. {
  100. return logGlobal;
  101. }
  102. events::EventBus * CPlayerEnvironment::eventBus() const
  103. {
  104. return cl->eventBus();//always get actual value
  105. }
  106. const CPlayerEnvironment::BattleCb * CPlayerEnvironment::battle(const BattleID & battleID) const
  107. {
  108. return mainCallback->getBattle(battleID).get();
  109. }
  110. const CPlayerEnvironment::GameCb * CPlayerEnvironment::game() const
  111. {
  112. return mainCallback.get();
  113. }
  114. CClient::CClient()
  115. {
  116. waitingRequest.clear();
  117. applier = std::make_shared<CApplier<CBaseForCLApply>>();
  118. registerTypesClientPacks(*applier);
  119. gs = nullptr;
  120. }
  121. CClient::~CClient() = default;
  122. const Services * CClient::services() const
  123. {
  124. return VLC; //todo: this should be CGI
  125. }
  126. const CClient::BattleCb * CClient::battle(const BattleID & battleID) const
  127. {
  128. return nullptr; //todo?
  129. }
  130. const CClient::GameCb * CClient::game() const
  131. {
  132. return this;
  133. }
  134. vstd::CLoggerBase * CClient::logger() const
  135. {
  136. return logGlobal;
  137. }
  138. events::EventBus * CClient::eventBus() const
  139. {
  140. return clientEventBus.get();
  141. }
  142. void CClient::newGame(CGameState * initializedGameState)
  143. {
  144. CSH->th->update();
  145. CMapService mapService;
  146. assert(initializedGameState);
  147. gs = initializedGameState;
  148. gs->preInit(VLC, this);
  149. logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff());
  150. if(!initializedGameState)
  151. {
  152. Load::ProgressAccumulator progressTracking;
  153. gs->init(&mapService, CSH->si.get(), progressTracking, settings["general"]["saveRandomMaps"].Bool());
  154. }
  155. logNetwork->trace("Initializing GameState (together): %d ms", CSH->th->getDiff());
  156. initMapHandler();
  157. reinitScripting();
  158. initPlayerEnvironments();
  159. initPlayerInterfaces();
  160. }
  161. void CClient::loadGame(CGameState * initializedGameState)
  162. {
  163. logNetwork->info("Loading procedure started!");
  164. logNetwork->info("Game state was transferred over network, loading.");
  165. gs = initializedGameState;
  166. gs->preInit(VLC, this);
  167. gs->updateOnLoad(CSH->si.get());
  168. logNetwork->info("Game loaded, initialize interfaces.");
  169. initMapHandler();
  170. reinitScripting();
  171. initPlayerEnvironments();
  172. initPlayerInterfaces();
  173. }
  174. void CClient::save(const std::string & fname)
  175. {
  176. if(!gs->currentBattles.empty())
  177. {
  178. logNetwork->error("Game cannot be saved during battle!");
  179. return;
  180. }
  181. SaveGame save_game(fname);
  182. sendRequest(&save_game, PlayerColor::NEUTRAL);
  183. }
  184. void CClient::endNetwork()
  185. {
  186. if (CGI->mh)
  187. CGI->mh->endNetwork();
  188. if (CPlayerInterface::battleInt)
  189. CPlayerInterface::battleInt->endNetwork();
  190. for(auto & i : playerint)
  191. {
  192. auto interface = std::dynamic_pointer_cast<CPlayerInterface>(i.second);
  193. if (interface)
  194. interface->endNetwork();
  195. }
  196. }
  197. void CClient::endGame()
  198. {
  199. #if SCRIPTING_ENABLED
  200. clientScripts.reset();
  201. #endif
  202. //suggest interfaces to finish their stuff (AI should interrupt any bg working threads)
  203. for(auto & i : playerint)
  204. i.second->finish();
  205. GH.curInt = nullptr;
  206. {
  207. logNetwork->info("Ending current game!");
  208. removeGUI();
  209. CGI->mh.reset();
  210. vstd::clear_pointer(gs);
  211. logNetwork->info("Deleted mapHandler and gameState.");
  212. }
  213. CPlayerInterface::battleInt.reset();
  214. playerint.clear();
  215. battleints.clear();
  216. battleCallbacks.clear();
  217. playerEnvironments.clear();
  218. logNetwork->info("Deleted playerInts.");
  219. logNetwork->info("Client stopped.");
  220. }
  221. void CClient::initMapHandler()
  222. {
  223. // TODO: CMapHandler initialization can probably go somewhere else
  224. // It's can't be before initialization of interfaces
  225. // During loading CPlayerInterface from serialized state it's depend on MH
  226. if(!settings["session"]["headless"].Bool())
  227. {
  228. CGI->mh = std::make_shared<CMapHandler>(gs->map);
  229. logNetwork->trace("Creating mapHandler: %d ms", CSH->th->getDiff());
  230. }
  231. pathCache.clear();
  232. }
  233. void CClient::initPlayerEnvironments()
  234. {
  235. playerEnvironments.clear();
  236. auto allPlayers = CSH->getAllClientPlayers(CSH->logicConnection->connectionID);
  237. bool hasHumanPlayer = false;
  238. for(auto & color : allPlayers)
  239. {
  240. logNetwork->info("Preparing environment for player %s", color.toString());
  241. playerEnvironments[color] = std::make_shared<CPlayerEnvironment>(color, this, std::make_shared<CCallback>(gs, color, this));
  242. if(!hasHumanPlayer && gs->players[color].isHuman())
  243. hasHumanPlayer = true;
  244. }
  245. if(!hasHumanPlayer && !settings["session"]["headless"].Bool())
  246. {
  247. Settings session = settings.write["session"];
  248. session["spectate"].Bool() = true;
  249. session["spectate-skip-battle-result"].Bool() = true;
  250. session["spectate-ignore-hero"].Bool() = true;
  251. }
  252. if(settings["session"]["spectate"].Bool())
  253. {
  254. playerEnvironments[PlayerColor::SPECTATOR] = std::make_shared<CPlayerEnvironment>(PlayerColor::SPECTATOR, this, std::make_shared<CCallback>(gs, std::nullopt, this));
  255. }
  256. }
  257. void CClient::initPlayerInterfaces()
  258. {
  259. for(auto & playerInfo : gs->scenarioOps->playerInfos)
  260. {
  261. PlayerColor color = playerInfo.first;
  262. if(!vstd::contains(CSH->getAllClientPlayers(CSH->logicConnection->connectionID), color))
  263. continue;
  264. if(!vstd::contains(playerint, color))
  265. {
  266. logNetwork->info("Preparing interface for player %s", color.toString());
  267. if(playerInfo.second.isControlledByAI() || settings["session"]["onlyai"].Bool())
  268. {
  269. bool alliedToHuman = false;
  270. for(auto & allyInfo : gs->scenarioOps->playerInfos)
  271. if (gs->getPlayerTeam(allyInfo.first) == gs->getPlayerTeam(playerInfo.first) && allyInfo.second.isControlledByHuman())
  272. alliedToHuman = true;
  273. auto AiToGive = aiNameForPlayer(playerInfo.second, false, alliedToHuman);
  274. logNetwork->info("Player %s will be lead by %s", color.toString(), AiToGive);
  275. installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);
  276. }
  277. else
  278. {
  279. logNetwork->info("Player %s will be lead by human", color.toString());
  280. installNewPlayerInterface(std::make_shared<CPlayerInterface>(color), color);
  281. }
  282. }
  283. }
  284. if(settings["session"]["spectate"].Bool())
  285. {
  286. installNewPlayerInterface(std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR), PlayerColor::SPECTATOR, true);
  287. }
  288. if(CSH->getAllClientPlayers(CSH->logicConnection->connectionID).count(PlayerColor::NEUTRAL))
  289. installNewBattleInterface(CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()), PlayerColor::NEUTRAL);
  290. logNetwork->trace("Initialized player interfaces %d ms", CSH->th->getDiff());
  291. }
  292. std::string CClient::aiNameForPlayer(const PlayerSettings & ps, bool battleAI, bool alliedToHuman) const
  293. {
  294. if(ps.name.size())
  295. {
  296. const boost::filesystem::path aiPath = VCMIDirs::get().fullLibraryPath("AI", ps.name);
  297. if(boost::filesystem::exists(aiPath))
  298. return ps.name;
  299. }
  300. return aiNameForPlayer(battleAI, alliedToHuman);
  301. }
  302. std::string CClient::aiNameForPlayer(bool battleAI, bool alliedToHuman) const
  303. {
  304. const int sensibleAILimit = settings["session"]["oneGoodAI"].Bool() ? 1 : PlayerColor::PLAYER_LIMIT_I;
  305. std::string goodAdventureAI = alliedToHuman ? settings["server"]["alliedAI"].String() : settings["server"]["playerAI"].String();
  306. std::string goodBattleAI = settings["server"]["neutralAI"].String();
  307. std::string goodAI = battleAI ? goodBattleAI : goodAdventureAI;
  308. std::string badAI = battleAI ? "StupidAI" : "EmptyAI";
  309. //TODO what about human players
  310. if(battleints.size() >= sensibleAILimit)
  311. return badAI;
  312. return goodAI;
  313. }
  314. void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, PlayerColor color, bool battlecb)
  315. {
  316. playerint[color] = gameInterface;
  317. logGlobal->trace("\tInitializing the interface for player %s", color.toString());
  318. auto cb = std::make_shared<CCallback>(gs, color, this);
  319. battleCallbacks[color] = cb;
  320. gameInterface->initGameInterface(playerEnvironments.at(color), cb);
  321. installNewBattleInterface(gameInterface, color, battlecb);
  322. }
  323. void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, PlayerColor color, bool needCallback)
  324. {
  325. battleints[color] = battleInterface;
  326. if(needCallback)
  327. {
  328. logGlobal->trace("\tInitializing the battle interface for player %s", color.toString());
  329. auto cbc = std::make_shared<CBattleCallback>(color, this);
  330. battleCallbacks[color] = cbc;
  331. battleInterface->initBattleInterface(playerEnvironments.at(color), cbc);
  332. }
  333. }
  334. void CClient::handlePack(CPack * pack)
  335. {
  336. CBaseForCLApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier
  337. if(apply)
  338. {
  339. apply->applyOnClBefore(this, pack);
  340. logNetwork->trace("\tMade first apply on cl: %s", typeid(*pack).name());
  341. {
  342. boost::unique_lock lock(CGameState::mutex);
  343. gs->apply(pack);
  344. }
  345. logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
  346. apply->applyOnClAfter(this, pack);
  347. logNetwork->trace("\tMade second apply on cl: %s", typeid(*pack).name());
  348. }
  349. else
  350. {
  351. logNetwork->error("Message %s cannot be applied, cannot find applier!", typeid(*pack).name());
  352. }
  353. delete pack;
  354. }
  355. int CClient::sendRequest(const CPackForServer * request, PlayerColor player)
  356. {
  357. static ui32 requestCounter = 1;
  358. ui32 requestID = requestCounter++;
  359. logNetwork->trace("Sending a request \"%s\". It'll have an ID=%d.", typeid(*request).name(), requestID);
  360. waitingRequest.pushBack(requestID);
  361. request->requestID = requestID;
  362. request->player = player;
  363. CSH->logicConnection->sendPack(request);
  364. if(vstd::contains(playerint, player))
  365. playerint[player]->requestSent(request, requestID);
  366. return requestID;
  367. }
  368. void CClient::battleStarted(const BattleInfo * info)
  369. {
  370. std::shared_ptr<CPlayerInterface> att;
  371. std::shared_ptr<CPlayerInterface> def;
  372. auto & leftSide = info->sides[0];
  373. auto & rightSide = info->sides[1];
  374. for(auto & battleCb : battleCallbacks)
  375. {
  376. if(!battleCb.first.isValidPlayer() || battleCb.first == leftSide.color || battleCb.first == rightSide.color)
  377. battleCb.second->onBattleStarted(info);
  378. }
  379. //If quick combat is not, do not prepare interfaces for battleint
  380. auto callBattleStart = [&](PlayerColor color, ui8 side)
  381. {
  382. if(vstd::contains(battleints, color))
  383. battleints[color]->battleStart(info->battleID, leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side, info->replayAllowed);
  384. };
  385. callBattleStart(leftSide.color, 0);
  386. callBattleStart(rightSide.color, 1);
  387. callBattleStart(PlayerColor::UNFLAGGABLE, 1);
  388. if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
  389. callBattleStart(PlayerColor::SPECTATOR, 1);
  390. if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
  391. att = std::dynamic_pointer_cast<CPlayerInterface>(playerint[leftSide.color]);
  392. if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)
  393. def = std::dynamic_pointer_cast<CPlayerInterface>(playerint[rightSide.color]);
  394. //Remove player interfaces for auto battle (quickCombat option)
  395. if((att && att->isAutoFightOn) || (def && def->isAutoFightOn))
  396. {
  397. auto endTacticPhaseIfEligible = [info](const CPlayerInterface * interface)
  398. {
  399. if (interface->cb->getBattle(info->battleID)->battleGetTacticDist())
  400. {
  401. auto side = interface->cb->getBattle(info->battleID)->playerToSide(interface->playerID);
  402. if(interface->playerID == info->sides[info->tacticsSide].color)
  403. {
  404. auto action = BattleAction::makeEndOFTacticPhase(*side);
  405. interface->cb->battleMakeTacticAction(info->battleID, action);
  406. }
  407. }
  408. };
  409. if(att && att->isAutoFightOn)
  410. endTacticPhaseIfEligible(att.get());
  411. else // def && def->isAutoFightOn
  412. endTacticPhaseIfEligible(def.get());
  413. att.reset();
  414. def.reset();
  415. }
  416. if(!settings["session"]["headless"].Bool())
  417. {
  418. if(att || def)
  419. {
  420. CPlayerInterface::battleInt = std::make_shared<BattleInterface>(info->getBattleID(), leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def);
  421. }
  422. else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
  423. {
  424. //TODO: This certainly need improvement
  425. auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);
  426. spectratorInt->cb->onBattleStarted(info);
  427. CPlayerInterface::battleInt = std::make_shared<BattleInterface>(info->getBattleID(), leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def, spectratorInt);
  428. }
  429. }
  430. if(info->tacticDistance)
  431. {
  432. auto tacticianColor = info->sides[info->tacticsSide].color;
  433. if (vstd::contains(battleints, tacticianColor))
  434. battleints[tacticianColor]->yourTacticPhase(info->battleID, info->tacticDistance);
  435. }
  436. }
  437. void CClient::battleFinished(const BattleID & battleID)
  438. {
  439. for(auto & side : gs->getBattle(battleID)->sides)
  440. if(battleCallbacks.count(side.color))
  441. battleCallbacks[side.color]->onBattleEnded(battleID);
  442. if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
  443. battleCallbacks[PlayerColor::SPECTATOR]->onBattleEnded(battleID);
  444. }
  445. void CClient::startPlayerBattleAction(const BattleID & battleID, PlayerColor color)
  446. {
  447. if (battleints.count(color) == 0)
  448. return; // not our combat in MP
  449. auto battleint = battleints.at(color);
  450. if (!battleint->human)
  451. {
  452. // we want to avoid locking gamestate and causing UI to freeze while AI is making turn
  453. auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
  454. battleint->activeStack(battleID, gs->getBattle(battleID)->battleGetStackByID(gs->getBattle(battleID)->activeStack, false));
  455. }
  456. else
  457. {
  458. battleint->activeStack(battleID, gs->getBattle(battleID)->battleGetStackByID(gs->getBattle(battleID)->activeStack, false));
  459. }
  460. }
  461. void CClient::invalidatePaths()
  462. {
  463. boost::unique_lock<boost::mutex> pathLock(pathCacheMutex);
  464. pathCache.clear();
  465. }
  466. std::shared_ptr<const CPathsInfo> CClient::getPathsInfo(const CGHeroInstance * h)
  467. {
  468. assert(h);
  469. boost::unique_lock<boost::mutex> pathLock(pathCacheMutex);
  470. auto iter = pathCache.find(h);
  471. if(iter == std::end(pathCache))
  472. {
  473. auto paths = std::make_shared<CPathsInfo>(getMapSize(), h);
  474. gs->calculatePaths(h, *paths.get());
  475. pathCache[h] = paths;
  476. return paths;
  477. }
  478. else
  479. {
  480. return iter->second;
  481. }
  482. }
  483. #if SCRIPTING_ENABLED
  484. scripting::Pool * CClient::getGlobalContextPool() const
  485. {
  486. return clientScripts.get();
  487. }
  488. #endif
  489. void CClient::reinitScripting()
  490. {
  491. clientEventBus = std::make_unique<events::EventBus>();
  492. #if SCRIPTING_ENABLED
  493. clientScripts.reset(new scripting::PoolImpl(this));
  494. #endif
  495. }
  496. void CClient::removeGUI() const
  497. {
  498. // CClient::endGame
  499. GH.curInt = nullptr;
  500. GH.windows().clear();
  501. adventureInt.reset();
  502. logGlobal->info("Removed GUI.");
  503. LOCPLINT = nullptr;
  504. }
  505. #ifdef VCMI_ANDROID
  506. extern "C" JNIEXPORT jboolean JNICALL Java_eu_vcmi_vcmi_NativeMethods_tryToSaveTheGame(JNIEnv * env, jclass cls)
  507. {
  508. logGlobal->info("Received emergency save game request");
  509. if(!LOCPLINT || !LOCPLINT->cb)
  510. {
  511. return false;
  512. }
  513. LOCPLINT->cb->save("Saves/_Android_Autosave");
  514. return true;
  515. }
  516. #endif