Pārlūkot izejas kodu

Merge pull request #3736 from IvanSavenko/fix_server_translations

Do not translate strings on server side
Ivan Savenko 1 gadu atpakaļ
vecāks
revīzija
bcd4a8c961

+ 1 - 2
AI/BattleAI/StackWithBonuses.cpp

@@ -208,8 +208,7 @@ void StackWithBonuses::removeUnitBonus(const std::vector<Bonus> & bonus)
 				&& one.sid == b->sid
 				&& one.valType == b->valType
 				&& one.additionalInfo == b->additionalInfo
-				&& one.effectRange == b->effectRange
-				&& one.description == b->description;
+				&& one.effectRange == b->effectRange;
 		});
 
 		removeUnitBonus(selector);

+ 0 - 1
client/Client.h

@@ -217,7 +217,6 @@ public:
 	void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override {};
 
 	void showInfoDialog(InfoWindow * iw) override {};
-	void showInfoDialog(const std::string & msg, PlayerColor player) override {};
 	void removeGUI() const;
 
 #if SCRIPTING_ENABLED

+ 7 - 7
config/battlefields.json

@@ -79,14 +79,14 @@
 				"type" : "MORALE",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER",
-				"description" : "Creatures of good town alignment on Holly Ground",
+				"description" : "core.arraytxt.123",
 				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["good"] }]
 			},
 			{
 				"type" : "MORALE",
 				"val" : -1,
 				"valueType" : "BASE_NUMBER",
-				"description" : "Creatures of evil town alignment on Holly Ground",
+				"description" : "core.arraytxt.124",
 				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["evil"] }]
 			}
 		]
@@ -99,7 +99,7 @@
 				"type" : "LUCK",
 				"val" : 2,
 				"valueType" : "BASE_NUMBER",
-				"description" : "Creatures of neutral town alignment on Clover Field",
+				"description" : "core.arraytxt.83",
 				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["neutral"] }]
 			}
 		]
@@ -112,14 +112,14 @@
 				"type" : "MORALE",
 				"val" : -1,
 				"valueType" : "BASE_NUMBER",
-				"description" : "Creatures of good town alignment on Evil Fog",
+				"description" : "core.arraytxt.126",
 				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["good"] }]
 			},
 			{
 				"type" : "MORALE",
 				"val" : 1,
 				"valueType" : "BASE_NUMBER",
-				"description" : "Creatures of evil town alignment on Evil Fog",
+				"description" : "core.arraytxt.125",
 				"limiters": [{ "type" : "CREATURE_ALIGNMENT_LIMITER", "parameters" : ["evil"] }]
 			}
 		]
@@ -132,13 +132,13 @@
 				"type" : "NO_MORALE",
 				"val" : 0,
 				"valueType" : "INDEPENDENT_MIN",
-				"description" : "Creatures on Cursed Ground"
+				"description" : "core.arraytxt.112"
 			},
 			{
 				"type" : "NO_LUCK",
 				"val" : 0,
 				"valueType" : "INDEPENDENT_MIN",
-				"description" : "Creatures on Cursed Ground"
+				"description" : "core.arraytxt.81"
 			},
 			{
 				"type" : "BLOCK_MAGIC_ABOVE",

+ 2 - 1
lib/CArtHandler.cpp

@@ -294,7 +294,8 @@ void CArtifact::addNewBonus(const std::shared_ptr<Bonus>& b)
 {
 	b->source = BonusSource::ARTIFACT;
 	b->duration = BonusDuration::PERMANENT;
-	b->description = getNameTranslated();
+	b->description.appendTextID(getNameTextID());
+	b->description.appendRawString(" %+d");
 	CBonusSystemNode::addNewBonus(b);
 }
 

+ 2 - 1
lib/CSkillHandler.cpp

@@ -94,7 +94,8 @@ void CSkill::addNewBonus(const std::shared_ptr<Bonus> & b, int level)
 	b->source = BonusSource::SECONDARY_SKILL;
 	b->sid = BonusSourceID(id);
 	b->duration = BonusDuration::PERMANENT;
-	b->description = getNameTranslated();
+	b->description.appendTextID(getNameTextID());
+	b->description.appendRawString(" %+d");
 	levels[level-1].effects.push_back(b);
 }
 

+ 6 - 16
lib/CTownHandler.cpp

