Browse Source

+ Handle Wide Connections
+ Possibly hide fictive and repulsive connections, needs testing

Tomasz Zieliński 2 years ago
parent
commit
65d10cf9f2

+ 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();

+ 37 - 8
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)
 			{
+				//TODO: Access information about connection type
+				if (connection.getConnectionType() == EConnectionType::EConnectionType::REPULSIVE)
+				{
+					continue;
+				}
+				auto neighbor = connection.getOtherZoneId(current);
 				if (!visited[neighbor])
 				{
 					visited[neighbor] = true;
@@ -132,7 +138,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 +186,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;
@@ -521,9 +527,14 @@ 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));
 			
@@ -601,6 +612,24 @@ 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;
+				forceVector -= localForce * (distancesBetweenZones[zone.second->getId()][otherZone->getId()]);
+			}
+		}
+
 		overlaps[zone.second] = overlap;
 		forceVector.z = 0; //operator - doesn't preserve z coordinate :/
 		forces[zone.second] = forceVector;
@@ -656,7 +685,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 			//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(firstZone->getConnectedZoneIds(), misplacedZones[i].second->getId()))
 			{
 				secondZone = misplacedZones[i].second;
 				break;
@@ -688,7 +717,7 @@ void CZonePlacer::moveOneZone(TZoneMap& zones, TForceVector& totalForces, TDista
 		//Move one zone towards most distant zone to reduce distance
 
 		float maxDistance = 0;
-		for (auto con : misplacedZone->getConnections())
+		for (auto con : misplacedZone->getConnectedZoneIds())
 		{
 			auto otherZone = zones[con];
 			float distance = static_cast<float>(otherZone->getCenter().dist2dSQ(ourCenter));

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

@@ -78,7 +78,7 @@ void ConnectionsPlacer::init()
 	POSTFUNCTION(RoadPlacer);
 	POSTFUNCTION(ObjectManager);
 	
-	for(auto c : map.getMapGenOptions().getMapTemplate()->getConnections())
+	for(auto c : map.getMapGenOptions().getMapTemplate()->getConnectedZoneIds())
 		addConnection(c);
 }
 
@@ -107,8 +107,58 @@ 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);
+
+							success = true;
+							break;
+						}
+					}
+				}
+			}
+		}
+	}
+
 	float maxDist = -10e6;
-	if(!directProhibited && directConnectionIterator != dNeighbourZones.end())
+	if(!success && !directProhibited && directConnectionIterator != dNeighbourZones.end())
 	{
 		int3 guardPos(-1, -1, -1);
 		int3 roadNode;
@@ -333,6 +383,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, &connection, &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())
 	{

+ 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)