소스 검색

Merge branch 'develop' into delete

Laserlicht 11 달 전
부모
커밋
0e5711f8bf
100개의 변경된 파일705개의 추가작업 그리고 433개의 파일을 삭제
  1. 6 6
      AI/Nullkiller/AIGateway.cpp
  2. 5 5
      AI/Nullkiller/AIUtility.cpp
  3. 1 1
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  4. 3 3
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  5. 2 2
      AI/Nullkiller/Pathfinding/AINodeStorage.cpp
  6. 3 3
      AI/VCAI/AIUtility.cpp
  7. 1 1
      AI/VCAI/ArmyManager.cpp
  8. 1 1
      AI/VCAI/Goals/CompleteQuest.cpp
  9. 1 1
      AI/VCAI/MapObjectsEvaluator.cpp
  10. 2 2
      AI/VCAI/Pathfinding/AINodeStorage.cpp
  11. 3 3
      AI/VCAI/VCAI.cpp
  12. 28 15
      Mods/vcmi/config/czech.json
  13. 3 3
      client/ArtifactsUIController.cpp
  14. 2 2
      client/CPlayerInterface.cpp
  15. 3 5
      client/HeroMovementController.cpp
  16. 3 3
      client/adventureMap/CMinimap.cpp
  17. 1 1
      client/adventureMap/MapAudioPlayer.cpp
  18. 9 4
      client/eventsSDL/InputSourceTouch.cpp
  19. 8 2
      client/eventsSDL/InputSourceTouch.h
  20. 21 2
      client/lobby/SelectionTab.cpp
  21. 3 0
      client/lobby/SelectionTab.h
  22. 12 12
      client/mapView/MapRenderer.cpp
  23. 2 2
      client/mapView/MapRendererContext.cpp
  24. 1 1
      client/mapView/mapHandler.cpp
  25. 12 0
      client/media/CSoundHandler.cpp
  26. 2 0
      client/media/CSoundHandler.h
  27. 34 2
      client/media/CVideoHandler.cpp
  28. 8 2
      client/media/CVideoHandler.h
  29. 2 0
      client/media/ISoundPlayer.h
  30. 7 0
      client/media/IVideoPlayer.h
  31. 1 1
      client/widgets/CArtifactsOfHeroBackpack.cpp
  32. 1 1
      client/widgets/CArtifactsOfHeroBase.cpp
  33. 1 1
      client/widgets/CArtifactsOfHeroMarket.cpp
  34. 3 3
      client/widgets/CComponentHolder.cpp
  35. 3 3
      client/widgets/CGarrisonInt.cpp
  36. 1 1
      client/widgets/MiscWidgets.cpp
  37. 42 3
      client/widgets/VideoWidget.cpp
  38. 5 0
      client/widgets/VideoWidget.h
  39. 1 1
      client/widgets/markets/CAltarArtifacts.cpp
  40. 2 1
      client/windows/CCastleInterface.cpp
  41. 6 6
      client/windows/CCreatureWindow.cpp
  42. 3 3
      client/windows/CExchangeWindow.cpp
  43. 1 1
      client/windows/CHeroBackpackWindow.cpp
  44. 3 2
      client/windows/CHeroOverview.cpp
  45. 2 1
      client/windows/CHeroOverview.h
  46. 2 2
      client/windows/CHeroWindow.cpp
  47. 1 1
      client/windows/CKingdomInterface.cpp
  48. 3 3
      client/windows/CMapOverview.cpp
  49. 1 1
      client/windows/CMarketWindow.cpp
  50. 10 8
      client/windows/CWindowWithArtifacts.cpp
  51. 1 1
      client/windows/CWindowWithArtifacts.h
  52. 2 2
      client/windows/GUIClasses.cpp
  53. 3 0
      clientapp/icons/vcmiclient.desktop
  54. 3 3
      config/objects/pyramid.json
  55. 12 12
      config/objects/shrine.json
  56. 13 0
      docs/translators/Translations.md
  57. 16 1
      launcher/modManager/chroniclesextractor.cpp
  58. 1 1
      launcher/translation/czech.ts
  59. 1 1
      launcher/vcmilauncher.desktop
  60. 7 7
      lib/CArtHandler.cpp
  61. 17 12
      lib/CArtifactInstance.cpp
  62. 13 2
      lib/CArtifactInstance.h
  63. 0 5
      lib/CCreatureHandler.cpp
  64. 0 2
      lib/CCreatureHandler.h
  65. 57 61
      lib/CCreatureSet.cpp
  66. 7 16
      lib/CCreatureSet.h
  67. 6 6
      lib/CGameInfoCallback.cpp
  68. 9 9
      lib/CStack.cpp
  69. 3 3
      lib/CStack.h
  70. 5 5
      lib/IGameCallback.cpp
  71. 1 1
      lib/IGameCallback.h
  72. 13 3
      lib/StartInfo.h
  73. 1 1
      lib/battle/BattleInfo.cpp
  74. 6 6
      lib/bonuses/Limiters.cpp
  75. 2 2
      lib/bonuses/Limiters.h
  76. 12 12
      lib/gameState/CGameState.cpp
  77. 5 5
      lib/gameState/CGameStateCampaign.cpp
  78. 1 1
      lib/gameState/GameStatistics.cpp
  79. 3 3
      lib/gameState/InfoAboutArmy.cpp
  80. 3 3
      lib/json/JsonRandom.cpp
  81. 1 1
      lib/mapObjectConstructors/CommonConstructors.cpp
  82. 1 1
      lib/mapObjectConstructors/DwellingInstanceConstructor.cpp
  83. 4 4
      lib/mapObjects/CBank.cpp
  84. 4 4
      lib/mapObjects/CGCreature.cpp
  85. 8 8
      lib/mapObjects/CGHeroInstance.cpp
  86. 5 2
      lib/mapObjects/CGHeroInstance.h
  87. 2 5
      lib/mapObjects/CGMarket.cpp
  88. 1 1
      lib/mapObjects/CGMarket.h
  89. 3 3
      lib/mapObjects/CGObjectInstance.cpp
  90. 7 9
      lib/mapObjects/CGTownInstance.cpp
  91. 4 12
      lib/mapObjects/CGTownInstance.h
  92. 1 1
      lib/mapObjects/CQuest.cpp
  93. 2 2
      lib/mapObjects/IObjectInterface.cpp
  94. 7 7
      lib/mapObjects/MiscObjects.cpp
  95. 6 6
      lib/mapping/CDrawRoadsOperation.cpp
  96. 69 26
      lib/mapping/CMap.cpp
  97. 1 1
      lib/mapping/CMap.h
  98. 59 11
      lib/mapping/CMapDefines.h
  99. 15 15
      lib/mapping/CMapOperation.cpp
  100. 1 1
      lib/mapping/MapEditUtils.cpp

+ 6 - 6
AI/Nullkiller/AIGateway.cpp

@@ -1055,7 +1055,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				//FIXME: why are the above possible to be null?
 
 				bool emptySlotFound = false;