@@ -575,20 +575,9 @@ std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType ty
 
 std::shared_ptr<Bonus> CTownHandler::createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype, const TPropagatorPtr & prop) const
 {
-	std::ostringstream descr;
-	descr << build->getNameTranslated();
-	return createBonusImpl(build->bid, build->town->faction->getId(), type, val, prop, descr.str(), subtype);
-}
+	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::TOWN_STRUCTURE, val, build->getUniqueTypeID(), subtype);
 
-std::shared_ptr<Bonus> CTownHandler::createBonusImpl(const BuildingID & building,
-													 const FactionID & faction,
-													 BonusType type,
-													 int val,
-													 const TPropagatorPtr & prop,
-													 const std::string & description,
-													 BonusSubtypeID subtype) const
-{
-	auto b = std::make_shared<Bonus>(BonusDuration::PERMANENT, type, BonusSource::TOWN_STRUCTURE, val, BuildingTypeUniqueID(faction, building), subtype, description);
+	b->description.appendTextID(build->getNameTextID());
 
 	if(prop)
 		b->addPropagator(prop);
@@ -600,12 +589,13 @@ void CTownHandler::loadSpecialBuildingBonuses(const JsonNode & source, BonusList
 {
 	for(const auto & b : source.Vector())
 	{
-		auto bonus = JsonUtils::parseBuildingBonus(b, building->town->faction->getId(), building->bid, building->getNameTranslated());
+		auto bonus = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, BonusSourceID(building->getUniqueTypeID()));
 
-		if(bonus == nullptr)
+		if(!JsonUtils::parseBonus(b, bonus.get()))
 			continue;
 
-		bonus->sid = BonusSourceID(building->getUniqueTypeID());
+		bonus->description.appendTextID(building->getNameTextID());
+
 		//JsonUtils::parseBuildingBonus produces UNKNOWN type propagator instead of empty.
 		if(bonus->propagator != nullptr
 			&& bonus->propagator->getPropagatorType() == CBonusSystemNode::ENodeTypes::UNKNOWN)

+ 0 - 7
lib/CTownHandler.h

@@ -303,13 +303,6 @@ class DLL_LINKAGE CTownHandler : public CHandlerBase<FactionID, Faction, CFactio
 	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val) const;
 	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype) const;
 	std::shared_ptr<Bonus> createBonus(CBuilding * build, BonusType type, int val, BonusSubtypeID subtype, const TPropagatorPtr & prop) const;
-	std::shared_ptr<Bonus> createBonusImpl(const BuildingID & building,
-										   const FactionID & faction,
-												  BonusType type,
-												  int val,
-												  const TPropagatorPtr & prop,
-												  const std::string & description,
-												  BonusSubtypeID subtype) const;
 
 	/// loads CStructure's into town
 	void loadStructure(CTown & town, const std::string & stringID, const JsonNode & source) const;

+ 0 - 1
lib/IGameCallback.h

@@ -78,7 +78,6 @@ public:
 	virtual void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) = 0;
 
 	virtual void showInfoDialog(InfoWindow * iw) = 0;
-	virtual void showInfoDialog(const std::string & msg, PlayerColor player) = 0;
 
 	virtual void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> &spells)=0;
 	virtual bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) = 0;

+ 9 - 1
lib/MetaString.cpp

@@ -169,7 +169,15 @@ DLL_LINKAGE std::string MetaString::toString() const
 				break;
 			case EMessage::REPLACE_POSITIVE_NUMBER:
 				if (dst.find("%+d") != std::string::npos)
-					boost::replace_first(dst, "%+d", '+' + std::to_string(numbers[nums++]));
+				{
+					int64_t value = numbers[nums];
+					if (value > 0)
+						boost::replace_first(dst, "%+d", '+' + std::to_string(value));
+					else
+						boost::replace_first(dst, "%+d", std::to_string(value));
+
+					nums++;
+				}
 				else
 					boost::replace_first(dst, "%d", std::to_string(numbers[nums++]));
 				break;

+ 1 - 2
lib/battle/BattleInfo.cpp

@@ -917,8 +917,7 @@ void BattleInfo::removeUnitBonus(uint32_t id, const std::vector<Bonus> & bonus)
 			&& one.sid == b->sid
 			&& one.valType == b->valType
 			&& one.additionalInfo == b->additionalInfo
-			&& one.effectRange == b->effectRange
-			&& one.description == b->description;
+			&& one.effectRange == b->effectRange;
 		};
 		sta->removeBonusesRecursive(selector);
 	}

