From 3740f8b02fac62cb8ab64abf9faf820ae619d580 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 14 Feb 2024 15:48:06 +0200 Subject: [PATCH] Moved bonus parsing to a new file --- lib/BattleFieldHandler.cpp | 2 +- lib/CArtHandler.cpp | 2 +- lib/CCreatureHandler.cpp | 2 +- lib/CHeroHandler.cpp | 1 + lib/CMakeLists.txt | 2 + lib/CSkillHandler.cpp | 1 + lib/CTownHandler.cpp | 2 +- lib/gameState/CGameState.cpp | 1 + lib/json/JsonBonus.cpp | 869 ++++++++++++++++++++++++++++++ lib/json/JsonBonus.h | 28 + lib/json/JsonRandom.cpp | 2 +- lib/json/JsonUtils.cpp | 836 ---------------------------- lib/json/JsonUtils.h | 9 - lib/mapObjects/CGHeroInstance.cpp | 2 +- lib/serializer/JsonUpdater.cpp | 2 +- lib/spells/CSpellHandler.cpp | 1 + lib/spells/TargetCondition.cpp | 2 +- lib/spells/effects/Moat.cpp | 2 +- lib/spells/effects/Timed.cpp | 2 +- 19 files changed, 913 insertions(+), 855 deletions(-) create mode 100644 lib/json/JsonBonus.cpp create mode 100644 lib/json/JsonBonus.h diff --git a/lib/BattleFieldHandler.cpp b/lib/BattleFieldHandler.cpp index 672e7cf12..4d1eb5591 100644 --- a/lib/BattleFieldHandler.cpp +++ b/lib/BattleFieldHandler.cpp @@ -11,7 +11,7 @@ #include #include "BattleFieldHandler.h" -#include "json/JsonUtils.h" +#include "json/JsonBonus.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index bbe046d43..6a205217a 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -15,7 +15,7 @@ #include "GameSettings.h" #include "mapObjects/MapObjects.h" #include "constants/StringConstants.h" -#include "json/JsonUtils.h" +#include "json/JsonBonus.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" #include "serializer/JsonSerializeFormat.h" diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 8fe85a7a1..6779c8289 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -20,7 +20,7 @@ #include "constants/StringConstants.h" #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" -#include "json/JsonUtils.h" +#include "json/JsonBonus.h" #include "serializer/JsonDeserializer.h" #include "serializer/JsonUpdater.h" #include "mapObjectConstructors/AObjectTypeHandler.h" diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 413d10fe6..5f96c9999 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -23,6 +23,7 @@ #include "BattleFieldHandler.h" #include "bonuses/Limiters.h" #include "bonuses/Updaters.h" +#include "json/JsonBonus.h" #include "json/JsonUtils.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a74e3d195..68838759d 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -61,6 +61,7 @@ set(lib_SRCS filesystem/MinizipExtensions.cpp filesystem/ResourcePath.cpp + json/JsonBonus.cpp json/JsonNode.cpp json/JsonParser.cpp json/JsonRandom.cpp @@ -403,6 +404,7 @@ set(lib_HEADERS filesystem/MinizipExtensions.h filesystem/ResourcePath.h + json/JsonBonus.h json/JsonNode.h json/JsonParser.h json/JsonRandom.h diff --git a/lib/CSkillHandler.cpp b/lib/CSkillHandler.cpp index f6fa25fe9..3c8a253e8 100644 --- a/lib/CSkillHandler.cpp +++ b/lib/CSkillHandler.cpp @@ -16,6 +16,7 @@ #include "CGeneralTextHandler.h" #include "filesystem/Filesystem.h" +#include "json/JsonBonus.h" #include "json/JsonUtils.h" #include "modding/IdentifierStorage.h" #include "modding/ModUtility.h" diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index a540a7c71..df3e33f57 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -22,7 +22,7 @@ #include "filesystem/Filesystem.h" #include "bonuses/Bonus.h" #include "bonuses/Propagators.h" -#include "json/JsonUtils.h" +#include "json/JsonBonus.h" #include "ResourceSet.h" #include "mapObjectConstructors/AObjectTypeHandler.h" #include "mapObjectConstructors/CObjectClassesHandler.h" diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index ce1d0b97f..9ebac6cbb 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -31,6 +31,7 @@ #include "../campaign/CampaignState.h" #include "../constants/StringConstants.h" #include "../filesystem/ResourcePath.h" +#include "../json/JsonBonus.h" #include "../json/JsonUtils.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" #include "../mapObjectConstructors/CObjectClassesHandler.h" diff --git a/lib/json/JsonBonus.cpp b/lib/json/JsonBonus.cpp new file mode 100644 index 000000000..ffc540ff8 --- /dev/null +++ b/lib/json/JsonBonus.cpp @@ -0,0 +1,869 @@ +/* + * 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 "../ScopeGuard.h" +#include "../bonuses/BonusParams.h" +#include "../bonuses/Bonus.h" +#include "../bonuses/Limiters.h" +#include "../bonuses/Propagators.h" +#include "../bonuses/Updaters.h" +#include "../filesystem/Filesystem.h" +#include "../modding/IdentifierStorage.h" +#include "../VCMI_Lib.h" //for identifier resolution +#include "../CGeneralTextHandler.h" +#include "../constants/StringConstants.h" +#include "../battle/BattleHex.h" + +VCMI_LIB_NAMESPACE_BEGIN + +static const JsonNode nullNode; + +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.meta); + subtype = BonusCustomSubtype(node.Integer()); + return; + } + + if (!node.isString()) + { + logMod->warn("Bonus subtype must be string! (%s)", node.meta); + 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: + { + VLC->identifiers()->requestIdentifier( "spellSchool", node, [&subtype](int32_t identifier) + { + subtype = SpellSchool(identifier); + }); + break; + } + case BonusType::NO_TERRAIN_PENALTY: + { + VLC->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier) + { + subtype = TerrainId(identifier); + }); + break; + } + case BonusType::PRIMARY_SKILL: + { + VLC->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: + { + VLC->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::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::NOT_ACTIVE: + { + VLC->identifiers()->requestIdentifier( "spell", node, [&subtype](int32_t identifier) + { + subtype = SpellID(identifier); + }); + break; + } + case BonusType::GENERATE_RESOURCE: + { + VLC->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 + { + VLC->identifiers()->requestIdentifier( "bonusSubtype", node, [&subtype](int32_t identifier) + { + subtype = BonusCustomSubtype(identifier); + }); + break; + } + default: + for(const auto & i : bonusNameMap) + if(i.second == type) + logMod->warn("Bonus type %s does not supports subtypes!", i.first ); + subtype = BonusSubtypeID(); + } +} + +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: + { + VLC->identifiers()->requestIdentifier( "artifact", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = ArtifactID(identifier); + }); + break; + } + case BonusSource::OBJECT_TYPE: + { + VLC->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: + { + VLC->identifiers()->requestIdentifier( "creature", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = CreatureID(identifier); + }); + break; + } + case BonusSource::TERRAIN_OVERLAY: + { + VLC->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = BattleField(identifier); + }); + break; + } + case BonusSource::SPELL_EFFECT: + { + VLC->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: + { + VLC->identifiers()->requestIdentifier( "secondarySkill", node, [&sourceInstance](int32_t identifier) + { + sourceInstance = SecondarySkill(identifier); + }); + break; + } + case BonusSource::HERO_SPECIAL: + { + VLC->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; + } +} + +std::shared_ptr JsonUtils::parseBonus(const JsonVector & ability_vec) +{ + auto b = std::make_shared(); + std::string type = ability_vec[0].String(); + auto it = bonusNameMap.find(type); + if (it == bonusNameMap.end()) + { + logMod->error("Error: invalid ability type %s.", type); + return b; + } + b->type = it->second; + + b->val = static_cast(ability_vec[1].Float()); + loadBonusSubtype(b->subtype, b->type, ability_vec[2]); + b->additionalInfo = static_cast(ability_vec[3].Float()); + b->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer) + b->turnsRemain = 0; + return b; +} + +template +const T parseByMap(const std::map & 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 +const T parseByMapN(const std::map & map, const JsonNode * val, const std::string & err) +{ + if(val->isNumber()) + return static_cast(val->Integer()); + else + return parseByMap(map, val, err); +} + +void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node) +{ + const JsonNode & value = node["addInfo"]; + if (!value.isNull()) + { + switch (value.getType()) + { + case JsonNode::JsonType::DATA_INTEGER: + var = static_cast(value.Integer()); + break; + case JsonNode::JsonType::DATA_FLOAT: + var = static_cast(value.Float()); + break; + case JsonNode::JsonType::DATA_STRING: + VLC->identifiers()->requestIdentifier(value, [&](si32 identifier) + { + var = identifier; + }); + break; + case JsonNode::JsonType::DATA_VECTOR: + { + const JsonVector & vec = value.Vector(); + var.resize(vec.size()); + for(int i = 0; i < vec.size(); i++) + { + switch(vec[i].getType()) + { + case JsonNode::JsonType::DATA_INTEGER: + var[i] = static_cast(vec[i].Integer()); + break; + case JsonNode::JsonType::DATA_FLOAT: + var[i] = static_cast(vec[i].Float()); + break; + case JsonNode::JsonType::DATA_STRING: + VLC->identifiers()->requestIdentifier(vec[i], [&var,i](si32 identifier) + { + var[i] = identifier; + }); + break; + default: + logMod->error("Error! Wrong identifier used for value of addInfo[%d].", i); + } + } + break; + } + default: + logMod->error("Error! Wrong identifier used for value of addInfo."); + } + } +} + +std::shared_ptr JsonUtils::parseLimiter(const JsonNode & limiter) +{ + switch(limiter.getType()) + { + case JsonNode::JsonType::DATA_VECTOR: + { + const JsonVector & subLimiters = limiter.Vector(); + if(subLimiters.empty()) + { + logMod->warn("Warning: empty limiter list"); + return std::make_shared(); + } + std::shared_ptr 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(); + else if(aggregator == AnyOfLimiter::aggregator) + result = std::make_shared(); + else if(aggregator == NoneOfLimiter::aggregator) + result = std::make_shared(); + } + if(!result) + { + // collapse for single limiter without explicit aggregate operator + if(subLimiters.size() == 1) + return parseLimiter(subLimiters[0]); + // implicit aggregator must be allOf + result = std::make_shared(); + offset = 0; + } + if(subLimiters.size() == offset) + logMod->warn("Warning: empty sub-limiter list"); + for(int sl = offset; sl < subLimiters.size(); ++sl) + result->add(parseLimiter(subLimiters[sl])); + return result; + } + break; + case JsonNode::JsonType::DATA_STRING: //pre-defined limiters + return parseByMap(bonusLimiterMap, &limiter, "limiter type "); + break; + case JsonNode::JsonType::DATA_STRUCT: //customizable limiters + { + std::string limiterType = limiter["type"].String(); + const JsonVector & parameters = limiter["parameters"].Vector(); + if(limiterType == "CREATURE_TYPE_LIMITER") + { + auto creatureLimiter = std::make_shared(); + VLC->identifiers()->requestIdentifier("creature", parameters[0], [=](si32 creature) + { + creatureLimiter->setCreature(CreatureID(creature)); + }); + auto includeUpgrades = false; + + if(parameters.size() > 1) + { + bool success = true; + includeUpgrades = parameters[1].TryBoolFromString(success); + + if(!success) + logMod->error("Second parameter of '%s' limiter should be Bool", limiterType); + } + creatureLimiter->includeUpgrades = includeUpgrades; + return creatureLimiter; + } + else if(limiterType == "HAS_ANOTHER_BONUS_LIMITER") + { + std::string anotherBonusType = parameters[0].String(); + auto it = bonusNameMap.find(anotherBonusType); + if(it == bonusNameMap.end()) + { + logMod->error("Error: invalid ability type %s.", anotherBonusType); + } + else + { + auto bonusLimiter = std::make_shared(); + bonusLimiter->type = it->second; + auto findSource = [&](const JsonNode & parameter) + { + if(parameter.getType() == JsonNode::JsonType::DATA_STRUCT) + { + auto sourceIt = bonusSourceMap.find(parameter["type"].String()); + if(sourceIt != bonusSourceMap.end()) + { + bonusLimiter->source = sourceIt->second; + bonusLimiter->isSourceRelevant = true; + if(!parameter["id"].isNull()) { + loadBonusSourceInstance(bonusLimiter->sid, bonusLimiter->source, parameter["id"]); + bonusLimiter->isSourceIDRelevant = true; + } + } + } + return false; + }; + if(parameters.size() > 1) + { + if(findSource(parameters[1]) && parameters.size() == 2) + return bonusLimiter; + else + { + loadBonusSubtype(bonusLimiter->subtype, bonusLimiter->type, parameters[1]); + bonusLimiter->isSubtypeRelevant = true; + if(parameters.size() > 2) + findSource(parameters[2]); + } + } + return bonusLimiter; + } + } + else if(limiterType == "CREATURE_ALIGNMENT_LIMITER") + { + int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, parameters[0].String()); + if(alignment == -1) + logMod->error("Error: invalid alignment %s.", parameters[0].String()); + else + return std::make_shared(static_cast(alignment)); + } + else if(limiterType == "FACTION_LIMITER" || limiterType == "CREATURE_FACTION_LIMITER") //Second name is deprecated, 1.2 compat + { + auto factionLimiter = std::make_shared(); + VLC->identifiers()->requestIdentifier("faction", parameters[0], [=](si32 faction) + { + factionLimiter->faction = FactionID(faction); + }); + return factionLimiter; + } + else if(limiterType == "CREATURE_LEVEL_LIMITER") + { + auto levelLimiter = std::make_shared(); + if(!parameters.empty()) //If parameters is empty, level limiter works as CREATURES_ONLY limiter + { + levelLimiter->minLevel = parameters[0].Integer(); + if(parameters[1].isNumber()) + levelLimiter->maxLevel = parameters[1].Integer(); + } + return levelLimiter; + } + else if(limiterType == "CREATURE_TERRAIN_LIMITER") + { + auto terrainLimiter = std::make_shared(); + if(!parameters.empty()) + { + VLC->identifiers()->requestIdentifier("terrain", parameters[0], [=](si32 terrain) + { + //TODO: support limiters + //terrainLimiter->terrainType = terrain; + }); + } + return terrainLimiter; + } + else if(limiterType == "UNIT_ON_HEXES") { + auto hexLimiter = std::make_shared(); + if(!parameters.empty()) + { + for (const auto & parameter: parameters){ + if(parameter.isNumber()) + hexLimiter->applicableHexes.insert(BattleHex(parameter.Integer())); + } + } + return hexLimiter; + } + else + { + logMod->error("Error: invalid customizable limiter type %s.", limiterType); + } + } + break; + default: + break; + } + return nullptr; +} + +std::shared_ptr JsonUtils::parseBonus(const JsonNode &ability) +{ + auto b = std::make_shared(); + if (!parseBonus(ability, b.get())) + { + // 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.toJson()); + b->type = BonusType::NONE; + return b; + } + return b; +} + +std::shared_ptr JsonUtils::parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description) +{ + /* duration = BonusDuration::PERMANENT + source = BonusSource::TOWN_STRUCTURE + bonusType, val, subtype - get from json + */ + auto b = std::make_shared(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, BuildingTypeUniqueID(faction, building), description); + + if(!parseBonus(ability, b.get())) + return nullptr; + return b; +} + +static BonusParams convertDeprecatedBonus(const JsonNode &ability) +{ + if(vstd::contains(deprecatedBonusSet, ability["type"].String())) + { + logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toJson()); + auto params = BonusParams(ability["type"].String(), + ability["subtype"].isString() ? ability["subtype"].String() : "", + ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1); + if(params.isConverted) + { + if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special + { + params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE; + params.targetType = BonusSource::SECONDARY_SKILL; + } + + logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toJson()); + return params; + } + else + logMod->error("Cannot convert bonus!\n%s", ability.toJson()); + } + BonusParams ret; + ret.isConverted = false; + return ret; +} + +static TUpdaterPtr parseUpdater(const JsonNode & updaterJson) +{ + switch(updaterJson.getType()) + { + case JsonNode::JsonType::DATA_STRING: + return parseByMap(bonusUpdaterMap, &updaterJson, "updater type "); + break; + case JsonNode::JsonType::DATA_STRUCT: + if(updaterJson["type"].String() == "GROWS_WITH_LEVEL") + { + auto updater = std::make_shared(); + const JsonVector param = updaterJson["parameters"].Vector(); + updater->valPer20 = static_cast(param[0].Integer()); + if(param.size() > 1) + updater->stepSize = static_cast(param[1].Integer()); + return updater; + } + else if (updaterJson["type"].String() == "ARMY_MOVEMENT") + { + auto updater = std::make_shared(); + if(updaterJson["parameters"].isVector()) + { + const auto & param = updaterJson["parameters"].Vector(); + if(param.size() < 4) + logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!"); + else + { + updater->base = static_cast(param.at(0).Integer()); + updater->divider = static_cast(param.at(1).Integer()); + updater->multiplier = static_cast(param.at(2).Integer()); + updater->max = static_cast(param.at(3).Integer()); + } + return updater; + } + } + else + logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String()); + break; + } + return nullptr; +} + +bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) +{ + const JsonNode * value = nullptr; + + std::string type = ability["type"].String(); + auto it = bonusNameMap.find(type); + auto params = std::make_unique(false); + if (it == bonusNameMap.end()) + { + params = std::make_unique(convertDeprecatedBonus(ability)); + if(!params->isConverted) + { + logMod->error("Error: invalid ability type %s.", type); + return false; + } + b->type = params->type; + b->val = params->val.value_or(0); + b->valType = params->valueType.value_or(BonusValueType::ADDITIVE_VALUE); + if(params->targetType) + b->targetSourceType = params->targetType.value(); + } + else + b->type = it->second; + + loadBonusSubtype(b->subtype, b->type, params->isConverted ? params->toJson()["subtype"] : ability["subtype"]); + + if(!params->isConverted) + { + b->val = static_cast(ability["val"].Float()); + + value = &ability["valueType"]; + if (!value->isNull()) + b->valType = static_cast(parseByMapN(bonusValueMap, value, "value type ")); + } + + b->stacking = ability["stacking"].String(); + + resolveAddInfo(b->additionalInfo, ability); + + b->turnsRemain = static_cast(ability["turns"].Float()); + + if(!ability["description"].isNull()) + { + if (ability["description"].isString()) + b->description = ability["description"].String(); + if (ability["description"].isNumber()) + b->description = VLC->generaltexth->translate("core.arraytxt", ability["description"].Integer()); + } + + value = &ability["effectRange"]; + if (!value->isNull()) + b->effectRange = static_cast(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(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(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()); + 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()) + { + auto it = bonusNameMap.find(value->String()); + if(it != bonusNameMap.end()) + { + type = it->second; + ret = ret.And(Selector::type()(it->second)); + } + } + 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 src = std::nullopt; //Fixes for GCC false maybe-uninitialized + std::optional 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)); + } + CAddInfo info; + value = &ability["addInfo"]; + if(!value->isNull()) + { + resolveAddInfo(info, ability["addInfo"]); + ret = ret.And(Selector::info()(info)); + } + 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 diff --git a/lib/json/JsonBonus.h b/lib/json/JsonBonus.h new file mode 100644 index 000000000..c994d63e5 --- /dev/null +++ b/lib/json/JsonBonus.h @@ -0,0 +1,28 @@ +/* + * JsonBonus.h, 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 + * + */ +#pragma once + +#include "JsonNode.h" +#include "../GameConstants.h" + +VCMI_LIB_NAMESPACE_BEGIN + +namespace JsonUtils +{ + DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); + DLL_LINKAGE std::shared_ptr parseBonus(const JsonNode & ability); + DLL_LINKAGE std::shared_ptr parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description); + DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement); + DLL_LINKAGE std::shared_ptr parseLimiter(const JsonNode & limiter); + DLL_LINKAGE CSelector parseSelector(const JsonNode &ability); + DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node); +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/json/JsonRandom.cpp b/lib/json/JsonRandom.cpp index e9527f4a2..9490ce244 100644 --- a/lib/json/JsonRandom.cpp +++ b/lib/json/JsonRandom.cpp @@ -13,7 +13,7 @@ #include -#include "JsonUtils.h" +#include "JsonBonus.h" #include "../CRandomGenerator.h" #include "../constants/StringConstants.h" diff --git a/lib/json/JsonUtils.cpp b/lib/json/JsonUtils.cpp index 8d1cb7152..7597c37b8 100644 --- a/lib/json/JsonUtils.cpp +++ b/lib/json/JsonUtils.cpp @@ -30,842 +30,6 @@ VCMI_LIB_NAMESPACE_BEGIN static const JsonNode nullNode; -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.meta); - subtype = BonusCustomSubtype(node.Integer()); - return; - } - - if (!node.isString()) - { - logMod->warn("Bonus subtype must be string! (%s)", node.meta); - 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: - { - VLC->identifiers()->requestIdentifier( "spellSchool", node, [&subtype](int32_t identifier) - { - subtype = SpellSchool(identifier); - }); - break; - } - case BonusType::NO_TERRAIN_PENALTY: - { - VLC->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier) - { - subtype = TerrainId(identifier); - }); - break; - } - case BonusType::PRIMARY_SKILL: - { - VLC->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: - { - VLC->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::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::NOT_ACTIVE: - { - VLC->identifiers()->requestIdentifier( "spell", node, [&subtype](int32_t identifier) - { - subtype = SpellID(identifier); - }); - break; - } - case BonusType::GENERATE_RESOURCE: - { - VLC->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 - { - VLC->identifiers()->requestIdentifier( "bonusSubtype", node, [&subtype](int32_t identifier) - { - subtype = BonusCustomSubtype(identifier); - }); - break; - } - default: - for(const auto & i : bonusNameMap) - if(i.second == type) - logMod->warn("Bonus type %s does not supports subtypes!", i.first ); - subtype = BonusSubtypeID(); - } -} - -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: - { - VLC->identifiers()->requestIdentifier( "artifact", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = ArtifactID(identifier); - }); - break; - } - case BonusSource::OBJECT_TYPE: - { - VLC->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: - { - VLC->identifiers()->requestIdentifier( "creature", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = CreatureID(identifier); - }); - break; - } - case BonusSource::TERRAIN_OVERLAY: - { - VLC->identifiers()->requestIdentifier( "spell", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = BattleField(identifier); - }); - break; - } - case BonusSource::SPELL_EFFECT: - { - VLC->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: - { - VLC->identifiers()->requestIdentifier( "secondarySkill", node, [&sourceInstance](int32_t identifier) - { - sourceInstance = SecondarySkill(identifier); - }); - break; - } - case BonusSource::HERO_SPECIAL: - { - VLC->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; - } -} - -std::shared_ptr JsonUtils::parseBonus(const JsonVector & ability_vec) -{ - auto b = std::make_shared(); - std::string type = ability_vec[0].String(); - auto it = bonusNameMap.find(type); - if (it == bonusNameMap.end()) - { - logMod->error("Error: invalid ability type %s.", type); - return b; - } - b->type = it->second; - - b->val = static_cast(ability_vec[1].Float()); - loadBonusSubtype(b->subtype, b->type, ability_vec[2]); - b->additionalInfo = static_cast(ability_vec[3].Float()); - b->duration = BonusDuration::PERMANENT; //TODO: handle flags (as integer) - b->turnsRemain = 0; - return b; -} - -template -const T parseByMap(const std::map & 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 -const T parseByMapN(const std::map & map, const JsonNode * val, const std::string & err) -{ - if(val->isNumber()) - return static_cast(val->Integer()); - else - return parseByMap(map, val, err); -} - -void JsonUtils::resolveAddInfo(CAddInfo & var, const JsonNode & node) -{ - const JsonNode & value = node["addInfo"]; - if (!value.isNull()) - { - switch (value.getType()) - { - case JsonNode::JsonType::DATA_INTEGER: - var = static_cast(value.Integer()); - break; - case JsonNode::JsonType::DATA_FLOAT: - var = static_cast(value.Float()); - break; - case JsonNode::JsonType::DATA_STRING: - VLC->identifiers()->requestIdentifier(value, [&](si32 identifier) - { - var = identifier; - }); - break; - case JsonNode::JsonType::DATA_VECTOR: - { - const JsonVector & vec = value.Vector(); - var.resize(vec.size()); - for(int i = 0; i < vec.size(); i++) - { - switch(vec[i].getType()) - { - case JsonNode::JsonType::DATA_INTEGER: - var[i] = static_cast(vec[i].Integer()); - break; - case JsonNode::JsonType::DATA_FLOAT: - var[i] = static_cast(vec[i].Float()); - break; - case JsonNode::JsonType::DATA_STRING: - VLC->identifiers()->requestIdentifier(vec[i], [&var,i](si32 identifier) - { - var[i] = identifier; - }); - break; - default: - logMod->error("Error! Wrong identifier used for value of addInfo[%d].", i); - } - } - break; - } - default: - logMod->error("Error! Wrong identifier used for value of addInfo."); - } - } -} - -std::shared_ptr JsonUtils::parseLimiter(const JsonNode & limiter) -{ - switch(limiter.getType()) - { - case JsonNode::JsonType::DATA_VECTOR: - { - const JsonVector & subLimiters = limiter.Vector(); - if(subLimiters.empty()) - { - logMod->warn("Warning: empty limiter list"); - return std::make_shared(); - } - std::shared_ptr 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(); - else if(aggregator == AnyOfLimiter::aggregator) - result = std::make_shared(); - else if(aggregator == NoneOfLimiter::aggregator) - result = std::make_shared(); - } - if(!result) - { - // collapse for single limiter without explicit aggregate operator - if(subLimiters.size() == 1) - return parseLimiter(subLimiters[0]); - // implicit aggregator must be allOf - result = std::make_shared(); - offset = 0; - } - if(subLimiters.size() == offset) - logMod->warn("Warning: empty sub-limiter list"); - for(int sl = offset; sl < subLimiters.size(); ++sl) - result->add(parseLimiter(subLimiters[sl])); - return result; - } - break; - case JsonNode::JsonType::DATA_STRING: //pre-defined limiters - return parseByMap(bonusLimiterMap, &limiter, "limiter type "); - break; - case JsonNode::JsonType::DATA_STRUCT: //customizable limiters - { - std::string limiterType = limiter["type"].String(); - const JsonVector & parameters = limiter["parameters"].Vector(); - if(limiterType == "CREATURE_TYPE_LIMITER") - { - auto creatureLimiter = std::make_shared(); - VLC->identifiers()->requestIdentifier("creature", parameters[0], [=](si32 creature) - { - creatureLimiter->setCreature(CreatureID(creature)); - }); - auto includeUpgrades = false; - - if(parameters.size() > 1) - { - bool success = true; - includeUpgrades = parameters[1].TryBoolFromString(success); - - if(!success) - logMod->error("Second parameter of '%s' limiter should be Bool", limiterType); - } - creatureLimiter->includeUpgrades = includeUpgrades; - return creatureLimiter; - } - else if(limiterType == "HAS_ANOTHER_BONUS_LIMITER") - { - std::string anotherBonusType = parameters[0].String(); - auto it = bonusNameMap.find(anotherBonusType); - if(it == bonusNameMap.end()) - { - logMod->error("Error: invalid ability type %s.", anotherBonusType); - } - else - { - auto bonusLimiter = std::make_shared(); - bonusLimiter->type = it->second; - auto findSource = [&](const JsonNode & parameter) - { - if(parameter.getType() == JsonNode::JsonType::DATA_STRUCT) - { - auto sourceIt = bonusSourceMap.find(parameter["type"].String()); - if(sourceIt != bonusSourceMap.end()) - { - bonusLimiter->source = sourceIt->second; - bonusLimiter->isSourceRelevant = true; - if(!parameter["id"].isNull()) { - loadBonusSourceInstance(bonusLimiter->sid, bonusLimiter->source, parameter["id"]); - bonusLimiter->isSourceIDRelevant = true; - } - } - } - return false; - }; - if(parameters.size() > 1) - { - if(findSource(parameters[1]) && parameters.size() == 2) - return bonusLimiter; - else - { - loadBonusSubtype(bonusLimiter->subtype, bonusLimiter->type, parameters[1]); - bonusLimiter->isSubtypeRelevant = true; - if(parameters.size() > 2) - findSource(parameters[2]); - } - } - return bonusLimiter; - } - } - else if(limiterType == "CREATURE_ALIGNMENT_LIMITER") - { - int alignment = vstd::find_pos(GameConstants::ALIGNMENT_NAMES, parameters[0].String()); - if(alignment == -1) - logMod->error("Error: invalid alignment %s.", parameters[0].String()); - else - return std::make_shared(static_cast(alignment)); - } - else if(limiterType == "FACTION_LIMITER" || limiterType == "CREATURE_FACTION_LIMITER") //Second name is deprecated, 1.2 compat - { - auto factionLimiter = std::make_shared(); - VLC->identifiers()->requestIdentifier("faction", parameters[0], [=](si32 faction) - { - factionLimiter->faction = FactionID(faction); - }); - return factionLimiter; - } - else if(limiterType == "CREATURE_LEVEL_LIMITER") - { - auto levelLimiter = std::make_shared(); - if(!parameters.empty()) //If parameters is empty, level limiter works as CREATURES_ONLY limiter - { - levelLimiter->minLevel = parameters[0].Integer(); - if(parameters[1].isNumber()) - levelLimiter->maxLevel = parameters[1].Integer(); - } - return levelLimiter; - } - else if(limiterType == "CREATURE_TERRAIN_LIMITER") - { - auto terrainLimiter = std::make_shared(); - if(!parameters.empty()) - { - VLC->identifiers()->requestIdentifier("terrain", parameters[0], [=](si32 terrain) - { - //TODO: support limiters - //terrainLimiter->terrainType = terrain; - }); - } - return terrainLimiter; - } - else if(limiterType == "UNIT_ON_HEXES") { - auto hexLimiter = std::make_shared(); - if(!parameters.empty()) - { - for (const auto & parameter: parameters){ - if(parameter.isNumber()) - hexLimiter->applicableHexes.insert(BattleHex(parameter.Integer())); - } - } - return hexLimiter; - } - else - { - logMod->error("Error: invalid customizable limiter type %s.", limiterType); - } - } - break; - default: - break; - } - return nullptr; -} - -std::shared_ptr JsonUtils::parseBonus(const JsonNode &ability) -{ - auto b = std::make_shared(); - if (!parseBonus(ability, b.get())) - { - // 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.toJson()); - b->type = BonusType::NONE; - return b; - } - return b; -} - -std::shared_ptr JsonUtils::parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description) -{ - /* duration = BonusDuration::PERMANENT - source = BonusSource::TOWN_STRUCTURE - bonusType, val, subtype - get from json - */ - auto b = std::make_shared(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, BuildingTypeUniqueID(faction, building), description); - - if(!parseBonus(ability, b.get())) - return nullptr; - return b; -} - -static BonusParams convertDeprecatedBonus(const JsonNode &ability) -{ - if(vstd::contains(deprecatedBonusSet, ability["type"].String())) - { - logMod->warn("There is deprecated bonus found:\n%s\nTrying to convert...", ability.toJson()); - auto params = BonusParams(ability["type"].String(), - ability["subtype"].isString() ? ability["subtype"].String() : "", - ability["subtype"].isNumber() ? ability["subtype"].Integer() : -1); - if(params.isConverted) - { - if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && bonusValueMap.find(ability["valueType"].String())->second == BonusValueType::PERCENT_TO_BASE) //assume secondary skill special - { - params.valueType = BonusValueType::PERCENT_TO_TARGET_TYPE; - params.targetType = BonusSource::SECONDARY_SKILL; - } - - logMod->warn("Please, use this bonus:\n%s\nConverted successfully!", params.toJson().toJson()); - return params; - } - else - logMod->error("Cannot convert bonus!\n%s", ability.toJson()); - } - BonusParams ret; - ret.isConverted = false; - return ret; -} - -static TUpdaterPtr parseUpdater(const JsonNode & updaterJson) -{ - switch(updaterJson.getType()) - { - case JsonNode::JsonType::DATA_STRING: - return parseByMap(bonusUpdaterMap, &updaterJson, "updater type "); - break; - case JsonNode::JsonType::DATA_STRUCT: - if(updaterJson["type"].String() == "GROWS_WITH_LEVEL") - { - auto updater = std::make_shared(); - const JsonVector param = updaterJson["parameters"].Vector(); - updater->valPer20 = static_cast(param[0].Integer()); - if(param.size() > 1) - updater->stepSize = static_cast(param[1].Integer()); - return updater; - } - else if (updaterJson["type"].String() == "ARMY_MOVEMENT") - { - auto updater = std::make_shared(); - if(updaterJson["parameters"].isVector()) - { - const auto & param = updaterJson["parameters"].Vector(); - if(param.size() < 4) - logMod->warn("Invalid ARMY_MOVEMENT parameters, using default!"); - else - { - updater->base = static_cast(param.at(0).Integer()); - updater->divider = static_cast(param.at(1).Integer()); - updater->multiplier = static_cast(param.at(2).Integer()); - updater->max = static_cast(param.at(3).Integer()); - } - return updater; - } - } - else - logMod->warn("Unknown updater type \"%s\"", updaterJson["type"].String()); - break; - } - return nullptr; -} - -bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) -{ - const JsonNode * value = nullptr; - - std::string type = ability["type"].String(); - auto it = bonusNameMap.find(type); - auto params = std::make_unique(false); - if (it == bonusNameMap.end()) - { - params = std::make_unique(convertDeprecatedBonus(ability)); - if(!params->isConverted) - { - logMod->error("Error: invalid ability type %s.", type); - return false; - } - b->type = params->type; - b->val = params->val.value_or(0); - b->valType = params->valueType.value_or(BonusValueType::ADDITIVE_VALUE); - if(params->targetType) - b->targetSourceType = params->targetType.value(); - } - else - b->type = it->second; - - loadBonusSubtype(b->subtype, b->type, params->isConverted ? params->toJson()["subtype"] : ability["subtype"]); - - if(!params->isConverted) - { - b->val = static_cast(ability["val"].Float()); - - value = &ability["valueType"]; - if (!value->isNull()) - b->valType = static_cast(parseByMapN(bonusValueMap, value, "value type ")); - } - - b->stacking = ability["stacking"].String(); - - resolveAddInfo(b->additionalInfo, ability); - - b->turnsRemain = static_cast(ability["turns"].Float()); - - if(!ability["description"].isNull()) - { - if (ability["description"].isString()) - b->description = ability["description"].String(); - if (ability["description"].isNumber()) - b->description = VLC->generaltexth->translate("core.arraytxt", ability["description"].Integer()); - } - - value = &ability["effectRange"]; - if (!value->isNull()) - b->effectRange = static_cast(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(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(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()); - 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()) - { - auto it = bonusNameMap.find(value->String()); - if(it != bonusNameMap.end()) - { - type = it->second; - ret = ret.And(Selector::type()(it->second)); - } - } - 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 src = std::nullopt; //Fixes for GCC false maybe-uninitialized - std::optional 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)); - } - CAddInfo info; - value = &ability["addInfo"]; - if(!value->isNull()) - { - resolveAddInfo(info, ability["addInfo"]); - ret = ret.And(Selector::info()(info)); - } - 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; -} - //returns first Key with value equal to given one template Key reverseMapFirst(const Val & val, const std::map & map) diff --git a/lib/json/JsonUtils.h b/lib/json/JsonUtils.h index ce1d88141..ec590e1d1 100644 --- a/lib/json/JsonUtils.h +++ b/lib/json/JsonUtils.h @@ -10,20 +10,11 @@ #pragma once #include "JsonNode.h" -#include "../GameConstants.h" VCMI_LIB_NAMESPACE_BEGIN namespace JsonUtils { - DLL_LINKAGE std::shared_ptr parseBonus(const JsonVector & ability_vec); - DLL_LINKAGE std::shared_ptr parseBonus(const JsonNode & ability); - DLL_LINKAGE std::shared_ptr parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description); - DLL_LINKAGE bool parseBonus(const JsonNode & ability, Bonus * placement); - DLL_LINKAGE std::shared_ptr parseLimiter(const JsonNode & limiter); - DLL_LINKAGE CSelector parseSelector(const JsonNode &ability); - DLL_LINKAGE void resolveAddInfo(CAddInfo & var, const JsonNode & node); - /** * @brief recursively merges source into dest, replacing identical fields * struct : recursively calls this function diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 0b4dca5b6..be24c0258 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -32,7 +32,7 @@ #include "../StartInfo.h" #include "CGTownInstance.h" #include "../campaign/CampaignState.h" -#include "../json/JsonUtils.h" +#include "../json/JsonBonus.h" #include "../pathfinder/TurnInfo.h" #include "../serializer/JsonSerializeFormat.h" #include "../mapObjectConstructors/AObjectTypeHandler.h" diff --git a/lib/serializer/JsonUpdater.cpp b/lib/serializer/JsonUpdater.cpp index d1719a029..92b4bfc45 100644 --- a/lib/serializer/JsonUpdater.cpp +++ b/lib/serializer/JsonUpdater.cpp @@ -12,7 +12,7 @@ #include "../bonuses/CBonusSystemNode.h" #include "../bonuses/Bonus.h" -#include "../json/JsonUtils.h" +#include "../json/JsonBonus.h" VCMI_LIB_NAMESPACE_BEGIN diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index e5908340a..0aa239f93 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -25,6 +25,7 @@ #include "../battle/BattleInfo.h" #include "../battle/CBattleInfoCallback.h" #include "../battle/Unit.h" +#include "../json/JsonBonus.h" #include "../json/JsonUtils.h" #include "../mapObjects/CGHeroInstance.h" //todo: remove #include "../serializer/CSerializer.h" diff --git a/lib/spells/TargetCondition.cpp b/lib/spells/TargetCondition.cpp index cabe2aa87..7b9407afe 100644 --- a/lib/spells/TargetCondition.cpp +++ b/lib/spells/TargetCondition.cpp @@ -17,7 +17,7 @@ #include "../battle/Unit.h" #include "../bonuses/BonusParams.h" #include "../bonuses/BonusList.h" -#include "../json/JsonUtils.h" +#include "../json/JsonBonus.h" #include "../modding/IdentifierStorage.h" #include "../modding/ModUtility.h" #include "../serializer/JsonSerializeFormat.h" diff --git a/lib/spells/effects/Moat.cpp b/lib/spells/effects/Moat.cpp index b51e91b1b..45531d8b1 100644 --- a/lib/spells/effects/Moat.cpp +++ b/lib/spells/effects/Moat.cpp @@ -18,7 +18,7 @@ #include "../../bonuses/Limiters.h" #include "../../battle/IBattleState.h" #include "../../battle/CBattleInfoCallback.h" -#include "../../json/JsonUtils.h" +#include "../../json/JsonBonus.h" #include "../../serializer/JsonSerializeFormat.h" #include "../../networkPacks/PacksForClient.h" #include "../../networkPacks/PacksForClientBattle.h" diff --git a/lib/spells/effects/Timed.cpp b/lib/spells/effects/Timed.cpp index 2fa25d090..a90c903d7 100644 --- a/lib/spells/effects/Timed.cpp +++ b/lib/spells/effects/Timed.cpp @@ -17,7 +17,7 @@ #include "../../battle/IBattleState.h" #include "../../battle/CBattleInfoCallback.h" #include "../../battle/Unit.h" -#include "../../json/JsonUtils.h" +#include "../../json/JsonBonus.h" #include "../../mapObjects/CGHeroInstance.h" #include "../../networkPacks/PacksForClientBattle.h" #include "../../networkPacks/SetStackEffect.h"