Przeglądaj źródła

Merge pull request #3125 from IvanSavenko/reduce_subid_usage

Reduce usage of CGObjectInstance::subID
Ivan Savenko 1 rok temu
rodzic
commit
0376873cb3
58 zmienionych plików z 582 dodań i 658 usunięć
  1. 1 1
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  2. 1 1
      AI/Nullkiller/Behaviors/StartupBehavior.cpp
  3. 2 5
      AI/Nullkiller/Engine/FuzzyHelper.cpp
  4. 21 10
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  5. 0 1
      AI/VCAI/AIUtility.h
  6. 1 1
      AI/VCAI/FuzzyEngines.cpp
  7. 2 9
      AI/VCAI/FuzzyHelper.cpp
  8. 2 2
      AI/VCAI/Goals/CollectRes.cpp
  9. 1 1
      AI/VCAI/Goals/GatherTroops.cpp
  10. 1 1
      AI/VCAI/Pathfinding/PathfindingManager.cpp
  11. 1 13
      AI/VCAI/ResourceManager.cpp
  12. 2 2
      AI/VCAI/VCAI.cpp
  13. 1 1
      AI/VCAI/VCAI.h
  14. 1 1
      CCallback.cpp
  15. 1 1
      client/NetPacksClient.cpp
  16. 1 5
      client/mapView/MapRendererContext.cpp
  17. 3 3
      client/windows/CCastleInterface.cpp
  18. 2 2
      client/windows/CTradeWindow.cpp
  19. 4 5
      lib/campaign/CampaignState.cpp
  20. 27 3
      lib/constants/EntityIdentifiers.h
  21. 44 281
      lib/gameState/CGameState.cpp
  22. 2 3
      lib/gameState/CGameState.h
  23. 7 7
      lib/gameState/CGameStateCampaign.cpp
  24. 3 3
      lib/gameState/TavernHeroesPool.cpp
  25. 1 1
      lib/mapObjects/CBank.cpp
  26. 49 12
      lib/mapObjects/CGCreature.cpp
  27. 2 0
      lib/mapObjects/CGCreature.h
  28. 122 60
      lib/mapObjects/CGDwelling.cpp
  29. 11 34
      lib/mapObjects/CGDwelling.h
  30. 41 23
      lib/mapObjects/CGHeroInstance.cpp
  31. 3 1
      lib/mapObjects/CGHeroInstance.h
  32. 1 1
      lib/mapObjects/CGMarket.cpp
  33. 14 4
      lib/mapObjects/CGObjectInstance.cpp
  34. 12 8
      lib/mapObjects/CGObjectInstance.h
  35. 2 2
      lib/mapObjects/CGTownBuilding.cpp
  36. 2 2
      lib/mapObjects/CGTownBuilding.h
  37. 38 14
      lib/mapObjects/CGTownInstance.cpp
  38. 2 1
      lib/mapObjects/CGTownInstance.h
  39. 0 10
      lib/mapObjects/CObjectHandler.cpp
  40. 0 11
      lib/mapObjects/CObjectHandler.h
  41. 1 1
      lib/mapObjects/CRewardableObject.cpp
  42. 3 0
      lib/mapObjects/IObjectInterface.cpp
  43. 4 2
      lib/mapObjects/IObjectInterface.h
  44. 75 15
      lib/mapObjects/MiscObjects.cpp
  45. 6 0
      lib/mapObjects/MiscObjects.h
  46. 2 2
      lib/mapping/CMap.cpp
  47. 1 1
      lib/mapping/CMap.h
  48. 19 45
      lib/mapping/MapFormatH3M.cpp
  49. 2 2
      lib/mapping/MapFormatJson.cpp
  50. 1 2
      lib/networkPacks/NetPacksLib.cpp
  51. 10 7
      lib/pathfinder/CPathfinder.cpp
  52. 6 6
      lib/rmg/RmgObject.cpp
  53. 1 1
      lib/rmg/modificators/TreasurePlacer.cpp
  54. 1 1
      lib/spells/ViewSpellInt.cpp
  55. 7 15
      mapeditor/inspector/inspector.cpp
  56. 2 2
      mapeditor/mapcontroller.cpp
  57. 3 3
      server/CGameHandler.cpp
  58. 7 7
      server/processors/HeroPoolProcessor.cpp

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

@@ -318,7 +318,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
 {
 	for(auto tdi : developmentInfos)
 	{
-		if(tdi.town->subID == alignment && tdi.town->hasBuilt(bid))
+		if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid))
 			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())
 	{
-		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::CAMPFIRE
 			|| 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
 
-	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());
 
@@ -161,10 +161,7 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
 	}
 	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:
 		return 0;

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

@@ -122,7 +122,7 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
 {
 	//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());
 	auto resources = bankInfo->getPossibleResourcesReward();
 	TResources result = TResources();
@@ -139,7 +139,7 @@ TResources getCreatureBankResources(const CGObjectInstance * target, const CGHer
 
 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());
 	auto creatures = bankInfo->getPossibleCreaturesReward();
 	uint64_t result = 0;
@@ -467,14 +467,20 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target) cons
 	switch(target->ID)
 	{
 	case Obj::MINE:
-		return target->subID == GameResID(EGameResID::GOLD)
+	{
+		auto mine = dynamic_cast<const CGMine *>(target);
+		return mine->producedResource == EGameResID::GOLD
 			? 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:
-		return target->subID == GameResID(EGameResID::GOLD)
+	{
+		auto resource = dynamic_cast<const CGResource *>(target);
+		return resource->resourceID() == EGameResID::GOLD
 			? 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:
 	{
@@ -626,12 +632,14 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
 	const int dailyIncomeMultiplier = 5;
 	const float enemyArmyEliminationGoldRewardRatio = 0.2f;
 	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)
 	{
 	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:
 		return 1500;
 	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);
 	case Obj::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::WINDMILL:
 		return 100;
@@ -1005,7 +1016,7 @@ public:
 
 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;
 
 	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 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 ALLOWED_ROAMING_HEROES = 8;
 

+ 1 - 1
AI/VCAI/FuzzyEngines.cpp

@@ -406,7 +406,7 @@ float VisitObjEngine::evaluate(Goals::VisitObj & goal)
 	else
 	{
 		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);

+ 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
 
-	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());
 
@@ -324,15 +324,8 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
 	case Obj::DRAGON_UTOPIA:
 	case Obj::SHIPWRECK: //shipwreck
 	case Obj::DERELICT_SHIP: //derelict ship
-							 //	case Obj::PYRAMID:
-		return estimateBankDanger(dynamic_cast<const CBank *>(obj));
 	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:
 		return 0;
 	}

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

@@ -43,10 +43,10 @@ TGoalVec CollectRes::getAllPossibleSubgoals()
 			return resID == GameResID(EGameResID::GOLD);
 			break;
 		case Obj::RESOURCE:
-			return obj->subID == resID;
+			return dynamic_cast<const CGResource*>(obj)->resourceID() == GameResID(resID);
 			break;
 		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
 			break;
 		case Obj::CAMPFIRE:

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

@@ -88,7 +88,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 		}
 
 		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>>
 			{

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

@@ -190,7 +190,7 @@ Goals::TSubgoal PathfindingManager::clearWayTo(HeroPtr hero, int3 firstTileToGet
 	if(isBlockedBorderGate(firstTileToGet))
 	{
 		//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);

+ 1 - 13
AI/VCAI/ResourceManager.cpp

@@ -59,19 +59,7 @@ TResources ResourceManager::estimateIncome() const
 		if (obj->ID == Obj::MINE)
 		{
 			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();
 		}
 	}
 

+ 2 - 2
AI/VCAI/VCAI.cpp

@@ -1760,11 +1760,11 @@ void VCAI::addVisitableObj(const CGObjectInstance * obj)
 		CGTeleport::addToChannel(knownTeleportChannels, teleportObj);
 }
 
-const CGObjectInstance * VCAI::lookForArt(int aid) const
+const CGObjectInstance * VCAI::lookForArt(ArtifactID aid) const
 {
 	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;
 	}
 

+ 1 - 1
AI/VCAI/VCAI.h

@@ -251,7 +251,7 @@ public:
 	void retrieveVisitableObjs();
 	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;
 	HeroPtr getHeroWithGrail() const;
 

+ 1 - 1
CCallback.cpp

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

+ 1 - 1
client/NetPacksClient.cpp

@@ -604,7 +604,7 @@ void ApplyClientNetPackVisitor::visitSetHeroesInTown(SetHeroesInTown & pack)
 void ApplyClientNetPackVisitor::visitHeroRecruited(HeroRecruited & pack)
 {
 	CGHeroInstance *h = gs.map->heroesOnMap.back();
-	if(h->subID != pack.hid)
+	if(h->getHeroType() != pack.hid)
 	{
 		logNetwork->error("Something wrong with hero recruited!");
 	}

+ 1 - 5
client/mapView/MapRendererContext.cpp

@@ -427,11 +427,7 @@ size_t MapRendererWorldViewContext::overlayImageIndex(const int3 & coordinates)
 		if(!object->visitableAt(coordinates.x, coordinates.y))
 			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);
 

+ 3 - 3
client/windows/CCastleInterface.cpp

@@ -920,7 +920,7 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow()
 
 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>(CComponent::building,town->getFaction(),building));
 	std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated();
 	std::string hasNotProduced;
 	std::string hasProduced;