+ 44 - 39
lib/bonuses/Bonus.cpp

@@ -90,51 +90,66 @@ JsonNode CAddInfo::toJsonNode() const
 }
 std::string Bonus::Description(std::optional<si32> customValue) const
 {
-	std::string str;
+	MetaString descriptionHelper = description;
+	auto valueToShow = customValue.value_or(val);
 
-	if(description.empty())
+	if(descriptionHelper.empty())
 	{
-		if(stacking.empty() || stacking == "ALWAYS")
+		// no custom description - try to generate one based on bonus source
+		switch(source)
 		{
-			switch(source)
-			{
 			case BonusSource::ARTIFACT:
-				str = sid.as<ArtifactID>().toEntity(VLC)->getNameTranslated();
+				descriptionHelper.appendName(sid.as<ArtifactID>());
 				break;
 			case BonusSource::SPELL_EFFECT:
-				str = sid.as<SpellID>().toEntity(VLC)->getNameTranslated();
+				descriptionHelper.appendName(sid.as<SpellID>());
 				break;
 			case BonusSource::CREATURE_ABILITY:
-				str = sid.as<CreatureID>().toEntity(VLC)->getNamePluralTranslated();
+				descriptionHelper.appendNamePlural(sid.as<CreatureID>());
 				break;
 			case BonusSource::SECONDARY_SKILL:
-				str = VLC->skills()->getById(sid.as<SecondarySkill>())->getNameTranslated();
+				descriptionHelper.appendTextID(sid.as<SecondarySkill>().toEntity(VLC)->getNameTextID());
 				break;
 			case BonusSource::HERO_SPECIAL:
-				str = VLC->heroTypes()->getById(sid.as<HeroTypeID>())->getNameTranslated();
+				descriptionHelper.appendTextID(sid.as<HeroTypeID>().toEntity(VLC)->getNameTextID());
 				break;
-			default:
-				//todo: handle all possible sources
-				str = "Unknown";
-				break;
-			}
 		}
-		else
-			str = stacking;
 	}
-	else
+
+	if(descriptionHelper.empty())
 	{
-		str = description;
+		// still no description - try to generate one based on duration
+		if ((duration & BonusDuration::ONE_BATTLE).any())
+		{
+			if (val > 0)
+				descriptionHelper.appendTextID("core.arraytxt.110"); //+%d Temporary until next battle"
+			else
+				descriptionHelper.appendTextID("core.arraytxt.109"); //-%d Temporary until next battle"
+
+			// erase sign - already present in description string
+			valueToShow = std::abs(valueToShow);
+		}
 	}
 
-	if(auto value = customValue.value_or(val)) {
-		//arraytxt already contains +-value
-		std::string valueString = boost::str(boost::format(" %+d") % value);
-		if(!boost::algorithm::ends_with(str, valueString))
-			str += valueString;
+	if(descriptionHelper.empty())
+	{
+		// still no description - generate placeholder one
+		descriptionHelper.appendRawString("Unknown");
 	}
 
-	return str;
+	if(valueToShow != 0)
+	{
+		descriptionHelper.replacePositiveNumber(valueToShow);
+		// there is one known string that uses '%s' placeholder for bonus value:
+		// "core.arraytxt.69" : "\nFountain of Fortune Visited %s",
+		// So also add string replacement to handle this case
+		if (valueToShow > 0)
+			descriptionHelper.replaceRawString(std::to_string(valueToShow));
+		else
+			descriptionHelper.replaceRawString("-" + std::to_string(valueToShow));
+	}
+
+	return descriptionHelper.toString();
 }
 
 static JsonNode additionalInfoToJson(BonusType type, CAddInfo addInfo)
@@ -170,7 +185,7 @@ JsonNode Bonus::toJsonNode() const
 	if(!stacking.empty())
 		root["stacking"].String() = stacking;
 	if(!description.empty())
-		root["description"].String() = description;
+		root["description"].String() = description.toString();
 	if(effectRange != BonusLimitEffect::NO_LIMIT)
 		root["effectRange"].String() = vstd::findKey(bonusLimitEffect, effectRange);
 	if(duration != BonusDuration::PERMANENT)
@@ -187,27 +202,17 @@ JsonNode Bonus::toJsonNode() const
 }
 
 Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID)
