소스 검색

Merge pull request #2297 from vcmi/rmg_roads

Rmg roads
DjWarmonger 2 년 전
부모
커밋
f6cc61e0be

+ 2 - 3
client/lobby/RandomMapTab.cpp

@@ -119,12 +119,11 @@ RandomMapTab::RandomMapTab():
 		std::string cbRoadType = "selectRoad_" + road->getJsonKey();
 		addCallback(cbRoadType, [&, road](bool on)
 		{
-			mapGenOptions->setRoadEnabled(road->getJsonKey(), on);
+			mapGenOptions->setRoadEnabled(road->getId(), on);
 			updateMapInfoByHost();
 		});
 	}
 	
-	
 	build(config);
 	
 	updateMapInfoByHost();
@@ -313,7 +312,7 @@ void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
 	{
 		if(auto w = widget<CToggleButton>(r->getJsonKey()))
 		{
-			w->setSelected(opts->isRoadEnabled(r->getJsonKey()));
+			w->setSelected(opts->isRoadEnabled(r->getId()));
 		}
 	}
 }

+ 18 - 6
lib/rmg/CMapGenOptions.cpp

@@ -26,6 +26,9 @@ CMapGenOptions::CMapGenOptions()
 	waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM), mapTemplate(nullptr)
 {
 	resetPlayersMap();
+	setRoadEnabled(RoadId(Road::DIRT_ROAD), true);
+	setRoadEnabled(RoadId(Road::GRAVEL_ROAD), true);
+	setRoadEnabled(RoadId(Road::COBBLESTONE_ROAD), true);
 }
 
 si32 CMapGenOptions::getWidth() const
@@ -233,17 +236,26 @@ void CMapGenOptions::setMapTemplate(const std::string & name)
 		setMapTemplate(VLC->tplh->getTemplate(name));
 }
 
-void CMapGenOptions::setRoadEnabled(const std::string & roadName, bool enable)
+void CMapGenOptions::setRoadEnabled(const RoadId & roadType, bool enable)
 {
-	if(enable)
-		disabledRoads.erase(roadName);
+	if (enable)
+	{
+		enabledRoads.insert(roadType);
+	}
 	else
-		disabledRoads.insert(roadName);
+	{
+		enabledRoads.erase(roadType);
+	}	
+}
+
+bool CMapGenOptions::isRoadEnabled(const RoadId & roadType) const
+{
+	return enabledRoads.count(roadType);
 }
 
-bool CMapGenOptions::isRoadEnabled(const std::string & roadName) const
+bool CMapGenOptions::isRoadEnabled() const
 {
-	return !disabledRoads.count(roadName);
+	return !enabledRoads.empty();
 }
 
 void CMapGenOptions::setPlayerTeam(const PlayerColor & color, const TeamID & team)

+ 5 - 4
lib/rmg/CMapGenOptions.h

@@ -110,8 +110,9 @@ public:
 	EMonsterStrength::EMonsterStrength getMonsterStrength() const;
 	void setMonsterStrength(EMonsterStrength::EMonsterStrength value);
 	
-	bool isRoadEnabled(const std::string & roadName) const;
-	void setRoadEnabled(const std::string & roadName, bool enable);
+	bool isRoadEnabled(const RoadId & roadType) const;
+	bool isRoadEnabled() const;
+	void setRoadEnabled(const RoadId & roadType, bool enable);
 
 	/// The first player colors belong to standard players and the last player colors belong to comp only players.
 	/// All standard players are by default of type EPlayerType::AI.
@@ -156,7 +157,7 @@ private:
 	EWaterContent::EWaterContent waterContent;
 	EMonsterStrength::EMonsterStrength monsterStrength;
 	std::map<PlayerColor, CPlayerSettings> players;
-	std::set<std::string> disabledRoads;
+	std::set<RoadId> enabledRoads;
 	
 	const CRmgTemplate * mapTemplate;
 
@@ -187,7 +188,7 @@ public:
 				setMapTemplate(templateName);
 			}
 			
-			h & disabledRoads;
+			h & enabledRoads;
 		}
 	}
 };

+ 0 - 5
lib/rmg/CMapGenerator.cpp

@@ -80,11 +80,6 @@ void CMapGenerator::loadConfig()
 	config.pandoraMultiplierSpells = randomMapJson["pandoras"]["valueMultiplierSpells"].Integer();
 	config.pandoraSpellSchool = randomMapJson["pandoras"]["valueSpellSchool"].Integer();
 	config.pandoraSpell60 = randomMapJson["pandoras"]["valueSpell60"].Integer();
