Browse Source

Merge pull request #2237 from vcmi/fictive_connections

Wide, fictive, repulsive connections
DjWarmonger 2 years ago
parent
commit
a560eaea51

+ 4 - 4
Mods/vcmi/config/vcmi/rmg/heroes3unused/ring.JSON

@@ -171,7 +171,7 @@
 		},
 		"connections" :
 		[
-			{ "a" : "1", "b" : "2", "guard" : 0 },
+			{ "a" : "1", "b" : "2", "type": "wide" },
 			{ "a" : "1", "b" : "3", "guard" : 0 },
 			{ "a" : "1", "b" : "4", "guard" : 0 },
 			{ "a" : "1", "b" : "5", "guard" : 2000 },
@@ -183,19 +183,19 @@
 			{ "a" : "2", "b" : "3", "guard" : 0 },
 			{ "a" : "2", "b" : "4", "guard" : 0 },
 			{ "a" : "2", "b" : "5", "guard" : 2000 },
-			{ "a" : "5", "b" : "6", "guard" : 0 },
+			{ "a" : "5", "b" : "6", "type": "wide" },
 			{ "a" : "5", "b" : "7", "guard" : 0 },
 			{ "a" : "5", "b" : "8", "guard" : 0 },
 			{ "a" : "6", "b" : "7", "guard" : 0 },
 			{ "a" : "6", "b" : "8", "guard" : 0 },
 			{ "a" : "6", "b" : "9", "guard" : 2000 },
-			{ "a" : "9", "b" : "10", "guard" : 0 },
+			{ "a" : "9", "b" : "10", "type": "wide" },
 			{ "a" : "9", "b" : "11", "guard" : 0 },
 			{ "a" : "9", "b" : "12", "guard" : 0 },
 			{ "a" : "10", "b" : "11", "guard" : 0 },
 			{ "a" : "10", "b" : "12", "guard" : 0 },
 			{ "a" : "10", "b" : "13", "guard" : 2000 },
-			{ "a" : "13", "b" : "14", "guard" : 0 },
+			{ "a" : "13", "b" : "14", "type": "wide" },
 			{ "a" : "13", "b" : "15", "guard" : 0 },
 			{ "a" : "13", "b" : "16", "guard" : 0 },
 			{ "a" : "14", "b" : "15", "guard" : 0 },

+ 4 - 4
Mods/vcmi/config/vcmi/rmg/symmetric/2sm0k.JSON

@@ -93,11 +93,11 @@
 		"connections" :
 		[
 			{ "a" : "1", "b" : "3", "guard" : 6000 },
-			{ "a" : "1", "b" : "5", "guard" : 0 },
-			{ "a" : "1", "b" : "7", "guard" : 0 },
+			{ "a" : "1", "b" : "5", "type": "wide" },
+			{ "a" : "1", "b" : "7", "type": "wide" },
 			{ "a" : "2", "b" : "4", "guard" : 6000 },
-			{ "a" : "2", "b" : "6", "guard" : 0 },
-			{ "a" : "2", "b" : "8", "guard" : 0 },
+			{ "a" : "2", "b" : "6", "type": "wide" },
+			{ "a" : "2", "b" : "8", "type": "wide" },
 			{ "a" : "5", "b" : "6", "guard" : 6000 },
 			{ "a" : "7", "b" : "8", "guard" : 6000 }
 		]

+ 2 - 2
Mods/vcmi/config/vcmi/rmg/symmetric/6lm10.JSON

@@ -299,8 +299,8 @@
 			{ "a" : "18", "b" : "22", "guard" : 9000 },
 			{ "a" : "19", "b" : "24", "guard" : 9000 },
 			{ "a" : "20", "b" : "25", "guard" : 9000 },
-			{ "a" : "21", "b" : "22", "guard" : 0 },
-			{ "a" : "24", "b" : "25", "guard" : 0 }
+			{ "a" : "21", "b" : "22", "type": "wide" },
+			{ "a" : "24", "b" : "25", "type": "wide" }
 		]
 	}
 }

+ 2 - 2
Mods/vcmi/config/vcmi/rmg/symmetric/6lm10a.JSON