-	: Bonus(Duration, Type, Src, Val, ID, BonusSubtypeID(), std::string())
-{}
-
-Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID, std::string Desc)
-	: Bonus(Duration, Type, Src, Val, ID, BonusSubtypeID(), Desc)
-{}
-
-Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID, BonusSubtypeID Subtype)
-	: Bonus(Duration, Type, Src, Val, ID, Subtype, std::string())
+	: Bonus(Duration, Type, Src, Val, ID, BonusSubtypeID())
 {}
 
-Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID, BonusSubtypeID Subtype, std::string Desc):
+Bonus::Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID ID, BonusSubtypeID Subtype):
 	duration(Duration),
 	type(Type),
 	subtype(Subtype),
 	source(Src),
 	val(Val),
-	sid(ID),
-	description(std::move(Desc))
+	sid(ID)
 {
-	boost::algorithm::trim(description);
 	targetSourceType = BonusSource::OTHER;
 }
 

+ 11 - 4
lib/bonuses/Bonus.h

@@ -13,6 +13,7 @@
 #include "BonusCustomTypes.h"
 #include "../constants/VariantIdentifier.h"
 #include "../constants/EntityIdentifiers.h"
+#include "../MetaString.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -77,12 +78,10 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 	TUpdaterPtr updater;
 	TUpdaterPtr propagationUpdater;
 
-	std::string description;
+	MetaString description;
 
 	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID);
-	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, std::string Desc);
 	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype);
-	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype, std::string Desc);
 	Bonus(BonusDuration::Type Duration, BonusType Type, BonusSource Src, si32 Val, BonusSourceID sourceID, BonusSubtypeID subtype, BonusValueType ValType);
 	Bonus() = default;
 
@@ -94,7 +93,15 @@ struct DLL_LINKAGE Bonus : public std::enable_shared_from_this<Bonus>
 		h & source;
 		h & val;
 		h & sid;
-		h & description;
+		if (h.version < Handler::Version::BONUS_META_STRING)
+		{
+			std::string oldDescription;
+			h & oldDescription;
+			description = MetaString::createFromRawString(oldDescription);
+		}
+		else
+			h & description;
+
 		h & additionalInfo;
 		h & turnsRemain;
 		h & valType;

+ 2 - 15
lib/json/JsonBonus.cpp

@@ -625,19 +625,6 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
 	return b;
 }
 
-std::shared_ptr<Bonus> 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<Bonus>(BonusDuration::PERMANENT, BonusType::NONE, BonusSource::TOWN_STRUCTURE, 0, BuildingTypeUniqueID(faction, building), description);
-
-	if(!parseBonus(ability, b.get()))
-		return nullptr;
-	return b;
-}
-
 bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 {
 	const JsonNode * value = nullptr;
@@ -682,9 +669,9 @@ bool JsonUtils::parseBonus(const JsonNode &ability, Bonus *b)
 	if(!ability["description"].isNull())
 	{
 		if (ability["description"].isString())
-			b->description = ability["description"].String();
+			b->description.appendTextID(ability["description"].String());
 		if (ability["description"].isNumber())
-			b->description = VLC->generaltexth->translate("core.arraytxt", ability["description"].Integer());
+			b->description.appendTextID("core.arraytxt." + std::to_string(ability["description"].Integer()));
 	}
 
 	value = &ability["effectRange"];

+ 0 - 1
lib/json/JsonBonus.h

@@ -23,7 +23,6 @@ namespace JsonUtils
 {
 	std::shared_ptr<Bonus> parseBonus(const JsonVector & ability_vec);
 	std::shared_ptr<Bonus> parseBonus(const JsonNode & ability);
-	std::shared_ptr<Bonus> parseBuildingBonus(const JsonNode & ability, const FactionID & faction, const BuildingID & building, const std::string & description);
 	bool parseBonus(const JsonNode & ability, Bonus * placement);
 	std::shared_ptr<ILimiter> parseLimiter(const JsonNode & limiter);
 	CSelector parseSelector(const JsonNode &ability);

+ 7 - 14
lib/mapObjects/CArmedInstance.cpp

@@ -101,28 +101,21 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 			factionsInArmy -= mixableFactions - 1;
 	}
 
-	std::string description;
+	MetaString bonusDescription;
 
 	if(factionsInArmy == 1)
 	{
 		b->val = +1;
-		description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
-		description = description.substr(0, description.size()-3);//trim "+1"
+		bonusDescription.appendTextID("core.arraytxt.115"); //All troops of one alignment +1
 	}
 	else if (!factions.empty()) // no bonus from empty garrison
 	{
 		b->val = 2 - static_cast<si32>(factionsInArmy);
-		MetaString formatter;
-		formatter.appendTextID("core.arraytxt.114"); //Troops of %d alignments %d
-		formatter.replaceNumber(factionsInArmy);
-		formatter.replaceNumber(b->val);
-
-		description = formatter.toString();
-		description = description.substr(0, description.size()-3);//trim value
+		bonusDescription.appendTextID("core.arraytxt.114"); //Troops of %d alignments %d
+		bonusDescription.replaceNumber(factionsInArmy);
 	}
 	
-	boost::algorithm::trim(description);
-	b->description = description;
+	b->description = bonusDescription;
 
 	CBonusSystemNode::treeHasChanged();
 
@@ -132,8 +125,8 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 	{
 		if(!undeadModifier)
 		{
-			undeadModifier = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, -1, BonusCustomSource::undeadMoraleDebuff, VLC->generaltexth->arraytxt[116]);
-			undeadModifier->description = undeadModifier->description.substr(0, undeadModifier->description.size()-2);//trim value
+			undeadModifier = std::make_shared<Bonus>(BonusDuration::PERMANENT, BonusType::MORALE, BonusSource::ARMY, -1, BonusCustomSource::undeadMoraleDebuff);
+			undeadModifier->description.appendTextID("core.arraytxt.116");
 			addNewBonus(undeadModifier);
 		}
 	}

