浏览代码

More generic support for bonus subtypes descriptions

Ivan Savenko 5 月之前
父节点
当前提交
6c5b2b8e63
共有 4 个文件被更改,包括 76 次插入23 次删除
  1. 19 17
      Mods/vcmi/Content/config/english.json
  2. 27 0
      config/bonuses.json
  3. 28 6
      lib/CBonusTypeHandler.cpp
  4. 2 0
      lib/CBonusTypeHandler.h

+ 19 - 17
Mods/vcmi/Content/config/english.json

@@ -621,9 +621,8 @@
 	
 	"mapObject.core.hillFort.object.description" : "Upgrades creatures. Levels 1 - 4 are less expensive than in associated town.",
 	
-	"core.bonus.ADDITIONAL_ATTACK.description" : "{Additional attacks}\nUnit can attack an additional {$val} times",
+	"core.bonus.ADDITIONAL_ATTACK.description" : "{Additional attacks}\nUnit can attack an additional {$val} times", // TODO: alternative descriptions for effect range
 	"core.bonus.ADDITIONAL_RETALIATION.description" : "{Additional retaliations}\nUnit can retaliate ${val} extra times",
-	"core.bonus.AIR_IMMUNITY.description" : "{Immune to Air Magic}\nImmune to all spells from the school of Air magic",
 	"core.bonus.ATTACKS_ALL_ADJACENT.description" : "{Attack all around}\nAttacks all adjacent enemies in addition to the primary target",
 	"core.bonus.BLOCKS_RANGED_RETALIATION.description" : "{No ranged retaliation}\nThe enemy cannot retaliate when shot by this unit",
 	"core.bonus.BLOCKS_RETALIATION.description" : "{No retaliation}\nThe enemy cannot retaliate when attacked in melee by this unit",
@@ -637,7 +636,6 @@
 	"core.bonus.DISINTEGRATE.description" : "{Disintegrate}\nWhen this unit dies, it will leave no corpse behind",
 	"core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "{Death Blow}\nHas a ${val}% chance of dealing double base damage when attacking",
 	"core.bonus.DRAGON_NATURE.description" : "{Dragon}\nThis creature is a Dragon",
-	"core.bonus.EARTH_IMMUNITY.description" : "{Immune to Earth Magic}\nImmune to all spells from the school of Earth magic",
 	"core.bonus.ENCHANTED.description" : "{Enchanted}\nPermanently affected by ${subtype.spell}",
 	"core.bonus.ENCHANTER.description" : "{Enchanter}\nCan cast ${subtype.spell} every turn",
 	"core.bonus.ENEMY_ATTACK_REDUCTION.description" : "{Ignore Attack (${val}%) }\nWhen being attacked, ${val}% of the attacker's attack is ignored",
@@ -645,19 +643,25 @@
 	"core.bonus.FEAR.description" : "{Fear}\nEnemy units have a 10% chance of freezing in fear",
 	"core.bonus.FEARLESS.description" : "{Fearless}\nImmune to Fear ability",
 	"core.bonus.FEROCITY.description" : "{Ferocity}\nAttacks ${val} additional times if killed anybody",
-	"core.bonus.FIRE_IMMUNITY.description" : "{Immune to Fire Magic}\nImmune to all spells from the school of Fire magic",
 	"core.bonus.FIRE_SHIELD.description" : "{Fire Shield (${val}%) }\nThe unit reflects ${val} of the melee damage received",
 	"core.bonus.FIRST_STRIKE.description" : "{First Strike}\nThe unit retaliates before being attacked",
+	"core.bonus.FIRST_STRIKE.description.bonusSubtype.damageTypeRanged" : "{First Strike}\nThe unit retaliates before being attacked by ranged attack",
+	"core.bonus.FIRST_STRIKE.description.bonusSubtype.damageTypeMelee" : "{First Strike}\nThe unit retaliates before being attacked in melee",
 	"core.bonus.FLYING.description" : "{Can Fly}\nThis unit can fly while moving and will ignore battlefield obstacles",
+	"core.bonus.FLYING.description.bonusSubtype.movementTeleporting" : "{Teleportation}\nThis unit can teleport to any hex and ignore battlefield obstacles",
 	"core.bonus.FREE_SHOOTING.description" : "{Shoot Close}\nRanged attacks of this unit can not be blocked by adjacent enemies",
 	"core.bonus.GARGOYLE.description" : "{Gargoyle}\nThis unit cannot be raised from the dead or healed",
 	"core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "{Reduce Damage (${val}%) }\nReduces physical damage from ranged or melee attacks by ${val}%",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.description.bonusSubtype.damageTypeRanged" : "{Reduce Damage (${val}%) }\nReduces physical damage from ranged attacks by ${val}%",
