Browse Source

- Implemented updating additional terrain types fully(including 2 special cases)

beegee1 12 years ago
parent
commit
298f862d86

+ 269 - 214
config/terrainViewPatterns.json

@@ -1,4 +1,4 @@
-// Defines terrain view patterns.
+// Defines terrain view/types patterns.
 
 // The following table shows the rules for the 3x3 pattern of all terrain types: 
 // I) normal(e.g. grass, lava, ...):
@@ -8,7 +8,7 @@
 // T:		Sand OR dirt border(all Ts in the pattern are replaced by dirt OR sand)
 // ?:		D,S or N
 // II) dirt:
-// N:		Native type
+// N:		Native type or normal type(grass, lava, ...)
 // S:		Sand border
 // ?:		Any border
 // III) sand:
@@ -18,216 +18,271 @@
 // S:		Sand border
 // ?:		Any border
 
+// Additional rule for validiting terrain type:
+// N!:		Native type always(unlike N for dirt)
+
 // The order of the patterns is important, do not change!
-[
-	// Extended mixed transitions
-	{
-		"id" : "x1",
-		"data" :
-		[
-			"T", "N", "N",
-			"N", "N", "T",
-			"N", "T", "T"
-		],
-		"mapping" : { "normal" : "73,74", "dirt" : "45" }
-	},
-	{
-		"id" : "x2",
-		"data" :
-		[
-			"D", "D", "N",
-			"D", "N", "N",
-			"N", "N", "S"
-		],
-		"mapping" : { "normal" : "75" }
-	},
-	{
-		"id" : "x3",
-		"data" :
-		[
-			"S", "S", "N",
-			"S", "N", "N",
-			"N", "N", "D"
-		],
-		"mapping" : { "normal" : "76" }
-	},
-	{
-		"id" : "x4",
-		"data" :
-		[
-			"N", "N", "S",
-			"N", "N", "D",
-			"S", "D", "D"
-		],
-		"mapping" : { "normal" : "77" }
-	},
-	{
-		"id" : "x5",
-		"data" :
-		[
-			"N", "N", "D",
-			"N", "N", "D",
-			"D", "D", "S"
-		],
-		"mapping" : { "normal" : "78" }
-	},
-	// No transition
-	{
-		"id" : "n1",
-		"data" :
-		[
-			"N", "N", "N",
-			"N", "N", "N",
-			"N", "N", "N"
-		],
-		"mapping" : { "normal" : "49-72", "dirt" : "21-44", "sand" : "0-23", "water" : "20-32", "rock": "0-7" }
-	},
-	// Mixed transitions
-	{
-		"id" : "m1",
-		"data" :
-		[
-			"T", "N", "N",
-			"N", "N", "N",
-			"N", "N", "T"
-		],
-		"mapping" : { "normal" : "40, 42", "dirt" : "20" }
-	},
-	{
-		"id" : "m2",
-		"data" :
-		[
-			"D", "N", "N",
-			"N", "N", "N",
-			"N", "N", "S"
-		],
-		"mapping" : { "normal" : "41" }
-	},
-	{
-		"id" : "m3",
-		"data" :
-		[
-			"N", "N", "D,N",
-			"N", "N", "D",
-			"S", "D,N", "D,N"
-		],
-		"mapping" : { "normal" : "43" }
-	},
-	{
-		"id" : "m4",
-		"data" :
-		[
-			"N", "N", "S",
-			"N", "N", "D",
-			"D,N", "D", "D,N"
-		],
-		"mapping" : { "normal" : "44" }
-	},
-	{
-		"id" : "m5",
-		"data" :
-		[
-			"N", "N", "D",
-			"N", "N", "D",
-			"N", "N", "S"
-		],
-		"mapping" : { "normal" : "45" }
-	},
-	{
-		"id" : "m6",
-		"data" :
-		[
-			"N", "N", "N",
-			"N", "N", "N",
-			"D,N", "D", "S"
-		],
-		"mapping" : { "normal" : "46" }
-	},
-	{
-		"id" : "m7",
-		"data" :
-		[
-			"N", "N", "?",
-			"N", "N", "S",
-			"D-1,N", "D-1,N", "?"
-		],
-		"minPoints" : 1,
-		"mapping" : { "normal" : "47" }
-	},
-	{
-		"id" : "m8",
-		"data" :
-		[
-			"N", "N", "D-1,N",
-			"N", "N", "D-1,N",
-			"?", "S", "?"
-		],
-		"minPoints" : 1,
-		"mapping" : { "normal" : "48" }
-	},
-	// Standard transitions
-	{
-		"id" : "s1",
-		"data" :
-		[
-			"T,N-1", "T,N-2", "T,N-3",
-			"T,N-2", "N", "N",
-			"T", "N", "N"
-		],
-		"maxPoints" : 3,
-		"mapping" : { "normal" : "0-3, 20-23", "dirt" : "0-3", "water" : "0-3", "rock": "4D:8-15" }
-	},
-	{
-		"id" : "s2",
-		"data" :
-		[
-			"?", "N", "N",
-			"T", "N", "N",
-			"?", "N", "N"
-		],
-		"mapping" : { "normal" : "4-7, 24-27", "dirt" : "4-7", "water" : "4-7", "rock": "2D:16-19" }
-	},
-	{
-		"id" : "s3",
-		"data" :
-		[
-			"?", "T", "?",
-			"N", "N", "N",
-			"N", "N", "N"
-		],
-		"mapping" : { "normal" : "8-11, 28-31", "dirt" : "8-11", "water" : "8-11", "rock": "2D:20-23" }
-	},
-	{
-		"id" : "s4",
-		"data" :
-		[
-			"N", "N", "N",
-			"N", "N", "s3-1,m7-1,m8-1",
-			"N", "s2-1,m7-1,m8-1", "T"
-		],
-		"minPoints" : 2,
-		"mapping" : { "normal" : "12-15, 32-35", "dirt" : "12-15", "water" : "12-15", "rock": "4D:24-31" }
-	},
-	{
-		"id" : "s5",
-		"data" :
-		[
-			"T", "T", "?",
-			"T", "N", "s6-1,m1-1,m2-1,N",
-			"?", "s6-1,m1-1,m2-1,N", "N"
-		],
-		"minPoints" : 1,
-		"mapping" : { "normal" : "16-17, 36-37", "dirt" : "16-17", "water" : "16-17", "rock": "4D:32-39" }
-	},
-	{
-		"id" : "s6",
-		"data" :
-		[
-			"N", "N", "N",
-			"N", "N", "s5-1,N",
-			"N", "s5-1,N", "T"
-		],
-		"minPoints" : 1,
-		"mapping" : { "normal" : "18-19, 38-39", "dirt" : "18-19", "water" : "18-19", "rock": "4D:40-47" }
-	}
-]
+{
+	"terrainView" :
+	[
+		// Extended mixed transitions
+		{
+			"id" : "x1",
+			"data" :
+			[
+				"T", "N", "N",
+				"N", "N", "T",
+				"N", "T", "T"
+			],
+			"mapping" : { "normal" : "73,74", "dirt" : "45" }
+		},
+		{
+			"id" : "x2",
+			"data" :
+			[
+				"D", "D", "N",
+				"D", "N", "N",
+				"N", "N", "S"
+			],
+			"mapping" : { "normal" : "75" }
+		},
+		{
+			"id" : "x3",
+			"data" :
+			[
+				"S", "S", "N",
+				"S", "N", "N",
+				"N", "N", "D"
+			],
+			"mapping" : { "normal" : "76" }
+		},
+		{
+			"id" : "x4",
+			"data" :
+			[
+				"N", "N", "S",
+				"N", "N", "D",
+				"S", "D", "D"
+			],
+			"mapping" : { "normal" : "77" }
+		},
+		{
+			"id" : "x5",
+			"data" :
+			[
+				"N", "N", "D",
+				"N", "N", "D",
+				"D", "D", "S"
+			],
+			"mapping" : { "normal" : "78" }
+		},
+		// No transition
+		{
+			"id" : "n1",
+			"data" :
+			[
+				"N", "N", "N",
+				"N", "N", "N",
+				"N", "N", "N"
+			],
+			"mapping" : { "normal" : "49-72", "dirt" : "21-44", "sand" : "0-23", "water" : "20-32", "rock": "0-7" }
+		},
+		// Mixed transitions
+		{
+			"id" : "m1",
+			"data" :
+			[
+				"T", "N", "N",
+				"N", "N", "N",
+				"N", "N", "T"
+			],
+			"mapping" : { "normal" : "40, 42", "dirt" : "20" }
+		},
+		{
+			"id" : "m2",
+			"data" :
+			[
+				"D", "N", "N",
+				"N", "N", "N",
+				"N", "N", "S"
+			],
+			"mapping" : { "normal" : "41" }
+		},
+		{
+			"id" : "m3",
+			"data" :
+			[
+				"N", "N", "D,N",
+				"N", "N", "D",
+				"S", "D,N", "D,N"
+			],
+			"mapping" : { "normal" : "43" }
+		},
+		{
+			"id" : "m4",
+			"data" :
+			[
+				"N", "N", "S",
+				"N", "N", "D",
+				"D,N", "D", "D,N"
+			],
+			"mapping" : { "normal" : "44" }
+		},
+		{
+			"id" : "m5",
+			"data" :
+			[
+				"N", "N", "D",
+				"N", "N", "D",
+				"N", "N", "S"
+			],
+			"mapping" : { "normal" : "45" }
+		},
+		{
+			"id" : "m6",
+			"data" :
+			[
+				"N", "N", "N",
+				"N", "N", "N",
+				"D,N", "D", "S"
+			],
+			"mapping" : { "normal" : "46" }
+		},
+		{
+			"id" : "m7",
+			"data" :
+			[
+				"N", "N", "?",
+				"N", "N", "S",
+				"D-1,N", "D-1,N", "?"
+			],
+			"minPoints" : 1,
+			"mapping" : { "normal" : "47" }
+		},
+		{
+			"id" : "m8",
+			"data" :
+			[
+				"N", "N", "D-1,N",
+				"N", "N", "D-1,N",
+				"?", "S", "?"
+			],
+			"minPoints" : 1,
+			"mapping" : { "normal" : "48" }
+		},
+		// Standard transitions
+		{
+			"id" : "s2",
+			"data" :
+			[
+				"?", "N", "N",
+				"T", "N", "N",
+				"?", "N", "N"
+			],
+			"mapping" : { "normal" : "4-7, 24-27", "dirt" : "4-7", "water" : "4-7", "rock": "2D:16-19" }
+		},
+		{
+			"id" : "s3",
+			"data" :
+			[
+				"?", "T", "?",
+				"N", "N", "N",
+				"N", "N", "N"
+			],
+			"mapping" : { "normal" : "8-11, 28-31", "dirt" : "8-11", "water" : "8-11", "rock": "2D:20-23" }
+		},
+		{
+			"id" : "s4",
+			"data" :
+			[
+				"N", "N", "N",
+				"N", "N", "s3-1,m7-1,m8-1",
+				"N", "s2-1,m7-1,m8-1", "T"
+			],
+			"minPoints" : 2,
+			"mapping" : { "normal" : "12-15, 32-35", "dirt" : "12-15", "water" : "12-15", "rock": "4D:24-31" }
+		},
+		{
+			"id" : "s5",
+			"data" :
+			[
+				"T", "T", "?",
+				"T", "N", "s6-1,m1-1,m2-1,N",
+				"?,x1-1,s1-1", "s6-1,m1-1,m2-1,N", "N"
+			],
+			"minPoints" : 1,
+			"mapping" : { "normal" : "16-17, 36-37", "dirt" : "16-17", "water" : "16-17", "rock": "4D:32-39" }
+		},
+		{
+			"id" : "s6",
+			"data" :
+			[
+				"N", "N", "N",
+				"N", "N", "s5-1,N",
+				"N", "s5-1,N", "T"
+			],
+			"minPoints" : 1,
+			"mapping" : { "normal" : "18-19, 38-39", "dirt" : "18-19", "water" : "18-19", "rock": "4D:40-47" }
+		},
+		{
+			"id" : "s1",
+			"data" :
+			[
+				"?", "?", "?",
+				"?", "N", "N",
+				"T", "N", "N"
+			],
+			"mapping" : { "normal" : "0-3, 20-23", "dirt" : "0-3", "water" : "0-3", "rock": "4D:8-15" }
+		}
+	],
+	"terrainType" :
+	[
+		{
+			"id" : "n1",
+			"data" :
+			[
+				"N!", "N!", "?",
+				"N!", "N!", "?",
+				"?", "?", "?"
+			]
+		},
+		{
+			"id" : "n2",
+			"data" :
+			[
+				"N!", "N!", "D,S",
+				"D,S", "N!", "N!",
+				"D,S", "D,S", "N!"
+			]
+		},
+		{
+			"id" : "n3",
+			"data" :
+			[
+				"D,S", "D,S", "N!",
+				"D,S", "N!", "N!",
+				"N!", "N!", "D,S"
+			]
+		},
+		{
+			"id" : "s1",
+			"data" :
+			[
+				"T", "N", "N",
+				"N", "N", "N",
+				"N", "T-1,N", "T-1,N"
+			],
+			"minPoints" : 1
+		},
+		{
+			"id" : "s2",
+			"data" :
+			[
+				"N", "N", "T",
+				"T-1,N", "N", "N",
+				"T-1,N", "N", "N"
+			],
+			"minPoints" : 1
+		},
+	]
+}

