Răsfoiți Sursa

Reduce usage of pointers to VLC entities

Final goal (of multiple PR's) is to remove all remaining pointers from
serializeable game state, and replace them with either identifiers or
with shared/unique pointers.

CGTownInstance::town and CGHeroInstance::type members have been removed.
Now this data is computed dynamically using subID member.

VLC entity of a town can now be accessed via following methods:
- getFactionID() returns ID of a faction
- getFaction() returns pointer to a faction
- getTown() returns pointer to a town

VLC entity of a hero can now be accessed via following methods:
- getHeroTypeID() returns ID of a hero
- getHeroClassID() returns ID of a hero class
- getHeroType() returns pointer to a hero
- getHeroClass() returns pointer to a hero class
Ivan Savenko 1 an în urmă
părinte
comite
3dd4fa2528
83 a modificat fișierele cu 445 adăugiri și 468 ștergeri
  1. 1 1
      AI/Nullkiller/AIGateway.cpp
  2. 2 2
      AI/Nullkiller/Analyzers/ArmyManager.cpp
  3. 5 5
      AI/Nullkiller/Analyzers/BuildAnalyzer.cpp
  4. 1 1
      AI/Nullkiller/Analyzers/HeroManager.cpp
  5. 1 1
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  6. 2 2
      AI/Nullkiller/Goals/BuildThis.cpp
  7. 4 4
      AI/VCAI/BuildingManager.cpp
  8. 1 1
      AI/VCAI/Goals/BuildThis.cpp
  9. 4 4
      AI/VCAI/Goals/GatherTroops.cpp
  10. 1 1
      AI/VCAI/MapObjectsEvaluator.cpp
  11. 2 2
      AI/VCAI/VCAI.cpp
  12. 1 1
      CCallback.cpp
  13. 1 1
      client/CPlayerInterface.cpp
  14. 1 1
      client/ClientCommandManager.cpp
  15. 1 1
      client/NetPacksClient.cpp
  16. 1 1
      client/adventureMap/CList.cpp
  17. 5 5
      client/battle/BattleInterfaceClasses.cpp
  18. 8 8
      client/battle/BattleSiegeController.cpp
  19. 4 5
      client/widgets/MiscWidgets.cpp
  20. 58 58
      client/windows/CCastleInterface.cpp
  21. 2 2
      client/windows/CExchangeWindow.cpp
  22. 3 3
      client/windows/CHeroWindow.cpp
  23. 4 4
      client/windows/CKingdomInterface.cpp
  24. 1 1
      client/windows/CMarketWindow.cpp
  25. 5 5
      client/windows/GUIClasses.cpp
  26. 5 5
      client/windows/QuickRecruitmentWindow.cpp
  27. 1 1
      include/vcmi/Entity.h
  28. 1 1
      lib/BasicTypes.cpp
  29. 1 1
      lib/CCreatureHandler.cpp
  30. 1 1
      lib/CCreatureHandler.h
  31. 2 2
      lib/CCreatureSet.cpp
  32. 1 1
      lib/CCreatureSet.h
  33. 3 3
      lib/CGameInfoCallback.cpp
  34. 2 2
      lib/battle/CUnitState.cpp
  35. 1 1
      lib/battle/CUnitState.h
  36. 3 3
      lib/bonuses/Limiters.cpp
  37. 4 4
      lib/campaign/CampaignState.cpp
  38. 1 1
      lib/entities/faction/CFaction.cpp
  39. 1 1
      lib/entities/faction/CFaction.h
  40. 15 20
      lib/gameState/CGameState.cpp
  41. 8 8
      lib/gameState/CGameStateCampaign.cpp
  42. 1 1
      lib/gameState/GameStatistics.cpp
  43. 1 1
      lib/gameState/InfoAboutArmy.cpp
  44. 4 4
      lib/gameState/TavernHeroesPool.cpp
  45. 1 7
      lib/mapObjectConstructors/CommonConstructors.cpp
  46. 0 1
      lib/mapObjectConstructors/CommonConstructors.h
  47. 1 1
      lib/mapObjects/CArmedInstance.cpp
  48. 20 15
      lib/mapObjects/CGCreature.cpp
  49. 2 1
      lib/mapObjects/CGCreature.h
  50. 1 1
      lib/mapObjects/CGDwelling.cpp
  51. 56 57
      lib/mapObjects/CGHeroInstance.cpp
  52. 11 4
      lib/mapObjects/CGHeroInstance.h
  53. 58 62
      lib/mapObjects/CGTownInstance.cpp
  54. 17 11
      lib/mapObjects/CGTownInstance.h
  55. 1 1
      lib/mapObjects/CQuest.cpp
  56. 2 2
      lib/mapObjects/TownBuildingInstance.cpp
  57. 1 1
      lib/mapping/CMap.cpp
  58. 6 6
      lib/mapping/MapFormatH3M.cpp
  59. 4 6
      lib/mapping/MapFormatJson.cpp
  60. 2 2
      lib/networkPacks/NetPacksLib.cpp
  61. 1 1
      lib/pathfinder/CPathfinder.cpp
  62. 2 2
      lib/rewardable/Limiter.cpp
  63. 1 1
      lib/rmg/modificators/ObjectManager.cpp
  64. 2 2
      lib/rmg/modificators/TownPlacer.cpp
  65. 6 6
      lib/rmg/modificators/TreasurePlacer.cpp
  66. 1 1
      lib/serializer/CSerializer.cpp
  67. 2 1
      lib/serializer/ESerializationVersion.h
  68. 1 1
      lib/spells/effects/Moat.cpp
  69. 1 1
      lib/spells/effects/Summon.cpp
  70. 13 30
      mapeditor/inspector/inspector.cpp
  71. 1 1
      mapeditor/inspector/townbuildingswidget.cpp
  72. 2 2
      mapeditor/inspector/towneventdialog.cpp
  73. 1 5
      mapeditor/mapcontroller.cpp
  74. 2 4
      mapeditor/playerparams.cpp
  75. 5 5
      mapeditor/validator.cpp
  76. 1 1
      scripting/lua/api/Creature.cpp
  77. 22 22
      server/CGameHandler.cpp
  78. 14 14
      server/processors/HeroPoolProcessor.cpp
  79. 5 5
      server/processors/NewTurnProcessor.cpp
  80. 1 1
      server/processors/PlayerMessageProcessor.cpp
  81. 1 1
      test/entity/CCreatureTest.cpp
  82. 1 1
      test/mock/mock_Creature.h
  83. 1 1
      test/mock/mock_battle_Unit.h

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -1454,7 +1454,7 @@ bool AIGateway::moveHeroToTile(int3 dst, HeroPtr h)
 
 void AIGateway::buildStructure(const CGTownInstance * t, BuildingID building)
 {
-	auto name = t->town->buildings.at(building)->getNameTranslated();
+	auto name = t->getTown()->buildings.at(building)->getNameTranslated();
 	logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString());
 	cb->buildBuilding(t, building); //just do this;
 }

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

@@ -144,7 +144,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 
 	for(auto & slot : sortedSlots)
 	{
-		alignmentMap[slot.creature->getFaction()] += slot.power;
+		alignmentMap[slot.creature->getFactionID()] += slot.power;
 	}
 
 	std::set<FactionID> allowedFactions;
