| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071 | #include "StdInc.h"#include "CMapEditManager.h"#include "../JsonNode.h"#include "../filesystem/Filesystem.h"#include "../mapObjects/CObjectClassesHandler.h"#include "../mapObjects/CGHeroInstance.h"#include "../VCMI_Lib.h"#include "CDrawRoadsOperation.h"#include "../mapping/CMap.h"MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0){}MapRect::MapRect(int3 pos, si32 width, si32 height) : x(pos.x), y(pos.y), z(pos.z), width(width), height(height){}MapRect MapRect::operator&(const MapRect & rect) const{	bool intersect = right() > rect.left() && rect.right() > left() &&			bottom() > rect.top() && rect.bottom() > top() &&			z == rect.z;	if(intersect)	{		MapRect ret;		ret.x = std::max(left(), rect.left());		ret.y = std::max(top(), rect.top());		ret.z = rect.z;		ret.width = std::min(right(), rect.right()) - ret.x;		ret.height = std::min(bottom(), rect.bottom()) - ret.y;		return ret;	}	else	{		return MapRect();	}}si32 MapRect::left() const{	return x;}si32 MapRect::right() const{	return x + width;}si32 MapRect::top() const{	return y;}si32 MapRect::bottom() const{	return y + height;}int3 MapRect::topLeft() const{	return int3(x, y, z);}int3 MapRect::topRight() const{	return int3(right(), y, z);}int3 MapRect::bottomLeft() const{	return int3(x, bottom(), z);}int3 MapRect::bottomRight() const{	return int3(right(), bottom(), z);}CTerrainSelection::CTerrainSelection(CMap * map) : CMapSelection(map){}void CTerrainSelection::selectRange(const MapRect & rect){	rect.forEach([this](const int3 pos)	{		this->select(pos);	});}void CTerrainSelection::deselectRange(const MapRect & rect){	rect.forEach([this](const int3 pos)	{		this->deselect(pos);	});}void CTerrainSelection::setSelection(std::vector<int3> & vec){	for (auto pos : vec)		this->select(pos);}void CTerrainSelection::selectAll(){	selectRange(MapRect(int3(0, 0, 0), getMap()->width, getMap()->height));	selectRange(MapRect(int3(0, 0, 1), getMap()->width, getMap()->height));}void CTerrainSelection::clearSelection(){	deselectRange(MapRect(int3(0, 0, 0), getMap()->width, getMap()->height));	deselectRange(MapRect(int3(0, 0, 1), getMap()->width, getMap()->height));}CObjectSelection::CObjectSelection(CMap * map) : CMapSelection(map){}CMapOperation::CMapOperation(CMap * map) : map(map){}std::string CMapOperation::getLabel() const{	return "";}MapRect CMapOperation::extendTileAround(const int3 & centerPos) const{	return MapRect(int3(centerPos.x - 1, centerPos.y - 1, centerPos.z), 3, 3);}MapRect CMapOperation::extendTileAroundSafely(const int3 & centerPos) const{	return extendTileAround(centerPos) & MapRect(int3(0, 0, centerPos.z), map->width, map->height);}CMapUndoManager::CMapUndoManager() : undoRedoLimit(10){}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){	assert(value >= 0);	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(std::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), terrainSel(map), objectSel(map){}CMap * CMapEditManager::getMap(){	return map;}void CMapEditManager::clearTerrain(CRandomGenerator * gen/* = nullptr*/){	execute(make_unique<CClearTerrainOperation>(map, gen ? gen : &(this->gen)));}void CMapEditManager::drawTerrain(ETerrainType terType, CRandomGenerator * gen/* = nullptr*/){	execute(make_unique<CDrawTerrainOperation>(map, terrainSel, terType, gen ? gen : &(this->gen)));	terrainSel.clearSelection();}void CMapEditManager::drawRoad(ERoadType::ERoadType roadType, CRandomGenerator* gen){	execute(make_unique<CDrawRoadsOperation>(map, terrainSel, roadType, gen ? gen : &(this->gen)));	terrainSel.clearSelection();}void CMapEditManager::insertObject(CGObjectInstance * obj, const int3 & pos){	execute(make_unique<CInsertObjectOperation>(map, obj, pos));}void CMapEditManager::execute(std::unique_ptr<CMapOperation> && operation){	operation->execute();	undoManager.addOperation(std::move(operation));}CTerrainSelection & CMapEditManager::getTerrainSelection(){	return terrainSel;}CObjectSelection & CMapEditManager::getObjectSelection(){	return objectSel;}CMapUndoManager & CMapEditManager::getUndoManager(){	return undoManager;}CComposedOperation::CComposedOperation(CMap * map) : CMapOperation(map){}void CComposedOperation::execute(){	for(auto & operation : operations)	{		operation->execute();	}}void CComposedOperation::undo(){	for(auto & operation : operations)	{		operation->undo();	}}void CComposedOperation::redo(){	for(auto & operation : operations)	{		operation->redo();	}}void CComposedOperation::addOperation(std::unique_ptr<CMapOperation> && operation){	operations.push_back(std::move(operation));}const std::string TerrainViewPattern::FLIP_MODE_DIFF_IMAGES = "D";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_NATIVE_STRONG = "N!";const std::string TerrainViewPattern::RULE_ANY = "?";TerrainViewPattern::TerrainViewPattern() : diffImages(false), rotationTypesCount(0), minPoints(0){	maxPoints = std::numeric_limits<int>::max();}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 || TerrainViewPattern::RULE_NATIVE_STRONG == name;}CTerrainViewPatternConfig::CTerrainViewPatternConfig(){	const JsonNode config(ResourceID("config/terrainViewPatterns.json"));	static const std::string patternTypes[] = { "terrainView", "terrainType" };	for(int i = 0; i < ARRAY_COUNT(patternTypes); ++i)	{		const auto & patternsVec = config[patternTypes[i]].Vector();		for(const auto & ptrnNode : patternsVec)		{			TerrainViewPattern pattern;			// Read pattern data			const JsonVector & data = ptrnNode["data"].Vector();			assert(data.size() == 9);			for(int j = 0; j < data.size(); ++j)			{				std::string cell = data[j].String();				boost::algorithm::erase_all(cell, " ");				std::vector<std::string> rules;				boost::split(rules, cell, boost::is_any_of(","));				for(std::string ruleStr : rules)				{					std::vector<std::string> ruleParts;					boost::split(ruleParts, ruleStr, boost::is_any_of("-"));					TerrainViewPattern::WeightedRule rule;					rule.name = ruleParts[0];					assert(!rule.name.empty());					if(ruleParts.size() > 1)					{						rule.points = boost::lexical_cast<int>(ruleParts[1]);					}					pattern.data[j].push_back(rule);				}			}			// Read various properties			pattern.id = ptrnNode["id"].String();			assert(!pattern.id.empty());			pattern.minPoints = static_cast<int>(ptrnNode["minPoints"].Float());			pattern.maxPoints = static_cast<int>(ptrnNode["maxPoints"].Float());			if(pattern.maxPoints == 0) pattern.maxPoints = std::numeric_limits<int>::max();			// Read mapping			if(i == 0)			{				const auto & mappingStruct = ptrnNode["mapping"].Struct();				for(const auto & mappingPair : mappingStruct)				{					TerrainViewPattern terGroupPattern = pattern;					auto mappingStr = mappingPair.second.String();					boost::algorithm::erase_all(mappingStr, " ");					auto colonIndex = mappingStr.find_first_of(":");					const auto & flipMode = mappingStr.substr(0, colonIndex);					terGroupPattern.diffImages = TerrainViewPattern::FLIP_MODE_DIFF_IMAGES == &(flipMode[flipMode.length() - 1]);					if(terGroupPattern.diffImages)					{						terGroupPattern.rotationTypesCount = boost::lexical_cast<int>(flipMode.substr(0, flipMode.length() - 1));						assert(terGroupPattern.rotationTypesCount == 2 || terGroupPattern.rotationTypesCount == 4);					}					mappingStr = mappingStr.substr(colonIndex + 1);					std::vector<std::string> mappings;					boost::split(mappings, mappingStr, boost::is_any_of(","));					for(std::string mapping : mappings)					{						std::vector<std::string> range;						boost::split(range, mapping, boost::is_any_of("-"));						terGroupPattern.mapping.push_back(std::make_pair(boost::lexical_cast<int>(range[0]),							boost::lexical_cast<int>(range.size() > 1 ? range[1] : range[0])));					}					// Add pattern to the patterns map					const auto & terGroup = getTerrainGroup(mappingPair.first);					terrainViewPatterns[terGroup].push_back(terGroupPattern);				}			}			else if(i == 1)			{				terrainTypePatterns[pattern.id] = pattern;			}		}	}}CTerrainViewPatternConfig::~CTerrainViewPatternConfig(){}ETerrainGroup::ETerrainGroup CTerrainViewPatternConfig::getTerrainGroup(const std::string & terGroup) const{	static const std::map<std::string, ETerrainGroup::ETerrainGroup> terGroups =	{		{"normal", ETerrainGroup::NORMAL},		{"dirt", ETerrainGroup::DIRT},		{"sand", ETerrainGroup::SAND},		{"water", ETerrainGroup::WATER},		{"rock", ETerrainGroup::ROCK},	};	auto it = terGroups.find(terGroup);	if(it == terGroups.end()) throw std::runtime_error(boost::str(boost::format("Terrain group '%s' does not exist.") % terGroup));	return it->second;}const std::vector<TerrainViewPattern> & CTerrainViewPatternConfig::getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const{	return terrainViewPatterns.find(terGroup)->second;}boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getTerrainViewPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const{	const std::vector<TerrainViewPattern> & groupPatterns = getTerrainViewPatternsForGroup(terGroup);	for(const TerrainViewPattern & pattern : groupPatterns)	{		if(id == pattern.id)		{			return boost::optional<const TerrainViewPattern &>(pattern);		}	}	return boost::optional<const TerrainViewPattern &>();}const TerrainViewPattern & CTerrainViewPatternConfig::getTerrainTypePatternById(const std::string & id) const{	auto it = terrainTypePatterns.find(id);	assert(it != terrainTypePatterns.end());	return it->second;}CDrawTerrainOperation::CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, ETerrainType terType, CRandomGenerator * gen)	: CMapOperation(map), terrainSel(terrainSel), terType(terType), gen(gen){}void CDrawTerrainOperation::execute(){	for(const auto & pos : terrainSel.getSelectedItems())	{		auto & tile = map->getTile(pos);		tile.terType = terType;		invalidateTerrainViews(pos);	}	updateTerrainTypes();	updateTerrainViews();}void CDrawTerrainOperation::undo(){	//TODO}void CDrawTerrainOperation::redo(){	//TODO}std::string CDrawTerrainOperation::getLabel() const{	return "Draw Terrain";}void CDrawTerrainOperation::updateTerrainTypes(){	auto positions = terrainSel.getSelectedItems();	while(!positions.empty())	{		const auto & centerPos = *(positions.begin());		auto centerTile = map->getTile(centerPos);		//logGlobal->debugStream() << boost::format("Set terrain tile at pos '%s' to type '%s'") % centerPos % centerTile.terType;		auto tiles = getInvalidTiles(centerPos);		auto updateTerrainType = [&](const int3 & pos)		{			map->getTile(pos).terType = centerTile.terType;			positions.insert(pos);			invalidateTerrainViews(pos);			//logGlobal->debugStream() << boost::format("Set additional terrain tile at pos '%s' to type '%s'") % pos % centerTile.terType;		};		// Fill foreign invalid tiles		for(const auto & tile : tiles.foreignTiles)		{			updateTerrainType(tile);		}		tiles = getInvalidTiles(centerPos);		if(tiles.nativeTiles.find(centerPos) != tiles.nativeTiles.end())		{			// Blow up			auto rect = extendTileAroundSafely(centerPos);			std::set<int3> suitableTiles;			int invalidForeignTilesCnt = std::numeric_limits<int>::max(), invalidNativeTilesCnt = 0;			bool centerPosValid = false;			rect.forEach([&](const int3 & posToTest)			{				auto & terrainTile = map->getTile(posToTest);				if(centerTile.terType != terrainTile.terType)				{					auto formerTerType = terrainTile.terType;					terrainTile.terType = centerTile.terType;					auto testTile = getInvalidTiles(posToTest);					int nativeTilesCntNorm = testTile.nativeTiles.empty() ? std::numeric_limits<int>::max() : testTile.nativeTiles.size();					bool putSuitableTile = false;					bool addToSuitableTiles = false;					if(testTile.centerPosValid)					{						if (!centerPosValid)						{							centerPosValid = true;							putSuitableTile = true;						}						else						{							if(testTile.foreignTiles.size() < invalidForeignTilesCnt)							{								putSuitableTile = true;							}							else							{								addToSuitableTiles = true;							}						}					}					else if (!centerPosValid)					{						if((nativeTilesCntNorm > invalidNativeTilesCnt) ||								(nativeTilesCntNorm == invalidNativeTilesCnt && testTile.foreignTiles.size() < invalidForeignTilesCnt))						{							putSuitableTile = true;						}						else if(nativeTilesCntNorm == invalidNativeTilesCnt && testTile.foreignTiles.size() == invalidForeignTilesCnt)						{							addToSuitableTiles = true;						}					}					if (putSuitableTile)					{						//if(!suitableTiles.empty())						//{						//	logGlobal->debugStream() << "Clear suitables tiles.";						//}						invalidNativeTilesCnt = nativeTilesCntNorm;						invalidForeignTilesCnt = testTile.foreignTiles.size();						suitableTiles.clear();						addToSuitableTiles = true;					}					if (addToSuitableTiles)					{						suitableTiles.insert(posToTest);						//logGlobal->debugStream() << boost::format(std::string("Found suitable tile '%s' for main tile '%s': ") +						//		"Invalid native tiles '%i', invalid foreign tiles '%i', centerPosValid '%i'") % posToTest % centerPos % testTile.nativeTiles.size() %						//		testTile.foreignTiles.size() % testTile.centerPosValid;					}					terrainTile.terType = formerTerType;				}			});			if(suitableTiles.size() == 1)			{				updateTerrainType(*suitableTiles.begin());			}			else			{				static const int3 directions[] = { int3(0, -1, 0), int3(-1, 0, 0), int3(0, 1, 0), int3(1, 0, 0),											int3(-1, -1, 0), int3(-1, 1, 0), int3(1, 1, 0), int3(1, -1, 0)};				for(auto & direction : directions)				{					auto it = suitableTiles.find(centerPos + direction);					if(it != suitableTiles.end())					{						updateTerrainType(*it);						break;					}				}			}		}		else		{			// add invalid native tiles which are not in the positions list			for(const auto & nativeTile : tiles.nativeTiles)			{				if(positions.find(nativeTile) == positions.end())				{					positions.insert(nativeTile);				}			}			positions.erase(centerPos);		}	}}void CDrawTerrainOperation::updateTerrainViews(){	for(const auto & pos : invalidatedTerViews)	{		const auto & patterns =				VLC->terviewh->getTerrainViewPatternsForGroup(getTerrainGroup(map->getTile(pos).terType));		// Detect a pattern which fits best		int bestPattern = -1;		ValidationResult valRslt(false);		for(int k = 0; k < patterns.size(); ++k)		{			const auto & pattern = patterns[k];			valRslt = validateTerrainView(pos, pattern);			if(valRslt.result)			{				/*logGlobal->debugStream() << boost::format("Pattern detected at pos '%s': Pattern '%s', Flip '%i', Repl. '%s'.") %											pos % pattern.id % valRslt.flip % valRslt.transitionReplacement;*/				bestPattern = k;				break;			}		}		//assert(bestPattern != -1);		if(bestPattern == -1)		{			// This shouldn't be the case			logGlobal->warnStream() << boost::format("No pattern detected at pos '%s'.") % pos;			CTerrainViewPatternUtils::printDebuggingInfoAboutTile(map, pos);			continue;		}		// Get mapping		const TerrainViewPattern & pattern = patterns[bestPattern];		std::pair<int, int> mapping;		if(valRslt.transitionReplacement.empty())		{			mapping = pattern.mapping[0];		}		else		{			mapping = valRslt.transitionReplacement == TerrainViewPattern::RULE_DIRT ? pattern.mapping[0] : pattern.mapping[1];		}		// Set terrain view		auto & tile = map->getTile(pos);		if(!pattern.diffImages)		{			tile.terView = gen->nextInt(mapping.first, mapping.second);			tile.extTileFlags = valRslt.flip;		}		else		{			const int framesPerRot = (mapping.second - mapping.first + 1) / pattern.rotationTypesCount;			int flip = (pattern.rotationTypesCount == 2 && valRslt.flip == 2) ? 1 : valRslt.flip;			int firstFrame = mapping.first + flip * framesPerRot;			tile.terView = gen->nextInt(firstFrame, firstFrame + framesPerRot - 1);			tile.extTileFlags =	0;		}	}}ETerrainGroup::ETerrainGroup CDrawTerrainOperation::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;	}}CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainView(const int3 & pos, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const{	//constructor for pattern object is very expensive, but we can't manipulate const object :(	auto flippedPattern = pattern;	for(int flip = 0; flip < 4; ++flip)	{		if (flip > 0)			flipPattern (flippedPattern, flip);		auto valRslt = validateTerrainViewInner(pos, flippedPattern, recDepth);		if(valRslt.result)		{			valRslt.flip = flip;			return valRslt;		}	}	return ValidationResult(false);}CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const{	auto centerTerType = map->getTile(pos).terType;	auto centerTerGroup = getTerrainGroup(centerTerType);	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;		int3 currentPos(cx, cy, pos.z);		bool isAlien = false;		ETerrainType terType;		if(!map->isInTheMap(currentPos))		{			// position is not in the map, so take the ter type from the neighbor tile			bool widthTooHigh = currentPos.x >= map->width;			bool widthTooLess = currentPos.x < 0;			bool heightTooHigh = currentPos.y >= map->height;			bool heightTooLess = currentPos.y < 0;			if ((widthTooHigh && heightTooHigh) || (widthTooHigh && heightTooLess) || (widthTooLess && heightTooHigh) || (widthTooLess && heightTooLess))			{				terType = centerTerType;			}			else if(widthTooHigh)			{				terType = map->getTile(int3(currentPos.x - 1, currentPos.y, currentPos.z)).terType;			}			else if(heightTooHigh)			{				terType = map->getTile(int3(currentPos.x, currentPos.y - 1, currentPos.z)).terType;			}			else if (widthTooLess)			{				terType = map->getTile(int3(currentPos.x + 1, currentPos.y, currentPos.z)).terType;			}			else if (heightTooLess)			{				terType = map->getTile(int3(currentPos.x, currentPos.y + 1, currentPos.z)).terType;			}		}		else		{			terType = map->getTile(currentPos).terType;			if(terType != centerTerType)			{				isAlien = true;			}		}		// Validate all rules per cell		int topPoints = -1;		for(auto & elem : pattern.data[i])		{			TerrainViewPattern::WeightedRule rule = elem;			if(!rule.isStandardRule())			{				if(recDepth == 0 && map->isInTheMap(currentPos))				{					if(terType == centerTerType)					{						const auto & patternForRule = VLC->terviewh->getTerrainViewPatternById(getTerrainGroup(centerTerType), rule.name);						if(patternForRule)						{							auto rslt = validateTerrainView(currentPos, *patternForRule, 1);							if(rslt.result) topPoints = std::max(topPoints, rule.points);						}					}					continue;				}				else				{					rule.name = TerrainViewPattern::RULE_NATIVE;				}			}			auto applyValidationRslt = [&](bool rslt)			{				if(rslt)				{					topPoints = std::max(topPoints, rule.points);				}			};			// Validate cell with the ruleset of the pattern			bool nativeTestOk, nativeTestStrongOk;			nativeTestOk = nativeTestStrongOk = (rule.name == TerrainViewPattern::RULE_NATIVE_STRONG || rule.name == TerrainViewPattern::RULE_NATIVE)  && !isAlien;			if(centerTerGroup == ETerrainGroup::NORMAL)			{				bool dirtTestOk = (rule.name == TerrainViewPattern::RULE_DIRT || rule.name == TerrainViewPattern::RULE_TRANSITION)						&& isAlien && !isSandType(terType);				bool sandTestOk = (rule.name == TerrainViewPattern::RULE_SAND || rule.name == TerrainViewPattern::RULE_TRANSITION)						&& isSandType(terType);				if(transitionReplacement.empty() && rule.name == TerrainViewPattern::RULE_TRANSITION						&& (dirtTestOk || sandTestOk))				{					transitionReplacement = dirtTestOk ? TerrainViewPattern::RULE_DIRT : TerrainViewPattern::RULE_SAND;				}				if(rule.name == TerrainViewPattern::RULE_TRANSITION)				{					applyValidationRslt((dirtTestOk && transitionReplacement != TerrainViewPattern::RULE_SAND) ||							(sandTestOk && transitionReplacement != TerrainViewPattern::RULE_DIRT));				}				else				{					applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || dirtTestOk || sandTestOk || nativeTestOk);				}			}			else if(centerTerGroup == ETerrainGroup::DIRT)			{				nativeTestOk = rule.name == TerrainViewPattern::RULE_NATIVE && !isSandType(terType);				bool sandTestOk = (rule.name == TerrainViewPattern::RULE_SAND || rule.name == TerrainViewPattern::RULE_TRANSITION)						&& isSandType(terType);				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk || nativeTestStrongOk);			}			else if(centerTerGroup == ETerrainGroup::SAND)			{				applyValidationRslt(true);			}			else if(centerTerGroup == ETerrainGroup::WATER || centerTerGroup == ETerrainGroup::ROCK)			{				bool sandTestOk = (rule.name == TerrainViewPattern::RULE_SAND || rule.name == TerrainViewPattern::RULE_TRANSITION)						&& isAlien;				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk);			}		}		if(topPoints == -1)		{			return ValidationResult(false);		}		else		{			totalPoints += topPoints;		}	}	if(totalPoints >= pattern.minPoints && totalPoints <= pattern.maxPoints)	{		return ValidationResult(true, transitionReplacement);	}	else	{		return ValidationResult(false);	}}bool CDrawTerrainOperation::isSandType(ETerrainType terType) const{	switch(terType)	{	case ETerrainType::WATER:	case ETerrainType::SAND:	case ETerrainType::ROCK:		return true;	default:		return false;	}}void CDrawTerrainOperation::flipPattern(TerrainViewPattern & pattern, int flip) const{	//flip in place to avoid expensive constructor. Seriously.	if(flip == 0)	{		return;	}	//always flip horizontal	for(int i = 0; i < 3; ++i)	{		int y = i * 3;		std::swap(pattern.data[y], pattern.data[y + 2]);	}	//flip vertical only at 2nd step	if(flip == FLIP_PATTERN_VERTICAL)	{		for(int i = 0; i < 3; ++i)		{			std::swap(pattern.data[i], pattern.data[6 + i]);		}	}}void CDrawTerrainOperation::invalidateTerrainViews(const int3 & centerPos){	auto rect = extendTileAroundSafely(centerPos);	rect.forEach([&](const int3 & pos)	{		invalidatedTerViews.insert(pos);	});}CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const int3 & centerPos) const{	InvalidTiles tiles;	auto centerTerType = map->getTile(centerPos).terType;	auto rect = extendTileAround(centerPos);	rect.forEach([&](const int3 & pos)	{		if(map->isInTheMap(pos))		{			auto ptrConfig = VLC->terviewh;			auto terType = map->getTile(pos).terType;			auto valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById("n1")).result;			// Special validity check for rock & water			if(valid && (terType == ETerrainType::WATER || terType == ETerrainType::ROCK))			{				static const std::string patternIds[] = { "s1", "s2" };				for(auto & patternId : patternIds)				{					valid = !validateTerrainView(pos, ptrConfig->getTerrainTypePatternById(patternId)).result;					if(!valid) break;				}			}			// Additional validity check for non rock OR water			else if(!valid && (terType != ETerrainType::WATER && terType != ETerrainType::ROCK))			{				static const std::string patternIds[] = { "n2", "n3" };				for(auto & patternId : patternIds)				{					valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById(patternId)).result;					if(valid) break;				}			}			if(!valid)			{				if(terType == centerTerType) tiles.nativeTiles.insert(pos);				else tiles.foreignTiles.insert(pos);			}			else if(centerPos == pos)			{				tiles.centerPosValid = true;			}		}	});	return tiles;}CDrawTerrainOperation::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement /*= ""*/)	: result(result), transitionReplacement(transitionReplacement){}void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap * map, int3 pos){	logGlobal->debugStream() << "Printing detailed info about nearby map tiles of pos '" << pos << "'";	for(int y = pos.y - 2; y <= pos.y + 2; ++y)	{		std::string line;		const int PADDED_LENGTH = 10;		for(int x = pos.x - 2; x <= pos.x + 2; ++x)		{			auto debugPos = int3(x, y, pos.z);			if(map->isInTheMap(debugPos))			{				auto debugTile = map->getTile(debugPos);				std::string terType = debugTile.terType.toString().substr(0, 6);				line += terType;				line.insert(line.end(), PADDED_LENGTH - terType.size(), ' ');			}			else			{				line += "X";				line.insert(line.end(), PADDED_LENGTH - 1, ' ');			}		}		logGlobal->debugStream() << line;	}}CClearTerrainOperation::CClearTerrainOperation(CMap * map, CRandomGenerator * gen) : CComposedOperation(map){	CTerrainSelection terrainSel(map);	terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height));	addOperation(make_unique<CDrawTerrainOperation>(map, terrainSel, ETerrainType::WATER, gen));	if(map->twoLevel)	{		terrainSel.clearSelection();		terrainSel.selectRange(MapRect(int3(0, 0, 1), map->width, map->height));		addOperation(make_unique<CDrawTerrainOperation>(map, terrainSel, ETerrainType::ROCK, gen));	}}std::string CClearTerrainOperation::getLabel() const{	return "Clear Terrain";}CInsertObjectOperation::CInsertObjectOperation(CMap * map, CGObjectInstance * obj, const int3 & pos)	: CMapOperation(map), pos(pos), obj(obj){}void CInsertObjectOperation::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->heroesOnMap.push_back(static_cast<CGHeroInstance*>(obj));	}	map->addBlockVisTiles(obj);}void CInsertObjectOperation::undo(){	//TODO}void CInsertObjectOperation::redo(){	execute();}std::string CInsertObjectOperation::getLabel() const{	return "Insert Object";}
 |