@@ -969,9 +969,9 @@ void CCastleBuildings::enterMagesGuild()
 	{
 		const StartInfo *si = LOCPLINT->cb->getStartInfo();
 		// 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",
-			(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..."
 			LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[736]);

+ 2 - 2
client/windows/CTradeWindow.cpp

@@ -667,10 +667,10 @@ CMarketplaceWindow::CMarketplaceWindow(const IMarket * Market, const CGHeroInsta
 			title = (*CGI->townh)[ETownType::STRONGHOLD]->town->buildings[BuildingID::FREELANCERS_GUILD]->getNameTranslated();
 			break;
 		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;
 		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
 			// this is workaround for bug in H3 files where this slot for ragdoll on this screen is missing

+ 4 - 5
lib/campaign/CampaignState.cpp

@@ -274,17 +274,16 @@ void CampaignState::setCurrentMapAsConquered(std::vector<CGHeroInstance *> heroe
 
 	for (auto * hero : heroes)
 	{
-		HeroTypeID heroType(hero->subID);
 		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
 		{
-			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);
 		}
 	}

+ 27 - 3
lib/constants/EntityIdentifiers.h

@@ -407,7 +407,7 @@ public:
 	static std::string entityType();
 };
 
-class ObjBase : public IdentifierBase
+class MapObjectBaseID : public IdentifierBase
 {
 public:
 	enum Type
@@ -552,15 +552,38 @@ public:
 	};
 };
 
-class DLL_LINKAGE Obj : public IdentifierWithEnum<Obj, ObjBase>
+class DLL_LINKAGE MapObjectID : public IdentifierWithEnum<MapObjectID, MapObjectBaseID>
 {
 public:
-	using IdentifierWithEnum<Obj, ObjBase>::IdentifierWithEnum;
+	using IdentifierWithEnum<MapObjectID, MapObjectBaseID>::IdentifierWithEnum;
 
 	static std::string encode(int32_t index);
 	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>
 {
 public:
@@ -966,6 +989,7 @@ public:
 
 // Deprecated
 // TODO: remove
+using Obj = MapObjectID;
 using ETownType = FactionID;
 using EGameResID = GameResID;
 using River = RiverId;

+ 44 - 281
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)
 {
 	const PlayerSettings &ps = scenarioOps->getIthPlayersSettings(owner);
@@ -153,223 +125,6 @@ HeroTypeID CGameState::pickUnusedHeroTypeRandomly(const PlayerColor & owner)
 	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 temp;
@@ -764,20 +519,21 @@ void CGameState::initRandomFactionsForPlayers()
 void CGameState::randomizeMapObjects()
 {
 	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
-		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;
 				}
 			}
@@ -810,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, VLC->heroh->objects[heroTypeId]->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();
 	map->getEditManager()->insertObject(hero);
 }
@@ -866,7 +630,7 @@ void CGameState::initHeroes()
 
 		hero->initHero(getRandomGenerator());
 		getPlayerState(hero->getOwner())->heroes.push_back(hero);
-		map->allHeroes[hero->type->getIndex()] = hero;
+		map->allHeroes[hero->getHeroType()] = hero;
 	}
 
 	// generate boats for all heroes on water
@@ -880,8 +644,6 @@ void CGameState::initHeroes()
 			CGBoat * boat = dynamic_cast<CGBoat*>(handler->create());
 			handler->configureObject(boat, gs->getRandomGenerator());
 
-			boat->ID = Obj::BOAT;
-			boat->subID = hero->getBoatType().getNum();
 			boat->pos = hero->pos;
 			boat->appearance = handler->getTemplates().front();
 			boat->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
@@ -896,19 +658,23 @@ void CGameState::initHeroes()
 	for(auto obj : map->objects) //prisons
 	{
 		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()] = hero;
+		}
 	}
 
 	std::set<HeroTypeID> heroesToCreate = getUnusedAllowedHeroes(); //ids of heroes to be created and put into the pool
 	for(auto ph : map->predefinedHeroes)
 	{
-		if(!vstd::contains(heroesToCreate, HeroTypeID(ph->subID)))
+		if(!vstd::contains(heroesToCreate, ph->getHeroType()))
 			continue;
 		ph->initHero(getRandomGenerator());
 		heroesPool->addHeroToPool(ph);
 		heroesToCreate.erase(ph->type->getId());
 
-		map->allHeroes[ph->subID] = ph;
+		map->allHeroes[ph->getHeroType()] = ph;
 	}
 
 	for(const HeroTypeID & htype : heroesToCreate) //all not used allowed heroes go with default state into the pool
@@ -1019,10 +785,8 @@ void CGameState::initTowns()
 	for (auto & elem : map->towns)
 	{
 		CGTownInstance * vti =(elem);
-		if(!vti->town)
-		{
-			vti->town = (*VLC->townh)[vti->subID]->town;
-		}
+		assert(vti->town);
+
 		if(vti->getNameTranslated().empty())
 		{
 			size_t nameID = getRandomGenerator().nextInt(vti->getTown()->getRandomNamesCount() - 1);
@@ -2132,12 +1896,15 @@ std::set<HeroTypeID> CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow
 		if(hero->type)
 			ret -= hero->type->getId();
 		else
-			ret -= HeroTypeID(hero->subID);
+			ret -= hero->getHeroType();
 	}
 
 	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;
 }
@@ -2149,23 +1916,19 @@ bool CGameState::isUsedHero(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
 	{
-		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;

+ 2 - 3
lib/gameState/CGameState.h

@@ -115,6 +115,8 @@ public:
 	void updateEntity(Metatype metatype, int32_t index, const JsonNode & data) override;
 
 	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);
 	BattleField battleGetBattlefieldType(int3 tile, CRandomGenerator & rand);
@@ -187,7 +189,6 @@ private:
 	void initGrailPosition();
 	void initRandomFactionsForPlayers();
 	void randomizeMapObjects();
-	void randomizeObject(CGObjectInstance *cur);
 	void initPlayerStates();
 	void placeStartingHeroes();
 	void placeStartingHero(const PlayerColor & playerColor, const HeroTypeID & heroTypeId, int3 townPos);
@@ -214,9 +215,7 @@ private:
 	CGHeroInstance * getUsedHero(const HeroTypeID & hid) const;
 	bool isUsedHero(const HeroTypeID & hid) const; //looks in heroes and prisons
 	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 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;
 
 	// ---- data -----

+ 7 - 7
lib/gameState/CGameStateCampaign.cpp

@@ -213,7 +213,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 	std::set<HeroTypeID> heroesToRemove = campaignState->getReservedHeroes();
 
 	for(auto & campaignHeroReplacement : campaignHeroReplacements)
-		heroesToRemove.insert(HeroTypeID(campaignHeroReplacement.hero->subID));
+		heroesToRemove.insert(campaignHeroReplacement.hero->getHeroType());
 
 	for(auto & heroID : heroesToRemove)
 	{
@@ -256,7 +256,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 			assert(0); // should not happen
 		}
 
-		hero->subID = heroTypeId;
+		hero->setHeroType(heroTypeId);
 		gameState->map->getEditManager()->insertObject(hero);
 	}
 }
@@ -339,7 +339,7 @@ void CGameStateCampaign::replaceHeroesPlaceholders(const std::vector<CampaignHer
 		if(heroPlaceholder->tempOwner.isValidPlayer())
 			heroToPlace->tempOwner = heroPlaceholder->tempOwner;
 		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();
 
 		gameState->map->removeBlockVisTiles(heroPlaceholder, true);
@@ -396,7 +396,7 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
 
 		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);
 	}
