Browse Source

All heroes-related strings are passed through translator

Ivan Savenko 2 years ago
parent
commit
fa6f7513e8
64 changed files with 356 additions and 239 deletions
  1. 8 8
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/Nullkiller/AIUtility.cpp
  3. 2 2
      AI/Nullkiller/Analyzers/HeroManager.cpp
  4. 2 2
      AI/Nullkiller/Behaviors/DefenceBehavior.cpp
  5. 1 1
      AI/Nullkiller/Goals/AbstractGoal.cpp
  6. 2 2
      AI/Nullkiller/Goals/AdventureSpellCast.cpp
  7. 3 3
      AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp
  8. 6 6
      AI/Nullkiller/Goals/ExecuteHeroChain.cpp
  9. 2 2
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  10. 1 1
      AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp
  11. 1 1
      AI/Nullkiller/Pathfinding/Actors.cpp
  12. 1 1
      AI/VCAI/AIUtility.cpp
  13. 1 1
      AI/VCAI/Goals/AbstractGoal.cpp
  14. 2 2
      AI/VCAI/Goals/AdventureSpellCast.cpp
  15. 2 2
      AI/VCAI/Goals/CompleteQuest.cpp
  16. 2 2
      AI/VCAI/Goals/Explore.cpp
  17. 1 1
      AI/VCAI/Goals/GatherArmy.cpp
  18. 1 1
      AI/VCAI/Goals/GatherTroops.cpp
  19. 1 1
      AI/VCAI/Goals/VisitHero.cpp
  20. 1 1
      AI/VCAI/Goals/VisitObj.cpp
  21. 1 1
      AI/VCAI/Goals/VisitTile.cpp
  22. 1 1
      AI/VCAI/Pathfinding/AIPathfinder.cpp
  23. 13 13
      AI/VCAI/VCAI.cpp
  24. 2 2
      client/CPlayerInterface.cpp
  25. 2 2
      client/ClientCommandManager.cpp
  26. 1 1
      client/battle/BattleInterfaceClasses.cpp
  27. 3 3
      client/battle/BattleWindow.cpp
  28. 1 1
      client/lobby/CBonusSelection.cpp
  29. 3 3
      client/lobby/OptionsTab.cpp
  30. 1 1
      client/mapHandler.cpp
  31. 1 1
      client/widgets/AdventureMapClasses.cpp
  32. 6 6
      client/windows/CCastleInterface.cpp
  33. 8 8
      client/windows/CHeroWindow.cpp
  34. 6 6
      client/windows/CKingdomInterface.cpp
  35. 3 3
      client/windows/CTradeWindow.cpp
  36. 9 9
      client/windows/GUIClasses.cpp
  37. 4 0
      include/vcmi/HeroClass.h
  38. 12 0
      include/vcmi/HeroType.h
  39. 6 6
      lib/CGameState.cpp
  40. 72 11
      lib/CHeroHandler.cpp
  41. 38 21
      lib/CHeroHandler.h
  42. 1 1
      lib/HeroBonus.cpp
  43. 1 1
      lib/mapObjects/CBank.cpp
  44. 46 24
      lib/mapObjects/CGHeroInstance.cpp
  45. 14 5
      lib/mapObjects/CGHeroInstance.h
  46. 7 7
      lib/mapObjects/CGPandoraBox.cpp
  47. 4 4
      lib/mapObjects/CGTownInstance.cpp
  48. 1 1
      lib/mapObjects/CObjectHandler.cpp
  49. 5 5
      lib/mapObjects/CQuest.cpp
  50. 1 1
      lib/mapObjects/CommonConstructors.cpp
  51. 2 2
      lib/mapObjects/MiscObjects.cpp
  52. 2 2
      lib/mapping/CMap.cpp
  53. 8 8
      lib/mapping/MapFormatH3M.cpp
  54. 3 3
      lib/mapping/MapFormatJson.cpp
  55. 2 2
      lib/serializer/CSerializer.cpp
  56. 4 4
      lib/spells/AdventureSpellMechanics.cpp
  57. 1 1
      lib/spells/effects/Summon.cpp
  58. 7 11
      mapeditor/inspector/inspector.cpp
  59. 1 1
      mapeditor/inspector/questwidget.cpp
  60. 1 5
      mapeditor/mapcontroller.cpp
  61. 1 1
      mapeditor/mapsettings.cpp
  62. 1 1
      mapeditor/validator.cpp
  63. 8 8
      server/CGameHandler.cpp
  64. 1 1
      server/CQuery.cpp

+ 8 - 8
AI/Nullkiller/AIGateway.cpp

@@ -283,7 +283,7 @@ void AIGateway::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID her
 	auto firstHero = cb->getHero(hero1);
 	auto secondHero = cb->getHero(hero2);
 
-	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->name % firstHero->tempOwner % secondHero->name % secondHero->tempOwner));
+	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->getNameTranslated() % firstHero->tempOwner % secondHero->getNameTranslated() % secondHero->tempOwner));
 
 	requestActionASAP([=]()
 	{
@@ -544,7 +544,7 @@ void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimaryS
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
 
-	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->name % hero->level));
+	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level));
 	HeroPtr hPtr = hero;
 
 	requestActionASAP([=]()
@@ -787,7 +787,7 @@ void AIGateway::makeTurn()
 		for (auto h : cb->getHeroesInfo())
 		{
 			if (h->movement)
-				logAi->warn("Hero %s has %d MP left", h->name, h->movement);
+				logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement);
 		}
 #if NKAI_TRACE_LEVEL == 0
 	}
@@ -808,7 +808,7 @@ void AIGateway::makeTurn()
 
 void AIGateway::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
 {
-	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString());
+	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
 	switch(obj->ID)
 	{
 	case Obj::CREATURE_GENERATOR1:
@@ -1070,7 +1070,7 @@ void AIGateway::battleStart(const CCreatureSet * army1, const CCreatureSet * arm
 	assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE);
 	status.setBattle(ONGOING_BATTLE);
 	const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
-	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->name : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
+	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 }
 
@@ -1172,7 +1172,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 		}
 	};
 
-	logAi->debug("Moving hero %s to tile %s", h->name, dst.toString());
+	logAi->debug("Moving hero %s to tile %s", h->getNameTranslated(), dst.toString());
 	int3 startHpos = h->visitablePos();
 	bool ret = false;
 	if(startHpos == dst)
@@ -1192,7 +1192,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 		cb->getPathsInfo(h.get())->getPath(path, dst);
 		if(path.nodes.empty())
 		{
-			logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
+			logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString());
 			return true;
 		}
 		int i = (int)path.nodes.size() - 1;
@@ -1344,7 +1344,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 			throw cannotFulfillGoalException("Invalid path found!");
 		}
 
-		logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->name, startHpos.toString(), h->visitablePos().toString(), ret);
+		logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->getNameTranslated(), startHpos.toString(), h->visitablePos().toString(), ret);
 	}
 	return ret;
 }

+ 1 - 1
AI/Nullkiller/AIUtility.cpp

@@ -69,7 +69,7 @@ HeroPtr::HeroPtr(const CGHeroInstance * H)
 	}
 
 	h = H;
-	name = h->name;
+	name = h->getNameTranslated();
 	hid = H->id;
 //	infosCount[ai->playerID][hid]++;
 }

+ 2 - 2
AI/Nullkiller/Analyzers/HeroManager.cpp

@@ -70,7 +70,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
 
 float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
 {
-	auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->ID.getNum());
+	auto heroSpecial = Selector::source(Bonus::HERO_SPECIAL, hero->type->getIndex());
 	auto secondarySkillBonus = Selector::type()(Bonus::SECONDARY_SKILL_PREMY);
 	auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
 	float specialityScore = 0.0f;
@@ -172,7 +172,7 @@ void HeroManager::update()
 
 	for(auto hero : myHeroes)
 	{
-		logAi->trace("Hero %s has role %s", hero->name, heroRoles[hero] == HeroRole::MAIN ? "main" : "scout");
+		logAi->trace("Hero %s has role %s", hero->getNameTranslated(), heroRoles[hero] == HeroRole::MAIN ? "main" : "scout");
 	}
 }
 

+ 2 - 2
AI/Nullkiller/Behaviors/DefenceBehavior.cpp

@@ -78,7 +78,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 
 		logAi->trace(
 			"Hero %s in garrison of town %s is suposed to defend the town",
-			town->garrisonHero->name,
+			town->garrisonHero->getNameTranslated(),
 			town->name);
 
 		return;
@@ -101,7 +101,7 @@ void DefenceBehavior::evaluateDefence(Goals::TGoalVec & tasks, const CGTownInsta
 			town->name,
 			treat.danger,
 			std::to_string(treat.turn),
-			treat.hero->name);
+			treat.hero->getNameTranslated());
 
 		bool treatIsUnderControl = false;
 

+ 1 - 1
AI/Nullkiller/Goals/AbstractGoal.cpp

@@ -69,7 +69,7 @@ std::string AbstractGoal::toString() const //TODO: virtualize
 		return boost::lexical_cast<std::string>(goalType);
 	}
 	if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal
-		desc += " (" + hero->name + ")";
+		desc += " (" + hero->getNameTranslated() + ")";
 	return desc;
 }
 

+ 2 - 2
AI/Nullkiller/Goals/AdventureSpellCast.cpp

@@ -33,7 +33,7 @@ void AdventureSpellCast::accept(AIGateway * ai)
 
 	auto spell = getSpell();
 
-	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->name);
+	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getNameTranslated(), hero->getNameTranslated());
 
 	if(!spell->isAdventure())
 		throw cannotFulfillGoalException(spell->getNameTranslated() + " is not an adventure spell.");
@@ -45,7 +45,7 @@ void AdventureSpellCast::accept(AIGateway * ai)
 		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getNameTranslated());
 
 	if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
-		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name);
+		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
 
 	if(town && spellID == SpellID::TOWN_PORTAL)
 	{

+ 3 - 3
AI/Nullkiller/Goals/ExchangeSwapTownHeroes.cpp

@@ -54,13 +54,13 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
 
 		if(currentGarrisonHero.get() != town->visitingHero.get())
 		{
-			logAi->error("VisitingHero is empty, expected %s", currentGarrisonHero->name);
+			logAi->error("VisitingHero is empty, expected %s", currentGarrisonHero->getNameTranslated());
 			return;
 		}
 
 		ai->buildArmyIn(town);
 		ai->nullkiller->unlockHero(currentGarrisonHero.get());
-		logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->name, town->name);
+		logAi->debug("Extracted hero %s from garrison of %s", currentGarrisonHero->getNameTranslated(), town->name);
 
 		return;
 	}
@@ -91,7 +91,7 @@ void ExchangeSwapTownHeroes::accept(AIGateway * ai)
 		ai->makePossibleUpgrades(town->visitingHero);
 	}
 
-	logAi->debug("Put hero %s to garrison of %s", garrisonHero->name, town->name);
+	logAi->debug("Put hero %s to garrison of %s", garrisonHero->getNameTranslated(), town->name);
 }
 
 }

+ 6 - 6
AI/Nullkiller/Goals/ExecuteHeroChain.cpp

@@ -75,7 +75,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 			continue;
 		}
 
