BattleOnlyModeTab.cpp 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. /*
  2. * BattleOnlyModeTab.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 "BattleOnlyModeTab.h"
  12. #include "CLobbyScreen.h"
  13. #include "../CServerHandler.h"
  14. #include "../GameEngine.h"
  15. #include "../GameInstance.h"
  16. #include "../render/IRenderHandler.h"
  17. #include "../render/CAnimation.h"
  18. #include "../render/Canvas.h"
  19. #include "../render/CanvasImage.h"
  20. #include "../render/IFont.h"
  21. #include "../gui/Shortcut.h"
  22. #include "../gui/WindowHandler.h"
  23. #include "../widgets/Buttons.h"
  24. #include "../widgets/GraphicalPrimitiveCanvas.h"
  25. #include "../widgets/TextControls.h"
  26. #include "../widgets/CTextInput.h"
  27. #include "../widgets/Images.h"
  28. #include "../widgets/CComponent.h"
  29. #include "../windows/GUIClasses.h"
  30. #include "../windows/CHeroOverview.h"
  31. #include "../windows/CCreatureWindow.h"
  32. #include "../windows/InfoWindows.h"
  33. #include "../../lib/CConfigHandler.h"
  34. #include "../../lib/GameLibrary.h"
  35. #include "../../lib/gameState/CGameState.h"
  36. #include "../../lib/networkPacks/PacksForLobby.h"
  37. #include "../../lib/StartInfo.h"
  38. #include "../../lib/VCMIDirs.h"
  39. #include "../../lib/CRandomGenerator.h"
  40. #include "../../lib/CSkillHandler.h"
  41. #include "../../lib/callback/EditorCallback.h"
  42. #include "../../lib/entities/hero/CHero.h"
  43. #include "../../lib/entities/hero/CHeroClass.h"
  44. #include "../../lib/entities/hero/CHeroHandler.h"
  45. #include "../../lib/entities/faction/CTown.h"
  46. #include "../../lib/entities/faction/CTownHandler.h"
  47. #include "../../lib/entities/artifact/CArtifact.h"
  48. #include "../../lib/entities/artifact/CArtHandler.h"
  49. #include "../../lib/mapObjects/CGHeroInstance.h"
  50. #include "../../lib/mapObjects/CGTownInstance.h"
  51. #include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
  52. #include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
  53. #include "../../lib/mapping/CMap.h"
  54. #include "../../lib/mapping/CMapInfo.h"
  55. #include "../../lib/mapping/CMapEditManager.h"
  56. #include "../../lib/mapping/CMapService.h"
  57. #include "../../lib/mapping/MapFormat.h"
  58. #include "../../lib/spells/CSpellHandler.h"
  59. #include "../../lib/spells/SpellSchoolHandler.h"
  60. #include "../../lib/texts/CGeneralTextHandler.h"
  61. #include "../../lib/texts/MetaString.h"
  62. #include "../../lib/texts/TextOperations.h"
  63. #include "../../lib/filesystem/Filesystem.h"
  64. #include "../../lib/serializer/JsonSerializer.h"
  65. #include "../../lib/serializer/JsonDeserializer.h"
  66. BattleOnlyModeTab::BattleOnlyModeTab()
  67. : startInfo(std::make_shared<BattleOnlyModeStartInfo>())
  68. , disabledColor(GAME->server().isHost() ? Colors::WHITE : Colors::ORANGE)
  69. , boxColor(ColorRGBA(128, 128, 128))
  70. , disabledBoxColor(GAME->server().isHost() ? boxColor : ColorRGBA(116, 92, 16))
  71. {
  72. OBJECT_CONSTRUCTION;
  73. try
  74. {
  75. JsonNode node = persistentStorage["battleModeSettings"];
  76. if(!node.isNull())
  77. {
  78. node.setModScope(ModScope::scopeGame());
  79. JsonDeserializer handler(nullptr, node);
  80. startInfo->serializeJson(handler);
  81. }
  82. }
  83. catch(std::exception & e)
  84. {
  85. logGlobal->error("Error loading saved battleModeSettings, received exception: %s", e.what());
  86. }
  87. init();
  88. backgroundImage = std::make_shared<CPicture>(ImagePath::builtin("AdventureOptionsBackgroundClear"), 0, 6);
  89. title = std::make_shared<CLabel>(220, 35, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyMode"));
  90. subTitle = std::make_shared<CMultiLineLabel>(Rect(55, 40, 333, 40), FONT_SMALL, ETextAlignment::BOTTOMCENTER, Colors::WHITE, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSubTitle"));
  91. battlefieldSelector = std::make_shared<CButton>(Point(57, 552), AnimationPath::builtin("GSPButtonClear"), CButton::tooltip(), [this](){ selectTerrain(); });
  92. battlefieldSelector->block(GAME->server().isGuest());
  93. buttonReset = std::make_shared<CButton>(Point(259, 552), AnimationPath::builtin("GSPBUT2"), CButton::tooltip(), [this](){ reset(); });
  94. buttonReset->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeReset"), EFonts::FONT_SMALL, Colors::WHITE);
  95. heroSelector1 = std::make_shared<BattleOnlyModeHeroSelector>(0, *this, Point(55, 90));
  96. heroSelector2 = std::make_shared<BattleOnlyModeHeroSelector>(1, *this, Point(55, 320));
  97. heroSelector1->setInputEnabled(GAME->server().isHost());
  98. onChange();
  99. }
  100. void BattleOnlyModeTab::reset()
  101. {
  102. if(GAME->server().isHost())
  103. {
  104. startInfo->selectedTerrain = TerrainId::DIRT;
  105. startInfo->selectedTown = FactionID::ANY;
  106. startInfo->selectedHero[0] = HeroTypeID::NONE;
  107. startInfo->selectedArmy[0].fill(CStackBasicDescriptor(CreatureID::NONE, 1));
  108. startInfo->secSkillLevel[0].fill(std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE));
  109. startInfo->artifacts[0].clear();
  110. startInfo->spellBook[0] = true;
  111. startInfo->warMachines[0] = false;
  112. startInfo->spells[0].clear();
  113. for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
  114. heroSelector1->selectedArmyInput.at(i)->disable();
  115. for(size_t i=0; i<8; i++)
  116. heroSelector1->selectedSecSkillInput.at(i)->disable();
  117. }
  118. startInfo->selectedHero[1] = HeroTypeID::NONE;
  119. startInfo->selectedArmy[1].fill(CStackBasicDescriptor(CreatureID::NONE, 1));
  120. startInfo->secSkillLevel[1].fill(std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE));
  121. startInfo->artifacts[1].clear();
  122. startInfo->spellBook[1] = true;
  123. startInfo->warMachines[1] = false;
  124. startInfo->spells[1].clear();
  125. for(size_t i=0; i<8; i++)
  126. heroSelector2->selectedSecSkillInput.at(i)->disable();
  127. onChange();
  128. }
  129. void BattleOnlyModeTab::selectTerrain()
  130. {
  131. std::vector<std::string> texts;
  132. std::vector<std::shared_ptr<IImage>> images;
  133. std::vector<std::shared_ptr<TerrainType>> terrains;
  134. std::copy_if(LIBRARY->terrainTypeHandler->objects.begin(), LIBRARY->terrainTypeHandler->objects.end(), std::back_inserter(terrains), [](auto terrain) { return terrain->isPassable(); });
  135. for (const auto & terrain : terrains)
  136. {
  137. texts.push_back(terrain->getNameTranslated());
  138. const auto & patterns = LIBRARY->terviewh->getTerrainViewPatterns(terrain->getId());
  139. TerrainViewPattern pattern;
  140. for(auto & p : patterns)
  141. if(p[0].id == "n1")
  142. pattern = p[0];
  143. auto image = ENGINE->renderHandler().loadImage(terrain->tilesFilename, pattern.mapping[0].first, 0, EImageBlitMode::OPAQUE);
  144. image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
  145. images.push_back(image);
  146. }
  147. auto factions = LIBRARY->townh->getDefaultAllowed();
  148. for (const auto & faction : factions)
  149. {
  150. texts.push_back(faction.toFaction()->getNameTranslated());
  151. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("ITPA"), faction.toFaction()->town->clientInfo.icons[true][false] + 2, 0, EImageBlitMode::OPAQUE);
  152. image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
  153. images.push_back(image);
  154. }
  155. ENGINE->windows().createAndPushWindow<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefield"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefieldSelect"), [this, terrains, factions](int index){
  156. if(terrains.size() > index)
  157. {
  158. startInfo->selectedTerrain = terrains[index]->getId();
  159. startInfo->selectedTown = FactionID::ANY;
  160. }
  161. else
  162. {
  163. startInfo->selectedTerrain = TerrainId::NONE;
  164. auto it = std::next(factions.begin(), index - terrains.size());
  165. if (it != factions.end())
  166. startInfo->selectedTown = *it;
  167. }
  168. onChange();
  169. }, (startInfo->selectedTerrain != TerrainId::NONE ? static_cast<int>(startInfo->selectedTerrain) : static_cast<int>(startInfo->selectedTown + terrains.size())), images, true, true);
  170. }
  171. void BattleOnlyModeTab::init()
  172. {
  173. map = std::make_unique<CMap>(nullptr);
  174. map->version = EMapFormat::VCMI;
  175. map->creationDateTime = std::time(nullptr);
  176. map->width = 10;
  177. map->height = 10;
  178. map->mapLevels = 1;
  179. map->battleOnly = true;
  180. map->name = MetaString::createFromTextID("vcmi.lobby.battleOnlyMode");
  181. cb = std::make_unique<EditorCallback>(map.get());
  182. map->cb = cb.get();
  183. }
  184. void BattleOnlyModeTab::onChange()
  185. {
  186. GAME->server().setBattleOnlyModeStartInfo(startInfo);
  187. }
  188. void BattleOnlyModeTab::update()
  189. {
  190. setTerrainButtonText();
  191. setStartButtonEnabled();
  192. heroSelector1->setHeroIcon();
  193. heroSelector1->setCreatureIcons();
  194. heroSelector1->setSecSkillIcons();
  195. heroSelector1->setArtifactIcons();
  196. heroSelector1->spellBook->setSelectedSilent(startInfo->spellBook[0]);
  197. heroSelector1->warMachines->setSelectedSilent(startInfo->warMachines[0]);
  198. heroSelector2->setHeroIcon();
  199. heroSelector2->setCreatureIcons();
  200. heroSelector2->setSecSkillIcons();
  201. heroSelector2->setArtifactIcons();
  202. heroSelector2->spellBook->setSelectedSilent(startInfo->spellBook[1]);
  203. heroSelector2->warMachines->setSelectedSilent(startInfo->warMachines[1]);
  204. redraw();
  205. JsonNode node;
  206. JsonSerializer handler(nullptr, node);
  207. startInfo->serializeJson(handler);
  208. Settings storage = persistentStorage.write["battleModeSettings"];
  209. storage->Struct() = node.Struct();
  210. }
  211. void BattleOnlyModeTab::applyStartInfo(std::shared_ptr<BattleOnlyModeStartInfo> si)
  212. {
  213. startInfo = si;
  214. update();
  215. }
  216. void BattleOnlyModeTab::setTerrainButtonText()
  217. {
  218. battlefieldSelector->setTextOverlay(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeBattlefield") + ": " + (startInfo->selectedTerrain != TerrainId::NONE ? startInfo->selectedTerrain.toEntity(LIBRARY)->getNameTranslated() : startInfo->selectedTown.toEntity(LIBRARY)->getNameTranslated()), EFonts::FONT_SMALL, disabledColor);
  219. }
  220. void BattleOnlyModeTab::setStartButtonEnabled()
  221. {
  222. bool army2Empty = std::all_of(startInfo->selectedArmy[1].begin(), startInfo->selectedArmy[1].end(), [](const auto x) { return x.getId() == CreatureID::NONE; });
  223. bool canStart = (startInfo->selectedTerrain != TerrainId::NONE || startInfo->selectedTown != FactionID::ANY);
  224. canStart &= (startInfo->selectedHero[0] != HeroTypeID::NONE && ((startInfo->selectedHero[1] != HeroTypeID::NONE) || (startInfo->selectedTown != FactionID::ANY && !army2Empty)));
  225. (static_cast<CLobbyScreen *>(parent))->buttonStart->block(!canStart || GAME->server().isGuest());
  226. }
  227. std::shared_ptr<IImage> drawBlackBox(Point size, std::string text, ColorRGBA color)
  228. {
  229. auto image = ENGINE->renderHandler().createImage(size, CanvasScalingPolicy::AUTO);
  230. Canvas canvas = image->getCanvas();
  231. canvas.drawColor(Rect(0, 0, size.x, size.y), Colors::BLACK);
  232. std::vector<std::string> lines;
  233. boost::split(lines, text, boost::is_any_of("\n"));
  234. int lineH = ENGINE->renderHandler().loadFont(FONT_TINY)->getLineHeight();
  235. int totalH = lines.size() * lineH;
  236. int startY = (size.y - totalH) / 2 + lineH / 2;
  237. for (size_t i = 0; i < lines.size(); ++i)
  238. canvas.drawText(Point(size.x / 2, startY + i * lineH), FONT_TINY, color, ETextAlignment::CENTER, lines[i]);
  239. return image;
  240. }
  241. BattleOnlyModeHeroSelector::BattleOnlyModeHeroSelector(int id, BattleOnlyModeTab& p, Point position)
  242. : parent(p)
  243. , id(id)
  244. {
  245. OBJECT_CONSTRUCTION;
  246. pos.x += position.x;
  247. pos.y += position.y;
  248. backgroundImage = std::make_shared<CPicture>(ImagePath::builtin("heroSlotsBlue"), Point(3, 4));
  249. for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
  250. {
  251. auto image = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), i, 0, 78 + i * 36, 26);
  252. primSkills.push_back(image);
  253. primSkillsBorder.push_back(std::make_shared<GraphicalPrimitiveCanvas>(Rect(78 + i * 36, 26, 32, 32)));
  254. primSkillsBorder.back()->addRectangle(Point(0, 0), Point(32, 32), ColorRGBA(44, 108, 255));
  255. primSkillsInput.push_back(std::make_shared<CTextInput>(Rect(78 + i * 36, 58, 32, 16), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
  256. primSkillsInput.back()->setColor(id == 1 ? Colors::WHITE : parent.disabledColor);
  257. primSkillsInput.back()->setFilterNumber(0, 100);
  258. primSkillsInput.back()->setText("0");
  259. primSkillsInput.back()->setCallback([this, i, id](const std::string & text){
  260. parent.startInfo->primSkillLevel[id][i] = std::stoi(primSkillsInput[i]->getText());
  261. parent.onChange();
  262. });
  263. }
  264. creatureImage.resize(GameConstants::ARMY_SIZE);
  265. for(size_t i=0; i<GameConstants::ARMY_SIZE; i++)
  266. {
  267. selectedArmyInput.push_back(std::make_shared<CTextInput>(Rect(5 + i * 36, 113, 32, 16), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
  268. selectedArmyInput.back()->setColor(id == 1 ? Colors::WHITE : parent.disabledColor);
  269. selectedArmyInput.back()->setFilterNumber(0, 10000000, 3);
  270. selectedArmyInput.back()->setText("1");
  271. selectedArmyInput.back()->setCallback([this, i, id](const std::string & text){
  272. if(parent.startInfo->selectedArmy[id][i].getId() != CreatureID::NONE)
  273. {
  274. parent.startInfo->selectedArmy[id][i].setCount(TextOperations::parseMetric<int>(text));
  275. parent.onChange();
  276. selectedArmyInput[i]->enable();
  277. }
  278. else
  279. selectedArmyInput[i]->disable();
  280. });
  281. }
  282. for(size_t i=0; i<8; i++)
  283. {
  284. bool isLeft = (i % 2 == 0);
  285. int line = (i / 2);
  286. Point textPos(261 + (isLeft ? 0 : 36), 41 + line * 54);
  287. selectedSecSkillInput.push_back(std::make_shared<CTextInput>(Rect(textPos, Point(32, 16)), EFonts::FONT_SMALL, ETextAlignment::CENTER, false));
  288. selectedSecSkillInput.back()->setColor(id == 1 ? Colors::WHITE : parent.disabledColor);
  289. selectedSecSkillInput.back()->setFilterNumber(0, 3);
  290. selectedSecSkillInput.back()->setText("3");
  291. selectedSecSkillInput.back()->setCallback([this, i, id](const std::string & text){
  292. if(parent.startInfo->secSkillLevel[id][i].second != MasteryLevel::NONE)
  293. {
  294. parent.startInfo->secSkillLevel[id][i].second = static_cast<MasteryLevel::Type>(std::stoi(text));
  295. parent.onChange();
  296. selectedSecSkillInput[i]->enable();
  297. }
  298. else
  299. selectedSecSkillInput[i]->disable();
  300. });
  301. }
  302. secSkillImage.resize(8);
  303. artifactImage.resize(14);
  304. auto tmpIcon = ENGINE->renderHandler().loadImage(AnimationPath::builtin("Artifact"), ArtifactID(ArtifactID::SPELLBOOK).toArtifact()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
  305. tmpIcon->scaleTo(Point(16, 16), EScalingAlgorithm::NEAREST);
  306. addIcon.push_back(std::make_shared<CPicture>(tmpIcon, Point(220, 32)));
  307. addIcon.back()->addLClickCallback([this](){ manageSpells(); });
  308. addIcon.back()->addRClickCallback([this, id](){
  309. std::vector<std::shared_ptr<CComponent>> comps;
  310. for(auto & spell : parent.startInfo->spells[id])
  311. comps.push_back(std::make_shared<CComponent>(ComponentType::SPELL, spell, std::nullopt, CComponent::ESize::large));
  312. CRClickPopup::createAndPush(LIBRARY->generaltexth->translate("artifact.core.spellBook.name"), comps);
  313. });
  314. spellBook = std::make_shared<CToggleButton>(Point(235, 31), AnimationPath::builtin("lobby/checkboxSmall"), CButton::tooltip(), [this, id](bool enabled){
  315. parent.startInfo->spellBook[id] = enabled;
  316. parent.onChange();
  317. redraw();
  318. });
  319. spellBook->setSelectedSilent(parent.startInfo->spellBook[id]);
  320. tmpIcon = ENGINE->renderHandler().loadImage(AnimationPath::builtin("Artifact"), ArtifactID(ArtifactID::BALLISTA).toArtifact()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
  321. tmpIcon->scaleTo(Point(16, 16), EScalingAlgorithm::NEAREST);
  322. addIcon.push_back(std::make_shared<CPicture>(tmpIcon, Point(220, 56)));
  323. warMachines = std::make_shared<CToggleButton>(Point(235, 55), AnimationPath::builtin("lobby/checkboxSmall"), CButton::tooltip(), [this, id](bool enabled){
  324. parent.startInfo->warMachines[id] = enabled;
  325. parent.onChange();
  326. redraw();
  327. });
  328. warMachines->setSelectedSilent(parent.startInfo->warMachines[id]);
  329. setHeroIcon();
  330. setCreatureIcons();
  331. setSecSkillIcons();
  332. setArtifactIcons();
  333. }
  334. void BattleOnlyModeHeroSelector::manageSpells()
  335. {
  336. std::vector<std::shared_ptr<CComponent>> resComps;
  337. for(auto & spellId : parent.startInfo->spells[id])
  338. resComps.push_back(std::make_shared<CComponent>(ComponentType::SPELL, spellId, std::nullopt, CComponent::ESize::large));
  339. std::vector<std::pair<AnimationPath, CFunctionList<void()>>> pom;
  340. for(int i = 0; i < 3; i++)
  341. pom.emplace_back(AnimationPath::builtin("settingsWindow/button80"), nullptr);
  342. auto allowedSet = LIBRARY->spellh->getDefaultAllowed();
  343. std::vector<SpellID> allSpells(allowedSet.begin(), allowedSet.end());
  344. allSpells.erase(std::remove_if(allSpells.begin(), allSpells.end(), [](const SpellID& spell) {
  345. return !spell.toSpell()->isCombat();
  346. }), allSpells.end());
  347. std::sort(allSpells.begin(), allSpells.end(), [](auto a, auto b) {
  348. auto A = a.toSpell();
  349. auto B = b.toSpell();
  350. if(A->getLevel() != B->getLevel())
  351. return A->getLevel() < B->getLevel();
  352. for (const auto schoolId : LIBRARY->spellSchoolHandler->getAllObjects())
  353. {
  354. if(A->schools.count(schoolId) && !B->schools.count(schoolId))
  355. return true;
  356. if(!A->schools.count(schoolId) && B->schools.count(schoolId))
  357. return false;
  358. }
  359. return TextOperations::compareLocalizedStrings(A->getNameTranslated(), B->getNameTranslated());
  360. });
  361. std::vector<SpellID> toAdd;
  362. std::vector<SpellID> toRemove;
  363. for (const auto& spell : allSpells)
  364. {
  365. bool inCurrent = std::find(parent.startInfo->spells[id].begin(), parent.startInfo->spells[id].end(), spell) != parent.startInfo->spells[id].end();
  366. if (inCurrent)
  367. toRemove.push_back(spell);
  368. else
  369. toAdd.push_back(spell);
  370. }
  371. auto openList = [this](std::vector<SpellID> list, bool add){
  372. std::vector<std::string> texts;
  373. std::vector<std::shared_ptr<IImage>> images;
  374. for (const auto & s : list)
  375. {
  376. texts.push_back(s.toSpell()->getNameTranslated());
  377. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("SpellInt"), s.toSpell()->getIconIndex() + 1, 0, EImageBlitMode::OPAQUE);
  378. image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
  379. images.push_back(image);
  380. }
  381. std::string title = LIBRARY->generaltexth->translate(add ? "vcmi.lobby.battleOnlySpellAdd" : "vcmi.lobby.battleOnlySpellRemove");
  382. auto window = std::make_shared<CObjectListWindow>(texts, nullptr, title, title, [this, list, add](int index){
  383. auto & v = parent.startInfo->spells[id];
  384. if(add)
  385. v.push_back(list[index]);
  386. else
  387. v.erase(std::remove(v.begin(), v.end(), list[index]), v.end());
  388. parent.onChange();
  389. manageSpells();
  390. }, 0, images, true, true);
  391. window->onPopup = [list](int index) {
  392. std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::SPELL, list[index]);
  393. CRClickPopup::createAndPush(list[index].toSpell()->getDescriptionTranslated(0), CInfoWindow::TCompsInfo(1, comp));
  394. };
  395. ENGINE->windows().pushWindow(window);
  396. };
  397. auto temp = std::make_shared<CInfoWindow>(LIBRARY->generaltexth->translate(parent.startInfo->spells[id].size() ? "vcmi.lobby.battleOnlySpellSelectCurrent" : "vcmi.lobby.battleOnlySpellSelect"), PlayerColor(0), resComps, pom);
  398. temp->buttons[0]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/addChannel")));
  399. temp->buttons[0]->addCallback([openList, toAdd](){ openList(toAdd, true); });
  400. temp->buttons[0]->addPopupCallback([](){ CRClickPopup::createAndPush(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlySpellAdd")); });
  401. temp->buttons[1]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("lobby/removeChannel")));
  402. temp->buttons[1]->addCallback([openList, toRemove](){ openList(toRemove, false); });
  403. temp->buttons[1]->addPopupCallback([](){ CRClickPopup::createAndPush(LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlySpellRemove")); });
  404. temp->buttons[1]->setEnabled(parent.startInfo->spells[id].size());
  405. temp->buttons[2]->setOverlay(std::make_shared<CPicture>(ImagePath::builtin("spellResearch/close")));
  406. temp->buttons[2]->addPopupCallback([](){ CRClickPopup::createAndPush(LIBRARY->generaltexth->translate("core.genrltxt.600")); });
  407. ENGINE->windows().pushWindow(temp);
  408. }
  409. void BattleOnlyModeHeroSelector::selectHero()
  410. {
  411. auto allowedSet = LIBRARY->heroh->getDefaultAllowed();
  412. std::vector<HeroTypeID> heroes(allowedSet.begin(), allowedSet.end());
  413. std::sort(heroes.begin(), heroes.end(), [](auto a, auto b) {
  414. auto heroA = a.toHeroType();
  415. auto heroB = b.toHeroType();
  416. if(heroA->heroClass->faction != heroB->heroClass->faction)
  417. return heroA->heroClass->faction < heroB->heroClass->faction;
  418. if(heroA->heroClass->getId() != heroB->heroClass->getId())
  419. return heroA->heroClass->getId() < heroB->heroClass->getId();
  420. return heroA->getNameTranslated() < heroB->getNameTranslated();
  421. });
  422. int selectedIndex = parent.startInfo->selectedHero[id] == HeroTypeID::NONE ? 0 : (1 + std::distance(heroes.begin(), std::find_if(heroes.begin(), heroes.end(), [this](auto heroID) {
  423. return heroID == parent.startInfo->selectedHero[id];
  424. })));
  425. std::vector<std::string> texts;
  426. std::vector<std::shared_ptr<IImage>> images;
  427. // Add "no hero" option
  428. texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
  429. images.push_back(nullptr);
  430. for (const auto & h : heroes)
  431. {
  432. texts.push_back(h.toHeroType()->getNameTranslated());
  433. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("PortraitsSmall"), h.toHeroType()->imageIndex, 0, EImageBlitMode::OPAQUE);
  434. image->scaleTo(Point(35, 23), EScalingAlgorithm::NEAREST);
  435. images.push_back(image);
  436. }
  437. auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeHeroSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeHeroSelect"), [this, heroes](int index){
  438. if(index == 0)
  439. {
  440. parent.startInfo->selectedHero[id] = HeroTypeID::NONE;
  441. parent.onChange();
  442. return;
  443. }
  444. index--;
  445. parent.startInfo->selectedHero[id] = heroes[index];
  446. for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
  447. parent.startInfo->primSkillLevel[id][i] = heroes[index].toHeroType()->heroClass->primarySkillInitial[i];
  448. for(size_t i=0; i<8; i++)
  449. if(heroes[index].toHeroType()->secSkillsInit.size() > i)
  450. parent.startInfo->secSkillLevel[id][i] = std::make_pair(heroes[index].toHeroType()->secSkillsInit[i].first, MasteryLevel::Type(heroes[index].toHeroType()->secSkillsInit[i].second));
  451. else
  452. parent.startInfo->secSkillLevel[id][i] = std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE);
  453. parent.startInfo->spellBook[id] = heroes[index].toHeroType()->haveSpellBook;
  454. parent.onChange();
  455. }, selectedIndex, images, true, true);
  456. window->onPopup = [heroes](int index) {
  457. if(index == 0)
  458. return;
  459. index--;
  460. ENGINE->windows().createAndPushWindow<CHeroOverview>(heroes.at(index));
  461. };
  462. ENGINE->windows().pushWindow(window);
  463. }
  464. void BattleOnlyModeHeroSelector::setHeroIcon()
  465. {
  466. OBJECT_CONSTRUCTION;
  467. if(parent.startInfo->selectedHero[id] == HeroTypeID::NONE)
  468. {
  469. heroImage = std::make_shared<CPicture>(drawBlackBox(Point(58, 64), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSelectHero"), id == 1 ? parent.boxColor : parent.disabledBoxColor), Point(6, 7));
  470. heroLabel = std::make_shared<CLabel>(160, 16, FONT_SMALL, ETextAlignment::CENTER, id == 1 ? Colors::WHITE : parent.disabledColor, LIBRARY->generaltexth->translate("core.genrltxt.507"));
  471. for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
  472. primSkillsInput[i]->setText("0");
  473. }
  474. else
  475. {
  476. heroImage = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("PortraitsLarge"), EImageBlitMode::COLORKEY)->getImage(parent.startInfo->selectedHero[id].toHeroType()->imageIndex), Point(6, 7));
  477. heroLabel = std::make_shared<CLabel>(160, 16, FONT_SMALL, ETextAlignment::CENTER, id == 1 ? Colors::WHITE : parent.disabledColor, parent.startInfo->selectedHero[id].toHeroType()->getNameTranslated());
  478. for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
  479. primSkillsInput[i]->setText(std::to_string(parent.startInfo->primSkillLevel[id][i]));
  480. }
  481. heroImage->addLClickCallback([this](){ selectHero(); });
  482. heroImage->addRClickCallback([this](){
  483. if(parent.startInfo->selectedHero[id] == HeroTypeID::NONE)
  484. return;
  485. ENGINE->windows().createAndPushWindow<CHeroOverview>(parent.startInfo->selectedHero[id].toHeroType()->getId());
  486. });
  487. }
  488. void BattleOnlyModeHeroSelector::selectCreature(int slot)
  489. {
  490. auto allowedSet = LIBRARY->creh->getDefaultAllowed();
  491. std::vector<CreatureID> creatures(allowedSet.begin(), allowedSet.end());
  492. std::sort(creatures.begin(), creatures.end(), [](auto a, auto b) {
  493. auto creatureA = a.toCreature();
  494. auto creatureB = b.toCreature();
  495. if ((creatureA->getFactionID() == FactionID::NEUTRAL) != (creatureB->getFactionID() == FactionID::NEUTRAL))
  496. return creatureA->getFactionID() != FactionID::NEUTRAL;
  497. if(creatureA->getFactionID() != creatureB->getFactionID())
  498. return creatureA->getFactionID() < creatureB->getFactionID();
  499. if(creatureA->getLevel() != creatureB->getLevel())
  500. return creatureA->getLevel() < creatureB->getLevel();
  501. if(creatureA->upgrades.size() != creatureB->upgrades.size())
  502. return creatureA->upgrades.size() > creatureB->upgrades.size();
  503. return creatureA->getNameSingularTranslated() < creatureB->getNameSingularTranslated();
  504. });
  505. int selectedIndex = parent.startInfo->selectedArmy[id][slot].getId() == CreatureID::NONE ? 0 : (1 + std::distance(creatures.begin(), std::find_if(creatures.begin(), creatures.end(), [this, slot](auto creatureID) {
  506. return creatureID == parent.startInfo->selectedArmy[id][slot].getId();
  507. })));
  508. std::vector<std::string> texts;
  509. std::vector<std::shared_ptr<IImage>> images;
  510. // Add "no creature" option
  511. texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
  512. images.push_back(nullptr);
  513. for (const auto & c : creatures)
  514. {
  515. texts.push_back(c.toCreature()->getNameSingularTranslated());
  516. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("CPRSMALL"), c.toCreature()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
  517. image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
  518. images.push_back(image);
  519. }
  520. auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeCreatureSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeCreatureSelect"), [this, creatures, slot](int index){
  521. if(index == 0)
  522. {
  523. parent.startInfo->selectedArmy[id][slot] = CStackBasicDescriptor(CreatureID::NONE, 1);
  524. parent.onChange();
  525. return;
  526. }
  527. index--;
  528. auto creature = creatures.at(index).toCreature();
  529. parent.startInfo->selectedArmy[id][slot] = CStackBasicDescriptor(creature->getId(), 100);
  530. parent.onChange();
  531. }, selectedIndex, images, true, true);
  532. window->onPopup = [creatures](int index) {
  533. if(index == 0)
  534. return;
  535. index--;
  536. ENGINE->windows().createAndPushWindow<CStackWindow>(creatures.at(index).toCreature(), true);
  537. };
  538. ENGINE->windows().pushWindow(window);
  539. }
  540. void BattleOnlyModeHeroSelector::setCreatureIcons()
  541. {
  542. OBJECT_CONSTRUCTION;
  543. for(int i = 0; i < creatureImage.size(); i++)
  544. {
  545. if(parent.startInfo->selectedArmy[id][i].getId() == CreatureID::NONE)
  546. {
  547. MetaString str;
  548. str.appendTextID("vcmi.lobby.battleOnlyModeSelectUnit");
  549. str.replaceNumber(i + 1);
  550. creatureImage[i] = std::make_shared<CPicture>(drawBlackBox(Point(32, 32), str.toString(), id == 1 ? parent.boxColor : parent.disabledBoxColor), Point(6 + i * 36, 78));
  551. selectedArmyInput[i]->disable();
  552. }
  553. else
  554. {
  555. auto unit = parent.startInfo->selectedArmy[id][i];
  556. auto creatureID = unit.getId();
  557. 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));
  558. selectedArmyInput[i]->setText(TextOperations::formatMetric(unit.getCount(), 3));
  559. selectedArmyInput[i]->enable();
  560. }
  561. creatureImage[i]->addLClickCallback([this, i](){ selectCreature(i); });
  562. creatureImage[i]->addRClickCallback([this, i](){
  563. if(parent.startInfo->selectedArmy[id][i].getId() == CreatureID::NONE)
  564. return;
  565. ENGINE->windows().createAndPushWindow<CStackWindow>(LIBRARY->creh->objects.at(parent.startInfo->selectedArmy[id][i].getId()).get(), true);
  566. });
  567. }
  568. }
  569. void BattleOnlyModeHeroSelector::selectSecSkill(int slot)
  570. {
  571. auto allowedSet = LIBRARY->skillh->getDefaultAllowed();
  572. std::vector<SecondarySkill> skills(allowedSet.begin(), allowedSet.end());
  573. skills.erase( // remove already added skills from selection
  574. std::remove_if(
  575. skills.begin(),
  576. skills.end(),
  577. [this, slot](auto & skill) {
  578. return std::any_of(
  579. parent.startInfo->secSkillLevel[id].begin(), parent.startInfo->secSkillLevel[id].end(),
  580. [&skill](auto & s) { return s.first == skill; }
  581. ) && parent.startInfo->secSkillLevel[id][slot].first != skill;
  582. }
  583. ),
  584. skills.end()
  585. );
  586. std::sort(skills.begin(), skills.end(), [](auto a, auto b) {
  587. auto skillA = a.toSkill();
  588. auto skillB = b.toSkill();
  589. return skillA->getNameTranslated() < skillB->getNameTranslated();
  590. });
  591. int selectedIndex = parent.startInfo->secSkillLevel[id][slot].second == MasteryLevel::NONE ? 0 : (1 + std::distance(skills.begin(), std::find_if(skills.begin(), skills.end(), [this, slot](auto skillID) {
  592. return skillID == parent.startInfo->secSkillLevel[id][slot].first;
  593. })));
  594. std::vector<std::string> texts;
  595. std::vector<std::shared_ptr<IImage>> images;
  596. // Add "no skill" option
  597. texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
  598. images.push_back(nullptr);
  599. for (const auto & c : skills)
  600. {
  601. texts.push_back(c.toSkill()->getNameTranslated());
  602. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("SECSK32"), c.toSkill()->getIconIndex(0), 0, EImageBlitMode::OPAQUE);
  603. image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
  604. images.push_back(image);
  605. }
  606. auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSecSkillSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeSecSkillSelect"), [this, skills, slot](int index){
  607. if(index == 0)
  608. {
  609. parent.startInfo->secSkillLevel[id][slot] = std::make_pair(SecondarySkill::NONE, MasteryLevel::NONE);
  610. parent.onChange();
  611. return;
  612. }
  613. index--;
  614. auto skill = skills.at(index).toSkill();
  615. parent.startInfo->secSkillLevel[id][slot] = std::make_pair(skill->getId(), MasteryLevel::EXPERT);
  616. parent.onChange();
  617. }, selectedIndex, images, true, true);
  618. window->onPopup = [skills](int index) {
  619. if(index == 0)
  620. return;
  621. index--;
  622. auto skillId = skills.at(index);
  623. std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::SEC_SKILL, skillId, MasteryLevel::EXPERT);
  624. CRClickPopup::createAndPush(skillId.toSkill()->getDescriptionTranslated(MasteryLevel::EXPERT), CInfoWindow::TCompsInfo(1, comp));
  625. };
  626. ENGINE->windows().pushWindow(window);
  627. }
  628. void BattleOnlyModeHeroSelector::setSecSkillIcons()
  629. {
  630. OBJECT_CONSTRUCTION;
  631. for(int i = 0; i < secSkillImage.size(); i++)
  632. {
  633. bool isLeft = (i % 2 == 0);
  634. int line = (i / 2);
  635. Point imgPos(261 + (isLeft ? 0 : 36), 7 + line * 54);
  636. auto skillInfo = parent.startInfo->secSkillLevel[id][i];
  637. if(skillInfo.second == MasteryLevel::NONE)
  638. {
  639. MetaString str;
  640. str.appendTextID("vcmi.lobby.battleOnlyModeSelectSkill");
  641. str.replaceNumber(i + 1);
  642. secSkillImage[i] = std::make_shared<CPicture>(drawBlackBox(Point(32, 32), str.toString(), id == 1 ? parent.boxColor : parent.disabledBoxColor), imgPos);
  643. selectedSecSkillInput[i]->disable();
  644. }
  645. else
  646. {
  647. secSkillImage[i] = std::make_shared<CPicture>(ENGINE->renderHandler().loadAnimation(AnimationPath::builtin("SECSK32"), EImageBlitMode::COLORKEY)->getImage(skillInfo.first.toSkill()->getIconIndex(skillInfo.second - 1)), imgPos);
  648. selectedSecSkillInput[i]->setText(std::to_string(skillInfo.second));
  649. selectedSecSkillInput[i]->enable();
  650. }
  651. secSkillImage[i]->addLClickCallback([this, i](){ selectSecSkill(i); });
  652. secSkillImage[i]->addRClickCallback([this, i](){
  653. auto skillId = parent.startInfo->secSkillLevel[id][i].first;
  654. auto skillLevel = parent.startInfo->secSkillLevel[id][i].second;
  655. if(skillLevel == MasteryLevel::NONE)
  656. return;
  657. std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::SEC_SKILL, skillId, skillLevel);
  658. CRClickPopup::createAndPush(skillId.toSkill()->getDescriptionTranslated(skillLevel), CInfoWindow::TCompsInfo(1, comp));
  659. });
  660. }
  661. }
  662. std::vector<ArtifactPosition> getArtPos()
  663. {
  664. std::vector<ArtifactPosition> artPos = {
  665. ArtifactPosition::HEAD, ArtifactPosition::SHOULDERS, ArtifactPosition::NECK, ArtifactPosition::RIGHT_HAND, ArtifactPosition::LEFT_HAND, ArtifactPosition::TORSO, ArtifactPosition::FEET,
  666. ArtifactPosition::RIGHT_RING, ArtifactPosition::LEFT_RING, ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5
  667. };
  668. return artPos;
  669. }
  670. void BattleOnlyModeHeroSelector::selectArtifact(int slot, ArtifactID artifactId)
  671. {
  672. auto artPos = getArtPos();
  673. auto allowedSet = LIBRARY->arth->getDefaultAllowed();
  674. std::vector<ArtifactID> artifacts(allowedSet.begin(), allowedSet.end());
  675. artifacts.erase( // remove already added and not for that slot allowed artifacts from selection
  676. std::remove_if(
  677. artifacts.begin(),
  678. artifacts.end(),
  679. [this, slot, artPos](auto & artifact) {
  680. auto possibleSlots = artifact.toArtifact()->getPossibleSlots();
  681. std::vector<ArtifactPosition> allowedSlots;
  682. if(possibleSlots.find(ArtBearer::HERO) != possibleSlots.end() && !possibleSlots.at(ArtBearer::HERO).empty())
  683. allowedSlots = possibleSlots.at(ArtBearer::HERO);
  684. return (std::any_of(parent.startInfo->artifacts[id].begin(), parent.startInfo->artifacts[id].end(), [&artifact](auto & a) {return a.second == artifact;})
  685. && parent.startInfo->artifacts[id][artPos[slot]] != artifact)
  686. || !std::any_of(allowedSlots.begin(), allowedSlots.end(), [slot, artPos](auto & p){ return p == artPos[slot]; });
  687. }
  688. ),
  689. artifacts.end()
  690. );
  691. std::sort(artifacts.begin(), artifacts.end(), [](auto a, auto b) {
  692. auto artifactA = a.toArtifact();
  693. auto artifactB = b.toArtifact();
  694. return artifactA->getNameTranslated() < artifactB->getNameTranslated();
  695. });
  696. int selectedIndex = artifactId == ArtifactID::NONE ? 0 : (1 + std::distance(artifacts.begin(), std::find_if(artifacts.begin(), artifacts.end(), [artifactId](auto artID) {
  697. return artID == artifactId;
  698. })));
  699. std::vector<std::string> texts;
  700. std::vector<std::shared_ptr<IImage>> images;
  701. // Add "no artifact" option
  702. texts.push_back(LIBRARY->generaltexth->translate("core.genrltxt.507"));
  703. images.push_back(nullptr);
  704. for (const auto & a : artifacts)
  705. {
  706. texts.push_back(a.toArtifact()->getNameTranslated());
  707. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("Artifact"), a.toArtifact()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
  708. image->scaleTo(Point(23, 23), EScalingAlgorithm::NEAREST);
  709. images.push_back(image);
  710. }
  711. auto window = std::make_shared<CObjectListWindow>(texts, nullptr, LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeArtifactSelect"), LIBRARY->generaltexth->translate("vcmi.lobby.battleOnlyModeArtifactSelect"), [this, artifacts, slot, artPos](int index){
  712. if(index == 0)
  713. {
  714. parent.startInfo->artifacts[id][artPos[slot]] = ArtifactID::NONE;
  715. parent.onChange();
  716. return;
  717. }
  718. index--;
  719. auto artifact = artifacts.at(index);
  720. parent.startInfo->artifacts[id][artPos[slot]] = artifact;
  721. parent.onChange();
  722. }, selectedIndex, images, true, true);
  723. window->onPopup = [artifacts](int index) {
  724. if(index == 0)
  725. return;
  726. index--;
  727. auto artifactId = artifacts.at(index);
  728. std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::ARTIFACT, artifactId);
  729. CRClickPopup::createAndPush(artifactId.toArtifact()->getDescriptionTranslated(), CInfoWindow::TCompsInfo(1, comp));
  730. };
  731. ENGINE->windows().pushWindow(window);
  732. }
  733. void BattleOnlyModeHeroSelector::setArtifactIcons()
  734. {
  735. OBJECT_CONSTRUCTION;
  736. auto artPos = getArtPos();
  737. for(int i = 0; i < artifactImage.size(); i++)
  738. {
  739. int xPos = i % 7;
  740. int yPos = i / 7;
  741. Point imgPos(6 + xPos * 36, 137 + yPos * 36);
  742. auto artifactId = parent.startInfo->artifacts[id][artPos[i]];
  743. if(artifactId == ArtifactID::NONE)
  744. {
  745. MetaString str;
  746. str.appendTextID("vcmi.lobby.battleOnlyModeSelectArtifact");
  747. str.replaceTextID("vcmi.lobby.battleOnlyModeSelectArtifact." + std::to_string(artPos[i]));
  748. artifactImage[i] = std::make_shared<CPicture>(drawBlackBox(Point(32, 32), str.toString(), id == 1 ? parent.boxColor : parent.disabledBoxColor), imgPos);
  749. }
  750. else
  751. {
  752. auto image = ENGINE->renderHandler().loadImage(AnimationPath::builtin("Artifact"), artifactId.toArtifact()->getIconIndex(), 0, EImageBlitMode::OPAQUE);
  753. image->scaleTo(Point(32, 32), EScalingAlgorithm::NEAREST);
  754. artifactImage[i] = std::make_shared<CPicture>(image, imgPos);
  755. }
  756. artifactImage[i]->addLClickCallback([this, i, artifactId](){ selectArtifact(i, artifactId); });
  757. artifactImage[i]->addRClickCallback([this, i, artPos](){
  758. auto artId = parent.startInfo->artifacts[id][artPos[i]];
  759. if(artId == ArtifactID::NONE)
  760. return;
  761. std::shared_ptr<CComponent> comp = std::make_shared<CComponent>(ComponentType::ARTIFACT, artId);
  762. CRClickPopup::createAndPush(artId.toArtifact()->getDescriptionTranslated(), CInfoWindow::TCompsInfo(1, comp));
  763. });
  764. }
  765. }
  766. void BattleOnlyModeTab::startBattle()
  767. {
  768. auto rng = &CRandomGenerator::getDefault();
  769. map->initTerrain();
  770. map->getEditManager()->clearTerrain(rng);
  771. map->getEditManager()->getTerrainSelection().selectAll();
  772. map->getEditManager()->drawTerrain(startInfo->selectedTerrain == TerrainId::NONE ? TerrainId::DIRT : startInfo->selectedTerrain, 0, rng);
  773. map->players[0].canComputerPlay = true;
  774. map->players[0].canHumanPlay = true;
  775. map->players[1] = map->players[0];
  776. auto knownHeroes = LIBRARY->objtypeh->knownSubObjects(Obj::HERO);
  777. auto addHero = [&, this](int sel, PlayerColor color, const int3 & position)
  778. {
  779. auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::HERO, startInfo->selectedHero[sel].toHeroType()->heroClass->getId());
  780. auto templates = factory->getTemplates();
  781. auto obj = std::dynamic_pointer_cast<CGHeroInstance>(factory->create(cb.get(), templates.front()));
  782. obj->setHeroType(startInfo->selectedHero[sel]);
  783. obj->setOwner(color);
  784. obj->pos = position;
  785. for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)
  786. obj->pushPrimSkill(PrimarySkill(i), startInfo->primSkillLevel[sel][i]);
  787. obj->clearSlots();
  788. for(int slot = 0; slot < GameConstants::ARMY_SIZE; slot++)
  789. if(startInfo->selectedArmy[sel][slot].getId() != CreatureID::NONE && startInfo->selectedArmy[sel][slot].getCount() > 0)
  790. obj->setCreature(SlotID(slot), startInfo->selectedArmy[sel][slot].getId(), startInfo->selectedArmy[sel][slot].getCount());
  791. // give spellbook
  792. if(!obj->getArt(ArtifactPosition::SPELLBOOK) && startInfo->spellBook[sel])
  793. obj->putArtifact(ArtifactPosition::SPELLBOOK, map->createArtifact(ArtifactID::SPELLBOOK));
  794. else if(obj->getArt(ArtifactPosition::SPELLBOOK) && !startInfo->spellBook[sel])
  795. obj->removeArtifact(ArtifactPosition::SPELLBOOK);
  796. if(startInfo->warMachines[sel])
  797. {
  798. obj->putArtifact(ArtifactPosition::MACH1, map->createArtifact(ArtifactID::BALLISTA));
  799. obj->putArtifact(ArtifactPosition::MACH2, map->createArtifact(ArtifactID::AMMO_CART));
  800. obj->putArtifact(ArtifactPosition::MACH3, map->createArtifact(ArtifactID::FIRST_AID_TENT));
  801. }
  802. for(const auto & spell : startInfo->spells[sel])
  803. obj->addSpellToSpellbook(spell);
  804. for(auto & artifact : startInfo->artifacts[sel])
  805. if(artifact.second != ArtifactID::NONE)
  806. obj->putArtifact(artifact.first, map->createArtifact(artifact.second));
  807. for(const auto & skill : LIBRARY->skillh->objects) // reset all standard skills
  808. obj->setSecSkillLevel(SecondarySkill(skill->getId()), MasteryLevel::NONE, ChangeValueMode::ABSOLUTE);
  809. for(int skillSlot = 0; skillSlot < 8; skillSlot++)
  810. obj->setSecSkillLevel(startInfo->secSkillLevel[sel][skillSlot].first, startInfo->secSkillLevel[sel][skillSlot].second, ChangeValueMode::ABSOLUTE);
  811. map->getEditManager()->insertObject(obj);
  812. };
  813. addHero(0, PlayerColor(0), int3(5, 6, 0));
  814. if(startInfo->selectedTown == FactionID::ANY)
  815. addHero(1, PlayerColor(1), int3(5, 5, 0));
  816. else
  817. {
  818. auto factory = LIBRARY->objtypeh->getHandlerFor(Obj::TOWN, startInfo->selectedTown);
  819. auto templates = factory->getTemplates();
  820. auto obj = factory->create(cb.get(), templates.front());
  821. auto townObj = std::dynamic_pointer_cast<CGTownInstance>(obj);
  822. obj->setOwner(PlayerColor(1));
  823. obj->pos = int3(5, 5, 0);
  824. for (const auto & building : townObj->getTown()->getAllBuildings())
  825. townObj->addBuilding(building);
  826. if(startInfo->selectedHero[1] == HeroTypeID::NONE)
  827. {
  828. for(int slot = 0; slot < GameConstants::ARMY_SIZE; slot++)
  829. if(startInfo->selectedArmy[1][slot].getId() != CreatureID::NONE && startInfo->selectedArmy[1][slot].getCount() > 0)
  830. townObj->getArmy()->setCreature(SlotID(slot), startInfo->selectedArmy[1][slot].getId(), startInfo->selectedArmy[1][slot].getCount());
  831. }
  832. else
  833. addHero(1, PlayerColor(1), int3(5, 5, 0));
  834. map->getEditManager()->insertObject(townObj);
  835. }
  836. auto path = VCMIDirs::get().userDataPath() / "Maps";
  837. boost::filesystem::create_directories(path);
  838. const std::string fileName = "BattleOnlyMode.vmap";
  839. const auto fullPath = path / fileName;
  840. CMapService mapService;
  841. mapService.saveMap(map, fullPath);
  842. CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount) { return true; });
  843. auto mapInfo = std::make_shared<CMapInfo>();
  844. mapInfo->mapInit("Maps/BattleOnlyMode");
  845. GAME->server().setMapInfo(mapInfo);
  846. ExtraOptionsInfo extraOptions;
  847. extraOptions.unlimitedReplay = true;
  848. GAME->server().setExtraOptionsInfo(extraOptions);
  849. GAME->server().sendStartGame();
  850. }