+ 5 - 4
lib/mapObjects/CBank.cpp

@@ -225,15 +225,15 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 			{
 			case Obj::SHIPWRECK:
 				textID = 123;
-				gbonus.bdescr.appendRawString(VLC->generaltexth->arraytxt[99]);
+				gbonus.bonus.description = MetaString::createFromTextID("core.arraytxt.99");
 				break;
 			case Obj::DERELICT_SHIP:
 				textID = 42;
-				gbonus.bdescr.appendRawString(VLC->generaltexth->arraytxt[101]);
+				gbonus.bonus.description = MetaString::createFromTextID("core.arraytxt.101");
 				break;
 			case Obj::CRYPT:
 				textID = 120;
-				gbonus.bdescr.appendRawString(VLC->generaltexth->arraytxt[98]);
+				gbonus.bonus.description = MetaString::createFromTextID("core.arraytxt.98");
 				break;
 			}
 			cb->giveHeroBonus(&gbonus);
@@ -244,7 +244,8 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 		case Obj::PYRAMID:
 		{
 			GiveBonus gb;
-			gb.bonus = Bonus(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, -2, BonusSourceID(id), VLC->generaltexth->arraytxt[70]);
+			gb.bonus = Bonus(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, -2, BonusSourceID(id));
+			gb.bonus.description = MetaString::createFromTextID("core.arraytxt.70");
 			gb.id = hero->id;
 			cb->giveHeroBonus(&gb);
 			textID = 107;

+ 3 - 5
lib/mapObjects/CGHeroInstance.h

@@ -144,6 +144,9 @@ public:
 	//////////////////////////////////////////////////////////////////////////
 
 	std::string getBiographyTranslated() const;
+	std::string getBiographyTextID() const;
+
+	std::string getNameTextID() const;
 	std::string getNameTranslated() const;
 
 	HeroTypeID getPortraitSource() const;
@@ -152,11 +155,6 @@ public:
 	std::string getClassNameTranslated() const;
 	std::string getClassNameTextID() const;
 
-private:
-	std::string getNameTextID() const;
-	std::string getBiographyTextID() const;
-public:
-
 	bool hasSpellbook() const;
 	int maxSpellLevel() const;
 	void addSpellToSpellbook(const SpellID & spell);

+ 22 - 16
lib/mapObjects/CGTownBuilding.cpp

@@ -98,27 +98,33 @@ std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus) const
 {
 	if(bonus.type == BonusType::TOWN_MAGIC_WELL)
 	{
-		auto bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingInTownMagicWell"));
-		auto buildingName = town->getTown()->getSpecialBuilding(bType)->getNameTranslated();
-		boost::algorithm::replace_first(bonusGreeting, "%s", buildingName);
-		return bonusGreeting;
+		MetaString wellGreeting = MetaString::createFromTextID("vcmi.townHall.greetingInTownMagicWell");
+
+		wellGreeting.replaceTextID(town->getTown()->getSpecialBuilding(bType)->getNameTextID());
+		return wellGreeting.toString();
 	}
-	auto bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingCustomBonus")); //"%s gives you +%d %s%s"
-	std::string param;
+
+	MetaString greeting = MetaString::createFromTextID("vcmi.townHall.greetingCustomBonus");
+
+	std::string paramTextID;
 	std::string until;
 
 	if(bonus.type == BonusType::MORALE)
-		param = VLC->generaltexth->allTexts[384];
-	else if(bonus.type == BonusType::LUCK)
-		param = VLC->generaltexth->allTexts[385];
+		paramTextID = "core.genrltxt.384"; // Morale
 
-	until = bonus.duration == BonusDuration::ONE_BATTLE
-			? VLC->generaltexth->translate("vcmi.townHall.greetingCustomUntil")
-			: ".";
+	if(bonus.type == BonusType::LUCK)
+		paramTextID = "core.genrltxt.385"; // Luck
+
+	greeting.replaceTextID(town->getTown()->getSpecialBuilding(bType)->getNameTextID());
+	greeting.replaceNumber(bonus.val);
+	greeting.replaceTextID(paramTextID);
+
+	if (bonus.duration == BonusDuration::ONE_BATTLE)
+		greeting.replaceTextID("vcmi.townHall.greetingCustomUntil");
+	else
+		greeting.replaceRawString(".");
 
-	boost::format fmt = boost::format(bonusGreeting) % bonus.description % bonus.val % param % until;
-	std::string greeting = fmt.str();
-	return greeting;
+	return greeting.toString();
 }
 
 COPWBonus::COPWBonus(IGameCallback *cb)
