JsonBonus.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. /*
  2. * JsonUtils.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 "JsonBonus.h"
  12. #include "JsonValidator.h"
  13. #include "../texts/CGeneralTextHandler.h"
  14. #include "../GameLibrary.h"
  15. #include "../bonuses/Limiters.h"
  16. #include "../bonuses/Propagators.h"
  17. #include "../bonuses/Updaters.h"
  18. #include "../CBonusTypeHandler.h"
  19. #include "../constants/StringConstants.h"
  20. #include "../modding/IdentifierStorage.h"
  21. VCMI_LIB_USING_NAMESPACE
  22. template <typename T>
  23. const T parseByMap(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
  24. {
  25. if (!val->isNull())
  26. {
  27. const std::string & type = val->String();
  28. auto it = map.find(type);
  29. if (it == map.end())
  30. {
  31. logMod->error("Error: invalid %s%s.", err, type);
  32. return {};
  33. }
  34. else
  35. {
  36. return it->second;
  37. }
  38. }
  39. else
  40. return {};
  41. }
  42. template <typename T>
  43. const T parseByMapN(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
  44. {
  45. if(val->isNumber())
  46. return static_cast<T>(val->Integer());
  47. else
  48. return parseByMap<T>(map, val, err);
  49. }
  50. static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node)
  51. {
  52. if (node.isNull())
  53. {
  54. subtype = BonusSubtypeID();
  55. return;
  56. }
  57. if (node.isNumber()) // Compatibility code for 1.3 or older
  58. {
  59. logMod->warn("Bonus subtype must be string! (%s)", node.getModScope());
  60. subtype = BonusCustomSubtype(node.Integer());
  61. return;
  62. }
  63. if (!node.isString())
  64. {
  65. logMod->warn("Bonus subtype must be string! (%s)", node.getModScope());
  66. subtype = BonusSubtypeID();
  67. return;
  68. }
  69. switch (type)
  70. {
  71. case BonusType::MAGIC_SCHOOL_SKILL:
  72. case BonusType::SPELL_DAMAGE:
  73. case BonusType::SPELLS_OF_SCHOOL:
  74. case BonusType::SPELL_DAMAGE_REDUCTION:
  75. case BonusType::SPELL_SCHOOL_IMMUNITY:
  76. case BonusType::NEGATIVE_EFFECTS_IMMUNITY:
  77. {
  78. LIBRARY->identifiers()->requestIdentifier( "spellSchool", node, [&subtype](int32_t identifier)
  79. {
  80. subtype = SpellSchool(identifier);
  81. });
  82. break;
  83. }
  84. case BonusType::HATES_TRAIT:
  85. {
  86. LIBRARY->identifiers()->requestIdentifier( "bonus", node, [&subtype](int32_t identifier)
  87. {
  88. subtype = BonusType(identifier);
  89. });
  90. break;
  91. }
  92. case BonusType::NO_TERRAIN_PENALTY:
  93. {
  94. LIBRARY->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier)
  95. {
  96. subtype = TerrainId(identifier);
  97. });
  98. break;
  99. }
  100. case BonusType::PRIMARY_SKILL:
  101. {
  102. LIBRARY->identifiers()->requestIdentifier( "primarySkill", node, [&subtype](int32_t identifier)
  103. {
  104. subtype = PrimarySkill(identifier);
  105. });
  106. break;
  107. }
  108. case BonusType::IMPROVED_NECROMANCY:
  109. case BonusType::HERO_GRANTS_ATTACKS:
  110. case BonusType::BONUS_DAMAGE_CHANCE:
  111. case BonusType::BONUS_DAMAGE_PERCENTAGE:
  112. case BonusType::SPECIAL_UPGRADE:
  113. case BonusType::HATE:
  114. case BonusType::SUMMON_GUARDIANS:
  115. case BonusType::MANUAL_CONTROL:
  116. case BonusType::SKELETON_TRANSFORMER_TARGET:
  117. {
  118. LIBRARY->identifiers()->requestIdentifier( "creature", node, [&subtype](int32_t identifier)
  119. {
  120. subtype = CreatureID(identifier);
  121. });
  122. break;
  123. }
  124. case BonusType::SPELL_IMMUNITY:
  125. case BonusType::SPELL_DURATION:
  126. case BonusType::SPECIAL_ADD_VALUE_ENCHANT:
  127. case BonusType::SPECIAL_FIXED_VALUE_ENCHANT:
  128. case BonusType::SPECIAL_PECULIAR_ENCHANT:
  129. case BonusType::SPECIAL_SPELL_LEV:
  130. case BonusType::SPECIFIC_SPELL_DAMAGE:
  131. case BonusType::SPECIFIC_SPELL_RANGE:
  132. case BonusType::SPELL:
  133. case BonusType::OPENING_BATTLE_SPELL:
  134. case BonusType::SPELL_LIKE_ATTACK:
  135. case BonusType::CATAPULT:
  136. case BonusType::CATAPULT_EXTRA_SHOTS:
  137. case BonusType::HEALER:
  138. case BonusType::SPELLCASTER:
  139. case BonusType::ENCHANTER:
  140. case BonusType::SPELL_AFTER_ATTACK:
  141. case BonusType::SPELL_BEFORE_ATTACK:
  142. case BonusType::SPECIFIC_SPELL_POWER:
  143. case BonusType::ENCHANTED:
  144. case BonusType::MORE_DAMAGE_FROM_SPELL:
  145. case BonusType::NOT_ACTIVE:
  146. {
  147. LIBRARY->identifiers()->requestIdentifier( "spell", node, [&subtype](int32_t identifier)
  148. {
  149. subtype = SpellID(identifier);
  150. });
  151. break;
  152. }
  153. case BonusType::GENERATE_RESOURCE:
  154. case BonusType::RESOURCES_CONSTANT_BOOST:
  155. case BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST:
  156. {
  157. LIBRARY->identifiers()->requestIdentifier( "resource", node, [&subtype](int32_t identifier)
  158. {
  159. subtype = GameResID(identifier);
  160. });
  161. break;
  162. }
  163. case BonusType::MOVEMENT:
  164. case BonusType::WATER_WALKING:
  165. case BonusType::FLYING_MOVEMENT:
  166. case BonusType::NEGATE_ALL_NATURAL_IMMUNITIES:
  167. case BonusType::CREATURE_DAMAGE:
  168. case BonusType::FLYING:
  169. case BonusType::FIRST_STRIKE:
  170. case BonusType::GENERAL_DAMAGE_REDUCTION:
  171. case BonusType::PERCENTAGE_DAMAGE_BOOST:
  172. case BonusType::SOUL_STEAL:
  173. case BonusType::TRANSMUTATION:
  174. case BonusType::DESTRUCTION:
  175. case BonusType::DEATH_STARE:
  176. case BonusType::REBIRTH:
  177. case BonusType::VISIONS:
  178. case BonusType::SPELLS_OF_LEVEL: // spell level
  179. case BonusType::CREATURE_GROWTH: // creature level
  180. {
  181. LIBRARY->identifiers()->requestIdentifier( "bonusSubtype", node, [&subtype](int32_t identifier)
  182. {
  183. subtype = BonusCustomSubtype(identifier);
  184. });
  185. break;
  186. }
  187. default:
  188. {
  189. logMod->warn("Bonus type %s does not supports subtypes!", LIBRARY->bth->bonusToString(type));
  190. subtype = BonusSubtypeID();
  191. }
  192. }
  193. }
  194. static void loadBonusAddInfo(CAddInfo & var, BonusType type, const JsonNode & value)
  195. {
  196. const auto & getFirstValue = [](const JsonNode & jsonNode) -> const JsonNode &
  197. {
  198. if (jsonNode.isVector())
  199. return jsonNode[0];
  200. else
  201. return jsonNode;
  202. };
  203. if (value.isNull())
  204. return;
  205. switch (type)
  206. {
  207. case BonusType::IMPROVED_NECROMANCY:
  208. case BonusType::SPECIAL_ADD_VALUE_ENCHANT:
  209. case BonusType::SPECIAL_FIXED_VALUE_ENCHANT:
  210. case BonusType::DESTRUCTION:
  211. case BonusType::LIMITED_SHOOTING_RANGE:
  212. case BonusType::ACID_BREATH:
  213. case BonusType::BIND_EFFECT:
  214. case BonusType::SPELLCASTER:
  215. case BonusType::FEROCITY:
  216. case BonusType::PRIMARY_SKILL:
  217. case BonusType::ENCHANTER:
  218. case BonusType::SPECIAL_PECULIAR_ENCHANT:
  219. case BonusType::SPELL_IMMUNITY:
  220. case BonusType::DARKNESS:
  221. case BonusType::FULL_MAP_SCOUTING:
  222. case BonusType::FULL_MAP_DARKNESS:
  223. case BonusType::OPENING_BATTLE_SPELL:
  224. // 1 number
  225. var = getFirstValue(value).Integer();
  226. break;
  227. case BonusType::SPECIAL_UPGRADE:
  228. case BonusType::TRANSMUTATION:
  229. // 1 creature ID
  230. LIBRARY->identifiers()->requestIdentifier("creature", getFirstValue(value), [&](si32 identifier) { var = identifier; });
  231. break;
  232. case BonusType::DEATH_STARE:
  233. // 1 spell ID
  234. LIBRARY->identifiers()->requestIdentifier("spell", getFirstValue(value), [&](si32 identifier) { var = identifier; });
  235. break;
  236. case BonusType::SPELL_BEFORE_ATTACK:
  237. case BonusType::SPELL_AFTER_ATTACK:
  238. // 3 numbers
  239. if (value.isNumber())
  240. {
  241. var = getFirstValue(value).Integer();
  242. }
  243. else
  244. {
  245. var.resize(3);
  246. var[0] = value[0].Integer();
  247. var[1] = value[1].Integer();
  248. var[2] = value[2].Integer();
  249. }
  250. break;
  251. case BonusType::MULTIHEX_UNIT_ATTACK:
  252. case BonusType::MULTIHEX_ENEMY_ATTACK:
  253. case BonusType::MULTIHEX_ANIMATION:
  254. for (const auto & sequence : value.Vector())
  255. {
  256. static const std::map<char, int> charToDirection = {
  257. { 'f', 1 }, { 'l', 6}, {'r', 2}, {'b', 4}
  258. };
  259. int converted = 0;
  260. for (const auto & ch : boost::adaptors::reverse(sequence.String()))
  261. {
  262. char chLower = std::tolower(ch);
  263. if (charToDirection.count(chLower))
  264. converted = 10 * converted + charToDirection.at(chLower);
  265. }
  266. var.push_back(converted);
  267. }
  268. break;
  269. default:
  270. logMod->warn("Bonus type %s does not supports addInfo!", LIBRARY->bth->bonusToString(type) );
  271. }
  272. }
  273. static void loadBonusSourceInstance(BonusSourceID & sourceInstance, BonusSource sourceType, const JsonNode & node)
  274. {
  275. if (node.isNull())
  276. {
  277. sourceInstance = BonusCustomSource();
  278. return;
  279. }
  280. if (node.isNumber()) // Compatibility code for 1.3 or older
  281. {
  282. logMod->warn("Bonus source must be string!");
  283. sourceInstance = BonusCustomSource(node.Integer());
  284. return;
  285. }
  286. if (!node.isString())
  287. {
  288. logMod->warn("Bonus source must be string!");
  289. sourceInstance = BonusCustomSource();
  290. return;
  291. }
  292. switch (sourceType)
  293. {
  294. case BonusSource::ARTIFACT:
  295. case BonusSource::ARTIFACT_INSTANCE:
  296. {
  297. LIBRARY->identifiers()->requestIdentifier( "artifact", node, [&sourceInstance](int32_t identifier)
  298. {
  299. sourceInstance = ArtifactID(identifier);
  300. });
  301. break;
  302. }
  303. case BonusSource::OBJECT_TYPE:
  304. {
  305. LIBRARY->identifiers()->requestIdentifier( "object", node, [&sourceInstance](int32_t identifier)
  306. {
  307. sourceInstance = Obj(identifier);
  308. });
  309. break;
  310. }
  311. case BonusSource::OBJECT_INSTANCE:
  312. case BonusSource::HERO_BASE_SKILL:
  313. sourceInstance = ObjectInstanceID(ObjectInstanceID::decode(node.String()));
  314. break;
  315. case BonusSource::CREATURE_ABILITY:
  316. {
  317. LIBRARY->identifiers()->requestIdentifier( "creature", node, [&sourceInstance](int32_t identifier)
  318. {
  319. sourceInstance = CreatureID(identifier);
  320. });
  321. break;
  322. }
  323. case BonusSource::TERRAIN_OVERLAY:
  324. {
  325. LIBRARY->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier)
  326. {
  327. sourceInstance = BattleField(identifier);
  328. });
  329. break;
  330. }
  331. case BonusSource::SPELL_EFFECT:
  332. {
  333. LIBRARY->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier)
  334. {
  335. sourceInstance = SpellID(identifier);
  336. });
  337. break;
  338. }
  339. case BonusSource::TOWN_STRUCTURE:
  340. assert(0); // TODO
  341. sourceInstance = BuildingTypeUniqueID();
  342. break;
  343. case BonusSource::SECONDARY_SKILL:
  344. {
  345. LIBRARY->identifiers()->requestIdentifier( "secondarySkill", node, [&sourceInstance](int32_t identifier)
  346. {
  347. sourceInstance = SecondarySkill(identifier);
  348. });
  349. break;
  350. }
  351. case BonusSource::HERO_SPECIAL:
  352. {
  353. LIBRARY->identifiers()->requestIdentifier( "hero", node, [&sourceInstance](int32_t identifier)
  354. {
  355. sourceInstance = HeroTypeID(identifier);
  356. });
  357. break;
  358. }
  359. case BonusSource::CAMPAIGN_BONUS:
  360. sourceInstance = CampaignScenarioID(CampaignScenarioID::decode(node.String()));
  361. break;
  362. case BonusSource::ARMY:
  363. case BonusSource::STACK_EXPERIENCE:
  364. case BonusSource::COMMANDER:
  365. case BonusSource::GLOBAL:
  366. case BonusSource::TERRAIN_NATIVE:
  367. case BonusSource::OTHER:
  368. default:
  369. sourceInstance = BonusSourceID();
  370. break;
  371. }
  372. }
  373. static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
  374. {
  375. const std::map<std::string, std::shared_ptr<IUpdater>> bonusUpdaterMap =
  376. {
  377. {"TIMES_HERO_LEVEL", std::make_shared<TimesHeroLevelUpdater>()},
  378. {"TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL", std::make_shared<TimesHeroLevelDivideStackLevelUpdater>()},
  379. {"DIVIDE_STACK_LEVEL", std::make_shared<DivideStackLevelUpdater>()},
  380. {"TIMES_STACK_LEVEL", std::make_shared<TimesStackLevelUpdater>()},
  381. {"TIMES_STACK_SIZE", std::make_shared<TimesStackSizeUpdater>()},
  382. {"BONUS_OWNER_UPDATER", std::make_shared<OwnerUpdater>()}
  383. };
  384. switch(updaterJson.getType())
  385. {
  386. case JsonNode::JsonType::DATA_STRING:
  387. {
  388. auto it = bonusUpdaterMap.find(updaterJson.String());
  389. if (it != bonusUpdaterMap.end())
  390. return it->second;
  391. logGlobal->error("Unknown bonus updater type '%s'", updaterJson.String());
  392. return nullptr;
  393. }
  394. case JsonNode::JsonType::DATA_STRUCT:
  395. if(updaterJson["type"].String() == "GROWS_WITH_LEVEL")
  396. {
  397. // MOD COMPATIBILITY - parameters is deprecated in 1.7
  398. const JsonNode & param = updaterJson["parameters"];
  399. int valPer20 = updaterJson["valPer20"].isNull() ? param[0].Integer() : updaterJson["valPer20"].Integer();
  400. int stepSize = updaterJson["stepSize"].isNull() ? param[1].Integer() : updaterJson["stepSize"].Integer();
  401. return std::make_shared<GrowsWithLevelUpdater>(valPer20, std::max(1, stepSize));
  402. }
  403. if(updaterJson["type"].String() == "TIMES_HERO_LEVEL")
  404. {
  405. int stepSize = updaterJson["stepSize"].Integer();
  406. return std::make_shared<TimesHeroLevelUpdater>(std::max(1, stepSize));
  407. }
  408. if(updaterJson["type"].String() == "TIMES_STACK_SIZE")
  409. {
  410. int minimum = updaterJson["minimum"].isNull() ? std::numeric_limits<int>::min() : updaterJson["minimum"].Integer();
  411. int maximum = updaterJson["maximum"].isNull() ? std::numeric_limits<int>::max() : updaterJson["maximum"].Integer();
  412. int stepSize = updaterJson["stepSize"].Integer();
  413. if (minimum > maximum)
  414. {
  415. logMod->warn("TIMES_STACK_SIZE updater: minimum value (%d) is above maximum value(%d)!", minimum, maximum);
  416. return std::make_shared<TimesStackSizeUpdater>(maximum, minimum, std::max(1, stepSize));
  417. }
  418. return std::make_shared<TimesStackSizeUpdater>(minimum, maximum, std::max(1, stepSize));
  419. }
  420. if(updaterJson["type"].String() == "TIMES_ARMY_SIZE")
  421. {
  422. auto result = std::make_shared<TimesArmySizeUpdater>();
  423. result->minimum = updaterJson["minimum"].isNull() ? std::numeric_limits<int>::min() : updaterJson["minimum"].Integer();
  424. result->maximum = updaterJson["maximum"].isNull() ? std::numeric_limits<int>::max() : updaterJson["maximum"].Integer();
  425. result->stepSize = updaterJson["stepSize"].isNull() ? 1 : updaterJson["stepSize"].Integer();
  426. result->filteredLevel = updaterJson["filteredLevel"].isNull() ? -1 : updaterJson["filteredLevel"].Integer();
  427. if (result->minimum > result->maximum)
  428. {
  429. logMod->warn("TIMES_ARMY_SIZE updater: minimum value (%d) is above maximum value(%d)!", result->minimum, result->maximum);
  430. std::swap(result->minimum, result->maximum);
  431. }
  432. if (!updaterJson["filteredFaction"].isNull())
  433. {
  434. LIBRARY->identifiers()->requestIdentifier( "faction", updaterJson["filteredFaction"], [result](int32_t identifier)
  435. {
  436. result->filteredFaction = FactionID(identifier);
  437. });
  438. }
  439. if (!updaterJson["filteredCreature"].isNull())
  440. {
  441. LIBRARY->identifiers()->requestIdentifier( "creature", updaterJson["filteredCreature"], [result](int32_t identifier)
  442. {
  443. result->filteredCreature = CreatureID(identifier);
  444. });
  445. }
  446. return result;
  447. }
  448. else
  449. logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
  450. break;
  451. }
  452. return nullptr;
  453. }
  454. VCMI_LIB_NAMESPACE_BEGIN
  455. std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonVector & ability_vec)
  456. {
  457. auto b = std::make_shared<Bonus>();
  458. const JsonNode & typeNode = ability_vec[0];
  459. const JsonNode & subtypeNode = ability_vec[2];
  460. LIBRARY->identifiers()->requestIdentifier("bonus", typeNode, [b, subtypeNode](si32 bonusID)
  461. {
  462. b->type = static_cast<BonusType>(bonusID);
  463. loadBonusSubtype(b->subtype, b->type, subtypeNode);
  464. });
  465. b->val = static_cast<si32>(ability_vec[1].Float());
  466. b->additionalInfo = static_cast<si32>(ability_vec[3].Float());
  467. b->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer)
  468. b->turnsRemain = 0;
  469. return b;
  470. }
  471. static std::shared_ptr<const ILimiter> parseAggregateLimiter(const JsonNode & limiter)
  472. {
  473. const JsonVector & subLimiters = limiter.Vector();
  474. if(subLimiters.empty())
  475. {
  476. logMod->warn("Warning: empty limiter list");
  477. return std::make_shared<AllOfLimiter>();
  478. }
  479. std::shared_ptr<AggregateLimiter> result;
  480. int offset = 1;
  481. // determine limiter type and offset for sub-limiters
  482. if(subLimiters[0].getType() == JsonNode::JsonType::DATA_STRING)
  483. {
  484. const std::string & aggregator = subLimiters[0].String();
  485. if(aggregator == AllOfLimiter::aggregator)
  486. result = std::make_shared<AllOfLimiter>();
  487. else if(aggregator == AnyOfLimiter::aggregator)
  488. result = std::make_shared<AnyOfLimiter>();
  489. else if(aggregator == NoneOfLimiter::aggregator)
  490. result = std::make_shared<NoneOfLimiter>();
  491. }
  492. if(!result)
  493. {
  494. // collapse for single limiter without explicit aggregate operator
  495. if(subLimiters.size() == 1)
  496. return JsonUtils::parseLimiter(subLimiters[0]);
  497. // implicit aggregator must be allOf
  498. result = std::make_shared<AllOfLimiter>();
  499. offset = 0;
  500. }
  501. if(subLimiters.size() == offset)
  502. logMod->warn("Warning: empty sub-limiter list");
  503. for(int sl = offset; sl < subLimiters.size(); ++sl)
  504. result->add(JsonUtils::parseLimiter(subLimiters[sl]));
  505. return result;
  506. }
  507. static std::shared_ptr<const ILimiter> parseCreatureTypeLimiter(const JsonNode & limiter)
  508. {
  509. auto creatureLimiter = std::make_shared<CCreatureTypeLimiter>();
  510. const JsonNode & parameters = limiter["parameters"];
  511. const JsonNode & creatureNode = limiter.Struct().count("creature") ? limiter["creature"] : parameters[0];
  512. const JsonNode & upgradesNode = limiter.Struct().count("includeUpgrades") ? limiter["includeUpgrades"] : parameters[1];
  513. LIBRARY->identifiers()->requestIdentifier( "creature", creatureNode, [creatureLimiter](si32 creature)
  514. {
  515. creatureLimiter->setCreature(CreatureID(creature));
  516. });
  517. if (upgradesNode.isString())
  518. {
  519. logGlobal->warn("CREATURE_TYPE_LIMITER: parameter 'includeUpgrades' is invalid! expected boolean, but string '%s' found!", upgradesNode.String());
  520. if (upgradesNode.String() == "true") // MOD COMPATIBILITY - broken mod, compensating
  521. creatureLimiter->includeUpgrades = true;
  522. }
  523. else
  524. {
  525. creatureLimiter->includeUpgrades = upgradesNode.Bool();
  526. }
  527. return creatureLimiter;
  528. }
  529. static std::shared_ptr<const ILimiter> parseHasAnotherBonusLimiter(const JsonNode & limiter)
  530. {
  531. auto bonusLimiter = std::make_shared<HasAnotherBonusLimiter>();
  532. const JsonNode & parameters = limiter["parameters"];
  533. const JsonNode & jsonType = limiter.Struct().count("bonusType") ? limiter["bonusType"] : parameters[0];
  534. const JsonNode & jsonSubtype = limiter.Struct().count("bonusSubtype") ? limiter["bonusSubtype"] : parameters[1];
  535. const JsonNode & jsonSourceType = limiter.Struct().count("bonusSourceType") ? limiter["bonusSourceType"] : parameters[2]["type"];
  536. const JsonNode & jsonSourceID = limiter.Struct().count("bonusSourceID") ? limiter["bonusSourceID"] : parameters[2]["id"];
  537. if (!jsonType.isNull())
  538. {
  539. LIBRARY->identifiers()->requestIdentifier("bonus", jsonType, [bonusLimiter, jsonSubtype](si32 bonusID)
  540. {
  541. bonusLimiter->type = static_cast<BonusType>(bonusID);
  542. if (!jsonSubtype.isNull())
  543. {
  544. loadBonusSubtype(bonusLimiter->subtype, bonusLimiter->type, jsonSubtype);
  545. bonusLimiter->isSubtypeRelevant = true;
  546. }
  547. });
  548. }
  549. if(!jsonSourceType.isNull())
  550. {
  551. auto sourceIt = bonusSourceMap.find(jsonSourceType.String());
  552. if(sourceIt != bonusSourceMap.end())
  553. {
  554. bonusLimiter->source = sourceIt->second;
  555. bonusLimiter->isSourceRelevant = true;
  556. if(!jsonSourceID.isNull()) {
  557. loadBonusSourceInstance(bonusLimiter->sid, bonusLimiter->source, jsonSourceID);
  558. bonusLimiter->isSourceIDRelevant = true;
  559. }
  560. }
  561. else
  562. logMod->warn("HAS_ANOTHER_BONUS_LIMITER: unknown bonus source type '%s'!", jsonSourceType.String());
  563. }
  564. return bonusLimiter;
  565. }
  566. static std::shared_ptr<const ILimiter> parseCreatureAlignmentLimiter(const JsonNode & limiter)
  567. {
  568. const JsonNode & parameters = limiter["parameters"];
  569. const JsonNode & alignmentNode = limiter.Struct().count("alignment") ? limiter["alignment"] : parameters[0];
  570. int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, alignmentNode.String());
  571. if(alignment == -1)
  572. {
  573. logMod->error("Error: invalid alignment %s.", alignmentNode.String());
  574. return nullptr;
  575. }
  576. else
  577. return std::make_shared<CreatureAlignmentLimiter>(static_cast<EAlignment>(alignment));
  578. }
  579. static std::shared_ptr<const ILimiter> parseFactionLimiter(const JsonNode & limiter)
  580. {
  581. const JsonNode & parameters = limiter["parameters"];
  582. const JsonNode & factionNode = limiter.Struct().count("faction") ? limiter["faction"] : parameters[0];
  583. auto factionLimiter = std::make_shared<FactionLimiter>();
  584. LIBRARY->identifiers()->requestIdentifier("faction", factionNode, [=](si32 faction)
  585. {
  586. factionLimiter->faction = FactionID(faction);
  587. });
  588. return factionLimiter;
  589. }
  590. static std::shared_ptr<const ILimiter> parseCreatureLevelLimiter(const JsonNode & limiter)
  591. {
  592. const JsonNode & parameters = limiter["parameters"];
  593. const JsonNode & minLevelNode = limiter.Struct().count("minLevel") ? limiter["minLevel"] : parameters[0];
  594. const JsonNode & maxLevelNode = limiter.Struct().count("maxlevel") ? limiter["maxlevel"] : parameters[1];
  595. //If parameters is empty, level limiter works as CREATURES_ONLY limiter
  596. auto levelLimiter = std::make_shared<CreatureLevelLimiter>();
  597. if (!minLevelNode.isNull())
  598. levelLimiter->minLevel = minLevelNode.Integer();
  599. if (!maxLevelNode.isNull())
  600. levelLimiter->maxLevel = maxLevelNode.Integer();
  601. return levelLimiter;
  602. }
  603. static std::shared_ptr<const ILimiter> parseCreatureTerrainLimiter(const JsonNode & limiter)
  604. {
  605. const JsonNode & parameters = limiter["parameters"];
  606. const JsonNode & terrainNode = limiter.Struct().count("terrain") ? limiter["terrain"] : parameters[0];
  607. auto terrainLimiter = std::make_shared<CreatureTerrainLimiter>();
  608. if(!terrainNode.isNull())
  609. {
  610. LIBRARY->identifiers()->requestIdentifier("terrain", terrainNode, [terrainLimiter](si32 terrain)
  611. {
  612. terrainLimiter->terrainType = terrain;
  613. });
  614. }
  615. return terrainLimiter;
  616. }
  617. static std::shared_ptr<const ILimiter> parseUnitOnHexLimiter(const JsonNode & limiter)
  618. {
  619. const JsonNode & parameters = limiter["parameters"];
  620. const JsonNode & hexesNode = limiter.Struct().count("hexes") ? limiter["hexes"] : parameters[0];
  621. auto hexLimiter = std::make_shared<UnitOnHexLimiter>();
  622. for (const auto & parameter : hexesNode.Vector())
  623. hexLimiter->applicableHexes.insert(BattleHex(parameter.Integer()));
  624. return hexLimiter;
  625. }
  626. static std::shared_ptr<const ILimiter> parseHasChargesLimiter(const JsonNode & limiter)
  627. {
  628. const JsonNode & parameters = limiter["parameters"];
  629. const JsonNode & costNode = limiter.Struct().count("cost") ? limiter["cost"] : parameters[0];
  630. auto hasChargesLimiter = std::make_shared<HasChargesLimiter>();
  631. if (!costNode.isNull())
  632. hasChargesLimiter->chargeCost = costNode.Integer();
  633. return hasChargesLimiter;
  634. }
  635. std::shared_ptr<const ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter)
  636. {
  637. static const std::map<std::string, std::function<std::shared_ptr<const ILimiter>(const JsonNode & limiter)>> limiterParsers = {
  638. {"CREATURE_TYPE_LIMITER", parseCreatureTypeLimiter },
  639. {"HAS_ANOTHER_BONUS_LIMITER", parseHasAnotherBonusLimiter },
  640. {"CREATURE_ALIGNMENT_LIMITER", parseCreatureAlignmentLimiter},
  641. {"FACTION_LIMITER", parseFactionLimiter },
  642. {"CREATURE_FACTION_LIMITER", parseFactionLimiter },
  643. {"CREATURE_LEVEL_LIMITER", parseCreatureLevelLimiter },
  644. {"CREATURE_TERRAIN_LIMITER", parseCreatureTerrainLimiter },
  645. {"UNIT_ON_HEXES", parseUnitOnHexLimiter },
  646. {"HAS_CHARGES_LIMITER", parseHasChargesLimiter },
  647. };
  648. switch(limiter.getType())
  649. {
  650. case JsonNode::JsonType::DATA_VECTOR:
  651. return parseAggregateLimiter(limiter);
  652. case JsonNode::JsonType::DATA_STRING: //pre-defined limiters
  653. return parseByMap(bonusLimiterMap, &limiter, "limiter type ");
  654. case JsonNode::JsonType::DATA_STRUCT: //customizable limiters
  655. {
  656. std::string limiterType = limiter["type"].String();
  657. if (limiterParsers.count(limiterType))
  658. return limiterParsers.at(limiterType)(limiter);
  659. else
  660. logMod->error("Error: invalid customizable limiter type %s.", limiterType);
  661. }
  662. }
  663. return nullptr;
  664. }
  665. std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability, const TextIdentifier & descriptionID)
  666. {
  667. auto b = std::make_shared<Bonus>();
  668. if (!parseBonus(ability, b.get(), descriptionID))
  669. {
  670. // caller code can not handle this case and presumes that returned bonus is always valid
  671. logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toString());
  672. b->type = BonusType::NONE;
  673. return b;
  674. }
  675. return b;
  676. }
  677. bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b, const TextIdentifier & descriptionID)
  678. {
  679. const JsonNode * value = nullptr;
  680. const JsonNode & subtypeNode = ability["subtype"];
  681. const JsonNode & addinfoNode = ability["addInfo"];
  682. if (ability["type"].isNull())
  683. {
  684. logMod->error("Failed to parse bonus. Description: '%s'. Config: '%s'", descriptionID.get(), ability.toCompactString());
  685. return false;
  686. }
  687. LIBRARY->identifiers()->requestIdentifier("bonus", ability["type"], [b, subtypeNode, addinfoNode](si32 bonusID)
  688. {
  689. b->type = static_cast<BonusType>(bonusID);
  690. loadBonusSubtype(b->subtype, b->type, subtypeNode);
  691. loadBonusAddInfo(b->additionalInfo, b->type, addinfoNode);
  692. });
  693. b->val = static_cast<si32>(ability["val"].Float());
  694. value = &ability["valueType"];
  695. if (!value->isNull())
  696. b->valType = static_cast<BonusValueType>(parseByMapN(bonusValueMap, value, "value type "));
  697. b->stacking = ability["stacking"].String();
  698. b->turnsRemain = static_cast<si32>(ability["turns"].Float());
  699. if(!ability["description"].isNull())
  700. {
  701. if (ability["description"].isString() && !ability["description"].String().empty())
  702. {
  703. if (ability["description"].String()[0] == '@')
  704. b->description.appendTextID(ability["description"].String().substr(1));
  705. else if (!descriptionID.get().empty())
  706. {
  707. LIBRARY->generaltexth->registerString(ability.getModScope(), descriptionID, ability["description"]);
  708. b->description.appendTextID(descriptionID.get());
  709. }
  710. }
  711. if (ability["description"].isNumber())
  712. b->description.appendTextID("core.arraytxt." + std::to_string(ability["description"].Integer()));
  713. }
  714. if(!ability["icon"].isNull())
  715. b->customIconPath = ImagePath::fromJson(ability["icon"]);
  716. b->hidden = !ability["hidden"].isNull() && ability["hidden"].Bool();
  717. value = &ability["effectRange"];
  718. if (!value->isNull())
  719. b->effectRange = static_cast<BonusLimitEffect>(parseByMapN(bonusLimitEffect, value, "effect range "));
  720. value = &ability["duration"];
  721. if (!value->isNull())
  722. {
  723. switch (value->getType())
  724. {
  725. case JsonNode::JsonType::DATA_STRING:
  726. b->duration = parseByMap(bonusDurationMap, value, "duration type ");
  727. break;
  728. case JsonNode::JsonType::DATA_VECTOR:
  729. {
  730. BonusDuration::Type dur = 0;
  731. for (const JsonNode & d : value->Vector())
  732. dur |= parseByMapN(bonusDurationMap, &d, "duration type ");
  733. b->duration = dur;
  734. }
  735. break;
  736. default:
  737. logMod->error("Error! Wrong bonus duration format.");
  738. }
  739. }
  740. value = &ability["sourceType"];
  741. if (!value->isNull())
  742. b->source = static_cast<BonusSource>(parseByMap(bonusSourceMap, value, "source type "));
  743. if (!ability["sourceID"].isNull())
  744. loadBonusSourceInstance(b->sid, b->source, ability["sourceID"]);
  745. value = &ability["targetSourceType"];
  746. if (!value->isNull())
  747. b->targetSourceType = static_cast<BonusSource>(parseByMap(bonusSourceMap, value, "target type "));
  748. value = &ability["limiters"];
  749. if (!value->isNull())
  750. b->limiter = parseLimiter(*value);
  751. value = &ability["propagator"];
  752. if (!value->isNull())
  753. {
  754. //ALL_CREATURES old propagator compatibility
  755. if(value->String() == "ALL_CREATURES")
  756. {
  757. logMod->warn("ALL_CREATURES propagator is deprecated. Use GLOBAL_EFFECT propagator with CREATURES_ONLY limiter");
  758. b->addLimiter(std::make_shared<CreatureLevelLimiter>());
  759. b->propagator = bonusPropagatorMap.at("GLOBAL_EFFECT");
  760. }
  761. else
  762. b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type ");
  763. }
  764. value = &ability["updater"];
  765. if(!value->isNull())
  766. b->addUpdater(parseUpdater(*value));
  767. value = &ability["propagationUpdater"];
  768. if(!value->isNull())
  769. b->propagationUpdater = parseUpdater(*value);
  770. return true;
  771. }
  772. CSelector JsonUtils::parseSelector(const JsonNode & ability)
  773. {
  774. CSelector ret = Selector::all;
  775. // Recursive parsers for anyOf, allOf, noneOf
  776. const auto * value = &ability["allOf"];
  777. if(value->isVector())
  778. {
  779. for(const auto & andN : value->Vector())
  780. ret = ret.And(parseSelector(andN));
  781. }
  782. value = &ability["anyOf"];
  783. if(value->isVector())
  784. {
  785. CSelector base = Selector::none;
  786. for(const auto & andN : value->Vector())
  787. base = base.Or(parseSelector(andN));
  788. ret = ret.And(base);
  789. }
  790. value = &ability["noneOf"];
  791. if(value->isVector())
  792. {
  793. CSelector base = Selector::none;
  794. for(const auto & andN : value->Vector())
  795. base = base.Or(parseSelector(andN));
  796. ret = ret.And(base.Not());
  797. }
  798. BonusType type = BonusType::NONE;
  799. // Actual selector parser
  800. value = &ability["type"];
  801. if(value->isString())
  802. {
  803. ret = ret.And(Selector::type()(static_cast<BonusType>(*LIBRARY->identifiers()->getIdentifier("bonus", value->String()))));
  804. }
  805. value = &ability["subtype"];
  806. if(!value->isNull() && type != BonusType::NONE)
  807. {
  808. BonusSubtypeID subtype;
  809. loadBonusSubtype(subtype, type, ability);
  810. ret = ret.And(Selector::subtype()(subtype));
  811. }
  812. value = &ability["sourceType"];
  813. std::optional<BonusSource> src = std::nullopt; //Fixes for GCC false maybe-uninitialized
  814. std::optional<BonusSourceID> id = std::nullopt;
  815. if(value->isString())
  816. {
  817. auto it = bonusSourceMap.find(value->String());
  818. if(it != bonusSourceMap.end())
  819. src = it->second;
  820. }
  821. value = &ability["sourceID"];
  822. if(!value->isNull() && src.has_value())
  823. {
  824. loadBonusSourceInstance(*id, *src, ability);
  825. }
  826. if(src && id)
  827. ret = ret.And(Selector::source(*src, *id));
  828. else if(src)
  829. ret = ret.And(Selector::sourceTypeSel(*src));
  830. value = &ability["targetSourceType"];
  831. if(value->isString())
  832. {
  833. auto it = bonusSourceMap.find(value->String());
  834. if(it != bonusSourceMap.end())
  835. ret = ret.And(Selector::targetSourceType()(it->second));
  836. }
  837. value = &ability["valueType"];
  838. if(value->isString())
  839. {
  840. auto it = bonusValueMap.find(value->String());
  841. if(it != bonusValueMap.end())
  842. ret = ret.And(Selector::valueType(it->second));
  843. }
  844. CAddInfo info;
  845. value = &ability["addInfo"];
  846. if(!value->isNull())
  847. {
  848. loadBonusAddInfo(info, type, ability["addInfo"]);
  849. ret = ret.And(Selector::info()(info));
  850. }
  851. value = &ability["effectRange"];
  852. if(value->isString())
  853. {
  854. auto it = bonusLimitEffect.find(value->String());
  855. if(it != bonusLimitEffect.end())
  856. ret = ret.And(Selector::effectRange()(it->second));
  857. }
  858. value = &ability["lastsTurns"];
  859. if(value->isNumber())
  860. ret = ret.And(Selector::turns(value->Integer()));
  861. value = &ability["lastsDays"];
  862. if(value->isNumber())
  863. ret = ret.And(Selector::days(value->Integer()));
  864. return ret;
  865. }
  866. VCMI_LIB_NAMESPACE_END