+ 135 - 94
lib/mapping/CMapEditManager.cpp

@@ -284,9 +284,10 @@ 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), terGroup(ETerrainGroup::NORMAL)
+TerrainViewPattern::TerrainViewPattern() : diffImages(false), rotationTypesCount(0), minPoints(0)
 {
 	maxPoints = std::numeric_limits<int>::max();
 }
@@ -300,7 +301,7 @@ 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_TRANSITION == name || TerrainViewPattern::RULE_NATIVE_STRONG == name;
 }
 
 boost::mutex CTerrainViewPatternConfig::smx;
@@ -315,70 +316,82 @@ CTerrainViewPatternConfig & CTerrainViewPatternConfig::get()
 CTerrainViewPatternConfig::CTerrainViewPatternConfig()
 {
 	const JsonNode config(ResourceID("config/terrainViewPatterns.json"));
-	const auto & patternsVec = config.Vector();
-	BOOST_FOREACH(const auto & ptrnNode, patternsVec)
+	static const std::string patternTypes[] = { "terrainView", "terrainType" };
+	for(int i = 0; i < ARRAY_COUNT(patternTypes); ++i)
 	{
-		TerrainViewPattern pattern;
-
-		// Read pattern data
-		const JsonVector & data = ptrnNode["data"].Vector();
-		assert(data.size() == 9);
-		for(int i = 0; i < data.size(); ++i)
+		const auto & patternsVec = config[patternTypes[i]].Vector();
+		BOOST_FOREACH(const auto & ptrnNode, patternsVec)
 		{
-			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)
+			TerrainViewPattern pattern;
+
+			// Read pattern data
+			const JsonVector & data = ptrnNode["data"].Vector();
+			assert(data.size() == 9);
+			for(int i = 0; i < data.size(); ++i)
 			{
-				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)
+				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)
 				{
-					rule.points = boost::lexical_cast<int>(ruleParts[1]);
+					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[i].push_back(rule);
 				}
-				pattern.data[i].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 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
-		const auto & mappingStruct = ptrnNode["mapping"].Struct();
-		BOOST_FOREACH(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)
+			// Read mapping
+			if(i == 0)
 			{
-				terGroupPattern.rotationTypesCount = boost::lexical_cast<int>(flipMode.substr(0, flipMode.length() - 1));
-				assert(terGroupPattern.rotationTypesCount == 2 || terGroupPattern.rotationTypesCount == 4);
+				const auto & mappingStruct = ptrnNode["mapping"].Struct();
+				BOOST_FOREACH(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(","));
+					BOOST_FOREACH(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);
+				}
 			}