@@ -160,7 +166,7 @@ void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
 			if(!h->hasBonusFrom(BonusSource::OBJECT_TYPE, BonusSourceID(Obj(Obj::STABLES)))) //does not stack with advMap Stables
 			{
 				GiveBonus gb;
-				gb.bonus = Bonus(BonusDuration::ONE_WEEK, BonusType::MOVEMENT, BonusSource::OBJECT_TYPE, 600, BonusSourceID(Obj(Obj::STABLES)), BonusCustomSubtype::heroMovementLand, VLC->generaltexth->arraytxt[100]);
+				gb.bonus = Bonus(BonusDuration::ONE_WEEK, BonusType::MOVEMENT, BonusSource::OBJECT_TYPE, 600, BonusSourceID(Obj(Obj::STABLES)), BonusCustomSubtype::heroMovementLand);
 				gb.id = heroID;
 				cb->giveHeroBonus(&gb);
 

+ 0 - 25
lib/networkPacks/NetPacksLib.cpp

@@ -1004,31 +1004,6 @@ void GiveBonus::applyGs(CGameState *gs)
 
 	auto b = std::make_shared<Bonus>(bonus);
 	cbsn->addNewBonus(b);
-
-	std::string &descr = b->description;
-
-	if(!bdescr.empty()) 
-	{
-		descr = bdescr.toString();
-	}
-	else if(!descr.empty())
-	{
-		//use preseet description
-	}
-	else if((bonus.type == BonusType::LUCK || bonus.type == BonusType::MORALE)
-		  && (bonus.source == BonusSource::OBJECT_TYPE || bonus.source == BonusSource::OBJECT_INSTANCE))
-	{
-		//no description, use generic
-		//?could use allways when Type == BonusDuration::Type::ONE_BATTLE
-		descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle"
-	}
-	else
-	{
-		logGlobal->debug("Empty bonus decription. Type=%d", (int) bonus.type);
-	}
-	// Some of(?) versions of H3 use " %s" here instead of %d. Try to replace both of them
-	boost::replace_first(descr, "%d", std::to_string(std::abs(bonus.val))); // " +/-%d Temporary until next battle
-	boost::replace_first(descr, " %s", boost::str(boost::format(" %+d") % bonus.val));  // " %s" in arraytxt.69, fountian of fortune
 }
 
 void ChangeObjPos::applyGs(CGameState *gs)

+ 0 - 2
lib/networkPacks/PacksForClient.h

@@ -382,7 +382,6 @@ struct DLL_LINKAGE GiveBonus : public CPackForClient
 	ETarget who = ETarget::OBJECT;
 	VariantIdentifier<ObjectInstanceID, PlayerColor, BattleID> id;
 	Bonus bonus;