+	"core.bonus.GENERAL_DAMAGE_REDUCTION.description.bonusSubtype.damageTypeMelee" : "{Reduce Damage (${val}%) }\nReduces physical damage from melee attacks by ${val}%",
 	"core.bonus.HATE.description" : "{Hates ${subtype.creature}}\nDoes ${val}% more damage to ${subtype.creature}",
 	"core.bonus.HEALER.description" : "{Healer}\nHeals allied units",
 	"core.bonus.HP_REGENERATION.description" : "{Regeneration}\nHeals ${val} hit points every round",
 	"core.bonus.INVINCIBLE.description" : "{Invincible}\nCannot be affected by anything",
 	"core.bonus.JOUSTING.description" : "{Jousting bonus}\nMoving before an attack increases the damage by ${val} for each hex travelled",
-	"core.bonus.KING.description" : "{King}\nReceives additional damage from units under Slayer effect of level ${val} or higher",
+	"core.bonus.KING.description" : "{King}\nReceives additional damage from units under Slayer spell",
+	"core.bonus.KING.description.2" : "{Advanced King}\nReceives additional damage from units under Advanced Slayer spell",
+	"core.bonus.KING.description.3" : "{Expert King}\nReceives additional damage from units under Expert Slayer spell",
 	"core.bonus.LEVEL_SPELL_IMMUNITY.description" : "{Immune to spells level 1-${val}}\nThis unit cannot be targeted by spells of levels 1-${val}",
 	"core.bonus.LIFE_DRAIN.description" : "{Drain life}\nDrains ${val}% of damage dealt",
 	"core.bonus.LIMITED_SHOOTING_RANGE.description" : "{Limited shooting range}\nUnable to use a ranged attack against units more than ${val} hexes away",
@@ -667,7 +671,7 @@
 	"core.bonus.MANA_DRAIN.description" : "{Drains enemy mana}\nDrains ${val} mana every turn from enemy hero",
 	"core.bonus.MECHANICAL.description" : "{Mechanical}\nThis unit is immune to effects that only affect living and can be repaired",
 	"core.bonus.MIND_IMMUNITY.description" : "{Mind Spell Immunity}\nThis unit cannot be targeted by spells that affect its mind",
-	"core.bonus.NO_DISTANCE_PENALTY.description" : "{No distance penalty}\nRanged attacks deal full damage at any range",
+	"core.bonus.NO_DISTANCE_PENALTY.description" : "{No distance penalty}\nRanged attacks deal full damage at any distance",
 	"core.bonus.NO_MELEE_PENALTY.description" : "{No melee penalty}\nThis ranged unit deals full damage with melee attacks",
 	"core.bonus.NO_MORALE.description" : "{Neutral Morale}\nCreature is immune to morale effects",
 	"core.bonus.NO_WALL_PENALTY.description" : "{No wall penalty}\nRanged attacks deal full damage to units behind walls",
@@ -685,30 +689,28 @@
 	"core.bonus.SPELL_AFTER_ATTACK.description" : "{Cast After Attack}\nHas a ${val}% chance to cast ${subtype.spell} after it attacks",
 	"core.bonus.SPELL_BEFORE_ATTACK.description" : "{Cast Before Attack}\nHas a ${val}% chance to cast ${subtype.spell} before it attacks",
 	"core.bonus.SPELL_DAMAGE_REDUCTION.description" : "{Spell Resistance}\nDamage from all spells reduced by ${val}%",
-	"core.bonus.SPELL_DAMAGE_REDUCTION.description.air" : "{Air Spells Resistance}\nDamage from Air Magic spells reduced by ${val}%",
-	"core.bonus.SPELL_DAMAGE_REDUCTION.description.earth" : "{Earth Spells Resistance}\nDamage from Earth Magic spells reduced by ${val}%",
-	"core.bonus.SPELL_DAMAGE_REDUCTION.description.fire" : "{Fire Spells Resistance}\nDamage from Fire Magic spells reduced by ${val}%",
-	"core.bonus.SPELL_DAMAGE_REDUCTION.description.water" : "{Water Spells Resistance}\nDamage from Water Magic spells reduced by ${val}%",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description.spellSchool.air" : "{Air Spells Resistance}\nDamage from Air Magic spells reduced by ${val}%",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description.spellSchool.earth" : "{Earth Spells Resistance}\nDamage from Earth Magic spells reduced by ${val}%",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description.spellSchool.fire" : "{Fire Spells Resistance}\nDamage from Fire Magic spells reduced by ${val}%",
+	"core.bonus.SPELL_DAMAGE_REDUCTION.description.spellSchool.water" : "{Water Spells Resistance}\nDamage from Water Magic spells reduced by ${val}%",
 	"core.bonus.SPELL_IMMUNITY.description" : "{Spell immunity}\nThis unit can not be affected by ${subtype.spell}",
 	"core.bonus.SPELL_LIKE_ATTACK.description" : "{Spell-like attack}\nAttacks with ${subtype.spell}",
 	"core.bonus.SPELL_RESISTANCE_AURA.description" : "{Aura of Resistance}\nAdjacent units get ${val}% magic resistance",
 	"core.bonus.SPELL_SCHOOL_IMMUNITY.description" : "{Spell immunity}\nThis unit is immune to all spells",
