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 7d7458918..5ae7c35fe 100644 Binary files a/test/TerrainViewTest.h3m and b/test/TerrainViewTest.h3m differ 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" }, {