2
0
Эх сурвалжийг харах

Implemented A* algorithm to draw shortest roads - but not correct roads yet.

DjWarmonger 10 жил өмнө
parent
commit
6d502ef1a1

+ 17 - 0
lib/GameConstants.h

@@ -482,6 +482,23 @@ enum class ETeleportChannelType
 	MIXED
 };
 
+
+namespace ERiverType
+{
+	enum ERiverType
+	{
+		NO_RIVER, CLEAR_RIVER, ICY_RIVER, MUDDY_RIVER, LAVA_RIVER
+	};
+}
+
+namespace ERoadType
+{
+	enum ERoadType
+	{
+		NO_ROAD, DIRT_ROAD, GRAVEL_ROAD, COBBLESTONE_ROAD
+	};
+}
+
 class Obj
 {
 public:

+ 2 - 0
lib/VCMI_lib.cbp

@@ -255,6 +255,8 @@
 		<Unit filename="mapObjects/ObjectTemplate.h" />
 		<Unit filename="mapping/CCampaignHandler.cpp" />
 		<Unit filename="mapping/CCampaignHandler.h" />
+		<Unit filename="mapping/CDrawRoadsOperation.cpp" />
+		<Unit filename="mapping/CDrawRoadsOperation.h" />
 		<Unit filename="mapping/CMap.cpp" />
 		<Unit filename="mapping/CMap.h" />
 		<Unit filename="mapping/CMapEditManager.cpp" />

+ 1 - 0
lib/VCMI_lib.vcxproj

@@ -231,6 +231,7 @@
     <ClCompile Include="mapping\CMapEditManager.cpp" />
     <ClCompile Include="mapping\MapFormatH3M.cpp" />
     <ClCompile Include="mapping\MapFormatJson.cpp" />
+    <ClCompile Include="mapping\CDrawRoadsOperation.cpp" />
     <ClCompile Include="registerTypes\RegisterTypes.cpp" />
     <ClCompile Include="registerTypes\TypesClientPacks1.cpp" />
     <ClCompile Include="registerTypes\TypesClientPacks2.cpp" />

+ 353 - 0
lib/mapping/CDrawRoadsOperation.cpp

@@ -0,0 +1,353 @@
+/*
+ * CDrawRoadsOperation.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 "CDrawRoadsOperation.h"
+
+const std::vector<CDrawRoadsOperation::RoadPattern> CDrawRoadsOperation::patterns = 
+{
+	//single tile. fall-back pattern
+	{
+		{
+          "-","-","-",
+          "-","+","-",
+          "-","-","-"
+        },
+        {14,14},
+        {9,9},
+        false,
+        false
+	},
+	//Road straight with angle
+	{
+		{
+          "?","-","+",
+          "-","+","+",
+          "+","+","?" 
+        },
+        {2,5},
+        {-1,-1},
+        true,
+        true
+	},
+	//Turn
+	{
+		{
+          "?","-","?",
+          "-","+","+",
+          "?","+","?" 
+        },
+        {0,1},
+        {0,3},
+        true,
+        true
+	},
+	//Dead end horizontal
+	{
+		{
+          "?","-","?",
+          "-","+","+",
+          "?","-","?"   
+        },
+        {15,15},{11,12},
+        true,
+        false
+	},
+	//Dead end vertical
+	{
+		{
+          "?","-","?",
+          "-","+","-",
+          "?","+","?" 
+        },
+        {14,14},{9,10},
+        false,
+        true
+	},
+	//T-cross horizontal
+	{
+		{
+          "?","+","?",
+          "-","+","+",
+          "?","+","?"  
+        },
+        {6,7},{7,8},
+        true,
+        false
+	},
+	//T-cross vertical
+	{
+		{
+          "?","-","?",
+          "+","+","+",
+          "?","+","?" 
+        },
+        {8,9},{5,6},
+        false,
+        true
+	},
+	//Straight Horizontal 
+	{
+		{
+          "?","-","?",
+          "+","+","+",
+          "?","-","?"  
+        },
+        {12,13},{11,12},
+        false,
+        false
+	},
+	//Straight Vertical 
+	{
+		{
+          "?","+","?",
+          "-","+","-",
+          "?","+","?" 
+        },
+        {10,11},{9,10},
+        false,
+        false
+	},
+	//X-cross 
+	{
+		{
+          "?","+","?",
+          "+","+","+",
+          "?","+","?"  
+        },
+        {16,16},{4,4},
+        false,
+        false
+	}			
+	
+};
+
+static bool ruleIsNone(const std::string & rule)
+{
+	return rule == "-";
+}
+
+static bool ruleIsSomething(const std::string & rule)
+{
+	return rule == "+";
+}
+
+static bool ruleIsAny(const std::string & rule)
+{
+	return rule == "?";
+}
+
+///CDrawRoadsOperation
+CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen):
+	CMapOperation(map),terrainSel(terrainSel), roadType(roadType), gen(gen)
+{
+	
+}
+
+void CDrawRoadsOperation::execute()
+{
+	std::set<int3> invalidated;
+	
+	for(const auto & pos : terrainSel.getSelectedItems())
+	{
+		auto & tile = map->getTile(pos);
+		tile.roadType = roadType;
+		
+		auto rect = extendTileAroundSafely(pos);
+		rect.forEach([&invalidated](const int3 & pos)
+		{
+			invalidated.insert(pos);
+		});
+	}
+	
+	updateTiles(invalidated);	
+}
+
+void CDrawRoadsOperation::undo()
+{
+  //TODO	
+}
+
+void CDrawRoadsOperation::redo()
+{
+  //TODO	
+}
+
+std::string CDrawRoadsOperation::getLabel() const
+{
+	return "Draw Roads";
+}
+
+bool CDrawRoadsOperation::canApplyPattern(const RoadPattern & pattern) const
+{
+	//TODO: this method should be virtual for river support	
+	return pattern.roadMapping.first >= 0;
+}
+
+void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const
+{
+	//todo: use cashing here and also in terrain patterns
+	
+	if(flip == 0)
+	{
+		return;
+	}
+
+
+	if(flip == FLIP_PATTERN_HORIZONTAL || flip == FLIP_PATTERN_BOTH)
+	{
+		for(int i = 0; i < 3; ++i)
+		{
+			int y = i * 3;
+			std::swap(pattern.data[y], pattern.data[y + 2]);
+		}
+	}
+
+	if(flip == FLIP_PATTERN_VERTICAL || flip == FLIP_PATTERN_BOTH)
+	{
+		for(int i = 0; i < 3; ++i)
+		{
+			std::swap(pattern.data[i], pattern.data[6 + i]);
+		}
+	}	
+}
+
+
+bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const
+{
+	return tile.roadType != ERoadType::NO_ROAD; //TODO: this method should be virtual for river support
+}
+
+void CDrawRoadsOperation::updateTiles(std::set<int3> & invalidated) 
+{
+	for(int3 coord : invalidated)
+	{
+		TerrainTile & tile = map->getTile(coord);
+		ValidationResult result(false);
+		
+		if(!needUpdateTile(tile))
+			continue;
+			
+		int bestPattern = -1;
+		
+		for(int k = 0; k < patterns.size(); ++k)
+		{
+			result = validateTile(patterns[k], coord);
+			
+			if(result.result)
+			{
+				bestPattern = k;
+				break;
+			}
+		}
+		
+		if(bestPattern != -1)
+		{
+			updateTile(tile, patterns[bestPattern], result.flip);
+		}
+		
+	}
+};
+
+bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const
+{
+//TODO: this method should be virtual for river support	
+
+   return map->getTile(pos).roadType != ERoadType::NO_ROAD;	
+}
+
+
+void CDrawRoadsOperation::updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip)
+{
+  //TODO: this method should be virtual for river support	
+  
+	const std::pair<int, int> & mapping  = pattern.roadMapping;
+  
+	tile.roadDir = gen->nextInt(mapping.first, mapping.second);
+	tile.extTileFlags = (tile.extTileFlags & 0xCF) | (flip << 4); 
+}
+
+CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const RoadPattern & pattern, const int3 & pos)
+{
+	ValidationResult result(false);
+	
+	if(!canApplyPattern(pattern))
+		return result;
+	
+	
+	for(int flip = 0; flip < 4; ++flip)
+	{
+		if((flip == FLIP_PATTERN_BOTH) && !(pattern.hasHFlip && pattern.hasVFlip)) 
+			continue;
+		if((flip == FLIP_PATTERN_HORIZONTAL) && !pattern.hasHFlip) 
+			continue;
+		if((flip == FLIP_PATTERN_VERTICAL) && !(pattern.hasVFlip)) 
+			continue;
+		
+		RoadPattern flipped = pattern;		
+		
+		flipPattern(flipped, flip);
+		
+		bool validated = true;
+		
+		for(int i = 0; i < 9; ++i)
+		{
+			if(4 == i)
+				continue;
+			int cx = pos.x + (i % 3) - 1;
+			int cy = pos.y + (i / 3) - 1;
+			
+			int3 currentPos(cx, cy, pos.z);
+			
+			bool hasSomething;
+			
+			if(!map->isInTheMap(currentPos))
+			{
+				hasSomething = true; //road/river can go out of map
+			}
+			else
+			{
+				hasSomething = tileHasSomething(currentPos);				
+			}
+			
+			if(ruleIsSomething(flipped.data[i]))
+			{
+				if(!hasSomething)
+				{
+					validated = false;
+					break;
+				}
+			}
+			else if(ruleIsNone(flipped.data[i]))
+			{
+				if(hasSomething)
+				{
+					validated = false;
+					break;
+				}		
+			}
+			else
+			{
+				assert(ruleIsAny(flipped.data[i]));			
+			}		
+			
+		}
+		
+		if(validated)
+		{
+			result.result = true;
+			result.flip = flip;
+			return result;			
+		}		
+	}
+	
+	return result;
+}
+

+ 58 - 0
lib/mapping/CDrawRoadsOperation.h

@@ -0,0 +1,58 @@
+/*
+ * CDrawRoadsOperation.h, 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 "../CRandomGenerator.h"
+#include "CMap.h"
+#include "CMapEditManager.h"
+
+
+class CDrawRoadsOperation : public CMapOperation
+{
+public:
+	CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen);
+	void execute() override;
+	void undo() override;
+	void redo() override;
+	std::string getLabel() const override;	
+private:
+	
+	struct RoadPattern
+	{
+		std::string data[9];
+		std::pair<int, int> roadMapping, riverMapping;
+		bool hasHFlip, hasVFlip;
+	};
+	
+	struct ValidationResult
+	{
+		ValidationResult(bool result): result(result), flip(0){};
+		bool result;
+		int flip;
+	};
+	
+	static const std::vector<RoadPattern> patterns;
+	
+	void flipPattern(RoadPattern & pattern, int flip) const;
+	
+	void updateTiles(std::set<int3> & invalidated);
+	
+	ValidationResult validateTile(const RoadPattern & pattern, const int3 & pos);
+	void updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip);
+	
+	bool canApplyPattern(const RoadPattern & pattern) const;
+	bool needUpdateTile(const TerrainTile & tile) const;
+	bool tileHasSomething(const int3 & pos) const;
+	
+	CTerrainSelection terrainSel;
+	ERoadType::ERoadType roadType;
+	CRandomGenerator * gen;	
+};

+ 0 - 16
lib/mapping/CMap.h

@@ -261,22 +261,6 @@ public:
 	}
 };
 
-namespace ERiverType
-{
-enum ERiverType
-{
-	NO_RIVER, CLEAR_RIVER, ICY_RIVER, MUDDY_RIVER, LAVA_RIVER
-};
-}
-
-namespace ERoadType
-{
-enum ERoadType
-{
-	NO_ROAD, DIRT_ROAD, GRAVEL_ROAD, COBBLESTONE_ROAD
-};
-}
-
 /// The terrain tile describes the terrain type and the visual representation of the terrain.
 /// Furthermore the struct defines whether the tile is visitable or/and blocked and which objects reside in it.
 struct DLL_LINKAGE TerrainTile

+ 20 - 10
lib/mapping/CMapEditManager.cpp

@@ -6,6 +6,7 @@
 #include "../mapObjects/CObjectClassesHandler.h"
 #include "../mapObjects/CGHeroInstance.h"
 #include "../VCMI_Lib.h"
+#include "CDrawRoadsOperation.h"
 
 MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0)
 {
@@ -132,6 +133,18 @@ std::string CMapOperation::getLabel() const
 	return "";
 }
 
+
+MapRect CMapOperation::extendTileAround(const int3 & centerPos) const
+{
+	return MapRect(int3(centerPos.x - 1, centerPos.y - 1, centerPos.z), 3, 3);
+}
+
+MapRect CMapOperation::extendTileAroundSafely(const int3 & centerPos) const
+{
+	return extendTileAround(centerPos) & MapRect(int3(0, 0, centerPos.z), map->width, map->height);
+}
+
+
 CMapUndoManager::CMapUndoManager() : undoRedoLimit(10)
 {
 
@@ -226,6 +239,13 @@ void CMapEditManager::drawTerrain(ETerrainType terType, CRandomGenerator * gen/*
 	terrainSel.clearSelection();
 }
 
+void CMapEditManager::drawRoad(ERoadType::ERoadType roadType, CRandomGenerator* gen)
+{
+	execute(make_unique<CDrawRoadsOperation>(map, terrainSel, roadType, gen ? gen : &(this->gen)));
+	terrainSel.clearSelection();
+}
+
+
 void CMapEditManager::insertObject(CGObjectInstance * obj, const int3 & pos)
 {
 	execute(make_unique<CInsertObjectOperation>(map, obj, pos));
@@ -960,16 +980,6 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const
 	return tiles;
 }
 
-MapRect CDrawTerrainOperation::extendTileAround(const int3 & centerPos) const
-{
-	return MapRect(int3(centerPos.x - 1, centerPos.y - 1, centerPos.z), 3, 3);
-}
-
-MapRect CDrawTerrainOperation::extendTileAroundSafely(const int3 & centerPos) const
-{
-	return extendTileAround(centerPos) & MapRect(int3(0, 0, centerPos.z), map->width, map->height);
-}
-
 CDrawTerrainOperation::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement /*= ""*/)
 	: result(result), transitionReplacement(transitionReplacement)
 {

+ 13 - 8
lib/mapping/CMapEditManager.h

@@ -113,7 +113,14 @@ public:
 	virtual void redo() = 0;
 	virtual std::string getLabel() const = 0; /// Returns a display-able name of the operation.
 
-protected:
+protected:	
+	MapRect extendTileAround(const int3 & centerPos) const;
+	MapRect extendTileAroundSafely(const int3 & centerPos) const; /// doesn't exceed map size	
+	
+	static const int FLIP_PATTERN_HORIZONTAL = 1;
+	static const int FLIP_PATTERN_VERTICAL = 2;
+	static const int FLIP_PATTERN_BOTH = 3;	
+	
 	CMap * map;
 };
 