-		logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->name, node.coord.toString());
+		logAi->debug("Executing chain node %d. Moving hero %s to %s", i, hero->getNameTranslated(), node.coord.toString());
 
 		try
 		{
@@ -111,7 +111,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 					{
 						logAi->error(
 							"Unable to complete chain. Expected hero %s to arrive to %s in 0 turns but he cannot do this",
-							hero->name,
+							hero->getNameTranslated(),
 							node.coord.toString());
 
 						return;
@@ -143,7 +143,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 
 							if(isOk && path.nodes.back().turns > 0)
 							{
-								logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->name, hero->movement, node.coord.toString());
+								logAi->warn("Hero %s has %d mp which is not enough to continue his way towards %s.", hero->getNameTranslated(), hero->movement, node.coord.toString());
 
 								ai->nullkiller->lockHero(hero, HeroLockedReason::HERO_CHAIN);
 								return;
@@ -162,7 +162,7 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 			{
 				logAi->error(
 					"Enable to complete chain. Expected hero %s to arive to %s but he is at %s", 
-					hero->name, 
+					hero->getNameTranslated(),
 					node.coord.toString(),
 					hero->visitablePos().toString());
 
@@ -187,14 +187,14 @@ void ExecuteHeroChain::accept(AIGateway * ai)
 
 std::string ExecuteHeroChain::toString() const
 {
-	return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->name;
+	return "ExecuteHeroChain " + targetName + " by " + chainPath.targetHero->getNameTranslated();
 }
 
 bool ExecuteHeroChain::moveHeroToTile(const CGHeroInstance * hero, const int3 & tile)
 {
 	if(tile == hero->visitablePos() && cb->getVisitableObjs(hero->visitablePos()).size() < 2)
 	{
-		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", hero->name, tile.toString());
+		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", hero->getNameTranslated(), tile.toString());
 
 		return true;
 	}

+ 2 - 2
AI/Nullkiller/Pathfinding/AINodeStorage.cpp

@@ -1437,10 +1437,10 @@ std::string AIPath::toString() const
 {
 	std::stringstream str;
 
-	str << targetHero->name << "[" << std::hex << chainMask << std::dec << "]" << ", turn " << (int)(turn()) << ": ";
+	str << targetHero->getNameTranslated() << "[" << std::hex << chainMask << std::dec << "]" << ", turn " << (int)(turn()) << ": ";
 
 	for(auto node : nodes)
-		str << node.targetHero->name << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; ";
+		str << node.targetHero->getNameTranslated() << "[" << std::hex << node.chainMask << std::dec << "]" << "->" << node.coord.toString() << "; ";
 
 	return str.str();
 }

+ 1 - 1
AI/Nullkiller/Pathfinding/Actions/BuyArmyAction.cpp

@@ -27,7 +27,7 @@ namespace AIPathfinding
 		if(!hero->visitedTown)
 		{
 			throw cannotFulfillGoalException(
-				hero->name + " being at " + hero->visitablePos().toString() + " has no town to recruit creatures.");
+				hero->getNameTranslated() + " being at " + hero->visitablePos().toString() + " has no town to recruit creatures.");
 		}
 
 		ai->recruitCreatures(hero->visitedTown, hero);

+ 1 - 1
AI/Nullkiller/Pathfinding/Actors.cpp

@@ -80,7 +80,7 @@ int ChainActor::maxMovePoints(CGPathNode::ELayer layer)
 
 std::string ChainActor::toString() const
 {
-	return hero->name;
+	return hero->getNameTranslated();
 }
 
 ObjectActor::ObjectActor(const CGObjectInstance * obj, const CCreatureSet * army, uint64_t chainMask, int initialTurn)

+ 1 - 1
AI/VCAI/AIUtility.cpp

@@ -69,7 +69,7 @@ HeroPtr::HeroPtr(const CGHeroInstance * H)
 	}
 
 	h = H;
-	name = h->name;
+	name = h->getNameTranslated();
 	hid = H->id;
 //	infosCount[ai->playerID][hid]++;
 }

+ 1 - 1
AI/VCAI/Goals/AbstractGoal.cpp

@@ -106,7 +106,7 @@ std::string AbstractGoal::name() const //TODO: virtualize
 		return boost::lexical_cast<std::string>(goalType);
 	}
 	if(hero.get(true)) //FIXME: used to crash when we lost hero and failed goal
-		desc += " (" + hero->name + ")";
+		desc += " (" + hero->getNameTranslated() + ")";
 	return desc;
 }
 

+ 2 - 2
AI/VCAI/Goals/AdventureSpellCast.cpp

@@ -33,7 +33,7 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
 
 	auto spell = getSpell();
 
-	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getName(), hero->name);
+	logAi->trace("Decomposing adventure spell cast of %s for hero %s", spell->getName(), hero->getNameTranslated());
 
 	if(!spell->isAdventure())
 		throw cannotFulfillGoalException(spell->getName() + " is not an adventure spell.");
@@ -45,7 +45,7 @@ TSubgoal AdventureSpellCast::whatToDoToAchieve()
 		throw cannotFulfillGoalException("Hero has not enough mana to cast " + spell->getName());
 
 	if(spellID == SpellID::TOWN_PORTAL && town && town->visitingHero)
-		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->name);
+		throw cannotFulfillGoalException("The town is already occupied by " + town->visitingHero->getNameTranslated());
 
 	return iAmElementar();
 }

+ 2 - 2
AI/VCAI/Goals/CompleteQuest.cpp

@@ -92,7 +92,7 @@ TSubgoal CompleteQuest::whatToDoToAchieve()
 		result->name(),
 		result->tile.toString(),
 		result->objid,
-		result->hero.validAndSet() ? result->hero->name : "not specified");
+		result->hero.validAndSet() ? result->hero->getNameTranslated() : "not specified");
 
 	return result;
 }
@@ -273,4 +273,4 @@ TGoalVec CompleteQuest::missionDestroyObj() const
 	}
 
 	return solutions;
-}
+}

+ 2 - 2
AI/VCAI/Goals/Explore.cpp

@@ -240,7 +240,7 @@ bool Explore::operator==(const Explore & other) const
 
 std::string Explore::completeMessage() const
 {
-	return "Hero " + hero.get()->name + " completed exploration";
+	return "Hero " + hero.get()->getNameTranslated() + " completed exploration";
 }
 
 TSubgoal Explore::whatToDoToAchieve()
@@ -339,7 +339,7 @@ TGoalVec Explore::getAllPossibleSubgoals()
 	{
 		for(auto h : heroes)
 		{
-			logAi->trace("Exploration searching for a new point for hero %s", h->name);
+			logAi->trace("Exploration searching for a new point for hero %s", h->getNameTranslated());
 
 			TSubgoal goal = explorationNewPoint(h);
 

+ 1 - 1
AI/VCAI/Goals/GatherArmy.cpp

@@ -33,7 +33,7 @@ bool GatherArmy::operator==(const GatherArmy & other) const
 
 std::string GatherArmy::completeMessage() const
 {
-	return "Hero " + hero.get()->name + " gathered army of value " + boost::lexical_cast<std::string>(value);
+	return "Hero " + hero.get()->getNameTranslated() + " gathered army of value " + boost::lexical_cast<std::string>(value);
 }
 
 TSubgoal GatherArmy::whatToDoToAchieve()

+ 1 - 1
AI/VCAI/Goals/GatherTroops.cpp

@@ -56,7 +56,7 @@ TSubgoal GatherTroops::whatToDoToAchieve()
 	{
 		if(getCreaturesCount(hero) >= this->value)
 		{
-			logAi->trace("Completing GATHER_TROOPS by hero %s", hero->name);
+			logAi->trace("Completing GATHER_TROOPS by hero %s", hero->getNameTranslated());
 
 			throw goalFulfilledException(sptr(*this));
 		}

+ 1 - 1
AI/VCAI/Goals/VisitHero.cpp

@@ -33,7 +33,7 @@ bool VisitHero::operator==(const VisitHero & other) const
 
 std::string VisitHero::completeMessage() const
 {
-	return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast<std::string>(objid);
+	return "hero " + hero.get()->getNameTranslated() + " visited hero " + boost::lexical_cast<std::string>(objid);
 }
 
 TSubgoal VisitHero::whatToDoToAchieve()

+ 1 - 1
AI/VCAI/Goals/VisitObj.cpp

@@ -33,7 +33,7 @@ bool VisitObj::operator==(const VisitObj & other) const
 
 std::string VisitObj::completeMessage() const
 {
-	return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<std::string>(objid);
+	return "hero " + hero.get()->getNameTranslated() + " captured Object ID = " + boost::lexical_cast<std::string>(objid);
 }
 
 TGoalVec VisitObj::getAllPossibleSubgoals()

+ 1 - 1
AI/VCAI/Goals/VisitTile.cpp

@@ -33,7 +33,7 @@ bool VisitTile::operator==(const VisitTile & other) const
 
 std::string VisitTile::completeMessage() const
 {
-	return "Hero " + hero.get()->name + " visited tile " + tile.toString();
+	return "Hero " + hero.get()->getNameTranslated() + " visited tile " + tile.toString();
 }
 
 TSubgoal VisitTile::whatToDoToAchieve()

+ 1 - 1
AI/VCAI/Pathfinding/AIPathfinder.cpp

@@ -55,7 +55,7 @@ void AIPathfinder::updatePaths(std::vector<HeroPtr> heroes)
 
 	auto calculatePaths = [&](const CGHeroInstance * hero, std::shared_ptr<AIPathfinding::AIPathfinderConfig> config)
 	{
-		logAi->debug("Recalculate paths for %s", hero->name);
+		logAi->debug("Recalculate paths for %s", hero->getNameTranslated());
 		
 		cb->calculatePaths(config);
 	};

+ 13 - 13
AI/VCAI/VCAI.cpp

@@ -303,7 +303,7 @@ void VCAI::heroExchangeStarted(ObjectInstanceID hero1, ObjectInstanceID hero2, Q
 	auto firstHero = cb->getHero(hero1);
 	auto secondHero = cb->getHero(hero2);
 
-	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->name % firstHero->tempOwner % secondHero->name % secondHero->tempOwner));
+	status.addQuery(query, boost::str(boost::format("Exchange between heroes %s (%d) and %s (%d)") % firstHero->getNameTranslated() % firstHero->tempOwner % secondHero->getNameTranslated() % secondHero->tempOwner));
 
 	requestActionASAP([=]()
 	{
@@ -617,7 +617,7 @@ void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill::PrimarySkill
 {
 	LOG_TRACE_PARAMS(logAi, "queryID '%i'", queryID);
 	NET_EVENT_HANDLER;
-	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->name % hero->level));
+	status.addQuery(queryID, boost::str(boost::format("Hero %s got level %d") % hero->getNameTranslated() % hero->level));
 	requestActionASAP([=](){ answerQuery(queryID, 0); });
 }
 
@@ -814,7 +814,7 @@ void VCAI::makeTurn()
 		for (auto h : cb->getHeroesInfo())
 		{
 			if (h->movement)
-				logAi->warn("Hero %s has %d MP left", h->name, h->movement);
+				logAi->warn("Hero %s has %d MP left", h->getNameTranslated(), h->movement);
 		}
 	}
 	catch (boost::thread_interrupted & e)
@@ -1034,7 +1034,7 @@ void VCAI::mainLoop()
 
 void VCAI::performObjectInteraction(const CGObjectInstance * obj, HeroPtr h)
 {
-	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->name % obj->getObjectName() % obj->pos.toString());
+	LOG_TRACE_PARAMS(logAi, "Hero %s and object %s at %s", h->getNameTranslated() % obj->getObjectName() % obj->pos.toString());
 	switch(obj->ID)
 	{
 	case Obj::CREATURE_GENERATOR1:
@@ -1427,7 +1427,7 @@ void VCAI::wander(HeroPtr h)
 			{
 				//TODO pick the truly best
 				const CGTownInstance * t = *boost::max_element(townsNotReachable, compareReinforcements);
-				logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->name, t->name, t->visitablePos().toString());
+				logAi->debug("%s can't reach any town, we'll try to make our way to %s at %s", h->getNameTranslated(), t->name, t->visitablePos().toString());
 				int3 pos1 = h->pos;
 				striveToGoal(sptr(Goals::ClearWayTo(t->visitablePos()).sethero(h))); //TODO: drop "strive", add to mainLoop
 				//if out hero is stuck, we may need to request another hero to clear the way we see
@@ -1581,7 +1581,7 @@ void VCAI::battleStart(const CCreatureSet * army1, const CCreatureSet * army2, i
 	assert(playerID > PlayerColor::PLAYER_LIMIT || status.getBattle() == UPCOMING_BATTLE);
 	status.setBattle(ONGOING_BATTLE);
 	const CGObjectInstance * presumedEnemy = vstd::backOrNull(cb->getVisitableObjs(tile)); //may be nullptr in some very are cases -> eg. visited monolith and fighting with an enemy at the FoW covered exit
-	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->name : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
+	battlename = boost::str(boost::format("Starting battle of %s attacking %s at %s") % (hero1 ? hero1->getNameTranslated() : "a army") % (presumedEnemy ? presumedEnemy->getObjectName() : "unknown enemy") % tile.toString());
 	CAdventureAI::battleStart(army1, army2, tile, hero1, hero2, side);
 }
 
@@ -1667,7 +1667,7 @@ void VCAI::validateVisitableObjs()
 	});
 	for(auto & p : reservedHeroesMap)
 	{
-		errorMsg = " shouldn't be on list for hero " + p.first->name + "!";
+		errorMsg = " shouldn't be on list for hero " + p.first->getNameTranslated() + "!";
 		vstd::erase_if(p.second, shouldBeErased);
 	}
 
