| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039 |
- /*
- * JsonUtils.cpp, part of VCMI engine
- *
- * Authors: listed in file AUTHORS in main folder
- *
- * License: GNU General Public License v2.0 or later
- * Full text of license available in license.txt file, in main folder
- *
- */
- #include "StdInc.h"
- #include "JsonBonus.h"
- #include "JsonValidator.h"
- #include "../texts/CGeneralTextHandler.h"
- #include "../GameLibrary.h"
- #include "../bonuses/Limiters.h"
- #include "../bonuses/Propagators.h"
- #include "../bonuses/Updaters.h"
- #include "../bonuses/BonusParameters.h"
- #include "../CBonusTypeHandler.h"
- #include "../constants/StringConstants.h"
- #include "../modding/IdentifierStorage.h"
- VCMI_LIB_USING_NAMESPACE
- template <typename T>
- const T parseByMap(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
- {
- if (!val->isNull())
- {
- const std::string & type = val->String();
- auto it = map.find(type);
- if (it == map.end())
- {
- logMod->error("Error: invalid %s%s.", err, type);
- return {};
- }
- else
- {
- return it->second;
- }
- }
- else
- return {};
- }
- template <typename T>
- const T parseByMapN(const std::map<std::string, T> & map, const JsonNode * val, const std::string & err)
- {
- if(val->isNumber())
- return static_cast<T>(val->Integer());
- else
- return parseByMap<T>(map, val, err);
- }
- static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const JsonNode & node)
- {
- if (node.isNull())
- {
- subtype = BonusSubtypeID();
- return;
- }
- if (node.isNumber()) // Compatibility code for 1.3 or older
- {
- logMod->warn("Bonus subtype must be string! (%s)", node.getModScope());
- subtype = BonusCustomSubtype(node.Integer());
- return;
- }
- if (!node.isString())
- {
- logMod->warn("Bonus subtype must be string! (%s)", node.getModScope());
- subtype = BonusSubtypeID();
- return;
- }
- switch (type)
- {
- case BonusType::MAGIC_SCHOOL_SKILL:
- case BonusType::SPELL_DAMAGE:
- case BonusType::SPELLS_OF_SCHOOL:
- case BonusType::SPELL_DAMAGE_REDUCTION:
- case BonusType::SPELL_SCHOOL_IMMUNITY:
- case BonusType::NEGATIVE_EFFECTS_IMMUNITY:
- {
- LIBRARY->identifiers()->requestIdentifier( "spellSchool", node, [&subtype](int32_t identifier)
- {
- subtype = SpellSchool(identifier);
- });
- break;
- }
- case BonusType::HATES_TRAIT:
- {
- LIBRARY->identifiers()->requestIdentifier( "bonus", node, [&subtype](int32_t identifier)
- {
- subtype = BonusType(identifier);
- });
- break;
- }
- case BonusType::NO_TERRAIN_PENALTY:
- {
- LIBRARY->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier)
- {
- subtype = TerrainId(identifier);
- });
- break;
- }
- case BonusType::PRIMARY_SKILL:
- {
- LIBRARY->identifiers()->requestIdentifier( "primarySkill", node, [&subtype](int32_t identifier)
- {
- subtype = PrimarySkill(identifier);
- });
- break;
- }
- case BonusType::IMPROVED_NECROMANCY:
- case BonusType::HERO_GRANTS_ATTACKS:
- case BonusType::BONUS_DAMAGE_CHANCE:
- case BonusType::BONUS_DAMAGE_PERCENTAGE:
- case BonusType::SPECIAL_UPGRADE:
- case BonusType::HATE:
- case BonusType::SUMMON_GUARDIANS:
- case BonusType::MANUAL_CONTROL:
- case BonusType::SKELETON_TRANSFORMER_TARGET:
- case BonusType::DEITYOFFIRE:
- {
- LIBRARY->identifiers()->requestIdentifier( "creature", node, [&subtype](int32_t identifier)
- {
- subtype = CreatureID(identifier);
- });
- break;
- }
- case BonusType::SPELL_IMMUNITY:
- case BonusType::SPELL_DURATION:
- case BonusType::SPECIAL_ADD_VALUE_ENCHANT:
- case BonusType::SPECIAL_FIXED_VALUE_ENCHANT:
- case BonusType::SPECIAL_PECULIAR_ENCHANT:
- case BonusType::SPECIAL_SPELL_LEV:
- case BonusType::SPECIFIC_SPELL_DAMAGE:
- case BonusType::SPECIFIC_SPELL_RANGE:
- case BonusType::SPELL:
- case BonusType::OPENING_BATTLE_SPELL:
- case BonusType::SPELL_LIKE_ATTACK:
- case BonusType::CATAPULT:
- case BonusType::CATAPULT_EXTRA_SHOTS:
- case BonusType::HEALER:
- case BonusType::SPELLCASTER:
- case BonusType::ENCHANTER:
- case BonusType::SPELL_AFTER_ATTACK:
- case BonusType::SPELL_BEFORE_ATTACK:
- case BonusType::SPECIFIC_SPELL_POWER:
- case BonusType::ENCHANTED:
- case BonusType::MORE_DAMAGE_FROM_SPELL:
- case BonusType::ADJACENT_SPELLCASTER:
- case BonusType::NOT_ACTIVE:
- {
- LIBRARY->identifiers()->requestIdentifier( "spell", node, [&subtype](int32_t identifier)
- {
- subtype = SpellID(identifier);
- });
- break;
- }
- case BonusType::GENERATE_RESOURCE:
- case BonusType::RESOURCES_CONSTANT_BOOST:
- case BonusType::RESOURCES_TOWN_MULTIPLYING_BOOST:
- {
- LIBRARY->identifiers()->requestIdentifier( "resource", node, [&subtype](int32_t identifier)
- {
- subtype = GameResID(identifier);
- });
- break;
- }
- case BonusType::MOVEMENT:
- case BonusType::WATER_WALKING:
- case BonusType::FLYING_MOVEMENT:
- case BonusType::NEGATE_ALL_NATURAL_IMMUNITIES:
- case BonusType::CREATURE_DAMAGE:
- case BonusType::FLYING:
- case BonusType::FIRST_STRIKE:
- case BonusType::GENERAL_DAMAGE_REDUCTION:
- case BonusType::PERCENTAGE_DAMAGE_BOOST:
- case BonusType::SOUL_STEAL:
- case BonusType::TRANSMUTATION:
- case BonusType::DESTRUCTION:
- case BonusType::DEATH_STARE:
- case BonusType::REBIRTH:
- case BonusType::VISIONS:
- case BonusType::SPELLS_OF_LEVEL: // spell level
- case BonusType::CREATURE_GROWTH: // creature level
- case BonusType::ON_COMBAT_EVENT:
- {
- LIBRARY->identifiers()->requestIdentifier( "bonusSubtype", node, [&subtype](int32_t identifier)
- {
- subtype = BonusCustomSubtype(identifier);
- });
- break;
- }
- default:
- {
- logMod->warn("Bonus type %s does not supports subtypes!", LIBRARY->bth->bonusToString(type));
- subtype = BonusSubtypeID();
- }
- }
- }
- static TBonusParametersPtr loadBonusAddInfo(BonusType type, const JsonNode & value)
- {
- const auto & getFirstValue = [](const JsonNode & jsonNode) -> const JsonNode &
- {
- if (jsonNode.isVector())
- return jsonNode[0];
- else
- return jsonNode;
- };
- if (value.isNull())
- return nullptr;
- auto result = std::make_shared<BonusParameters>();
- BonusParameters & var = *result;
- switch (type)
- {
- case BonusType::IMPROVED_NECROMANCY:
- case BonusType::SPECIAL_ADD_VALUE_ENCHANT:
- case BonusType::SPECIAL_FIXED_VALUE_ENCHANT:
- case BonusType::DESTRUCTION:
- case BonusType::LIMITED_SHOOTING_RANGE:
- case BonusType::ACID_BREATH:
- case BonusType::BIND_EFFECT:
- case BonusType::SPELLCASTER:
- case BonusType::FEROCITY:
- case BonusType::PRIMARY_SKILL:
- case BonusType::ENCHANTER:
- case BonusType::SPECIAL_PECULIAR_ENCHANT:
- case BonusType::SPELL_IMMUNITY:
- case BonusType::DARKNESS:
- case BonusType::FULL_MAP_SCOUTING:
- case BonusType::FULL_MAP_DARKNESS:
- case BonusType::OPENING_BATTLE_SPELL:
- // 1 number
- var = static_cast<int32_t>(getFirstValue(value).Integer());
- break;
- case BonusType::SPECIAL_UPGRADE:
- case BonusType::TRANSMUTATION:
- // 1 creature ID
- LIBRARY->identifiers()->requestIdentifier("creature", getFirstValue(value), [&](si32 identifier) { var = CreatureID(identifier); });
- break;
- case BonusType::DEATH_STARE:
- // 1 spell ID
- LIBRARY->identifiers()->requestIdentifier("spell", getFirstValue(value), [&](si32 identifier) { var = SpellID(identifier); });
- break;
- case BonusType::SPELL_BEFORE_ATTACK:
- case BonusType::SPELL_AFTER_ATTACK:
- // 3 numbers
- if (value.isNumber())
- {
- std::vector<int32_t> loadedData{
- static_cast<int32_t>(getFirstValue(value).Integer()),
- -1,
- -1
- };
- var = loadedData;
- }
- else
- {
- std::vector<int32_t> loadedData{
- static_cast<int32_t>(value[0].Integer()),
- static_cast<int32_t>(value[1].Integer()),
- static_cast<int32_t>(value[2].Integer())
- };
- var = loadedData;
- }
- break;
- case BonusType::MULTIHEX_UNIT_ATTACK:
- case BonusType::MULTIHEX_ENEMY_ATTACK:
- case BonusType::MULTIHEX_ANIMATION:
- {
- std::vector<int32_t> loadedData;
- for (const auto & sequence : value.Vector())
- {
- static const std::map<char, int> charToDirection = {
- { 'f', 1 }, { 'l', 6}, {'r', 2}, {'b', 4}
- };
- int converted = 0;
- for (const auto & ch : boost::adaptors::reverse(sequence.String()))
- {
- char chLower = std::tolower(ch);
- if (charToDirection.count(chLower))
- converted = 10 * converted + charToDirection.at(chLower);
- }
- loadedData.push_back(converted);
- }
- var = loadedData;
- break;
- }
- case BonusType::FORCE_NEUTRAL_ENCOUNTER_STACK_COUNT:
- {
- std::vector<int32_t> loadedData;
- for(const auto & sequence : value.Vector())
- loadedData.push_back(sequence.Integer());
- var = loadedData;
- break;
- }
- case BonusType::ON_COMBAT_EVENT:
- {
- var = BonusParameters(BonusParametersOnCombatEvent());
- auto & loadedData = result->toCustom<BonusParametersOnCombatEvent>();
- for (const auto & effect : value["effect"].Vector())
- {
- int index = loadedData.effects.size();
- bool targetEnemy = effect["targetEnemy"].Bool();
- if (effect["action"].String() == "bonus")
- {
- const auto bonus = JsonUtils::parseBonus(effect["bonus"]);
- loadedData.effects.push_back(BonusParametersOnCombatEvent::CombatEffectBonus{
- bonus, targetEnemy
- });
- }
- if (effect["action"].String() == "spell")
- {
- int mastery = effect["mastery"].Integer();
- const auto bonus = JsonUtils::parseBonus(effect["bonus"]);
- loadedData.effects.push_back(BonusParametersOnCombatEvent::CombatEffectSpell{
- SpellID(), mastery, targetEnemy
- });
- LIBRARY->identifiers()->requestIdentifier( "spell", effect["spell"], [&loadedData, index](int32_t identifier)
- {
- auto * targetEffect = std::get_if<BonusParametersOnCombatEvent::CombatEffectSpell>(&loadedData.effects[index]);
- targetEffect->spell = SpellID(identifier);
- });
- }
- }
- break;
- }
- default:
- logMod->warn("Bonus type %s does not supports addInfo!", LIBRARY->bth->bonusToString(type) );
- }
- return result;
- }
- static void loadBonusSourceInstance(BonusSourceID & sourceInstance, BonusSource sourceType, const JsonNode & node)
- {
- if (node.isNull())
- {
- sourceInstance = BonusCustomSource();
- return;
- }
- if (node.isNumber()) // Compatibility code for 1.3 or older
- {
- logMod->warn("Bonus source must be string!");
- sourceInstance = BonusCustomSource(node.Integer());
- return;
- }
- if (!node.isString())
- {
- logMod->warn("Bonus source must be string!");
- sourceInstance = BonusCustomSource();
- return;
- }
- switch (sourceType)
- {
- case BonusSource::ARTIFACT:
- case BonusSource::ARTIFACT_INSTANCE:
- {
- LIBRARY->identifiers()->requestIdentifier( "artifact", node, [&sourceInstance](int32_t identifier)
- {
- sourceInstance = ArtifactID(identifier);
- });
- break;
- }
- case BonusSource::OBJECT_TYPE:
- {
- LIBRARY->identifiers()->requestIdentifier( "object", node, [&sourceInstance](int32_t identifier)
- {
- sourceInstance = Obj(identifier);
- });
- break;
- }
- case BonusSource::OBJECT_INSTANCE:
- case BonusSource::HERO_BASE_SKILL:
- sourceInstance = ObjectInstanceID(ObjectInstanceID::decode(node.String()));
- break;
- case BonusSource::CREATURE_ABILITY:
- {
- LIBRARY->identifiers()->requestIdentifier( "creature", node, [&sourceInstance](int32_t identifier)
- {
- sourceInstance = CreatureID(identifier);
- });
- break;
- }
- case BonusSource::TERRAIN_OVERLAY:
- {
- LIBRARY->identifiers()->requestIdentifier( "battlefield", node, [&sourceInstance](int32_t identifier)
- {
- sourceInstance = BattleField(identifier);
- });
- break;
- }
- case BonusSource::SPELL_EFFECT:
- {
- LIBRARY->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier)
- {
- sourceInstance = SpellID(identifier);
- });
- break;
- }
- case BonusSource::TOWN_STRUCTURE:
- assert(0); // TODO
- sourceInstance = BuildingTypeUniqueID();
- break;
- case BonusSource::SECONDARY_SKILL:
- {
- LIBRARY->identifiers()->requestIdentifier( "secondarySkill", node, [&sourceInstance](int32_t identifier)
- {
- sourceInstance = SecondarySkill(identifier);
- });
- break;
- }
- case BonusSource::HERO_SPECIAL:
- {
- LIBRARY->identifiers()->requestIdentifier( "hero", node, [&sourceInstance](int32_t identifier)
- {
- sourceInstance = HeroTypeID(identifier);
- });
- break;
- }
- case BonusSource::CAMPAIGN_BONUS:
- sourceInstance = CampaignScenarioID(CampaignScenarioID::decode(node.String()));
- break;
- case BonusSource::ARMY:
- case BonusSource::STACK_EXPERIENCE:
- case BonusSource::COMMANDER:
- case BonusSource::GLOBAL:
- case BonusSource::TERRAIN_NATIVE:
- case BonusSource::OTHER:
- default:
- sourceInstance = BonusSourceID();
- break;
- }
- }
- static TUpdaterPtr parseUpdater(const JsonNode & updaterJson)
- {
- const std::map<std::string, std::shared_ptr<IUpdater>> bonusUpdaterMap =
- {
- {"TIMES_HERO_LEVEL", std::make_shared<TimesHeroLevelUpdater>()},
- {"TIMES_HERO_LEVEL_DIVIDE_STACK_LEVEL", std::make_shared<TimesHeroLevelDivideStackLevelUpdater>()},
- {"DIVIDE_STACK_LEVEL", std::make_shared<DivideStackLevelUpdater>()},
- {"TIMES_STACK_LEVEL", std::make_shared<TimesStackLevelUpdater>()},
- {"TIMES_STACK_SIZE", std::make_shared<TimesStackSizeUpdater>()},
- {"BONUS_OWNER_UPDATER", std::make_shared<OwnerUpdater>()}
- };
- switch(updaterJson.getType())
- {
- case JsonNode::JsonType::DATA_STRING:
- {
- auto it = bonusUpdaterMap.find(updaterJson.String());
- if (it != bonusUpdaterMap.end())
- return it->second;
- logGlobal->error("Unknown bonus updater type '%s'", updaterJson.String());
- return nullptr;
- }
- case JsonNode::JsonType::DATA_VECTOR:
- {
- auto result = std::make_shared<CompositeUpdater>();
- for (const auto & entry : updaterJson.Vector())
- result->updaters.push_back(parseUpdater(entry));
- return result;
- }
- case JsonNode::JsonType::DATA_STRUCT:
- if(updaterJson["type"].String() == "GROWS_WITH_LEVEL")
- {
- // MOD COMPATIBILITY - parameters is deprecated in 1.7
- const JsonNode & param = updaterJson["parameters"];
- int valPer20 = updaterJson["valPer20"].isNull() ? param[0].Integer() : updaterJson["valPer20"].Integer();
- int stepSize = updaterJson["stepSize"].isNull() ? param[1].Integer() : updaterJson["stepSize"].Integer();
- return std::make_shared<GrowsWithLevelUpdater>(valPer20, std::max(1, stepSize));
- }
- if(updaterJson["type"].String() == "TIMES_HERO_LEVEL")
- {
- int stepSize = updaterJson["stepSize"].Integer();
- return std::make_shared<TimesHeroLevelUpdater>(std::max(1, stepSize));
- }
- if(updaterJson["type"].String() == "TIMES_STACK_SIZE")
- {
- int minimum = updaterJson["minimum"].isNull() ? std::numeric_limits<int>::min() : updaterJson["minimum"].Integer();
- int maximum = updaterJson["maximum"].isNull() ? std::numeric_limits<int>::max() : updaterJson["maximum"].Integer();
- int stepSize = updaterJson["stepSize"].Integer();
- int stepValue = updaterJson["stepValue"].Integer();
- if (minimum > maximum)
- {
- logMod->warn("TIMES_STACK_SIZE updater: minimum value (%d) is above maximum value(%d)!", minimum, maximum);
- return std::make_shared<TimesStackSizeUpdater>(maximum, minimum, std::max(1, stepSize), std::max(1, stepValue));
- }
- return std::make_shared<TimesStackSizeUpdater>(minimum, maximum, std::max(1, stepSize), std::max(1, stepValue));
- }
- if(updaterJson["type"].String() == "TIMES_ARMY_SIZE")
- {
- auto result = std::make_shared<TimesArmySizeUpdater>();
- result->minimum = updaterJson["minimum"].isNull() ? std::numeric_limits<int>::min() : updaterJson["minimum"].Integer();
- result->maximum = updaterJson["maximum"].isNull() ? std::numeric_limits<int>::max() : updaterJson["maximum"].Integer();
- result->stepSize = updaterJson["stepSize"].isNull() ? 1 : updaterJson["stepSize"].Integer();
- result->filteredLevel = updaterJson["filteredLevel"].isNull() ? -1 : updaterJson["filteredLevel"].Integer();
- if (result->minimum > result->maximum)
- {
- logMod->warn("TIMES_ARMY_SIZE updater: minimum value (%d) is above maximum value(%d)!", result->minimum, result->maximum);
- std::swap(result->minimum, result->maximum);
- }
- if (!updaterJson["filteredFaction"].isNull())
- {
- LIBRARY->identifiers()->requestIdentifier( "faction", updaterJson["filteredFaction"], [result](int32_t identifier)
- {
- result->filteredFaction = FactionID(identifier);
- });
- }
- if (!updaterJson["filteredCreature"].isNull())
- {
- LIBRARY->identifiers()->requestIdentifier( "creature", updaterJson["filteredCreature"], [result](int32_t identifier)
- {
- result->filteredCreature = CreatureID(identifier);
- });
- }
- return result;
- }
- else
- logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String());
- break;
- }
- return nullptr;
- }
- VCMI_LIB_NAMESPACE_BEGIN
- std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonVector & ability_vec)
- {
- auto b = std::make_shared<Bonus>();
- const JsonNode & typeNode = ability_vec[0];
- const JsonNode & subtypeNode = ability_vec[2];
- LIBRARY->identifiers()->requestIdentifier("bonus", typeNode, [b, subtypeNode](si32 bonusID)
- {
- b->type = static_cast<BonusType>(bonusID);
- loadBonusSubtype(b->subtype, b->type, subtypeNode);
- });
- b->val = static_cast<si32>(ability_vec[1].Float());
- b->parameters = std::make_shared<BonusParameters>(static_cast<si32>(ability_vec[3].Integer()));
- b->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer)
- b->turnsRemain = 0;
- return b;
- }
- static std::shared_ptr<const ILimiter> parseAggregateLimiter(const JsonNode & limiter)
- {
- const JsonVector & subLimiters = limiter.Vector();
- if(subLimiters.empty())
- {
- logMod->warn("Warning: empty limiter list");
- return std::make_shared<AllOfLimiter>();
- }
- std::shared_ptr<AggregateLimiter> result;
- int offset = 1;
- // determine limiter type and offset for sub-limiters
- if(subLimiters[0].getType() == JsonNode::JsonType::DATA_STRING)
- {
- const std::string & aggregator = subLimiters[0].String();
- if(aggregator == AllOfLimiter::aggregator)
- result = std::make_shared<AllOfLimiter>();
- else if(aggregator == AnyOfLimiter::aggregator)
- result = std::make_shared<AnyOfLimiter>();
- else if(aggregator == NoneOfLimiter::aggregator)
- result = std::make_shared<NoneOfLimiter>();
- }
- if(!result)
- {
- // collapse for single limiter without explicit aggregate operator
- if(subLimiters.size() == 1)
- return JsonUtils::parseLimiter(subLimiters[0]);
- // implicit aggregator must be allOf
- result = std::make_shared<AllOfLimiter>();
- offset = 0;
- }
- if(subLimiters.size() == offset)
- logMod->warn("Warning: empty sub-limiter list");
- for(int sl = offset; sl < subLimiters.size(); ++sl)
- result->add(JsonUtils::parseLimiter(subLimiters[sl]));
- return result;
- }
- static std::shared_ptr<const ILimiter> parseCreatureTypeLimiter(const JsonNode & limiter)
- {
- auto creatureLimiter = std::make_shared<CCreatureTypeLimiter>();
- const JsonNode & parameters = limiter["parameters"];
- const JsonNode & creatureNode = limiter.Struct().count("creature") ? limiter["creature"] : parameters[0];
- const JsonNode & upgradesNode = limiter.Struct().count("includeUpgrades") ? limiter["includeUpgrades"] : parameters[1];
- LIBRARY->identifiers()->requestIdentifier( "creature", creatureNode, [creatureLimiter](si32 creature)
- {
- creatureLimiter->setCreature(CreatureID(creature));
- });
- if (upgradesNode.isString())
- {
- logGlobal->warn("CREATURE_TYPE_LIMITER: parameter 'includeUpgrades' is invalid! expected boolean, but string '%s' found!", upgradesNode.String());
- if (upgradesNode.String() == "true") // MOD COMPATIBILITY - broken mod, compensating
- creatureLimiter->includeUpgrades = true;
- }
- else
- {
- creatureLimiter->includeUpgrades = upgradesNode.Bool();
- }
- return creatureLimiter;
- }
- static std::shared_ptr<const ILimiter> parseHasAnotherBonusLimiter(const JsonNode & limiter)
- {
- auto bonusLimiter = std::make_shared<HasAnotherBonusLimiter>();
- const JsonNode & parameters = limiter["parameters"];
- const JsonNode & jsonType = limiter.Struct().count("bonusType") ? limiter["bonusType"] : parameters[0];
- const JsonNode & jsonSubtype = limiter.Struct().count("bonusSubtype") ? limiter["bonusSubtype"] : parameters[1];
- const JsonNode & jsonSourceType = limiter.Struct().count("bonusSourceType") ? limiter["bonusSourceType"] : parameters[2]["type"];
- const JsonNode & jsonSourceID = limiter.Struct().count("bonusSourceID") ? limiter["bonusSourceID"] : parameters[2]["id"];
- const JsonNode & jsonMinValue = limiter["bonusMinValue"];
- const JsonNode & jsonMaxValue = limiter["bonusMaxValue"];
- if (!jsonMinValue.isNull())
- bonusLimiter->minValue = jsonMinValue.Integer();
- if (!jsonMaxValue.isNull())
- bonusLimiter->maxValue = jsonMaxValue.Integer();
- if (!jsonType.isNull())
- {
- LIBRARY->identifiers()->requestIdentifier("bonus", jsonType, [bonusLimiter, jsonSubtype](si32 bonusID)
- {
- bonusLimiter->type = static_cast<BonusType>(bonusID);
- if (!jsonSubtype.isNull())
- {
- loadBonusSubtype(bonusLimiter->subtype, bonusLimiter->type, jsonSubtype);
- bonusLimiter->isSubtypeRelevant = true;
- }
- });
- }
- if(!jsonSourceType.isNull())
- {
- auto sourceIt = bonusSourceMap.find(jsonSourceType.String());
- if(sourceIt != bonusSourceMap.end())
- {
- bonusLimiter->source = sourceIt->second;
- bonusLimiter->isSourceRelevant = true;
- if(!jsonSourceID.isNull()) {
- loadBonusSourceInstance(bonusLimiter->sid, bonusLimiter->source, jsonSourceID);
- bonusLimiter->isSourceIDRelevant = true;
- }
- }
- else
- logMod->warn("HAS_ANOTHER_BONUS_LIMITER: unknown bonus source type '%s'!", jsonSourceType.String());
- }
- return bonusLimiter;
- }
- static std::shared_ptr<const ILimiter> parseCreatureAlignmentLimiter(const JsonNode & limiter)
- {
- const JsonNode & parameters = limiter["parameters"];
- const JsonNode & alignmentNode = limiter.Struct().count("alignment") ? limiter["alignment"] : parameters[0];
- int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, alignmentNode.String());
- if(alignment == -1)
- {
- logMod->error("Error: invalid alignment %s.", alignmentNode.String());
- return nullptr;
- }
- else
- return std::make_shared<CreatureAlignmentLimiter>(static_cast<EAlignment>(alignment));
- }
- static std::shared_ptr<const ILimiter> parseFactionLimiter(const JsonNode & limiter)
- {
- const JsonNode & parameters = limiter["parameters"];
- const JsonNode & factionNode = limiter.Struct().count("faction") ? limiter["faction"] : parameters[0];
- auto factionLimiter = std::make_shared<FactionLimiter>();
- LIBRARY->identifiers()->requestIdentifier("faction", factionNode, [=](si32 faction)
- {
- factionLimiter->faction = FactionID(faction);
- });
- return factionLimiter;
- }
- static std::shared_ptr<const ILimiter> parseCreatureLevelLimiter(const JsonNode & limiter)
- {
- const JsonNode & parameters = limiter["parameters"];
- const JsonNode & minLevelNode = limiter.Struct().count("minLevel") ? limiter["minLevel"] : parameters[0];
- const JsonNode & maxLevelNode = limiter.Struct().count("maxlevel") ? limiter["maxlevel"] : parameters[1];
- //If parameters is empty, level limiter works as CREATURES_ONLY limiter
- auto levelLimiter = std::make_shared<CreatureLevelLimiter>();
- if (!minLevelNode.isNull())
- levelLimiter->minLevel = minLevelNode.Integer();
- if (!maxLevelNode.isNull())
- levelLimiter->maxLevel = maxLevelNode.Integer();
- return levelLimiter;
- }
- static std::shared_ptr<const ILimiter> parseTerrainLimiter(const JsonNode & limiter)
- {
- const JsonNode & parameters = limiter["parameters"];
- const JsonNode & terrainNode = limiter.Struct().count("terrain") ? limiter["terrain"] : parameters[0];
- auto terrainLimiter = std::make_shared<TerrainLimiter>();
- if(!terrainNode.isNull())
- {
- LIBRARY->identifiers()->requestIdentifier("terrain", terrainNode, [terrainLimiter](si32 terrain)
- {
- terrainLimiter->terrainType = terrain;
- });
- }
- return terrainLimiter;
- }
- static std::shared_ptr<const ILimiter> parseUnitOnHexLimiter(const JsonNode & limiter)
- {
- const JsonNode & parameters = limiter["parameters"];
- const JsonNode & hexesNode = limiter.Struct().count("hexes") ? limiter["hexes"] : parameters[0];
- auto hexLimiter = std::make_shared<UnitOnHexLimiter>();
- for (const auto & parameter : hexesNode.Vector())
- hexLimiter->applicableHexes.insert(BattleHex(parameter.Integer()));
- return hexLimiter;
- }
- static std::shared_ptr<const ILimiter> parseUnitAdjacentLimiter(const JsonNode & limiter)
- {
- const JsonNode & creatureNode = limiter["creature"];
- auto unitLimiter = std::make_shared<UnitAdjacentLimiter>();
- LIBRARY->identifiers()->requestIdentifier("creature", creatureNode, [unitLimiter](si32 creatureID)
- {
- unitLimiter->targetUnit = creatureID;
- });
- return unitLimiter;
- }
- static std::shared_ptr<const ILimiter> parseHasChargesLimiter(const JsonNode & limiter)
- {
- const JsonNode & parameters = limiter["parameters"];
- const JsonNode & costNode = limiter.Struct().count("cost") ? limiter["cost"] : parameters[0];
- auto hasChargesLimiter = std::make_shared<HasChargesLimiter>();
- if (!costNode.isNull())
- hasChargesLimiter->chargeCost = costNode.Integer();
- return hasChargesLimiter;
- }
- std::shared_ptr<const ILimiter> JsonUtils::parseLimiter(const JsonNode & limiter)
- {
- static const std::map<std::string, std::function<std::shared_ptr<const ILimiter>(const JsonNode & limiter)>> limiterParsers = {
- {"CREATURE_TYPE_LIMITER", parseCreatureTypeLimiter },
- {"HAS_ANOTHER_BONUS_LIMITER", parseHasAnotherBonusLimiter },
- {"CREATURE_ALIGNMENT_LIMITER", parseCreatureAlignmentLimiter},
- {"FACTION_LIMITER", parseFactionLimiter },
- {"CREATURE_FACTION_LIMITER", parseFactionLimiter },
- {"CREATURE_LEVEL_LIMITER", parseCreatureLevelLimiter },
- {"TERRAIN_LIMITER", parseTerrainLimiter },
- {"CREATURE_TERRAIN_LIMITER", parseTerrainLimiter },
- {"UNIT_ON_HEXES", parseUnitOnHexLimiter },
- {"UNIT_ADJACENT", parseUnitAdjacentLimiter },
- {"HAS_CHARGES_LIMITER", parseHasChargesLimiter },
- };
- switch(limiter.getType())
- {
- case JsonNode::JsonType::DATA_VECTOR:
- return parseAggregateLimiter(limiter);
- case JsonNode::JsonType::DATA_STRING: //pre-defined limiters
- return parseByMap(bonusLimiterMap, &limiter, "limiter type ");
- case JsonNode::JsonType::DATA_STRUCT: //customizable limiters
- {
- std::string limiterType = limiter["type"].String();
- if (limiterParsers.count(limiterType))
- return limiterParsers.at(limiterType)(limiter);
- else
- logMod->error("Error: invalid customizable limiter type %s.", limiterType);
- }
- }
- return nullptr;
- }
- std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability, const TextIdentifier & descriptionID)
- {
- auto b = std::make_shared<Bonus>();
- if (!parseBonus(ability, b.get(), descriptionID))
- {
- // caller code can not handle this case and presumes that returned bonus is always valid
- logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toString());
- b->type = BonusType::NONE;
- return b;
- }
- return b;
- }
- bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b, const TextIdentifier & descriptionID)
- {
- const JsonNode * value = nullptr;
- const JsonNode & subtypeNode = ability["subtype"];
- const JsonNode & addinfoNode = ability["addInfo"];
- if (ability["type"].isNull())
- {
- logMod->error("Failed to parse bonus. Description: '%s'. Config: '%s'", descriptionID.get(), ability.toCompactString());
- return false;
- }
- LIBRARY->identifiers()->requestIdentifier("bonus", ability["type"], [b, subtypeNode, addinfoNode](si32 bonusID)
- {
- b->type = static_cast<BonusType>(bonusID);
- loadBonusSubtype(b->subtype, b->type, subtypeNode);
- b->parameters = loadBonusAddInfo(b->type, addinfoNode);
- });
- b->val = static_cast<si32>(ability["val"].Float());
- value = &ability["valueType"];
- if (!value->isNull())
- b->valType = static_cast<BonusValueType>(parseByMapN(bonusValueMap, value, "value type "));
- b->stacking = ability["stacking"].String();
- b->turnsRemain = static_cast<si32>(ability["turns"].Float());
- if(!ability["description"].isNull())
- {
- if (ability["description"].isString() && !ability["description"].String().empty())
- {
- if (ability["description"].String()[0] == '@')
- b->description.appendTextID(ability["description"].String().substr(1));
- else if (!descriptionID.get().empty())
- {
- LIBRARY->generaltexth->registerString(ability.getModScope(), descriptionID, ability["description"]);
- b->description.appendTextID(descriptionID.get());
- }
- }
- if (ability["description"].isNumber())
- b->description.appendTextID("core.arraytxt." + std::to_string(ability["description"].Integer()));
- }
- if(!ability["icon"].isNull())
- b->customIconPath = ImagePath::fromJson(ability["icon"]);
- b->hidden = !ability["hidden"].isNull() && ability["hidden"].Bool();
- value = &ability["effectRange"];
- if (!value->isNull())
- b->effectRange = static_cast<BonusLimitEffect>(parseByMapN(bonusLimitEffect, value, "effect range "));
- value = &ability["duration"];
- if (!value->isNull())
- {
- switch (value->getType())
- {
- case JsonNode::JsonType::DATA_STRING:
- b->duration = parseByMap(bonusDurationMap, value, "duration type ");
- break;
- case JsonNode::JsonType::DATA_VECTOR:
- {
- BonusDuration::Type dur = 0;
- for (const JsonNode & d : value->Vector())
- dur |= parseByMapN(bonusDurationMap, &d, "duration type ");
- b->duration = dur;
- }
- break;
- default:
- logMod->error("Error! Wrong bonus duration format.");
- }
- }
- value = &ability["sourceType"];
- if (!value->isNull())
- b->source = static_cast<BonusSource>(parseByMap(bonusSourceMap, value, "source type "));
- if (!ability["sourceID"].isNull())
- loadBonusSourceInstance(b->sid, b->source, ability["sourceID"]);
- value = &ability["targetSourceType"];
- if (!value->isNull())
- b->targetSourceType = static_cast<BonusSource>(parseByMap(bonusSourceMap, value, "target type "));
- value = &ability["limiters"];
- if (!value->isNull())
- b->limiter = parseLimiter(*value);
- value = &ability["propagator"];
- if (!value->isNull())
- {
- //ALL_CREATURES old propagator compatibility
- if(value->String() == "ALL_CREATURES")
- {
- logMod->warn("ALL_CREATURES propagator is deprecated. Use GLOBAL_EFFECT propagator with CREATURES_ONLY limiter");
- b->addLimiter(std::make_shared<CreatureLevelLimiter>());
- b->propagator = bonusPropagatorMap.at("GLOBAL_EFFECT");
- }
- else
- b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type ");
- }
- value = &ability["updater"];
- if(!value->isNull())
- b->addUpdater(parseUpdater(*value));
- value = &ability["propagationUpdater"];
- if(!value->isNull())
- b->propagationUpdater = parseUpdater(*value);
- return true;
- }
- CSelector JsonUtils::parseSelector(const JsonNode & ability)
- {
- CSelector ret = Selector::all;
- // Recursive parsers for anyOf, allOf, noneOf
- const auto * value = &ability["allOf"];
- if(value->isVector())
- {
- for(const auto & andN : value->Vector())
- ret = ret.And(parseSelector(andN));
- }
- value = &ability["anyOf"];
- if(value->isVector())
- {
- CSelector base = Selector::none;
- for(const auto & andN : value->Vector())
- base = base.Or(parseSelector(andN));
- ret = ret.And(base);
- }
- value = &ability["noneOf"];
- if(value->isVector())
- {
- CSelector base = Selector::none;
- for(const auto & andN : value->Vector())
- base = base.Or(parseSelector(andN));
- ret = ret.And(base.Not());
- }
- BonusType type = BonusType::NONE;
- // Actual selector parser
- value = &ability["type"];
- if(value->isString())
- {
- ret = ret.And(Selector::type()(static_cast<BonusType>(*LIBRARY->identifiers()->getIdentifier("bonus", value->String()))));
- }
- value = &ability["subtype"];
- if(!value->isNull() && type != BonusType::NONE)
- {
- BonusSubtypeID subtype;
- loadBonusSubtype(subtype, type, ability);
- ret = ret.And(Selector::subtype()(subtype));
- }
- value = &ability["sourceType"];
- std::optional<BonusSource> src = std::nullopt; //Fixes for GCC false maybe-uninitialized
- std::optional<BonusSourceID> id = std::nullopt;
- if(value->isString())
- {
- auto it = bonusSourceMap.find(value->String());
- if(it != bonusSourceMap.end())
- src = it->second;
- }
- value = &ability["sourceID"];
- if(!value->isNull() && src.has_value())
- {
- loadBonusSourceInstance(*id, *src, ability);
- }
- if(src && id)
- ret = ret.And(Selector::source(*src, *id));
- else if(src)
- ret = ret.And(Selector::sourceTypeSel(*src));
- value = &ability["targetSourceType"];
- if(value->isString())
- {
- auto it = bonusSourceMap.find(value->String());
- if(it != bonusSourceMap.end())
- ret = ret.And(Selector::targetSourceType()(it->second));
- }
- value = &ability["valueType"];
- if(value->isString())
- {
- auto it = bonusValueMap.find(value->String());
- if(it != bonusValueMap.end())
- ret = ret.And(Selector::valueType(it->second));
- }
- value = &ability["effectRange"];
- if(value->isString())
- {
- auto it = bonusLimitEffect.find(value->String());
- if(it != bonusLimitEffect.end())
- ret = ret.And(Selector::effectRange()(it->second));
- }
- value = &ability["lastsTurns"];
- if(value->isNumber())
- ret = ret.And(Selector::turns(value->Integer()));
- value = &ability["lastsDays"];
- if(value->isNumber())
- ret = ret.And(Selector::days(value->Integer()));
- return ret;
- }
- VCMI_LIB_NAMESPACE_END
|