-	"core.bonus.SPELL_SCHOOL_IMMUNITY.description.air" : "{Air immunity}\nThis unit is immune to all Air school spells",
-	"core.bonus.SPELL_SCHOOL_IMMUNITY.description.earth" : "{Earth immunity}\nThis unit is immune to all Earth school spells",
-	"core.bonus.SPELL_SCHOOL_IMMUNITY.description.fire" : "{Fire immunity}\nThis unit is immune to all Fire school spells",
-	"core.bonus.SPELL_SCHOOL_IMMUNITY.description.water" : "{Water immunity}\nThis unit is immune to all Water school spells",
+	"core.bonus.SPELL_SCHOOL_IMMUNITY.description.spellSchool.air"   : "{Immune to Air Magic}\nImmune to all spells from the school of Air Magic",
+	"core.bonus.SPELL_SCHOOL_IMMUNITY.description.spellSchool.earth" : "{Immune to Earth Magic}\nImmune to all spells from the school of Earth Magic",
+	"core.bonus.SPELL_SCHOOL_IMMUNITY.description.spellSchool.fire"  : "{Immune to Fire Magic}\nImmune to all spells from the school of Fire Magic",
+	"core.bonus.SPELL_SCHOOL_IMMUNITY.description.spellSchool.water" : "{Immune to Water Magic}\nImmune to all spells from the school of Water Magic",
 	"core.bonus.SPELLCASTER.description" : "{Spellcaster}\nCan cast ${subtype.spell}",
 	"core.bonus.SUMMON_GUARDIANS.description" : "{Summon guardians}\nAt the start of battle summons ${subtype.creature} (${val}%)",
 	"core.bonus.SYNERGY_TARGET.description" : "{Synergizable}\nThis creature is vulnerable to synergy effect",
 	"core.bonus.THREE_HEADED_ATTACK.description" : "{Three-headed attack}\nAttacks three adjacent units",
 	"core.bonus.TRANSMUTATION.description" : "{Transmutation}\n${val}% chance to transform attacked unit to a different type",
-	"core.bonus.TWO_HEX_ATTACK_BREATH.description" : "{Dragon Breath}\nAttacks by this unit will also hit any unit positioned immediately behind the target",
+	"core.bonus.TWO_HEX_ATTACK_BREATH.description" : "{Breath Attack}\nAttacks by this unit will also hit any unit positioned immediately behind the target",
 	"core.bonus.UNDEAD.description" : "{Undead}\nCreature is Undead and is immune to effects that only affect living",
 	"core.bonus.UNLIMITED_RETALIATIONS.description" : "{Unlimited retaliations}\nThis unit can retaliate against an unlimited number of attacks",
-	"core.bonus.WATER_IMMUNITY.description" : "{Immune to Water Magic}\nImmune to all spells from the school of Water magic",
 	"core.bonus.WIDE_BREATH.description" : "{Wide breath}\nThis unit attacks all units around its target",
 
-
 	"spell.core.castleMoat.name" : "Moat",
 	"spell.core.castleMoatTrigger.name" : "Moat",
 	"spell.core.catapultShot.name" : "Catapult shot",

+ 27 - 0
config/bonuses.json

@@ -92,6 +92,10 @@
 
 	"FIRST_STRIKE":
 	{
+		"subtypeDescriptions" : {
+			"bonusSubtype.damageTypeRanged" : null,
+			"bonusSubtype.damageTypeMelee" : null,
+		}
 	},
 
 	"FEAR":
@@ -108,6 +112,9 @@
 
 	"FLYING":
 	{
+		"subtypeDescriptions" : {
+			"bonusSubtype.movementTeleporting" : null,
+		}
 	},
 
 	"FREE_SHOOTING":
@@ -120,6 +127,10 @@
 
 	"GENERAL_DAMAGE_REDUCTION":
 	{
+		"subtypeDescriptions" : {
+			"bonusSubtype.damageTypeRanged" : null,
+			"bonusSubtype.damageTypeMelee" : null,
+		}
 	},
 
 	"HATE":