@@ -161,7 +168,11 @@ public:
 
 	/// Draws terrain at the current terrain selection. The selection will be cleared automatically.
 	void drawTerrain(ETerrainType terType, CRandomGenerator * gen = nullptr);
-	void insertObject(CGObjectInstance * obj, const int3 & pos);
+	
+	/// Draws roads at the current terrain selection. The selection will be cleared automatically.
+	void drawRoad(ERoadType::ERoadType roadType, CRandomGenerator * gen = nullptr);
+	
+	void insertObject(CGObjectInstance * obj, const int3 & pos);	
 
 	CTerrainSelection & getTerrainSelection();
 	CObjectSelection & getObjectSelection();
@@ -326,8 +337,6 @@ private:
 	void updateTerrainTypes();
 	void invalidateTerrainViews(const int3 & centerPos);
 	InvalidTiles getInvalidTiles(const int3 & centerPos) const;
-	MapRect extendTileAround(const int3 & centerPos) const;
-	MapRect extendTileAroundSafely(const int3 & centerPos) const; /// doesn't exceed map size
 
 	void updateTerrainViews();
 	ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType terType) const;
@@ -339,10 +348,6 @@ private:
 	bool isSandType(ETerrainType terType) const;
 	void flipPattern(TerrainViewPattern & pattern, int flip) const;
 
