JsonBonus.cpp 24 KB

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