浏览代码

Merge remote-tracking branch 'origin/develop' into fix_rmg_teams

# Conflicts:
#	lib/rmg/CMapGenOptions.cpp
#	lib/rmg/CMapGenOptions.h
Tomasz Zieliński 1 年之前
父节点
当前提交
c909bd766e
共有 100 个文件被更改,包括 1100 次插入1096 次删除
  1. 13 12
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  3. 1 1
      AI/Nullkiller/Behaviors/StartupBehavior.cpp
  4. 2 5
      AI/Nullkiller/Engine/FuzzyHelper.cpp
  5. 21 10
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  6. 0 1
      AI/VCAI/AIUtility.h
  7. 1 1
      AI/VCAI/FuzzyEngines.cpp
  8. 2 9
      AI/VCAI/FuzzyHelper.cpp
  9. 2 2
      AI/VCAI/Goals/CollectRes.cpp
  10. 1 1
      AI/VCAI/Goals/GatherTroops.cpp
  11. 1 1
      AI/VCAI/Pathfinding/PathfindingManager.cpp
  12. 1 13
      AI/VCAI/ResourceManager.cpp
  13. 14 13
      AI/VCAI/VCAI.cpp
  14. 1 1
      AI/VCAI/VCAI.h
  15. 1 1
      CCallback.cpp
  16. 10 16
      client/CPlayerInterface.cpp
  17. 1 1
      client/HeroMovementController.cpp
  18. 11 11
      client/NetPacksClient.cpp
  19. 17 13
      client/adventureMap/CInfoBar.cpp
  20. 1 1
      client/battle/BattleActionsController.cpp
  21. 10 2
      client/battle/BattleObstacleController.cpp
  22. 1 1
      client/lobby/CBonusSelection.cpp
  23. 4 2
      client/lobby/CSelectionBase.cpp
  24. 1 1
      client/lobby/OptionsTab.cpp
  25. 2 3
      client/mainmenu/CHighScoreScreen.cpp
  26. 1 5
      client/mapView/MapRendererContext.cpp
  27. 11 3
      client/render/CAnimation.cpp
  28. 10 10
      client/widgets/CArtifactHolder.cpp
  29. 2 2
      client/widgets/CArtifactsOfHeroAltar.cpp
  30. 2 2
      client/widgets/CArtifactsOfHeroBackpack.cpp
  31. 3 3
      client/widgets/CArtifactsOfHeroBase.cpp
  32. 3 2
      client/widgets/CArtifactsOfHeroKingdom.cpp
  33. 3 2
      client/widgets/CArtifactsOfHeroMain.cpp
  34. 183 133
      client/widgets/CComponent.cpp
  35. 11 20
      client/widgets/CComponent.h
  36. 8 6
      client/widgets/CGarrisonInt.cpp
  37. 6 6
      client/widgets/CWindowWithArtifacts.cpp
  38. 19 25
      client/widgets/MiscWidgets.cpp
  39. 5 5
      client/widgets/MiscWidgets.h
  40. 26 17
      client/windows/CCastleInterface.cpp
  41. 8 7
      client/windows/CCreatureWindow.cpp
  42. 14 22
      client/windows/CHeroWindow.cpp
  43. 2 2
      client/windows/CKingdomInterface.cpp
  44. 2 2
      client/windows/CSpellWindow.cpp
  45. 4 4
      client/windows/CTradeWindow.cpp
  46. 4 2
      client/windows/CWindowObject.cpp
  47. 2 1
      client/windows/CWindowObject.h
  48. 6 11
      client/windows/GUIClasses.cpp
  49. 1 1
      client/windows/GUIClasses.h
  50. 2 2
      cmake_modules/VCMI_lib.cmake
  51. 14 0
      cmake_modules/create_link.cmake
  52. 4 0
      docs/players/Installation_iOS.md
  53. 3 4
      launcher/CMakeLists.txt
  54. 2 0
      launcher/settingsView/csettingsview_moc.cpp
  55. 44 1
      lib/ArtifactUtils.cpp
  56. 2 0
      lib/ArtifactUtils.h
  57. 7 7
      lib/CArtHandler.cpp
  58. 9 9
      lib/CArtifactInstance.cpp
  59. 5 4
      lib/CArtifactInstance.h
  60. 6 6
      lib/CBuildingHandler.cpp
  61. 1 1
      lib/CBuildingHandler.h
  62. 16 27
      lib/CCreatureHandler.cpp
  63. 7 2
      lib/CCreatureSet.cpp
  64. 2 1
      lib/CCreatureSet.h
  65. 17 0
      lib/CGameInfoCallback.cpp
  66. 3 0
      lib/CGameInfoCallback.h
  67. 10 10
      lib/CHeroHandler.cpp
  68. 1 7
      lib/CHeroHandler.h
  69. 2 21
      lib/CSkillHandler.cpp
  70. 0 5
      lib/CSkillHandler.h
  71. 1 1
      lib/CTownHandler.cpp
  72. 3 3
      lib/IGameCallback.cpp
  73. 3 3
      lib/JsonRandom.cpp
  74. 6 6
      lib/MetaString.cpp
  75. 0 13
      lib/ResourceSet.cpp
  76. 2 3
      lib/ResourceSet.h
  77. 2 2
      lib/battle/CBattleInfoCallback.cpp
  78. 1 1
      lib/battle/SideInBattle.cpp
  79. 1 1
      lib/battle/Unit.cpp
  80. 3 3
      lib/bonuses/Bonus.cpp
  81. 3 3
      lib/bonuses/Limiters.cpp
  82. 9 10
      lib/campaign/CampaignState.cpp
  83. 43 6
      lib/constants/EntityIdentifiers.cpp
  84. 51 17
      lib/constants/EntityIdentifiers.h
  85. 1 1
      lib/constants/VariantIdentifier.h
  86. 50 285
      lib/gameState/CGameState.cpp
  87. 2 3
      lib/gameState/CGameState.h
  88. 14 14
      lib/gameState/CGameStateCampaign.cpp
  89. 3 3
      lib/gameState/TavernHeroesPool.cpp
  90. 11 14
      lib/mapObjectConstructors/CObjectClassesHandler.cpp
  91. 9 9
      lib/mapObjectConstructors/CObjectClassesHandler.h
  92. 2 2
      lib/mapObjects/CArmedInstance.cpp
  93. 18 22
      lib/mapObjects/CBank.cpp
  94. 55 18
      lib/mapObjects/CGCreature.cpp
  95. 2 0
      lib/mapObjects/CGCreature.h
  96. 127 65
      lib/mapObjects/CGDwelling.cpp
  97. 11 34
      lib/mapObjects/CGDwelling.h
  98. 50 29
      lib/mapObjects/CGHeroInstance.cpp
  99. 3 1
      lib/mapObjects/CGHeroInstance.h
  100. 1 1
      lib/mapObjects/CGMarket.cpp

+ 13 - 12
AI/Nullkiller/AIGateway.cpp

@@ -9,6 +9,7 @@
  */
  */
 #include "StdInc.h"
 #include "StdInc.h"
 
 
+#include "../../lib/ArtifactUtils.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/ObjectTemplate.h"
 #include "../../lib/mapObjects/ObjectTemplate.h"
@@ -663,7 +664,7 @@ void AIGateway::showBlockingDialog(const std::string & text, const std::vector<C
 		// TODO: Find better way to understand it is Chest of Treasures
 		// TODO: Find better way to understand it is Chest of Treasures
 		if(hero.validAndSet()
 		if(hero.validAndSet()
 			&& components.size() == 2
 			&& components.size() == 2
-			&& components.front().id == Component::EComponentType::RESOURCE
+			&& components.front().type == ComponentType::RESOURCE
 			&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
 			&& (nullkiller->heroManager->getHeroRole(hero) != HeroRole::MAIN
 			|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
 			|| nullkiller->buildAnalyzer->getGoldPreasure() > MAX_GOLD_PEASURE))
 		{
 		{
@@ -995,21 +996,21 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				for(auto p : h->artifactsWorn)
 				for(auto p : h->artifactsWorn)
 				{
 				{
 					if(p.second.artifact)
 					if(p.second.artifact)
-						allArtifacts.push_back(ArtifactLocation(h, p.first));
+						allArtifacts.push_back(ArtifactLocation(h->id, p.first));
 				}
 				}
 			}
 			}
 			for(auto slot : h->artifactsInBackpack)
 			for(auto slot : h->artifactsInBackpack)
-				allArtifacts.push_back(ArtifactLocation(h, h->getArtPos(slot.artifact)));
+				allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.artifact)));
 
 
 			if(otherh)
 			if(otherh)
 			{
 			{
 				for(auto p : otherh->artifactsWorn)
 				for(auto p : otherh->artifactsWorn)
 				{
 				{
 					if(p.second.artifact)
 					if(p.second.artifact)
-						allArtifacts.push_back(ArtifactLocation(otherh, p.first));
+						allArtifacts.push_back(ArtifactLocation(otherh->id, p.first));
 				}
 				}
 				for(auto slot : otherh->artifactsInBackpack)
 				for(auto slot : otherh->artifactsInBackpack)
-					allArtifacts.push_back(ArtifactLocation(otherh, otherh->getArtPos(slot.artifact)));
+					allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.artifact)));
 			}
 			}
 			//we give stuff to one hero or another, depending on giveStuffToFirstHero
 			//we give stuff to one hero or another, depending on giveStuffToFirstHero
 
 
@@ -1021,13 +1022,13 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 
 
 			for(auto location : allArtifacts)
 			for(auto location : allArtifacts)
 			{
 			{
-				if(location.relatedObj() == target && location.slot < ArtifactPosition::AFTER_LAST)
+				if(location.artHolder == target->id && ArtifactUtils::isSlotEquipment(location.slot))
 					continue; //don't reequip artifact we already wear
 					continue; //don't reequip artifact we already wear
 
 
 				if(location.slot == ArtifactPosition::MACH4) // don't attempt to move catapult
 				if(location.slot == ArtifactPosition::MACH4) // don't attempt to move catapult
 					continue;
 					continue;
 
 
-				auto s = location.getSlot();
+				auto s = cb->getHero(location.artHolder)->getSlot(location.slot);
 				if(!s || s->locked) //we can't move locks
 				if(!s || s->locked) //we can't move locks
 					continue;
 					continue;
 				auto artifact = s->artifact;
 				auto artifact = s->artifact;
@@ -1038,9 +1039,9 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 				bool emptySlotFound = false;
 				bool emptySlotFound = false;
 				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 				{
 				{
-					ArtifactLocation destLocation(target, slot);
-					if(target->isPositionFree(slot) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
+					if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
 					{
 					{
+						ArtifactLocation destLocation(target->id, slot);
 						cb->swapArtifacts(location, destLocation); //just put into empty slot
 						cb->swapArtifacts(location, destLocation); //just put into empty slot
 						emptySlotFound = true;
 						emptySlotFound = true;
 						changeMade = true;
 						changeMade = true;
@@ -1054,11 +1055,11 @@ void AIGateway::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance
 						auto otherSlot = target->getSlot(slot);
 						auto otherSlot = target->getSlot(slot);
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
 						{
 						{
-							ArtifactLocation destLocation(target, slot);
 							//if that artifact is better than what we have, pick it
 							//if that artifact is better than what we have, pick it
-							if(compareArtifacts(artifact, otherSlot->artifact) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
+							if(compareArtifacts(artifact, otherSlot->artifact) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
 							{
 							{
-								cb->swapArtifacts(location, ArtifactLocation(target, target->getArtPos(otherSlot->artifact)));
+								ArtifactLocation destLocation(target->id, slot);
+								cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->artifact)));
 								changeMade = true;
 								changeMade = true;
 								break;
 								break;
 							}
 							}

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

@@ -318,7 +318,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
 {
 {
 	for(auto tdi : developmentInfos)
 	for(auto tdi : developmentInfos)
 	{
 	{
-		if(tdi.town->subID == alignment && tdi.town->hasBuilt(bid))
+		if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid))
 			return true;
 			return true;
 	}
 	}
 
 

+ 1 - 1
AI/Nullkiller/Behaviors/StartupBehavior.cpp

@@ -71,7 +71,7 @@ bool needToRecruitHero(const CGTownInstance * startupTown)
 
 
 	for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
 	for(auto obj : ai->nullkiller->objectClusterizer->getNearbyObjects())
 	{
 	{
-		if((obj->ID == Obj::RESOURCE && obj->subID == GameResID(EGameResID::GOLD))
+		if((obj->ID == Obj::RESOURCE && dynamic_cast<const CGResource *>(obj)->resourceID() == EGameResID::GOLD)
 			|| obj->ID == Obj::TREASURE_CHEST
 			|| obj->ID == Obj::TREASURE_CHEST
 			|| obj->ID == Obj::CAMPFIRE
 			|| obj->ID == Obj::CAMPFIRE
 			|| obj->ID == Obj::WATER_WHEEL)
 			|| obj->ID == Obj::WATER_WHEEL)

+ 2 - 5
AI/Nullkiller/Engine/FuzzyHelper.cpp

@@ -24,7 +24,7 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
 {
 {
 	//this one is not fuzzy anymore, just calculate weighted average
 	//this one is not fuzzy anymore, just calculate weighted average
 
 
-	auto objectInfo = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance);
+	auto objectInfo = bank->getObjectHandler()->getObjectInfo(bank->appearance);
 
 
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 
 
@@ -161,10 +161,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
 	}
 	}
 	case Obj::PYRAMID:
 	case Obj::PYRAMID:
 	{
 	{
-		if(obj->subID == 0)
-			return estimateBankDanger(dynamic_cast<const CBank *>(obj));
-		else
-			return 0;
+		return estimateBankDanger(dynamic_cast<const CBank *>(obj));
 	}
 	}
 	default:
 	default:
 		return 0;
 		return 0;

+ 21 - 10
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -122,7 +122,7 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
 {
 {
 	//Fixme: unused variable hero
 	//Fixme: unused variable hero
 
 
-	auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
+	auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 	auto resources = bankInfo->getPossibleResourcesReward();
 	auto resources = bankInfo->getPossibleResourcesReward();
 	TResources result = TResources();
 	TResources result = TResources();
@@ -139,7 +139,7 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
 
 
 uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
 uint64_t getCreatureBankArmyReward(const CGObjectInstance * target, const CGHeroInstance * hero)
 {
 {
-	auto objectInfo = VLC->objtypeh->getHandlerFor(target->ID, target->subID)->getObjectInfo(target->appearance);
+	auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 	auto creatures = bankInfo->getPossibleCreaturesReward();
 	auto creatures = bankInfo->getPossibleCreaturesReward();
 	uint64_t result = 0;
 	uint64_t result = 0;
@@ -467,14 +467,20 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 	switch(target->ID)
 	switch(target->ID)
 	{
 	{
 	case Obj::MINE:
 	case Obj::MINE:
-		return target->subID == GameResID(EGameResID::GOLD)
+	{
+		auto mine = dynamic_cast<const CGMine *>(target);
+		return mine->producedResource == EGameResID::GOLD
 			? 0.5f 
 			? 0.5f 
-			: 0.4f * getTotalResourceRequirementStrength(target->subID) + 0.1f * getResourceRequirementStrength(target->subID);
+			: 0.4f * getTotalResourceRequirementStrength(mine->producedResource) + 0.1f * getResourceRequirementStrength(mine->producedResource);
+	}
 
 
 	case Obj::RESOURCE:
 	case Obj::RESOURCE:
-		return target->subID == GameResID(EGameResID::GOLD)
+	{
+		auto resource = dynamic_cast<const CGResource *>(target);
+		return resource->resourceID() == EGameResID::GOLD
 			? 0
 			? 0
-			: 0.2f * getTotalResourceRequirementStrength(target->subID) + 0.4f * getResourceRequirementStrength(target->subID);
+			: 0.2f * getTotalResourceRequirementStrength(resource->resourceID()) + 0.4f * getResourceRequirementStrength(resource->resourceID());
+	}
 
 
 	case Obj::CREATURE_BANK:
 	case Obj::CREATURE_BANK:
 	{
 	{
@@ -626,12 +632,14 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 	const int dailyIncomeMultiplier = 5;
 	const int dailyIncomeMultiplier = 5;
 	const float enemyArmyEliminationGoldRewardRatio = 0.2f;
 	const float enemyArmyEliminationGoldRewardRatio = 0.2f;
 	const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
 	const int32_t heroEliminationBonus = GameConstants::HERO_GOLD_COST / 2;
-	auto isGold = target->subID == GameResID(EGameResID::GOLD); // TODO: other resorces could be sold but need to evaluate market power
 
 
 	switch(target->ID)
 	switch(target->ID)
 	{
 	{
 	case Obj::RESOURCE:
 	case Obj::RESOURCE:
-		return isGold ? 600 : 100;
+	{
+		auto * res = dynamic_cast<const CGResource*>(target);
+		return res->resourceID() == GameResID::GOLD ? 600 : 100;
+	}
 	case Obj::TREASURE_CHEST:
 	case Obj::TREASURE_CHEST:
 		return 1500;
 		return 1500;
 	case Obj::WATER_WHEEL:
 	case Obj::WATER_WHEEL:
@@ -640,7 +648,10 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 		return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero);
 		return dailyIncomeMultiplier * estimateTownIncome(ai->cb.get(), target, hero);
 	case Obj::MINE:
 	case Obj::MINE:
 	case Obj::ABANDONED_MINE:
 	case Obj::ABANDONED_MINE:
-		return dailyIncomeMultiplier * (isGold ? 1000 : 75);
+	{
+		auto * mine = dynamic_cast<const CGMine*>(target);
+		return dailyIncomeMultiplier * (mine->producedResource == GameResID::GOLD ? 1000 : 75);
+	}
 	case Obj::MYSTICAL_GARDEN:
 	case Obj::MYSTICAL_GARDEN:
 	case Obj::WINDMILL:
 	case Obj::WINDMILL:
 		return 100;
 		return 100;
@@ -1005,7 +1016,7 @@ public:
 
 
 uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
 uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
 {
 {
-	if(ai->buildAnalyzer->hasAnyBuilding(town->subID, bi.id))
+	if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id))
 		return 0;
 		return 0;
 
 
 	auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);
 	auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);

+ 0 - 1
AI/VCAI/AIUtility.h

@@ -26,7 +26,6 @@ using crint3 = const int3 &;
 using crstring = const std::string &;
 using crstring = const std::string &;
 using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
 using dwellingContent = std::pair<ui32, std::vector<CreatureID>>;
 
 
-const int GOLD_MINE_PRODUCTION = 1000, WOOD_ORE_MINE_PRODUCTION = 2, RESOURCE_MINE_PRODUCTION = 1;
 const int ACTUAL_RESOURCE_COUNT = 7;
 const int ACTUAL_RESOURCE_COUNT = 7;
 const int ALLOWED_ROAMING_HEROES = 8;
 const int ALLOWED_ROAMING_HEROES = 8;
 
 

+ 1 - 1
AI/VCAI/FuzzyEngines.cpp

@@ -406,7 +406,7 @@ float VisitObjEngine::evaluate(Goals::VisitObj & goal)
 	else
 	else
 	{
 	{
 		MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0);
 		MapObjectsEvaluator::getInstance().addObjectData(obj->ID, obj->subID, 0);
-		logGlobal->error("AI met object type it doesn't know - ID: " + std::to_string(obj->ID) + ", subID: " + std::to_string(obj->subID) + " - adding to database with value " + std::to_string(objValue));
+		logGlobal->error("AI met object type it doesn't know - ID: %d, subID: %d - adding to database with value %d ", obj->ID, obj->subID, objValue);
 	}
 	}
 
 
 	setSharedFuzzyVariables(goal);
 	setSharedFuzzyVariables(goal);

+ 2 - 9
AI/VCAI/FuzzyHelper.cpp

@@ -66,7 +66,7 @@ ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
 {
 {
 	//this one is not fuzzy anymore, just calculate weighted average
 	//this one is not fuzzy anymore, just calculate weighted average
 
 
-	auto objectInfo = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance);
+	auto objectInfo = bank->getObjectHandler()->getObjectInfo(bank->appearance);
 
 
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 	CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
 
 
@@ -324,15 +324,8 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
 	case Obj::DRAGON_UTOPIA:
 	case Obj::DRAGON_UTOPIA:
 	case Obj::SHIPWRECK: //shipwreck
 	case Obj::SHIPWRECK: //shipwreck
 	case Obj::DERELICT_SHIP: //derelict ship
 	case Obj::DERELICT_SHIP: //derelict ship
-							 //	case Obj::PYRAMID:
-		return estimateBankDanger(dynamic_cast<const CBank *>(obj));
 	case Obj::PYRAMID:
 	case Obj::PYRAMID:
-	{
-		if(obj->subID == 0)
-			return estimateBankDanger(dynamic_cast<const CBank *>(obj));
-		else
-			return 0;
-	}
+		return estimateBankDanger(dynamic_cast<const CBank *>(obj));
 	default:
 	default:
 		return 0;
 		return 0;
 	}
 	}

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

@@ -43,10 +43,10 @@ TGoalVec CollectRes::getAllPossibleSubgoals()
 			return resID == GameResID(EGameResID::GOLD);
 			return resID == GameResID(EGameResID::GOLD);
 			break;
 			break;
 		case Obj::RESOURCE:
 		case Obj::RESOURCE:
-			return obj->subID == resID;
+			return dynamic_cast<const CGResource*>(obj)->resourceID() == GameResID(resID);
 			break;
 			break;
 		case Obj::MINE:
 		case Obj::MINE:
-			return (obj->subID == resID &&
+			return (dynamic_cast<const CGMine*>(obj)->producedResource == GameResID(resID) &&
 				(cb->getPlayerRelations(obj->tempOwner, ai->playerID) == PlayerRelations::ENEMIES)); //don't capture our mines
 				(cb->getPlayerRelations(obj->tempOwner, ai->playerID) == PlayerRelations::ENEMIES)); //don't capture our mines
 			break;
 			break;
 		case Obj::CAMPFIRE:
 		case Obj::CAMPFIRE:

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

@@ -88,7 +88,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 		}
 		}
 
 
 		auto creature = VLC->creatures()->getByIndex(objid);
 		auto creature = VLC->creatures()->getByIndex(objid);
-		if(t->subID == creature->getFaction()) //TODO: how to force AI to build unupgraded creatures? :O
+		if(t->getFaction() == creature->getFaction()) //TODO: how to force AI to build unupgraded creatures? :O
 		{
 		{
 			auto tryFindCreature = [&]() -> std::optional<std::vector<CreatureID>>
 			auto tryFindCreature = [&]() -> std::optional<std::vector<CreatureID>>
 			{
 			{

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

@@ -190,7 +190,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
 	if(isBlockedBorderGate(firstTileToGet))
 	if(isBlockedBorderGate(firstTileToGet))
 	{
 	{
 		//FIXME: this way we'll not visit gate and activate quest :?
 		//FIXME: this way we'll not visit gate and activate quest :?
-		return sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTile(firstTileToGet)->visitableObjects.back()->subID));
+		return sptr(Goals::FindObj(Obj::KEYMASTER, cb->getTile(firstTileToGet)->visitableObjects.back()->getObjTypeIndex()));
 	}
 	}
 
 
 	auto topObj = cb->getTopObj(firstTileToGet);
 	auto topObj = cb->getTopObj(firstTileToGet);

+ 1 - 13
AI/VCAI/ResourceManager.cpp

@@ -59,19 +59,7 @@ TResources ResourceManager::estimateIncome() const
 		if (obj->ID == Obj::MINE)
 		if (obj->ID == Obj::MINE)
 		{
 		{
 			auto mine = dynamic_cast<const CGMine*>(obj);
 			auto mine = dynamic_cast<const CGMine*>(obj);
-			switch (mine->producedResource.toEnum())
-			{
-			case EGameResID::WOOD:
-			case EGameResID::ORE:
-				ret[obj->subID] += WOOD_ORE_MINE_PRODUCTION;
-				break;
-			case EGameResID::GOLD:
-				ret[EGameResID::GOLD] += GOLD_MINE_PRODUCTION;
-				break;
-			default:
-				ret[obj->subID] += RESOURCE_MINE_PRODUCTION;
-				break;
-			}
+			ret += mine->dailyIncome();
 		}
 		}
 	}
 	}
 
 

+ 14 - 13
AI/VCAI/VCAI.cpp

@@ -14,6 +14,7 @@
 #include "BuildingManager.h"
 #include "BuildingManager.h"
 #include "Goals/Goals.h"
 #include "Goals/Goals.h"
 
 
+#include "../../lib/ArtifactUtils.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/MapObjects.h"
 #include "../../lib/mapObjects/ObjectTemplate.h"
 #include "../../lib/mapObjects/ObjectTemplate.h"
@@ -1166,21 +1167,21 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 				for(auto p : h->artifactsWorn)
 				for(auto p : h->artifactsWorn)
 				{
 				{
 					if(p.second.artifact)
 					if(p.second.artifact)
-						allArtifacts.push_back(ArtifactLocation(h, p.first));
+						allArtifacts.push_back(ArtifactLocation(h->id, p.first));
 				}
 				}
 			}
 			}
 			for(auto slot : h->artifactsInBackpack)
 			for(auto slot : h->artifactsInBackpack)
-				allArtifacts.push_back(ArtifactLocation(h, h->getArtPos(slot.artifact)));
+				allArtifacts.push_back(ArtifactLocation(h->id, h->getArtPos(slot.artifact)));
 
 
 			if(otherh)
 			if(otherh)
 			{
 			{
 				for(auto p : otherh->artifactsWorn)
 				for(auto p : otherh->artifactsWorn)
 				{
 				{
 					if(p.second.artifact)
 					if(p.second.artifact)
-						allArtifacts.push_back(ArtifactLocation(otherh, p.first));
+						allArtifacts.push_back(ArtifactLocation(otherh->id, p.first));
 				}
 				}
 				for(auto slot : otherh->artifactsInBackpack)
 				for(auto slot : otherh->artifactsInBackpack)
-					allArtifacts.push_back(ArtifactLocation(otherh, otherh->getArtPos(slot.artifact)));
+					allArtifacts.push_back(ArtifactLocation(otherh->id, otherh->getArtPos(slot.artifact)));
 			}
 			}
 			//we give stuff to one hero or another, depending on giveStuffToFirstHero
 			//we give stuff to one hero or another, depending on giveStuffToFirstHero
 
 
@@ -1195,10 +1196,10 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 				if(location.slot == ArtifactPosition::MACH4 || location.slot == ArtifactPosition::SPELLBOOK)
 				if(location.slot == ArtifactPosition::MACH4 || location.slot == ArtifactPosition::SPELLBOOK)
 					continue; // don't attempt to move catapult and spellbook
 					continue; // don't attempt to move catapult and spellbook
 
 
-				if(location.relatedObj() == target && location.slot < ArtifactPosition::AFTER_LAST)
+				if(location.artHolder == target->id && ArtifactUtils::isSlotEquipment(location.slot))
 					continue; //don't reequip artifact we already wear
 					continue; //don't reequip artifact we already wear
 
 
-				auto s = location.getSlot();
+				auto s = cb->getHero(location.artHolder)->getSlot(location.slot);
 				if(!s || s->locked) //we can't move locks
 				if(!s || s->locked) //we can't move locks
 					continue;
 					continue;
 				auto artifact = s->artifact;
 				auto artifact = s->artifact;
@@ -1209,9 +1210,9 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 				bool emptySlotFound = false;
 				bool emptySlotFound = false;
 				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 				for(auto slot : artifact->artType->getPossibleSlots().at(target->bearerType()))
 				{
 				{
-					ArtifactLocation destLocation(target, slot);
-					if(target->isPositionFree(slot) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
+					if(target->isPositionFree(slot) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
 					{
 					{
+						ArtifactLocation destLocation(target->id, slot);
 						cb->swapArtifacts(location, destLocation); //just put into empty slot
 						cb->swapArtifacts(location, destLocation); //just put into empty slot
 						emptySlotFound = true;
 						emptySlotFound = true;
 						changeMade = true;
 						changeMade = true;
@@ -1225,11 +1226,11 @@ void VCAI::pickBestArtifacts(const CGHeroInstance * h, const CGHeroInstance * ot
 						auto otherSlot = target->getSlot(slot);
 						auto otherSlot = target->getSlot(slot);
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
 						if(otherSlot && otherSlot->artifact) //we need to exchange artifact for better one
 						{
 						{
-							ArtifactLocation destLocation(target, slot);
 							//if that artifact is better than what we have, pick it
 							//if that artifact is better than what we have, pick it
-							if(compareArtifacts(artifact, otherSlot->artifact) && artifact->canBePutAt(destLocation, true)) //combined artifacts are not always allowed to move
+							if(compareArtifacts(artifact, otherSlot->artifact) && artifact->canBePutAt(target, slot, true)) //combined artifacts are not always allowed to move
 							{
 							{
-								cb->swapArtifacts(location, ArtifactLocation(target, target->getArtPos(otherSlot->artifact)));
+								ArtifactLocation destLocation(target->id, slot);
+								cb->swapArtifacts(location, ArtifactLocation(target->id, target->getArtPos(otherSlot->artifact)));
 								changeMade = true;
 								changeMade = true;
 								break;
 								break;
 							}
 							}
@@ -1759,11 +1760,11 @@ void VCAI::addVisitableObj(const CGObjectInstance * obj)
 		CGTeleport::addToChannel(knownTeleportChannels, teleportObj);
 		CGTeleport::addToChannel(knownTeleportChannels, teleportObj);
 }
 }
 
 
-const CGObjectInstance * VCAI::lookForArt(int aid) const
+const CGObjectInstance * VCAI::lookForArt(ArtifactID aid) const
 {
 {
 	for(const CGObjectInstance * obj : ai->visitableObjs)
 	for(const CGObjectInstance * obj : ai->visitableObjs)
 	{
 	{
-		if(obj->ID == Obj::ARTIFACT && obj->subID == aid)
+		if(obj->ID == Obj::ARTIFACT && dynamic_cast<const CGArtifact *>(obj)->getArtifact()  == aid)
 			return obj;
 			return obj;
 	}
 	}
 
 

+ 1 - 1
AI/VCAI/VCAI.h

@@ -251,7 +251,7 @@ public:
 	void retrieveVisitableObjs();
 	void retrieveVisitableObjs();
 	virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
 	virtual std::vector<const CGObjectInstance *> getFlaggedObjects() const;
 
 
-	const CGObjectInstance * lookForArt(int aid) const;
+	const CGObjectInstance * lookForArt(ArtifactID aid) const;
 	bool isAccessible(const int3 & pos) const;
 	bool isAccessible(const int3 & pos) const;
 	HeroPtr getHeroWithGrail() const;
 	HeroPtr getHeroWithGrail() const;
 
 

+ 1 - 1
CCallback.cpp

@@ -265,7 +265,7 @@ void CCallback::recruitHero(const CGObjectInstance *townOrTavern, const CGHeroIn
 	assert(townOrTavern);
 	assert(townOrTavern);
 	assert(hero);
 	assert(hero);
 
 
-	HireHero pack(HeroTypeID(hero->subID), townOrTavern->id);
+	HireHero pack(hero->getHeroType(), townOrTavern->id);
 	pack.player = *player;
 	pack.player = *player;
 	sendRequest(&pack);
 	sendRequest(&pack);
 }
 }

+ 10 - 16
client/CPlayerInterface.cpp

@@ -293,7 +293,7 @@ void CPlayerInterface::yourTurn(QueryID queryID)
 			std::string msg = CGI->generaltexth->allTexts[13];
 			std::string msg = CGI->generaltexth->allTexts[13];
 			boost::replace_first(msg, "%s", cb->getStartInfo()->playerInfos.find(playerID)->second.name);
 			boost::replace_first(msg, "%s", cb->getStartInfo()->playerInfos.find(playerID)->second.name);
 			std::vector<std::shared_ptr<CComponent>> cmp;
 			std::vector<std::shared_ptr<CComponent>> cmp;
-			cmp.push_back(std::make_shared<CComponent>(CComponent::flag, playerID.getNum(), 0));
+			cmp.push_back(std::make_shared<CComponent>(ComponentType::FLAG, playerID));
 			showInfoDialog(msg, cmp);
 			showInfoDialog(msg, cmp);
 		}
 		}
 		else
 		else
@@ -326,7 +326,7 @@ void CPlayerInterface::acceptTurn(QueryID queryID)
 		auto playerColor = *cb->getPlayerID();
 		auto playerColor = *cb->getPlayerID();
 
 
 		std::vector<Component> components;
 		std::vector<Component> components;
-		components.emplace_back(Component::EComponentType::FLAG, playerColor.getNum(), 0, 0);
+		components.emplace_back(ComponentType::FLAG, playerColor);
 		MetaString text;
 		MetaString text;
 
 
 		const auto & optDaysWithoutCastle = cb->getPlayerState(playerColor)->daysWithoutCastle;
 		const auto & optDaysWithoutCastle = cb->getPlayerState(playerColor)->daysWithoutCastle;
@@ -1228,7 +1228,7 @@ void CPlayerInterface::showArtifactAssemblyDialog(const Artifact * artifact, con
 		text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getNameTranslated());
 		text += boost::str(boost::format(CGI->generaltexth->allTexts[732]) % assembledArtifact->getNameTranslated());
 
 
 		// Picture of assembled artifact at bottom.
 		// Picture of assembled artifact at bottom.
-		auto sc = std::make_shared<CComponent>(CComponent::artifact, assembledArtifact->getIndex(), 0);
+		auto sc = std::make_shared<CComponent>(ComponentType::ARTIFACT, assembledArtifact->getId());
 		scs.push_back(sc);
 		scs.push_back(sc);
 	}
 	}
 	else
 	else
@@ -1441,7 +1441,7 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
 			std::string msg = CGI->generaltexth->translate("vcmi.adventureMap.playerAttacked");
 			std::string msg = CGI->generaltexth->translate("vcmi.adventureMap.playerAttacked");
 			boost::replace_first(msg, "%s", cb->getStartInfo()->playerInfos.find(playerID)->second.name);
 			boost::replace_first(msg, "%s", cb->getStartInfo()->playerInfos.find(playerID)->second.name);
 			std::vector<std::shared_ptr<CComponent>> cmp;
 			std::vector<std::shared_ptr<CComponent>> cmp;
-			cmp.push_back(std::make_shared<CComponent>(CComponent::flag, playerID.getNum(), 0));
+			cmp.push_back(std::make_shared<CComponent>(ComponentType::FLAG, playerID));
 			makingTurn = true; //workaround for stiff showInfoDialog implementation
 			makingTurn = true; //workaround for stiff showInfoDialog implementation
 			showInfoDialog(msg, cmp);
 			showInfoDialog(msg, cmp);
 			makingTurn = false;
 			makingTurn = false;
@@ -1753,8 +1753,7 @@ void CPlayerInterface::requestReturningToMainMenu(bool won)
 
 
 void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
 void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
 {
 {
-	auto hero = std::visit(HeroObjectRetriever(), al.artHolder);
-	if(hero)
+	if(auto hero = cb->getHero(al.artHolder))
 	{
 	{
 		auto art = hero->getArt(al.slot);
 		auto art = hero->getArt(al.slot);
 		if(art == nullptr)
 		if(art == nullptr)
@@ -1770,15 +1769,13 @@ void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
 void CPlayerInterface::artifactPut(const ArtifactLocation &al)
 void CPlayerInterface::artifactPut(const ArtifactLocation &al)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	auto hero = std::visit(HeroObjectRetriever(), al.artHolder);
-	adventureInt->onHeroChanged(hero);
+	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
 }
 }
 
 
 void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
 void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	auto hero = std::visit(HeroObjectRetriever(), al.artHolder);
-	adventureInt->onHeroChanged(hero);
+	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
 
 
 	for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
 	for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
 		artWin->artifactRemoved(al);
 		artWin->artifactRemoved(al);
@@ -1789,8 +1786,7 @@ void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
 void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst)
 void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const ArtifactLocation &dst)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	auto hero = std::visit(HeroObjectRetriever(), dst.artHolder);
-	adventureInt->onHeroChanged(hero);
+	adventureInt->onHeroChanged(cb->getHero(dst.artHolder));
 
 
 	bool redraw = true;
 	bool redraw = true;
 	// If a bulk transfer has arrived, then redrawing only the last art movement.
 	// If a bulk transfer has arrived, then redrawing only the last art movement.
@@ -1815,8 +1811,7 @@ void CPlayerInterface::bulkArtMovementStart(size_t numOfArts)
 void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
 void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	auto hero = std::visit(HeroObjectRetriever(), al.artHolder);
-	adventureInt->onHeroChanged(hero);
+	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
 
 
 	for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
 	for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
 		artWin->artifactAssembled(al);
 		artWin->artifactAssembled(al);
@@ -1825,8 +1820,7 @@ void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
 void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
 void CPlayerInterface::artifactDisassembled(const ArtifactLocation &al)
 {
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	EVENT_HANDLER_CALLED_BY_CLIENT;
-	auto hero = std::visit(HeroObjectRetriever(), al.artHolder);
-	adventureInt->onHeroChanged(hero);
+	adventureInt->onHeroChanged(cb->getHero(al.artHolder));
 
 
 	for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
 	for(auto artWin : GH.windows().findWindows<CArtifactHolder>())
 		artWin->artifactDisassembled(al);
 		artWin->artifactDisassembled(al);

+ 1 - 1
client/HeroMovementController.cpp

@@ -359,7 +359,7 @@ void HeroMovementController::moveOnce(const CGHeroInstance * h, const CGPath & p
 	{
 	{
 		stopMovementSound();
 		stopMovementSound();
 		logGlobal->trace("Requesting hero teleportation to %s", nextNode.coord.toString());
 		logGlobal->trace("Requesting hero teleportation to %s", nextNode.coord.toString());
-		LOCPLINT->cb->moveHero(h, nextCoord, false);
+		LOCPLINT->cb->moveHero(h, h->pos, false);
 		return;
 		return;
 	}
 	}
 	else
 	else

+ 11 - 11
client/NetPacksClient.cpp

@@ -267,14 +267,14 @@ void ApplyClientNetPackVisitor::visitBulkSmartRebalanceStacks(BulkSmartRebalance
 
 
 void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
 void ApplyClientNetPackVisitor::visitPutArtifact(PutArtifact & pack)
 {
 {
-	callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::artifactPut, pack.al);
+	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactPut, pack.al);
 	if(pack.askAssemble)
 	if(pack.askAssemble)
-		callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::askToAssembleArtifact, pack.al);
+		callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::askToAssembleArtifact, pack.al);
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitEraseArtifact(EraseArtifact & pack)
 void ApplyClientNetPackVisitor::visitEraseArtifact(EraseArtifact & pack)
 {
 {
-	callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::artifactRemoved, pack.al);
+	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactRemoved, pack.al);
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitMoveArtifact(MoveArtifact & pack)
 void ApplyClientNetPackVisitor::visitMoveArtifact(MoveArtifact & pack)
@@ -286,9 +286,9 @@ void ApplyClientNetPackVisitor::visitMoveArtifact(MoveArtifact & pack)
 			callInterfaceIfPresent(cl, player, &IGameEventsReceiver::askToAssembleArtifact, pack.dst);
 			callInterfaceIfPresent(cl, player, &IGameEventsReceiver::askToAssembleArtifact, pack.dst);
 	};
 	};
 
 
-	moveArtifact(pack.src.owningPlayer());
-	if(pack.src.owningPlayer() != pack.dst.owningPlayer())
-		moveArtifact(pack.dst.owningPlayer());
+	moveArtifact(cl.getOwner(pack.src.artHolder));
+	if(cl.getOwner(pack.src.artHolder) != cl.getOwner(pack.dst.artHolder))
+		moveArtifact(cl.getOwner(pack.dst.artHolder));
 
 
 	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
 	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
 }
 }
@@ -306,8 +306,8 @@ void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
 		}
 		}
 	};
 	};
 
 
-	auto srcOwner = std::get<ConstTransitivePtr<CGHeroInstance>>(pack.srcArtHolder)->tempOwner;
-	auto dstOwner = std::get<ConstTransitivePtr<CGHeroInstance>>(pack.dstArtHolder)->tempOwner;
+	auto srcOwner = cl.getOwner(pack.srcArtHolder);
+	auto dstOwner = cl.getOwner(pack.dstArtHolder);
 
 
 	// Begin a session of bulk movement of arts. It is not necessary but useful for the client optimization.
 	// Begin a session of bulk movement of arts. It is not necessary but useful for the client optimization.
 	callInterfaceIfPresent(cl, srcOwner, &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
 	callInterfaceIfPresent(cl, srcOwner, &IGameEventsReceiver::bulkArtMovementStart, pack.artsPack0.size() + pack.artsPack1.size());
@@ -321,14 +321,14 @@ void ApplyClientNetPackVisitor::visitBulkMoveArtifacts(BulkMoveArtifacts & pack)
 
 
 void ApplyClientNetPackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
 void ApplyClientNetPackVisitor::visitAssembledArtifact(AssembledArtifact & pack)
 {
 {
-	callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::artifactAssembled, pack.al);
+	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactAssembled, pack.al);
 
 
 	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
 	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
 }
 }
 
 
 void ApplyClientNetPackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack)
 void ApplyClientNetPackVisitor::visitDisassembledArtifact(DisassembledArtifact & pack)
 {
 {
-	callInterfaceIfPresent(cl, pack.al.owningPlayer(), &IGameEventsReceiver::artifactDisassembled, pack.al);
+	callInterfaceIfPresent(cl, cl.getOwner(pack.al.artHolder), &IGameEventsReceiver::artifactDisassembled, pack.al);
 
 
 	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
 	cl.invalidatePaths(); // hero might have equipped/unequipped Angel Wings
 }
 }
@@ -604,7 +604,7 @@ void ApplyClientNetPackVisitor::visitSetHeroesInTown(SetHeroesInTown & pack)
 void ApplyClientNetPackVisitor::visitHeroRecruited(HeroRecruited & pack)
 void ApplyClientNetPackVisitor::visitHeroRecruited(HeroRecruited & pack)
 {
 {
 	CGHeroInstance *h = gs.map->heroesOnMap.back();
 	CGHeroInstance *h = gs.map->heroesOnMap.back();
-	if(h->subID != pack.hid)
+	if(h->getHeroType() != pack.hid)
 	{
 	{
 		logNetwork->error("Something wrong with hero recruited!");
 		logNetwork->error("Something wrong with hero recruited!");
 	}
 	}

+ 17 - 13
client/adventureMap/CInfoBar.cpp

@@ -376,47 +376,51 @@ void CInfoBar::pushComponents(const std::vector<Component> & components, std::st
 		std::array<std::pair<std::vector<Component>, int>, 10> reward_map;
 		std::array<std::pair<std::vector<Component>, int>, 10> reward_map;
 		for(const auto & c : components)
 		for(const auto & c : components)
 		{
 		{
-			switch(c.id)
+			switch(c.type)
 			{
 			{
-				case Component::EComponentType::PRIM_SKILL:
-				case Component::EComponentType::EXPERIENCE: 
+				case ComponentType::PRIM_SKILL:
+				case ComponentType::EXPERIENCE:
+				case ComponentType::LEVEL:
+				case ComponentType::MANA:
 					reward_map.at(0).first.push_back(c);
 					reward_map.at(0).first.push_back(c);
 					reward_map.at(0).second = 8; //At most 8, cannot be more
 					reward_map.at(0).second = 8; //At most 8, cannot be more
 					break;
 					break;
-				case Component::EComponentType::SEC_SKILL:
+				case ComponentType::SEC_SKILL:
 					reward_map.at(1).first.push_back(c);
 					reward_map.at(1).first.push_back(c);
 					reward_map.at(1).second = 4; //At most 4
 					reward_map.at(1).second = 4; //At most 4
 					break;
 					break;
-				case Component::EComponentType::SPELL: 
+				case ComponentType::SPELL:
 					reward_map.at(2).first.push_back(c);
 					reward_map.at(2).first.push_back(c);
 					reward_map.at(2).second = 4; //At most 4
 					reward_map.at(2).second = 4; //At most 4
 					break;
 					break;
-				case Component::EComponentType::ARTIFACT:
+				case ComponentType::ARTIFACT:
+				case ComponentType::SPELL_SCROLL:
 					reward_map.at(3).first.push_back(c);
 					reward_map.at(3).first.push_back(c);
 					reward_map.at(3).second = 4; //At most 4, too long names
 					reward_map.at(3).second = 4; //At most 4, too long names
 					break;
 					break;
-				case Component::EComponentType::CREATURE:
+				case ComponentType::CREATURE:
 					reward_map.at(4).first.push_back(c);
 					reward_map.at(4).first.push_back(c);
 					reward_map.at(4).second = 4; //At most 4, too long names
 					reward_map.at(4).second = 4; //At most 4, too long names
 					break;
 					break;
-				case Component::EComponentType::RESOURCE:
+				case ComponentType::RESOURCE:
+				case ComponentType::RESOURCE_PER_DAY:
 					reward_map.at(5).first.push_back(c);
 					reward_map.at(5).first.push_back(c);
 					reward_map.at(5).second = 7; //At most 7
 					reward_map.at(5).second = 7; //At most 7
 					break;
 					break;
-				case Component::EComponentType::MORALE: 
-				case Component::EComponentType::LUCK:
+				case ComponentType::MORALE:
+				case ComponentType::LUCK:
 					reward_map.at(6).first.push_back(c);
 					reward_map.at(6).first.push_back(c);
 					reward_map.at(6).second = 2; //At most 2 - 1 for morale + 1 for luck
 					reward_map.at(6).second = 2; //At most 2 - 1 for morale + 1 for luck
 					break;
 					break;
-				case Component::EComponentType::BUILDING:
+				case ComponentType::BUILDING:
 					reward_map.at(7).first.push_back(c);
 					reward_map.at(7).first.push_back(c);
 					reward_map.at(7).second = 1; //At most 1 - only large icons available AFAIK
 					reward_map.at(7).second = 1; //At most 1 - only large icons available AFAIK
 					break;
 					break;
-				case Component::EComponentType::HERO_PORTRAIT:
+				case ComponentType::HERO_PORTRAIT:
 					reward_map.at(8).first.push_back(c);
 					reward_map.at(8).first.push_back(c);
 					reward_map.at(8).second = 1; //I do not think than we even can get more than 1 hero
 					reward_map.at(8).second = 1; //I do not think than we even can get more than 1 hero
 					break;
 					break;
-				case Component::EComponentType::FLAG:
+				case ComponentType::FLAG:
 					reward_map.at(9).first.push_back(c);
 					reward_map.at(9).first.push_back(c);
 					reward_map.at(9).second = 1; //I do not think than we even can get more than 1 player in notification
 					reward_map.at(9).second = 1; //I do not think than we even can get more than 1 player in notification
 					break;
 					break;

+ 1 - 1
client/battle/BattleActionsController.cpp

@@ -905,7 +905,7 @@ void BattleActionsController::tryActivateStackSpellcasting(const CStack *casterS
 const spells::Caster * BattleActionsController::getCurrentSpellcaster() const
 const spells::Caster * BattleActionsController::getCurrentSpellcaster() const
 {
 {
 	if (heroSpellToCast)
 	if (heroSpellToCast)
-		return owner.getActiveHero();
+		return owner.currentHero();
 	else
 	else
 		return owner.stacksController->getActiveStack();
 		return owner.stacksController->getActiveStack();
 }
 }

+ 10 - 2
client/battle/BattleObstacleController.cpp

@@ -27,6 +27,7 @@
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/battle/CObstacleInstance.h"
 #include "../../lib/ObstacleHandler.h"
 #include "../../lib/ObstacleHandler.h"
+#include "../../lib/serializer/JsonDeserializer.h"
 
 
 BattleObstacleController::BattleObstacleController(BattleInterface & owner):
 BattleObstacleController::BattleObstacleController(BattleInterface & owner):
 	owner(owner),
 	owner(owner),
@@ -77,7 +78,14 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 			continue;
 			continue;
 		}
 		}
 
 
-		auto animation = GH.renderHandler().loadAnimation(AnimationPath::fromJson(obstacle["appearAnimation"]));
+		AnimationPath animationPath;
+		JsonDeserializer ser(nullptr, obstacle);
+		ser.serializeStruct("appearAnimation", animationPath);
+
+		if(animationPath.empty())
+			continue;
+
+		auto animation = GH.renderHandler().loadAnimation(animationPath);
 		animation->preload();
 		animation->preload();
 
 
 		auto first = animation->getImage(0, 0);
 		auto first = animation->getImage(0, 0);
@@ -88,7 +96,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 		// -> if we know how to blit obstacle, let's blit the effect in the same place
 		// -> if we know how to blit obstacle, let's blit the effect in the same place
 		Point whereTo = getObstaclePosition(first, obstacle);
 		Point whereTo = getObstaclePosition(first, obstacle);
 		//AFAIK, in H3 there is no sound of obstacle removal
 		//AFAIK, in H3 there is no sound of obstacle removal
-		owner.stacksController->addNewAnim(new EffectAnimation(owner, AnimationPath::fromJson(obstacle["appearAnimation"]), whereTo, obstacle["position"].Integer(), 0, true));
+		owner.stacksController->addNewAnim(new EffectAnimation(owner, animationPath, whereTo, obstacle["position"].Integer(), 0, true));
 
 
 		obstacleAnimations.erase(oi.id);
 		obstacleAnimations.erase(oi.id);
 		//so when multiple obstacles are removed, they show up one after another
 		//so when multiple obstacles are removed, they show up one after another

+ 1 - 1
client/lobby/CBonusSelection.cpp

@@ -161,7 +161,7 @@ void CBonusSelection::createBonusesIcons()
 			break;
 			break;
 		case CampaignBonusType::BUILDING:
 		case CampaignBonusType::BUILDING:
 		{
 		{
-			int faction = -1;
+			FactionID faction;
 			for(auto & elem : CSH->si->playerInfos)
 			for(auto & elem : CSH->si->playerInfos)
 			{
 			{
 				if(elem.second.isControlledByHuman())
 				if(elem.second.isControlledByHuman())

+ 4 - 2
client/lobby/CSelectionBase.cpp

@@ -209,7 +209,6 @@ void InfoCard::changeSelection()
 		return;
 		return;
 
 
 	labelSaveDate->setText(mapInfo->date);
 	labelSaveDate->setText(mapInfo->date);
-	labelMapSize->setText(std::to_string(mapInfo->mapHeader->width) + "x" + std::to_string(mapInfo->mapHeader->height));
 	mapName->setText(mapInfo->getNameTranslated());
 	mapName->setText(mapInfo->getNameTranslated());
 	mapDescription->setText(mapInfo->getDescriptionTranslated());
 	mapDescription->setText(mapInfo->getDescriptionTranslated());
 
 
@@ -220,8 +219,11 @@ void InfoCard::changeSelection()
 	if(SEL->screenType == ESelectionScreen::campaignList)
 	if(SEL->screenType == ESelectionScreen::campaignList)
 		return;
 		return;
 
 
-	iconsMapSizes->setFrame(mapInfo->getMapSizeIconId());
 	const CMapHeader * header = mapInfo->mapHeader.get();
 	const CMapHeader * header = mapInfo->mapHeader.get();
+
+	labelMapSize->setText(std::to_string(header->width) + "x" + std::to_string(header->height));
+	iconsMapSizes->setFrame(mapInfo->getMapSizeIconId());
+
 	iconsVictoryCondition->setFrame(header->victoryIconIndex);
 	iconsVictoryCondition->setFrame(header->victoryIconIndex);
 	labelVictoryConditionText->setText(header->victoryMessage.toString());
 	labelVictoryConditionText->setText(header->victoryMessage.toString());
 	iconsLossCondition->setFrame(header->defeatIconIndex);
 	iconsLossCondition->setFrame(header->defeatIconIndex);

+ 1 - 1
client/lobby/OptionsTab.cpp

@@ -571,7 +571,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genTownWindow()
 	for(auto & elem : town->creatures)
 	for(auto & elem : town->creatures)
 	{
 	{
 		if(!elem.empty())
 		if(!elem.empty())
-			components.push_back(std::make_shared<CComponent>(CComponent::creature, elem.front(), 0, CComponent::tiny));
+			components.push_back(std::make_shared<CComponent>(ComponentType::CREATURE, elem.front(), 0, CComponent::tiny));
 	}
 	}
 	boxAssociatedCreatures = std::make_shared<CComponentBox>(components, Rect(10, 140, pos.w - 20, 140));
 	boxAssociatedCreatures = std::make_shared<CComponentBox>(components, Rect(10, 140, pos.w - 20, 140));
 }
 }

+ 2 - 3
client/mainmenu/CHighScoreScreen.cpp

@@ -252,7 +252,7 @@ int CHighScoreInputScreen::addEntry(std::string text) {
 	auto sortFunctor = [](const JsonNode & left, const JsonNode & right)
 	auto sortFunctor = [](const JsonNode & left, const JsonNode & right)
 	{
 	{
 		if(left["points"].Integer() == right["points"].Integer())
 		if(left["points"].Integer() == right["points"].Integer())
-			return left["posFlag"].Integer() > right["posFlag"].Integer();
+			return left["posFlag"].Bool() > right["posFlag"].Bool();
 		return left["points"].Integer() > right["points"].Integer();
 		return left["points"].Integer() > right["points"].Integer();
 	};
 	};
 
 
@@ -325,7 +325,6 @@ void CHighScoreInputScreen::deactivate()
 {
 {
 	CCS->videoh->close();
 	CCS->videoh->close();
 	CCS->soundh->stopSound(videoSoundHandle);
 	CCS->soundh->stopSound(videoSoundHandle);
-	CIntObject::deactivate();
 }
 }
 
 
 void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
 void CHighScoreInputScreen::clickPressed(const Point & cursorPosition)
@@ -361,7 +360,7 @@ void CHighScoreInputScreen::keyPressed(EShortcut key)
 }
 }
 
 
 CHighScoreInput::CHighScoreInput(std::string playerName, std::function<void(std::string text)> readyCB)
 CHighScoreInput::CHighScoreInput(std::string playerName, std::function<void(std::string text)> readyCB)
-	: CWindowObject(0, ImagePath::builtin("HIGHNAME")), ready(readyCB)
+	: CWindowObject(NEEDS_ANIMATED_BACKGROUND, ImagePath::builtin("HIGHNAME")), ready(readyCB)
 {
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
 

+ 1 - 5
client/mapView/MapRendererContext.cpp

@@ -427,11 +427,7 @@ size_t MapRendererWorldViewContext::overlayImageIndex(const int3 & coordinates)
 		if(!object->visitableAt(coordinates.x, coordinates.y))
 		if(!object->visitableAt(coordinates.x, coordinates.y))
 			continue;
 			continue;
 
 
-		ObjectPosInfo info;
-		info.pos = coordinates;
-		info.id = object->ID;
-		info.subId = object->subID;
-		info.owner = object->tempOwner;
+		ObjectPosInfo info(object);
 
 
 		size_t iconIndex = selectOverlayImageForObject(info);
 		size_t iconIndex = selectOverlayImageForObject(info);
 
 

+ 11 - 3
client/render/CAnimation.cpp

@@ -204,11 +204,19 @@ void CAnimation::printError(size_t frame, size_t group, std::string type) const
 
 
 CAnimation::CAnimation(const AnimationPath & Name):
 CAnimation::CAnimation(const AnimationPath & Name):
 	name(boost::starts_with(Name.getName(), "SPRITES") ? Name : Name.addPrefix("SPRITES/")),
 	name(boost::starts_with(Name.getName(), "SPRITES") ? Name : Name.addPrefix("SPRITES/")),
-	preloaded(false),
-	defFile()
+	preloaded(false)
 {
 {
 	if(CResourceHandler::get()->existsResource(name))
 	if(CResourceHandler::get()->existsResource(name))
-		defFile = std::make_shared<CDefFile>(name);
+	{
+		try
+		{
+			defFile = std::make_shared<CDefFile>(name);
+		}
+		catch ( const std::runtime_error & e)
+		{
+			logAnim->error("Def file %s failed to load! Reason: %s", Name.getOriginalName(), e.what());
+		}
+	}
 
 
 	init();
 	init();
 
 

+ 10 - 10
client/widgets/CArtifactHolder.cpp

@@ -49,7 +49,7 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
 		if(settings["general"]["enableUiEnhancements"].Bool())
 		if(settings["general"]["enableUiEnhancements"].Bool())
 		{
 		{
 			imageIndex = spellID.num;
 			imageIndex = spellID.num;
-			if(baseType != CComponent::spell)
+			if(component.type != ComponentType::SPELL_SCROLL)
 			{
 			{
 				image->setScale(Point(pos.w, 34));
 				image->setScale(Point(pos.w, 34));
 				image->setAnimationPath(AnimationPath::builtin("spellscr"), imageIndex);
 				image->setAnimationPath(AnimationPath::builtin("spellscr"), imageIndex);
@@ -57,21 +57,20 @@ void CArtPlace::setInternals(const CArtifactInstance * artInst)
 			}
 			}
 		}
 		}
 		// Add spell component info (used to provide a pic in r-click popup)
 		// Add spell component info (used to provide a pic in r-click popup)
-		baseType = CComponent::spell;
-		type = spellID;
+		component.type = ComponentType::SPELL_SCROLL;
+		component.subType = spellID;
 	}
 	}
 	else
 	else
 	{
 	{
-		if(settings["general"]["enableUiEnhancements"].Bool() && baseType != CComponent::artifact)
+		if(settings["general"]["enableUiEnhancements"].Bool() && component.type != ComponentType::ARTIFACT)
 		{
 		{
 			image->setScale(Point());
 			image->setScale(Point());
 			image->setAnimationPath(AnimationPath::builtin("artifact"), imageIndex);
 			image->setAnimationPath(AnimationPath::builtin("artifact"), imageIndex);
 			image->moveTo(Point(pos.x, pos.y));
 			image->moveTo(Point(pos.x, pos.y));
 		}
 		}
-		baseType = CComponent::artifact;
-		type = artInst->getTypeId();
+		component.type = ComponentType::ARTIFACT;
+		component.subType = artInst->getTypeId();
 	}
 	}
-	bonusValue = 0;
 	image->enable();
 	image->enable();
 	text = artInst->getDescription();
 	text = artInst->getDescription();
 }
 }
@@ -121,10 +120,11 @@ void CCommanderArtPlace::returnArtToHeroCallback()
 	}
 	}
 	else
 	else
 	{
 	{
-		ArtifactLocation src(commanderOwner->commander.get(), artifactPos);
-		ArtifactLocation dst(commanderOwner, freeSlot);
+		ArtifactLocation src(commanderOwner->id, artifactPos);
+		src.creature = SlotID::COMMANDER_SLOT_PLACEHOLDER;
+		ArtifactLocation dst(commanderOwner->id, freeSlot);
 
 
-		if(ourArt->canBePutAt(dst, true))
+		if(ourArt->canBePutAt(commanderOwner, freeSlot, true))
 		{
 		{
 			LOCPLINT->cb->swapArtifacts(src, dst);
 			LOCPLINT->cb->swapArtifacts(src, dst);
 			setArtifact(nullptr);
 			setArtifact(nullptr);

+ 2 - 2
client/widgets/CArtifactsOfHeroAltar.cpp

@@ -72,7 +72,7 @@ void CArtifactsOfHeroAltar::pickUpArtifact(CHeroArtPlace & artPlace)
 		if(ArtifactUtils::isSlotBackpack(pickedArtFromSlot))
 		if(ArtifactUtils::isSlotBackpack(pickedArtFromSlot))
 			pickedArtFromSlot = curHero->getSlotByInstance(art);
 			pickedArtFromSlot = curHero->getSlotByInstance(art);
 		assert(pickedArtFromSlot != ArtifactPosition::PRE_FIRST);
 		assert(pickedArtFromSlot != ArtifactPosition::PRE_FIRST);
-		LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, pickedArtFromSlot), ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+		LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, pickedArtFromSlot), ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
 	}
 	}
 }
 }
 
 
@@ -89,7 +89,7 @@ void CArtifactsOfHeroAltar::pickedArtMoveToAltar(const ArtifactPosition & slot)
 	if(ArtifactUtils::isSlotBackpack(slot) || ArtifactUtils::isSlotEquipment(slot) || slot == ArtifactPosition::TRANSITION_POS)
 	if(ArtifactUtils::isSlotBackpack(slot) || ArtifactUtils::isSlotEquipment(slot) || slot == ArtifactPosition::TRANSITION_POS)
 	{
 	{
 		assert(curHero->getSlot(slot)->getArt());
 		assert(curHero->getSlot(slot)->getArt());
-		LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, slot), ArtifactLocation(curHero, pickedArtFromSlot));
+		LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, slot), ArtifactLocation(curHero->id, pickedArtFromSlot));
 		pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
 		pickedArtFromSlot = ArtifactPosition::PRE_FIRST;
 	}
 	}
 }
 }

+ 2 - 2
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -80,8 +80,8 @@ void CArtifactsOfHeroBackpack::swapArtifacts(const ArtifactLocation & srcLoc, co
 
 
 void CArtifactsOfHeroBackpack::pickUpArtifact(CHeroArtPlace & artPlace)
 void CArtifactsOfHeroBackpack::pickUpArtifact(CHeroArtPlace & artPlace)
 {
 {
-	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, artPlace.slot),
-		ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
+		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
 }
 }
 
 
 void CArtifactsOfHeroBackpack::scrollBackpack(int offset)
 void CArtifactsOfHeroBackpack::scrollBackpack(int offset)

+ 3 - 3
client/widgets/CArtifactsOfHeroBase.cpp

@@ -39,11 +39,11 @@ void CArtifactsOfHeroBase::putBackPickedArtifact()
 		auto slot = ArtifactUtils::getArtAnyPosition(curHero, curHero->artifactsTransitionPos.begin()->artifact->getTypeId());
 		auto slot = ArtifactUtils::getArtAnyPosition(curHero, curHero->artifactsTransitionPos.begin()->artifact->getTypeId());
 		if(slot == ArtifactPosition::PRE_FIRST)
 		if(slot == ArtifactPosition::PRE_FIRST)
 		{
 		{
-			LOCPLINT->cb->eraseArtifactByClient(ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+			LOCPLINT->cb->eraseArtifactByClient(ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
 		}
 		}
 		else
 		else
 		{
 		{
-			LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS), ArtifactLocation(curHero, slot));
+			LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS), ArtifactLocation(curHero->id, slot));
 		}
 		}
 	}
 	}
 	if(putBackPickedArtCallback)
 	if(putBackPickedArtCallback)
