diff --git a/config/heroes/castle.json b/config/heroes/castle.json index a5cca7a54..68fcd3297 100644 --- a/config/heroes/castle.json +++ b/config/heroes/castle.json @@ -9,17 +9,19 @@ { "skill" : "leadership", "level": "basic" }, { "skill" : "archery", "level": "basic" } ], - "specialty" : [ - { - "type" : "SECONDARY_SKILL_PREMY", - "subtype" : "skill.archery", - "valueType" : "PERCENT_TO_BASE", - "updater" : { - "type" : "GROWS_WITH_LEVEL", - "parameters" : [100] + "specialty" : { + "bonuses" : { + "archery" : { + "type" : "SECONDARY_SKILL_PREMY", + "subtype" : "skill.archery", + "valueType" : "PERCENT_TO_BASE", + "updater" : { + "type" : "GROWS_WITH_LEVEL", + "parameters" : [100] + } } } - ] + } }, "valeska": { @@ -31,55 +33,45 @@ { "skill" : "leadership", "level": "basic" }, { "skill" : "archery", "level": "basic" } ], - "specialty" : [ - { + "specialty" : { + "base" : { "limiter" : { "parameters" : [ "archer", true ], "type" : "CREATURE_TYPE_LIMITER" - }, - "type" : "STACKS_SPEED", - "val" : 1 - }, - { - "limiter" : { - "parameters" : [ - "archer", - true - ], - "type" : "CREATURE_TYPE_LIMITER" - }, - "subtype" : "primSkill.attack", - "type" : "PRIMARY_SKILL", - "updater" : { - "parameters" : [ - 6, - 2 - ], - "type" : "GROWS_WITH_LEVEL" } }, - { - "limiter" : { - "parameters" : [ - "archer", - true - ], - "type" : "CREATURE_TYPE_LIMITER" + "bonuses" : { + "attack" : { + "subtype" : "primSkill.attack", + "type" : "PRIMARY_SKILL", + "updater" : { + "parameters" : [ + 6, + 2 + ], + "type" : "GROWS_WITH_LEVEL" + } }, - "subtype" : "primSkill.defence", - "type" : "PRIMARY_SKILL", - "updater" : { - "parameters" : [ - 3, - 2 - ], - "type" : "GROWS_WITH_LEVEL" + "defence" : { + "subtype" : "primSkill.defence", + "type" : "PRIMARY_SKILL", + "updater" : { + "parameters" : [ + 3, + 2 + ], + "type" : "GROWS_WITH_LEVEL" + } + }, + "speed" : { + "type" : "STACKS_SPEED", + "val" : 1 } } - ] + } }, "edric": { diff --git a/config/schemas/hero.json b/config/schemas/hero.json index ddbf338ab..99c532621 100644 --- a/config/schemas/hero.json +++ b/config/schemas/hero.json @@ -115,11 +115,11 @@ "additionalItems" : true }, "specialty": { - "type":"array", - "description": "Description of hero specialty using bonus system", - "items": { - "oneOf" : [ - { + "oneOf" : [ + { + "type":"array", + "description": "Description of hero specialty using bonus system (deprecated)", + "items": { "type" : "object", "additionalProperties" : false, "required" : [ "bonuses" ], @@ -134,14 +134,26 @@ "items" : { "$ref" : "vcmi:bonus" } } } - }, - { - "type" : "object", - "description" : "List of bonuses", - "items" : { "$ref" : "vcmi:bonus" } } - ] - } + }, + { + "type" : "object", + "description": "Description of hero specialty using bonus system", + "additionalProperties" : false, + "required" : [ "bonuses" ], + "properties" : { + "base" : { + "type" : "object", + "description" : "Will be merged with all bonuses." + }, + "bonuses" : { + "type" : "object", + "description" : "Set of bonuses", + "additionalProperties" : { "$ref" : "vcmi:bonus" } + } + } + } + ] }, "spellbook": { "type":"array", diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index c0591807c..c058bc9f8 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -533,6 +533,21 @@ std::vector> SpecialtyInfoToBonuses(const SSpecialtyInfo return result; } +void CHeroHandler::beforeValidate(JsonNode & object) +{ + //handle "base" specialty info + const JsonNode & specialtyNode = object["specialty"]; + if(specialtyNode.getType() == JsonNode::DATA_STRUCT) + { + const JsonNode & base = specialtyNode["base"]; + if(!base.isNull()) + { + for(auto keyValue : specialtyNode["bonuses"].Struct()) + JsonUtils::inherit(keyValue.second, base); + } + } +} + void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node) { int sid = hero->ID.getNum(); @@ -545,11 +560,11 @@ void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node) }; //deprecated, used only for original specialties - const JsonNode & specialties = node["specialties"]; - if (!specialties.isNull()) + const JsonNode & specialtiesNode = node["specialties"]; + if (!specialtiesNode.isNull()) { logMod->warn("Hero %s has deprecated specialties format.", hero->identifier); - for(const JsonNode &specialty : node["specialties"].Vector()) + for(const JsonNode &specialty : specialtiesNode.Vector()) { SSpecialtyInfo spec; spec.type = specialty["type"].Float(); @@ -560,19 +575,22 @@ void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node) hero->specDeprecated.push_back(spec); } } - //new format, using bonus system - for(const JsonNode & specialty : node["specialty"].Vector()) + //new(er) format, using bonus system + const JsonNode & specialtyNode = node["specialty"]; + if(specialtyNode.getType() == JsonNode::DATA_VECTOR) { - //deprecated new format - if(!specialty["bonuses"].isNull()) + //deprecated middle-aged format + for(const JsonNode & specialty : node["specialty"].Vector()) { for (const JsonNode & bonus : specialty["bonuses"].Vector()) hero->specialty.push_back(prepSpec(JsonUtils::parseBonus(bonus))); } - else //proper new format - { - hero->specialty.push_back(prepSpec(JsonUtils::parseBonus(specialty))); - } + } + else if(specialtyNode.getType() == JsonNode::DATA_STRUCT) + { + //proper new format + for(auto keyValue : specialtyNode["bonuses"].Struct()) + hero->specialty.push_back(prepSpec(JsonUtils::parseBonus(keyValue.second))); } } diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 5a667a8ed..77be2ccc3 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -300,6 +300,7 @@ public: std::vector loadLegacyData(size_t dataSize) override; + void beforeValidate(JsonNode & object); void loadObject(std::string scope, std::string name, const JsonNode & data) override; void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; void afterLoadFinalization() override;