@@ -422,7 +422,7 @@ std::vector<CampaignHeroReplacement> CGameStateCampaign::generateCampaignHeroesT
 			CGHeroInstance * hero = CampaignState::crossoverDeserialize(*nodeListIter, gameState->map);
 			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);
 		}
@@ -468,7 +468,7 @@ void CGameStateCampaign::initHeroes()
 		{
 			for (auto & heroe : heroes)
 			{
-				if (heroe->subID == chosenBonus->info1)
+				if (heroe->getHeroType().getNum() == chosenBonus->info1)
 				{
 					giveCampaignBonusToHero(heroe);
 					break;
@@ -557,7 +557,7 @@ void CGameStateCampaign::initTowns()
 		if(gameState->scenarioOps->campState->formatVCMI())
 			newBuilding = BuildingID(chosenBonus->info1);
 		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
 		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;
 	for(const auto & slot : currentTavern)
-		pool.erase(HeroTypeID(slot.hero->subID));
+		pool.erase(slot.hero->getHeroType());
 
 	return pool;
 }
@@ -34,7 +34,7 @@ TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const
 {
 	for (auto const & slot : currentTavern)
 	{
-		if (HeroTypeID(slot.hero->subID) == hero)
+		if (slot.hero->getHeroType() == hero)
 			return slot.role;
 	}
 	return TavernSlotRole::NONE;
@@ -132,7 +132,7 @@ void TavernHeroesPool::onNewDay()
 
 void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero)
 {
-	heroesPool[HeroTypeID(hero->subID)] = hero;
+	heroesPool[hero->getHeroType()] = hero;
 }
 
 void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set<PlayerColor> mask)

+ 1 - 1
lib/mapObjects/CBank.cpp

@@ -46,7 +46,7 @@ void CBank::initObj(CRandomGenerator & rand)
 {
 	daycounter = 0;
 	resetDuration = 0;
-	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
+	getObjectHandler()->configureObject(this, rand);
 }
 
 bool CBank::isCoastVisitable() const

+ 49 - 12
lib/mapObjects/CGCreature.cpp

@@ -28,7 +28,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const
 	if(stacks.empty())
 	{
 		//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";
 	}
 
@@ -41,7 +41,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const
 	else
 		ms.appendLocalString(EMetaText::ARRAY_TXT, quantityTextIndex);
 	ms.appendRawString(" ");
-	ms.appendLocalString(EMetaText::CRE_PL_NAMES,subID);
+	ms.appendLocalString(EMetaText::CRE_PL_NAMES, getCreature());
 	hoverName = ms.toString();
 	return hoverName;
 }
@@ -54,7 +54,7 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 		MetaString ms;
 		ms.appendNumber(stacks.begin()->second->count);
 		ms.appendRawString(" ");
-		ms.appendLocalString(EMetaText::CRE_PL_NAMES,subID);
+		ms.appendLocalString(EMetaText::CRE_PL_NAMES, getCreature());
 
 		ms.appendRawString("\n\n");
 
@@ -132,7 +132,7 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
 			BlockingDialog ynd(true,false);
 			ynd.player = h->tempOwner;
 			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);
 			break;
 		}
@@ -146,13 +146,50 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
 			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(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);
 			cb->showBlockingDialog(&ynd);
 			break;
 		}
 	}
+}
+
+CreatureID CGCreature::getCreature() const
+{
+	return CreatureID(getObjTypeIndex().getNum());
+}
 
+void CGCreature::pickRandomObject(CRandomGenerator & rand)
+{
+	switch(ID)
+	{
+		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)
@@ -177,16 +214,16 @@ void CGCreature::initObj(CRandomGenerator & rand)
 		break;
 	}
 
-	stacks[SlotID(0)]->setType(CreatureID(subID));
+	stacks[SlotID(0)]->setType(getCreature());
 	TQuantity &amount = stacks[SlotID(0)]->count;
-	CCreature &c = *VLC->creh->objects[subID];
+	const Creature * c = VLC->creatures()->getById(getCreature());
 	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
 		{
-			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;
 		}
 	}
@@ -252,7 +289,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 		powerFactor = -3;
 
 	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->upgrades.begin(), myCreature->upgrades.end()); //our upgrades
 
@@ -290,7 +327,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 			return JOIN_FOR_FREE;
 
 		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
@@ -404,7 +441,7 @@ void CGCreature::flee( const CGHeroInstance * h ) const
 	BlockingDialog ynd(true,false);
 	ynd.player = h->tempOwner;
 	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);
 }
 

+ 2 - 0
lib/mapObjects/CGCreature.h

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

+ 122 - 60
lib/mapObjects/CGDwelling.cpp

@@ -11,8 +11,10 @@
 #include "StdInc.h"
 #include "CGDwelling.h"
 #include "../serializer/JsonSerializeFormat.h"
+#include "../mapping/CMap.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
+#include "../mapObjectConstructors/DwellingInstanceConstructor.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../networkPacks/StackLocation.h"
 #include "../networkPacks/PacksForClient.h"
@@ -26,66 +28,144 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-CSpecObjInfo::CSpecObjInfo():
-	owner(nullptr)
-{
-
-}
-
-void CCreGenAsCastleInfo::serializeJson(JsonSerializeFormat & handler)
+void CGDwellingRandomizationInfo::serializeJson(JsonSerializeFormat & handler)
 {
 	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)
 	{
-		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 < 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(VLC->creh->objects[cid]))
+					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)
@@ -95,7 +175,7 @@ void CGDwelling::initObj(CRandomGenerator & rand)
 	case Obj::CREATURE_GENERATOR1:
 	case Obj::CREATURE_GENERATOR4:
 		{
-			VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
+			getObjectHandler()->configureObject(this, rand);
 
 			if (getOwner() != PlayerColor::NEUTRAL)
 				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)
 {
 	switch (what)
@@ -425,9 +488,6 @@ void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer)
 
 void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
 {
-	if(!handler.saving)
-		initRandomObjectInfo();
-
 	switch (ID)
 	{
 	case Obj::WAR_MACHINE_FACTORY:
@@ -437,8 +497,10 @@ void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler)
 	case Obj::RANDOM_DWELLING:
 	case Obj::RANDOM_DWELLING_LVL:
 	case Obj::RANDOM_DWELLING_FACTION:
-		info->serializeJson(handler);
-		//fall through
+		if (!handler.saving)
+			randomizationInfo = CGDwellingRandomizationInfo();
+		randomizationInfo->serializeJson(handler);
+		[[fallthrough]];
 	default:
 		serializeJsonOwner(handler);
 		break;

+ 11 - 34
lib/mapObjects/CGDwelling.h

@@ -16,62 +16,39 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 class CGDwelling;
 
-class DLL_LINKAGE CSpecObjInfo
+class DLL_LINKAGE CGDwellingRandomizationInfo
 {
 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
-	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
 {
 public:
 	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>
 
 	CGDwelling();
 	~CGDwelling() override;
 
-	void initRandomObjectInfo();
 protected:
 	void serializeJsonOptions(JsonSerializeFormat & handler) override;
 
 private:
+	FactionID randomizeFaction(CRandomGenerator & rand);
+	int randomizeLevel(CRandomGenerator & rand);
+
+	void pickRandomObject(CRandomGenerator & rand) override;
 	void initObj(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void newTurn(CRandomGenerator & rand) const override;

+ 41 - 23
lib/mapObjects/CGHeroInstance.cpp

@@ -285,27 +285,28 @@ PlayerColor CGHeroInstance::getOwner() const
 	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)
 {
 	assert(validTypes(true));
 	if(!type)
-		type = VLC->heroh->objects[subID];
+		type = VLC->heroh->objects[getHeroType().getNum()];
 
 	if (ID == Obj::HERO)
 		appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
@@ -566,18 +567,35 @@ void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter()
 	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()
@@ -1050,7 +1068,7 @@ HeroTypeID CGHeroInstance::getPortraitSource() const
 	if (customPortraitSource.isValid())
 		return customPortraitSource;
 	else
-		return HeroTypeID(subID);
+		return getHeroType();
 }
 
 int32_t CGHeroInstance::getIconIndex() const
@@ -1502,7 +1520,7 @@ std::string CGHeroInstance::getHeroTypeName() const
 		}
 		else
 		{
-			return VLC->heroh->objects[subID]->getJsonKey();
+			return VLC->heroh->objects[getHeroType()]->getJsonKey();
 		}
 	}
 	return "";
@@ -1529,14 +1547,14 @@ void CGHeroInstance::afterAddToMap(CMap * map)
 		}
 	}
 