@@ -178,7 +178,7 @@ void CArtifactsOfHeroBase::scrollBackpackForArtSet(int offset, const CArtifactSe
 void CArtifactsOfHeroBase::markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved)
 void CArtifactsOfHeroBase::markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved)
 {
 {
 	for(auto artPlace : artWorn)
 	for(auto artPlace : artWorn)
-		artPlace.second->selectSlot(art->artType->canBePutAt(curHero, artPlace.second->slot, assumeDestRemoved));
+		artPlace.second->selectSlot(art->canBePutAt(curHero, artPlace.second->slot, assumeDestRemoved));
 }
 }
 
 
 void CArtifactsOfHeroBase::unmarkSlots()
 void CArtifactsOfHeroBase::unmarkSlots()

+ 3 - 2
client/widgets/CArtifactsOfHeroKingdom.cpp

@@ -13,6 +13,7 @@
 #include "Buttons.h"
 #include "Buttons.h"
 
 
 #include "../CPlayerInterface.h"
 #include "../CPlayerInterface.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
 
 
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
@@ -56,7 +57,7 @@ void CArtifactsOfHeroKingdom::swapArtifacts(const ArtifactLocation & srcLoc, con
 
 
 void CArtifactsOfHeroKingdom::pickUpArtifact(CHeroArtPlace & artPlace)
 void CArtifactsOfHeroKingdom::pickUpArtifact(CHeroArtPlace & artPlace)
 {
 {
-	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, artPlace.slot),
-		ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
+		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
 }
 }
 
 

+ 3 - 2
client/widgets/CArtifactsOfHeroMain.cpp

@@ -11,6 +11,7 @@
 #include "CArtifactsOfHeroMain.h"
 #include "CArtifactsOfHeroMain.h"
 
 
 #include "../CPlayerInterface.h"
 #include "../CPlayerInterface.h"
+#include "../../lib/mapObjects/CGHeroInstance.h"
 
 
 #include "../../CCallback.h"
 #include "../../CCallback.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
 #include "../../lib/networkPacks/ArtifactLocation.h"
@@ -36,6 +37,6 @@ void CArtifactsOfHeroMain::swapArtifacts(const ArtifactLocation & srcLoc, const
 
 
 void CArtifactsOfHeroMain::pickUpArtifact(CHeroArtPlace & artPlace)
 void CArtifactsOfHeroMain::pickUpArtifact(CHeroArtPlace & artPlace)
 {
 {
-	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero, artPlace.slot),
-		ArtifactLocation(curHero, ArtifactPosition::TRANSITION_POS));
+	LOCPLINT->cb->swapArtifacts(ArtifactLocation(curHero->id, artPlace.slot),
+		ArtifactLocation(curHero->id, ArtifactPosition::TRANSITION_POS));
 }
 }

+ 183 - 133
client/widgets/CComponent.cpp

@@ -39,41 +39,35 @@
 #include "../../lib/CArtHandler.h"
 #include "../../lib/CArtHandler.h"
 #include "../../lib/CArtifactInstance.h"
 #include "../../lib/CArtifactInstance.h"
 
 
-CComponent::CComponent(Etype Type, int Subtype, int Val, ESize imageSize, EFonts font):
-	perDay(false)
+CComponent::CComponent(ComponentType Type, ComponentSubType Subtype, std::optional<int32_t> Val, ESize imageSize, EFonts font)
 {
 {
 	init(Type, Subtype, Val, imageSize, font, "");
 	init(Type, Subtype, Val, imageSize, font, "");
 }
 }
 
 
-CComponent::CComponent(Etype Type, int Subtype, std::string Val, ESize imageSize, EFonts font):
-	perDay(false)
+CComponent::CComponent(ComponentType Type, ComponentSubType Subtype, const std::string & Val, ESize imageSize, EFonts font)
 {
 {
-	init(Type, Subtype, 0, imageSize, font, Val);
+	init(Type, Subtype, std::nullopt, imageSize, font, Val);
 }
 }
 
 
 CComponent::CComponent(const Component & c, ESize imageSize, EFonts font)
 CComponent::CComponent(const Component & c, ESize imageSize, EFonts font)
-	: perDay(false)
 {
 {
-	if(c.id == Component::EComponentType::RESOURCE && c.when==-1)
-		perDay = true;
-
-	init((Etype)c.id, c.subtype, c.val, imageSize, font);
+	init(c.type, c.subType, c.value, imageSize, font, "");
 }
 }
 
 
-void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts fnt, std::string ValText)
+void CComponent::init(ComponentType Type, ComponentSubType Subtype, std::optional<int32_t> Val, ESize imageSize, EFonts fnt, const std::string & ValText)
 {
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
 
 	addUsedEvents(SHOW_POPUP);
 	addUsedEvents(SHOW_POPUP);
 
 
-	compType = Type;
-	subtype = Subtype;
-	val = Val;
-	valText = ValText;
+	data.type = Type;
+	data.subType = Subtype;
+	data.value = Val;
+
+	customSubtitle = ValText;
 	size = imageSize;
 	size = imageSize;
 	font = fnt;
 	font = fnt;
 
 
-	assert(compType < typeInvalid);
 	assert(size < sizeInvalid);
 	assert(size < sizeInvalid);
 
 
 	setSurface(getFileName()[size], (int)getIndex());
 	setSurface(getFileName()[size], (int)getIndex());
@@ -94,7 +88,7 @@ void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts
 	if (size < small)
 	if (size < small)
 		max = 30;
 		max = 30;
 
 
-	if(Type == Etype::resource && !valText.empty())
+	if(Type == ComponentType::RESOURCE && !ValText.empty())
 		max = 80;
 		max = 80;
 
 
 	std::vector<std::string> textLines = CMessage::breakText(getSubtitle(), std::max<int>(max, pos.w), font);
 	std::vector<std::string> textLines = CMessage::breakText(getSubtitle(), std::max<int>(max, pos.w), font);
@@ -113,153 +107,209 @@ void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts
 	}
 	}
 }
 }
 
 
-std::vector<AnimationPath> CComponent::getFileName()
+std::vector<AnimationPath> CComponent::getFileName() const
 {
 {
-	static const std::string  primSkillsArr [] = {"PSKIL32",        "PSKIL32",        "PSKIL42",        "PSKILL"};
-	static const std::string  secSkillsArr [] =  {"SECSK32",        "SECSK32",        "SECSKILL",       "SECSK82"};
-	static const std::string  resourceArr [] =   {"SMALRES",        "RESOURCE",       "RESOURCE",       "RESOUR82"};
-	static const std::string  creatureArr [] =   {"CPRSMALL",       "CPRSMALL",       "CPRSMALL",       "TWCRPORT"};
-	static const std::string  artifactArr[]  =   {"Artifact",       "Artifact",       "Artifact",       "Artifact"};
-	static const std::string  spellsArr [] =     {"SpellInt",       "SpellInt",       "SpellInt",       "SPELLSCR"};
-	static const std::string  moraleArr [] =     {"IMRL22",         "IMRL30",         "IMRL42",         "imrl82"};
-	static const std::string  luckArr [] =       {"ILCK22",         "ILCK30",         "ILCK42",         "ilck82"};
-	static const std::string  heroArr [] =       {"PortraitsSmall", "PortraitsSmall", "PortraitsSmall", "PortraitsLarge"};
-	static const std::string  flagArr [] =       {"CREST58",        "CREST58",        "CREST58",        "CREST58"};
-
-	auto gen = [](const std::string * arr) -> std::vector<AnimationPath>
+	static const std::array<std::string, 4>  primSkillsArr = {"PSKIL32",        "PSKIL32",        "PSKIL42",        "PSKILL"};
+	static const std::array<std::string, 4>  secSkillsArr =  {"SECSK32",        "SECSK32",        "SECSKILL",       "SECSK82"};
+	static const std::array<std::string, 4>  resourceArr =   {"SMALRES",        "RESOURCE",       "RESOURCE",       "RESOUR82"};
+	static const std::array<std::string, 4>  creatureArr =   {"CPRSMALL",       "CPRSMALL",       "CPRSMALL",       "TWCRPORT"};
+	static const std::array<std::string, 4>  artifactArr =   {"Artifact",       "Artifact",       "Artifact",       "Artifact"};
+	static const std::array<std::string, 4>  spellsArr =     {"SpellInt",       "SpellInt",       "SpellInt",       "SPELLSCR"};
+	static const std::array<std::string, 4>  moraleArr =     {"IMRL22",         "IMRL30",         "IMRL42",         "imrl82"};
+	static const std::array<std::string, 4>  luckArr =       {"ILCK22",         "ILCK30",         "ILCK42",         "ilck82"};
+	static const std::array<std::string, 4>  heroArr =       {"PortraitsSmall", "PortraitsSmall", "PortraitsSmall", "PortraitsLarge"};
+	static const std::array<std::string, 4>  flagArr =       {"CREST58",        "CREST58",        "CREST58",        "CREST58"};
+
+	auto gen = [](const std::array<std::string, 4> & arr) -> std::vector<AnimationPath>
 	{
 	{
 		return { AnimationPath::builtin(arr[0]), AnimationPath::builtin(arr[1]), AnimationPath::builtin(arr[2]), AnimationPath::builtin(arr[3]) };
 		return { AnimationPath::builtin(arr[0]), AnimationPath::builtin(arr[1]), AnimationPath::builtin(arr[2]), AnimationPath::builtin(arr[3]) };
 	};
 	};
 
 
-	switch(compType)
+	switch(data.type)
 	{
 	{
-	case primskill:  return gen(primSkillsArr);
-	case secskill:   return gen(secSkillsArr);
-	case resource:   return gen(resourceArr);
-	case creature:   return gen(creatureArr);
-	case artifact:   return gen(artifactArr);
-	case experience: return gen(primSkillsArr);
-	case spell:      return gen(spellsArr);
-	case morale:     return gen(moraleArr);
-	case luck:       return gen(luckArr);
-	case building:   return std::vector<AnimationPath>(4, (*CGI->townh)[subtype]->town->clientInfo.buildingsIcons);
-	case hero:       return gen(heroArr);
-	case flag:       return gen(flagArr);
+		case ComponentType::PRIM_SKILL:
+		case ComponentType::EXPERIENCE:
+		case ComponentType::MANA:
+		case ComponentType::LEVEL:
+			return gen(primSkillsArr);
+		case ComponentType::SEC_SKILL:
+			return gen(secSkillsArr);
+		case ComponentType::RESOURCE:
+		case ComponentType::RESOURCE_PER_DAY:
+			return gen(resourceArr);
+		case ComponentType::CREATURE:
+			return gen(creatureArr);
+		case ComponentType::ARTIFACT:
+			return gen(artifactArr);
+		case ComponentType::SPELL_SCROLL:
+		case ComponentType::SPELL:
+			return gen(spellsArr);
+		case ComponentType::MORALE:
+			return gen(moraleArr);
+		case ComponentType::LUCK:
+			return gen(luckArr);
+		case ComponentType::BUILDING:
+			return std::vector<AnimationPath>(4, (*CGI->townh)[data.subType.as<BuildingTypeUniqueID>().getFaction()]->town->clientInfo.buildingsIcons);
+		case ComponentType::HERO_PORTRAIT:
+			return gen(heroArr);
+		case ComponentType::FLAG:
+			return gen(flagArr);
+		default:
+			assert(0);
+			return {};
 	}
 	}
-	assert(0);
-	return {};
 }
 }
 
 