-	static const int FLIP_PATTERN_HORIZONTAL = 1;
-	static const int FLIP_PATTERN_VERTICAL = 2;
-	static const int FLIP_PATTERN_BOTH = 3;
-
 	CTerrainSelection terrainSel;
 	ETerrainType terType;
 	CRandomGenerator * gen;

+ 93 - 22
lib/rmg/CMapGenerator.cpp

@@ -13,6 +13,8 @@
 #include "CZonePlacer.h"
 #include "../mapObjects/CObjectClassesHandler.h"
 
+static const int3 dirs4[] = {int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0)};
+
 void CMapGenerator::foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo)
 {
 	for(const int3 &dir : dirs)
@@ -23,6 +25,16 @@ void CMapGenerator::foreach_neighbour(const int3 &pos, std::function<void(int3&
 	}
 }
 
+void CMapGenerator::foreachDirectNeighbour(const int3& pos, std::function<void(int3& pos)> foo)
+{
+	for(const int3 &dir : dirs4)
+	{
+		int3 n = pos + dir;
+		if(map->isInTheMap(n))
+			foo(n);
+	}	
+}
+
 
 CMapGenerator::CMapGenerator() :
     zonesTotal(0), monolithIndex(0)
@@ -132,7 +144,7 @@ std::string CMapGenerator::getMapDescription() const
 
     std::stringstream ss;
     ss << boost::str(boost::format(std::string("Map created by the Random Map Generator.\nTemplate was %s, Random seed was %d, size %dx%d") +
-        ", levels %s, humans %d, computers %d, water %s, monster %s, second expansion map") % mapGenOptions->getMapTemplate()->getName() %
+        ", levels %s, humans %d, computers %d, water %s, monster %s, VCMI map") % mapGenOptions->getMapTemplate()->getName() %
 		randomSeed % map->width % map->height % (map->twoLevel ? "2" : "1") % static_cast<int>(mapGenOptions->getPlayerCount()) %
 		static_cast<int>(mapGenOptions->getCompOnlyPlayerCount()) % waterContentStr[mapGenOptions->getWaterContent()] %
 		monsterStrengthStr[monsterStrengthIndex]);
