Quellcode durchsuchen

vcmi: deprecated bonus converter

It converts almost all sorts of deprecated bonuses from mods
when loading json. It can print to console correct new variant
or bonus.

Also removed a bunch of deprecated bonuses from list.
It will break saves!!!
Konstantin vor 2 Jahren
Ursprung
Commit
930955f268
5 geänderte Dateien mit 318 neuen und 30 gelöschten Zeilen
  1. 0 7
      config/bonuses.json
  2. 11 6
      lib/CHeroHandler.cpp
  3. 221 5
      lib/HeroBonus.cpp
  4. 22 4
      lib/HeroBonus.h
  5. 64 8
      lib/JsonNode.cpp

+ 0 - 7
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":

+ 11 - 6
lib/CHeroHandler.cpp

@@ -555,12 +555,17 @@ std::vector<std::shared_ptr<Bonus>> 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;

+ 221 - 5
lib/HeroBonus.cpp

@@ -34,7 +34,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 #define BONUS_NAME(x) { #x, Bonus::x },
 	const std::map<std::string, Bonus::BonusType> 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<std::string, TUpdaterPtr> bonusUpdaterMap =
 	{"ARMY_MOVEMENT", std::make_shared<ArmyMovementUpdater>()}
 };
 
+const std::set<std::string> 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)
 {

+ 22 - 4
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<Bonus>
 
 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<std::string, Bonus::LimitEffect> bonusLimitEff
 extern DLL_LINKAGE const std::map<std::string, TLimiterPtr> bonusLimiterMap;
 extern DLL_LINKAGE const std::map<std::string, TPropagatorPtr> bonusPropagatorMap;
 extern DLL_LINKAGE const std::map<std::string, TUpdaterPtr> bonusUpdaterMap;
+extern DLL_LINKAGE const std::set<std::string> deprecatedBonusSet;
 
 // BonusList template that requires full interface of CBonusSystemNode
 template <class InputIterator>

+ 64 - 8
lib/JsonNode.cpp

@@ -805,26 +805,82 @@ std::shared_ptr<Bonus> 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<si32>(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<BonusParams>(false);
 	if (it == bonusNameMap.end())
 	{
-		logMod->error("Error: invalid ability type %s.", type);
-		return false;
+		params = std::make_unique<BonusParams>(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<si32>(ability["val"].Float());
+	if(!params->isConverted)
+	{
+		b->val = static_cast<si32>(ability["val"].Float());
 
-	value = &ability["valueType"];
-	if (!value->isNull())
-		b->valType = static_cast<Bonus::ValueType>(parseByMapN(bonusValueMap, value, "value type "));
+		value = &ability["valueType"];
+		if (!value->isNull())
+			b->valType = static_cast<Bonus::ValueType>(parseByMapN(bonusValueMap, value, "value type "));
+	}
 
 	b->stacking = ability["stacking"].String();