From 298f862d863df42c2f7b52669f3e9dfb7e5fdb98 Mon Sep 17 00:00:00 2001 From: beegee1 Date: Fri, 3 May 2013 10:15:59 +0000 Subject: [PATCH] - Implemented updating additional terrain types fully(including 2 special cases) --- config/terrainViewPatterns.json | 483 ++++++++++++++++++-------------- lib/mapping/CMapEditManager.cpp | 229 ++++++++------- lib/mapping/CMapEditManager.h | 21 +- test/CMapEditManagerTest.cpp | 59 +++- test/TerrainViewTest.h3m | Bin 4214 -> 4221 bytes test/terrainViewMappings.json | 2 +- 6 files changed, 474 insertions(+), 320 deletions(-) diff --git a/config/terrainViewPatterns.json b/config/terrainViewPatterns.json index 1e1bd1222..2e0a46bcd 100644 --- a/config/terrainViewPatterns.json +++ b/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 + }, + ] +} diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index 6ff356cfc..2bbbe5cdd 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/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::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 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 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 rules; + boost::split(rules, cell, boost::is_any_of(",")); + BOOST_FOREACH(std::string ruleStr, rules) { - rule.points = boost::lexical_cast(ruleParts[1]); + std::vector 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(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(ptrnNode["minPoints"].Float()); - pattern.maxPoints = static_cast(ptrnNode["maxPoints"].Float()); - if(pattern.maxPoints == 0) pattern.maxPoints = std::numeric_limits::max(); + // Read various properties + pattern.id = ptrnNode["id"].String(); + assert(!pattern.id.empty()); + pattern.minPoints = static_cast(ptrnNode["minPoints"].Float()); + pattern.maxPoints = static_cast(ptrnNode["maxPoints"].Float()); + if(pattern.maxPoints == 0) pattern.maxPoints = std::numeric_limits::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(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(flipMode.substr(0, flipMode.length() - 1)); + assert(terGroupPattern.rotationTypesCount == 2 || terGroupPattern.rotationTypesCount == 4); + } + mappingStr = mappingStr.substr(colonIndex + 1); + std::vector mappings; + boost::split(mappings, mappingStr, boost::is_any_of(",")); + BOOST_FOREACH(std::string mapping, mappings) + { + std::vector range; + boost::split(range, mapping, boost::is_any_of("-")); + terGroupPattern.mapping.push_back(std::make_pair(boost::lexical_cast(range[0]), + boost::lexical_cast(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 mappings; - boost::split(mappings, mappingStr, boost::is_any_of(",")); - BOOST_FOREACH(std::string mapping, mappings) + else if(i == 1) { - std::vector range; - boost::split(range, mapping, boost::is_any_of("-")); - terGroupPattern.mapping.push_back(std::make_pair(boost::lexical_cast(range[0]), - boost::lexical_cast(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 & CTerrainViewPatternConfig::getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const +const std::vector & CTerrainViewPatternConfig::getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const { - return patterns.find(terGroup)->second; + return terrainViewPatterns.find(terGroup)->second; } -boost::optional CTerrainViewPatternConfig::getPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const +boost::optional CTerrainViewPatternConfig::getTerrainViewPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const { - const std::vector & groupPatterns = getPatternsForGroup(terGroup); + const std::vector & groupPatterns = getTerrainViewPatternsForGroup(terGroup); BOOST_FOREACH(const TerrainViewPattern & pattern, groupPatterns) { if(id == pattern.id) @@ -416,6 +429,13 @@ boost::optional CTerrainViewPatternConfig::getPatter return boost::optional(); } +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 suitableTiles; - int invalidForeignTilesCnt, invalidNativeTilesCnt; - invalidForeignTilesCnt = invalidNativeTilesCnt = std::numeric_limits::max(); + int invalidForeignTilesCnt = std::numeric_limits::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::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); diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index 782207c9b..686a469b6 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -42,9 +42,9 @@ struct DLL_LINKAGE MapRect template 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, 9> data; + std::array, 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 & getPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const; - boost::optional getPatternById(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) const; + const std::vector & getTerrainViewPatternsForGroup(ETerrainGroup::ETerrainGroup terGroup) const; + boost::optional 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 > patterns; + std::map > terrainViewPatterns; + std::map terrainTypePatterns; static boost::mutex smx; }; diff --git a/test/CMapEditManagerTest.cpp b/test/CMapEditManagerTest.cpp index 4379651a4..78a4f3179 100644 --- a/test/CMapEditManagerTest.cpp +++ b/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(); + 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(); diff --git a/test/TerrainViewTest.h3m b/test/TerrainViewTest.h3m index 7d74589187d5f687c5ff80550fbb6a8cfc965493..5ae7c35fec03d177de76ec23fd84c6669d9d7675 100644 GIT binary patch delta 4193 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 delta 4186 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 diff --git a/test/terrainViewMappings.json b/test/terrainViewMappings.json index d5a52a041..7a6cdb2d0 100644 --- a/test/terrainViewMappings.json +++ b/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" }, {