CCampaignHandler.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. /*
  2. * CCampaignHandler.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 "CCampaignHandler.h"
  12. #include "../filesystem/Filesystem.h"
  13. #include "../filesystem/CCompressedStream.h"
  14. #include "../filesystem/CMemoryStream.h"
  15. #include "../filesystem/CBinaryReader.h"
  16. #include "../VCMI_Lib.h"
  17. #include "../vcmi_endian.h"
  18. #include "../CGeneralTextHandler.h"
  19. #include "../TextOperations.h"
  20. #include "../StartInfo.h"
  21. #include "../CModHandler.h"
  22. #include "../CArtHandler.h" //for hero crossover
  23. #include "../mapObjects/CGHeroInstance.h"//for hero crossover
  24. #include "../CHeroHandler.h"
  25. #include "../Languages.h"
  26. #include "../StringConstants.h"
  27. #include "CMapService.h"
  28. #include "CMap.h"
  29. #include "CMapInfo.h"
  30. // For hero crossover
  31. #include "serializer/CSerializer.h"
  32. #include "serializer/JsonDeserializer.h"
  33. #include "serializer/JsonSerializer.h"
  34. VCMI_LIB_NAMESPACE_BEGIN
  35. CampaignRegions::RegionDescription CampaignRegions::RegionDescription::fromJson(const JsonNode & node)
  36. {
  37. CampaignRegions::RegionDescription rd;
  38. rd.infix = node["infix"].String();
  39. rd.xpos = static_cast<int>(node["x"].Float());
  40. rd.ypos = static_cast<int>(node["y"].Float());
  41. return rd;
  42. }
  43. CampaignRegions CampaignRegions::fromJson(const JsonNode & node)
  44. {
  45. CampaignRegions cr;
  46. cr.campPrefix = node["prefix"].String();
  47. cr.colorSuffixLength = static_cast<int>(node["color_suffix_length"].Float());
  48. for(const JsonNode & desc : node["desc"].Vector())
  49. cr.regions.push_back(CampaignRegions::RegionDescription::fromJson(desc));
  50. return cr;
  51. }
  52. CampaignRegions CampaignRegions::getLegacy(int campId)
  53. {
  54. static std::vector<CampaignRegions> campDescriptions;
  55. if(campDescriptions.empty()) //read once
  56. {
  57. const JsonNode config(ResourceID("config/campaign_regions.json"));
  58. for(const JsonNode & campaign : config["campaign_regions"].Vector())
  59. campDescriptions.push_back(CampaignRegions::fromJson(campaign));
  60. }
  61. return campDescriptions.at(campId);
  62. }
  63. bool CampaignBonus::isBonusForHero() const
  64. {
  65. return type == CampaignBonusType::SPELL ||
  66. type == CampaignBonusType::MONSTER ||
  67. type == CampaignBonusType::ARTIFACT ||
  68. type == CampaignBonusType::SPELL_SCROLL ||
  69. type == CampaignBonusType::PRIMARY_SKILL ||
  70. type == CampaignBonusType::SECONDARY_SKILL;
  71. }
  72. void CampaignHeader::loadLegacyData(ui8 campId)
  73. {
  74. campaignRegions = CampaignRegions::getLegacy(campId);
  75. numberOfScenarios = VLC->generaltexth->getCampaignLength(campId);
  76. }
  77. CampaignHeader CampaignHandler::getHeader( const std::string & name)
  78. {
  79. ResourceID resourceID(name, EResType::CAMPAIGN);
  80. std::string modName = VLC->modh->findResourceOrigin(resourceID);
  81. std::string language = VLC->modh->getModLanguage(modName);
  82. std::string encoding = Languages::getLanguageOptions(language).encoding;
  83. auto fileStream = CResourceHandler::get(modName)->load(resourceID);
  84. std::vector<ui8> cmpgn = getFile(std::move(fileStream), true)[0];
  85. JsonNode jsonCampaign((const char*)cmpgn.data(), cmpgn.size());
  86. if(jsonCampaign.isNull())
  87. {
  88. //legacy OH3 campaign (*.h3c)
  89. CMemoryStream stream(cmpgn.data(), cmpgn.size());
  90. CBinaryReader reader(&stream);
  91. return readHeaderFromMemory(reader, resourceID.getName(), modName, encoding);
  92. }
  93. //VCMI (*.vcmp)
  94. return readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding);
  95. }
  96. std::shared_ptr<CampaignState> CampaignHandler::getCampaign( const std::string & name )
  97. {
  98. ResourceID resourceID(name, EResType::CAMPAIGN);
  99. std::string modName = VLC->modh->findResourceOrigin(resourceID);
  100. std::string language = VLC->modh->getModLanguage(modName);
  101. std::string encoding = Languages::getLanguageOptions(language).encoding;
  102. auto ret = std::make_unique<CampaignState>();
  103. auto fileStream = CResourceHandler::get(modName)->load(resourceID);
  104. std::vector<std::vector<ui8>> files = getFile(std::move(fileStream), false);
  105. if (files[0].front() < uint8_t(' ')) // binary format
  106. {
  107. CMemoryStream stream(files[0].data(), files[0].size());
  108. CBinaryReader reader(&stream);
  109. ret->header = readHeaderFromMemory(reader, resourceID.getName(), modName, encoding);
  110. for(int g = 0; g < ret->header.numberOfScenarios; ++g)
  111. {
  112. auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
  113. ret->scenarios[scenarioID] = readScenarioFromMemory(reader, ret->header);
  114. }
  115. }
  116. else // text format (json)
  117. {
  118. JsonNode jsonCampaign((const char*)files[0].data(), files[0].size());
  119. ret->header = readHeaderFromJson(jsonCampaign, resourceID.getName(), modName, encoding);
  120. for(auto & scenario : jsonCampaign["scenarios"].Vector())
  121. {
  122. auto scenarioID = static_cast<CampaignScenarioID>(ret->scenarios.size());
  123. ret->scenarios[scenarioID] = readScenarioFromJson(scenario);
  124. }
  125. }
  126. //first entry is campaign header. start loop from 1
  127. for(int scenarioID = 0, g = 1; g < files.size() && scenarioID < ret->header.numberOfScenarios; ++g)
  128. {
  129. auto id = static_cast<CampaignScenarioID>(scenarioID);
  130. while(!ret->scenarios[id].isNotVoid()) //skip void scenarios
  131. scenarioID++;
  132. std::string scenarioName = resourceID.getName();
  133. boost::to_lower(scenarioName);
  134. scenarioName += ':' + std::to_string(g - 1);
  135. //set map piece appropriately, convert vector to string
  136. ret->mapPieces[id].assign(reinterpret_cast<const char*>(files[g].data()), files[g].size());
  137. CMapService mapService;
  138. auto hdr = mapService.loadMapHeader(
  139. reinterpret_cast<const ui8 *>(ret->mapPieces[id].c_str()),
  140. static_cast<int>(ret->mapPieces[id].size()),
  141. scenarioName,
  142. modName,
  143. encoding);
  144. ret->scenarios[id].scenarioName = hdr->name;
  145. scenarioID++;
  146. }
  147. for(int i = 0; i < ret->scenarios.size(); i++)
  148. {
  149. auto scenarioID = static_cast<CampaignScenarioID>(i);
  150. if(vstd::contains(ret->mapPieces, scenarioID)) //not all maps must be present in a campaign
  151. ret->mapsRemaining.push_back(scenarioID);
  152. }
  153. return ret;
  154. }
  155. static std::string convertMapName(std::string input)
  156. {
  157. boost::algorithm::to_lower(input);
  158. boost::algorithm::trim(input);
  159. size_t slashPos = input.find_last_of("/");
  160. if (slashPos != std::string::npos)
  161. return input.substr(slashPos + 1);
  162. return input;
  163. }
  164. std::string CampaignHandler::readLocalizedString(CBinaryReader & reader, std::string filename, std::string modName, std::string encoding, std::string identifier)
  165. {
  166. TextIdentifier stringID( "campaign", convertMapName(filename), identifier);
  167. std::string input = TextOperations::toUnicode(reader.readBaseString(), encoding);
  168. if (input.empty())
  169. return "";
  170. VLC->generaltexth->registerString(modName, stringID, input);
  171. return VLC->generaltexth->translate(stringID.get());
  172. }
  173. CampaignHeader CampaignHandler::readHeaderFromJson(JsonNode & reader, std::string filename, std::string modName, std::string encoding)
  174. {
  175. CampaignHeader ret;
  176. ret.version = static_cast<CampaignVersion>(reader["version"].Integer());
  177. if(ret.version < CampaignVersion::VCMI_MIN || ret.version > CampaignVersion::VCMI_MAX)
  178. {
  179. logGlobal->info("VCMP Loading: Unsupported campaign %s version %d", filename, static_cast<int>(ret.version));
  180. return ret;
  181. }
  182. ret.version = CampaignVersion::VCMI;
  183. ret.campaignRegions = CampaignRegions::fromJson(reader["regions"]);
  184. ret.numberOfScenarios = reader["scenarios"].Vector().size();
  185. ret.name = reader["name"].String();
  186. ret.description = reader["description"].String();
  187. ret.difficultyChoosenByPlayer = reader["allowDifficultySelection"].Bool();
  188. //skip ret.music because it's unused in vcmi
  189. ret.filename = filename;
  190. ret.modName = modName;
  191. ret.encoding = encoding;
  192. ret.valid = true;
  193. return ret;
  194. }
  195. CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
  196. {
  197. auto prologEpilogReader = [](JsonNode & identifier) -> CampaignScenarioPrologEpilog
  198. {
  199. CampaignScenarioPrologEpilog ret;
  200. ret.hasPrologEpilog = !identifier.isNull();
  201. if(ret.hasPrologEpilog)
  202. {
  203. ret.prologVideo = identifier["video"].String();
  204. ret.prologMusic = identifier["music"].String();
  205. ret.prologText = identifier["text"].String();
  206. }
  207. return ret;
  208. };
  209. CampaignScenario ret;
  210. ret.conquered = false;
  211. ret.mapName = reader["map"].String();
  212. for(auto & g : reader["preconditions"].Vector())
  213. ret.preconditionRegions.insert(static_cast<CampaignScenarioID>(g.Integer()));
  214. ret.regionColor = reader["color"].Integer();
  215. ret.difficulty = reader["difficulty"].Integer();
  216. ret.regionText = reader["regionText"].String();
  217. ret.prolog = prologEpilogReader(reader["prolog"]);
  218. ret.epilog = prologEpilogReader(reader["epilog"]);
  219. ret.travelOptions = readScenarioTravelFromJson(reader);
  220. return ret;
  221. }
  222. CampaignTravel CampaignHandler::readScenarioTravelFromJson(JsonNode & reader)
  223. {
  224. CampaignTravel ret;
  225. std::map<std::string, CampaignStartOptions> startOptionsMap = {
  226. {"none", CampaignStartOptions::NONE},
  227. {"bonus", CampaignStartOptions::START_BONUS},
  228. {"crossover", CampaignStartOptions::HERO_CROSSOVER},
  229. {"hero", CampaignStartOptions::HERO_OPTIONS}
  230. };
  231. std::map<std::string, CampaignBonusType> bonusTypeMap = {
  232. {"spell", CampaignBonusType::SPELL},
  233. {"creature", CampaignBonusType::MONSTER},
  234. {"building", CampaignBonusType::BUILDING},
  235. {"artifact", CampaignBonusType::ARTIFACT},
  236. {"scroll", CampaignBonusType::SPELL_SCROLL},
  237. {"primarySkill", CampaignBonusType::PRIMARY_SKILL},
  238. {"secondarySkill", CampaignBonusType::SECONDARY_SKILL},
  239. {"resource", CampaignBonusType::RESOURCE},
  240. //{"prevHero", CScenarioTravel::STravelBonus::EBonusType::HEROES_FROM_PREVIOUS_SCENARIO},
  241. //{"hero", CScenarioTravel::STravelBonus::EBonusType::HERO},
  242. };
  243. std::map<std::string, ui32> primarySkillsMap = {
  244. {"attack", 0},
  245. {"defence", 8},
  246. {"spellpower", 16},
  247. {"knowledge", 24},
  248. };
  249. std::map<std::string, ui16> heroSpecialMap = {
  250. {"strongest", 0xFFFD},
  251. {"generated", 0xFFFE},
  252. {"random", 0xFFFF}
  253. };
  254. std::map<std::string, ui8> resourceTypeMap = {
  255. //FD - wood+ore
  256. //FE - mercury+sulfur+crystal+gem
  257. {"wood", 0},
  258. {"mercury", 1},
  259. {"ore", 2},
  260. {"sulfur", 3},
  261. {"crystal", 4},
  262. {"gems", 5},
  263. {"gold", 6},
  264. {"common", 0xFD},
  265. {"rare", 0xFE}
  266. };
  267. for(auto & k : reader["heroKeeps"].Vector())
  268. {
  269. if(k.String() == "experience") ret.whatHeroKeeps.experience = true;
  270. if(k.String() == "primarySkills") ret.whatHeroKeeps.primarySkills = true;
  271. if(k.String() == "secondarySkills") ret.whatHeroKeeps.secondarySkills = true;
  272. if(k.String() == "spells") ret.whatHeroKeeps.spells = true;
  273. if(k.String() == "artifacts") ret.whatHeroKeeps.artifacts = true;
  274. }
  275. for(auto & k : reader["keepCreatures"].Vector())
  276. {
  277. if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "creature", k.String()))
  278. ret.monstersKeptByHero.insert(CreatureID(identifier.value()));
  279. else
  280. logGlobal->warn("VCMP Loading: keepCreatures contains unresolved identifier %s", k.String());
  281. }
  282. for(auto & k : reader["keepArtifacts"].Vector())
  283. {
  284. if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "artifact", k.String()))
  285. ret.artifactsKeptByHero.insert(ArtifactID(identifier.value()));
  286. else
  287. logGlobal->warn("VCMP Loading: keepArtifacts contains unresolved identifier %s", k.String());
  288. }
  289. ret.startOptions = startOptionsMap[reader["startOptions"].String()];
  290. switch(ret.startOptions)
  291. {
  292. case CampaignStartOptions::NONE:
  293. //no bonuses. Seems to be OK
  294. break;
  295. case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
  296. {
  297. ret.playerColor = reader["playerColor"].Integer();
  298. for(auto & bjson : reader["bonuses"].Vector())
  299. {
  300. CampaignBonus bonus;
  301. bonus.type = bonusTypeMap[bjson["what"].String()];
  302. switch (bonus.type)
  303. {
  304. case CampaignBonusType::RESOURCE:
  305. bonus.info1 = resourceTypeMap[bjson["type"].String()];
  306. bonus.info2 = bjson["amount"].Integer();
  307. break;
  308. case CampaignBonusType::BUILDING:
  309. bonus.info1 = vstd::find_pos(EBuildingType::names, bjson["type"].String());
  310. if(bonus.info1 == -1)
  311. logGlobal->warn("VCMP Loading: unresolved building identifier %s", bjson["type"].String());
  312. break;
  313. default:
  314. if(int heroId = heroSpecialMap[bjson["hero"].String()])
  315. bonus.info1 = heroId;
  316. else
  317. if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()))
  318. bonus.info1 = identifier.value();
  319. else
  320. logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String());
  321. bonus.info3 = bjson["amount"].Integer();
  322. switch(bonus.type)
  323. {
  324. case CampaignBonusType::SPELL:
  325. case CampaignBonusType::MONSTER:
  326. case CampaignBonusType::SECONDARY_SKILL:
  327. case CampaignBonusType::ARTIFACT:
  328. if(auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), bjson["what"].String(), bjson["type"].String()))
  329. bonus.info2 = identifier.value();
  330. else
  331. logGlobal->warn("VCMP Loading: unresolved %s identifier %s", bjson["what"].String(), bjson["type"].String());
  332. break;
  333. case CampaignBonusType::SPELL_SCROLL:
  334. if(auto Identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "spell", bjson["type"].String()))
  335. bonus.info2 = Identifier.value();
  336. else
  337. logGlobal->warn("VCMP Loading: unresolved spell scroll identifier %s", bjson["type"].String());
  338. break;
  339. case CampaignBonusType::PRIMARY_SKILL:
  340. for(auto & ps : primarySkillsMap)
  341. bonus.info2 |= bjson[ps.first].Integer() << ps.second;
  342. break;
  343. default:
  344. bonus.info2 = bjson["type"].Integer();
  345. }
  346. break;
  347. }
  348. ret.bonusesToChoose.push_back(bonus);
  349. }
  350. break;
  351. }
  352. case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
  353. {
  354. for(auto & bjson : reader["bonuses"].Vector())
  355. {
  356. CampaignBonus bonus;
  357. bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
  358. bonus.info1 = bjson["playerColor"].Integer(); //player color
  359. bonus.info2 = bjson["scenario"].Integer(); //from what scenario
  360. ret.bonusesToChoose.push_back(bonus);
  361. }
  362. break;
  363. }
  364. case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
  365. {
  366. for(auto & bjson : reader["bonuses"].Vector())
  367. {
  368. CampaignBonus bonus;
  369. bonus.type = CampaignBonusType::HERO;
  370. bonus.info1 = bjson["playerColor"].Integer(); //player color
  371. if(int heroId = heroSpecialMap[bjson["hero"].String()])
  372. bonus.info2 = heroId;
  373. else
  374. if (auto identifier = VLC->modh->identifiers.getIdentifier(CModHandler::scopeMap(), "hero", bjson["hero"].String()))
  375. bonus.info2 = identifier.value();
  376. else
  377. logGlobal->warn("VCMP Loading: unresolved hero identifier %s", bjson["hero"].String());
  378. ret.bonusesToChoose.push_back(bonus);
  379. }
  380. break;
  381. }
  382. default:
  383. {
  384. logGlobal->warn("VCMP Loading: Unsupported start options value");
  385. break;
  386. }
  387. }
  388. return ret;
  389. }
  390. CampaignHeader CampaignHandler::readHeaderFromMemory( CBinaryReader & reader, std::string filename, std::string modName, std::string encoding )
  391. {
  392. CampaignHeader ret;
  393. ret.version = static_cast<CampaignVersion>(reader.readUInt32());
  394. ui8 campId = reader.readUInt8() - 1;//change range of it from [1, 20] to [0, 19]
  395. ret.loadLegacyData(campId);
  396. ret.name = readLocalizedString(reader, filename, modName, encoding, "name");
  397. ret.description = readLocalizedString(reader, filename, modName, encoding, "description");
  398. if (ret.version > CampaignVersion::RoE)
  399. ret.difficultyChoosenByPlayer = reader.readInt8();
  400. else
  401. ret.difficultyChoosenByPlayer = false;
  402. reader.readInt8(); //music - skip as unused
  403. ret.filename = filename;
  404. ret.modName = modName;
  405. ret.encoding = encoding;
  406. ret.valid = true;
  407. return ret;
  408. }
  409. CampaignScenario CampaignHandler::readScenarioFromMemory( CBinaryReader & reader, const CampaignHeader & header)
  410. {
  411. auto prologEpilogReader = [&](const std::string & identifier) -> CampaignScenarioPrologEpilog
  412. {
  413. CampaignScenarioPrologEpilog ret;
  414. ret.hasPrologEpilog = reader.readUInt8();
  415. if(ret.hasPrologEpilog)
  416. {
  417. ret.prologVideo = CampaignHandler::prologVideoName(reader.readUInt8());
  418. ret.prologMusic = CampaignHandler::prologMusicName(reader.readUInt8());
  419. ret.prologText = readLocalizedString(reader, header.filename, header.modName, header.encoding, identifier);
  420. }
  421. return ret;
  422. };
  423. CampaignScenario ret;
  424. ret.conquered = false;
  425. ret.mapName = reader.readBaseString();
  426. reader.readUInt32(); //packedMapSize - not used
  427. if(header.numberOfScenarios > 8) //unholy alliance
  428. {
  429. ret.loadPreconditionRegions(reader.readUInt16());
  430. }
  431. else
  432. {
  433. ret.loadPreconditionRegions(reader.readUInt8());
  434. }
  435. ret.regionColor = reader.readUInt8();
  436. ret.difficulty = reader.readUInt8();
  437. ret.regionText = readLocalizedString(reader, header.filename, header.modName, header.encoding, ret.mapName + ".region");
  438. ret.prolog = prologEpilogReader(ret.mapName + ".prolog");
  439. ret.epilog = prologEpilogReader(ret.mapName + ".epilog");
  440. ret.travelOptions = readScenarioTravelFromMemory(reader, header.version);
  441. return ret;
  442. }
  443. void CampaignScenario::loadPreconditionRegions(ui32 regions)
  444. {
  445. for (int i=0; i<32; i++) //for each bit in region. h3c however can only hold up to 16
  446. {
  447. if ( (1 << i) & regions)
  448. preconditionRegions.insert(static_cast<CampaignScenarioID>(i));
  449. }
  450. }
  451. template<typename Identifier>
  452. static void readContainer(std::set<Identifier> container, CBinaryReader & reader, int sizeBytes)
  453. {
  454. for(int iId = 0, byte = 0; iId < sizeBytes * 8; ++iId)
  455. {
  456. if(iId % 8 == 0)
  457. byte = reader.readUInt8();
  458. if(byte & (1 << iId % 8))
  459. container.insert(Identifier(iId));
  460. }
  461. }
  462. CampaignTravel CampaignHandler::readScenarioTravelFromMemory(CBinaryReader & reader, CampaignVersion version )
  463. {
  464. CampaignTravel ret;
  465. ui8 whatHeroKeeps = reader.readUInt8();
  466. ret.whatHeroKeeps.experience = whatHeroKeeps & 1;
  467. ret.whatHeroKeeps.primarySkills = whatHeroKeeps & 2;
  468. ret.whatHeroKeeps.secondarySkills = whatHeroKeeps & 4;
  469. ret.whatHeroKeeps.spells = whatHeroKeeps & 8;
  470. ret.whatHeroKeeps.artifacts = whatHeroKeeps & 16;
  471. readContainer(ret.monstersKeptByHero, reader, 19);
  472. readContainer(ret.artifactsKeptByHero, reader, version < CampaignVersion::SoD ? 17 : 18);
  473. ret.startOptions = static_cast<CampaignStartOptions>(reader.readUInt8());
  474. switch(ret.startOptions)
  475. {
  476. case CampaignStartOptions::NONE:
  477. //no bonuses. Seems to be OK
  478. break;
  479. case CampaignStartOptions::START_BONUS: //reading of bonuses player can choose
  480. {
  481. ret.playerColor = reader.readUInt8();
  482. ui8 numOfBonuses = reader.readUInt8();
  483. for (int g=0; g<numOfBonuses; ++g)
  484. {
  485. CampaignBonus bonus;
  486. bonus.type = static_cast<CampaignBonusType>(reader.readUInt8());
  487. //hero: FFFD means 'most powerful' and FFFE means 'generated'
  488. switch(bonus.type)
  489. {
  490. case CampaignBonusType::SPELL:
  491. {
  492. bonus.info1 = reader.readUInt16(); //hero
  493. bonus.info2 = reader.readUInt8(); //spell ID
  494. break;
  495. }
  496. case CampaignBonusType::MONSTER:
  497. {
  498. bonus.info1 = reader.readUInt16(); //hero
  499. bonus.info2 = reader.readUInt16(); //monster type
  500. bonus.info3 = reader.readUInt16(); //monster count
  501. break;
  502. }
  503. case CampaignBonusType::BUILDING:
  504. {
  505. bonus.info1 = reader.readUInt8(); //building ID (0 - town hall, 1 - city hall, 2 - capitol, etc)
  506. break;
  507. }
  508. case CampaignBonusType::ARTIFACT:
  509. {
  510. bonus.info1 = reader.readUInt16(); //hero
  511. bonus.info2 = reader.readUInt16(); //artifact ID
  512. break;
  513. }
  514. case CampaignBonusType::SPELL_SCROLL:
  515. {
  516. bonus.info1 = reader.readUInt16(); //hero
  517. bonus.info2 = reader.readUInt8(); //spell ID
  518. break;
  519. }
  520. case CampaignBonusType::PRIMARY_SKILL:
  521. {
  522. bonus.info1 = reader.readUInt16(); //hero
  523. bonus.info2 = reader.readUInt32(); //bonuses (4 bytes for 4 skills)
  524. break;
  525. }
  526. case CampaignBonusType::SECONDARY_SKILL:
  527. {
  528. bonus.info1 = reader.readUInt16(); //hero
  529. bonus.info2 = reader.readUInt8(); //skill ID
  530. bonus.info3 = reader.readUInt8(); //skill level
  531. break;
  532. }
  533. case CampaignBonusType::RESOURCE:
  534. {
  535. bonus.info1 = reader.readUInt8(); //type
  536. //FD - wood+ore
  537. //FE - mercury+sulfur+crystal+gem
  538. bonus.info2 = reader.readUInt32(); //count
  539. break;
  540. }
  541. default:
  542. logGlobal->warn("Corrupted h3c file");
  543. break;
  544. }
  545. ret.bonusesToChoose.push_back(bonus);
  546. }
  547. break;
  548. }
  549. case CampaignStartOptions::HERO_CROSSOVER: //reading of players (colors / scenarios ?) player can choose
  550. {
  551. ui8 numOfBonuses = reader.readUInt8();
  552. for (int g=0; g<numOfBonuses; ++g)
  553. {
  554. CampaignBonus bonus;
  555. bonus.type = CampaignBonusType::HEROES_FROM_PREVIOUS_SCENARIO;
  556. bonus.info1 = reader.readUInt8(); //player color
  557. bonus.info2 = reader.readUInt8(); //from what scenario
  558. ret.bonusesToChoose.push_back(bonus);
  559. }
  560. break;
  561. }
  562. case CampaignStartOptions::HERO_OPTIONS: //heroes player can choose between
  563. {
  564. ui8 numOfBonuses = reader.readUInt8();
  565. for (int g=0; g<numOfBonuses; ++g)
  566. {
  567. CampaignBonus bonus;
  568. bonus.type = CampaignBonusType::HERO;
  569. bonus.info1 = reader.readUInt8(); //player color
  570. bonus.info2 = reader.readUInt16(); //hero, FF FF - random
  571. ret.bonusesToChoose.push_back(bonus);
  572. }
  573. break;
  574. }
  575. default:
  576. {
  577. logGlobal->warn("Corrupted h3c file");
  578. break;
  579. }
  580. }
  581. return ret;
  582. }
  583. std::vector< std::vector<ui8> > CampaignHandler::getFile(std::unique_ptr<CInputStream> file, bool headerOnly)
  584. {
  585. CCompressedStream stream(std::move(file), true);
  586. std::vector< std::vector<ui8> > ret;
  587. do
  588. {
  589. std::vector<ui8> block(stream.getSize());
  590. stream.read(block.data(), block.size());
  591. ret.push_back(block);
  592. }
  593. while (!headerOnly && stream.getNextBlock());
  594. return ret;
  595. }
  596. bool CampaignState::conquerable(CampaignScenarioID whichScenario) const
  597. {
  598. //check for void scenraio
  599. if (!scenarios.at(whichScenario).isNotVoid())
  600. {
  601. return false;
  602. }
  603. if (scenarios.at(whichScenario).conquered)
  604. {
  605. return false;
  606. }
  607. //check preconditioned regions
  608. for (auto const & it : scenarios.at(whichScenario).preconditionRegions)
  609. {
  610. if (!scenarios.at(it).conquered)
  611. return false;
  612. }
  613. return true;
  614. }
  615. bool CampaignScenario::isNotVoid() const
  616. {
  617. return !mapName.empty();
  618. }
  619. const CGHeroInstance * CampaignScenario::strongestHero(const PlayerColor & owner)
  620. {
  621. std::function<bool(JsonNode & node)> isOwned = [owner](JsonNode & node)
  622. {
  623. auto * h = CampaignState::crossoverDeserialize(node);
  624. bool result = h->tempOwner == owner;
  625. vstd::clear_pointer(h);
  626. return result;
  627. };
  628. auto ownedHeroes = crossoverHeroes | boost::adaptors::filtered(isOwned);
  629. auto i = vstd::maxElementByFun(ownedHeroes, [](JsonNode & node)
  630. {
  631. auto * h = CampaignState::crossoverDeserialize(node);
  632. double result = h->getHeroStrength();
  633. vstd::clear_pointer(h);
  634. return result;
  635. });
  636. return i == ownedHeroes.end() ? nullptr : CampaignState::crossoverDeserialize(*i);
  637. }
  638. std::vector<CGHeroInstance *> CampaignScenario::getLostCrossoverHeroes()
  639. {
  640. std::vector<CGHeroInstance *> lostCrossoverHeroes;
  641. if(conquered)
  642. {
  643. for(auto node2 : placedCrossoverHeroes)
  644. {
  645. auto * hero = CampaignState::crossoverDeserialize(node2);
  646. auto it = range::find_if(crossoverHeroes, [hero](JsonNode node)
  647. {
  648. auto * h = CampaignState::crossoverDeserialize(node);
  649. bool result = hero->subID == h->subID;
  650. vstd::clear_pointer(h);
  651. return result;
  652. });
  653. if(it == crossoverHeroes.end())
  654. {
  655. lostCrossoverHeroes.push_back(hero);
  656. }
  657. }
  658. }
  659. return lostCrossoverHeroes;
  660. }
  661. void CampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *> & heroes)
  662. {
  663. scenarios.at(*currentMap).crossoverHeroes.clear();
  664. for(CGHeroInstance * hero : heroes)
  665. {
  666. scenarios.at(*currentMap).crossoverHeroes.push_back(crossoverSerialize(hero));
  667. }
  668. mapsConquered.push_back(*currentMap);
  669. mapsRemaining -= *currentMap;
  670. scenarios.at(*currentMap).conquered = true;
  671. }
  672. std::optional<CampaignBonus> CampaignState::getBonusForCurrentMap() const
  673. {
  674. auto bonuses = getCurrentScenario().travelOptions.bonusesToChoose;
  675. assert(chosenCampaignBonuses.count(*currentMap) || bonuses.size() == 0);
  676. if(bonuses.empty())
  677. return std::optional<CampaignBonus>();
  678. else
  679. return bonuses[currentBonusID()];
  680. }
  681. const CampaignScenario & CampaignState::getCurrentScenario() const
  682. {
  683. return scenarios.at(*currentMap);
  684. }
  685. CampaignScenario & CampaignState::getCurrentScenario()
  686. {
  687. return scenarios.at(*currentMap);
  688. }
  689. ui8 CampaignState::currentBonusID() const
  690. {
  691. return chosenCampaignBonuses.at(*currentMap);
  692. }
  693. std::unique_ptr<CMap> CampaignState::getMap(CampaignScenarioID scenarioId) const
  694. {
  695. // FIXME: there is certainly better way to handle maps inside campaigns
  696. if(scenarioId == CampaignScenarioID::NONE)
  697. scenarioId = currentMap.value();
  698. CMapService mapService;
  699. std::string scenarioName = header.filename.substr(0, header.filename.find('.'));
  700. boost::to_lower(scenarioName);
  701. scenarioName += ':' + std::to_string(static_cast<int>(scenarioId));
  702. const std::string & mapContent = mapPieces.find(scenarioId)->second;
  703. const auto * buffer = reinterpret_cast<const ui8 *>(mapContent.data());
  704. return mapService.loadMap(buffer, static_cast<int>(mapContent.size()), scenarioName, header.modName, header.encoding);
  705. }
  706. std::unique_ptr<CMapHeader> CampaignState::getHeader(CampaignScenarioID scenarioId) const
  707. {
  708. if(scenarioId == CampaignScenarioID::NONE)
  709. scenarioId = currentMap.value();
  710. CMapService mapService;
  711. std::string scenarioName = header.filename.substr(0, header.filename.find('.'));
  712. boost::to_lower(scenarioName);
  713. scenarioName += ':' + std::to_string(static_cast<int>(scenarioId));
  714. const std::string & mapContent = mapPieces.find(scenarioId)->second;
  715. const auto * buffer = reinterpret_cast<const ui8 *>(mapContent.data());
  716. return mapService.loadMapHeader(buffer, static_cast<int>(mapContent.size()), scenarioName, header.modName, header.encoding);
  717. }
  718. std::shared_ptr<CMapInfo> CampaignState::getMapInfo(CampaignScenarioID scenarioId) const
  719. {
  720. if(scenarioId == CampaignScenarioID::NONE)
  721. scenarioId = currentMap.value();
  722. auto mapInfo = std::make_shared<CMapInfo>();
  723. mapInfo->fileURI = header.filename;
  724. mapInfo->mapHeader = getHeader(scenarioId);
  725. mapInfo->countPlayers();
  726. return mapInfo;
  727. }
  728. JsonNode CampaignState::crossoverSerialize(CGHeroInstance * hero)
  729. {
  730. JsonNode node;
  731. JsonSerializer handler(nullptr, node);
  732. hero->serializeJsonOptions(handler);
  733. return node;
  734. }
  735. CGHeroInstance * CampaignState::crossoverDeserialize(JsonNode & node)
  736. {
  737. JsonDeserializer handler(nullptr, node);
  738. auto * hero = new CGHeroInstance();
  739. hero->ID = Obj::HERO;
  740. hero->serializeJsonOptions(handler);
  741. return hero;
  742. }
  743. std::string CampaignHandler::prologVideoName(ui8 index)
  744. {
  745. JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
  746. auto vids = config["videos"].Vector();
  747. if(index < vids.size())
  748. return vids[index].String();
  749. return "";
  750. }
  751. std::string CampaignHandler::prologMusicName(ui8 index)
  752. {
  753. std::vector<std::string> music;
  754. return VLC->generaltexth->translate("core.cmpmusic." + std::to_string(static_cast<int>(index)));
  755. }
  756. std::string CampaignHandler::prologVoiceName(ui8 index)
  757. {
  758. JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));
  759. auto audio = config["voice"].Vector();
  760. if(index < audio.size())
  761. return audio[index].String();
  762. return "";
  763. }
  764. VCMI_LIB_NAMESPACE_END