-				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
+				for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
 				{
 					if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
 					{
@@ -1068,7 +1068,7 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				}
 				if(!emptySlotFound) //try to put that atifact in already occupied slot
 				{
-					for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
+					for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
 					{
 						auto otherSlot = target->getSlot(slot);
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
@@ -1079,8 +1079,8 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 							{
 								logAi->trace(
 									"Exchange artifacts %s <-> %s",
-									artifact->artType->getNameTranslated(),
-									otherSlot->artifact->artType->getNameTranslated());
+									artifact->getType()->getNameTranslated(),
+									otherSlot->artifact->getType()->getNameTranslated());
 
 								if(!otherSlot->artifact->canBePutAt(artHolder, location.slot, true))
 								{
@@ -1129,10 +1129,10 @@ void AIGateway::recruitCreatures(const CGDwelling * d, const CArmedInstance * re
 		{
 			for(auto stack : recruiter->Slots())
 			{
-				if(!stack.second->type)
+				if(!stack.second->getType())
 					continue;
 				
-				auto duplicatingSlot = recruiter->getSlotFor(stack.second->type);
+				auto duplicatingSlot = recruiter->getSlotFor(stack.second->getCreature());
 
 				if(duplicatingSlot != stack.first)
 				{

+ 5 - 5
AI/Nullkiller/AIUtility.cpp

@@ -193,7 +193,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 {
 	// TODO: Such information should be provided by pathfinder
 	// Tile must be free or with unoccupied boat
-	if(!t->blocked)
+	if(!t->blocked())
 	{
 		return true;
 	}
@@ -267,8 +267,8 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
 
 bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2)
 {
-	auto art1 = a1->artType;
-	auto art2 = a2->artType;
+	auto art1 = a1->getType();
+	auto art2 = a2->getType();
 
 	if(art1->getPrice() == art2->getPrice())
 		return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);
@@ -312,7 +312,7 @@ int getDuplicatingSlots(const CArmedInstance * army)
 
 	for(auto stack : army->Slots())
 	{
-		if(stack.second->type && army->getSlotFor(stack.second->type) != stack.first)
+		if(stack.second->getCreature() && army->getSlotFor(stack.second->getCreature()) != stack.first)
 			duplicatingSlots++;
 	}
 
@@ -387,7 +387,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	{
 		for(auto slot : h->Slots())
 		{
-			if(slot.second->type->hasUpgrades())
+			if(slot.second->getType()->hasUpgrades())
 				return true; //TODO: check price?
 		}
 		return false;

+ 1 - 1
AI/Nullkiller/Analyzers/ArmyManager.cpp

@@ -90,7 +90,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
 	{
 		for(auto & i : armyPtr->Slots())
 		{
-			auto cre = dynamic_cast<const CCreature*>(i.second->type);
+			auto cre = dynamic_cast<const CCreature*>(i.second->getType());
 			auto & slotInfp = creToPower[cre];
 
 			slotInfp.creature = cre;

+ 3 - 3
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -155,10 +155,10 @@ uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHero
 	for (auto c : creatures)
 	{
 		//Only if hero has slot for this creature in the army
-		auto ccre = dynamic_cast<const CCreature*>(c.data.type);
+		auto ccre = dynamic_cast<const CCreature*>(c.data.getType());
 		if (hero->getSlotFor(ccre).validSlot() || duplicatingSlots > 0)
 		{
-			result += (c.data.type->getAIValue() * c.data.count) * c.chance;
+			result += (c.data.getType()->getAIValue() * c.data.count) * c.chance;
 		}
 		/*else
 		{
@@ -290,7 +290,7 @@ uint64_t RewardEvaluator::getArmyReward(
 	case Obj::CREATURE_GENERATOR4:
 		return getDwellingArmyValue(ai->cb.get(), target, checkGold);
 	case Obj::ARTIFACT:
-		return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->artType);
+		return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->getType());
 	case Obj::HERO:
 		return  relations == PlayerRelations::ENEMIES
 			? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()

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

@@ -130,10 +130,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 				for(pos.y = 0; pos.y < sizes.y; ++pos.y)
 				{
 					const TerrainTile & tile = gs->map->getTile(pos);
-					if (!tile.terType->isPassable())
+					if (!tile.getTerrain()->isPassable())
 						continue;
 
-					if (tile.terType->isWater())
+					if (tile.isWater())
 					{
 						resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
 						if (useFlying)

+ 3 - 3
AI/VCAI/AIUtility.cpp

@@ -186,7 +186,7 @@ bool canBeEmbarkmentPoint(const TerrainTile * t, bool fromWater)
 {
 	// TODO: Such information should be provided by pathfinder
 	// Tile must be free or with unoccupied boat
-	if(!t->blocked)
+	if(!t->blocked())
 	{
 		return true;
 	}
@@ -247,8 +247,8 @@ bool compareArmyStrength(const CArmedInstance * a1, const CArmedInstance * a2)
 
 bool compareArtifacts(const CArtifactInstance * a1, const CArtifactInstance * a2)
 {
-	auto art1 = a1->artType;
-	auto art2 = a2->artType;
+	auto art1 = a1->getType();
+	auto art2 = a2->getType();
 
 	if(art1->getPrice() == art2->getPrice())
 		return art1->valOfBonuses(BonusType::PRIMARY_SKILL) > art2->valOfBonuses(BonusType::PRIMARY_SKILL);

+ 1 - 1
AI/VCAI/ArmyManager.cpp

@@ -36,7 +36,7 @@ std::vector<SlotInfo> ArmyManager::getSortedSlots(const CCreatureSet * target, c
 	{
 		for(auto & i : armyPtr->Slots())
 		{
-			auto cre = dynamic_cast<const CCreature*>(i.second->type);
+			auto cre = dynamic_cast<const CCreature*>(i.second->getType());
 			auto & slotInfp = creToPower[cre];
 
 			slotInfp.creature = cre;

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

@@ -162,7 +162,7 @@ TGoalVec CompleteQuest::missionArmy() const
 
 	for(auto creature : q.quest->mission.creatures)
 	{
-		solutions.push_back(sptr(GatherTroops(creature.type->getId(), creature.count)));
+		solutions.push_back(sptr(GatherTroops(creature.getId(), creature.count)));
 	}
 
 	return solutions;

+ 1 - 1
AI/VCAI/MapObjectsEvaluator.cpp

@@ -92,7 +92,7 @@ std::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance *
 	else if(obj->ID == Obj::ARTIFACT)
 	{
 		auto artifactObject = dynamic_cast<const CGArtifact *>(obj);
-		switch(artifactObject->storedArtifact->artType->aClass)
+		switch(artifactObject->storedArtifact->getType()->aClass)
 		{
 		case CArtifact::EartClass::ART_TREASURE:
 			return 2000;

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

@@ -46,10 +46,10 @@ void AINodeStorage::initialize(const PathfinderOptions & options, const CGameSta
 			for(pos.y=0; pos.y < sizes.y; ++pos.y)
 			{
 				const TerrainTile & tile = gs->map->getTile(pos);
-				if(!tile.terType->isPassable())
+				if(!tile.getTerrain()->isPassable())
 					continue;
 				
-				if(tile.terType->isWater())
+				if(tile.getTerrain()->isWater())
 				{
 					resetTile(pos, ELayer::SAIL, PathfinderUtil::evaluateAccessibility<ELayer::SAIL>(pos, tile, fow, player, gs));
 					if(useFlying)

+ 3 - 3
AI/VCAI/VCAI.cpp

@@ -1180,7 +1180,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 				//FIXME: why are the above possible to be null?
 
 				bool emptySlotFound = false;
-				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
+				for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
 				{
 					if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
 					{
@@ -1193,7 +1193,7 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 				}
 				if(!emptySlotFound) //try to put that atifact in already occupied slot
 				{
-					for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
+					for(auto slot : artifact->getType()->getPossibleSlots().at(target->bearerType()))
 					{
 						auto otherSlot = target->getSlot(slot);
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
@@ -2818,7 +2818,7 @@ bool shouldVisit(HeroPtr h, const CGObjectInstance * obj)
 	{
 		for(auto slot : h->Slots())
 		{
-			if(slot.second->type->hasUpgrades())
+			if(slot.second->getType()->hasUpgrades())
 				return true; //TODO: check price?
 		}
 		return false;

+ 28 - 15
Mods/vcmi/config/czech.json

@@ -28,6 +28,13 @@
 	"vcmi.adventureMap.movementPointsHeroInfo" 			 : "(Body pohybu: %REMAINING / %POINTS)",
 	"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Omlouváme se, přehrání tahu soupeře ještě není implementováno!",
 
+	"vcmi.bonusSource.artifact" : "Artefakt",
+	"vcmi.bonusSource.creature" : "Schopnost",
+	"vcmi.bonusSource.spell" : "Kouzlo",
+	"vcmi.bonusSource.hero" : "Hrdina",
+	"vcmi.bonusSource.commander" : "Velitel",
+	"vcmi.bonusSource.other" : "Ostatní",
+
 	"vcmi.capitalColors.0" : "Červený",
 	"vcmi.capitalColors.1" : "Modrý",
 	"vcmi.capitalColors.2" : "Hnědý",
@@ -36,25 +43,25 @@
 	"vcmi.capitalColors.5" : "Fialový",
 	"vcmi.capitalColors.6" : "Tyrkysový",
 	"vcmi.capitalColors.7" : "Růžový",
-	
+
 	"vcmi.heroOverview.startingArmy" : "Počáteční jednotky",
 	"vcmi.heroOverview.warMachine" : "Bojové stroje",
 	"vcmi.heroOverview.secondarySkills" : "Druhotné schopnosti",
 	"vcmi.heroOverview.spells" : "Kouzla",
-	
+
 	"vcmi.quickExchange.moveUnit" : "Přesunout jednotku",
 	"vcmi.quickExchange.moveAllUnits" : "Přesunout všechny jednotky",
 	"vcmi.quickExchange.swapAllUnits" : "Vyměnit armády",
 	"vcmi.quickExchange.moveAllArtifacts" : "Přesunout všechny artefakty",
 	"vcmi.quickExchange.swapAllArtifacts" : "Vyměnit artefakt",
-	
+
 	"vcmi.radialWheel.mergeSameUnit" : "Sloučit stejné jednotky",
 	"vcmi.radialWheel.fillSingleUnit" : "Vyplnit jednou jednotkou",
 	"vcmi.radialWheel.splitSingleUnit" : "Rozdělit jedinou jednotku",
 	"vcmi.radialWheel.splitUnitEqually" : "Rozdělit jednotky rovnoměrně",
 	"vcmi.radialWheel.moveUnit" : "Přesunout jednotky do jiného oddílu",
 	"vcmi.radialWheel.splitUnit" : "Rozdělit jednotku do jiné pozice",
-	
+
 	"vcmi.radialWheel.heroGetArmy" : "Získat armádu jiného hrdiny",
 	"vcmi.radialWheel.heroSwapArmy" : "Vyměnit armádu s jiným hrdinou",
 	"vcmi.radialWheel.heroExchange" : "Otevřít výměnu hrdinů",
@@ -66,7 +73,7 @@
 	"vcmi.radialWheel.moveUp" : "Posunout výše",
 	"vcmi.radialWheel.moveDown" : "Posunout níže",
 	"vcmi.radialWheel.moveBottom" : "Přesunout dolů",
-	
+
 	"vcmi.randomMap.description" : "Mapa vytvořená Generátorem náhodných map.\nŠablona: %s, rozměry: %dx%d, úroveň: %d, hráči: %d, AI hráči: %d, množství vody: %s, síla jednotek: %s, VCMI mapa",
 	"vcmi.randomMap.description.isHuman" : ", %s je lidský hráč",
 	"vcmi.randomMap.description.townChoice" : ", volba města pro %s je %s",
@@ -106,14 +113,20 @@
 	"vcmi.lobby.handicap.resource" : "Dává hráčům odpovídající zdroje navíc k běžným startovním zdrojům. Jsou povoleny záporné hodnoty, ale jsou omezeny na celkovou hodnotu 0 (hráč nikdy nezačíná se zápornými zdroji).",
 	"vcmi.lobby.handicap.income" : "Mění různé příjmy hráče podle procent. Výsledek je zaokrouhlen nahoru.",
 	"vcmi.lobby.handicap.growth" : "Mění rychlost růstu jednotel v městech vlastněných hráčem. Výsledek je zaokrouhlen nahoru.",
-		
+	"vcmi.lobby.deleteUnsupportedSave" : "Nalezeny nepodporované uložené hry (např. z předchozích verzí).\n\nChcete je odstranit?",
+	"vcmi.lobby.deleteSaveGameTitle" : "Vyberte uloženou hru k odstranění",
+	"vcmi.lobby.deleteMapTitle" : "Vyberte scénář k odstranění",
+	"vcmi.lobby.deleteFile" : "Chcete smazat následující soubor?",
+	"vcmi.lobby.deleteFolder" : "Chcete smazat následující složku?",
+	"vcmi.lobby.deleteMode" : "Přepnout do režimu mazání a zpět",
+
 	"vcmi.lobby.login.title" : "Online lobby VCMI",
 	"vcmi.lobby.login.username" : "Uživatelské jméno:",
 	"vcmi.lobby.login.connecting" : "Připojování...",
 	"vcmi.lobby.login.error" : "Chyba při připojování: %s",
 	"vcmi.lobby.login.create" : "Nový účet",
 	"vcmi.lobby.login.login" : "Přihlásit se",
-	"vcmi.lobby.login.as" : "Přilásit se jako %s",
+	"vcmi.lobby.login.as" : "Přihlásit se jako %s",
 	"vcmi.lobby.header.rooms" : "Herní místnosti - %d",
 	"vcmi.lobby.header.channels" : "Kanály konverzace",
 	"vcmi.lobby.header.chat.global" : "Globální konverzace hry - %s", // %s -> language name
@@ -178,7 +191,7 @@
 	"vcmi.server.errors.modDependencyLoop" : "Nelze načíst modifikaci {'%s'}!\n Modifikace může být součástí (nepřímé) závislostní smyčky.",
 	"vcmi.server.errors.modConflict" : "Nelze načíst modifikaci {'%s'}!\n Je v kolizi s aktivní modifikací {'%s'}!\n",
 	"vcmi.server.errors.unknownEntity" : "Nelze načíst uloženou pozici! Neznámá entita '%s' nalezena v uložené pozici! Uložná pozice nemusí být kompatibilní s aktuálními verzemi modifikací!",
-	
+
 	"vcmi.dimensionDoor.seaToLandError" : "Pomocí dimenzní brány není možné se teleportovat z moře na pevninu nebo naopak.",
 
 	"vcmi.settingsMainWindow.generalTab.hover" : "Obecné",
@@ -324,13 +337,13 @@
 	"vcmi.battleWindow.damageEstimation.damage.1" : "%d poškození",
 	"vcmi.battleWindow.damageEstimation.kills" : "%d zahyne",
 	"vcmi.battleWindow.damageEstimation.kills.1" : "%d zahyne",
-	
+
 	"vcmi.battleWindow.damageRetaliation.will" : "Provede odvetu ",
 	"vcmi.battleWindow.damageRetaliation.may" : "Může provést odvetu",
 	"vcmi.battleWindow.damageRetaliation.never" : "Neprovede odvetu.",
 	"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
 	"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
-	
+
 	"vcmi.battleWindow.killed" : "Zabito",
 	"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s bylo zabito přesnými zásahy!",
 	"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s byl zabit přesným zásahem!",
@@ -445,7 +458,7 @@
 	"vcmi.optionsTab.simturns.blocked1"       : "Souběžně: 1 týden, setkání zablokována",
 	"vcmi.optionsTab.simturns.blocked2"       : "Souběžně: 2 týdny, setkání zablokována",
 	"vcmi.optionsTab.simturns.blocked4"       : "Souběžně: 1 měsíc, setkání zablokována",
-	
+
 	// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
 	// Using this information, VCMI will automatically select correct plural form for every possible amount
 	"vcmi.optionsTab.simturns.days.0" : " %d dní",
@@ -489,7 +502,7 @@
 	"vcmi.stackExperience.rank.8" : "Elitní",
 	"vcmi.stackExperience.rank.9" : "Mistr",
 	"vcmi.stackExperience.rank.10" : "Eso",
-	
+
 	// Strings for HotA Seer Hut / Quest Guards
 	"core.seerhut.quest.heroClass.complete.0" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?",
 	"core.seerhut.quest.heroClass.complete.1" : "Ah, vy jste %s. Tady máte dárek. Přijmete ho?",
@@ -521,7 +534,7 @@
 	"core.seerhut.quest.heroClass.visit.3" : "Stráže zde povolí průchod pouze %s.",
 	"core.seerhut.quest.heroClass.visit.4" : "Stráže zde povolí průchod pouze %s.",
 	"core.seerhut.quest.heroClass.visit.5" : "Stráže zde povolí průchod pouze %s.",
-	
+
 	"core.seerhut.quest.reachDate.complete.0" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?",
 	"core.seerhut.quest.reachDate.complete.1" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?",
 	"core.seerhut.quest.reachDate.complete.2" : "Jsem nyní volný. Tady máte, co jsem pro vás měl. Přijmete to?",
@@ -552,9 +565,9 @@
 	"core.seerhut.quest.reachDate.visit.3" : "Zavřeno do %s.",
 	"core.seerhut.quest.reachDate.visit.4" : "Zavřeno do %s.",
 	"core.seerhut.quest.reachDate.visit.5" : "Zavřeno do %s.",
-	
+
 	"mapObject.core.hillFort.object.description" : "Zde můžeš vylepšit jednotky. Vylepšení jednotek úrovně 1 až 4 je zde levnější než v jejich domovském městě.",
-	
+
 	"core.bonus.ADDITIONAL_ATTACK.name": "Dvojitý útok",
 	"core.bonus.ADDITIONAL_ATTACK.description": "Útočí dvakrát",
 	"core.bonus.ADDITIONAL_RETALIATION.name": "Další odvetné útoky",

+ 3 - 3
client/ArtifactsUIController.cpp

@@ -71,7 +71,7 @@ bool ArtifactsUIController::askToAssemble(const CGHeroInstance * hero, const Art
 					}
 
 					bool assembleConfirmed = false;
-					MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID());
+					MetaString message = MetaString::createFromTextID(art->getType()->getDescriptionTextID());
 					message.appendEOL();
 					message.appendEOL();
 					if(combinedArt->isFused())
@@ -107,10 +107,10 @@ bool ArtifactsUIController::askToDisassemble(const CGHeroInstance * hero, const
 
 	if(art->hasParts())
 	{
-		if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->artType->getConstituents().size() - 1))
+		if(ArtifactUtils::isSlotBackpack(slot) && !ArtifactUtils::isBackpackFreeSlots(hero, art->getType()->getConstituents().size() - 1))
 			return false;
 
-		MetaString message = MetaString::createFromTextID(art->artType->getDescriptionTextID());
+		MetaString message = MetaString::createFromTextID(art->getType()->getDescriptionTextID());
 		message.appendEOL();
 		message.appendEOL();
 		message.appendRawString(CGI->generaltexth->allTexts[733]); // Do you wish to disassemble this artifact?

+ 2 - 2
client/CPlayerInterface.cpp

@@ -238,7 +238,7 @@ void CPlayerInterface::performAutosave()
 				std::string name = cb->getMapHeader()->name.toString();
 				int txtlen = TextOperations::getUnicodeCharactersCount(name);
 
-				TextOperations::trimRightUnicode(name, std::max(0, txtlen - 15));
+				TextOperations::trimRightUnicode(name, std::max(0, txtlen - 14));
 				auto const & isSymbolIllegal = [&](char c) {
 					static const std::string forbiddenChars("\\/:*?\"<>| ");
 
@@ -249,7 +249,7 @@ void CPlayerInterface::performAutosave()
 				};
 				std::replace_if(name.begin(), name.end(), isSymbolIllegal, '_' );
 
-				prefix = name + "_" + cb->getStartInfo()->startTimeIso8601 + "/";
+				prefix = vstd::getFormattedDateTime(cb->getStartInfo()->startTime, "%Y-%m-%d_%H-%M") + "_" + name + "/";
 			}
 		}
 

+ 3 - 5
client/HeroMovementController.cpp

@@ -291,14 +291,12 @@ AudioPath HeroMovementController::getMovementSoundFor(const CGHeroInstance * her
 	auto prevTile = LOCPLINT->cb->getTile(posPrev);
 	auto nextTile = LOCPLINT->cb->getTile(posNext);
 
-	auto prevRoad = prevTile->roadType;
-	auto nextRoad = nextTile->roadType;
-	bool movingOnRoad = prevRoad->getId() != Road::NO_ROAD && nextRoad->getId() != Road::NO_ROAD;
+	bool movingOnRoad = prevTile->hasRoad() && nextTile->hasRoad();
 
 	if(movingOnRoad)
-		return nextTile->terType->horseSound;
+		return nextTile->getTerrain()->horseSound;
 	else
-		return nextTile->terType->horseSoundPenalty;
+		return nextTile->getTerrain()->horseSoundPenalty;
 };
 
 void HeroMovementController::updateMovementSound(const CGHeroInstance * h, int3 posPrev, int3 nextCoord, EPathNodeAction moveType)

+ 3 - 3
client/adventureMap/CMinimap.cpp

@@ -50,10 +50,10 @@ ColorRGBA CMinimapInstance::getTileColor(const int3 & pos) const
 			return graphics->playerColors[player.getNum()];
 	}
 
-	if (tile->blocked && (!tile->visitable))
-		return tile->terType->minimapBlocked;
+	if (tile->blocked() && !tile->visitable())
+		return tile->getTerrain()->minimapBlocked;
 	else
-		return tile->terType->minimapUnblocked;
+		return tile->getTerrain()->minimapUnblocked;
 }
 
 void CMinimapInstance::refreshTile(const int3 &tile)

+ 1 - 1
client/adventureMap/MapAudioPlayer.cpp

@@ -182,7 +182,7 @@ void MapAudioPlayer::updateMusic()
 		const auto * tile = LOCPLINT->cb->getTile(currentSelection->visitablePos());
 
 		if (tile)
-			CCS->musich->playMusicFromSet("terrain", tile->terType->getJsonKey(), true, false);
+			CCS->musich->playMusicFromSet("terrain", tile->getTerrain()->getJsonKey(), true, false);
 	}
 
 	if(audioPlaying && enemyMakingTurn)

+ 9 - 4
client/eventsSDL/InputSourceTouch.cpp

@@ -83,16 +83,18 @@ void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfin
 			break;
 		}
 		case TouchState::TAP_DOWN_SHORT:
+		case TouchState::TAP_DOWN_LONG_AWAIT:
 		{
 			Point distance = convertTouchToMouse(tfinger) - lastTapPosition;
 			if ( std::abs(distance.x) > params.panningSensitivityThreshold || std::abs(distance.y) > params.panningSensitivityThreshold)
 			{
-				state = TouchState::TAP_DOWN_PANNING;
+				state = state == TouchState::TAP_DOWN_SHORT ? TouchState::TAP_DOWN_PANNING : TouchState::TAP_DOWN_PANNING_POPUP;
 				GH.events().dispatchGesturePanningStarted(lastTapPosition);
 			}
 			break;
 		}
 		case TouchState::TAP_DOWN_PANNING:
+		case TouchState::TAP_DOWN_PANNING_POPUP:
 		{
 			emitPanningEvent(tfinger);
 			break;
@@ -103,7 +105,6 @@ void InputSourceTouch::handleEventFingerMotion(const SDL_TouchFingerEvent & tfin
 			break;
 		}
 		case TouchState::TAP_DOWN_LONG:
-		case TouchState::TAP_DOWN_LONG_AWAIT:
 		{
 			// no-op
 			break;
@@ -157,8 +158,11 @@ void InputSourceTouch::handleEventFingerDown(const SDL_TouchFingerEvent & tfinge
 			CSH->getGlobalLobby().activateInterface();
 			break;
 		}
-		case TouchState::TAP_DOWN_LONG:
 		case TouchState::TAP_DOWN_LONG_AWAIT:
+			lastTapPosition = convertTouchToMouse(tfinger);
+			break;
+		case TouchState::TAP_DOWN_LONG:
+		case TouchState::TAP_DOWN_PANNING_POPUP:
 		{
 			// no-op
 			break;
@@ -205,9 +209,10 @@ void InputSourceTouch::handleEventFingerUp(const SDL_TouchFingerEvent & tfinger)
 			break;
 		}
 		case TouchState::TAP_DOWN_PANNING:
+		case TouchState::TAP_DOWN_PANNING_POPUP:
 		{
 			GH.events().dispatchGesturePanningEnded(lastTapPosition, convertTouchToMouse(tfinger));
-			state = TouchState::IDLE;
+			state = state == TouchState::TAP_DOWN_PANNING ? TouchState::IDLE : TouchState::TAP_DOWN_LONG_AWAIT;
 			break;
 		}
 		case TouchState::TAP_DOWN_DOUBLE:

+ 8 - 2
client/eventsSDL/InputSourceTouch.h

@@ -45,6 +45,12 @@ enum class TouchState
 	// UP -> transition to IDLE
 	TAP_DOWN_PANNING,
 
+	// single finger is moving across screen
+	// DOWN -> ignored
+	// MOTION -> emit panning event
+	// UP -> transition to TAP_DOWN_LONG_AWAIT
+	TAP_DOWN_PANNING_POPUP,
+
 	// two fingers are touching the screen
 	// DOWN -> ??? how to handle 3rd finger? Ignore?
 	// MOTION -> emit pinch event
@@ -59,7 +65,7 @@ enum class TouchState
 
 	// right-click popup is active, waiting for new tap to hide popup
 	// DOWN -> ignored
-	// MOTION -> ignored
+	// MOTION -> transition to TAP_DOWN_PANNING_POPUP
 	// UP -> transition to IDLE, generate closePopup() event
 	TAP_DOWN_LONG_AWAIT,
 };
@@ -79,7 +85,7 @@ struct TouchInputParameters
 	uint32_t doubleTouchToleranceDistance = 50;
 
 	/// moving finger for distance larger than specified will be qualified as panning gesture instead of long press
-	uint32_t panningSensitivityThreshold = 10;
+	uint32_t panningSensitivityThreshold = 15;
 
 	/// gesture will be qualified as pinch if distance between fingers is at least specified here
 	uint32_t pinchSensitivityThreshold = 10;

+ 21 - 2
client/lobby/SelectionTab.cpp

@@ -326,7 +326,7 @@ void SelectionTab::clickReleased(const Point & cursorPosition)
 {
 	int line = getLine();
 
-	if(line != -1)
+	if(line != -1 && curItems.size() > line)
 	{
 		if(!deleteMode)
 			select(line);
@@ -551,6 +551,7 @@ void SelectionTab::filter(int size, bool selectFirst)
 			auto folder = std::make_shared<ElementInfo>();
 			folder->isFolder = true;
 			folder->folderName = folderName;
+			folder->isAutoSaveFolder = boost::starts_with(baseFolder, "Autosave/") && folderName != "Autosave";
 			auto itemIt = boost::range::find_if(curItems, [folder](std::shared_ptr<ElementInfo> e) { return e->folderName == folder->folderName; });
 			if (itemIt == curItems.end() && folderName != "") {
 				curItems.push_back(folder);
@@ -611,7 +612,11 @@ void SelectionTab::sort()
 
 	int firstMapIndex = boost::range::find_if(curItems, [](std::shared_ptr<ElementInfo> e) { return !e->isFolder; }) - curItems.begin();
 	if(!sortModeAscending)
+	{
+		if(firstMapIndex)
+			std::reverse(std::next(curItems.begin(), boost::starts_with(curItems[0]->folderName, "..") ? 1 : 0), std::next(curItems.begin(), firstMapIndex - 1));
 		std::reverse(std::next(curItems.begin(), firstMapIndex), curItems.end());
+	}
 
 	updateListItems();
 	redraw();
@@ -983,7 +988,7 @@ SelectionTab::ListItem::ListItem(Point position)
 {
 	OBJECT_CONSTRUCTION;
 	pictureEmptyLine = std::make_shared<CPicture>(ImagePath::builtin("camcust"), Rect(25, 121, 349, 26), -8, -14);
-	labelName = std::make_shared<CLabel>(184, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "", 185);
+	labelName = std::make_shared<CLabel>(LABEL_POS_X, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, "", 185);
 	labelName->setAutoRedraw(false);
 	labelAmountOfPlayers = std::make_shared<CLabel>(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 	labelAmountOfPlayers->setAutoRedraw(false);
@@ -1027,6 +1032,16 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> info, bool
 		labelNumberOfCampaignMaps->disable();
 		labelName->enable();
 		labelName->setMaxWidth(316);
+		if(info->isAutoSaveFolder) // align autosave folder left (starting timestamps in list should be in one line)
+		{
+			labelName->alignment = ETextAlignment::CENTERLEFT;
+			labelName->moveTo(Point(pos.x + 80, labelName->pos.y));
+		}
+		else
+		{
+			labelName->alignment = ETextAlignment::CENTER;
+			labelName->moveTo(Point(pos.x + LABEL_POS_X, labelName->pos.y));
+		}
 		labelName->setText(info->folderName);
 		labelName->setColor(color);
 		return;
@@ -1048,6 +1063,8 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> info, bool
 		labelNumberOfCampaignMaps->setText(ostr.str());
 		labelNumberOfCampaignMaps->setColor(color);
 		labelName->setMaxWidth(316);
+		labelName->alignment = ETextAlignment::CENTER;
+		labelName->moveTo(Point(pos.x + LABEL_POS_X, labelName->pos.y));
 	}
 	else
 	{
@@ -1069,6 +1086,8 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> info, bool
 		iconLossCondition->enable();
 		iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0);
 		labelName->setMaxWidth(185);
+		labelName->alignment = ETextAlignment::CENTER;
+		labelName->moveTo(Point(pos.x + LABEL_POS_X, labelName->pos.y));
 	}
 	labelName->setText(info->name);
 	labelName->setColor(color);

+ 3 - 0
client/lobby/SelectionTab.h

@@ -35,6 +35,7 @@ public:
 	std::string folderName = "";
 	std::string name = "";
 	bool isFolder = false;
+	bool isAutoSaveFolder = false;
 };
 
 /// Class which handles map sorting by different criteria
@@ -60,6 +61,8 @@ class SelectionTab : public CIntObject
 		std::shared_ptr<CPicture> pictureEmptyLine;
 		std::shared_ptr<CLabel> labelName;
 
+		const int LABEL_POS_X = 184;
+
 		ListItem(Point position);
 		void updateItem(std::shared_ptr<ElementInfo> info = {}, bool selected = false);
 	};

+ 12 - 12
client/mapView/MapRenderer.cpp

@@ -143,7 +143,7 @@ void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & targ
 {
 	const TerrainTile & mapTile = context.getMapTile(coordinates);
 
-	int32_t terrainIndex = mapTile.terType->getIndex();
+	int32_t terrainIndex = mapTile.getTerrainID();
 	int32_t imageIndex = mapTile.terView;
 	int32_t rotationIndex = mapTile.extTileFlags % 4;
 
@@ -152,11 +152,11 @@ void MapRendererTerrain::renderTile(IMapRendererContext & context, Canvas & targ
 	assert(image);
 	if (!image)
 	{
-		logGlobal->error("Failed to find image %d for terrain %s on tile %s", imageIndex, mapTile.terType->getNameTranslated(), coordinates.toString());
+		logGlobal->error("Failed to find image %d for terrain %s on tile %s", imageIndex, mapTile.getTerrain()->getNameTranslated(), coordinates.toString());
 		return;
 	}
 
-	for( auto const & element : mapTile.terType->paletteAnimation)
+	for( auto const & element : mapTile.getTerrain()->paletteAnimation)
 		image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length));
 
 	target.draw(image, Point(0, 0));
@@ -166,7 +166,7 @@ uint8_t MapRendererTerrain::checksum(IMapRendererContext & context, const int3 &
 {
 	const TerrainTile & mapTile = context.getMapTile(coordinates);
 
-	if(!mapTile.terType->paletteAnimation.empty())
+	if(!mapTile.getTerrain()->paletteAnimation.empty())
 		return context.terrainImageIndex(250);
 	return 0xff - 1;
 }
@@ -184,16 +184,16 @@ void MapRendererRiver::renderTile(IMapRendererContext & context, Canvas & target
 {
 	const TerrainTile & mapTile = context.getMapTile(coordinates);
 
-	if(mapTile.riverType->getId() == River::NO_RIVER)
+	if(!mapTile.hasRiver())
 		return;
 
-	int32_t terrainIndex = mapTile.riverType->getIndex();
+	int32_t terrainIndex = mapTile.getRiverID();
 	int32_t imageIndex = mapTile.riverDir;
 	int32_t rotationIndex = (mapTile.extTileFlags >> 2) % 4;
 
 	const auto & image = storage.find(terrainIndex, rotationIndex, imageIndex);
 
-	for( auto const & element : mapTile.riverType->paletteAnimation)
+	for( auto const & element : mapTile.getRiver()->paletteAnimation)
 		image->shiftPalette(element.start, element.length, context.terrainImageIndex(element.length));
 
 	target.draw(image, Point(0, 0));
@@ -203,7 +203,7 @@ uint8_t MapRendererRiver::checksum(IMapRendererContext & context, const int3 & c
 {
 	const TerrainTile & mapTile = context.getMapTile(coordinates);
 
-	if(!mapTile.riverType->paletteAnimation.empty())
+	if(!mapTile.getRiver()->paletteAnimation.empty())
 		return context.terrainImageIndex(250);
 	return 0xff-1;
 }
@@ -224,9 +224,9 @@ void MapRendererRoad::renderTile(IMapRendererContext & context, Canvas & target,
 	if(context.isInMap(coordinatesAbove))
 	{
 		const TerrainTile & mapTileAbove = context.getMapTile(coordinatesAbove);
-		if(mapTileAbove.roadType->getId() != Road::NO_ROAD)
+		if(mapTileAbove.hasRoad())
 		{
-			int32_t terrainIndex = mapTileAbove.roadType->getIndex();
+			int32_t terrainIndex = mapTileAbove.getRoadID();
 			int32_t imageIndex = mapTileAbove.roadDir;
 			int32_t rotationIndex = (mapTileAbove.extTileFlags >> 4) % 4;
 
@@ -236,9 +236,9 @@ void MapRendererRoad::renderTile(IMapRendererContext & context, Canvas & target,
 	}
 
 	const TerrainTile & mapTile = context.getMapTile(coordinates);
-	if(mapTile.roadType->getId() != Road::NO_ROAD)
+	if(mapTile.hasRoad())
 	{
-		int32_t terrainIndex = mapTile.roadType->getIndex();
+		int32_t terrainIndex = mapTile.getRoadID();
 		int32_t imageIndex = mapTile.roadDir;
 		int32_t rotationIndex = (mapTile.extTileFlags >> 4) % 4;
 

+ 2 - 2
client/mapView/MapRendererContext.cpp

@@ -275,7 +275,7 @@ std::string MapRendererAdventureContext::overlayText(const int3 & coordinates) c
 
 	const auto & tile = getMapTile(coordinates);
 
-	if (!tile.visitable)
+	if (!tile.visitable())
 		return {};
 
 	return tile.visitableObjects.back()->getObjectName();
@@ -288,7 +288,7 @@ ColorRGBA MapRendererAdventureContext::overlayTextColor(const int3 & coordinates
 
 	const auto & tile = getMapTile(coordinates);
 
-	if (!tile.visitable)
+	if (!tile.visitable())
 		return {};
 
 	const auto * object = tile.visitableObjects.back();

+ 1 - 1
client/mapView/mapHandler.cpp

@@ -55,7 +55,7 @@ std::string CMapHandler::getTerrainDescr(const int3 & pos, bool rightClick) cons
 	if(t.hasFavorableWinds())
 		return CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS, 0);
 
-	std::string result = t.terType->getNameTranslated();
+	std::string result = t.getTerrain()->getNameTranslated();
 
 	for(const auto & object : map->objects)
 	{

+ 12 - 0
client/media/CSoundHandler.cpp

@@ -240,6 +240,18 @@ void CSoundHandler::stopSound(int handler)
 		Mix_HaltChannel(handler);
 }
 
+void CSoundHandler::pauseSound(int handler)
+{
+	if(isInitialized() && handler != -1)
+		Mix_Pause(handler);
+}
+
+void CSoundHandler::resumeSound(int handler)
+{
+	if(isInitialized() && handler != -1)
+		Mix_Resume(handler);
+}
+
 ui32 CSoundHandler::getVolume() const
 {
 	return volume;

+ 2 - 0
client/media/CSoundHandler.h

@@ -67,6 +67,8 @@ public:
 	int playSound(std::pair<std::unique_ptr<ui8[]>, si64> & data, int repeats = 0, bool cache = false) final;
 	int playSoundFromSet(std::vector<soundBase::soundID> & sound_vec) final;
 	void stopSound(int handler) final;
+	void pauseSound(int handler) final;
+	void resumeSound(int handler) final;
 
 	void setCallback(int channel, std::function<void()> function) final;
 	void resetCallback(int channel) final;

+ 34 - 2
client/media/CVideoHandler.cpp

@@ -316,6 +316,12 @@ bool CVideoInstance::loadNextFrame()
 	return true;
 }
 
+
+double CVideoInstance::timeStamp()
+{
+	return getCurrentFrameEndTime();
+}
+
 bool CVideoInstance::videoEnded()
 {
 	return getCurrentFrame() == nullptr;
@@ -385,12 +391,38 @@ void CVideoInstance::tick(uint32_t msPassed)
 	if(videoEnded())
 		throw std::runtime_error("Video already ended!");
 
-	frameTime += msPassed / 1000.0;
+	if(startTime == std::chrono::steady_clock::time_point())
+		startTime = std::chrono::steady_clock::now();
 
-	if(frameTime >= getCurrentFrameEndTime())
+	auto nowTime = std::chrono::steady_clock::now();
+	double difference = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - startTime).count() / 1000.0;
+
+	int frameskipCounter = 0;
+	while(!videoEnded() && difference >= getCurrentFrameEndTime() + getCurrentFrameDuration() && frameskipCounter < MAX_FRAMESKIP) // Frameskip
+	{
+		decodeNextFrame();
+		frameskipCounter++;
+	}
+	if(!videoEnded() && difference >= getCurrentFrameEndTime())
 		loadNextFrame();
 }
 
+
+void CVideoInstance::activate()
+{
+	if(deactivationStartTime != std::chrono::steady_clock::time_point())
+	{
+		auto pauseDuration = std::chrono::steady_clock::now() - deactivationStartTime;
+		startTime += pauseDuration;
+		deactivationStartTime = std::chrono::steady_clock::time_point();
+	}
+}
+
+void CVideoInstance::deactivate()
+{
+	deactivationStartTime = std::chrono::steady_clock::now();
+}
+
 struct FFMpegFormatDescription
 {
 	uint8_t sampleSizeBytes;

+ 8 - 2
client/media/CVideoHandler.h

@@ -77,10 +77,13 @@ class CVideoInstance final : public IVideoInstance, public FFMpegStream
 	SDL_Surface * surface = nullptr;
 	Point dimensions;
 
-	/// video playback current progress, in seconds
-	double frameTime = 0.0;
+	/// video playback start time point
+	std::chrono::steady_clock::time_point startTime;
+	std::chrono::steady_clock::time_point deactivationStartTime;
 
 	void prepareOutput(float scaleFactor, bool useTextureOutput);
+	
+	const int MAX_FRAMESKIP = 5;
 
 public:
 	~CVideoInstance();
@@ -88,11 +91,14 @@ public:
 	void openVideo();
 	bool loadNextFrame();
 
+	double timeStamp() final;
 	bool videoEnded() final;
 	Point size() final;
 
 	void show(const Point & position, Canvas & canvas) final;
 	void tick(uint32_t msPassed) final;
+	void activate() final;
+	void deactivate() final;
 };
 
 class CVideoPlayer final : public IVideoPlayer

+ 2 - 0
client/media/ISoundPlayer.h

@@ -22,6 +22,8 @@ public:
 	virtual int playSound(std::pair<std::unique_ptr<ui8[]>, si64> & data, int repeats = 0, bool cache = false) = 0;
 	virtual int playSoundFromSet(std::vector<soundBase::soundID> & sound_vec) = 0;
 	virtual void stopSound(int handler) = 0;
+	virtual void pauseSound(int handler) = 0;
+	virtual void resumeSound(int handler) = 0;
 
 	virtual ui32 getVolume() const = 0;
 	virtual void setVolume(ui32 percent) = 0;

+ 7 - 0
client/media/IVideoPlayer.h

@@ -20,6 +20,9 @@ VCMI_LIB_NAMESPACE_END
 class IVideoInstance
 {
 public:
+	/// Returns current video timestamp
+	virtual double timeStamp() = 0;
+
 	/// Returns true if video playback is over
 	virtual bool videoEnded() = 0;
 
@@ -32,6 +35,10 @@ public:
 	/// Advances video playback by specified duration
 	virtual void tick(uint32_t msPassed) = 0;
 
+	/// activate or deactivate video
+	virtual void activate() = 0;
+	virtual void deactivate() = 0;
+
 	virtual ~IVideoInstance() = default;
 };
 

+ 1 - 1
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -152,7 +152,7 @@ void CArtifactsOfHeroQuickBackpack::setHero(const CGHeroInstance * hero)
 		std::map<const ArtifactID, const CArtifactInstance*> filteredArts;
 		for(auto & slotInfo : curHero->artifactsInBackpack)
 			if(slotInfo.artifact->getTypeId() != artInSlotId &&	!slotInfo.artifact->isScroll() &&
-				slotInfo.artifact->artType->canBePutAt(curHero, filterBySlot, true))
+				slotInfo.artifact->getType()->canBePutAt(curHero, filterBySlot, true))
 			{
 				filteredArts.insert(std::pair(slotInfo.artifact->getTypeId(), slotInfo.artifact));
 			}

+ 1 - 1
client/widgets/CArtifactsOfHeroBase.cpp

@@ -273,7 +273,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
 
 		// If the artifact is part of at least one combined artifact, add additional information
 		std::map<const ArtifactID, std::vector<ArtifactID>> arts;
-		for(const auto combinedArt : slotInfo->artifact->artType->getPartOf())
+		for(const auto combinedArt : slotInfo->artifact->getType()->getPartOf())
 		{
 			assert(combinedArt->isCombined());
 			arts.try_emplace(combinedArt->getId());

+ 1 - 1
client/widgets/CArtifactsOfHeroMarket.cpp

@@ -32,7 +32,7 @@ void CArtifactsOfHeroMarket::clickPressedArtPlace(CComponentHolder & artPlace, c
 
 	if(const auto art = getArt(ownedPlace->slot))
 	{
-		if(onSelectArtCallback && art->artType->isTradable())
+		if(onSelectArtCallback && art->getType()->isTradable())
 		{
 			unmarkSlots();
 			artPlace.selectSlot(true);

+ 3 - 3
client/widgets/CComponentHolder.cpp

@@ -266,11 +266,11 @@ CSecSkillPlace::CSecSkillPlace(const Point & position, const ImageSize & imageSi
 {
 	OBJECT_CONSTRUCTION;
 
-	auto imagePath = AnimationPath::builtin("SECSKILL");
+	auto imagePath = AnimationPath::builtin("SECSK82");
 	if(imageSize == ImageSize::MEDIUM)
-		imagePath = AnimationPath::builtin("SECSK32");
+		imagePath = AnimationPath::builtin("SECSKILL");
 	if(imageSize == ImageSize::SMALL)
-		imagePath = AnimationPath::builtin("SECSK82");
+		imagePath = AnimationPath::builtin("SECSK32");
 
 	image = std::make_shared<CAnimImage>(imagePath, 0);
 	component.type = ComponentType::SEC_SKILL;

+ 3 - 3
client/widgets/CGarrisonInt.cpp

@@ -373,7 +373,7 @@ void CGarrisonSlot::gesture(bool on, const Point & initialPosition, const Point
 	const auto * otherArmy = upg == EGarrisonType::UPPER ? owner->lowerArmy() : owner->upperArmy();
 
 	bool stackExists = myStack != nullptr;
-	bool hasSameUnit = stackExists && !owner->army(upg)->getCreatureSlots(myStack->type, ID).empty();
+	bool hasSameUnit = stackExists && !owner->army(upg)->getCreatureSlots(myStack->getCreature(), ID).empty();
 	bool hasOwnEmptySlots = stackExists && owner->army(upg)->getFreeSlot() != SlotID();
 	bool exchangeMode = stackExists && owner->upperArmy() && owner->lowerArmy();
 	bool hasOtherEmptySlots = exchangeMode && otherArmy->getFreeSlot() != SlotID();
@@ -398,7 +398,7 @@ void CGarrisonSlot::update()
 	{
 		addUsedEvents(LCLICK | SHOW_POPUP | GESTURE | HOVER);
 		myStack = getObj()->getStackPtr(ID);
-		creature = myStack ? myStack->type : nullptr;
+		creature = myStack ? myStack->getCreature() : nullptr;
 	}
 	else
 	{
@@ -426,7 +426,7 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, EGa
 	: ID(IID),
 	owner(Owner),
 	myStack(creature_),
-	creature(creature_ ? creature_->type : nullptr),
+	creature(creature_ ? creature_->getCreature() : nullptr),
 	upg(Upg)
 {
 	OBJECT_CONSTRUCTION;

+ 1 - 1
client/widgets/MiscWidgets.cpp

@@ -280,7 +280,7 @@ void CArmyTooltip::init(const InfoAboutArmy &army)
 			continue;
 		}
 
-		icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), slot.second.type->getIconIndex(), 0, slotsPos[slot.first.getNum()].x, slotsPos[slot.first.getNum()].y));
+		icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), slot.second.getType()->getIconIndex(), 0, slotsPos[slot.first.getNum()].x, slotsPos[slot.first.getNum()].y));
 
 		std::string subtitle;
 		if(army.army.isDetailed)

+ 42 - 3
client/widgets/VideoWidget.cpp

@@ -9,6 +9,7 @@
  */
 #include "StdInc.h"
 #include "VideoWidget.h"
+#include "TextControls.h"
 
 #include "../CGameInfo.h"
 #include "../gui/CGuiHandler.h"
@@ -16,6 +17,8 @@
 #include "../media/IVideoPlayer.h"
 #include "../render/Canvas.h"
 
+#include "../../lib/filesystem/Filesystem.h"
+
 VideoWidgetBase::VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio)
 	: VideoWidgetBase(position, video, playAudio, 1.0)
 {
@@ -33,11 +36,22 @@ VideoWidgetBase::~VideoWidgetBase() = default;
 
 void VideoWidgetBase::playVideo(const VideoPath & fileToPlay)
 {
+	OBJECT_CONSTRUCTION;
+
+	JsonPath subTitlePath = fileToPlay.toType<EResType::JSON>();
+	JsonPath subTitlePathVideoDir = subTitlePath.addPrefix("VIDEO/");
+	if(CResourceHandler::get()->existsResource(subTitlePath))
+		subTitleData = JsonNode(subTitlePath);
+	else if(CResourceHandler::get()->existsResource(subTitlePathVideoDir))
+		subTitleData = JsonNode(subTitlePathVideoDir);
+
 	videoInstance = CCS->videoh->open(fileToPlay, scaleFactor);
 	if (videoInstance)
 	{
 		pos.w = videoInstance->size().x;
 		pos.h = videoInstance->size().y;
+		if(!subTitleData.isNull())
+			subTitle = std::make_unique<CMultiLineLabel>(Rect(0, (pos.h / 5) * 4, pos.w, pos.h / 5), EFonts::FONT_HIGH_SCORE, ETextAlignment::CENTER, Colors::WHITE);
 	}
 
 	if (playAudio)
@@ -52,6 +66,8 @@ void VideoWidgetBase::show(Canvas & to)
 {
 	if(videoInstance)
 		videoInstance->show(pos.topLeft(), to);
+	if(subTitle)
+		subTitle->showAll(to);
 }
 
 void VideoWidgetBase::loadAudio(const VideoPath & fileToPlay)
@@ -77,7 +93,7 @@ void VideoWidgetBase::startAudio()
 			{
 				this->audioHandle = -1;
 			}
-			);
+		);
 	}
 }
 
@@ -91,22 +107,43 @@ void VideoWidgetBase::stopAudio()
 	}
 }
 
+std::string VideoWidgetBase::getSubTitleLine(double timestamp)
+{
+	if(subTitleData.isNull())
+		return {};
+
+	for(auto & segment : subTitleData.Vector())
+		if(timestamp > segment["timeStart"].Float() && timestamp < segment["timeEnd"].Float())
+			return segment["text"].String();
+	
+	return {};
+}
+
 void VideoWidgetBase::activate()
 {
 	CIntObject::activate();
-	startAudio();
+	if(audioHandle != -1)
+		CCS->soundh->resumeSound(audioHandle);
+	else
+		startAudio();
+	if(videoInstance)
+		videoInstance->activate();
 }
 
 void VideoWidgetBase::deactivate()
 {
 	CIntObject::deactivate();
-	stopAudio();
+	CCS->soundh->pauseSound(audioHandle);
+	if(videoInstance)
+		videoInstance->deactivate();
 }
 
 void VideoWidgetBase::showAll(Canvas & to)
 {
 	if(videoInstance)
 		videoInstance->show(pos.topLeft(), to);
+	if(subTitle)
+		subTitle->showAll(to);
 }
 
 void VideoWidgetBase::tick(uint32_t msPassed)
@@ -122,6 +159,8 @@ void VideoWidgetBase::tick(uint32_t msPassed)
 			onPlaybackFinished();
 		}
 	}
+	if(subTitle && videoInstance)
+		subTitle->setText(getSubTitleLine(videoInstance->timeStamp()));
 }
 
 VideoWidget::VideoWidget(const Point & position, const VideoPath & prologue, const VideoPath & looped, bool playAudio)

+ 5 - 0
client/widgets/VideoWidget.h

@@ -12,21 +12,26 @@
 #include "../gui/CIntObject.h"
 
 #include "../lib/filesystem/ResourcePath.h"
+#include "../lib/json/JsonNode.h"
 
 class IVideoInstance;
+class CMultiLineLabel;
 
 class VideoWidgetBase : public CIntObject
 {
 	std::unique_ptr<IVideoInstance> videoInstance;
+	std::unique_ptr<CMultiLineLabel> subTitle;
 
 	std::pair<std::unique_ptr<ui8[]>, si64> audioData = {nullptr, 0};
 	int audioHandle = -1;
 	bool playAudio = false;
 	float scaleFactor = 1.0;
+	JsonNode subTitleData;
 
 	void loadAudio(const VideoPath & file);
 	void startAudio();
 	void stopAudio();
+	std::string getSubTitleLine(double timestamp);
 
 protected:
 	VideoWidgetBase(const Point & position, const VideoPath & video, bool playAudio);

+ 1 - 1
client/widgets/markets/CAltarArtifacts.cpp

@@ -201,7 +201,7 @@ void CAltarArtifacts::onSlotClickPressed(const std::shared_ptr<CTradeableItem> &
 	{
 		if(pickedArtInst->canBePutAt(altarArtifactsStorage))
 		{
-			if(pickedArtInst->artType->isTradable())
+			if(pickedArtInst->getType()->isTradable())
 			{
 				if(altarSlot->id == -1)
 					tradeSlotsMap.try_emplace(altarSlot, pickedArtInst);

+ 2 - 1
client/windows/CCastleInterface.cpp

@@ -238,7 +238,8 @@ std::string CBuildingRect::getSubtitle()//hover text for building
 		return town->getTown()->buildings.at(getBuilding()->bid)->getNameTranslated();
 	else//dwellings - recruit %creature%
 	{
-		auto & availableCreatures = town->creatures[(bid-30)%town->getTown()->creatures.size()].second;
+		int level = BuildingID::getLevelFromDwelling(getBuilding()->bid);
+		auto & availableCreatures = town->creatures[level].second;
 		if(availableCreatures.size())
 		{
 			int creaID = availableCreatures.back();//taking last of available creatures

+ 6 - 6
client/windows/CCreatureWindow.cpp

@@ -90,7 +90,7 @@ public:
 	std::string getName() const
 	{
 		if(commander)
-			return commander->type->getNameSingularTranslated();
+			return commander->getType()->getNameSingularTranslated();
 		else
 			return creature->getNamePluralTranslated();
 	}
@@ -705,7 +705,7 @@ CStackWindow::CStackWindow(const CStackInstance * stack, bool popup)
 	info(new UnitView())
 {
 	info->stackNode = stack;
-	info->creature = stack->type;
+	info->creature = stack->getCreature();
 	info->creatureCount = stack->count;
 	info->popupWindow = popup;
 	info->owner = dynamic_cast<const CGHeroInstance *> (stack->armyObj);
@@ -717,7 +717,7 @@ CStackWindow::CStackWindow(const CStackInstance * stack, std::function<void()> d
 	info(new UnitView())
 {
 	info->stackNode = stack;
-	info->creature = stack->type;
+	info->creature = stack->getCreature();
 	info->creatureCount = stack->count;
 
 	info->upgradeInfo = std::make_optional(UnitView::StackUpgradeInfo());
@@ -734,7 +734,7 @@ CStackWindow::CStackWindow(const CCommanderInstance * commander, bool popup)
 	info(new UnitView())
 {
 	info->stackNode = commander;
-	info->creature = commander->type;
+	info->creature = commander->getCreature();
 	info->commander = commander;
 	info->creatureCount = 1;
 	info->popupWindow = popup;
@@ -747,7 +747,7 @@ CStackWindow::CStackWindow(const CCommanderInstance * commander, std::vector<ui3
 	info(new UnitView())
 {
 	info->stackNode = commander;
-	info->creature = commander->type;
+	info->creature = commander->getCreature();
 	info->commander = commander;
 	info->creatureCount = 1;
 	info->levelupInfo = std::make_optional(UnitView::CommanderLevelInfo());
@@ -879,7 +879,7 @@ std::string CStackWindow::generateStackExpDescription()
 	const CStackInstance * stack = info->stackNode;
 	const CCreature * creature = info->creature;
 
-	int tier = stack->type->getLevel();
+	int tier = stack->getType()->getLevel();
 	int rank = stack->getExpRank();
 	if (!vstd::iswithin(tier, 1, 7))
 		tier = 0;

+ 3 - 3
client/windows/CExchangeWindow.cpp

@@ -82,7 +82,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 
 
 		for(int m=0; m < hero->secSkills.size(); ++m)
-			secSkills[leftRight].push_back(std::make_shared<CSecSkillPlace>(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), CSecSkillPlace::ImageSize::MEDIUM,
+			secSkills[leftRight].push_back(std::make_shared<CSecSkillPlace>(Point(32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88), CSecSkillPlace::ImageSize::SMALL,
 				hero->secSkills[m].first, hero->secSkills[m].second));
 
 		specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45);
@@ -95,12 +95,12 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 	}
 
 	artifs[0] = std::make_shared<CArtifactsOfHeroMain>(Point(-334, 151));
-	artifs[0]->clickPressedCallback = [this, hero = heroInst[0]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false);};
+	artifs[0]->clickPressedCallback = [this, hero = heroInst[0]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false, cursorPosition);};
 	artifs[0]->showPopupCallback = [this, heroArts = artifs[0]](CArtPlace & artPlace, const Point & cursorPosition){showArtifactAssembling(*heroArts, artPlace, cursorPosition);};
 	artifs[0]->gestureCallback = [this, hero = heroInst[0]](const CArtPlace & artPlace, const Point & cursorPosition){showQuickBackpackWindow(hero, artPlace.slot, cursorPosition);};
 	artifs[0]->setHero(heroInst[0]);
 	artifs[1] = std::make_shared<CArtifactsOfHeroMain>(Point(98, 151));
-	artifs[1]->clickPressedCallback = [this, hero = heroInst[1]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false);};
+	artifs[1]->clickPressedCallback = [this, hero = heroInst[1]](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(hero, artPlace.slot, true, false, false, cursorPosition);};
 	artifs[1]->showPopupCallback = [this, heroArts = artifs[1]](CArtPlace & artPlace, const Point & cursorPosition){showArtifactAssembling(*heroArts, artPlace, cursorPosition);};
 	artifs[1]->gestureCallback = [this, hero = heroInst[1]](const CArtPlace & artPlace, const Point & cursorPosition){showQuickBackpackWindow(hero, artPlace.slot, cursorPosition);};
 	artifs[1]->setHero(heroInst[1]);

+ 1 - 1
client/windows/CHeroBackpackWindow.cpp

@@ -35,7 +35,7 @@ CHeroBackpackWindow::CHeroBackpackWindow(const CGHeroInstance * hero, const std:
 	arts->moveBy(Point(windowMargin, windowMargin));
 	arts->clickPressedCallback = [this](const CArtPlace & artPlace, const Point & cursorPosition)
 	{
-		clickPressedOnArtPlace(arts->getHero(), artPlace.slot, true, false, true);
+		clickPressedOnArtPlace(arts->getHero(), artPlace.slot, true, false, true, cursorPosition);
 	};
 	arts->showPopupCallback = [this](CArtPlace & artPlace, const Point & cursorPosition)
 	{

+ 3 - 2
client/windows/CHeroOverview.cpp

@@ -18,7 +18,7 @@
 #include "../render/Colors.h"
 #include "../render/IImage.h"
 #include "../renderSDL/RenderHandler.h"
-#include "../widgets/CComponent.h"
+#include "../widgets/CComponentHolder.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
 #include "../widgets/GraphicalPrimitiveCanvas.h"
@@ -206,7 +206,8 @@ void CHeroOverview::genControls()
     i = 0;
     for(auto & skill : (*CGI->heroh)[heroIdx]->secSkillsInit)
     {
-        imageSecSkills.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), (*CGI->skillh)[skill.first]->getIconIndex(skill.second + 2), 0, 302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)));
+        secSkills.push_back(std::make_shared<CSecSkillPlace>(Point(302, 7 * borderOffset + yOffset + 186 + i * (32 + borderOffset)),
+            CSecSkillPlace::ImageSize::SMALL, skill.first, skill.second));
         labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) - 5, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->levels[skill.second - 1]));
         labelSecSkillsNames.push_back(std::make_shared<CLabel>(334 + 2 * borderOffset, 8 * borderOffset + yOffset + 186 + i * (32 + borderOffset) + 10, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, (*CGI->skillh)[skill.first]->getNameTranslated()));
         i++;

+ 2 - 1
client/windows/CHeroOverview.h

@@ -19,6 +19,7 @@ class CComponentBox;
 class CTextBox;
 class TransparentFilledRectangle;
 class SimpleLine;
+class CSecSkillPlace;
 
 class CHeroOverview : public CWindowObject
 {
@@ -60,7 +61,7 @@ class CHeroOverview : public CWindowObject
     std::vector<std::shared_ptr<CLabel>> labelSpellsNames;
 
     std::shared_ptr<CLabel> labelSecSkillTitle;
-    std::vector<std::shared_ptr<CAnimImage>> imageSecSkills;
+    std::vector<std::shared_ptr<CSecSkillPlace>> secSkills;
     std::vector<std::shared_ptr<CLabel>> labelSecSkillsNames;
 
     void genBackground();

+ 2 - 2
client/windows/CHeroWindow.cpp

@@ -152,7 +152,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 	for(int i = 0; i < std::min<size_t>(hero->secSkills.size(), 8u); ++i)
 	{
 		Rect r = Rect(i%2 == 0  ?  18  :  162,  276 + 48 * (i/2),  136,  42);
-		secSkills.emplace_back(std::make_shared<CSecSkillPlace>(r.topLeft(), CSecSkillPlace::ImageSize::LARGE));
+		secSkills.emplace_back(std::make_shared<CSecSkillPlace>(r.topLeft(), CSecSkillPlace::ImageSize::MEDIUM));
 
 		int x = (i % 2) ? 212 : 68;
 		int y = 280 + 48 * (i/2);
@@ -209,7 +209,7 @@ void CHeroWindow::update()
 		if(!arts)
 		{
 			arts = std::make_shared<CArtifactsOfHeroMain>(Point(-65, -8));
-			arts->clickPressedCallback = [this](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(curHero, artPlace.slot, true, false, false);};
+			arts->clickPressedCallback = [this](const CArtPlace & artPlace, const Point & cursorPosition){clickPressedOnArtPlace(curHero, artPlace.slot, true, false, false, cursorPosition);};
 			arts->showPopupCallback = [this](CArtPlace & artPlace, const Point & cursorPosition){showArtifactAssembling(*arts, artPlace, cursorPosition);};
 			arts->gestureCallback = [this](const CArtPlace & artPlace, const Point & cursorPosition){showQuickBackpackWindow(curHero, artPlace.slot, cursorPosition);};
 			arts->setHero(curHero);

+ 1 - 1
client/windows/CKingdomInterface.cpp

@@ -552,7 +552,7 @@ std::shared_ptr<CIntObject> CKingdomInterface::createMainTab(size_t index)
 			{
 				newHeroSet->clickPressedCallback = [this, newHeroSet](const CArtPlace & artPlace, const Point & cursorPosition)
 				{
-					clickPressedOnArtPlace(newHeroSet->getHero(), artPlace.slot, false, false, false);
+					clickPressedOnArtPlace(newHeroSet->getHero(), artPlace.slot, false, false, false, cursorPosition);
 				};
 				newHeroSet->showPopupCallback = [this, newHeroSet](CArtPlace & artPlace, const Point & cursorPosition)
 				{

+ 3 - 3
client/windows/CMapOverview.cpp

@@ -67,9 +67,9 @@ Canvas CMapOverviewWidget::createMinimapForLayer(std::unique_ptr<CMap> & map, in
 		{
 			TerrainTile & tile = map->getTile(int3(x, y, layer));
 
-			ColorRGBA color = tile.terType->minimapUnblocked;
-			if (tile.blocked && (!tile.visitable))
-				color = tile.terType->minimapBlocked;
+			ColorRGBA color = tile.getTerrain()->minimapUnblocked;
+			if (tile.blocked() && !tile.visitable())
+				color = tile.getTerrain()->minimapBlocked;
 
 			if(drawPlayerElements)
 				// if object at tile is owned - it will be colored as its owner

+ 1 - 1
client/windows/CMarketWindow.cpp

@@ -262,7 +262,7 @@ void CMarketWindow::createAltarArtifacts(const IMarket * market, const CGHeroIns
 	const auto heroArts = altarArtifactsStorage->getAOHset();
 	heroArts->clickPressedCallback = [this, heroArts](const CArtPlace & artPlace, const Point & cursorPosition)
 	{
-		clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false);
+		clickPressedOnArtPlace(heroArts->getHero(), artPlace.slot, true, true, false, cursorPosition);
 	};
 	heroArts->showPopupCallback = [this, heroArts](CArtPlace & artPlace, const Point & cursorPosition)
 	{

+ 10 - 8
client/windows/CWindowWithArtifacts.cpp

@@ -71,7 +71,7 @@ const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact() const
 }
 
 void CWindowWithArtifacts::clickPressedOnArtPlace(const CGHeroInstance * hero, const ArtifactPosition & slot,
-	bool allowExchange, bool altarTrading, bool closeWindow)
+	bool allowExchange, bool altarTrading, bool closeWindow, const Point & cursorPosition)
 {
 	if(!LOCPLINT->makingTurn)
 		return;
@@ -85,8 +85,7 @@ void CWindowWithArtifacts::clickPressedOnArtPlace(const CGHeroInstance * hero, c
 	}
 	else if(GH.isKeyboardShiftDown())
 	{
-		if(ArtifactUtils::isSlotEquipment(slot))
-			GH.windows().createAndPushWindow<CHeroQuickBackpackWindow>(hero, slot);
+		showQuickBackpackWindow(hero, slot, cursorPosition);
 	}
 	else if(auto art = hero->getArt(slot))
 	{
@@ -134,6 +133,9 @@ void CWindowWithArtifacts::showQuickBackpackWindow(const CGHeroInstance * hero,
 	if(!settings["general"]["enableUiEnhancements"].Bool())
 		return;
 
+	if(!ArtifactUtils::isSlotEquipment(slot))
+		return;
+
 	GH.windows().createAndPushWindow<CHeroQuickBackpackWindow>(hero, slot);
 	auto backpackWindow = GH.windows().topWindow<CHeroQuickBackpackWindow>();
 	backpackWindow->moveTo(cursorPosition - Point(1, 1));
@@ -198,7 +200,7 @@ void CWindowWithArtifacts::markPossibleSlots() const
 				continue;
 
 			if(getHeroPickedArtifact() == hero || !std::dynamic_pointer_cast<CArtifactsOfHeroKingdom>(artSet))
-				artSet->markPossibleSlots(pickedArtInst->artType, hero->tempOwner == LOCPLINT->playerID);
+				artSet->markPossibleSlots(pickedArtInst->getType(), hero->tempOwner == LOCPLINT->playerID);
 		}
 	}
 }
@@ -219,7 +221,7 @@ bool CWindowWithArtifacts::checkSpecialArts(const CArtifactInstance & artInst, c
 			std::vector<std::shared_ptr<CComponent>>(1, std::make_shared<CComponent>(ComponentType::ARTIFACT, ArtifactID(ArtifactID::CATAPULT))));
 		return false;
 	}
-	if(isTrade && !artInst.artType->isTradable())
+	if(isTrade && !artInst.getType()->isTradable())
 	{
 		LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[21],
 			std::vector<std::shared_ptr<CComponent>>(1, std::make_shared<CComponent>(ComponentType::ARTIFACT, artId)));
@@ -240,7 +242,7 @@ void CWindowWithArtifacts::setCursorAnimation(const CArtifactInstance & artInst)
 	}
 	else
 	{
-		CCS->curh->dragAndDropCursor(AnimationPath::builtin("artifact"), artInst.artType->getIconIndex());
+		CCS->curh->dragAndDropCursor(AnimationPath::builtin("artifact"), artInst.getType()->getIconIndex());
 	}
 }
 
@@ -253,10 +255,10 @@ void CWindowWithArtifacts::putPickedArtifact(const CGHeroInstance & curHero, con
 
 	if(ArtifactUtils::isSlotBackpack(dstLoc.slot))
 	{
-		if(pickedArt->artType->isBig())
+		if(pickedArt->getType()->isBig())
 		{
 			// War machines cannot go to backpack
-			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % pickedArt->artType->getNameTranslated()));
+			LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % pickedArt->getType()->getNameTranslated()));
 		}
 		else
 		{

+ 1 - 1
client/windows/CWindowWithArtifacts.h

@@ -28,7 +28,7 @@ public:
 	const CGHeroInstance * getHeroPickedArtifact() const;
 	const CArtifactInstance * getPickedArtifact() const;
 	void clickPressedOnArtPlace(const CGHeroInstance * hero, const ArtifactPosition & slot,
-		bool allowExchange, bool altarTrading, bool closeWindow);
+		bool allowExchange, bool altarTrading, bool closeWindow, const Point & cursorPosition);
 	void swapArtifactAndClose(const CArtifactsOfHeroBase & artsInst, const ArtifactPosition & slot, const ArtifactLocation & dstLoc);
 	void showArtifactAssembling(const CArtifactsOfHeroBase & artsInst, CArtPlace & artPlace, const Point & cursorPosition) const;
 	void showQuickBackpackWindow(const CGHeroInstance * hero, const ArtifactPosition & slot, const Point & cursorPosition) const;

+ 2 - 2
client/windows/GUIClasses.cpp

@@ -896,7 +896,7 @@ CUniversityWindow::CItem::CItem(CUniversityWindow * _parent, int _ID, int X, int
 	pos.x += X;
 	pos.y += Y;
 
-	skill = std::make_shared<CSecSkillPlace>(Point(), CSecSkillPlace::ImageSize::LARGE, _ID, 1);
+	skill = std::make_shared<CSecSkillPlace>(Point(), CSecSkillPlace::ImageSize::MEDIUM, _ID, 1);
 	skill->setClickPressedCallback([this](const CComponentHolder&, const Point& cursorPosition)
 		{
 			bool skillKnown = parent->hero->getSecSkillLevel(ID);
@@ -1056,7 +1056,7 @@ CGarrisonWindow::CGarrisonWindow(const CArmedInstance * up, const CGHeroInstance
 		if(up->Slots().size() > 0)
 		{
 			titleText = CGI->generaltexth->allTexts[35];
-			boost::algorithm::replace_first(titleText, "%s", up->Slots().begin()->second->type->getNamePluralTranslated());
+			boost::algorithm::replace_first(titleText, "%s", up->Slots().begin()->second->getType()->getNamePluralTranslated());
 		}
 		else
 		{

+ 3 - 0
clientapp/icons/vcmiclient.desktop

@@ -1,8 +1,11 @@
 [Desktop Entry]
 Type=Application
 Name=VCMI Client
+Name[cs]=VCMI Klient
 GenericName=Strategy Game Engine
+GenericName[cs]=Engine strategické hry
 Comment=Open engine for Heroes of Might and Magic 3
+Comment[cs]=Open-source engine pro Heroes of Might and Magic III
 Icon=vcmiclient
 Exec=vcmiclient
 Categories=Game;StrategyGame;

+ 3 - 3
config/objects/pyramid.json

@@ -46,7 +46,7 @@
 						"spells" : [
 							"@gainedSpell"
 						],
-						"message" : [ 106, "%s." ], // Upon defeating monsters, you learn new spell
+						"message" : [ 106, "{%s}." ], // Upon defeating monsters, you learn new spell
 						"guards" : [
 							{ "amount" : 40, "type" : "goldGolem" },
 							{ "amount" : 10, "type" : "diamondGolem" },
@@ -63,10 +63,10 @@
 								}
 							]
 						},
-						"message" : [ 106, "%s. ", 108 ] // No Wisdom
+						"message" : [ 106, "{%s}. ", 108 ] // No Wisdom
 					},
 					{
-						"message" : [ 106, "%s. ", 109 ] // No spellbook
+						"message" : [ 106, "{%s}. ", 109 ] // No spellbook
 					}
 				]
 

+ 12 - 12
config/objects/shrine.json

@@ -46,10 +46,10 @@
 							"@gainedSpell"
 						],
 						"description" : "@core.genrltxt.355",
-						"message" : [ 127, "%s." ] // You learn new spell
+						"message" : [ 127, "{%s}." ] // You learn new spell
 					}
 				],
-				"onVisitedMessage" : [ 127, "%s. ", 174 ], // You already known this spell
+				"onVisitedMessage" : [ 127, "{%s}. ", 174 ], // You already known this spell
 				"onEmpty" : [
 					{
 						"limiter" : {
@@ -59,10 +59,10 @@
 								}
 							]
 						},
-						"message" : [ 127, "%s. ", 130 ] // No Wisdom
+						"message" : [ 127, "{%s}. ", 130 ] // No Wisdom
 					},
 					{
-						"message" : [ 127, "%s. ", 131 ] // No spellbook
+						"message" : [ 127, "{%s}. ", 131 ] // No spellbook
 					}
 				]
 			}
@@ -115,10 +115,10 @@
 							"@gainedSpell"
 						],
 						"description" : "@core.genrltxt.355",
-						"message" : [ 128, "%s." ] // You learn new spell
+						"message" : [ 128, "{%s}." ] // You learn new spell
 					}
 				],
-				"onVisitedMessage" : [ 128, "%s. ", 174 ], // You already known this spell
+				"onVisitedMessage" : [ 128, "{%s}. ", 174 ], // You already known this spell
 				"onEmpty" : [
 					{
 						"limiter" : {
@@ -128,10 +128,10 @@
 								}
 							]
 						},
-						"message" : [ 128, "%s. ", 130 ] // No Wisdom
+						"message" : [ 128, "{%s}. ", 130 ] // No Wisdom
 					},
 					{
-						"message" : [ 128, "%s. ", 131 ] // No spellbook
+						"message" : [ 128, "{%s}. ", 131 ] // No spellbook
 					}
 				]
 			}
