Selaa lähdekoodia

Merge pull request #3298 from vcmi/random_prison_distributor

Fixes to hero / prison distribution
Ivan Savenko 1 vuosi sitten
vanhempi
sitoutus
b1bd44de1e

+ 2 - 0
cmake_modules/VCMI_lib.cmake

@@ -156,6 +156,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.cpp
 		${MAIN_LIB_DIR}/rmg/modificators/RoadPlacer.cpp
 		${MAIN_LIB_DIR}/rmg/modificators/TreasurePlacer.cpp
+		${MAIN_LIB_DIR}/rmg/modificators/PrisonHeroPlacer.cpp
 		${MAIN_LIB_DIR}/rmg/modificators/QuestArtifactPlacer.cpp
 		${MAIN_LIB_DIR}/rmg/modificators/ConnectionsPlacer.cpp
 		${MAIN_LIB_DIR}/rmg/modificators/WaterAdopter.cpp
@@ -526,6 +527,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
 		${MAIN_LIB_DIR}/rmg/modificators/ObjectDistributor.h
 		${MAIN_LIB_DIR}/rmg/modificators/RoadPlacer.h
 		${MAIN_LIB_DIR}/rmg/modificators/TreasurePlacer.h
+		${MAIN_LIB_DIR}/rmg/modificators/PrisonHeroPlacer.h
 		${MAIN_LIB_DIR}/rmg/modificators/QuestArtifactPlacer.h
 		${MAIN_LIB_DIR}/rmg/modificators/ConnectionsPlacer.h
 		${MAIN_LIB_DIR}/rmg/modificators/WaterAdopter.h

+ 9 - 0
lib/mapping/CMap.cpp

@@ -649,9 +649,18 @@ void CMap::banWaterHeroes()
 
 void CMap::banHero(const HeroTypeID & id)
 {
+	if (!vstd::contains(allowedHeroes, id))
+		logGlobal->warn("Attempt to ban hero %s, who is already not allowed", id.encode(id));
 	allowedHeroes.erase(id);
 }
 
+void CMap::unbanHero(const HeroTypeID & id)
+{
+	if (vstd::contains(allowedHeroes, id))
+		logGlobal->warn("Attempt to unban hero %s, who is already allowed", id.encode(id));
+	allowedHeroes.insert(id);
+}
+
 void CMap::initTerrain()
 {
 	terrain.resize(boost::extents[levels()][width][height]);

+ 1 - 0
lib/mapping/CMap.h

@@ -112,6 +112,7 @@ public:
 	void banWaterArtifacts();
 	void banWaterHeroes();
 	void banHero(const HeroTypeID& id);
+	void unbanHero(const HeroTypeID & id);
 	void banWaterSpells();
 	void banWaterSkills();
 	void banWaterContent();

+ 4 - 17
lib/rmg/CMapGenerator.cpp

@@ -35,7 +35,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 
 CMapGenerator::CMapGenerator(CMapGenOptions& mapGenOptions, int RandomSeed) :
 	mapGenOptions(mapGenOptions), randomSeed(RandomSeed),
-	allowedPrisons(0), monolithIndex(0)
+	monolithIndex(0)
 {
 	loadConfig();
 	rand.setSeed(this->randomSeed);
@@ -96,12 +96,6 @@ const CMapGenOptions& CMapGenerator::getMapGenOptions() const
 	return mapGenOptions;
 }
 
-void CMapGenerator::initPrisonsRemaining()
-{
-	allowedPrisons = map->getMap(this).allowedHeroes.size();
-	allowedPrisons = std::max<int> (0, allowedPrisons - 16 * mapGenOptions.getHumanOrCpuPlayerCount()); //so at least 16 heroes will be available for every player
-}
-
 void CMapGenerator::initQuestArtsRemaining()
 {
 	//TODO: Move to QuestArtifactPlacer?
@@ -122,7 +116,6 @@ std::unique_ptr<CMap> CMapGenerator::generate()
 		addHeaderInfo();
 		map->initTiles(*this, rand);
 		Load::Progress::step();
-		initPrisonsRemaining();
 		initQuestArtsRemaining();
 		genZones();
 		Load::Progress::step();
@@ -468,11 +461,6 @@ int CMapGenerator::getNextMonlithIndex()
 	}
 }
 