@@ -1808,7 +1808,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 		}
 	};
 
-	logAi->debug("Moving hero %s to tile %s", h->name, dst.toString());
+	logAi->debug("Moving hero %s to tile %s", h->getNameTranslated(), dst.toString());
 	int3 startHpos = h->visitablePos();
 	bool ret = false;
 	if(startHpos == dst)
@@ -1828,7 +1828,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 		cb->getPathsInfo(h.get())->getPath(path, dst);
 		if(path.nodes.empty())
 		{
-			logAi->error("Hero %s cannot reach %s.", h->name, dst.toString());
+			logAi->error("Hero %s cannot reach %s.", h->getNameTranslated(), dst.toString());
 			throw goalFulfilledException(sptr(Goals::VisitTile(dst).sethero(h)));
 		}
 		int i = (int)path.nodes.size() - 1;
@@ -1990,7 +1990,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 			throw cannotFulfillGoalException("Invalid path found!");
 		}
 		evaluateGoal(h); //new hero position means new game situation
-		logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->name, startHpos.toString(), h->visitablePos().toString(), ret);
+		logAi->debug("Hero %s moved from %s to %s. Returning %d.", h->getNameTranslated(), startHpos.toString(), h->visitablePos().toString(), ret);
 	}
 	return ret;
 }
@@ -2027,7 +2027,7 @@ void VCAI::tryRealize(Goals::VisitTile & g)
 		throw cannotFulfillGoalException("Cannot visit tile: hero is out of MPs!");
 	if(g.tile == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2)
 	{
-		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->name, g.tile.toString());
+		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->getNameTranslated(), g.tile.toString());
 		throw goalFulfilledException(sptr(g));
 	}
 	if(ai->moveHeroToTile(g.tile, g.hero.get()))
@@ -2043,7 +2043,7 @@ void VCAI::tryRealize(Goals::VisitObj & g)
 		throw cannotFulfillGoalException("Cannot visit object: hero is out of MPs!");
 	if(position == g.hero->visitablePos() && cb->getVisitableObjs(g.hero->visitablePos()).size() < 2)
 	{
-		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->name, g.tile.toString());
+		logAi->warn("Why do I want to move hero %s to tile %s? Already standing on that tile! ", g.hero->getNameTranslated(), g.tile.toString());
 		throw goalFulfilledException(sptr(g));
 	}
 	if(ai->moveHeroToTile(position, g.hero.get()))
@@ -2404,7 +2404,7 @@ void VCAI::performTypicalActions()
 		if(!h) //hero might be lost. getUnblockedHeroes() called once on start of turn
 			continue;
 
-		logAi->debug("Hero %s started wandering, MP=%d", h->name.c_str(), h->movement);
+		logAi->debug("Hero %s started wandering, MP=%d", h->getNameTranslated(), h->movement);
 		makePossibleUpgrades(*h);
 		pickBestArtifacts(*h);
 		try

+ 2 - 2
client/CPlayerInterface.cpp

@@ -410,7 +410,7 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 void CPlayerInterface::heroKilled(const CGHeroInstance* hero)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	LOG_TRACE_PARAMS(logGlobal, "Hero %s killed handler for player %s", hero->name % playerID);
+	LOG_TRACE_PARAMS(logGlobal, "Hero %s killed handler for player %s", hero->getNameTranslated() % playerID);
 
 	const CArmedInstance *newSelection = nullptr;
 	if (makingTurn)
@@ -1268,7 +1268,7 @@ template <typename Handler> void CPlayerInterface::serializeTempl( Handler &h, c
 			if (p.second.nodes.size())
 				pathsMap[p.first] = p.second.endPos();
 			else
-				logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->name);
+				logGlobal->debug("%s has assigned an empty path! Ignoring it...", p.first->getNameTranslated());
 		}
 		h & pathsMap;
 	}

+ 2 - 2
client/ClientCommandManager.cpp

@@ -317,7 +317,7 @@ void ClientCommandManager::processCommand(const std::string &message, bool calle
 		if(what == "hs")
 		{
 			for(const CGHeroInstance *h : LOCPLINT->cb->getHeroesInfo())
-				if(h->type->ID.getNum() == id1)
+				if(h->type->getIndex() == id1)
 					if(const CArtifactInstance *a = h->getArt(ArtifactPosition(id2)))
 						printCommandMessage(a->nodeName());
 		}
@@ -491,4 +491,4 @@ void ClientCommandManager::printCommandMessage(const std::string &commandMessage
 			LOCPLINT->cingconsole->print(commandMessage);
 		}
 	}
-}
+}

+ 1 - 1
client/battle/BattleInterfaceClasses.cpp

@@ -613,7 +613,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 		if (ourHero)
 		{
 			str += CGI->generaltexth->allTexts[305];
-			boost::algorithm::replace_first(str, "%s", ourHero->name);
+			boost::algorithm::replace_first(str, "%s", ourHero->getNameTranslated());
 			boost::algorithm::replace_first(str, "%d", boost::lexical_cast<std::string>(br.exp[weAreAttacker ? 0 : 1]));
 		}
 

+ 3 - 3
client/battle/BattleWindow.cpp

@@ -274,10 +274,10 @@ void BattleWindow::bFleef()
 		//calculating fleeing hero's name
 		if (owner.attackingHeroInstance)
 			if (owner.attackingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
-				heroName = owner.attackingHeroInstance->name;
+				heroName = owner.attackingHeroInstance->getNameTranslated();
 		if (owner.defendingHeroInstance)
 			if (owner.defendingHeroInstance->tempOwner == owner.curInt->cb->getMyColor())
-				heroName = owner.defendingHeroInstance->name;
+				heroName = owner.defendingHeroInstance->getNameTranslated();
 		//calculating text
 		auto txt = boost::format(CGI->generaltexth->allTexts[340]) % heroName; //The Shackles of War are present.  %s can not retreat!
 
@@ -416,7 +416,7 @@ void BattleWindow::bSpellf()
 			const auto artID = ArtifactID(blockingBonus->sid);
 			//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
 			//TODO check who *really* is source of bonus
-			std::string heroName = myHero->hasArt(artID) ? myHero->name : owner.enemyHero().name;
+			std::string heroName = myHero->hasArt(artID) ? myHero->getNameTranslated() : owner.enemyHero().name;
 
 			//%s wields the %s, an ancient artifact which creates a p dead to all magic.
 			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])

+ 1 - 1
client/lobby/CBonusSelection.cpp

@@ -318,7 +318,7 @@ void CBonusSelection::createBonusesIcons()
 			}
 			else
 			{
-				boost::algorithm::replace_first(desc, "%s", CGI->heroh->objects[bonDescs[i].info2]->name);
+				boost::algorithm::replace_first(desc, "%s", CGI->heroh->objects[bonDescs[i].info2]->getNameTranslated());
 			}
 			break;
 		}

+ 3 - 3
client/lobby/OptionsTab.cpp

@@ -201,7 +201,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getName()
 			if(!settings.heroName.empty())
 				return settings.heroName;
 			auto index = settings.hero >= CGI->heroh->size() ? 0 : settings.hero;
-			return (*CGI->heroh)[index]->name;
+			return (*CGI->heroh)[index]->getNameTranslated();
 		}
 		}
 	}
@@ -257,7 +257,7 @@ std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
 	case HERO:
 	{
 		if(settings.hero >= 0)
-			return getName() + " - " + (*CGI->heroh)[heroIndex]->heroClass->name;
+			return getName() + " - " + (*CGI->heroh)[heroIndex]->heroClass->getNameTranslated();
 		return getName();
 	}
 
@@ -398,7 +398,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow()
 	auto heroIndex = settings.hero >= CGI->heroh->size() ? 0 : settings.hero;
 
 	imageSpeciality = std::make_shared<CAnimImage>("UN44", (*CGI->heroh)[heroIndex]->imageIndex, 0, pos.w / 2 - 22, 134);
-	labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->specName);
+	labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->getSpecialtyNameTranslated());
 }
 
 void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()

+ 1 - 1
client/mapHandler.cpp

@@ -960,7 +960,7 @@ CMapHandler::AnimBitmapHolder CMapHandler::CMapBlitter::findHeroBitmap(const CGH
 	{
 		if(hero->tempOwner >= PlayerColor::PLAYER_LIMIT) //Neutral hero?
 		{
-			logGlobal->error("A neutral hero (%s) at %s. Should not happen!", hero->name, hero->pos.toString());
+			logGlobal->error("A neutral hero (%s) at %s. Should not happen!", hero->getNameTranslated(), hero->pos.toString());
 			return CMapHandler::AnimBitmapHolder();
 		}
 

+ 1 - 1
client/widgets/AdventureMapClasses.cpp

@@ -242,7 +242,7 @@ void CHeroList::CHeroItem::showTooltip()
 
 std::string CHeroList::CHeroItem::getHoverText()
 {
-	return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->name % hero->type->heroClass->name);
+	return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->getNameTranslated() % hero->type->heroClass->getNameTranslated());
 }
 
 std::shared_ptr<CIntObject> CHeroList::createHeroItem(size_t index)

+ 6 - 6
client/windows/CCastleInterface.cpp

@@ -353,25 +353,25 @@ void CHeroGSlot::hover(bool on)
 		if(isSelected())//view NNN
 		{
 			temp = CGI->generaltexth->tcommands[4];
-			boost::algorithm::replace_first(temp,"%s",hero->name);
+			boost::algorithm::replace_first(temp,"%s",hero->getNameTranslated());
 		}
 		else if(other->hero && other->isSelected())//exchange
 		{
 			temp = CGI->generaltexth->tcommands[7];
-			boost::algorithm::replace_first(temp,"%s",hero->name);
-			boost::algorithm::replace_first(temp,"%s",other->hero->name);
+			boost::algorithm::replace_first(temp,"%s",hero->getNameTranslated());
+			boost::algorithm::replace_first(temp,"%s",other->hero->getNameTranslated());
 		}
 		else// select NNN (in ZZZ)
 		{
 			if(upg)//down - visiting
 			{
 				temp = CGI->generaltexth->tcommands[32];
-				boost::algorithm::replace_first(temp,"%s",hero->name);
+				boost::algorithm::replace_first(temp,"%s",hero->getNameTranslated());
 			}
 			else //up - garrison
 			{
 				temp = CGI->generaltexth->tcommands[12];
-				boost::algorithm::replace_first(temp,"%s",hero->name);
+				boost::algorithm::replace_first(temp,"%s",hero->getNameTranslated());
 			}
 		}
 	}
@@ -380,7 +380,7 @@ void CHeroGSlot::hover(bool on)
 		if(other->isSelected() && other->hero) //move NNNN
 		{
 			temp = CGI->generaltexth->tcommands[6];
-			boost::algorithm::replace_first(temp,"%s",other->hero->name);
+			boost::algorithm::replace_first(temp,"%s",other->hero->getNameTranslated());
 		}
 		else //empty
 		{

+ 8 - 8
client/windows/CHeroWindow.cpp

@@ -211,19 +211,19 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 
 	assert(hero == curHero);
 
-	name->setText(curHero->name);
-	title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->type->heroClass->name).str());
+	name->setText(curHero->getNameTranslated());
+	title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->type->heroClass->getNameTranslated()).str());
 
-	specArea->text = curHero->type->specDescr;
+	specArea->text = curHero->type->getSpecialtyDescriptionTranslated();
 	specImage->setFrame(curHero->type->imageIndex);