@@ -266,7 +278,49 @@ void CMapGenerator::fillZones()
 	createObstaclesCommon2();
 	//place actual obstacles matching zone terrain
 	for (auto it : zones)
+	{
 		it.second->createObstacles2(this);
+	}
+
+	#define PRINT_MAP_BEFORE_ROADS true
+	if (PRINT_MAP_BEFORE_ROADS) //enable to debug
+	{
+		std::ofstream out("road debug");
+		int levels = map->twoLevel ? 2 : 1;
+		int width = map->width;
+		int height = map->height;
+		for (int k = 0; k < levels; k++)
+		{
+			for (int j = 0; j<height; j++)
+			{
+				for (int i = 0; i<width; i++)
+				{
+					char t = '?';
+					switch (getTile(int3(i, j, k)).getTileType())
+					{
+					case ETileType::FREE:
+						t = ' '; break;
+					case ETileType::BLOCKED:
+						t = '#'; break;
+					case ETileType::POSSIBLE:
+						t = '-'; break;
+					case ETileType::USED:
+						t = 'O'; break;
+					}
+
+					out << t;
+				}
+				out << std::endl;
+			}
+			out << std::endl;
+		}
+		out << std::endl;
+	}
+
+	for (auto it : zones)
+	{
+		it.second->connectRoads(this); //draw roads after everything else has been placed
+	}
 
 	//find place for Grail
 	if (treasureZones.empty())
@@ -421,8 +475,11 @@ void CMapGenerator::createConnections()
 					setOccupied (guardPos, ETileType::FREE); //just in case monster is too weak to spawn
 					zoneA->addMonster (this, guardPos, connection.getGuardStrength(), false, true);
 					//zones can make paths only in their own area
-					zoneA->crunchPath (this, guardPos, posA, zoneA->getId(), zoneA->getFreePaths()); //make connection towards our zone center
-					zoneB->crunchPath (this, guardPos, posB, zoneB->getId(), zoneB->getFreePaths()); //make connection towards other zone center
+					zoneA->crunchPath(this, guardPos, posA, zoneA->getFreePaths()); //make connection towards our zone center
+					zoneB->crunchPath(this, guardPos, posB, zoneB->getFreePaths()); //make connection towards other zone center		
+					
+					zoneA->addRoadNode(guardPos);
+					zoneB->addRoadNode(guardPos);
 					break; //we're done with this connection
 				}
 			}
@@ -516,6 +573,13 @@ void CMapGenerator::addHeaderInfo()
 	addPlayerInfo();
 }
 