-			mappingStr = mappingStr.substr(colonIndex + 1);
-			std::vector<std::string> mappings;
-			boost::split(mappings, mappingStr, boost::is_any_of(","));
-			BOOST_FOREACH(std::string mapping, mappings)
+			else if(i == 1)
 			{
-				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])));
+				terrainTypePatterns[pattern.id] = pattern;
 			}
-			const auto & terGroup = getTerrainGroup(mappingPair.first);
-			terGroupPattern.terGroup = terGroup;
-			patterns[terGroup].push_back(terGroupPattern);
 		}
 	}
 }
@@ -398,14 +411,14 @@ ETerrainGroup::ETerrainGroup CTerrainViewPatternConfig::getTerrainGroup(const st
 	return it->second;
 }
 
-const std::vector<TerrainViewPattern> & CTerrainViewPatternConfig::getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const
+const std::vector<TerrainViewPattern> & CTerrainViewPatternConfig::getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const
 {
-	return patterns.find(terGroup)->second;
+	return terrainViewPatterns.find(terGroup)->second;
 }
 
-boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const
+boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getTerrainViewPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const
 {
-	const std::vector<TerrainViewPattern> & groupPatterns = getPatternsForGroup(terGroup);
+	const std::vector<TerrainViewPattern> & groupPatterns = getTerrainViewPatternsForGroup(terGroup);
 	BOOST_FOREACH(const TerrainViewPattern & pattern, groupPatterns)
 	{
 		if(id == pattern.id)
@@ -416,6 +429,13 @@ boost::optional<const TerrainViewPattern &> CTerrainViewPatternConfig::getPatter
 	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)
 {
@@ -459,9 +479,10 @@ void CDrawTerrainOperation::updateTerrainTypes()
 		const auto & centerPos = *(positions.begin());
 		auto centerTile = map->getTile(centerPos);
 		auto tiles = getInvalidTiles(centerPos);
-		auto updateTerrainType = [&](const int3 & pos)
+		auto updateTerrainType = [&](const int3 & pos, bool tileRequiresValidation)
 		{
 			map->getTile(pos).terType = centerTile.terType;
+			if(tileRequiresValidation) positions.insert(pos);
 			invalidateTerrainViews(pos);
 			logGlobal->debugStream() << boost::format("Update terrain tile at '%s' to type '%i'.") % pos % centerTile.terType;
 		};
@@ -469,7 +490,7 @@ void CDrawTerrainOperation::updateTerrainTypes()
 		// Fill foreign invalid tiles
 		BOOST_FOREACH(const auto & tile, tiles.foreignTiles)
 		{
-			updateTerrainType(tile);
+			updateTerrainType(tile, true);
 		}
 
 		if(tiles.nativeTiles.find(centerPos) != tiles.nativeTiles.end())
@@ -477,8 +498,7 @@ void CDrawTerrainOperation::updateTerrainTypes()
 			// Blow up
 			auto rect = extendTileAroundSafely(centerPos);
 			std::set<int3> suitableTiles;
-			int invalidForeignTilesCnt, invalidNativeTilesCnt;
-			invalidForeignTilesCnt = invalidNativeTilesCnt = std::numeric_limits<int>::max();
+			int invalidForeignTilesCnt = std::numeric_limits<int>::max(), invalidNativeTilesCnt = 0;
 			rect.forEach([&](const int3 & posToTest)
 			{
 				auto & terrainTile = map->getTile(posToTest);
@@ -490,16 +510,21 @@ void CDrawTerrainOperation::updateTerrainTypes()
 					auto addToSuitableTiles = [&](const int3 & pos)
 					{
 						suitableTiles.insert(pos);
-						logGlobal->debugStream() << boost::format("Found suitable tile '%s' for main tile '%s'.") % pos % centerPos;
+						logGlobal->debugStream() << boost::format(std::string("Found suitable tile '%s' for main tile '%s': ") +
+								"Invalid native tiles '%i', invalid foreign tiles '%i'.") % pos % centerPos % testTile.nativeTiles.size() %
+								testTile.foreignTiles.size();
 					};
 
-					if(testTile.nativeTiles.size() < invalidNativeTilesCnt ||
-							(testTile.nativeTiles.size() == invalidNativeTilesCnt && testTile.foreignTiles.size() < invalidForeignTilesCnt))
+					int nativeTilesCntNorm = testTile.nativeTiles.empty() ? std::numeric_limits<int>::max() : testTile.nativeTiles.size();
+					if(nativeTilesCntNorm > invalidNativeTilesCnt ||
+							(nativeTilesCntNorm == invalidNativeTilesCnt && testTile.foreignTiles.size() < invalidForeignTilesCnt))
 					{
+						invalidNativeTilesCnt = nativeTilesCntNorm;
+						invalidForeignTilesCnt = testTile.foreignTiles.size();
 						suitableTiles.clear();
 						addToSuitableTiles(posToTest);
 					}
-					else if(testTile.nativeTiles.size() == invalidNativeTilesCnt &&
+					else if(nativeTilesCntNorm == invalidNativeTilesCnt &&
 							testTile.foreignTiles.size() == invalidForeignTilesCnt)
 					{
 						addToSuitableTiles(posToTest);
@@ -508,20 +533,21 @@ void CDrawTerrainOperation::updateTerrainTypes()
 				}
 			});
 
+			bool tileRequiresValidation = invalidForeignTilesCnt > 0;
 			if(suitableTiles.size() == 1)
 			{
-				updateTerrainType(*suitableTiles.begin());
+				updateTerrainType(*suitableTiles.begin(), tileRequiresValidation);
 			}
 			else
 			{
-				const int3 directions[] = { int3(0, -1, 0), int3(-1, 0, 0), int3(0, 1, 0), int3(1, 0, 0),
+				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(int i = 0; i < ARRAY_COUNT(directions); ++i)
 				{
 					auto it = suitableTiles.find(centerPos + directions[i]);
 					if(it != suitableTiles.end())
 					{
-						updateTerrainType(*it);
+						updateTerrainType(*it, tileRequiresValidation);
 						break;
 					}
 				}
@@ -539,7 +565,7 @@ void CDrawTerrainOperation::updateTerrainViews()
 	BOOST_FOREACH(const auto & pos, invalidatedTerViews)
 	{
 		const auto & patterns =
-				CTerrainViewPatternConfig::get().getPatternsForGroup(getTerrainGroup(map->getTile(pos).terType));
+				CTerrainViewPatternConfig::get().getTerrainViewPatternsForGroup(getTerrainGroup(map->getTile(pos).terType));
 
 		// Detect a pattern which fits best
 		int bestPattern = -1;
@@ -556,7 +582,7 @@ void CDrawTerrainOperation::updateTerrainViews()
 				break;
 			}
 		}
-		assert(bestPattern != -1);
+		//assert(bestPattern != -1);
 		if(bestPattern == -1)
 		{
 			// This shouldn't be the case
@@ -627,7 +653,8 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 
 CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth /*= 0*/) const
 {
-	ETerrainType centerTerType = map->getTile(pos).terType;
+	auto centerTerType = map->getTile(pos).terType;
+	auto centerTerGroup = getTerrainGroup(centerTerType);
 	int totalPoints = 0;
 	std::string transitionReplacement;
 
@@ -667,11 +694,14 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 			{
 				if(recDepth == 0 && map->isInTheMap(currentPos))
 				{
-					const auto & patternForRule = CTerrainViewPatternConfig::get().getPatternById(getTerrainGroup(terType), rule.name);
-					if(patternForRule)
+					if(terType == centerTerType)
 					{
-						auto rslt = validateTerrainView(currentPos, *patternForRule, 1);
-						if(rslt.result) topPoints = std::max(topPoints, rule.points);
+						const auto & patternForRule = CTerrainViewPatternConfig::get().getTerrainViewPatternById(getTerrainGroup(centerTerType), rule.name);
+						if(patternForRule)
+						{
+							auto rslt = validateTerrainView(currentPos, *patternForRule, 1);
+							if(rslt.result) topPoints = std::max(topPoints, rule.points);
+						}
 					}
 					continue;
 				}
@@ -690,7 +720,9 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 			};
 
 			// Validate cell with the ruleset of the pattern
-			if(pattern.terGroup == ETerrainGroup::NORMAL)
+			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);
@@ -709,24 +741,22 @@ CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainVi
 				}
 				else
 				{
-					bool nativeTestOk = rule.name == TerrainViewPattern::RULE_NATIVE && !isAlien;
 					applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || dirtTestOk || sandTestOk || nativeTestOk);
 				}
 			}
-			else if(pattern.terGroup == ETerrainGroup::DIRT)
+			else if(centerTerGroup == ETerrainGroup::DIRT)
 			{
-				bool nativeTestOk = rule.name == TerrainViewPattern::RULE_NATIVE && !isSandType(terType);
+				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);
+				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk || nativeTestStrongOk);
 			}