@@ -310,8 +310,8 @@
 			{ "a" : "19", "b" : "24", "guard" : 9000 },
 			{ "a" : "20", "b" : "25", "guard" : 9000 },
 
-			{ "a" : "21", "b" : "22", "guard" : 0 },
-			{ "a" : "24", "b" : "25", "guard" : 0 }
+			{ "a" : "21", "b" : "22", "type": "wide" },
+			{ "a" : "24", "b" : "25", "type": "wide" }
 		]
 	}
 }

+ 4 - 4
Mods/vcmi/config/vcmi/rmg/symmetric/8sm0c.JSON

@@ -159,10 +159,10 @@
 			{ "a" : "8", "b" : "9", "guard" : 3000 },
 			{ "a" : "8", "b" : "11", "guard" : 6000 },
 			{ "a" : "8", "b" : "12", "guard" : 3000 },
-			{ "a" : "9", "b" : "10", "guard" : 0 },
-			{ "a" : "9", "b" : "12", "guard" : 0 },
-			{ "a" : "10", "b" : "13", "guard" : 0 },
-			{ "a" : "12", "b" : "13", "guard" : 0 }
+			{ "a" : "9", "b" : "10", "type": "wide" },
+			{ "a" : "9", "b" : "12", "type": "wide" },
+			{ "a" : "10", "b" : "13", "type": "wide" },
+			{ "a" : "12", "b" : "13", "type": "wide" }
 		]
 	}
 }

+ 51 - 11
lib/rmg/CRmgTemplate.cpp

@@ -12,6 +12,7 @@
 #include <vstd/ContainerUtils.h>
 #include <boost/bimap.hpp>
 #include "CRmgTemplate.h"
+#include "Functions.h"
 
 #include "../VCMI_Lib.h"
 #include "../CTownHandler.h"
@@ -285,14 +286,20 @@ TRmgTemplateZoneId ZoneOptions::getTreasureLikeZone() const
     return treasureLikeZone;
 }
 
-void ZoneOptions::addConnection(TRmgTemplateZoneId otherZone)
+void ZoneOptions::addConnection(const ZoneConnection & connection)
 {
-	connections.push_back (otherZone);
+	connectedZoneIds.push_back(connection.getOtherZoneId(getId()));
+	connectionDetails.push_back(connection);
 }
 
-std::vector<TRmgTemplateZoneId> ZoneOptions::getConnections() const
+std::vector<ZoneConnection> ZoneOptions::getConnections() const
 {
-	return connections;
+	return connectionDetails;
+}
+
+std::vector<TRmgTemplateZoneId> ZoneOptions::getConnectedZoneIds() const
+{
+	return connectedZoneIds;
 }
 
 bool ZoneOptions::areTownsSameType() const
@@ -429,7 +436,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
 ZoneConnection::ZoneConnection()
 	: zoneA(-1),
 	zoneB(-1),
-	guardStrength(0)
+	guardStrength(0),
+	connectionType(EConnectionType::EConnectionType::GUARDED)
 {
 
 }
@@ -444,10 +452,31 @@ TRmgTemplateZoneId ZoneConnection::getZoneB() const
 	return zoneB;
 }
 
+TRmgTemplateZoneId ZoneConnection::getOtherZoneId(TRmgTemplateZoneId id) const
+{
+	if (id == zoneA)
+	{
+		return zoneB;
+	}
+	else if (id == zoneB)
+	{
+		return zoneA;
+	}
+	else
+	{
+		throw rmgException("Zone does not belong to this connection");
+	}
+}
+
 int ZoneConnection::getGuardStrength() const
 {
 	return guardStrength;
 }
+
+EConnectionType::EConnectionType ZoneConnection::getConnectionType() const
+{
+	return connectionType;
+}
 	
 bool operator==(const ZoneConnection & l, const ZoneConnection & r)
 {
@@ -456,9 +485,18 @@ bool operator==(const ZoneConnection & l, const ZoneConnection & r)
 
 void ZoneConnection::serializeJson(JsonSerializeFormat & handler)
 {
+	static const std::vector<std::string> connectionTypes =
+	{
+		"guarded",
+		"fictive",
+		"repulsive",
+		"wide"
+	};
+
 	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);
 }
 
 }