+void CMapGenerator::checkIsOnMap(const int3& tile) const
+{
+	if (!map->isInTheMap(tile))
+		throw  rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));	
+}
+
+
 std::map<TRmgTemplateZoneId, CRmgTemplateZone*> CMapGenerator::getZones() const
 {
 	return zones;
@@ -523,67 +587,74 @@ std::map<TRmgTemplateZoneId, CRmgTemplateZone*> CMapGenerator::getZones() const
 
 bool CMapGenerator::isBlocked(const int3 &tile) const
 {
-	if (!map->isInTheMap(tile))
-		throw  rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
 
 	return tiles[tile.x][tile.y][tile.z].isBlocked();
 }
 bool CMapGenerator::shouldBeBlocked(const int3 &tile) const
 {
-	if (!map->isInTheMap(tile))
-		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
 
 	return tiles[tile.x][tile.y][tile.z].shouldBeBlocked();
 }
 bool CMapGenerator::isPossible(const int3 &tile) const
 {
-	if (!map->isInTheMap(tile))
-		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
 
 	return tiles[tile.x][tile.y][tile.z].isPossible();
 }
 bool CMapGenerator::isFree(const int3 &tile) const
 {
-	if (!map->isInTheMap(tile))
-		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
 
 	return tiles[tile.x][tile.y][tile.z].isFree();
 }
 bool CMapGenerator::isUsed(const int3 &tile) const
 {
-	if (!map->isInTheMap(tile))
-		throw  rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
 
 	return tiles[tile.x][tile.y][tile.z].isUsed();
 }
+
+bool CMapGenerator::isRoad(const int3& tile) const
+{
+	checkIsOnMap(tile);
+	
+	return tiles[tile.x][tile.y][tile.z].isRoad();	
+}
+
 void CMapGenerator::setOccupied(const int3 &tile, ETileType::ETileType state)
 {
-	if (!map->isInTheMap(tile))
-		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
 
 	tiles[tile.x][tile.y][tile.z].setOccupied(state);
 }
 
-CTileInfo CMapGenerator::getTile(const int3&  tile) const
+void CMapGenerator::setRoad(const int3& tile, ERoadType::ERoadType roadType)
 {
-	if (!map->isInTheMap(tile))
-		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
+
+	tiles[tile.x][tile.y][tile.z].setRoadType(roadType);	
+}
+
+
+CTileInfo CMapGenerator::getTile(const int3& tile) const
+{
+	checkIsOnMap(tile);
 
 	return tiles[tile.x][tile.y][tile.z];
 }
 
 void CMapGenerator::setNearestObjectDistance(int3 &tile, float value)
 {
-	if (!map->isInTheMap(tile))
-		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
 
 	tiles[tile.x][tile.y][tile.z].setNearestObjectDistance(value);
 }
 
 float CMapGenerator::getNearestObjectDistance(const int3 &tile) const
 {
-	if (!map->isInTheMap(tile))
-		throw rmgException(boost::to_string(boost::format("Tile %s is outside the map") % tile));
+	checkIsOnMap(tile);
 
 	return tiles[tile.x][tile.y][tile.z].getNearestObjectDistance();
 }

+ 6 - 0
lib/rmg/CMapGenerator.h

@@ -66,13 +66,18 @@ public:
 	void createConnections();
 	void findZonesForQuestArts();
 	void foreach_neighbour(const int3 &pos, std::function<void(int3& pos)> foo);
+	void foreachDirectNeighbour(const int3 &pos, std::function<void(int3& pos)> foo);
 
 	bool isBlocked(const int3 &tile) const;
 	bool shouldBeBlocked(const int3 &tile) const;
 	bool isPossible(const int3 &tile) const;
 	bool isFree(const int3 &tile) const;
 	bool isUsed(const int3 &tile) const;
+	bool isRoad(const int3 &tile) const;
+	
 	void setOccupied(const int3 &tile, ETileType::ETileType state);
+	void setRoad(const int3 &tile, ERoadType::ERoadType roadType);
+	
 	CTileInfo getTile(const int3 & tile) const;
 
 	float getNearestObjectDistance(const int3 &tile) const; 
@@ -99,6 +104,7 @@ private:
 	//int questArtsRemaining;
 	int monolithIndex;
 	std::vector<ArtifactID> questArtifacts;
+	void checkIsOnMap(const int3 &tile) const; //throws
 
 	/// Generation methods
 	std::string getMapDescription() const;

+ 183 - 16
lib/rmg/CRmgTemplateZone.cpp

@@ -33,6 +33,11 @@ CRmgTemplateZone::CTownInfo::CTownInfo() : townCount(0), castleCount(0), townDen
 
 }
 
+void CRmgTemplateZone::addRoadNode(const int3& node)
+{
+	roadNodes.insert(node);
+}
+
 int CRmgTemplateZone::CTownInfo::getTownCount() const
 {
 	return townCount;
@@ -81,7 +86,7 @@ void CRmgTemplateZone::CTownInfo::setCastleDensity(int value)
 	castleDensity = value;
 }
 
-CTileInfo::CTileInfo():nearestObjectDistance(INT_MAX), terrain(ETerrainType::WRONG) 
+CTileInfo::CTileInfo():nearestObjectDistance(INT_MAX), terrain(ETerrainType::WRONG),roadType(ERoadType::NO_ROAD) 
 {
 	occupied = ETileType::POSSIBLE; //all tiles are initially possible to place objects or passages
 }