@@ -184,10 +184,10 @@
 							"@gainedSpell"
 						],
 						"description" : "@core.genrltxt.355",
-						"message" : [ 129, "%s." ] // You learn new spell
+						"message" : [ 129, "{%s}." ] // You learn new spell
 					}
 				],
-				"onVisitedMessage" : [ 129, "%s. ", 174 ], // You already known this spell
+				"onVisitedMessage" : [ 129, "{%s}. ", 174 ], // You already known this spell
 				"onEmpty" : [
 					{
 						"limiter" : {
@@ -197,10 +197,10 @@
 								}
 							]
 						},
-						"message" : [ 129, "%s. ", 130 ] // No Wisdom
+						"message" : [ 129, "{%s}. ", 130 ] // No Wisdom
 					},
 					{
-						"message" : [ 129, "%s. ", 131 ] // No spellbook
+						"message" : [ 129, "{%s}. ", 131 ] // No spellbook
 					}
 				]
 			}

+ 13 - 0
docs/translators/Translations.md

@@ -56,6 +56,19 @@ This will export all strings from game into `Documents/My Games/VCMI/extracted/t
 
 To export maps and campaigns, use '/translate maps' command instead.
 
+### Video subtitles
+It's possible to add video subtitles. Create a JSON file in `video` folder of translation mod with the name of the video (e.g. `H3Intro.json`):
+```
+[
+    {
+        "timeStart" : 5.640, // start time, seconds
+        "timeEnd" : 8.120, // end time, seconds
+        "text" : " ... " // text to show during this period
+    },
+    ...
+]
+```
+
 ## Translating VCMI data
 
 VCMI contains several new strings, to cover functionality not existing in Heroes III. It can be roughly split into following parts:

+ 16 - 1
launcher/modManager/chroniclesextractor.cpp

@@ -106,7 +106,7 @@ void ChroniclesExtractor::createBaseMod() const
 		{ "author", "3DO" },
 		{ "version", "1.0" },
 		{ "contact", "vcmi.eu" },
-		{ "heroes", QJsonArray({"config/heroes/portraitsChronicles.json"}) },
+		{ "heroes", QJsonArray({"config/portraitsChronicles.json"}) },
 		{ "settings", QJsonObject({{"mapFormat", QJsonObject({{"chronicles", QJsonObject({{
 			{"supported", true},
 			{"portraits", QJsonObject({
@@ -123,6 +123,21 @@ void ChroniclesExtractor::createBaseMod() const
 	QFile jsonFile(dir.filePath("mod.json"));
     jsonFile.open(QFile::WriteOnly);
     jsonFile.write(QJsonDocument(mod).toJson());
+
+	for(auto & dataPath : VCMIDirs::get().dataPaths())
+	{
+		auto file = dataPath / "config" / "heroes" / "portraitsChronicles.json";
+		auto destFolder = VCMIDirs::get().userDataPath() / "Mods" / "chronicles" / "content" / "config";
+		if(boost::filesystem::exists(file))
+		{
+			boost::filesystem::create_directories(destFolder);
+#if BOOST_VERSION >= 107400
+			boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_options::overwrite_existing);
+#else
+			boost::filesystem::copy_file(file, destFolder / "portraitsChronicles.json", boost::filesystem::copy_option::overwrite_if_exists);
+#endif
+		}
+	}
 }
 
 void ChroniclesExtractor::createChronicleMod(int no)

+ 1 - 1
launcher/translation/czech.ts

@@ -749,7 +749,7 @@ Nainstalovat úspěšně stažené?</translation>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="859"/>
         <source>Online Lobby address</source>
-        <translation>Adresa online předsíně</translation>
+        <translation>Adresa online lobby</translation>
     </message>
     <message>
         <location filename="../settingsView/csettingsview_moc.ui" line="1079"/>

+ 1 - 1
launcher/vcmilauncher.desktop

@@ -5,7 +5,7 @@ GenericName=Strategy Game
 GenericName[cs]=Strategická hra
 GenericName[de]=Strategiespiel
 Comment=Open-source recreation of Heroes of Might & Magic III
-Comment[cs]=Spouštěč enginu s otevřeným kódem pro Heroes of Might and Magic III
+Comment[cs]=Open-source engine pro Heroes of Might and Magic III
 Comment[de]=Open-Source-Nachbau von Heroes of Might and Magic III
 Icon=vcmiclient
 Exec=vcmilauncher

+ 7 - 7
lib/CArtHandler.cpp

@@ -732,7 +732,7 @@ ArtifactPosition CArtifactSet::getArtPos(const ArtifactID & aid, bool onlyWorn,
 		for(const auto & artInfo : artifactsInBackpack)
 		{
 			const auto art = artInfo.getArt();
-			if(art && art->artType->getId() == aid)
+			if(art && art->getType()->getId() == aid)
 				return ArtifactPosition(backpackPositionIdx);
 			backpackPositionIdx++;
 		}
@@ -757,7 +757,7 @@ ArtifactPosition CArtifactSet::getArtPos(const CArtifactInstance * artInst) cons
 {
 	if(artInst)
 	{
-		for(const auto & slot : artInst->artType->getPossibleSlots().at(bearerType()))
+		for(const auto & slot : artInst->getType()->getPossibleSlots().at(bearerType()))
 			if(getArt(slot) == artInst)
 				return slot;
 
@@ -805,11 +805,11 @@ CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition &
 	};
 
 	putToSlot(slot, art, false);
-	if(art->artType->isCombined() && ArtifactUtils::isSlotEquipment(slot))
+	if(art->getType()->isCombined() && ArtifactUtils::isSlotEquipment(slot))
 	{
 		const CArtifactInstance * mainPart = nullptr;
 		for(const auto & part : art->getPartsInfo())
-			if(vstd::contains(part.art->artType->getPossibleSlots().at(bearerType()), slot)
+			if(vstd::contains(part.art->getType()->getPossibleSlots().at(bearerType()), slot)
 				&& (part.slot == ArtifactPosition::PRE_FIRST))
 			{
 				mainPart = part.art;
@@ -821,7 +821,7 @@ CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition &
 			if(part.art != mainPart)
 			{
 				auto partSlot = part.slot;
-				if(!part.art->artType->canBePutAt(this, partSlot))
+				if(!part.art->getType()->canBePutAt(this, partSlot))
 					partSlot = ArtifactUtils::getArtAnyPosition(this, part.art->getTypeId());
 
 				assert(ArtifactUtils::isSlotEquipment(partSlot));
@@ -995,7 +995,7 @@ void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler)
 		{
 			auto * artifact = ArtifactUtils::createArtifact(artifactID);
 			auto slot = ArtifactPosition::BACKPACK_START + artifactsInBackpack.size();
-			if(artifact->artType->canBePutAt(this, slot))
+			if(artifact->getType()->canBePutAt(this, slot))
 			{
 				auto artsMap = putArtifact(slot, artifact);
 				artifact->addPlacementMap(artsMap);
@@ -1036,7 +1036,7 @@ void CArtifactSet::serializeJsonSlot(JsonSerializeFormat & handler, const Artifa
 		{
 			auto * artifact = ArtifactUtils::createArtifact(artifactID.toEnum());
 
-			if(artifact->artType->canBePutAt(this, slot))
+			if(artifact->getType()->canBePutAt(this, slot))
 			{
 				auto artsMap = putArtifact(slot, artifact);
 				artifact->addPlacementMap(artsMap);

+ 17 - 12
lib/CArtifactInstance.cpp

@@ -20,12 +20,12 @@ VCMI_LIB_NAMESPACE_BEGIN
 void CCombinedArtifactInstance::addPart(CArtifactInstance * art, const ArtifactPosition & slot)
 {
 	auto artInst = static_cast<CArtifactInstance*>(this);
-	assert(vstd::contains_if(artInst->artType->getConstituents(),
+	assert(vstd::contains_if(artInst->getType()->getConstituents(),
 		[=](const CArtifact * partType)
 		{
 			return partType->getId() == art->getTypeId();
 		}));
-	assert(art->getParentNodes().size() == 1  &&  art->getParentNodes().front() == art->artType);
+	assert(art->getParentNodes().size() == 1  &&  art->getParentNodes().front() == art->getType());
 	partsInfo.emplace_back(art, slot);
 	artInst->attachTo(*art);
 }
@@ -77,7 +77,7 @@ void CGrowingArtifactInstance::growingUp()
 {
 	auto artInst = static_cast<CArtifactInstance*>(this);
 	
-	if(artInst->artType->isGrowing())
+	if(artInst->getType()->isGrowing())
 	{
 
 		auto bonus = std::make_shared<Bonus>();
@@ -86,7 +86,7 @@ void CGrowingArtifactInstance::growingUp()
 		bonus->duration = BonusDuration::COMMANDER_KILLED;
 		artInst->accumulateBonus(bonus);
 
-		for(const auto & bonus : artInst->artType->getBonusesPerLevel())
+		for(const auto & bonus : artInst->getType()->getBonusesPerLevel())
 		{
 			// Every n levels
 			if(artInst->valOfBonuses(BonusType::LEVEL_COUNTER) % bonus.first == 0)
@@ -94,7 +94,7 @@ void CGrowingArtifactInstance::growingUp()
 				artInst->accumulateBonus(std::make_shared<Bonus>(bonus.second));
 			}
 		}
-		for(const auto & bonus : artInst->artType->getThresholdBonuses())
+		for(const auto & bonus : artInst->getType()->getThresholdBonuses())
 		{
 			// At n level
 			if(artInst->valOfBonuses(BonusType::LEVEL_COUNTER) == bonus.first)
@@ -125,18 +125,23 @@ CArtifactInstance::CArtifactInstance()
 
 void CArtifactInstance::setType(const CArtifact * art)
 {
-	artType = art;
+	artTypeID = art->getId();
 	attachToSource(*art);
 }
 
 std::string CArtifactInstance::nodeName() const
 {
-	return "Artifact instance of " + (artType ? artType->getJsonKey() : std::string("uninitialized")) + " type";
+	return "Artifact instance of " + (getType() ? getType()->getJsonKey() : std::string("uninitialized")) + " type";
 }
 
 ArtifactID CArtifactInstance::getTypeId() const
 {
-	return artType->getId();
+	return artTypeID;
+}
+
+const CArtifact * CArtifactInstance::getType() const
+{
+	return artTypeID.toArtifact();
 }
 
 ArtifactInstanceID CArtifactInstance::getId() const
@@ -151,22 +156,22 @@ void CArtifactInstance::setId(ArtifactInstanceID id)
 
 bool CArtifactInstance::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) const
 {
-	return artType->canBePutAt(artSet, slot, assumeDestRemoved);
+	return getType()->canBePutAt(artSet, slot, assumeDestRemoved);
 }
 
 bool CArtifactInstance::isCombined() const
 {
-	return artType->isCombined();
+	return getType()->isCombined();
 }
 
 bool CArtifactInstance::isScroll() const
 {
-	return artType->isScroll();
+	return getType()->isScroll();
 }
 
 void CArtifactInstance::deserializationFix()
 {
-	setType(artType);
+	setType(artTypeID.toArtifact());
 	for(PartInfo & part : partsInfo)
 		attachTo(*part.art);
 }

+ 13 - 2
lib/CArtifactInstance.h

@@ -73,14 +73,15 @@ protected:
 	void init();
 
 	ArtifactInstanceID id;
+	ArtifactID artTypeID;
 public:
-	const CArtifact * artType = nullptr;
 
 	CArtifactInstance(const CArtifact * art);
 	CArtifactInstance();
 	void setType(const CArtifact * art);
 	std::string nodeName() const override;
 	ArtifactID getTypeId() const;
+	const CArtifact * getType() const;
 	ArtifactInstanceID getId() const;
 	void setId(ArtifactInstanceID id);
 
@@ -94,7 +95,17 @@ public:
 	{
 		h & static_cast<CBonusSystemNode&>(*this);
 		h & static_cast<CCombinedArtifactInstance&>(*this);
-		h & artType;
+		if (h.version >= Handler::Version::REMOVE_VLC_POINTERS)
+		{
+			h & artTypeID;
+		}
+		else
+		{
+			bool isNull = false;
+			h & isNull;
+			if (!isNull)
+				h & artTypeID;
+		}
 		h & id;
 		BONUS_TREE_DESERIALIZATION_FIX
 	}

+ 0 - 5
lib/CCreatureHandler.cpp

@@ -343,11 +343,6 @@ bool CCreature::isMyUpgrade(const CCreature *anotherCre) const
 	return vstd::contains(upgrades, anotherCre->getId());
 }
 
-bool CCreature::valid() const
-{
-	return this == (*VLC->creh)[idNumber];
-}
-
 std::string CCreature::nodeName() const
 {
 	return "\"" + getNamePluralTextID() + "\"";

+ 0 - 2
lib/CCreatureHandler.h

@@ -166,8 +166,6 @@ public:
 	static int estimateCreatureCount(ui32 countID); //reverse version of above function, returns middle of range
 	bool isMyUpgrade(const CCreature *anotherCre) const;
 
-	bool valid() const;
-
 	void addBonus(int val, BonusType type);
 	void addBonus(int val, BonusType type, BonusSubtypeID subtype);
 	std::string nodeName() const override;

+ 57 - 61
lib/CCreatureSet.cpp

@@ -48,7 +48,7 @@ const CCreature * CCreatureSet::getCreature(const SlotID & slot) const
 {
 	auto i = stacks.find(slot);
 	if (i != stacks.end())
-		return i->second->type;
+		return i->second->getCreature();
 	else
 		return nullptr;
 }
@@ -84,11 +84,10 @@ SlotID CCreatureSet::getSlotFor(const CreatureID & creature, ui32 slotsAmount) c
 
 SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const
 {
-	assert(c && c->valid());
+	assert(c);
 	for(const auto & elem : stacks)
 	{
-		assert(elem.second->type->valid());
-		if(elem.second->type == c)
+		if(elem.second->getType() == c)
 		{
 			return elem.first; //if there is already such creature we return its slot id
 		}
@@ -98,18 +97,16 @@ SlotID CCreatureSet::getSlotFor(const CCreature *c, ui32 slotsAmount) const
 
 bool CCreatureSet::hasCreatureSlots(const CCreature * c, const SlotID & exclude) const
 {
-	assert(c && c->valid());
+	assert(c);
 	for(const auto & elem : stacks) // elem is const
 	{
 		if(elem.first == exclude) // Check slot
 			continue;
 
-		if(!elem.second || !elem.second->type) // Check creature
+		if(!elem.second || !elem.second->getType()) // Check creature
 			continue;
 
-		assert(elem.second->type->valid());
-
-		if(elem.second->type == c)
+		if(elem.second->getType() == c)
 			return true;
 	}
 	return false;
@@ -117,7 +114,7 @@ bool CCreatureSet::hasCreatureSlots(const CCreature * c, const SlotID & exclude)
 
 std::vector<SlotID> CCreatureSet::getCreatureSlots(const CCreature * c, const SlotID & exclude, TQuantity ignoreAmount) const
 {
-	assert(c && c->valid());
+	assert(c);
 	std::vector<SlotID> result;
 
 	for(const auto & elem : stacks)
@@ -125,13 +122,12 @@ std::vector<SlotID> CCreatureSet::getCreatureSlots(const CCreature * c, const Sl
 		if(elem.first == exclude)
 			continue;
 
-		if(!elem.second || !elem.second->type || elem.second->type != c)
+		if(!elem.second || !elem.second->getType() || elem.second->getType() != c)
 			continue;
 
 		if(elem.second->count == ignoreAmount || elem.second->count < 1)
 			continue;
 
-		assert(elem.second->type->valid());
 		result.push_back(elem.first);
 	}
 	return result;
@@ -139,13 +135,13 @@ std::vector<SlotID> CCreatureSet::getCreatureSlots(const CCreature * c, const Sl
 
 bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmount) const
 {
-	assert(c && c->valid());
+	assert(c);
 	TQuantity max = 0;
 	auto min = std::numeric_limits<TQuantity>::max();
 
 	for(const auto & elem : stacks)
 	{
-		if(!elem.second || !elem.second->type || elem.second->type != c)
+		if(!elem.second || !elem.second->getType() || elem.second->getType() != c)
 			continue;
 
 		const auto count = elem.second->count;
@@ -153,7 +149,6 @@ bool CCreatureSet::isCreatureBalanced(const CCreature * c, TQuantity ignoreAmoun
 		if(count == ignoreAmount || count < 1)
 			continue;
 
-		assert(elem.second->type->valid());
 
 		if(count > max)
 			max = count;
@@ -214,7 +209,7 @@ TMapCreatureSlot CCreatureSet::getCreatureMap() const
 	// https://www.cplusplus.com/reference/map/map/key_comp/
 	for(const auto & pair : stacks)
 	{
-		const auto * creature = pair.second->type;
+		const auto * creature = pair.second->getCreature();
 		auto slot = pair.first;
 		auto lb = creatureMap.lower_bound(creature);
 
@@ -234,7 +229,7 @@ TCreatureQueue CCreatureSet::getCreatureQueue(const SlotID & exclude) const
 	{
 		if(pair.first == exclude)
 			continue;
-		creatureQueue.push(std::make_pair(pair.second->type, pair.first));
+		creatureQueue.push(std::make_pair(pair.second->getCreature(), pair.first));
 	}
 	return creatureQueue;
 }
@@ -262,10 +257,10 @@ bool CCreatureSet::mergeableStacks(std::pair<SlotID, SlotID> & out, const SlotID
 	//try to match creature to our preferred stack
 	if(preferable.validSlot() &&  vstd::contains(stacks, preferable))
 	{
-		const CCreature *cr = stacks.find(preferable)->second->type;
+		const CCreature *cr = stacks.find(preferable)->second->getCreature();
 		for(const auto & elem : stacks)
 		{
-			if(cr == elem.second->type && elem.first != preferable)
+			if(cr == elem.second->getType() && elem.first != preferable)
 			{
 				out.first = preferable;
 				out.second = elem.first;
@@ -278,7 +273,7 @@ bool CCreatureSet::mergeableStacks(std::pair<SlotID, SlotID> & out, const SlotID
 	{
 		for(const auto & elem : stacks)
 		{
-			if(stack.second->type == elem.second->type && stack.first != elem.first)
+			if(stack.second->getType() == elem.second->getType() && stack.first != elem.first)
 			{
 				out.first = stack.first;
 				out.second = elem.first;
@@ -328,7 +323,7 @@ void CCreatureSet::addToSlot(const SlotID & slot, CStackInstance * stack, bool a
 	{
 		putStack(slot, stack);
 	}
-	else if(allowMerging && stack->type == getCreature(slot))
+	else if(allowMerging && stack->getType() == getCreature(slot))
 	{
 		joinStack(slot, stack);
 	}
@@ -514,7 +509,7 @@ void CCreatureSet::putStack(const SlotID & slot, CStackInstance * stack)
 void CCreatureSet::joinStack(const SlotID & slot, CStackInstance * stack)
 {
 	[[maybe_unused]] const CCreature *c = getCreature(slot);
-	assert(c == stack->type);
+	assert(c == stack->getType());
 	assert(c);
 
 	//TODO move stuff
@@ -577,9 +572,9 @@ bool CCreatureSet::canBeMergedWith(const CCreatureSet &cs, bool allowMergingStac
 		std::set<const CCreature*> cresToAdd;
 		for(const auto & elem : cs.stacks)
 		{
-			SlotID dest = getSlotFor(elem.second->type);
+			SlotID dest = getSlotFor(elem.second->getCreature());
 			if(!dest.validSlot() || hasStackAtSlot(dest))
-				cresToAdd.insert(elem.second->type);
+				cresToAdd.insert(elem.second->getCreature());
 		}
 		return cresToAdd.size() <= freeSlots;
 	}
@@ -590,13 +585,13 @@ bool CCreatureSet::canBeMergedWith(const CCreatureSet &cs, bool allowMergingStac
 
 		//get types of creatures that need their own slot
 		for(const auto & elem : cs.stacks)
-			if ((j = cres.getSlotFor(elem.second->type)).validSlot())
-				cres.addToSlot(j, elem.second->type->getId(), 1, true);  //merge if possible
+			if ((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
+				cres.addToSlot(j, elem.second->getId(), 1, true);  //merge if possible
 			//cres.addToSlot(elem.first, elem.second->type->getId(), 1, true);
 		for(const auto & elem : stacks)
 		{
-			if ((j = cres.getSlotFor(elem.second->type)).validSlot())
-				cres.addToSlot(j, elem.second->type->getId(), 1, true);  //merge if possible
+			if ((j = cres.getSlotFor(elem.second->getCreature())).validSlot())
+				cres.addToSlot(j, elem.second->getId(), 1, true);  //merge if possible
 			else
 				return false; //no place found
 		}
@@ -693,7 +688,7 @@ void CStackInstance::init()
 {
 	experience = 0;
 	count = 0;
-	type = nullptr;
+	setType(nullptr);
 	_armyObj = nullptr;
 	setNodeType(STACK_INSTANCE);
 }
@@ -707,7 +702,7 @@ int CStackInstance::getExpRank() const
 {
 	if (!VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
 		return 0;
-	int tier = type->getLevel();
+	int tier = getType()->getLevel();
 	if (vstd::iswithin(tier, 1, 7))
 	{
 		for(int i = static_cast<int>(VLC->creh->expRanks[tier].size()) - 2; i > -1; --i) //sic!
@@ -730,12 +725,12 @@ int CStackInstance::getExpRank() const
 
 int CStackInstance::getLevel() const
 {
-	return std::max(1, static_cast<int>(type->getLevel()));
+	return std::max(1, static_cast<int>(getType()->getLevel()));
 }
 
 void CStackInstance::giveStackExp(TExpType exp)
 {
-	int level = type->getLevel();
+	int level = getType()->getLevel();
 	if (!vstd::iswithin(level, 1, 7))
 		level = 0;
 
@@ -756,17 +751,17 @@ void CStackInstance::setType(const CreatureID & creID)
 
 void CStackInstance::setType(const CCreature *c)
 {
-	if(type)
+	if(getCreature())
 	{
-		detachFromSource(*type);
-		if (type->isMyUpgrade(c) && VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
+		detachFromSource(*getCreature());
+		if (getCreature()->isMyUpgrade(c) && VLC->engineSettings()->getBoolean(EGameSettings::MODULE_STACK_EXPERIENCE))
 			experience = static_cast<TExpType>(experience * VLC->creh->expAfterUpgrade / 100.0);
 	}
 
 	CStackBasicDescriptor::setType(c);
 
-	if(type)
-		attachToSource(*type);
+	if(getCreature())
+		attachToSource(*getCreature());
 }
 std::string CStackInstance::bonusToString(const std::shared_ptr<Bonus>& bonus, bool description) const
 {
@@ -808,7 +803,7 @@ bool CStackInstance::valid(bool allowUnrandomized) const
 {
 	if(!randomStack)
 	{
-		return (type && type == type->getId().toEntity(VLC));
+		return (getType() && getType() == getId().toEntity(VLC));
 	}
 	else
 		return allowUnrandomized;
@@ -818,8 +813,8 @@ std::string CStackInstance::nodeName() const
 {
 	std::ostringstream oss;
 	oss << "Stack of " << count << " of ";
-	if(type)
-		oss << type->getNamePluralTextID();
+	if(getType())
+		oss << getType()->getNamePluralTextID();
 	else
 		oss << "[UNDEFINED TYPE]";
 
@@ -841,21 +836,21 @@ void CStackInstance::deserializationFix()
 
 CreatureID CStackInstance::getCreatureID() const
 {
-	if(type)
-		return type->getId();
+	if(getType())
+		return getType()->getId();
 	else
 		return CreatureID::NONE;
 }
 
 std::string CStackInstance::getName() const
 {
-	return (count > 1) ? type->getNamePluralTranslated() : type->getNameSingularTranslated();
+	return (count > 1) ? getType()->getNamePluralTranslated() : getType()->getNameSingularTranslated();
 }
 
 ui64 CStackInstance::getPower() const
 {
-	assert(type);
-	return static_cast<ui64>(type->getAIValue()) * count;
+	assert(getType());
+	return static_cast<ui64>(getType()->getAIValue()) * count;
 }
 
 ArtBearer::ArtBearer CStackInstance::bearerType() const
@@ -899,7 +894,7 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler)
 	else
 	{
 		//type set by CStackBasicDescriptor::serializeJson
-		if(type == nullptr)
+		if(getType() == nullptr)
 		{
 			uint8_t level = 0;
 			uint8_t upgrade = 0;
@@ -914,8 +909,8 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler)
 
 FactionID CStackInstance::getFactionID() const
 {
-	if(type)
-		return type->getFactionID();
+	if(getType())
+		return getType()->getFactionID();
 		
 	return FactionID::NEUTRAL;
 }
@@ -943,7 +938,7 @@ void CCommanderInstance::init()
 	experience = 0;
 	level = 1;
 	count = 1;
-	type = nullptr;
+	setType(nullptr);
 	_armyObj = nullptr;
 	setNodeType (CBonusSystemNode::COMMANDER);
 	secondarySkills.resize (ECommander::SPELL_POWER + 1);
@@ -998,24 +993,29 @@ bool CCommanderInstance::gainsLevel() const
 CStackBasicDescriptor::CStackBasicDescriptor() = default;
 
 CStackBasicDescriptor::CStackBasicDescriptor(const CreatureID & id, TQuantity Count):
-	type(id.toCreature()),
+	typeID(id),
 	count(Count)
 {
 }
 
 CStackBasicDescriptor::CStackBasicDescriptor(const CCreature *c, TQuantity Count)
-	: type(c), count(Count)
+	: typeID(c ? c->getId() : CreatureID()), count(Count)
 {
 }
 
+const CCreature * CStackBasicDescriptor::getCreature() const
+{
+	return typeID.toCreature();
+}
+
 const Creature * CStackBasicDescriptor::getType() const
 {
-	return type;
+	return typeID.toEntity(VLC);
 }
 
 CreatureID CStackBasicDescriptor::getId() const
 {
-	return type->getId();
+	return typeID;
 }
 
 TQuantity CStackBasicDescriptor::getCount() const
@@ -1023,18 +1023,14 @@ TQuantity CStackBasicDescriptor::getCount() const
 	return count;
 }
 
-
 void CStackBasicDescriptor::setType(const CCreature * c)
 {
-	type = c;
+	typeID = c ? c->getId() : CreatureID();
 }
 
 bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r)
 {
-	return (!l.type && !r.type)
-	|| (l.type && r.type
-		&& l.type->getId() == r.type->getId()
-		&& l.count == r.count);
+	return l.typeID == r.typeID && l.count == r.count;
 }
 
 void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
@@ -1043,9 +1039,9 @@ void CStackBasicDescriptor::serializeJson(JsonSerializeFormat & handler)
 
 	if(handler.saving)
 	{
-		if(type)
+		if(typeID.hasValue())
 		{
-			std::string typeName = type->getJsonKey();
+			std::string typeName = typeID.toEntity(VLC)->getJsonKey();
 			handler.serializeString("type", typeName);
 		}
 	}

+ 7 - 16
lib/CCreatureSet.h

@@ -31,8 +31,8 @@ class JsonSerializeFormat;
 
 class DLL_LINKAGE CStackBasicDescriptor
 {
+	CreatureID typeID;
 public:
-	const CCreature *type = nullptr;
 	TQuantity count = -1; //exact quantity or quantity ID from CCreature::getQuantityID when getting info about enemy army
 
 	CStackBasicDescriptor();
@@ -41,29 +41,20 @@ public:
 	virtual ~CStackBasicDescriptor() = default;
 
 	const Creature * getType() const;
+	const CCreature * getCreature() const;
 	CreatureID getId() const;
 	TQuantity getCount() const;
 
 	virtual void setType(const CCreature * c);
-	
+
 	friend bool operator== (const CStackBasicDescriptor & l, const CStackBasicDescriptor & r);
 
 	template <typename Handler> void serialize(Handler &h)
 	{
-		if(h.saving)
-		{
-			auto idNumber = type ? type->getId() : CreatureID(CreatureID::NONE);
-			h & idNumber;
-		}
-		else
-		{
-			CreatureID idNumber;
-			h & idNumber;
-			if(idNumber != CreatureID::NONE)
-				setType(dynamic_cast<const CCreature*>(VLC->creatures()->getById(idNumber)));
-			else
-				type = nullptr;
-		}
+		h & typeID;
+		if(!h.saving)
+			setType(typeID.toCreature());
+
 		h & count;
 	}
 

+ 6 - 6
lib/CGameInfoCallback.cpp

@@ -345,10 +345,10 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 
 			for(auto & elem : info.army)
 			{
-				if(static_cast<int>(elem.second.type->getAIValue()) > maxAIValue)
+				if(static_cast<int>(elem.second.getCreature()->getAIValue()) > maxAIValue)
 				{
-					maxAIValue = elem.second.type->getAIValue();
-					mostStrong = elem.second.type;
+					maxAIValue = elem.second.getCreature()->getAIValue();
+					mostStrong = elem.second.getCreature();
 				}
 			}
 
@@ -357,7 +357,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 			else
 				for(auto & elem : info.army)
 				{
-					elem.second.type = mostStrong;
+					elem.second.setType(mostStrong);
 				}
 		};
 
@@ -390,7 +390,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 
 			if(nullptr != mostStrong) //possible, faction may have no creatures at all
 				for(auto & elem : info.army)
-					elem.second.type = mostStrong;
+					elem.second.setType(mostStrong);
 		};
 
 
@@ -630,7 +630,7 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu
 	{
 		const TerrainTile *tile = getTile(t->bestLocation(), false);
 
-		if(!tile || !tile->terType->isWater())
+		if(!tile || !tile->isWater())
 			return EBuildingState::NO_WATER; //lack of water
 	}
 

+ 9 - 9
lib/CStack.cpp

@@ -28,7 +28,7 @@ CStack::CStack(const CStackInstance * Base, const PlayerColor & O, int I, Battle
 	CBonusSystemNode(STACK_BATTLE),
 	base(Base),
 	ID(I),
-	type(Base->type),
+	typeID(Base->getId()),
 	baseAmount(Base->count),
 	owner(O),
 	slot(S),
@@ -48,7 +48,7 @@ CStack::CStack():
 CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I, BattleSide Side, const SlotID & S):
 	CBonusSystemNode(STACK_BATTLE),
 	ID(I),
-	type(stack->type),
+	typeID(stack->getId()),
 	baseAmount(stack->count),
 	owner(O),
 	slot(S),
@@ -60,7 +60,7 @@ CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I
 void CStack::localInit(BattleInfo * battleInfo)
 {
 	battle = battleInfo;
-	assert(type);
+	assert(typeID.hasValue());
 
 	exportBonuses();
 	if(base) //stack originating from "real" stack in garrison -> attach to it
@@ -72,7 +72,7 @@ void CStack::localInit(BattleInfo * battleInfo)
 		CArmedInstance * army = battle->battleGetArmyObject(side);
 		assert(army);
 		attachTo(*army);
-		attachToSource(*type);
+		attachToSource(*typeID.toCreature());
 	}
 	nativeTerrain = getNativeTerrain(); //save nativeTerrain in the variable on the battle start to avoid dead lock
 	CUnitState::localInit(this); //it causes execution of the CStack::isOnNativeTerrain where nativeTerrain will be considered
@@ -164,8 +164,8 @@ std::string CStack::nodeName() const
 	std::ostringstream oss;
 	oss << owner.toString();
 	oss << " battle stack [" << ID << "]: " << getCount() << " of ";
-	if(type)
-		oss << type->getNamePluralTextID();
+	if(typeID.hasValue())
+		oss << typeID.toEntity(VLC)->getNamePluralTextID();
 	else
 		oss << "[UNDEFINED TYPE]";
 
@@ -304,7 +304,7 @@ bool CStack::isMeleeAttackPossible(const battle::Unit * attacker, const battle::
 
 std::string CStack::getName() const
 {
-	return (getCount() == 1) ? type->getNameSingularTranslated() : type->getNamePluralTranslated(); //War machines can't use base
+	return (getCount() == 1) ? typeID.toEntity(VLC)->getNameSingularTranslated() : typeID.toEntity(VLC)->getNamePluralTranslated(); //War machines can't use base
 }
 
 bool CStack::canBeHealed() const
@@ -326,7 +326,7 @@ bool CStack::isOnTerrain(TerrainId terrain) const
 
 const CCreature * CStack::unitType() const
 {
-	return type;
+	return typeID.toCreature();
 }
 
 int32_t CStack::unitBaseAmount() const
@@ -352,7 +352,7 @@ bool CStack::unitHasAmmoCart(const battle::Unit * unit) const
 	const auto * ownerHero = battle->battleGetOwnerHero(unit);
 	if(ownerHero && ownerHero->artifactsWorn.find(ArtifactPosition::MACH2) != ownerHero->artifactsWorn.end())
 	{
-		if(battle->battleGetOwnerHero(unit)->artifactsWorn.at(ArtifactPosition::MACH2).artifact->artType->getId() == ArtifactID::AMMO_CART)
+		if(battle->battleGetOwnerHero(unit)->artifactsWorn.at(ArtifactPosition::MACH2).artifact->getTypeId() == ArtifactID::AMMO_CART)
 		{
 			return true;
 		}

+ 3 - 3
lib/CStack.h

@@ -27,7 +27,7 @@ class DLL_LINKAGE CStack : public CBonusSystemNode, public battle::CUnitState, p
 {
 private:
 	ui32 ID = -1; //unique ID of stack
-	const CCreature * type = nullptr;
+	CreatureID typeID;
 	TerrainId nativeTerrain; //tmp variable to save native terrain value on battle init
 	ui32 baseAmount = -1;
 
@@ -98,7 +98,7 @@ public:
 		//stackState is not serialized here
 		assert(isIndependentNode());
 		h & static_cast<CBonusSystemNode&>(*this);
-		h & type;
+		h & typeID;
 		h & ID;
 		h & baseAmount;
 		h & owner;
@@ -133,7 +133,7 @@ public:
 			else if(!army || extSlot == SlotID() || !army->hasStackAtSlot(extSlot))
 			{
 				base = nullptr;
-				logGlobal->warn("%s doesn't have a base stack!", type->getNameSingularTranslated());
+				logGlobal->warn("%s doesn't have a base stack!", typeID.toEntity(VLC)->getNameSingularTranslated());
 			}
 			else
 			{

+ 5 - 5
lib/IGameCallback.cpp

@@ -72,7 +72,7 @@ void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
 			for (int yd = 0; yd < gs->map->height; yd++)
 			{
 				tinfo = getTile(int3 (xd,yd,zd));
-				if (tinfo->terType->isLand() && tinfo->terType->isPassable() && !tinfo->blocked) //land and free
+				if (tinfo->isLand() && tinfo->getTerrain()->isPassable() && !tinfo->blocked()) //land and free
 					tiles.emplace_back(xd, yd, zd);
 			}
 		}
@@ -149,14 +149,14 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3> & tiles, std:
 	}
 }
 
-void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact *> & out, vstd::RNG & rand)
+void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector<ArtifactID> & out, vstd::RNG & rand)
 {
 	for (int j = 0; j < 3 ; j++)
-		out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_TREASURE).toArtifact());
+		out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_TREASURE));
 	for (int j = 0; j < 3 ; j++)
-		out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_MINOR).toArtifact());
+		out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_MINOR));
 
-	out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_MAJOR).toArtifact());
+	out.push_back(gameState()->pickRandomArtifact(rand, CArtifact::ART_MAJOR));
 }
 
 void CPrivilegedInfoCallback::getAllowedSpells(std::vector<SpellID> & out, std::optional<ui16> level)

+ 1 - 1
lib/IGameCallback.h

@@ -75,7 +75,7 @@ public:
 	void getAllTiles(std::unordered_set<int3> &tiles, std::optional<PlayerColor> player, int level, std::function<bool(const TerrainTile *)> filter) const;
 
 	//gives 3 treasures, 3 minors, 1 major -> used by Black Market and Artifact Merchant
-	void pickAllowedArtsSet(std::vector<const CArtifact *> & out, vstd::RNG & rand);
+	void pickAllowedArtsSet(std::vector<ArtifactID> & out, vstd::RNG & rand);
 	void getAllowedSpells(std::vector<SpellID> &out, std::optional<ui16> level = std::nullopt);
 
 	void saveCommonState(CSaveFile &out) const; //stores GS and VLC

+ 13 - 3
lib/StartInfo.h

@@ -146,7 +146,7 @@ struct DLL_LINKAGE StartInfo : public Serializeable
 	using TPlayerInfos = std::map<PlayerColor, PlayerSettings>;
 	TPlayerInfos playerInfos; //color indexed
 
-	std::string startTimeIso8601;
+	time_t startTime;
 	std::string fileURI;
 	SimturnsInfo simturnsInfo;
 	TurnTimerInfo turnTimerInfo;
@@ -180,7 +180,17 @@ struct DLL_LINKAGE StartInfo : public Serializeable
 			h & oldSeeds;
 			h & oldSeeds;
 		}
-		h & startTimeIso8601;
+		if (h.version < Handler::Version::FOLDER_NAME_REWORK)
+		{
+			std::string startTimeLegacy;
+			h & startTimeLegacy;
+			struct std::tm tm;
+			std::istringstream ss(startTimeLegacy);
+			ss >> std::get_time(&tm, "%Y%m%dT%H%M%S");
+			startTime = mktime(&tm);
+		}
+		else
+			h & startTime;
 		h & fileURI;
 		h & simturnsInfo;
 		h & turnTimerInfo;
@@ -193,7 +203,7 @@ struct DLL_LINKAGE StartInfo : public Serializeable
 	StartInfo()
 		: mode(EStartMode::INVALID)
 		, difficulty(1)
-		, startTimeIso8601(vstd::getDateTimeISO8601Basic(std::time(nullptr)))
+		, startTime(std::time(nullptr))
 	{
 
 	}

+ 1 - 1
lib/battle/BattleInfo.cpp

@@ -303,7 +303,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
 
 		if(nullptr != warMachineArt && hex.isValid())
 		{
-			CreatureID cre = warMachineArt->artType->getWarMachine();
+			CreatureID cre = warMachineArt->getType()->getWarMachine();
 
 			if(cre != CreatureID::NONE)
 				currentBattle->generateNewStack(currentBattle->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex);

+ 6 - 6
lib/bonuses/Limiters.cpp

@@ -75,7 +75,7 @@ static const CCreature * retrieveCreature(const CBonusSystemNode *node)
 	default:
 		const CStackInstance * csi = retrieveStackInstance(node);
 		if(csi)
-			return csi->type;
+			return csi->getCreature();
 		return nullptr;
 	}
 }
@@ -103,25 +103,25 @@ ILimiter::EDecision CCreatureTypeLimiter::limit(const BonusLimitationContext &co
 	if(!c)
 		return ILimiter::EDecision::DISCARD;
 	
-	auto accept =  c->getId() == creature->getId() || (includeUpgrades && creature->isMyUpgrade(c));
+	auto accept =  c->getId() == creatureID || (includeUpgrades && creatureID.toCreature()->isMyUpgrade(c));
 	return accept ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 	//drop bonus if it's not our creature and (we don`t check upgrades or its not our upgrade)
 }
 
 CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool IncludeUpgrades)