-	if(ID == Obj::HERO)
+	if(ID != Obj::PRISON)
 	{		
 		map->heroesOnMap.emplace_back(this);
 	}
 }
 void CGHeroInstance::afterRemoveFromMap(CMap* map)
 {
-	if (ID == Obj::HERO)
+	if (ID == Obj::PRISON)
 		vstd::erase_if_present(map->heroesOnMap, this);
 }
 
@@ -1736,7 +1754,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 			if(!appearance)
 			{
 				// crossoverDeserialize
-				type = VLC->heroh->objects[subID];
+				type = VLC->heroh->objects[getHeroType()];
 				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, const HeroTypeID & SUBID);
@@ -294,6 +295,7 @@ public:
 	void deserializationFix();
 
 	void initObj(CRandomGenerator & rand) override;
+	void pickRandomObject(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) 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)
 {
-	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
+	getObjectHandler()->configureObject(this, rand);
 }
 
 void CGMarket::onHeroVisit(const CGHeroInstance * h) const

+ 14 - 4
lib/mapObjects/CGObjectInstance.cpp

@@ -40,12 +40,12 @@ CGObjectInstance::CGObjectInstance():
 //must be instantiated in .cpp file for access to complete types of all member fields
 CGObjectInstance::~CGObjectInstance() = default;
 
-int32_t CGObjectInstance::getObjGroupIndex() const
+MapObjectID CGObjectInstance::getObjGroupIndex() const
 {
-	return ID.num;
+	return ID;
 }
 
-int32_t CGObjectInstance::getObjTypeIndex() const
+MapObjectSubID CGObjectInstance::getObjTypeIndex() const
 {
 	return subID;
 }
@@ -122,7 +122,7 @@ std::set<int3> CGObjectInstance::getBlockedOffsets() const
 	return appearance->getBlockedOffsets();
 }
 
-void CGObjectInstance::setType(si32 newID, si32 newSubID)
+void CGObjectInstance::setType(MapObjectID newID, MapObjectSubID newSubID)
 {
 	auto position = visitablePos();
 	auto oldOffset = getVisitableOffset();
@@ -166,6 +166,11 @@ void CGObjectInstance::setType(si32 newID, si32 newSubID)
 	cb->gameState()->map->addBlockVisTiles(this);
 }
 
+void CGObjectInstance::pickRandomObject(CRandomGenerator & rand)
+{
+	// no-op
+}
+
 void CGObjectInstance::initObj(CRandomGenerator & rand)
 {
 	switch(ID)
@@ -197,6 +202,11 @@ void CGObjectInstance::setProperty( ui8 what, ui32 val )
 	}
 }
 
+TObjectTypeHandler CGObjectInstance::getObjectHandler() const
+{
+	return VLC->objtypeh->getHandlerFor(ID, subID);
+}
+
 void CGObjectInstance::setPropertyDer( ui8 what, ui32 val )
 {}
 

+ 12 - 8
lib/mapObjects/CGObjectInstance.h

@@ -21,6 +21,8 @@ struct Component;
 class JsonSerializeFormat;
 class ObjectTemplate;
 class CMap;
+class AObjectTypeHandler;
+using TObjectTypeHandler = std::shared_ptr<AObjectTypeHandler>;
 
 class DLL_LINKAGE CGObjectInstance : public IObjectInterface
 {
@@ -28,9 +30,9 @@ public:
 	/// Position of bottom-right corner of object on map
 	int3 pos;
 	/// Type of object, e.g. town, hero, creature.
-	Obj ID;
+	MapObjectID ID;
 	/// Subtype of object, depends on type
-	si32 subID;
+	MapObjectSubID subID;
 	/// Current owner of an object (when below PLAYER_LIMIT)
 	PlayerColor tempOwner;
 	/// Index of object in map's list of objects
@@ -45,8 +47,8 @@ public:
 	CGObjectInstance(); //TODO: remove constructor
 	~CGObjectInstance() override;
 
-	int32_t getObjGroupIndex() const override;
-	int32_t getObjTypeIndex() const override;
+	MapObjectID getObjGroupIndex() const override;
+	MapObjectSubID getObjTypeIndex() const override;
 
 	/// "center" tile from which the sight distance is calculated
 	int3 getSightCenter() const;
@@ -94,6 +96,8 @@ public:
 	std::optional<AudioPath> getVisitSound() const;
 	std::optional<AudioPath> getRemovalSound() const;
 
+	TObjectTypeHandler getObjectHandler() const;
+
 	/** VIRTUAL METHODS **/
 
 	/// Returns true if player can pass through visitable tiles of this object
@@ -102,10 +106,6 @@ public:
 	virtual int getSightRadius() const;
 	/// returns (x,y,0) offset to a visitable tile of object
 	virtual int3 getVisitableOffset() const;
-	/// Called mostly during map randomization to turn random object into a regular one (e.g. "Random Monster" into "Pikeman")
-	virtual void setType(si32 ID, si32 subID);
-
-	/// returns text visible in status bar with specific hero/player active.
 
 	/// Returns generic name of object, without any player-specific info
 	virtual std::string getObjectName() const;
@@ -124,6 +124,7 @@ public:
 	/** OVERRIDES OF IObjectInterface **/
 
 	void initObj(CRandomGenerator & rand) override;
+	void pickRandomObject(CRandomGenerator & rand) override;
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	/// method for synchronous update. Note: For new properties classes should override setPropertyDer instead
 	void setProperty(ui8 what, ui32 val) final;
@@ -155,6 +156,9 @@ protected:
 	/// virtual method that allows synchronously update object state on server and all clients
 	virtual void setPropertyDer(ui8 what, ui32 val);
 
+	/// Called mostly during map randomization to turn random object into a regular one (e.g. "Random Monster" into "Pikeman")
+	void setType(MapObjectID ID, MapObjectSubID subID);
+
 	/// Gives dummy bonus from this object to hero. Can be used to track visited state
 	void giveDummyBonus(const ObjectInstanceID & heroID, BonusDuration::Type duration = BonusDuration::ONE_DAY) const;
 

+ 2 - 2
lib/mapObjects/CGTownBuilding.cpp

@@ -24,12 +24,12 @@ PlayerColor CGTownBuilding::getOwner() const
 	return town->getOwner();
 }
 
-int32_t CGTownBuilding::getObjGroupIndex() const
+MapObjectID CGTownBuilding::getObjGroupIndex() const
 {
 	return -1;
 }
 
-int32_t CGTownBuilding::getObjTypeIndex() const
+MapObjectSubID CGTownBuilding::getObjTypeIndex() const
 {
 	return 0;
 }

+ 2 - 2
lib/mapObjects/CGTownBuilding.h

@@ -45,8 +45,8 @@ public:
 	}
 
 	PlayerColor getOwner() const override;
-	int32_t getObjGroupIndex() const override;
-	int32_t getObjTypeIndex() const override;
+	MapObjectID getObjGroupIndex() const override;
+	MapObjectSubID getObjTypeIndex() const override;
 
 	int3 visitablePos() const override;
 	int3 getPosition() const override;

+ 38 - 14
lib/mapObjects/CGTownInstance.cpp

@@ -20,6 +20,7 @@
 #include "../gameState/CGameState.h"
 #include "../mapping/CMap.h"
 #include "../CPlayerState.h"
+#include "../StartInfo.h"
 #include "../TerrainHandler.h"
 #include "../mapObjectConstructors/AObjectTypeHandler.h"
 #include "../mapObjectConstructors/CObjectClassesHandler.h"
@@ -459,6 +460,40 @@ void CGTownInstance::deleteTownBonus(BuildingID bid)
 	delete freeIt;
 }
 