@@ -111,6 +116,12 @@ bool CTileInfo::isFree() const
 {
 	return occupied == ETileType::FREE;
 }
+
+bool CTileInfo::isRoad() const
+{
+	return roadType != ERoadType::NO_ROAD;
+}
+
 bool CTileInfo::isUsed() const
 {
 	return occupied == ETileType::USED;
@@ -135,6 +146,13 @@ void CTileInfo::setTerrainType(ETerrainType value)
 	terrain = value;
 }
 
+void CTileInfo::setRoadType(ERoadType::ERoadType value)
+{
+	roadType = value;
+//	setOccupied(ETileType::FREE);
+}
+
+
 CRmgTemplateZone::CRmgTemplateZone() :
 	id(0),
 	type(ETemplateZoneType::PLAYER_START),
@@ -517,11 +535,11 @@ void CRmgTemplateZone::fractalize(CMapGenerator* gen)
 		}
 
 		//connect with all the paths
-		crunchPath(gen, node, findClosestTile(freePaths, node), id, &freePaths);
+		crunchPath(gen, node, findClosestTile(freePaths, node), &freePaths);
 		//connect with nearby nodes
 		for (auto nearbyNode : nearbyNodes)
 		{
-			crunchPath(gen, node, nearbyNode, id, &freePaths);
+			crunchPath(gen, node, nearbyNode, &freePaths);
 		}
 	}
 	for (auto node : nodes)
@@ -590,7 +608,7 @@ void CRmgTemplateZone::fractalize(CMapGenerator* gen)
 	//logGlobal->infoStream() << boost::format ("Zone %d subdivided fractally") %id;
 }
 