-	specName->setText(curHero->type->specName);
+	specName->setText(curHero->type->getSpecialtyNameTranslated());
 
 	tacticsButton = std::make_shared<CToggleButton>(Point(539, 483), "hsbtns8.def", std::make_pair(heroscrn[26], heroscrn[31]), 0, SDLK_b);
 	tacticsButton->addHoverText(CButton::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]);
 
-	dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->name % curHero->type->heroClass->name));
-	portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->name % curHero->type->heroClass->name);
-	portraitArea->text = curHero->getBiography();
+	dismissButton->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->heroscrn[16]) % curHero->getNameTranslated() % curHero->type->heroClass->getNameTranslated()));
+	portraitArea->hoverText = boost::str(boost::format(CGI->generaltexth->allTexts[15]) % curHero->getNameTranslated() % curHero->type->heroClass->getNameTranslated());
+	portraitArea->text = curHero->getBiographyTranslated();
 	portraitImage->setFrame(curHero->portrait);
 
 	{
@@ -291,7 +291,7 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 
 	//printing spell points, boost::format can't be used due to locale issues
 	spellPointsArea->text = CGI->generaltexth->allTexts[205];
-	boost::replace_first(spellPointsArea->text, "%s", boost::lexical_cast<std::string>(curHero->name));
+	boost::replace_first(spellPointsArea->text, "%s", boost::lexical_cast<std::string>(curHero->getNameTranslated()));
 	boost::replace_first(spellPointsArea->text, "%d", boost::lexical_cast<std::string>(curHero->mana));
 	boost::replace_first(spellPointsArea->text, "%d", boost::lexical_cast<std::string>(heroWArt.manaLimit()));
 

+ 6 - 6
client/windows/CKingdomInterface.cpp

@@ -163,7 +163,7 @@ std::string InfoBoxAbstractHeroData::getNameText()
 	case HERO_EXPERIENCE:
 		return CGI->generaltexth->jktexts[6];
 	case HERO_SPECIAL:
-		return CGI->heroh->objects[getSubID()]->specName;
+		return CGI->heroh->objects[getSubID()]->getSpecialtyNameTranslated();
 	case HERO_SECONDARY_SKILL:
 		if (getValue())
 			return CGI->skillh->getByIndex(getSubID())->getNameTranslated();
@@ -256,7 +256,7 @@ void InfoBoxAbstractHeroData::prepareMessage(std::string & text, std::shared_ptr
 	switch (type)
 	{
 	case HERO_SPECIAL:
-		text = CGI->heroh->objects[getSubID()]->specDescr;
+		text = CGI->heroh->objects[getSubID()]->getSpecialtyDescriptionTranslated();
 		break;
 	case HERO_PRIMARY_SKILL:
 		text = CGI->generaltexth->arraytxt[2+getSubID()];
@@ -303,7 +303,7 @@ int InfoBoxHeroData::getSubID()
 		else
 			return 0;
 	case HERO_SPECIAL:
-		return hero->type->ID.getNum();
+		return hero->type->getIndex();
 	case HERO_MANA:
 	case HERO_EXPERIENCE:
 		return 0;
@@ -390,7 +390,7 @@ void InfoBoxHeroData::prepareMessage(std::string & text, std::shared_ptr<CCompon
 	{
 	case HERO_MANA:
 		text = CGI->generaltexth->allTexts[205];
-		boost::replace_first(text, "%s", boost::lexical_cast<std::string>(hero->name));
+		boost::replace_first(text, "%s", boost::lexical_cast<std::string>(hero->getNameTranslated()));
 		boost::replace_first(text, "%d", boost::lexical_cast<std::string>(hero->mana));
 		boost::replace_first(text, "%d", boost::lexical_cast<std::string>(hero->manaLimit()));
 		break;
@@ -863,7 +863,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
 	arts2->recActions = SHARE_POS;
 	backpack->recActions = SHARE_POS;
 
-	name = std::make_shared<CLabel>(75, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->name);
+	name = std::make_shared<CLabel>(75, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->getNameTranslated());
 
 	//layout is not trivial: MACH4 - catapult - excluded, MISC[x] rearranged
 	assert(arts1->arts.size() == 9);
@@ -919,7 +919,7 @@ CHeroItem::CHeroItem(const CGHeroInstance * Hero)
 	portrait = std::make_shared<CAnimImage>("PortraitsLarge", hero->portrait, 0, 5, 6);
 	heroArea = std::make_shared<CHeroArea>(5, 6, hero);
 
-	name = std::make_shared<CLabel>(73, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->name);
+	name = std::make_shared<CLabel>(73, 7, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero->getNameTranslated());
 	artsText = std::make_shared<CLabel>(320, 55, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[2]);
 
 	for(size_t i=0; i<GameConstants::PRIMARY_SKILLS; i++)

+ 3 - 3
client/windows/CTradeWindow.cpp

@@ -738,11 +738,11 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
 		break;
 	case EMarketMode::CREATURE_RESOURCE:
 		//%s's Creatures
-		labels.push_back(std::make_shared<CLabel>(152, 102, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)));
+		labels.push_back(std::make_shared<CLabel>(152, 102, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated())));
 		break;
 	case EMarketMode::ARTIFACT_RESOURCE:
 		//%s's Artifacts
-		labels.push_back(std::make_shared<CLabel>(152, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->name)));
+		labels.push_back(std::make_shared<CLabel>(152, 56, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[271]) % hero->getNameTranslated())));
 		break;
 	}
 
@@ -1099,7 +1099,7 @@ CAltarWindow::CAltarWindow(const IMarket * Market, const CGHeroInstance * Hero,
 	{
 		//%s's Creatures
 		labels.push_back(std::make_shared<CLabel>(155, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW,
-				   boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->name)));
+				   boost::str(boost::format(CGI->generaltexth->allTexts[272]) % hero->getNameTranslated())));
 
 		//Altar of Sacrifice
 		labels.push_back(std::make_shared<CLabel>(450, 30, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[479]));

+ 9 - 9
client/windows/GUIClasses.cpp

@@ -404,11 +404,11 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill::PrimarySki
 	ok = std::make_shared<CButton>(Point(297, 413), "IOKAY", CButton::tooltip(), std::bind(&CLevelWindow::close, this), SDLK_RETURN);
 
 	//%s has gained a level.
-	mainTitle = std::make_shared<CLabel>(192, 33, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[444]) % hero->name));
+	mainTitle = std::make_shared<CLabel>(192, 33, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, boost::str(boost::format(CGI->generaltexth->allTexts[444]) % hero->getNameTranslated()));
 
 	//%s is now a level %d %s.
 	levelTitle = std::make_shared<CLabel>(192, 162, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE,
-		boost::str(boost::format(CGI->generaltexth->allTexts[445]) % hero->name % hero->level % hero->type->heroClass->name));
+		boost::str(boost::format(CGI->generaltexth->allTexts[445]) % hero->getNameTranslated() % hero->level % hero->type->heroClass->getNameTranslated()));
 
 	skillIcon = std::make_shared<CAnimImage>("PSKIL42", pskill, 0, 174, 190);
 
@@ -743,7 +743,7 @@ void CTavernWindow::show(SDL_Surface * to)
 			oldSelected = selected;
 
 			//Recruit %s the %s
-			recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->name % sel->h->type->heroClass->name));
+			recruit->addHoverText(CButton::NORMAL, boost::str(boost::format(CGI->generaltexth->tavernInfo[3]) % sel->h->getNameTranslated() % sel->h->type->heroClass->getNameTranslated()));
 		}
 
 		printAtMiddleWBLoc(sel->description, 146, 395, FONT_SMALL, 200, Colors::WHITE, to);
@@ -777,7 +777,7 @@ CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const
 	if(H)
 	{
 		hoverName = CGI->generaltexth->tavernInfo[4];
-		boost::algorithm::replace_first(hoverName,"%s",H->name);
+		boost::algorithm::replace_first(hoverName,"%s",H->getNameTranslated());
 
 		int artifs = (int)h->artifactsWorn.size() + (int)h->artifactsInBackpack.size();
 		for(int i=13; i<=17; i++) //war machines and spellbook don't count
@@ -785,9 +785,9 @@ CTavernWindow::HeroPortrait::HeroPortrait(int & sel, int id, int x, int y, const
 				artifs--;
 
 		description = CGI->generaltexth->allTexts[215];
-		boost::algorithm::replace_first(description, "%s", h->name);
+		boost::algorithm::replace_first(description, "%s", h->getNameTranslated());
 		boost::algorithm::replace_first(description, "%d", boost::lexical_cast<std::string>(h->level));
-		boost::algorithm::replace_first(description, "%s", h->type->heroClass->name);
+		boost::algorithm::replace_first(description, "%s", h->type->heroClass->getNameTranslated());
 		boost::algorithm::replace_first(description, "%d", boost::lexical_cast<std::string>(artifs));
 
 		portrait = std::make_shared<CAnimImage>("portraitsLarge", h->portrait);
@@ -1078,7 +1078,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 	auto genTitle = [](const CGHeroInstance * h)
 	{
 		boost::format fmt(CGI->generaltexth->allTexts[138]);
-		fmt % h->name % h->level % h->type->heroClass->name;
+		fmt % h->getNameTranslated() % h->level % h->type->heroClass->getNameTranslated();
 		return boost::str(fmt);
 	};
 
@@ -1178,7 +1178,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		specialtyAreas[b] = std::make_shared<LRClickableAreaWText>();
 		specialtyAreas[b]->pos = genRect(32, 32, pos.x + 69 + 490*b, pos.y + (qeLayout ? 41 : 45));
 		specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27];
-		specialtyAreas[b]->text = hero->type->specDescr;
+		specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated();
 
 		experienceAreas[b] = std::make_shared<LRClickableAreaWText>();
 		experienceAreas[b]->pos = genRect(32, 32, pos.x + 105 + 490*b, pos.y + (qeLayout ? 41 : 45));
@@ -1192,7 +1192,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		spellPointsAreas[b]->pos = genRect(32, 32, pos.x + 141 + 490*b, pos.y + (qeLayout ? 41 : 45));
 		spellPointsAreas[b]->hoverText = CGI->generaltexth->heroscrn[22];
 		spellPointsAreas[b]->text = CGI->generaltexth->allTexts[205];
-		boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->name);
+		boost::algorithm::replace_first(spellPointsAreas[b]->text, "%s", hero->getNameTranslated());
 		boost::algorithm::replace_first(spellPointsAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->mana));
 		boost::algorithm::replace_first(spellPointsAreas[b]->text, "%d", boost::lexical_cast<std::string>(hero->manaLimit()));
 

+ 4 - 0
include/vcmi/HeroClass.h

@@ -18,7 +18,11 @@ class HeroClassID;
 
 class DLL_LINKAGE HeroClass : public EntityT<HeroClassID>
 {
+	using EntityT<HeroClassID>::getName;
 public:
+	virtual std::string getNameTranslated() const = 0;
+	virtual std::string getNameTextID() const = 0;
+
 
 };
 

+ 12 - 0
include/vcmi/HeroType.h

@@ -18,7 +18,19 @@ class HeroTypeID;
 
 class DLL_LINKAGE HeroType : public EntityT<HeroTypeID>
 {
+	using EntityT<HeroTypeID>::getName;
 public:
+	virtual std::string getNameTranslated() const = 0;
+	virtual std::string getBiographyTranslated() const = 0;
+	virtual std::string getSpecialtyNameTranslated() const = 0;
+	virtual std::string getSpecialtyDescriptionTranslated() const = 0;
+	virtual std::string getSpecialtyTooltipTranslated() const = 0;
+
+	virtual std::string getNameTextID() const = 0;
+	virtual std::string getBiographyTextID() const = 0;
+	virtual std::string getSpecialtyNameTextID() const = 0;
+	virtual std::string getSpecialtyDescriptionTextID() const = 0;
+	virtual std::string getSpecialtyTooltipTextID() const = 0;
 
 };
 

+ 6 - 6
lib/CGameState.cpp

@@ -1451,7 +1451,7 @@ void CGameState::initHeroes()
 
 		hero->initHero(getRandomGenerator());
 		getPlayerState(hero->getOwner())->heroes.push_back(hero);
