CArtHandler.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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. {
  115. art->bonusesPerLevel.emplace_back(static_cast<ui16>(bonus["level"].Float()), Bonus());
  116. JsonUtils::parseBonus(bonus["bonus"], &art->bonusesPerLevel.back().second);
  117. }
  118. for(auto bonus : node["growing"]["thresholdBonuses"].Vector())
  119. {
  120. art->thresholdBonuses.emplace_back(static_cast<ui16>(bonus["level"].Float()), Bonus());
  121. JsonUtils::parseBonus(bonus["bonus"], &art->thresholdBonuses.back().second);
  122. }
  123. }
  124. art->id = ArtifactID(index);
  125. art->identifier = identifier;
  126. art->modScope = scope;
  127. const JsonNode & text = node["text"];
  128. LIBRARY->generaltexth->registerString(scope, art->getNameTextID(), text["name"]);
  129. LIBRARY->generaltexth->registerString(scope, art->getDescriptionTextID(), text["description"]);
  130. LIBRARY->generaltexth->registerString(scope, art->getEventTextID(), text["event"]);
  131. const JsonNode & graphics = node["graphics"];
  132. art->image = graphics["image"].String();
  133. if(!graphics["scenarioBonus"].isNull())
  134. art->scenarioBonus = graphics["scenarioBonus"].String();
  135. else
  136. art->scenarioBonus = art->image; // MOD COMPATIBILITY fallback for pre-1.7 mods
  137. art->advMapDef = graphics["map"].String();
  138. art->price = static_cast<ui32>(node["value"].Float());
  139. art->onlyOnWaterMap = node["onlyOnWaterMap"].Bool();
  140. loadSlots(art.get(), node);
  141. loadClass(art.get(), node);
  142. loadType(art.get(), node);
  143. loadComponents(art.get(), node);
  144. if (node["bonuses"].isVector())
  145. {
  146. for(const auto & b : node["bonuses"].Vector())
  147. {
  148. auto bonus = JsonUtils::parseBonus(b);
  149. art->addNewBonus(bonus);
  150. }
  151. }
  152. else
  153. {
  154. for(const auto & b : node["bonuses"].Struct())
  155. {
  156. if (b.second.isNull())
  157. continue;
  158. auto bonus = JsonUtils::parseBonus(b.second, art->getBonusTextID(b.first));
  159. art->addNewBonus(bonus);
  160. }
  161. }
  162. for(const auto & b : node["instanceBonuses"].Struct())
  163. {
  164. if (b.second.isNull())
  165. continue;
  166. auto bonus = JsonUtils::parseBonus(b.second, art->getBonusTextID(b.first));
  167. bonus->source = BonusSource::ARTIFACT;
  168. bonus->duration = BonusDuration::PERMANENT;
  169. bonus->description.appendTextID(art->getNameTextID());
  170. bonus->description.appendRawString(" %+d");
  171. art->instanceBonuses.push_back(bonus);
  172. }
  173. const JsonNode & warMachine = node["warMachine"];
  174. if(!warMachine.isNull())
  175. {
  176. LIBRARY->identifiers()->requestIdentifier("creature", warMachine, [art](si32 id)
  177. {
  178. art->warMachine = CreatureID(id);
  179. //this assumes that creature object is stored before registration
  180. LIBRARY->creh->objects.at(id)->warMachine = art->id;
  181. });
  182. }
  183. LIBRARY->identifiers()->requestIdentifier(scope, "object", "artifact", [scope, art](si32 index)
  184. {
  185. JsonNode conf;
  186. conf.setModScope(scope);
  187. LIBRARY->objtypeh->loadSubObject(art->identifier, conf, Obj::ARTIFACT, art->getIndex());
  188. if(!art->advMapDef.empty())
  189. {
  190. JsonNode templ;
  191. templ["animation"].String() = art->advMapDef;
  192. templ.setModScope(scope);
  193. // add new template.
  194. // Necessary for objects added via mods that don't have any templates in H3
  195. LIBRARY->objtypeh->getHandlerFor(Obj::ARTIFACT, art->getIndex())->addTemplate(templ);
  196. }
  197. });
  198. if(art->isTradable())
  199. art->possibleSlots.at(ArtBearer::ALTAR).push_back(ArtifactPosition::ALTAR);
  200. if(!node["charged"].isNull())
  201. {
  202. art->setCondition(stringToDischargeCond(node["charged"]["usageType"].String()));
  203. if(!node["charged"]["removeOnDepletion"].isNull())
  204. art->setRemoveOnDepletion(node["charged"]["removeOnDepletion"].Bool());
  205. if(!node["charged"]["startingCharges"].isNull())
  206. {
  207. const auto charges = node["charged"]["startingCharges"].Integer();
  208. if(charges < 0)
  209. logMod->warn("Warning! Charged artifact %s number of charges cannot be less than zero %d!", art->getNameTranslated(), charges);
  210. else
  211. art->setDefaultStartCharges(charges);
  212. }
  213. if(art->getDischargeCondition() == DischargeArtifactCondition::SPELLCAST && art->getBonusesOfType(BonusType::SPELL)->size() == 0)
  214. logMod->warn("Warning! %s condition of discharge is \"SPELLCAST\", but there is not a single spell.", art->getNameTranslated());
  215. }
  216. return art;
  217. }
  218. int32_t ArtifactPositionBase::decode(const std::string & slotName)
  219. {
  220. #define ART_POS(x) { #x, ArtifactPosition::x },
  221. static const std::map<std::string, ArtifactPosition> artifactPositionMap = { ART_POS_LIST };
  222. #undef ART_POS
  223. auto it = artifactPositionMap.find (slotName);
  224. if (it != artifactPositionMap.end())
  225. return it->second;
  226. else
  227. return PRE_FIRST;
  228. }
  229. std::string ArtifactPositionBase::encode(int32_t index)
  230. {
  231. #define ART_POS(x) #x ,
  232. const std::vector<std::string> artSlots = { ART_POS_LIST };
  233. #undef ART_POS
  234. return artSlots.at(index);
  235. }
  236. std::string ArtifactPositionBase::entityType()
  237. {
  238. return "artifactSlot";
  239. }
  240. void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) const
  241. {
  242. static const std::vector<ArtifactPosition> miscSlots =
  243. {
  244. ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5
  245. };
  246. static const std::vector<ArtifactPosition> ringSlots =
  247. {
  248. ArtifactPosition::RIGHT_RING, ArtifactPosition::LEFT_RING
  249. };
  250. if (slotID == "MISC")
  251. {
  252. vstd::concatenate(art->possibleSlots[ArtBearer::HERO], miscSlots);
  253. }
  254. else if (slotID == "RING")
  255. {
  256. vstd::concatenate(art->possibleSlots[ArtBearer::HERO], ringSlots);
  257. }
  258. else
  259. {
  260. auto slot = ArtifactPosition::decode(slotID);
  261. if (slot != ArtifactPosition::PRE_FIRST)
  262. art->possibleSlots[ArtBearer::HERO].push_back(slot);
  263. }
  264. }
  265. void CArtHandler::loadSlots(CArtifact * art, const JsonNode & node) const
  266. {
  267. if (!node["slot"].isNull()) //we assume non-hero slots are irrelevant?
  268. {
  269. if (node["slot"].getType() == JsonNode::JsonType::DATA_STRING)
  270. addSlot(art, node["slot"].String());
  271. else
  272. {
  273. for (const JsonNode & slot : node["slot"].Vector())
  274. addSlot(art, slot.String());
  275. }
  276. std::sort(art->possibleSlots.at(ArtBearer::HERO).begin(), art->possibleSlots.at(ArtBearer::HERO).end());
  277. }
  278. }
  279. EArtifactClass CArtHandler::stringToClass(const std::string & className)
  280. {
  281. static const std::map<std::string, EArtifactClass> artifactClassMap =
  282. {
  283. {"TREASURE", EArtifactClass::ART_TREASURE},
  284. {"MINOR", EArtifactClass::ART_MINOR},
  285. {"MAJOR", EArtifactClass::ART_MAJOR},
  286. {"RELIC", EArtifactClass::ART_RELIC},
  287. {"SPECIAL", EArtifactClass::ART_SPECIAL}
  288. };
  289. auto it = artifactClassMap.find (className);
  290. if (it != artifactClassMap.end())
  291. return it->second;
  292. logMod->warn("Warning! Artifact rarity %s not recognized!", className);
  293. return EArtifactClass::ART_SPECIAL;
  294. }
  295. DischargeArtifactCondition CArtHandler::stringToDischargeCond(const std::string & cond) const
  296. {
  297. const std::unordered_map<std::string, DischargeArtifactCondition> growingConditionsMap =
  298. {
  299. {"SPELLCAST", DischargeArtifactCondition::SPELLCAST},
  300. {"BATTLE", DischargeArtifactCondition::BATTLE},
  301. //{"BUILDING", DischargeArtifactCondition::BUILDING},
  302. };
  303. return growingConditionsMap.at(cond);
  304. }
  305. void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) const
  306. {
  307. art->aClass = stringToClass(node["class"].String());
  308. }
  309. void CArtHandler::loadType(CArtifact * art, const JsonNode & node) const
  310. {
  311. #define ART_BEARER(x) { #x, ArtBearer::x },
  312. static const std::map<std::string, ArtBearer> artifactBearerMap = { ART_BEARER_LIST };
  313. #undef ART_BEARER
  314. for (const JsonNode & b : node["type"].Vector())
  315. {
  316. auto it = artifactBearerMap.find (b.String());
  317. if (it != artifactBearerMap.end())
  318. {
  319. ArtBearer bearerType = it->second;
  320. switch (bearerType)
  321. {
  322. case ArtBearer::HERO://TODO: allow arts having several possible bearers
  323. break;
  324. case ArtBearer::COMMANDER:
  325. makeItCommanderArt (art); //original artifacts should have only one bearer type
  326. break;
  327. case ArtBearer::CREATURE:
  328. makeItCreatureArt (art);
  329. break;
  330. }
  331. }
  332. else
  333. logMod->warn("Warning! Artifact type %s not recognized!", b.String());
  334. }
  335. }
  336. void CArtHandler::loadComponents(CArtifact * art, const JsonNode & node)
  337. {
  338. if(!node["components"].isNull())
  339. {
  340. for(const auto & component : node["components"].Vector())
  341. {
  342. LIBRARY->identifiers()->requestIdentifier("artifact", component, [this, art](int32_t id)
  343. {
  344. // when this code is called both combinational art as well as component are loaded
  345. // so it is safe to access any of them
  346. art->constituents.push_back(ArtifactID(id).toArtifact());
  347. objects[id]->partOf.insert(art);
  348. });
  349. }
  350. }
  351. if(!node["fusedComponents"].isNull())
  352. art->setFused(node["fusedComponents"].Bool());
  353. }
  354. void CArtHandler::makeItCreatureArt(CArtifact * a, bool onlyCreature)
  355. {
  356. if (onlyCreature)
  357. {
  358. a->possibleSlots[ArtBearer::HERO].clear();
  359. a->possibleSlots[ArtBearer::COMMANDER].clear();
  360. }
  361. a->possibleSlots[ArtBearer::CREATURE].push_back(ArtifactPosition::CREATURE_SLOT);
  362. }
  363. void CArtHandler::makeItCommanderArt(CArtifact * a, bool onlyCommander)
  364. {
  365. if (onlyCommander)
  366. {
  367. a->possibleSlots[ArtBearer::HERO].clear();
  368. a->possibleSlots[ArtBearer::CREATURE].clear();
  369. }
  370. for(const auto & slot : ArtifactUtils::commanderSlots())
  371. a->possibleSlots[ArtBearer::COMMANDER].push_back(ArtifactPosition(slot));
  372. }
  373. bool CArtHandler::legalArtifact(const ArtifactID & id) const
  374. {
  375. auto art = id.toArtifact();
  376. if(art->isCombined())
  377. return false; //no combo artifacts spawning
  378. if(art->aClass < EArtifactClass::ART_TREASURE || art->aClass > EArtifactClass::ART_RELIC)
  379. return false; // invalid class
  380. if(art->possibleSlots.count(ArtBearer::HERO) && !art->possibleSlots.at(ArtBearer::HERO).empty())
  381. return true;
  382. if(art->possibleSlots.count(ArtBearer::CREATURE) && !art->possibleSlots.at(ArtBearer::CREATURE).empty() && LIBRARY->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_ARTIFACT))
  383. return true;
  384. if(art->possibleSlots.count(ArtBearer::COMMANDER) && !art->possibleSlots.at(ArtBearer::COMMANDER).empty() && LIBRARY->engineSettings()->getBoolean(EGameSettings::MODULE_COMMANDERS))
  385. return true;
  386. return false;
  387. }
  388. std::set<ArtifactID> CArtHandler::getDefaultAllowed() const
  389. {
  390. std::set<ArtifactID> allowedArtifacts;
  391. for (const auto & artifact : objects)
  392. {
  393. if (!artifact->isCombined())
  394. allowedArtifacts.insert(artifact->getId());
  395. }
  396. return allowedArtifacts;
  397. }
  398. void CArtHandler::afterLoadFinalization()
  399. {
  400. //All artifacts have their id, so we can properly update their bonuses' source ids.
  401. for(auto &art : objects)
  402. {
  403. for(auto &bonus : art->getExportedBonusList())
  404. {
  405. assert(bonus->source == BonusSource::ARTIFACT);
  406. bonus->sid = BonusSourceID(art->id);
  407. }
  408. art->nodeHasChanged();
  409. }
  410. }
  411. VCMI_LIB_NAMESPACE_END