mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	- Refactored CMapEditManager(added structure for undo functionality) - Refactored CMap(terrain pointer is private, safe access via getTile)
This commit is contained in:
		
							
								
								
									
										5
									
								
								Global.h
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
| }; | ||||
|   | ||||
| @@ -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(); | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| 	{ | ||||
|   | ||||
| @@ -5,321 +5,141 @@ | ||||
| #include "../filesystem/CResourceLoader.h" | ||||
| #include "../CDefObjInfoHandler.h" | ||||
|  | ||||
| CMapEditManager::CMapEditManager(CMap * map, int randomSeed /*= std::time(nullptr)*/) | ||||
| 	: map(map) | ||||
| CMapOperation::CMapOperation(CMap * map) : map(map) | ||||
| { | ||||
| 	gen.seed(randomSeed); | ||||
|  | ||||
| } | ||||
|  | ||||
| void CMapEditManager::clearTerrain() | ||||
| 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) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| 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(ETerrainType terType, int posx, int posy, int width, int height, bool underground) | ||||
| void CMapEditManager::drawTerrain(const MapRect & rect, ETerrainType terType, CRandomGenerator * gen) | ||||
| { | ||||
| 	bool mapLevel = underground ? 1 : 0; | ||||
| 	for(int i = posx; i < posx + width; ++i) | ||||
| 	{ | ||||
| 		for(int j = posy; j < posy + height; ++j) | ||||
| 		{ | ||||
| 			map->terrain[i][j][mapLevel].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); | ||||
| 	execute(make_unique<DrawTerrainOperation>(map, rect, terType, gen)); | ||||
| } | ||||
|  | ||||
| void CMapEditManager::updateTerrainViews(int posx, int posy, int width, int height, int mapLevel) | ||||
| void CMapEditManager::insertObject(const int3 & pos, CGObjectInstance * obj) | ||||
| { | ||||
| 	for(int i = posx; i < posx + width; ++i) | ||||
| 	{ | ||||
| 		for(int j = posy; j < posy + height; ++j) | ||||
| 		{ | ||||
| 			const std::vector<TerrainViewPattern> & patterns = | ||||
| 					CTerrainViewPatternConfig::get().getPatternsForGroup(getTerrainGroup(map->terrain[i][j][mapLevel].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]; | ||||
|  | ||||
| 				for(int flip = 0; flip < 4; ++flip) | ||||
| 				{ | ||||
| 					ValidationResult valRslt = validateTerrainView(i, j, mapLevel, 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; | ||||
|  | ||||
| 						bestPattern = k; | ||||
| 						bestFlip = flip; | ||||
| 						transitionReplacement = valRslt.transitionReplacement; | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			if(bestPattern == -1) | ||||
| 			{ | ||||
| 				// This shouldn't be the case | ||||
|                 logGlobal->warnStream() << "No pattern detected at pos " << i << "x" << j << "x" << mapLevel; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			// Get mapping | ||||
| 			const TerrainViewPattern & pattern = patterns[bestPattern]; | ||||
| 			std::pair<int, int> mapping; | ||||
| 			if(transitionReplacement.empty()) | ||||
| 			{ | ||||
| 				mapping = pattern.mapping[0]; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				mapping = transitionReplacement == TerrainViewPattern::RULE_DIRT ? pattern.mapping[0] : pattern.mapping[1]; | ||||
| 			} | ||||
|  | ||||
| 			// Set terrain view | ||||
| 			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; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				int range = (mapping.second - mapping.first) / 4; | ||||
| 				map->terrain[i][j][mapLevel].terView = gen.getInteger(mapping.first + bestFlip * range, | ||||
| 					mapping.first + (bestFlip + 1) * range - 1); | ||||
| 				map->terrain[i][j][mapLevel].extTileFlags =	0; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	execute(make_unique<InsertObjectOperation>(map, pos, obj)); | ||||
| } | ||||
|  | ||||
| ETerrainGroup::ETerrainGroup CMapEditManager::getTerrainGroup(ETerrainType terType) const | ||||
| void CMapEditManager::execute(unique_ptr<CMapOperation> && operation) | ||||
| { | ||||
| 	switch(terType) | ||||
| 	{ | ||||
| 	case ETerrainType::DIRT: | ||||
| 		return ETerrainGroup::DIRT; | ||||
| 	case ETerrainType::SAND: | ||||
| 		return ETerrainGroup::SAND; | ||||
| 	case ETerrainType::WATER: | ||||
| 		return ETerrainGroup::WATER; | ||||
| 	case ETerrainType::ROCK: | ||||
| 		return ETerrainGroup::ROCK; | ||||
| 	default: | ||||
| 		return ETerrainGroup::NORMAL; | ||||
| 	} | ||||
| 	operation->execute(); | ||||
| 	undoManager.addOperation(std::move(operation)); | ||||
| } | ||||
|  | ||||
| CMapEditManager::ValidationResult CMapEditManager::validateTerrainView(int posx, int posy, int mapLevel, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const | ||||
| void CMapEditManager::undo() | ||||
| { | ||||
| 	ETerrainType centerTerType = map->terrain[posx][posy][mapLevel].terType; | ||||
| 	int totalPoints = 0; | ||||
| 	std::string transitionReplacement; | ||||
|  | ||||
| 	for(int i = 0; i < 9; ++i) | ||||
| 	{ | ||||
| 		// The center, middle cell can be skipped | ||||
| 		if(i == 4) | ||||
| 		{ | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		// Get terrain group of the current cell | ||||
| 		int cx = posx + (i % 3) - 1; | ||||
| 		int cy = posy + (i / 3) - 1; | ||||
| 		bool isAlien = false; | ||||
| 		ETerrainType terType; | ||||
| 		if(cx < 0 || cx >= map->width || cy < 0 || cy >= map->height) | ||||
| 		{ | ||||
| 			terType = centerTerType; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			terType = map->terrain[cx][cy][mapLevel].terType; | ||||
| 			if(terType != centerTerType) | ||||
| 			{ | ||||
| 				isAlien = true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Validate all rules per cell | ||||
| 		int topPoints = -1; | ||||
| 		for(int j = 0; j < pattern.data[i].size(); ++j) | ||||
| 		{ | ||||
| 			TerrainViewPattern::WeightedRule rule = pattern.data[i][j]; | ||||
| 			if(!rule.isStandardRule()) | ||||
| 			{ | ||||
| 				if(recDepth == 0) | ||||
| 				{ | ||||
| 					const TerrainViewPattern & patternForRule = CTerrainViewPatternConfig::get().getPatternById(pattern.terGroup, rule.name); | ||||
| 					ValidationResult rslt = validateTerrainView(cx, cy, mapLevel, patternForRule, 1); | ||||
| 					if(!rslt.result) | ||||
| 					{ | ||||
| 						return ValidationResult(false); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						topPoints = std::max(topPoints, rule.points); | ||||
| 						continue; | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					rule.name = TerrainViewPattern::RULE_NATIVE; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			bool nativeTestOk = (rule.name == TerrainViewPattern::RULE_NATIVE || rule.name == TerrainViewPattern::RULE_ANY) && !isAlien; | ||||
| 			auto applyValidationRslt = [&](bool rslt) | ||||
| 			{ | ||||
| 				if(rslt) | ||||
| 				{ | ||||
| 					topPoints = std::max(topPoints, rule.points); | ||||
| 				} | ||||
| 			}; | ||||
|  | ||||
| 			// Validate cell with the ruleset of the pattern | ||||
| 			if(pattern.terGroup == ETerrainGroup::NORMAL) | ||||
| 			{ | ||||
| 				bool dirtTestOk = (rule.name == TerrainViewPattern::RULE_DIRT | ||||
| 						|| rule.name == TerrainViewPattern::RULE_TRANSITION || rule.name == TerrainViewPattern::RULE_ANY) | ||||
| 						&& isAlien && !isSandType(terType); | ||||
| 				bool sandTestOk = (rule.name == TerrainViewPattern::RULE_SAND || rule.name == TerrainViewPattern::RULE_TRANSITION | ||||
| 						|| rule.name == TerrainViewPattern::RULE_ANY) | ||||
| 						&& isSandType(terType); | ||||
|  | ||||
| 				if(transitionReplacement.empty() && (rule.name == TerrainViewPattern::RULE_TRANSITION | ||||
| 					|| rule.name == TerrainViewPattern::RULE_ANY) && (dirtTestOk || sandTestOk)) | ||||
| 				{ | ||||
| 					transitionReplacement = dirtTestOk ? TerrainViewPattern::RULE_DIRT : TerrainViewPattern::RULE_SAND; | ||||
| 				} | ||||
| 				applyValidationRslt((dirtTestOk && transitionReplacement != TerrainViewPattern::RULE_SAND) | ||||
| 						|| (sandTestOk && transitionReplacement != TerrainViewPattern::RULE_DIRT) | ||||
| 						|| nativeTestOk); | ||||
| 			} | ||||
| 			else if(pattern.terGroup == ETerrainGroup::DIRT) | ||||
| 			{ | ||||
| 				bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && isSandType(terType); | ||||
| 				bool dirtTestOk = rule.name == TerrainViewPattern::RULE_DIRT && !isSandType(terType) && !nativeTestOk; | ||||
| 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || dirtTestOk || nativeTestOk); | ||||
| 			} | ||||
| 			else if(pattern.terGroup == ETerrainGroup::SAND) | ||||
| 			{ | ||||
| 				bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && isAlien; | ||||
| 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); | ||||
| 			} | ||||
| 			else if(pattern.terGroup == ETerrainGroup::WATER) | ||||
| 			{ | ||||
| 				bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && terType != ETerrainType::DIRT | ||||
| 						&& terType != ETerrainType::WATER; | ||||
| 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); | ||||
| 			} | ||||
| 			else if(pattern.terGroup == ETerrainGroup::ROCK) | ||||
| 			{ | ||||
| 				bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && terType != ETerrainType::DIRT | ||||
| 						&& terType != ETerrainType::ROCK; | ||||
| 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(topPoints == -1) | ||||
| 		{ | ||||
| 			return ValidationResult(false); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			totalPoints += topPoints; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(pattern.minPoints > totalPoints) | ||||
| 	{ | ||||
| 		return ValidationResult(false); | ||||
| 	} | ||||
|  | ||||
| 	return ValidationResult(true, transitionReplacement); | ||||
| 	undoManager.undo(); | ||||
| } | ||||
|  | ||||
| bool CMapEditManager::isSandType(ETerrainType terType) const | ||||
| void CMapEditManager::redo() | ||||
| { | ||||
| 	switch(terType) | ||||
| 	{ | ||||
| 	case ETerrainType::WATER: | ||||
| 	case ETerrainType::SAND: | ||||
| 	case ETerrainType::ROCK: | ||||
| 		return true; | ||||
| 	default: | ||||
| 		return false; | ||||
| 	} | ||||
| 	undoManager.redo(); | ||||
| } | ||||
|  | ||||
| TerrainViewPattern CMapEditManager::getFlippedPattern(const TerrainViewPattern & pattern, int flip) const | ||||
| CMapUndoManager & CMapEditManager::getUndoManager() | ||||
| { | ||||
| 	if(flip == 0) | ||||
| 	{ | ||||
| 		return pattern; | ||||
| 	} | ||||
|  | ||||
| 	TerrainViewPattern ret = pattern; | ||||
| 	if(flip == FLIP_PATTERN_HORIZONTAL || flip == FLIP_PATTERN_BOTH) | ||||
| 	{ | ||||
| 		for(int i = 0; i < 3; ++i) | ||||
| 		{ | ||||
| 			int y = i * 3; | ||||
| 			std::swap(ret.data[y], ret.data[y + 2]); | ||||
| 		} | ||||
| 	} | ||||
| 	if(flip == FLIP_PATTERN_VERTICAL || flip == FLIP_PATTERN_BOTH) | ||||
| 	{ | ||||
| 		for(int i = 0; i < 3; ++i) | ||||
| 		{ | ||||
| 			std::swap(ret.data[i], ret.data[6 + i]); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	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 /*= ""*/) | ||||
| 	: result(result), transitionReplacement(transitionReplacement) | ||||
| { | ||||
|  | ||||
| 	return undoManager; | ||||
| } | ||||
|  | ||||
| const std::string TerrainViewPattern::FLIP_MODE_SAME_IMAGE = "sameImage"; | ||||
| @@ -446,3 +266,338 @@ const TerrainViewPattern & CTerrainViewPatternConfig::getPatternById(ETerrainGro | ||||
| 	} | ||||
| 	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() | ||||
| { | ||||
| 	for(int i = rect.pos.x; i < rect.pos.x + rect.width; ++i) | ||||
| 	{ | ||||
| 		for(int j = rect.pos.y; j < rect.pos.y + rect.height; ++j) | ||||
| 		{ | ||||
| 			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(MapRect(int3(rect.pos.x - 1, rect.pos.y - 1, rect.pos.z), rect.width + 2, rect.height + 2)); | ||||
| } | ||||
|  | ||||
| void DrawTerrainOperation::undo() | ||||
| { | ||||
| 	//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 = rect.pos.y; j < rect.pos.y + rect.height; ++j) | ||||
| 		{ | ||||
| 			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 auto & pattern = patterns[k]; | ||||
|  | ||||
| 				for(int flip = 0; flip < 4; ++flip) | ||||
| 				{ | ||||
| 					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" << rect.pos.z << ": P-Nr. " << k | ||||
| 							  << ", Flip " << flip << ", Repl. " << valRslt.transitionReplacement; | ||||
|  | ||||
| 						bestPattern = k; | ||||
| 						bestFlip = flip; | ||||
| 						transitionReplacement = valRslt.transitionReplacement; | ||||
| 						break; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			if(bestPattern == -1) | ||||
| 			{ | ||||
| 				// This shouldn't be the case | ||||
| 				logGlobal->warnStream() << "No pattern detected at pos " << i << "x" << j << "x" << rect.pos.z; | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			// Get mapping | ||||
| 			const TerrainViewPattern & pattern = patterns[bestPattern]; | ||||
| 			std::pair<int, int> mapping; | ||||
| 			if(transitionReplacement.empty()) | ||||
| 			{ | ||||
| 				mapping = pattern.mapping[0]; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				mapping = transitionReplacement == TerrainViewPattern::RULE_DIRT ? pattern.mapping[0] : pattern.mapping[1]; | ||||
| 			} | ||||
|  | ||||
| 			// Set terrain view | ||||
| 			auto & tile = map->getTile(int3(i, j, rect.pos.z)); | ||||
| 			if(pattern.flipMode == TerrainViewPattern::FLIP_MODE_SAME_IMAGE) | ||||
| 			{ | ||||
| 				tile.terView = gen->getInteger(mapping.first, mapping.second); | ||||
| 				tile.extTileFlags = bestFlip; | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				int range = (mapping.second - mapping.first) / 4; | ||||
| 				tile.terView = gen->getInteger(mapping.first + bestFlip * range, | ||||
| 					mapping.first + (bestFlip + 1) * range - 1); | ||||
| 				tile.extTileFlags =	0; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| ETerrainGroup::ETerrainGroup DrawTerrainOperation::getTerrainGroup(ETerrainType terType) const | ||||
| { | ||||
| 	switch(terType) | ||||
| 	{ | ||||
| 	case ETerrainType::DIRT: | ||||
| 		return ETerrainGroup::DIRT; | ||||
| 	case ETerrainType::SAND: | ||||
| 		return ETerrainGroup::SAND; | ||||
| 	case ETerrainType::WATER: | ||||
| 		return ETerrainGroup::WATER; | ||||
| 	case ETerrainType::ROCK: | ||||
| 		return ETerrainGroup::ROCK; | ||||
| 	default: | ||||
| 		return ETerrainGroup::NORMAL; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| DrawTerrainOperation::ValidationResult DrawTerrainOperation::validateTerrainView(const int3 & pos, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const | ||||
| { | ||||
| 	ETerrainType centerTerType = map->getTile(pos).terType; | ||||
| 	int totalPoints = 0; | ||||
| 	std::string transitionReplacement; | ||||
|  | ||||
| 	for(int i = 0; i < 9; ++i) | ||||
| 	{ | ||||
| 		// The center, middle cell can be skipped | ||||
| 		if(i == 4) | ||||
| 		{ | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		// Get terrain group of the current cell | ||||
| 		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) | ||||
| 		{ | ||||
| 			terType = centerTerType; | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			terType = map->getTile(int3(cx, cy, pos.z)).terType; | ||||
| 			if(terType != centerTerType) | ||||
| 			{ | ||||
| 				isAlien = true; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Validate all rules per cell | ||||
| 		int topPoints = -1; | ||||
| 		for(int j = 0; j < pattern.data[i].size(); ++j) | ||||
| 		{ | ||||
| 			TerrainViewPattern::WeightedRule rule = pattern.data[i][j]; | ||||
| 			if(!rule.isStandardRule()) | ||||
| 			{ | ||||
| 				if(recDepth == 0) | ||||
| 				{ | ||||
| 					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); | ||||
| 					} | ||||
| 					else | ||||
| 					{ | ||||
| 						topPoints = std::max(topPoints, rule.points); | ||||
| 						continue; | ||||
| 					} | ||||
| 				} | ||||
| 				else | ||||
| 				{ | ||||
| 					rule.name = TerrainViewPattern::RULE_NATIVE; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			bool nativeTestOk = (rule.name == TerrainViewPattern::RULE_NATIVE || rule.name == TerrainViewPattern::RULE_ANY) && !isAlien; | ||||
| 			auto applyValidationRslt = [&](bool rslt) | ||||
| 			{ | ||||
| 				if(rslt) | ||||
| 				{ | ||||
| 					topPoints = std::max(topPoints, rule.points); | ||||
| 				} | ||||
| 			}; | ||||
|  | ||||
| 			// Validate cell with the ruleset of the pattern | ||||
| 			if(pattern.terGroup == ETerrainGroup::NORMAL) | ||||
| 			{ | ||||
| 				bool dirtTestOk = (rule.name == TerrainViewPattern::RULE_DIRT | ||||
| 						|| rule.name == TerrainViewPattern::RULE_TRANSITION || rule.name == TerrainViewPattern::RULE_ANY) | ||||
| 						&& isAlien && !isSandType(terType); | ||||
| 				bool sandTestOk = (rule.name == TerrainViewPattern::RULE_SAND || rule.name == TerrainViewPattern::RULE_TRANSITION | ||||
| 						|| rule.name == TerrainViewPattern::RULE_ANY) | ||||
| 						&& isSandType(terType); | ||||
|  | ||||
| 				if(transitionReplacement.empty() && (rule.name == TerrainViewPattern::RULE_TRANSITION | ||||
| 					|| rule.name == TerrainViewPattern::RULE_ANY) && (dirtTestOk || sandTestOk)) | ||||
| 				{ | ||||
| 					transitionReplacement = dirtTestOk ? TerrainViewPattern::RULE_DIRT : TerrainViewPattern::RULE_SAND; | ||||
| 				} | ||||
| 				applyValidationRslt((dirtTestOk && transitionReplacement != TerrainViewPattern::RULE_SAND) | ||||
| 						|| (sandTestOk && transitionReplacement != TerrainViewPattern::RULE_DIRT) | ||||
| 						|| nativeTestOk); | ||||
| 			} | ||||
| 			else if(pattern.terGroup == ETerrainGroup::DIRT) | ||||
| 			{ | ||||
| 				bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && isSandType(terType); | ||||
| 				bool dirtTestOk = rule.name == TerrainViewPattern::RULE_DIRT && !isSandType(terType) && !nativeTestOk; | ||||
| 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || dirtTestOk || nativeTestOk); | ||||
| 			} | ||||
| 			else if(pattern.terGroup == ETerrainGroup::SAND) | ||||
| 			{ | ||||
| 				bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && isAlien; | ||||
| 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); | ||||
| 			} | ||||
| 			else if(pattern.terGroup == ETerrainGroup::WATER) | ||||
| 			{ | ||||
| 				bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && terType != ETerrainType::DIRT | ||||
| 						&& terType != ETerrainType::WATER; | ||||
| 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); | ||||
| 			} | ||||
| 			else if(pattern.terGroup == ETerrainGroup::ROCK) | ||||
| 			{ | ||||
| 				bool sandTestOk = rule.name == TerrainViewPattern::RULE_SAND && terType != ETerrainType::DIRT | ||||
| 						&& terType != ETerrainType::ROCK; | ||||
| 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if(topPoints == -1) | ||||
| 		{ | ||||
| 			return ValidationResult(false); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			totalPoints += topPoints; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if(pattern.minPoints > totalPoints) | ||||
| 	{ | ||||
| 		return ValidationResult(false); | ||||
| 	} | ||||
|  | ||||
| 	return ValidationResult(true, transitionReplacement); | ||||
| } | ||||
|  | ||||
| bool DrawTerrainOperation::isSandType(ETerrainType terType) const | ||||
| { | ||||
| 	switch(terType) | ||||
| 	{ | ||||
| 	case ETerrainType::WATER: | ||||
| 	case ETerrainType::SAND: | ||||
| 	case ETerrainType::ROCK: | ||||
| 		return true; | ||||
| 	default: | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TerrainViewPattern DrawTerrainOperation::getFlippedPattern(const TerrainViewPattern & pattern, int flip) const | ||||
| { | ||||
| 	if(flip == 0) | ||||
| 	{ | ||||
| 		return pattern; | ||||
| 	} | ||||
|  | ||||
| 	TerrainViewPattern ret = pattern; | ||||
| 	if(flip == FLIP_PATTERN_HORIZONTAL || flip == FLIP_PATTERN_BOTH) | ||||
| 	{ | ||||
| 		for(int i = 0; i < 3; ++i) | ||||
| 		{ | ||||
| 			int y = i * 3; | ||||
| 			std::swap(ret.data[y], ret.data[y + 2]); | ||||
| 		} | ||||
| 	} | ||||
| 	if(flip == FLIP_PATTERN_VERTICAL || flip == FLIP_PATTERN_BOTH) | ||||
| 	{ | ||||
| 		for(int i = 0; i < 3; ++i) | ||||
| 		{ | ||||
| 			std::swap(ret.data[i], ret.data[6 + i]); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| DrawTerrainOperation::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement /*= ""*/) | ||||
| 	: result(result), transitionReplacement(transitionReplacement) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| InsertObjectOperation::InsertObjectOperation(CMap * map, const int3 & pos, CGObjectInstance * obj) | ||||
| 	: CMapOperation(map), pos(pos), obj(obj) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| void InsertObjectOperation::execute() | ||||
| { | ||||
| 	obj->pos = pos; | ||||
| 	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); | ||||
| } | ||||
|  | ||||
| void InsertObjectOperation::undo() | ||||
| { | ||||
| 	//TODO | ||||
| } | ||||
|  | ||||
| void InsertObjectOperation::redo() | ||||
| { | ||||
| 	execute(); | ||||
| } | ||||
|  | ||||
| std::string InsertObjectOperation::getLabel() const | ||||
| { | ||||
| 	return "Insert Object"; | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
| }; | ||||
|   | ||||
| @@ -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; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -186,5 +186,5 @@ private: | ||||
| 	std::unique_ptr<CMap> map; | ||||
| 	CRandomGenerator gen; | ||||
| 	int randomSeed; | ||||
| 	std::unique_ptr<CMapEditManager> mapMgr; | ||||
| 	CMapEditManager * editManager; | ||||
| }; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user