-	//override config with game options
-	if(!mapGenOptions.isRoadEnabled(config.secondaryRoadType))
-		config.secondaryRoadType = "";
-	if(!mapGenOptions.isRoadEnabled(config.defaultRoadType))
-		config.defaultRoadType = config.secondaryRoadType;
 	config.singleThread = randomMapJson["singleThread"].Bool();
 }
 

+ 18 - 4
lib/rmg/CRmgTemplate.cpp

@@ -435,11 +435,12 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 	}
 }
 
-ZoneConnection::ZoneConnection()
-	: zoneA(-1),
+ZoneConnection::ZoneConnection():
+	zoneA(-1),
 	zoneB(-1),
 	guardStrength(0),
-	connectionType(EConnectionType::EConnectionType::GUARDED)
+	connectionType(rmg::EConnectionType::GUARDED),
+	hasRoad(rmg::ERoadOption::ROAD_TRUE)
 {
 
 }
@@ -475,10 +476,15 @@ int ZoneConnection::getGuardStrength() const
 	return guardStrength;
 }
 
-EConnectionType::EConnectionType ZoneConnection::getConnectionType() const
+rmg::EConnectionType ZoneConnection::getConnectionType() const
 {
 	return connectionType;
 }
+
+rmg::ERoadOption ZoneConnection::getRoadOption() const
+{
+	return hasRoad;
+}
 	
 bool operator==(const ZoneConnection & l, const ZoneConnection & r)
 {
@@ -495,10 +501,18 @@ void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
 		"wide"
 	};
 
+	static const std::vector<std::string> roadOptions =
+	{
+		"true",
+		"false",
+		"random"
+	};
+
 	handler.serializeId<TRmgTemplateZoneId, TRmgTemplateZoneId, ZoneEncoder>("a", zoneA, -1);
 	handler.serializeId<TRmgTemplateZoneId, TRmgTemplateZoneId, ZoneEncoder>("b", zoneB, -1);
 	handler.serializeInt("guard", guardStrength, 0);
 	handler.serializeEnum("type", connectionType, connectionTypes);
+	handler.serializeEnum("road", hasRoad, roadOptions);
 }
 
 }

+ 18 - 12
lib/rmg/CRmgTemplate.h

@@ -67,19 +67,23 @@ public:
 	void serializeJson(JsonSerializeFormat & handler);
 };
 
-namespace EConnectionType
+namespace rmg
 {
-	enum class EConnectionType
-	{
-		GUARDED = 0, //default
-		FICTIVE,
-		REPULSIVE,
-		WIDE
-	};
-}
 
-namespace rmg
+enum class EConnectionType
 {
+	GUARDED = 0, //default
+	FICTIVE,
+	REPULSIVE,
+	WIDE
+};
+
+enum class ERoadOption
+{
+	ROAD_TRUE,
+	ROAD_FALSE,
+	ROAD_RANDOM
+};
 
 class DLL_LINKAGE ZoneConnection
 {
@@ -91,7 +95,8 @@ public:
 	TRmgTemplateZoneId getZoneB() const;
 	TRmgTemplateZoneId getOtherZoneId(TRmgTemplateZoneId id) const;
 	int getGuardStrength() const;
-	EConnectionType::EConnectionType getConnectionType() const;
+	rmg::EConnectionType getConnectionType() const;
+	rmg::ERoadOption getRoadOption() const;
 
 	void serializeJson(JsonSerializeFormat & handler);
 	
@@ -100,7 +105,8 @@ private:
 	TRmgTemplateZoneId zoneA;
 	TRmgTemplateZoneId zoneB;
 	int guardStrength;
-	EConnectionType::EConnectionType connectionType;
+	rmg::EConnectionType connectionType;
+	rmg::ERoadOption hasRoad;
 };
 
 class DLL_LINKAGE ZoneOptions

+ 5 - 5
lib/rmg/CZonePlacer.cpp