+FactionID CGTownInstance::randomizeFaction(CRandomGenerator & rand)
+{
+	if(getOwner().isValidPlayer())
+		return cb->gameState()->scenarioOps->getIthPlayersSettings(getOwner()).castle;
+
+	if(alignmentToPlayer.isValidPlayer())
+		return cb->gameState()->scenarioOps->getIthPlayersSettings(alignmentToPlayer).castle;
+
+	std::vector<FactionID> potentialPicks;
+
+	for (FactionID faction(0); faction < VLC->townh->size(); ++faction)
+		if (VLC->factions()->getById(faction)->hasTown())
+			potentialPicks.push_back(faction);
+
+	assert(!potentialPicks.empty());
+	return *RandomGeneratorUtil::nextItem(potentialPicks, rand);
+}
+
+void CGTownInstance::pickRandomObject(CRandomGenerator & rand)
+{
+	assert(ID == MapObjectID::TOWN || ID == MapObjectID::RANDOM_TOWN);
+	if (ID == MapObjectID::RANDOM_TOWN)
+	{
+		ID = MapObjectID::TOWN;
+		subID = randomizeFaction(rand);
+	}
+
+	assert(ID == Obj::TOWN); // just in case
+	setType(ID, subID);
+	town = (*VLC->townh)[subID]->town;
+	randomizeArmy(subID);
+	updateAppearance();
+}
+
 void CGTownInstance::initObj(CRandomGenerator & rand) ///initialize town structures
 {
 	blockVisit = true;
@@ -750,20 +785,11 @@ std::vector<int> CGTownInstance::availableItemsIds(EMarketMode mode) const
 		return IMarket::availableItemsIds(mode);
 }
 
-void CGTownInstance::setType(si32 ID, si32 subID)
-{
-	assert(ID == Obj::TOWN); // just in case
-	CGObjectInstance::setType(ID, subID);
-	town = (*VLC->townh)[subID]->town;
-	randomizeArmy(subID);
-	updateAppearance();
-}
-
 void CGTownInstance::updateAppearance()
 {
 	auto terrain = cb->gameState()->getTile(visitablePos())->terType->getId();
 	//FIXME: not the best way to do this
-	auto app = VLC->objtypeh->getHandlerFor(ID, subID)->getOverride(terrain, this);
+	auto app = getObjectHandler()->getOverride(terrain, this);
 	if (app)
 		appearance = app;
 }
@@ -1074,14 +1100,12 @@ void CGTownInstance::onTownCaptured(const PlayerColor & winner) const
 
 void CGTownInstance::afterAddToMap(CMap * map)
 {
-	if(ID == Obj::TOWN)
-		map->towns.emplace_back(this);
+	map->towns.emplace_back(this);
 }
 
 void CGTownInstance::afterRemoveFromMap(CMap * map)
 {
-	if (ID == Obj::TOWN)
-		vstd::erase_if_present(map->towns, this);
+	vstd::erase_if_present(map->towns, this);
 }
 
 void CGTownInstance::reset()

+ 2 - 1
lib/mapObjects/CGTownInstance.h

@@ -141,7 +141,6 @@ public:
 	bool allowsTrade(EMarketMode mode) const override;
 	std::vector<int> availableItemsIds(EMarketMode mode) const override;
 
-	void setType(si32 ID, si32 subID) override;
 	void updateAppearance();
 
 	//////////////////////////////////////////////////////////////////////////
@@ -197,6 +196,7 @@ public:
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void onHeroLeave(const CGHeroInstance * h) const override;
 	void initObj(CRandomGenerator & rand) override;
+	void pickRandomObject(CRandomGenerator & rand) override;
 	void battleFinished(const CGHeroInstance * hero, const BattleResult & result) const override;
 	std::string getObjectName() const override;
 
@@ -216,6 +216,7 @@ protected:
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 
 private:
+	FactionID randomizeFaction(CRandomGenerator & rand);
 	void setOwner(const PlayerColor & owner) const;
 	void onTownCaptured(const PlayerColor & winner) const;
 	int getDwellingBonus(const std::vector<CreatureID>& creatureIds, const std::vector<ConstTransitivePtr<CGDwelling> >& dwellings) const;

+ 0 - 10
lib/mapObjects/CObjectHandler.cpp

@@ -28,14 +28,4 @@ CObjectHandler::CObjectHandler()
 	logGlobal->trace("\t\tDone loading resource prices!");
 }
 
-CGObjectInstanceBySubIdFinder::CGObjectInstanceBySubIdFinder(CGObjectInstance * obj) : obj(obj)
-{
-
-}
-
-bool CGObjectInstanceBySubIdFinder::operator()(CGObjectInstance * obj) const
-{
-	return this->obj->subID == obj->subID;
-}
-
 VCMI_LIB_NAMESPACE_END

+ 0 - 11
lib/mapObjects/CObjectHandler.h

@@ -16,17 +16,6 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CGObjectInstance;
 class int3;
 
-/// function object which can be used to find an object with an specific sub ID
-class CGObjectInstanceBySubIdFinder
-{
-public:
-	CGObjectInstanceBySubIdFinder(CGObjectInstance * obj);
-	bool operator()(CGObjectInstance * obj) const;
-
-private:
-	CGObjectInstance * obj;
-};
-
 class DLL_LINKAGE CObjectHandler
 {
 public:

+ 1 - 1
lib/mapObjects/CRewardableObject.cpp

@@ -373,7 +373,7 @@ void CRewardableObject::newTurn(CRandomGenerator & rand) const
 
 void CRewardableObject::initObj(CRandomGenerator & rand)
 {
-	VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
+	getObjectHandler()->configureObject(this, rand);
 }
 
 CRewardableObject::CRewardableObject()

+ 3 - 0
lib/mapObjects/IObjectInterface.cpp

@@ -46,6 +46,9 @@ void IObjectInterface::newTurn(CRandomGenerator & rand) const
 void IObjectInterface::initObj(CRandomGenerator & rand)
 {}
 
+void IObjectInterface::pickRandomObject(CRandomGenerator & rand)
+{}
+
 void IObjectInterface::setProperty( ui8 what, ui32 val )
 {}
 

+ 4 - 2
lib/mapObjects/IObjectInterface.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../networkPacks/EInfoWindowMode.h"
+#include "../constants/EntityIdentifiers.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -33,8 +34,8 @@ public:
 
 	virtual ~IObjectInterface() = default;
 
-	virtual int32_t getObjGroupIndex() const = 0;
-	virtual int32_t getObjTypeIndex() const = 0;
+	virtual MapObjectID getObjGroupIndex() const = 0;
+	virtual MapObjectSubID getObjTypeIndex() const = 0;
 
 	virtual PlayerColor getOwner() const = 0;
 	virtual int3 visitablePos() const = 0;
@@ -44,6 +45,7 @@ public:
 	virtual void onHeroLeave(const CGHeroInstance * h) const;
 	virtual void newTurn(CRandomGenerator & rand) const;
 	virtual void initObj(CRandomGenerator & rand); //synchr
+	virtual void pickRandomObject(CRandomGenerator & rand);
 	virtual void setProperty(ui8 what, ui32 val);//synchr
 
 	//Called when queries created DURING HERO VISIT are resolved

+ 75 - 15
lib/mapObjects/MiscObjects.cpp

@@ -85,7 +85,7 @@ void CGMine::onHeroVisit( const CGHeroInstance * h ) const
 	{
 		BlockingDialog ynd(true,false);
 		ynd.player = h->tempOwner;
-		ynd.text.appendLocalString(EMetaText::ADVOB_TXT, subID == 7 ? 84 : 187);
+		ynd.text.appendLocalString(EMetaText::ADVOB_TXT, isAbandoned() ? 84 : 187);
 		cb->showBlockingDialog(&ynd);
 		return;
 	}
@@ -119,19 +119,27 @@ void CGMine::initObj(CRandomGenerator & rand)
 	}
 	else
 	{
-		producedResource = GameResID(subID);
+		producedResource = GameResID(getObjTypeIndex());
 	}
 	producedQuantity = defaultResProduction();
 }
 
 bool CGMine::isAbandoned() const
 {
-	return (subID >= 7);
+	return (getObjTypeIndex() >= 7);
+}
+
+ResourceSet CGMine::dailyIncome() const
+{
+	ResourceSet result;
+	result[producedResource] += defaultResProduction();
+
+	return result;
 }
 
 std::string CGMine::getObjectName() const
 {
-	return VLC->generaltexth->translate("core.minename", subID);
+	return VLC->generaltexth->translate("core.minename", getObjTypeIndex());
 }
 
 std::string CGMine::getHoverText(PlayerColor player) const