-size_t CComponent::getIndex()
+size_t CComponent::getIndex() const
 {
 {
-	switch(compType)
+	switch(data.type)
 	{
 	{
-	case primskill:  return subtype;
-	case secskill:   return subtype*3 + 3 + val - 1;
-	case resource:   return subtype;
-	case creature:   return CGI->creatures()->getByIndex(subtype)->getIconIndex();
-	case artifact:   return CGI->artifacts()->getByIndex(subtype)->getIconIndex();
-	case experience: return 4;
-	case spell:      return (size < large) ? subtype + 1 : subtype;
-	case morale:     return val+3;
-	case luck:       return val+3;
-	case building:   return val;
-	case hero:       return CGI->heroTypes()->getByIndex(subtype)->getIconIndex();
-	case flag:       return subtype;
+		case ComponentType::PRIM_SKILL:
+			return data.subType.getNum();
+		case ComponentType::EXPERIENCE:
+		case ComponentType::LEVEL:
+			return 4; // for whatever reason, in H3 experience icon is located in primary skills icons
+		case ComponentType::MANA:
+			return 5; // for whatever reason, in H3 mana points icon is located in primary skills icons
+		case ComponentType::SEC_SKILL:
+			return data.subType.getNum() * 3 + 3 + data.value.value_or(0) - 1;
+		case ComponentType::RESOURCE:
+		case ComponentType::RESOURCE_PER_DAY:
+			return data.subType.getNum();
+		case ComponentType::CREATURE:
+			return CGI->creatures()->getById(data.subType.as<CreatureID>())->getIconIndex();
+		case ComponentType::ARTIFACT:
+			return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getIconIndex();
+		case ComponentType::SPELL_SCROLL:
+		case ComponentType::SPELL:
+			return (size < large) ? data.subType.getNum() + 1 : data.subType.getNum();
+		case ComponentType::MORALE:
+			return data.value.value_or(0) + 3;
+		case ComponentType::LUCK:
+			return data.value.value_or(0) + 3;
+		case ComponentType::BUILDING:
+			return data.subType.as<BuildingTypeUniqueID>().getBuilding();
+		case ComponentType::HERO_PORTRAIT:
+			return CGI->heroTypes()->getById(data.subType.as<HeroTypeID>())->getIconIndex();
+		case ComponentType::FLAG:
+			return data.subType.getNum();
+		default:
+			assert(0);
+			return 0;
 	}
 	}
-	assert(0);
-	return 0;
 }
 }
 
 
-std::string CComponent::getDescription()
+std::string CComponent::getDescription() const
 {
 {
-	switch(compType)
-	{
-	case primskill:  return (subtype < 4)? CGI->generaltexth->arraytxt[2+subtype] //Primary skill
-										 : CGI->generaltexth->allTexts[149]; //mana
-	case secskill:   return CGI->skillh->getByIndex(subtype)->getDescriptionTranslated(val);
-	case resource:   return CGI->generaltexth->allTexts[242];
-	case creature:   return "";
-	case artifact:
+	switch(data.type)
 	{
 	{
-		auto artID = ArtifactID(subtype);
-		auto description = VLC->arth->objects[artID]->getDescriptionTranslated();
-		if(artID == ArtifactID::SPELL_SCROLL)
+		case ComponentType::PRIM_SKILL:
+			return CGI->generaltexth->arraytxt[2+data.subType.getNum()];
+		case ComponentType::EXPERIENCE:
+		case ComponentType::LEVEL:
+			return CGI->generaltexth->allTexts[241];
+		case ComponentType::MANA:
+			return CGI->generaltexth->allTexts[149];
+		case ComponentType::SEC_SKILL:
+			return CGI->skillh->getByIndex(data.subType.getNum())->getDescriptionTranslated(data.value.value_or(0));
+		case ComponentType::RESOURCE:
+		case ComponentType::RESOURCE_PER_DAY:
+			return CGI->generaltexth->allTexts[242];
+		case ComponentType::CREATURE:
+			return "";
+		case ComponentType::ARTIFACT:
+			return VLC->artifacts()->getById(data.subType.as<ArtifactID>())->getDescriptionTranslated();
+		case ComponentType::SPELL_SCROLL:
 		{
 		{
-			ArtifactUtils::insertScrrollSpellName(description, SpellID(val));
+			auto description = VLC->arth->objects[ArtifactID::SPELL_SCROLL]->getDescriptionTranslated();
+			ArtifactUtils::insertScrrollSpellName(description, data.subType.as<SpellID>());
+			return description;
 		}
 		}
-		return description;
-	}
-	case experience: return CGI->generaltexth->allTexts[241];
-	case spell:      return (*CGI->spellh)[subtype]->getDescriptionTranslated(val);
-	case morale:     return CGI->generaltexth->heroscrn[ 4 - (val>0) + (val<0)];
-	case luck:       return CGI->generaltexth->heroscrn[ 7 - (val>0) + (val<0)];
-	case building:   return (*CGI->townh)[subtype]->town->buildings[BuildingID(val)]->getDescriptionTranslated();
-	case hero:       return "";
-	case flag:       return "";
+		case ComponentType::SPELL:
+			return VLC->spells()->getById(data.subType.as<SpellID>())->getDescriptionTranslated(data.value.value_or(0));
+		case ComponentType::MORALE:
+			return CGI->generaltexth->heroscrn[ 4 - (data.value.value_or(0)>0) + (data.value.value_or(0)<0)];
+		case ComponentType::LUCK:
+			return CGI->generaltexth->heroscrn[ 7 - (data.value.value_or(0)>0) + (data.value.value_or(0)<0)];
+		case ComponentType::BUILDING:
+		{
+			auto index = data.subType.as<BuildingTypeUniqueID>();
+			return (*CGI->townh)[index.getFaction()]->town->buildings[index.getBuilding()]->getDescriptionTranslated();
+		}
+		case ComponentType::HERO_PORTRAIT:
+			return "";
+		case ComponentType::FLAG:
+			return "";
+		default:
+			assert(0);
+			return "";
 	}
 	}
-	assert(0);
-	return "";
 }
 }
 
 
-std::string CComponent::getSubtitle()
+std::string CComponent::getSubtitle() const
 {
 {
-	if(!perDay)
-		return getSubtitleInternal();
-
-	std::string ret = CGI->generaltexth->allTexts[3];
-	boost::replace_first(ret, "%d", getSubtitleInternal());
-	return ret;
-}
+	if (!customSubtitle.empty())
+		return customSubtitle;
 
 
-std::string CComponent::getSubtitleInternal()
-{
-	//FIXME: some of these are horrible (e.g creature)
-	switch(compType)
+	switch(data.type)
 	{
 	{
-	case primskill:  return boost::str(boost::format("%+d %s") % val % (subtype < 4 ? CGI->generaltexth->primarySkillNames[subtype] : CGI->generaltexth->allTexts[387]));
-	case secskill:   return CGI->generaltexth->levels[val-1] + "\n" + CGI->skillh->getByIndex(subtype)->getNameTranslated();
-	case resource:   return valText.empty() ? std::to_string(val) : valText;
-	case creature:
-		{
-			auto creature = CGI->creh->getByIndex(subtype);
-			if ( val )
-				return std::to_string(val) + " " + (val > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated());
+		case ComponentType::PRIM_SKILL:
+			if (data.value)
+				return boost::str(boost::format("%+d %s") % data.value.value_or(0) % CGI->generaltexth->primarySkillNames[data.subType.getNum()]);
 			else
 			else
-				return val > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated();
+				return CGI->generaltexth->primarySkillNames[data.subType.getNum()];
+		case ComponentType::EXPERIENCE:
+			return std::to_string(data.value.value_or(0));
+		case ComponentType::LEVEL:
+		{
+			std::string level = CGI->generaltexth->allTexts[442];
+			boost::replace_first(level, "1", std::to_string(data.value.value_or(0)));
+			return level;
 		}
 		}
-	case artifact:   return CGI->artifacts()->getByIndex(subtype)->getNameTranslated();
-	case experience:
+		case ComponentType::MANA:
+			return boost::str(boost::format("%+d %s") % data.value.value_or(0) % CGI->generaltexth->allTexts[387]);
+		case ComponentType::SEC_SKILL:
+			return CGI->generaltexth->levels[data.value.value_or(0)-1] + "\n" + CGI->skillh->getById(data.subType.as<SecondarySkill>())->getNameTranslated();
+		case ComponentType::RESOURCE:
+			return std::to_string(data.value.value_or(0));
+		case ComponentType::RESOURCE_PER_DAY:
+			return boost::str(boost::format(CGI->generaltexth->allTexts[3]) % data.value.value_or(0));
+		case ComponentType::CREATURE:
 		{
 		{
-			if(subtype == 1) //+1 level - tree of knowledge
-			{
-				std::string level = CGI->generaltexth->allTexts[442];
-				boost::replace_first(level, "1", std::to_string(val));
-				return level;
-			}
+			auto creature = CGI->creh->getById(data.subType.as<CreatureID>());
+			if(data.value)
+				return std::to_string(*data.value) + " " + (*data.value > 1 ? creature->getNamePluralTranslated() : creature->getNameSingularTranslated());
 			else
 			else
-			{
-				return std::to_string(val); //amount of experience OR level required for seer hut;
-			}
+				return creature->getNamePluralTranslated();
 		}
 		}
-	case spell:      return CGI->spells()->getByIndex(subtype)->getNameTranslated();
-	case morale:     return "";
-	case luck:       return "";
-	case building:
-		{
-			auto building = (*CGI->townh)[subtype]->town->buildings[BuildingID(val)];
-			if(!building)
+		case ComponentType::ARTIFACT:
+			return CGI->artifacts()->getById(data.subType.as<ArtifactID>())->getNameTranslated();
+		case ComponentType::SPELL_SCROLL:
+		case ComponentType::SPELL:
+			return CGI->spells()->getById(data.subType.as<SpellID>())->getNameTranslated();
+		case ComponentType::MORALE:
+			return "";
+		case ComponentType::LUCK:
+			return "";
+		case ComponentType::BUILDING:
 			{
 			{
-				logGlobal->error("Town of faction %s has no building #%d", (*CGI->townh)[subtype]->town->faction->getNameTranslated(), val);
-				return (boost::format("Missing building #%d") % val).str();
+				auto index = data.subType.as<BuildingTypeUniqueID>();
+				auto building = (*CGI->townh)[index.getFaction()]->town->buildings[index.getBuilding()];
+				if(!building)
+				{
+					logGlobal->error("Town of faction %s has no building #%d", (*CGI->townh)[index.getFaction()]->town->faction->getNameTranslated(), index.getBuilding().getNum());
+					return (boost::format("Missing building #%d") % index.getBuilding().getNum()).str();
+				}
+				return building->getNameTranslated();
 			}
 			}
-			return building->getNameTranslated();
-		}
-	case hero:       return "";
-	case flag:       return CGI->generaltexth->capColors[subtype];
+		case ComponentType::HERO_PORTRAIT:
+			return "";
+		case ComponentType::FLAG:
+			return CGI->generaltexth->capColors[data.subType.as<PlayerColor>().getNum()];
+		default:
+			assert(0);
+			return "";
 	}
 	}
-	logGlobal->error("Invalid CComponent type: %d", (int)compType);
-	return "";
 }
 }
 
 
 void CComponent::setSurface(const AnimationPath & defName, int imgPos)
 void CComponent::setSurface(const AnimationPath & defName, int imgPos)
@@ -299,7 +349,7 @@ CSelectableComponent::CSelectableComponent(const Component &c, std::function<voi
 	init();
 	init();
 }
 }
 
 
-CSelectableComponent::CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize, std::function<void()> OnSelect):
+CSelectableComponent::CSelectableComponent(ComponentType Type, ComponentSubType Sub, int Val, ESize imageSize, std::function<void()> OnSelect):
 	CComponent(Type,Sub,Val, imageSize),onSelect(OnSelect)
 	CComponent(Type,Sub,Val, imageSize),onSelect(OnSelect)
 {
 {
 	setRedrawParent(true);
 	setRedrawParent(true);

+ 11 - 20
client/widgets/CComponent.h

@@ -12,6 +12,7 @@
 #include "../gui/CIntObject.h"
 #include "../gui/CIntObject.h"
 #include "../render/EFont.h"
 #include "../render/EFont.h"
 #include "../../lib/filesystem/ResourcePath.h"
 #include "../../lib/filesystem/ResourcePath.h"
+#include "../../lib/networkPacks/Component.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
@@ -26,11 +27,6 @@ class CLabel;
 class CComponent : public virtual CIntObject
 class CComponent : public virtual CIntObject
 {
 {
 public:
 public:
-	enum Etype
-	{
-		primskill, secskill, resource, creature, artifact, experience, spell, morale, luck, building, hero, flag, typeInvalid
-	};
-
 	//NOTE: not all types have exact these sizes or have less than 4 of them. In such cases closest one will be used
 	//NOTE: not all types have exact these sizes or have less than 4 of them. In such cases closest one will be used
 	enum ESize
 	enum ESize
 	{
 	{
@@ -44,29 +40,24 @@ public:
 private:
 private:
 	std::vector<std::shared_ptr<CLabel>> lines;
 	std::vector<std::shared_ptr<CLabel>> lines;
 
 
-	size_t getIndex();
-	std::vector<AnimationPath> getFileName();
+	size_t getIndex() const;
+	std::vector<AnimationPath> getFileName() const;
 	void setSurface(const AnimationPath & defName, int imgPos);
 	void setSurface(const AnimationPath & defName, int imgPos);
-	std::string getSubtitleInternal();
 
 
-	void init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts font = FONT_SMALL, std::string ValText="");
+	void init(ComponentType Type, ComponentSubType Subtype, std::optional<int32_t> Val, ESize imageSize, EFonts font, const std::string & ValText);
 
 
 public:
 public:
 	std::shared_ptr<CAnimImage> image;
 	std::shared_ptr<CAnimImage> image;
-
-	Etype compType; //component type
+	Component data;
+	std::string customSubtitle;
 	ESize size; //component size.
 	ESize size; //component size.
 	EFonts font; //Font size of label
 	EFonts font; //Font size of label
-	int subtype; //type-dependant subtype. See getSomething methods for details
-	int val; // value \ strength \ amount of component. See getSomething methods for details
-	std::string valText; // value instead of amount; currently only for resource
-	bool perDay; // add "per day" text to subtitle
 
 
-	std::string getDescription();
-	std::string getSubtitle();
+	std::string getDescription() const;
+	std::string getSubtitle() const;
 
 
-	CComponent(Etype Type, int Subtype, int Val = 0, ESize imageSize=large, EFonts font = FONT_SMALL);
-	CComponent(Etype Type, int Subtype, std::string Val, ESize imageSize=large, EFonts font = FONT_SMALL);
+	CComponent(ComponentType Type, ComponentSubType Subtype, std::optional<int32_t> Val = std::nullopt, ESize imageSize=large, EFonts font = FONT_SMALL);
+	CComponent(ComponentType Type, ComponentSubType Subtype, const std::string & Val, ESize imageSize=large, EFonts font = FONT_SMALL);
 	CComponent(const Component &c, ESize imageSize=large, EFonts font = FONT_SMALL);
 	CComponent(const Component &c, ESize imageSize=large, EFonts font = FONT_SMALL);
 
 
 	void showPopupWindow(const Point & cursorPosition) override; //call-in
 	void showPopupWindow(const Point & cursorPosition) override; //call-in
@@ -86,7 +77,7 @@ public:
 
 
 	void clickPressed(const Point & cursorPosition) override; //call-in
 	void clickPressed(const Point & cursorPosition) override; //call-in
 	void clickDouble(const Point & cursorPosition) override; //call-in
 	void clickDouble(const Point & cursorPosition) override; //call-in
-	CSelectableComponent(Etype Type, int Sub, int Val, ESize imageSize=large, std::function<void()> OnSelect = nullptr);
+	CSelectableComponent(ComponentType Type, ComponentSubType Sub, int Val, ESize imageSize=large, std::function<void()> OnSelect = nullptr);
 	CSelectableComponent(const Component & c, std::function<void()> OnSelect = nullptr);
 	CSelectableComponent(const Component & c, std::function<void()> OnSelect = nullptr);
 };
 };
 
 

+ 8 - 6
client/widgets/CGarrisonInt.cpp

@@ -196,16 +196,18 @@ bool CGarrisonSlot::highlightOrDropArtifact()
 			artSelected = true;
 			artSelected = true;
 			if (myStack) // try dropping the artifact only if the slot isn't empty
 			if (myStack) // try dropping the artifact only if the slot isn't empty
 			{
 			{
-				ArtifactLocation src(srcHero, ArtifactPosition::TRANSITION_POS);
-				ArtifactLocation dst(myStack, ArtifactPosition::CREATURE_SLOT);
-				if(pickedArtInst->canBePutAt(dst, true))
+				ArtifactLocation src(srcHero->id, ArtifactPosition::TRANSITION_POS);
+				ArtifactLocation dst(getObj()->id, ArtifactPosition::CREATURE_SLOT);
+				dst.creature = getSlot();
+
+				if(pickedArtInst->canBePutAt(myStack, ArtifactPosition::CREATURE_SLOT, true))
 				{	//equip clicked stack
 				{	//equip clicked stack
-					if(dst.getArt())
+					if(auto dstArt = myStack->getArt(ArtifactPosition::CREATURE_SLOT))
 					{
 					{
 						//creature can wear only one active artifact
 						//creature can wear only one active artifact
 						//if we are placing a new one, the old one will be returned to the hero's backpack
 						//if we are placing a new one, the old one will be returned to the hero's backpack
-						LOCPLINT->cb->swapArtifacts(dst, ArtifactLocation(srcHero,
-							ArtifactUtils::getArtBackpackPosition(srcHero, dst.getArt()->getTypeId())));
+						LOCPLINT->cb->swapArtifacts(dst, ArtifactLocation(srcHero->id,
+							ArtifactUtils::getArtBackpackPosition(srcHero, dstArt->getTypeId())));
 					}
 					}
 					LOCPLINT->cb->swapArtifacts(src, dst);
 					LOCPLINT->cb->swapArtifacts(src, dst);
 				}
 				}

+ 6 - 6
client/widgets/CWindowWithArtifacts.cpp

@@ -95,7 +95,7 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 		if(artPlace.getArt()->getTypeId() == ArtifactID::CATAPULT)
 		if(artPlace.getArt()->getTypeId() == ArtifactID::CATAPULT)
 		{
 		{
 			// The Catapult must be equipped
 			// The Catapult must be equipped
-			std::vector<std::shared_ptr<CComponent>> catapult(1, std::make_shared<CComponent>(CComponent::artifact, 3, 0));
+			std::vector<std::shared_ptr<CComponent>> catapult(1, std::make_shared<CComponent>(ComponentType::ARTIFACT, ArtifactID(ArtifactID::CATAPULT)));
 			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], catapult);
 			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[312], catapult);
 			return false;
 			return false;
 		}
 		}
@@ -122,8 +122,8 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 
 
 				if(pickedArtInst)
 				if(pickedArtInst)
 				{
 				{
-					auto srcLoc = ArtifactLocation(heroPickedArt, ArtifactPosition::TRANSITION_POS);
-					auto dstLoc = ArtifactLocation(hero, artPlace.slot);
+					auto srcLoc = ArtifactLocation(heroPickedArt->id, ArtifactPosition::TRANSITION_POS);
+					auto dstLoc = ArtifactLocation(hero->id, artPlace.slot);
 
 
 					if(ArtifactUtils::isSlotBackpack(artPlace.slot))
 					if(ArtifactUtils::isSlotBackpack(artPlace.slot))
 					{
 					{
@@ -141,7 +141,7 @@ void CWindowWithArtifacts::leftClickArtPlaceHero(CArtifactsOfHeroBase & artsInst
 						}
 						}
 					}
 					}
 					// Check if artifact transfer is possible
 					// Check if artifact transfer is possible
-					else if(pickedArtInst->canBePutAt(dstLoc, true) && (!artPlace.getArt() || hero->tempOwner == LOCPLINT->playerID))
+					else if(pickedArtInst->canBePutAt(hero, artPlace.slot, true) && (!artPlace.getArt() || hero->tempOwner == LOCPLINT->playerID))
 					{
 					{
 						isTransferAllowed = true;
 						isTransferAllowed = true;
 					}
 					}
@@ -270,7 +270,7 @@ void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const
 	// we have a different artifact may look surprising... but it's valid.
 	// we have a different artifact may look surprising... but it's valid.
 
 
 	auto pickedArtInst = std::get<const CArtifactInstance*>(curState.value());
 	auto pickedArtInst = std::get<const CArtifactInstance*>(curState.value());
-	assert(!pickedArtInst || destLoc.isHolder(std::get<const CGHeroInstance*>(curState.value())));
+	assert(!pickedArtInst || destLoc.artHolder == std::get<const CGHeroInstance*>(curState.value())->id);
 
 
 	auto artifactMovedBody = [this, withRedraw, &destLoc, &pickedArtInst](auto artSetWeak) -> void
 	auto artifactMovedBody = [this, withRedraw, &destLoc, &pickedArtInst](auto artSetWeak) -> void
 	{
 	{
@@ -316,7 +316,7 @@ void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const
 			}
 			}
 
 
 			// Make sure the status bar is updated so it does not display old text
 			// Make sure the status bar is updated so it does not display old text
-			if(destLoc.getHolderArtSet() == hero)
+			if(destLoc.artHolder == hero->id)
 			{
 			{
 				if(auto artPlace = artSetPtr->getArtPlace(destLoc.slot))
 				if(auto artPlace = artSetPtr->getArtPlace(destLoc.slot))
 					artPlace->hover(true);
 					artPlace->hover(true);

+ 19 - 25
client/widgets/MiscWidgets.cpp

@@ -95,16 +95,16 @@ void LRClickableAreaWTextComp::clickPressed(const Point & cursorPosition)
 	LOCPLINT->showInfoDialog(text, comp);
 	LOCPLINT->showInfoDialog(text, comp);
 }
 }
 
 
-LRClickableAreaWTextComp::LRClickableAreaWTextComp(const Rect &Pos, int BaseType)
-	: LRClickableAreaWText(Pos), baseType(BaseType), bonusValue(-1)
+LRClickableAreaWTextComp::LRClickableAreaWTextComp(const Rect &Pos, ComponentType BaseType)
+	: LRClickableAreaWText(Pos)
 {
 {
-	type = -1;
+	component.type = BaseType;
 }
 }
 
 
 std::shared_ptr<CComponent> LRClickableAreaWTextComp::createComponent() const
 std::shared_ptr<CComponent> LRClickableAreaWTextComp::createComponent() const
 {
 {
-	if(baseType >= 0)
-		return std::make_shared<CComponent>(CComponent::Etype(baseType), type, bonusValue);
+	if(component.type != ComponentType::NONE)
+		return std::make_shared<CComponent>(component);
 	else
 	else
 		return std::shared_ptr<CComponent>();
 		return std::shared_ptr<CComponent>();
 }
 }
@@ -164,17 +164,11 @@ void CHeroArea::hover(bool on)
 void LRClickableAreaOpenTown::clickPressed(const Point & cursorPosition)
 void LRClickableAreaOpenTown::clickPressed(const Point & cursorPosition)
 {
 {
 	if(town)
 	if(town)
-	{
 		LOCPLINT->openTownWindow(town);
 		LOCPLINT->openTownWindow(town);
-		if ( type == 2 )
-			LOCPLINT->castleInt->builds->buildingClicked(BuildingID::VILLAGE_HALL);
-		else if ( type == 3 && town->fortLevel() )
-			LOCPLINT->castleInt->builds->buildingClicked(BuildingID::FORT);
-	}
 }
 }
 
 
 LRClickableAreaOpenTown::LRClickableAreaOpenTown(const Rect & Pos, const CGTownInstance * Town)
 LRClickableAreaOpenTown::LRClickableAreaOpenTown(const Rect & Pos, const CGTownInstance * Town)
-	: LRClickableAreaWTextComp(Pos, -1), town(Town)
+	: LRClickableAreaWTextComp(Pos), town(Town)
 {
 {
 }
 }
 
 
@@ -542,20 +536,21 @@ void MoraleLuckBox::set(const AFactionMember * node)
 {
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 
 
-	const int textId[] = {62, 88}; //eg %s \n\n\n {Current Luck Modifiers:}
+	const std::array textId = {62, 88}; //eg %s \n\n\n {Current Luck Modifiers:}
 	const int noneTxtId = 108; //Russian version uses same text for neutral morale\luck
 	const int noneTxtId = 108; //Russian version uses same text for neutral morale\luck
-	const int neutralDescr[] = {60, 86}; //eg {Neutral Morale} \n\n Neutral morale means your armies will neither be blessed with extra attacks or freeze in combat.
-	const int componentType[] = {CComponent::luck, CComponent::morale};
-	const int hoverTextBase[] = {7, 4};
+	const std::array neutralDescr = {60, 86}; //eg {Neutral Morale} \n\n Neutral morale means your armies will neither be blessed with extra attacks or freeze in combat.
+	const std::array componentType = {ComponentType::LUCK, ComponentType::MORALE};
+	const std::array hoverTextBase = {7, 4};
 	TConstBonusListPtr modifierList = std::make_shared<const BonusList>();
 	TConstBonusListPtr modifierList = std::make_shared<const BonusList>();
-	bonusValue = 0;
+
+	component.value = 0;
 
 
 	if(node)
 	if(node)
-		bonusValue = morale ? node->moraleValAndBonusList(modifierList) : node->luckValAndBonusList(modifierList);
+		component.value = morale ? node->moraleValAndBonusList(modifierList) : node->luckValAndBonusList(modifierList);
 
 
-	int mrlt = (bonusValue>0)-(bonusValue<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good
+	int mrlt = (component.value>0)-(component.value<0); //signum: -1 - bad luck / morale, 0 - neutral, 1 - good
 	hoverText = CGI->generaltexth->heroscrn[hoverTextBase[morale] - mrlt];
 	hoverText = CGI->generaltexth->heroscrn[hoverTextBase[morale] - mrlt];
-	baseType = componentType[morale];
+	component.type = componentType[morale];
 	text = CGI->generaltexth->arraytxt[textId[morale]];
 	text = CGI->generaltexth->arraytxt[textId[morale]];
 	boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]);
 	boost::algorithm::replace_first(text,"%s",CGI->generaltexth->arraytxt[neutralDescr[morale]-mrlt]);
 
 
@@ -563,19 +558,19 @@ void MoraleLuckBox::set(const AFactionMember * node)
 			|| node->getBonusBearer()->hasBonusOfType(BonusType::NON_LIVING)))
 			|| node->getBonusBearer()->hasBonusOfType(BonusType::NON_LIVING)))
 	{
 	{
 		text += CGI->generaltexth->arraytxt[113]; //unaffected by morale
 		text += CGI->generaltexth->arraytxt[113]; //unaffected by morale
-		bonusValue = 0;
+		component.value = 0;
 	}
 	}
 	else if(morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_MORALE))
 	else if(morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_MORALE))
 	{
 	{
 		auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_MORALE));
 		auto noMorale = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_MORALE));
 		text += "\n" + noMorale->Description();
 		text += "\n" + noMorale->Description();
-		bonusValue = 0;
+		component.value = 0;
 	}
 	}
 	else if (!morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
 	else if (!morale && node && node->getBonusBearer()->hasBonusOfType(BonusType::NO_LUCK))
 	{
 	{
 		auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_LUCK));
 		auto noLuck = node->getBonusBearer()->getBonus(Selector::type()(BonusType::NO_LUCK));
 		text += "\n" + noLuck->Description();
 		text += "\n" + noLuck->Description();
-		bonusValue = 0;
+		component.value = 0;
 	}
 	}
 	else
 	else
 	{
 	{
@@ -595,7 +590,7 @@ void MoraleLuckBox::set(const AFactionMember * node)
 	else
 	else
 		imageName = morale ? "IMRL42" : "ILCK42";
 		imageName = morale ? "IMRL42" : "ILCK42";
 
 
-	image = std::make_shared<CAnimImage>(AnimationPath::builtin(imageName), bonusValue + 3);
+	image = std::make_shared<CAnimImage>(AnimationPath::builtin(imageName), *component.value + 3);
 	image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2));//center icon
 	image->moveBy(Point(pos.w/2 - image->pos.w/2, pos.h/2 - image->pos.h/2));//center icon
 }
 }
 
 
@@ -603,7 +598,6 @@ MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small)
 	: morale(Morale),
 	: morale(Morale),
 	small(Small)
 	small(Small)
 {
 {
-	bonusValue = 0;
 	pos = r + pos.topLeft();
 	pos = r + pos.topLeft();
 	defActions = 255-DISPOSE;
 	defActions = 255-DISPOSE;
 }
 }

+ 5 - 5
client/widgets/MiscWidgets.h

@@ -10,6 +10,7 @@
 #pragma once
 #pragma once
 
 
 #include "../gui/CIntObject.h"
 #include "../gui/CIntObject.h"
+#include "../../lib/networkPacks/Component.h"
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
@@ -202,13 +203,12 @@ private:
 class LRClickableAreaWTextComp: public LRClickableAreaWText
 class LRClickableAreaWTextComp: public LRClickableAreaWText
 {
 {
 public:
 public:
-	int type;
-	int baseType;
-	int bonusValue;
+	Component component;
+
 	void clickPressed(const Point & cursorPosition) override;
 	void clickPressed(const Point & cursorPosition) override;
 	void showPopupWindow(const Point & cursorPosition) override;
 	void showPopupWindow(const Point & cursorPosition) override;
 
 
-	LRClickableAreaWTextComp(const Rect &Pos = Rect(0,0,0,0), int BaseType = -1);
+	LRClickableAreaWTextComp(const Rect &Pos = Rect(0,0,0,0), ComponentType baseType = ComponentType::NONE);
 	std::shared_ptr<CComponent> createComponent() const;
 	std::shared_ptr<CComponent> createComponent() const;
 };
 };
 
 
@@ -263,4 +263,4 @@ class SimpleLine : public CIntObject
 public:
 public:
     SimpleLine(Point pos1, Point pos2, ColorRGBA color);
     SimpleLine(Point pos1, Point pos2, ColorRGBA color);
     void showAll(Canvas & to) override;
     void showAll(Canvas & to) override;
-};
+};

+ 26 - 17
client/windows/CCastleInterface.cpp

@@ -157,7 +157,7 @@ void CBuildingRect::showPopupWindow(const Point & cursorPosition)
 	if (bid < BuildingID::DWELL_FIRST)
 	if (bid < BuildingID::DWELL_FIRST)
 	{
 	{
 		CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()),
 		CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()),
-									std::make_shared<CComponent>(CComponent::building, bld->town->faction->getIndex(), bld->bid));
+									std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(bld->town->faction->getId(), bld->bid)));
 	}
 	}
 	else
 	else
 	{
 	{
@@ -860,7 +860,7 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
 
 
 void CCastleBuildings::enterBuilding(BuildingID building)
 void CCastleBuildings::enterBuilding(BuildingID building)
 {
 {
-	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(CComponent::building, town->subID, building));
+	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building)));
 	LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps);
 	LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps);
 }
 }
 
 
@@ -920,7 +920,7 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow()
 
 
 void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades)
 void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID upgrades)
 {
 {
-	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(CComponent::building,town->subID,building));
+	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building)));
 	std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated();
 	std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated();
 	std::string hasNotProduced;
 	std::string hasNotProduced;
 	std::string hasProduced;
 	std::string hasProduced;
@@ -969,9 +969,9 @@ void CCastleBuildings::enterMagesGuild()
 	{
 	{
 		const StartInfo *si = LOCPLINT->cb->getStartInfo();
 		const StartInfo *si = LOCPLINT->cb->getStartInfo();
 		// it would be nice to find a way to move this hack to config/mapOverrides.json
 		// it would be nice to find a way to move this hack to config/mapOverrides.json
-		if(si && si->campState &&                				// We're in campaign,
+		if(si && si->campState &&                                   // We're in campaign,
 			(si->campState->getFilename() == "DATA/YOG.H3C") && // which is "Birth of a Barbarian",
 			(si->campState->getFilename() == "DATA/YOG.H3C") && // which is "Birth of a Barbarian",
-			(hero->subID == 45))                                // and the hero is Yog (based on Solmyr)
+			(hero->getHeroType() == 45))                        // and the hero is Yog (based on Solmyr)
 		{
 		{
 			// "Yog has given up magic in all its forms..."
 			// "Yog has given up magic in all its forms..."
 			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[736]);
 			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[736]);
@@ -986,7 +986,7 @@ void CCastleBuildings::enterMagesGuild()
 			CFunctionList<void()> onYes = [this](){ openMagesGuild(); };
 			CFunctionList<void()> onYes = [this](){ openMagesGuild(); };
 			CFunctionList<void()> onNo = onYes;
 			CFunctionList<void()> onNo = onYes;
 			onYes += [hero](){ LOCPLINT->cb->buyArtifact(hero, ArtifactID::SPELLBOOK); };
 			onYes += [hero](){ LOCPLINT->cb->buyArtifact(hero, ArtifactID::SPELLBOOK); };
-			std::vector<std::shared_ptr<CComponent>> components(1, std::make_shared<CComponent>(CComponent::artifact,ArtifactID::SPELLBOOK,0));
+			std::vector<std::shared_ptr<CComponent>> components(1, std::make_shared<CComponent>(ComponentType::ARTIFACT, ArtifactID(ArtifactID::SPELLBOOK)));
 
 
 			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[214], onYes, onNo, components);
 			LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[214], onYes, onNo, components);
 		}
 		}
@@ -1129,7 +1129,7 @@ void CCreaInfo::showPopupWindow(const Point & cursorPosition)
 	if (showAvailable)
 	if (showAvailable)
 		GH.windows().createAndPushWindow<CDwellingInfoBox>(GH.screenDimensions().x / 2, GH.screenDimensions().y / 2, town, level);
 		GH.windows().createAndPushWindow<CDwellingInfoBox>(GH.screenDimensions().x / 2, GH.screenDimensions().y / 2, town, level);
 	else
 	else
-		CRClickPopup::createAndPush(genGrowthText(), std::make_shared<CComponent>(CComponent::creature, creature->getId()));
+		CRClickPopup::createAndPush(genGrowthText(), std::make_shared<CComponent>(ComponentType::CREATURE, creature->getId()));
 }
 }
 
 
 bool CCreaInfo::getShowAvailable()
 bool CCreaInfo::getShowAvailable()
@@ -1180,7 +1180,7 @@ void CTownInfo::showPopupWindow(const Point & cursorPosition)
 {
 {
 	if(building)
 	if(building)
 	{
 	{
-		auto c =  std::make_shared<CComponent>(CComponent::building, building->town->faction->getIndex(), building->bid);
+		auto c =  std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(building->town->faction->getId(), building->bid));
 		CRClickPopup::createAndPush(CInfoWindow::genText(building->getNameTranslated(), building->getDescriptionTranslated()), c);
 		CRClickPopup::createAndPush(CInfoWindow::genText(building->getNameTranslated(), building->getDescriptionTranslated()), c);
 	}
 	}
 }
 }