-	: creature(&creature_), includeUpgrades(IncludeUpgrades)
+	: creatureID(creature_.getId()), includeUpgrades(IncludeUpgrades)
 {
 }
 
 void CCreatureTypeLimiter::setCreature(const CreatureID & id)
 {
-	creature = id.toCreature();
+	creatureID = id;
 }
 
 std::string CCreatureTypeLimiter::toString() const
 {
 	boost::format fmt("CCreatureTypeLimiter(creature=%s, includeUpgrades=%s)");
-	fmt % creature->getJsonKey() % (includeUpgrades ? "true" : "false");
+	fmt % creatureID.toEntity(VLC)->getJsonKey() % (includeUpgrades ? "true" : "false");
 	return fmt.str();
 }
 
@@ -130,7 +130,7 @@ JsonNode CCreatureTypeLimiter::toJsonNode() const
 	JsonNode root;
 
 	root["type"].String() = "CREATURE_TYPE_LIMITER";
-	root["parameters"].Vector().emplace_back(creature->getJsonKey());
+	root["parameters"].Vector().emplace_back(creatureID.toEntity(VLC)->getJsonKey());
 	root["parameters"].Vector().emplace_back(includeUpgrades);
 
 	return root;

+ 2 - 2
lib/bonuses/Limiters.h