-			else if(pattern.terGroup == ETerrainGroup::SAND)
+			else if(centerTerGroup == ETerrainGroup::SAND)
 			{
 				applyValidationRslt(true);
 			}
-			else if(pattern.terGroup == ETerrainGroup::WATER || pattern.terGroup == ETerrainGroup::ROCK)
+			else if(centerTerGroup == ETerrainGroup::WATER || centerTerGroup == ETerrainGroup::ROCK)
 			{
-				bool nativeTestOk = rule.name == TerrainViewPattern::RULE_NATIVE && !isAlien;
 				bool sandTestOk = (rule.name == TerrainViewPattern::RULE_SAND || rule.name == TerrainViewPattern::RULE_TRANSITION)
 						&& isAlien;
 				applyValidationRslt(rule.name == TerrainViewPattern::RULE_ANY || sandTestOk || nativeTestOk);
@@ -811,20 +841,31 @@ CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const
 	{
 		if(map->isInTheMap(pos))
 		{
+			auto & ptrConfig = CTerrainViewPatternConfig::get();
 			auto terType = map->getTile(pos).terType;
-			// Pattern 2x2
-			const int3 translations[4] = { int3(-1, -1, 0), int3(0, -1, 0), int3(-1, 0, 0), int3(0, 0, 0) };
-			bool valid = true;
-			for(int i = 0; i < ARRAY_COUNT(translations); ++i)
+			auto valid = validateTerrainView(pos, ptrConfig.getTerrainTypePatternById("n1")).result;
+
+			// Special validity check for rock & water
+			if(valid && centerTerType != terType && (terType == ETerrainType::WATER || terType == ETerrainType::ROCK))
 			{
-				valid = true;
-				MapRect square(int3(pos.x + translations[i].x, pos.y + translations[i].y, pos.z), 2, 2);
-				square.forEach([&](const int3 & pos)
+				static const std::string patternIds[] = { "s1", "s2" };
+				for(int i = 0; i < ARRAY_COUNT(patternIds); ++i)
 				{
-					if(map->isInTheMap(pos) && map->getTile(pos).terType != terType) valid = false;
-				});
-				if(valid) break;
+					valid = !validateTerrainView(pos, ptrConfig.getTerrainTypePatternById(patternIds[i])).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(int i = 0; i < ARRAY_COUNT(patternIds); ++i)
+				{
+					valid = validateTerrainView(pos, ptrConfig.getTerrainTypePatternById(patternIds[i])).result;
+					if(valid) break;
+				}
 			}
+
 			if(!valid)
 			{
 				if(terType == centerTerType) tiles.nativeTiles.insert(pos);

+ 12 - 9
lib/mapping/CMapEditManager.h

@@ -42,9 +42,9 @@ struct DLL_LINKAGE MapRect
 	template<typename Func>
 	void forEach(Func f) const
 	{
-		for(int i = x; i < right(); ++i)
+		for(int j = y; j < bottom(); ++j)
 		{
-			for(int j = y; j < bottom(); ++j)
+			for(int i = x; i < right(); ++i)
 			{
 				f(int3(i, j, z));
 			}
@@ -225,6 +225,7 @@ struct DLL_LINKAGE TerrainViewPattern
 		int points;
 	};
 
+	static const int PATTERN_DATA_SIZE = 9;
 	/// Constant for the flip mode different images. Pattern will be flipped and different images will be used(mapping area is divided into 4 parts)
 	static const std::string FLIP_MODE_DIFF_IMAGES;
 	/// Constant for the rule dirt, meaning a dirty border is required.
@@ -233,8 +234,10 @@ struct DLL_LINKAGE TerrainViewPattern
 	static const std::string RULE_SAND;
 	/// Constant for the rule transition, meaning a dirty OR sandy border is required.
 	static const std::string RULE_TRANSITION;
-	/// Constant for the rule native, meaning a native type is required.
+	/// Constant for the rule native, meaning a native border is required.
 	static const std::string RULE_NATIVE;
+	/// Constant for the rule native strong, meaning a native type is required.
+	static const std::string RULE_NATIVE_STRONG;
 	/// Constant for the rule any, meaning a native type, dirty OR sandy border is required.
 	static const std::string RULE_ANY;
 
@@ -250,7 +253,7 @@ struct DLL_LINKAGE TerrainViewPattern
 	/// can be used. Their meaning differs also from type to type.
 	///
 	/// std::vector -> several rules can be used in one cell
-	std::array<std::vector<WeightedRule>, 9> data;
+	std::array<std::vector<WeightedRule>, PATTERN_DATA_SIZE> data;
 
 	/// The identifier of the pattern, if it's referenced from a another pattern.
 	std::string id;
@@ -270,8 +273,6 @@ struct DLL_LINKAGE TerrainViewPattern
 
 	/// The minimum and maximum points to reach to validate the pattern successfully.
 	int minPoints, maxPoints;
-
-	ETerrainGroup::ETerrainGroup terGroup;
 };
 
 /// The terrain view pattern config loads pattern data from the filesystem.
@@ -280,15 +281,17 @@ class DLL_LINKAGE CTerrainViewPatternConfig : public boost::noncopyable
 public:
 	static CTerrainViewPatternConfig & get();
 
-	const std::vector<TerrainViewPattern> & getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const;
-	boost::optional<const TerrainViewPattern &> getPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const;
+	const std::vector<TerrainViewPattern> & getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const;
+	boost::optional<const TerrainViewPattern &> getTerrainViewPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const;
+	const TerrainViewPattern & getTerrainTypePatternById(const std::string & id) const;
 	ETerrainGroup::ETerrainGroup getTerrainGroup(const std::string & terGroup) const;
 
 private:
 	CTerrainViewPatternConfig();
 	~CTerrainViewPatternConfig();
 
-	std::map<ETerrainGroup::ETerrainGroup, std::vector<TerrainViewPattern> > patterns;
+	std::map<ETerrainGroup::ETerrainGroup, std::vector<TerrainViewPattern> > terrainViewPatterns;
+	std::map<std::string, TerrainViewPattern> terrainTypePatterns;
 	static boost::mutex smx;
 };
 

+ 57 - 2
test/CMapEditManagerTest.cpp

@@ -21,7 +21,62 @@
 #include "../lib/int3.h"
 #include "../lib/CRandomGenerator.h"
 
-BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain)
+BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type)
+{
+	try
+	{
+		auto map = make_unique<CMap>();
+		map->width = 100;
+		map->height = 100;
+		map->initTerrain();
+		auto editManager = map->getEditManager();
+		editManager->clearTerrain();
+
+		// 1x1 Blow up
+		editManager->getTerrainSelection().select(int3(5, 5, 0));
+		editManager->drawTerrain(ETerrainType::GRASS);
+		static const int3 squareCheck[] = { int3(5,5,0), int3(5,4,0), int3(4,4,0), int3(4,5,0) };
+		for(int i = 0; i < ARRAY_COUNT(squareCheck); ++i)
+		{
+			BOOST_CHECK(map->getTile(squareCheck[i]).terType == ETerrainType::GRASS);
+		}
+
+		// Concat to square
+		editManager->getTerrainSelection().select(int3(6, 5, 0));
+		editManager->drawTerrain(ETerrainType::GRASS);
+		BOOST_CHECK(map->getTile(int3(6, 4, 0)).terType == ETerrainType::GRASS);
+		editManager->getTerrainSelection().select(int3(6, 5, 0));
+		editManager->drawTerrain(ETerrainType::LAVA);
+		BOOST_CHECK(map->getTile(int3(4, 4, 0)).terType == ETerrainType::GRASS);
+		BOOST_CHECK(map->getTile(int3(7, 4, 0)).terType == ETerrainType::LAVA);
+
+		// Special case water,rock
+		editManager->getTerrainSelection().selectRange(MapRect(int3(10, 10, 0), 10, 5));
+		editManager->drawTerrain(ETerrainType::GRASS);
+		editManager->getTerrainSelection().selectRange(MapRect(int3(15, 17, 0), 10, 5));
+		editManager->drawTerrain(ETerrainType::GRASS);
+		editManager->getTerrainSelection().select(int3(21, 16, 0));
+		editManager->drawTerrain(ETerrainType::GRASS);
+		BOOST_CHECK(map->getTile(int3(20, 15, 0)).terType == ETerrainType::GRASS);
+
+		// Special case non water,rock
+		static const int3 diagonalCheck[] = { int3(31,42,0), int3(32,42,0), int3(32,43,0), int3(33,43,0), int3(33,44,0),
+											  int3(34,44,0), int3(34,45,0), int3(35,45,0), int3(35,46,0), int3(36,46,0),
+											  int3(36,47,0), int3(37,47,0)};
+		for(int i = 0; i < ARRAY_COUNT(diagonalCheck); ++i)
+		{
+			editManager->getTerrainSelection().select(diagonalCheck[i]);
+		}
+		editManager->drawTerrain(ETerrainType::GRASS);
+		BOOST_CHECK(map->getTile(int3(35, 44, 0)).terType == ETerrainType::WATER);
+	}
+	catch(const std::exception & e)
+	{
+		logGlobal-> errorStream() << e.what();
+	}
+}
+
+BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
 {
 	try
 	{
@@ -49,7 +104,7 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain)
 			auto terGroup = CTerrainViewPatternConfig::get().getTerrainGroup(groupStr);
 
 			// Get mapping range
-			const auto & pattern = CTerrainViewPatternConfig::get().getPatternById(terGroup, id);
+			const auto & pattern = CTerrainViewPatternConfig::get().getTerrainViewPatternById(terGroup, id);
 			const auto & mapping = (*pattern).mapping;
 
 			const auto & positionsNode = node["pos"].Vector();

BIN
test/TerrainViewTest.h3m


+ 1 - 1
test/terrainViewMappings.json

@@ -18,7 +18,7 @@
 			"pattern" : "normal.s4"
 		},
 		{
-			"pos" : [ [ 5,14,0 ], [ 31,13,0 ] ],
+			"pos" : [ [ 5,14,0 ], [ 31,13,0 ], [ 17,3,0 ], [ 13,8,0 ] ],
 			"pattern" : "normal.s5"
 		},
 		{