@@ -1526,15 +1526,24 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin
 	//Create components for all required resources
 	//Create components for all required resources
 	std::vector<std::shared_ptr<CComponent>> components;
 	std::vector<std::shared_ptr<CComponent>> components;
 
 
-	for(int i = 0; i<GameConstants::RESOURCE_QUANTITY; i++)
+	for(GameResID i : GameResID::ALL_RESOURCES())
 	{
 	{
 		if(building->resources[i])
 		if(building->resources[i])
 		{
 		{
-			std::string text = std::to_string(building->resources[i]);
-			int resAfterBuy = LOCPLINT->cb->getResourceAmount(GameResID(i)) - building->resources[i];
-			if(resAfterBuy < 0 && state != EBuildingState::ALREADY_PRESENT && settings["general"]["enableUiEnhancements"].Bool())
-				text = "{H3Red|" + std::to_string(LOCPLINT->cb->getResourceAmount(GameResID(i))) + "}" + " / " + text;
-			components.push_back(std::make_shared<CComponent>(CComponent::resource, i, text, CComponent::small));
+			MetaString message;
+			int resourceAmount = LOCPLINT->cb->getResourceAmount(i);
+			bool canAfford = resourceAmount >= building->resources[i];
+
+			if(!canAfford && state != EBuildingState::ALREADY_PRESENT && settings["general"]["enableUiEnhancements"].Bool())
+			{
+				message.appendRawString("{H3Red|%d}/%d");
+				message.replaceNumber(resourceAmount);
+			}
+			else
+				message.appendRawString("%d");
+
+			message.replaceNumber(building->resources[i]);
+			components.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, i, message.toString(), CComponent::small));
 		}
 		}
 	}
 	}
 
 
@@ -1889,12 +1898,12 @@ CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell)
 
 
 void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition)
 void CMageGuildScreen::Scroll::clickPressed(const Point & cursorPosition)
 {
 {
-	LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
+	LOCPLINT->showInfoDialog(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(ComponentType::SPELL, spell->id));
 }
 }
 
 
 void CMageGuildScreen::Scroll::showPopupWindow(const Point & cursorPosition)
 void CMageGuildScreen::Scroll::showPopupWindow(const Point & cursorPosition)
 {
 {
-	CRClickPopup::createAndPush(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(CComponent::spell, spell->id));
+	CRClickPopup::createAndPush(spell->getDescriptionTranslated(0), std::make_shared<CComponent>(ComponentType::SPELL, spell->id));
 }
 }
 
 
 void CMageGuildScreen::Scroll::hover(bool on)
 void CMageGuildScreen::Scroll::hover(bool on)
@@ -1935,7 +1944,7 @@ CBlacksmithDialog::CBlacksmithDialog(bool possible, CreatureID creMachineID, Art
 	cancelText.appendTextID("core.genrltxt.596");
 	cancelText.appendTextID("core.genrltxt.596");
 	cancelText.replaceTextID(creature->getNameSingularTextID());
 	cancelText.replaceTextID(creature->getNameSingularTextID());
 
 
-	std::string costString = std::to_string(aid.toArtifact(CGI->artifacts())->getPrice());
+	std::string costString = std::to_string(aid.toEntity(CGI)->getPrice());
 
 
 	title = std::make_shared<CLabel>(165, 28, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, titleString.toString());
 	title = std::make_shared<CLabel>(165, 28, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, titleString.toString());
 	costText = std::make_shared<CLabel>(165, 218, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->jktexts[43]);
 	costText = std::make_shared<CLabel>(165, 218, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->jktexts[43]);

+ 8 - 7
client/windows/CCreatureWindow.cpp

@@ -325,7 +325,7 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
 				std::vector<std::shared_ptr<CComponent>> resComps;
 				std::vector<std::shared_ptr<CComponent>> resComps;
 				for(TResources::nziterator i(totalCost); i.valid(); i++)
 				for(TResources::nziterator i(totalCost); i.valid(); i++)
 				{
 				{
-					resComps.push_back(std::make_shared<CComponent>(CComponent::resource, i->resType, (int)i->resVal));
+					resComps.push_back(std::make_shared<CComponent>(ComponentType::RESOURCE, i->resType, i->resVal));
 				}
 				}
 
 
 				if(LOCPLINT->cb->getResourceAmount().canAfford(totalCost))
 				if(LOCPLINT->cb->getResourceAmount().canAfford(totalCost))
@@ -582,10 +582,10 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 			const CCommanderInstance * commander = parent->info->commander;
 			const CCommanderInstance * commander = parent->info->commander;
 			expRankIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 4, 0, pos.x, pos.y);
 			expRankIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL42"), 4, 0, pos.x, pos.y);
 
 
-			auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(pos.x, pos.y, 44, 44), CComponent::experience);
+			auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(pos.x, pos.y, 44, 44), ComponentType::EXPERIENCE);
 			expArea = area;
 			expArea = area;
 			area->text = CGI->generaltexth->allTexts[2];
 			area->text = CGI->generaltexth->allTexts[2];
-			area->bonusValue =	commander->getExpRank();
+			area->component.value = commander->getExpRank();
 			boost::replace_first(area->text, "%d", std::to_string(commander->getExpRank()));
 			boost::replace_first(area->text, "%d", std::to_string(commander->getExpRank()));
 			boost::replace_first(area->text, "%d", std::to_string(CGI->heroh->reqExp(commander->getExpRank() + 1)));
 			boost::replace_first(area->text, "%d", std::to_string(CGI->heroh->reqExp(commander->getExpRank() + 1)));
 			boost::replace_first(area->text, "%d", std::to_string(commander->experience));
 			boost::replace_first(area->text, "%d", std::to_string(commander->experience));
@@ -611,8 +611,8 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
 		if(art)
 		if(art)
 		{
 		{
 			parent->stackArtifactIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y);
 			parent->stackArtifactIcon = std::make_shared<CAnimImage>(AnimationPath::builtin("ARTIFACT"), art->artType->getIconIndex(), 0, pos.x, pos.y);
-			parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), CComponent::artifact);
-			parent->stackArtifactHelp->type = art->artType->getId();
+			parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), ComponentType::ARTIFACT);
+			parent->stackArtifactHelp->component.subType = art->artType->getId();
 
 
 			if(parent->info->owner)
 			if(parent->info->owner)
 			{
 			{
@@ -974,11 +974,12 @@ void CStackWindow::removeStackArtifact(ArtifactPosition pos)
 	const auto slot = ArtifactUtils::getArtBackpackPosition(info->owner, art->getTypeId());
 	const auto slot = ArtifactUtils::getArtBackpackPosition(info->owner, art->getTypeId());
 	if(slot != ArtifactPosition::PRE_FIRST)
 	if(slot != ArtifactPosition::PRE_FIRST)
 	{
 	{
-		LOCPLINT->cb->swapArtifacts(ArtifactLocation(info->stackNode, pos), ArtifactLocation(info->owner, slot));
+		auto artLoc = ArtifactLocation(info->owner->id, pos);
+		artLoc.creature = info->stackNode->armyObj->findStack(info->stackNode);
+		LOCPLINT->cb->swapArtifacts(artLoc, ArtifactLocation(info->owner->id, slot));
 		stackArtifactButton.reset();
 		stackArtifactButton.reset();
 		stackArtifactHelp.reset();
 		stackArtifactHelp.reset();
 		stackArtifactIcon.reset();
 		stackArtifactIcon.reset();
 		redraw();
 		redraw();
 	}
 	}
 }
 }
-

+ 14 - 22
client/windows/CHeroWindow.cpp

@@ -119,9 +119,9 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 
 
 	for(int v = 0; v < GameConstants::PRIMARY_SKILLS; ++v)
 	for(int v = 0; v < GameConstants::PRIMARY_SKILLS; ++v)
 	{
 	{
-		auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(30 + 70 * v, 109, 42, 64), CComponent::primskill);
+		auto area = std::make_shared<LRClickableAreaWTextComp>(Rect(30 + 70 * v, 109, 42, 64), ComponentType::PRIM_SKILL);
 		area->text = CGI->generaltexth->arraytxt[2+v];
 		area->text = CGI->generaltexth->arraytxt[2+v];
-		area->type = v;
+		area->component.subType = PrimarySkill(v);
 		area->hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % CGI->generaltexth->primarySkillNames[v]);
 		area->hoverText = boost::str(boost::format(CGI->generaltexth->heroscrn[1]) % CGI->generaltexth->primarySkillNames[v]);
 		primSkillAreas.push_back(area);
 		primSkillAreas.push_back(area);
 
 
@@ -154,7 +154,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
 	for(int i = 0; i < std::min<size_t>(hero->secSkills.size(), 8u); ++i)
 	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);
 		Rect r = Rect(i%2 == 0  ?  18  :  162,  276 + 48 * (i/2),  136,  42);
-		secSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>(r, CComponent::secskill));
+		secSkillAreas.push_back(std::make_shared<LRClickableAreaWTextComp>(r, ComponentType::SEC_SKILL));
 		secSkillImages.push_back(std::make_shared<CAnimImage>(secSkills, 0, 0, r.x, r.y));
 		secSkillImages.push_back(std::make_shared<CAnimImage>(secSkills, 0, 0, r.x, r.y));
 
 
 		int x = (i % 2) ? 212 : 68;
 		int x = (i % 2) ? 212 : 68;
@@ -232,20 +232,21 @@ void CHeroWindow::update(const CGHeroInstance * hero, bool redrawNeeded)
 	//primary skills support
 	//primary skills support
 	for(size_t g=0; g<primSkillAreas.size(); ++g)
 	for(size_t g=0; g<primSkillAreas.size(); ++g)
 	{
 	{
-		primSkillAreas[g]->bonusValue = curHero->getPrimSkillLevel(static_cast<PrimarySkill>(g));
-		primSkillValues[g]->setText(std::to_string(primSkillAreas[g]->bonusValue));
+		int value = curHero->getPrimSkillLevel(static_cast<PrimarySkill>(g));
+		primSkillAreas[g]->component.value = value;
+		primSkillValues[g]->setText(std::to_string(value));
 	}
 	}
 
 
 	//secondary skills support
 	//secondary skills support
 	for(size_t g=0; g< secSkillAreas.size(); ++g)
 	for(size_t g=0; g< secSkillAreas.size(); ++g)
 	{
 	{
-		int skill = curHero->secSkills[g].first;
-		int	level = curHero->getSecSkillLevel(SecondarySkill(curHero->secSkills[g].first));
+		SecondarySkill skill = curHero->secSkills[g].first;
+		int	level = curHero->getSecSkillLevel(skill);
 		std::string skillName = CGI->skillh->getByIndex(skill)->getNameTranslated();
 		std::string skillName = CGI->skillh->getByIndex(skill)->getNameTranslated();
 		std::string skillValue = CGI->generaltexth->levels[level-1];
 		std::string skillValue = CGI->generaltexth->levels[level-1];
 
 
-		secSkillAreas[g]->type = skill;
-		secSkillAreas[g]->bonusValue = level;
+		secSkillAreas[g]->component.subType = skill;
+		secSkillAreas[g]->component.value = level;
 		secSkillAreas[g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
 		secSkillAreas[g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
 		secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % skillValue % skillName);
 		secSkillAreas[g]->hoverText = boost::str(boost::format(heroscrn[21]) % skillValue % skillName);
 		secSkillImages[g]->setFrame(skill*3 + level + 2);
 		secSkillImages[g]->setFrame(skill*3 + level + 2);
@@ -334,20 +335,11 @@ void CHeroWindow::commanderWindow()
 	if(pickedArtInst)
 	if(pickedArtInst)
 	{
 	{
 		const auto freeSlot = ArtifactUtils::getArtAnyPosition(curHero->commander, pickedArtInst->getTypeId());
 		const auto freeSlot = ArtifactUtils::getArtAnyPosition(curHero->commander, pickedArtInst->getTypeId());
-		if(freeSlot < ArtifactPosition::COMMANDER_AFTER_LAST) //we don't want to put it in commander's backpack!
+		if(vstd::contains(ArtifactUtils::commanderSlots(), freeSlot)) // We don't want to put it in commander's backpack!
 		{
 		{
-			ArtifactLocation src(hero, ArtifactPosition::TRANSITION_POS);
-			ArtifactLocation dst(curHero->commander.get(), freeSlot);
-
-			if(pickedArtInst->canBePutAt(dst, true))
-			{	//equip clicked stack
-				if(dst.getArt())
-				{
-					LOCPLINT->cb->swapArtifacts(dst, ArtifactLocation(hero,
-						ArtifactUtils::getArtBackpackPosition(hero, pickedArtInst->getTypeId())));
-				}
-				LOCPLINT->cb->swapArtifacts(src, dst);
-			}
+			ArtifactLocation dst(curHero->id, freeSlot);
+			dst.creature = SlotID::COMMANDER_SLOT_PLACEHOLDER;
+			LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero->id, ArtifactPosition::TRANSITION_POS), dst);
 		}
 		}
 	}
 	}
 	else
 	else

+ 2 - 2
client/windows/CKingdomInterface.cpp

@@ -256,7 +256,7 @@ void InfoBoxAbstractHeroData::prepareMessage(std::string & text, std::shared_ptr
 		break;
 		break;
 	case HERO_PRIMARY_SKILL:
 	case HERO_PRIMARY_SKILL:
 		text = CGI->generaltexth->arraytxt[2+getSubID()];
 		text = CGI->generaltexth->arraytxt[2+getSubID()];
-		comp = std::make_shared<CComponent>(CComponent::primskill, getSubID(), (int)getValue());
+		comp = std::make_shared<CComponent>(ComponentType::PRIM_SKILL, PrimarySkill(getSubID()), getValue());
 		break;
 		break;
 	case HERO_MANA:
 	case HERO_MANA:
 		text = CGI->generaltexth->allTexts[149];
 		text = CGI->generaltexth->allTexts[149];
@@ -271,7 +271,7 @@ void InfoBoxAbstractHeroData::prepareMessage(std::string & text, std::shared_ptr
 			if(value)
 			if(value)
 			{
 			{
 				text = CGI->skillh->getByIndex(subID)->getDescriptionTranslated((int)value);
 				text = CGI->skillh->getByIndex(subID)->getDescriptionTranslated((int)value);
-				comp = std::make_shared<CComponent>(CComponent::secskill, subID, (int)value);
+				comp = std::make_shared<CComponent>(ComponentType::SEC_SKILL, SecondarySkill(subID), (int)value);
 			}
 			}
 			break;
 			break;
 		}
 		}

+ 2 - 2
client/windows/CSpellWindow.cpp

@@ -558,7 +558,7 @@ void CSpellWindow::SpellArea::clickPressed(const Point & cursorPosition)
 		//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
 		//battle spell on adv map or adventure map spell during combat => display infowindow, not cast
 		if((combatSpell ^ inCombat) || inCastle)
 		if((combatSpell ^ inCombat) || inCastle)
 		{
 		{
-			std::vector<std::shared_ptr<CComponent>> hlp(1, std::make_shared<CComponent>(CComponent::spell, mySpell->id, 0));
+			std::vector<std::shared_ptr<CComponent>> hlp(1, std::make_shared<CComponent>(ComponentType::SPELL, mySpell->id));
 			LOCPLINT->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp);
 			LOCPLINT->showInfoDialog(mySpell->getDescriptionTranslated(schoolLevel), hlp);
 		}
 		}
 		else if(combatSpell)
 		else if(combatSpell)
@@ -614,7 +614,7 @@ void CSpellWindow::SpellArea::showPopupWindow(const Point & cursorPosition)
 			boost::algorithm::replace_first(dmgInfo, "%d", std::to_string(causedDmg));
 			boost::algorithm::replace_first(dmgInfo, "%d", std::to_string(causedDmg));
 		}
 		}
 
 
-		CRClickPopup::createAndPush(mySpell->getDescriptionTranslated(schoolLevel) + dmgInfo, std::make_shared<CComponent>(CComponent::spell, mySpell->id));
+		CRClickPopup::createAndPush(mySpell->getDescriptionTranslated(schoolLevel) + dmgInfo, std::make_shared<CComponent>(ComponentType::SPELL, mySpell->id));
 	}
 	}
 }
 }
 
 

+ 4 - 4
client/windows/CTradeWindow.cpp

@@ -191,8 +191,8 @@ void CTradeWindow::CTradeableItem::clickPressed(const Point & cursorPosition)
 				const auto hero = aw->arts->getHero();
 				const auto hero = aw->arts->getHero();
 				const auto slot = hero->getSlotByInstance(art);
 				const auto slot = hero->getSlotByInstance(art);
 				assert(slot != ArtifactPosition::PRE_FIRST);
 				assert(slot != ArtifactPosition::PRE_FIRST);
-				LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero, slot),
-					ArtifactLocation(hero, ArtifactPosition::TRANSITION_POS));
+				LOCPLINT->cb->swapArtifacts(ArtifactLocation(hero->id, slot),
+					ArtifactLocation(hero->id, ArtifactPosition::TRANSITION_POS));
 				aw->arts->pickedArtFromSlot = slot;
 				aw->arts->pickedArtFromSlot = slot;
 				aw->arts->artifactsOnAltar.erase(art);
 				aw->arts->artifactsOnAltar.erase(art);
 				setID(-1);
 				setID(-1);