-bool CRmgTemplateZone::crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone, std::set<int3>* clearedTiles)
+bool CRmgTemplateZone::crunchPath(CMapGenerator* gen, const int3 &src, const int3 &dst, std::set<int3>* clearedTiles)
 {
 /*
 make shortest path with free tiles, reachning dst or closest already free tile. Avoid blocks.
@@ -611,7 +629,8 @@ do not leave zone border
 		}
 
 		auto lastDistance = distance;
-		gen->foreach_neighbour (currentPos, [this, gen, &currentPos, dst, &distance, &result, &end, clearedTiles](int3 &pos)
+			
+		auto processNeighbours = [this, gen, &currentPos, dst, &distance, &result, &end, clearedTiles](int3 &pos)
 		{
 			if (!result) //not sure if lambda is worth it...
 			{
@@ -643,15 +662,18 @@ do not leave zone border
 					}
 				}
 			}
-		});
-
+		};
+		
+		gen->foreach_neighbour (currentPos,processNeighbours);
+						
 		int3 anotherPos(-1, -1, -1);
 
-		if (!(result || distance < lastDistance)) //we do not advance, use more advaced pathfinding algorithm?
+		if (!(result || distance < lastDistance)) //we do not advance, use more advanced pathfinding algorithm?
 		{
 			//try any nearby tiles, even if its not closer than current
 			float lastDistance = 2 * distance; //start with significantly larger value
-			gen->foreach_neighbour(currentPos, [this, gen, &currentPos, dst, &lastDistance, &anotherPos, &end, clearedTiles](int3 &pos)
+			
+			auto processNeighbours2 = [this, gen, &currentPos, dst, &lastDistance, &anotherPos, &end, clearedTiles](int3 &pos)
 			{
 				if (currentPos.dist2dSQ(dst) < lastDistance) //try closest tiles from all surrounding unused tiles
 				{
@@ -666,7 +688,11 @@ do not leave zone border
 						}
 					}
 				}
-			});
+			};			
+		
+			gen->foreach_neighbour (currentPos,processNeighbours2);
+						
+			
 			if (anotherPos.valid())
 			{
 				if (clearedTiles)
@@ -686,6 +712,82 @@ do not leave zone border
 	return result;
 }
 
+bool CRmgTemplateZone::createRoad(CMapGenerator* gen, const int3& src, const int3& dst)
+{
+	//A* algorithm taken from Wiki http://en.wikipedia.org/wiki/A*_search_algorithm
+
+	std::set<int3> closed;    // The set of nodes already evaluated.
+	std::set<int3> open{src};    // The set of tentative nodes to be evaluated, initially containing the start node
+	std::map<int3, int3> cameFrom;  // The map of navigated nodes.
+	std::map<int3, int> distances;
+
+	int3 currentNode = src;
+
+	cameFrom[src] = int3(-1, -1, -1); //first node points to finish condition
+	distances[src] = 0;
+	// Cost from start along best known path.
+	// Estimated total cost from start to goal through y.
+
+	while (open.size())
+	{
+		int3 currentNode = *boost::min_element(open, [&distances](const int3 &pos1, const int3 &pos2) -> bool
+		{
+			return distances[pos1], distances[pos2];
+		});
+
+		vstd::erase_if_present(open, currentNode);
+		closed.insert(currentNode);
+
+		if (currentNode == dst || gen->isRoad(currentNode))
+		{
+			// The goal node was reached. Trace the path using
+			// the saved parent information and return path
+			int3 backTracking = currentNode;
+			while (cameFrom[backTracking].valid())
+			{
+				// add node to path
+				roads.insert(backTracking);
+				gen->setRoad(backTracking, ERoadType::COBBLESTONE_ROAD);
+				logGlobal->traceStream() << boost::format("Setting road at tile %s") % backTracking;
+				// do the same for the predecessor
+				backTracking = cameFrom[backTracking];
+			}
+			return true;
+		}
+		else
+		{
+			gen->foreach_neighbour(currentNode, [gen, this, &open, &closed, &cameFrom, &currentNode, &distances](int3& pos)
+			{
+				int distance = distances[currentNode] + 1;
+				int bestDistanceSoFar = 1e6; //FIXME: boost::limits
+				auto it = distances.find(pos);
+				if (it != distances.end())
+					bestDistanceSoFar = it->second;
+
+				if (distance < bestDistanceSoFar || !vstd::contains(closed, pos))
+				{
+					if (gen->map->checkForVisitableDir(currentNode, &gen->map->getTile(pos), pos))
+					//if (gen->isFree(pos))
+					{
+						if (vstd::contains(this->tileinfo, pos))
+						{
+							cameFrom[pos] = currentNode;
+							open.insert(pos);
+							distances[pos] = distance;
+							logGlobal->traceStream() << boost::format("Found connection between node %s and %s, current distance %d") % currentNode % pos % distance;
+						}
+					}
+				}
+			});
+		}
+
+	}
+	logGlobal->warnStream() << boost::format("Failed to create road from %s to %s") % src %dst;
+	return false;
+
+}
+
+
 void CRmgTemplateZone::addRequiredObject(CGObjectInstance * obj, si32 strength)
 {
 	requiredObjects.push_back(std::make_pair(obj, strength));
@@ -919,7 +1021,7 @@ bool CRmgTemplateZone::createTreasurePile(CMapGenerator* gen, int3 &pos, float m
 				gen->setOccupied(tile, ETileType::BLOCKED); //so that crunch path doesn't cut through objects
 		}
 
-		if (!crunchPath (gen, closestTile, closestFreeTile, id))
+		if (!crunchPath (gen, closestTile, closestFreeTile))
 		{
 			//we can't connect this pile, just block it off and start over
 			for (auto treasure : treasures)
@@ -1246,6 +1348,7 @@ bool CRmgTemplateZone::placeMines (CMapGenerator* gen)
 bool CRmgTemplateZone::createRequiredObjects(CMapGenerator* gen)
 {
 	logGlobal->traceStream() << "Creating required objects";
+	
 	for(const auto &obj : requiredObjects)
 	{
 		int3 pos;
@@ -1254,11 +1357,11 @@ bool CRmgTemplateZone::createRequiredObjects(CMapGenerator* gen)
 			logGlobal->errorStream() << boost::format("Failed to fill zone %d due to lack of space") %id;
 			//TODO CLEANUP!
 			return false;
-		}
-
+		}		
+	
 		placeObject (gen, obj.first, pos);
 		guardObject (gen, obj.first, obj.second, (obj.first->ID == Obj::MONOLITH_TWO_WAY), true);
-		//paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones
+		//paths to required objects constitute main paths of zone. otherwise they just may lead to middle and create dead zones	
 	}
 
 	for (const auto &obj : closeObjects)
@@ -1446,6 +1549,52 @@ void CRmgTemplateZone::createObstacles2(CMapGenerator* gen)
 	}
 }
 
+void CRmgTemplateZone::connectRoads(CMapGenerator* gen)
+{
+	logGlobal->debug("Started building roads");
+	
+	std::set<int3> processed;
+	
+	while(!roadNodes.empty())
+	{
+		int3 node = *roadNodes.begin(); 
+		roadNodes.erase(node);
+		if(roads.empty())
+		{
+			//start road network
+			roads.insert(node);
+			logGlobal->debugStream() << "First node of road network: " << node; 
+		}
+		else
+		{
+			auto comparator = [=](int3 lhs, int3 rhs) { return node.dist2dSQ(lhs)  < node.dist2dSQ(rhs); };
+			
+			int3 cross = * boost::range::min_element(processed, comparator);
+			logGlobal->debugStream() << "Building road from " << node << " to " << cross; 
+			createRoad(gen, node, cross);
+		}
+		
+		processed.insert(node);		
+	}
+
+	drawRoads(gen);
+	
+	logGlobal->debug("Finished building roads");	
+}
+
+void CRmgTemplateZone::drawRoads(CMapGenerator* gen)
+{
+	std::vector<int3> tiles;
+	for (auto tile : roads)
+	{
+		if(gen->map->isInTheMap(tile))	
+			tiles.push_back (tile);
+	}
+	gen->editManager->getTerrainSelection().setSelection(tiles);	
+	gen->editManager->drawRoad(ERoadType::COBBLESTONE_ROAD, &gen->rand);	
+}
+
+
 bool CRmgTemplateZone::fill(CMapGenerator* gen)
 {
 	initTerrainType(gen);
@@ -1458,7 +1607,7 @@ bool CRmgTemplateZone::fill(CMapGenerator* gen)
 	placeMines(gen);
 	createRequiredObjects(gen);
 	createTreasures(gen);
-
+	
 	logGlobal->infoStream() << boost::format ("Zone %d filled successfully") %id;
 	return true;
 }
@@ -1669,6 +1818,24 @@ void CRmgTemplateZone::placeObject(CMapGenerator* gen, CGObjectInstance* object,
 		auto artid = sh->quest->m5arts.front();
 		logGlobal->warnStream() << boost::format("Placed Seer Hut at %s, quest artifact %d is %s") % object->pos % artid % VLC->arth->artifacts[artid]->Name();
 	}
+
+	
+	switch (object->ID)
+	{
+	case Obj::TOWN:
+	case Obj::RANDOM_TOWN:
+	case Obj::MONOLITH_TWO_WAY:
+	case Obj::MONOLITH_ONE_WAY_ENTRANCE:
+	case Obj::MONOLITH_ONE_WAY_EXIT:
+	case Obj::SUBTERRANEAN_GATE:
+		{
+			roadNodes.insert(object->visitablePos());
+		}
+		break;
+	
+	default:
+		break;
+	}		
 }
 
 void CRmgTemplateZone::placeAndGuardObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos, si32 str, bool zoneGuard)
@@ -1715,7 +1882,7 @@ bool CRmgTemplateZone::guardObject(CMapGenerator* gen, CGObjectInstance* object,
 	{
 		//crunching path may fail if center of the zone is directly over wide object
 		//make sure object is accessible before surrounding it with blocked tiles
-		if (crunchPath (gen, tile, findClosestTile(freePaths, tile), id, addToFreePaths ? &freePaths : nullptr))
+		if (crunchPath (gen, tile, findClosestTile(freePaths, tile), addToFreePaths ? &freePaths : nullptr))
 		{
 			guardTile = tile;
 			break;

+ 14 - 2
lib/rmg/CRmgTemplateZone.h

@@ -47,15 +47,18 @@ public:
 	bool isPossible() const;
 	bool isFree() const;
 	bool isUsed() const;
+	bool isRoad() const;	
 	void setOccupied(ETileType::ETileType value);
 	ETerrainType getTerrainType() const;
 	ETileType::ETileType getTileType() const;
 	void setTerrainType(ETerrainType value);
-
+	
+	void setRoadType(ERoadType::ERoadType value);
 private:
 	float nearestObjectDistance;
 	ETileType::ETileType occupied;
 	ETerrainType terrain;
+	ERoadType::ERoadType roadType;
 };
 
 class DLL_LINKAGE CTreasureInfo
@@ -166,7 +169,8 @@ public:
 	void createTreasures(CMapGenerator* gen);
 	void createObstacles1(CMapGenerator* gen);
 	void createObstacles2(CMapGenerator* gen);
-	bool crunchPath (CMapGenerator* gen, const int3 &src, const int3 &dst, TRmgTemplateZoneId zone, std::set<int3>* clearedTiles = nullptr);
+	bool crunchPath(CMapGenerator* gen, const int3 &src, const int3 &dst, std::set<int3>* clearedTiles = nullptr);
+	
 	std::vector<int3> getAccessibleOffsets (CMapGenerator* gen, CGObjectInstance* object);
 
 	void addConnection(TRmgTemplateZoneId otherZone);
@@ -179,6 +183,8 @@ public:
 	ObjectInfo getRandomObject (CMapGenerator* gen, CTreasurePileInfo &info, ui32 desiredValue, ui32 maxValue, ui32 currentValue);
 
 	void placeAndGuardObject(CMapGenerator* gen, CGObjectInstance* object, const int3 &pos, si32 str, bool zoneGuard = false);
+	void addRoadNode(const int3 & node);
+	void connectRoads(CMapGenerator * gen); //fills "roads" according to "roadNodes"
 
 private:
 	//template info
@@ -215,6 +221,12 @@ private:
 	std::set<int3> possibleTiles; //optimization purposes for treasure generation
 	std::vector<TRmgTemplateZoneId> connections; //list of adjacent zones
 	std::set<int3> freePaths; //core paths of free tiles that all other objects will be linked to
+	
+	std::set<int3> roadNodes; //tiles to be connected with roads
+	std::set<int3> roads; //all tiles with roads
+	
+	bool createRoad(CMapGenerator* gen, const int3 &src, const int3 &dst);
+	void drawRoads(CMapGenerator * gen); //actually updates tiles
 
 	bool pointIsIn(int x, int y);
 	void addAllPossibleObjects (CMapGenerator* gen); //add objects, including zone-specific, to possibleObjects

+ 1 - 1
vcmi.workspace

@@ -27,13 +27,13 @@
 			<Depends filename="lib/VCMI_lib.cbp" />
 		</Project>
 		<Project filename="test/Test.cbp">
-			<Depends filename="client/VCMI_client.cbp" />
 			<Depends filename="server/VCMI_server.cbp" />
 			<Depends filename="AI/EmptyAI/EmptyAI.cbp" />
 			<Depends filename="AI/VCAI/VCAI.cbp" />
 			<Depends filename="AI/StupidAI/StupidAI.cbp" />
 			<Depends filename="AI/BattleAI/BattleAI.cbp" />
 			<Depends filename="lib/VCMI_lib.cbp" />
+			<Depends filename="client/VCMI_client.cbp" />
 		</Project>
 		<Project filename="scripting/erm/ERM.cbp">
 			<Depends filename="lib/VCMI_lib.cbp" />