-	MetaString bdescr;
 
 	void visitTyped(ICPackVisitor & visitor) override;
 
@@ -390,7 +389,6 @@ struct DLL_LINKAGE GiveBonus : public CPackForClient
 	{
 		h & bonus;
 		h & id;
-		h & bdescr;
 		h & who;
 		assert(id.getNum() != -1);
 	}

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -38,6 +38,7 @@ enum class ESerializationVersion : int32_t
 	CAMPAIGN_MAP_TRANSLATIONS, // 835 +campaigns include translations for its maps
 	JSON_FLAGS, // 836 json uses new format for flags
 	MANA_LIMIT,	// 837 change MANA_PER_KNOWLEGDE to percentage
+	BONUS_META_STRING,	// 838 bonuses use MetaString instead of std::string for descriptions
 
-	CURRENT = MANA_LIMIT
+	CURRENT = BONUS_META_STRING
 };

+ 1 - 2
lib/serializer/JsonUpdater.cpp

@@ -207,8 +207,7 @@ void JsonUpdater::serializeBonuses(const std::string & fieldName, CBonusSystemNo
 				&& mask->sid == b->sid
 				&& mask->valType == b->valType
 				&& mask->additionalInfo == b->additionalInfo
-				&& mask->effectRange == b->effectRange
-				&& mask->description == b->description;
+				&& mask->effectRange == b->effectRange;
 			};
 
 			value->removeBonuses(selector);

+ 24 - 4
lib/spells/BonusCaster.cpp

@@ -16,6 +16,9 @@
 #include "../MetaString.h"
 #include "../battle/Unit.h"
 #include "../bonuses/Bonus.h"
+#include "../VCMI_Lib.h"
+#include "../CSkillHandler.h"
+#include "../CHeroHandler.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -32,10 +35,27 @@ BonusCaster::~BonusCaster() = default;
 
 void BonusCaster::getCasterName(MetaString & text) const
 {
-	if(!bonus->description.empty())
-		text.replaceRawString(bonus->description);
-	else
-		actualCaster->getCasterName(text);
+	switch(bonus->source)
+	{
+		case BonusSource::ARTIFACT:
+			text.replaceName(bonus->sid.as<ArtifactID>());
+			break;
+		case BonusSource::SPELL_EFFECT:
+			text.replaceName(bonus->sid.as<SpellID>());
+			break;
+		case BonusSource::CREATURE_ABILITY:
+			text.replaceNamePlural(bonus->sid.as<CreatureID>());
+			break;
+		case BonusSource::SECONDARY_SKILL:
+			text.replaceTextID(bonus->sid.as<SecondarySkill>().toEntity(VLC)->getNameTextID());
+			break;
+		case BonusSource::HERO_SPECIAL:
+			text.replaceTextID(bonus->sid.as<HeroTypeID>().toEntity(VLC)->getNameTextID());
+			break;
+		default:
+			actualCaster->getCasterName(text);
+			break;
+	}
 }
 
 void BonusCaster::getCastDescription(const Spell * spell, const std::vector<const battle::Unit*> & attacked, MetaString & text) const

+ 1 - 1
scripting/lua/api/BonusSystem.cpp

@@ -143,7 +143,7 @@ int BonusProxy::getDescription(lua_State * L)
 	std::shared_ptr<const Bonus> object;
 	if(!S.tryGet(1, object))
 		return S.retNil();
-	return LuaStack::quickRetStr(L, object->description);
+	return LuaStack::quickRetStr(L, object->description.toString());
 }
 
 int BonusProxy::toJsonNode(lua_State * L)

+ 4 - 12
server/CGameHandler.cpp

@@ -388,7 +388,7 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo
 		InfoWindow iw;
 		iw.player = hero->tempOwner;
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 1); //can gain no more XP
-		iw.text.replaceRawString(hero->getNameTranslated());
+		iw.text.replaceTextID(hero->getNameTextID());
 		sendAndApply(&iw);
 	}
 
@@ -1597,7 +1597,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 		iw.components.emplace_back(ComponentType::SEC_SKILL, scholarSkill, scholarSkillLevel);
 
 		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 139);//"%s, who has studied magic extensively,
-		iw.text.replaceRawString(h1->getNameTranslated());
+		iw.text.replaceTextID(h1->getNameTextID());
 
 		if (!cs2.spells.empty())//if found new spell - apply
 		{
@@ -1618,7 +1618,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 				}
 			}
 			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 142);//from %s