@@ -667,10 +667,10 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
 			title = (*CGI->townh)[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->getNameTranslated();
 			title = (*CGI->townh)[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->getNameTranslated();
 			break;
 			break;
 		case EMarketMode::RESOURCE_ARTIFACT:
 		case EMarketMode::RESOURCE_ARTIFACT:
-			title = (*CGI->townh)[o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
+			title = (*CGI->townh)[o->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
 			break;
 			break;
 		case EMarketMode::ARTIFACT_RESOURCE:
 		case EMarketMode::ARTIFACT_RESOURCE:
-			title = (*CGI->townh)[o->subID]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
+			title = (*CGI->townh)[o->getFaction()]->town->buildings[BuildingID::ARTIFACT_MERCHANT]->getNameTranslated();
 
 
 			// create image that copies part of background containing slot MISC_1 into position of slot MISC_5
 			// create image that copies part of background containing slot MISC_1 into position of slot MISC_5
 			// this is workaround for bug in H3 files where this slot for ragdoll on this screen is missing
 			// this is workaround for bug in H3 files where this slot for ragdoll on this screen is missing

+ 4 - 2
client/windows/CWindowObject.cpp

@@ -39,7 +39,8 @@ CWindowObject::CWindowObject(int options_, const ImagePath & imageName, Point ce
 	options(options_),
 	options(options_),
 	background(createBg(imageName, options & PLAYER_COLORED))
 	background(createBg(imageName, options & PLAYER_COLORED))
 {
 {
-	assert(parent == nullptr); //Safe to remove, but windows should not have parent
+	if(!(options & NEEDS_ANIMATED_BACKGROUND)) //currently workaround for highscores (currently uses window as normal control, because otherwise videos are not played in background yet)
+		assert(parent == nullptr); //Safe to remove, but windows should not have parent
 
 
 	defActions = 255-DISPOSE;
 	defActions = 255-DISPOSE;
 
 
@@ -60,7 +61,8 @@ CWindowObject::CWindowObject(int options_, const ImagePath & imageName):
 	options(options_),
 	options(options_),
 	background(createBg(imageName, options_ & PLAYER_COLORED))
 	background(createBg(imageName, options_ & PLAYER_COLORED))
 {
 {
-	assert(parent == nullptr); //Safe to remove, but windows should not have parent
+	if(!(options & NEEDS_ANIMATED_BACKGROUND)) //currently workaround for highscores (currently uses window as normal control, because otherwise videos are not played in background yet)
+		assert(parent == nullptr); //Safe to remove, but windows should not have parent
 
 
 	defActions = 255-DISPOSE;
 	defActions = 255-DISPOSE;
 
 

+ 2 - 1
client/windows/CWindowObject.h

@@ -38,7 +38,8 @@ public:
 		PLAYER_COLORED=1, //background will be player-colored
 		PLAYER_COLORED=1, //background will be player-colored
 		RCLICK_POPUP=2, // window will behave as right-click popup
 		RCLICK_POPUP=2, // window will behave as right-click popup
 		BORDERED=4, // window will have border if current resolution is bigger than size of window
 		BORDERED=4, // window will have border if current resolution is bigger than size of window
-		SHADOW_DISABLED=8 //this window won't display any shadow
+		SHADOW_DISABLED=8, //this window won't display any shadow
+		NEEDS_ANIMATED_BACKGROUND=16 //there are videos in the background that have to be played
 	};
 	};
 
 
 	/*
 	/*

+ 6 - 11
client/windows/GUIClasses.cpp

@@ -401,7 +401,7 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std
 		std::vector<std::shared_ptr<CSelectableComponent>> comps;
 		std::vector<std::shared_ptr<CSelectableComponent>> comps;
 		for(auto & skill : skills)
 		for(auto & skill : skills)
 		{
 		{
-			auto comp = std::make_shared<CSelectableComponent>(CComponent::secskill, skill, hero->getSecSkillLevel(SecondarySkill(skill))+1, CComponent::medium);
+			auto comp = std::make_shared<CSelectableComponent>(ComponentType::SEC_SKILL, skill, hero->getSecSkillLevel(SecondarySkill(skill))+1, CComponent::medium);
 			comp->onChoose = std::bind(&CLevelWindow::close, this);
 			comp->onChoose = std::bind(&CLevelWindow::close, this);
 			comps.push_back(comp);
 			comps.push_back(comp);
 		}
 		}
@@ -693,9 +693,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		else
 		else
 			primSkillAreas[g]->pos = Rect(Point(pos.x + 329, pos.y + 19 + 36 * g), Point(140, 32));
 			primSkillAreas[g]->pos = Rect(Point(pos.x + 329, pos.y + 19 + 36 * g), Point(140, 32));
 		primSkillAreas[g]->text = CGI->generaltexth->arraytxt[2+g];
 		primSkillAreas[g]->text = CGI->generaltexth->arraytxt[2+g];
-		primSkillAreas[g]->type = g;
-		primSkillAreas[g]->bonusValue = 0;
-		primSkillAreas[g]->baseType = 0;
+		primSkillAreas[g]->component = Component( ComponentType::PRIM_SKILL, PrimarySkill(g));
 		primSkillAreas[g]->hoverText = CGI->generaltexth->heroscrn[1];
 		primSkillAreas[g]->hoverText = CGI->generaltexth->heroscrn[1];
 		boost::replace_first(primSkillAreas[g]->hoverText, "%s", CGI->generaltexth->primarySkillNames[g]);
 		boost::replace_first(primSkillAreas[g]->hoverText, "%s", CGI->generaltexth->primarySkillNames[g]);
 	}
 	}
@@ -708,14 +706,11 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		//secondary skill's clickable areas
 		//secondary skill's clickable areas
 		for(int g=0; g<hero->secSkills.size(); ++g)
 		for(int g=0; g<hero->secSkills.size(); ++g)
 		{
 		{
-			int skill = hero->secSkills[g].first,
-				level = hero->secSkills[g].second; // <1, 3>
+			SecondarySkill skill = hero->secSkills[g].first;
+			int level = hero->secSkills[g].second; // <1, 3>
 			secSkillAreas[b].push_back(std::make_shared<LRClickableAreaWTextComp>());
 			secSkillAreas[b].push_back(std::make_shared<LRClickableAreaWTextComp>());
 			secSkillAreas[b][g]->pos = Rect(Point(pos.x + 32 + g * 36 + b * 454 , pos.y + (qeLayout ? 83 : 88)), Point(32, 32) );
 			secSkillAreas[b][g]->pos = Rect(Point(pos.x + 32 + g * 36 + b * 454 , pos.y + (qeLayout ? 83 : 88)), Point(32, 32) );
-			secSkillAreas[b][g]->baseType = 1;
-
-			secSkillAreas[b][g]->type = skill;
-			secSkillAreas[b][g]->bonusValue = level;
+			secSkillAreas[b][g]->component = Component(ComponentType::SEC_SKILL, skill, level);
 			secSkillAreas[b][g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
 			secSkillAreas[b][g]->text = CGI->skillh->getByIndex(skill)->getDescriptionTranslated(level);
 
 
 			secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21];
 			secSkillAreas[b][g]->hoverText = CGI->generaltexth->heroscrn[21];
@@ -1082,7 +1077,7 @@ void CUniversityWindow::CItem::clickPressed(const Point & cursorPosition)
 
 
 void CUniversityWindow::CItem::showPopupWindow(const Point & cursorPosition)
 void CUniversityWindow::CItem::showPopupWindow(const Point & cursorPosition)
 {
 {
-	CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared<CComponent>(CComponent::secskill, ID, 1));
+	CRClickPopup::createAndPush(CGI->skillh->getByIndex(ID)->getDescriptionTranslated(1), std::make_shared<CComponent>(ComponentType::SEC_SKILL, ID, 1));
 }
 }
 
 
 void CUniversityWindow::CItem::hover(bool on)
 void CUniversityWindow::CItem::hover(bool on)

+ 1 - 1
client/windows/GUIClasses.h

@@ -375,7 +375,7 @@ class CUniversityWindow : public CStatusbarWindow
 		std::shared_ptr<CLabel> name;
 		std::shared_ptr<CLabel> name;
 		std::shared_ptr<CLabel> level;
 		std::shared_ptr<CLabel> level;
 	public:
 	public:
-		int ID;//id of selected skill
+		SecondarySkill ID;//id of selected skill
 		CUniversityWindow * parent;
 		CUniversityWindow * parent;
 
 
 		void showAll(Canvas & to) override;
 		void showAll(Canvas & to) override;

+ 2 - 2
cmake_modules/VCMI_lib.cmake

@@ -686,8 +686,8 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
 		add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
 			COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config
 			COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config
 			COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods
 			COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods
-			COMMAND ${CMAKE_COMMAND} -E copy_directory ${MAIN_LIB_DIR}/../config ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config
-			COMMAND ${CMAKE_COMMAND} -E copy_directory ${MAIN_LIB_DIR}/../Mods ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods
+			COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${MAIN_LIB_DIR}/../config ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/config
+			COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${MAIN_LIB_DIR}/../Mods ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/Mods
 		)
 		)
 	endif()
 	endif()
 
 

+ 14 - 0
cmake_modules/create_link.cmake

@@ -0,0 +1,14 @@
+
+#message(${CMAKE_ARGV0}) # cmake.exe
+#message(${CMAKE_ARGV1}) # -P
+#message(${CMAKE_ARGV2}) # thisfilename
+#message(${CMAKE_ARGV3}) # existing
+#message(${CMAKE_ARGV4}) # linkname
+if (WIN32)
+	file(TO_NATIVE_PATH ${CMAKE_ARGV3} existing_native)
+	file(TO_NATIVE_PATH ${CMAKE_ARGV4} linkname_native)
+	execute_process(COMMAND cmd.exe /c RD /Q "${linkname_native}")
+	execute_process(COMMAND cmd.exe /c mklink /J "${linkname_native}" "${existing_native}")
+else()
+	execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_ARGV3} ${CMAKE_ARGV4})
+endif()

+ 4 - 0
docs/players/Installation_iOS.md

@@ -16,6 +16,10 @@ have the following options:
 - [Create signing assets on macOS from terminal](https://github.com/kambala-decapitator/xcode-auto-signing-assets). In the command replace `your.bundle.id` with something like `com.MY-NAME.vcmi`. After that use the above signer tool.
 - [Create signing assets on macOS from terminal](https://github.com/kambala-decapitator/xcode-auto-signing-assets). In the command replace `your.bundle.id` with something like `com.MY-NAME.vcmi`. After that use the above signer tool.
 - [Sign from any OS](https://github.com/indygreg/PyOxidizer/tree/main/tugger-code-signing). You'd still need to find a way to create signing assets (private key and provisioning profile) though.
 - [Sign from any OS](https://github.com/indygreg/PyOxidizer/tree/main/tugger-code-signing). You'd still need to find a way to create signing assets (private key and provisioning profile) though.
 
 
+To install the signed ipa on your device, you can use Xcode or Apple Configurator (available on the Mac App Store for free). The latter also allows installing ipa from the command line, here's an example that assumes you have only 1 device connected to your Mac and the signed ipa is on your desktop:
+
+    /Applications/Apple\ Configurator.app/Contents/MacOS/cfgutil install-app ~/Desktop/vcmi.ipa
+
 ## Step 2: Installing Heroes III data files
 ## Step 2: Installing Heroes III data files
 
 
 Note: if you don't need in-game videos, you can omit downloading/copying file VIDEO.VID from data folder - it will save your time and space. The same applies to the Mp3 directory.
 Note: if you don't need in-game videos, you can omit downloading/copying file VIDEO.VID from data folder - it will save your time and space. The same applies to the Mp3 directory.

+ 3 - 4
launcher/CMakeLists.txt

@@ -174,12 +174,11 @@ if(APPLE_IOS)
 else()
 else()
 	set(RESOURCES_DESTINATION ${DATA_DIR}/launcher)
 	set(RESOURCES_DESTINATION ${DATA_DIR}/launcher)
 
 
-	# Copy to build directory for easier debugging
+	# Link to build directory for easier debugging
 	add_custom_command(TARGET vcmilauncher POST_BUILD
 	add_custom_command(TARGET vcmilauncher POST_BUILD
 		COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher
 		COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher
-		COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_SOURCE_DIR}/launcher/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons
-		COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_CURRENT_BINARY_DIR}/translation ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translation
-
+		COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${CMAKE_SOURCE_DIR}/launcher/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons
+		COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${CMAKE_CURRENT_BINARY_DIR}/translation ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translation
 	)
 	)
 
 
 	install(TARGETS vcmilauncher DESTINATION ${BIN_DIR})
 	install(TARGETS vcmilauncher DESTINATION ${BIN_DIR})

+ 2 - 0
launcher/settingsView/csettingsview_moc.cpp

@@ -90,7 +90,9 @@ void CSettingsView::loadSettings()
 	ui->comboBoxFriendlyAI->setCurrentText(QString::fromStdString(settings["server"]["friendlyAI"].String()));
 	ui->comboBoxFriendlyAI->setCurrentText(QString::fromStdString(settings["server"]["friendlyAI"].String()));
 	ui->comboBoxNeutralAI->setCurrentText(QString::fromStdString(settings["server"]["neutralAI"].String()));
 	ui->comboBoxNeutralAI->setCurrentText(QString::fromStdString(settings["server"]["neutralAI"].String()));
 	ui->comboBoxEnemyAI->setCurrentText(QString::fromStdString(settings["server"]["enemyAI"].String()));
 	ui->comboBoxEnemyAI->setCurrentText(QString::fromStdString(settings["server"]["enemyAI"].String()));
+
 	ui->comboBoxEnemyPlayerAI->setCurrentText(QString::fromStdString(settings["server"]["playerAI"].String()));
 	ui->comboBoxEnemyPlayerAI->setCurrentText(QString::fromStdString(settings["server"]["playerAI"].String()));
+	ui->comboBoxAlliedPlayerAI->setCurrentText(QString::fromStdString(settings["server"]["alliedAI"].String()));
 
 
 	ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Integer());
 	ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Integer());
 
 

+ 44 - 1
lib/ArtifactUtils.cpp

@@ -74,6 +74,49 @@ DLL_LINKAGE const std::vector<ArtifactPosition> & ArtifactUtils::constituentWorn
 	return positions;
 	return positions;
 }
 }
 
 
+DLL_LINKAGE const std::vector<ArtifactPosition> & ArtifactUtils::allWornSlots()
+{
+	static const std::vector<ArtifactPosition> positions =
+	{
+		ArtifactPosition::HEAD,
+		ArtifactPosition::SHOULDERS,
+		ArtifactPosition::NECK,
+		ArtifactPosition::RIGHT_HAND,
+		ArtifactPosition::LEFT_HAND,
+		ArtifactPosition::TORSO,
+		ArtifactPosition::RIGHT_RING,
+		ArtifactPosition::LEFT_RING,
+		ArtifactPosition::FEET,
+		ArtifactPosition::MISC1,
+		ArtifactPosition::MISC2,
+		ArtifactPosition::MISC3,
+		ArtifactPosition::MISC4,
+		ArtifactPosition::MISC5,
+		ArtifactPosition::MACH1,
+		ArtifactPosition::MACH2,
+		ArtifactPosition::MACH3,
+		ArtifactPosition::MACH4,
+		ArtifactPosition::SPELLBOOK
+	};
+
+	return positions;
+}
+
+DLL_LINKAGE const std::vector<ArtifactPosition> & ArtifactUtils::commanderSlots()
+{
+	static const std::vector<ArtifactPosition> positions =
+	{
+		ArtifactPosition::COMMANDER1,
+		ArtifactPosition::COMMANDER2,
+		ArtifactPosition::COMMANDER3,
+		ArtifactPosition::COMMANDER4,
+		ArtifactPosition::COMMANDER5,
+		ArtifactPosition::COMMANDER6
+	};
+
+	return positions;
+}
+
 DLL_LINKAGE bool ArtifactUtils::isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot)
 DLL_LINKAGE bool ArtifactUtils::isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot)
 {
 {
 	return slot.second.artifact
 	return slot.second.artifact
@@ -214,7 +257,7 @@ DLL_LINKAGE void ArtifactUtils::insertScrrollSpellName(std::string & description
 	if(sid.getNum() >= 0)
 	if(sid.getNum() >= 0)
 	{
 	{
 		if(nameStart != std::string::npos && nameEnd != std::string::npos)
 		if(nameStart != std::string::npos && nameEnd != std::string::npos)
-			description = description.replace(nameStart, nameEnd - nameStart + 1, sid.toSpell(VLC->spells())->getNameTranslated());
+			description = description.replace(nameStart, nameEnd - nameStart + 1, sid.toEntity(VLC->spells())->getNameTranslated());
 	}
 	}
 }
 }
 
 

+ 2 - 0
lib/ArtifactUtils.h

@@ -31,6 +31,8 @@ namespace ArtifactUtils
 	// TODO: Make this constexpr when the toolset is upgraded
 	// TODO: Make this constexpr when the toolset is upgraded
 	DLL_LINKAGE const std::vector<ArtifactPosition> & unmovableSlots();
 	DLL_LINKAGE const std::vector<ArtifactPosition> & unmovableSlots();
 	DLL_LINKAGE const std::vector<ArtifactPosition> & constituentWornSlots();
 	DLL_LINKAGE const std::vector<ArtifactPosition> & constituentWornSlots();
+	DLL_LINKAGE const std::vector<ArtifactPosition> & allWornSlots();
+	DLL_LINKAGE const std::vector<ArtifactPosition> & commanderSlots();
 	DLL_LINKAGE bool isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot);
 	DLL_LINKAGE bool isArtRemovable(const std::pair<ArtifactPosition, ArtSlotInfo> & slot);
 	DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, const ArtifactID & artID, const ArtifactPosition & slot);
 	DLL_LINKAGE bool checkSpellbookIsNeeded(const CGHeroInstance * heroPtr, const ArtifactID & artID, const ArtifactPosition & slot);
 	DLL_LINKAGE bool isSlotBackpack(const ArtifactPosition & slot);
 	DLL_LINKAGE bool isSlotBackpack(const ArtifactPosition & slot);

+ 7 - 7
lib/CArtHandler.cpp

@@ -167,7 +167,7 @@ bool CArtifact::isBig() const
 
 
 bool CArtifact::isTradable() const
 bool CArtifact::isTradable() const
 {
 {
-	switch(id)
+	switch(id.toEnum())
 	{
 	{
 	case ArtifactID::SPELLBOOK:
 	case ArtifactID::SPELLBOOK:
 	case ArtifactID::GRAIL:
 	case ArtifactID::GRAIL:
@@ -693,8 +693,8 @@ void CArtHandler::makeItCommanderArt(CArtifact * a, bool onlyCommander)
 		a->possibleSlots[ArtBearer::HERO].clear();
 		a->possibleSlots[ArtBearer::HERO].clear();
 		a->possibleSlots[ArtBearer::CREATURE].clear();
 		a->possibleSlots[ArtBearer::CREATURE].clear();
 	}
 	}
-	for (int i = ArtifactPosition::COMMANDER1; i <= ArtifactPosition::COMMANDER6; ++i)
-		a->possibleSlots[ArtBearer::COMMANDER].push_back(ArtifactPosition(i));
+	for(const auto & slot : ArtifactUtils::commanderSlots())
+		a->possibleSlots[ArtBearer::COMMANDER].push_back(ArtifactPosition(slot));
 }
 }
 
 
 bool CArtHandler::legalArtifact(const ArtifactID & id)
 bool CArtHandler::legalArtifact(const ArtifactID & id)
@@ -975,9 +975,9 @@ const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const
 	}
 	}
 	if(vstd::contains(artifactsWorn, pos))
 	if(vstd::contains(artifactsWorn, pos))
 		return &artifactsWorn.at(pos);
 		return &artifactsWorn.at(pos);
-	if(pos >= ArtifactPosition::AFTER_LAST )
+	if(ArtifactUtils::isSlotBackpack(pos))
 	{
 	{
-		int backpackPos = (int)pos - ArtifactPosition::BACKPACK_START;
+		auto backpackPos = pos - ArtifactPosition::BACKPACK_START;
 		if(backpackPos < 0 || backpackPos >= artifactsInBackpack.size())
 		if(backpackPos < 0 || backpackPos >= artifactsInBackpack.size())
 			return nullptr;
 			return nullptr;
 		else
 		else
@@ -1080,9 +1080,9 @@ void CArtifactSet::serializeJsonArtifacts(JsonSerializeFormat & handler, const s
 
 
 void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler, CMap * map)
 void CArtifactSet::serializeJsonHero(JsonSerializeFormat & handler, CMap * map)
 {
 {
-	for(ArtifactPosition ap = ArtifactPosition::HEAD; ap < ArtifactPosition::AFTER_LAST; ap.advance(1))
+	for(const auto & slot : ArtifactUtils::allWornSlots())
 	{
 	{
-		serializeJsonSlot(handler, ap, map);
+		serializeJsonSlot(handler, slot, map);
 	}
 	}
 
 
 	std::vector<ArtifactID> backpackTemp;
 	std::vector<ArtifactID> backpackTemp;

+ 9 - 9
lib/CArtifactInstance.cpp

@@ -155,9 +155,9 @@ void CArtifactInstance::setId(ArtifactInstanceID id)
 	this->id = id;
 	this->id = id;
 }
 }
 
 
-bool CArtifactInstance::canBePutAt(const ArtifactLocation & al, bool assumeDestRemoved) const
+bool CArtifactInstance::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, bool assumeDestRemoved) const
 {
 {
-	return artType->canBePutAt(al.getHolderArtSet(), al.slot, assumeDestRemoved);
+	return artType->canBePutAt(artSet, slot, assumeDestRemoved);
 }
 }
 
 
 bool CArtifactInstance::isCombined() const
 bool CArtifactInstance::isCombined() const
@@ -165,15 +165,15 @@ bool CArtifactInstance::isCombined() const
 	return artType->isCombined();
 	return artType->isCombined();
 }
 }
 
 
-void CArtifactInstance::putAt(const ArtifactLocation & al)
+void CArtifactInstance::putAt(CArtifactSet & set, const ArtifactPosition slot)
 {
 {
-	auto placementMap = al.getHolderArtSet()->putArtifact(al.slot, this);
+	auto placementMap = set.putArtifact(slot, this);
 	addPlacementMap(placementMap);
 	addPlacementMap(placementMap);
 }
 }
 
 
-void CArtifactInstance::removeFrom(const ArtifactLocation & al)
+void CArtifactInstance::removeFrom(CArtifactSet & set, const ArtifactPosition slot)
 {
 {
-	al.getHolderArtSet()->removeArtifact(al.slot);
+	set.removeArtifact(slot);
 	for(auto & part : partsInfo)
 	for(auto & part : partsInfo)
 	{
 	{
 		if(part.slot != ArtifactPosition::PRE_FIRST)
 		if(part.slot != ArtifactPosition::PRE_FIRST)
@@ -181,10 +181,10 @@ void CArtifactInstance::removeFrom(const ArtifactLocation & al)
 	}
 	}
 }
 }
 
 
-void CArtifactInstance::move(const ArtifactLocation & src, const ArtifactLocation & dst)
+void CArtifactInstance::move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot)
 {
 {
-	removeFrom(src);
-	putAt(dst);
+	removeFrom(srcSet, srcSlot);
+	putAt(dstSet, dstSlot);
 }
 }
 
 
 void CArtifactInstance::deserializationFix()
 void CArtifactInstance::deserializationFix()

+ 5 - 4
lib/CArtifactInstance.h

@@ -84,11 +84,12 @@ public:
 	ArtifactInstanceID getId() const;
 	ArtifactInstanceID getId() const;
 	void setId(ArtifactInstanceID id);
 	void setId(ArtifactInstanceID id);
 
 
-	bool canBePutAt(const ArtifactLocation & al, bool assumeDestRemoved = false) const;
+	bool canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot = ArtifactPosition::FIRST_AVAILABLE,
+		bool assumeDestRemoved = false) const;
 	bool isCombined() const;
 	bool isCombined() const;
-	void putAt(const ArtifactLocation & al);
-	void removeFrom(const ArtifactLocation & al);
-	void move(const ArtifactLocation & src, const ArtifactLocation & dst);
+	void putAt(CArtifactSet & set, const ArtifactPosition slot);
+	void removeFrom(CArtifactSet & set, const ArtifactPosition slot);
+	void move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot);
 	
 	
 	void deserializationFix();
 	void deserializationFix();
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)

+ 6 - 6
lib/CBuildingHandler.cpp

@@ -12,7 +12,7 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
-BuildingID CBuildingHandler::campToERMU(int camp, int townType, const std::set<BuildingID> & builtBuildings)
+BuildingID CBuildingHandler::campToERMU(int camp, FactionID townType, const std::set<BuildingID> & builtBuildings)
 {
 {
 	static const std::vector<BuildingID> campToERMU = 
 	static const std::vector<BuildingID> campToERMU = 
 	{
 	{
@@ -47,13 +47,13 @@ BuildingID CBuildingHandler::campToERMU(int camp, int townType, const std::set<B
 
 
 		if (i < 5) // last two levels don't have reserved horde ID. Yet another H3C weirdeness
 		if (i < 5) // last two levels don't have reserved horde ID. Yet another H3C weirdeness
 		{
 		{
-			if (vstd::contains(hordeLvlsPerTType[townType], i))
+			if (vstd::contains(hordeLvlsPerTType[townType.getNum()], i))
 			{
 			{
 				if (camp == curPos)
 				if (camp == curPos)
 				{
 				{
-					if (hordeLvlsPerTType[townType][0] == i)
+					if (hordeLvlsPerTType[townType.getNum()][0] == i)
 					{
 					{
-						BuildingID dwellingID(BuildingID::DWELL_UP_FIRST + hordeLvlsPerTType[townType][0]);
+						BuildingID dwellingID(BuildingID::DWELL_UP_FIRST + hordeLvlsPerTType[townType.getNum()][0]);
 
 
 						if(vstd::contains(builtBuildings, dwellingID)) //if upgraded dwelling is built
 						if(vstd::contains(builtBuildings, dwellingID)) //if upgraded dwelling is built
 							return BuildingID::HORDE_1_UPGR;
 							return BuildingID::HORDE_1_UPGR;
@@ -62,9 +62,9 @@ BuildingID CBuildingHandler::campToERMU(int camp, int townType, const std::set<B
 					}
 					}
 					else
 					else
 					{
 					{
-						if(hordeLvlsPerTType[townType].size() > 1)
+						if(hordeLvlsPerTType[townType.getNum()].size() > 1)
 						{
 						{
-							BuildingID dwellingID(BuildingID::DWELL_UP_FIRST + hordeLvlsPerTType[townType][1]);
+							BuildingID dwellingID(BuildingID::DWELL_UP_FIRST + hordeLvlsPerTType[townType.getNum()][1]);
 
 
 							if(vstd::contains(builtBuildings, dwellingID)) //if upgraded dwelling is built
 							if(vstd::contains(builtBuildings, dwellingID)) //if upgraded dwelling is built
 								return BuildingID::HORDE_2_UPGR;
 								return BuildingID::HORDE_2_UPGR;

+ 1 - 1
lib/CBuildingHandler.h

@@ -16,7 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class DLL_LINKAGE CBuildingHandler
 class DLL_LINKAGE CBuildingHandler
 {
 {
 public:
 public:
-	static BuildingID campToERMU(int camp, int townType, const std::set<BuildingID> & builtBuildings);
+	static BuildingID campToERMU(int camp, FactionID townType, const std::set<BuildingID> & builtBuildings);
 };
 };
 
 
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 16 - 27
lib/CCreatureHandler.cpp

@@ -263,7 +263,7 @@ bool CCreature::isDoubleWide() const
  */
  */
 bool CCreature::isGood () const
 bool CCreature::isGood () const
 {
 {
-	return VLC->factions()->getByIndex(faction)->getAlignment() == EAlignment::GOOD;
+	return VLC->factions()->getById(faction)->getAlignment() == EAlignment::GOOD;
 }
 }
 
 
 /**
 /**
@@ -272,7 +272,7 @@ bool CCreature::isGood () const
  */
  */
 bool CCreature::isEvil () const
 bool CCreature::isEvil () const
 {
 {
-	return VLC->factions()->getByIndex(faction)->getAlignment() == EAlignment::EVIL;
+	return VLC->factions()->getById(faction)->getAlignment() == EAlignment::EVIL;
 }
 }
 
 
 si32 CCreature::maxAmount(const TResources &res) const //how many creatures can be bought
 si32 CCreature::maxAmount(const TResources &res) const //how many creatures can be bought
@@ -388,7 +388,7 @@ void CCreature::serializeJson(JsonSerializeFormat & handler)
 	if(handler.updating)
 	if(handler.updating)
 	{
 	{
 		cost.serializeJson(handler, "cost");
 		cost.serializeJson(handler, "cost");
-		handler.serializeInt("faction", faction);//TODO: unify with deferred resolve
+		handler.serializeId("faction", faction);
 	}
 	}
 
 
 	handler.serializeInt("level", level);
 	handler.serializeInt("level", level);
@@ -1350,34 +1350,23 @@ CCreatureHandler::~CCreatureHandler()
 
 
 CreatureID CCreatureHandler::pickRandomMonster(CRandomGenerator & rand, int tier) const
 CreatureID CCreatureHandler::pickRandomMonster(CRandomGenerator & rand, int tier) const
 {
 {
-	int r = 0;
-	if(tier == -1) //pick any allowed creature
+	std::vector<CreatureID> allowed;
+	for(const auto & creature : objects)
 	{
 	{
-		do
-		{
-			r = (*RandomGeneratorUtil::nextItem(objects, rand))->getId();
-		} while (objects[r] && objects[r]->special); // find first "not special" creature
-	}
-	else
-	{
-		assert(vstd::iswithin(tier, 1, 7));
-		std::vector<CreatureID> allowed;
-		for(const auto & creature : objects)
-		{
-			if(!creature->special && creature->level == tier)
-				allowed.push_back(creature->getId());
-		}
+		if(creature->special)
+			continue;
 
 
-		if(allowed.empty())
-		{
-			logGlobal->warn("Cannot pick a random creature of tier %d!", tier);
-			return CreatureID::NONE;
-		}
+		if (creature->level == tier || tier == -1)
+			allowed.push_back(creature->getId());
+	}
 
 
-		return *RandomGeneratorUtil::nextItem(allowed, rand);
+	if(allowed.empty())
+	{
+		logGlobal->warn("Cannot pick a random creature of tier %d!", tier);
+		return CreatureID::NONE;
 	}
 	}
-	assert (r >= 0); //should always be, but it crashed
-	return CreatureID(r);
+
+	return *RandomGeneratorUtil::nextItem(allowed, rand);
 }
 }
 
 
 
 

+ 7 - 2
lib/CCreatureSet.cpp

@@ -458,7 +458,7 @@ const CStackInstance & CCreatureSet::getStack(const SlotID & slot) const
 	return *getStackPtr(slot);
 	return *getStackPtr(slot);
 }
 }
 
 
-const CStackInstance * CCreatureSet::getStackPtr(const SlotID & slot) const
+CStackInstance * CCreatureSet::getStackPtr(const SlotID & slot) const
 {
 {
 	if(hasStackAtSlot(slot))
 	if(hasStackAtSlot(slot))
 		return stacks.find(slot)->second;
 		return stacks.find(slot)->second;
@@ -870,7 +870,7 @@ ArtBearer::ArtBearer CStackInstance::bearerType() const
 CStackInstance::ArtPlacementMap CStackInstance::putArtifact(ArtifactPosition pos, CArtifactInstance * art)
 CStackInstance::ArtPlacementMap CStackInstance::putArtifact(ArtifactPosition pos, CArtifactInstance * art)
 {
 {
 	assert(!getArt(pos));
 	assert(!getArt(pos));
-	assert(art->artType->canBePutAt(this, pos));
+	assert(art->canBePutAt(this, pos));
 
 
 	attachTo(*art);
 	attachTo(*art);
 	return CArtifactSet::putArtifact(pos, art);
 	return CArtifactSet::putArtifact(pos, art);
@@ -1017,6 +1017,11 @@ const Creature * CStackBasicDescriptor::getType() const
 	return type;
 	return type;
 }
 }
 
 
+CreatureID CStackBasicDescriptor::getId() const
+{
+	return type->getId();
+}
+
 TQuantity CStackBasicDescriptor::getCount() const
 TQuantity CStackBasicDescriptor::getCount() const
 {
 {
 	return count;
 	return count;

+ 2 - 1
lib/CCreatureSet.h

@@ -39,6 +39,7 @@ public:
 	virtual ~CStackBasicDescriptor() = default;
 	virtual ~CStackBasicDescriptor() = default;
 
 
 	const Creature * getType() const;
 	const Creature * getType() const;
+	CreatureID getId() const;
 	TQuantity getCount() const;
 	TQuantity getCount() const;
 
 
 	virtual void setType(const CCreature * c);
 	virtual void setType(const CCreature * c);
@@ -253,7 +254,7 @@ public:
 	void setToArmy(CSimpleArmy &src); //erases all our army and moves stacks from src to us; src MUST NOT be an armed object! WARNING: use it wisely. Or better do not use at all.
 	void setToArmy(CSimpleArmy &src); //erases all our army and moves stacks from src to us; src MUST NOT be an armed object! WARNING: use it wisely. Or better do not use at all.
 
 
 	const CStackInstance & getStack(const SlotID & slot) const; //stack must exist
 	const CStackInstance & getStack(const SlotID & slot) const; //stack must exist
-	const CStackInstance * getStackPtr(const SlotID & slot) const; //if stack doesn't exist, returns nullptr
+	CStackInstance * getStackPtr(const SlotID & slot) const; //if stack doesn't exist, returns nullptr
 	const CCreature * getCreature(const SlotID & slot) const; //workaround of map issue;
 	const CCreature * getCreature(const SlotID & slot) const; //workaround of map issue;
 	int getStackCount(const SlotID & slot) const;
 	int getStackCount(const SlotID & slot) const;
 	TExpType getStackExperience(const SlotID & slot) const;
 	TExpType getStackExperience(const SlotID & slot) const;

+ 17 - 0
lib/CGameInfoCallback.cpp

@@ -16,6 +16,7 @@
 #include "gameState/TavernHeroesPool.h"
 #include "gameState/TavernHeroesPool.h"
 #include "gameState/QuestInfo.h"
 #include "gameState/QuestInfo.h"
 #include "mapObjects/CGHeroInstance.h"
 #include "mapObjects/CGHeroInstance.h"
+#include "networkPacks/ArtifactLocation.h"
 #include "CGeneralTextHandler.h"
 #include "CGeneralTextHandler.h"
 #include "StartInfo.h" // for StartInfo
 #include "StartInfo.h" // for StartInfo
 #include "battle/BattleInfo.h" // for BattleInfo
 #include "battle/BattleInfo.h" // for BattleInfo
@@ -966,6 +967,22 @@ const CGObjectInstance * CGameInfoCallback::getObjInstance( ObjectInstanceID oid
 	return gs->map->objects[oid.num];
 	return gs->map->objects[oid.num];
 }
 }
 
 
+CArtifactSet * CGameInfoCallback::getArtSet(const ArtifactLocation & loc) const
+{
+	auto hero = const_cast<CGHeroInstance*>(getHero(loc.artHolder));
+	if(loc.creature.has_value())
+	{
+		if(loc.creature.value() == SlotID::COMMANDER_SLOT_PLACEHOLDER)
+			return hero->commander;
+		else
+			return hero->getStackPtr(loc.creature.value());
+	}
+	else
+	{
+		return hero;
+	}
+}
+
 std::vector<ObjectInstanceID> CGameInfoCallback::getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const
 std::vector<ObjectInstanceID> CGameInfoCallback::getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const
 {
 {
 	vstd::erase_if(ids, [&](const ObjectInstanceID & id) -> bool
 	vstd::erase_if(ids, [&](const ObjectInstanceID & id) -> bool

+ 3 - 0
lib/CGameInfoCallback.h

@@ -40,6 +40,8 @@ class CGameState;
 class PathfinderConfig;
 class PathfinderConfig;
 struct TurnTimerInfo;
 struct TurnTimerInfo;
 
 
+struct ArtifactLocation;
+class CArtifactSet;
 class CArmedInstance;
 class CArmedInstance;
 class CGObjectInstance;
 class CGObjectInstance;
 class CGHeroInstance;
 class CGHeroInstance;
@@ -174,6 +176,7 @@ public:
 	virtual int64_t estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
 	virtual int64_t estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const; //estimates damage of given spell; returns 0 if spell causes no dmg
 	virtual const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
 	virtual const CArtifactInstance * getArtInstance(ArtifactInstanceID aid) const;
 	virtual const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const;
 	virtual const CGObjectInstance * getObjInstance(ObjectInstanceID oid) const;
+	virtual CArtifactSet * getArtSet(const ArtifactLocation & loc) const;
 	//virtual const CGObjectInstance * getArmyInstance(ObjectInstanceID oid) const;
 	//virtual const CGObjectInstance * getArmyInstance(ObjectInstanceID oid) const;
 
 
 	//objects
 	//objects

+ 10 - 10
lib/CHeroHandler.cpp

@@ -125,15 +125,17 @@ SecondarySkill CHeroClass::chooseSecSkill(const std::set<SecondarySkill> & possi
 {
 {
 	int totalProb = 0;
 	int totalProb = 0;
 	for(const auto & possible : possibles)
 	for(const auto & possible : possibles)
-	{
-		totalProb += secSkillProbability[possible];
-	}
+		if (secSkillProbability.count(possible) != 0)
+			totalProb += secSkillProbability.at(possible);
+
 	if (totalProb != 0) // may trigger if set contains only banned skills (0 probability)
 	if (totalProb != 0) // may trigger if set contains only banned skills (0 probability)
 	{
 	{
 		auto ran = rand.nextInt(totalProb - 1);
 		auto ran = rand.nextInt(totalProb - 1);
 		for(const auto & possible : possibles)
 		for(const auto & possible : possibles)
 		{
 		{
-			ran -= secSkillProbability[possible];
+			if (secSkillProbability.count(possible) != 0)
+				ran -= secSkillProbability.at(possible);
+
 			if(ran < 0)
 			if(ran < 0)
 			{
 			{
 				return possible;
 				return possible;
@@ -151,7 +153,7 @@ bool CHeroClass::isMagicHero() const
 
 
 EAlignment CHeroClass::getAlignment() const
 EAlignment CHeroClass::getAlignment() const
 {
 {
-	return VLC->factions()->getByIndex(faction)->getAlignment();
+	return VLC->factions()->getById(faction)->getAlignment();
 }
 }
 
 
 int32_t CHeroClass::getIndex() const
 int32_t CHeroClass::getIndex() const
@@ -209,7 +211,7 @@ CHeroClass::CHeroClass():
 
 
 void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass * heroClass, PrimarySkill pSkill) const
 void CHeroClassHandler::fillPrimarySkillData(const JsonNode & node, CHeroClass * heroClass, PrimarySkill pSkill) const
 {
 {
-	const auto & skillName = NPrimarySkill::names[static_cast<int>(pSkill)];
+	const auto & skillName = NPrimarySkill::names[pSkill.getNum()];
 	auto currentPrimarySkillValue = static_cast<int>(node["primarySkills"][skillName].Integer());
 	auto currentPrimarySkillValue = static_cast<int>(node["primarySkills"][skillName].Integer());
 	//minimal value is 0 for attack and defense and 1 for spell power and knowledge
 	//minimal value is 0 for attack and defense and 1 for spell power and knowledge
 	auto primarySkillLegalMinimum = (pSkill == PrimarySkill::ATTACK || pSkill == PrimarySkill::DEFENSE) ? 0 : 1;
 	auto primarySkillLegalMinimum = (pSkill == PrimarySkill::ATTACK || pSkill == PrimarySkill::DEFENSE) ? 0 : 1;
@@ -271,8 +273,6 @@ CHeroClass * CHeroClassHandler::loadFromJson(const std::string & scope, const Js
 		int probability = static_cast<int>(skillPair.second.Integer());
 		int probability = static_cast<int>(skillPair.second.Integer());
 		VLC->identifiers()->requestIdentifier(skillPair.second.meta, "skill", skillPair.first, [heroClass, probability](si32 skillID)
 		VLC->identifiers()->requestIdentifier(skillPair.second.meta, "skill", skillPair.first, [heroClass, probability](si32 skillID)
 		{
 		{
-			if(heroClass->secSkillProbability.size() <= skillID)
-				heroClass->secSkillProbability.resize(skillID + 1, -1); // -1 = override with default later
 			heroClass->secSkillProbability[skillID] = probability;
 			heroClass->secSkillProbability[skillID] = probability;
 		});
 		});
 	}
 	}
@@ -369,11 +369,11 @@ void CHeroClassHandler::afterLoadFinalization()
 			auto chance = static_cast<float>(heroClass->defaultTavernChance * faction->town->defaultTavernChance);
 			auto chance = static_cast<float>(heroClass->defaultTavernChance * faction->town->defaultTavernChance);
 			heroClass->selectionProbability[faction->getId()] = static_cast<int>(sqrt(chance) + 0.5); //FIXME: replace with std::round once MVS supports it
 			heroClass->selectionProbability[faction->getId()] = static_cast<int>(sqrt(chance) + 0.5); //FIXME: replace with std::round once MVS supports it
 		}
 		}
+
 		// set default probabilities for gaining secondary skills where not loaded previously
 		// set default probabilities for gaining secondary skills where not loaded previously
-		heroClass->secSkillProbability.resize(VLC->skillh->size(), -1);
 		for(int skillID = 0; skillID < VLC->skillh->size(); skillID++)
 		for(int skillID = 0; skillID < VLC->skillh->size(); skillID++)
 		{
 		{
-			if(heroClass->secSkillProbability[skillID] < 0)
+			if(heroClass->secSkillProbability.count(skillID) == 0)
 			{
 			{
 				const CSkill * skill = (*VLC->skillh)[SecondarySkill(skillID)];
 				const CSkill * skill = (*VLC->skillh)[SecondarySkill(skillID)];
 				logMod->trace("%s: no probability for %s, using default", heroClass->identifier, skill->getJsonKey());
 				logMod->trace("%s: no probability for %s, using default", heroClass->identifier, skill->getJsonKey());

+ 1 - 7
lib/CHeroHandler.h

@@ -157,7 +157,7 @@ public:
 	std::vector<int> primarySkillLowLevel; // probability (%) of getting point of primary skill when getting level
 	std::vector<int> primarySkillLowLevel; // probability (%) of getting point of primary skill when getting level
 	std::vector<int> primarySkillHighLevel;// same for high levels (> 10)
 	std::vector<int> primarySkillHighLevel;// same for high levels (> 10)
 
 
-	std::vector<int> secSkillProbability; //probabilities of gaining secondary skills (out of 112), in id order
+	std::map<SecondarySkill, int> secSkillProbability; //probabilities of gaining secondary skills (out of 112), in id order
 
 
 	std::map<FactionID, int> selectionProbability; //probability of selection in towns
 	std::map<FactionID, int> selectionProbability; //probability of selection in towns
 
 
@@ -201,12 +201,6 @@ public:
 		h & imageBattleFemale;
 		h & imageBattleFemale;
 		h & imageMapMale;
 		h & imageMapMale;
 		h & imageMapFemale;
 		h & imageMapFemale;
-
-		if(!h.saving)
-		{
-			for(int & i : secSkillProbability)
-				vstd::amax(i, 0);
-		}
 	}
 	}
 	EAlignment getAlignment() const;
 	EAlignment getAlignment() const;
 };
 };

+ 2 - 21
lib/CSkillHandler.cpp

@@ -120,7 +120,7 @@ DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill::LevelInf
 
 
 DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill & skill)
 DLL_LINKAGE std::ostream & operator<<(std::ostream & out, const CSkill & skill)
 {
 {
-	out << "Skill(" << (int)skill.id << "," << skill.identifier << "): [";
+	out << "Skill(" << skill.id.getNum() << "," << skill.identifier << "): [";
 	for(int i=0; i < skill.levels.size(); i++)
 	for(int i=0; i < skill.levels.size(); i++)
 		out << (i ? "," : "") << skill.levels[i];
 		out << (i ? "," : "") << skill.levels[i];
 	return out << "]";
 	return out << "]";
@@ -233,7 +233,7 @@ CSkill * CSkillHandler::loadFromJson(const std::string & scope, const JsonNode &
 		skillAtLevel.iconMedium = levelNode["images"]["medium"].String();
 		skillAtLevel.iconMedium = levelNode["images"]["medium"].String();
 		skillAtLevel.iconLarge = levelNode["images"]["large"].String();
 		skillAtLevel.iconLarge = levelNode["images"]["large"].String();
 	}
 	}
-	logMod->debug("loaded secondary skill %s(%d)", identifier, (int)skill->id);
+	logMod->debug("loaded secondary skill %s(%d)", identifier, skill->id.getNum());
 
 
 	return skill;
 	return skill;
 }
 }
@@ -262,23 +262,4 @@ std::vector<bool> CSkillHandler::getDefaultAllowed() const
 	return allowedSkills;
 	return allowedSkills;
 }
 }
 
 
-si32 CSkillHandler::decodeSkill(const std::string & identifier)
-{
-	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeMap(), "skill", identifier);
-	if(rawId)
-		return rawId.value();
-	else
-		return -1;
-}
-
-std::string CSkillHandler::encodeSkill(const si32 index)
-{
-	return (*VLC->skillh)[SecondarySkill(index)]->identifier;
-}
-
-std::string CSkillHandler::encodeSkillWithType(const si32 index)
-{
-	return ModUtility::makeFullIdentifier("", "skill", encodeSkill(index));
-}
-
 VCMI_LIB_NAMESPACE_END
 VCMI_LIB_NAMESPACE_END

+ 0 - 5
lib/CSkillHandler.h

@@ -111,11 +111,6 @@ public:
 
 
 	std::vector<bool> getDefaultAllowed() const override;
 	std::vector<bool> getDefaultAllowed() const override;
 
 
-	///json serialization helpers
-	static si32 decodeSkill(const std::string & identifier);
-	static std::string encodeSkill(const si32 index);
-	static std::string encodeSkillWithType(const si32 index);
-
 	template <typename Handler> void serialize(Handler & h, const int version)
 	template <typename Handler> void serialize(Handler & h, const int version)
 	{
 	{
 		h & objects;
 		h & objects;

+ 1 - 1
lib/CTownHandler.cpp

@@ -686,7 +686,7 @@ void CTownHandler::loadBuilding(CTown * town, const std::string & stringID, cons
 	//MODS COMPATIBILITY FOR 0.96
 	//MODS COMPATIBILITY FOR 0.96
 	if(!ret->produce.nonZero())
 	if(!ret->produce.nonZero())
 	{
 	{
-		switch (ret->bid) {
+		switch (ret->bid.toEnum()) {
 			break; case BuildingID::VILLAGE_HALL: ret->produce[EGameResID::GOLD] = 500;
 			break; case BuildingID::VILLAGE_HALL: ret->produce[EGameResID::GOLD] = 500;
 			break; case BuildingID::TOWN_HALL :   ret->produce[EGameResID::GOLD] = 1000;
 			break; case BuildingID::TOWN_HALL :   ret->produce[EGameResID::GOLD] = 1000;
 			break; case BuildingID::CITY_HALL :   ret->produce[EGameResID::GOLD] = 2000;
 			break; case BuildingID::CITY_HALL :   ret->produce[EGameResID::GOLD] = 2000;

+ 3 - 3
lib/IGameCallback.cpp

@@ -148,11 +148,11 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3> & tiles, std:
 void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact *> & out, CRandomGenerator & rand) const
 void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector<const CArtifact *> & out, CRandomGenerator & rand) const
 {
 {
 	for (int j = 0; j < 3 ; j++)
 	for (int j = 0; j < 3 ; j++)
-		out.push_back(VLC->arth->objects[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE)]);
+		out.push_back(VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE).toArtifact());
 	for (int j = 0; j < 3 ; j++)
 	for (int j = 0; j < 3 ; j++)
-		out.push_back(VLC->arth->objects[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR)]);
+		out.push_back(VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR).toArtifact());
 
 
-	out.push_back(VLC->arth->objects[VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR)]);
+	out.push_back(VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR).toArtifact());
 }
 }
 
 
 void CPrivilegedInfoCallback::getAllowedSpells(std::vector<SpellID> & out, std::optional<ui16> level)
 void CPrivilegedInfoCallback::getAllowedSpells(std::vector<SpellID> & out, std::optional<ui16> level)

+ 3 - 3
lib/JsonRandom.cpp

@@ -113,7 +113,7 @@ namespace JsonRandom
 		if (value.empty() || value[0] != '@')
 		if (value.empty() || value[0] != '@')
 			return PrimarySkill(*VLC->identifiers()->getIdentifier(modScope, "primarySkill", value));
 			return PrimarySkill(*VLC->identifiers()->getIdentifier(modScope, "primarySkill", value));
 		else
 		else
-			return PrimarySkill(loadVariable("primarySkill", value, variables, static_cast<int>(PrimarySkill::NONE)));
+			return PrimarySkill(loadVariable("primarySkill", value, variables, PrimarySkill::NONE.getNum()));
 	}
 	}
 
 
 	/// Method that allows type-specific object filtering
 	/// Method that allows type-specific object filtering
@@ -322,7 +322,7 @@ namespace JsonRandom
 			for(const auto & pair : value.Struct())
 			for(const auto & pair : value.Struct())
 			{
 			{
 				PrimarySkill id = decodeKey<PrimarySkill>(pair.second.meta, pair.first, variables);
 				PrimarySkill id = decodeKey<PrimarySkill>(pair.second.meta, pair.first, variables);
-				ret[static_cast<int>(id)] += loadValue(pair.second, rng, variables);
+				ret[id.getNum()] += loadValue(pair.second, rng, variables);
 			}
 			}
 		}
 		}
 		if(value.isVector())
 		if(value.isVector())
@@ -333,7 +333,7 @@ namespace JsonRandom
 				PrimarySkill skillID = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
 				PrimarySkill skillID = *RandomGeneratorUtil::nextItem(potentialPicks, rng);
 
 
 				defaultSkills.erase(skillID);
 				defaultSkills.erase(skillID);
-				ret[static_cast<int>(skillID)] += loadValue(element, rng, variables);
+				ret[skillID.getNum()] += loadValue(element, rng, variables);
 			}
 			}
 		}
 		}
 		return ret;
 		return ret;

+ 6 - 6
lib/MetaString.cpp

@@ -114,35 +114,35 @@ std::string MetaString::getLocalString(const std::pair<EMetaText, ui32> & txt) c
 	{
 	{
 		case EMetaText::ART_NAMES:
 		case EMetaText::ART_NAMES:
 		{
 		{
-			const auto * art = ArtifactID(ser).toArtifact(VLC->artifacts());
+			const auto * art = ArtifactID(ser).toEntity(VLC);
 			if(art)
 			if(art)
 				return art->getNameTranslated();
 				return art->getNameTranslated();
 			return "#!#";
 			return "#!#";
 		}
 		}
 		case EMetaText::ART_DESCR:
 		case EMetaText::ART_DESCR:
 		{
 		{
-			const auto * art = ArtifactID(ser).toArtifact(VLC->artifacts());
+			const auto * art = ArtifactID(ser).toEntity(VLC);
 			if(art)
 			if(art)
 				return art->getDescriptionTranslated();
 				return art->getDescriptionTranslated();
 			return "#!#";
 			return "#!#";
 		}
 		}
 		case EMetaText::ART_EVNTS:
 		case EMetaText::ART_EVNTS:
 		{
 		{
-			const auto * art = ArtifactID(ser).toArtifact(VLC->artifacts());
+			const auto * art = ArtifactID(ser).toEntity(VLC);
 			if(art)
 			if(art)
 				return art->getEventTranslated();
 				return art->getEventTranslated();
 			return "#!#";
 			return "#!#";
 		}
 		}
 		case EMetaText::CRE_PL_NAMES:
 		case EMetaText::CRE_PL_NAMES:
 		{
 		{
-			const auto * cre = CreatureID(ser).toCreature(VLC->creatures());
+			const auto * cre = CreatureID(ser).toEntity(VLC);
 			if(cre)
 			if(cre)
 				return cre->getNamePluralTranslated();
 				return cre->getNamePluralTranslated();
 			return "#!#";
 			return "#!#";
 		}
 		}
 		case EMetaText::CRE_SING_NAMES:
 		case EMetaText::CRE_SING_NAMES:
 		{
 		{
-			const auto * cre = CreatureID(ser).toCreature(VLC->creatures());
+			const auto * cre = CreatureID(ser).toEntity(VLC);
 			if(cre)
 			if(cre)
 				return cre->getNameSingularTranslated();
 				return cre->getNameSingularTranslated();
 			return "#!#";
 			return "#!#";
@@ -157,7 +157,7 @@ std::string MetaString::getLocalString(const std::pair<EMetaText, ui32> & txt) c
 		}
 		}
 		case EMetaText::SPELL_NAME:
 		case EMetaText::SPELL_NAME:
 		{
 		{
-			const auto * spell = SpellID(ser).toSpell(VLC->spells());
+			const auto * spell = SpellID(ser).toEntity(VLC);
 			if(spell)
 			if(spell)
 				return spell->getNameTranslated();
 				return spell->getNameTranslated();
 			return "#!#";
 			return "#!#";

+ 0 - 13
lib/ResourceSet.cpp

@@ -25,19 +25,6 @@ ResourceSet::ResourceSet(const JsonNode & node)
 		container[i] = static_cast<int>(node[GameConstants::RESOURCE_NAMES[i]].Float());
 		container[i] = static_cast<int>(node[GameConstants::RESOURCE_NAMES[i]].Float());
 }
 }
 
 
-ResourceSet::ResourceSet(TResource wood, TResource mercury, TResource ore, TResource sulfur, TResource crystal,
-							TResource gems, TResource gold, TResource mithril)
-{
-	container[GameResID(EGameResID::WOOD)] = wood;
-	container[GameResID(EGameResID::MERCURY)] = mercury;
-	container[GameResID(EGameResID::ORE)] = ore;
-	container[GameResID(EGameResID::SULFUR)] = sulfur;
-	container[GameResID(EGameResID::CRYSTAL)] = crystal;
-	container[GameResID(EGameResID::GEMS)] = gems;
-	container[GameResID(EGameResID::GOLD)] = gold;
-	container[GameResID(EGameResID::MITHRIL)] = mithril;
-}
-
 void ResourceSet::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
 void ResourceSet::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
 {
 {
 	if(handler.saving && !nonZero())
 	if(handler.saving && !nonZero())

+ 2 - 3
lib/ResourceSet.h

@@ -26,12 +26,11 @@ class ResourceSet;
 class ResourceSet
 class ResourceSet
 {
 {
 private:
 private:
-	std::array<TResource, GameConstants::RESOURCE_QUANTITY> container;
+	std::array<TResource, GameConstants::RESOURCE_QUANTITY> container = {};
 public:
 public:
 	// read resources set from json. Format example: { "gold": 500, "wood":5 }
 	// read resources set from json. Format example: { "gold": 500, "wood":5 }
 	DLL_LINKAGE ResourceSet(const JsonNode & node);
 	DLL_LINKAGE ResourceSet(const JsonNode & node);
-	DLL_LINKAGE ResourceSet(TResource wood = 0, TResource mercury = 0, TResource ore = 0, TResource sulfur = 0, TResource crystal = 0,
-							TResource gems = 0, TResource gold = 0, TResource mithril = 0);
+	DLL_LINKAGE ResourceSet() = default;
 
 
 
 
 #define scalarOperator(OPSIGN)									\
 #define scalarOperator(OPSIGN)									\

+ 2 - 2
lib/battle/CBattleInfoCallback.cpp

@@ -1155,7 +1155,7 @@ std::pair<const battle::Unit *, BattleHex> CBattleInfoCallback::getNearestStack(
 
 
 BattleHex CBattleInfoCallback::getAvaliableHex(const CreatureID & creID, ui8 side, int initialPos) const
 BattleHex CBattleInfoCallback::getAvaliableHex(const CreatureID & creID, ui8 side, int initialPos) const
 {
 {
-	bool twoHex = VLC->creh->objects[creID]->isDoubleWide();
+	bool twoHex = VLC->creatures()->getById(creID)->isDoubleWide();
 
 
 	int pos;
 	int pos;
 	if (initialPos > -1)
 	if (initialPos > -1)
@@ -1663,7 +1663,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
 		|| !(spellID.toSpell()->canBeCast(this, spells::Mode::CREATURE_ACTIVE, subject)))
 		|| !(spellID.toSpell()->canBeCast(this, spells::Mode::CREATURE_ACTIVE, subject)))
 			continue;
 			continue;
 
 
-		switch (spellID)
+		switch (spellID.toEnum())
 		{
 		{
 		case SpellID::SHIELD:
 		case SpellID::SHIELD:
 		case SpellID::FIRE_SHIELD: // not if all enemy units are shooters
 		case SpellID::FIRE_SHIELD: // not if all enemy units are shooters

+ 1 - 1
lib/battle/SideInBattle.cpp

@@ -18,7 +18,7 @@ void SideInBattle::init(const CGHeroInstance * Hero, const CArmedInstance * Army
 	hero = Hero;
 	hero = Hero;
 	armyObject = Army;
 	armyObject = Army;
 
 
-	switch(armyObject->ID)
+	switch(armyObject->ID.toEnum())
 	{
 	{
 		case Obj::CREATURE_GENERATOR1:
 		case Obj::CREATURE_GENERATOR1:
 		case Obj::CREATURE_GENERATOR2:
 		case Obj::CREATURE_GENERATOR2:

+ 1 - 1
lib/battle/Unit.cpp

@@ -218,7 +218,7 @@ int Unit::getRawSurrenderCost() const
 void UnitInfo::serializeJson(JsonSerializeFormat & handler)
 void UnitInfo::serializeJson(JsonSerializeFormat & handler)
 {
 {
 	handler.serializeInt("count", count);
 	handler.serializeInt("count", count);
-	handler.serializeId("type", type, CreatureID::NONE);
+	handler.serializeId("type", type, CreatureID(CreatureID::NONE));
 	handler.serializeInt("side", side);
 	handler.serializeInt("side", side);
 	handler.serializeInt("position", position);
 	handler.serializeInt("position", position);
 	handler.serializeBool("summoned", summoned);
 	handler.serializeBool("summoned", summoned);

+ 3 - 3
lib/bonuses/Bonus.cpp

@@ -100,13 +100,13 @@ std::string Bonus::Description(std::optional<si32> customValue) const
 			switch(source)
 			switch(source)
 			{
 			{
 			case BonusSource::ARTIFACT:
 			case BonusSource::ARTIFACT:
-				str << sid.as<ArtifactID>().toArtifact(VLC->artifacts())->getNameTranslated();
+				str << sid.as<ArtifactID>().toEntity(VLC)->getNameTranslated();
 				break;
 				break;
 			case BonusSource::SPELL_EFFECT:
 			case BonusSource::SPELL_EFFECT:
-				str << sid.as<SpellID>().toSpell(VLC->spells())->getNameTranslated();
+				str << sid.as<SpellID>().toEntity(VLC)->getNameTranslated();
 				break;
 				break;
 			case BonusSource::CREATURE_ABILITY:
 			case BonusSource::CREATURE_ABILITY:
-				str << sid.as<CreatureID>().toCreature(VLC->creatures())->getNamePluralTranslated();
+				str << sid.as<CreatureID>().toEntity(VLC)->getNamePluralTranslated();
 				break;
 				break;
 			case BonusSource::SECONDARY_SKILL:
 			case BonusSource::SECONDARY_SKILL:
 				str << VLC->skills()->getById(sid.as<SecondarySkill>())->getNameTranslated();
 				str << VLC->skills()->getById(sid.as<SecondarySkill>())->getNameTranslated();

+ 3 - 3
lib/bonuses/Limiters.cpp

@@ -114,7 +114,7 @@ CCreatureTypeLimiter::CCreatureTypeLimiter(const CCreature & creature_, bool Inc
 
 
 void CCreatureTypeLimiter::setCreature(const CreatureID & id)
 void CCreatureTypeLimiter::setCreature(const CreatureID & id)
 {
 {
-	creature = VLC->creh->objects[id];
+	creature = id.toCreature();
 }
 }
 
 
 std::string CCreatureTypeLimiter::toString() const
 std::string CCreatureTypeLimiter::toString() const
@@ -317,7 +317,7 @@ ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context)
 std::string FactionLimiter::toString() const
 std::string FactionLimiter::toString() const
 {
 {
 	boost::format fmt("FactionLimiter(faction=%s)");
 	boost::format fmt("FactionLimiter(faction=%s)");
-	fmt % VLC->factions()->getByIndex(faction)->getJsonKey();
+	fmt % VLC->factions()->getById(faction)->getJsonKey();
 	return fmt.str();
 	return fmt.str();
 }
 }
 
 
@@ -326,7 +326,7 @@ JsonNode FactionLimiter::toJsonNode() const
 	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
 	JsonNode root(JsonNode::JsonType::DATA_STRUCT);
 
 
 	root["type"].String() = "FACTION_LIMITER";
 	root["type"].String() = "FACTION_LIMITER";
-	root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getByIndex(faction)->getJsonKey()));
+	root["parameters"].Vector().push_back(JsonUtils::stringNode(VLC->factions()->getById(faction)->getJsonKey()));
 
 
 	return root;
 	return root;
 }
 }

+ 9 - 10
lib/campaign/CampaignState.cpp

@@ -73,13 +73,13 @@ ImagePath CampaignRegions::getBackgroundName() const
 
 
 Point CampaignRegions::getPosition(CampaignScenarioID which) const
 Point CampaignRegions::getPosition(CampaignScenarioID which) const
 {
 {
-	auto const & region = regions[static_cast<int>(which)];
+	auto const & region = regions[which.getNum()];
 	return Point(region.xpos, region.ypos);
 	return Point(region.xpos, region.ypos);
 }
 }
 
 
 ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, std::string type) const
 ImagePath CampaignRegions::getNameFor(CampaignScenarioID which, int colorIndex, std::string type) const
 {
 {
-	auto const & region = regions[static_cast<int>(which)];
+	auto const & region = regions[which.getNum()];
 
 
 	static const std::string colors[2][8] =
 	static const std::string colors[2][8] =
 	{
 	{
@@ -267,24 +267,23 @@ void CampaignState::setCurrentMapAsConquered(std::vector<CGHeroInstance *> heroe
 		return a->getHeroStrength() > b->getHeroStrength();
 		return a->getHeroStrength() > b->getHeroStrength();
 	});
 	});
 
 
-	logGlobal->info("Scenario %d of campaign %s (%s) has been completed", static_cast<int>(*currentMap), getFilename(), getNameTranslated());
+	logGlobal->info("Scenario %d of campaign %s (%s) has been completed", currentMap->getNum(), getFilename(), getNameTranslated());
 
 
 	mapsConquered.push_back(*currentMap);
 	mapsConquered.push_back(*currentMap);
 	auto reservedHeroes = getReservedHeroes();
 	auto reservedHeroes = getReservedHeroes();
 
 
 	for (auto * hero : heroes)
 	for (auto * hero : heroes)
 	{
 	{
-		HeroTypeID heroType(hero->subID);
 		JsonNode node = CampaignState::crossoverSerialize(hero);
 		JsonNode node = CampaignState::crossoverSerialize(hero);
 
 
-		if (reservedHeroes.count(heroType))
+		if (reservedHeroes.count(hero->getHeroType()))
 		{
 		{
-			logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->subID, hero->getNameTranslated());
-			globalHeroPool[heroType] = node;
+			logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroType(), hero->getNameTranslated());
+			globalHeroPool[hero->getHeroType()] = node;
 		}
 		}
 		else
 		else
 		{
 		{
-			logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->subID, hero->getNameTranslated());
+			logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->getHeroType(), hero->getNameTranslated());
 			scenarioHeroPool[*currentMap].push_back(node);
 			scenarioHeroPool[*currentMap].push_back(node);
 		}
 		}
 	}
 	}
@@ -321,7 +320,7 @@ std::unique_ptr<CMap> CampaignState::getMap(CampaignScenarioID scenarioId) const
 	CMapService mapService;
 	CMapService mapService;
 	std::string scenarioName = getFilename().substr(0, getFilename().find('.'));
 	std::string scenarioName = getFilename().substr(0, getFilename().find('.'));
 	boost::to_lower(scenarioName);
 	boost::to_lower(scenarioName);
-	scenarioName += ':' + std::to_string(static_cast<int>(scenarioId));
+	scenarioName += ':' + std::to_string(scenarioId.getNum());
 	const auto & mapContent = mapPieces.find(scenarioId)->second;
 	const auto & mapContent = mapPieces.find(scenarioId)->second;
 	return mapService.loadMap(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding());
 	return mapService.loadMap(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding());
 }
 }
@@ -334,7 +333,7 @@ std::unique_ptr<CMapHeader> CampaignState::getMapHeader(CampaignScenarioID scena
 	CMapService mapService;
 	CMapService mapService;
 	std::string scenarioName = getFilename().substr(0, getFilename().find('.'));
 	std::string scenarioName = getFilename().substr(0, getFilename().find('.'));
 	boost::to_lower(scenarioName);
 	boost::to_lower(scenarioName);
-	scenarioName += ':' + std::to_string(static_cast<int>(scenarioId));
+	scenarioName += ':' + std::to_string(scenarioId.getNum());
 	const auto & mapContent = mapPieces.find(scenarioId)->second;
 	const auto & mapContent = mapPieces.find(scenarioId)->second;
 	return mapService.loadMapHeader(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding());
 	return mapService.loadMapHeader(mapContent.data(), mapContent.size(), scenarioName, getModName(), getEncoding());
 }
 }

+ 43 - 6
lib/constants/EntityIdentifiers.cpp

@@ -21,6 +21,7 @@
 #include <vcmi/HeroTypeService.h>
 #include <vcmi/HeroTypeService.h>
 #include <vcmi/HeroClass.h>
 #include <vcmi/HeroClass.h>
 #include <vcmi/HeroClassService.h>
 #include <vcmi/HeroClassService.h>
+#include <vcmi/Services.h>
 
 
 #include <vcmi/spells/Spell.h>
 #include <vcmi/spells/Spell.h>
 #include <vcmi/spells/Service.h>
 #include <vcmi/spells/Service.h>
@@ -28,6 +29,7 @@
 #include "modding/IdentifierStorage.h"
 #include "modding/IdentifierStorage.h"
 #include "modding/ModScope.h"
 #include "modding/ModScope.h"
 #include "VCMI_Lib.h"
 #include "VCMI_Lib.h"
+#include "CHeroHandler.h"
 #include "CArtHandler.h"//todo: remove
 #include "CArtHandler.h"//todo: remove
 #include "CCreatureHandler.h"//todo: remove
 #include "CCreatureHandler.h"//todo: remove
 #include "spells/CSpellHandler.h" //todo: remove
 #include "spells/CSpellHandler.h" //todo: remove
@@ -191,12 +193,12 @@ std::string HeroTypeID::entityType()
 
 
 const CArtifact * ArtifactIDBase::toArtifact() const
 const CArtifact * ArtifactIDBase::toArtifact() const
 {
 {
-	return dynamic_cast<const CArtifact*>(toArtifact(VLC->artifacts()));
+	return dynamic_cast<const CArtifact*>(toEntity(VLC));
 }
 }
 
 
-const Artifact * ArtifactIDBase::toArtifact(const ArtifactService * service) const
+const Artifact * ArtifactIDBase::toEntity(const Services * services) const
 {
 {
-	return service->getByIndex(num);
+	return services->artifacts()->getByIndex(num);
 }
 }
 
 
 si32 ArtifactID::decode(const std::string & identifier)
 si32 ArtifactID::decode(const std::string & identifier)
@@ -237,7 +239,12 @@ const CCreature * CreatureIDBase::toCreature() const
 	return VLC->creh->objects.at(num);
 	return VLC->creh->objects.at(num);
 }
 }
 
 
-const Creature * CreatureIDBase::toCreature(const CreatureService * creatures) const
+const Creature * CreatureIDBase::toEntity(const Services * services) const
+{
+	return toEntity(services->creatures());
+}
+
+const Creature * CreatureIDBase::toEntity(const CreatureService * creatures) const
 {
 {
 	return creatures->getByIndex(num);
 	return creatures->getByIndex(num);
 }
 }
@@ -271,11 +278,26 @@ const CSpell * SpellIDBase::toSpell() const
 	return VLC->spellh->objects[num];
 	return VLC->spellh->objects[num];
 }
 }
 
 
-const spells::Spell * SpellIDBase::toSpell(const spells::Service * service) const
+const spells::Spell * SpellIDBase::toEntity(const Services * services) const
+{
+	return toEntity(services->spells());
+}
+
+const spells::Spell * SpellIDBase::toEntity(const spells::Service * service) const
 {
 {
 	return service->getByIndex(num);
 	return service->getByIndex(num);
 }
 }
 
 
+const CHero * HeroTypeID::toHeroType() const
+{
+	return dynamic_cast<const CHero*>(toEntity(VLC));
+}
+
+const HeroType * HeroTypeID::toEntity(const Services * services) const
+{
+	return services->heroTypes()->getByIndex(num);
+}
+
 si32 SpellID::decode(const std::string & identifier)
 si32 SpellID::decode(const std::string & identifier)
 {
 {
 	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeGame(), "spell", identifier);
 	auto rawId = VLC->identifiers()->getIdentifier(ModScope::scopeGame(), "spell", identifier);
@@ -369,7 +391,7 @@ si32 FactionID::decode(const std::string & identifier)
 	if(rawId)
 	if(rawId)
 		return rawId.value();
 		return rawId.value();
 	else
 	else
-		return FactionID::DEFAULT;
+		return FactionID::DEFAULT.getNum();
 }
 }
 
 
 std::string FactionID::encode(const si32 index)
 std::string FactionID::encode(const si32 index)
@@ -458,6 +480,21 @@ std::string GameResID::entityType()
 	return "resource";
 	return "resource";
 }
 }
 
 
+const std::array<GameResID, 7> & GameResID::ALL_RESOURCES()
+{
+	static const std::array allResources = {
+		GameResID(WOOD),
+		GameResID(MERCURY),
+		GameResID(ORE),
+		GameResID(SULFUR),
+		GameResID(CRYSTAL),
+		GameResID(GEMS),
+		GameResID(GOLD)
+	};
+
+	return allResources;
+}
+
 std::string SecondarySkill::entityType()
 std::string SecondarySkill::entityType()
 {
 {
 	return "secondarySkill";
 	return "secondarySkill";

+ 51 - 17
lib/constants/EntityIdentifiers.h

@@ -14,10 +14,14 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
+class Services;
 class Artifact;
 class Artifact;
 class ArtifactService;
 class ArtifactService;
 class Creature;
 class Creature;
 class CreatureService;
 class CreatureService;
+class HeroType;
+class CHero;
+class HeroTypeService;
 
 
 namespace spells
 namespace spells
 {
 {
@@ -186,6 +190,9 @@ public:
 	DLL_LINKAGE static std::string encode(const si32 index);
 	DLL_LINKAGE static std::string encode(const si32 index);
 	static std::string entityType();
 	static std::string entityType();
 
 
+	const CHero * toHeroType() const;
+	const HeroType * toEntity(const Services * services) const;
+
 	DLL_LINKAGE static const HeroTypeID NONE;
 	DLL_LINKAGE static const HeroTypeID NONE;
 	DLL_LINKAGE static const HeroTypeID RANDOM;
 	DLL_LINKAGE static const HeroTypeID RANDOM;
 
 
@@ -401,13 +408,9 @@ class BuildingID : public IdentifierWithEnum<BuildingID, BuildingIDBase>
 {
 {
 public:
 public:
 	using IdentifierWithEnum<BuildingID, BuildingIDBase>::IdentifierWithEnum;
 	using IdentifierWithEnum<BuildingID, BuildingIDBase>::IdentifierWithEnum;
-
-	DLL_LINKAGE static si32 decode(const std::string & identifier);
-	DLL_LINKAGE static std::string encode(const si32 index);
-	static std::string entityType();
 };
 };
 
 
-class ObjBase : public IdentifierBase
+class MapObjectBaseID : public IdentifierBase
 {
 {
 public:
 public:
 	enum Type
 	enum Type
@@ -552,15 +555,38 @@ public:
 	};
 	};
 };
 };
 
 
-class DLL_LINKAGE Obj : public IdentifierWithEnum<Obj, ObjBase>
+class DLL_LINKAGE MapObjectID : public IdentifierWithEnum<MapObjectID, MapObjectBaseID>
 {
 {
 public:
 public:
-	using IdentifierWithEnum<Obj, ObjBase>::IdentifierWithEnum;
+	using IdentifierWithEnum<MapObjectID, MapObjectBaseID>::IdentifierWithEnum;
 
 
 	static std::string encode(int32_t index);
 	static std::string encode(int32_t index);
 	static si32 decode(const std::string & identifier);
 	static si32 decode(const std::string & identifier);
 };
 };
 
 
+class MapObjectSubID : public Identifier<MapObjectSubID>
+{
+public:
+	constexpr MapObjectSubID(const IdentifierBase & value):
+		Identifier<MapObjectSubID>(value.getNum())
+	{}
+	constexpr MapObjectSubID(int32_t value = -1):
+		Identifier<MapObjectSubID>(value)
+	{}
+
+	MapObjectSubID & operator =(int32_t value)
+	{
+		this->num = value;
+		return *this;
+	}
+
+	MapObjectSubID & operator =(const IdentifierBase & value)
+	{
+		this->num = value.getNum();
+		return *this;
+	}
+};
+
 class DLL_LINKAGE RoadId : public Identifier<RoadId>
 class DLL_LINKAGE RoadId : public Identifier<RoadId>
 {
 {
 public:
 public:
@@ -607,20 +633,23 @@ public:
 		TRANSITION_POS = -3,
 		TRANSITION_POS = -3,
 		FIRST_AVAILABLE = -2,
 		FIRST_AVAILABLE = -2,
 		PRE_FIRST = -1, //sometimes used as error, sometimes as first free in backpack
 		PRE_FIRST = -1, //sometimes used as error, sometimes as first free in backpack
+		
+		// Hero
 		HEAD, SHOULDERS, NECK, RIGHT_HAND, LEFT_HAND, TORSO, //5
 		HEAD, SHOULDERS, NECK, RIGHT_HAND, LEFT_HAND, TORSO, //5
 		RIGHT_RING, LEFT_RING, FEET, //8
 		RIGHT_RING, LEFT_RING, FEET, //8
 		MISC1, MISC2, MISC3, MISC4, //12
 		MISC1, MISC2, MISC3, MISC4, //12
 		MACH1, MACH2, MACH3, MACH4, //16
 		MACH1, MACH2, MACH3, MACH4, //16
 		SPELLBOOK, MISC5, //18
 		SPELLBOOK, MISC5, //18
-		AFTER_LAST,
-		//cres
+		BACKPACK_START = 19,
+		
+		// Creatures
 		CREATURE_SLOT = 0,
 		CREATURE_SLOT = 0,
-		COMMANDER1 = 0, COMMANDER2, COMMANDER3, COMMANDER4, COMMANDER5, COMMANDER6, COMMANDER_AFTER_LAST,
-
-		BACKPACK_START = 19
+		
+		// Commander
+		COMMANDER1 = 0, COMMANDER2, COMMANDER3, COMMANDER4, COMMANDER5, COMMANDER6
 	};
 	};
 
 
-	static_assert (AFTER_LAST == BACKPACK_START, "incorrect number of artifact slots");
+	static_assert(MISC5 < BACKPACK_START, "incorrect number of artifact slots");
 
 
 	DLL_LINKAGE static si32 decode(const std::string & identifier);
 	DLL_LINKAGE static si32 decode(const std::string & identifier);
 	DLL_LINKAGE static std::string encode(const si32 index);
 	DLL_LINKAGE static std::string encode(const si32 index);
@@ -653,7 +682,7 @@ public:
 	};
 	};
 
 
 	DLL_LINKAGE const CArtifact * toArtifact() const;
 	DLL_LINKAGE const CArtifact * toArtifact() const;
-	DLL_LINKAGE const Artifact * toArtifact(const ArtifactService * service) const;
+	DLL_LINKAGE const Artifact * toEntity(const Services * service) const;
 };
 };
 
 
 class ArtifactID : public IdentifierWithEnum<ArtifactID, ArtifactIDBase>
 class ArtifactID : public IdentifierWithEnum<ArtifactID, ArtifactIDBase>
@@ -693,7 +722,8 @@ public:
 	};
 	};
 
 
 	DLL_LINKAGE const CCreature * toCreature() const;
 	DLL_LINKAGE const CCreature * toCreature() const;