@@ -140,6 +151,10 @@
 
 	"KING":
 	{
+		"valueDescriptions" : {
+			"2" : null,
+			"3" : null
+		}
 	},
 
 	"LEARN_BATTLE_SPELL_CHANCE":
@@ -281,6 +296,12 @@
 
 	"SPELL_DAMAGE_REDUCTION":
 	{
+		"subtypeDescriptions" : {
+			"spellSchool.air" : null,
+			"spellSchool.earth" : null,
+			"spellSchool.fire" : null,
+			"spellSchool.water" : null,
+		}
 	},
 
 	"SPELL_IMMUNITY":
@@ -293,6 +314,12 @@
 
 	"SPELL_SCHOOL_IMMUNITY":
 	{
+		"subtypeDescriptions" : {
+			"spellSchool.air" : null,
+			"spellSchool.earth" : null,
+			"spellSchool.fire" : null,
+			"spellSchool.water" : null,
+		}
 	},
 
 	"SPELL_RESISTANCE_AURA":

+ 28 - 6
lib/CBonusTypeHandler.cpp

@@ -60,23 +60,27 @@ CBonusTypeHandler::~CBonusTypeHandler() = default;
 std::string CBonusTypeHandler::bonusToString(const std::shared_ptr<Bonus> & bonus, const IBonusBearer * bearer) const
 {
 	const CBonusType & bt = bonusTypes[vstd::to_underlying(bonus->type)];
+	int bonusValue = bearer->valOfBonuses(bonus->type, bonus->subtype);
 	if(bt.hidden)
 		return "";
 
 	std::string textID = bt.getDescriptionTextID();
 	std::string text = LIBRARY->generaltexth->translate(textID);
 
-	auto school = bonus->subtype.as<SpellSchool>();
-	if (school.hasValue() && school != SpellSchool::ANY)
+	auto subtype = bonus->subtype.getNum();
+	if (bt.subtypeDescriptions.count(subtype))
 	{
-		std::string schoolName = school.encode(school.getNum());
-		std::string baseTextID = bt.getDescriptionTextID();
-		std::string fullTextID = baseTextID + '.' + schoolName;
+		std::string fullTextID = textID + '.' + bt.subtypeDescriptions.at(subtype);
+		text = LIBRARY->generaltexth->translate(fullTextID);
+	}
+	else if (bt.valueDescriptions.count(bonusValue))
+	{
+		std::string fullTextID = textID + '.' + bt.valueDescriptions.at(bonusValue);
 		text = LIBRARY->generaltexth->translate(fullTextID);
 	}
 
 	if (text.find("${val}") != std::string::npos)
-		boost::algorithm::replace_all(text, "${val}", std::to_string(bearer->valOfBonuses(bonus->type, bonus->subtype)));
+		boost::algorithm::replace_all(text, "${val}", std::to_string(bonusValue));
 
 	if (text.find("${subtype.creature}") != std::string::npos && bonus->subtype.as<CreatureID>().hasValue())
 		boost::algorithm::replace_all(text, "${subtype.creature}", bonus->subtype.as<CreatureID>().toCreature()->getNamePluralTranslated());
@@ -161,6 +165,24 @@ void CBonusTypeHandler::loadItem(const JsonNode & source, CBonusType & dest, con
 		int value = std::stoi(additionalIcon.first);
 		dest.valueIcons[value] = path;
 	}
+
+	for (const auto & additionalDescription : source["subtypeDescriptions"].Struct())
+	{
+		LIBRARY->generaltexth->registerString( "vcmi", dest.getDescriptionTextID() + "." + additionalDescription.first, additionalDescription.second);
+		auto stringID = additionalDescription.first;
+		LIBRARY->identifiers()->requestIdentifier(additionalDescription.second.getModScope(), additionalDescription.first, [&dest, stringID](int32_t index)
+		{
+			dest.subtypeDescriptions[index] = stringID;
+		});
+	}
+
+	for (const auto & additionalDescription : source["valueDescriptions"].Struct())
+	{
+		LIBRARY->generaltexth->registerString( "vcmi", dest.getDescriptionTextID() + "." + additionalDescription.first, additionalDescription.second);
+		auto stringID = additionalDescription.first;
+		int value = std::stoi(additionalDescription.first);
+		dest.valueDescriptions[value] = stringID;
+	}
 }
 
 VCMI_LIB_NAMESPACE_END

+ 2 - 0
lib/CBonusTypeHandler.h

@@ -32,6 +32,8 @@ private:
 	ImagePath icon;
 	std::map<int, ImagePath> subtypeIcons;
 	std::map<int, ImagePath> valueIcons;
+	std::map<int, std::string> subtypeDescriptions;
+	std::map<int, std::string> valueDescriptions;
 	std::string identifier;
 
 	bool hidden;