@@ -94,7 +94,7 @@ public:
 class DLL_LINKAGE CCreatureTypeLimiter : public ILimiter //affect only stacks of given creature (and optionally it's upgrades)
 {
 public:
-	const CCreature * creature = nullptr;
+	CreatureID creatureID;
 	bool includeUpgrades = false;
 
 	CCreatureTypeLimiter() = default;
@@ -108,7 +108,7 @@ public:
 	template <typename Handler> void serialize(Handler &h)
 	{
 		h & static_cast<ILimiter&>(*this);
-		h & creature;
+		h & creatureID;
 		h & includeUpgrades;
 	}
 };

+ 12 - 12
lib/gameState/CGameState.cpp

@@ -437,10 +437,10 @@ void CGameState::initGrailPosition()
 				for(int y = BORDER_WIDTH; y < map->height - BORDER_WIDTH; y++)
 				{
 					const TerrainTile &t = map->getTile(int3(x, y, z));
-					if(!t.blocked
-						&& !t.visitable
-						&& t.terType->isLand()
-						&& t.terType->isPassable()
+					if(!t.blocked()
+					   && !t.visitable()
+						&& t.isLand()
+						&& t.getTerrain()->isPassable()
 						&& (int)map->grailPos.dist2dSQ(int3(x, y, z)) <= (map->grailRadius * map->grailRadius))
 						allowedPos.emplace_back(x, y, z);
 				}
@@ -608,7 +608,7 @@ void CGameState::initHeroes()
 	{
 		assert(map->isInTheMap(hero->visitablePos()));
 		const auto & tile = map->getTile(hero->visitablePos());
-		if (tile.terType->isWater())
+		if (tile.isWater())
 		{
 			auto handler = VLC->objtypeh->getHandlerFor(Obj::BOAT, hero->getBoatType().getNum());
 			auto boat = dynamic_cast<CGBoat*>(handler->create(callback, nullptr));
@@ -1074,10 +1074,10 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, vstd::RNG & rand)
 	if(map->isCoastalTile(tile)) //coastal tile is always ground
 		return BattleField(*VLC->identifiers()->getIdentifier("core", "battlefield.sand_shore"));
 	
-	if (t.terType->battleFields.empty())
-		throw std::runtime_error("Failed to find battlefield for terrain " + t.terType->getJsonKey());
+	if (t.getTerrain()->battleFields.empty())
+		throw std::runtime_error("Failed to find battlefield for terrain " + t.getTerrain()->getJsonKey());
 
-	return BattleField(*RandomGeneratorUtil::nextItem(t.terType->battleFields, rand));
+	return BattleField(*RandomGeneratorUtil::nextItem(t.getTerrain()->battleFields, rand));
 }
 
 void CGameState::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo &out) const