@@ -178,7 +178,7 @@ std::vector<SlotInfo> ArmyManager::getBestArmy(const IBonusBearer * armyCarrier,
 
 		for(auto & slot : sortedSlots)
 		{
-			if(vstd::contains(allowedFactions, slot.creature->getFaction()))
+			if(vstd::contains(allowedFactions, slot.creature->getFactionID()))
 			{
 				auto slotID = newArmyInstance.getSlotFor(slot.creature->getId());
 

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

@@ -17,7 +17,7 @@ namespace NKAI
 
 void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
 {
-	auto townInfo = developmentInfo.town->town;
+	auto townInfo = developmentInfo.town->getTown();
 	auto creatures = townInfo->creatures;
 	auto buildings = townInfo->getAllBuildings();
 
@@ -31,7 +31,7 @@ void BuildAnalyzer::updateTownDwellings(TownDevelopmentInfo & developmentInfo)
 		}
 	}
 
-	for(int level = 0; level < developmentInfo.town->town->creatures.size(); level++)
+	for(int level = 0; level < developmentInfo.town->getTown()->creatures.size(); level++)
 	{
 		logAi->trace("Checking dwelling level %d", level);
 		BuildingInfo nextToBuild = BuildingInfo();
@@ -82,7 +82,7 @@ void BuildAnalyzer::updateOtherBuildings(TownDevelopmentInfo & developmentInfo)
 	{
 		for(auto & buildingID : buildingSet)
 		{
-			if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->town->buildings.count(buildingID))
+			if(!developmentInfo.town->hasBuilt(buildingID) && developmentInfo.town->getTown()->buildings.count(buildingID))
 			{
 				developmentInfo.addBuildingToBuild(getBuildingOrPrerequisite(developmentInfo.town, buildingID));
 
@@ -198,7 +198,7 @@ BuildingInfo BuildAnalyzer::getBuildingOrPrerequisite(
 	bool excludeDwellingDependencies) const
 {
 	BuildingID building = toBuild;
-	auto townInfo = town->town;
+	auto townInfo = town->getTown();
 
 	const CBuilding * buildPtr = townInfo->buildings.at(building);
 	const CCreature * creature = nullptr;
@@ -327,7 +327,7 @@ bool BuildAnalyzer::hasAnyBuilding(int32_t alignment, BuildingID bid) const
 {
 	for(auto tdi : developmentInfos)
 	{
-		if(tdi.town->getFaction() == alignment && tdi.town->hasBuilt(bid))
+		if(tdi.town->getFactionID() == alignment && tdi.town->hasBuilt(bid))
 			return true;
 	}
 

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

@@ -71,7 +71,7 @@ float HeroManager::evaluateSecSkill(SecondarySkill skill, const CGHeroInstance *
 
 float HeroManager::evaluateSpeciality(const CGHeroInstance * hero) const
 {
-	auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->type->getId()));
+	auto heroSpecial = Selector::source(BonusSource::HERO_SPECIAL, BonusSourceID(hero->getHeroTypeID()));
 	auto secondarySkillBonus = Selector::targetSourceType()(BonusSource::SECONDARY_SKILL);
 	auto specialSecondarySkillBonuses = hero->getBonuses(heroSpecial.And(secondarySkillBonus));
 	auto secondarySkillBonuses = hero->getBonuses(Selector::sourceTypeSel(BonusSource::SECONDARY_SKILL));

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

@@ -1120,7 +1120,7 @@ public:
 
 uint64_t RewardEvaluator::getUpgradeArmyReward(const CGTownInstance * town, const BuildingInfo & bi) const
 {
-	if(ai->buildAnalyzer->hasAnyBuilding(town->getFaction(), bi.id))
+	if(ai->buildAnalyzer->hasAnyBuilding(town->getFactionID(), bi.id))
 		return 0;
 
 	auto creaturesToUpgrade = ai->armyManager->getTotalCreaturesAvailable(bi.baseCreatureID);

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

@@ -23,7 +23,7 @@ BuildThis::BuildThis(BuildingID Bid, const CGTownInstance * tid)
 	: ElementarGoal(Goals::BUILD_STRUCTURE)
 {
 	buildingInfo = BuildingInfo(
-		tid->town->buildings.at(Bid),
+		tid->getTown()->buildings.at(Bid),
 		nullptr,
 		CreatureID::NONE,
 		tid,
@@ -52,7 +52,7 @@ void BuildThis::accept(AIGateway * ai)
 		if(cb->canBuildStructure(town, b) == EBuildingState::ALLOWED)
 		{
 			logAi->debug("Player %d will build %s in town of %s at %s",
-				ai->playerID, town->town->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->anchorPos().toString());
+				ai->playerID, town->getTown()->buildings.at(b)->getNameTranslated(), town->getNameTranslated(), town->anchorPos().toString());
 			cb->buildBuilding(town, b);
 
 			return;

+ 4 - 4
AI/VCAI/BuildingManager.cpp

@@ -23,13 +23,13 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
 		return false;
 	}
 
-	if (!vstd::contains(t->town->buildings, building))
+	if (!vstd::contains(t->getTown()->buildings, building))
 		return false; // no such building in town
 
 	if (t->hasBuilt(building)) //Already built? Shouldn't happen in general
 		return true;
 
-	const CBuilding * buildPtr = t->town->buildings.at(building);
+	const CBuilding * buildPtr = t->getTown()->buildings.at(building);
 
 	auto toBuild = buildPtr->requirements.getFulfillmentCandidates([&](const BuildingID & buildID)
 	{
@@ -51,7 +51,7 @@ bool BuildingManager::tryBuildThisStructure(const CGTownInstance * t, BuildingID
 
 	for (const auto & buildID : toBuild)
 	{
-		const CBuilding * b = t->town->buildings.at(buildID);
+		const CBuilding * b = t->getTown()->buildings.at(buildID);
 
 		EBuildingState canBuild = cb->canBuildStructure(t, buildID);
 		if (canBuild == EBuildingState::ALLOWED)
@@ -220,7 +220,7 @@ bool BuildingManager::getBuildingOptions(const CGTownInstance * t)
 
 	//at the end, try to get and build any extra buildings with nonstandard slots (for example HotA 3rd level dwelling)
 	std::vector<BuildingID> extraBuildings;
-	for (auto buildingInfo : t->town->buildings)
+	for (auto buildingInfo : t->getTown()->buildings)
 	{
 		if (buildingInfo.first > BuildingID::DWELL_UP2_FIRST)
 			extraBuildings.push_back(buildingInfo.first);

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

@@ -56,7 +56,7 @@ TSubgoal BuildThis::whatToDoToAchieve()
 		case EBuildingState::ALLOWED:
 		case EBuildingState::NO_RESOURCES:
 		{
-			auto res = town->town->buildings.at(BuildingID(bid))->resources;
+			auto res = town->getTown()->buildings.at(BuildingID(bid))->resources;
 			return ai->ah->whatToDo(res, iAmElementar()); //realize immediately or gather resources
 		}
 		break;

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

@@ -88,13 +88,13 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 		}
 
 		auto creature = VLC->creatures()->getByIndex(objid);
-		if(t->getFaction() == creature->getFaction()) //TODO: how to force AI to build unupgraded creatures? :O
+		if(t->getFactionID() == creature->getFactionID()) //TODO: how to force AI to build unupgraded creatures? :O
 		{
 			auto tryFindCreature = [&]() -> std::optional<std::vector<CreatureID>>
 			{
-				if(vstd::isValidIndex(t->town->creatures, creature->getLevel() - 1))
+				if(vstd::isValidIndex(t->getTown()->creatures, creature->getLevel() - 1))
 				{
-					auto itr = t->town->creatures.begin();
+					auto itr = t->getTown()->creatures.begin();
 					std::advance(itr, creature->getLevel() - 1);
 					return make_optional(*itr);
 				}
@@ -109,7 +109,7 @@ TGoalVec GatherTroops::getAllPossibleSubgoals()
 			if(upgradeNumber < 0)
 				continue;
 
-			BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->town->creatures.size());
+			BuildingID bid(BuildingID::DWELL_FIRST + creature->getLevel() - 1 + upgradeNumber * t->getTown()->creatures.size());
 			if(t->hasBuilt(bid) && ai->ah->freeResources().canAfford(creature->getFullRecruitCost())) //this assumes only creatures with dwellings are assigned to faction
 			{
 				solutions.push_back(sptr(BuyArmy(t, creature->getAIValue() * this->value).setobjid(objid)));

+ 1 - 1
AI/VCAI/MapObjectsEvaluator.cpp

@@ -69,7 +69,7 @@ std::optional<int> MapObjectsEvaluator::getObjectValue(const CGObjectInstance *
 	{
 		//special case handling: in-game heroes have hero ID as object subID, but when reading configs available hero object subID's are hero classes
 		auto hero = dynamic_cast<const CGHeroInstance*>(obj);
-		return getObjectValue(obj->ID, hero->type->heroClass->getIndex());
+		return getObjectValue(obj->ID, hero->getHeroClassID());
 	}
 	else if(obj->ID == Obj::PRISON)
 	{

+ 2 - 2
AI/VCAI/VCAI.cpp

@@ -1994,7 +1994,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 
 void VCAI::buildStructure(const CGTownInstance * t, BuildingID building)
 {
-	auto name = t->town->buildings.at(building)->getNameTranslated();
+	auto name = t->getTown()->buildings.at(building)->getNameTranslated();
 	logAi->debug("Player %d will build %s in town of %s at %s", ai->playerID, name, t->getNameTranslated(), t->anchorPos().toString());
 	cb->buildBuilding(t, building); //just do this;
 }
@@ -2081,7 +2081,7 @@ void VCAI::tryRealize(Goals::BuildThis & g)
 		if (cb->canBuildStructure(t, b) == EBuildingState::ALLOWED)
 		{
 			logAi->debug("Player %d will build %s in town of %s at %s",
-				playerID, t->town->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->anchorPos().toString());
+				playerID, t->getTown()->buildings.at(b)->getNameTranslated(), t->getNameTranslated(), t->anchorPos().toString());
 			cb->buildBuilding(t, b);
 			throw goalFulfilledException(sptr(g));
 		}

+ 1 - 1
CCallback.cpp

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

+ 1 - 1
client/CPlayerInterface.cpp

@@ -1138,7 +1138,7 @@ void CPlayerInterface::showMapObjectSelectDialog(QueryID askID, const Component
 		const CGTownInstance * t = dynamic_cast<const CGTownInstance *>(cb->getObj(obj));
 		if(t)
 		{
-			auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
+			auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
 			image->scaleTo(Point(35, 23));
 			images.push_back(image);
 		}

+ 1 - 1
client/ClientCommandManager.cpp

@@ -453,7 +453,7 @@ void ClientCommandManager::handleTellCommand(std::istringstream& singleWordBuffe
 	if(what == "hs")
 	{
 		for(const CGHeroInstance* h : LOCPLINT->cb->getHeroesInfo())
-			if(h->type->getIndex() == id1)
+			if(h->getHeroTypeID().getNum() == id1)
 				if(const CArtifactInstance* a = h->getArt(ArtifactPosition(id2)))
 					printCommandMessage(a->nodeName());
 	}

+ 1 - 1
client/NetPacksClient.cpp

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

+ 1 - 1
client/adventureMap/CList.cpp

@@ -432,7 +432,7 @@ std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
 
 void CTownList::CTownItem::update()
 {
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	picture->setFrame(iconIndex + 2);
 	redraw();

+ 5 - 5
client/battle/BattleInterfaceClasses.cpp

@@ -389,13 +389,13 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 {
 	AnimationPath animationPath;
 
-	if(!hero->type->battleImage.empty())
-		animationPath = hero->type->battleImage;
+	if(!hero->getHeroType()->battleImage.empty())
+		animationPath = hero->getHeroType()->battleImage;
 	else
 	if(hero->gender == EHeroGender::FEMALE)
-		animationPath = hero->type->heroClass->imageBattleFemale;
+		animationPath = hero->getHeroClass()->imageBattleFemale;
 	else
-		animationPath = hero->type->heroClass->imageBattleMale;
+		animationPath = hero->getHeroClass()->imageBattleMale;
 
 	animation = GH.renderHandler().loadAnimation(animationPath, EImageBlitMode::ALPHA);
 
@@ -1027,7 +1027,7 @@ void StackQueue::update()
 
 int32_t StackQueue::getSiegeShooterIconID()
 {
-	return owner.siegeController->getSiegedTown()->town->faction->getIndex();
+	return owner.siegeController->getSiegedTown()->getFactionID().getNum();
 }
 
 std::optional<uint32_t> StackQueue::getHoveredUnitIdIfAny() const

+ 8 - 8
client/battle/BattleSiegeController.cpp

@@ -58,14 +58,14 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual
 		};
 	};
 
-	const std::string & prefix = town->town->clientInfo.siegePrefix;
+	const std::string & prefix = town->getTown()->clientInfo.siegePrefix;
 	std::string addit = std::to_string(getImageIndex());
 
 	switch(what)
 	{
 	case EWallVisual::BACKGROUND_WALL:
 		{
-			auto faction = town->town->faction->getIndex();
+			auto faction = town->getFactionID();
 
 			if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD)
 				return ImagePath::builtinTODO(prefix + "TPW1.BMP");
@@ -111,7 +111,7 @@ ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual
 
 void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what)
 {
-	auto & ci = town->town->clientInfo;
+	auto & ci = town->getTown()->clientInfo;
 	auto const & pos = ci.siegePositions[what];
 
 	if ( wallPieceImages[what] && pos.isValid())
@@ -120,7 +120,7 @@ void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVis
 
 ImagePath BattleSiegeController::getBattleBackgroundName() const
 {
-	const std::string & prefix = town->town->clientInfo.siegePrefix;
+	const std::string & prefix = town->getTown()->clientInfo.siegePrefix;
 	return ImagePath::builtinTODO(prefix + "BACK.BMP");
 }
 
@@ -130,8 +130,8 @@ bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what)
 
 	switch (what)
 	{
-	case EWallVisual::MOAT:              return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
-	case EWallVisual::MOAT_BANK:         return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
+	case EWallVisual::MOAT:              return fortifications.hasMoat && town->getTown()->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();
+	case EWallVisual::MOAT_BANK:         return fortifications.hasMoat && town->getTown()->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();
 	case EWallVisual::KEEP_BATTLEMENT:   return fortifications.citadelHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;
 	case EWallVisual::UPPER_BATTLEMENT:  return fortifications.upperTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;
 	case EWallVisual::BOTTOM_BATTLEMENT: return fortifications.lowerTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;
@@ -218,8 +218,8 @@ Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) con
 	if (posID != 0)
 	{
 		return {
-			town->town->clientInfo.siegePositions[posID].x,
-			town->town->clientInfo.siegePositions[posID].y
+			town->getTown()->clientInfo.siegePositions[posID].x,
+			town->getTown()->clientInfo.siegePositions[posID].y
 		};
 	}
 

+ 4 - 5
client/widgets/MiscWidgets.cpp

@@ -468,8 +468,8 @@ void CInteractableTownTooltip::init(const CGTownInstance * town)
 				LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 		}
 	}, [town]{
-		if(!town->town->faction->getDescriptionTranslated().empty())
-			CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
+		if(!town->getFaction()->getDescriptionTranslated().empty())
+			CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
 	});
 	fastMarket = std::make_shared<LRClickableArea>(Rect(143, 31, 30, 34), []()
 	{
@@ -532,8 +532,7 @@ CreatureTooltip::CreatureTooltip(Point pos, const CGCreature * creature)
 {
 	OBJECT_CONSTRUCTION;
 
-	auto creatureID = creature->getCreature();
-	int32_t creatureIconIndex = CGI->creatures()->getById(creatureID)->getIconIndex();
+	int32_t creatureIconIndex = creature->getCreature()->getIconIndex();
 
 	creatureImage = std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), creatureIconIndex);
 	creatureImage->center(Point(parent->pos.x + parent->pos.w / 2, parent->pos.y + creatureImage->pos.h / 2 + 11));
@@ -633,7 +632,7 @@ CCreaturePic::CCreaturePic(int x, int y, const CCreature * cre, bool Big, bool A
 	pos.x+=x;
 	pos.y+=y;
 
-	auto faction = cre->getFaction();
+	auto faction = cre->getFactionID();
 
 	assert(CGI->townh->size() > faction);
 

+ 58 - 58
client/windows/CCastleInterface.cpp

@@ -82,7 +82,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
 
 	// special animation frame manipulation for castle shipyard with and without ship
 	// done due to .def used in special way, not to animate building - first image is for shipyard without citadel moat, 2nd image is for including moat
-	if(Town->town->faction->getId() == FactionID::CASTLE && Str->building &&
+	if(Town->getFactionID() == FactionID::CASTLE && Str->building &&
 		(Str->building->bid == BuildingID::SHIPYARD || Str->building->bid == BuildingID::SHIP))
 	{
 		if(Town->hasBuilt(BuildingID::CITADEL))
@@ -107,7 +107,7 @@ const CBuilding * CBuildingRect::getBuilding()
 		return nullptr;
 
 	if (str->hiddenUpgrade) // hidden upgrades, e.g. hordes - return base (dwelling for hordes)
-		return town->town->buildings.at(str->building->getBase());
+		return town->getTown()->buildings.at(str->building->getBase());
 
 	return str->building;
 }
@@ -156,7 +156,7 @@ void CBuildingRect::showPopupWindow(const Point & cursorPosition)
 		return;
 
 	BuildingID bid = getBuilding()->bid;
-	const CBuilding *bld = town->town->buildings.at(bid);
+	const CBuilding *bld = town->getTown()->buildings.at(bid);
 	if (bid < BuildingID::DWELL_FIRST)
 	{
 		CRClickPopup::createAndPush(CInfoWindow::genText(bld->getNameTranslated(), bld->getDescriptionTranslated()),
@@ -235,10 +235,10 @@ std::string CBuildingRect::getSubtitle()//hover text for building
 	int bid = getBuilding()->bid;
 
 	if (bid<30)//non-dwellings - only building name
-		return town->town->buildings.at(getBuilding()->bid)->getNameTranslated();
+		return town->getTown()->buildings.at(getBuilding()->bid)->getNameTranslated();
 	else//dwellings - recruit %creature%
 	{
-		auto & availableCreatures = town->creatures[(bid-30)%town->town->creatures.size()].second;
+		auto & availableCreatures = town->creatures[(bid-30)%town->getTown()->creatures.size()].second;
 		if(availableCreatures.size())
 		{
 			int creaID = availableCreatures.back();//taking last of available creatures
@@ -566,7 +566,7 @@ CCastleBuildings::CCastleBuildings(const CGTownInstance* Town):
 {
 	OBJECT_CONSTRUCTION;
 
-	background = std::make_shared<CPicture>(town->town->clientInfo.townBackground);
+	background = std::make_shared<CPicture>(town->getTown()->clientInfo.townBackground);
 	background->needRefresh = true;
 	background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
 	pos.w = background->pos.w;
@@ -602,7 +602,7 @@ void CCastleBuildings::recreate()
 		}
 	}
 
-	for(const CStructure * structure : town->town->clientInfo.structures)
+	for(const CStructure * structure : town->getTown()->clientInfo.structures)
 	{
 		if(!structure->building)
 		{
@@ -617,7 +617,7 @@ void CCastleBuildings::recreate()
 
 	for(auto & entry : groups)
 	{
-		const CBuilding * build = town->town->buildings.at(entry.first);
+		const CBuilding * build = town->getTown()->buildings.at(entry.first);
 
 		const CStructure * toAdd = *boost::max_element(entry.second, [=](const CStructure * a, const CStructure * b)
 		{
@@ -648,7 +648,7 @@ void CCastleBuildings::recreate()
 void CCastleBuildings::addBuilding(BuildingID building)
 {
 	//FIXME: implement faster method without complete recreation of town
-	BuildingID base = town->town->buildings.at(building)->getBase();
+	BuildingID base = town->getTown()->buildings.at(building)->getBase();
 
 	recreate();
 
@@ -687,7 +687,7 @@ void CCastleBuildings::buildingClicked(BuildingID building)
 	BuildingID buildingToEnter = building;
 	for(;;)
 	{
-		const CBuilding *b = town->town->buildings.find(buildingToEnter)->second;
+		const CBuilding *b = town->getTown()->buildings.find(buildingToEnter)->second;
 
 		if (buildingTryActivateCustomUI(buildingToEnter, building))
 			return;
@@ -705,7 +705,7 @@ void CCastleBuildings::buildingClicked(BuildingID building)
 bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, BuildingID buildingTarget)
 {
 	logGlobal->trace("You've clicked on %d", (int)buildingToTest.toEnum());
-	const CBuilding *b = town->town->buildings.at(buildingToTest);
+	const CBuilding *b = town->getTown()->buildings.at(buildingToTest);
 
 	if (town->getWarMachineInBuilding(buildingToTest).hasValue())
 	{
@@ -744,7 +744,7 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
 		}
 	}
 
-	if (town->rewardableBuildings.count(buildingToTest) && town->town->buildings.at(buildingToTest)->manualHeroVisit)
+	if (town->rewardableBuildings.count(buildingToTest) && town->getTown()->buildings.at(buildingToTest)->manualHeroVisit)
 	{
 		enterRewardable(buildingToTest);
 		return true;
@@ -820,10 +820,10 @@ bool CCastleBuildings::buildingTryActivateCustomUI(BuildingID buildingToTest, Bu
 						return false;
 
 				case BuildingSubID::PORTAL_OF_SUMMONING:
-						if (town->creatures[town->town->creatures.size()].second.empty())//No creatures
+						if (town->creatures[town->getTown()->creatures.size()].second.empty())//No creatures
 							LOCPLINT->showInfoDialog(CGI->generaltexth->tcommands[30]);
 						else
-							enterDwelling(town->town->creatures.size());
+							enterDwelling(town->getTown()->creatures.size());
 						return true;
 
 				case BuildingSubID::BANK:
@@ -850,7 +850,7 @@ void CCastleBuildings::enterRewardable(BuildingID building)
 	{
 		MetaString message;
 		message.appendTextID("core.genrltxt.273"); // only visiting heroes may visit %s
-		message.replaceTextID(town->town->buildings.at(building)->getNameTextID());
+		message.replaceTextID(town->getTown()->buildings.at(building)->getNameTextID());
 
 		LOCPLINT->showInfoDialog(message.toString());
 	}
@@ -868,7 +868,7 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI
 	const CGHeroInstance *hero = town->visitingHero;
 	if(!hero)
 	{
-		LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->town->buildings.find(building)->second->getNameTranslated()));
+		LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[273]) % town->getTown()->buildings.find(building)->second->getNameTranslated()));
 		return;
 	}
 	auto art = artifactID.toArtifact();
@@ -897,8 +897,8 @@ void CCastleBuildings::enterBlacksmith(BuildingID building, ArtifactID artifactI
 
 void CCastleBuildings::enterBuilding(BuildingID building)
 {
-	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building)));
-	LOCPLINT->showInfoDialog( town->town->buildings.find(building)->second->getDescriptionTranslated(), comps);
+	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building)));
+	LOCPLINT->showInfoDialog( town->getTown()->buildings.find(building)->second->getDescriptionTranslated(), comps);
 }
 
 void CCastleBuildings::enterCastleGate()
@@ -915,20 +915,20 @@ void CCastleBuildings::enterCastleGate()
 	{
 		const CGTownInstance *t = Town;
 		if (t->id != this->town->id && t->visitingHero == nullptr && //another town, empty and this is
-			t->town->faction->getId() == town->town->faction->getId() && //the town of the same faction
+			t->getFactionID() == town->getFactionID() && //the town of the same faction
 			t->hasBuilt(BuildingSubID::CASTLE_GATE)) //and the town has a castle gate
 		{
 			availableTowns.push_back(t->id.getNum());//add to the list
 			if(settings["general"]["enableUiEnhancements"].Bool())
 			{
-				auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->town->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
+				auto image = GH.renderHandler().loadImage(AnimationPath::builtin("ITPA"), t->getTown()->clientInfo.icons[t->hasFort()][false] + 2, 0, EImageBlitMode::OPAQUE);
 				image->scaleTo(Point(35, 23));
 				images.push_back(image);
 			}
 		}
 	}
 
-	auto gateIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, BuildingID::CASTLE_GATE);//will be deleted by selection window
+	auto gateIcon = std::make_shared<CAnimImage>(town->getTown()->clientInfo.buildingsIcons, BuildingID::CASTLE_GATE);//will be deleted by selection window
 	auto wnd = std::make_shared<CObjectListWindow>(availableTowns, gateIcon, CGI->generaltexth->jktexts[40],
 		CGI->generaltexth->jktexts[41], std::bind (&CCastleInterface::castleTeleport, LOCPLINT->castleInt, _1), 0, images);
 	wnd->onPopup = [availableTowns](int index) { CRClickPopup::createAndPush(LOCPLINT->cb->getObjInstance(ObjectInstanceID(availableTowns[index])), GH.getCursorPosition()); };
@@ -940,7 +940,7 @@ void CCastleBuildings::enterDwelling(int level)
 	if (level < 0 || level >= town->creatures.size() || town->creatures[level].second.empty())
 	{
 		assert(0);
-		logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->town->faction->getNameTranslated());
+		logGlobal->error("Attempt to enter into invalid dwelling of level %d in town %s (%s)", level, town->getNameTranslated(), town->getFaction()->getNameTranslated());
 		return;
 	}
 
@@ -954,8 +954,8 @@ void CCastleBuildings::enterDwelling(int level)
 void CCastleBuildings::enterToTheQuickRecruitmentWindow()
 {
 	const auto beginIt = town->creatures.cbegin();
-	const auto afterLastIt = town->creatures.size() > town->town->creatures.size()
-		? std::next(beginIt, town->town->creatures.size())
+	const auto afterLastIt = town->creatures.size() > town->getTown()->creatures.size()
+		? std::next(beginIt, town->getTown()->creatures.size())
 		: town->creatures.cend();
 	const auto hasSomeoneToRecruit = std::any_of(beginIt, afterLastIt,
 		[](const auto & creatureInfo) { return creatureInfo.first > 0; });
@@ -967,8 +967,8 @@ 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>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), building)));
-	std::string descr = town->town->buildings.find(building)->second->getDescriptionTranslated();
+	std::vector<std::shared_ptr<CComponent>> comps(1, std::make_shared<CComponent>(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), building)));
+	std::string descr = town->getTown()->buildings.find(building)->second->getDescriptionTranslated();
 	std::string hasNotProduced;
 	std::string hasProduced;
 
@@ -977,10 +977,10 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID:
 
 	bool isMysticPondOrItsUpgrade = subID == BuildingSubID::MYSTIC_POND
 		|| (upgrades != BuildingID::NONE
-			&& town->town->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND);
+			&& town->getTown()->buildings.find(BuildingID(upgrades))->second->subId == BuildingSubID::MYSTIC_POND);
 
 	if(upgrades != BuildingID::NONE)
-		descr += "\n\n"+town->town->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated();
+		descr += "\n\n"+town->getTown()->buildings.find(BuildingID(upgrades))->second->getDescriptionTranslated();
 
 	if(isMysticPondOrItsUpgrade) //for vanila Rampart like towns
 	{
@@ -1056,7 +1056,7 @@ void CCastleBuildings::enterTownHall()
 
 void CCastleBuildings::openMagesGuild()
 {
-	auto mageGuildBackground = LOCPLINT->castleInt->town->town->clientInfo.guildBackground;
+	auto mageGuildBackground = LOCPLINT->castleInt->town->getTown()->clientInfo.guildBackground;
 	GH.windows().createAndPushWindow<CMageGuildScreen>(LOCPLINT->castleInt, mageGuildBackground);
 }
 
@@ -1247,7 +1247,7 @@ CTownInfo::CTownInfo(int posX, int posY, const CGTownInstance * Town, bool townH
 			return;//FIXME: suspicious statement, fix or comment
 		picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITMCL.DEF"), town->fortLevel()-1);
 	}
-	building = town->town->buildings.at(BuildingID(buildID));
+	building = town->getTown()->buildings.at(BuildingID(buildID));
 	pos = picture->pos;
 }
 
@@ -1322,7 +1322,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 	recreateIcons();
 	if (!from)
 		adventureInt->onAudioPaused();
-	CCS->musich->playMusicFromSet("faction", town->town->faction->getJsonKey(), true, false);
+	CCS->musich->playMusicFromSet("faction", town->getFaction()->getJsonKey(), true, false);
 }
 
 CCastleInterface::~CCastleInterface()
@@ -1403,7 +1403,7 @@ void CCastleInterface::removeBuilding(BuildingID bid)
 void CCastleInterface::recreateIcons()
 {
 	OBJECT_CONSTRUCTION;
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	icon->setFrame(iconIndex);
 	TResources townIncome = town->dailyIncome();
@@ -1425,8 +1425,8 @@ void CCastleInterface::recreateIcons()
 		if(town->hasBuilt(BuildingID::TAVERN))
 			LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 	}, [this]{
-		if(!town->town->faction->getDescriptionTranslated().empty())
-			CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
+		if(!town->getFaction()->getDescriptionTranslated().empty())
+			CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
 	});
 
 	creainfo.clear();
@@ -1527,7 +1527,7 @@ CHallInterface::CBuildingBox::CBuildingBox(int x, int y, const CGTownInstance *
 		-1, -1, -1, 0, 0, 1, 2, -1, 1, 1, -1, -1
 	};
 
-	icon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, building->bid, 0, 2, 2);
+	icon = std::make_shared<CAnimImage>(town->getTown()->clientInfo.buildingsIcons, building->bid, 0, 2, 2);
 	header = std::make_shared<CAnimImage>(AnimationPath::builtin("TPTHBAR"), panelIndex[static_cast<int>(state)], 0, 1, 73);
 	if(iconIndex[static_cast<int>(state)] >=0)
 		mark = std::make_shared<CAnimImage>(AnimationPath::builtin("TPTHCHK"), iconIndex[static_cast<int>(state)], 0, 136, 56);
@@ -1569,7 +1569,7 @@ void CHallInterface::CBuildingBox::showPopupWindow(const Point & cursorPosition)
 }
 
 CHallInterface::CHallInterface(const CGTownInstance * Town):
-	CWindowObject(PLAYER_COLORED | BORDERED, Town->town->clientInfo.hallBackground),
+	CWindowObject(PLAYER_COLORED | BORDERED, Town->getTown()->clientInfo.hallBackground),
 	town(Town)
 {
 	OBJECT_CONSTRUCTION;
@@ -1581,10 +1581,10 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
 	auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), barRect, 5, 556);
 	statusbar = CGStatusBar::create(statusbarBackground);
 
-	title = std::make_shared<CLabel>(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->town->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated());
+	title = std::make_shared<CLabel>(399, 12, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, town->getTown()->buildings.at(BuildingID(town->hallLevel()+BuildingID::VILLAGE_HALL))->getNameTranslated());
 	exit = std::make_shared<CButton>(Point(748, 556), AnimationPath::builtin("TPMAGE1.DEF"), CButton::tooltip(CGI->generaltexth->hcommands[8]), [&](){close();}, EShortcut::GLOBAL_RETURN);
 
-	auto & boxList = town->town->clientInfo.hallSlots;
+	auto & boxList = town->getTown()->clientInfo.hallSlots;
 	boxes.resize(boxList.size());
 	for(size_t row=0; row<boxList.size(); row++) //for each row
 	{
@@ -1595,11 +1595,11 @@ CHallInterface::CHallInterface(const CGTownInstance * Town):
 			{
 				if (!buildingID.hasValue())
 				{
-					logMod->warn("Invalid building ID found in hallSlots of town '%s'", town->town->faction->getJsonKey() );
+					logMod->warn("Invalid building ID found in hallSlots of town '%s'", town->getFaction()->getJsonKey() );
 					continue;
 				}
 
-				const CBuilding * current = town->town->buildings.at(buildingID);
+				const CBuilding * current = town->getTown()->buildings.at(buildingID);
 				if(town->hasBuilt(buildingID))
 				{
 					building = current;
@@ -1629,7 +1629,7 @@ CBuildWindow::CBuildWindow(const CGTownInstance *Town, const CBuilding * Buildin
 {
 	OBJECT_CONSTRUCTION;
 
-	icon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, building->bid, 0, 125, 50);
+	icon = std::make_shared<CAnimImage>(town->getTown()->clientInfo.buildingsIcons, building->bid, 0, 125, 50);
 	auto statusbarBackground = std::make_shared<CPicture>(background->getSurface(), Rect(8, pos.h - 26, pos.w - 16, 19), 8, pos.h - 26);
 	statusbar = CGStatusBar::create(statusbarBackground);
 
@@ -1711,7 +1711,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state)
 		{
 			auto toStr = [&](const BuildingID build) -> std::string
 			{
-				return town->town->buildings.at(build)->getNameTranslated();
+				return town->getTown()->buildings.at(build)->getNameTranslated();
 			};
 
 			ret = CGI->generaltexth->allTexts[52];
@@ -1721,7 +1721,7 @@ std::string CBuildWindow::getTextForState(EBuildingState state)
 	case EBuildingState::MISSING_BASE:
 		{
 			std::string msg = CGI->generaltexth->translate("vcmi.townHall.missingBase");
-			ret = boost::str(boost::format(msg) % town->town->buildings.at(building->upgrade)->getNameTranslated());
+			ret = boost::str(boost::format(msg) % town->getTown()->buildings.at(building->upgrade)->getNameTranslated());
 			break;
 		}
 	}
@@ -1780,11 +1780,11 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
 {
 	OBJECT_CONSTRUCTION;
 	ui32 fortSize = static_cast<ui32>(town->creatures.size());
-	if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty())
+	if(fortSize > town->getTown()->creatures.size() && town->creatures.back().second.empty())
 		fortSize--;
 	fortSize = std::min(fortSize, static_cast<ui32>(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning
 
-	const CBuilding * fortBuilding = town->town->buildings.at(BuildingID(town->fortLevel()+6));
+	const CBuilding * fortBuilding = town->getTown()->buildings.at(BuildingID(town->fortLevel()+6));
 	title = std::make_shared<CLabel>(400, 12, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE, fortBuilding->getNameTranslated());
 
 	std::string text = boost::str(boost::format(CGI->generaltexth->fcommands[6]) % fortBuilding->getNameTranslated());
@@ -1810,7 +1810,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
 	for(ui32 i=0; i<fortSize; i++)
 	{
 		BuildingID buildingID;
-		if(fortSize == town->town->creatures.size())
+		if(fortSize == town->getTown()->creatures.size())
 		{
 			BuildingID dwelling = BuildingID::getDwellingFromLevel(i, 1);
 
@@ -1839,7 +1839,7 @@ CFortScreen::CFortScreen(const CGTownInstance * town):
 ImagePath CFortScreen::getBgName(const CGTownInstance * town)
 {
 	ui32 fortSize = static_cast<ui32>(town->creatures.size());
-	if(fortSize > town->town->creatures.size() && town->creatures.back().second.empty())
+	if(fortSize > town->getTown()->creatures.size() && town->creatures.back().second.empty())
 		fortSize--;
 	fortSize = std::min(fortSize, static_cast<ui32>(GameConstants::CREATURES_PER_TOWN)); // for 8 creatures + portal of summoning
 
@@ -1877,7 +1877,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 
 	if(getMyBuilding() != nullptr)
 	{
-		buildingIcon = std::make_shared<CAnimImage>(town->town->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21);
+		buildingIcon = std::make_shared<CAnimImage>(town->getTown()->clientInfo.buildingsIcons, getMyBuilding()->bid, 0, 4, 21);
 		buildingName = std::make_shared<CLabel>(78, 101, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, getMyBuilding()->getNameTranslated(), 152);
 
 		if(town->hasBuilt(getMyBuilding()->bid))
@@ -1913,8 +1913,8 @@ const CCreature * CFortScreen::RecruitArea::getMyCreature()
 {
 	if(!town->creatures.at(level).second.empty()) // built
 		return town->creatures.at(level).second.back().toCreature();
-	if(!town->town->creatures.at(level).empty()) // there are creatures on this level
-		return town->town->creatures.at(level).front().toCreature();
+	if(!town->getTown()->creatures.at(level).empty()) // there are creatures on this level
+		return town->getTown()->creatures.at(level).front().toCreature();
 	return nullptr;
 }
 
@@ -1922,17 +1922,17 @@ const CBuilding * CFortScreen::RecruitArea::getMyBuilding()
 {
 	BuildingID myID = BuildingID(BuildingID::getDwellingFromLevel(level, 0));
 
-	if (level == town->town->creatures.size())
-		return town->town->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING);
+	if (level == town->getTown()->creatures.size())
+		return town->getTown()->getSpecialBuilding(BuildingSubID::PORTAL_OF_SUMMONING);
 
-	if (!town->town->buildings.count(myID))
+	if (!town->getTown()->buildings.count(myID))
 		return nullptr;
 
-	const CBuilding * build = town->town->buildings.at(myID);
-	while (town->town->buildings.count(myID))
+	const CBuilding * build = town->getTown()->buildings.at(myID);
+	while (town->getTown()->buildings.count(myID))
 	{
 		if (town->hasBuilt(myID))
-			build = town->town->buildings.at(myID);
+			build = town->getTown()->buildings.at(myID);
 		BuildingID::advanceDwelling(myID);
 	}
 
@@ -1972,7 +1972,7 @@ CMageGuildScreen::CMageGuildScreen(CCastleInterface * owner, const ImagePath & i
 {
 	OBJECT_CONSTRUCTION;
 
-	window = std::make_shared<CPicture>(owner->town->town->clientInfo.guildWindow, 332, 76);
+	window = std::make_shared<CPicture>(owner->town->getTown()->clientInfo.guildWindow, 332, 76);
 
 	resdatabar = std::make_shared<CMinorResDataBar>();
 	resdatabar->moveBy(pos.topLeft(), true);
@@ -2007,7 +2007,7 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
 
 	const CGTownInstance * town = LOCPLINT->cb->getTown(townId);
 
-	for(size_t i=0; i<town->town->mageLevel; i++)
+	for(size_t i=0; i<town->getTown()->mageLevel; i++)
 	{
 		size_t spellCount = town->spellsAtLevel((int)i+1,false); //spell at level with -1 hmmm?
 		for(size_t j=0; j<spellCount; j++)

+ 2 - 2
client/windows/CExchangeWindow.cpp

@@ -84,7 +84,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		for(int m=0; m < hero->secSkills.size(); ++m)
 			secSkillIcons[leftRight].push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("SECSK32"), 0, 0, 32 + 36 * m + 454 * leftRight, qeLayout ? 83 : 88));
 
-		specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->type->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45);
+		specImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("UN32"), hero->getHeroType()->imageIndex, 0, 67 + 490 * leftRight, qeLayout ? 41 : 45);
 
 		expImages[leftRight] = std::make_shared<CAnimImage>(AnimationPath::builtin("PSKIL32"), 4, 0, 103 + 490 * leftRight, qeLayout ? 41 : 45);
 		expValues[leftRight] = std::make_shared<CLabel>(119 + 490 * leftRight, qeLayout ? 66 : 71, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
@@ -151,7 +151,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
 		specialtyAreas[b] = std::make_shared<LRClickableAreaWText>();
 		specialtyAreas[b]->pos = Rect(Point(pos.x + 69 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));
 		specialtyAreas[b]->hoverText = CGI->generaltexth->heroscrn[27];
-		specialtyAreas[b]->text = hero->type->getSpecialtyDescriptionTranslated();
+		specialtyAreas[b]->text = hero->getHeroType()->getSpecialtyDescriptionTranslated();
 
 		experienceAreas[b] = std::make_shared<LRClickableAreaWText>();
 		experienceAreas[b]->pos = Rect(Point(pos.x + 105 + 490 * b, pos.y + (qeLayout ? 41 : 45)), Point(32, 32));

+ 3 - 3
client/windows/CHeroWindow.cpp

@@ -184,9 +184,9 @@ void CHeroWindow::update()
 	name->setText(curHero->getNameTranslated());
 	title->setText((boost::format(CGI->generaltexth->allTexts[342]) % curHero->level % curHero->getClassNameTranslated()).str());
 
-	specArea->text = curHero->type->getSpecialtyDescriptionTranslated();
-	specImage->setFrame(curHero->type->imageIndex);
-	specName->setText(curHero->type->getSpecialtyNameTranslated());
+	specArea->text = curHero->getHeroType()->getSpecialtyDescriptionTranslated();
+	specImage->setFrame(curHero->getHeroType()->imageIndex);
+	specName->setText(curHero->getHeroType()->getSpecialtyNameTranslated());
 
 	tacticsButton = std::make_shared<CToggleButton>(Point(539, 483), AnimationPath::builtin("hsbtns8.def"), std::make_pair(heroscrn[26], heroscrn[31]), 0, EShortcut::HERO_TOGGLE_TACTICS);
 	tacticsButton->addHoverText(EButtonState::HIGHLIGHTED, CGI->generaltexth->heroscrn[25]);

+ 4 - 4
client/windows/CKingdomInterface.cpp

@@ -300,7 +300,7 @@ int InfoBoxHeroData::getSubID()
 		else
 			return 0;
 	case HERO_SPECIAL:
-		return hero->type->getIndex();
+		return hero->getHeroTypeID().getNum();
 	case HERO_MANA:
 	case HERO_EXPERIENCE:
 		return 0;
@@ -800,7 +800,7 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 	garr = std::make_shared<CGarrisonInt>(Point(313, 3), 4, Point(232,0), town->getUpperArmy(), town->visitingHero, true, true, CGarrisonInt::ESlotsLayout::TWO_ROWS);
 	heroes = std::make_shared<HeroSlots>(town, Point(244,6), Point(475,6), garr, false);
 
-	size_t iconIndex = town->town->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
+	size_t iconIndex = town->getTown()->clientInfo.icons[town->hasFort()][town->built >= LOCPLINT->cb->getSettings().getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
 	picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPT"), iconIndex, 0, 5, 6);
 	openTown = std::make_shared<LRClickableAreaOpenTown>(Rect(5, 6, 58, 64), town);
@@ -823,8 +823,8 @@ CTownItem::CTownItem(const CGTownInstance * Town)
 		if(town->hasBuilt(BuildingID::TAVERN))
 			LOCPLINT->showTavernWindow(town, nullptr, QueryID::NONE);
 	}, [&]{
-		if(!town->town->faction->getDescriptionTranslated().empty())
-			CRClickPopup::createAndPush(town->town->faction->getDescriptionTranslated());
+		if(!town->getTown()->faction->getDescriptionTranslated().empty())
+			CRClickPopup::createAndPush(town->getFaction()->getDescriptionTranslated());
 	});
 	fastMarket = std::make_shared<LRClickableArea>(Rect(153, 6, 65, 64), []()
 	{

+ 1 - 1
client/windows/CMarketWindow.cpp

@@ -192,7 +192,7 @@ std::string CMarketWindow::getMarketTitle(const ObjectInstanceID marketId, const
 	{
 		for(const auto & buildingId : town->getBuildings())
 		{
-			if(const auto building = town->town->buildings.at(buildingId); vstd::contains(building->marketModes, mode))
+			if(const auto building = town->getTown()->buildings.at(buildingId); vstd::contains(building->marketModes, mode))
 				return building->getNameTranslated();
 		}
 	}

+ 5 - 5
client/windows/GUIClasses.cpp

@@ -522,9 +522,9 @@ CTavernWindow::CTavernWindow(const CGObjectInstance * TavernObj, const std::func
 			recruit->block(true);
 	}
 	if(LOCPLINT->castleInt)
-		videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), LOCPLINT->castleInt->town->town->clientInfo.tavernVideo, false);
+		videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), LOCPLINT->castleInt->town->getTown()->clientInfo.tavernVideo, false);
 	else if(const auto * townObj = dynamic_cast<const CGTownInstance *>(TavernObj))
-		videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), townObj->town->clientInfo.tavernVideo, false);
+		videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), townObj->getTown()->clientInfo.tavernVideo, false);
 	else
 		videoPlayer = std::make_shared<VideoWidget>(Point(70, 56), VideoPath::builtin("TAVERN.BIK"), false);
 
@@ -548,7 +548,7 @@ void CTavernWindow::addInvite()
 
 	if(!inviteableHeroes.empty())
 	{
-		int imageIndex = heroToInvite ? (*CGI->heroh)[heroToInvite->getHeroType()]->imageIndex : 156; // 156 => special id for random
+		int imageIndex = heroToInvite ? heroToInvite->getIconIndex() : 156; // 156 => special id for random
 		if(!heroToInvite)
 			heroToInvite = (*RandomGeneratorUtil::nextItem(inviteableHeroes, CRandomGenerator::getDefault())).second;
 
@@ -563,7 +563,7 @@ void CTavernWindow::recruitb()
 	const CGHeroInstance *toBuy = (selected ? h2 : h1)->h;
 	const CGObjectInstance *obj = tavernObj;
 
-	LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroType() : HeroTypeID::NONE);
+	LOCPLINT->cb->recruitHero(obj, toBuy, heroToInvite ? heroToInvite->getHeroTypeID() : HeroTypeID::NONE);
 	close();
 }
 
@@ -963,7 +963,7 @@ CUniversityWindow::CUniversityWindow(const CGHeroInstance * _hero, BuildingID bu
 
 	if(auto town = dynamic_cast<const CGTownInstance *>(_market))
 	{
-		auto faction = town->town->faction->getId();
+		auto faction = town->getTown()->faction->getId();
 		titlePic = std::make_shared<CAnimImage>((*CGI->townh)[faction]->town->clientInfo.buildingsIcons, building);
 	}
 	else if(auto uni = dynamic_cast<const CGUniversity *>(_market); uni->appearance)

+ 5 - 5
client/windows/QuickRecruitmentWindow.cpp

@@ -51,9 +51,9 @@ void QuickRecruitmentWindow::setCreaturePurchaseCards()
 {
 	int availableAmount = getAvailableCreatures();
 	Point position = Point((pos.w - 100*availableAmount - 8*(availableAmount-1))/2,64);
-	for (int i = 0; i < town->town->creatures.size(); i++)
+	for (int i = 0; i < town->getTown()->creatures.size(); i++)
 	{
-		if(!town->town->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first)
+		if(!town->getTown()->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first)
 		{
 			cards.push_back(std::make_shared<CreaturePurchaseCard>(town->creatures[i].second, position, town->creatures[i].first, this));
 			position.x += 108;
@@ -108,7 +108,7 @@ void QuickRecruitmentWindow::purchaseUnits()
 		{
 			int level = 0;
 			int i = 0;
-			for(auto c : town->town->creatures)
+			for(auto c : town->getTown()->creatures)
 			{
 				for(auto c2 : c)
 					if(c2 == selected->creatureOnTheCard->getId())
@@ -129,8 +129,8 @@ void QuickRecruitmentWindow::purchaseUnits()
 int QuickRecruitmentWindow::getAvailableCreatures()
 {
 	int creaturesAmount = 0;
-	for (int i=0; i< town->town->creatures.size(); i++)
-		if(!town->town->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first)
+	for (int i=0; i< town->getTown()->creatures.size(); i++)
+		if(!town->getTown()->creatures.at(i).empty() && !town->creatures.at(i).second.empty() && town->creatures[i].first)
 			creaturesAmount++;
 	return creaturesAmount;
 }

+ 1 - 1
include/vcmi/Entity.h

@@ -26,7 +26,7 @@ class DLL_LINKAGE INativeTerrainProvider
 {
 public:
 	virtual TerrainId getNativeTerrain() const = 0;
-	virtual FactionID getFaction() const = 0;
+	virtual FactionID getFactionID() const = 0;
 	virtual bool isNativeTerrain(TerrainId terrain) const;
 };
 

+ 1 - 1
lib/BasicTypes.cpp

@@ -38,7 +38,7 @@ TerrainId AFactionMember::getNativeTerrain() const
 	//this code is used in the CreatureTerrainLimiter::limit to setup battle bonuses
 	//and in the CGHeroInstance::getNativeTerrain() to setup movement bonuses or/and penalties.
 	return getBonusBearer()->hasBonus(selectorNoTerrainPenalty, cachingStringNoTerrainPenalty)
-		? TerrainId::ANY_TERRAIN : VLC->factions()->getById(getFaction())->getNativeTerrain();
+			 ? TerrainId::ANY_TERRAIN : getFactionID().toEntity(VLC)->getNativeTerrain();
 }
 
 int32_t AFactionMember::magicResistance() const

+ 1 - 1
lib/CCreatureHandler.cpp

@@ -117,7 +117,7 @@ int32_t CCreature::getHorde() const
 	return hordeGrowth;
 }
 
-FactionID CCreature::getFaction() const
+FactionID CCreature::getFactionID() const
 {
 	return FactionID(faction);
 }

+ 1 - 1
lib/CCreatureHandler.h

@@ -127,7 +127,7 @@ public:
 	std::string getNamePluralTextID() const override;
 	std::string getNameSingularTextID() const override;
 
-	FactionID getFaction() const override;
+	FactionID getFactionID() const override;
 	int32_t getIndex() const override;
 	int32_t getIconIndex() const override;
 	std::string getJsonKey() const override;

+ 2 - 2
lib/CCreatureSet.cpp

@@ -912,10 +912,10 @@ void CStackInstance::serializeJson(JsonSerializeFormat & handler)
 	}
 }
 
-FactionID CStackInstance::getFaction() const
+FactionID CStackInstance::getFactionID() const
 {
 	if(type)
-		return type->getFaction();
+		return type->getFactionID();
 		
 	return FactionID::NEUTRAL;
 }

+ 1 - 1
lib/CCreatureSet.h

@@ -106,7 +106,7 @@ public:
 	//IConstBonusProvider
 	const IBonusBearer* getBonusBearer() const override;
 	//INativeTerrainProvider
-	FactionID getFaction() const override;
+	FactionID getFactionID() const override;
 
 	virtual ui64 getPower() const;
 	CCreature::CreatureQuantityId getQuantityID() const;

+ 3 - 3
lib/CGameInfoCallback.cpp

@@ -381,7 +381,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 
 			for(const auto & creature : VLC->creh->objects)
 			{
-				if(creature->getFaction() == factionIndex && static_cast<int>(creature->getAIValue()) > maxAIValue)
+				if(creature->getFactionID() == factionIndex && static_cast<int>(creature->getAIValue()) > maxAIValue)
 				{
 					maxAIValue = creature->getAIValue();
 					mostStrong = creature.get();
@@ -575,10 +575,10 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu
 {
 	ERROR_RET_VAL_IF(!canGetFullInfo(t), "Town is not owned!", EBuildingState::TOWN_NOT_OWNED);
 
-	if(!t->town->buildings.count(ID))
+	if(!t->getTown()->buildings.count(ID))
 		return EBuildingState::BUILDING_ERROR;
 
-	const CBuilding * building = t->town->buildings.at(ID);
+	const CBuilding * building = t->getTown()->buildings.at(ID);
 
 
 	if(t->hasBuilt(ID))	//already built

+ 2 - 2
lib/battle/CUnitState.cpp

@@ -416,9 +416,9 @@ int32_t CUnitState::creatureIconIndex() const
 	return unitType()->getIconIndex();
 }
 
-FactionID CUnitState::getFaction() const
+FactionID CUnitState::getFactionID() const
 {
-	return unitType()->getFaction();
+	return unitType()->getFactionID();
 }
 
 int32_t CUnitState::getCasterUnitId() const

+ 1 - 1
lib/battle/CUnitState.h

@@ -253,7 +253,7 @@ public:
 	void localInit(const IUnitEnvironment * env_);
 	void serializeJson(JsonSerializeFormat & handler);
 
-	FactionID getFaction() const override;
+	FactionID getFactionID() const override;
 
 	void afterAttack(bool ranged, bool counter);
 

+ 3 - 3
lib/bonuses/Limiters.cpp

@@ -300,15 +300,15 @@ ILimiter::EDecision FactionLimiter::limit(const BonusLimitationContext &context)
 	if(bearer)
 	{
 		if(faction != FactionID::DEFAULT)
-			return bearer->getFaction() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+			return bearer->getFactionID() == faction ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 
 		switch(context.b.source)
 		{
 			case BonusSource::CREATURE_ABILITY:
-				return bearer->getFaction() == context.b.sid.as<CreatureID>().toCreature()->getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+				return bearer->getFactionID() == context.b.sid.as<CreatureID>().toCreature()->getFactionID() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 			
 			case BonusSource::TOWN_STRUCTURE:
-				return bearer->getFaction() == context.b.sid.as<BuildingTypeUniqueID>().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
+				return bearer->getFactionID() == context.b.sid.as<BuildingTypeUniqueID>().getFaction() ? ILimiter::EDecision::ACCEPT : ILimiter::EDecision::DISCARD;
 
 			//TODO: other sources of bonuses
 		}

+ 4 - 4
lib/campaign/CampaignState.cpp

@@ -351,14 +351,14 @@ void CampaignState::setCurrentMapAsConquered(std::vector<CGHeroInstance *> heroe
 	{
 		JsonNode node = CampaignState::crossoverSerialize(hero);
 
-		if (reservedHeroes.count(hero->getHeroType()))
+		if (reservedHeroes.count(hero->getHeroTypeID()))
 		{
-			logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroType(), hero->getNameTranslated());
-			globalHeroPool[hero->getHeroType()] = node;
+			logGlobal->info("Hero crossover: %d (%s) exported to global pool", hero->getHeroTypeID(), hero->getNameTranslated());
+			globalHeroPool[hero->getHeroTypeID()] = node;
 		}
 		else
 		{
-			logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->getHeroType(), hero->getNameTranslated());
+			logGlobal->info("Hero crossover: %d (%s) exported to scenario pool", hero->getHeroTypeID(), hero->getNameTranslated());
 			scenarioHeroPool[*currentMap].push_back(node);
 		}
 	}

+ 1 - 1
lib/entities/faction/CFaction.cpp

@@ -92,7 +92,7 @@ FactionID CFaction::getId() const
 	return FactionID(index);
 }
 
-FactionID CFaction::getFaction() const
+FactionID CFaction::getFactionID() const
 {
 	return FactionID(index);
 }

+ 1 - 1
lib/entities/faction/CFaction.h

@@ -39,7 +39,7 @@ class DLL_LINKAGE CFaction : public Faction
 
 	FactionID index = FactionID::NEUTRAL;
 
-	FactionID getFaction() const override; //This function should not be used
+	FactionID getFactionID() const override; //This function should not be used
 
 public:
 	TerrainId nativeTerrain;

+ 15 - 20
lib/gameState/CGameState.cpp

@@ -599,7 +599,7 @@ void CGameState::initHeroes()
 		}
 
 		hero->initHero(getRandomGenerator());
-		map->allHeroes[hero->getHeroType().getNum()] = hero;
+		map->allHeroes[hero->getHeroTypeID().getNum()] = hero;
 	}
 
 	// generate boats for all heroes on water
@@ -629,20 +629,20 @@ void CGameState::initHeroes()
 		{
 			auto * hero = dynamic_cast<CGHeroInstance*>(obj.get());
 			hero->initHero(getRandomGenerator());
-			map->allHeroes[hero->getHeroType().getNum()] = hero;
+			map->allHeroes[hero->getHeroTypeID().getNum()] = 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, ph->getHeroType()))
+		if(!vstd::contains(heroesToCreate, ph->getHeroTypeID()))
 			continue;
 		ph->initHero(getRandomGenerator());
 		heroesPool->addHeroToPool(ph);
-		heroesToCreate.erase(ph->type->getId());
+		heroesToCreate.erase(ph->getHeroTypeID());
 
-		map->allHeroes[ph->getHeroType().getNum()] = ph;
+		map->allHeroes[ph->getHeroTypeID().getNum()] = ph;
 	}
 
 	for(const HeroTypeID & htype : heroesToCreate) //all not used allowed heroes go with default state into the pool
@@ -756,12 +756,12 @@ void CGameState::initTownNames()
 
 	for(auto & vti : map->towns)
 	{
-		assert(vti->town);
+		assert(vti->getTown());
 
 		if(!vti->getNameTextID().empty())
 			continue;
 
-		FactionID faction = vti->getFaction();
+		FactionID faction = vti->getFactionID();
 
 		if(availableNames.empty())
 		{
@@ -798,8 +798,8 @@ void CGameState::initTowns()
 
 	for (auto & vti : map->towns)
 	{
-		assert(vti->town);
-		assert(vti->town->creatures.size() <= GameConstants::CREATURES_PER_TOWN); 
+		assert(vti->getTown());
+		assert(vti->getTown()->creatures.size() <= GameConstants::CREATURES_PER_TOWN);
 
 		constexpr std::array basicDwellings = { BuildingID::DWELL_FIRST, BuildingID::DWELL_LVL_2, BuildingID::DWELL_LVL_3, BuildingID::DWELL_LVL_4, BuildingID::DWELL_LVL_5, BuildingID::DWELL_LVL_6, BuildingID::DWELL_LVL_7, BuildingID::DWELL_LVL_8 };
 		constexpr std::array upgradedDwellings = { BuildingID::DWELL_UP_FIRST, BuildingID::DWELL_LVL_2_UP, BuildingID::DWELL_LVL_3_UP, BuildingID::DWELL_LVL_4_UP, BuildingID::DWELL_LVL_5_UP, BuildingID::DWELL_LVL_6_UP, BuildingID::DWELL_LVL_7_UP, BuildingID::DWELL_LVL_8_UP };
@@ -828,7 +828,7 @@ void CGameState::initTowns()
 		vti->addBuilding(BuildingID::VILLAGE_HALL);
 
 		//init hordes
-		for (int i = 0; i < vti->town->creatures.size(); i++)
+		for (int i = 0; i < vti->getTown()->creatures.size(); i++)
 		{
 			if(vti->hasBuilt(hordes[i])) //if we have horde for this level
 			{
@@ -894,7 +894,7 @@ void CGameState::initTowns()
 			int sel = -1;
 
 			for(ui32 ps=0;ps<vti->possibleSpells.size();ps++)
-				total += vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction());
+				total += vti->possibleSpells[ps].toSpell()->getProbability(vti->getFactionID());
 
 			if (total == 0) // remaining spells have 0 probability
 				break;
@@ -902,7 +902,7 @@ void CGameState::initTowns()
 			auto r = getRandomGenerator().nextInt(total - 1);
 			for(ui32 ps=0; ps<vti->possibleSpells.size();ps++)
 			{
-				r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFaction());
+				r -= vti->possibleSpells[ps].toSpell()->getProbability(vti->getFactionID());
 				if(r<0)
 				{
 					sel = ps;
@@ -1655,18 +1655,13 @@ std::set<HeroTypeID> CGameState::getUnusedAllowedHeroes(bool alsoIncludeNotAllow
 	}
 
 	for(auto hero : map->heroesOnMap)  //heroes instances initialization
-	{
-		if(hero->type)
-			ret -= hero->type->getId();
-		else
-			ret -= hero->getHeroType();
-	}
+		ret -= hero->getHeroTypeID();
 
 	for(auto obj : map->objects) //prisons
 	{
 		auto * hero = dynamic_cast<const CGHeroInstance *>(obj.get());
 		if(hero && hero->ID == Obj::PRISON)
-			ret -= hero->getHeroType();
+			ret -= hero->getHeroTypeID();
 	}
 
 	return ret;
@@ -1690,7 +1685,7 @@ CGHeroInstance * CGameState::getUsedHero(const HeroTypeID & hid) const
 		auto * hero = dynamic_cast<CGHeroInstance *>(obj.get());
 		assert(hero);
 
-		if (hero->getHeroType() == hid)
+		if (hero->getHeroTypeID() == hid)
 			return hero;
 	}
 

+ 8 - 8
lib/gameState/CGameStateCampaign.cpp

@@ -86,7 +86,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr
 					.And(Selector::subtype()(BonusSubtypeID(g)))
 					.And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL));
 
-				hero.hero->getLocalBonus(sel)->val = hero.hero->type->heroClass->primarySkillInitial[g.getNum()];
+				hero.hero->getLocalBonus(sel)->val = hero.hero->getHeroClass()->primarySkillInitial[g.getNum()];
 			}
 		}
 	}
@@ -96,7 +96,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr
 		//trimming sec skills
 		for(auto & hero : campaignHeroReplacements)
 		{
-			hero.hero->secSkills = hero.hero->type->secSkillsInit;
+			hero.hero->secSkills = hero.hero->getHeroType()->secSkillsInit;
 			hero.hero->recreateSecondarySkillsBonuses();
 		}
 	}
@@ -240,7 +240,7 @@ void CGameStateCampaign::placeCampaignHeroes()
 
 	for(auto & replacement : campaignHeroReplacements)
 		if (replacement.heroPlaceholderId.hasValue())
-			heroesToRemove.insert(replacement.hero->getHeroType());
+			heroesToRemove.insert(replacement.hero->getHeroTypeID());
 
 	for(auto & heroID : heroesToRemove)
 	{
@@ -369,8 +369,8 @@ void CGameStateCampaign::replaceHeroesPlaceholders()
 		if(heroPlaceholder->tempOwner.isValidPlayer())
 			heroToPlace->tempOwner = heroPlaceholder->tempOwner;
 		heroToPlace->setAnchorPos(heroPlaceholder->anchorPos());
-		heroToPlace->type = heroToPlace->getHeroType().toHeroType();
-		heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->type->heroClass->getIndex())->getTemplates().front();
+		heroToPlace->setHeroType(heroToPlace->getHeroTypeID());
+		heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, heroToPlace->getHeroTypeID())->getTemplates().front();
 
 		gameState->map->removeBlockVisTiles(heroPlaceholder, true);
 		gameState->map->objects[heroPlaceholder->id.getNum()] = nullptr;
@@ -563,7 +563,7 @@ void CGameStateCampaign::initHeroes()
 		{
 			for (auto & hero : heroes)
 			{
-				if (hero->getHeroType().getNum() == chosenBonus->info1)
+				if (hero->getHeroTypeID().getNum() == chosenBonus->info1)
 				{
 					giveCampaignBonusToHero(hero);
 					break;
@@ -662,7 +662,7 @@ void CGameStateCampaign::initTowns()
 		if(gameState->scenarioOps->campState->formatVCMI())
 			newBuilding = BuildingID(chosenBonus->info1);
 		else
-			newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFaction(), town->getBuildings());
+			newBuilding = CBuildingHandler::campToERMU(chosenBonus->info1, town->getFactionID(), town->getBuildings());
 
 		// Build granted building & all prerequisites - e.g. Mages Guild Lvl 3 should also give Mages Guild Lvl 1 & 2
 		while(true)
@@ -675,7 +675,7 @@ void CGameStateCampaign::initTowns()
 
 			town->addBuilding(newBuilding);
 
-			auto building = town->town->buildings.at(newBuilding);
+			auto building = town->getTown()->buildings.at(newBuilding);
 			newBuilding = building->upgrade;
 		}
 		break;

+ 1 - 1
lib/gameState/GameStatistics.cpp

@@ -381,7 +381,7 @@ float Statistic::getTownBuiltRatio(const PlayerState * ps)
 	for(const auto & t : ps->getTowns())
 	{
 		built += t->getBuildings().size();
-		for(const auto & b : t->town->buildings)
+		for(const auto & b : t->getTown()->buildings)
 			if(!t->forbiddenBuildings.count(b.first))
 				total += 1;
 	}

+ 1 - 1
lib/gameState/InfoAboutArmy.cpp

@@ -115,7 +115,7 @@ void InfoAboutHero::initFromHero(const CGHeroInstance *h, InfoAboutHero::EInfoLe
 
 	initFromArmy(h, detailed);
 
-	hclass = h->type->heroClass;
+	hclass = h->getHeroClass();
 	name = h->getNameTranslated();
 	portraitSource = h->getPortraitSource();
 

+ 4 - 4
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(slot.hero->getHeroType());
+		pool.erase(slot.hero->getHeroTypeID());
 
 	return pool;
 }
@@ -34,7 +34,7 @@ TavernSlotRole TavernHeroesPool::getSlotRole(HeroTypeID hero) const
 {
 	for (auto const & slot : currentTavern)
 	{
-		if (slot.hero->getHeroType() == hero)
+		if (slot.hero->getHeroTypeID() == hero)
 			return slot.role;
 	}
 	return TavernSlotRole::NONE;
@@ -106,7 +106,7 @@ CGHeroInstance * TavernHeroesPool::takeHeroFromPool(HeroTypeID hero)
 	heroesPool.erase(hero);
 
 	vstd::erase_if(currentTavern, [&](const TavernSlot & entry){
-		return entry.hero->type->getId() == hero;
+		return entry.hero->getHeroTypeID() == hero;
 	});
 
 	assert(result);
@@ -138,7 +138,7 @@ void TavernHeroesPool::onNewDay()
 
 void TavernHeroesPool::addHeroToPool(CGHeroInstance * hero)
 {
-	heroesPool[hero->getHeroType()] = hero;
+	heroesPool[hero->getHeroTypeID()] = hero;
 }
 
 void TavernHeroesPool::setAvailability(HeroTypeID hero, std::set<PlayerColor> mask)

+ 1 - 7
lib/mapObjectConstructors/CommonConstructors.cpp

@@ -96,7 +96,6 @@ bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, std
 
 void CTownInstanceConstructor::initializeObject(CGTownInstance * obj) const
 {
-	obj->town = faction->town;
 	obj->tempOwner = PlayerColor::NEUTRAL;
 }
 
@@ -144,7 +143,7 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std
 
 	auto heroTest = [&](const HeroTypeID & id)
 	{
-		return hero->type->getId() == id;
+		return hero->getHeroTypeID() == id;
 	};
 
 	if(filters.count(templ->stringID))
@@ -154,11 +153,6 @@ bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, std
 	return false;
 }
 
-void CHeroInstanceConstructor::initializeObject(CGHeroInstance * obj) const
-{
-	obj->type = nullptr; //FIXME: set to valid value. somehow.
-}
-
 void CHeroInstanceConstructor::randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const
 {
 

+ 0 - 1
lib/mapObjectConstructors/CommonConstructors.h

@@ -81,7 +81,6 @@ public:
 	const CHeroClass * heroClass = nullptr;
 	std::map<std::string, LogicalExpression<HeroTypeID>> filters;
 
-	void initializeObject(CGHeroInstance * object) const override;
 	void randomizeObject(CGHeroInstance * object, vstd::RNG & rng) const override;
 	void afterLoadFinalization() override;
 

+ 1 - 1
lib/mapObjects/CArmedInstance.cpp

@@ -78,7 +78,7 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 		const CStackInstance * inst = slot.second;
 		const auto * creature  = inst->getCreatureID().toEntity(VLC);
 
-		factions.insert(creature->getFaction());
+		factions.insert(creature->getFactionID());
 		// Check for undead flag instead of faction (undead mummies are neutral)
 		if (!hasUndead)
 		{

+ 20 - 15
lib/mapObjects/CGCreature.cpp

@@ -45,7 +45,7 @@ std::string CGCreature::getHoverText(PlayerColor player) const
 	else
 		ms.appendLocalString(EMetaText::ARRAY_TXT, quantityTextIndex);
 	ms.appendRawString(" ");
-	ms.appendNamePlural(getCreature());
+	ms.appendNamePlural(getCreatureID());
 
 	return ms.toString();
 }
@@ -57,7 +57,7 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 		MetaString ms;
 		ms.appendNumber(stacks.begin()->second->count);
 		ms.appendRawString(" ");
-		ms.appendName(getCreature(), stacks.begin()->second->count);
+		ms.appendName(getCreatureID(), stacks.begin()->second->count);
 		return ms.toString();
 	}
 	else
@@ -69,11 +69,11 @@ std::string CGCreature::getHoverText(const CGHeroInstance * hero) const
 std::string CGCreature::getMonsterLevelText() const
 {
 	std::string monsterLevel = VLC->generaltexth->translate("vcmi.adventureMap.monsterLevel");
-	bool isRanged = VLC->creatures()->getById(getCreature())->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER);
+	bool isRanged = getCreature()->getBonusBearer()->hasBonusOfType(BonusType::SHOOTER);
 	std::string attackTypeKey = isRanged ? "vcmi.adventureMap.monsterRangedType" : "vcmi.adventureMap.monsterMeleeType";
 	std::string attackType = VLC->generaltexth->translate(attackTypeKey);
-	boost::replace_first(monsterLevel, "%TOWN", (*VLC->townh)[VLC->creatures()->getById(getCreature())->getFaction()]->getNameTranslated());
-	boost::replace_first(monsterLevel, "%LEVEL", std::to_string(VLC->creatures()->getById(getCreature())->getLevel()));
+	boost::replace_first(monsterLevel, "%TOWN", getCreature()->getFactionID().toEntity(VLC)->getNameTranslated());
+	boost::replace_first(monsterLevel, "%LEVEL", std::to_string(getCreature()->getLevel()));
 	boost::replace_first(monsterLevel, "%ATTACK_TYPE", attackType);
 	return monsterLevel;
 }
@@ -150,7 +150,7 @@ std::string CGCreature::getPopupText(PlayerColor player) const
 std::vector<Component> CGCreature::getPopupComponents(PlayerColor player) const
 {
 	return {
-		Component(ComponentType::CREATURE, getCreature())
+		Component(ComponentType::CREATURE, getCreatureID())
 	};
 }
 
@@ -182,7 +182,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.replaceName(getCreature(), getStackCount(SlotID(0)));
+			ynd.text.replaceName(getCreatureID(), getStackCount(SlotID(0)));
 			cb->showBlockingDialog(this, &ynd);
 			break;
 		}
@@ -197,7 +197,7 @@ 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->creatures()->getById(getCreature())->getNamePluralTranslated());
+			boost::algorithm::replace_first(tmp,"%s",getCreature()->getNamePluralTranslated());
 			ynd.text.appendRawString(tmp);
 			cb->showBlockingDialog(this, &ynd);
 			break;
@@ -205,11 +205,16 @@ void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
 	}
 }
 