-			iw.text.replaceRawString(h2->getNameTranslated());
+			iw.text.replaceTextID(h2->getNameTextID());
 			sendAndApply(&cs2);
 		}
 
@@ -1646,7 +1646,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 				}
 			}
 			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 148);//from %s
-			iw.text.replaceRawString(h2->getNameTranslated());
+			iw.text.replaceTextID(h2->getNameTextID());
 			sendAndApply(&cs1);
 		}
 		sendAndApply(&iw);
@@ -4269,14 +4269,6 @@ void CGameHandler::showInfoDialog(InfoWindow * iw)
 	sendAndApply(iw);
 }
 
-void CGameHandler::showInfoDialog(const std::string & msg, PlayerColor player)
-{
-	InfoWindow iw;
-	iw.player = player;
-	iw.text.appendRawString(msg);
-	showInfoDialog(&iw);
-}
-
 CRandomGenerator & CGameHandler::getRandomGenerator()
 {
 	return CRandomGenerator::getDefault();

+ 0 - 1
server/CGameHandler.h

@@ -160,7 +160,6 @@ public:
 	void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value) override;
 	void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override;
 	void showInfoDialog(InfoWindow * iw) override;
-	void showInfoDialog(const std::string & msg, PlayerColor player) override;
 
 	//////////////////////////////////////////////////////////////////////////
 	void useScholarSkill(ObjectInstanceID hero1, ObjectInstanceID hero2);

+ 16 - 15
server/battles/BattleActionProcessor.cpp

@@ -1523,26 +1523,27 @@ void BattleActionProcessor::addGenericKilledLog(BattleLogMessage & blm, const CS
 {
 	if(killed > 0)
 	{
-		const int32_t txtIndex = (killed > 1) ? 379 : 378;
-		std::string formatString = VLC->generaltexth->allTexts[txtIndex];
-
-		// these default h3 texts have unnecessary new lines, so get rid of them before displaying (and trim just in case, trimming newlines does not works for some reason)
-		formatString.erase(std::remove(formatString.begin(), formatString.end(), '\n'), formatString.end());
-		formatString.erase(std::remove(formatString.begin(), formatString.end(), '\r'), formatString.end());
-		boost::algorithm::trim(formatString);
+		MetaString line;
 
-		boost::format txt(formatString);
-		if(killed > 1)
+		if (killed > 1)
 		{
-			txt % killed % (multiple ? VLC->generaltexth->allTexts[43] : defender->unitType()->getNamePluralTranslated()); // creatures perish
+			line.appendTextID("core.genrltxt.379"); // %d %s perished
+			line.replaceNumber(killed);
 		}
-		else //killed == 1
+		else
+			line.appendTextID("core.genrltxt.378"); // One %s perishes
+
+		if (multiple)
 		{
-			txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->unitType()->getNameSingularTranslated()); // creature perishes
+			if (killed > 1)
+				line.replaceTextID("core.genrltxt.43"); // creatures
+			else
+				line.replaceTextID("core.genrltxt.42"); // creature
 		}
-		MetaString line;
-		line.appendRawString(txt.str());
-		blm.lines.push_back(std::move(line));
+		else
+			line.replaceName(defender->unitType()->getId(), killed);
+
+		blm.lines.push_back(line);
 	}
 }
 

+ 0 - 1
test/mock/mock_IGameCallback.h

@@ -38,7 +38,6 @@ public:
 	void setObjPropertyValue(ObjectInstanceID objid, ObjProperty prop, int32_t value = 0) override {}
 	void setObjPropertyID(ObjectInstanceID objid, ObjProperty prop, ObjPropertyID identifier) override {}
 	void showInfoDialog(InfoWindow * iw) override {}
-	void showInfoDialog(const std::string & msg, PlayerColor player) override {}
 
 	void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> &spells) override {}
 	bool removeObject(const CGObjectInstance * obj, const PlayerColor & initiator) override {return false;}

+ 1 - 2
test/spells/effects/EffectFixture.cpp

@@ -33,8 +33,7 @@ bool operator==(const Bonus & b1, const Bonus & b2)
 		&& b1.sid == b2.sid
 		&& b1.valType == b2.valType
 		&& b1.additionalInfo == b2.additionalInfo
-		&& b1.effectRange == b2.effectRange
-		&& b1.description == b2.description;
+		&& b1.effectRange == b2.effectRange;
 }
 
 namespace test