@@ -206,7 +214,7 @@ void CGMine::serializeJsonOptions(JsonSerializeFormat & handler)
 		if(handler.saving)
 		{
 			JsonNode node(JsonNode::JsonType::DATA_VECTOR);
-			for(auto const & resID : abandonedMineResources)
+			for(const auto & resID : abandonedMineResources)
 			{
 				JsonNode one(JsonNode::JsonType::DATA_STRING);
 				one.String() = GameConstants::RESOURCE_NAMES[resID];
@@ -237,9 +245,26 @@ void CGMine::serializeJsonOptions(JsonSerializeFormat & handler)
 	}
 }
 
+GameResID CGResource::resourceID() const
+{
+	return getObjTypeIndex().getNum();
+}
+
 std::string CGResource::getHoverText(PlayerColor player) const
 {
-	return VLC->generaltexth->restypes[subID];
+	return VLC->generaltexth->restypes[resourceID()];
+}
+
+void CGResource::pickRandomObject(CRandomGenerator & rand)
+{
+	assert(ID == Obj::RESOURCE || ID == Obj::RANDOM_RESOURCE);
+
+	if (ID == Obj::RANDOM_RESOURCE)
+	{
+		ID = Obj::RESOURCE;
+		subID = rand.nextInt(EGameResID::WOOD, EGameResID::GOLD);
+		setType(ID, subID);
+	}
 }
 
 void CGResource::initObj(CRandomGenerator & rand)
@@ -248,7 +273,7 @@ void CGResource::initObj(CRandomGenerator & rand)
 
 	if(amount == CGResource::RANDOM_AMOUNT)
 	{
-		switch(static_cast<EGameResID>(subID))
+		switch(resourceID())
 		{
 		case EGameResID::GOLD:
 			amount = rand.nextInt(5, 10) * 100;
@@ -285,7 +310,7 @@ void CGResource::onHeroVisit( const CGHeroInstance * h ) const
 
 void CGResource::collectRes(const PlayerColor & player) const
 {
-	cb->giveResource(player, static_cast<EGameResID>(subID), amount);
+	cb->giveResource(player, resourceID(), amount);
 	InfoWindow sii;
 	sii.player = player;
 	if(!message.empty())
@@ -297,9 +322,9 @@ void CGResource::collectRes(const PlayerColor & player) const
 	{
 		sii.type = EInfoWindowMode::INFO;
 		sii.text.appendLocalString(EMetaText::ADVOB_TXT,113);
-		sii.text.replaceLocalString(EMetaText::RES_NAMES, subID);
+		sii.text.replaceLocalString(EMetaText::RES_NAMES, resourceID());
 	}
-	sii.components.emplace_back(Component::EComponentType::RESOURCE,subID,amount,0);
+	sii.components.emplace_back(Component::EComponentType::RESOURCE, resourceID(), amount, 0);
 	sii.soundID = soundBase::pickup01 + CRandomGenerator::getDefault().nextInt(6);
 	cb->showInfoDialog(&sii);
 	cb->removeObject(this, player);
@@ -449,7 +474,7 @@ TeleportChannelID CGMonolith::findMeChannel(const std::vector<Obj> & IDs, int Su
 		if(!obj)
 			continue;
 
-		const auto * teleportObj = dynamic_cast<const CGTeleport *>(cb->getObj(obj->id));
+		const auto * teleportObj = dynamic_cast<const CGMonolith *>(cb->getObj(obj->id));
 		if(teleportObj && vstd::contains(IDs, teleportObj->ID) && teleportObj->subID == SubID)
 			return teleportObj->channel;
 	}
@@ -688,6 +713,41 @@ bool CGWhirlpool::isProtected(const CGHeroInstance * h)
 	|| (h->stacksCount() == 1 && h->Slots().begin()->second->count == 1);
 }
 
+ArtifactID CGArtifact::getArtifact() const
+{
+	if(ID == Obj::SPELL_SCROLL)
+		return ArtifactID::SPELL_SCROLL;
+	else
+		return getObjTypeIndex().getNum();
+}
+
+void CGArtifact::pickRandomObject(CRandomGenerator & rand)
+{
+	switch(ID)
+	{
+		case MapObjectID::RANDOM_ART:
+			subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR | CArtifact::ART_RELIC);
+			break;
+		case MapObjectID::RANDOM_TREASURE_ART:
+			subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_TREASURE);
+			break;
+		case MapObjectID::RANDOM_MINOR_ART:
+			subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MINOR);
+			break;
+		case MapObjectID::RANDOM_MAJOR_ART:
+			subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_MAJOR);
+			break;
+		case MapObjectID::RANDOM_RELIC_ART:
+			subID = VLC->arth->pickRandomArtifact(rand, CArtifact::ART_RELIC);
+			break;
+	}
+
+	if (ID != Obj::SPELL_SCROLL)
+		ID = MapObjectID::ARTIFACT;
+
+	setType(ID, subID);
+}
+
 void CGArtifact::initObj(CRandomGenerator & rand)
 {
 	blockVisit = true;
@@ -700,7 +760,7 @@ void CGArtifact::initObj(CRandomGenerator & rand)
 			storedArtifact = a;
 		}
 		if(!storedArtifact->artType)
-			storedArtifact->setType(VLC->arth->objects[subID]);
+			storedArtifact->setType(VLC->arth->objects[getArtifact()]);
 	}
 	if(ID == Obj::SPELL_SCROLL)
 		subID = 1;
@@ -713,7 +773,7 @@ void CGArtifact::initObj(CRandomGenerator & rand)
 
 std::string CGArtifact::getObjectName() const
 {
-	return VLC->artifacts()->getByIndex(subID)->getNameTranslated();
+	return VLC->artifacts()->getByIndex(getArtifact())->getNameTranslated();
 }
 
 void CGArtifact::onHeroVisit(const CGHeroInstance * h) const
@@ -730,11 +790,11 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const
 			{
 			case Obj::ARTIFACT:
 			{
-				iw.components.emplace_back(Component::EComponentType::ARTIFACT, subID, 0, 0);
+				iw.components.emplace_back(Component::EComponentType::ARTIFACT, getArtifact(), 0, 0);
 				if(!message.empty())
 					iw.text = message;
 				else
-					iw.text.appendLocalString(EMetaText::ART_EVNTS, subID);
+					iw.text.appendLocalString(EMetaText::ART_EVNTS, getArtifact());
 			}
 			break;
 			case Obj::SPELL_SCROLL:

+ 6 - 0
lib/mapObjects/MiscObjects.h

@@ -89,10 +89,13 @@ public:
 
 	void pick( const CGHeroInstance * h ) const;
 	void initObj(CRandomGenerator & rand) override;
+	void pickRandomObject(CRandomGenerator & rand) override;
 
 	void afterAddToMap(CMap * map) override;
 	BattleField getBattlefield() const override;
 
+	ArtifactID getArtifact() const;
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CArmedInstance&>(*this);
@@ -113,11 +116,13 @@ public:
 
 	void onHeroVisit(const CGHeroInstance * h) const override;
 	void initObj(CRandomGenerator & rand) override;
+	void pickRandomObject(CRandomGenerator & rand) override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override;
 	std::string getHoverText(PlayerColor player) const override;
 
 	void collectRes(const PlayerColor & player) const;
+	GameResID resourceID() const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
@@ -137,6 +142,7 @@ public:
 	std::set<GameResID> abandonedMineResources;
 	
 	bool isAbandoned() const;
+	ResourceSet dailyIncome() const;
 
 private:
 	void onHeroVisit(const CGHeroInstance * h) const override;

+ 2 - 2
lib/mapping/CMap.cpp

@@ -250,10 +250,10 @@ void CMap::calculateGuardingGreaturePositions()
 	}
 }
 
-CGHeroInstance * CMap::getHero(int heroID)
+CGHeroInstance * CMap::getHero(HeroTypeID heroID)
 {
 	for(auto & elem : heroesOnMap)
-		if(elem->subID == heroID)
+		if(elem->getObjTypeIndex() == heroID.getNum())
 			return elem;
 	return nullptr;
 }

+ 1 - 1
lib/mapping/CMap.h

@@ -118,7 +118,7 @@ public:
 
 	/// Gets object of specified type on requested position
 	const CGObjectInstance * getObjectiveObjectFrom(const int3 & pos, Obj type);
