diff --git a/config/bonuses.json b/config/bonuses.json index d10ab1388..ff077c3b1 100644 --- a/config/bonuses.json +++ b/config/bonuses.json @@ -1,5 +1,4 @@ //TODO: selector-based config -// SECONDARY_SKILL_PREMY // school immunities // LEVEL_SPELL_IMMUNITY @@ -432,12 +431,6 @@ } }, - "SECONDARY_SKILL_PREMY": - { - "hidden": true - //todo: selector based config - }, - "SELF_LUCK": { "graphics": diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 112c318af..3e25fd54c 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -555,12 +555,17 @@ std::vector> SpecialtyInfoToBonuses(const SSpecialtyInfo AddSpecialtyForCreature(spec.additionalinfo, bonus, result); break; case 2: //secondary skill - bonus->type = Bonus::SECONDARY_SKILL_PREMY; - bonus->valType = Bonus::PERCENT_TO_BASE; - bonus->subtype = spec.subtype; - bonus->updater.reset(new TimesHeroLevelUpdater()); - result.push_back(bonus); - break; + { + auto params = BonusParams("SECONDARY_SKILL_PREMY", "", spec.subtype); + bonus->type = params.type; + if(params.subtypeRelevant) + bonus->subtype = params.subtype; + bonus->valType = Bonus::PERCENT_TO_TARGET_TYPE; + bonus->targetSourceType = Bonus::SECONDARY_SKILL; + bonus->updater.reset(new TimesHeroLevelUpdater()); + result.push_back(bonus); + break; + } case 3: //spell damage bonus, level dependent but calculated elsewhere bonus->type = Bonus::SPECIAL_SPELL_LEV; bonus->subtype = spec.subtype; diff --git a/lib/HeroBonus.cpp b/lib/HeroBonus.cpp index b146e9d3c..c95538799 100644 --- a/lib/HeroBonus.cpp +++ b/lib/HeroBonus.cpp @@ -34,7 +34,6 @@ VCMI_LIB_NAMESPACE_BEGIN #define BONUS_NAME(x) { #x, Bonus::x }, const std::map bonusNameMap = { BONUS_LIST - {"SIGHT_RADIOUS", Bonus::SIGHT_RADIUS} /*the correct word is RADIUS, but this one's already used in mods. Deprecated. */ }; #undef BONUS_NAME @@ -99,6 +98,21 @@ const std::map bonusUpdaterMap = {"ARMY_MOVEMENT", std::make_shared()} }; +const std::set deprecatedBonusSet = { + "SECONDARY_SKILL_PREMY", + "SECONDARY_SKILL_VAL2", + "MAXED_SPELL", + "LAND_MOVEMENT", + "SEA_MOVEMENT", + "SIGHT_RADIOUS", + "NO_TYPE", + "SPECIAL_SECONDARY_SKILL", + "FULL_HP_REGENERATION", + "KING1", + "KING2", + "KING3", +}; + ///CBonusProxy CBonusProxy::CBonusProxy(const IBonusBearer * Target, CSelector Selector) : bonusListCachedLast(0), @@ -1655,8 +1669,6 @@ JsonNode subtypeToJson(Bonus::BonusType type, int subtype) { case Bonus::PRIMARY_SKILL: return JsonUtils::stringNode("primSkill." + PrimarySkill::names[subtype]); - case Bonus::SECONDARY_SKILL_PREMY: - return JsonUtils::stringNode(CSkillHandler::encodeSkillWithType(subtype)); case Bonus::SPECIAL_SPELL_LEV: case Bonus::SPECIFIC_SPELL_DAMAGE: case Bonus::SPELL: @@ -1762,8 +1774,6 @@ std::string Bonus::nameForBonus() const { case Bonus::PRIMARY_SKILL: return PrimarySkill::names[subtype]; - case Bonus::SECONDARY_SKILL_PREMY: - return CSkillHandler::encodeSkill(subtype); case Bonus::SPECIAL_SPELL_LEV: case Bonus::SPECIFIC_SPELL_DAMAGE: case Bonus::SPELL: @@ -1782,6 +1792,212 @@ std::string Bonus::nameForBonus() const } } +BonusParams::BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr, int deprecatedSubtype): + isConverted(true) +{ + if(deprecatedTypeStr == "SECONDARY_SKILL_PREMY" || deprecatedTypeStr == "SPECIAL_SECONDARY_SKILL") + { + if(deprecatedSubtype == SecondarySkill::PATHFINDING || deprecatedSubtypeStr == "skill.pathfinding") + type = Bonus::ROUGH_TERRAIN_DISCOUNT; + else if(deprecatedSubtype == SecondarySkill::DIPLOMACY || deprecatedSubtypeStr == "skill.diplomacy") + type = Bonus::WANDERING_CREATURES_JOIN_BONUS; + else if(deprecatedSubtype == SecondarySkill::WISDOM || deprecatedSubtypeStr == "skill.wisdom") + type = Bonus::MAX_LEARNABLE_SPELL_LEVEL; + else if(deprecatedSubtype == SecondarySkill::MYSTICISM || deprecatedSubtypeStr == "skill.mysticism") + type = Bonus::MANA_REGENERATION; + else if(deprecatedSubtype == SecondarySkill::NECROMANCY || deprecatedSubtypeStr == "skill.necromancy") + type = Bonus::UNDEAD_RAISE_PERCENTAGE; + else if(deprecatedSubtype == SecondarySkill::LEARNING || deprecatedSubtypeStr == "skill.learning") + type = Bonus::HERO_EXPERIENCE_GAIN_PERCENT; + else if(deprecatedSubtype == SecondarySkill::RESISTANCE || deprecatedSubtypeStr == "skill.resistance") + type = Bonus::MAGIC_RESISTANCE; + else if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye") + type = Bonus::LEARN_BATTLE_SPELL_CHANCE; + else if(deprecatedSubtype == SecondarySkill::INTELLIGENCE || deprecatedSubtypeStr == "skill.intelligence") + { + type = Bonus::MANA_PER_KNOWLEDGE; + valueType = Bonus::PERCENT_TO_BASE; + valueTypeRelevant = true; + } + else if(deprecatedSubtype == SecondarySkill::SORCERY || deprecatedSubtypeStr == "skill.sorcery") + type = Bonus::SPELL_DAMAGE; + else if(deprecatedSubtype == SecondarySkill::SCHOLAR || deprecatedSubtypeStr == "skill.scholar") + type = Bonus::LEARN_MEETING_SPELL_LIMIT; + else if(deprecatedSubtype == SecondarySkill::ARCHERY|| deprecatedSubtypeStr == "skill.archery") + { + subtype = 1; + subtypeRelevant = true; + type = Bonus::PERCENTAGE_DAMAGE_BOOST; + } + else if(deprecatedSubtype == SecondarySkill::OFFENCE || deprecatedSubtypeStr == "skill.offence") + { + subtype = 0; + subtypeRelevant = true; + type = Bonus::PERCENTAGE_DAMAGE_BOOST; + } + else if(deprecatedSubtype == SecondarySkill::ARMORER || deprecatedSubtypeStr == "skill.armorer") + { + subtype = -1; + subtypeRelevant = true; + type = Bonus::GENERAL_DAMAGE_REDUCTION; + } + else if(deprecatedSubtype == SecondarySkill::NAVIGATION || deprecatedSubtypeStr == "skill.navigation") + { + subtype = 0; + subtypeRelevant = true; + valueType = Bonus::PERCENT_TO_BASE; + valueTypeRelevant = true; + type = Bonus::MOVEMENT; + } + else if(deprecatedSubtype == SecondarySkill::LOGISTICS || deprecatedSubtypeStr == "skill.logistics") + { + subtype = 0; + subtypeRelevant = true; + valueType = Bonus::PERCENT_TO_BASE; + valueTypeRelevant = true; + type = Bonus::MOVEMENT; + } + else if(deprecatedSubtype == SecondarySkill::ESTATES || deprecatedSubtypeStr == "skill.estates") + { + type = Bonus::GENERATE_RESOURCE; + subtype = Res::GOLD; + subtypeRelevant = true; + } + else if(deprecatedSubtype == SecondarySkill::AIR_MAGIC || deprecatedSubtypeStr == "skill.airMagic") + { + type = Bonus::MAGIC_SCHOOL_SKILL; + subtypeRelevant = true; + subtype = 4; + } + else if(deprecatedSubtype == SecondarySkill::WATER_MAGIC || deprecatedSubtypeStr == "skill.waterMagic") + { + type = Bonus::MAGIC_SCHOOL_SKILL; + subtypeRelevant = true; + subtype = 1; + } + else if(deprecatedSubtype == SecondarySkill::FIRE_MAGIC || deprecatedSubtypeStr == "skill.fireMagic") + { + type = Bonus::MAGIC_SCHOOL_SKILL; + subtypeRelevant = true; + subtype = 2; + } + else if(deprecatedSubtype == SecondarySkill::EARTH_MAGIC || deprecatedSubtypeStr == "skill.earthMagic") + { + type = Bonus::MAGIC_SCHOOL_SKILL; + subtypeRelevant = true; + subtype = 8; + } + else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery") + { + type = Bonus::BONUS_DAMAGE_CHANCE; + subtypeRelevant = true; + subtypeStr = "core:creature.ballista"; + } + else if (deprecatedSubtype == SecondarySkill::FIRST_AID || deprecatedSubtypeStr == "skill.firstAid") + { + type = Bonus::SPECIFIC_SPELL_POWER; + subtypeRelevant = true; + subtypeStr = "core:spell.firstAid"; + } + else if (deprecatedSubtype == SecondarySkill::BALLISTICS || deprecatedSubtypeStr == "skill.ballistics") + { + type = Bonus::CATAPULT_EXTRA_SHOTS; + subtypeRelevant = true; + subtypeStr = "core:spell.catapultShot"; + } + else + isConverted = false; + } + else if (deprecatedTypeStr == "SECONDARY_SKILL_VAL2") + { + if(deprecatedSubtype == SecondarySkill::EAGLE_EYE || deprecatedSubtypeStr == "skill.eagleEye") + type = Bonus::LEARN_BATTLE_SPELL_LEVEL_LIMIT; + else if (deprecatedSubtype == SecondarySkill::ARTILLERY || deprecatedSubtypeStr == "skill.artillery") + { + type = Bonus::HERO_GRANTS_ATTACKS; + subtypeRelevant = true; + subtypeStr = "core:creature.ballista"; + } + else + isConverted = false; + } + else if (deprecatedTypeStr == "SEA_MOVEMENT") + { + subtype = 0; + subtypeRelevant = true; + valueType = Bonus::ADDITIVE_VALUE; + valueTypeRelevant = true; + type = Bonus::MOVEMENT; + } + else if (deprecatedTypeStr == "LAND_MOVEMENT") + { + subtype = 1; + subtypeRelevant = true; + valueType = Bonus::ADDITIVE_VALUE; + valueTypeRelevant = true; + type = Bonus::MOVEMENT; + } + else if (deprecatedTypeStr == "MAXED_SPELL") + { + type = Bonus::SPELL; + subtypeStr = deprecatedSubtypeStr; + subtypeRelevant = true; + valueType = Bonus::INDEPENDENT_MAX; + valueTypeRelevant = true; + val = 3; + valRelevant = true; + } + else if (deprecatedTypeStr == "FULL_HP_REGENERATION") + { + type = Bonus::HP_REGENERATION; + val = 100000; //very high value to always chose stack health + valRelevant = true; + } + else if (deprecatedTypeStr == "KING1") + { + type = Bonus::KING; + val = 0; + valRelevant = true; + } + else if (deprecatedTypeStr == "KING2") + { + type = Bonus::KING; + val = 2; + valRelevant = true; + } + else if (deprecatedTypeStr == "KING3") + { + type = Bonus::KING; + val = 3; + valRelevant = true; + } + else if (deprecatedTypeStr == "SIGHT_RADIOUS") + type = Bonus::SIGHT_RADIUS; + else + isConverted = false; +} + +const JsonNode & BonusParams::toJson() +{ + assert(isConverted); + if(ret.isNull()) + { + ret["type"].String() = vstd::findKey(bonusNameMap, type); + if(subtypeRelevant && !subtypeStr.empty()) + ret["subtype"].String() = subtypeStr; + else if(subtypeRelevant) + ret["subtype"].Integer() = subtype; + if(valueTypeRelevant) + ret["valueType"].String() = vstd::findKey(bonusValueMap, valueType); + if(valRelevant) + ret["val"].Float() = val; + if(targetTypeRelevant) + ret["targetSourceType"].String() = vstd::findKey(bonusSourceMap, targetType); + jsonCreated = true; + } + return ret; +}; + Bonus::Bonus(Bonus::BonusDuration Duration, BonusType Type, BonusSource Src, si32 Val, ui32 ID, std::string Desc, si32 Subtype) : duration((ui16)Duration), type(Type), subtype(Subtype), source(Src), val(Val), sid(ID), description(Desc) { diff --git a/lib/HeroBonus.h b/lib/HeroBonus.h index 70b61248e..ba28ff938 100644 --- a/lib/HeroBonus.h +++ b/lib/HeroBonus.h @@ -179,7 +179,6 @@ public: BONUS_NAME(MANA_REGENERATION) /*points per turn apart from normal (1 + mysticism)*/ \ BONUS_NAME(FULL_MANA_REGENERATION) /*all mana points are replenished every day*/ \ BONUS_NAME(NONEVIL_ALIGNMENT_MIX) /*good and neutral creatures can be mixed without morale penalty*/ \ - BONUS_NAME(SECONDARY_SKILL_PREMY) /*%*/ \ BONUS_NAME(SURRENDER_DISCOUNT) /*%*/ \ BONUS_NAME(STACKS_SPEED) /*additional info - percent of speed bonus applied after direct bonuses; >0 - added, <0 - subtracted to this part*/ \ BONUS_NAME(FLYING_MOVEMENT) /*value - penalty percentage*/ \ @@ -209,7 +208,6 @@ public: BONUS_NAME(IMPROVED_NECROMANCY) /* raise more powerful creatures: subtype - creature type raised, addInfo - [required necromancy level, required stack level], val - necromancy level for this purpose */ \ BONUS_NAME(CREATURE_GROWTH_PERCENT) /*increases growth of all units in all towns, val - percentage*/ \ BONUS_NAME(FREE_SHIP_BOARDING) /*movement points preserved with ship boarding and landing*/ \ - BONUS_NAME(NO_TYPE) \ BONUS_NAME(FLYING) \ BONUS_NAME(SHOOTER) \ BONUS_NAME(CHARGE_IMMUNITY) \ @@ -281,7 +279,6 @@ public: BONUS_NAME(NO_LUCK) /*eg. when fighting on cursed ground*/ \ BONUS_NAME(NO_MORALE) /*eg. when fighting on cursed ground*/ \ BONUS_NAME(DARKNESS) /*val = radius */ \ - BONUS_NAME(SPECIAL_SECONDARY_SKILL) /*subtype = id, val = value per level in percent*/ \ BONUS_NAME(SPECIAL_SPELL_LEV) /*subtype = id, val = value per level in percent*/\ BONUS_NAME(SPELL_DAMAGE) /*val = value, now works for sorcery*/\ BONUS_NAME(SPECIFIC_SPELL_DAMAGE) /*subtype = id of spell, val = value*/\ @@ -315,7 +312,6 @@ public: BONUS_NAME(CATAPULT_EXTRA_SHOTS) /*val - power of catapult effect, requires CATAPULT bonus to work*/\ BONUS_NAME(RANGED_RETALIATION) /*allows shooters to perform ranged retaliation*/\ BONUS_NAME(BLOCKS_RANGED_RETALIATION) /*disallows ranged retaliation for shooter unit, BLOCKS_RETALIATION bonus is for melee retaliation only*/\ - BONUS_NAME(SECONDARY_SKILL_VAL2) /*deprecated. has no effect, will be converted to actual bonus*/ \ BONUS_NAME(MANUAL_CONTROL) /* manually control warmachine with id = subtype, chance = val */ \ BONUS_NAME(WIDE_BREATH) /* initial desigh: dragon breath affecting multiple nearby hexes */\ BONUS_NAME(FIRST_STRIKE) /* first counterattack, then attack if possible */\ @@ -542,6 +538,27 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this DLL_LINKAGE std::ostream & operator<<(std::ostream &out, const Bonus &bonus); +struct DLL_LINKAGE BonusParams { + bool isConverted; + Bonus::BonusType type = Bonus::NONE; + TBonusSubtype subtype = -1; + std::string subtypeStr = ""; + bool subtypeRelevant = false; + Bonus::ValueType valueType = Bonus::BASE_NUMBER; + bool valueTypeRelevant = false; + si32 val = 0; + bool valRelevant = false; + Bonus::BonusSource targetType = Bonus::SECONDARY_SKILL; + bool targetTypeRelevant = false; + + BonusParams(bool isConverted = true) : isConverted(isConverted) {}; + BonusParams(std::string deprecatedTypeStr, std::string deprecatedSubtypeStr = "", int deprecatedSubtype = 0); + const JsonNode & toJson(); +private: + JsonNode ret; + bool jsonCreated = false; +}; + class DLL_LINKAGE BonusList { public: @@ -1232,6 +1249,7 @@ extern DLL_LINKAGE const std::map bonusLimitEff extern DLL_LINKAGE const std::map bonusLimiterMap; extern DLL_LINKAGE const std::map bonusPropagatorMap; extern DLL_LINKAGE const std::map bonusUpdaterMap; +extern DLL_LINKAGE const std::set deprecatedBonusSet; // BonusList template that requires full interface of CBonusSystemNode template diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index aa2a17c0e..6d534f9a3 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -805,26 +805,82 @@ std::shared_ptr JsonUtils::parseBuildingBonus(const JsonNode &ability, Bu 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(!params.valRelevant) { + params.val = static_cast(ability["val"].Float()); + params.valRelevant = true; + if(params.type == Bonus::SPECIFIC_SPELL_POWER) //First Aid value should be substracted by 10 + params.val -= 10; //Base First Aid value + } + Bonus::ValueType valueType = Bonus::ADDITIVE_VALUE; + if(!ability["valueType"].isNull()) + valueType = bonusValueMap.find(ability["valueType"].String())->second; + + if(ability["type"].String() == "SECONDARY_SKILL_PREMY" && valueType == Bonus::PERCENT_TO_BASE) //assume secondary skill special + { + params.valueType = Bonus::PERCENT_TO_TARGET_TYPE; + params.targetType = Bonus::SECONDARY_SKILL; + params.targetTypeRelevant = true; + } + + if(!params.valueTypeRelevant) { + params.valueType = valueType; + params.valueTypeRelevant = true; + } + logMod->warn("Please, use this bonus:\n%s\nConverted sucessfully!", params.toJson().toJson()); + return params; + } + else + logMod->error("Cannot convert bonus!\n%s", ability.toJson()); + } + BonusParams ret; + ret.isConverted = false; + return ret; +} + bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b) { const JsonNode *value; std::string type = ability["type"].String(); auto it = bonusNameMap.find(type); + auto params = std::make_unique(false); if (it == bonusNameMap.end()) { - logMod->error("Error: invalid ability type %s.", type); - return false; + 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; + b->valType = params->valueType; + if(params->targetTypeRelevant) + b->targetSourceType = params->targetType; } - b->type = it->second; + else + b->type = it->second; - resolveIdentifier(b->subtype, ability, "subtype"); + resolveIdentifier(b->subtype, params->isConverted ? params->toJson() : ability, "subtype"); - b->val = static_cast(ability["val"].Float()); + 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 ")); + value = &ability["valueType"]; + if (!value->isNull()) + b->valType = static_cast(parseByMapN(bonusValueMap, value, "value type ")); + } b->stacking = ability["stacking"].String();