-int CMapGenerator::getPrisonsRemaning() const
-{
-	return allowedPrisons;
-}
-
 std::shared_ptr<CZonePlacer> CMapGenerator::getZonePlacer() const
 {
 	return placer;
@@ -488,6 +476,7 @@ const std::vector<HeroTypeID> CMapGenerator::getAllPossibleHeroes() const
 	auto isWaterMap = map->getMap(this).isWaterMap();
 	//Skip heroes that were banned, including the ones placed in prisons
 	std::vector<HeroTypeID> ret;
+
 	for (HeroTypeID hero : map->getMap(this).allowedHeroes)
 	{
 		auto * h = dynamic_cast<const CHero*>(VLC->heroTypes()->getById(hero));
@@ -517,14 +506,12 @@ const std::vector<HeroTypeID> CMapGenerator::getAllPossibleHeroes() const
 
 void CMapGenerator::banQuestArt(const ArtifactID & id)
 {
-	//TODO: Protect with mutex
 	map->getMap(this).allowedArtifact.erase(id);
 }
 
-void CMapGenerator::banHero(const HeroTypeID & id)
+void CMapGenerator::unbanQuestArt(const ArtifactID & id)
 {
-	//TODO: Protect with mutex
-	map->getMap(this).banHero(id);
+	map->getMap(this).allowedArtifact.insert(id);
 }
 
 Zone * CMapGenerator::getZoneWater() const

+ 1 - 3
lib/rmg/CMapGenerator.h

@@ -65,8 +65,7 @@ public:
 	const std::vector<ArtifactID> & getAllPossibleQuestArtifacts() const;
 	const std::vector<HeroTypeID> getAllPossibleHeroes() const;
 	void banQuestArt(const ArtifactID & id);
-	void banHero(const HeroTypeID& id);
-
+	void unbanQuestArt(const ArtifactID & id);
 	Zone * getZoneWater() const;
 	void addWaterTreasuresInfo();
 
@@ -82,7 +81,6 @@ private:
 	
 	std::vector<rmg::ZoneConnection> connectionsLeft;
 	
-	int allowedPrisons;
 	int monolithIndex;
 	std::vector<ArtifactID> questArtifacts;
 

+ 7 - 0
lib/rmg/RmgMap.cpp

@@ -19,6 +19,7 @@
 #include "modificators/ObjectManager.h"
 #include "modificators/RoadPlacer.h"
 #include "modificators/TreasurePlacer.h"
+#include "modificators/PrisonHeroPlacer.h"
 #include "modificators/QuestArtifactPlacer.h"
 #include "modificators/ConnectionsPlacer.h"
 #include "modificators/TownPlacer.h"
@@ -127,6 +128,7 @@ void RmgMap::initTiles(CMapGenerator & generator, CRandomGenerator & rand)
 void RmgMap::addModificators()
 {
 	bool hasObjectDistributor = false;
+	bool hasHeroPlacer = false;
 	bool hasRockFiller = false;
 
 	for(auto & z : getZones())
@@ -139,6 +141,11 @@ void RmgMap::addModificators()
 			zone->addModificator<ObjectDistributor>();
 			hasObjectDistributor = true;
 		}
+		if (!hasHeroPlacer)
+		{
+			zone->addModificator<PrisonHeroPlacer>();
+			hasHeroPlacer = true;
+		}
 		zone->addModificator<TreasurePlacer>();
 		zone->addModificator<ObstaclePlacer>();
 		zone->addModificator<TerrainPainter>();

+ 13 - 2
lib/rmg/modificators/ObjectDistributor.cpp

@@ -15,6 +15,7 @@
 #include "../RmgMap.h"
 #include "../CMapGenerator.h"
 #include "TreasurePlacer.h"
+#include "PrisonHeroPlacer.h"
 #include "QuestArtifactPlacer.h"
 #include "TownPlacer.h"
 #include "TerrainPainter.h"
@@ -75,7 +76,6 @@ void ObjectDistributor::distributeLimitedObjects()
 
 					auto rmgInfo = handler->getRMGInfo();
 
-					// FIXME: Random order of distribution
 					RandomGeneratorUtil::randomShuffle(matchingZones, zone.getRand());
 					for (auto& zone : matchingZones)
 					{
@@ -146,7 +146,18 @@ void ObjectDistributor::distributePrisons()
 
 	RandomGeneratorUtil::randomShuffle(zones, zone.getRand());
 
-	size_t allowedPrisons = generator.getPrisonsRemaning();
+	// TODO: Some shorthand for unique Modificator
+	PrisonHeroPlacer * prisonHeroPlacer = nullptr;
+	for(auto & z : map.getZones())
+	{
+		prisonHeroPlacer = z.second->getModificator<PrisonHeroPlacer>();
+		if (prisonHeroPlacer)
+		{
+			break;
+		}
+	}
+
+	size_t allowedPrisons = prisonHeroPlacer->getPrisonsRemaning();
 	for (int i = zones.size() - 1; i >= 0; i--)
 	{
 		auto zone = zones[i].second;

+ 73 - 0
lib/rmg/modificators/PrisonHeroPlacer.cpp

@@ -0,0 +1,73 @@
+/*
+* PrisonHeroPlacer.cpp, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#include "StdInc.h"
+#include "PrisonHeroPlacer.h"
+#include "../CMapGenerator.h"
+#include "../RmgMap.h"
+#include "TreasurePlacer.h"
+#include "../CZonePlacer.h"
+#include "../../VCMI_Lib.h"
+#include "../../mapObjectConstructors/AObjectTypeHandler.h"
+#include "../../mapObjectConstructors/CObjectClassesHandler.h"
+#include "../../mapObjects/MapObjects.h" 
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+void PrisonHeroPlacer::process()
+{
+	getAllowedHeroes();
+}
+
+void PrisonHeroPlacer::init()
+{
+	// Reserve at least 16 heroes for each player
+	reservedHeroes = 16 * generator.getMapGenOptions().getHumanOrCpuPlayerCount();
+}
+
+void PrisonHeroPlacer::getAllowedHeroes()
+{
+	// TODO: Give each zone unique HeroPlacer with private hero list?
+
+	// Call that only once
+	if (allowedHeroes.empty())
+	{
+    	allowedHeroes = generator.getAllPossibleHeroes();
+	}
+}
+
+int PrisonHeroPlacer::getPrisonsRemaning() const
+{
+	return std::max<int>(allowedHeroes.size() - reservedHeroes, 0);
+}
+
+HeroTypeID PrisonHeroPlacer::drawRandomHero()
+{
+	RecursiveLock lock(externalAccessMutex);
+	if (getPrisonsRemaning() > 0)
+	{
+		RandomGeneratorUtil::randomShuffle(allowedHeroes, zone.getRand());
+        HeroTypeID ret = allowedHeroes.back();
+        allowedHeroes.pop_back();
+		return ret;
+	}
+	else
+	{
+		throw rmgException("No unused heroes left for prisons!");
+	}
+}
+
+void PrisonHeroPlacer::restoreDrawnHero(const HeroTypeID & hid)
+{
+	RecursiveLock lock(externalAccessMutex);
+	allowedHeroes.push_back(hid);
+}
+
+VCMI_LIB_NAMESPACE_END

+ 41 - 0
lib/rmg/modificators/PrisonHeroPlacer.h

@@ -0,0 +1,41 @@
+/*
+* PrisonHeroPlacer, part of VCMI engine
+*
+* Authors: listed in file AUTHORS in main folder
+*
+* License: GNU General Public License v2.0 or later
+* Full text of license available in license.txt file, in main folder
+*
+*/
+
+#pragma once
+#include "../Zone.h"
+#include "../Functions.h"
+#include "../../mapObjects/ObjectTemplate.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class CRandomGenerator;
+
+class PrisonHeroPlacer : public Modificator
+{
+public:
+	MODIFICATOR(PrisonHeroPlacer);
+
+	void process() override;
+	void init() override;
+
+	int getPrisonsRemaning() const;
+	[[nodiscard]] HeroTypeID drawRandomHero();
+	void restoreDrawnHero(const HeroTypeID & hid);
+
+private:
+    void getAllowedHeroes();
+	size_t reservedHeroes;
+
+protected:
+
+    std::vector<HeroTypeID> allowedHeroes;
+};
+
+VCMI_LIB_NAMESPACE_END

+ 12 - 3
lib/rmg/modificators/QuestArtifactPlacer.cpp

@@ -40,11 +40,18 @@ void QuestArtifactPlacer::addQuestArtZone(std::shared_ptr<Zone> otherZone)
 
 void QuestArtifactPlacer::addQuestArtifact(const ArtifactID& id)
 {
+	logGlobal->info("Need to place quest artifact %s", VLC->artifacts()->getById(id)->getNameTranslated());
 	RecursiveLock lock(externalAccessMutex);
-	logGlobal->info("Need to place quest artifact artifact %s", VLC->artifacts()->getById(id)->getNameTranslated());
 	questArtifactsToPlace.emplace_back(id);
 }
 
+void QuestArtifactPlacer::removeQuestArtifact(const ArtifactID& id)
+{
+	logGlobal->info("Will not try to place quest artifact %s", VLC->artifacts()->getById(id)->getNameTranslated());
+	RecursiveLock lock(externalAccessMutex);
+	vstd::erase_if_present(questArtifactsToPlace, id);
+}
+
 void QuestArtifactPlacer::rememberPotentialArtifactToReplace(CGObjectInstance* obj)
 {
 	RecursiveLock lock(externalAccessMutex);
@@ -131,9 +138,10 @@ ArtifactID QuestArtifactPlacer::drawRandomArtifact()
 	RecursiveLock lock(externalAccessMutex);
 	if (!questArtifacts.empty())
 	{
+		RandomGeneratorUtil::randomShuffle(questArtifacts, zone.getRand());
 		ArtifactID ret = questArtifacts.back();
 		questArtifacts.pop_back();
-		RandomGeneratorUtil::randomShuffle(questArtifacts, zone.getRand());
+		generator.banQuestArt(ret);
 		return ret;
 	}
 	else
@@ -142,10 +150,11 @@ ArtifactID QuestArtifactPlacer::drawRandomArtifact()
 	}
 }
 
-void QuestArtifactPlacer::addRandomArtifact(ArtifactID artid)
+void QuestArtifactPlacer::addRandomArtifact(const ArtifactID & artid)
 {
 	RecursiveLock lock(externalAccessMutex);
 	questArtifacts.push_back(artid);
+	generator.unbanQuestArt(artid);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 3 - 2
lib/rmg/modificators/QuestArtifactPlacer.h

@@ -29,14 +29,15 @@ public:
 	void findZonesForQuestArts();
 
 	void addQuestArtifact(const ArtifactID& id);
+	void removeQuestArtifact(const ArtifactID& id);
 	void rememberPotentialArtifactToReplace(CGObjectInstance* obj);
 	std::vector<CGObjectInstance*> getPossibleArtifactsToReplace() const;
 	void placeQuestArtifacts(CRandomGenerator & rand);
 	void dropReplacedArtifact(CGObjectInstance* obj);
 
 	size_t getMaxQuestArtifactCount() const;
-	ArtifactID drawRandomArtifact();
-	void addRandomArtifact(ArtifactID artid);
+	[[nodiscard]] ArtifactID drawRandomArtifact();
+	void addRandomArtifact(const ArtifactID & artid);
 
 protected:
 

+ 57 - 26
lib/rmg/modificators/TreasurePlacer.cpp

@@ -18,6 +18,7 @@
 #include "../RmgMap.h"
 #include "../TileInfo.h"
 #include "../CZonePlacer.h"
+#include "PrisonHeroPlacer.h"
 #include "QuestArtifactPlacer.h"
 #include "../../ArtifactUtils.h"
 #include "../../mapObjectConstructors/AObjectTypeHandler.h"
@@ -32,6 +33,12 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+ObjectInfo::ObjectInfo():
+	destroyObject([](){})
+{
+
+}
+
 void TreasurePlacer::process()
 {
 	addAllPossibleObjects();
@@ -45,6 +52,7 @@ void TreasurePlacer::init()
 	maxPrisons = 0; //Should be in the constructor, but we use macro for that
 	DEPENDENCY(ObjectManager);
 	DEPENDENCY(ConnectionsPlacer);
+	DEPENDENCY_ALL(PrisonHeroPlacer);
 	POSTFUNCTION(RoadPlacer);
 }
 
@@ -90,6 +98,16 @@ void TreasurePlacer::addAllPossibleObjects()
 	auto prisonTemplates = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType());
 	if (!prisonTemplates.empty())
 	{
+		PrisonHeroPlacer * prisonHeroPlacer = nullptr;
+		for(auto & z : map.getZones())
+		{
+			prisonHeroPlacer = z.second->getModificator<PrisonHeroPlacer>();
+		 	if (prisonHeroPlacer)
+			{
+				break;
+			}
+		}
+
 		//prisons
 		//levels 1, 5, 10, 20, 30
 		static int prisonsLevels = std::min(generator.getConfig().prisonExperience.size(), generator.getConfig().prisonValues.size());
@@ -97,16 +115,22 @@ void TreasurePlacer::addAllPossibleObjects()
 		size_t prisonsLeft = getMaxPrisons();
 		for (int i = prisonsLevels - 1; i >= 0; i--)
 		{
+			ObjectInfo oi; // Create new instance which will hold destructor operation
+
 			oi.value = generator.getConfig().prisonValues[i];
 			if (oi.value > zone.getMaxTreasureValue())
 			{
 				continue;
 			}
 
-			oi.generateObject = [i, this]() -> CGObjectInstance*
+			oi.generateObject = [i, this, prisonHeroPlacer, &oi]() -> CGObjectInstance*
 			{
-				auto possibleHeroes = generator.getAllPossibleHeroes();
-				HeroTypeID hid = *RandomGeneratorUtil::nextItem(possibleHeroes, zone.getRand());
+				HeroTypeID hid = prisonHeroPlacer->drawRandomHero();
+				oi.destroyObject = [hid, prisonHeroPlacer]()
+				{
+					// Hero can be used again
+					prisonHeroPlacer->restoreDrawnHero(hid);
+				};
 
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
 				auto* obj = dynamic_cast<CGHeroInstance*>(factory->create());
@@ -114,7 +138,6 @@ void TreasurePlacer::addAllPossibleObjects()
 				obj->setHeroType(hid); //will be initialized later
 				obj->exp = generator.getConfig().prisonExperience[i];
 				obj->setOwner(PlayerColor::NEUTRAL);
-				generator.banHero(hid);
 
 				return obj;
 			};
@@ -441,6 +464,19 @@ void TreasurePlacer::addAllPossibleObjects()
 		
 		RandomGeneratorUtil::randomShuffle(creatures, zone.getRand());
 
+		auto setRandomArtifact = [qap, &oi](CGSeerHut * obj)
+		{
+			ArtifactID artid = qap->drawRandomArtifact();
+			oi.destroyObject = [artid, qap]()
+			{
+				// Artifact can be used again
+				qap->addRandomArtifact(artid);
+				qap->removeQuestArtifact(artid);
+			};
+			obj->quest->mission.artifacts.push_back(artid);
+			qap->addQuestArtifact(artid);
+		};
+
 		for(int i = 0; i < static_cast<int>(creatures.size()); i++)
 		{
 			auto * creature = creatures[i];
@@ -451,7 +487,8 @@ void TreasurePlacer::addAllPossibleObjects()
 			
 			int randomAppearance = chooseRandomAppearance(zone.getRand(), Obj::SEER_HUT, zone.getTerrainType());
 			
-			oi.generateObject = [creature, creaturesAmount, randomAppearance, this, qap]() -> CGObjectInstance *
+			// FIXME: Remove duplicated code for gold, exp and creaure reward
+			oi.generateObject = [creature, creaturesAmount, randomAppearance, setRandomArtifact]() -> CGObjectInstance *
 			{
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
@@ -461,11 +498,7 @@ void TreasurePlacer::addAllPossibleObjects()
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				obj->configuration.info.push_back(reward);
 								
-				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->mission.artifacts.push_back(artid);
-				
-				generator.banQuestArt(artid);
-				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
+				setRandomArtifact(obj);
 				
 				return obj;
 			};
@@ -499,7 +532,7 @@ void TreasurePlacer::addAllPossibleObjects()
 			oi.probability = 10;
 			oi.maxPerZone = 1;
 			
-			oi.generateObject = [i, randomAppearance, this, qap]() -> CGObjectInstance *
+			oi.generateObject = [i, randomAppearance, this, setRandomArtifact]() -> CGObjectInstance *
 			{
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
@@ -508,20 +541,16 @@ void TreasurePlacer::addAllPossibleObjects()
 				reward.reward.heroExperience = generator.getConfig().questRewardValues[i];
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				obj->configuration.info.push_back(reward);
-				
-				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->mission.artifacts.push_back(artid);
-				
-				generator.banQuestArt(artid);
-				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
-				
+
+				setRandomArtifact(obj);
+
 				return obj;
 			};
 			
 			if(!oi.templates.empty())
 				possibleSeerHuts.push_back(oi);
 			
-			oi.generateObject = [i, randomAppearance, this, qap]() -> CGObjectInstance *
+			oi.generateObject = [i, randomAppearance, this, setRandomArtifact]() -> CGObjectInstance *
 			{
 				auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
 				auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
@@ -531,11 +560,7 @@ void TreasurePlacer::addAllPossibleObjects()
 				reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
 				obj->configuration.info.push_back(reward);
 				
-				ArtifactID artid = qap->drawRandomArtifact();
-				obj->quest->mission.artifacts.push_back(artid);
-				
-				generator.banQuestArt(artid);
-				zone.getModificator<QuestArtifactPlacer>()->addQuestArtifact(artid);
+				setRandomArtifact(obj);
 				
 				return obj;
 			};
@@ -641,8 +666,14 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 		}
 		
 		auto * object = oi->generateObject();
+
 		if(oi->templates.empty())
+		{
+			logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName());
+			oi->destroyObject();
+			delete object;
 			continue;
+		}
 		
 		auto templates = object->getObjectHandler()->getMostSpecificTemplates(zone.getTerrainType());
 
@@ -721,7 +752,7 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
 					instanceAccessibleArea.add(instance.getVisitablePosition());
 			}
 			
-			//first object is good
+			//Do not clean up after first object
 			if(rmgObject.instances().size() == 1)
 				break;
 
@@ -800,10 +831,10 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
 	{
 		for (auto* oi : treasurePile)
 		{
+			oi->destroyObject();
 			oi->maxPerZone++;
 		}
 	};
-
 	//place biggest treasures first at large distance, place smaller ones inbetween
 	auto treasureInfo = zone.getTreasureInfo();
 	boost::sort(treasureInfo, valueComparator);

+ 3 - 0
lib/rmg/modificators/TreasurePlacer.h

@@ -22,12 +22,15 @@ class CRandomGenerator;
 
 struct ObjectInfo
 {
+	ObjectInfo();
+
 	std::vector<std::shared_ptr<const ObjectTemplate>> templates;
 	ui32 value = 0;
 	ui16 probability = 0;
 	ui32 maxPerZone = 1;
 	//ui32 maxPerMap; //unused
 	std::function<CGObjectInstance *()> generateObject;
+	std::function<void()> destroyObject;
 	
 	void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain);
 };

+ 5 - 2
mapeditor/mapcontroller.cpp

@@ -131,7 +131,12 @@ void MapController::repairMap(CMap * map) const
 		//fix hero instance
 		if(auto * nih = dynamic_cast<CGHeroInstance*>(obj.get()))
 		{
+			// All heroes present on map or in prisons need to be allowed to rehire them after they are defeated
+
+			// FIXME: How about custom scenarios where defeated hero cannot be hired again?
+
 			map->allowedHeroes.insert(nih->getHeroType());
+
 			auto type = VLC->heroh->objects[nih->subID];
 			assert(type->heroClass);
 			//TODO: find a way to get proper type name
@@ -198,8 +203,6 @@ void MapController::repairMap(CMap * map) const
 				auto a = ArtifactUtils::createScroll(*RandomGeneratorUtil::nextItem(out, CRandomGenerator::getDefault()));
 				art->storedArtifact = a;
 			}
-			else
-				map->allowedArtifact.insert(art->getArtifact());
 		}
 	}
 }