-	CGHeroInstance * getHero(int heroId);
+	CGHeroInstance * getHero(HeroTypeID heroId);
 
 	/// Sets the victory/loss condition objectives ??
 	void checkForObjectives();

+ 19 - 45
lib/mapping/MapFormatH3M.cpp

@@ -1322,60 +1322,34 @@ CGObjectInstance * CMapLoaderH3M::readDwellingRandom(const int3 & mapPosition, s
 {
 	auto * object = new CGDwelling();
 
-	CSpecObjInfo * spec = nullptr;
-	switch(objectTemplate->id)
-	{
-		case Obj::RANDOM_DWELLING:
-			spec = new CCreGenLeveledCastleInfo();
-			break;
-		case Obj::RANDOM_DWELLING_LVL:
-			spec = new CCreGenAsCastleInfo();
-			break;
-		case Obj::RANDOM_DWELLING_FACTION:
-			spec = new CCreGenLeveledInfo();
-			break;
-		default:
-			throw std::runtime_error("Invalid random dwelling format");
-	}
-	spec->owner = object;
-
 	setOwnerAndValidate(mapPosition, object, reader->readPlayer32());
 
-	//216 and 217
-	if(auto * castleSpec = dynamic_cast<CCreGenAsCastleInfo *>(spec))
-	{
-		castleSpec->instanceId = "";
-		castleSpec->identifier = reader->readUInt32();
-		if(!castleSpec->identifier)
-		{
-			castleSpec->asCastle = false;
-			const int MASK_SIZE = 8;
-			ui8 mask[2];
-			mask[0] = reader->readUInt8();
-			mask[1] = reader->readUInt8();
+	object->randomizationInfo = CGDwellingRandomizationInfo();
 
-			castleSpec->allowedFactions.clear();
-			castleSpec->allowedFactions.resize(VLC->townh->size(), false);
+	bool hasFactionInfo = objectTemplate->id == Obj::RANDOM_DWELLING || objectTemplate->id == Obj::RANDOM_DWELLING_LVL;
+	bool hasLevelInfo = objectTemplate->id == Obj::RANDOM_DWELLING || objectTemplate->id == Obj::RANDOM_DWELLING_FACTION;
 
-			for(int i = 0; i < MASK_SIZE; i++)
-				castleSpec->allowedFactions[i] = ((mask[0] & (1 << i)) > 0);
+	if (hasFactionInfo)
+	{
+		object->randomizationInfo->identifier = reader->readUInt32();
 
-			for(int i = 0; i < (GameConstants::F_NUMBER - MASK_SIZE); i++)
-				castleSpec->allowedFactions[i + MASK_SIZE] = ((mask[1] & (1 << i)) > 0);
-		}
-		else
-		{
-			castleSpec->asCastle = true;
-		}
+		if(object->randomizationInfo->identifier == 0)
+			reader->readBitmaskFactions(object->randomizationInfo->allowedFactions, false);
 	}
+	else
+		object->randomizationInfo->allowedFactions.insert(FactionID(objectTemplate->subid));
 
-	//216 and 218
-	if(auto * lvlSpec = dynamic_cast<CCreGenLeveledInfo *>(spec))
+	if(hasLevelInfo)
 	{
-		lvlSpec->minLevel = std::max(reader->readUInt8(), static_cast<ui8>(0)) + 1;
-		lvlSpec->maxLevel = std::min(reader->readUInt8(), static_cast<ui8>(6)) + 1;
+		object->randomizationInfo->minLevel = std::max(reader->readUInt8(), static_cast<ui8>(0)) + 1;
+		object->randomizationInfo->maxLevel = std::min(reader->readUInt8(), static_cast<ui8>(6)) + 1;
 	}
-	object->info = spec;
+	else
+	{
+		object->randomizationInfo->minLevel = objectTemplate->subid;
+		object->randomizationInfo->maxLevel = objectTemplate->subid;
+	}
+
 	return object;
 }
 

+ 2 - 2
lib/mapping/MapFormatJson.cpp

@@ -1229,7 +1229,7 @@ void CMapLoaderJson::MapObjectLoader::configure()
 		else if(art->ID  == Obj::ARTIFACT)
 		{
 			//specific artifact
-			artID = ArtifactID(art->subID);
+			artID = art->getArtifact();
 		}
 
 		art->storedArtifact = ArtifactUtils::createArtifact(owner->map, artID, spellID.getNum());
@@ -1263,7 +1263,7 @@ void CMapLoaderJson::readObjects()
 
 	std::sort(map->heroesOnMap.begin(), map->heroesOnMap.end(), [](const ConstTransitivePtr<CGHeroInstance> & a, const ConstTransitivePtr<CGHeroInstance> & b)
 	{
-		return a->subID < b->subID;
+		return a->getObjTypeIndex() < b->getObjTypeIndex();
 	});
 }
 

+ 1 - 2
lib/networkPacks/NetPacksLib.cpp

@@ -1487,6 +1487,7 @@ void NewObject::applyGs(CGameState *gs)
 
 	CGObjectInstance * o = handler->create();
 	handler->configureObject(o, gs->getRandomGenerator());
+	assert(o->ID == this->ID);
 	
 	if (ID == Obj::MONSTER) //probably more options will be needed
 	{
@@ -1514,8 +1515,6 @@ void NewObject::applyGs(CGameState *gs)
 		o->appearance = handler->getTemplates().front();
 
 	o->id = ObjectInstanceID(static_cast<si32>(gs->map->objects.size()));
-	o->ID = ID;
-	o->subID = subID;
 	o->pos = targetPos + o->getVisitableOffset();
 
 	gs->map->objects.emplace_back(o);

+ 10 - 7
lib/pathfinder/CPathfinder.cpp

@@ -260,15 +260,18 @@ std::vector<int3> CPathfinderHelper::getTeleportExits(const PathNodeInfo & sourc
 			teleportationExits.push_back(exit);
 		}
 	}
-	else if(options.useCastleGate
-		&& (source.nodeObject->ID == Obj::TOWN && source.nodeObject->subID == ETownType::INFERNO
-		&& source.objectRelations != PlayerRelations::ENEMIES))
+	else if(options.useCastleGate && source.nodeObject->ID == Obj::TOWN && source.objectRelations != PlayerRelations::ENEMIES)
 	{
-		/// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo
-		/// This may be handy if we allow to use teleportation to friendly towns
-		for(const auto & exit : getCastleGates(source))
+		auto * town = dynamic_cast<const CGTownInstance *>(source.nodeObject);
+		assert(town);
+		if (town && town->getFaction() == FactionID::INFERNO)
 		{
-			teleportationExits.push_back(exit);
+			/// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo
+			/// This may be handy if we allow to use teleportation to friendly towns
+			for(const auto & exit : getCastleGates(source))
+			{
+				teleportationExits.push_back(exit);
+			}
 		}
 	}
 

+ 6 - 6
lib/rmg/RmgObject.cpp

@@ -113,9 +113,9 @@ void Object::Instance::setPositionRaw(const int3 & position)
 
 void Object::Instance::setAnyTemplate(CRandomGenerator & rng)
 {
-	auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates();
+	auto templates = dObject.getObjectHandler()->getTemplates();
 	if(templates.empty())
-		throw rmgException(boost::str(boost::format("Did not find any graphics for object (%d,%d)") % dObject.ID % dObject.subID));
+		throw rmgException(boost::str(boost::format("Did not find any graphics for object (%d,%d)") % dObject.ID % dObject.getObjTypeIndex()));
 
 	dObject.appearance = *RandomGeneratorUtil::nextItem(templates, rng);
 	dAccessibleAreaCache.clear();
@@ -124,11 +124,11 @@ void Object::Instance::setAnyTemplate(CRandomGenerator & rng)
 
 void Object::Instance::setTemplate(TerrainId terrain, CRandomGenerator & rng)
 {
-	auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrain);
+	auto templates = dObject.getObjectHandler()->getTemplates(terrain);
 	if (templates.empty())
 	{
 		auto terrainName = VLC->terrainTypeHandler->getById(terrain)->getNameTranslated();
-		throw rmgException(boost::str(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.subID % terrainName));
+		throw rmgException(boost::str(boost::format("Did not find graphics for object (%d,%d) at %s") % dObject.ID % dObject.getObjTypeIndex() % terrainName));
 	}
 	
 	dObject.appearance = *RandomGeneratorUtil::nextItem(templates, rng);
@@ -335,10 +335,10 @@ void Object::Instance::finalize(RmgMap & map, CRandomGenerator & rng)
 	if (!dObject.appearance)
 	{
 		const auto * terrainType = map.getTile(getPosition(true)).terType;
-		auto templates = VLC->objtypeh->getHandlerFor(dObject.ID, dObject.subID)->getTemplates(terrainType->getId());
+		auto templates = dObject.getObjectHandler()->getTemplates(terrainType->getId());
 		if (templates.empty())
 		{
-			throw rmgException(boost::str(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % dObject.ID % dObject.subID % getPosition(true).toString() % terrainType));
+			throw rmgException(boost::str(boost::format("Did not find graphics for object (%d,%d) at %s (terrain %d)") % dObject.ID % dObject.getObjTypeIndex() % getPosition(true).toString() % terrainType));
 		}
 		else
 		{

+ 1 - 1
lib/rmg/modificators/TreasurePlacer.cpp

@@ -111,7 +111,7 @@ void TreasurePlacer::addAllPossibleObjects()
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
 				auto* obj = dynamic_cast<CGHeroInstance*>(factory->create());
 
-				obj->subID = hid; //will be initialized later
+				obj->setHeroType(hid); //will be initialized later
 				obj->exp = generator.getConfig().prisonExperience[i];
 				obj->setOwner(PlayerColor::NEUTRAL);
 				generator.banHero(hid);

+ 1 - 1
lib/spells/ViewSpellInt.cpp

@@ -16,7 +16,7 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 ObjectPosInfo::ObjectPosInfo(const CGObjectInstance * obj):
-	pos(obj->visitablePos()), id(obj->ID), subId(obj->subID), owner(obj->tempOwner)
+	pos(obj->visitablePos()), id(obj->ID), subId(obj->getObjTypeIndex()), owner(obj->tempOwner)
 {
 
 }

+ 7 - 15
mapeditor/inspector/inspector.cpp

@@ -87,14 +87,6 @@ void Initializer::initialize(CGDwelling * o)
 	if(!o) return;
 	
 	o->tempOwner = defaultPlayer;
-	
-	switch(o->ID)
-	{
-		case Obj::RANDOM_DWELLING:
-		case Obj::RANDOM_DWELLING_LVL:
-		case Obj::RANDOM_DWELLING_FACTION:
-			o->initRandomObjectInfo();
-	}
 }
 
 void Initializer::initialize(CGGarrison * o)
@@ -243,7 +235,7 @@ void Inspector::updateProperties(CGDwelling * o)
 	
 	addProperty("Owner", o->tempOwner, false);
 	
-	if(dynamic_cast<CCreGenAsCastleInfo*>(o->info))
+	if (o->ID == Obj::RANDOM_DWELLING || o->ID == Obj::RANDOM_DWELLING_LVL)
 	{
 		auto * delegate = new PickObjectDelegate(controller, PickObjectDelegate::typedFilter<CGTownInstance>);
 		addProperty("Same as town", PropertyEditorPlaceholder(), delegate, false);
@@ -641,12 +633,12 @@ void Inspector::setProperty(CGDwelling * o, const QString & key, const QVariant
 	
 	if(key == "Same as town")
 	{
-		if(auto * info = dynamic_cast<CCreGenAsCastleInfo*>(o->info))
-		{
-			info->instanceId = "";
-			if(CGTownInstance * town = data_cast<CGTownInstance>(value.toLongLong()))
-				info->instanceId = town->instanceName;
-		}
+		if (!o->randomizationInfo.has_value())
+			o->randomizationInfo = CGDwellingRandomizationInfo();
+
+		o->randomizationInfo->instanceId = "";
+		if(CGTownInstance * town = data_cast<CGTownInstance>(value.toLongLong()))
+			o->randomizationInfo->instanceId = town->instanceName;
 	}
 }
 

+ 2 - 2
mapeditor/mapcontroller.cpp

@@ -217,7 +217,7 @@ void MapController::repairMap(CMap * map) const
 				art->storedArtifact = a;
 			}
 			else
-				map->allowedArtifact.at(art->subID) = true;
+				map->allowedArtifact.at(art->getArtifact()) = true;
 		}
 	}
 }