@@ -526,9 +564,9 @@ const CRmgTemplate::Zones & CRmgTemplate::getZones() const
 	return zones;
 }
 
-const std::vector<ZoneConnection> & CRmgTemplate::getConnections() const
+const std::vector<ZoneConnection> & CRmgTemplate::getConnectedZoneIds() const
 {
-	return connections;
+	return connectedZoneIds;
 }
 
 void CRmgTemplate::validate() const
@@ -644,7 +682,7 @@ void CRmgTemplate::serializeJson(JsonSerializeFormat & handler)
 
 	{
 		auto connectionsData = handler.enterArray("connections");
-		connectionsData.serializeStruct(connections);
+		connectionsData.serializeStruct(connectedZoneIds);
 	}
 	
 	{
@@ -759,16 +797,18 @@ void CRmgTemplate::afterLoad()
 		inheritTreasureInfo(zone);
 	}
 
-	for(const auto & connection : connections)
+	for(const auto & connection : connectedZoneIds)
 	{
+		//TODO: Remember connection details and allow to access them from anywhere
+
 		auto id1 = connection.getZoneA();
 		auto id2 = connection.getZoneB();
 
 		auto zone1 = zones.at(id1);
 		auto zone2 = zones.at(id2);
 
-		zone1->addConnection(id2);
-		zone2->addConnection(id1);
+		zone1->addConnection(connection);
+		zone2->addConnection(connection);
 	}
 	
 	if(allowedWaterContent.empty() || allowedWaterContent.count(EWaterContent::RANDOM))

+ 22 - 5
lib/rmg/CRmgTemplate.h

@@ -67,17 +67,31 @@ public:
 	void serializeJson(JsonSerializeFormat & handler);
 };
 
