Browse Source

- Refactored CMapEditManager(added structure for undo functionality) - Refactored CMap(terrain pointer is private, safe access via getTile)

beegee1 12 years ago
parent
commit
03c2aa9153

+ 5 - 0
Global.h

@@ -467,6 +467,11 @@ namespace vstd
 	{
 		return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3)));
 	}
+	template<typename T, typename Arg1, typename Arg2, typename Arg3, typename Arg4>
+	std::unique_ptr<T> make_unique(Arg1 &&arg1, Arg2 &&arg2, Arg3 &&arg3, Arg4 &&arg4)
+	{
+		return std::unique_ptr<T>(new T(std::forward<Arg1>(arg1), std::forward<Arg2>(arg2), std::forward<Arg3>(arg3), std::forward<Arg4>(arg4)));
+	}
 
 	template <typename Container>
 	typename Container::const_reference circularAt(const Container &r, size_t index)

+ 6 - 6
client/mapHandler.cpp

@@ -478,7 +478,7 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
 				continue;
 
 			const TerrainTile2 & tile = ttiles[pos.x][pos.y][pos.z];
-			const TerrainTile &tinfo = map->terrain[pos.x][pos.y][pos.z];
+			const TerrainTile &tinfo = map->getTile(int3(pos.x, pos.y, pos.z));
 
 			SDL_Rect sr;
 			sr.x=srx;
@@ -501,9 +501,9 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
 			}
 
 			//Roads are shifted by 16 pixels to bottom. We have to draw both parts separately