-		map->allHeroes[hero->type->ID.getNum()] = hero;
+		map->allHeroes[hero->type->getIndex()] = hero;
 	}
 
 	for(auto obj : map->objects) //prisons
@@ -1468,7 +1468,7 @@ void CGameState::initHeroes()
 		ph->initHero(getRandomGenerator());
 		hpool.heroesPool[ph->subID] = ph;
 		hpool.pavailable[ph->subID] = 0xff;
-		heroesToCreate.erase(ph->type->ID);
+		heroesToCreate.erase(ph->type->getId());
 
 		map->allHeroes[ph->subID] = ph;
 	}
@@ -2796,7 +2796,7 @@ std::set<HeroTypeID> CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow
 	for(auto hero : map->heroesOnMap)  //heroes instances initialization
 	{
 		if(hero->type)
-			ret -= hero->type->ID;
+			ret -= hero->type->getId();
 		else
 			ret -= HeroTypeID(hero->subID);
 	}
@@ -2918,7 +2918,7 @@ CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const
 {
 	for(auto hero : map->heroesOnMap)  //heroes instances initialization
 	{
-		if(hero->type && hero->type->ID == hid)
+		if(hero->type && hero->type->getId() == hid)
 		{
 			return hero;
 		}
@@ -2930,7 +2930,7 @@ CGHeroInstance * CGameState::getUsedHero(HeroTypeID hid) const
 		{
 			auto hero = dynamic_cast<CGHeroInstance *>(obj.get());
 			assert(hero);
-			if ( hero->type && hero->type->ID == hid )
+			if ( hero->type && hero->type->getId() == hid )
 				return hero;
 		}
 	}
@@ -3024,7 +3024,7 @@ void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLe
 	initFromArmy(h, detailed);
 
 	hclass = h->type->heroClass;
-	name = h->name;
+	name = h->getNameTranslated();
 	portrait = h->portrait;
 
 	if(detailed)

+ 72 - 11
lib/CHeroHandler.cpp

@@ -43,7 +43,7 @@ int32_t CHero::getIconIndex() const
 
 const std::string & CHero::getName() const
 {
-	return name;
+	return identifier;
 }
 
 const std::string & CHero::getJsonKey() const
@@ -56,6 +56,56 @@ HeroTypeID CHero::getId() const
 	return ID;
 }
 
+std::string CHero::getNameTranslated() const
+{
+	return VLC->generaltexth->translate(getNameTextID());
+}
+
+std::string CHero::getBiographyTranslated() const
+{
+	return VLC->generaltexth->translate(getBiographyTextID());
+}
+
+std::string CHero::getSpecialtyNameTranslated() const
+{
+	return VLC->generaltexth->translate(getSpecialtyNameTextID());
+}
+
+std::string CHero::getSpecialtyDescriptionTranslated() const
+{
+	return VLC->generaltexth->translate(getSpecialtyDescriptionTextID());
+}
+
+std::string CHero::getSpecialtyTooltipTranslated() const
+{
+	return VLC->generaltexth->translate(getSpecialtyTooltipTextID());
+}
+
+std::string CHero::getNameTextID() const
+{
+	return TextIdentifier("hero", modScope, identifier, "name").get();
+}
+
+std::string CHero::getBiographyTextID() const
+{
+	return TextIdentifier("hero", modScope, identifier, "biography").get();
+}
+
+std::string CHero::getSpecialtyNameTextID() const
+{
+	return TextIdentifier("hero", modScope, identifier, "specialty", "name").get();
+}
+
+std::string CHero::getSpecialtyDescriptionTextID() const
+{
+	return TextIdentifier("hero", modScope, identifier, "specialty", "description").get();
+}
+
+std::string CHero::getSpecialtyTooltipTextID() const
+{
+	return TextIdentifier("hero", modScope, identifier, "specialty", "tooltip").get();
+}
+
 void CHero::registerIcons(const IconRegistar & cb) const
 {
 	cb(getIconIndex(), 0, "UN32", iconSpecSmall);
@@ -120,7 +170,7 @@ int32_t CHeroClass::getIconIndex() const
 
 const std::string & CHeroClass::getName() const
 {
-	return name;
+	return identifier;
 }
 
 const std::string & CHeroClass::getJsonKey() const
@@ -138,6 +188,16 @@ void CHeroClass::registerIcons(const IconRegistar & cb) const
 
 }
 
+std::string CHeroClass::getNameTranslated() const
+{
+	return VLC->generaltexth->translate(getNameTextID());
+}
+
+std::string CHeroClass::getNameTextID() const
+{
+	return TextIdentifier("heroClass", modScope, identifier, "specialty", "name").get();
+}
+
 void CHeroClass::updateFrom(const JsonNode & data)
 {
 	//TODO: CHeroClass::updateFrom
@@ -164,7 +224,7 @@ void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass *
 	if(currentPrimarySkillValue < primarySkillLegalMinimum)
 	{
 		logMod->error("Hero class '%s' has incorrect initial value '%d' for skill '%s'. Value '%d' will be used instead.",
-			heroClass->identifier, currentPrimarySkillValue, skillName, primarySkillLegalMinimum);
+			heroClass->getName(), currentPrimarySkillValue, skillName, primarySkillLegalMinimum);
 		currentPrimarySkillValue = primarySkillLegalMinimum;
 	}
 	heroClass->primarySkillInitial.push_back(currentPrimarySkillValue);
@@ -192,7 +252,8 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 	heroClass->imageMapFemale    = node["animation"]["map"]["female"].String();
 	heroClass->imageMapMale      = node["animation"]["map"]["male"].String();
 
-	heroClass->name = node["name"].String();
+	VLC->generaltexth->registerString( heroClass->getNameTextID(), node["name"].String());
+
 	heroClass->affinity = vstd::find_pos(affinityStr, node["affinity"].String());
 
 	fillPrimarySkillData(node, heroClass, PrimarySkill::ATTACK);
@@ -362,11 +423,11 @@ CHero * CHeroHandler::loadFromJson(const std::string & scope, const JsonNode & n
 	hero->sex = node["female"].Bool();
 	hero->special = node["special"].Bool();
 
-	hero->name        = node["texts"]["name"].String();
-	hero->biography   = node["texts"]["biography"].String();
-	hero->specName    = node["texts"]["specialty"]["name"].String();
-	hero->specTooltip = node["texts"]["specialty"]["tooltip"].String();
-	hero->specDescr   = node["texts"]["specialty"]["description"].String();
+	VLC->generaltexth->registerString( hero->getNameTextID(), node["texts"]["name"].String());
+	VLC->generaltexth->registerString( hero->getBiographyTextID(), node["texts"]["biography"].String());
+	VLC->generaltexth->registerString( hero->getSpecialtyNameTextID(), node["texts"]["specialty"]["name"].String());
+	VLC->generaltexth->registerString( hero->getSpecialtyTooltipTextID(), node["texts"]["specialty"]["tooltip"].String());
+	VLC->generaltexth->registerString( hero->getSpecialtyDescriptionTextID(), node["texts"]["specialty"]["description"].String());
 
 	hero->iconSpecSmall = node["images"]["specialtySmall"].String();
 	hero->iconSpecLarge = node["images"]["specialtyLarge"].String();
@@ -703,7 +764,7 @@ void CHeroHandler::loadHeroSpecialty(CHero * hero, const JsonNode & node)
 	const JsonNode & specialtiesNode = node["specialties"];
 	if (!specialtiesNode.isNull())
 	{
-		logMod->warn("Hero %s has deprecated specialties format.", hero->identifier);
+		logMod->warn("Hero %s has deprecated specialties format.", hero->getNameTranslated());
 		for(const JsonNode &specialty : specialtiesNode.Vector())
 		{
 			SSpecialtyInfo spec;
@@ -892,7 +953,7 @@ void CHeroHandler::afterLoadFinalization()
 
 		if(hero->specDeprecated.size() > 0 || hero->specialtyDeprecated.size() > 0)
 		{
-			logMod->debug("Converting specialty format for hero %s(%s)", hero->identifier, FactionID::encode(hero->heroClass->faction));
+			logMod->debug("Converting specialty format for hero %s(%s)", hero->getNameTranslated(), FactionID::encode(hero->heroClass->faction));
 			std::vector<std::shared_ptr<Bonus>> convertedBonuses;
 			for(const SSpecialtyInfo & spec : hero->specDeprecated)
 			{

+ 38 - 21
lib/CHeroHandler.h

@@ -30,7 +30,8 @@ class JsonSerializeFormat;
 class BattleField;
 
 struct SSpecialtyInfo
-{	si32 type;
+{
+	si32 type;
 	si32 val;
 	si32 subtype;
 	si32 additionalinfo;
@@ -57,6 +58,16 @@ struct SSpecialtyBonus
 
 class DLL_LINKAGE CHero : public HeroType
 {
+	friend class CHeroHandler;
+
+	HeroTypeID ID;
+	std::string identifier;
+	std::string modScope;
+	std::string nameTextID; //name of hero
+	std::string biographyTextID;
+
+	const std::string & getName() const override;
+
 public:
 	struct InitialArmyStack
 	{
@@ -71,8 +82,6 @@ public:
 			h & creature;
 		}
 	};
-	std::string identifier;
-	HeroTypeID ID;
 	si32 imageIndex;
 
 	std::vector<InitialArmyStack> initialArmy;
@@ -87,13 +96,6 @@ public:
 	bool special; // hero is special and won't be placed in game (unless preset on map), e.g. campaign heroes
 	ui8 sex; // default sex: 0=male, 1=female
 
-	/// Localized texts
-	std::string name; //name of hero
-	std::string biography;
-	std::string specName;
-	std::string specDescr;
-	std::string specTooltip;
-
 	/// Graphics
 	std::string iconSpecSmall;
 	std::string iconSpecLarge;
@@ -106,11 +108,22 @@ public:
 
 	int32_t getIndex() const override;
 	int32_t getIconIndex() const override;
-	const std::string & getName() const override;
 	const std::string & getJsonKey() const override;
 	HeroTypeID getId() const override;
 	void registerIcons(const IconRegistar & cb) const override;
 
+	std::string getNameTranslated() const override;
+	std::string getBiographyTranslated() const override;
+	std::string getSpecialtyNameTranslated() const override;
+	std::string getSpecialtyDescriptionTranslated() const override;
+	std::string getSpecialtyTooltipTranslated() const override;
+
+	std::string getNameTextID() const override;
+	std::string getBiographyTextID() const override;
+	std::string getSpecialtyNameTextID() const override;
+	std::string getSpecialtyDescriptionTextID() const override;
+	std::string getSpecialtyTooltipTextID() const override;
+
 	void updateFrom(const JsonNode & data);
 	void serializeJson(JsonSerializeFormat & handler);
 
@@ -126,11 +139,8 @@ public:
 		h & haveSpellBook;
 		h & sex;
 		h & special;
-		h & name;
-		h & biography;
-		h & specName;
-		h & specDescr;
-		h & specTooltip;
+		h & nameTextID;
+		h & biographyTextID;
 		h & iconSpecSmall;
 		h & iconSpecLarge;
 		h & portraitSmall;
@@ -146,6 +156,13 @@ std::vector<std::shared_ptr<Bonus>> SpecialtyBonusToBonuses(const SSpecialtyBonu
 
 class DLL_LINKAGE CHeroClass : public HeroClass
 {
+	friend class CHeroClassHandler;
+	HeroClassID id; // use getId instead
+	std::string modScope;
+	std::string identifier; // use getJsonKey instead
+	std::string nameTextID;
+
+	const std::string & getName() const override;
 public:
 	enum EClassAffinity
 	{
@@ -153,11 +170,8 @@ public:
 		MAGIC
 	};
 
-	std::string identifier;
-	std::string name; // translatable
 	//double aggression; // not used in vcmi.
 	TFaction faction;
-	HeroClassID id;
 	ui8 affinity; // affinity, using EClassAffinity enum
 
 	// default chance for hero of specific class to appear in tavern, if field "tavern" was not set
@@ -183,11 +197,13 @@ public:
 
 	int32_t getIndex() const override;
 	int32_t getIconIndex() const override;
-	const std::string & getName() const override;
 	const std::string & getJsonKey() const override;
 	HeroClassID getId() const override;
 	void registerIcons(const IconRegistar & cb) const override;
 
+	std::string getNameTranslated() const override;
+	std::string getNameTextID() const override;
+
 	bool isMagicHero() const;
 	SecondarySkill chooseSecSkill(const std::set<SecondarySkill> & possibles, CRandomGenerator & rand) const; //picks secondary skill out from given possibilities
 
@@ -196,8 +212,9 @@ public:
 
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
+		h & modScope;
 		h & identifier;
-		h & name;
+		h & nameTextID;
 		h & faction;
 		h & id;
 		h & defaultTavernChance;

+ 1 - 1
lib/HeroBonus.cpp

@@ -1606,7 +1606,7 @@ std::string Bonus::Description() const
 				str << VLC->skillh->getByIndex(sid)->getNameTranslated();
 				break;
 			case HERO_SPECIAL:
-				str << VLC->heroh->objects[sid]->name;
+				str << VLC->heroh->objects[sid]->getNameTranslated();
 				break;
 			default:
 				//todo: handle all possible sources

+ 1 - 1
lib/mapObjects/CBank.cpp

@@ -327,7 +327,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 				iw.text.addTxt(MetaString::ADVOB_TXT, 186);
 
 			iw.text.addReplacement(loot.buildList());
-			iw.text.addReplacement(hero->name);
+			iw.text.addReplacement(hero->getNameTranslated());
 			cb->showInfoDialog(&iw);
 			cb->giveCreatures(this, hero, ourArmy, false);
 		}

+ 46 - 24
lib/mapObjects/CGHeroInstance.cpp

@@ -65,7 +65,7 @@ static int lowestSpeed(const CGHeroInstance * chi)
 			return chi->commander->valOfBonuses(selectorSTACKS_SPEED, keySTACKS_SPEED);
 		}
 
-		logGlobal->error("Hero %d (%s) has no army!", chi->id.getNum(), chi->name);
+		logGlobal->error("Hero %d (%s) has no army!", chi->id.getNum(), chi->getNameTranslated());
 		return 20;
 	}
 
@@ -309,8 +309,6 @@ void CGHeroInstance::initHero(CRandomGenerator & rand)
 	}
 	if(secSkills.size() == 1 && secSkills[0] == std::pair<SecondarySkill,ui8>(SecondarySkill::DEFAULT, -1)) //set secondary skills to default
 		secSkills = type->secSkillsInit;
-	if (!name.length())
-		name = type->name;
 
 	if (sex == 0xFF)//sex is default
 		sex = type->sex;
@@ -373,7 +371,7 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor * dst)
 
 		if(creature == nullptr)
 		{
-			logGlobal->error("Hero %s has invalid creature with id %d in initial army", name, stack.creature.toEnum());
+			logGlobal->error("Hero %s has invalid creature with id %d in initial army", getNameTranslated(), stack.creature.toEnum());
 			continue;
 		}
 
@@ -394,11 +392,11 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor * dst)
 				if(!getArt(slot))
 					putArtifact(slot, CArtifactInstance::createNewArtifactInstance(aid));
 				else
-					logGlobal->warn("Hero %s already has artifact at %d, omitting giving artifact %d", name, slot.toEnum(), aid.toEnum());
+					logGlobal->warn("Hero %s already has artifact at %d, omitting giving artifact %d", getNameTranslated(), slot.toEnum(), aid.toEnum());
 			}
 			else
 			{
-				logGlobal->error("Hero %s has invalid war machine in initial army", name);
+				logGlobal->error("Hero %s has invalid war machine in initial army", getNameTranslated());
 			}
 		}
 		else
@@ -469,21 +467,14 @@ std::string CGHeroInstance::getObjectName() const
 	if(ID != Obj::PRISON)
 	{
 		std::string hoverName = VLC->generaltexth->allTexts[15];
-		boost::algorithm::replace_first(hoverName,"%s",name);
-		boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name);
+		boost::algorithm::replace_first(hoverName,"%s",getNameTranslated());
+		boost::algorithm::replace_first(hoverName,"%s", type->heroClass->getNameTranslated());
 		return hoverName;
 	}
 	else
 		return CGObjectInstance::getObjectName();
 }
 
