BattleOnlyMode.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /*
  2. * BattleOnlyMode.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 "BattleOnlyMode.h"
  12. #include "../CServerHandler.h"
  13. #include "../GameEngine.h"
  14. #include "../GameInstance.h"
  15. #include "../render/IRenderHandler.h"
  16. #include "../render/CAnimation.h"
  17. #include "../render/Canvas.h"
  18. #include "../render/CanvasImage.h"
  19. #include "../gui/Shortcut.h"
  20. #include "../gui/WindowHandler.h"
  21. #include "../widgets/Buttons.h"
  22. #include "../widgets/GraphicalPrimitiveCanvas.h"
  23. #include "../widgets/TextControls.h"
  24. #include "../widgets/CTextInput.h"
  25. #include "../widgets/Images.h"
  26. #include "../windows/GUIClasses.h"
  27. #include "../windows/CHeroOverview.h"
  28. #include "../windows/CCreatureWindow.h"
  29. #include "../../lib/GameLibrary.h"
  30. #include "../../lib/gameState/CGameState.h"
  31. #include "../../lib/StartInfo.h"
  32. #include "../../lib/VCMIDirs.h"
  33. #include "../../lib/CRandomGenerator.h"
  34. #include "../../lib/callback/EditorCallback.h"
  35. #include "../../lib/entities/hero/CHero.h"
  36. #include "../../lib/entities/hero/CHeroClass.h"
  37. #include "../../lib/entities/hero/CHeroHandler.h"
  38. #include "../../lib/entities/faction/CTown.h"
  39. #include "../../lib/entities/faction/CTownHandler.h"
  40. #include "../../lib/mapObjects/CGHeroInstance.h"
  41. #include "../../lib/mapObjects/CGTownInstance.h"
  42. #include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
  43. #include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
  44. #include "../../lib/mapping/CMap.h"
  45. #include "../../lib/mapping/CMapInfo.h"
  46. #include "../../lib/mapping/CMapEditManager.h"
  47. #include "../../lib/mapping/CMapService.h"
  48. #include "../../lib/mapping/MapFormat.h"
  49. #include "../../lib/texts/CGeneralTextHandler.h"
  50. #include "../../lib/texts/TextOperations.h"
  51. #include "../../lib/filesystem/Filesystem.h"
  52. void BattleOnlyMode::openBattleWindow()
  53. {
  54. ENGINE->windows().createAndPushWindow<BattleOnlyModeWindow>();
  55. }
  56. BattleOnlyModeWindow::BattleOnlyModeWindow()
  57. : CWindowObject(BORDERED)
  58. , selectedTerrain(TerrainId::DIRT)
  59. , selectedTown(FactionID::NONE)
  60. {
  61. OBJECT_CONSTRUCTION;
  62. pos.w = 519;
  63. pos.h = 238;
  64. updateShadow();
  65. center();
  66. init();
  67. backgroundTexture = std::make_shared<FilledTexturePlayerColored>(Rect(0, 0, pos.w, pos.h));
  68. backgroundTexture->setPlayerColor(PlayerColor(1));
  69. buttonOk = std::make_shared<CButton>(Point(191, 203), AnimationPath::builtin("MuBchck"), CButton::tooltip(), [this](){ startBattle(); }, EShortcut::GLOBAL_ACCEPT);
  70. buttonAbort = std::make_shared<CButton>(Point(265, 203), AnimationPath::builtin("MuBcanc"), CButton::tooltip(), [this](){ close(); }, EShortcut::GLOBAL_CANCEL);
  71. title = std::make_shared<CLabel>(260, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode"));
  72. battlegroundSelector = std::make_shared<CButton>(Point(29, 174), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){
  73. std::vector<std::string> texts;
  74. std::vector<std::shared_ptr<IImage>> images;
  75. auto terrains = LIBRARY->terrainTypeHandler->objects;
  76. for (const auto & terrain : terrains)
  77. {
  78. texts.push_back(terrain->getNameTranslated());
  79. const auto & patterns = LIBRARY->terviewh->getTerrainViewPatterns(terrain->getId());
  80. TerrainViewPattern pattern;
  81. for(auto & p : patterns)
  82. if(p[0].id == "n1")
  83. pattern = p[0];
  84. auto image = ENGINE->renderHandler().loadImage(terrain->tilesFilename, pattern.mapping[0].first, 0, EImageBlitMode::OPAQUE);
  85. image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
  86. images.push_back(image);
  87. }
  88. auto factions = LIBRARY->townh->objects;
  89. factions.erase(std::remove_if(factions.begin(), factions.end(), [](const std::shared_ptr<CFaction>& n) {
  90. return !n->town;
  91. }), factions.end());
  92. for (const auto & faction : factions)
  93. {
  94. texts.push_back(faction->getNameTranslated());
  95. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("ITPA"), faction->town->clientInfo.icons[true][false] + 2, 0, EImageBlitMode::OPAQUE);
  96. image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
  97. images.push_back(image);
  98. }
  99. ENGINE->windows().createAndPushWindow<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattleground"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlegroundSelect"), [this, terrains, factions](int index){
  100. if(terrains.size() > index)
  101. {
  102. selectedTerrain = terrains[index]->getId();
  103. selectedTown = FactionID::NONE;
  104. }
  105. else
  106. {
  107. selectedTerrain = TerrainId::NONE;
  108. selectedTown = factions[index - terrains.size()]->getId();
  109. }
  110. setTerrainButtonText();
  111. setOkButtonEnabled();
  112. }, (selectedTerrain != TerrainId::NONE ? static_cast<int>(selectedTerrain) : static_cast<int>(selectedTown + terrains.size())), images, true, true);
  113. });
  114. buttonReset = std::make_shared<CButton>(Point(289, 174), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){
  115. selectedTerrain = TerrainId::DIRT;
  116. selectedTown = FactionID::NONE;
  117. setTerrainButtonText();
  118. setOkButtonEnabled();
  119. heroSelector1->selectedHero.reset();
  120. heroSelector2->selectedHero.reset();
  121. heroSelector1->selectedArmy->clearSlots();
  122. heroSelector2->selectedArmy->clearSlots();
  123. for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
  124. {
  125. heroSelector1->selectedArmyInput.at(i)->setText("0");
  126. heroSelector2->selectedArmyInput.at(i)->setText("0");
  127. }
  128. heroSelector1->setHeroIcon();
  129. heroSelector1->setCreatureIcons();
  130. heroSelector2->setHeroIcon();
  131. heroSelector2->setCreatureIcons();
  132. redraw();
  133. });
  134. buttonReset->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeReset"), EFonts::FONT_SMALL, Colors::WHITE);
  135. setTerrainButtonText();
  136. setOkButtonEnabled();
  137. heroSelector1 = std::make_shared<BattleOnlyModeHeroSelector>(*this, Point(0, 40));
  138. heroSelector2 = std::make_shared<BattleOnlyModeHeroSelector>(*this, Point(260, 40));
  139. }
  140. void BattleOnlyModeWindow::init()
  141. {
  142. map = std::make_unique<CMap>(nullptr);
  143. map->version = EMapFormat::VCMI;
  144. map->creationDateTime = std::time(nullptr);
  145. map->width = 10;
  146. map->height = 10;
  147. map->mapLevels = 1;
  148. map->battleOnly = true;
  149. cb = std::make_unique<EditorCallback>(map.get());
  150. }
  151. void BattleOnlyModeWindow::setTerrainButtonText()
  152. {
  153. battlegroundSelector->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattleground") + ": " + (selectedTerrain != TerrainId::NONE ? selectedTerrain.toEntity(LIBRARY)->getNameTranslated() : selectedTown.toEntity(LIBRARY)->getNameTranslated()), EFonts::FONT_SMALL, Colors::WHITE);
  154. }
  155. void BattleOnlyModeWindow::setOkButtonEnabled()
  156. {
  157. bool canStart = (selectedTerrain != TerrainId::NONE || selectedTown != FactionID::NONE);
  158. canStart &= (heroSelector1 && heroSelector1->selectedHero && ((heroSelector2 && heroSelector2->selectedHero) || (selectedTown != FactionID::NONE && heroSelector2->selectedArmy->stacksCount())));
  159. buttonOk->block(!canStart);
  160. }
  161. std::shared_ptr<IImage> drawBlackBox(Point size, std::string text)
  162. {
  163. auto image = ENGINE->renderHandler().createImage(size, CanvasScalingPolicy::AUTO);
  164. Canvas canvas = image->getCanvas();
  165. canvas.drawColor(Rect(0, 0, size.x, size.y), Colors::BLACK);
  166. canvas.drawText(Point(size.x / 2, size.y / 2), FONT_TINY, Colors::WHITE, ETextAlignment::CENTER, text);
  167. return image;
  168. }
  169. BattleOnlyModeHeroSelector::BattleOnlyModeHeroSelector(BattleOnlyModeWindow& parent, Point position)
  170. : parent(parent)
  171. , selectedArmy(std::make_shared<CCreatureSet>())
  172. {
  173. OBJECT_CONSTRUCTION;
  174. pos.x += position.x;
  175. pos.y += position.y;
  176. backgroundImage = std::make_shared<CPicture>(ImagePath::builtin("heroSlotsBlue"), Point(3, 4));
  177. for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
  178. {
  179. auto image = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), i, 0, 78 + i * 36, 26);
  180. primSkills.push_back(image);
  181. primSkillsBorder.push_back(std::make_shared<GraphicalPrimitiveCanvas>(Rect(78 + i * 36, 26, 32, 32)));
  182. primSkillsBorder.back()->addRectangle(Point(0, 0), Point(32, 32), ColorRGBA(44, 108, 255));
  183. primSkillsInput.push_back(std::make_shared<CTextInput>(Rect(78 + i * 36, 58, 32, 16), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
  184. primSkillsInput.back()->setFilterNumber(0, 100);
  185. primSkillsInput.back()->setText("0");
  186. }
  187. creatureImage.resize(GameConstants::ARMY_SIZE);
  188. for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
  189. {
  190. selectedArmyInput.push_back(std::make_shared<CTextInput>(Rect(5 + i * 36, 113, 32, 16), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
  191. selectedArmyInput.back()->setFilterNumber(0, 10000000, 3);
  192. selectedArmyInput.back()->setText("0");
  193. }
  194. setHeroIcon();
  195. setCreatureIcons();
  196. }
  197. void BattleOnlyModeHeroSelector::setHeroIcon()
  198. {
  199. OBJECT_CONSTRUCTION;
  200. if(!selectedHero)
  201. {
  202. heroImage = std::make_shared<CPicture>(drawBlackBox(Point(58, 64), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelect")), Point(6, 7));
  203. heroLabel = std::make_shared<CLabel>(160, 16, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, LIBRARY->generaltexth->translate("core.genrltxt.507"));
  204. }
  205. else
  206. {
  207. heroImage = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("PortraitsLarge"), EImageBlitMode::COLORKEY)->getImage(selectedHero->getHeroType()->imageIndex), Point(6, 7));
  208. heroLabel = std::make_shared<CLabel>(160, 16, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, selectedHero->getNameTranslated());
  209. }
  210. heroImage->addLClickCallback([this](){
  211. auto heroes = LIBRARY->heroh->objects;
  212. std::sort(heroes.begin(), heroes.end(), [](const std::shared_ptr<CHero>& a, const std::shared_ptr<CHero>& b) {
  213. if (a->heroClass->getId() == b->heroClass->getId())
  214. return a->getNameTranslated() < b->getNameTranslated();
  215. if (a->heroClass->faction == b->heroClass->faction)
  216. return a->heroClass->getId() < b->heroClass->getId();
  217. return a->heroClass->faction < b->heroClass->faction;
  218. });
  219. heroes.erase(std::remove_if(heroes.begin(), heroes.end(), [](const std::shared_ptr<CHero>& n) {
  220. return n->special;
  221. }), heroes.end());
  222. int selectedIndex = !selectedHero ? 0 : (1 + std::distance(heroes.begin(), std::find_if(heroes.begin(), heroes.end(), [this](const std::shared_ptr<CHero>& heroPtr) {
  223. return heroPtr.get() == selectedHero->getHeroType();
  224. })));
  225. std::vector<std::string> texts;
  226. std::vector<std::shared_ptr<IImage>> images;
  227. // Add "no hero" option
  228. texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
  229. images.push_back(nullptr);
  230. for (const auto & h : heroes)
  231. {
  232. texts.push_back(h->getNameTranslated());
  233. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("PortraitsSmall"), h->imageIndex, 0, EImageBlitMode::OPAQUE);
  234. image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
  235. images.push_back(image);
  236. }
  237. auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("object.core.hero.name"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeHeroSelect"), [this, heroes](int index){
  238. if(index == 0)
  239. {
  240. selectedHero.reset();
  241. setHeroIcon();
  242. return;
  243. }
  244. index--;
  245. auto hero = heroes[index];
  246. auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::HERO, hero->heroClass->getId());
  247. auto templates = factory->getTemplates();
  248. auto obj = std::dynamic_pointer_cast<CGHeroInstance>(factory->create(parent.cb.get(), templates.front()));
  249. obj->setHeroType(hero->getId());
  250. selectedHero = obj;
  251. setHeroIcon();
  252. parent.setOkButtonEnabled();
  253. }, selectedIndex, images, true, true);
  254. window->onPopup = [heroes](int index) {
  255. if(index == 0)
  256. return;
  257. index--;
  258. ENGINE->windows().createAndPushWindow<CHeroOverview>(heroes[index]->getId());
  259. };
  260. ENGINE->windows().pushWindow(window);
  261. });
  262. heroImage->addRClickCallback([this](){
  263. if(!selectedHero)
  264. return;
  265. ENGINE->windows().createAndPushWindow<CHeroOverview>(selectedHero->getHeroType()->getId());
  266. });
  267. }
  268. void BattleOnlyModeHeroSelector::setCreatureIcons()
  269. {
  270. OBJECT_CONSTRUCTION;
  271. for(int i = 0; i < creatureImage.size(); i++)
  272. {
  273. if(selectedArmy->slotEmpty(SlotID(i)))
  274. creatureImage[i] = std::make_shared<CPicture>(drawBlackBox(Point(32, 32), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelect")), Point(6 + i * 36, 78));
  275. else
  276. {
  277. auto creatureID = selectedArmy->Slots().at(SlotID(i))->getCreatureID();
  278. creatureImage[i] = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL"), EImageBlitMode::COLORKEY)->getImage(LIBRARY->creh->objects.at(creatureID)->getIconIndex()), Point(6 + i * 36, 78));
  279. }
  280. creatureImage[i]->addLClickCallback([this, i](){
  281. auto creatures = LIBRARY->creh->objects;
  282. std::sort(creatures.begin(), creatures.end(), [](const std::shared_ptr<CCreature>& a, const std::shared_ptr<CCreature>& b) {
  283. if (a->getLevel() == b->getLevel())
  284. return a->getNameSingularTranslated() < b->getNameSingularTranslated();
  285. if (a->getFactionID() == b->getFactionID())
  286. return a->getLevel() < b->getLevel();
  287. return a->getFactionID() < b->getFactionID();
  288. });
  289. creatures.erase(std::remove_if(creatures.begin(), creatures.end(), [](const std::shared_ptr<CCreature>& n) {
  290. return n->special;
  291. }), creatures.end());
  292. int selectedIndex = selectedArmy->slotEmpty(SlotID(i)) ? 0 : (1 + std::distance(creatures.begin(), std::find_if(creatures.begin(), creatures.end(), [this, i](const std::shared_ptr<CCreature>& creaturePtr) {
  293. return creaturePtr->getId() == selectedArmy->Slots().at(SlotID(i))->getId();
  294. })));
  295. std::vector<std::string> texts;
  296. std::vector<std::shared_ptr<IImage>> images;
  297. // Add "no creature" option
  298. texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
  299. images.push_back(nullptr);
  300. for (const auto & c : creatures)
  301. {
  302. texts.push_back(c->getNameSingularTranslated());
  303. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("CPRSMALL"), c->getIconIndex(), 0, EImageBlitMode::OPAQUE);
  304. image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
  305. images.push_back(image);
  306. }
  307. auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("core.genrltxt.42"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeCreatureSelect"), [this, creatures, i](int index){
  308. if(index == 0)
  309. {
  310. if(!selectedArmy->slotEmpty(SlotID(i)))
  311. selectedArmy->eraseStack(SlotID(i));
  312. setCreatureIcons();
  313. return;
  314. }
  315. index--;
  316. auto creature = creatures[index];
  317. selectedArmy->setCreature(SlotID(i), creature->getId(), 100);
  318. selectedArmyInput[SlotID(i)]->setText("100");
  319. setCreatureIcons();
  320. }, selectedIndex, images, true, true);
  321. window->onPopup = [creatures](int index) {
  322. if(index == 0)
  323. return;
  324. index--;
  325. ENGINE->windows().createAndPushWindow<CStackWindow>(creatures.at(index).get(), true);
  326. };
  327. ENGINE->windows().pushWindow(window);
  328. });
  329. creatureImage[i]->addRClickCallback([this, i](){
  330. if(selectedArmy->slotEmpty(SlotID(i)))
  331. return;
  332. ENGINE->windows().createAndPushWindow<CStackWindow>(LIBRARY->creh->objects.at(selectedArmy->Slots().at(SlotID(i))->getCreatureID()).get(), true);
  333. });
  334. }
  335. }
  336. void BattleOnlyModeWindow::startBattle()
  337. {
  338. auto rng = &CRandomGenerator::getDefault();
  339. map->initTerrain();
  340. map->getEditManager()->clearTerrain(rng);
  341. map->getEditManager()->getTerrainSelection().selectAll();
  342. map->getEditManager()->drawTerrain(selectedTerrain == TerrainId::NONE ? TerrainId::DIRT : selectedTerrain, 0, rng);
  343. map->players[0].canComputerPlay = true;
  344. map->players[0].canHumanPlay = true;
  345. map->players[1] = map->players[0];
  346. auto knownHeroes = LIBRARY->objtypeh->knownSubObjects(Obj::HERO);
  347. auto addHero = [&](auto & selector, PlayerColor color, const int3 & position)
  348. {
  349. selector->selectedHero->setOwner(color);
  350. selector->selectedHero->pos = position;
  351. for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
  352. selector->selectedHero->pushPrimSkill(PrimarySkill(i), std::stoi(selector->primSkillsInput[i]->getText()));
  353. selector->selectedHero->clearSlots();
  354. for(int slot = 0; slot < GameConstants::ARMY_SIZE; slot++)
  355. if(!selector->selectedArmy->slotEmpty(SlotID(slot)))
  356. {
  357. selector->selectedHero->putStack(SlotID(slot), selector->selectedArmy->detachStack(SlotID(slot)));
  358. selector->selectedHero->getArmy()->setStackCount(SlotID(slot), TextOperations::parseMetric<int>(selector->selectedArmyInput[slot]->getText()));
  359. }
  360. map->getEditManager()->insertObject(selector->selectedHero);
  361. };
  362. addHero(heroSelector1, PlayerColor(0), int3(5, 6, 0));
  363. if(selectedTown == FactionID::NONE)
  364. addHero(heroSelector2, PlayerColor(1), int3(5, 5, 0));
  365. else
  366. {
  367. auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::TOWN, selectedTown);
  368. auto templates = factory->getTemplates();
  369. auto obj = factory->create(cb.get(), templates.front());
  370. auto townObj = std::dynamic_pointer_cast<CGTownInstance>(obj);
  371. obj->setOwner(PlayerColor(1));
  372. obj->pos = int3(5, 5, 0);
  373. for (const auto & building : townObj->getTown()->getAllBuildings())
  374. townObj->addBuilding(building);
  375. if(!heroSelector2->selectedHero)
  376. {
  377. for(int slot = 0; slot < GameConstants::ARMY_SIZE; slot++)
  378. if(!heroSelector2->selectedArmy->slotEmpty(SlotID(slot)))
  379. {
  380. townObj->getArmy()->putStack(SlotID(slot), heroSelector2->selectedArmy->detachStack(SlotID(slot)));
  381. townObj->getArmy()->setStackCount(SlotID(slot), TextOperations::parseMetric<int>(heroSelector2->selectedArmyInput[slot]->getText()));
  382. }
  383. }
  384. else
  385. addHero(heroSelector2, PlayerColor(1), int3(5, 5, 0));
  386. map->getEditManager()->insertObject(townObj);
  387. }
  388. auto path = VCMIDirs::get().userDataPath() / "Maps";
  389. boost::filesystem::create_directories(path);
  390. const std::string fileName = "BattleOnlyMode.vmap";
  391. const auto fullPath = path / fileName;
  392. CMapService mapService;
  393. mapService.saveMap(map, fullPath);
  394. CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount) { return true; });
  395. auto mapInfo = std::make_shared<CMapInfo>();
  396. mapInfo->mapInit("Maps/BattleOnlyMode");
  397. GAME->server().setMapInfo(mapInfo);
  398. ExtraOptionsInfo extraOptions;
  399. extraOptions.unlimitedReplay = true;
  400. GAME->server().setExtraOptionsInfo(extraOptions);
  401. GAME->server().sendStartGame();
  402. }