-	DLL_LINKAGE const Creature * toCreature(const CreatureService * creatures) const;
+	DLL_LINKAGE const Creature * toEntity(const Services * services) const;
+	DLL_LINKAGE const Creature * toEntity(const CreatureService * creatures) const;
 };
 };
 
 
 class DLL_LINKAGE CreatureID : public IdentifierWithEnum<CreatureID, CreatureIDBase>
 class DLL_LINKAGE CreatureID : public IdentifierWithEnum<CreatureID, CreatureIDBase>
@@ -811,7 +841,8 @@ public:
 	};
 	};
 
 
 	const CSpell * toSpell() const; //deprecated
 	const CSpell * toSpell() const; //deprecated
-	const spells::Spell * toSpell(const spells::Service * service) const;
+	const spells::Spell * toEntity(const Services * service) const;
+	const spells::Spell * toEntity(const spells::Service * service) const;
 };
 };
 
 
 class DLL_LINKAGE SpellID : public IdentifierWithEnum<SpellID, SpellIDBase>
 class DLL_LINKAGE SpellID : public IdentifierWithEnum<SpellID, SpellIDBase>
@@ -934,9 +965,11 @@ public:
 	static si32 decode(const std::string & identifier);
 	static si32 decode(const std::string & identifier);
 	static std::string encode(const si32 index);
 	static std::string encode(const si32 index);
 	static std::string entityType();
 	static std::string entityType();
+
+	static const std::array<GameResID, 7> & ALL_RESOURCES();
 };
 };
 
 
-class BuildingTypeUniqueID : public Identifier<BuildingTypeUniqueID>
+class DLL_LINKAGE BuildingTypeUniqueID : public Identifier<BuildingTypeUniqueID>
 {
 {
 public:
 public:
 	BuildingTypeUniqueID(FactionID faction, BuildingID building );
 	BuildingTypeUniqueID(FactionID faction, BuildingID building );
@@ -963,6 +996,7 @@ public:
 
 
 // Deprecated
 // Deprecated
 // TODO: remove
 // TODO: remove
+using Obj = MapObjectID;
 using ETownType = FactionID;
 using ETownType = FactionID;
 using EGameResID = GameResID;
 using EGameResID = GameResID;
 using River = RiverId;
 using River = RiverId;

+ 1 - 1
lib/constants/VariantIdentifier.h

@@ -15,7 +15,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 /// This class represents field that may contain value of multiple different identifer types
 /// This class represents field that may contain value of multiple different identifer types
 template<typename... Types>
 template<typename... Types>
-class DLL_LINKAGE VariantIdentifier
+class VariantIdentifier
 {
 {
 	std::variant<Types...> value;
 	std::variant<Types...> value;
 public:
 public:

+ 50 - 285
lib/gameState/CGameState.cpp

@@ -77,34 +77,6 @@ public:
 	}
 	}
 };
 };
 
 
-static CGObjectInstance * createObject(const Obj & id, int subid, const int3 & pos, const PlayerColor & owner)
-{
-	CGObjectInstance * nobj;
-	switch(id)
-	{
-	case Obj::HERO:
-		{
-			auto handler = VLC->objtypeh->getHandlerFor(id, VLC->heroh->objects[subid]->heroClass->getIndex());
-			nobj = handler->create(handler->getTemplates().front());
-			break;
-		}
-	case Obj::TOWN:
-		nobj = new CGTownInstance();
-		break;
-	default: //rest of objects
-		nobj = new CGObjectInstance();
-		break;
-	}
-	nobj->ID = id;
-	nobj->subID = subid;
-	nobj->pos = pos;
-	nobj->tempOwner = owner;
-	if (id != Obj::HERO)
-		nobj->appearance = VLC->objtypeh->getHandlerFor(id, subid)->getTemplates().front();
-
-	return nobj;
-}
-
 HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
 HeroTypeID CGameState::pickNextHeroType(const PlayerColor & owner)
 {
 {
 	const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
 	const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
@@ -153,223 +125,6 @@ HeroTypeID CGameState::pickUnusedHeroTypeRandomly(const PlayerColor & owner)
 	return HeroTypeID::NONE; // no available heroes at all
 	return HeroTypeID::NONE; // no available heroes at all
 }
 }
 
 
-std::pair<Obj,int> CGameState::pickObject (CGObjectInstance *obj)
-{
-	switch(obj->ID)
-	{
-	case Obj::RANDOM_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC));
-	case Obj::RANDOM_TREASURE_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE));
-	case Obj::RANDOM_MINOR_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_MINOR));
-	case Obj::RANDOM_MAJOR_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_MAJOR));
-	case Obj::RANDOM_RELIC_ART:
-		return std::make_pair(Obj::ARTIFACT, VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_RELIC));
-	case Obj::RANDOM_HERO:
-		return std::make_pair(Obj::HERO, pickNextHeroType(obj->tempOwner));
-	case Obj::RANDOM_MONSTER:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator()));
-	case Obj::RANDOM_MONSTER_L1:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 1));
-	case Obj::RANDOM_MONSTER_L2:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 2));
-	case Obj::RANDOM_MONSTER_L3:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 3));
-	case Obj::RANDOM_MONSTER_L4:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 4));
-	case Obj::RANDOM_RESOURCE:
-		return std::make_pair(Obj::RESOURCE,getRandomGenerator().nextInt(6)); //now it's OH3 style, use %8 for mithril
-	case Obj::RANDOM_TOWN:
-		{
-			PlayerColor align = (dynamic_cast<CGTownInstance *>(obj))->alignmentToPlayer;
-			si32 f; // can be negative (for random)
-			if(!align.isValidPlayer()) //same as owner / random
-			{
-				if(!obj->tempOwner.isValidPlayer())
-					f = -1; //random
-				else
-					f = scenarioOps->getIthPlayersSettings(obj->tempOwner).castle;
-			}
-			else
-			{
-				f = scenarioOps->getIthPlayersSettings(align).castle;
-			}
-			if(f<0)
-			{
-				do
-				{
-					f = getRandomGenerator().nextInt((int)VLC->townh->size() - 1);
-				}
-				while ((*VLC->townh)[f]->town == nullptr); // find playable faction
-			}
-			return std::make_pair(Obj::TOWN,f);
-		}
-	case Obj::RANDOM_MONSTER_L5:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 5));
-	case Obj::RANDOM_MONSTER_L6:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 6));
-	case Obj::RANDOM_MONSTER_L7:
-		return std::make_pair(Obj::MONSTER, VLC->creh->pickRandomMonster(getRandomGenerator(), 7));
-	case Obj::RANDOM_DWELLING:
-	case Obj::RANDOM_DWELLING_LVL:
-	case Obj::RANDOM_DWELLING_FACTION:
-		{
-			auto * dwl = dynamic_cast<CGDwelling *>(obj);
-			int faction;
-
-			//if castle alignment available
-			if(auto * info = dynamic_cast<CCreGenAsCastleInfo *>(dwl->info))
-			{
-				faction = getRandomGenerator().nextInt((int)VLC->townh->size() - 1);
-				if(info->asCastle && !info->instanceId.empty())
-				{
-					auto iter = map->instanceNames.find(info->instanceId);
-
-					if(iter == map->instanceNames.end())
-						logGlobal->error("Map object not found: %s", info->instanceId);
-					else
-					{
-						auto elem = iter->second;
-						if(elem->ID==Obj::RANDOM_TOWN)
-						{
-							randomizeObject(elem.get()); //we have to randomize the castle first
-							faction = elem->subID;
-						}
-						else if(elem->ID==Obj::TOWN)
-							faction = elem->subID;
-						else
-							logGlobal->error("Map object must be town: %s", info->instanceId);
-					}
-				}
-				else if(info->asCastle)
-				{
-
-					for(auto & elem : map->objects)
-					{
-						if(!elem)
-							continue;
-
-						if(elem->ID==Obj::RANDOM_TOWN
-							&& dynamic_cast<CGTownInstance*>(elem.get())->identifier == info->identifier)
-						{
-							randomizeObject(elem); //we have to randomize the castle first
-							faction = elem->subID;
-							break;
-						}
-						else if(elem->ID==Obj::TOWN
-							&& dynamic_cast<CGTownInstance*>(elem.get())->identifier == info->identifier)
-						{
-							faction = elem->subID;
-							break;
-						}
-					}
-				}
-				else
-				{
-					std::set<int> temp;
-
-					for(int i = 0; i < info->allowedFactions.size(); i++)
-						if(info->allowedFactions[i])
-							temp.insert(i);
-
-					if(temp.empty())
-						logGlobal->error("Random faction selection failed. Nothing is allowed. Fall back to random.");
-					else
-						faction = *RandomGeneratorUtil::nextItem(temp, getRandomGenerator());
-				}
-			}
-			else // castle alignment fixed
-				faction = obj->subID;
-
-			int level;
-
-			//if level set to range
-			if(auto * info = dynamic_cast<CCreGenLeveledInfo *>(dwl->info))
-			{
-				level = getRandomGenerator().nextInt(info->minLevel, info->maxLevel) - 1;
-			}
-			else // fixed level
-			{
-				level = obj->subID;
-			}
-
-			delete dwl->info;
-			dwl->info = nullptr;
-
-			std::pair<Obj, int> result(Obj::NO_OBJ, -1);
-			CreatureID cid;
-			if((*VLC->townh)[faction]->town)
-				cid = (*VLC->townh)[faction]->town->creatures[level][0];
-			else
-			{
-				//neutral faction
-				std::vector<CCreature*> possibleCreatures;
-				std::copy_if(VLC->creh->objects.begin(), VLC->creh->objects.end(), std::back_inserter(possibleCreatures), [faction](const CCreature * c)
-				{
-					return c->getFaction().getNum() == faction;
-				});
-				assert(!possibleCreatures.empty());
-				cid = (*RandomGeneratorUtil::nextItem(possibleCreatures, getRandomGenerator()))->getId();
-			}
-
-			//NOTE: this will pick last dwelling with this creature (Mantis #900)
-			//check for block map equality is better but more complex solution
-			auto testID = [&](const Obj & primaryID) -> void
-			{
-				auto dwellingIDs = VLC->objtypeh->knownSubObjects(primaryID);
-				for (si32 entry : dwellingIDs)
-				{
-					const auto * handler = dynamic_cast<const DwellingInstanceConstructor *>(VLC->objtypeh->getHandlerFor(primaryID, entry).get());
-
-					if (handler->producesCreature(VLC->creh->objects[cid]))
-						result = std::make_pair(primaryID, entry);
-				}
-			};
-
-			testID(Obj::CREATURE_GENERATOR1);
-			if (result.first == Obj::NO_OBJ)
-				testID(Obj::CREATURE_GENERATOR4);
-
-			if (result.first == Obj::NO_OBJ)
-			{
-				logGlobal->error("Error: failed to find dwelling for %s of level %d", (*VLC->townh)[faction]->getNameTranslated(), int(level));
-				result = std::make_pair(Obj::CREATURE_GENERATOR1, *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), getRandomGenerator()));
-			}
-
-			return result;
-		}
-	}
-	return std::make_pair(Obj::NO_OBJ,-1);
-}
-
-void CGameState::randomizeObject(CGObjectInstance *cur)
-{
-	std::pair<Obj,int> ran = pickObject(cur);
-	if(ran.first == Obj::NO_OBJ || ran.second<0) //this is not a random object, or we couldn't find anything
-	{
-		if(cur->ID==Obj::TOWN || cur->ID==Obj::MONSTER)
-			cur->setType(cur->ID, cur->subID); // update def, if necessary
-	}
-	else if(ran.first==Obj::HERO)//special code for hero
-	{
-		auto * h = dynamic_cast<CGHeroInstance *>(cur);
-		cur->setType(ran.first, ran.second);
-		map->heroesOnMap.emplace_back(h);
-	}
-	else if(ran.first==Obj::TOWN)//special code for town
-	{
-		auto * t = dynamic_cast<CGTownInstance *>(cur);
-		cur->setType(ran.first, ran.second);
-		map->towns.emplace_back(t);
-	}
-	else
-	{
-		cur->setType(ran.first, ran.second);
-	}
-}
-
 int CGameState::getDate(Date mode) const
 int CGameState::getDate(Date mode) const
 {
 {
 	int temp;
 	int temp;
@@ -409,6 +164,8 @@ CGameState::CGameState()
 
 
 CGameState::~CGameState()
 CGameState::~CGameState()
 {
 {
+	// explicitly delete all ongoing battles first - BattleInfo destructor requires valid CGameState
+	currentBattles.clear();
 	map.dellNull();
 	map.dellNull();
 }
 }
 
 
@@ -469,7 +226,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, Load::Prog
 	// Explicitly initialize static variables
 	// Explicitly initialize static variables
 	for(auto & elem : players)
 	for(auto & elem : players)
 	{
 	{
-		CGKeys::playerKeyMap[elem.first] = std::set<ui8>();
+		CGKeys::playerKeyMap[elem.first] = std::set<MapObjectSubID>();
 	}
 	}
 	for(auto & elem : teams)
 	for(auto & elem : teams)
 	{
 	{
@@ -762,20 +519,21 @@ void CGameState::initRandomFactionsForPlayers()
 void CGameState::randomizeMapObjects()
 void CGameState::randomizeMapObjects()
 {
 {
 	logGlobal->debug("\tRandomizing objects");
 	logGlobal->debug("\tRandomizing objects");
-	for(CGObjectInstance *obj : map->objects)
+	for(CGObjectInstance *object : map->objects)
 	{
 	{
-		if(!obj) continue;
+		if(!object)
+			continue;
 
 
-		randomizeObject(obj);
+		object->pickRandomObject(getRandomGenerator());
 
 
 		//handle Favouring Winds - mark tiles under it
 		//handle Favouring Winds - mark tiles under it
-		if(obj->ID == Obj::FAVORABLE_WINDS)
+		if(object->ID == Obj::FAVORABLE_WINDS)
 		{
 		{
-			for (int i = 0; i < obj->getWidth() ; i++)
+			for (int i = 0; i < object->getWidth() ; i++)
 			{
 			{
-				for (int j = 0; j < obj->getHeight() ; j++)
+				for (int j = 0; j < object->getHeight() ; j++)
 				{
 				{
-					int3 pos = obj->pos - int3(i,j,0);
+					int3 pos = object->pos - int3(i,j,0);
 					if(map->isInTheMap(pos)) map->getTile(pos).extTileFlags |= 128;
 					if(map->isInTheMap(pos)) map->getTile(pos).extTileFlags |= 128;
 				}
 				}
 			}
 			}
@@ -808,7 +566,15 @@ void CGameState::placeStartingHero(const PlayerColor & playerColor, const HeroTy
 		}
 		}
 	}
 	}
 
 
-	CGObjectInstance * hero = createObject(Obj::HERO, heroTypeId.getNum(), townPos, playerColor);
+	auto handler = VLC->objtypeh->getHandlerFor(Obj::HERO, heroTypeId.toHeroType()->heroClass->getIndex());
+	CGObjectInstance * obj = handler->create(handler->getTemplates().front());
+	CGHeroInstance * hero = dynamic_cast<CGHeroInstance *>(obj);
+
+	hero->ID = Obj::HERO;
+	hero->setHeroType(heroTypeId);
+	hero->tempOwner = playerColor;
+
+	hero->pos = townPos;
 	hero->pos += hero->getVisitableOffset();
 	hero->pos += hero->getVisitableOffset();
 	map->getEditManager()->insertObject(hero);
 	map->getEditManager()->insertObject(hero);
 }
 }
@@ -864,7 +630,7 @@ void CGameState::initHeroes()
 
 
 		hero->initHero(getRandomGenerator());
 		hero->initHero(getRandomGenerator());
 		getPlayerState(hero->getOwner())->heroes.push_back(hero);
 		getPlayerState(hero->getOwner())->heroes.push_back(hero);
-		map->allHeroes[hero->type->getIndex()] = hero;
+		map->allHeroes[hero->getHeroType().getNum()] = hero;
 	}
 	}
 
 
 	// generate boats for all heroes on water
 	// generate boats for all heroes on water
@@ -878,8 +644,6 @@ void CGameState::initHeroes()
 			CGBoat * boat = dynamic_cast<CGBoat*>(handler->create());
 			CGBoat * boat = dynamic_cast<CGBoat*>(handler->create());
 			handler->configureObject(boat, gs->getRandomGenerator());
 			handler->configureObject(boat, gs->getRandomGenerator());
 
 
-			boat->ID = Obj::BOAT;
-			boat->subID = hero->getBoatType().getNum();
 			boat->pos = hero->pos;
 			boat->pos = hero->pos;
 			boat->appearance = handler->getTemplates().front();
 			boat->appearance = handler->getTemplates().front();
 			boat->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
 			boat->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
@@ -894,19 +658,23 @@ void CGameState::initHeroes()
 	for(auto obj : map->objects) //prisons
 	for(auto obj : map->objects) //prisons
 	{
 	{
 		if(obj && obj->ID == Obj::PRISON)
 		if(obj && obj->ID == Obj::PRISON)
-			map->allHeroes[obj->subID] = dynamic_cast<CGHeroInstance*>(obj.get());
+		{
+			auto * hero = dynamic_cast<CGHeroInstance*>(obj.get());
+			hero->initHero(getRandomGenerator());
+			map->allHeroes[hero->getHeroType().getNum()] = hero;
+		}
 	}
 	}
 
 
 	std::set<HeroTypeID> heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool
 	std::set<HeroTypeID> heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool
 	for(auto ph : map->predefinedHeroes)
 	for(auto ph : map->predefinedHeroes)
 	{
 	{
-		if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID)))
+		if(!vstd::contains(heroesToCreate, ph->getHeroType()))
 			continue;
 			continue;
 		ph->initHero(getRandomGenerator());
 		ph->initHero(getRandomGenerator());
 		heroesPool->addHeroToPool(ph);
 		heroesPool->addHeroToPool(ph);
 		heroesToCreate.erase(ph->type->getId());
 		heroesToCreate.erase(ph->type->getId());
 
 
-		map->allHeroes[ph->subID] = ph;
+		map->allHeroes[ph->getHeroType().getNum()] = ph;
 	}
 	}
 
 
 	for(const HeroTypeID & htype : heroesToCreate) //all not used allowed heroes go with default state into the pool
 	for(const HeroTypeID & htype : heroesToCreate) //all not used allowed heroes go with default state into the pool