-            if (pos.y > 0 && map->terrain[pos.x][pos.y-1][pos.z].roadType != ERoadType::NO_ROAD)
+			if (pos.y > 0 && map->getTile(int3(pos.x, pos.y-1, pos.z)).roadType != ERoadType::NO_ROAD)
 			{ //part from top tile
-				const TerrainTile &topTile = map->terrain[pos.x][pos.y-1][pos.z];
+				const TerrainTile &topTile = map->getTile(int3(pos.x, pos.y-1, pos.z));
 				Rect source(0, 16, 32, 16);
 				Rect dest(sr.x, sr.y, sr.w, sr.h/2);
                 blitterWithRotationAndAlpha(roadDefs[topTile.roadType - 1]->ourImages[topTile.roadDir].bitmap, source, extSurf, dest, (topTile.extTileFlags>>4)%4);
@@ -720,7 +720,7 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
 
 				// TODO: these should be activable by the console
 #ifdef MARK_BLOCKED_POSITIONS
-				if(map->terrain[pos.x][pos.y][top_tile.z].blocked) //temporary hiding blocked positions
+				if(map->getTile(int3(pos.x, pos.y, top_tile.z)).blocked) //temporary hiding blocked positions
 				{
 					SDL_Rect sr;
 
@@ -733,7 +733,7 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std::
 				}
 #endif
 #ifdef MARK_VISITABLE_POSITIONS
-				if(map->terrain[pos.x][pos.y][top_tile.z].visitable) //temporary hiding visitable positions
+				if(map->getTile(int3(pos.x, pos.y, top_tile.z)).visitable) //temporary hiding visitable positions
 				{
 					SDL_Rect sr;
 
@@ -1085,7 +1085,7 @@ void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terN
 {
 	out.clear();
 	TerrainTile2 & tt = ttiles[pos.x][pos.y][pos.z];
-	const TerrainTile &t = map->terrain[pos.x][pos.y][pos.z];
+	const TerrainTile &t = map->getTile(pos);
 	for(std::vector < std::pair<const CGObjectInstance*,SDL_Rect> >::const_iterator i = tt.objects.begin(); i != tt.objects.end(); i++)
 	{
 		if(i->first->ID == Obj::HOLE) //Hole

+ 8 - 8
lib/CGameState.cpp

@@ -959,7 +959,7 @@ void CGameState::init(StartInfo * si)
 			{
 				for (int k = 0; k < (map->twoLevel ? 2 : 1); k++)
 				{
-					const TerrainTile &t = map->terrain[i][j][k];
+					const TerrainTile &t = map->getTile(int3(i, j, k));
 					if(!t.blocked
 						&& !t.visitable
                         && t.terType != ETerrainType::WATER
@@ -1914,8 +1914,8 @@ int CGameState::getMovementCost(const CGHeroInstance *h, const int3 &src, const
 	if(src == dest) //same tile
 		return 0;
 
-	TerrainTile &s = map->terrain[src.x][src.y][src.z],
-		&d = map->terrain[dest.x][dest.y][dest.z];
+	TerrainTile &s = map->getTile(src),
+		&d = map->getTile(dest);
 
 	//get basic cost
 	int ret = h->getTileCost(d,s);
@@ -1993,7 +1993,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 	if (!map->isInTheMap(pos))
 		return guards;
 
-	const TerrainTile &posTile = map->terrain[pos.x][pos.y][pos.z];
+	const TerrainTile &posTile = map->getTile(pos);
 	if (posTile.visitable)
 	{
 		BOOST_FOREACH (CGObjectInstance* obj, posTile.visitableObjects)
@@ -2012,7 +2012,7 @@ std::vector<CGObjectInstance*> CGameState::guardingCreatures (int3 pos) const
 		{
 			if (map->isInTheMap(pos))
 			{
-				TerrainTile &tile = map->terrain[pos.x][pos.y][pos.z];
+				const auto & tile = map->getTile(pos);
                 if (tile.visitable && (tile.isWater() == posTile.isWater()))
 				{
 					BOOST_FOREACH (CGObjectInstance* obj, tile.visitableObjects)
@@ -2040,7 +2040,7 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const
 	// Give monster at position priority.
 	if (!map->isInTheMap(pos))
 		return int3(-1, -1, -1);
-	const TerrainTile &posTile = map->terrain[pos.x][pos.y][pos.z];
+	const TerrainTile &posTile = map->getTile(pos);
 	if (posTile.visitable)
 	{
 		BOOST_FOREACH (CGObjectInstance* obj, posTile.visitableObjects)
@@ -2063,7 +2063,7 @@ int3 CGameState::guardingCreaturePosition (int3 pos) const
 		{
 			if (map->isInTheMap(pos))
 			{
-				TerrainTile &tile = map->terrain[pos.x][pos.y][pos.z];
+				const auto & tile = map->getTile(pos);
                 if (tile.visitable && (tile.isWater() == posTile.isWater()))
 				{
 					BOOST_FOREACH (CGObjectInstance* obj, tile.visitableObjects)
@@ -3009,7 +3009,7 @@ void CPathfinder::initializeGraph()
 			for(size_t k=0; k < out.sizes.z; ++k)
 			{
 				curPos = int3(i,j,k);
-				const TerrainTile *tinfo = &gs->map->terrain[i][j][k];
+				const TerrainTile *tinfo = &gs->map->getTile(int3(i, j, k));
 				CGPathNode &node = graph[i][j][k];
 				node.accessible = evaluateAccessibility(tinfo);
 				node.turns = 0xff;

+ 9 - 61
lib/CRandomGenerator.h

@@ -34,101 +34,49 @@ typedef boost::uniform_real<double> TRealDist;
 typedef boost::variate_generator<TGenerator &, TIntDist> TRandI;
 typedef boost::variate_generator<TGenerator &, TRealDist> TRand;
 
-/**
- * The random generator randomly generates integers and real numbers("doubles") between
- * a given range. This is a header only class and mainly a wrapper for
- * convenient usage of the boost random API.
- */
+/// The random generator randomly generates integers and real numbers("doubles") between
+/// a given range. This is a header only class and mainly a wrapper for
+/// convenient usage of the boost random API.
 class CRandomGenerator
 {
 public:
-	/**
-	 * Constructor. Seeds the generator with the current time by default.
-	 */
+	/// Seeds the generator with the current time by default.
 	CRandomGenerator() 
 	{
-		gen.seed(std::time(nullptr)); 
+		gen.seed(std::time(nullptr));
 	}
 
-	/**
-	 * Seeds the generator with the given value.
-	 *
-	 * @param value the random seed
-	 */
 	void seed(int value)
 	{
 		gen.seed(value);
 	}
 
-	/**
-	 * Gets a generator which generates integers in the given range.
-	 *
-	 * Example how to use:
-	 * @code
-	 * TRandI rand = getRangeI(0, 10);
-	 * int a = rand(); // with the operator() the next value can be obtained
-	 * int b = rand(); // you can generate more values
-	 * @endcode
-	 *
-	 * @param lower the lower boundary
-	 * @param upper the upper boundary
-	 * @return the generator which can be used to generate integer numbers
-	 */
+	/// Generate several integer numbers within the same range.
+	/// e.g.: auto a = gen.getRangeI(0,10); a(); a(); a();
 	TRandI getRangeI(int lower, int upper)
 	{
 		TIntDist range(lower, upper);
 		return TRandI(gen, range);
 	}
 	
-	/**
-	 * Gets a integer in the given range. In comparison to getRangeI it's
-	 * a convenient method if you want to generate only one value in a given
-	 * range.
-	 *
-	 * @param lower the lower boundary
-	 * @param upper the upper boundary
-	 * @return the generated integer
-	 */
 	int getInteger(int lower, int upper)
 	{
 		return getRangeI(lower, upper)();
 	}
 	
-	/**
-	 * Gets a generator which generates doubles in the given range.
-	 *
-	 * Example how to use:
-	 * @code
-	 * TRand rand = getRange(23.56, 32.10);
-	 * double a = rand(); // with the operator() the next value can be obtained
-	 * double b = rand(); // you can generate more values
-	 * @endcode
-	 *
-	 * @param lower the lower boundary
-	 * @param upper the upper boundary
-	 * @return the generated double
-	 */
+	/// Generate several double/real numbers within the same range.
+	/// e.g.: auto a = gen.getRangeI(0,10); a(); a(); a();
 	TRand getRange(double lower, double upper)
 	{
 		TRealDist range(lower, upper);
 		return TRand(gen, range);
 	}
 	
-	/**
-	 * Gets a double in the given range. In comparison to getRange it's
-	 * a convenient method if you want to generate only one value in a given
-	 * range.
-	 *
-	 * @param lower the lower boundary
-	 * @param upper the upper boundary
-	 * @return the generated double
-	 */
 	double getDouble(double lower, double upper)
 	{
 		return getRange(lower, upper)();
 	}
 
 private:
-	/** The actual boost random generator. */
 	TGenerator gen;
 };

+ 21 - 4
lib/mapping/CMap.cpp

@@ -7,6 +7,7 @@
 #include "../CHeroHandler.h"
 #include "../CDefObjInfoHandler.h"
 #include "../CSpellHandler.h"
+#include "CMapEditManager.h"
 
 SHeroName::SHeroName() : heroId(-1)
 {
@@ -144,7 +145,7 @@ CMapHeader::~CMapHeader()
 
 }
 
-CMap::CMap() : checksum(0), terrain(nullptr), grailRadious(0)
+CMap::CMap() : checksum(0), grailRadious(0), terrain(nullptr)
 {
 	allowedAbilities = VLC->heroh->getDefaultAllowedAbilities();
 	allowedArtifact = VLC->arth->getDefaultAllowedArtifacts();
@@ -227,7 +228,7 @@ CGHeroInstance * CMap::getHero(int heroID)
 	return nullptr;
 }
 
-bool CMap::isInTheMap(const int3 &pos) const
+bool CMap::isInTheMap(const int3 & pos) const
 {
 	if(pos.x < 0 || pos.y < 0 || pos.z < 0 || pos.x >= width || pos.y >= height
 			|| pos.z > (twoLevel ? 1 : 0))
@@ -240,13 +241,23 @@ bool CMap::isInTheMap(const int3 &pos) const
 	}
 }
 
-TerrainTile & CMap::getTile( const int3 & tile )
+void CMap::getTileRangeCheck(const int3 & tile) const
 {
+	if(!isInTheMap(tile))
+	{
+		throw std::runtime_error(boost::str(boost::format("Cannot get map tile for position x '%d', y '%d', z '%d'.") % tile.x % tile.y % tile.z));
+	}
+}
+
+TerrainTile & CMap::getTile(const int3 & tile)
+{
+	getTileRangeCheck(tile);
 	return terrain[tile.x][tile.y][tile.z];
 }
 
-const TerrainTile & CMap::getTile( const int3 & tile ) const
+const TerrainTile & CMap::getTile(const int3 & tile) const
 {
+	getTileRangeCheck(tile);
 	return terrain[tile.x][tile.y][tile.z];
 }
 
@@ -312,3 +323,9 @@ void CMap::initTerrain()
 		}
 	}
 }
+
+CMapEditManager * CMap::getEditManager()
+{
+	if(!editManager) editManager = make_unique<CMapEditManager>(this);
+	return editManager.get();
+}

+ 10 - 1
lib/mapping/CMap.h

@@ -28,6 +28,7 @@ class CGTownInstance;
 class IModableArt;
 class IQuestObject;
 class CInputStream;
+class CMapEditManager;
 
 /// The hero name struct consists of the hero id and the hero name.
 struct DLL_LINKAGE SHeroName
@@ -326,6 +327,7 @@ public:
 	~CMap();
 	void initTerrain();
 
+	CMapEditManager * getEditManager();
 	TerrainTile & getTile(const int3 & tile);
 	const TerrainTile & getTile(const int3 & tile) const;
 	bool isInTheMap(const int3 & pos) const;
@@ -347,7 +349,6 @@ public:
 
 	ui32 checksum;
 	/// a 3-dimensional array of terrain tiles, access is as follows: x, y, level. where level=1 is underground
-	TerrainTile*** terrain;
 	std::vector<Rumor> rumors;
 	std::vector<DisposedHero> disposedHeroes;
 	std::vector<ConstTransitivePtr<CGHeroInstance> > predefinedHeroes;
@@ -367,6 +368,14 @@ public:
 	/// associative list to identify which hero/creature id belongs to which object id(index for objects)
 	bmap<si32, ObjectInstanceID> questIdentifierToId;
 
+	unique_ptr<CMapEditManager> editManager;
+
+private:
+	void getTileRangeCheck(const int3 & tile) const;
+
+	TerrainTile*** terrain;
+
+public:
 	template <typename Handler>
 	void serialize(Handler &h, const int formatVersion)
 	{

+ 316 - 161
lib/mapping/CMapEditManager.cpp

@@ -5,70 +5,328 @@
 #include "../filesystem/CResourceLoader.h"
 #include "../CDefObjInfoHandler.h"
 
-CMapEditManager::CMapEditManager(CMap * map, int randomSeed /*= std::time(nullptr)*/)
+CMapOperation::CMapOperation(CMap * map) : map(map)
+{
+
+}
+
+std::string CMapOperation::getLabel() const
+{
+	return "";
+}
+
+CMapUndoManager::CMapUndoManager() : undoRedoLimit(10)
+{
+
+}
+
+void CMapUndoManager::undo()
+{
+	doOperation(undoStack, redoStack, true);
+}
+
+void CMapUndoManager::redo()
+{
+	doOperation(redoStack, undoStack, false);
+}
+
+void CMapUndoManager::clearAll()
+{
+	undoStack.clear();
+	redoStack.clear();
+}
+
+int CMapUndoManager::getUndoRedoLimit() const
+{
+	return undoRedoLimit;
+}
+
+void CMapUndoManager::setUndoRedoLimit(int value)
+{
+	if(value < 0) throw std::runtime_error("Cannot set a negative value for the undo redo limit.");
+	undoStack.resize(std::min(undoStack.size(), static_cast<TStack::size_type>(value)));
+	redoStack.resize(std::min(redoStack.size(), static_cast<TStack::size_type>(value)));
+}
+
+const CMapOperation * CMapUndoManager::peekRedo() const
+{
+	return peek(redoStack);
+}
+
+const CMapOperation * CMapUndoManager::peekUndo() const
+{
+	return peek(undoStack);
+}
+
+void CMapUndoManager::addOperation(unique_ptr<CMapOperation> && operation)
+{
+	undoStack.push_front(std::move(operation));
+	if(undoStack.size() > undoRedoLimit) undoStack.pop_back();
+	redoStack.clear();
+}
+
+void CMapUndoManager::doOperation(TStack & fromStack, TStack & toStack, bool doUndo)
+{
+	if(fromStack.empty()) return;
+	auto & operation = fromStack.front();
+	if(doUndo)
+	{
+		operation->undo();
+	}
+	else
+	{
+		operation->redo();
+	}
+	toStack.push_front(std::move(operation));
+	fromStack.pop_front();
+}
+
+const CMapOperation * CMapUndoManager::peek(const TStack & stack) const
+{
+	if(stack.empty()) return nullptr;
+	return stack.front().get();
+}
+
+CMapEditManager::CMapEditManager(CMap * map)
 	: map(map)
 {
-	gen.seed(randomSeed);
+
 }
 
-void CMapEditManager::clearTerrain()
+void CMapEditManager::clearTerrain(CRandomGenerator * gen)
 {
 	for(int i = 0; i < map->width; ++i)
 	{
 		for(int j = 0; j < map->height; ++j)
 		{
-			map->terrain[i][j][0].terType = ETerrainType::WATER;
-			map->terrain[i][j][0].terView = gen.getInteger(20, 32);
+			map->getTile(int3(i, j, 0)).terType = ETerrainType::WATER;
+			map->getTile(int3(i, j, 0)).terView = gen->getInteger(20, 32);
 
 			if(map->twoLevel)
 			{
-				map->terrain[i][j][1].terType = ETerrainType::ROCK;
-				map->terrain[i][j][1].terView = 0;
+				map->getTile(int3(i, j, 1)).terType = ETerrainType::ROCK;
+				map->getTile(int3(i, j, 1)).terView = 0;
+			}
+		}
+	}
+}
+
+void CMapEditManager::drawTerrain(const MapRect & rect, ETerrainType terType, CRandomGenerator * gen)
+{
+	execute(make_unique<DrawTerrainOperation>(map, rect, terType, gen));
+}
+
+void CMapEditManager::insertObject(const int3 & pos, CGObjectInstance * obj)
+{
+	execute(make_unique<InsertObjectOperation>(map, pos, obj));
+}
+
+void CMapEditManager::execute(unique_ptr<CMapOperation> && operation)
+{
+	operation->execute();
+	undoManager.addOperation(std::move(operation));
+}
+
+void CMapEditManager::undo()
+{
+	undoManager.undo();
+}
+
+void CMapEditManager::redo()
+{
+	undoManager.redo();
+}
+
+CMapUndoManager & CMapEditManager::getUndoManager()
+{
+	return undoManager;
+}
+
+const std::string TerrainViewPattern::FLIP_MODE_SAME_IMAGE = "sameImage";
+const std::string TerrainViewPattern::FLIP_MODE_DIFF_IMAGES = "diffImages";
+
+const std::string TerrainViewPattern::RULE_DIRT = "D";
+const std::string TerrainViewPattern::RULE_SAND = "S";
+const std::string TerrainViewPattern::RULE_TRANSITION = "T";
+const std::string TerrainViewPattern::RULE_NATIVE = "N";
+const std::string TerrainViewPattern::RULE_ANY = "?";
+
+TerrainViewPattern::TerrainViewPattern() : minPoints(0), flipMode(FLIP_MODE_SAME_IMAGE),
+	terGroup(ETerrainGroup::NORMAL)
+{
+
+}
+
+TerrainViewPattern::WeightedRule::WeightedRule() : points(0)
+{
+
+}
+
+bool TerrainViewPattern::WeightedRule::isStandardRule() const
+{
+	return TerrainViewPattern::RULE_ANY == name || TerrainViewPattern::RULE_DIRT == name
+		|| TerrainViewPattern::RULE_NATIVE == name || TerrainViewPattern::RULE_SAND == name
+		|| TerrainViewPattern::RULE_TRANSITION == name;
+}
+
+boost::mutex CTerrainViewPatternConfig::smx;
+
+CTerrainViewPatternConfig & CTerrainViewPatternConfig::get()
+{
+	TLockGuard _(smx);
+	static CTerrainViewPatternConfig instance;
+	return instance;
+}
+
+CTerrainViewPatternConfig::CTerrainViewPatternConfig()
+{
+	const JsonNode config(ResourceID("config/terrainViewPatterns.json"));
+	const std::map<std::string, ETerrainGroup::ETerrainGroup> terGroups
+			= boost::assign::map_list_of("normal", ETerrainGroup::NORMAL)("dirt", ETerrainGroup::DIRT)
+			("sand", ETerrainGroup::SAND)("water", ETerrainGroup::WATER)("rock", ETerrainGroup::ROCK);
+	BOOST_FOREACH(auto terMapping, terGroups)
+	{
+		BOOST_FOREACH(const JsonNode & ptrnNode, config[terMapping.first].Vector())
+		{
+			TerrainViewPattern pattern;
+
+			// Read pattern data
+			const JsonVector & data = ptrnNode["data"].Vector();
+			if(data.size() != 9)
+			{
+				throw std::runtime_error("Size of pattern's data vector has to be 9.");
 			}
+			for(int i = 0; i < data.size(); ++i)
+			{
+				std::string cell = data[i].String();
+				boost::algorithm::erase_all(cell, " ");
+				std::vector<std::string> rules;
+				boost::split(rules, cell, boost::is_any_of(","));
+				BOOST_FOREACH(std::string ruleStr, rules)
+				{
+					std::vector<std::string> ruleParts;
+					boost::split(ruleParts, ruleStr, boost::is_any_of("-"));
+					TerrainViewPattern::WeightedRule rule;
+					rule.name = ruleParts[0];
+					if(ruleParts.size() > 1)
+					{
+						rule.points = boost::lexical_cast<int>(ruleParts[1]);
+					}
+					pattern.data[i].push_back(rule);
+				}
+			}
+
+			// Read mapping
+			std::string mappingStr = ptrnNode["mapping"].String();
+			boost::algorithm::erase_all(mappingStr, " ");
+			std::vector<std::string> mappings;
+			boost::split(mappings, mappingStr, boost::is_any_of(","));
+			BOOST_FOREACH(std::string mapping, mappings)
+			{
+				std::vector<std::string> range;
+				boost::split(range, mapping, boost::is_any_of("-"));
+				pattern.mapping.push_back(std::make_pair(boost::lexical_cast<int>(range[0]),
+					boost::lexical_cast<int>(range.size() > 1 ? range[1] : range[0])));
+			}
+
+			// Read optional attributes
+			pattern.id = ptrnNode["id"].String();
+			pattern.minPoints = static_cast<int>(ptrnNode["minPoints"].Float());
+			pattern.flipMode = ptrnNode["flipMode"].String();
+			if(pattern.flipMode.empty())
+			{
+				pattern.flipMode = TerrainViewPattern::FLIP_MODE_SAME_IMAGE;
+			}
+
+			pattern.terGroup = terMapping.second;
+			patterns[terMapping.second].push_back(pattern);
 		}
 	}
 }
 
-void CMapEditManager::drawTerrain(ETerrainType terType, int posx, int posy, int width, int height, bool underground)
+CTerrainViewPatternConfig::~CTerrainViewPatternConfig()
+{
+
+}
+
+const std::vector<TerrainViewPattern> & CTerrainViewPatternConfig::getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const
+{
+	return patterns.find(terGroup)->second;
+}
+
+const TerrainViewPattern & CTerrainViewPatternConfig::getPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const
+{
+	const std::vector<TerrainViewPattern> & groupPatterns = getPatternsForGroup(terGroup);
+	BOOST_FOREACH(const TerrainViewPattern & pattern, groupPatterns)
+	{
+		if(id == pattern.id)
+		{
+			return pattern;
+		}
+	}
+	throw std::runtime_error("Pattern with ID not found: " + id);
+}
+
+DrawTerrainOperation::DrawTerrainOperation(CMap * map, const MapRect & rect, ETerrainType terType, CRandomGenerator * gen)
+	: CMapOperation(map), rect(rect), terType(terType), gen(gen)
+{
+
+}
+
+void DrawTerrainOperation::execute()
 {
-	bool mapLevel = underground ? 1 : 0;
-	for(int i = posx; i < posx + width; ++i)
+	for(int i = rect.pos.x; i < rect.pos.x + rect.width; ++i)
 	{
-		for(int j = posy; j < posy + height; ++j)
+		for(int j = rect.pos.y; j < rect.pos.y + rect.height; ++j)
 		{
-			map->terrain[i][j][mapLevel].terType = terType;
+			map->getTile(int3(i, j, rect.pos.z)).terType = terType;
 		}
 	}
 
 	//TODO there are situations where more tiles are affected implicitely
 	//TODO add coastal bit to extTileFlags appropriately
 
-	updateTerrainViews(posx - 1, posy - 1, width + 2, height + 2, mapLevel);
+	updateTerrainViews(MapRect(int3(rect.pos.x - 1, rect.pos.y - 1, rect.pos.z), rect.width + 2, rect.height + 2));
 }
 
-void CMapEditManager::updateTerrainViews(int posx, int posy, int width, int height, int mapLevel)
+void DrawTerrainOperation::undo()
 {
-	for(int i = posx; i < posx + width; ++i)
+	//TODO
+}
+
+void DrawTerrainOperation::redo()
+{
+	//TODO
+}
+
+std::string DrawTerrainOperation::getLabel() const
+{
+	return "Draw Terrain";
+}
+
+void DrawTerrainOperation::updateTerrainViews(const MapRect & rect)
+{
+	for(int i = rect.pos.x; i < rect.pos.x + rect.width; ++i)
 	{
-		for(int j = posy; j < posy + height; ++j)
+		for(int j = rect.pos.y; j < rect.pos.y + rect.height; ++j)
 		{
-			const std::vector<TerrainViewPattern> & patterns =
-					CTerrainViewPatternConfig::get().getPatternsForGroup(getTerrainGroup(map->terrain[i][j][mapLevel].terType));
+			const auto & patterns =
+					CTerrainViewPatternConfig::get().getPatternsForGroup(getTerrainGroup(map->getTile(int3(i, j, rect.pos.z)).terType));
 
 			// Detect a pattern which fits best
 			int bestPattern = -1, bestFlip = -1;
 			std::string transitionReplacement;
 			for(int k = 0; k < patterns.size(); ++k)
 			{
-				const TerrainViewPattern & pattern = patterns[k];
+				const auto & pattern = patterns[k];
 
 				for(int flip = 0; flip < 4; ++flip)
 				{
-					ValidationResult valRslt = validateTerrainView(i, j, mapLevel, flip > 0 ? getFlippedPattern(pattern, flip) : pattern);
+					auto valRslt = validateTerrainView(int3(i, j, rect.pos.z), flip > 0 ? getFlippedPattern(pattern, flip) : pattern);
 					if(valRslt.result)
 					{
-                        logGlobal->debugStream() << "Pattern detected at pos " << i << "x" << j << "x" << mapLevel << ": P-Nr. " << k
-                              << ", Flip " << flip << ", Repl. " << valRslt.transitionReplacement;
+						logGlobal->debugStream() << "Pattern detected at pos " << i << "x" << j << "x" << rect.pos.z << ": P-Nr. " << k
+							  << ", Flip " << flip << ", Repl. " << valRslt.transitionReplacement;
 
 						bestPattern = k;
 						bestFlip = flip;
@@ -80,7 +338,7 @@ void CMapEditManager::updateTerrainViews(int posx, int posy, int width, int heig
 			if(bestPattern == -1)
 			{
 				// This shouldn't be the case
-                logGlobal->warnStream() << "No pattern detected at pos " << i << "x" << j << "x" << mapLevel;
+				logGlobal->warnStream() << "No pattern detected at pos " << i << "x" << j << "x" << rect.pos.z;
 				continue;
 			}
 
@@ -97,23 +355,24 @@ void CMapEditManager::updateTerrainViews(int posx, int posy, int width, int heig
 			}
 
 			// Set terrain view
+			auto & tile = map->getTile(int3(i, j, rect.pos.z));
 			if(pattern.flipMode == TerrainViewPattern::FLIP_MODE_SAME_IMAGE)
 			{
-				map->terrain[i][j][mapLevel].terView = gen.getInteger(mapping.first, mapping.second);
-				map->terrain[i][j][mapLevel].extTileFlags = bestFlip;
+				tile.terView = gen->getInteger(mapping.first, mapping.second);
+				tile.extTileFlags = bestFlip;
 			}
 			else
 			{
 				int range = (mapping.second - mapping.first) / 4;
-				map->terrain[i][j][mapLevel].terView = gen.getInteger(mapping.first + bestFlip * range,
+				tile.terView = gen->getInteger(mapping.first + bestFlip * range,
 					mapping.first + (bestFlip + 1) * range - 1);
-				map->terrain[i][j][mapLevel].extTileFlags =	0;
+				tile.extTileFlags =	0;
 			}
 		}
 	}
 }
 
-ETerrainGroup::ETerrainGroup CMapEditManager::getTerrainGroup(ETerrainType terType) const
+ETerrainGroup::ETerrainGroup DrawTerrainOperation::getTerrainGroup(ETerrainType terType) const
 {
 	switch(terType)
 	{
@@ -130,9 +389,9 @@ ETerrainGroup::ETerrainGroup CMapEditManager::getTerrainGroup(ETerrainType terTy
 	}
 }
 
-CMapEditManager::ValidationResult CMapEditManager::validateTerrainView(int posx, int posy, int mapLevel, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const
+DrawTerrainOperation::ValidationResult DrawTerrainOperation::validateTerrainView(const int3 & pos, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const
 {
-	ETerrainType centerTerType = map->terrain[posx][posy][mapLevel].terType;
+	ETerrainType centerTerType = map->getTile(pos).terType;
 	int totalPoints = 0;
 	std::string transitionReplacement;
 
@@ -145,8 +404,8 @@ CMapEditManager::ValidationResult CMapEditManager::validateTerrainView(int posx,
 		}
 
 		// Get terrain group of the current cell
-		int cx = posx + (i % 3) - 1;
-		int cy = posy + (i / 3) - 1;
+		int cx = pos.x + (i % 3) - 1;
+		int cy = pos.y + (i / 3) - 1;
 		bool isAlien = false;
 		ETerrainType terType;
 		if(cx < 0 || cx >= map->width || cy < 0 || cy >= map->height)
@@ -155,7 +414,7 @@ CMapEditManager::ValidationResult CMapEditManager::validateTerrainView(int posx,
 		}
 		else
 		{
-			terType = map->terrain[cx][cy][mapLevel].terType;
+			terType = map->getTile(int3(cx, cy, pos.z)).terType;
 			if(terType != centerTerType)
 			{
 				isAlien = true;
@@ -171,8 +430,8 @@ CMapEditManager::ValidationResult CMapEditManager::validateTerrainView(int posx,
 			{
 				if(recDepth == 0)
 				{
-					const TerrainViewPattern & patternForRule = CTerrainViewPatternConfig::get().getPatternById(pattern.terGroup, rule.name);
-					ValidationResult rslt = validateTerrainView(cx, cy, mapLevel, patternForRule, 1);
+					const auto & patternForRule = CTerrainViewPatternConfig::get().getPatternById(pattern.terGroup, rule.name);
+					auto rslt = validateTerrainView(int3(cx, cy, pos.z), patternForRule, 1);
 					if(!rslt.result)
 					{
 						return ValidationResult(false);
@@ -260,7 +519,7 @@ CMapEditManager::ValidationResult CMapEditManager::validateTerrainView(int posx,
 	return ValidationResult(true, transitionReplacement);
 }
 
-bool CMapEditManager::isSandType(ETerrainType terType) const
+bool DrawTerrainOperation::isSandType(ETerrainType terType) const
 {
 	switch(terType)
 	{
@@ -273,7 +532,7 @@ bool CMapEditManager::isSandType(ETerrainType terType) const
 	}
 }
 
-TerrainViewPattern CMapEditManager::getFlippedPattern(const TerrainViewPattern & pattern, int flip) const
+TerrainViewPattern DrawTerrainOperation::getFlippedPattern(const TerrainViewPattern & pattern, int flip) const
 {
 	if(flip == 0)
 	{
@@ -300,149 +559,45 @@ TerrainViewPattern CMapEditManager::getFlippedPattern(const TerrainViewPattern &
 	return ret;
 }
 
-void CMapEditManager::insertObject(CGObjectInstance * obj, int posx, int posy, bool underground)
-{
-	obj->pos = int3(posx, posy, underground ? 1 : 0);
-	obj->id = ObjectInstanceID(map->objects.size());
-	map->objects.push_back(obj);
-	if(obj->ID == Obj::TOWN)
-	{
-		map->towns.push_back(static_cast<CGTownInstance *>(obj));
-	}
-	if(obj->ID == Obj::HERO)
-	{
-		map->heroes.push_back(static_cast<CGHeroInstance*>(obj));
-	}
-	map->addBlockVisTiles(obj);
-}
-
-CMapEditManager::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement /*= ""*/)
+DrawTerrainOperation::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement /*= ""*/)
 	: result(result), transitionReplacement(transitionReplacement)
 {
 
 }
 
-const std::string TerrainViewPattern::FLIP_MODE_SAME_IMAGE = "sameImage";
-const std::string TerrainViewPattern::FLIP_MODE_DIFF_IMAGES = "diffImages";
-
-const std::string TerrainViewPattern::RULE_DIRT = "D";
-const std::string TerrainViewPattern::RULE_SAND = "S";
-const std::string TerrainViewPattern::RULE_TRANSITION = "T";
-const std::string TerrainViewPattern::RULE_NATIVE = "N";
-const std::string TerrainViewPattern::RULE_ANY = "?";
-
-TerrainViewPattern::TerrainViewPattern() : minPoints(0), flipMode(FLIP_MODE_SAME_IMAGE),
-	terGroup(ETerrainGroup::NORMAL)
+InsertObjectOperation::InsertObjectOperation(CMap * map, const int3 & pos, CGObjectInstance * obj)
+	: CMapOperation(map), pos(pos), obj(obj)
 {
 
 }
 
-TerrainViewPattern::WeightedRule::WeightedRule() : points(0)
-{
-
-}
-
-bool TerrainViewPattern::WeightedRule::isStandardRule() const
-{
-	return TerrainViewPattern::RULE_ANY == name || TerrainViewPattern::RULE_DIRT == name
-		|| TerrainViewPattern::RULE_NATIVE == name || TerrainViewPattern::RULE_SAND == name
-		|| TerrainViewPattern::RULE_TRANSITION == name;
-}
-
-boost::mutex CTerrainViewPatternConfig::smx;
-
-CTerrainViewPatternConfig & CTerrainViewPatternConfig::get()
-{
-	TLockGuard _(smx);
-	static CTerrainViewPatternConfig instance;
-	return instance;
-}
-
-CTerrainViewPatternConfig::CTerrainViewPatternConfig()
+void InsertObjectOperation::execute()
 {
-	const JsonNode config(ResourceID("config/terrainViewPatterns.json"));
-	const std::map<std::string, ETerrainGroup::ETerrainGroup> terGroups
-			= boost::assign::map_list_of("normal", ETerrainGroup::NORMAL)("dirt", ETerrainGroup::DIRT)
-			("sand", ETerrainGroup::SAND)("water", ETerrainGroup::WATER)("rock", ETerrainGroup::ROCK);
-	BOOST_FOREACH(auto terMapping, terGroups)
+	obj->pos = pos;
+	obj->id = ObjectInstanceID(map->objects.size());
+	map->objects.push_back(obj);
+	if(obj->ID == Obj::TOWN)
 	{
-		BOOST_FOREACH(const JsonNode & ptrnNode, config[terMapping.first].Vector())
-		{
-			TerrainViewPattern pattern;
-
-			// Read pattern data
-			const JsonVector & data = ptrnNode["data"].Vector();
-			if(data.size() != 9)
-			{
-				throw std::runtime_error("Size of pattern's data vector has to be 9.");
-			}
-			for(int i = 0; i < data.size(); ++i)
-			{
-				std::string cell = data[i].String();
-				boost::algorithm::erase_all(cell, " ");
-				std::vector<std::string> rules;
-				boost::split(rules, cell, boost::is_any_of(","));
-				BOOST_FOREACH(std::string ruleStr, rules)
-				{
-					std::vector<std::string> ruleParts;
-					boost::split(ruleParts, ruleStr, boost::is_any_of("-"));
-					TerrainViewPattern::WeightedRule rule;
-					rule.name = ruleParts[0];
-					if(ruleParts.size() > 1)
-					{
-						rule.points = boost::lexical_cast<int>(ruleParts[1]);
-					}
-					pattern.data[i].push_back(rule);
-				}
-			}
-
-			// Read mapping
-			std::string mappingStr = ptrnNode["mapping"].String();
-			boost::algorithm::erase_all(mappingStr, " ");
-			std::vector<std::string> mappings;
-			boost::split(mappings, mappingStr, boost::is_any_of(","));
-			BOOST_FOREACH(std::string mapping, mappings)
-			{
-				std::vector<std::string> range;
-				boost::split(range, mapping, boost::is_any_of("-"));
-				pattern.mapping.push_back(std::make_pair(boost::lexical_cast<int>(range[0]),
-					boost::lexical_cast<int>(range.size() > 1 ? range[1] : range[0])));
-			}
-
-			// Read optional attributes
-			pattern.id = ptrnNode["id"].String();
-			pattern.minPoints = static_cast<int>(ptrnNode["minPoints"].Float());
-			pattern.flipMode = ptrnNode["flipMode"].String();
-			if(pattern.flipMode.empty())
-			{
-				pattern.flipMode = TerrainViewPattern::FLIP_MODE_SAME_IMAGE;
-			}
-
-			pattern.terGroup = terMapping.second;
-			patterns[terMapping.second].push_back(pattern);
-		}
+		map->towns.push_back(static_cast<CGTownInstance *>(obj));
+	}
+	if(obj->ID == Obj::HERO)
+	{
+		map->heroes.push_back(static_cast<CGHeroInstance*>(obj));
 	}
+	map->addBlockVisTiles(obj);
 }
 
-CTerrainViewPatternConfig::~CTerrainViewPatternConfig()
+void InsertObjectOperation::undo()
 {
-
+	//TODO
 }
 
-const std::vector<TerrainViewPattern> & CTerrainViewPatternConfig::getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const
+void InsertObjectOperation::redo()
 {
-	return patterns.find(terGroup)->second;
+	execute();
 }
 
-const TerrainViewPattern & CTerrainViewPatternConfig::getPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const
+std::string InsertObjectOperation::getLabel() const
 {
-	const std::vector<TerrainViewPattern> & groupPatterns = getPatternsForGroup(terGroup);
-	BOOST_FOREACH(const TerrainViewPattern & pattern, groupPatterns)
-	{
-		if(id == pattern.id)
-		{
-			return pattern;
-		}
-	}
-	throw std::runtime_error("Pattern with ID not found: " + id);
+	return "Insert Object";
 }

+ 121 - 27
lib/mapping/CMapEditManager.h

@@ -30,43 +30,83 @@ namespace ETerrainGroup
 	};
 }
 
-/// The map edit manager provides functionality for drawing terrain and placing
-/// objects on the map.
-class CMapEditManager
+/// Represents a map rectangle.
+struct DLL_LINKAGE MapRect
+{
+	MapRect(int3 pos, si32 width, si32 height) : pos(pos), width(width), height(height) { };
+	int3 pos;
+	si32 width, height;
+};
+
+/// The abstract base class CMapOperation defines an operation that can be executed, undone and redone.
+class CMapOperation
 {
 public:
-	CMapEditManager(CMap * map, int randomSeed = std::time(nullptr));
+	CMapOperation(CMap * map);
+	virtual ~CMapOperation() { };
 
-	/// Clears the terrain. The free level is filled with water and the underground level with rock.
-	void clearTerrain();
+	virtual void execute() = 0;
+	virtual void undo() = 0;
+	virtual void redo() = 0;
+	virtual std::string getLabel() const; /// Returns a display-able name of the operation.
 
-	void drawTerrain(ETerrainType terType, int posx, int posy, int width, int height, bool underground);
-	void insertObject(CGObjectInstance * obj, int posx, int posy, bool underground);
+protected:
+	CMap * map;
+};
+
+/// The CMapUndoManager provides the functionality to save operations and undo/redo them.
+class CMapUndoManager
+{
+public:
+	CMapUndoManager();
+
+	void undo();
+	void redo();
+	void clearAll();
+
+	/// The undo redo limit is a number which says how many undo/redo items can be saved. The default
+	/// value is 10. If the value is 0, no undo/redo history will be maintained.
+	int getUndoRedoLimit() const;
+	void setUndoRedoLimit(int value);
+
+	const CMapOperation * peekRedo() const;
+	const CMapOperation * peekUndo() const;
+
+	void addOperation(unique_ptr<CMapOperation> && operation); /// Client code does not need to call this method.
 
 private:
-	struct ValidationResult
-	{
-		ValidationResult(bool result, const std::string & transitionReplacement = "");
+	typedef std::list<unique_ptr<CMapOperation> > TStack;
 
-		bool result;
-		/// The replacement of a T rule, either D or S.
-		std::string transitionReplacement;
-	};
+	inline void doOperation(TStack & fromStack, TStack & toStack, bool doUndo);
+	inline const CMapOperation * peek(const TStack & stack) const;
 
-	void updateTerrainViews(int posx, int posy, int width, int height, int mapLevel);
-	ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType terType) const;
-	/// Validates the terrain view of the given position and with the given pattern.
-	ValidationResult validateTerrainView(int posx, int posy, int mapLevel, const TerrainViewPattern & pattern, int recDepth = 0) const;
-	/// Tests whether the given terrain type is a sand type. Sand types are: Water, Sand and Rock
-	bool isSandType(ETerrainType terType) const;
-	TerrainViewPattern getFlippedPattern(const TerrainViewPattern & pattern, int flip) const;
+	TStack undoStack;
+	TStack redoStack;
+	int undoRedoLimit;
+};
 
-	static const int FLIP_PATTERN_HORIZONTAL = 1;
-	static const int FLIP_PATTERN_VERTICAL = 2;
-	static const int FLIP_PATTERN_BOTH = 3;
+/// The map edit manager provides functionality for drawing terrain and placing
+/// objects on the map.
+class DLL_LINKAGE CMapEditManager
+{
+public:
+	CMapEditManager(CMap * map);
+
+	/// Clears the terrain. The free level is filled with water and the underground level with rock.
+	void clearTerrain(CRandomGenerator * gen);
+
+	void drawTerrain(const MapRect & rect, ETerrainType terType, CRandomGenerator * gen);
+	void insertObject(const int3 & pos, CGObjectInstance * obj);
+
+	CMapUndoManager & getUndoManager();
+	void undo();
+	void redo();
+
+private:
+	void execute(unique_ptr<CMapOperation> && operation);
 
 	CMap * map;
-	CRandomGenerator gen;
+	CMapUndoManager undoManager;
 };
 
 /* ---------------------------------------------------------------------------- */
@@ -146,7 +186,7 @@ struct TerrainViewPattern
 };
 
 /// The terrain view pattern config loads pattern data from the filesystem.
-class CTerrainViewPatternConfig
+class CTerrainViewPatternConfig : public boost::noncopyable
 {
 public:
 	static CTerrainViewPatternConfig & get();
@@ -161,3 +201,57 @@ private:
 	std::map<ETerrainGroup::ETerrainGroup, std::vector<TerrainViewPattern> > patterns;
 	static boost::mutex smx;
 };
+
+/// The DrawTerrainOperation class draws a terrain area on the map.
+class DrawTerrainOperation : public CMapOperation
+{
+public:
+	DrawTerrainOperation(CMap * map, const MapRect & rect, ETerrainType terType, CRandomGenerator * gen);
+
+	void execute();
+	void undo();
+	void redo();
+	std::string getLabel() const;
+
+private:
+	struct ValidationResult
+	{
+		ValidationResult(bool result, const std::string & transitionReplacement = "");
+
+		bool result;
+		/// The replacement of a T rule, either D or S.
+		std::string transitionReplacement;
+	};
+
+	void updateTerrainViews(const MapRect & rect);
+	ETerrainGroup::ETerrainGroup getTerrainGroup(ETerrainType terType) const;
+	/// Validates the terrain view of the given position and with the given pattern.
+	ValidationResult validateTerrainView(const int3 & pos, const TerrainViewPattern & pattern, int recDepth = 0) const;
+	/// Tests whether the given terrain type is a sand type. Sand types are: Water, Sand and Rock
+	bool isSandType(ETerrainType terType) const;
+	TerrainViewPattern getFlippedPattern(const 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;
+
+	MapRect rect;
+	ETerrainType terType;
+	CRandomGenerator * gen;
+};
+
+/// The InsertObjectOperation class inserts an object to the map.
+class InsertObjectOperation : public CMapOperation
+{
+public:
+	InsertObjectOperation(CMap * map, const int3 & pos, CGObjectInstance * obj);
+
+	void execute();
+	void undo();
+	void redo();
+	std::string getLabel() const;
+
+private:
+	int3 pos;
+	CGObjectInstance * obj;
+};

+ 10 - 9
lib/mapping/MapFormatH3M.cpp

@@ -711,15 +711,16 @@ void CMapLoaderH3M::readTerrain()
 		{
 			for(int z = 0; z < map->height; z++)
 			{
-				map->terrain[z][c][a].terType = ETerrainType(reader.readUInt8());
-				map->terrain[z][c][a].terView = reader.readUInt8();
-				map->terrain[z][c][a].riverType = static_cast<ERiverType::ERiverType>(reader.readUInt8());
-				map->terrain[z][c][a].riverDir = reader.readUInt8();
-				map->terrain[z][c][a].roadType = static_cast<ERoadType::ERoadType>(reader.readUInt8());
-				map->terrain[z][c][a].roadDir = reader.readUInt8();
-				map->terrain[z][c][a].extTileFlags = reader.readUInt8();
-				map->terrain[z][c][a].blocked = (map->terrain[z][c][a].terType == ETerrainType::ROCK ? 1 : 0); //underground tiles are always blocked
-				map->terrain[z][c][a].visitable = 0;
+				auto & tile = map->getTile(int3(z, c, a));
+				tile.terType = ETerrainType(reader.readUInt8());
+				tile.terView = reader.readUInt8();
+				tile.riverType = static_cast<ERiverType::ERiverType>(reader.readUInt8());
+				tile.riverDir = reader.readUInt8();
+				tile.roadType = static_cast<ERoadType::ERoadType>(reader.readUInt8());
+				tile.roadDir = reader.readUInt8();
+				tile.extTileFlags = reader.readUInt8();
+				tile.blocked = (tile.terType == ETerrainType::ROCK ? 1 : 0); //underground tiles are always blocked
+				tile.visitable = 0;
 			}
 		}
 	}

+ 6 - 6
lib/rmg/CMapGenerator.cpp

@@ -179,14 +179,14 @@ const std::map<PlayerColor, CMapGenOptions::CPlayerSettings> & CMapGenOptions::g
 void CMapGenOptions::setStartingTownForPlayer(PlayerColor color, si32 town)
 {
 	auto it = players.find(color);
-	if(it == players.end()) throw std::runtime_error(boost::str(boost::format("Cannot set starting town for the player with the color '%s'.") % std::to_string(color.getNum())));
+	if(it == players.end()) throw std::runtime_error(boost::str(boost::format("Cannot set starting town for the player with the color '%s'.") % color));
 	it->second.setStartingTown(town);
 }
 
 void CMapGenOptions::setPlayerTypeForStandardPlayer(PlayerColor color, EPlayerType::EPlayerType playerType)
 {
 	auto it = players.find(color);
-	if(it == players.end()) throw std::runtime_error(boost::str(boost::format("Cannot set player type for the player with the color '%s'.") % std::to_string(color.getNum())));
+	if(it == players.end()) throw std::runtime_error(boost::str(boost::format("Cannot set player type for the player with the color '%s'.") % color));
 	if(playerType == EPlayerType::COMP_ONLY) throw std::runtime_error("Cannot set player type computer only to a standard player.");
 	it->second.setPlayerType(playerType);
 }
@@ -375,9 +375,9 @@ std::unique_ptr<CMap> CMapGenerator::generate()
 	//TODO select a template based on the map gen options or adapt it if necessary
 
 	map = make_unique<CMap>();
+	editManager = map->getEditManager();
 	addHeaderInfo();
 
-	mapMgr = make_unique<CMapEditManager>(map.get(), randomSeed);
 	genTerrain();
 	genTowns();
 
@@ -473,8 +473,8 @@ void CMapGenerator::addPlayerInfo()
 void CMapGenerator::genTerrain()
 {
 	map->initTerrain(); //FIXME nicer solution
-	mapMgr->clearTerrain();
-	mapMgr->drawTerrain(ETerrainType::GRASS, 10, 10, 20, 30, false);
+	editManager->clearTerrain(&gen);
+	editManager->drawTerrain(MapRect(int3(10, 10, 0), 20, 30), ETerrainType::GRASS, &gen);
 }
 
 void CMapGenerator::genTowns()
@@ -497,7 +497,7 @@ void CMapGenerator::genTowns()
 		town->defInfo = VLC->dobjinfo->gobjs[town->ID][town->subID];
 		town->builtBuildings.insert(BuildingID::FORT);
 		town->builtBuildings.insert(BuildingID::DEFAULT);
-		mapMgr->insertObject(town, townPos[side].x, townPos[side].y + (i / 2) * 5, false);
+		editManager->insertObject(int3(townPos[side].x, townPos[side].y + (i / 2) * 5, 0), town);
 
 		// Update player info
 		playerInfo.allowedFactions.clear();

+ 1 - 1
lib/rmg/CMapGenerator.h

@@ -186,5 +186,5 @@ private:
 	std::unique_ptr<CMap> map;
 	CRandomGenerator gen;
 	int randomSeed;
-	std::unique_ptr<CMapEditManager> mapMgr;
+	CMapEditManager * editManager;
 };

+ 3 - 3
server/CGameHandler.cpp

@@ -1699,7 +1699,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player
 		return false;
 	}
 
-	TerrainTile t = gs->map->terrain[hmpos.x][hmpos.y][hmpos.z];
+	TerrainTile t = gs->map->getTile(hmpos);
 	int cost = gs->getMovementCost(h, h->getPosition(false), CGHeroInstance::convertPosition(dst,false),h->movement);
 	int3 guardPos = gs->guardingCreaturePosition(hmpos);
 
@@ -1740,7 +1740,7 @@ bool CGameHandler::moveHero( ObjectInstanceID hid, int3 dst, ui8 instant, Player
 	// should be called if hero changes tile but before applying TryMoveHero package
 	auto leaveTile = [&]()
 	{
-		BOOST_FOREACH(CGObjectInstance *obj, gs->map->terrain[h->pos.x-1][h->pos.y][h->pos.z].visitableObjects)
+		BOOST_FOREACH(CGObjectInstance *obj, gs->map->getTile(int3(h->pos.x-1, h->pos.y, h->pos.z)).visitableObjects)
 		{
 			obj->onHeroLeave(h);
 		}
@@ -5637,7 +5637,7 @@ bool CGameHandler::tryAttackingGuard(const int3 &guardPos, const CGHeroInstance
 	if(!gs->map->isInTheMap(guardPos))
 		return false;
 
-	const TerrainTile &guardTile = gs->map->terrain[guardPos.x][guardPos.y][guardPos.z];
+	const TerrainTile &guardTile = gs->map->getTile(guardPos);
 	objectVisited(guardTile.visitableObjects.back(), h);
 	visitObjectAfterVictory = true;
 	return true;