@@ -1091,7 +1091,7 @@ void CGameState::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, Upg
 UpgradeInfo CGameState::fillUpgradeInfo(const CStackInstance &stack) const
 {
 	UpgradeInfo ret;
-	const CCreature *base = stack.type;
+	const CCreature *base = stack.getCreature();
 
 	if (stack.armyObj->ID == Obj::HERO)
 	{
@@ -1171,7 +1171,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 		return guards;
 
 	const TerrainTile &posTile = map->getTile(pos);
-	if (posTile.visitable)
+	if (posTile.visitable())
 	{
 		for (CGObjectInstance* obj : posTile.visitableObjects)
 		{
@@ -1190,7 +1190,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 			if (map->isInTheMap(pos))
 			{
 				const auto & tile = map->getTile(pos);
-				if (tile.visitable && (tile.isWater() == posTile.isWater()))
+				if (tile.visitable() && (tile.isWater() == posTile.isWater()))
 				{
 					for (CGObjectInstance* obj : tile.visitableObjects)
 					{
@@ -1571,7 +1571,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 			{
 				for(const auto & it : elem->Slots())
 				{
-					CreatureID toCmp = it.second->type->getId(); //ID of creature we should compare with the best one
+					CreatureID toCmp = it.second->getId(); //ID of creature we should compare with the best one
 					if(bestCre == CreatureID::NONE || bestCre.toEntity(VLC)->getAIValue() < toCmp.toEntity(VLC)->getAIValue())
 					{
 						bestCre = toCmp;

+ 5 - 5
lib/gameState/CGameStateCampaign.cpp

@@ -137,18 +137,18 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr
 
 				ArtifactLocation al(hero.hero->id, artifactPosition);
 
-				bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
+				bool takeable = travelOptions.artifactsKeptByHero.count(art->getTypeId());
 				bool locked = hero.hero->getSlot(al.slot)->locked;
 
 				if (!locked && takeable)
 				{
-					logGlobal->debug("Artifact %s from slot %d of hero %s will be transferred to next scenario", art->artType->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName());
+					logGlobal->debug("Artifact %s from slot %d of hero %s will be transferred to next scenario", art->getType()->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName());
 					hero.transferrableArtifacts.push_back(artifactPosition);
 				}
 
 				if (!locked && !takeable)
 				{
-					logGlobal->debug("Removing artifact %s from slot %d of hero %s", art->artType->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName());
+					logGlobal->debug("Removing artifact %s from slot %d of hero %s", art->getType()->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName());
 					gameState->map->removeArtifactInstance(*hero.hero, al.slot);
 					return true;
 				}
@@ -424,12 +424,12 @@ void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelO
 		{
 			auto * artifact = donorHero->getArt(artLocation);
 
-			logGlobal->debug("Removing artifact %s from slot %d of hero %s for transfer", artifact->artType->getJsonKey(), artLocation.getNum(), donorHero->getHeroTypeName());
+			logGlobal->debug("Removing artifact %s from slot %d of hero %s for transfer", artifact->getType()->getJsonKey(), artLocation.getNum(), donorHero->getHeroTypeName());
 			gameState->map->removeArtifactInstance(*donorHero, artLocation);
 
 			if (receiver)
 			{
-				logGlobal->debug("Granting artifact %s to hero %s for transfer", artifact->artType->getJsonKey(), receiver->getHeroTypeName());
+				logGlobal->debug("Granting artifact %s to hero %s for transfer", artifact->getType()->getJsonKey(), receiver->getHeroTypeName());
 
 				const auto slot = ArtifactUtils::getArtAnyPosition(receiver, artifact->getTypeId());
 				if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))

+ 1 - 1
lib/gameState/GameStatistics.cpp

@@ -291,7 +291,7 @@ float Statistic::getMapExploredRatio(const CGameState * gs, PlayerColor player)
 			{
 				TerrainTile tile = gs->map->getTile(int3(x, y, layer));
 
-				if(tile.blocked && (!tile.visitable))
+				if(tile.blocked() && !tile.visitable())
 					continue;
 
 				if(gs->isVisible(int3(x, y, layer), player))

+ 3 - 3
lib/gameState/InfoAboutArmy.cpp

@@ -26,7 +26,7 @@ ArmyDescriptor::ArmyDescriptor(const CArmedInstance *army, bool detailed)
 		if(detailed)
 			(*this)[elem.first] = *elem.second;
 		else
-			(*this)[elem.first] = CStackBasicDescriptor(elem.second->type, (int)elem.second->getQuantityID());
+			(*this)[elem.first] = CStackBasicDescriptor(elem.second->getCreature(), (int)elem.second->getQuantityID());
 	}
 }
 
@@ -42,12 +42,12 @@ int ArmyDescriptor::getStrength() const
 	if(isDetailed)
 	{
 		for(const auto & elem : *this)
-			ret += elem.second.type->getAIValue() * elem.second.count;
+			ret += elem.second.getType()->getAIValue() * elem.second.count;
 	}
 	else
 	{
 		for(const auto & elem : *this)
-			ret += elem.second.type->getAIValue() * CCreature::estimateCreatureCount(elem.second.count);
+			ret += elem.second.getType()->getAIValue() * CCreature::estimateCreatureCount(elem.second.count);
 	}
 	return static_cast<int>(ret);
 }

+ 3 - 3
lib/json/JsonRandom.cpp

@@ -485,13 +485,13 @@ VCMI_LIB_NAMESPACE_BEGIN
 		else
 			logMod->warn("Failed to select suitable random creature!");
 
-		stack.type = pickedCreature.toCreature();
+		stack.setType(pickedCreature.toCreature());
 		stack.count = loadValue(value, rng, variables);
-		if (!value["upgradeChance"].isNull() && !stack.type->upgrades.empty())
+		if (!value["upgradeChance"].isNull() && !stack.getCreature()->upgrades.empty())
 		{
 			if (int(value["upgradeChance"].Float()) > rng.nextInt(99)) // select random upgrade
 			{
-				stack.type = RandomGeneratorUtil::nextItem(stack.type->upgrades, rng)->toCreature();
+				stack.setType(RandomGeneratorUtil::nextItem(stack.getCreature()->upgrades, rng)->toCreature());
 			}
 		}
 		return stack;

+ 1 - 1
lib/mapObjectConstructors/CommonConstructors.cpp

@@ -101,7 +101,7 @@ void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const
 
 void CTownInstanceConstructor::randomizeObject(CGTownInstance * object, vstd::RNG & rng) const
 {
-	auto templ = getOverride(object->cb->getTile(object->pos)->terType->getId(), object);
+	auto templ = getOverride(object->cb->getTile(object->pos)->getTerrainID(), object);
 	if(templ)
 		object->appearance = templ;
 }

+ 1 - 1
lib/mapObjectConstructors/DwellingInstanceConstructor.cpp

@@ -102,7 +102,7 @@ void DwellingInstanceConstructor::randomizeObject(CGDwelling * dwelling, vstd::R
 		JsonRandom::Variables emptyVariables;
 		for(auto & stack : randomizer.loadCreatures(guards, rng, emptyVariables))
 		{
-			dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->getId(), stack.count));
+			dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.getId(), stack.count));
 		}
 	}
 	else //default condition - creatures are of level 5 or higher

+ 4 - 4
lib/mapObjects/CBank.cpp

@@ -94,7 +94,7 @@ void CBank::setConfig(const BankConfig & config)
 	clearSlots(); // remove all stacks, if any
 
 	for(const auto & stack : config.guards)
-		setCreature (SlotID(stacksCount()), stack.type->getId(), stack.count);
+		setCreature (SlotID(stacksCount()), stack.getId(), stack.count);
 
 	daycounter = 1; //yes, 1 since "today" daycounter won't be incremented
 }
@@ -190,8 +190,8 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 			iw.text.appendLocalString(EMetaText::ADVOB_TXT, 34);
 			const auto * strongest = boost::range::max_element(bankConfig->guards, [](const CStackBasicDescriptor & a, const CStackBasicDescriptor & b)
 			{
-				return a.type->getFightValue() < b.type->getFightValue();
-			})->type;
+				return a.getType()->getFightValue() < b.getType()->getFightValue();
+			})->getType();
 
 			iw.text.replaceNamePlural(strongest->getId());
 			iw.text.replaceRawString(loot.buildList());
@@ -244,7 +244,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 		CCreatureSet ourArmy;
 		for(const auto & slot : bankConfig->creatures)
 		{
-			ourArmy.addToSlot(ourArmy.getSlotFor(slot.type->getId()), slot.type->getId(), slot.count);
+			ourArmy.addToSlot(ourArmy.getSlotFor(slot.getId()), slot.getId(), slot.count);
 		}
 
 		for(const auto & elem : ourArmy.Slots())

+ 4 - 4
lib/mapObjects/CGCreature.cpp

@@ -359,7 +359,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 	for(const auto & elem : h->Slots())
 	{
 		bool isOurUpgrade = vstd::contains(getCreature()->upgrades, elem.second->getCreatureID());
-		bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreatureID());
+		bool isOurDowngrade = vstd::contains(elem.second->getCreature()->upgrades, getCreatureID());
 
 		if(isOurUpgrade || isOurDowngrade)
 			count += elem.second->count;
@@ -480,7 +480,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const
 		if (containsUpgradedStack()) //upgrade
 		{
 			SlotID slotID = SlotID(static_cast<si32>(std::floor(static_cast<float>(stacks.size()) / 2.0f)));
-			const auto & upgrades = getStack(slotID).type->upgrades;
+			const auto & upgrades = getStack(slotID).getCreature()->upgrades;
 			if(!upgrades.empty())
 			{
 				auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator());
@@ -521,7 +521,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &
 		const CCreature * cre = getCreature();
 		for(i = stacks.begin(); i != stacks.end(); i++)
 		{
-			if(cre->isMyUpgrade(i->second->type))
+			if(cre->isMyUpgrade(i->second->getCreature()))
 			{
 				cb->changeStackType(StackLocation(this, i->first), cre); //un-upgrade creatures
 			}
@@ -536,7 +536,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &
 			// TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere)
 			i = stacks.end();
 			i--;
-			SlotID slot = getSlotFor(i->second->type);
+			SlotID slot = getSlotFor(i->second->getCreature());
 			if(slot == i->first) //no reason to move stack to its own slot
 				break;
 			else

+ 8 - 8
lib/mapObjects/CGHeroInstance.cpp

@@ -102,16 +102,16 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain
 	int64_t ret = GameConstants::BASE_MOVEMENT_COST;
 
 	//if there is road both on dest and src tiles - use src road movement cost
-	if(dest.roadType->getId() != Road::NO_ROAD && from.roadType->getId() != Road::NO_ROAD)
+	if(dest.hasRoad() && from.hasRoad())
 	{
-		ret = from.roadType->movementCost;
+		ret = from.getRoad()->movementCost;
 	}
-	else if(ti->nativeTerrain != from.terType->getId() &&//the terrain is not native
+	else if(ti->nativeTerrain != from.getTerrainID() &&//the terrain is not native
 			ti->nativeTerrain != ETerrainId::ANY_TERRAIN && //no special creature bonus
-			!ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.terType->getId()))) //no special movement bonus
+			!ti->hasBonusOfType(BonusType::NO_TERRAIN_PENALTY, BonusSubtypeID(from.getTerrainID()))) //no special movement bonus
 	{
 
-		ret = VLC->terrainTypeHandler->getById(from.terType->getId())->moveCost;
+		ret = VLC->terrainTypeHandler->getById(from.getTerrainID())->moveCost;
 		ret -= ti->valOfBonuses(BonusType::ROUGH_TERRAIN_DISCOUNT);
 		if(ret < GameConstants::BASE_MOVEMENT_COST)
 			ret = GameConstants::BASE_MOVEMENT_COST;
@@ -1806,14 +1806,14 @@ bool CGHeroInstance::isMissionCritical() const
 
 void CGHeroInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const
 {
-	TConstBonusListPtr lista = getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.type->getId())));
+	TConstBonusListPtr lista = getBonuses(Selector::typeSubtype(BonusType::SPECIAL_UPGRADE, BonusSubtypeID(stack.getId())));
 	for(const auto & it : *lista)
 	{
 		auto nid = CreatureID(it->additionalInfo[0]);
-		if (nid != stack.type->getId()) //in very specific case the upgrade is available by default (?)
+		if (nid != stack.getId()) //in very specific case the upgrade is available by default (?)
 		{
 			info.newID.push_back(nid);
-			info.cost.push_back(nid.toCreature()->getFullRecruitCost() - stack.type->getFullRecruitCost());
+			info.cost.push_back(nid.toCreature()->getFullRecruitCost() - stack.getType()->getFullRecruitCost());
 		}
 	}
 }