+namespace EConnectionType
+{
+	enum class EConnectionType
+	{
+		GUARDED = 0, //default
+		FICTIVE,
+		REPULSIVE,
+		WIDE
+	};
+}
+
 namespace rmg
 {
 
 class DLL_LINKAGE ZoneConnection
 {
 public:
+
 	ZoneConnection();
 
 	TRmgTemplateZoneId getZoneA() const;
 	TRmgTemplateZoneId getZoneB() const;
+	TRmgTemplateZoneId getOtherZoneId(TRmgTemplateZoneId id) const;
 	int getGuardStrength() const;
+	EConnectionType::EConnectionType getConnectionType() const;
 
 	void serializeJson(JsonSerializeFormat & handler);
 	
@@ -86,6 +100,7 @@ private:
 	TRmgTemplateZoneId zoneA;
 	TRmgTemplateZoneId zoneB;
 	int guardStrength;
+	EConnectionType::EConnectionType connectionType;
 };
 
 class DLL_LINKAGE ZoneOptions
@@ -149,8 +164,9 @@ public:
 	TRmgTemplateZoneId getTerrainTypeLikeZone() const;
 	TRmgTemplateZoneId getTreasureLikeZone() const;
 
-	void addConnection(TRmgTemplateZoneId otherZone);
-	std::vector<TRmgTemplateZoneId> getConnections() const;
+	void addConnection(const ZoneConnection & connection);
+	std::vector<ZoneConnection> getConnections() const;
+	std::vector<TRmgTemplateZoneId> getConnectedZoneIds() const;
 
 	void serializeJson(JsonSerializeFormat & handler);
 	
@@ -178,7 +194,8 @@ protected:
 
 	std::vector<CTreasureInfo> treasureInfo;
 
-	std::vector<TRmgTemplateZoneId> connections; //list of adjacent zones
+	std::vector<TRmgTemplateZoneId> connectedZoneIds; //list of adjacent zone ids
+	std::vector<ZoneConnection> connectionDetails; //list of connections linked to that zone
 
 	TRmgTemplateZoneId minesLikeZone;
 	TRmgTemplateZoneId terrainTypeLikeZone;
@@ -223,7 +240,7 @@ public:
 	const CPlayerCountRange & getCpuPlayers() const;
 	std::pair<int3, int3> getMapSizes() const;
 	const Zones & getZones() const;
-	const std::vector<rmg::ZoneConnection> & getConnections() const;
+	const std::vector<rmg::ZoneConnection> & getConnectedZoneIds() const;
 
 	void validate() const; /// Tests template on validity and throws exception on failure
 
@@ -235,7 +252,7 @@ private:
 	int3 minSize, maxSize;
 	CPlayerCountRange players, cpuPlayers;
 	Zones zones;
-	std::vector<rmg::ZoneConnection> connections;
+	std::vector<rmg::ZoneConnection> connectedZoneIds;
 	std::set<EWaterContent::EWaterContent> allowedWaterContent;
 
 	void afterLoad();

+ 77 - 22
lib/rmg/CZonePlacer.cpp

@@ -71,10 +71,16 @@ void CZonePlacer::findPathsBetweenZones()
 			q.pop();
 
 			const auto& currentZone = zones.at(current);
-			const auto& connections = currentZone->getConnections();
+			const auto& connectedZoneIds = currentZone->getConnections();
 
-			for (uint32_t neighbor : connections)
+			for (auto & connection : connectedZoneIds)
 			{
+				if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+				{
+					//Do not consider virtual connections for graph distance
+					continue;
+				}
+				auto neighbor = connection.getOtherZoneId(current);
 				if (!visited[neighbor])
 				{
 					visited[neighbor] = true;
@@ -98,7 +104,6 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand)
 	GridType grid(boost::extents[gridSize][gridSize]);
 
 	TZoneVector zonesVector(zones.begin(), zones.end());
-	RandomGeneratorUtil::randomShuffle(zonesVector, *rand);
 
 	//Place first zone
 
@@ -132,7 +137,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand)
 	{
 		case ETemplateZoneType::PLAYER_START:
 		case ETemplateZoneType::CPU_START:
-			if (firstZone->getConnections().size() > 2)
+			if (firstZone->getConnectedZoneIds().size() > 2)
 			{
 				getRandomEdge(x, y);
 			}
@@ -180,7 +185,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand)
 	for (size_t i = 1; i < zones.size(); i++)
 	{
 		auto zone = zonesVector[i].second;
-		auto connections = zone->getConnections();
+		auto connectedZoneIds = zone->getConnectedZoneIds();
 
 		float maxDistance = -1000.0;
 		int3 mostDistantPlace;
@@ -220,16 +225,7 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand)
 									localDistance = -potentialPos.dist2d(int3(existingX, existingY, 0));
 								}
 
-								//Spread apart player starting zones
-								if (zone->getOwner() && existingZone->getOwner()) //Players participate in game
-								{
-									int firstPlayer = zone->getOwner().value();
-									int secondPlayer = existingZone->getOwner().value();
-
-									//Players with lower indexes (especially 1 and 2) will be placed further apart
-
-									localDistance *= (1.0f + (2.0f / (firstPlayer * secondPlayer)));
-								}
+								localDistance *= scaleForceBetweenZones(zone, existingZone);
 
 								distance += localDistance;
 							}
@@ -287,6 +283,23 @@ void CZonePlacer::placeOnGrid(CRandomGenerator* rand)
 	}
 }
 