-CreatureID CGCreature::getCreature() const
+CreatureID CGCreature::getCreatureID() const
 {
 	return CreatureID(getObjTypeIndex().getNum());
 }
 
+const CCreature * CGCreature::getCreature() const
+{
+	return getCreatureID().toCreature();
+}
+
 void CGCreature::pickRandomObject(vstd::RNG & rand)
 {
 	switch(ID.toEnum())
@@ -279,7 +284,7 @@ void CGCreature::initObj(vstd::RNG & rand)
 
 	stacks[SlotID(0)]->setType(getCreature());
 	TQuantity &amount = stacks[SlotID(0)]->count;
-	const Creature * c = VLC->creatures()->getById(getCreature());
+	const Creature * c = getCreature();
 	if(amount == 0)
 	{
 		amount = rand.nextInt(c->getAdvMapAmountMin(), c->getAdvMapAmountMax());
@@ -353,8 +358,8 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 
 	for(const auto & elem : h->Slots())
 	{
-		bool isOurUpgrade = vstd::contains(getCreature().toCreature()->upgrades, elem.second->getCreatureID());
-		bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreature());
+		bool isOurUpgrade = vstd::contains(getCreature()->upgrades, elem.second->getCreatureID());
+		bool isOurDowngrade = vstd::contains(elem.second->type->upgrades, getCreatureID());
 
 		if(isOurUpgrade || isOurDowngrade)
 			count += elem.second->count;
@@ -380,7 +385,7 @@ int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
 
 		if(diplomacy * 2 + sympathy + 1 >= character)
 		{
-			int32_t recruitCost = VLC->creatures()->getById(getCreature())->getRecruitCost(EGameResID::GOLD);
+			int32_t recruitCost = getCreature()->getRecruitCost(EGameResID::GOLD);
 			int32_t stackCount = getStackCount(SlotID(0));
 			return recruitCost * stackCount; //join for gold
 		}
@@ -493,7 +498,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.replaceName(getCreature(), getStackCount(SlotID(0)));
+	ynd.text.replaceName(getCreatureID(), getStackCount(SlotID(0)));
 	cb->showBlockingDialog(this, &ynd);
 }
 
@@ -513,7 +518,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &
 	{
 		//merge stacks into one
 		TSlots::const_iterator i;
-		const CCreature * cre = getCreature().toCreature();
+		const CCreature * cre = getCreature();
 		for(i = stacks.begin(); i != stacks.end(); i++)
 		{
 			if(cre->isMyUpgrade(i->second->type))

+ 2 - 1
lib/mapObjects/CGCreature.h

@@ -49,7 +49,8 @@ public:
 	void newTurn(vstd::RNG & rand) const override;
 	void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
 	void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override;
-	CreatureID getCreature() const;
+	CreatureID getCreatureID() const;
+	const CCreature * getCreature() const;
 
 	//stack formation depends on position,
 	bool containsUpgradedStack() const;

+ 1 - 1
lib/mapObjects/CGDwelling.cpp

@@ -93,7 +93,7 @@ FactionID CGDwelling::randomizeFaction(vstd::RNG & rand)
 
 		assert(linkedTown->ID == Obj::TOWN);
 		if(linkedTown->ID==Obj::TOWN)
-			return linkedTown->getFaction();
+			return linkedTown->getFactionID();
 	}
 
 	if(!randomizationInfo->allowedFactions.empty())

+ 56 - 57
lib/mapObjects/CGHeroInstance.cpp

@@ -116,9 +116,9 @@ ui32 CGHeroInstance::getTileMovementCost(const TerrainTile & dest, const Terrain
 	return static_cast<ui32>(ret);
 }
 
-FactionID CGHeroInstance::getFaction() const
+FactionID CGHeroInstance::getFactionID() const
 {
-	return FactionID(type->heroClass->faction);
+	return getHeroClass()->faction;
 }
 
 const IBonusBearer* CGHeroInstance::getBonusBearer() const
@@ -229,10 +229,10 @@ bool CGHeroInstance::canLearnSkill(const SecondarySkill & which) const
 	if (getSecSkillLevel(which) > 0)
 		return false;
 
-	if (type->heroClass->secSkillProbability.count(which) == 0)
+	if (getHeroClass()->secSkillProbability.count(which) == 0)
 		return false;
 
-	if (type->heroClass->secSkillProbability.at(which) == 0)
+	if (getHeroClass()->secSkillProbability.at(which) == 0)
 		return false;
 
 	return true;
@@ -282,7 +282,6 @@ int CGHeroInstance::movementPointsLimitCached(bool onLand, const TurnInfo * ti)
 
 CGHeroInstance::CGHeroInstance(IGameCallback * cb)
 	: CArmedInstance(cb),
-	type(nullptr),
 	tacticFormationEnabled(false),
 	inTownGarrison(false),
 	moveDir(4),
@@ -303,14 +302,30 @@ PlayerColor CGHeroInstance::getOwner() const
 	return tempOwner;
 }
 
-HeroTypeID CGHeroInstance::getHeroType() const
+const CHeroClass * CGHeroInstance::getHeroClass() const
 {
+	return getHeroType()->heroClass;
+}
+
+HeroClassID CGHeroInstance::getHeroClassID() const
+{
+	return getHeroType()->heroClass->getId();
+}
+
+const CHero * CGHeroInstance::getHeroType() const
+{
+	return getHeroTypeID().toHeroType();
+}
+
+HeroTypeID CGHeroInstance::getHeroTypeID() const
+{
+	if (ID == Obj::RANDOM_HERO)
+		return HeroTypeID::NONE;
 	return HeroTypeID(getObjTypeIndex().getNum());
 }
 
 void CGHeroInstance::setHeroType(HeroTypeID heroType)
 {
-	assert(type == nullptr);
 	subID = heroType;
 }
 
@@ -323,16 +338,14 @@ void CGHeroInstance::initHero(vstd::RNG & rand, const HeroTypeID & SUBID)
 void CGHeroInstance::initHero(vstd::RNG & rand)
 {
 	assert(validTypes(true));
-	if(!type)
-		type = getHeroType().toHeroType();
-
+	
 	if (ID == Obj::HERO)
-		appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
+		appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClass()->getIndex())->getTemplates().front();
 
 	if(!vstd::contains(spells, SpellID::PRESET))
 	{
 		// hero starts with default spells
-		for(const auto & spellID : type->spells)
+		for(const auto & spellID : getHeroType()->spells)
 			spells.insert(spellID);
 	}
 	else //remove placeholder
@@ -341,7 +354,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 	if(!vstd::contains(spells, SpellID::SPELLBOOK_PRESET))
 	{
 		// hero starts with default spellbook presence status
-		if(!getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook)
+		if(!getArt(ArtifactPosition::SPELLBOOK) && getHeroType()->haveSpellBook)
 		{
 			auto artifact = ArtifactUtils::createArtifact(ArtifactID::SPELLBOOK);
 			putArtifact(ArtifactPosition::SPELLBOOK, artifact);
@@ -360,14 +373,14 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 	{
 		for(int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
 		{
-			pushPrimSkill(static_cast<PrimarySkill>(g), type->heroClass->primarySkillInitial[g]);
+			pushPrimSkill(static_cast<PrimarySkill>(g), getHeroClass()->primarySkillInitial[g]);
 		}
 	}
 	if(secSkills.size() == 1 && secSkills[0] == std::pair<SecondarySkill,ui8>(SecondarySkill::NONE, -1)) //set secondary skills to default
-		secSkills = type->secSkillsInit;
+		secSkills = getHeroType()->secSkillsInit;
 
 	if (gender == EHeroGender::DEFAULT)
-		gender = type->gender;
+		gender = getHeroType()->gender;
 
 	setFormation(EArmyFormation::LOOSE);
 	if (!stacksCount()) //standard army//initial army
@@ -403,9 +416,9 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 		addNewBonus(bonus);
 	}
 
-	if (cb->getSettings().getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && type->heroClass->commander.hasValue())
+	if (cb->getSettings().getBoolean(EGameSettings::MODULE_COMMANDERS) && !commander && getHeroClass()->commander.hasValue())
 	{
-		commander = new CCommanderInstance(type->heroClass->commander);
+		commander = new CCommanderInstance(getHeroClass()->commander);
 		commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders
 		commander->giveStackExp (exp); //after our exp is set
 	}
@@ -413,7 +426,7 @@ void CGHeroInstance::initHero(vstd::RNG & rand)
 	skillsInfo = SecondarySkillsInfo();
 
 	//copy active (probably growing) bonuses from hero prototype to hero object
-	for(const std::shared_ptr<Bonus> & b : type->specialty)
+	for(const std::shared_ptr<Bonus> & b : getHeroType()->specialty)
 		addNewBonus(b);
 
 	//initialize bonuses
@@ -433,14 +446,14 @@ void CGHeroInstance::initArmy(vstd::RNG & rand, IArmyDescriptor * dst)
 	auto stacksCountChances = cb->getSettings().getVector(EGameSettings::HEROES_STARTING_STACKS_CHANCES);
 	int stacksCountInitRandomNumber = rand.nextInt(1, 100);
 
-	size_t maxStacksCount = std::min(stacksCountChances.size(), type->initialArmy.size());
+	size_t maxStacksCount = std::min(stacksCountChances.size(), getHeroType()->initialArmy.size());
 
 	for(int stackNo=0; stackNo < maxStacksCount; stackNo++)
 	{
 		if (stacksCountInitRandomNumber > stacksCountChances[stackNo])
 			continue;
 
-		auto & stack = type->initialArmy[stackNo];
+		auto & stack = getHeroType()->initialArmy[stackNo];
 
 		int count = rand.nextInt(stack.minAmount, stack.maxAmount);
 
@@ -588,11 +601,11 @@ std::string CGHeroInstance::getMovementPointsTextIfOwner(PlayerColor player) con
 
 ui8 CGHeroInstance::maxlevelsToMagicSchool() const
 {
-	return type->heroClass->isMagicHero() ? 3 : 4;
+	return getHeroClass()->isMagicHero() ? 3 : 4;
 }
 ui8 CGHeroInstance::maxlevelsToWisdom() const
 {
-	return type->heroClass->isMagicHero() ? 3 : 6;
+	return getHeroClass()->isMagicHero() ? 3 : 6;
 }
 
 CGHeroInstance::SecondarySkillsInfo::SecondarySkillsInfo():
@@ -617,11 +630,8 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand)
 	{
 		ID = Obj::HERO;
 		subID = cb->gameState()->pickNextHeroType(getOwner());
-		type = getHeroType().toHeroType();
-		randomizeArmy(type->heroClass->faction);
+		randomizeArmy(getHeroClass()->faction);
 	}
-	else
-		type = getHeroType().toHeroType();
 
 	auto oldSubID = subID;
 
@@ -629,7 +639,7 @@ void CGHeroInstance::pickRandomObject(vstd::RNG & rand)
 	// after setType subID used to store unique hero identify id. Check issue 2277 for details
 	// exclude prisons which should use appearance as set in map, via map editor or RMG
 	if (ID != Obj::PRISON)
-		setType(ID, type->heroClass->getIndex());
+		setType(ID, getHeroClass()->getIndex());
 
 	this->subID = oldSubID;
 }
@@ -1041,7 +1051,7 @@ si32 CGHeroInstance::getManaNewTurn() const
 
 BoatId CGHeroInstance::getBoatType() const
 {
-	return BoatId(VLC->townh->getById(type->heroClass->faction)->getBoatType());
+	return BoatId(VLC->townh->getById(getHeroClass()->faction)->getBoatType());
 }
 
 void CGHeroInstance::getOutOffsets(std::vector<int3> &offsets) const
@@ -1080,7 +1090,7 @@ void CGHeroInstance::pushPrimSkill( PrimarySkill which, int val )
 
 EAlignment CGHeroInstance::getAlignment() const
 {
-	return type->heroClass->getAlignment();
+	return getHeroClass()->getAlignment();
 }
 
 void CGHeroInstance::initExp(vstd::RNG & rand)
@@ -1104,12 +1114,12 @@ HeroTypeID CGHeroInstance::getPortraitSource() const
 	if (customPortraitSource.isValid())
 		return customPortraitSource;
 	else
-		return getHeroType();
+		return getHeroTypeID();
 }
 
 int32_t CGHeroInstance::getIconIndex() const
 {
-	return VLC->heroTypes()->getById(getPortraitSource())->getIconIndex();
+	return getPortraitSource().toEntity(VLC)->getIconIndex();
 }
 
 std::string CGHeroInstance::getNameTranslated() const
@@ -1126,15 +1136,15 @@ std::string CGHeroInstance::getClassNameTextID() const
 {
 	if (isCampaignGem())
 		return "core.genrltxt.735";
-	return type->heroClass->getNameTextID();
+	return getHeroClass()->getNameTextID();
 }
 
 std::string CGHeroInstance::getNameTextID() const
 {
 	if (!nameCustomTextId.empty())
 		return nameCustomTextId;
-	if (type)
-		return type->getNameTextID();
+	if (getHeroTypeID().hasValue())
+		return getHeroType()->getNameTextID();
 
 	// FIXME: called by logging from some specialties (mods?) before type is set on deserialization
 	// assert(0);
@@ -1150,8 +1160,8 @@ std::string CGHeroInstance::getBiographyTextID() const
 {
 	if (!biographyCustomTextId.empty())
 		return biographyCustomTextId;
-	if (type)
-		return type->getBiographyTextID();
+	if (getHeroTypeID().hasValue())
+		return getHeroType()->getBiographyTextID();
 	
 	return ""; //for random hero
 }
@@ -1349,11 +1359,11 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelUpProposedSecondarySkills(vs
 		SecondarySkill selection;
 
 		if (selectWisdom)
-			selection = type->heroClass->chooseSecSkill(intersect(options, wisdomList), rand);
+			selection = getHeroClass()->chooseSecSkill(intersect(options, wisdomList), rand);
 		else if (selectSchool)
-			selection = type->heroClass->chooseSecSkill(intersect(options, schoolList), rand);
+			selection = getHeroClass()->chooseSecSkill(intersect(options, schoolList), rand);
 		else
-			selection = type->heroClass->chooseSecSkill(options, rand);
+			selection = getHeroClass()->chooseSecSkill(options, rand);
 
 		skills.push_back(selection);
 		options.erase(selection);
@@ -1384,7 +1394,7 @@ PrimarySkill CGHeroInstance::nextPrimarySkill(vstd::RNG & rand) const
 {
 	assert(gainsLevel());
 	const auto isLowLevelHero = level < GameConstants::HERO_HIGH_LEVEL;
-	const auto & skillChances = isLowLevelHero ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel;
+	const auto & skillChances = isLowLevelHero ? getHeroClass()->primarySkillLowLevel : getHeroClass()->primarySkillHighLevel;
 
 	if (isCampaignYog())
 	{
@@ -1524,25 +1534,15 @@ bool CGHeroInstance::hasVisions(const CGObjectInstance * target, BonusSubtypeID
 std::string CGHeroInstance::getHeroTypeName() const
 {
 	if(ID == Obj::HERO || ID == Obj::PRISON)
-	{
-		if(type)
-		{
-			return type->getJsonKey();
-		}
-		else
-		{
-			return getHeroType().toEntity(VLC)->getJsonKey();
-		}
-	}
+		return getHeroType()->getJsonKey();
+
 	return "";
 }
 
 void CGHeroInstance::afterAddToMap(CMap * map)
 {
 	if(ID != Obj::PRISON)
-	{		
 		map->heroesOnMap.emplace_back(this);
-	}
 }
 void CGHeroInstance::afterRemoveFromMap(CMap* map)
 {
@@ -1729,8 +1729,7 @@ void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 		if(!appearance)
 		{
 			// crossoverDeserialize
-			type = getHeroType().toHeroType();
-			appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
+			appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, getHeroClassID())->getTemplates().front();
 		}
 	}
 
@@ -1817,7 +1816,7 @@ bool CGHeroInstance::isCampaignYog() const
 	if (!boost::starts_with(campaign, "DATA/YOG")) // "Birth of a Barbarian"
 		return false;
 
-	if (getHeroType() != HeroTypeID::SOLMYR) // Yog (based on Solmyr)
+	if (getHeroTypeID() != HeroTypeID::SOLMYR) // Yog (based on Solmyr)
 		return false;
 
 	return true;
@@ -1835,7 +1834,7 @@ bool CGHeroInstance::isCampaignGem() const
 	if (!boost::starts_with(campaign, "DATA/GEM") &&  !boost::starts_with(campaign, "DATA/FINAL")) // "New Beginning" and "Unholy Alliance"
 		return false;
 
-	if (getHeroType() != HeroTypeID::GEM) // Yog (based on Solmyr)
+	if (getHeroTypeID() != HeroTypeID::GEM) // Yog (based on Solmyr)
 		return false;
 
 	return true;

+ 11 - 4
lib/mapObjects/CGHeroInstance.h

@@ -72,7 +72,6 @@ public:
 
 	//////////////////////////////////////////////////////////////////////////
 
-	const CHero * type;
 	TExpType exp; //experience points
 	ui32 level; //current level of hero
 
@@ -171,7 +170,7 @@ public:
 	const IOwnableObject * asOwnable() const final;
 
 	//INativeTerrainProvider
-	FactionID getFaction() const override;
+	FactionID getFactionID() const override;
 	TerrainId getNativeTerrain() const override;
 	int getLowestCreatureSpeed() const;
 	si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day
@@ -235,7 +234,11 @@ public:
 
 	//////////////////////////////////////////////////////////////////////////
 
-	HeroTypeID getHeroType() const;
+	const CHeroClass * getHeroClass() const;
+	HeroClassID getHeroClassID() const;
+
+	const CHero * getHeroType() const;
+	HeroTypeID getHeroTypeID() const;
 	void setHeroType(HeroTypeID type);
 
 	void initHero(vstd::RNG & rand);
@@ -352,7 +355,11 @@ public:
 		h & skillsInfo;
 		h & visitedTown;
 		h & boat;
-		h & type;
+		if (h.version < Handler::Version::REMOVE_TOWN_PTR)
+		{
+			CHero * type = nullptr;
+			h & type;
+		}
 		h & commander;
 		h & visitedObjects;
 		BONUS_TREE_DESERIALIZATION_FIX

+ 58 - 62
lib/mapObjects/CGTownInstance.cpp

@@ -45,7 +45,7 @@ int CGTownInstance::getSightRadius() const //returns sight distance
 
 	for(const auto & bid : builtBuildings)
 	{
-		auto height = town->buildings.at(bid)->height;
+		auto height = getTown()->buildings.at(bid)->height;
 		if(ret < height)
 			ret = height;
 	}
@@ -115,7 +115,7 @@ int CGTownInstance::mageGuildLevel() const
 
 int CGTownInstance::getHordeLevel(const int & HID)  const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present
 {
-	return town->hordeLvl.at(HID);
+	return getTown()->hordeLvl.at(HID);
 }
 
 int CGTownInstance::creatureGrowth(const int & level) const
@@ -127,7 +127,7 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
 {
 	GrowthInfo ret;
 
-	if (level<0 || level >=town->creatures.size())
+	if (level<0 || level >=getTown()->creatures.size())
 		return ret;
 	if (creatures[level].second.empty())
 		return ret; //no dwelling
@@ -151,11 +151,11 @@ GrowthInfo CGTownInstance::getGrowthInfo(int level) const
 	else if (hasBuilt(BuildingID::CITADEL))
 		ret.entries.emplace_back(subID, BuildingID::CITADEL, castleBonus = base / 2);
 
-	if(town->hordeLvl.at(0) == level)//horde 1
+	if(getTown()->hordeLvl.at(0) == level)//horde 1
 		if(hasBuilt(BuildingID::HORDE_1))
 			ret.entries.emplace_back(subID, BuildingID::HORDE_1, creature->getHorde());
 
-	if(town->hordeLvl.at(1) == level)//horde 2
+	if(getTown()->hordeLvl.at(1) == level)//horde 2
 		if(hasBuilt(BuildingID::HORDE_2))
 			ret.entries.emplace_back(subID, BuildingID::HORDE_2, creature->getHorde());
 
@@ -209,11 +209,11 @@ int CGTownInstance::getDwellingBonus(const std::vector<CreatureID>& creatureIds,
 TResources CGTownInstance::dailyIncome() const
 {
 	TResources ret;
-	for(const auto & p : town->buildings)
+	for(const auto & p : getTown()->buildings)
 	{
 		BuildingID buildingUpgrade;
 
-		for(const auto & p2 : town->buildings)
+		for(const auto & p2 : getTown()->buildings)
 		{
 			if (p2.second->upgrade == p.first)
 			{
@@ -251,10 +251,10 @@ bool CGTownInstance::hasCapitol() const
 
 TownFortifications CGTownInstance::fortificationsLevel() const
 {
-	auto result = town->fortifications;
+	auto result = getTown()->fortifications;
 
 	for (auto const	& buildingID : builtBuildings)
-		result += town->buildings.at(buildingID)->fortifications;
+		result += getTown()->buildings.at(buildingID)->fortifications;
 
 	if (result.wallsHealth == 0)
 		return TownFortifications();
@@ -264,7 +264,6 @@ TownFortifications CGTownInstance::fortificationsLevel() const
 
 CGTownInstance::CGTownInstance(IGameCallback *cb):
 	CGDwelling(cb),
-	town(nullptr),
 	built(0),
 	destroyed(0),
 	identifier(0),
@@ -379,17 +378,17 @@ void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
 
 std::string CGTownInstance::getObjectName() const
 {
-	return getNameTranslated() + ", " + town->faction->getNameTranslated();
+	return getNameTranslated() + ", " + getTown()->faction->getNameTranslated();
 }
 
 bool CGTownInstance::townEnvisagesBuilding(BuildingSubID::EBuildingSubID subId) const
 {
-	return town->getBuildingType(subId) != BuildingID::NONE;
+	return getTown()->getBuildingType(subId) != BuildingID::NONE;
 }
 
 void CGTownInstance::initializeConfigurableBuildings(vstd::RNG & rand)
 {
-	for(const auto & kvp : town->buildings)
+	for(const auto & kvp : getTown()->buildings)
 	{
 		if(!kvp.second->rewardableObjectInfo.getParameters().isNull())
 			rewardableBuildings[kvp.first] = new TownRewardableBuildingInstance(this, kvp.second->bid, rand);
@@ -457,8 +456,7 @@ void CGTownInstance::pickRandomObject(vstd::RNG & rand)
 
 	assert(ID == Obj::TOWN); // just in case
 	setType(ID, subID);
-	town = (*VLC->townh)[getFaction()]->town;
-	randomizeArmy(getFaction());
+	randomizeArmy(getFactionID());
 	updateAppearance();
 }
 
@@ -467,19 +465,19 @@ void CGTownInstance::initObj(vstd::RNG & rand) ///initialize town structures
 	blockVisit = true;
 
 	if(townEnvisagesBuilding(BuildingSubID::PORTAL_OF_SUMMONING)) //Dungeon for example
-		creatures.resize(town->creatures.size() + 1);
+		creatures.resize(getTown()->creatures.size() + 1);
 	else
-		creatures.resize(town->creatures.size());
+		creatures.resize(getTown()->creatures.size());
 
-	for (int level = 0; level < town->creatures.size(); level++)
+	for (int level = 0; level < getTown()->creatures.size(); level++)
 	{
 		BuildingID buildID = BuildingID(BuildingID::getDwellingFromLevel(level, 0));
 		int upgradeNum = 0;
 
-		for (; town->buildings.count(buildID); upgradeNum++, BuildingID::advanceDwelling(buildID))
+		for (; getTown()->buildings.count(buildID); upgradeNum++, BuildingID::advanceDwelling(buildID))
 		{
-			if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum)
-				creatures[level].second.push_back(town->creatures[level][upgradeNum]);
+			if (hasBuilt(buildID) && getTown()->creatures.at(level).size() > upgradeNum)
+				creatures[level].second.push_back(getTown()->creatures[level][upgradeNum]);
 		}
 	}
 	initializeConfigurableBuildings(rand);
@@ -623,9 +621,9 @@ void CGTownInstance::removeCapitols(const PlayerColor & owner) const
 	if (hasCapitol()) // search if there's an older capitol
 	{
 		PlayerState* state = cb->gameState()->getPlayerState(owner); //get all towns owned by player
-		for (const auto & town : state->getTowns())
+		for (const auto & otherTown : state->getTowns())
 		{
-			if (town != this && town->hasCapitol())
+			if (otherTown != this && otherTown->hasCapitol())
 			{
 				RazeStructures rs;
 				rs.tid = id;
@@ -648,7 +646,7 @@ void CGTownInstance::clearArmy() const
 
 BoatId CGTownInstance::getBoatType() const
 {
-	return town->faction->boatType;
+	return getTown()->faction->boatType;
 }
 
 int CGTownInstance::getMarketEfficiency() const
@@ -703,7 +701,7 @@ void CGTownInstance::updateAppearance()
 
 std::string CGTownInstance::nodeName() const
 {
-	return "Town (" + (town ? town->faction->getNameTranslated() : "unknown") + ") of " + getNameTranslated();
+	return "Town (" + getTown()->faction->getNameTranslated() + ") of " + getNameTranslated();
 }
 
 void CGTownInstance::deserializationFix()
@@ -752,7 +750,7 @@ void CGTownInstance::recreateBuildingsBonuses()
 
 		for(const auto & upgradeID : builtBuildings)
 		{
-			const auto & upgrade = town->buildings.at(upgradeID);
+			const auto & upgrade = getTown()->buildings.at(upgradeID);
 			if (upgrade->getBase() == bid && upgrade->upgradeReplacesBonuses)
 				bonusesReplacedByUpgrade = true;
 		}
@@ -761,7 +759,7 @@ void CGTownInstance::recreateBuildingsBonuses()
 		if (bonusesReplacedByUpgrade)
 			continue;
 
-		auto building = town->buildings.at(bid);
+		auto building = getTown()->buildings.at(bid);
 
 		if(building->buildingBonuses.empty())
 			continue;
@@ -828,21 +826,6 @@ bool CGTownInstance::armedGarrison() const
 	return !stacks.empty() || garrisonHero;
 }
 
-const CTown * CGTownInstance::getTown() const
-{
-    if(ID == Obj::RANDOM_TOWN)
-		return VLC->townh->randomTown;
-	else
-	{
-		if(nullptr == town)
-		{
-			return (*VLC->townh)[getFaction()]->town;
-		}
-		else
-			return town;
-	}
-}
-
 int CGTownInstance::getTownLevel() const
 {
 	// count all buildings that are not upgrades
@@ -850,7 +833,7 @@ int CGTownInstance::getTownLevel() const
 
 	for(const auto & bid : builtBuildings)
 	{
-		if(town->buildings.at(bid)->upgrade == BuildingID::NONE)
+		if(getTown()->buildings.at(bid)->upgrade == BuildingID::NONE)
 			level++;
 	}
 	return level;
@@ -892,7 +875,7 @@ bool CGTownInstance::hasBuilt(BuildingSubID::EBuildingSubID buildingID) const
 {
 	for(const auto & bid : builtBuildings)
 	{
-		if(town->buildings.at(bid)->subId == buildingID)
+		if(getTown()->buildings.at(bid)->subId == buildingID)
 			return true;
 	}
 	return false;
@@ -905,7 +888,7 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID) const
 
 bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) const
 {
-	if (townID == town->faction->getId() || townID == FactionID::ANY)
+	if (townID == getTown()->faction->getId() || townID == FactionID::ANY)
 		return hasBuilt(buildingID);
 	return false;
 }
@@ -923,7 +906,7 @@ std::set<EMarketMode> CGTownInstance::availableModes() const
 	std::set<EMarketMode> result;
 	for (const auto & buildingID : builtBuildings)
 	{
-		const auto * buildingPtr = town->buildings.at(buildingID).get();
+		const auto * buildingPtr = getTown()->buildings.at(buildingID).get();
 		result.insert(buildingPtr->marketModes.begin(), buildingPtr->marketModes.end());
 	}
 
@@ -950,8 +933,8 @@ std::set<BuildingID> CGTownInstance::getBuildings() const
 
 TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const
 {
-	if (vstd::contains(town->buildings, buildingID))
-		return town->buildings.at(buildingID)->resources;
+	if (vstd::contains(getTown()->buildings, buildingID))
+		return getTown()->buildings.at(buildingID)->resources;
 	else
 	{
 		logGlobal->error("Town %s at %s has no possible building %d!", getNameTranslated(), anchorPos().toString(), buildingID.toEnum());
@@ -962,7 +945,7 @@ TResources CGTownInstance::getBuildingCost(const BuildingID & buildingID) const
 
 CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID & buildID, bool deep) const
 {
-	const CBuilding * building = town->buildings.at(buildID);
+	const CBuilding * building = getTown()->buildings.at(buildID);
 
 	//TODO: find better solution to prevent infinite loops
 	std::set<BuildingID> processed;
@@ -970,13 +953,13 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID &
 	std::function<CBuilding::TRequired::Variant(const BuildingID &)> dependTest =
 	[&](const BuildingID & id) -> CBuilding::TRequired::Variant
 	{
-		if (town->buildings.count(id) == 0)
+		if (getTown()->buildings.count(id) == 0)
 		{
 			logMod->error("Invalid building ID %d in building dependencies!", id.getNum());
 			return CBuilding::TRequired::OperatorAll();
 		}
 
-		const CBuilding * build = town->buildings.at(id);
+		const CBuilding * build = getTown()->buildings.at(id);
 		CBuilding::TRequired::OperatorAll requirements;
 
 		if (!hasBuilt(id))
@@ -1001,7 +984,7 @@ CBuilding::TRequired CGTownInstance::genBuildingRequirements(const BuildingID &
 	CBuilding::TRequired::OperatorAll requirements;
 	if (building->upgrade != BuildingID::NONE)
 	{
-		const CBuilding * upgr = town->buildings.at(building->upgrade);
+		const CBuilding * upgr = getTown()->buildings.at(building->upgrade);
 
 		requirements.expressions.push_back(dependTest(upgr->bid));
 		processed.clear();
@@ -1151,14 +1134,27 @@ void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
 	}
 }
 
-FactionID CGTownInstance::getFaction() const
+const CFaction * CGTownInstance::getFaction() const
+{
+	return getFactionID().toFaction();
+}
+
+const CTown * CGTownInstance::getTown() const
+{
+	if(ID == Obj::RANDOM_TOWN)
+		return VLC->townh->randomTown;
+
+	return getFaction()->town;
+}
+
+FactionID CGTownInstance::getFactionID() const
 {
-	return  FactionID(subID.getNum());
+	return FactionID(subID.getNum());
 }
 
 TerrainId CGTownInstance::getNativeTerrain() const
 {
-	return town->faction->getNativeTerrain();
+	return getTown()->faction->getNativeTerrain();
 }
 
 ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const
@@ -1166,21 +1162,21 @@ ArtifactID CGTownInstance::getWarMachineInBuilding(BuildingID building) const
 	if (builtBuildings.count(building) == 0)
 		return ArtifactID::NONE;
 
-	if (building == BuildingID::BLACKSMITH && town->warMachineDeprecated.hasValue())
-		return town->warMachineDeprecated.toCreature()->warMachine;
+	if (building == BuildingID::BLACKSMITH && getTown()->warMachineDeprecated.hasValue())
+		return getTown()->warMachineDeprecated.toCreature()->warMachine;
 
-	return town->buildings.at(building)->warMachine;
+	return getTown()->buildings.at(building)->warMachine;
 }
 
 bool CGTownInstance::isWarMachineAvailable(ArtifactID warMachine) const
 {
 	for (auto const & buildingID : builtBuildings)
-		if (town->buildings.at(buildingID)->warMachine == warMachine)
+		if (getTown()->buildings.at(buildingID)->warMachine == warMachine)
 			return true;
 
 	if (builtBuildings.count(BuildingID::BLACKSMITH) &&
-	   town->warMachineDeprecated.hasValue() &&
-	   town->warMachineDeprecated.toCreature()->warMachine == warMachine)
+	   getTown()->warMachineDeprecated.hasValue() &&
+	   getTown()->warMachineDeprecated.toCreature()->warMachine == warMachine)
 		return true;
 
 	return false;
@@ -1200,7 +1196,7 @@ GrowthInfo::Entry::Entry(int subID, const BuildingID & building, int _count): co
 {
 	MetaString formatter;
 	formatter.appendRawString("%s %+d");
-	formatter.replaceRawString((*VLC->townh)[subID]->town->buildings.at(building)->getNameTranslated());
+	formatter.replaceRawString(FactionID(subID).toFaction()->town->buildings.at(building)->getNameTranslated());
 	formatter.replacePositiveNumber(count);
 
 	description = formatter.toString();

+ 17 - 11
lib/mapObjects/CGTownInstance.h

@@ -50,6 +50,7 @@ struct DLL_LINKAGE GrowthInfo
 
 class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket, public INativeTerrainProvider, public ICreatureUpgrader
 {
+	friend class CTownInstanceConstructor;
 	std::string nameTextId; // name of town
 
 	std::map<BuildingID, TownRewardableBuildingInstance*> convertOldBuildings(std::vector<TownRewardableBuildingInstance*> oldVector);
@@ -59,7 +60,6 @@ public:
 	enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3};
 
 	CTownAndVisitingHero townAndVis;
-	const CTown * town;
 	si32 built; //how many buildings has been built this turn
 	si32 destroyed; //how many buildings has been destroyed this turn
 	ConstTransitivePtr<CGHeroInstance> garrisonHero, visitingHero;
@@ -112,16 +112,21 @@ public:
 			rewardableBuildings = convertOldBuildings(oldVector);
 		}
 
-		if (h.saving)
+		if (h.version < Handler::Version::REMOVE_TOWN_PTR)
 		{
-			CFaction * faction = town ? town->faction : nullptr;
-			h & faction;
-		}
-		else
-		{
-			CFaction * faction = nullptr;
-			h & faction;
-			town = faction ? faction->town : nullptr;
+			CTown * town = nullptr;
+
+			if (h.saving)
+			{
+				CFaction * faction = town ? town->faction : nullptr;
+				h & faction;
+			}
+			else
+			{
+				CFaction * faction = nullptr;
+				h & faction;
+				town = faction ? faction->town : nullptr;
+			}
 		}
 
 		h & townAndVis;
@@ -213,9 +218,10 @@ public:
 	DamageRange getKeepDamageRange() const;
 
 	const CTown * getTown() const;
+	const CFaction * getFaction() const;
 
 	/// INativeTerrainProvider
-	FactionID getFaction() const override;
+	FactionID getFactionID() const override;
 	TerrainId getNativeTerrain() const override;
 
 	/// Returns ID of war machine that is produced by specified building or NONE if this is not built or if building does not produce war machines

+ 1 - 1
lib/mapObjects/CQuest.cpp

@@ -431,7 +431,7 @@ void CGSeerHut::setObjToKill()
 	
 	if(getCreatureToKill(true))
 	{
-		quest->stackToKill = getCreatureToKill(false)->getCreature();
+		quest->stackToKill = getCreatureToKill(false)->getCreatureID();
 		assert(quest->stackToKill != CreatureID::NONE);
 		quest->stackDirection = checkDirection();
 	}

+ 2 - 2
lib/mapObjects/TownBuildingInstance.cpp

@@ -73,14 +73,14 @@ TownRewardableBuildingInstance::TownRewardableBuildingInstance(CGTownInstance *
 
 void TownRewardableBuildingInstance::initObj(vstd::RNG & rand)
 {
-	assert(town && town->town);
+	assert(town && town->getTown());
 	configuration = generateConfiguration(rand);
 }
 
 Rewardable::Configuration TownRewardableBuildingInstance::generateConfiguration(vstd::RNG & rand) const
 {
 	Rewardable::Configuration result;
-	auto building = town->town->buildings.at(getBuildingType());
+	auto building = town->getTown()->buildings.at(getBuildingType());
 
 	building->rewardableObjectInfo.configureObject(result, rand, cb);
 	for(auto & rewardInfo : result.info)

+ 1 - 1
lib/mapping/CMap.cpp

@@ -302,7 +302,7 @@ void CMap::calculateGuardingGreaturePositions()
 CGHeroInstance * CMap::getHero(HeroTypeID heroID)
 {
 	for(auto & elem : heroesOnMap)
-		if(elem->getHeroType() == heroID)
+		if(elem->getHeroTypeID() == heroID)
 			return elem;
 	return nullptr;
 }

+ 6 - 6
lib/mapping/MapFormatH3M.cpp

@@ -896,7 +896,7 @@ void CMapLoaderH3M::readPredefinedHeroes()
 		}
 		map->predefinedHeroes.emplace_back(hero);
 
-		logGlobal->debug("Map '%s': Hero predefined in map: %s", mapName, VLC->heroh->getById(hero->getHeroType())->getJsonKey());
+		logGlobal->debug("Map '%s': Hero predefined in map: %s", mapName, hero->getHeroType()->getJsonKey());
 	}
 }
 
@@ -913,7 +913,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
 
 	if(!hero->artifactsWorn.empty() || !hero->artifactsInBackpack.empty())
 	{
-		logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroType().getNum(), hero->anchorPos().toString());
+		logGlobal->debug("Hero %d at %s has set artifacts twice (in map properties and on adventure map instance). Using the latter set...", hero->getHeroTypeID().getNum(), hero->anchorPos().toString());
 
 		hero->artifactsInBackpack.clear();
 		while(!hero->artifactsWorn.empty())
@@ -1778,7 +1778,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
 
 	for(auto & elem : map->disposedHeroes)
 	{
-		if(elem.heroId == object->getHeroType())
+		if(elem.heroId == object->getHeroTypeID())
 		{
 			object->nameCustomTextId = elem.name;
 			object->customPortraitSource = elem.portrait;
@@ -1788,7 +1788,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
 
 	bool hasName = reader->readBool();
 	if(hasName)
-		object->nameCustomTextId = readLocalizedString(TextIdentifier("heroes", object->getHeroType().getNum(), "name"));
+		object->nameCustomTextId = readLocalizedString(TextIdentifier("heroes", object->getHeroTypeID().getNum(), "name"));
 
 	if(features.levelSOD)
 	{
@@ -1891,7 +1891,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
 			auto ps = object->getAllBonuses(Selector::type()(BonusType::PRIMARY_SKILL).And(Selector::sourceType()(BonusSource::HERO_BASE_SKILL)), nullptr);
 			if(ps->size())
 			{
-				logGlobal->debug("Hero %s has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getHeroType().getNum() );
+				logGlobal->debug("Hero %s has set primary skills twice (in map properties and on adventure map instance). Using the latter set...", object->getHeroTypeID().getNum() );
 				for(const auto & b : *ps)
 					object->removeBonus(b);
 			}
@@ -1904,7 +1904,7 @@ CGObjectInstance * CMapLoaderH3M::readHero(const int3 & mapPosition, const Objec
 	}
 
 	if (object->subID != MapObjectSubID())
-		logGlobal->debug("Map '%s': Hero on map: %s at %s, owned by %s", mapName, VLC->heroh->getById(object->getHeroType())->getJsonKey(), mapPosition.toString(), object->getOwner().toString());
+		logGlobal->debug("Map '%s': Hero on map: %s at %s, owned by %s", mapName, object->getHeroType()->getJsonKey(), mapPosition.toString(), object->getOwner().toString());
 	else
 		logGlobal->debug("Map '%s': Hero on map: (random) at %s, owned by %s", mapName, mapPosition.toString(), object->getOwner().toString());
 

+ 4 - 6
lib/mapping/MapFormatJson.cpp

@@ -436,10 +436,8 @@ void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler)
 						if(hero->ID == Obj::HERO)
 						{
 							std::string temp;
-							if(hero->type)
-								temp = hero->type->getJsonKey();
-							else
-								temp = hero->getHeroType().toEntity(VLC)->getJsonKey();
+							if(hero->getHeroTypeID().hasValue())
+								temp = hero->getHeroType()->getJsonKey();
 
 							handler.serializeString("type", temp);
 						}
@@ -1154,10 +1152,10 @@ void CMapLoaderJson::readObjects()
 
 		auto * hero = dynamic_cast<const CGHeroInstance *>(object.get());
 
-		if (debugHeroesOnMap.count(hero->getHeroType()))
+		if (debugHeroesOnMap.count(hero->getHeroTypeID()))
 			logGlobal->error("Hero is already on the map at %s", hero->visitablePos().toString());
 
-		debugHeroesOnMap.insert(hero->getHeroType());
+		debugHeroesOnMap.insert(hero->getHeroTypeID());
 	}
 }
 

+ 2 - 2
lib/networkPacks/NetPacksLib.cpp

@@ -1355,7 +1355,7 @@ void NewStructures::applyGs(CGameState *gs)
 
 	for(const auto & id : bid)
 	{
-		assert(t->town->buildings.at(id) != nullptr);
+		assert(t->getTown()->buildings.at(id) != nullptr);
 		t->addBuilding(id);
 	}
 	t->updateAppearance();
@@ -1470,7 +1470,7 @@ void GiveHero::applyGs(CGameState *gs)
 
 	auto oldVisitablePos = h->visitablePos();
 	gs->map->removeBlockVisTiles(h,true);
-	h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->getIndex())->getTemplates().front();
+	h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->getHeroClassID().getNum())->getTemplates().front();
 
 	h->setOwner(player);
 	h->setMovementPoints(h->movementPointsLimit(true));

+ 1 - 1
lib/pathfinder/CPathfinder.cpp

@@ -292,7 +292,7 @@ TeleporterTilesVector CPathfinderHelper::getTeleportExits(const PathNodeInfo & s
 	{
 		auto * town = dynamic_cast<const CGTownInstance *>(source.nodeObject);
 		assert(town);
-		if (town && town->getFaction() == FactionID::INFERNO)
+		if (town && town->getFactionID() == FactionID::INFERNO)
 		{
 			/// TODO: Find way to reuse CPlayerSpecificInfoCallback::getTownsInfo
 			/// This may be handy if we allow to use teleportation to friendly towns

+ 2 - 2
lib/rewardable/Limiter.cpp

@@ -173,10 +173,10 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
 	if(!players.empty() && !vstd::contains(players, hero->getOwner()))
 		return false;
 	
-	if(!heroes.empty() && !vstd::contains(heroes, hero->type->getId()))
+	if(!heroes.empty() && !vstd::contains(heroes, hero->getHeroTypeID()))
 		return false;
 	
-	if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->type->heroClass->getId()))
+	if(!heroClasses.empty() && !vstd::contains(heroClasses, hero->getHeroClassID()))
 		return false;
 		
 	

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

@@ -733,7 +733,7 @@ CGCreature * ObjectManager::chooseGuard(si32 strength, bool zoneGuard)
 			continue;
 		if(!cre->getAIValue()) //bug #2681
 			continue;
-		if(!vstd::contains(zone.getMonsterTypes(), cre->getFaction()))
+		if(!vstd::contains(zone.getMonsterTypes(), cre->getFactionID()))
 			continue;
 		if((static_cast<si32>(cre->getAIValue() * (cre->ammMin + cre->ammMax) / 2) < strength) && (strength < static_cast<si32>(cre->getAIValue()) * 100)) //at least one full monster. size between average size of given stack and 100
 		{

+ 2 - 2
lib/rmg/modificators/TownPlacer.cpp

@@ -90,7 +90,7 @@ void TownPlacer::placeTowns(ObjectManager & manager)
 		
 		totalTowns++;
 		//register MAIN town of zone only
-		map.registerZone(town->getFaction());
+		map.registerZone(town->getFactionID());
 		
 		if(player.isValidPlayer()) //configure info for owning player
 		{
@@ -213,7 +213,7 @@ void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player
 		{
 			//FIXME: discovered bug with small zones - getPos is close to map boarder and we have outOfMap exception
 			//register MAIN town of zone
-			map.registerZone(town->getFaction());
+			map.registerZone(town->getFactionID());
 			//first town in zone goes in the middle
 			placeMainTown(manager, *town);
 		}

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

@@ -51,7 +51,7 @@ void TreasurePlacer::process()
 	// Add all native creatures
 	for(auto const & cre : VLC->creh->objects)
 	{
-		if(!cre->special && cre->getFaction() == zone.getTownType())
+		if(!cre->special && cre->getFactionID() == zone.getTownType())
 		{
 			creatures.push_back(cre.get());
 		}
@@ -192,7 +192,7 @@ void TreasurePlacer::addPrisons()
 			{
 				// Hero can be used again
 				auto* hero = dynamic_cast<CGHeroInstance*>(obj);
-				prisonHeroPlacer->restoreDrawnHero(hero->getHeroType());
+				prisonHeroPlacer->restoreDrawnHero(hero->getHeroTypeID());
 			};
 
 			oi.setTemplates(Obj::PRISON, 0, zone.getTerrainType());
@@ -236,9 +236,9 @@ void TreasurePlacer::addDwellings()
 				continue;
 
 			const auto * cre = creatures.front();
-			if(cre->getFaction() == zone.getTownType())
+			if(cre->getFactionID() == zone.getTownType())
 			{
-				auto nativeZonesCount = static_cast<float>(map.getZoneCount(cre->getFaction()));
+				auto nativeZonesCount = static_cast<float>(map.getZoneCount(cre->getFactionID()));
 				ObjectInfo oi(dwellingType, secondaryID);
 				setBasicProperties(oi, CompoundMapObjectID(dwellingType, secondaryID));
 
@@ -375,7 +375,7 @@ void TreasurePlacer::addPandoraBoxesWithCreatures()
 			return obj;
 		};
 		oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
-		oi.value = static_cast<ui32>(creature->getAIValue() * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount()));
+		oi.value = static_cast<ui32>(creature->getAIValue() * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFactionID())) / map.getTotalZoneCount()));
 		oi.probability = 3;
 		if(!oi.templates.empty())
 			addObjectToRandomPool(oi);
@@ -555,7 +555,7 @@ void TreasurePlacer::addSeerHuts()
 			oi.destroyObject = destroyObject;
 			oi.probability = 3;
 			oi.setTemplates(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
-			oi.value = static_cast<ui32>(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) - 4000) / 3);
+			oi.value = static_cast<ui32>(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFactionID())) / map.getTotalZoneCount())) - 4000) / 3);
 			if (oi.value > zone.getMaxTreasureValue())
 			{
 				continue;

+ 1 - 1
lib/serializer/CSerializer.cpp

@@ -26,7 +26,7 @@ void CSerializer::addStdVecItems(CGameState *gs, LibClasses *lib)
 	registerVectoredType<CGObjectInstance, ObjectInstanceID>(&gs->map->objects,
 		[](const CGObjectInstance &obj){ return obj.id; });
 	registerVectoredType<CGHeroInstance, HeroTypeID>(&gs->map->allHeroes,
-		[](const CGHeroInstance &h){ return h.type->getId(); });
+		[](const CGHeroInstance &h){ return h.getHeroType()->getId(); });
 	registerVectoredType<CArtifactInstance, ArtifactInstanceID>(&gs->map->artInstances,
 		[](const CArtifactInstance &artInst){ return artInst.getId(); });
 	registerVectoredType<CQuest, si32>(&gs->map->quests,

+ 2 - 1
lib/serializer/ESerializationVersion.h

@@ -63,6 +63,7 @@ enum class ESerializationVersion : int32_t
 	REGION_LABEL, // 864 - labels for campaign regions
 	SPELL_RESEARCH, // 865 - spell research
 	LOCAL_PLAYER_STATE_DATA, // 866 - player state contains arbitrary client-side data
+	REMOVE_TOWN_PTR, // 867 - removed pointer to CTown from CGTownInstance
 
-	CURRENT = LOCAL_PLAYER_STATE_DATA
+	CURRENT = REMOVE_TOWN_PTR
 };

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

@@ -88,7 +88,7 @@ void Moat::convertBonus(const Mechanics * m, std::vector<Bonus> & converted) con
 
 		if(m->battle()->battleGetDefendedTown() && m->battle()->battleGetFortifications().hasMoat)
 		{
-			nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->town->buildings.at(BuildingID::CITADEL)->getUniqueTypeID());
+			nb.sid = BonusSourceID(m->battle()->battleGetDefendedTown()->getTown()->buildings.at(BuildingID::CITADEL)->getUniqueTypeID());
 			nb.source = BonusSource::TOWN_STRUCTURE;
 		}
 		else

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

@@ -79,7 +79,7 @@ bool Summon::applicable(Problem & problem, const Mechanics * m) const
 
 				text.replaceNamePlural(elemental->creatureId());
 
-				if(caster->type->gender == EHeroGender::FEMALE)
+				if(caster->gender == EHeroGender::FEMALE)
 					text.replaceLocalString(EMetaText::GENERAL_TXT, 540);
 				else
 					text.replaceLocalString(EMetaText::GENERAL_TXT, 539);

+ 13 - 30
mapeditor/inspector/inspector.cpp

@@ -138,25 +138,11 @@ void Initializer::initialize(CGHeroInstance * o)
 		o->subID = 0;
 		o->tempOwner = PlayerColor::NEUTRAL;
 	}
-	
-	if(o->ID == Obj::HERO)
-	{
-		for(auto const & t : VLC->heroh->objects)
-		{
-			if(t->heroClass->getId() == HeroClassID(o->subID))
-			{
-				o->type = t.get();
-				break;
-			}
-		}
-	}
-	
-	if(o->type)
+
+	if(o->getHeroTypeID().hasValue())
 	{
-		//	o->type = VLC->heroh->objects.at(o->subID);
-		
-		o->gender = o->type->gender;
-		o->randomizeArmy(o->type->heroClass->faction);
+		o->gender = o->getHeroType()->gender;
+		o->randomizeArmy(o->getFactionID());
 	}
 }
 
@@ -304,7 +290,7 @@ void Inspector::updateProperties(CGHeroInstance * o)
 	auto isPrison = o->ID == Obj::PRISON;
 	addProperty("Owner", o->tempOwner, new OwnerDelegate(controller, isPrison), isPrison); //field is not editable for prison
 	addProperty<int>("Experience", o->exp, false);
-	addProperty("Hero class", o->type ? o->type->heroClass->getNameTranslated() : "", true);
+	addProperty("Hero class", o->getHeroClassID().hasValue() ? o->getHeroClass()->getNameTranslated() : "", true);
 	
 	{ //Gender
 		auto * delegate = new InspectorDelegate;
@@ -319,20 +305,20 @@ void Inspector::updateProperties(CGHeroInstance * o)
 	addProperty("Skills", PropertyEditorPlaceholder(), delegate, false);
 	addProperty("Spells", PropertyEditorPlaceholder(), new HeroSpellDelegate(*o), false);
 	
-	if(o->type || o->ID == Obj::PRISON)
+	if(o->getHeroTypeID().hasValue() || o->ID == Obj::PRISON)
 	{ //Hero type
 		auto * delegate = new InspectorDelegate;
-		for(int i = 0; i < VLC->heroh->objects.size(); ++i)
+		for(const auto & heroPtr : VLC->heroh->objects)
 		{
-			if(controller.map()->allowedHeroes.count(HeroTypeID(i)) != 0)
+			if(controller.map()->allowedHeroes.count(heroPtr->getId()) != 0)
 			{
-				if(o->ID == Obj::PRISON || (o->type && VLC->heroh->objects[i]->heroClass->getIndex() == o->type->heroClass->getIndex()))
+				if(o->ID == Obj::PRISON || heroPtr->heroClass->getIndex() == o->getHeroClassID())
 				{
-					delegate->options.push_back({QObject::tr(VLC->heroh->objects[i]->getNameTranslated().c_str()), QVariant::fromValue(VLC->heroh->objects[i]->getId().getNum())});
+					delegate->options.push_back({QObject::tr(heroPtr->getNameTranslated().c_str()), QVariant::fromValue(heroPtr->getIndex())});
 				}
 			}
 		}
-		addProperty("Hero type", o->type ? o->type->getNameTranslated() : "", delegate, false);
+		addProperty("Hero type", o->getHeroTypeID().hasValue() ? o->getHeroType()->getNameTranslated() : "", delegate, false);
 	}
 }
 
@@ -706,13 +692,10 @@ void Inspector::setProperty(CGHeroInstance * o, const QString & key, const QVari
 		for(auto const & t : VLC->heroh->objects)
 		{
 			if(t->getId() == value.toInt())
-			{
 				o->subID = value.toInt();
-				o->type = t.get();
-			}
 		}
-		o->gender = o->type->gender;
-		o->randomizeArmy(o->type->heroClass->faction);
+		o->gender = o->getHeroType()->gender;
+		o->randomizeArmy(o->getHeroType()->heroClass->faction);
 		updateProperties(); //updating other properties after change
 	}
 }

+ 1 - 1
mapeditor/inspector/townbuildingswidget.cpp

@@ -331,7 +331,7 @@ void TownBuildingsDelegate::setEditorData(QWidget *editor, const QModelIndex &in
 {
 	if(auto * ed = qobject_cast<TownBuildingsWidget *>(editor))
 	{
-		auto * ctown = town.town;
+		auto * ctown = town.getTown();
 		if(!ctown)
 			ctown = VLC->townh->randomTown;
 		if(!ctown)

+ 2 - 2
mapeditor/inspector/towneventdialog.cpp

@@ -100,7 +100,7 @@ void TownEventDialog::initResources()
 
 void TownEventDialog::initBuildings()
 {
-	auto * ctown = town.town;
+	auto * ctown = town.getTown();
 	if (!ctown)
 		ctown = VLC->townh->randomTown;
 	if (!ctown)
@@ -156,7 +156,7 @@ QStandardItem * TownEventDialog::addBuilding(const CTown& ctown, BuildingID buil
 void TownEventDialog::initCreatures()
 {
 	auto creatures = params.value("creatures").toList();
-	auto * ctown = town.town;
+	auto * ctown = town.getTown();
 	if (!ctown)
 		ui->creaturesTable->setRowCount(GameConstants::CREATURES_PER_TOWN);
 	else 

+ 1 - 5
mapeditor/mapcontroller.cpp

@@ -138,7 +138,7 @@ void MapController::repairMap(CMap * map) const
 
 			// FIXME: How about custom scenarios where defeated hero cannot be hired again?
 
-			map->allowedHeroes.insert(nih->getHeroType());
+			map->allowedHeroes.insert(nih->getHeroTypeID());
 
 			auto const & type = VLC->heroh->objects[nih->subID];
 			assert(type->heroClass);
@@ -154,10 +154,6 @@ void MapController::repairMap(CMap * map) const
 				nih->subTypeName = "prison";
 				nih->subID = 0;
 			}
-			
-			if(obj->ID != Obj::RANDOM_HERO)
-				nih->type = type.get();
-			
 			if(nih->ID == Obj::HERO) //not prison
 				nih->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->getIndex())->getTemplates().front();
 			//fix spellbook

+ 2 - 4
mapeditor/playerparams.cpp

@@ -77,12 +77,10 @@ PlayerParams::PlayerParams(MapController & ctrl, int playerId, QWidget *parent)
 	{
 		if(auto town = dynamic_cast<CGTownInstance*>(controller.map()->objects[i].get()))
 		{
-			auto * ctown = town->town;
+			auto * ctown = town->getTown();
 			if(!ctown)
-			{
 				ctown = VLC->townh->randomTown;
-				town->town = ctown;
-			}
+
 			if(ctown && town->getOwner().getNum() == playerColor)
 			{
 				if(playerInfo.hasMainTown && playerInfo.posOfMainTown == town->pos)

+ 5 - 5
mapeditor/validator.cpp

@@ -122,13 +122,13 @@ std::set<Validator::Issue> Validator::validate(const CMap * map)
 
 					++amountOfHeroes[ins->getOwner()];
 				}
-				if(ins->type)
+				if(ins->getHeroTypeID().hasValue())
 				{
-					if(map->allowedHeroes.count(ins->getHeroType()) == 0)
-						issues.insert({ tr("Hero %1 is prohibited by map settings").arg(ins->type->getNameTranslated().c_str()), false });
+					if(map->allowedHeroes.count(ins->getHeroTypeID()) == 0)
+						issues.insert({ tr("Hero %1 is prohibited by map settings").arg(ins->getHeroType()->getNameTranslated().c_str()), false });
 					
-					if(!allHeroesOnMap.insert(ins->type).second)
-						issues.insert({ tr("Hero %1 has duplicate on map").arg(ins->type->getNameTranslated().c_str()), false });
+					if(!allHeroesOnMap.insert(ins->getHeroType()).second)
+						issues.insert({ tr("Hero %1 has duplicate on map").arg(ins->getHeroType()->getNameTranslated().c_str()), false });
 				}
 				else if(ins->ID != Obj::RANDOM_HERO)
 					issues.insert({ tr("Hero %1 has an empty type and must be removed").arg(ins->instanceName.c_str()), true });

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

@@ -46,7 +46,7 @@ const std::vector<CreatureProxy::CustomRegType> CreatureProxy::REGISTER_CUSTOM =
 	{"getLevel", LuaMethodWrapper<Creature, decltype(&Creature::getLevel), &Creature::getLevel>::invoke, false},
 	{"getGrowth", LuaMethodWrapper<Creature, decltype(&Creature::getGrowth), &Creature::getGrowth>::invoke, false},
 	{"getHorde", LuaMethodWrapper<Creature, decltype(&Creature::getHorde), &Creature::getHorde>::invoke, false},
-	{"getFaction", LuaMethodWrapper<Creature, decltype(&Creature::getFaction), &Creature::getFaction>::invoke, false},
+	{"getFactionID", LuaMethodWrapper<Creature, decltype(&Creature::getFactionID), &Creature::getFactionID>::invoke, false},
 
 	{"getBaseAttack", LuaMethodWrapper<Creature, decltype(&Creature::getBaseAttack), &Creature::getBaseAttack>::invoke, false},
 	{"getBaseDefense", LuaMethodWrapper<Creature, decltype(&Creature::getBaseDefense), &Creature::getBaseDefense>::invoke, false},

+ 22 - 22
server/CGameHandler.cpp

@@ -162,7 +162,7 @@ void CGameHandler::levelUpHero(const CGHeroInstance * hero)
 	hlu.player = hero->tempOwner;
 	hlu.heroId = hero->id;
 	hlu.primskill = primarySkill;
-	hlu.skills = hero->getLevelUpProposedSecondarySkills(heroPool->getHeroSkillsRandomGenerator(hero->getHeroType()));
+	hlu.skills = hero->getLevelUpProposedSecondarySkills(heroPool->getHeroSkillsRandomGenerator(hero->getHeroTypeID()));
 
 	if (hlu.skills.size() == 0)
 	{
@@ -553,7 +553,7 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack
 	for (auto & elem : gs->map->allHeroes)
 	{
 		if(elem)
-			heroPool->getHeroSkillsRandomGenerator(elem->getHeroType()); // init RMG seed
+			heroPool->getHeroSkillsRandomGenerator(elem->getHeroTypeID()); // init RMG seed
 	}
 
 	reinitScripting();
@@ -569,12 +569,12 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa
 		return;
 	}
 
-	if (forced || town->creatures.at(town->town->creatures.size()).second.empty())//we need to change creature
+	if (forced || town->creatures.at(town->getTown()->creatures.size()).second.empty())//we need to change creature
 		{
 			SetAvailableCreatures ssi;
 			ssi.tid = town->id;
 			ssi.creatures = town->creatures;
-			ssi.creatures[town->town->creatures.size()].second.clear();//remove old one
+			ssi.creatures[town->getTown()->creatures.size()].second.clear();//remove old one
 
 			std::set<CreatureID> availableCreatures;
 			for (const auto & dwelling : p->getOwnedObjects())
@@ -590,13 +590,13 @@ void CGameHandler::setPortalDwelling(const CGTownInstance * town, bool forced=fa
 
 			if (clear)
 			{
-				ssi.creatures[town->town->creatures.size()].first = std::max(1, (creatureId.toEntity(VLC)->getGrowth())/2);
+				ssi.creatures[town->getTown()->creatures.size()].first = std::max(1, (creatureId.toEntity(VLC)->getGrowth())/2);
 			}
 			else
 			{
-				ssi.creatures[town->town->creatures.size()].first = creatureId.toEntity(VLC)->getGrowth();
+				ssi.creatures[town->getTown()->creatures.size()].first = creatureId.toEntity(VLC)->getGrowth();
 			}
-			ssi.creatures[town->town->creatures.size()].second.push_back(creatureId);
+			ssi.creatures[town->getTown()->creatures.size()].second.push_back(creatureId);
 			sendAndApply(ssi);
 		}
 }
@@ -657,7 +657,7 @@ void CGameHandler::onNewTurn()
 		PlayerColor player = t->tempOwner;
 
 		if(t->hasBuilt(BuildingID::GRAIL)
-			&& t->town->buildings.at(BuildingID::GRAIL)->height == CBuilding::HEIGHT_SKYSHIP)
+			&& t->getTown()->buildings.at(BuildingID::GRAIL)->height == CBuilding::HEIGHT_SKYSHIP)
 		{
 			// Skyship, probably easier to handle same as Veil of darkness
 			// do it every new day before veils
@@ -1048,7 +1048,7 @@ bool CGameHandler::teleportHero(ObjectInstanceID hid, ObjectInstanceID dstid, ui
 	if (((h->getOwner() != t->getOwner())
 		&& complain("Cannot teleport hero to another player"))
 
-	|| (from->town->faction->getId() != t->town->faction->getId()
+	|| (from->getFactionID() != t->getFactionID()
 		&& complain("Source town and destination town should belong to the same faction"))
 
 	|| ((!from || !from->hasBuilt(BuildingSubID::CASTLE_GATE))
@@ -1212,7 +1212,7 @@ void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vector<cons
 
 	for (auto & building : t->rewardableBuildings)
 	{
-		if (!t->town->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first))
+		if (!t->getTown()->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first))
 			buildingsToVisit.push_back(building.first);
 	}
 
@@ -2036,12 +2036,12 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 	const CGTownInstance * t = getTown(tid);
 	if(!t)
 		COMPLAIN_RETF("No such town (ID=%s)!", tid);
-	if(!t->town->buildings.count(requestedID))
-		COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->town->faction->getNameTranslated() % requestedID);
+	if(!t->getTown()->buildings.count(requestedID))
+		COMPLAIN_RETF("Town of faction %s does not have info about building ID=%s!", t->getFaction()->getNameTranslated() % requestedID);
 	if(t->hasBuilt(requestedID))
-		COMPLAIN_RETF("Building %s is already built in %s", t->town->buildings.at(requestedID)->getNameTranslated() % t->getNameTranslated());
+		COMPLAIN_RETF("Building %s is already built in %s", t->getTown()->buildings.at(requestedID)->getNameTranslated() % t->getNameTranslated());
 
-	const CBuilding * requestedBuilding = t->town->buildings.at(requestedID);
+	const CBuilding * requestedBuilding = t->getTown()->buildings.at(requestedID);
 
 	//Vector with future list of built building and buildings in auto-mode that are not yet built.
 	std::vector<const CBuilding*> remainingAutoBuildings;
@@ -2081,7 +2081,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 			int level = BuildingID::getLevelFromDwelling(buildingID);
 			int upgradeNumber = BuildingID::getUpgradedFromDwelling(buildingID);
 
-			if(upgradeNumber >= t->town->creatures.at(level).size())
+			if(upgradeNumber >= t->getTown()->creatures.at(level).size())
 			{
 				complain(boost::str(boost::format("Error encountered when building dwelling (bid=%s):"
 													"no creature found (upgrade number %d, level %d!")
@@ -2089,7 +2089,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 				return;
 			}
 
-			const CCreature * crea = t->town->creatures.at(level).at(upgradeNumber).toCreature();
+			const CCreature * crea = t->getTown()->creatures.at(level).at(upgradeNumber).toCreature();
 
 			SetAvailableCreatures ssi;
 			ssi.tid = t->id;
@@ -2099,7 +2099,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 			ssi.creatures[level].second.push_back(crea->getId());
 			sendAndApply(ssi);
 		}
-		if(t->town->buildings.at(buildingID)->subId == BuildingSubID::PORTAL_OF_SUMMONING)
+		if(t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::PORTAL_OF_SUMMONING)
 		{
 			setPortalDwelling(t);
 		}
@@ -2110,9 +2110,9 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 	{
 		auto isMageGuild = (buildingID <= BuildingID::MAGES_GUILD_5 && buildingID >= BuildingID::MAGES_GUILD_1);
 		auto isLibrary = isMageGuild ? false
-			: t->town->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY;
+			: t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY;
 
-		if(isMageGuild || isLibrary || (t->getFaction() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL))
+		if(isMageGuild || isLibrary || (t->getFactionID() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL))
 		{
 			if(t->visitingHero)
 				giveSpells(t,t->visitingHero);
@@ -2128,7 +2128,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 	};
 
 	//Init the vectors
-	for(auto & build : t->town->buildings)
+	for(auto & build : t->getTown()->buildings)
 	{
 		if(t->hasBuilt(build.first))
 		{
@@ -2212,7 +2212,7 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid)
 	if(!t->hasBuilt(bid))
 		return false;
 
-	auto subID = t->town->buildings.at(bid)->subId;
+	auto subID = t->getTown()->buildings.at(bid)->subId;
 
 	if(subID == BuildingSubID::EBuildingSubID::BANK)
 	{
@@ -2224,7 +2224,7 @@ bool CGameHandler::visitTownBuilding(ObjectInstanceID tid, BuildingID bid)
 		return true;
 	}
 
-	if (t->rewardableBuildings.count(bid) && t->visitingHero && t->town->buildings.at(bid)->manualHeroVisit)
+	if (t->rewardableBuildings.count(bid) && t->visitingHero && t->getTown()->buildings.at(bid)->manualHeroVisit)
 	{
 		std::vector<BuildingID> buildingsToVisit;
 		std::vector<const CGHeroInstance*> visitors;

+ 14 - 14
server/processors/HeroPoolProcessor.cpp

@@ -47,8 +47,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(heroes[0]->getHeroType());
-	auto roleRight = heroesPool->getSlotRole(heroes[1]->getHeroType());
+	auto roleLeft = heroesPool->getSlotRole(heroes[0]->getHeroTypeID());
+	auto roleRight = heroesPool->getSlotRole(heroes[1]->getHeroTypeID());
 
 	if (roleLeft > roleRight)
 		return TavernHeroSlot::RANDOM;
@@ -70,7 +70,7 @@ void HeroPoolProcessor::onHeroSurrendered(const PlayerColor & color, const CGHer
 
 	sah.slotID = selectSlotForRole(color, sah.roleID);
 	sah.player = color;
-	sah.hid = hero->getHeroType();
+	sah.hid = hero->getHeroTypeID();
 	sah.replenishPoints = false;
 	gameHandler->sendAndApply(sah);
 }
@@ -82,9 +82,9 @@ void HeroPoolProcessor::onHeroEscaped(const PlayerColor & color, const CGHeroIns
 
 	sah.slotID = selectSlotForRole(color, sah.roleID);
 	sah.player = color;
-	sah.hid = hero->getHeroType();
+	sah.hid = hero->getHeroTypeID();
 	sah.army.clearSlots();
-	sah.army.setCreature(SlotID(0), hero->type->initialArmy.at(0).creature, 1);
+	sah.army.setCreature(SlotID(0), hero->getHeroType()->initialArmy.at(0).creature, 1);
 	sah.replenishPoints = false;
 
 	gameHandler->sendAndApply(sah);
@@ -112,7 +112,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe
 
 	if (newHero)
 	{
-		sah.hid = newHero->getHeroType();
+		sah.hid = newHero->getHeroTypeID();
 
 		if (giveArmy)
 		{
@@ -123,7 +123,7 @@ void HeroPoolProcessor::selectNewHeroForSlot(const PlayerColor & color, TavernHe
 		{
 			sah.roleID = TavernSlotRole::SINGLE_UNIT;
 			sah.army.clearSlots();
-			sah.army.setCreature(SlotID(0), newHero->type->initialArmy[0].creature, 1);
+			sah.army.setCreature(SlotID(0), newHero->getHeroType()->initialArmy[0].creature, 1);
 		}
 	}
 	else
@@ -208,7 +208,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 
 	for(const auto & hero : recruitableHeroes)
 	{
-		if(hero->getHeroType() == heroToRecruit)
+		if(hero->getHeroTypeID() == heroToRecruit)
 			recruitedHero = hero;
 	}
 
@@ -221,7 +221,7 @@ bool HeroPoolProcessor::hireHero(const ObjectInstanceID & objectID, const HeroTy
 
 	HeroRecruited hr;
 	hr.tid = mapObject->id;
-	hr.hid = recruitedHero->getHeroType();
+	hr.hid = recruitedHero->getHeroTypeID();
 	hr.player = player;
 	hr.tile = recruitedHero->convertFromVisitablePos(targetPos );
 	if(gameHandler->getTile(targetPos)->isWater() && !recruitedHero->boat)
@@ -258,14 +258,14 @@ std::vector<const CHeroClass *> HeroPoolProcessor::findAvailableClassesFor(const
 
 	for(const auto & elem : heroesPool->unusedHeroesFromPool())
 	{
-		if (vstd::contains(result, elem.second->type->heroClass))
+		if (vstd::contains(result, elem.second->getHeroClass()))
 			continue;
 
 		bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player);
-		bool heroClassBanned = elem.second->type->heroClass->tavernProbability(factionID) == 0;
+		bool heroClassBanned = elem.second->getHeroClass()->tavernProbability(factionID) == 0;
 
 		if(heroAvailable && !heroClassBanned)
-			result.push_back(elem.second->type->heroClass);
+			result.push_back(elem.second->getHeroClass());
 	}
 
 	return result;
@@ -282,7 +282,7 @@ std::vector<CGHeroInstance *> HeroPoolProcessor::findAvailableHeroesFor(const Pl
 		assert(!vstd::contains(result, elem.second));
 
 		bool heroAvailable = heroesPool->isHeroAvailableFor(elem.first, player);
-		bool heroClassMatches = elem.second->type->heroClass == heroClass;
+		bool heroClassMatches = elem.second->getHeroClass() == heroClass;
 
 		if(heroAvailable && heroClassMatches)
 			result.push_back(elem.second);
@@ -318,7 +318,7 @@ const CHeroClass * HeroPoolProcessor::pickClassFor(bool isNative, const PlayerCo
 			continue;
 
 		bool hasSameClass = vstd::contains_if(currentTavern, [&](const CGHeroInstance * hero){
-			return hero->type->heroClass == heroClass;
+			return hero->getHeroClass() == heroClass;
 		});
 
 		if (hasSameClass)

+ 5 - 5
server/processors/NewTurnProcessor.cpp

@@ -95,10 +95,10 @@ void NewTurnProcessor::handleTownEvents(const CGTownInstance * town)
 			// 1. Building exists in town (don't attempt to build Lvl 5 guild in Fortress
 			// 2. Building was not built yet
 			// othervice, silently ignore / skip it
-			if (town->town->buildings.count(i) && !town->hasBuilt(i))
+			if (town->getTown()->buildings.count(i) && !town->hasBuilt(i))
 			{
 				gameHandler->buildStructure(town->id, i, true);
-				iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFaction(), i));
+				iw.components.emplace_back(ComponentType::BUILDING, BuildingTypeUniqueID(town->getFactionID(), i));
 			}
 		}
 
@@ -246,7 +246,7 @@ SetAvailableCreatures NewTurnProcessor::generateTownGrowth(const CGTownInstance
 	sac.tid = t->id;
 	sac.creatures = t->creatures;
 
-	for (int k=0; k < t->town->creatures.size(); k++)
+	for (int k=0; k < t->getTown()->creatures.size(); k++)
 	{
 		if (t->creatures.at(k).second.empty())
 			continue;
@@ -345,7 +345,7 @@ void NewTurnProcessor::updateNeutralTownGarrison(const CGTownInstance * t, int c
 	{
 		const auto * creature = slot.second->type;
 
-		if (creature->getFaction() != t->getFaction())
+		if (creature->getFactionID() != t->getFactionID())
 			continue;
 
 		if (creature->getLevel() != tierToGrow)
@@ -518,7 +518,7 @@ std::tuple<EWeekType, CreatureID> NewTurnProcessor::pickWeekType(bool newMonth)
 			{
 				newMonster.second = VLC->creh->pickRandomMonster(gameHandler->getRandomGenerator());
 			} while (VLC->creh->objects[newMonster.second] &&
-					(*VLC->townh)[VLC->creatures()->getById(newMonster.second)->getFaction()]->town == nullptr); // find first non neutral creature
+					(*VLC->townh)[VLC->creatures()->getById(newMonster.second)->getFactionID()]->town == nullptr); // find first non neutral creature
 
 			return { EWeekType::BONUS_GROWTH, newMonster.second};
 		}

+ 1 - 1
server/processors/PlayerMessageProcessor.cpp

@@ -370,7 +370,7 @@ void PlayerMessageProcessor::cheatBuildTown(PlayerColor player, const CGTownInst
 	if (!town)
 		return;
 
-	for (auto & build : town->town->buildings)
+	for (auto & build : town->getTown()->buildings)
 	{
 		if (!town->hasBuilt(build.first)
 			&& !build.second->getNameTranslated().empty()

+ 1 - 1
test/entity/CCreatureTest.cpp

@@ -100,7 +100,7 @@ TEST_F(CCreatureTest, DISABLED_JsonUpdate)
 
 	EXPECT_EQ(subject->getFightValue(), 2420);
 	EXPECT_EQ(subject->getLevel(), 6);
-	EXPECT_EQ(subject->getFaction().getNum(), 55);
+	EXPECT_EQ(subject->getFactionID().getNum(), 55);
 	EXPECT_TRUE(subject->isDoubleWide());
 }
 

+ 1 - 1
test/mock/mock_Creature.h

@@ -44,7 +44,7 @@ public:
 	MOCK_CONST_METHOD0(getLevel, int32_t());
 	MOCK_CONST_METHOD0(getGrowth, int32_t());
 	MOCK_CONST_METHOD0(getHorde, int32_t());
-	MOCK_CONST_METHOD0(getFaction, FactionID());
+	MOCK_CONST_METHOD0(getFactionID, FactionID());
 
 	MOCK_CONST_METHOD0(getBaseAttack, int32_t());
 	MOCK_CONST_METHOD0(getBaseDefense, int32_t());

+ 1 - 1
test/mock/mock_battle_Unit.h

@@ -82,7 +82,7 @@ public:
 	MOCK_CONST_METHOD1(willMove, bool(int));
 	MOCK_CONST_METHOD1(waited, bool(int));
 
-	MOCK_CONST_METHOD0(getFaction, FactionID());
+	MOCK_CONST_METHOD0(getFactionID, FactionID());
 
 	MOCK_CONST_METHOD1(battleQueuePhase, battle::BattlePhases::Type(int));