+ 5 - 2
lib/mapObjects/CGHeroInstance.h

@@ -359,8 +359,11 @@ public:
 		h & boat;
 		if (h.version < Handler::Version::REMOVE_TOWN_PTR)
 		{
-			CHero * type = nullptr;
-			h & type;
+			HeroTypeID type;
+			bool isNull = false;
+			h & isNull;
+			if(!isNull)
+				h & type;
 		}
 		h & commander;
 		h & visitedObjects;

+ 2 - 5
lib/mapObjects/CGMarket.cpp

@@ -88,11 +88,8 @@ std::vector<TradeItemBuy> CGBlackMarket::availableItemsIds(EMarketMode mode) con
 	case EMarketMode::RESOURCE_ARTIFACT:
 		{
 			std::vector<TradeItemBuy> ret;
-			for(const CArtifact *a : artifacts)
-				if(a)
-					ret.push_back(a->getId());
-				else
-					ret.push_back(ArtifactID{});
+			for(const auto & a : artifacts)
+				ret.push_back(a);
 			return ret;
 		}
 	default:

+ 1 - 1
lib/mapObjects/CGMarket.h

@@ -70,7 +70,7 @@ class DLL_LINKAGE CGBlackMarket : public CGMarket
 public:
 	using CGMarket::CGMarket;
 
-	std::vector<const CArtifact *> artifacts; //available artifacts
+	std::vector<ArtifactID> artifacts; //available artifacts
 
 	void newTurn(vstd::RNG & rand) const override; //reset artifacts for black market every month
 	std::vector<TradeItemBuy> availableItemsIds(EMarketMode mode) const override;

+ 3 - 3
lib/mapObjects/CGObjectInstance.cpp

@@ -128,13 +128,13 @@ void CGObjectInstance::setType(MapObjectID newID, MapObjectSubID newSubID)
 	cb->gameState()->map->removeBlockVisTiles(this, true);
 	auto handler = VLC->objtypeh->getHandlerFor(newID, newSubID);
 
-	if(!handler->getTemplates(tile.terType->getId()).empty())
+	if(!handler->getTemplates(tile.getTerrainID()).empty())
 	{
-		appearance = handler->getTemplates(tile.terType->getId())[0];
+		appearance = handler->getTemplates(tile.getTerrainID())[0];
 	}
 	else
 	{
-		logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", newID, newSubID, visitablePos().toString(), tile.terType->getNameTranslated());
+		logGlobal->warn("Object %d:%d at %s has no templates suitable for terrain %s", newID, newSubID, visitablePos().toString(), tile.getTerrain()->getNameTranslated());
 		appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash
 	}
 

+ 7 - 9
lib/mapObjects/CGTownInstance.cpp

@@ -670,11 +670,9 @@ std::vector<TradeItemBuy> CGTownInstance::availableItemsIds(EMarketMode mode) co
 	if(mode == EMarketMode::RESOURCE_ARTIFACT)
 	{
 		std::vector<TradeItemBuy> ret;
-		for(const CArtifact *a : cb->gameState()->map->townMerchantArtifacts)
-			if(a)
-				ret.push_back(a->getId());
-			else
-				ret.push_back(ArtifactID{});
+		for(const ArtifactID a : cb->gameState()->map->townMerchantArtifacts)
+			ret.push_back(a);
+
 		return ret;
 	}
 	else if ( mode == EMarketMode::RESOURCE_SKILL )
