/* * CDrawRoadsOperation.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #include "StdInc.h" #include "CDrawRoadsOperation.h" const std::vector CDrawRoadsOperation::patterns = { //single tile. fall-back pattern { { "-","-","-", "-","+","-", "-","-","-" }, {14,14}, {9,9}, false, false }, //Road straight with angle { { "?","-","+", "-","+","+", "+","+","?" }, {2,5}, {-1,-1}, true, true }, //Turn { { "?","-","?", "-","+","+", "?","+","?" }, {0,1}, {0,3}, true, true }, //Dead end horizontal { { "?","-","?", "-","+","+", "?","-","?" }, {15,15},{11,12}, true, false }, //Dead end vertical { { "?","-","?", "-","+","-", "?","+","?" }, {14,14},{9,10}, false, true }, //T-cross horizontal { { "?","+","?", "-","+","+", "?","+","?" }, {6,7},{7,8}, true, false }, //T-cross vertical { { "?","-","?", "+","+","+", "?","+","?" }, {8,9},{5,6}, false, true }, //Straight Horizontal { { "?","-","?", "+","+","+", "?","-","?" }, {12,13},{11,12}, false, false }, //Straight Vertical { { "?","+","?", "-","+","-", "?","+","?" }, {10,11},{9,10}, false, false }, //X-cross { { "?","+","?", "+","+","+", "?","+","?" }, {16,16},{4,4}, false, false } }; static bool ruleIsNone(const std::string & rule) { return rule == "-"; } static bool ruleIsSomething(const std::string & rule) { return rule == "+"; } static bool ruleIsAny(const std::string & rule) { return rule == "?"; } ///CDrawRoadsOperation CDrawRoadsOperation::CDrawRoadsOperation(CMap * map, const CTerrainSelection & terrainSel, ERoadType::ERoadType roadType, CRandomGenerator * gen): CMapOperation(map),terrainSel(terrainSel), roadType(roadType), gen(gen) { } void CDrawRoadsOperation::execute() { std::set invalidated; for(const auto & pos : terrainSel.getSelectedItems()) { auto & tile = map->getTile(pos); tile.roadType = roadType; auto rect = extendTileAroundSafely(pos); rect.forEach([&invalidated](const int3 & pos) { invalidated.insert(pos); }); } updateTiles(invalidated); } void CDrawRoadsOperation::undo() { //TODO } void CDrawRoadsOperation::redo() { //TODO } std::string CDrawRoadsOperation::getLabel() const { return "Draw Roads"; } bool CDrawRoadsOperation::canApplyPattern(const RoadPattern & pattern) const { //TODO: this method should be virtual for river support return pattern.roadMapping.first >= 0; } void CDrawRoadsOperation::flipPattern(RoadPattern& pattern, int flip) const { //todo: use cashing here and also in terrain patterns if(flip == 0) { return; } if(flip == FLIP_PATTERN_HORIZONTAL || flip == FLIP_PATTERN_BOTH) { for(int i = 0; i < 3; ++i) { int y = i * 3; std::swap(pattern.data[y], pattern.data[y + 2]); } } if(flip == FLIP_PATTERN_VERTICAL || flip == FLIP_PATTERN_BOTH) { for(int i = 0; i < 3; ++i) { std::swap(pattern.data[i], pattern.data[6 + i]); } } } bool CDrawRoadsOperation::needUpdateTile(const TerrainTile & tile) const { return tile.roadType != ERoadType::NO_ROAD; //TODO: this method should be virtual for river support } void CDrawRoadsOperation::updateTiles(std::set & invalidated) { for(int3 coord : invalidated) { TerrainTile & tile = map->getTile(coord); ValidationResult result(false); if(!needUpdateTile(tile)) continue; int bestPattern = -1; for(int k = 0; k < patterns.size(); ++k) { result = validateTile(patterns[k], coord); if(result.result) { bestPattern = k; break; } } if(bestPattern != -1) { updateTile(tile, patterns[bestPattern], result.flip); } } }; bool CDrawRoadsOperation::tileHasSomething(const int3& pos) const { //TODO: this method should be virtual for river support return map->getTile(pos).roadType != ERoadType::NO_ROAD; } void CDrawRoadsOperation::updateTile(TerrainTile & tile, const RoadPattern & pattern, const int flip) { //TODO: this method should be virtual for river support const std::pair & mapping = pattern.roadMapping; tile.roadDir = gen->nextInt(mapping.first, mapping.second); tile.extTileFlags = (tile.extTileFlags & 0xCF) | (flip << 4); } CDrawRoadsOperation::ValidationResult CDrawRoadsOperation::validateTile(const RoadPattern & pattern, const int3 & pos) { ValidationResult result(false); if(!canApplyPattern(pattern)) return result; for(int flip = 0; flip < 4; ++flip) { if((flip == FLIP_PATTERN_BOTH) && !(pattern.hasHFlip && pattern.hasVFlip)) continue; if((flip == FLIP_PATTERN_HORIZONTAL) && !pattern.hasHFlip) continue; if((flip == FLIP_PATTERN_VERTICAL) && !(pattern.hasVFlip)) continue; RoadPattern flipped = pattern; flipPattern(flipped, flip); bool validated = true; for(int i = 0; i < 9; ++i) { if(4 == i) continue; int cx = pos.x + (i % 3) - 1; int cy = pos.y + (i / 3) - 1; int3 currentPos(cx, cy, pos.z); bool hasSomething; if(!map->isInTheMap(currentPos)) { hasSomething = true; //road/river can go out of map } else { hasSomething = tileHasSomething(currentPos); } if(ruleIsSomething(flipped.data[i])) { if(!hasSomething) { validated = false; break; } } else if(ruleIsNone(flipped.data[i])) { if(hasSomething) { validated = false; break; } } else { assert(ruleIsAny(flipped.data[i])); } } if(validated) { result.result = true; result.flip = flip; return result; } } return result; }