+float CZonePlacer::scaleForceBetweenZones(const std::shared_ptr<Zone> zoneA, const std::shared_ptr<Zone> zoneB) const
+{
+	if (zoneA->getOwner() && zoneB->getOwner()) //Players participate in game
+	{
+		int firstPlayer = zoneA->getOwner().value();
+		int secondPlayer = zoneB->getOwner().value();
+
+		//Players with lower indexes (especially 1 and 2) will be placed further apart
+
+		return (1.0f + (2.0f / (firstPlayer * secondPlayer)));
+	}
+	else
+	{
+		return 1;
+	}
+}
+
 void CZonePlacer::placeZones(CRandomGenerator * rand)
 {
 	logGlobal->info("Starting zone placement");
@@ -521,13 +534,18 @@ void CZonePlacer::attractConnectedZones(TZoneMap & zones, TForceVector & forces,
 		float3 pos = zone.second->getCenter();
 		float totalDistance = 0;
 
-		for (auto con : zone.second->getConnections())
+		for (const auto & connection : zone.second->getConnections())
 		{
-			auto otherZone = zones[con];
+			if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			{
+				continue;
+			}
+
+			auto otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
 			float3 otherZoneCenter = otherZone->getCenter();
 			auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
 			
-			forceVector += (otherZoneCenter - pos) * distance * gravityConstant; //positive value
+			forceVector += (otherZoneCenter - pos) * distance * gravityConstant * scaleForceBetweenZones(zone.second, otherZone); //positive value
 
 			//Attract zone centers always
 
@@ -569,6 +587,7 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces
 			{
 				float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness;
 				//negative value
+				localForce *= scaleForceBetweenZones(zone.second, otherZone.second);
 				forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone.second->getId()] / 2.0f);
 				overlap += (minDistance - distance); //overlapping of small zones hurts us more
 			}
@@ -601,6 +620,25 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces
 		{
 			pushAwayFromBoundary(pos.x, 1);
 		}
+
+		//Always move repulsive zones away, no matter their distance
+		//TODO: Consider z plane?
+		for (auto& connection : zone.second->getConnections())
+		{
+			if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			{
+				auto & otherZone = zones[connection.getOtherZoneId(zone.second->getId())];
+				float3 otherZoneCenter = otherZone->getCenter();
+
+				//TODO: Roll into lambda?
+				auto distance = static_cast<float>(pos.dist2d(otherZoneCenter));
+				float minDistance = (zone.second->getSize() + otherZone->getSize()) / mapSize;
+				float3 localForce = (((otherZoneCenter - pos)*(minDistance / (distance ? distance : 1e-3f))) / getDistance(distance)) * stifness;
+				localForce *= (distancesBetweenZones[zone.second->getId()][otherZone->getId()]);
+				forceVector -= localForce * scaleForceBetweenZones(zone.second, otherZone);
+			}
+		}
+
 		overlaps[zone.second] = overlap;
 		forceVector.z = 0; //operator - doesn't preserve z coordinate :/
 		forces[zone.second] = forceVector;
@@ -609,7 +647,9 @@ void CZonePlacer::separateOverlappingZones(TZoneMap &zones, TForceVector &forces
 
 void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDistanceVector& distances, TDistanceVector& overlaps)
 {
-	const int maxDistanceMovementRatio = zones.size() * zones.size(); //The more zones, the greater total distance expected
+	//The more zones, the greater total distance expected
+	//Also, higher stiffness make expected movement lower
+	const int maxDistanceMovementRatio = zones.size() * zones.size() * (stiffnessConstant / stifness);
 
 	typedef std::pair<float, std::shared_ptr<Zone>> Misplacement;
 	std::vector<Misplacement> misplacedZones;
@@ -638,7 +678,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 
 	boost::sort(misplacedZones, [](const Misplacement& lhs, Misplacement& rhs)
 	{
-		return lhs.first > rhs.first; //Biggest first
+		return lhs.first > rhs.first; //Largest dispalcement first
 	});
 
 	logGlobal->trace("Worst misplacement/movement ratio: %3.2f", misplacedZones.front().first);
@@ -649,14 +689,24 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 
 		auto firstZone = misplacedZones.front().second;
 		std::shared_ptr<Zone> secondZone;
+		std::set<TRmgTemplateZoneId> connectedZones;
+		for (const auto& connection : firstZone->getConnections())
+		{
+			//FIXME: Should we also exclude fictive connections?
+			if (connection.getConnectionType() != EConnectionType::EConnectionType::REPULSIVE)
+			{
+				connectedZones.insert(connection.getOtherZoneId(firstZone->getId()));
+			}
+		}
 
 		auto level = firstZone->getCenter().z;
 		for (size_t i = 1; i < misplacedZones.size(); i++)
 		{
 			//Only swap zones on the same level
 			//Don't swap zones that should be connected (Jebus)
+
 			if (misplacedZones[i].second->getCenter().z == level &&
-				!vstd::contains(firstZone->getConnections(), misplacedZones[i].second->getId()))
+				!vstd::contains(connectedZones, misplacedZones[i].second->getId()))
 			{
 				secondZone = misplacedZones[i].second;
 				break;
@@ -690,7 +740,12 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 		float maxDistance = 0;
 		for (auto con : misplacedZone->getConnections())
 		{
-			auto otherZone = zones[con];
+			if (con.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+			{
+				continue;
+			}
+
+			auto otherZone = zones[con.getOtherZoneId(misplacedZone->getId())];
 			float distance = static_cast<float>(otherZone->getCenter().dist2dSQ(ourCenter));
 			if (distance > maxDistance)
 			{

+ 1 - 0
lib/rmg/CZonePlacer.h

@@ -40,6 +40,7 @@ public:
 	void placeZones(CRandomGenerator * rand);
 	void findPathsBetweenZones();
 	void placeOnGrid(CRandomGenerator* rand);
+	float scaleForceBetweenZones(const std::shared_ptr<Zone> zoneA, const std::shared_ptr<Zone> zoneB) const;
 	void assignZones(CRandomGenerator * rand);
 
 	const TDistanceMap & getDistanceMap();

+ 81 - 2
lib/rmg/modificators/ConnectionsPlacer.cpp

@@ -79,7 +79,7 @@ void ConnectionsPlacer::init()
 	POSTFUNCTION(RoadPlacer);
 	POSTFUNCTION(ObjectManager);
 	
-	for(auto c : map.getMapGenOptions().getMapTemplate()->getConnections())
+	for(auto c : map.getMapGenOptions().getMapTemplate()->getConnectedZoneIds())
 		addConnection(c);
 }
 
@@ -108,8 +108,67 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 						 || vstd::contains(otherTerrain->prohibitTransitions, zone.getTerrainType());
 	auto directConnectionIterator = dNeighbourZones.find(otherZoneId);
 
+	if (directConnectionIterator != dNeighbourZones.end())
+	{
+		if (connection.getConnectionType() == EConnectionType::EConnectionType::WIDE)
+		{
+			for (auto borderPos : directConnectionIterator->second)
+			{
+				//TODO: Refactor common code with direct connection
+				int3 potentialPos = zone.areaPossible().nearest(borderPos);
+				assert(borderPos != potentialPos);
+
+				auto safetyGap = rmg::Area({ potentialPos });
+				safetyGap.unite(safetyGap.getBorderOutside());
+				safetyGap.intersect(zone.areaPossible());
+				if (!safetyGap.empty())
+				{
+					safetyGap.intersect(otherZone->areaPossible());
+					if (safetyGap.empty())
+					{
+						rmg::Area border(zone.getArea().getBorder());
+						border.unite(otherZone->getArea().getBorder());
+
+						auto costFunction = [&border](const int3& s, const int3& d)
+						{
+							return 1.f / (1.f + border.distanceSqr(d));
+						};
+
+						auto ourArea = zone.areaPossible() + zone.freePaths();
+						auto theirArea = otherZone->areaPossible() + otherZone->freePaths();
+						theirArea.add(potentialPos);
+						rmg::Path ourPath(ourArea);
+						rmg::Path theirPath(theirArea);
+						ourPath.connect(zone.freePaths());
+						ourPath = ourPath.search(potentialPos, true, costFunction);
+						theirPath.connect(otherZone->freePaths());
+						theirPath = theirPath.search(potentialPos, true, costFunction);
+
+						if (ourPath.valid() && theirPath.valid())
+						{
+							zone.connectPath(ourPath);
+							otherZone->connectPath(theirPath);
+							otherZone->getModificator<ObjectManager>()->updateDistances(potentialPos);
+
+							success = true;
+							break;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	if (connection.getConnectionType() == EConnectionType::EConnectionType::FICTIVE || 
+		connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+	{
+		//Fictive or repulsive connections are not real, take no action
+		dCompleted.push_back(connection);
+		return;
+	}
+
 	float maxDist = -10e6;
-	if(!directProhibited && directConnectionIterator != dNeighbourZones.end())
+	if(!success && !directProhibited && directConnectionIterator != dNeighbourZones.end())
 	{
 		int3 guardPos(-1, -1, -1);
 		int3 roadNode;
@@ -187,9 +246,12 @@ void ConnectionsPlacer::selfSideDirectConnection(const rmg::ZoneConnection & con
 				}
 				else
 				{
+					//Update distances from empty passage, too
 					zone.areaPossible().erase(guardPos);
 					zone.freePaths().add(guardPos);
 					map.setOccupied(guardPos, ETileType::FREE);
+					manager.updateDistances(guardPos);
+					otherZone->getModificator<ObjectManager>()->updateDistances(guardPos);
 				}
 				
 				assert(zone.getModificator<RoadPlacer>());
@@ -334,6 +396,23 @@ void ConnectionsPlacer::createBorder()
 		return map.isOnMap(tile) && map.getZones()[map.getZoneID(tile)]->getType() != ETemplateZoneType::WATER;
 	});
 
+	//No border for wide connections
+	for (auto& connection : zone.getConnections()) // We actually placed that connection already
+	{
+		auto otherZone = connection.getOtherZoneId(zone.getId());
+
+		if (connection.getConnectionType() == EConnectionType::EConnectionType::WIDE)
+		{
+			auto sharedBorder = borderArea.getSubarea([this, otherZone, &borderOutsideArea](const int3 & t)
+			{
+				auto tile = borderOutsideArea.nearest(t);
+				return map.isOnMap(tile) && map.getZones()[map.getZoneID(tile)]->getId() == otherZone;
+			});
+
+			blockBorder.subtract(sharedBorder);
+		}
+	};
+
 	Zone::Lock lock(zone.areaMutex); //Protect from erasing same tiles again
 	for(const auto & tile : blockBorder.getTilesVector())
 	{

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

@@ -76,12 +76,28 @@ void ObjectManager::addNearbyObject(CGObjectInstance * obj, CGObjectInstance * n
 }
 
 void ObjectManager::updateDistances(const rmg::Object & obj)
+{
+	updateDistances([obj](const int3& tile) -> ui32
+	{
+		return obj.getArea().distanceSqr(tile); //optimization, only relative distance is interesting
+	});
+}
+
+void ObjectManager::updateDistances(const int3 & pos)
+{
+	updateDistances([pos](const int3& tile) -> ui32
+	{
+		return pos.dist2dSQ(tile); //optimization, only relative distance is interesting
+	});
+}
+
+void ObjectManager::updateDistances(std::function<ui32(const int3 & tile)> distanceFunction)
 {
 	RecursiveLock lock(externalAccessMutex);
 	tilesByDistance.clear();
 	for (auto tile : zone.areaPossible().getTiles()) //don't need to mark distance for not possible tiles
 	{
-		ui32 d = obj.getArea().distanceSqr(tile); //optimization, only relative distance is interesting
+		ui32 d = distanceFunction(tile);
 		map.setNearestObjectDistance(tile, std::min(static_cast<float>(d), map.getNearestObjectDistance(tile)));
 		tilesByDistance.push(std::make_pair(tile, map.getNearestObjectDistance(tile)));
 	}

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

@@ -62,6 +62,8 @@ public:
 	void placeObject(rmg::Object & object, bool guarded, bool updateDistance);
 
 	void updateDistances(const rmg::Object & obj);
+	void updateDistances(const int3& pos);
+	void updateDistances(std::function<ui32(const int3 & tile)> distanceFunction);
 	void createDistancesPriorityQueue();
 
 	const rmg::Area & getVisitableArea() const;

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

@@ -404,7 +404,7 @@ void TreasurePlacer::addAllPossibleObjects()
 	
 	//Seer huts with creatures or generic rewards
 
-	if(zone.getConnections().size()) //Unlikely, but...
+	if(zone.getConnectedZoneIds().size()) //Unlikely, but...
 	{
 		auto * qap = zone.getModificator<QuestArtifactPlacer>();
 		if(!qap)