@@ -992,7 +760,7 @@ void CGameState::initStartingBonus()
 					logGlobal->error("Cannot give starting artifact - no heroes!");
 					logGlobal->error("Cannot give starting artifact - no heroes!");
 					break;
 					break;
 				}
 				}
-				const Artifact * toGive = VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE).toArtifact(VLC->artifacts());
+				const Artifact * toGive = VLC->arth->pickRandomArtifact(getRandomGenerator(), CArtifact::ART_TREASURE).toEntity(VLC);
 
 
 				CGHeroInstance *hero = elem.second.heroes[0];
 				CGHeroInstance *hero = elem.second.heroes[0];
 				if(!giveHeroArtifact(hero, toGive->getId()))
 				if(!giveHeroArtifact(hero, toGive->getId()))
@@ -1017,10 +785,8 @@ void CGameState::initTowns()
 	for (auto & elem : map->towns)
 	for (auto & elem : map->towns)
 	{
 	{
 		CGTownInstance * vti =(elem);
 		CGTownInstance * vti =(elem);
-		if(!vti->town)
-		{
-			vti->town = (*VLC->townh)[vti->subID]->town;
-		}
+		assert(vti->town);
+
 		if(vti->getNameTranslated().empty())
 		if(vti->getNameTranslated().empty())
 		{
 		{
 			size_t nameID = getRandomGenerator().nextInt(vti->getTown()->getRandomNamesCount() - 1);
 			size_t nameID = getRandomGenerator().nextInt(vti->getTown()->getRandomNamesCount() - 1);
@@ -1165,7 +931,7 @@ void CGameState::initMapObjects()
 		if(!obj)
 		if(!obj)
 			continue;
 			continue;
 
 
-		switch (obj->ID)
+		switch(obj->ID.toEnum())
 		{
 		{
 			case Obj::QUEST_GUARD:
 			case Obj::QUEST_GUARD:
 			case Obj::SEER_HUT:
 			case Obj::SEER_HUT:
@@ -2103,7 +1869,7 @@ bool CGameState::giveHeroArtifact(CGHeroInstance * h, const ArtifactID & aid)
 	 auto slot = ArtifactUtils::getArtAnyPosition(h, aid);
 	 auto slot = ArtifactUtils::getArtAnyPosition(h, aid);
 	 if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
 	 if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
 	 {
 	 {
-		 ai->putAt(ArtifactLocation(h, slot));
+		 ai->putAt(*h, slot);
 		 return true;
 		 return true;
 	 }
 	 }
 	 else
 	 else
@@ -2130,12 +1896,15 @@ std::set<HeroTypeID> CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow
 		if(hero->type)
 		if(hero->type)
 			ret -= hero->type->getId();
 			ret -= hero->type->getId();
 		else
 		else
-			ret -= HeroTypeID(hero->subID);
+			ret -= hero->getHeroType();
 	}
 	}
 
 
 	for(auto obj : map->objects) //prisons
 	for(auto obj : map->objects) //prisons
-		if(obj && obj->ID == Obj::PRISON)
-			ret -= HeroTypeID(obj->subID);
+	{
+		auto * hero = dynamic_cast<const CGHeroInstance *>(obj.get());
+		if(hero && hero->ID == Obj::PRISON)
+			ret -= hero->getHeroType();
+	}
 
 
 	return ret;
 	return ret;
 }
 }
@@ -2147,23 +1916,19 @@ bool CGameState::isUsedHero(const HeroTypeID & hid) const
 
 
 CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const
 CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const
 {
 {
-	for(auto hero : map->heroesOnMap)  //heroes instances initialization
-	{
-		if(hero->type && hero->type->getId() == hid)
-		{
-			return hero;
-		}
-	}
-
 	for(auto obj : map->objects) //prisons
 	for(auto obj : map->objects) //prisons
 	{
 	{
-		if(obj && obj->ID == Obj::PRISON )
-		{
-			auto * hero = dynamic_cast<CGHeroInstance *>(obj.get());
-			assert(hero);
-			if ( hero->type && hero->type->getId() == hid )
-				return hero;
-		}
+		if (!obj)
+			continue;
+
+		if ( obj->ID !=Obj::PRISON && obj->ID != Obj::HERO)
+			continue;
+
+		auto * hero = dynamic_cast<CGHeroInstance *>(obj.get());
+		assert(hero);
+
+		if (hero->getHeroType() == hid)
+			return hero;
 	}
 	}
 
 
 	return nullptr;
 	return nullptr;

+ 2 - 3
lib/gameState/CGameState.h

@@ -115,6 +115,8 @@ public:
 	void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override;
 	void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override;
 
 
 	bool giveHeroArtifact(CGHeroInstance * h, const ArtifactID & aid);
 	bool giveHeroArtifact(CGHeroInstance * h, const ArtifactID & aid);
+	/// picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
+	HeroTypeID pickNextHeroType(const PlayerColor & owner);
 
 
 	void apply(CPack *pack);
 	void apply(CPack *pack);
 	BattleField battleGetBattlefieldType(int3 tile, CRandomGenerator & rand);
 	BattleField battleGetBattlefieldType(int3 tile, CRandomGenerator & rand);
@@ -187,7 +189,6 @@ private:
 	void initGrailPosition();
 	void initGrailPosition();
 	void initRandomFactionsForPlayers();
 	void initRandomFactionsForPlayers();
 	void randomizeMapObjects();
 	void randomizeMapObjects();
-	void randomizeObject(CGObjectInstance *cur);
 	void initPlayerStates();
 	void initPlayerStates();
 	void placeStartingHeroes();
 	void placeStartingHeroes();
 	void placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos);
 	void placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos);
@@ -214,9 +215,7 @@ private:
 	CGHeroInstance * getUsedHero(const HeroTypeID & hid) const;
 	CGHeroInstance * getUsedHero(const HeroTypeID & hid) const;
 	bool isUsedHero(const HeroTypeID & hid) const; //looks in heroes and prisons
 	bool isUsedHero(const HeroTypeID & hid) const; //looks in heroes and prisons
 	std::set<HeroTypeID> getUnusedAllowedHeroes(bool alsoIncludeNotAllowed = false) const;
 	std::set<HeroTypeID> getUnusedAllowedHeroes(bool alsoIncludeNotAllowed = false) const;
-	std::pair<Obj,int> pickObject(CGObjectInstance *obj); //chooses type of object to be randomized, returns <type, subtype>
 	HeroTypeID pickUnusedHeroTypeRandomly(const PlayerColor & owner); // picks a unused hero type randomly
 	HeroTypeID pickUnusedHeroTypeRandomly(const PlayerColor & owner); // picks a unused hero type randomly
-	HeroTypeID pickNextHeroType(const PlayerColor & owner); // picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
 	UpgradeInfo fillUpgradeInfo(const CStackInstance &stack) const;
 	UpgradeInfo fillUpgradeInfo(const CStackInstance &stack) const;
 
 
 	// ---- data -----
 	// ---- data -----

+ 14 - 14
lib/gameState/CGameStateCampaign.cpp

@@ -90,7 +90,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
 					.And(Selector::subtype()(BonusSubtypeID(g)))
 					.And(Selector::subtype()(BonusSubtypeID(g)))
 					.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
 					.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
 
 
-				cgh->getBonusLocalFirst(sel)->val = cgh->type->heroClass->primarySkillInitial[g];
+				cgh->getBonusLocalFirst(sel)->val = cgh->type->heroClass->primarySkillInitial[g.getNum()];
 			}
 			}
 		}
 		}
 	}
 	}
@@ -134,9 +134,9 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(std::vector<CampaignHeroR
 
 
 				bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
 				bool takeable = travelOptions.artifactsKeptByHero.count(art->artType->getId());
 
 
-				ArtifactLocation al(hero, artifactPosition);
-				if(!takeable  &&  !al.getSlot()->locked)  //don't try removing locked artifacts -> it crashes #1719
-					al.removeArtifact();
+				ArtifactLocation al(hero->id, artifactPosition);
+				if(!takeable && !hero->getSlot(al.slot)->locked)  //don't try removing locked artifacts -> it crashes #1719
+					hero->getArt(al.slot)->removeFrom(*hero, al.slot);
 			};
 			};
 
 
 			// process on copy - removal of artifact will invalidate container
 			// process on copy - removal of artifact will invalidate container
@@ -213,7 +213,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 	std::set<HeroTypeID> heroesToRemove = campaignState->getReservedHeroes();
 	std::set<HeroTypeID> heroesToRemove = campaignState->getReservedHeroes();
 
 
 	for(auto & campaignHeroReplacement : campaignHeroReplacements)
 	for(auto & campaignHeroReplacement : campaignHeroReplacements)
-		heroesToRemove.insert(HeroTypeID(campaignHeroReplacement.hero->subID));
+		heroesToRemove.insert(campaignHeroReplacement.hero->getHeroType());
 
 
 	for(auto & heroID : heroesToRemove)
 	for(auto & heroID : heroesToRemove)
 	{
 	{
@@ -256,7 +256,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 			assert(0); // should not happen
 			assert(0); // should not happen
 		}
 		}
 
 
-		hero->subID = heroTypeId;
+		hero->setHeroType(heroTypeId);
 		gameState->map->getEditManager()->insertObject(hero);
 		gameState->map->getEditManager()->insertObject(hero);
 	}
 	}
 }
 }
@@ -300,7 +300,7 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
 			CArtifactInstance * scroll = ArtifactUtils::createScroll(SpellID(curBonus->info2));
 			CArtifactInstance * scroll = ArtifactUtils::createScroll(SpellID(curBonus->info2));
 			const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId());
 			const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId());
 			if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
 			if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
-				scroll->putAt(ArtifactLocation(hero, slot));
+				scroll->putAt(*hero, slot);
 			else
 			else
 				logGlobal->error("Cannot give starting scroll - no free slots!");
 				logGlobal->error("Cannot give starting scroll - no free slots!");
 			break;
 			break;
@@ -339,7 +339,7 @@ void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector<CampaignHer
 		if(heroPlaceholder->tempOwner.isValidPlayer())
 		if(heroPlaceholder->tempOwner.isValidPlayer())
 			heroToPlace->tempOwner = heroPlaceholder->tempOwner;
 			heroToPlace->tempOwner = heroPlaceholder->tempOwner;
 		heroToPlace->pos = heroPlaceholder->pos;
 		heroToPlace->pos = heroPlaceholder->pos;
-		heroToPlace->type = VLC->heroh->objects[heroToPlace->subID];
+		heroToPlace->type = VLC->heroh->objects[heroToPlace->getHeroType().getNum()];
 		heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front();
 		heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front();
 
 
 		gameState->map->removeBlockVisTiles(heroPlaceholder, true);
 		gameState->map->removeBlockVisTiles(heroPlaceholder, true);
@@ -375,7 +375,7 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
 		auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
 		auto * heroPlaceholder = dynamic_cast<CGHeroPlaceholder *>(obj.get());
 
 
 		// only 1 field must be set
 		// only 1 field must be set
-		assert(heroPlaceholder->powerRank != heroPlaceholder->heroType);
+		assert(heroPlaceholder->powerRank.has_value() != heroPlaceholder->heroType.has_value());
 
 
 		if(heroPlaceholder->powerRank)
 		if(heroPlaceholder->powerRank)
 			placeholdersByPower.push_back(heroPlaceholder);
 			placeholdersByPower.push_back(heroPlaceholder);
@@ -396,7 +396,7 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
 
 
 		CGHeroInstance * hero = CampaignState::crossoverDeserialize(node, gameState->map);
 		CGHeroInstance * hero = CampaignState::crossoverDeserialize(node, gameState->map);
 
 
-		logGlobal->info("Hero crossover: Loading placeholder for %d (%s)", hero->subID, hero->getNameTranslated());
+		logGlobal->info("Hero crossover: Loading placeholder for %d (%s)", hero->getHeroType(), hero->getNameTranslated());
 
 
 		campaignHeroReplacements.emplace_back(hero, placeholder->id);
 		campaignHeroReplacements.emplace_back(hero, placeholder->id);
 	}
 	}
@@ -422,7 +422,7 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
 			CGHeroInstance * hero = CampaignState::crossoverDeserialize(*nodeListIter, gameState->map);
 			CGHeroInstance * hero = CampaignState::crossoverDeserialize(*nodeListIter, gameState->map);
 			nodeListIter++;
 			nodeListIter++;
 
 
-			logGlobal->info("Hero crossover: Loading placeholder as %d (%s)", hero->subID, hero->getNameTranslated());
+			logGlobal->info("Hero crossover: Loading placeholder as %d (%s)", hero->getHeroType(), hero->getNameTranslated());
 
 
 			campaignHeroReplacements.emplace_back(hero, placeholder->id);
 			campaignHeroReplacements.emplace_back(hero, placeholder->id);
 		}
 		}
@@ -468,7 +468,7 @@ void CGameStateCampaign::initHeroes()
 		{
 		{
 			for (auto & heroe : heroes)
 			for (auto & heroe : heroes)
 			{
 			{
-				if (heroe->subID == chosenBonus->info1)
+				if (heroe->getHeroType().getNum() == chosenBonus->info1)
 				{
 				{
 					giveCampaignBonusToHero(heroe);
 					giveCampaignBonusToHero(heroe);
 					break;
 					break;
@@ -498,7 +498,7 @@ void CGameStateCampaign::initStartingResources()
 		std::vector<const PlayerSettings *> people = getHumanPlayerInfo(); //players we will give resource bonus
 		std::vector<const PlayerSettings *> people = getHumanPlayerInfo(); //players we will give resource bonus
 		for(const PlayerSettings *ps : people)
 		for(const PlayerSettings *ps : people)
 		{
 		{
-			std::vector<int> res; //resources we will give
+			std::vector<GameResID> res; //resources we will give
 			switch (chosenBonus->info1)
 			switch (chosenBonus->info1)
 			{
 			{
 				case 0: case 1: case 2: case 3: case 4: case 5: case 6:
 				case 0: case 1: case 2: case 3: case 4: case 5: case 6:
@@ -557,7 +557,7 @@ void CGameStateCampaign::initTowns()
 		if(gameState->scenarioOps->campState->formatVCMI())
 		if(gameState->scenarioOps->campState->formatVCMI())
 			newBuilding = BuildingID(chosenBonus->info1);
 			newBuilding = BuildingID(chosenBonus->info1);
 		else
 		else
-			newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->subID, town->builtBuildings);
+			newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->builtBuildings);
 
 
 		// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
 		// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
 		while(true)
 		while(true)

+ 3 - 3
lib/gameState/TavernHeroesPool.cpp

@@ -25,7 +25,7 @@ std::map<HeroTypeID, CGHeroInstance*> TavernHeroesPool::unusedHeroesFromPool() c
 {
 {
 	std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool;
 	std::map<HeroTypeID, CGHeroInstance*> pool = heroesPool;
 	for(const auto & slot : currentTavern)
 	for(const auto & slot : currentTavern)
-		pool.erase(HeroTypeID(slot.hero->subID));
+		pool.erase(slot.hero->getHeroType());
 
 
 	return pool;
 	return pool;
 }
 }
@@ -34,7 +34,7 @@ TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const
 {
 {
 	for (auto const & slot : currentTavern)
 	for (auto const & slot : currentTavern)
 	{
 	{
-		if (HeroTypeID(slot.hero->subID) == hero)
+		if (slot.hero->getHeroType() == hero)
 			return slot.role;
 			return slot.role;
 	}
 	}
 	return TavernSlotRole::NONE;
 	return TavernSlotRole::NONE;
@@ -132,7 +132,7 @@ void TavernHeroesPool::onNewDay()
 
 
 void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero)
 void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero)
 {
 {
-	heroesPool[HeroTypeID(hero->subID)] = hero;
+	heroesPool[hero->getHeroType()] = hero;
 }
 }
 
 
 void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set<PlayerColor> mask)
 void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set<PlayerColor> mask)

+ 11 - 14
lib/mapObjectConstructors/CObjectClassesHandler.cpp

@@ -285,10 +285,9 @@ void CObjectClassesHandler::loadObject(std::string scope, std::string name, cons
 	VLC->identifiersHandler->registerObject(scope, "object", name, object->id);
 	VLC->identifiersHandler->registerObject(scope, "object", name, object->id);
 }
 }
 
 
-void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, si32 ID, si32 subID)
+void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, MapObjectID ID, MapObjectSubID subID)
 {
 {
 	config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not NULL
 	config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not NULL
-	assert(ID < objects.size());
 	assert(objects[ID]);
 	assert(objects[ID]);
 
 
 	if ( subID >= objects[ID]->objects.size())
 	if ( subID >= objects[ID]->objects.size())
@@ -298,9 +297,8 @@ void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNo
 	loadSubObject(config.meta, identifier, config, objects[ID], subID);
 	loadSubObject(config.meta, identifier, config, objects[ID], subID);
 }
 }
 
 
-void CObjectClassesHandler::removeSubObject(si32 ID, si32 subID)
+void CObjectClassesHandler::removeSubObject(MapObjectID ID, MapObjectSubID subID)
 {
 {
-	assert(ID < objects.size());
 	assert(objects[ID]);
 	assert(objects[ID]);
 	assert(subID < objects[ID]->objects.size());
 	assert(subID < objects[ID]->objects.size());
 	objects[ID]->objects[subID] = nullptr;
 	objects[ID]->objects[subID] = nullptr;
@@ -311,7 +309,7 @@ std::vector<bool> CObjectClassesHandler::getDefaultAllowed() const
 	return std::vector<bool>(); //TODO?
 	return std::vector<bool>(); //TODO?
 }
 }
 
 
-TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const
+TObjectTypeHandler CObjectClassesHandler::getHandlerFor(MapObjectID type, MapObjectSubID subtype) const
 {
 {
 	try
 	try
 	{
 	{
@@ -352,9 +350,9 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID comp
 	return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID);
 	return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID);
 }
 }
 
 
-std::set<si32> CObjectClassesHandler::knownObjects() const
+std::set<MapObjectID> CObjectClassesHandler::knownObjects() const
 {
 {
-	std::set<si32> ret;
+	std::set<MapObjectID> ret;
 
 
 	for(auto * entry : objects)
 	for(auto * entry : objects)
 		if (entry)
 		if (entry)
@@ -363,9 +361,9 @@ std::set<si32> CObjectClassesHandler::knownObjects() const
 	return ret;
 	return ret;
 }
 }
 
 
-std::set<si32> CObjectClassesHandler::knownSubObjects(si32 primaryID) const
+std::set<MapObjectSubID> CObjectClassesHandler::knownSubObjects(MapObjectID primaryID) const
 {
 {
-	std::set<si32> ret;
+	std::set<MapObjectSubID> ret;
 
 
 	if (!objects.at(primaryID))
 	if (!objects.at(primaryID))
 	{
 	{
@@ -461,7 +459,7 @@ void CObjectClassesHandler::generateExtraMonolithsForRMG()
 	}
 	}
 }
 }
 
 
-std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const
+std::string CObjectClassesHandler::getObjectName(MapObjectID type, MapObjectSubID subtype) const
 {
 {
 	const auto handler = getHandlerFor(type, subtype);
 	const auto handler = getHandlerFor(type, subtype);
 	if (handler && handler->hasNameTextID())
 	if (handler && handler->hasNameTextID())
@@ -470,7 +468,7 @@ std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const
 		return objects[type]->getNameTranslated();
 		return objects[type]->getNameTranslated();
 }
 }
 
 
-SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type, si32 subtype) const
+SObjectSounds CObjectClassesHandler::getObjectSounds(MapObjectID type, MapObjectSubID subtype) const
 {
 {
 	// TODO: these objects may have subID's that does not have associated handler:
 	// TODO: these objects may have subID's that does not have associated handler:
 	// Prison: uses hero type as subID
 	// Prison: uses hero type as subID
@@ -479,19 +477,18 @@ SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type, si32 subtype) co
 	if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL)
 	if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL)
 		subtype = 0;
 		subtype = 0;
 
 
-	assert(type < objects.size());
 	assert(objects[type]);
 	assert(objects[type]);
 	assert(subtype < objects[type]->objects.size());
 	assert(subtype < objects[type]->objects.size());
 
 
 	return getHandlerFor(type, subtype)->getSounds();
 	return getHandlerFor(type, subtype)->getSounds();
 }
 }
 
 
-std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const
+std::string CObjectClassesHandler::getObjectHandlerName(MapObjectID type) const
 {
 {
 	return objects.at(type)->handlerName;
 	return objects.at(type)->handlerName;
 }
 }
 
 
-std::string CObjectClassesHandler::getJsonKey(si32 type) const
+std::string CObjectClassesHandler::getJsonKey(MapObjectID type) const
 {
 {
 	return objects.at(type)->getJsonKey();
 	return objects.at(type)->getJsonKey();
 }
 }

+ 9 - 9
lib/mapObjectConstructors/CObjectClassesHandler.h

@@ -105,8 +105,8 @@ public:
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
 	void loadObject(std::string scope, std::string name, const JsonNode & data) override;
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
 	void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override;
 
 
-	void loadSubObject(const std::string & identifier, JsonNode config, si32 ID, si32 subID);
-	void removeSubObject(si32 ID, si32 subID);
+	void loadSubObject(const std::string & identifier, JsonNode config, MapObjectID ID, MapObjectSubID subID);
+	void removeSubObject(MapObjectID ID, MapObjectSubID subID);
 
 
 	void beforeValidate(JsonNode & object) override;
 	void beforeValidate(JsonNode & object) override;
 	void afterLoadFinalization() override;
 	void afterLoadFinalization() override;
@@ -114,22 +114,22 @@ public:
 	std::vector<bool> getDefaultAllowed() const override;
 	std::vector<bool> getDefaultAllowed() const override;
 
 
 	/// Queries to detect loaded objects
 	/// Queries to detect loaded objects
-	std::set<si32> knownObjects() const;
-	std::set<si32> knownSubObjects(si32 primaryID) const;
+	std::set<MapObjectID> knownObjects() const;
+	std::set<MapObjectSubID> knownSubObjects(MapObjectID primaryID) const;
 
 
 	/// returns handler for specified object (ID-based). ObjectHandler keeps ownership
 	/// returns handler for specified object (ID-based). ObjectHandler keeps ownership
-	TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const;
+	TObjectTypeHandler getHandlerFor(MapObjectID type, MapObjectSubID subtype) const;
 	TObjectTypeHandler getHandlerFor(const std::string & scope, const std::string & type, const std::string & subtype) const;
 	TObjectTypeHandler getHandlerFor(const std::string & scope, const std::string & type, const std::string & subtype) const;
 	TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const;
 	TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const;
 
 
-	std::string getObjectName(si32 type, si32 subtype) const;
+	std::string getObjectName(MapObjectID type, MapObjectSubID subtype) const;
 
 
-	SObjectSounds getObjectSounds(si32 type, si32 subtype) const;
+	SObjectSounds getObjectSounds(MapObjectID type, MapObjectSubID subtype) const;
 
 
 	/// Returns handler string describing the handler (for use in client)
 	/// Returns handler string describing the handler (for use in client)
-	std::string getObjectHandlerName(si32 type) const;
+	std::string getObjectHandlerName(MapObjectID type) const;
 
 
-	std::string getJsonKey(si32 type) const;
+	std::string getJsonKey(MapObjectID type) const;
 
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 	{

+ 2 - 2
lib/mapObjects/CArmedInstance.cpp

@@ -73,7 +73,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 	for(const auto & slot : Slots())
 	for(const auto & slot : Slots())
 	{
 	{
 		const CStackInstance * inst = slot.second;
 		const CStackInstance * inst = slot.second;
-		const CCreature * creature  = VLC->creh->objects[inst->getCreatureID()];
+		const auto * creature  = inst->getCreatureID().toEntity(VLC);
 
 
 		factions.insert(creature->getFaction());
 		factions.insert(creature->getFaction());
 		// Check for undead flag instead of faction (undead mummies are neutral)
 		// Check for undead flag instead of faction (undead mummies are neutral)
@@ -92,7 +92,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 
 
 		for(auto f : factions)
 		for(auto f : factions)
 		{
 		{
-			if (VLC->factions()->getByIndex(f)->getAlignment() != EAlignment::EVIL)
+			if (VLC->factions()->getById(f)->getAlignment() != EAlignment::EVIL)
 				mixableFactions++;
 				mixableFactions++;
 		}
 		}
 		if (mixableFactions > 0)
 		if (mixableFactions > 0)

+ 18 - 22
lib/mapObjects/CBank.cpp

@@ -46,7 +46,7 @@ void CBank::initObj(CRandomGenerator & rand)
 {
 {
 	daycounter = 0;
 	daycounter = 0;
 	resetDuration = 0;
 	resetDuration = 0;
-	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
+	getObjectHandler()->configureObject(this, rand);
 }
 }
 
 
 bool CBank::isCoastVisitable() const
 bool CBank::isCoastVisitable() const
@@ -79,11 +79,7 @@ std::vector<Component> CBank::getPopupComponents(PlayerColor player) const
 
 
 	for (auto const & guard : guardsAmounts)
 	for (auto const & guard : guardsAmounts)
 	{
 	{
-		Component comp;
-		comp.id = Component::EComponentType::CREATURE;
-		comp.subtype = guard.first.getNum();
-		comp.val = guard.second;
-
+		Component comp(ComponentType::CREATURE, guard.first, guard.second);
 		result.push_back(comp);
 		result.push_back(comp);
 	}
 	}
 	return result;
 	return result;
@@ -141,7 +137,7 @@ void CBank::onHeroVisit(const CGHeroInstance * h) const
 	cb->sendAndApply(&cov);
 	cb->sendAndApply(&cov);
 
 
 	int banktext = 0;
 	int banktext = 0;
-	switch (ID)
+	switch (ID.toEnum())
 	{
 	{
 	case Obj::DERELICT_SHIP:
 	case Obj::DERELICT_SHIP:
 		banktext = 41;
 		banktext = 41;
@@ -184,7 +180,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 
 
 	if (bc)
 	if (bc)
 	{
 	{
-		switch (ID)
+		switch (ID.toEnum())
 		{
 		{
 		case Obj::DERELICT_SHIP:
 		case Obj::DERELICT_SHIP:
 			textID = 43;
 			textID = 43;
@@ -207,7 +203,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 	}
 	}
 	else
 	else
 	{
 	{
-		switch (ID)
+		switch (ID.toEnum())
 		{
 		{
 		case Obj::SHIPWRECK:
 		case Obj::SHIPWRECK:
 		case Obj::DERELICT_SHIP:
 		case Obj::DERELICT_SHIP:
@@ -220,7 +216,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 			gbonus.bonus.sid = BonusSourceID(ID);
 			gbonus.bonus.sid = BonusSourceID(ID);
 			gbonus.bonus.type = BonusType::MORALE;
 			gbonus.bonus.type = BonusType::MORALE;
 			gbonus.bonus.val = -1;
 			gbonus.bonus.val = -1;
-			switch (ID)
+			switch (ID.toEnum())
 			{
 			{
 			case Obj::SHIPWRECK:
 			case Obj::SHIPWRECK:
 				textID = 123;
 				textID = 123;
@@ -236,7 +232,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 				break;
 				break;
 			}
 			}
 			cb->giveHeroBonus(&gbonus);
 			cb->giveHeroBonus(&gbonus);
-			iw.components.emplace_back(Component::EComponentType::MORALE, 0, -1, 0);
+			iw.components.emplace_back(ComponentType::MORALE, -1);
 			iw.soundID = soundBase::GRAVEYARD;
 			iw.soundID = soundBase::GRAVEYARD;
 			break;
 			break;
 		}
 		}
@@ -247,7 +243,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 			gb.id = hero->id.getNum();
 			gb.id = hero->id.getNum();
 			cb->giveHeroBonus(&gb);
 			cb->giveHeroBonus(&gb);
 			textID = 107;
 			textID = 107;
-			iw.components.emplace_back(Component::EComponentType::LUCK, 0, -2, 0);
+			iw.components.emplace_back(ComponentType::LUCK, -2);
 			break;
 			break;
 		}
 		}
 		case Obj::CREATURE_BANK:
 		case Obj::CREATURE_BANK:
@@ -267,24 +263,24 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 	//grant resources
 	//grant resources
 	if (bc)
 	if (bc)
 	{
 	{
-		for (int it = 0; it < bc->resources.size(); it++)
+		for (GameResID it : GameResID::ALL_RESOURCES())
 		{
 		{
 			if (bc->resources[it] != 0)
 			if (bc->resources[it] != 0)
 			{
 			{
-				iw.components.emplace_back(Component::EComponentType::RESOURCE, it, bc->resources[it], 0);
+				iw.components.emplace_back(ComponentType::RESOURCE, it, bc->resources[it]);
 				loot.appendRawString("%d %s");
 				loot.appendRawString("%d %s");
-				loot.replaceNumber(iw.components.back().val);
-				loot.replaceLocalString(EMetaText::RES_NAMES, iw.components.back().subtype);
-				cb->giveResource(hero->getOwner(), static_cast<EGameResID>(it), bc->resources[it]);
+				loot.replaceNumber(bc->resources[it]);
+				loot.replaceLocalString(EMetaText::RES_NAMES, it);
+				cb->giveResource(hero->getOwner(), it, bc->resources[it]);
 			}
 			}
 		}
 		}
 		//grant artifacts
 		//grant artifacts
 		for (auto & elem : bc->artifacts)
 		for (auto & elem : bc->artifacts)
 		{
 		{
-			iw.components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0);
+			iw.components.emplace_back(ComponentType::ARTIFACT, elem);
 			loot.appendRawString("%s");
 			loot.appendRawString("%s");
 			loot.replaceLocalString(EMetaText::ART_NAMES, elem);
 			loot.replaceLocalString(EMetaText::ART_NAMES, elem);
-			cb->giveHeroNewArtifact(hero, VLC->arth->objects[elem], ArtifactPosition::FIRST_AVAILABLE);
+			cb->giveHeroNewArtifact(hero, elem.toArtifact(), ArtifactPosition::FIRST_AVAILABLE);
 		}
 		}
 		//display loot
 		//display loot
 		if (!iw.components.empty())
 		if (!iw.components.empty())
@@ -318,14 +314,14 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 			}
 			}
 			for(const SpellID & spellId : bc->spells)
 			for(const SpellID & spellId : bc->spells)
 			{
 			{
-				const auto * spell = spellId.toSpell(VLC->spells());
+				const auto * spell = spellId.toEntity(VLC);
 				iw.text.appendLocalString(EMetaText::SPELL_NAME, spellId);
 				iw.text.appendLocalString(EMetaText::SPELL_NAME, spellId);
 				if(spell->getLevel() <= hero->maxSpellLevel())
 				if(spell->getLevel() <= hero->maxSpellLevel())
 				{
 				{
 					if(hero->canLearnSpell(spell))
 					if(hero->canLearnSpell(spell))
 					{
 					{
 						spells.insert(spellId);
 						spells.insert(spellId);
-						iw.components.emplace_back(Component::EComponentType::SPELL, spellId, 0, 0);
+						iw.components.emplace_back(ComponentType::SPELL, spellId);
 					}
 					}
 				}
 				}
 				else
 				else
@@ -356,7 +352,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
 
 
 		for(const auto & elem : ourArmy.Slots())
 		for(const auto & elem : ourArmy.Slots())
 		{
 		{
-			iw.components.emplace_back(*elem.second);
+			iw.components.emplace_back(ComponentType::CREATURE, elem.second->getId(), elem.second->getCount());
 			loot.appendRawString("%s");
 			loot.appendRawString("%s");
 			loot.replaceCreatureName(*elem.second);
 			loot.replaceCreatureName(*elem.second);
 		}
 		}

+ 55 - 18
lib/mapObjects/CGCreature.cpp

@@ -28,7 +28,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const
 	if(stacks.empty())
 	if(stacks.empty())
 	{
 	{
 		//should not happen...
 		//should not happen...
-		logGlobal->error("Invalid stack at tile %s: subID=%d; id=%d", pos.toString(), subID, id.getNum());
+		logGlobal->error("Invalid stack at tile %s: subID=%d; id=%d", pos.toString(), getCreature(), id.getNum());
 		return "INVALID_STACK";
 		return "INVALID_STACK";
 	}
 	}
 
 
@@ -41,7 +41,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const
 	else
 	else
 		ms.appendLocalString(EMetaText::ARRAY_TXT, quantityTextIndex);
 		ms.appendLocalString(EMetaText::ARRAY_TXT, quantityTextIndex);
 	ms.appendRawString(" ");
 	ms.appendRawString(" ");
-	ms.appendLocalString(EMetaText::CRE_PL_NAMES,subID);
+	ms.appendLocalString(EMetaText::CRE_PL_NAMES, getCreature());
 	hoverName = ms.toString();
 	hoverName = ms.toString();
 	return hoverName;
 	return hoverName;
 }
 }
@@ -54,7 +54,7 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 		MetaString ms;
 		MetaString ms;
 		ms.appendNumber(stacks.begin()->second->count);
 		ms.appendNumber(stacks.begin()->second->count);
 		ms.appendRawString(" ");
 		ms.appendRawString(" ");
-		ms.appendLocalString(EMetaText::CRE_PL_NAMES,subID);
+		ms.appendLocalString(EMetaText::CRE_PL_NAMES, getCreature());
 
 
 		ms.appendRawString("\n\n");
 		ms.appendRawString("\n\n");
 
 
@@ -132,7 +132,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
 			BlockingDialog ynd(true,false);
 			BlockingDialog ynd(true,false);
 			ynd.player = h->tempOwner;
 			ynd.player = h->tempOwner;
 			ynd.text.appendLocalString(EMetaText::ADVOB_TXT, 86);
 			ynd.text.appendLocalString(EMetaText::ADVOB_TXT, 86);
-			ynd.text.replaceLocalString(EMetaText::CRE_PL_NAMES, subID);
+			ynd.text.replaceLocalString(EMetaText::CRE_PL_NAMES, getCreature());
 			cb->showBlockingDialog(&ynd);
 			cb->showBlockingDialog(&ynd);
 			break;
 			break;
 		}
 		}
@@ -146,13 +146,50 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
 			std::string tmp = VLC->generaltexth->advobtxt[90];
 			std::string tmp = VLC->generaltexth->advobtxt[90];
 			boost::algorithm::replace_first(tmp, "%d", std::to_string(getStackCount(SlotID(0))));
 			boost::algorithm::replace_first(tmp, "%d", std::to_string(getStackCount(SlotID(0))));
 			boost::algorithm::replace_first(tmp, "%d", std::to_string(action));
 			boost::algorithm::replace_first(tmp, "%d", std::to_string(action));
-			boost::algorithm::replace_first(tmp,"%s",VLC->creh->objects[subID]->getNamePluralTranslated());
+			boost::algorithm::replace_first(tmp,"%s",VLC->creatures()->getById(getCreature())->getNamePluralTranslated());
 			ynd.text.appendRawString(tmp);
 			ynd.text.appendRawString(tmp);
 			cb->showBlockingDialog(&ynd);
 			cb->showBlockingDialog(&ynd);
 			break;
 			break;
 		}
 		}
 	}
 	}