@@ -75,7 +75,7 @@ void CZonePlacer::findPathsBetweenZones()
 
 			for (auto & connection : connectedZoneIds)
 			{
-				if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+				if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 				{
 					//Do not consider virtual connections for graph distance
 					continue;
@@ -536,7 +536,7 @@ void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces,
 
 		for (const auto & connection : zone.second->getConnections())
 		{
-			if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 			{
 				continue;
 			}
@@ -625,7 +625,7 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces
 		//TODO: Consider z plane?
 		for (auto& connection : zone.second->getConnections())
 		{
-			if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			if (connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 			{
 				auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
 				float3 otherZoneCenter = otherZone->getCenter();
@@ -693,7 +693,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 		for (const auto& connection : firstZone->getConnections())
 		{
 			//FIXME: Should we also exclude fictive connections?
-			if (connection.getConnectionType() != EConnectionType::EConnectionType::REPULSIVE)
+			if (connection.getConnectionType() != rmg::EConnectionType::REPULSIVE)
 			{
 				connectedZones.insert(connection.getOtherZoneId(firstZone->getId()));
 			}
@@ -740,7 +740,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 		float maxDistance = 0;
 		for (auto con : misplacedZone->getConnections())
 		{
-			if (con.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			if (con.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 			{
 				continue;
 			}

+ 32 - 18
lib/rmg/modificators/ConnectionsPlacer.cpp

@@ -98,6 +98,7 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 	bool success = false;
 	auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
 	auto & otherZone = map.getZones().at(otherZoneId);
+	bool createRoad = shouldGenerateRoad(connection);
 	
 	//1. Try to make direct connection
 	//Do if it's not prohibited by terrain settings
@@ -110,7 +111,7 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 
 	if (directConnectionIterator != dNeighbourZones.end())
 	{
-		if (connection.getConnectionType() == EConnectionType::EConnectionType::WIDE)
+		if (connection.getConnectionType() == rmg::EConnectionType::WIDE)
 		{
 			for (auto borderPos : directConnectionIterator->second)
 			{
@@ -159,8 +160,8 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 		}
 	}
 
-	if (connection.getConnectionType() == EConnectionType::EConnectionType::FICTIVE || 
-		connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+	if (connection.getConnectionType() == rmg::EConnectionType::FICTIVE || 
+		connection.getConnectionType() == rmg::EConnectionType::REPULSIVE)
 	{
 		//Fictive or repulsive connections are not real, take no action
 		dCompleted.push_back(connection);
@@ -254,15 +255,18 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 					otherZone->getModificator<ObjectManager>()->updateDistances(guardPos);
 				}
 				
-				assert(zone.getModificator<RoadPlacer>());
-				zone.getModificator<RoadPlacer>()->addRoadNode(guardPos);
-				
-				assert(otherZone->getModificator<RoadPlacer>());
-				otherZone->getModificator<RoadPlacer>()->addRoadNode(roadNode);
-				
-				assert(otherZone->getModificator<ConnectionsPlacer>());
-				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
-				
+				if (createRoad)
+				{
+					assert(zone.getModificator<RoadPlacer>());
+					zone.getModificator<RoadPlacer>()->addRoadNode(guardPos);
+
+					assert(otherZone->getModificator<RoadPlacer>());
+					otherZone->getModificator<RoadPlacer>()->addRoadNode(roadNode);
+
+					assert(otherZone->getModificator<ConnectionsPlacer>());
+					otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
+				}
+
 				success = true;
 			}
 		}
@@ -274,7 +278,7 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 	{
 		if(generator.getZoneWater() && generator.getZoneWater()->getModificator<WaterProxy>())
 		{
-			if(generator.getZoneWater()->getModificator<WaterProxy>()->waterKeepConnection(connection.getZoneA(), connection.getZoneB()))
+			if(generator.getZoneWater()->getModificator<WaterProxy>()->waterKeepConnection(connection, createRoad))
 			{
 				assert(otherZone->getModificator<ConnectionsPlacer>());
 				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
@@ -292,6 +296,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 	bool success = false;
 	auto otherZoneId = (connection.getZoneA() == zone.getId() ? connection.getZoneB() : connection.getZoneA());
 	auto & otherZone = map.getZones().at(otherZoneId);
+
+	bool allowRoad = shouldGenerateRoad(connection);
 	
 	//3. place subterrain gates
 	if(zone.isUnderground() != otherZone->isUnderground())
@@ -341,8 +347,8 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 				zone.connectPath(path1);
 				otherZone->connectPath(path2);
 				
-				manager.placeObject(rmgGate1, guarded1, true);
-				managerOther.placeObject(rmgGate2, guarded2, true);
+				manager.placeObject(rmgGate1, guarded1, true, allowRoad);
+				managerOther.placeObject(rmgGate2, guarded2, true, allowRoad);
 				
 				assert(otherZone->getModificator<ConnectionsPlacer>());
 				otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
@@ -359,8 +365,10 @@ void ConnectionsPlacer::selfSideIndirectConnection(const rmg::ZoneConnection & c
 		auto * teleport1 = factory->create();
 		auto * teleport2 = factory->create();
 
-		zone.getModificator<ObjectManager>()->addRequiredObject(teleport1, connection.getGuardStrength());
-		otherZone->getModificator<ObjectManager>()->addRequiredObject(teleport2, connection.getGuardStrength());
+		RequiredObjectInfo obj1(teleport1, connection.getGuardStrength(), allowRoad);
+		RequiredObjectInfo obj2(teleport2, connection.getGuardStrength(), allowRoad);
+		zone.getModificator<ObjectManager>()->addRequiredObject(obj1);
+		otherZone->getModificator<ObjectManager>()->addRequiredObject(obj2);
 		
 		assert(otherZone->getModificator<ConnectionsPlacer>());
 		otherZone->getModificator<ConnectionsPlacer>()->otherSideConnection(connection);
@@ -386,6 +394,12 @@ void ConnectionsPlacer::collectNeighbourZones()
 	}
 }
 
+bool ConnectionsPlacer::shouldGenerateRoad(const rmg::ZoneConnection& connection) const
+{
+	return connection.getRoadOption() == rmg::ERoadOption::ROAD_TRUE ||
+		(connection.getRoadOption() == rmg::ERoadOption::ROAD_RANDOM && zone.getRand().nextDouble() >= 0.5f);
+}
+
 void ConnectionsPlacer::createBorder()
 {
 	rmg::Area borderArea(zone.getArea().getBorder());
@@ -401,7 +415,7 @@ void ConnectionsPlacer::createBorder()
 	{
 		auto otherZone = connection.getOtherZoneId(zone.getId());
 
-		if (connection.getConnectionType() == EConnectionType::EConnectionType::WIDE)
+		if (connection.getConnectionType() == rmg::EConnectionType::WIDE)
 		{
 			auto sharedBorder = borderArea.getSubarea([this, otherZone, &borderOutsideArea](const int3 & t)
 			{

+ 2 - 0
lib/rmg/modificators/ConnectionsPlacer.h

@@ -28,6 +28,8 @@ public:
 	void selfSideIndirectConnection(const rmg::ZoneConnection & connection);
 	void otherSideConnection(const rmg::ZoneConnection & connection);
 	void createBorder();
+
+	bool shouldGenerateRoad(const rmg::ZoneConnection& connection) const;
 	
 protected:
 	void collectNeighbourZones();

+ 13 - 6
lib/rmg/modificators/MinePlacer.cpp

@@ -63,8 +63,11 @@ bool MinePlacer::placeMines(ObjectManager & manager)
 			createdMines.push_back(mine);
 
 
-			if(!i && (res == EGameResID::WOOD || res == EGameResID::ORE))
-				manager.addCloseObject(mine, rmginfo.value); //only first wood&ore mines are close
+			if (!i && (res == EGameResID::WOOD || res == EGameResID::ORE))
+			{
+				//only first wood & ore mines are close
+				manager.addCloseObject(RequiredObjectInfo(mine, rmginfo.value));
+			}
 			else
 				requiredObjects.push_back(std::pair<CGObjectInstance*, ui32>(mine, rmginfo.value));
 		}
@@ -74,7 +77,7 @@ bool MinePlacer::placeMines(ObjectManager & manager)
 	RandomGeneratorUtil::randomShuffle(requiredObjects, zone.getRand());
 	for (const auto& obj : requiredObjects)
 	{
-		manager.addRequiredObject(obj.first, obj.second);
+		manager.addRequiredObject(RequiredObjectInfo(obj.first, obj.second));
 	}
 
 	//create extra resources
@@ -84,9 +87,13 @@ bool MinePlacer::placeMines(ObjectManager & manager)
 		{
 			for(int rc = zone.getRand().nextInt(1, extraRes); rc > 0; --rc)
 			{
-				auto * resourse = dynamic_cast<CGResource *>(VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create());
-				resourse->amount = CGResource::RANDOM_AMOUNT;
-				manager.addNearbyObject(resourse, mine);
+				auto * resource = dynamic_cast<CGResource *>(VLC->objtypeh->getHandlerFor(Obj::RESOURCE, mine->producedResource)->create());
+				resource->amount = CGResource::RANDOM_AMOUNT;
+
+				RequiredObjectInfo roi;
+				roi.obj = resource;
+				roi.nearbyTarget = mine;
+				manager.addNearbyObject(roi);
 			}
 		}
 	}

+ 54 - 41
lib/rmg/modificators/ObjectManager.cpp

@@ -57,22 +57,22 @@ void ObjectManager::createDistancesPriorityQueue()
 	}
 }
 
-void ObjectManager::addRequiredObject(CGObjectInstance * obj, si32 strength)
+void ObjectManager::addRequiredObject(const RequiredObjectInfo & info)
 {
 	RecursiveLock lock(externalAccessMutex);
-	requiredObjects.emplace_back(obj, strength);
+	requiredObjects.emplace_back(info);
 }
 
-void ObjectManager::addCloseObject(CGObjectInstance * obj, si32 strength)
+void ObjectManager::addCloseObject(const RequiredObjectInfo & info)
 {
 	RecursiveLock lock(externalAccessMutex);
-	closeObjects.emplace_back(obj, strength);
+	closeObjects.emplace_back(info);
 }
 
-void ObjectManager::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget)
+void ObjectManager::addNearbyObject(const RequiredObjectInfo & info)
 {
 	RecursiveLock lock(externalAccessMutex);
-	nearbyObjects.emplace_back(obj, nearbyTarget);
+	nearbyObjects.emplace_back(info);
 }
 
 void ObjectManager::updateDistances(const rmg::Object & obj)
@@ -331,13 +331,11 @@ bool ObjectManager::createRequiredObjects()
 	logGlobal->trace("Creating required objects");
 	
 	//RecursiveLock lock(externalAccessMutex); //Why could requiredObjects be modified during the loop?
-	for(const auto & object : requiredObjects)
+	for(const auto & objInfo : requiredObjects)
 	{
-		auto * obj = object.first;
-		//FIXME: Invalid dObject inside object?
-		rmg::Object rmgObject(*obj);
+		rmg::Object rmgObject(*objInfo.obj);
 		rmgObject.setTemplate(zone.getTerrainType());
-		bool guarded = addGuard(rmgObject, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY));
+		bool guarded = addGuard(rmgObject, objInfo.guardStrength, (objInfo.obj->ID == Obj::MONOLITH_TWO_WAY));
 
 		Zone::Lock lock(zone.areaMutex);
 		auto path = placeAndConnectObject(zone.areaPossible(), rmgObject, 3, guarded, false, OptimizeType::DISTANCE);
@@ -349,14 +347,14 @@ bool ObjectManager::createRequiredObjects()
 		}
 		
 		zone.connectPath(path);
-		placeObject(rmgObject, guarded, true);
+		placeObject(rmgObject, guarded, true, objInfo.createRoad);
 		
 		for(const auto & nearby : nearbyObjects)
 		{
-			if(nearby.second != obj)
+			if(nearby.nearbyTarget != nearby.obj)
 				continue;
 			
-			rmg::Object rmgNearObject(*nearby.first);
+			rmg::Object rmgNearObject(*nearby.obj);
 			rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
 			possibleArea.intersect(zone.areaPossible());
 			if(possibleArea.empty())
@@ -366,21 +364,18 @@ bool ObjectManager::createRequiredObjects()
 			}
 			
 			rmgNearObject.setPosition(*RandomGeneratorUtil::nextItem(possibleArea.getTiles(), zone.getRand()));
-			placeObject(rmgNearObject, false, false);
+			placeObject(rmgNearObject, false, false, nearby.createRoad);
 		}
 	}
 	
-	for(const auto & object : closeObjects)
+	for(const auto & objInfo : closeObjects)
 	{
-		auto * obj = object.first;
-
-		//TODO: Wrap into same area proxy?
 		Zone::Lock lock(zone.areaMutex);
 		auto possibleArea = zone.areaPossible();
 
-		rmg::Object rmgObject(*obj);
+		rmg::Object rmgObject(*objInfo.obj);
 		rmgObject.setTemplate(zone.getTerrainType());
-		bool guarded = addGuard(rmgObject, object.second, (obj->ID == Obj::MONOLITH_TWO_WAY));
+		bool guarded = addGuard(rmgObject, objInfo.guardStrength, (objInfo.obj->ID == Obj::MONOLITH_TWO_WAY));
 		auto path = placeAndConnectObject(zone.areaPossible(), rmgObject,
 										  [this, &rmgObject](const int3 & tile)
 		{
@@ -401,10 +396,10 @@ bool ObjectManager::createRequiredObjects()
 		
 		for(const auto & nearby : nearbyObjects)
 		{
-			if(nearby.second != obj)
+			if(nearby.nearbyTarget != objInfo.obj)
 				continue;
 			
-			rmg::Object rmgNearObject(*nearby.first);
+			rmg::Object rmgNearObject(*nearby.obj);
 			rmg::Area possibleArea(rmgObject.instances().front()->getBlockedArea().getBorderOutside());
 			possibleArea.intersect(zone.areaPossible());
 			if(possibleArea.empty())
@@ -420,10 +415,10 @@ bool ObjectManager::createRequiredObjects()
 	
 	//create object on specific positions
 	//TODO: implement guards
-	for (const auto &obj : instantObjects)
+	for (const auto &objInfo : instantObjects)
 	{
-		rmg::Object rmgObject(*obj.first);
-		rmgObject.setPosition(obj.second);
+		rmg::Object rmgObject(*objInfo.obj);
+		rmgObject.setPosition(objInfo.pos);
 		placeObject(rmgObject, false, false);
 	}
 	
@@ -435,7 +430,7 @@ bool ObjectManager::createRequiredObjects()
 	return true;
 }
 
-void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateDistance)
+void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateDistance, bool createRoad/* = false*/)
 {	
 	object.finalize(map);
 
@@ -492,25 +487,27 @@ void ObjectManager::placeObject(rmg::Object & object, bool guarded, bool updateD
 				break;
 		}
 	}
-	
-	switch(object.instances().front()->object().ID)
+
+	if (createRoad)
 	{
-		case Obj::TOWN:
-		case Obj::RANDOM_TOWN:
-		case Obj::MONOLITH_TWO_WAY:
-		case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+		if (auto* m = zone.getModificator<RoadPlacer>())
+			m->addRoadNode(object.instances().front()->getVisitablePosition());
+	}
+
+	//TODO: Add road node to these objects:
+	/*
+	 	case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+	 	case Obj::RANDOM_TOWN:
 		case Obj::MONOLITH_ONE_WAY_EXIT:
-		case Obj::SUBTERRANEAN_GATE:
-		case Obj::SHIPYARD:
-			if(auto * m = zone.getModificator<RoadPlacer>())
-				m->addRoadNode(object.instances().front()->getVisitablePosition());
-			break;
-			
+	*/
+
+	switch (object.instances().front()->object().ID)
+	{
 		case Obj::WATER_WHEEL:
-			if(auto * m = zone.getModificator<RiverPlacer>())
+			if (auto* m = zone.getModificator<RiverPlacer>())
 				m->addRiverNode(object.instances().front()->getVisitablePosition());
 			break;
-			
+
 		default:
 			break;
 	}
@@ -613,4 +610,20 @@ bool ObjectManager::addGuard(rmg::Object & object, si32 strength, bool zoneGuard
 	return true;
 }
 
+RequiredObjectInfo::RequiredObjectInfo():
+	obj(nullptr),
+	nearbyTarget(nullptr),
+	guardStrength(0),
+	createRoad(true)
+{}
+
+RequiredObjectInfo::RequiredObjectInfo(CGObjectInstance* obj, ui32 guardStrength, bool createRoad, CGObjectInstance* nearbyTarget):
+	obj(obj),
+	nearbyTarget(nearbyTarget),
+	guardStrength(guardStrength),
+	createRoad(createRoad)
+{}
+
 VCMI_LIB_NAMESPACE_END
+
+

+ 20 - 8
lib/rmg/modificators/ObjectManager.h

@@ -29,6 +29,18 @@ struct DistanceMaximizeFunctor
 	}
 };
 
+struct RequiredObjectInfo
+{
+	RequiredObjectInfo();
+	RequiredObjectInfo(CGObjectInstance* obj, ui32 guardStrength = 0, bool createRoad = false, CGObjectInstance* nearbyTarget = nullptr);
+
+	CGObjectInstance* obj;
+	CGObjectInstance* nearbyTarget;
+	int3 pos;
+	ui32 guardStrength;
+	bool createRoad;
+};
+
 class ObjectManager: public Modificator
 {
 public:
@@ -45,9 +57,9 @@ public:
 	void process() override;
 	void init() override;
 
-	void addRequiredObject(CGObjectInstance * obj, si32 guardStrength=0);
-	void addCloseObject(CGObjectInstance * obj, si32 guardStrength = 0);
-	void addNearbyObject(CGObjectInstance * obj, CGObjectInstance * nearbyTarget);
+	void addRequiredObject(const RequiredObjectInfo & info);
+	void addCloseObject(const RequiredObjectInfo & info);
+	void addNearbyObject(const RequiredObjectInfo & info);
 
 	bool createRequiredObjects();
 
@@ -59,7 +71,7 @@ public:
 
 	CGCreature * chooseGuard(si32 strength, bool zoneGuard = false);
 	bool addGuard(rmg::Object & object, si32 strength, bool zoneGuard = false);
-	void placeObject(rmg::Object & object, bool guarded, bool updateDistance);
+	void placeObject(rmg::Object & object, bool guarded, bool updateDistance, bool createRoad = false);
 
 	void updateDistances(const rmg::Object & obj);
 	void updateDistances(const int3& pos);
@@ -72,10 +84,10 @@ public:
 	
 protected:
 	//content info
-	std::vector<std::pair<CGObjectInstance*, ui32>> requiredObjects;
-	std::vector<std::pair<CGObjectInstance*, ui32>> closeObjects;
-	std::vector<std::pair<CGObjectInstance*, int3>> instantObjects;
-	std::vector<std::pair<CGObjectInstance*, CGObjectInstance*>> nearbyObjects;
+	std::vector<RequiredObjectInfo> requiredObjects;
+	std::vector<RequiredObjectInfo> closeObjects;
+	std::vector<RequiredObjectInfo> instantObjects;
+	std::vector<RequiredObjectInfo> nearbyObjects;
 	std::vector<CGObjectInstance*> objects;
 	rmg::Area objectsVisitableArea;
 	

+ 24 - 8
lib/rmg/modificators/RoadPlacer.cpp

@@ -79,25 +79,41 @@ bool RoadPlacer::createRoad(const int3 & dst)
 }
 
 void RoadPlacer::drawRoads(bool secondary)
-{
-	if((secondary && generator.getConfig().secondaryRoadType.empty())
-	   || (!secondary && generator.getConfig().defaultRoadType.empty()))
-		return;
-	
-	//RecursiveLock lock(externalAccessMutex);
+{	
 	{
-		//FIXME: double lock - unsafe
+		//Clean space under roads even if they won't be eventually generated
 		Zone::Lock lock(zone.areaMutex);
 
 		zone.areaPossible().subtract(roads);
 		zone.freePaths().unite(roads);
 	}
 
+	if (!generator.getMapGenOptions().isRoadEnabled())
+	{
+		return;
+	}
+
+	if((secondary && generator.getConfig().secondaryRoadType.empty())
+		|| (!secondary && generator.getConfig().defaultRoadType.empty()))
+		return;
+
+	//TODO: Allow custom road type for object
+	//TODO: Remove these default types
+
 	auto tiles = roads.getTilesVector();
 
 	std::string roadName = (secondary ? generator.getConfig().secondaryRoadType : generator.getConfig().defaultRoadType);
 	RoadId roadType(*VLC->modh->identifiers.getIdentifier(CModHandler::scopeGame(), "road", roadName));
-	mapProxy->drawRoads(zone.getRand(), tiles, roadType);
+
+	//If our road type is not enabled, choose highest below it
+	for (int8_t bestRoad = roadType.getNum(); bestRoad > RoadId(Road::NO_ROAD).getNum(); bestRoad--)
+	{
+		if (generator.getMapGenOptions().isRoadEnabled(RoadId(bestRoad)))
+		{
+			mapProxy->drawRoads(zone.getRand(), tiles, RoadId(bestRoad));
+			return;
+		}
+	}
 }
 
 void RoadPlacer::addRoadNode(const int3& node)

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

@@ -152,7 +152,7 @@ int3 TownPlacer::placeMainTown(ObjectManager & manager, CGTownInstance & town)
 			}, ObjectManager::OptimizeType::WEIGHT);
 	}
 	rmgObject.setPosition(position + int3(2, 2, 0)); //place visitable tile in the exact center of a zone
-	manager.placeObject(rmgObject, false, true);
+	manager.placeObject(rmgObject, false, true, true);
 	cleanupBoundaries(rmgObject);
 	zone.setPos(rmgObject.getVisitablePosition()); //roads lead to main town
 	return position;
@@ -216,7 +216,9 @@ void TownPlacer::addNewTowns(int count, bool hasFort, const PlayerColor & player
 			placeMainTown(manager, *town);
 		}
 		else
-			manager.addRequiredObject(town);
+		{
+			manager.addRequiredObject(RequiredObjectInfo(town, 0, true));
+		}
 		totalTowns++;
 	}
 }

+ 24 - 8
lib/rmg/modificators/WaterProxy.cpp

@@ -133,6 +133,8 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 	
 	if(adopter->getCoastTiles().empty())
 		return result;
+
+	bool createRoad = false;
 	
 	//block zones are not connected by template
 	for(auto& lake : lakes)
@@ -162,17 +164,23 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 			int zoneTowns = 0;
 			if(auto * m = dst.getModificator<TownPlacer>())
 				zoneTowns = m->getTotalTowns();
+
+			if (vstd::contains(lake.keepRoads, dst.getId()))
+			{
+				createRoad = true;
+			}
 			
+			//FIXME: Why are Shipyards not allowed in zones with no towns?
 			if(dst.getType() == ETemplateZoneType::PLAYER_START || dst.getType() == ETemplateZoneType::CPU_START || zoneTowns)
 			{
-				if(placeShipyard(dst, lake, generator.getConfig().shipyardGuard, result))
+				if(placeShipyard(dst, lake, generator.getConfig().shipyardGuard, createRoad, result))
 				{
 					logGlobal->info("Shipyard successfully placed at zone %d", dst.getId());
 				}
 				else
 				{
 					logGlobal->warn("Shipyard placement failed, trying boat at zone %d", dst.getId());
-					if(placeBoat(dst, lake, result))
+					if(placeBoat(dst, lake, createRoad, result))
 					{
 						logGlobal->warn("Boat successfully placed at zone %d", dst.getId());
 					}
@@ -184,7 +192,7 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 			}
 			else
 			{
-				if(placeBoat(dst, lake, result))
+				if(placeBoat(dst, lake,  createRoad, result))
 				{
 					logGlobal->info("Boat successfully placed at zone %d", dst.getId());
 				}
@@ -199,21 +207,29 @@ RouteInfo WaterProxy::waterRoute(Zone & dst)
 	return result;
 }
 
-bool WaterProxy::waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB)
+bool WaterProxy::waterKeepConnection(const rmg::ZoneConnection & connection, bool createRoad)
 {
+	const auto & zoneA = connection.getZoneA();
+	const auto & zoneB = connection.getZoneB();
+
 	for(auto & lake : lakes)
 	{
 		if(lake.neighbourZones.count(zoneA) && lake.neighbourZones.count(zoneB))
 		{
 			lake.keepConnections.insert(zoneA);
 			lake.keepConnections.insert(zoneB);
+			if (createRoad)
+			{
+				lake.keepRoads.insert(zoneA);
+				lake.keepRoads.insert(zoneB);
+			}
 			return true;
 		}
 	}
 	return false;
 }
 
-bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
+bool WaterProxy::placeBoat(Zone & land, const Lake & lake, bool createRoad, RouteInfo & info)
 {
 	auto * manager = zone.getModificator<ObjectManager>();
 	if(!manager)
@@ -284,7 +300,7 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
 
 		zone.connectPath(path);
 		land.connectPath(landPath);
-		manager->placeObject(rmgObject, false, true);
+		manager->placeObject(rmgObject, false, true, createRoad);
 		land.getModificator<ObjectManager>()->updateDistances(rmgObject); //Keep land objects away from the boat
 		break;
 	}
@@ -292,7 +308,7 @@ bool WaterProxy::placeBoat(Zone & land, const Lake & lake, RouteInfo & info)
 	return !boardingPositions.empty();
 }
 
-bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, RouteInfo & info)
+bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, bool createRoad, RouteInfo & info)
 {
 	auto * manager = land.getModificator<ObjectManager>();
 	if(!manager)
@@ -372,7 +388,7 @@ bool WaterProxy::placeShipyard(Zone & land, const Lake & lake, si32 guard, Route
 		info.boarding = boardingPosition;
 		info.water = shipPositions;
 		
-		manager->placeObject(rmgObject, guarded, true);
+		manager->placeObject(rmgObject, guarded, true, createRoad);
 		
 		zone.areaPossible().subtract(shipyardOutToBlock);
 		for(const auto & i : shipyardOutToBlock.getTilesVector())

+ 4 - 3
lib/rmg/modificators/WaterProxy.h

@@ -34,9 +34,10 @@ public:
 		std::map<int, rmg::Tileset> reverseDistanceMap;
 		std::map<TRmgTemplateZoneId, rmg::Area> neighbourZones; //zones boardered. Area - part of land
 		std::set<TRmgTemplateZoneId> keepConnections;
+		std::set<TRmgTemplateZoneId> keepRoads;
 	};
 		
-	bool waterKeepConnection(TRmgTemplateZoneId zoneA, TRmgTemplateZoneId zoneB);
+	bool waterKeepConnection(const rmg::ZoneConnection & connection, bool createRoad);
 	RouteInfo waterRoute(Zone & dst);
 	
 	void process() override;
@@ -47,8 +48,8 @@ public:
 protected:
 	void collectLakes();
 	
-	bool placeShipyard(Zone & land, const Lake & lake, si32 guard, RouteInfo & info);
-	bool placeBoat(Zone & land, const Lake & lake, RouteInfo & info);
+	bool placeShipyard(Zone & land, const Lake & lake, si32 guard, bool createRoad, RouteInfo & info);
+	bool placeBoat(Zone & land, const Lake & lake, bool createRoad, RouteInfo & info);
 
 protected:
 	std::vector<Lake> lakes; //disconnected parts of zone. Used to work with water zones