-const std::string & CGHeroInstance::getBiography() const
-{
-	if (biography.length())
-		return biography;
-	return type->biography;
-}
-
 ui8 CGHeroInstance::maxlevelsToMagicSchool() const
 {
 	return type->heroClass->isMagicHero() ? 3 : 4;
@@ -536,14 +527,13 @@ void CGHeroInstance::initObj(CRandomGenerator & rand)
 		for(std::shared_ptr<Bonus> b : sb.bonuses)
 			addNewBonus(b);
 	for(SSpecialtyInfo & spec : type->specDeprecated)
-		for(std::shared_ptr<Bonus> b : SpecialtyInfoToBonuses(spec, type->ID.getNum()))
+		for(std::shared_ptr<Bonus> b : SpecialtyInfoToBonuses(spec, type->getIndex()))
 			addNewBonus(b);
 
 	//initialize bonuses
 	recreateSecondarySkillsBonuses();
 
 	mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one
-	type->name = name;
 }
 
 void CGHeroInstance::recreateSecondarySkillsBonuses()
@@ -686,7 +676,7 @@ PlayerColor CGHeroInstance::getCasterOwner() const
 void CGHeroInstance::getCasterName(MetaString & text) const
 {
 	//FIXME: use local name, MetaString need access to gamestate as hero name is part of map object
-	text.addReplacement(name);
+	text.addReplacement(getNameTranslated());
 }
 
 void CGHeroInstance::getCastDescription(const spells::Spell * spell, const std::vector<const battle::Unit *> & attacked, MetaString & text) const
@@ -995,7 +985,39 @@ void CGHeroInstance::initExp(CRandomGenerator & rand)
 
 std::string CGHeroInstance::nodeName() const
 {
-	return "Hero " + name;
+	return "Hero " + getNameTextID();
+}
+
+std::string CGHeroInstance::getNameTranslated() const
+{
+	return VLC->generaltexth->translate(getNameTextID());
+}
+
+std::string CGHeroInstance::getNameTextID() const
+{
+	if (!nameCustom.empty())
+		return nameCustom;
+	if (type)
+		return type->getNameTextID();
+
+	assert(0);
+	return "";
+}
+
+std::string CGHeroInstance::getBiographyTranslated() const
+{
+	return VLC->generaltexth->translate(getBiographyTextID());
+}
+
+std::string CGHeroInstance::getBiographyTextID() const
+{
+	if (!biographyCustom.empty())
+		return biographyCustom;
+	if (type)
+		return type->getBiographyTextID();
+
+	assert(0);
+	return "";
 }
 
 void CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance *art)
@@ -1230,7 +1252,7 @@ PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill(CRandomGenerator & r
 	if(primarySkill >= GameConstants::PRIMARY_SKILLS)
 	{
 		primarySkill = rand.nextInt(GameConstants::PRIMARY_SKILLS - 1);
-		logGlobal->error("Wrong values in primarySkill%sLevel for hero class %s", isLowLevelHero ? "Low" : "High", type->heroClass->identifier);
+		logGlobal->error("Wrong values in primarySkill%sLevel for hero class %s", isLowLevelHero ? "Low" : "High", type->heroClass->getNameTranslated());
 		randomValue = 100 / GameConstants::PRIMARY_SKILLS;
 	}
 	logGlobal->trace("The hero gets the primary skill %d with a probability of %d %%.", primarySkill, randomValue);
@@ -1378,11 +1400,11 @@ std::string CGHeroInstance::getHeroTypeName() const
 	{
 		if(type)
 		{
-			return type->identifier;
+			return type->getJsonKey();
 		}
 		else
 		{
-			return VLC->heroh->objects[subID]->identifier;
+			return VLC->heroh->objects[subID]->getJsonKey();
 		}
 	}
 	return "";
@@ -1421,7 +1443,7 @@ void CGHeroInstance::updateFrom(const JsonNode & data)
 
 void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 {
-	handler.serializeString("biography", biography);
+	handler.serializeString("biography", biographyCustom);
 	handler.serializeInt("experience", exp, 0);
 
 	if(!handler.saving && exp != 0xffffffff) //do not gain levels if experience is not initialized
@@ -1432,7 +1454,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 		}
 	}
 
-	handler.serializeString("name", name);
+	handler.serializeString("name", nameCustom);
 	handler.serializeBool<ui8>("female", sex, 1, 0, 0xFF);
 
 	{

+ 14 - 5
lib/mapObjects/CGHeroInstance.h

@@ -45,11 +45,13 @@ class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator,
 	// We serialize heroes into JSON for crossover
 	friend class CCampaignState;
 	friend class CMapLoaderH3M;
+	friend class CMapFormatJson;
 
 private:
 	std::set<SpellID> spells; //known spells (spell IDs)
 
 public:
+
 	//////////////////////////////////////////////////////////////////////////
 
 	ui8 moveDir; //format:	123
@@ -62,13 +64,15 @@ public:
 	ConstTransitivePtr<CHero> type;
 	TExpType exp; //experience points
 	ui32 level; //current level of hero
-	std::string name; //may be custom
-	std::string biography; //if custom
 	si32 portrait; //may be custom
 	si32 mana; // remaining spell points
 	std::vector<std::pair<SecondarySkill,ui8> > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities
 	ui32 movement; //remaining movement points
 	ui8 sex;
+
+	std::string nameCustom;
+	std::string biographyCustom;
+
 	bool inTownGarrison; // if hero is in town garrison
 	ConstTransitivePtr<CGTownInstance> visitedTown; //set if hero is visiting town or in the town garrison
 	ConstTransitivePtr<CCommanderInstance> commander;
@@ -145,6 +149,12 @@ public:
 
 	//////////////////////////////////////////////////////////////////////////
 
+	std::string getNameTranslated() const;
+	std::string getNameTextID() const;
+
+	std::string getBiographyTranslated() const;
+	std::string getBiographyTextID() const;
+
 	bool hasSpellbook() const;
 	int maxSpellLevel() const;
 	void addSpellToSpellbook(SpellID spell);
@@ -153,7 +163,6 @@ public:
 	void removeSpellbook();
 	const std::set<SpellID> & getSpellsInSpellbook() const;
 	EAlignment::EAlignment getAlignment() const;
-	const std::string &getBiography() const;
 	bool needsLastStack()const override;
 
 	ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from, const TurnInfo * ti) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling
@@ -299,8 +308,8 @@ public:
 		h & static_cast<CArtifactSet&>(*this);
 		h & exp;
 		h & level;
-		h & name;
-		h & biography;
+		h & nameCustom;
+		h & biographyCustom;
 		h & portrait;
 		h & mana;
 		h & secSkills;

+ 7 - 7
lib/mapObjects/CGPandoraBox.cpp

@@ -102,7 +102,7 @@ void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const
 		TExpType expVal = h->calculateXp(gainedExp);
 		//getText(iw,afterBattle,175,h); //wtf?
 		iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something
-		iw.text.addReplacement(h->name);
+		iw.text.addReplacement(h->getNameTranslated());
 
 		if(expVal)
 			iw.components.push_back(Component(Component::EXPERIENCE,0,static_cast<si32>(expVal),0));
@@ -183,7 +183,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
 				{
 					iw.text.addTxt(MetaString::ADVOB_TXT, 184); //%s learns a spell
 				}
-				iw.text.addReplacement(h->name);
+				iw.text.addReplacement(h->getNameTranslated());
 				cb->changeSpells(h, true, spellsToGive);
 				cb->showInfoDialog(&iw);
 			}
@@ -249,7 +249,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
 	iw.components.clear();
 	// 	getText(iw,afterBattle,183,h);
 	iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure
-	iw.text.addReplacement(h->name);
+	iw.text.addReplacement(h->getNameTranslated());
 	for(auto & elem : artifacts)
 	{
 		iw.components.push_back(Component(Component::ARTIFACT,elem,0,0));
@@ -258,7 +258,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
 			cb->showInfoDialog(&iw);
 			iw.components.clear();
 			iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - once more?
-			iw.text.addReplacement(h->name);
+			iw.text.addReplacement(h->getNameTranslated());
 		}
 	}
 	if(iw.components.size())