@@ -623,7 +623,7 @@ ModCompatibilityInfo MapController::modAssessmentMap(const CMap & map)
 		if(obj->ID == Obj::HERO)
 			continue; //stub! 
 		
-		auto handler = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID);
+		auto handler = obj->getObjectHandler();
 		auto modName = QString::fromStdString(handler->getJsonKey()).split(":").at(0).toStdString();
 		if(modName != "core")
 			result[modName] = VLC->modh->getModInfo(modName).getVerificationInfo();

+ 3 - 3
server/CGameHandler.cpp

@@ -2315,7 +2315,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 		auto isLibrary = isMageGuild ? false
 			: t->town->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY;
 
-		if(isMageGuild || isLibrary || (t->subID == ETownType::CONFLUX && buildingID == BuildingID::GRAIL))
+		if(isMageGuild || isLibrary || (t->getFaction() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL))
 		{
 			if(t->visitingHero)
 				giveSpells(t,t->visitingHero);
@@ -3284,7 +3284,7 @@ void CGameHandler::handleTownEvents(CGTownInstance * town, NewTurn &n)
 				if (!town->hasBuilt(i))
 				{
 					buildStructure(town->id, i, true);
-					iw.components.emplace_back(Component::EComponentType::BUILDING, town->subID, i, 0);
+					iw.components.emplace_back(Component::EComponentType::BUILDING, town->getFaction(), i, 0);
 				}
 			}
 
@@ -3430,7 +3430,7 @@ void CGameHandler::objectVisited(const CGObjectInstance * obj, const CGHeroInsta
 {
 	using events::ObjectVisitStarted;
 
-	logGlobal->debug("%s visits %s (%d:%d)", h->nodeName(), obj->getObjectName(), obj->ID, obj->subID);
+	logGlobal->debug("%s visits %s (%d)", h->nodeName(), obj->getObjectName(), obj->ID);
 
 	if (getVisitingHero(obj) != nullptr)
 	{

+ 7 - 7
server/processors/HeroPoolProcessor.cpp

@@ -50,8 +50,8 @@ TavernHeroSlot HeroPoolProcessor::selectSlotForRole(const PlayerColor & player,
 	// try to find "better" slot to overwrite
 	// we want to avoid overwriting retreated heroes when tavern still has slot with random hero
 	// as well as avoid overwriting surrendered heroes if we can overwrite retreated hero
-	auto roleLeft = heroesPool->getSlotRole(HeroTypeID(heroes[0]->subID));
-	auto roleRight = heroesPool->getSlotRole(HeroTypeID(heroes[1]->subID));
+	auto roleLeft = heroesPool->getSlotRole(heroes[0]->getHeroType());
+	auto roleRight = heroesPool->getSlotRole(heroes[1]->getHeroType());
 
 	if (roleLeft > roleRight)
 		return TavernHeroSlot::RANDOM;
@@ -73,7 +73,7 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer
 
 	sah.slotID = selectSlotForRole(color, sah.roleID);
 	sah.player = color;
-	sah.hid.setNum(hero->subID);
+	sah.hid = hero->getHeroType();
 	gameHandler->sendAndApply(&sah);
 }
 
@@ -84,7 +84,7 @@ void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroIns
 
 	sah.slotID = selectSlotForRole(color, sah.roleID);
 	sah.player = color;
-	sah.hid.setNum(hero->subID);
+	sah.hid = hero->getHeroType();
 	sah.army.clearSlots();
 	sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1);
 
@@ -111,7 +111,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe
 
 	if (newHero)
 	{
-		sah.hid.setNum(newHero->subID);
+		sah.hid = newHero->getHeroType();
 
 		if (giveArmy)
 		{
@@ -193,7 +193,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 
 	for(const auto & hero : recruitableHeroes)
 	{
-		if(hero->subID == heroToRecruit)
+		if(hero->getHeroType() == heroToRecruit)
 			recruitedHero = hero;
 	}
 
@@ -206,7 +206,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 
 	HeroRecruited hr;
 	hr.tid = mapObject->id;
-	hr.hid.setNum(recruitedHero->subID);
+	hr.hid = recruitedHero->getHeroType();
 	hr.player = player;
 	hr.tile = recruitedHero->convertFromVisitablePos(targetPos );
 	if(gameHandler->getTile(targetPos)->isWater() && !recruitedHero->boat)