CArtHandler.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. /*
  2. * CArtHandler.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 "CArtHandler.h"
  12. #include "ArtifactUtils.h"
  13. #include "../../CCreatureHandler.h"
  14. #include "../../ExceptionsCommon.h"
  15. #include "../../GameLibrary.h"
  16. #include "../../IGameSettings.h"
  17. #include "../../json/JsonBonus.h"
  18. #include "../../mapObjectConstructors/AObjectTypeHandler.h"
  19. #include "../../mapObjectConstructors/CObjectClassesHandler.h"
  20. #include "../../modding/IdentifierStorage.h"
  21. #include "../../texts/CGeneralTextHandler.h"
  22. #include "../../texts/CLegacyConfigParser.h"
  23. // Note: list must match entries in ArtTraits.txt
  24. #define ART_POS_LIST \
  25. ART_POS(SPELLBOOK) \
  26. ART_POS(MACH4) \
  27. ART_POS(MACH3) \
  28. ART_POS(MACH2) \
  29. ART_POS(MACH1) \
  30. ART_POS(MISC5) \
  31. ART_POS(MISC4) \
  32. ART_POS(MISC3) \
  33. ART_POS(MISC2) \
  34. ART_POS(MISC1) \
  35. ART_POS(FEET) \
  36. ART_POS(LEFT_RING) \
  37. ART_POS(RIGHT_RING) \
  38. ART_POS(TORSO) \
  39. ART_POS(LEFT_HAND) \
  40. ART_POS(RIGHT_HAND) \
  41. ART_POS(NECK) \
  42. ART_POS(SHOULDERS) \
  43. ART_POS(HEAD)
  44. VCMI_LIB_NAMESPACE_BEGIN
  45. CArtHandler::~CArtHandler() = default;
  46. std::vector<JsonNode> CArtHandler::loadLegacyData()
  47. {
  48. size_t dataSize = LIBRARY->engineSettings()->getInteger(EGameSettings::TEXTS_ARTIFACT);
  49. objects.resize(dataSize);
  50. std::vector<JsonNode> h3Data;
  51. h3Data.reserve(dataSize);
  52. #define ART_POS(x) #x ,
  53. const std::vector<std::string> artSlots = { ART_POS_LIST };
  54. #undef ART_POS
  55. static const std::map<char, std::string> classes =
  56. {{'S',"SPECIAL"}, {'T',"TREASURE"},{'N',"MINOR"},{'J',"MAJOR"},{'R',"RELIC"},};
  57. CLegacyConfigParser parser(TextPath::builtin("DATA/ARTRAITS.TXT"));
  58. CLegacyConfigParser events(TextPath::builtin("DATA/ARTEVENT.TXT"));
  59. parser.endLine(); // header
  60. parser.endLine();
  61. for (size_t i = 0; i < dataSize; i++)
  62. {
  63. JsonNode artData;
  64. artData["text"]["name"].String() = parser.readString();
  65. artData["text"]["event"].String() = events.readString();
  66. artData["value"].Float() = parser.readNumber();
  67. for(const auto & artSlot : artSlots)
  68. {
  69. if(parser.readString() == "x")
  70. {
  71. artData["slot"].Vector().emplace_back(artSlot);
  72. }
  73. }
  74. std::string artClass = parser.readString();
  75. if (classes.count(artClass[0]))
  76. artData["class"].String() = classes.at(artClass[0]);
  77. else
  78. throw DataLoadingException("File ARTRAITS.TXT is invalid or corrupted! Please reinstall Heroes III data files");
  79. artData["text"]["description"].String() = parser.readString();
  80. parser.endLine();
  81. events.endLine();
  82. h3Data.push_back(artData);
  83. }
  84. return h3Data;
  85. }
  86. void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data)
  87. {
  88. auto object = loadFromJson(scope, data, name, objects.size());
  89. object->iconIndex = object->getIndex() + 5;
  90. objects.emplace_back(object);
  91. registerObject(scope, "artifact", name, object->id.getNum());
  92. }
  93. void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index)
  94. {
  95. auto object = loadFromJson(scope, data, name, index);
  96. object->iconIndex = object->getIndex();
  97. assert(objects[index] == nullptr); // ensure that this id was not loaded before
  98. objects[index] = object;
  99. registerObject(scope, "artifact", name, object->id.getNum());
  100. }
  101. const std::vector<std::string> & CArtHandler::getTypeNames() const
  102. {
  103. static const std::vector<std::string> typeNames = { "artifact" };
  104. return typeNames;
  105. }
  106. std::shared_ptr<CArtifact> CArtHandler::loadFromJson(const std::string & scope, const JsonNode & node, const std::string & identifier, size_t index)
  107. {
  108. assert(identifier.find(':') == std::string::npos);
  109. assert(!scope.empty());
  110. auto art = std::make_shared<CArtifact>();
  111. if(!node["growing"].isNull())
  112. {
  113. for(auto bonus : node["growing"]["bonusesPerLevel"].Vector())
  114. art->bonusesPerLevel.emplace_back(static_cast<ui16>(bonus["level"].Float()), JsonUtils::parseBonus(bonus["bonus"]));
  115. for(auto bonus : node["growing"]["thresholdBonuses"].Vector())
  116. art->thresholdBonuses.emplace_back(static_cast<ui16>(bonus["level"].Float()), JsonUtils::parseBonus(bonus["bonus"]));
  117. }
  118. art->id = ArtifactID(index);
  119. art->identifier = identifier;
  120. art->modScope = scope;
  121. const JsonNode & text = node["text"];
  122. LIBRARY->generaltexth->registerString(scope, art->getNameTextID(), text["name"]);
  123. LIBRARY->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"]);
  124. LIBRARY->generaltexth->registerString(scope, art->getEventTextID(), text["event"]);
  125. const JsonNode & graphics = node["graphics"];
  126. art->image = graphics["image"].String();
  127. if(!graphics["scenarioBonus"].isNull())
  128. art->scenarioBonus = graphics["scenarioBonus"].String();
  129. else
  130. art->scenarioBonus = art->image; // MOD COMPATIBILITY fallback for pre-1.7 mods
  131. art->advMapDef = graphics["map"].String();
  132. art->price = static_cast<ui32>(node["value"].Float());
  133. art->onlyOnWaterMap = node["onlyOnWaterMap"].Bool();
  134. loadSlots(art.get(), node);
  135. loadClass(art.get(), node);
  136. loadType(art.get(), node);
  137. loadComponents(art.get(), node);
  138. if (node["bonuses"].isVector())
  139. {
  140. for(const auto & b : node["bonuses"].Vector())
  141. {
  142. auto bonus = JsonUtils::parseBonus(b);
  143. bonus->sid = art->getId();
  144. art->addNewBonus(bonus);
  145. }
  146. }
  147. else
  148. {
  149. for(const auto & b : node["bonuses"].Struct())
  150. {
  151. if (b.second.isNull())
  152. continue;
  153. auto bonus = JsonUtils::parseBonus(b.second, art->getBonusTextID(b.first));
  154. bonus->sid = art->getId();
  155. art->addNewBonus(bonus);
  156. }
  157. }
  158. for(const auto & b : node["instanceBonuses"].Struct())
  159. {
  160. if (b.second.isNull())
  161. continue;
  162. auto bonus = JsonUtils::parseBonus(b.second, art->getBonusTextID(b.first));
  163. bonus->sid = art->getId();
  164. bonus->source = BonusSource::ARTIFACT;
  165. bonus->duration = BonusDuration::PERMANENT;
  166. bonus->description.appendTextID(art->getNameTextID());
  167. bonus->description.appendRawString(" %+d");
  168. art->instanceBonuses.push_back(bonus);
  169. }
  170. const JsonNode & warMachine = node["warMachine"];
  171. if(!warMachine.isNull())
  172. {
  173. LIBRARY->identifiers()->requestIdentifier("creature", warMachine, [art](si32 id)
  174. {
  175. art->warMachine = CreatureID(id);
  176. //this assumes that creature object is stored before registration
  177. LIBRARY->creh->objects.at(id)->warMachine = art->id;
  178. });
  179. }
  180. LIBRARY->identifiers()->requestIdentifier(scope, "object", "artifact", [scope, art](si32 index)
  181. {
  182. JsonNode conf;
  183. conf.setModScope(scope);
  184. LIBRARY->objtypeh->loadSubObject(art->identifier, conf, Obj::ARTIFACT, art->getIndex());
  185. if(!art->advMapDef.empty())
  186. {
  187. JsonNode templ;
  188. templ["animation"].String() = art->advMapDef;
  189. templ.setModScope(scope);
  190. // add new template.
  191. // Necessary for objects added via mods that don't have any templates in H3
  192. LIBRARY->objtypeh->getHandlerFor(Obj::ARTIFACT, art->getIndex())->addTemplate(templ);
  193. }
  194. });
  195. if(art->isTradable())
  196. art->possibleSlots.at(ArtBearer::ALTAR).push_back(ArtifactPosition::ALTAR);
  197. if(!node["charged"].isNull())
  198. {
  199. art->setCondition(stringToDischargeCond(node["charged"]["usageType"].String()));
  200. if(!node["charged"]["removeOnDepletion"].isNull())
  201. art->setRemoveOnDepletion(node["charged"]["removeOnDepletion"].Bool());
  202. if(!node["charged"]["startingCharges"].isNull())
  203. {
  204. const auto charges = node["charged"]["startingCharges"].Integer();
  205. if(charges < 0)
  206. logMod->warn("Warning! Charged artifact %s number of charges cannot be less than zero %d!", art->getNameTranslated(), charges);
  207. else
  208. art->setDefaultStartCharges(charges);
  209. }
  210. }
  211. // Some bonuses must be located in the instance.
  212. for(const auto & b : art->getExportedBonusList())
  213. {
  214. if(std::dynamic_pointer_cast<const HasChargesLimiter>(b->limiter))
  215. {
  216. b->source = BonusSource::ARTIFACT;
  217. b->duration = BonusDuration::PERMANENT;
  218. b->description.appendTextID(art->getNameTextID());
  219. b->description.appendRawString(" %+d");
  220. art->instanceBonuses.push_back(b);
  221. art->removeBonus(b);
  222. }
  223. }
  224. return art;
  225. }
  226. int32_t ArtifactPositionBase::decode(const std::string & slotName)
  227. {
  228. #define ART_POS(x) { #x, ArtifactPosition::x },
  229. static const std::map<std::string, ArtifactPosition> artifactPositionMap = { ART_POS_LIST };
  230. #undef ART_POS
  231. auto it = artifactPositionMap.find (slotName);
  232. if (it != artifactPositionMap.end())
  233. return it->second;
  234. else
  235. return PRE_FIRST;
  236. }
  237. std::string ArtifactPositionBase::encode(int32_t index)
  238. {
  239. #define ART_POS(x) #x ,
  240. const std::vector<std::string> artSlots = { ART_POS_LIST };
  241. #undef ART_POS
  242. return artSlots.at(index);
  243. }
  244. std::string ArtifactPositionBase::entityType()
  245. {
  246. return "artifactSlot";
  247. }
  248. void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) const
  249. {
  250. static const std::vector<ArtifactPosition> miscSlots =
  251. {
  252. ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5
  253. };
  254. static const std::vector<ArtifactPosition> ringSlots =
  255. {
  256. ArtifactPosition::RIGHT_RING, ArtifactPosition::LEFT_RING
  257. };
  258. if (slotID == "MISC")
  259. {
  260. vstd::concatenate(art->possibleSlots[ArtBearer::HERO], miscSlots);
  261. }
  262. else if (slotID == "RING")
  263. {
  264. vstd::concatenate(art->possibleSlots[ArtBearer::HERO], ringSlots);
  265. }
  266. else
  267. {
  268. auto slot = ArtifactPosition::decode(slotID);
  269. if (slot != ArtifactPosition::PRE_FIRST)
  270. art->possibleSlots[ArtBearer::HERO].push_back(slot);
  271. }
  272. }
  273. void CArtHandler::loadSlots(CArtifact * art, const JsonNode & node) const
  274. {
  275. if (!node["slot"].isNull()) //we assume non-hero slots are irrelevant?
  276. {
  277. if (node["slot"].getType() == JsonNode::JsonType::DATA_STRING)
  278. addSlot(art, node["slot"].String());
  279. else
  280. {
  281. for (const JsonNode & slot : node["slot"].Vector())
  282. addSlot(art, slot.String());
  283. }
  284. std::sort(art->possibleSlots.at(ArtBearer::HERO).begin(), art->possibleSlots.at(ArtBearer::HERO).end());
  285. }
  286. }
  287. EArtifactClass CArtHandler::stringToClass(const std::string & className)
  288. {
  289. static const std::map<std::string, EArtifactClass> artifactClassMap =
  290. {
  291. {"TREASURE", EArtifactClass::ART_TREASURE},
  292. {"MINOR", EArtifactClass::ART_MINOR},
  293. {"MAJOR", EArtifactClass::ART_MAJOR},
  294. {"RELIC", EArtifactClass::ART_RELIC},
  295. {"SPECIAL", EArtifactClass::ART_SPECIAL}
  296. };
  297. auto it = artifactClassMap.find (className);
  298. if (it != artifactClassMap.end())
  299. return it->second;
  300. logMod->warn("Warning! Artifact rarity %s not recognized!", className);
  301. return EArtifactClass::ART_SPECIAL;
  302. }
  303. DischargeArtifactCondition CArtHandler::stringToDischargeCond(const std::string & cond) const
  304. {
  305. const std::unordered_map<std::string, DischargeArtifactCondition> growingConditionsMap =
  306. {
  307. {"SPELLCAST", DischargeArtifactCondition::SPELLCAST},
  308. {"BATTLE", DischargeArtifactCondition::BATTLE},
  309. //{"BUILDING", DischargeArtifactCondition::BUILDING},
  310. };
  311. return growingConditionsMap.at(cond);
  312. }
  313. void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) const
  314. {
  315. art->aClass = stringToClass(node["class"].String());
  316. }
  317. void CArtHandler::loadType(CArtifact * art, const JsonNode & node) const
  318. {
  319. #define ART_BEARER(x) { #x, ArtBearer::x },
  320. static const std::map<std::string, ArtBearer> artifactBearerMap = { ART_BEARER_LIST };
  321. #undef ART_BEARER
  322. for (const JsonNode & b : node["type"].Vector())
  323. {
  324. auto it = artifactBearerMap.find (b.String());
  325. if (it != artifactBearerMap.end())
  326. {
  327. ArtBearer bearerType = it->second;
  328. switch (bearerType)
  329. {
  330. case ArtBearer::HERO://TODO: allow arts having several possible bearers
  331. break;
  332. case ArtBearer::COMMANDER:
  333. makeItCommanderArt (art); //original artifacts should have only one bearer type
  334. break;
  335. case ArtBearer::CREATURE:
  336. makeItCreatureArt (art);
  337. break;
  338. }
  339. }
  340. else
  341. logMod->warn("Warning! Artifact type %s not recognized!", b.String());
  342. }
  343. }
  344. void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node)
  345. {
  346. if(!node["components"].isNull())
  347. {
  348. for(const auto & component : node["components"].Vector())
  349. {
  350. LIBRARY->identifiers()->requestIdentifier("artifact", component, [this, art](int32_t id)
  351. {
  352. // when this code is called both combinational art as well as component are loaded
  353. // so it is safe to access any of them
  354. art->constituents.push_back(ArtifactID(id).toArtifact());
  355. objects[id]->partOf.insert(art);
  356. });
  357. }
  358. }
  359. if(!node["fusedComponents"].isNull())
  360. art->setFused(node["fusedComponents"].Bool());
  361. }
  362. void CArtHandler::makeItCreatureArt(CArtifact * a, bool onlyCreature)
  363. {
  364. if (onlyCreature)
  365. {
  366. a->possibleSlots[ArtBearer::HERO].clear();
  367. a->possibleSlots[ArtBearer::COMMANDER].clear();
  368. }
  369. a->possibleSlots[ArtBearer::CREATURE].push_back(ArtifactPosition::CREATURE_SLOT);
  370. }
  371. void CArtHandler::makeItCommanderArt(CArtifact * a, bool onlyCommander)
  372. {
  373. if (onlyCommander)
  374. {
  375. a->possibleSlots[ArtBearer::HERO].clear();
  376. a->possibleSlots[ArtBearer::CREATURE].clear();
  377. }
  378. for(const auto & slot : ArtifactUtils::commanderSlots())
  379. a->possibleSlots[ArtBearer::COMMANDER].push_back(ArtifactPosition(slot));
  380. }
  381. bool CArtHandler::legalArtifact(const ArtifactID & id) const
  382. {
  383. auto art = id.toArtifact();
  384. if(art->isCombined())
  385. return false; //no combo artifacts spawning
  386. if(art->aClass < EArtifactClass::ART_TREASURE || art->aClass > EArtifactClass::ART_RELIC)
  387. return false; // invalid class
  388. if(art->possibleSlots.count(ArtBearer::HERO) && !art->possibleSlots.at(ArtBearer::HERO).empty())
  389. return true;
  390. if(art->possibleSlots.count(ArtBearer::CREATURE) && !art->possibleSlots.at(ArtBearer::CREATURE).empty() && LIBRARY->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_ARTIFACT))
  391. return true;
  392. if(art->possibleSlots.count(ArtBearer::COMMANDER) && !art->possibleSlots.at(ArtBearer::COMMANDER).empty() && LIBRARY->engineSettings()->getBoolean(EGameSettings::MODULE_COMMANDERS))
  393. return true;
  394. return false;
  395. }
  396. std::set<ArtifactID> CArtHandler::getDefaultAllowed() const
  397. {
  398. std::set<ArtifactID> allowedArtifacts;
  399. for (const auto & artifact : objects)
  400. {
  401. if (!artifact->isCombined())
  402. allowedArtifacts.insert(artifact->getId());
  403. }
  404. return allowedArtifacts;
  405. }
  406. void CArtHandler::afterLoadFinalization()
  407. {
  408. //All artifacts have their id, so we can properly update their bonuses' source ids.
  409. for(auto &art : objects)
  410. {
  411. for(auto &bonus : art->getExportedBonusList())
  412. {
  413. assert(bonus->source == BonusSource::ARTIFACT);
  414. bonus->sid = BonusSourceID(art->id);
  415. }
  416. art->nodeHasChanged();
  417. }
  418. }
  419. VCMI_LIB_NAMESPACE_END