@@ -290,7 +290,7 @@ void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const
 			iw.text.addTxt(MetaString::ADVOB_TXT, 186);
 
 		iw.text.addReplacement(loot.buildList());
-		iw.text.addReplacement(h->name);
+		iw.text.addReplacement(h->getNameTranslated());
 
 		cb->showInfoDialog(&iw);
 		cb->giveCreatures(this, h, creatures, false);
@@ -307,7 +307,7 @@ void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const C
 	if(afterBattle || !message.size())
 	{
 		iw.text.addTxt(MetaString::ADVOB_TXT,text);//%s has lost treasure.
-		iw.text.addReplacement(h->name);
+		iw.text.addReplacement(h->getNameTranslated());
 	}
 	else
 	{
@@ -323,7 +323,7 @@ void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int nega
 	if(afterBattle || !message.size())
 	{
 		iw.text.addTxt(MetaString::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases
-		iw.text.addReplacement(h->name);
+		iw.text.addReplacement(h->getNameTranslated());
 	}
 	else
 	{

+ 4 - 4
lib/mapObjects/CGTownInstance.cpp

@@ -720,7 +720,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
 	}
 	else
 	{
-		logGlobal->error("%s visits allied town of %s from different pos?", h->name, name);
+		logGlobal->error("%s visits allied town of %s from different pos?", h->getNameTranslated(), name);
 	}
 }
 
@@ -730,10 +730,10 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
 	if(visitingHero == h)
 	{
 		cb->stopHeroVisitCastle(this, h);
-		logGlobal->trace("%s correctly left town %s", h->name, name);
+		logGlobal->trace("%s correctly left town %s", h->getNameTranslated(), name);
 	}
 	else
-		logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->name, name);
+		logGlobal->warn("Warning, %s tries to leave the town %s but hero is not inside.", h->getNameTranslated(), name);
 }
 
 std::string CGTownInstance::getObjectName() const
@@ -1451,7 +1451,7 @@ void CGTownInstance::addHeroToStructureVisitors(const CGHeroInstance *h, si64 st
 	else
 	{
 		//should never ever happen
-		logGlobal->error("Cannot add hero %s to visitors of structure # %d", h->name, structureInstanceID);
+		logGlobal->error("Cannot add hero %s to visitors of structure # %d", h->getNameTranslated(), structureInstanceID);
 		throw std::runtime_error("internal error");
 	}
 }

+ 1 - 1
lib/mapObjects/CObjectHandler.cpp

@@ -494,7 +494,7 @@ void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visit
 		if(visitor)
 		{
 			out.addTxt(MetaString::GENERAL_TXT, 134);
-			out.addReplacement(visitor->name);
+			out.addReplacement(visitor->getNameTranslated());
 		}
 		else
 			out.addTxt(MetaString::ADVOB_TXT, 189);

+ 5 - 5
lib/mapObjects/CQuest.cpp

@@ -170,7 +170,7 @@ bool CQuest::checkQuest(const CGHeroInstance * h) const
 			}
 			return true;
 		case MISSION_HERO:
-			if(m13489val == h->type->ID.getNum())
+			if(m13489val == h->type->getIndex())
 				return true;
 			return false;
 		case MISSION_PLAYER:
@@ -230,7 +230,7 @@ void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components
 			//FIXME: portrait may not match hero, if custom portrait was set in map editor
 			components.push_back(Component(Component::HERO_PORTRAIT, VLC->heroh->objects[m13489val]->imageIndex, 0, 0));
 			if(!isCustom)
-				iwText.addReplacement(VLC->heroh->objects[m13489val]->name);
+				iwText.addReplacement(VLC->heroh->objects[m13489val]->getNameTextID());
 			break;
 		case MISSION_KILL_CREATURE:
 			{
@@ -369,7 +369,7 @@ void CQuest::getRolloverText(MetaString &ms, bool onHover) const
 			}
 			break;
 		case MISSION_HERO:
-			ms.addReplacement(VLC->heroh->objects[m13489val]->name);
+			ms.addReplacement(VLC->heroh->objects[m13489val]->getNameTextID());
 			break;
 		case MISSION_PLAYER:
 			ms.addReplacement(VLC->generaltexth->colors[m13489val]);
@@ -452,7 +452,7 @@ void CQuest::getCompletionText(MetaString &iwText, std::vector<Component> &compo
 			break;
 		case MISSION_HERO:
 			if (!isCustomComplete)
-				iwText.addReplacement(VLC->heroh->objects[m13489val]->name);
+				iwText.addReplacement(VLC->heroh->objects[m13489val]->getNameTextID());
 			break;
 		case MISSION_PLAYER:
 			if (!isCustomComplete)
@@ -567,7 +567,7 @@ void CGSeerHut::setObjToKill()
 	}
 	else if(quest->missionType == CQuest::MISSION_KILL_HERO)
 	{
-		quest->heroName = getHeroToKill(false)->name;
+		quest->heroName = getHeroToKill(false)->getNameTranslated();
 		quest->heroPortrait = getHeroToKill(false)->portrait;
 	}
 }

+ 1 - 1
lib/mapObjects/CommonConstructors.cpp

@@ -123,7 +123,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std
 
 	auto heroTest = [&](const HeroTypeID & id)
 	{
-		return hero->type->ID == id;
+		return hero->type->getId() == id;
 	};
 
 	if(filters.count(templ->stringID))

+ 2 - 2
lib/mapObjects/MiscObjects.cpp

@@ -596,7 +596,7 @@ void CGCreature::giveReward(const CGHeroInstance * h) const
 	if(iw.components.size())
 	{
 		iw.text.addTxt(MetaString::ADVOB_TXT, 183); // % has found treasure
-		iw.text.addReplacement(h->name);
+		iw.text.addReplacement(h->getNameTranslated());
 		cb->showInfoDialog(&iw);
 	}
 }
@@ -1347,7 +1347,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const
 					else //fix for mod artifacts with no event text
 					{
 						iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure
-						iw.text.addReplacement(h->name);
+						iw.text.addReplacement(h->getNameTranslated());
 					}
 				}
 			}

+ 2 - 2
lib/mapping/CMap.cpp

@@ -568,7 +568,7 @@ void CMap::checkForObjectives()
 							boost::algorithm::replace_first(event.onFulfill, "%s", town->name);
 						const CGHeroInstance *hero = dynamic_cast<const CGHeroInstance*>(cond.object);
 						if (hero)
-							boost::algorithm::replace_first(event.onFulfill, "%s", hero->name);
+							boost::algorithm::replace_first(event.onFulfill, "%s", hero->getNameTranslated());
 					}
 					break;
 
@@ -580,7 +580,7 @@ void CMap::checkForObjectives()
 					{
 						const CGHeroInstance *hero = dynamic_cast<const CGHeroInstance*>(cond.object);
 						if (hero)
-							boost::algorithm::replace_first(event.onFulfill, "%s", hero->name);
+							boost::algorithm::replace_first(event.onFulfill, "%s", hero->getNameTranslated());
 					}
 					break;
 				case EventCondition::TRANSPORT:

+ 8 - 8
lib/mapping/MapFormatH3M.cpp

@@ -787,7 +787,7 @@ void CMapLoaderH3M::readPredefinedHeroes()
 				bool hasCustomBio = reader.readBool();
 				if(hasCustomBio)
 				{
-					hero->biography = reader.readString();
+					hero->biographyCustom = reader.readString();
 				}
 
 				// 0xFF is default, 00 male, 01 female
@@ -825,7 +825,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
 	{
 		if(hero->artifactsWorn.size() ||  hero->artifactsInBackpack.size())
 		{
-			logGlobal->warn("Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->name, hero->pos.toString());
+			logGlobal->warn("Hero %s at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getNameTranslated(), hero->pos.toString());
 			hero->artifactsInBackpack.clear();
 			while(hero->artifactsWorn.size())
 				hero->eraseArtSlot(hero->artifactsWorn.begin()->first);
@@ -1436,7 +1436,7 @@ void CMapLoaderH3M::readObjects()
 				}
 				else
 				{
-					logGlobal->info("Hero placeholder: %s at %s", VLC->heroh->objects[htid]->name, objPos.toString());
+					logGlobal->info("Hero placeholder: %s at %s", VLC->heroh->objects[htid]->getNameTranslated(), objPos.toString());
 					hp->power = 0;
 				}
 
@@ -1592,7 +1592,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i
 	{
 		if(elem.heroId == nhi->subID)
 		{
-			nhi->name = elem.name;
+			nhi->nameCustom = elem.name;
 			nhi->portrait = elem.portrait;
 			break;
 		}
@@ -1601,7 +1601,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i
 	bool hasName = reader.readBool();
 	if(hasName)
 	{
-		nhi->name = reader.readString();
+		nhi->nameCustom = reader.readString();
 	}
 	if(map->version > EMapFormat::AB)
 	{
@@ -1666,7 +1666,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i
 		bool hasCustomBiography = reader.readBool();
 		if(hasCustomBiography)
 		{
-			nhi->biography = reader.readString();
+			nhi->biographyCustom = reader.readString();
 		}
 		nhi->sex = reader.readUInt8();
 
@@ -1688,7 +1688,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i
 		if(nhi->spells.size())
 		{
 			nhi->clear();
-			logGlobal->warn("Hero %s subID=%d has spells set twice (in map properties and on adventure map instance). Using the latter set...", nhi->name, nhi->subID);
+			logGlobal->warn("Hero %s subID=%d has spells set twice (in map properties and on adventure map instance). Using the latter set...", nhi->getNameTranslated(), nhi->subID);
 		}
 
 		if(hasCustomSpells)
@@ -1721,7 +1721,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(ObjectInstanceID idToBeGiven, const i
 								.And(Selector::sourceType()(Bonus::HERO_BASE_SKILL)), nullptr);
 			if(ps->size())
 			{
-				logGlobal->warn("Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", nhi->name, nhi->subID);
+				logGlobal->warn("Hero %s subID=%d has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", nhi->getNameTranslated(), nhi->subID);
 				for(auto b : *ps)
 					nhi->removeBonus(b);
 			}

+ 3 - 3
lib/mapping/MapFormatJson.cpp

@@ -524,18 +524,18 @@ void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler)
 					if(hero)
 					{
 						auto heroData = handler.enterStruct(hero->instanceName);
-						heroData->serializeString("name", hero->name);
+						heroData->serializeString("name", hero->nameCustom);
 
 						if(hero->ID == Obj::HERO)
 						{
 							std::string temp;
 							if(hero->type)
 							{
-								temp = hero->type->identifier;
+								temp = hero->type->getJsonKey();
 							}
 							else
 							{
-								temp = VLC->heroh->objects[hero->subID]->identifier;
+								temp = VLC->heroh->objects[hero->subID]->getJsonKey();
 							}
 							handler.serializeString("type", temp);
 						}

+ 2 - 2
lib/serializer/CSerializer.cpp

@@ -34,9 +34,9 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib)
 	registerVectoredType<CGObjectInstance, ObjectInstanceID>(&gs->map->objects,
 		[](const CGObjectInstance &obj){ return obj.id; });
 	registerVectoredType<CHero, HeroTypeID>(&lib->heroh->objects,
-		[](const CHero &h){ return h.ID; });
+		[](const CHero &h){ return h.getId(); });
 	registerVectoredType<CGHeroInstance, HeroTypeID>(&gs->map->allHeroes,
-		[](const CGHeroInstance &h){ return h.type->ID; });
+		[](const CGHeroInstance &h){ return h.type->getId(); });
 	registerVectoredType<CCreature, CreatureID>(&lib->creh->objects,
 		[](const CCreature &cre){ return cre.idNumber; });
 	registerVectoredType<CArtifact, ArtifactID>(&lib->arth->objects,

+ 4 - 4
lib/spells/AdventureSpellMechanics.cpp

@@ -147,7 +147,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
 		InfoWindow iw;
 		iw.player = parameters.caster->tempOwner;
 		iw.text.addTxt(MetaString::GENERAL_TXT, 333);//%s is already in boat
-		iw.text.addReplacement(parameters.caster->name);
+		iw.text.addReplacement(parameters.caster->getNameTranslated());
 		env->apply(&iw);
 		return ESpellCastResult::CANCEL;
 	}
@@ -170,7 +170,7 @@ ESpellCastResult SummonBoatMechanics::applyAdventureEffects(SpellCastEnvironment
 		InfoWindow iw;
 		iw.player = parameters.caster->tempOwner;
 		iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed.
-		iw.text.addReplacement(parameters.caster->name);
+		iw.text.addReplacement(parameters.caster->getNameTranslated());
 		env->apply(&iw);
 		return ESpellCastResult::OK;
 	}
@@ -236,7 +236,7 @@ ESpellCastResult ScuttleBoatMechanics::applyAdventureEffects(SpellCastEnvironmen
 		InfoWindow iw;
 		iw.player = parameters.caster->tempOwner;
 		iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed
-		iw.text.addReplacement(parameters.caster->name);
+		iw.text.addReplacement(parameters.caster->getNameTranslated());
 		env->apply(&iw);
 		return ESpellCastResult::OK;
 	}
@@ -307,7 +307,7 @@ ESpellCastResult DimensionDoorMechanics::applyAdventureEffects(SpellCastEnvironm
 		InfoWindow iw;
 		iw.player = parameters.caster->tempOwner;
 		iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today.
-		iw.text.addReplacement(parameters.caster->name);
+		iw.text.addReplacement(parameters.caster->getNameTranslated());
 		env->apply(&iw);
 		return ESpellCastResult::CANCEL;
 	}

+ 1 - 1
lib/spells/effects/Summon.cpp

@@ -80,7 +80,7 @@ bool Summon::applicable(Problem & problem, const Mechanics * m) const
 			auto caster = dynamic_cast<const CGHeroInstance *>(m->caster);
 			if(caster)
 			{
-				text.addReplacement(caster->name);
+				text.addReplacement(caster->getNameTranslated());
 
 				text.addReplacement(MetaString::CRE_PL_NAMES, elemental->creatureIndex());
 

+ 7 - 11
mapeditor/inspector/inspector.cpp

@@ -131,9 +131,7 @@ void Initializer::initialize(CGHeroInstance * o)
 	if(!o->type)
 		o->type = VLC->heroh->objects.at(o->subID);
 	
-	o->name = o->type->getName();
 	o->sex = o->type->sex;
-	o->biography = o->type->biography;
 	o->portrait = o->type->imageIndex;
 	o->randomizeArmy(o->type->heroClass->faction);
 }
@@ -237,15 +235,15 @@ void Inspector::updateProperties(CGHeroInstance * o)
 	
 	addProperty("Owner", o->tempOwner, o->ID == Obj::PRISON); //field is not editable for prison
 	addProperty<int>("Experience", o->exp, false);
-	addProperty("Hero class", o->type->heroClass->getName(), true);
+	addProperty("Hero class", o->type->heroClass->getNameTranslated(), true);
 	
 	{ //Sex
 		auto * delegate = new InspectorDelegate;
 		delegate->options << "MALE" << "FEMALE";
 		addProperty<std::string>("Sex", (o->sex ? "FEMALE" : "MALE"), delegate , false);
 	}
-	addProperty("Name", o->name, false);
-	addProperty("Biography", o->biography, new MessageDelegate, false);
+	addProperty("Name", o->nameCustom, false);
+	addProperty("Biography", o->biographyCustom, new MessageDelegate, false);
 	addProperty("Portrait", o->portrait, false);
 	
 	{ //Hero type
@@ -255,10 +253,10 @@ void Inspector::updateProperties(CGHeroInstance * o)
 			if(map->allowedHeroes.at(i))
 			{
 				if(o->ID == Obj::PRISON || (o->type && VLC->heroh->objects[i]->heroClass->getIndex() == o->type->heroClass->getIndex()))
-					delegate->options << QObject::tr(VLC->heroh->objects[i]->getName().c_str());
+					delegate->options << QObject::tr(VLC->heroh->objects[i]->getNameTranslated().c_str());
 			}
 		}
-		addProperty("Hero type", o->type->getName(), delegate, false);
+		addProperty("Hero type", o->type->getNameTranslated(), delegate, false);
 	}
 }
 