@@ -692,7 +690,7 @@ ObjectInstanceID CGTownInstance::getObjInstanceID() const
 
 void CGTownInstance::updateAppearance()
 {
-	auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId();
+	auto terrain = cb->gameState()->getTile(visitablePos())->getTerrainID();
 	//FIXME: not the best way to do this
 	auto app = getObjectHandler()->getOverride(terrain, this);
 	if (app)
@@ -1227,14 +1225,14 @@ void CGTownInstance::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &s
 {
 	for(const CGTownInstance::TCreaturesSet::value_type & dwelling : creatures)
 	{
-		if (vstd::contains(dwelling.second, stack.type->getId())) //Dwelling with our creature
+		if (vstd::contains(dwelling.second, stack.getId())) //Dwelling with our creature
 		{
 			for(const auto & upgrID : dwelling.second)
 			{
-				if(vstd::contains(stack.type->upgrades, upgrID)) //possible upgrade
+				if(vstd::contains(stack.getCreature()->upgrades, upgrID)) //possible upgrade
 				{
 					info.newID.push_back(upgrID);
-					info.cost.push_back(upgrID.toCreature()->getFullRecruitCost() - stack.type->getFullRecruitCost());
+					info.cost.push_back(upgrID.toCreature()->getFullRecruitCost() - stack.getType()->getFullRecruitCost());
 				}
 			}
 		}

+ 4 - 12
lib/mapObjects/CGTownInstance.h

@@ -114,19 +114,11 @@ public:
 
 		if (h.version < Handler::Version::REMOVE_TOWN_PTR)
 		{
-			CTown * town = nullptr;
-
-			if (h.saving)
-			{
-				CFaction * faction = town ? town->faction : nullptr;
-				h & faction;
-			}
-			else
-			{
-				CFaction * faction = nullptr;
+			FactionID faction;
+			bool isNull = false;
+			h & isNull;
+			if (!isNull)
 				h & faction;
-				town = faction ? faction->town : nullptr;
-			}
 		}
 
 		h & townAndVis;

+ 1 - 1
lib/mapObjects/CQuest.cpp

@@ -110,7 +110,7 @@ bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army)
 	{
 		for(count = 0, it = army->Slots().begin(); it != army->Slots().end(); ++it)
 		{
-			if(it->second->type == cre->type)
+			if(it->second->getType() == cre->getType())
 			{
 				count += it->second->count;
 				slotsCount++;

+ 2 - 2
lib/mapObjects/IObjectInterface.cpp

@@ -90,10 +90,10 @@ int3 IBoatGenerator::bestLocation() const
 		if(!tile)
 			continue; // tile not visible / outside the map
 
-		if(!tile->terType->isWater())
+		if(!tile->isWater())
 			continue;
 
-		if (tile->blocked)
+		if (tile->blocked())
 		{
 			bool hasBoat = false;
 			for (auto const * object : tile->blockingObjects)

+ 7 - 7
lib/mapObjects/MiscObjects.cpp

@@ -775,13 +775,13 @@ void CGArtifact::initObj(vstd::RNG & rand)
 			storedArtifact = ArtifactUtils::createArtifact(ArtifactID());
 			cb->gameState()->map->addNewArtifactInstance(storedArtifact);
 		}
-		if(!storedArtifact->artType)
+		if(!storedArtifact->getType())
 			storedArtifact->setType(getArtifact().toArtifact());
 	}
 	if(ID == Obj::SPELL_SCROLL)
 		subID = 1;
 
-	assert(storedArtifact->artType);
+	assert(storedArtifact->getType());
 	assert(!storedArtifact->getParentNodes().empty());
 
 	//assert(storedArtifact->artType->id == subID); //this does not stop desync
@@ -825,7 +825,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const
 		iw.type = EInfoWindowMode::AUTO;
 		iw.player = h->tempOwner;
 
-		if(storedArtifact->artType->canBePutAt(h))
+		if(storedArtifact->getType()->canBePutAt(h))
 		{
 			switch (ID.toEnum())
 			{
@@ -1152,7 +1152,7 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
 			if(drown)
 			{
 				cb->changeStackCount(StackLocation(h, i->first), -drown);
-				xp += drown * i->second->type->getMaxHealth();
+				xp += drown * i->second->getType()->getMaxHealth();
 			}
 		}
 
@@ -1318,7 +1318,7 @@ void HillFort::onHeroVisit(const CGHeroInstance * h) const
 
 void HillFort::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack) const
 {
-	int32_t level = stack.type->getLevel();
+	int32_t level = stack.getType()->getLevel();
 	int32_t index = std::clamp<int32_t>(level - 1, 0, upgradeCostPercentage.size() - 1);
 
 	int costModifier = upgradeCostPercentage[index];
@@ -1326,10 +1326,10 @@ void HillFort::fillUpgradeInfo(UpgradeInfo & info, const CStackInstance &stack)
 	if (costModifier < 0)
 		return; // upgrade not allowed
 
-	for(const auto & nid : stack.type->upgrades)
+	for(const auto & nid : stack.getCreature()->upgrades)
 	{
 		info.newID.push_back(nid);
-		info.cost.push_back((nid.toCreature()->getFullRecruitCost() - stack.type->getFullRecruitCost()) * costModifier / 100);
+		info.cost.push_back((nid.toCreature()->getFullRecruitCost() - stack.getType()->getFullRecruitCost()) * costModifier / 100);
 	}
 }
 

+ 6 - 6
lib/mapping/CDrawRoadsOperation.cpp

@@ -344,12 +344,12 @@ std::string CDrawRiversOperation::getLabel() const
 
 void CDrawRoadsOperation::executeTile(TerrainTile & tile)
 {
-	tile.roadType = const_cast<RoadType*>(VLC->roadTypeHandler->getByIndex(roadType.getNum()));
+	tile.roadType = roadType;
 }
 
 void CDrawRiversOperation::executeTile(TerrainTile & tile)
 {
-	tile.riverType = const_cast<RiverType*>(VLC->riverTypeHandler->getByIndex(riverType.getNum()));
+	tile.riverType = riverType;
 }
 
 bool CDrawRoadsOperation::canApplyPattern(const LinePattern & pattern) const
@@ -364,22 +364,22 @@ bool CDrawRiversOperation::canApplyPattern(const LinePattern & pattern) const
 
 bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
 {
-	return tile.roadType->getId() != Road::NO_ROAD;
+	return tile.hasRoad();
 }
 
 bool CDrawRiversOperation::needUpdateTile(const TerrainTile & tile) const
 {
-	return tile.riverType->getId() != River::NO_RIVER;
+	return tile.hasRiver();
 }
 
 bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
 {
-	return map->getTile(pos).roadType->getId() != Road::NO_ROAD;
+	return map->getTile(pos).hasRoad();
 }
 
 bool CDrawRiversOperation::tileHasSomething(const int3& pos) const
 {
-	return map->getTile(pos).riverType->getId() != River::NO_RIVER;
+	return map->getTile(pos).hasRiver();
 }
 
 void CDrawRoadsOperation::updateTile(TerrainTile & tile, const LinePattern & pattern, const int flip)

+ 69 - 26
lib/mapping/CMap.cpp

@@ -131,32 +131,29 @@ void CCastleEvent::serializeJson(JsonSerializeFormat & handler)
 }
 
 TerrainTile::TerrainTile():
-	terType(nullptr),
-	riverType(VLC->riverTypeHandler->getById(River::NO_RIVER)),
-	roadType(VLC->roadTypeHandler->getById(Road::NO_ROAD)),
+	riverType(River::NO_RIVER),
+	roadType(Road::NO_ROAD),
 	terView(0),
 	riverDir(0),
 	roadDir(0),
-	extTileFlags(0),
-	visitable(false),
-	blocked(false)
+	extTileFlags(0)
 {
 }
 
 bool TerrainTile::entrableTerrain(const TerrainTile * from) const
 {
-	return entrableTerrain(from ? from->terType->isLand() : true, from ? from->terType->isWater() : true);
+	return entrableTerrain(from ? from->isLand() : true, from ? from->isWater() : true);
 }
 
 bool TerrainTile::entrableTerrain(bool allowLand, bool allowSea) const
 {
-	return terType->isPassable()
-			&& ((allowSea && terType->isWater())  ||  (allowLand && terType->isLand()));
+	return getTerrain()->isPassable()
+			&& ((allowSea && isWater())  ||  (allowLand && isLand()));
 }
 
 bool TerrainTile::isClear(const TerrainTile * from) const
 {
-	return entrableTerrain(from) && !blocked;
+	return entrableTerrain(from) && !blocked();
 }
 
 Obj TerrainTile::topVisitableId(bool excludeTop) const
@@ -177,7 +174,7 @@ CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const
 
 EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const
 {
-	if(terType->isWater() || !terType->isPassable())
+	if(isWater() || !getTerrain()->isPassable())
 		return EDiggingStatus::WRONG_TERRAIN;
 
 	int allowedBlocked = excludeTop ? 1 : 0;
@@ -194,9 +191,65 @@ bool TerrainTile::hasFavorableWinds() const
 
 bool TerrainTile::isWater() const
 {
-	return terType->isWater();
+	return getTerrain()->isWater();
 }
 
+bool TerrainTile::isLand() const
+{
+	return getTerrain()->isLand();
+}
+
+bool TerrainTile::visitable() const
+{
+	return !visitableObjects.empty();
+}
+
+bool TerrainTile::blocked() const
+{
+	return !blockingObjects.empty();
+}
+
+bool TerrainTile::hasRiver() const
+{
+	return getRiverID() != RiverId::NO_RIVER;
+}
+
+bool TerrainTile::hasRoad() const
+{
+	return getRoadID() != RoadId::NO_ROAD;
+}
+
+const TerrainType * TerrainTile::getTerrain() const
+{
+	return terrainType.toEntity(VLC);
+}
+
+const RiverType * TerrainTile::getRiver() const
+{
+	return riverType.toEntity(VLC);
+}
+
+const RoadType * TerrainTile::getRoad() const
+{
+	return roadType.toEntity(VLC);
+}
+
+TerrainId TerrainTile::getTerrainID() const
+{
+	return terrainType;
+}
+
+RiverId TerrainTile::getRiverID() const
+{
+	return riverType;
+}
+
+RoadId TerrainTile::getRoadID() const
+{
+	return roadType;
+}
+
+
 CMap::CMap(IGameCallback * cb)
 	: GameCallbackHolder(cb)
 	, checksum(0)
@@ -243,15 +296,10 @@ void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
 			{
 				TerrainTile & curt = terrain[zVal][xVal][yVal];
 				if(total || obj->visitableAt(int3(xVal, yVal, zVal)))
-				{
 					curt.visitableObjects -= obj;
-					curt.visitable = curt.visitableObjects.size();
-				}
+
 				if(total || obj->blockingAt(int3(xVal, yVal, zVal)))
-				{
 					curt.blockingObjects -= obj;
-					curt.blocked = curt.blockingObjects.size();
-				}
 			}
 		}
 	}
@@ -270,15 +318,10 @@ void CMap::addBlockVisTiles(CGObjectInstance * obj)
 			{
 				TerrainTile & curt = terrain[zVal][xVal][yVal];
 				if(obj->visitableAt(int3(xVal, yVal, zVal)))
-				{
 					curt.visitableObjects.push_back(obj);
-					curt.visitable = true;
-				}
+
 				if(obj->blockingAt(int3(xVal, yVal, zVal)))
-				{
 					curt.blockingObjects.push_back(obj);
-					curt.blocked = true;
-				}
 			}
 		}
 	}
@@ -381,7 +424,7 @@ int3 CMap::guardingCreaturePosition (int3 pos) const
 	if (!isInTheMap(pos))
 		return int3(-1, -1, -1);
 	const TerrainTile &posTile = getTile(pos);
-	if (posTile.visitable)
+	if (posTile.visitable())
 	{
 		for (CGObjectInstance* obj : posTile.visitableObjects)
 		{
@@ -401,7 +444,7 @@ int3 CMap::guardingCreaturePosition (int3 pos) const
 			if (isInTheMap(pos))
 			{
 				const auto & tile = getTile(pos);
-                if (tile.visitable && (tile.isWater() == water))
+				if (tile.visitable() && (tile.isWater() == water))
 				{
 					for (CGObjectInstance* obj : tile.visitableObjects)
 					{

+ 1 - 1
lib/mapping/CMap.h

@@ -180,7 +180,7 @@ public:
 	ui8 obeliskCount = 0; //how many obelisks are on map
 	std::map<TeamID, ui8> obelisksVisited; //map: team_id => how many obelisks has been visited
 
-	std::vector<const CArtifact *> townMerchantArtifacts;
+	std::vector<ArtifactID> townMerchantArtifacts;
 	std::vector<TradeItemBuy> townUniversitySkills;
 
 	void overrideGameSettings(const JsonNode & input);

+ 59 - 11
lib/mapping/CMapDefines.h

@@ -104,20 +104,33 @@ struct DLL_LINKAGE TerrainTile
 	Obj topVisitableId(bool excludeTop = false) const;
 	CGObjectInstance * topVisitableObj(bool excludeTop = false) const;
 	bool isWater() const;
-	EDiggingStatus getDiggingStatus(const bool excludeTop = true) const;
+	bool isLand() const;
+	EDiggingStatus getDiggingStatus(bool excludeTop = true) const;
 	bool hasFavorableWinds() const;
 
-	const TerrainType * terType;
-	const RiverType * riverType;
-	const RoadType * roadType;
+	bool visitable() const;
+	bool blocked() const;
+
+	const TerrainType * getTerrain() const;
+	const RiverType * getRiver() const;
+	const RoadType * getRoad() const;
+
+	TerrainId getTerrainID() const;
+	RiverId getRiverID() const;
+	RoadId getRoadID() const;
+
+	bool hasRiver() const;
+	bool hasRoad() const;
+
+	TerrainId terrainType;
+	RiverId riverType;
+	RoadId roadType;
 	ui8 terView;
 	ui8 riverDir;
 	ui8 roadDir;
 	/// first two bits - how to rotate terrain graphic (next two - river graphic, next two - road);
 	///	7th bit - whether tile is coastal (allows disembarking if land or block movement if water); 8th bit - Favorable Winds effect
 	ui8 extTileFlags;
-	bool visitable;
-	bool blocked;
 
 	std::vector<CGObjectInstance *> visitableObjects;
 	std::vector<CGObjectInstance *> blockingObjects;
@@ -125,15 +138,50 @@ struct DLL_LINKAGE TerrainTile
 	template <typename Handler>
 	void serialize(Handler & h)
 	{
-		h & terType;
+		if (h.version >= Handler::Version::REMOVE_VLC_POINTERS)
+		{
+			h & terrainType;
+		}
+		else
+		{
+			bool isNull = false;
+			h & isNull;
+			if (isNull)
+				h & terrainType;
+		}
+		h & terrainType;
 		h & terView;
-		h & riverType;
+		if (h.version >= Handler::Version::REMOVE_VLC_POINTERS)
+		{
+			h & riverType;
+		}
+		else
+		{
+			bool isNull = false;
+			h & isNull;
+			if (isNull)
+				h & riverType;
+		}
 		h & riverDir;
-		h & roadType;
+		if (h.version >= Handler::Version::REMOVE_VLC_POINTERS)
+		{
+			h & roadType;
+		}
+		else
+		{
+			bool isNull = false;
+			h & isNull;
+			if (isNull)
+				h & roadType;
+		}
 		h & roadDir;
 		h & extTileFlags;
-		h & visitable;
-		h & blocked;
+		if (h.version < Handler::Version::REMOVE_VLC_POINTERS)
+		{
+			bool unused = false;
+			h & unused;
+			h & unused;
+		}
 		h & visitableObjects;
 		h & blockingObjects;
 	}

+ 15 - 15
lib/mapping/CMapOperation.cpp

@@ -103,7 +103,7 @@ void CDrawTerrainOperation::execute()
 	for(const auto & pos : terrainSel.getSelectedItems())
 	{
 		auto & tile = map->getTile(pos);
-		tile.terType = const_cast<TerrainType*>(VLC->terrainTypeHandler->getById(terType));
+		tile.terrainType = terType;
 		invalidateTerrainViews(pos);
 	}
 
@@ -137,7 +137,7 @@ void CDrawTerrainOperation::updateTerrainTypes()
 		auto tiles = getInvalidTiles(centerPos);
 		auto updateTerrainType = [&](const int3& pos)
 		{
-			map->getTile(pos).terType = centerTile.terType;
+			map->getTile(pos).terrainType = centerTile.terrainType;
 			positions.insert(pos);
 			invalidateTerrainViews(pos);
 			//logGlobal->debug("Set additional terrain tile at pos '%s' to type '%s'", pos, centerTile.terType);
@@ -161,10 +161,10 @@ void CDrawTerrainOperation::updateTerrainTypes()
 			rect.forEach([&](const int3& posToTest)
 				{
 					auto & terrainTile = map->getTile(posToTest);
-					if(centerTile.terType->getId() != terrainTile.terType->getId())
+					if(centerTile.getTerrain() != terrainTile.getTerrain())
 					{
-						const auto * formerTerType = terrainTile.terType;
-						terrainTile.terType = centerTile.terType;
+						const auto formerTerType = terrainTile.terrainType;
+						terrainTile.terrainType = centerTile.terrainType;
 						auto testTile = getInvalidTiles(posToTest);
 
 						int nativeTilesCntNorm = testTile.nativeTiles.empty() ? std::numeric_limits<int>::max() : static_cast<int>(testTile.nativeTiles.size());
@@ -221,7 +221,7 @@ void CDrawTerrainOperation::updateTerrainTypes()
 							suitableTiles.insert(posToTest);
 						}
 
-						terrainTile.terType = formerTerType;
+						terrainTile.terrainType = formerTerType;
 					}
 				});
 
@@ -264,7 +264,7 @@ void CDrawTerrainOperation::updateTerrainViews()
 {
 	for(const auto & pos : invalidatedTerViews)
 	{
-		const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType->getId());
+		const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).getTerrainID());
 
 		// Detect a pattern which fits best
 		int bestPattern = -1;
@@ -340,7 +340,7 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 
 CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainViewInner(const int3& pos, const TerrainViewPattern& pattern, int recDepth) const
 {
-	const auto * centerTerType = map->getTile(pos).terType;
+	const auto * centerTerType = map->getTile(pos).getTerrain();
 	int totalPoints = 0;
 	std::string transitionReplacement;
 
@@ -372,24 +372,24 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 			}
 			else if(widthTooHigh)
 			{
-				terType = map->getTile(int3(currentPos.x - 1, currentPos.y, currentPos.z)).terType;
+				terType = map->getTile(int3(currentPos.x - 1, currentPos.y, currentPos.z)).getTerrain();
 			}
 			else if(heightTooHigh)
 			{
-				terType = map->getTile(int3(currentPos.x, currentPos.y - 1, currentPos.z)).terType;
+				terType = map->getTile(int3(currentPos.x, currentPos.y - 1, currentPos.z)).getTerrain();
 			}
 			else if(widthTooLess)
 			{
-				terType = map->getTile(int3(currentPos.x + 1, currentPos.y, currentPos.z)).terType;
+				terType = map->getTile(int3(currentPos.x + 1, currentPos.y, currentPos.z)).getTerrain();
 			}
 			else if(heightTooLess)
 			{
-				terType = map->getTile(int3(currentPos.x, currentPos.y + 1, currentPos.z)).terType;
+				terType = map->getTile(int3(currentPos.x, currentPos.y + 1, currentPos.z)).getTerrain();
 			}
 		}
 		else
 		{
-			terType = map->getTile(currentPos).terType;
+			terType = map->getTile(currentPos).getTerrain();
 			if(terType != centerTerType && (terType->isPassable() || centerTerType->isPassable()))
 			{
 				isAlien = true;
@@ -509,13 +509,13 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const
 {
 	//TODO: this is very expensive function for RMG, needs optimization
 	InvalidTiles tiles;
-	const auto * centerTerType = map->getTile(centerPos).terType;
+	const auto * centerTerType = map->getTile(centerPos).getTerrain();
 	auto rect = extendTileAround(centerPos);
 	rect.forEach([&](const int3& pos)
 		{
 			if(map->isInTheMap(pos))
 			{
-				const auto * terType = map->getTile(pos).terType;
+				const auto * terType = map->getTile(pos).getTerrain();
 				auto valid = validateTerrainView(pos, VLC->terviewh->getTerrainTypePatternById("n1")).result;
 
 				// Special validity check for rock & water

+ 1 - 1
lib/mapping/MapEditUtils.cpp

@@ -356,7 +356,7 @@ void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap * map, con
 			{
 				auto debugTile = map->getTile(debugPos);
 
-				std::string terType = debugTile.terType->shortIdentifier;
+				std::string terType = debugTile.getTerrain()->shortIdentifier;
 				line += terType;
 				line.insert(line.end(), PADDED_LENGTH - terType.size(), ' ');
 			}

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.