+}
+
+CreatureID CGCreature::getCreature() const
+{
+	return CreatureID(getObjTypeIndex().getNum());
+}
 
 
+void CGCreature::pickRandomObject(CRandomGenerator & rand)
+{
+	switch(ID.toEnum())
+	{
+		case MapObjectID::RANDOM_MONSTER:
+			subID = VLC->creh->pickRandomMonster(rand);
+			break;
+		case MapObjectID::RANDOM_MONSTER_L1:
+			subID = VLC->creh->pickRandomMonster(rand, 1);
+			break;
+		case MapObjectID::RANDOM_MONSTER_L2:
+			subID = VLC->creh->pickRandomMonster(rand, 2);
+			break;
+		case MapObjectID::RANDOM_MONSTER_L3:
+			subID = VLC->creh->pickRandomMonster(rand, 3);
+			break;
+		case MapObjectID::RANDOM_MONSTER_L4:
+			subID = VLC->creh->pickRandomMonster(rand, 4);
+			break;
+		case MapObjectID::RANDOM_MONSTER_L5:
+			subID = VLC->creh->pickRandomMonster(rand, 5);
+			break;
+		case MapObjectID::RANDOM_MONSTER_L6:
+			subID = VLC->creh->pickRandomMonster(rand, 6);
+			break;
+		case MapObjectID::RANDOM_MONSTER_L7:
+			subID = VLC->creh->pickRandomMonster(rand, 7);
+			break;
+	}
+	ID = MapObjectID::MONSTER;
+	setType(ID, subID);
 }
 }
 
 
 void CGCreature::initObj(CRandomGenerator & rand)
 void CGCreature::initObj(CRandomGenerator & rand)
@@ -177,16 +214,16 @@ void CGCreature::initObj(CRandomGenerator & rand)
 		break;
 		break;
 	}
 	}
 
 
-	stacks[SlotID(0)]->setType(CreatureID(subID));
+	stacks[SlotID(0)]->setType(getCreature());
 	TQuantity &amount = stacks[SlotID(0)]->count;
 	TQuantity &amount = stacks[SlotID(0)]->count;
-	CCreature &c = *VLC->creh->objects[subID];
+	const Creature * c = VLC->creatures()->getById(getCreature());
 	if(amount == 0)
 	if(amount == 0)
 	{
 	{
-		amount = rand.nextInt(c.ammMin, c.ammMax);
+		amount = rand.nextInt(c->getAdvMapAmountMin(), c->getAdvMapAmountMax());
 
 
 		if(amount == 0) //armies with 0 creatures are illegal
 		if(amount == 0) //armies with 0 creatures are illegal
 		{
 		{
-			logGlobal->warn("Stack %s cannot have 0 creatures. Check properties of %s", nodeName(), c.nodeName());
+			logGlobal->warn("Stack cannot have 0 creatures. Check properties of %s", c->getJsonKey());
 			amount = 1;
 			amount = 1;
 		}
 		}
 	}
 	}
@@ -252,7 +289,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 		powerFactor = -3;
 		powerFactor = -3;
 
 
 	std::set<CreatureID> myKindCres; //what creatures are the same kind as we
 	std::set<CreatureID> myKindCres; //what creatures are the same kind as we
-	const CCreature * myCreature = VLC->creh->objects[subID];
+	const CCreature * myCreature = VLC->creh->objects[getCreature().getNum()];
 	myKindCres.insert(myCreature->getId()); //we
 	myKindCres.insert(myCreature->getId()); //we
 	myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades
 	myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades
 
 
@@ -290,7 +327,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 			return JOIN_FOR_FREE;
 			return JOIN_FOR_FREE;
 
 
 		else if(diplomacy * 2  +  sympathy  +  1 >= character)
 		else if(diplomacy * 2  +  sympathy  +  1 >= character)
-			return VLC->creatures()->getByIndex(subID)->getRecruitCost(EGameResID::GOLD) * getStackCount(SlotID(0)); //join for gold
+			return VLC->creatures()->getById(getCreature())->getRecruitCost(EGameResID::GOLD) * getStackCount(SlotID(0)); //join for gold
 	}
 	}
 
 
 	//we are still here - creatures have not joined hero, flee or fight
 	//we are still here - creatures have not joined hero, flee or fight
@@ -390,7 +427,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const
 			if(!upgrades.empty())
 			if(!upgrades.empty())
 			{
 			{
 				auto it = RandomGeneratorUtil::nextItem(upgrades, CRandomGenerator::getDefault());
 				auto it = RandomGeneratorUtil::nextItem(upgrades, CRandomGenerator::getDefault());
-				cb->changeStackType(StackLocation(this, slotID), VLC->creh->objects[*it]);
+				cb->changeStackType(StackLocation(this, slotID), it->toCreature());
 			}
 			}
 		}
 		}
 	}
 	}
@@ -404,7 +441,7 @@ void CGCreature::flee( const CGHeroInstance * h ) const
 	BlockingDialog ynd(true,false);
 	BlockingDialog ynd(true,false);
 	ynd.player = h->tempOwner;
 	ynd.player = h->tempOwner;
 	ynd.text.appendLocalString(EMetaText::ADVOB_TXT,91);
 	ynd.text.appendLocalString(EMetaText::ADVOB_TXT,91);
-	ynd.text.replaceLocalString(EMetaText::CRE_PL_NAMES, subID);
+	ynd.text.replaceLocalString(EMetaText::CRE_PL_NAMES, getCreature());
 	cb->showBlockingDialog(&ynd);
 	cb->showBlockingDialog(&ynd);
 }
 }
 
 
@@ -526,17 +563,17 @@ void CGCreature::giveReward(const CGHeroInstance * h) const
 	if(!resources.empty())
 	if(!resources.empty())
 	{
 	{
 		cb->giveResources(h->tempOwner, resources);
 		cb->giveResources(h->tempOwner, resources);
-		for(int i = 0; i < resources.size(); i++)
+		for(auto const & res : GameResID::ALL_RESOURCES())
 		{
 		{
-			if(resources[i] > 0)
-				iw.components.emplace_back(Component::EComponentType::RESOURCE, i, resources[i], 0);
+			if(resources[res] > 0)
+				iw.components.emplace_back(ComponentType::RESOURCE, res, resources[res]);
 		}
 		}
 	}
 	}
 
 
 	if(gainedArtifact != ArtifactID::NONE)
 	if(gainedArtifact != ArtifactID::NONE)
 	{
 	{
-		cb->giveHeroNewArtifact(h, VLC->arth->objects[gainedArtifact], ArtifactPosition::FIRST_AVAILABLE);
-		iw.components.emplace_back(Component::EComponentType::ARTIFACT, gainedArtifact, 0, 0);
+		cb->giveHeroNewArtifact(h, gainedArtifact.toArtifact(), ArtifactPosition::FIRST_AVAILABLE);
+		iw.components.emplace_back(ComponentType::ARTIFACT, gainedArtifact);
 	}
 	}
 
 
 	if(!iw.components.empty())
 	if(!iw.components.empty())

+ 2 - 0
lib/mapObjects/CGCreature.h

@@ -41,9 +41,11 @@ public:
 	std::string getHoverText(PlayerColor player) const override;
 	std::string getHoverText(PlayerColor player) const override;
 	std::string getHoverText(const CGHeroInstance * hero) const override;
 	std::string getHoverText(const CGHeroInstance * hero) const override;
 	void initObj(CRandomGenerator & rand) override;
 	void initObj(CRandomGenerator & rand) override;
+	void pickRandomObject(CRandomGenerator & rand) override;
 	void newTurn(CRandomGenerator & rand) const override;
 	void newTurn(CRandomGenerator & rand) const override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
+	CreatureID getCreature() const;
 
 
 	//stack formation depends on position,
 	//stack formation depends on position,
 	bool containsUpgradedStack() const;
 	bool containsUpgradedStack() const;

+ 127 - 65
lib/mapObjects/CGDwelling.cpp

@@ -11,8 +11,10 @@
 #include "StdInc.h"
 #include "StdInc.h"
 #include "CGDwelling.h"
 #include "CGDwelling.h"
 #include "../serializer/JsonSerializeFormat.h"
 #include "../serializer/JsonSerializeFormat.h"
+#include "../mapping/CMap.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
+#include "../mapObjectConstructors/DwellingInstanceConstructor.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../networkPacks/StackLocation.h"
 #include "../networkPacks/StackLocation.h"
 #include "../networkPacks/PacksForClient.h"
 #include "../networkPacks/PacksForClient.h"
@@ -26,76 +28,154 @@
 
 
 VCMI_LIB_NAMESPACE_BEGIN
 VCMI_LIB_NAMESPACE_BEGIN
 
 
-CSpecObjInfo::CSpecObjInfo():
-	owner(nullptr)
-{
-
-}
-
-void CCreGenAsCastleInfo::serializeJson(JsonSerializeFormat & handler)
+void CGDwellingRandomizationInfo::serializeJson(JsonSerializeFormat & handler)
 {
 {
 	handler.serializeString("sameAsTown", instanceId);
 	handler.serializeString("sameAsTown", instanceId);
+	handler.serializeIdArray("allowedFactions", allowedFactions);
+	handler.serializeInt("minLevel", minLevel, static_cast<uint8_t>(1));
+	handler.serializeInt("maxLevel", maxLevel, static_cast<uint8_t>(7));
 
 
 	if(!handler.saving)
 	if(!handler.saving)
 	{
 	{
-		asCastle = !instanceId.empty();
-		allowedFactions.clear();
+		//todo: safely allow any level > 7
+		vstd::abetween<uint8_t>(minLevel, 1, 7);
+		vstd::abetween<uint8_t>(maxLevel, minLevel, 7);
 	}
 	}
+}
 
 
-	if(!asCastle)
-	{
-		std::vector<bool> standard;
-		standard.resize(VLC->townh->size(), true);
+CGDwelling::CGDwelling() = default;
+CGDwelling::~CGDwelling() = default;
+
+FactionID CGDwelling::randomizeFaction(CRandomGenerator & rand)
+{
+	if (ID == Obj::RANDOM_DWELLING_FACTION)
+		return FactionID(subID);
 
 
-		JsonSerializeFormat::LIC allowedLIC(standard, &FactionID::decode, &FactionID::encode);
-		allowedLIC.any = allowedFactions;
+	assert(ID == Obj::RANDOM_DWELLING || ID == Obj::RANDOM_DWELLING_LVL);
+	assert(randomizationInfo.has_value());
+	if (!randomizationInfo)
+		return FactionID::CASTLE;
 
 
-		handler.serializeLIC("allowedFactions", allowedLIC);
+	CGTownInstance * linkedTown = nullptr;
+
+	if (!randomizationInfo->instanceId.empty())
+	{
+		auto iter = cb->gameState()->map->instanceNames.find(randomizationInfo->instanceId);
+
+		if(iter == cb->gameState()->map->instanceNames.end())
+			logGlobal->error("Map object not found: %s", randomizationInfo->instanceId);
+		linkedTown = dynamic_cast<CGTownInstance *>(iter->second.get());
+	}
 
 
-		if(!handler.saving)
+	if (randomizationInfo->identifier != 0)
+	{
+		for(auto & elem : cb->gameState()->map->objects)
 		{
 		{
-			allowedFactions = allowedLIC.any;
+			auto town = dynamic_cast<CGTownInstance*>(elem.get());
+			if(town && town->identifier == randomizationInfo->identifier)
+			{
+				linkedTown = town;
+				break;
+			}
 		}
 		}
 	}
 	}
-}
 
 
-void CCreGenLeveledInfo::serializeJson(JsonSerializeFormat & handler)
-{
-	handler.serializeInt("minLevel", minLevel, static_cast<uint8_t>(1));
-	handler.serializeInt("maxLevel", maxLevel, static_cast<uint8_t>(7));
-
-	if(!handler.saving)
+	if (linkedTown)
 	{
 	{
-		//todo: safely allow any level > 7
-		vstd::abetween<uint8_t>(minLevel, 1, 7);
-		vstd::abetween<uint8_t>(maxLevel, minLevel, 7);
+		if(linkedTown->ID==Obj::RANDOM_TOWN)
+			linkedTown->pickRandomObject(rand); //we have to randomize the castle first
+
+		assert(linkedTown->ID == Obj::TOWN);
+		if(linkedTown->ID==Obj::TOWN)
+			return linkedTown->getFaction();
 	}
 	}
-}
 
 
-void CCreGenLeveledCastleInfo::serializeJson(JsonSerializeFormat & handler)
-{
-	CCreGenAsCastleInfo::serializeJson(handler);
-	CCreGenLeveledInfo::serializeJson(handler);
+	if(!randomizationInfo->allowedFactions.empty())
+		return *RandomGeneratorUtil::nextItem(randomizationInfo->allowedFactions, rand);
+
+
+	std::vector<FactionID> potentialPicks;
+
+	for (FactionID faction(0); faction < FactionID(VLC->townh->size()); ++faction)
+		if (VLC->factions()->getById(faction)->hasTown())
+			potentialPicks.push_back(faction);
+
+	assert(!potentialPicks.empty());
+	return *RandomGeneratorUtil::nextItem(potentialPicks, rand);
 }
 }
 
 
-CGDwelling::CGDwelling()
-	: info(nullptr)
+int CGDwelling::randomizeLevel(CRandomGenerator & rand)
 {
 {
+	if (ID == Obj::RANDOM_DWELLING_LVL)
+		return subID.getNum();
+
+	assert(ID == Obj::RANDOM_DWELLING || ID == Obj::RANDOM_DWELLING_FACTION);
+	assert(randomizationInfo.has_value());
+
+	if (!randomizationInfo)
+		return rand.nextInt(1, 7) - 1;
+
+	if(randomizationInfo->minLevel == randomizationInfo->maxLevel)
+		return randomizationInfo->minLevel - 1;
+
+	return rand.nextInt(randomizationInfo->minLevel, randomizationInfo->maxLevel) - 1;
 }
 }
 
 
-CGDwelling::~CGDwelling()
+void CGDwelling::pickRandomObject(CRandomGenerator & rand)
 {
 {
-	vstd::clear_pointer(info);
+	if (ID == Obj::RANDOM_DWELLING || ID == Obj::RANDOM_DWELLING_LVL || ID == Obj::RANDOM_DWELLING_FACTION)
+	{
+		FactionID faction = randomizeFaction(rand);
+		int level = randomizeLevel(rand);
+		assert(faction != FactionID::NONE && faction != FactionID::NEUTRAL);
+		assert(level >= 0 && level <= 6);
+		randomizationInfo.reset();
+
+		CreatureID cid = (*VLC->townh)[faction]->town->creatures[level][0];
+
+		//NOTE: this will pick last dwelling with this creature (Mantis #900)
+		//check for block map equality is better but more complex solution
+		auto testID = [&](const Obj & primaryID) -> MapObjectSubID
+		{
+			auto dwellingIDs = VLC->objtypeh->knownSubObjects(primaryID);
+			for (si32 entry : dwellingIDs)
+			{
+				const auto * handler = dynamic_cast<const DwellingInstanceConstructor *>(VLC->objtypeh->getHandlerFor(primaryID, entry).get());
+
+				if (handler->producesCreature(cid.toCreature()))
+					return MapObjectSubID(entry);
+			}
+			return MapObjectSubID();
+		};
+
+		ID = Obj::CREATURE_GENERATOR1;
+		subID = testID(Obj::CREATURE_GENERATOR1);
+
+		if (subID == MapObjectSubID())
+		{
+			ID = Obj::CREATURE_GENERATOR4;
+			subID = testID(Obj::CREATURE_GENERATOR4);
+		}
+
+		if (subID == MapObjectSubID())
+		{
+			logGlobal->error("Error: failed to find dwelling for %s of level %d", (*VLC->townh)[faction]->getNameTranslated(), int(level));
+			ID = Obj::CREATURE_GENERATOR4;
+			subID = *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), rand);
+		}
+
+		setType(ID, subID);
+	}
 }
 }
 
 
 void CGDwelling::initObj(CRandomGenerator & rand)
 void CGDwelling::initObj(CRandomGenerator & rand)
 {
 {
-	switch(ID)
+	switch(ID.toEnum())
 	{
 	{
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR4:
 	case Obj::CREATURE_GENERATOR4:
 		{
 		{
-			VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
+			getObjectHandler()->configureObject(this, rand);
 
 
 			if (getOwner() != PlayerColor::NEUTRAL)
 			if (getOwner() != PlayerColor::NEUTRAL)
 				cb->gameState()->players[getOwner()].dwellings.emplace_back(this);
 				cb->gameState()->players[getOwner()].dwellings.emplace_back(this);
@@ -121,23 +201,6 @@ void CGDwelling::initObj(CRandomGenerator & rand)
 	}
 	}
 }
 }
 
 
-void CGDwelling::initRandomObjectInfo()
-{
-	vstd::clear_pointer(info);
-	switch(ID)
-	{
-		case Obj::RANDOM_DWELLING: info = new CCreGenLeveledCastleInfo();
-			break;
-		case Obj::RANDOM_DWELLING_LVL: info = new CCreGenAsCastleInfo();
-			break;
-		case Obj::RANDOM_DWELLING_FACTION: info = new CCreGenLeveledInfo();
-			break;
-	}
-
-	if(info)
-		info->owner = this;
-}
-
 void CGDwelling::setPropertyDer(ui8 what, ui32 val)
 void CGDwelling::setPropertyDer(ui8 what, ui32 val)
 {
 {
 	switch (what)
 	switch (what)
@@ -256,7 +319,7 @@ void CGDwelling::newTurn(CRandomGenerator & rand) const
 			else
 			else
 				creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL);
 				creaturesAccumulate = VLC->settings()->getBoolean(EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL);
 
 
-			CCreature *cre = VLC->creh->objects[creatures[i].second[0]];
+			const CCreature * cre =creatures[i].second[0].toCreature();
 			TQuantity amount = cre->getGrowth() * (1 + cre->valOfBonuses(BonusType::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(BonusType::CREATURE_GROWTH);
 			TQuantity amount = cre->getGrowth() * (1 + cre->valOfBonuses(BonusType::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(BonusType::CREATURE_GROWTH);
 			if (creaturesAccumulate && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures
 			if (creaturesAccumulate && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures
 				sac.creatures[i].first += amount;
 				sac.creatures[i].first += amount;
@@ -281,7 +344,7 @@ void CGDwelling::updateGuards() const
 	//default condition - creatures are of level 5 or higher
 	//default condition - creatures are of level 5 or higher
 	for (auto creatureEntry : creatures)
 	for (auto creatureEntry : creatures)
 	{
 	{
-		if (VLC->creatures()->getByIndex(creatureEntry.second.at(0))->getLevel() >= 5 && ID != Obj::REFUGEE_CAMP)
+		if (VLC->creatures()->getById(creatureEntry.second.at(0))->getLevel() >= 5 && ID != Obj::REFUGEE_CAMP)
 		{
 		{
 			guarded = true;
 			guarded = true;
 			break;
 			break;
@@ -292,7 +355,7 @@ void CGDwelling::updateGuards() const
 	{
 	{
 		for (auto creatureEntry : creatures)
 		for (auto creatureEntry : creatures)
 		{
 		{
-			const CCreature * crea = VLC->creh->objects[creatureEntry.second.at(0)];
+			const CCreature * crea = creatureEntry.second.at(0).toCreature();
 			SlotID slot = getSlotFor(crea->getId());
 			SlotID slot = getSlotFor(crea->getId());
 
 
 			if (hasStackAtSlot(slot)) //stack already exists, overwrite it
 			if (hasStackAtSlot(slot)) //stack already exists, overwrite it
@@ -425,10 +488,7 @@ void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 
 
 void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
 void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
 {
 {
-	if(!handler.saving)
-		initRandomObjectInfo();
-
-	switch (ID)
+	switch (ID.toEnum())
 	{
 	{
 	case Obj::WAR_MACHINE_FACTORY:
 	case Obj::WAR_MACHINE_FACTORY:
 	case Obj::REFUGEE_CAMP:
 	case Obj::REFUGEE_CAMP:
@@ -437,8 +497,10 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
 	case Obj::RANDOM_DWELLING:
 	case Obj::RANDOM_DWELLING:
 	case Obj::RANDOM_DWELLING_LVL:
 	case Obj::RANDOM_DWELLING_LVL:
 	case Obj::RANDOM_DWELLING_FACTION:
 	case Obj::RANDOM_DWELLING_FACTION:
-		info->serializeJson(handler);
-		//fall through
+		if (!handler.saving)
+			randomizationInfo = CGDwellingRandomizationInfo();
+		randomizationInfo->serializeJson(handler);
+		[[fallthrough]];
 	default:
 	default:
 		serializeJsonOwner(handler);
 		serializeJsonOwner(handler);
 		break;
 		break;

+ 11 - 34
lib/mapObjects/CGDwelling.h

@@ -16,62 +16,39 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 class CGDwelling;
 class CGDwelling;
 
 
-class DLL_LINKAGE CSpecObjInfo
+class DLL_LINKAGE CGDwellingRandomizationInfo
 {
 {
 public:
 public:
-	CSpecObjInfo();
-	virtual ~CSpecObjInfo() = default;
-
-	virtual void serializeJson(JsonSerializeFormat & handler) = 0;
-
-	const CGDwelling * owner;
-};
-
-class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo
-{
-public:
-	bool asCastle = false;
-	ui32 identifier = 0;//h3m internal identifier
-
-	std::vector<bool> allowedFactions;
+	std::set<FactionID> allowedFactions;
 
 
 	std::string instanceId;//vcmi map instance identifier
 	std::string instanceId;//vcmi map instance identifier
-	void serializeJson(JsonSerializeFormat & handler) override;
-};
+	int32_t identifier = 0;//h3m internal identifier
 
 
-class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo
-{
-public:
-	ui8 minLevel = 1;
-	ui8 maxLevel = 7; //minimal and maximal level of creature in dwelling: <1, 7>
-
-	void serializeJson(JsonSerializeFormat & handler) override;
-};
+	uint8_t minLevel = 1;
+	uint8_t maxLevel = 7; //minimal and maximal level of creature in dwelling: <1, 7>
 
 
-class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo
-{
-public:
-	CCreGenLeveledCastleInfo() = default;
-	void serializeJson(JsonSerializeFormat & handler) override;
+	void serializeJson(JsonSerializeFormat & handler);
 };
 };
 
 
-
 class DLL_LINKAGE CGDwelling : public CArmedInstance
 class DLL_LINKAGE CGDwelling : public CArmedInstance
 {
 {
 public:
 public:
 	typedef std::vector<std::pair<ui32, std::vector<CreatureID> > > TCreaturesSet;
 	typedef std::vector<std::pair<ui32, std::vector<CreatureID> > > TCreaturesSet;
 
 
-	CSpecObjInfo * info; //random dwelling options; not serialized
+	std::optional<CGDwellingRandomizationInfo> randomizationInfo; //random dwelling options; not serialized
 	TCreaturesSet creatures; //creatures[level] -> <vector of alternative ids (base creature and upgrades, creatures amount>
 	TCreaturesSet creatures; //creatures[level] -> <vector of alternative ids (base creature and upgrades, creatures amount>
 
 
 	CGDwelling();
 	CGDwelling();
 	~CGDwelling() override;
 	~CGDwelling() override;
 
 
-	void initRandomObjectInfo();
 protected:
 protected:
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 
 private:
 private:
+	FactionID randomizeFaction(CRandomGenerator & rand);
+	int randomizeLevel(CRandomGenerator & rand);
+
+	void pickRandomObject(CRandomGenerator & rand) override;
 	void initObj(CRandomGenerator & rand) override;
 	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void newTurn(CRandomGenerator & rand) const override;
 	void newTurn(CRandomGenerator & rand) const override;

+ 50 - 29
lib/mapObjects/CGHeroInstance.cpp

@@ -218,7 +218,10 @@ bool CGHeroInstance::canLearnSkill(const SecondarySkill & which) const
 	if (getSecSkillLevel(which) > 0)
 	if (getSecSkillLevel(which) > 0)
 		return false;
 		return false;
 
 
-	if (type->heroClass->secSkillProbability[which] == 0)
+	if (type->heroClass->secSkillProbability.count(which) == 0)
+		return false;
+
+	if (type->heroClass->secSkillProbability.at(which) == 0)
 		return false;
 		return false;
 
 
 	return true;
 	return true;
@@ -285,27 +288,28 @@ PlayerColor CGHeroInstance::getOwner() const
 	return tempOwner;
 	return tempOwner;
 }
 }
 
 
-void CGHeroInstance::initHero(CRandomGenerator & rand, const HeroTypeID & SUBID)
+HeroTypeID CGHeroInstance::getHeroType() const
 {
 {
-	subID = SUBID.getNum();
-	initHero(rand);
+	return HeroTypeID(getObjTypeIndex().getNum());
 }
 }
 
 
-void CGHeroInstance::setType(si32 ID, si32 subID)
+void CGHeroInstance::setHeroType(HeroTypeID heroType)
 {
 {
-	assert(ID == Obj::HERO); // just in case
-	type = VLC->heroh->objects[subID];
+	assert(type == nullptr);
+	subID = heroType;
+}
 
 
-	CGObjectInstance::setType(ID, type->heroClass->getIndex()); // to find object handler we must use heroClass->id
-	this->subID = subID; // after setType subID used to store unique hero identify id. Check issue 2277 for details
-	randomizeArmy(type->heroClass->faction);
+void CGHeroInstance::initHero(CRandomGenerator & rand, const HeroTypeID & SUBID)
+{
+	subID = SUBID.getNum();
+	initHero(rand);
 }
 }
 
 
 void CGHeroInstance::initHero(CRandomGenerator & rand)
 void CGHeroInstance::initHero(CRandomGenerator & rand)
 {
 {
 	assert(validTypes(true));
 	assert(validTypes(true));
 	if(!type)
 	if(!type)
-		type = VLC->heroh->objects[subID];
+		type = VLC->heroh->objects[getHeroType().getNum()];
 
 
 	if (ID == Obj::HERO)
 	if (ID == Obj::HERO)
 		appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
 		appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
@@ -566,18 +570,35 @@ void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
 	wisdomCounter = 1;
 	wisdomCounter = 1;
 }
 }
 
 
-void CGHeroInstance::initObj(CRandomGenerator & rand)
+void CGHeroInstance::pickRandomObject(CRandomGenerator & rand)
 {
 {
-	if(!type)
-		initHero(rand); //TODO: set up everything for prison before specialties are configured
+	assert(ID == Obj::HERO || ID == Obj::PRISON || ID == Obj::RANDOM_HERO);
 
 
-	if (ID != Obj::PRISON)
+	if (ID == Obj::RANDOM_HERO)
 	{
 	{
-		auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId();
-		auto customApp = VLC->objtypeh->getHandlerFor(ID, type->heroClass->getIndex())->getOverride(terrain, this);
-		if (customApp)
-			appearance = customApp;
+		ID = Obj::HERO;
+		subID = cb->gameState()->pickNextHeroType(getOwner());
+		type = VLC->heroh->objects[subID];
+		randomizeArmy(type->heroClass->faction);
 	}
 	}
+	else
+		type = VLC->heroh->objects[subID];
+
+	auto oldSubID = subID;
+
+	// to find object handler we must use heroClass->id
+	// after setType subID used to store unique hero identify id. Check issue 2277 for details
+	if (ID != Obj::PRISON)
+		setType(ID, type->heroClass->getIndex());
+	else
+		setType(ID, 0);
+
+	this->subID = oldSubID;
+}
+
+void CGHeroInstance::initObj(CRandomGenerator & rand)
+{
+
 }
 }
 
 
 void CGHeroInstance::recreateSecondarySkillsBonuses()
 void CGHeroInstance::recreateSecondarySkillsBonuses()
@@ -926,7 +947,7 @@ void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedSta
 	iw.type = EInfoWindowMode::AUTO;
 	iw.type = EInfoWindowMode::AUTO;
 	iw.soundID = soundBase::pickup01 + rand.nextInt(6);
 	iw.soundID = soundBase::pickup01 + rand.nextInt(6);
 	iw.player = tempOwner;
 	iw.player = tempOwner;
-	iw.components.emplace_back(raisedStack);
+	iw.components.emplace_back(ComponentType::CREATURE, raisedStack.getId(), raisedStack.count);
 
 
 	if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural)
 	if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural)
 	{
 	{
@@ -1050,7 +1071,7 @@ HeroTypeID CGHeroInstance::getPortraitSource() const
 	if (customPortraitSource.isValid())
 	if (customPortraitSource.isValid())
 		return customPortraitSource;
 		return customPortraitSource;
 	else
 	else
-		return HeroTypeID(subID);
+		return getHeroType();
 }
 }
 
 
 int32_t CGHeroInstance::getIconIndex() const
 int32_t CGHeroInstance::getIconIndex() const
@@ -1092,7 +1113,7 @@ std::string CGHeroInstance::getBiographyTextID() const
 
 
 CGHeroInstance::ArtPlacementMap CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance * art)
 CGHeroInstance::ArtPlacementMap CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance * art)
 {
 {
-	assert(art->artType->canBePutAt(this, pos));
+	assert(art->canBePutAt(this, pos));
 
 
 	if(ArtifactUtils::isSlotEquipment(pos))
 	if(ArtifactUtils::isSlotEquipment(pos))
 		attachTo(*art);
 		attachTo(*art);
@@ -1135,7 +1156,7 @@ void CGHeroInstance::removeSpellbook()
 
 
 	if(hasSpellbook())
 	if(hasSpellbook())
 	{
 	{
-		ArtifactLocation(this, ArtifactPosition(ArtifactPosition::SPELLBOOK)).removeArtifact();
+		getArt(ArtifactPosition::SPELLBOOK)->removeFrom(*this, ArtifactPosition::SPELLBOOK);
 	}
 	}
 }
 }
 
 
@@ -1502,7 +1523,7 @@ std::string CGHeroInstance::getHeroTypeName() const
 		}
 		}
 		else
 		else
 		{
 		{
-			return VLC->heroh->objects[subID]->getJsonKey();
+			return VLC->heroh->objects[getHeroType()]->getJsonKey();
 		}
 		}
 	}
 	}
 	return "";
 	return "";
@@ -1512,7 +1533,7 @@ void CGHeroInstance::afterAddToMap(CMap * map)
 {
 {
 	auto existingHero = std::find_if(map->objects.begin(), map->objects.end(), [&](const CGObjectInstance * o) ->bool
 	auto existingHero = std::find_if(map->objects.begin(), map->objects.end(), [&](const CGObjectInstance * o) ->bool
 		{
 		{
-			return (o->ID == Obj::HERO || o->ID == Obj::PRISON) && o->subID == subID && o != this;
+			return o && (o->ID == Obj::HERO || o->ID == Obj::PRISON) && o->subID == subID && o != this;
 		});
 		});
 
 
 	if(existingHero != map->objects.end())
 	if(existingHero != map->objects.end())
@@ -1529,14 +1550,14 @@ void CGHeroInstance::afterAddToMap(CMap * map)
 		}
 		}
 	}
 	}
 
 
-	if(ID == Obj::HERO)
+	if(ID != Obj::PRISON)
 	{		
 	{		
 		map->heroesOnMap.emplace_back(this);
 		map->heroesOnMap.emplace_back(this);
 	}
 	}
 }
 }
 void CGHeroInstance::afterRemoveFromMap(CMap* map)
 void CGHeroInstance::afterRemoveFromMap(CMap* map)
 {
 {
-	if (ID == Obj::HERO)
+	if (ID == Obj::PRISON)
 		vstd::erase_if_present(map->heroesOnMap, this);
 		vstd::erase_if_present(map->heroesOnMap, this);
 }
 }
 
 
@@ -1661,7 +1682,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
 		{
 		{
 			auto addSkill = [this](const std::string & skillId, const std::string & levelId)
 			auto addSkill = [this](const std::string & skillId, const std::string & levelId)
 			{
 			{
-				const int rawId = CSkillHandler::decodeSkill(skillId);
+				const int rawId = SecondarySkill::decode(skillId);
 				if(rawId < 0)
 				if(rawId < 0)
 				{
 				{
 					logGlobal->error("Invalid secondary skill %s", skillId);
 					logGlobal->error("Invalid secondary skill %s", skillId);
@@ -1736,7 +1757,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 			if(!appearance)
 			if(!appearance)
 			{
 			{
 				// crossoverDeserialize
 				// crossoverDeserialize
-				type = VLC->heroh->objects[subID];
+				type = VLC->heroh->objects[getHeroType()];
 				appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
 				appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
 			}
 			}
 
 

+ 3 - 1
lib/mapObjects/CGHeroInstance.h

@@ -231,7 +231,8 @@ public:
 
 
 	//////////////////////////////////////////////////////////////////////////
 	//////////////////////////////////////////////////////////////////////////
 
 
-	void setType(si32 ID, si32 subID) override;
+	HeroTypeID getHeroType() const;
+	void setHeroType(HeroTypeID type);
 
 
 	void initHero(CRandomGenerator & rand);
 	void initHero(CRandomGenerator & rand);
 	void initHero(CRandomGenerator & rand, const HeroTypeID & SUBID);
 	void initHero(CRandomGenerator & rand, const HeroTypeID & SUBID);
@@ -294,6 +295,7 @@ public:
 	void deserializationFix();
 	void deserializationFix();
 
 
 	void initObj(CRandomGenerator & rand) override;
 	void initObj(CRandomGenerator & rand) override;
+	void pickRandomObject(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	std::string getObjectName() const override;
 	std::string getObjectName() const override;
 
 

+ 1 - 1
lib/mapObjects/CGMarket.cpp

@@ -25,7 +25,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 
 void CGMarket::initObj(CRandomGenerator & rand)
 void CGMarket::initObj(CRandomGenerator & rand)
 {
 {
-	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
+	getObjectHandler()->configureObject(this, rand);
 }
 }
 
 
 void CGMarket::onHeroVisit(const CGHeroInstance * h) const
 void CGMarket::onHeroVisit(const CGHeroInstance * h) const

部分文件因为文件数量过多而无法显示