@@ -554,7 +552,7 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
 		o->sex = value.toString() == "MALE" ? 0 : 1;
 	
 	if(key == "Name")
-		o->name = value.toString().toStdString();
+		o->nameCustom = value.toString().toStdString();
 	
 	if(key == "Experience")
 		o->exp = value.toInt();
@@ -563,12 +561,10 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
 	{
 		for(auto t : VLC->heroh->objects)
 		{
-			if(t->getName() == value.toString().toStdString())
+			if(t->getNameTranslated() == value.toString().toStdString())
 				o->type = t.get();
 		}
-		o->name = o->type->getName();
 		o->sex = o->type->sex;
-		o->biography = o->type->biography;
 		o->portrait = o->type->imageIndex;
 		o->randomizeArmy(o->type->heroClass->faction);
 		updateProperties(); //updating other properties after change

+ 1 - 1
mapeditor/inspector/questwidget.cpp

@@ -95,7 +95,7 @@ void QuestWidget::obtainData()
 		case CQuest::Emission::MISSION_HERO:
 			activeId = true;
 			for(int i = 0; i < map.allowedHeroes.size(); ++i)
-				ui->targetId->addItem(QString::fromStdString(VLC->heroh->objects.at(i)->getName()));
+				ui->targetId->addItem(QString::fromStdString(VLC->heroh->objects.at(i)->getNameTranslated()));
 			ui->targetId->setCurrentIndex(seerhut.quest->m13489val);
 			break;
 		case CQuest::Emission::MISSION_PLAYER:

+ 1 - 5
mapeditor/mapcontroller.cpp

@@ -131,7 +131,7 @@ void MapController::repairMap()
 			if(obj->ID == Obj::HERO)
 			{
 				nih->typeName = "hero";
-				nih->subTypeName = type->heroClass->identifier;
+				nih->subTypeName = type->heroClass->getJsonKey();
 			}
 			if(obj->ID == Obj::PRISON)
 			{
@@ -140,10 +140,6 @@ void MapController::repairMap()
 			}
 			
 			nih->type = type;
-			if(nih->name.empty())
-				nih->name = nih->type->name;
-			if(nih->biography.empty())
-				nih->biography = nih->type->biography;
 			
 			if(nih->ID == Obj::HERO) //not prison
 				nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();

+ 1 - 1
mapeditor/mapsettings.cpp

@@ -58,7 +58,7 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) :
 	}
 	for(int i = 0; i < controller.map()->allowedHeroes.size(); ++i)
 	{
-		auto * item = new QListWidgetItem(QString::fromStdString(VLC->heroh->objects[i]->getName()));
+		auto * item = new QListWidgetItem(QString::fromStdString(VLC->heroh->objects[i]->getNameTranslated()));
 		item->setData(Qt::UserRole, QVariant::fromValue(i));
 		item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
 		item->setCheckState(controller.map()->allowedHeroes[i] ? Qt::Checked : Qt::Unchecked);

+ 1 - 1
mapeditor/validator.cpp

@@ -119,7 +119,7 @@ std::list<Validator::Issue> Validator::validate(const CMap * map)
 				if(ins->type)
 				{
 					if(!map->allowedHeroes[ins->type->getId().getNum()])
-						issues.emplace_back(QString("Hero %1 is prohibited by map settings").arg(ins->type->getName().c_str()), false);
+						issues.emplace_back(QString("Hero %1 is prohibited by map settings").arg(ins->type->getNameTranslated().c_str()), false);
 				}
 				else
 					issues.emplace_back(QString("Hero %1 has an empty type and must be removed").arg(ins->instanceName.c_str()), true);

+ 8 - 8
server/CGameHandler.cpp

@@ -423,7 +423,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero)
 	}
 
 	// give primary skill
-	logGlobal->trace("%s got level %d", hero->name, hero->level);
+	logGlobal->trace("%s got level %d", hero->getNameTranslated(), hero->level);
 	auto primarySkill = hero->nextPrimarySkill(getRandomGenerator());
 
 	SetPrimSkill sps;
@@ -633,7 +633,7 @@ void CGameHandler::changePrimSkill(const CGHeroInstance * hero, PrimarySkill::Pr
 				InfoWindow iw;
 				iw.player = hero->tempOwner;
 				iw.text.addTxt(MetaString::GENERAL_TXT, 1); //can gain no more XP
-				iw.text.addReplacement(hero->name);
+				iw.text.addReplacement(hero->getNameTextID());
 				sendAndApply(&iw);
 			}
 		}
@@ -856,7 +856,7 @@ void CGameHandler::endBattle(int3 tile, const CGHeroInstance * heroAttacker, con
 		InfoWindow iw;
 		iw.player = finishingBattle->winnerHero->tempOwner;
 		iw.text.addTxt(MetaString::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s
-		iw.text.addReplacement(finishingBattle->winnerHero->name);
+		iw.text.addReplacement(finishingBattle->winnerHero->getNameTextID());
 
 		std::ostringstream names;
 		for (int i = 0; i < cs.spells.size(); i++)
@@ -2392,7 +2392,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 	auto doMove = [&](TryMoveHero::EResult result, EGuardLook lookForGuards,
 								EVisitDest visitDest, ELEaveTile leavingTile) -> bool
 	{
-		LOG_TRACE_PARAMS(logGlobal, "Hero %s starts movement from %s to %s", h->name % tmh.start.toString() % tmh.end.toString());
+		LOG_TRACE_PARAMS(logGlobal, "Hero %s starts movement from %s to %s", h->getNameTranslated() % tmh.start.toString() % tmh.end.toString());
 
 		auto moveQuery = std::make_shared<CHeroMovementQuery>(this, tmh, h);
 		queries.addQuery(moveQuery);
@@ -2422,7 +2422,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, boo
 		}
 
 		queries.popIfTop(moveQuery);
-		logGlobal->trace("Hero %s ends movement", h->name);
+		logGlobal->trace("Hero %s ends movement", h->getNameTranslated());
 		return result != TryMoveHero::FAILED;
 	};
 
@@ -2822,7 +2822,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 		iw.components.push_back(Component(Component::SEC_SKILL, 18, ScholarSkillLevel, 0));
 
 		iw.text.addTxt(MetaString::GENERAL_TXT, 139);//"%s, who has studied magic extensively,
-		iw.text.addReplacement(h1->name);
+		iw.text.addReplacement(h1->getNameTextID());
 
 		if (!cs2.spells.empty())//if found new spell - apply
 		{
@@ -2840,7 +2840,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 				}
 			}
 			iw.text.addTxt(MetaString::GENERAL_TXT, 142);//from %s
-			iw.text.addReplacement(h2->name);
+			iw.text.addReplacement(h2->getNameTextID());
 			sendAndApply(&cs2);
 		}
 
@@ -2864,7 +2864,7 @@ void CGameHandler::useScholarSkill(ObjectInstanceID fromHero, ObjectInstanceID t
 					default:	iw.text << ", ";
 				}			}
 			iw.text.addTxt(MetaString::GENERAL_TXT, 148);//from %s
-			iw.text.addReplacement(h2->name);
+			iw.text.addReplacement(h2->getNameTextID());
 			sendAndApply(&cs1);
 		}
 		sendAndApply(&iw);

+ 1 - 1
server/CQuery.cpp

@@ -486,7 +486,7 @@ void CHeroMovementQuery::onExposure(QueryPtr topQuery)
 	if(visitDestAfterVictory && hero->tempOwner == players[0]) //hero still alive, so he won with the guard
 		//TODO what if there were H4-like escape? we should also check pos
 	{
-		logGlobal->trace("Hero %s after victory over guard finishes visit to %s", hero->name, tmh.end.toString());
+		logGlobal->trace("Hero %s after victory over guard finishes visit to %s", hero->getNameTranslated(), tmh.end.toString());
 		//finish movement
 		visitDestAfterVictory = false;
 		gh->visitObjectOnTile(*gh->getTile(hero->convertToVisitablePos(tmh.end)), hero);