diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 930a3e72f..4800fa9ac 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -68,7 +68,9 @@ set(lib_SRCS mapping/CMap.cpp mapping/CMapEditManager.cpp mapping/CMapInfo.cpp + mapping/CMapOperation.cpp mapping/CMapService.cpp + mapping/MapEditUtils.cpp mapping/MapFormatH3M.cpp mapping/MapFormatJson.cpp @@ -301,7 +303,9 @@ set(lib_HEADERS mapping/CMapEditManager.h mapping/CMap.h mapping/CMapInfo.h + mapping/CMapOperation.h mapping/CMapService.h + mapping/MapEditUtils.h mapping/MapFormatH3M.h mapping/MapFormatJson.h diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 2e40ea49d..51cc432b7 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1403,6 +1403,11 @@ void CGHeroInstance::afterAddToMap(CMap * map) if(ID == Obj::HERO) map->heroesOnMap.push_back(this); } +void CGHeroInstance::afterRemoveFromMap(CMap* map) +{ + if (ID == Obj::HERO) + vstd::erase_if_present(map->heroesOnMap, this); +} void CGHeroInstance::setHeroTypeName(const std::string & identifier) { diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 4263fb818..b0c2a122c 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -268,6 +268,7 @@ public: std::string getObjectName() const override; void afterAddToMap(CMap * map) override; + void afterRemoveFromMap(CMap* map) override; void updateFrom(const JsonNode & data) override; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 843f21326..bb320caee 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1441,6 +1441,12 @@ void CGTownInstance::afterAddToMap(CMap * map) map->towns.push_back(this); } +void CGTownInstance::afterRemoveFromMap(CMap* map) +{ + if (ID == Obj::TOWN) + vstd::erase_if_present(map->towns, this); +} + void CGTownInstance::reset() { CGTownInstance::merchantArtifacts.clear(); diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 5a0153f9c..088ca5183 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -342,6 +342,7 @@ public: std::string getObjectName() const override; void afterAddToMap(CMap * map) override; + void afterRemoveFromMap(CMap* map) override; static void reset(); inline bool isBattleOutsideTown(const CGHeroInstance * defendingHero) const diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 9f5d7b1da..26597a351 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -385,6 +385,11 @@ void CGObjectInstance::afterAddToMap(CMap * map) //nothing here } +void CGObjectInstance::afterRemoveFromMap(CMap* map) +{ + //nothing here +} + void CGObjectInstance::serializeJsonOptions(JsonSerializeFormat & handler) { //nothing here diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 851796db8..be8890b9b 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -197,6 +197,7 @@ public: void setProperty(ui8 what, ui32 val) override final; virtual void afterAddToMap(CMap * map); + virtual void afterRemoveFromMap(CMap* map); ///Entry point of binary (de-)serialization template void serialize(Handler &h, const int version) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index acf6d28ae..741ad4ba9 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1425,6 +1425,9 @@ void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) void CGArtifact::afterAddToMap(CMap * map) { + //Artifacts from map objects are never removed + //FIXME: This should be revertible in map editor + if(ID == Obj::SPELL_SCROLL && storedArtifact && storedArtifact->id.getNum() < 0) map->addNewArtifactInstance(storedArtifact); } diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index abf169695..01c851a20 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -233,7 +233,8 @@ CMapHeader::~CMapHeader() CMap::CMap() : checksum(0), grailPos(-1, -1, -1), grailRadius(0), terrain(nullptr), - guardingCreaturePositions(nullptr) + guardingCreaturePositions(nullptr), + uidCounter(0) { allHeroes.resize(allowedHeroes.size()); allowedAbilities = VLC->skillh->getDefaultAllowed(); @@ -593,6 +594,7 @@ void CMap::addNewArtifactInstance(CArtifactInstance * art) void CMap::eraseArtifactInstance(CArtifactInstance * art) { + //TODO: handle for artifacts removed in map editor assert(artInstances[art->id.getNum()] == art); artInstances[art->id.getNum()].dellNull(); } @@ -603,22 +605,49 @@ void CMap::addNewQuestInstance(CQuest* quest) quests.push_back(quest); } +void CMap::removeQuestInstance(CQuest* quest) +{ + //TODO: should be called only by map editor. + //During game, completed quests or quests from removed objects stay forever + + //Shift indexes + auto iter = std::next(quests.begin(), quest->qid); + iter = quests.erase(iter); + for (int i = quest->qid; iter != quests.end(); ++i, ++iter) + { + (*iter)->qid = i; + } +} + +void CMap::setUniqueInstanceName(CGObjectInstance* obj) +{ + //this gives object unique name even if objects are removed later + + auto uid = uidCounter++; + + boost::format fmt("%s_%d"); + fmt % obj->typeName % uid; + obj->instanceName = fmt.str(); +} + void CMap::addNewObject(CGObjectInstance * obj) { + if(obj->id != ObjectInstanceID((si32)objects.size())) throw std::runtime_error("Invalid object instance id"); if(obj->instanceName == "") throw std::runtime_error("Object instance name missing"); - auto it = instanceNames.find(obj->instanceName); - if(it != instanceNames.end()) + if (vstd::contains(instanceNames, obj->instanceName)) throw std::runtime_error("Object instance name duplicated: "+obj->instanceName); objects.push_back(obj); instanceNames[obj->instanceName] = obj; addBlockVisTiles(obj); + //TODO: how about deafeated heroes recruited again? + obj->afterAddToMap(this); } @@ -642,15 +671,9 @@ void CMap::removeObject(CGObjectInstance * obj) (*iter)->id = ObjectInstanceID(i); } - auto iterTown = std::find(towns.begin(), towns.end(), obj); - if(iterTown != towns.end()) - towns.erase(iterTown); - auto iterHero = std::find(allHeroes.begin(), allHeroes.end(), obj); - if(iterHero != allHeroes.end()) - allHeroes.erase(iterHero); - iterHero = std::find(heroesOnMap.begin(), heroesOnMap.end(), obj); - if(iterHero != heroesOnMap.end()) - heroesOnMap.erase(iterHero); + obj->afterRemoveFromMap(this); + + //TOOD: Clean artifact instances (mostly worn by hero?) and quests related to this object } void CMap::initTerrain() diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index d15ed8b24..2b93ab8a7 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -361,7 +361,9 @@ public: void eraseArtifactInstance(CArtifactInstance * art); void addNewQuestInstance(CQuest * quest); + void removeQuestInstance(CQuest* quest); + void setUniqueInstanceName(CGObjectInstance* obj); ///Use only this method when creating new map object instances void addNewObject(CGObjectInstance * obj); void moveObject(CGObjectInstance * obj, const int3 & dst); @@ -411,6 +413,7 @@ public: private: /// a 3-dimensional array of terrain tiles, access is as follows: x, y, level. where level=1 is underground TerrainTile*** terrain; + si32 uidCounter; //TODO: initialize when loading an old map public: template @@ -488,5 +491,14 @@ public: h & CGTownInstance::universitySkills; h & instanceNames; + + if (!h.saving && formatVersion < 804) + { + uidCounter = objects.size(); + } + else + { + h & uidCounter; + } } }; diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index 9e5dcf59e..faa2b9dc1 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -10,156 +10,18 @@ #include "StdInc.h" #include "CMapEditManager.h" -#include "../JsonNode.h" -#include "../filesystem/Filesystem.h" + #include "../mapObjects/CObjectClassesHandler.h" #include "../mapObjects/CGHeroInstance.h" #include "../VCMI_Lib.h" #include "CDrawRoadsOperation.h" #include "../mapping/CMap.h" - -MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0) -{ - -} - -MapRect::MapRect(int3 pos, si32 width, si32 height) : x(pos.x), y(pos.y), z(pos.z), width(width), height(height) -{ - -} - -MapRect MapRect::operator&(const MapRect & rect) const -{ - bool intersect = right() > rect.left() && rect.right() > left() && - bottom() > rect.top() && rect.bottom() > top() && - z == rect.z; - if(intersect) - { - MapRect ret; - ret.x = std::max(left(), rect.left()); - ret.y = std::max(top(), rect.top()); - ret.z = rect.z; - ret.width = std::min(right(), rect.right()) - ret.x; - ret.height = std::min(bottom(), rect.bottom()) - ret.y; - return ret; - } - else - { - return MapRect(); - } -} - -si32 MapRect::left() const -{ - return x; -} - -si32 MapRect::right() const -{ - return x + width; -} - -si32 MapRect::top() const -{ - return y; -} - -si32 MapRect::bottom() const -{ - return y + height; -} - -int3 MapRect::topLeft() const -{ - return int3(x, y, z); -} - -int3 MapRect::topRight() const -{ - return int3(right(), y, z); -} - -int3 MapRect::bottomLeft() const -{ - return int3(x, bottom(), z); -} - -int3 MapRect::bottomRight() const -{ - return int3(right(), bottom(), z); -} - -CTerrainSelection::CTerrainSelection(CMap * map) : CMapSelection(map) -{ - -} - -void CTerrainSelection::selectRange(const MapRect & rect) -{ - rect.forEach([this](const int3 pos) - { - this->select(pos); - }); -} - -void CTerrainSelection::deselectRange(const MapRect & rect) -{ - rect.forEach([this](const int3 pos) - { - this->deselect(pos); - }); -} - -void CTerrainSelection::setSelection(const std::vector & vec) -{ - for (auto pos : vec) - this->select(pos); -} - -void CTerrainSelection::selectAll() -{ - selectRange(MapRect(int3(0, 0, 0), getMap()->width, getMap()->height)); - selectRange(MapRect(int3(0, 0, 1), getMap()->width, getMap()->height)); -} - -void CTerrainSelection::clearSelection() -{ - deselectRange(MapRect(int3(0, 0, 0), getMap()->width, getMap()->height)); - deselectRange(MapRect(int3(0, 0, 1), getMap()->width, getMap()->height)); -} - -CObjectSelection::CObjectSelection(CMap * map) : CMapSelection(map) -{ - -} - -CMapOperation::CMapOperation(CMap * map) : map(map) -{ - -} - -std::string CMapOperation::getLabel() const -{ - return ""; -} - - -MapRect CMapOperation::extendTileAround(const int3 & centerPos) const -{ - return MapRect(int3(centerPos.x - 1, centerPos.y - 1, centerPos.z), 3, 3); -} - -MapRect CMapOperation::extendTileAroundSafely(const int3 & centerPos) const -{ - return extendTileAround(centerPos) & MapRect(int3(0, 0, centerPos.z), map->width, map->height); -} - +#include "CMapOperation.h" CMapUndoManager::CMapUndoManager() : - undoRedoLimit(10), + undoRedoLimit(100000), //not sure if we ever need to bother about undo limit undoCallback([](bool, bool) {}) { - //TODO: unlimited undo } void CMapUndoManager::undo() @@ -190,6 +52,7 @@ void CMapUndoManager::setUndoRedoLimit(int value) assert(value >= 0); undoStack.resize(std::min(undoStack.size(), static_cast(value))); redoStack.resize(std::min(redoStack.size(), static_cast(value))); + onUndoRedo(); } const CMapOperation * CMapUndoManager::peekRedo() const @@ -207,6 +70,7 @@ void CMapUndoManager::addOperation(std::unique_ptr && operation) undoStack.push_front(std::move(operation)); if(undoStack.size() > undoRedoLimit) undoStack.pop_back(); redoStack.clear(); + onUndoRedo(); } void CMapUndoManager::doOperation(TStack & fromStack, TStack & toStack, bool doUndo) @@ -285,6 +149,16 @@ void CMapEditManager::insertObject(CGObjectInstance * obj) execute(make_unique(map, obj)); } +void CMapEditManager::insertObjects(std::set& objects) +{ + auto composedOperation = make_unique(map); + for (auto obj : objects) + { + composedOperation->addOperation(make_unique(map, obj)); + } + execute(std::move(composedOperation)); +} + void CMapEditManager::moveObject(CGObjectInstance * obj, const int3 & pos) { execute(make_unique(map, obj, pos)); @@ -295,6 +169,16 @@ void CMapEditManager::removeObject(CGObjectInstance * obj) execute(make_unique(map, obj)); } +void CMapEditManager::removeObjects(std::set & objects) +{ + auto composedOperation = make_unique(map); + for (auto obj : objects) + { + composedOperation->addOperation(make_unique(map, obj)); + } + execute(std::move(composedOperation)); +} + void CMapEditManager::execute(std::unique_ptr && operation) { operation->execute(); @@ -314,845 +198,4 @@ CObjectSelection & CMapEditManager::getObjectSelection() CMapUndoManager & CMapEditManager::getUndoManager() { return undoManager; -} - -CComposedOperation::CComposedOperation(CMap * map) : CMapOperation(map) -{ - -} - -void CComposedOperation::execute() -{ - for(auto & operation : operations) - { - operation->execute(); - } -} - -void CComposedOperation::undo() -{ - for(auto & operation : operations) - { - operation->undo(); - } -} - -void CComposedOperation::redo() -{ - //TODO: double-chekcif the order is correct - for(auto & operation : operations) - { - operation->redo(); - } -} - -void CComposedOperation::addOperation(std::unique_ptr && operation) -{ - operations.push_back(std::move(operation)); -} - -const std::string TerrainViewPattern::FLIP_MODE_DIFF_IMAGES = "D"; - -const std::string TerrainViewPattern::RULE_DIRT = "D"; -const std::string TerrainViewPattern::RULE_SAND = "S"; -const std::string TerrainViewPattern::RULE_TRANSITION = "T"; -const std::string TerrainViewPattern::RULE_NATIVE = "N"; -const std::string TerrainViewPattern::RULE_NATIVE_STRONG = "N!"; -const std::string TerrainViewPattern::RULE_ANY = "?"; - -TerrainViewPattern::TerrainViewPattern() : diffImages(false), rotationTypesCount(0), minPoints(0) -{ - maxPoints = std::numeric_limits::max(); -} - -TerrainViewPattern::WeightedRule::WeightedRule(std::string &Name) : points(0), name(Name) -{ - standardRule = (TerrainViewPattern::RULE_ANY == Name || TerrainViewPattern::RULE_DIRT == Name - || TerrainViewPattern::RULE_NATIVE == Name || TerrainViewPattern::RULE_SAND == Name - || TerrainViewPattern::RULE_TRANSITION == Name || TerrainViewPattern::RULE_NATIVE_STRONG == Name); - anyRule = (Name == TerrainViewPattern::RULE_ANY); - dirtRule = (Name == TerrainViewPattern::RULE_DIRT); - sandRule = (Name == TerrainViewPattern::RULE_SAND); - transitionRule = (Name == TerrainViewPattern::RULE_TRANSITION); - nativeStrongRule = (Name == TerrainViewPattern::RULE_NATIVE_STRONG); - nativeRule = (Name == TerrainViewPattern::RULE_NATIVE); -} - -void TerrainViewPattern::WeightedRule::setNative() -{ - nativeRule = true; - standardRule = true; - //TODO: would look better as a bitfield - dirtRule = sandRule = transitionRule = nativeStrongRule = anyRule = false; //no idea what they mean, but look mutually exclusive -} - -CTerrainViewPatternConfig::CTerrainViewPatternConfig() -{ - const JsonNode config(ResourceID("config/terrainViewPatterns.json")); - static const std::string patternTypes[] = { "terrainView", "terrainType" }; - for(int i = 0; i < ARRAY_COUNT(patternTypes); ++i) - { - const auto & patternsVec = config[patternTypes[i]].Vector(); - for(const auto & ptrnNode : patternsVec) - { - TerrainViewPattern pattern; - - // Read pattern data - const JsonVector & data = ptrnNode["data"].Vector(); - assert(data.size() == 9); - for(int j = 0; j < data.size(); ++j) - { - std::string cell = data[j].String(); - boost::algorithm::erase_all(cell, " "); - std::vector rules; - boost::split(rules, cell, boost::is_any_of(",")); - for(std::string ruleStr : rules) - { - std::vector ruleParts; - boost::split(ruleParts, ruleStr, boost::is_any_of("-")); - TerrainViewPattern::WeightedRule rule(ruleParts[0]); - assert(!rule.name.empty()); - if(ruleParts.size() > 1) - { - rule.points = boost::lexical_cast(ruleParts[1]); - } - pattern.data[j].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 mapping - if(i == 0) - { - const auto & mappingStruct = ptrnNode["mapping"].Struct(); - for(const auto & mappingPair : mappingStruct) - { - TerrainViewPattern terGroupPattern = pattern; - auto mappingStr = mappingPair.second.String(); - boost::algorithm::erase_all(mappingStr, " "); - auto colonIndex = mappingStr.find_first_of(":"); - const auto & flipMode = mappingStr.substr(0, colonIndex); - terGroupPattern.diffImages = TerrainViewPattern::FLIP_MODE_DIFF_IMAGES == &(flipMode[flipMode.length() - 1]); - if(terGroupPattern.diffImages) - { - terGroupPattern.rotationTypesCount = boost::lexical_cast(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(",")); - for(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 - std::vector terrainViewPatternFlips; - terrainViewPatternFlips.push_back(terGroupPattern); - - for (int i = 1; i < 4; ++i) - { - //auto p = terGroupPattern; - flipPattern(terGroupPattern, i); //FIXME: we flip in place - doesn't make much sense now, but used to work - terrainViewPatternFlips.push_back(terGroupPattern); - } - - terrainViewPatterns[mappingPair.first].push_back(terrainViewPatternFlips); - } - } - else if(i == 1) - { - terrainTypePatterns[pattern.id].push_back(pattern); - for (int i = 1; i < 4; ++i) - { - //auto p = pattern; - flipPattern(pattern, i); ///FIXME: we flip in place - doesn't make much sense now - terrainTypePatterns[pattern.id].push_back(pattern); - } - } - } - } -} - -CTerrainViewPatternConfig::~CTerrainViewPatternConfig() -{ - -} - -const std::vector & CTerrainViewPatternConfig::getTerrainViewPatterns(const Terrain & terrain) const -{ - auto iter = terrainViewPatterns.find(Terrain::Manager::getInfo(terrain).terrainViewPatterns); - if(iter == terrainViewPatterns.end()) - return terrainViewPatterns.at("normal"); - return iter->second; -} - -boost::optional CTerrainViewPatternConfig::getTerrainViewPatternById(std::string patternId, const std::string & id) const -{ - auto iter = terrainViewPatterns.find(patternId); - const std::vector & groupPatterns = (iter == terrainViewPatterns.end()) ? terrainViewPatterns.at("normal") : iter->second; - - for (const TVPVector & patternFlips : groupPatterns) - { - const TerrainViewPattern & pattern = patternFlips.front(); - - if(id == pattern.id) - { - return boost::optional(pattern); - } - } - return boost::optional(); -} - -boost::optional CTerrainViewPatternConfig::getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const -{ - const std::vector & groupPatterns = getTerrainViewPatterns(terrain); - for (const TVPVector & patternFlips : groupPatterns) - { - const TerrainViewPattern & pattern = patternFlips.front(); - if (id == pattern.id) - { - return boost::optional(patternFlips); - } - } - return boost::optional(); -} - - -const CTerrainViewPatternConfig::TVPVector * CTerrainViewPatternConfig::getTerrainTypePatternById(const std::string & id) const -{ - auto it = terrainTypePatterns.find(id); - assert(it != terrainTypePatterns.end()); - return &(it->second); -} - -void CTerrainViewPatternConfig::flipPattern(TerrainViewPattern & pattern, int flip) const -{ - //flip in place to avoid expensive constructor. Seriously. - - if (flip == 0) - { - return; - } - - //always flip horizontal - for (int i = 0; i < 3; ++i) - { - int y = i * 3; - std::swap(pattern.data[y], pattern.data[y + 2]); - } - //flip vertical only at 2nd step - if (flip == CMapOperation::FLIP_PATTERN_VERTICAL) - { - for (int i = 0; i < 3; ++i) - { - std::swap(pattern.data[i], pattern.data[6 + i]); - } - } -} - - -CDrawTerrainOperation::CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, Terrain terType, CRandomGenerator * gen) - : CMapOperation(map), terrainSel(terrainSel), terType(terType), gen(gen) -{ - -} - -void CDrawTerrainOperation::execute() -{ - for(const auto & pos : terrainSel.getSelectedItems()) - { - auto & tile = map->getTile(pos); - tile.terType = terType; - invalidateTerrainViews(pos); - } - - updateTerrainTypes(); - updateTerrainViews(); -} - -void CDrawTerrainOperation::undo() -{ - //TODO -} - -void CDrawTerrainOperation::redo() -{ - //TODO -} - -std::string CDrawTerrainOperation::getLabel() const -{ - return "Draw Terrain"; -} - -void CDrawTerrainOperation::updateTerrainTypes() -{ - auto positions = terrainSel.getSelectedItems(); - while(!positions.empty()) - { - const auto & centerPos = *(positions.begin()); - auto centerTile = map->getTile(centerPos); - //logGlobal->debug("Set terrain tile at pos '%s' to type '%s'", centerPos, centerTile.terType); - auto tiles = getInvalidTiles(centerPos); - auto updateTerrainType = [&](const int3 & pos) - { - map->getTile(pos).terType = centerTile.terType; - positions.insert(pos); - invalidateTerrainViews(pos); - //logGlobal->debug("Set additional terrain tile at pos '%s' to type '%s'", pos, centerTile.terType); - }; - - // Fill foreign invalid tiles - for(const auto & tile : tiles.foreignTiles) - { - updateTerrainType(tile); - } - - tiles = getInvalidTiles(centerPos); - if(tiles.nativeTiles.find(centerPos) != tiles.nativeTiles.end()) - { - // Blow up - auto rect = extendTileAroundSafely(centerPos); - std::set suitableTiles; - int invalidForeignTilesCnt = std::numeric_limits::max(), invalidNativeTilesCnt = 0; - bool centerPosValid = false; - rect.forEach([&](const int3 & posToTest) - { - auto & terrainTile = map->getTile(posToTest); - if(centerTile.terType != terrainTile.terType) - { - auto formerTerType = terrainTile.terType; - terrainTile.terType = centerTile.terType; - auto testTile = getInvalidTiles(posToTest); - - int nativeTilesCntNorm = testTile.nativeTiles.empty() ? std::numeric_limits::max() : (int)testTile.nativeTiles.size(); - - bool putSuitableTile = false; - bool addToSuitableTiles = false; - if(testTile.centerPosValid) - { - if (!centerPosValid) - { - centerPosValid = true; - putSuitableTile = true; - } - else - { - if(testTile.foreignTiles.size() < invalidForeignTilesCnt) - { - putSuitableTile = true; - } - else - { - addToSuitableTiles = true; - } - } - } - else if (!centerPosValid) - { - if((nativeTilesCntNorm > invalidNativeTilesCnt) || - (nativeTilesCntNorm == invalidNativeTilesCnt && testTile.foreignTiles.size() < invalidForeignTilesCnt)) - { - putSuitableTile = true; - } - else if(nativeTilesCntNorm == invalidNativeTilesCnt && testTile.foreignTiles.size() == invalidForeignTilesCnt) - { - addToSuitableTiles = true; - } - } - - if (putSuitableTile) - { - //if(!suitableTiles.empty()) - //{ - // logGlobal->debug("Clear suitables tiles."); - //} - - invalidNativeTilesCnt = nativeTilesCntNorm; - invalidForeignTilesCnt = static_cast(testTile.foreignTiles.size()); - suitableTiles.clear(); - addToSuitableTiles = true; - } - - if (addToSuitableTiles) - { - suitableTiles.insert(posToTest); - } - - terrainTile.terType = formerTerType; - } - }); - - if(suitableTiles.size() == 1) - { - updateTerrainType(*suitableTiles.begin()); - } - else - { - static const int3 directions[] = { int3(0, -1, 0), int3(-1, 0, 0), int3(0, 1, 0), int3(1, 0, 0), - int3(-1, -1, 0), int3(-1, 1, 0), int3(1, 1, 0), int3(1, -1, 0)}; - for(auto & direction : directions) - { - auto it = suitableTiles.find(centerPos + direction); - if(it != suitableTiles.end()) - { - updateTerrainType(*it); - break; - } - } - } - } - else - { - // add invalid native tiles which are not in the positions list - for(const auto & nativeTile : tiles.nativeTiles) - { - if(positions.find(nativeTile) == positions.end()) - { - positions.insert(nativeTile); - } - } - - positions.erase(centerPos); - } - } -} - -void CDrawTerrainOperation::updateTerrainViews() -{ - for(const auto & pos : invalidatedTerViews) - { - const auto & patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType); - - // Detect a pattern which fits best - int bestPattern = -1; - ValidationResult valRslt(false); - for(int k = 0; k < patterns.size(); ++k) - { - const auto & pattern = patterns[k]; - //(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) - valRslt = validateTerrainView(pos, &pattern); - if(valRslt.result) - { - bestPattern = k; - break; - } - } - //assert(bestPattern != -1); - if(bestPattern == -1) - { - // This shouldn't be the case - logGlobal->warn("No pattern detected at pos '%s'.", pos.toString()); - CTerrainViewPatternUtils::printDebuggingInfoAboutTile(map, pos); - continue; - } - - // Get mapping - const TerrainViewPattern & pattern = patterns[bestPattern][valRslt.flip]; - std::pair mapping; - if(valRslt.transitionReplacement.empty()) - { - mapping = pattern.mapping[0]; - } - else - { - mapping = valRslt.transitionReplacement == TerrainViewPattern::RULE_DIRT ? pattern.mapping[0] : pattern.mapping[1]; - } - - // Set terrain view - auto & tile = map->getTile(pos); - if(!pattern.diffImages) - { - tile.terView = gen->nextInt(mapping.first, mapping.second); - tile.extTileFlags = valRslt.flip; - } - else - { - const int framesPerRot = (mapping.second - mapping.first + 1) / pattern.rotationTypesCount; - int flip = (pattern.rotationTypesCount == 2 && valRslt.flip == 2) ? 1 : valRslt.flip; - int firstFrame = mapping.first + flip * framesPerRot; - tile.terView = gen->nextInt(firstFrame, firstFrame + framesPerRot - 1); - tile.extTileFlags = 0; - } - } -} - -CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainView(const int3 & pos, const std::vector * pattern, int recDepth) const -{ - for(int flip = 0; flip < 4; ++flip) - { - auto valRslt = validateTerrainViewInner(pos, pattern->at(flip), recDepth); - if(valRslt.result) - { - valRslt.flip = flip; - return valRslt; - } - } - return ValidationResult(false); -} - -CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth) const -{ - auto centerTerType = map->getTile(pos).terType; - int totalPoints = 0; - std::string transitionReplacement; - - for(int i = 0; i < 9; ++i) - { - // The center, middle cell can be skipped - if(i == 4) - { - continue; - } - - // Get terrain group of the current cell - int cx = pos.x + (i % 3) - 1; - int cy = pos.y + (i / 3) - 1; - int3 currentPos(cx, cy, pos.z); - bool isAlien = false; - Terrain terType; - if(!map->isInTheMap(currentPos)) - { - // position is not in the map, so take the ter type from the neighbor tile - bool widthTooHigh = currentPos.x >= map->width; - bool widthTooLess = currentPos.x < 0; - bool heightTooHigh = currentPos.y >= map->height; - bool heightTooLess = currentPos.y < 0; - - if ((widthTooHigh && heightTooHigh) || (widthTooHigh && heightTooLess) || (widthTooLess && heightTooHigh) || (widthTooLess && heightTooLess)) - { - terType = centerTerType; - } - else if(widthTooHigh) - { - terType = map->getTile(int3(currentPos.x - 1, currentPos.y, currentPos.z)).terType; - } - else if(heightTooHigh) - { - terType = map->getTile(int3(currentPos.x, currentPos.y - 1, currentPos.z)).terType; - } - else if (widthTooLess) - { - terType = map->getTile(int3(currentPos.x + 1, currentPos.y, currentPos.z)).terType; - } - else if (heightTooLess) - { - terType = map->getTile(int3(currentPos.x, currentPos.y + 1, currentPos.z)).terType; - } - } - else - { - terType = map->getTile(currentPos).terType; - if(terType != centerTerType && (terType.isPassable() || centerTerType.isPassable())) - { - isAlien = true; - } - } - - // Validate all rules per cell - int topPoints = -1; - for(auto & elem : pattern.data[i]) - { - TerrainViewPattern::WeightedRule rule = elem; - if(!rule.isStandardRule()) - { - if(recDepth == 0 && map->isInTheMap(currentPos)) - { - if(terType == centerTerType) - { - const auto & patternForRule = VLC->terviewh->getTerrainViewPatternsById(centerTerType, rule.name); - if(auto p = patternForRule) - { - auto rslt = validateTerrainView(currentPos, &(*p), 1); - if(rslt.result) topPoints = std::max(topPoints, rule.points); - } - } - continue; - } - else - { - rule.setNative(); - } - } - - auto applyValidationRslt = [&](bool rslt) - { - if(rslt) - { - topPoints = std::max(topPoints, rule.points); - } - }; - - // Validate cell with the ruleset of the pattern - bool nativeTestOk, nativeTestStrongOk; - nativeTestOk = nativeTestStrongOk = (rule.isNativeStrong() || rule.isNativeRule()) && !isAlien; - - if(centerTerType == Terrain("dirt")) - { - nativeTestOk = rule.isNativeRule() && !terType.isTransitionRequired(); - bool sandTestOk = (rule.isSandRule() || rule.isTransition()) - && terType.isTransitionRequired(); - applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk); - } - else if(centerTerType == Terrain("sand")) - { - applyValidationRslt(true); - } - else if(centerTerType.isTransitionRequired()) //water, rock and some special terrains require sand transition - { - bool sandTestOk = (rule.isSandRule() || rule.isTransition()) - && isAlien; - applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk); - } - else - { - bool dirtTestOk = (rule.isDirtRule() || rule.isTransition()) - && isAlien && !terType.isTransitionRequired(); - bool sandTestOk = (rule.isSandRule() || rule.isTransition()) - && terType.isTransitionRequired(); - - if (transitionReplacement.empty() && rule.isTransition() - && (dirtTestOk || sandTestOk)) - { - transitionReplacement = dirtTestOk ? TerrainViewPattern::RULE_DIRT : TerrainViewPattern::RULE_SAND; - } - if (rule.isTransition()) - { - applyValidationRslt((dirtTestOk && transitionReplacement != TerrainViewPattern::RULE_SAND) || - (sandTestOk && transitionReplacement != TerrainViewPattern::RULE_DIRT)); - } - else - { - applyValidationRslt(rule.isAnyRule() || dirtTestOk || sandTestOk || nativeTestOk); - } - } - } - - if(topPoints == -1) - { - return ValidationResult(false); - } - else - { - totalPoints += topPoints; - } - } - - if(totalPoints >= pattern.minPoints && totalPoints <= pattern.maxPoints) - { - return ValidationResult(true, transitionReplacement); - } - else - { - return ValidationResult(false); - } -} - -void CDrawTerrainOperation::invalidateTerrainViews(const int3 & centerPos) -{ - auto rect = extendTileAroundSafely(centerPos); - rect.forEach([&](const int3 & pos) - { - invalidatedTerViews.insert(pos); - }); -} - -CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const int3 & centerPos) const -{ - //TODO: this is very expensive function for RMG, needs optimization - InvalidTiles tiles; - auto centerTerType = map->getTile(centerPos).terType; - auto rect = extendTileAround(centerPos); - rect.forEach([&](const int3 & pos) - { - if(map->isInTheMap(pos)) - { - auto ptrConfig = VLC->terviewh; - auto terType = map->getTile(pos).terType; - auto valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById("n1")).result; - - // Special validity check for rock & water - if(valid && (terType.isWater() || !terType.isPassable())) - { - static const std::string patternIds[] = { "s1", "s2" }; - for(auto & patternId : patternIds) - { - valid = !validateTerrainView(pos, ptrConfig->getTerrainTypePatternById(patternId)).result; - if(!valid) break; - } - } - // Additional validity check for non rock OR water - else if(!valid && (terType.isLand() && terType.isPassable())) - { - static const std::string patternIds[] = { "n2", "n3" }; - for(auto & patternId : patternIds) - { - valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById(patternId)).result; - if(valid) break; - } - } - - if(!valid) - { - if(terType == centerTerType) tiles.nativeTiles.insert(pos); - else tiles.foreignTiles.insert(pos); - } - else if(centerPos == pos) - { - tiles.centerPosValid = true; - } - } - }); - return tiles; -} - -CDrawTerrainOperation::ValidationResult::ValidationResult(bool result, const std::string & transitionReplacement) - : result(result), transitionReplacement(transitionReplacement), flip(0) -{ - -} - -void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap * map, int3 pos) -{ - logGlobal->debug("Printing detailed info about nearby map tiles of pos '%s'", pos.toString()); - for(int y = pos.y - 2; y <= pos.y + 2; ++y) - { - std::string line; - const int PADDED_LENGTH = 10; - for(int x = pos.x - 2; x <= pos.x + 2; ++x) - { - auto debugPos = int3(x, y, pos.z); - if(map->isInTheMap(debugPos)) - { - auto debugTile = map->getTile(debugPos); - - std::string terType = static_cast(debugTile.terType).substr(0, 6); - line += terType; - line.insert(line.end(), PADDED_LENGTH - terType.size(), ' '); - } - else - { - line += "X"; - line.insert(line.end(), PADDED_LENGTH - 1, ' '); - } - } - - logGlobal->debug(line); - } -} - -CClearTerrainOperation::CClearTerrainOperation(CMap * map, CRandomGenerator * gen) : CComposedOperation(map) -{ - CTerrainSelection terrainSel(map); - terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height)); - addOperation(make_unique(map, terrainSel, Terrain("water"), gen)); - if(map->twoLevel) - { - terrainSel.clearSelection(); - terrainSel.selectRange(MapRect(int3(0, 0, 1), map->width, map->height)); - addOperation(make_unique(map, terrainSel, Terrain("rock"), gen)); - } -} - -std::string CClearTerrainOperation::getLabel() const -{ - return "Clear Terrain"; -} - -CInsertObjectOperation::CInsertObjectOperation(CMap * map, CGObjectInstance * obj) - : CMapOperation(map), obj(obj) -{ - -} - -void CInsertObjectOperation::execute() -{ - obj->id = ObjectInstanceID((si32)map->objects.size()); - - boost::format fmt("%s_%d"); - fmt % obj->typeName % obj->id.getNum(); - obj->instanceName = fmt.str(); - - map->addNewObject(obj); -} - -void CInsertObjectOperation::undo() -{ - map->removeObject(obj); -} - -void CInsertObjectOperation::redo() -{ - execute(); -} - -std::string CInsertObjectOperation::getLabel() const -{ - return "Insert Object"; -} - -CMoveObjectOperation::CMoveObjectOperation(CMap * map, CGObjectInstance * obj, const int3 & targetPosition) - : CMapOperation(map), - obj(obj), - initialPos(obj->pos), - targetPos(targetPosition) -{ -} - -void CMoveObjectOperation::execute() -{ - map->moveObject(obj, targetPos); - logGlobal->debug("Moved object %s to position %s", obj->instanceName, targetPos.toString()); -} - -void CMoveObjectOperation::undo() -{ - map->moveObject(obj, initialPos); - logGlobal->debug("Moved object %s back to position %s", obj->instanceName, initialPos.toString()); -} - -void CMoveObjectOperation::redo() -{ - execute(); -} - -std::string CMoveObjectOperation::getLabel() const -{ - return "Move Object"; -} - -CRemoveObjectOperation::CRemoveObjectOperation(CMap * map, CGObjectInstance * obj) - : CMapOperation(map), obj(obj) -{ - -} - -void CRemoveObjectOperation::execute() -{ - map->removeObject(obj); -} - -void CRemoveObjectOperation::undo() -{ - //TODO -} - -void CRemoveObjectOperation::redo() -{ - execute(); -} - -std::string CRemoveObjectOperation::getLabel() const -{ - return "Remove Object"; -} +} \ No newline at end of file diff --git a/lib/mapping/CMapEditManager.h b/lib/mapping/CMapEditManager.h index 5cebfdc91..93dcaf705 100644 --- a/lib/mapping/CMapEditManager.h +++ b/lib/mapping/CMapEditManager.h @@ -10,122 +10,14 @@ #pragma once -#include "../CRandomGenerator.h" -#include "../int3.h" #include "../GameConstants.h" -#include "Terrain.h" +#include "CMapOperation.h" class CGObjectInstance; class CTerrainViewPatternConfig; struct TerrainViewPattern; class CMap; -/// Represents a map rectangle. -struct DLL_LINKAGE MapRect -{ - MapRect(); - MapRect(int3 pos, si32 width, si32 height); - si32 x, y, z; - si32 width, height; - - si32 left() const; - si32 right() const; - si32 top() const; - si32 bottom() const; - - int3 topLeft() const; /// Top left corner of this rect. - int3 topRight() const; /// Top right corner of this rect. - int3 bottomLeft() const; /// Bottom left corner of this rect. - int3 bottomRight() const; /// Bottom right corner of this rect. - - /// Returns a MapRect of the intersection of this rectangle and the given one. - MapRect operator&(const MapRect & rect) const; - - template - void forEach(Func f) const - { - for(int j = y; j < bottom(); ++j) - { - for(int i = x; i < right(); ++i) - { - f(int3(i, j, z)); - } - } - } -}; - -/// Generic selection class to select any type -template -class DLL_LINKAGE CMapSelection -{ -public: - explicit CMapSelection(CMap * map) : map(map) { } - virtual ~CMapSelection() { }; - void select(const T & item) - { - selectedItems.insert(item); - } - void deselect(const T & item) - { - selectedItems.erase(item); - } - std::set getSelectedItems() - { - return selectedItems; - } - CMap * getMap() { return map; } - virtual void selectRange(const MapRect & rect) { } - virtual void deselectRange(const MapRect & rect) { } - virtual void selectAll() { } - virtual void clearSelection() { } - -private: - std::set selectedItems; - CMap * map; -}; - -/// Selection class to select terrain. -class DLL_LINKAGE CTerrainSelection : public CMapSelection -{ -public: - explicit CTerrainSelection(CMap * map); - void selectRange(const MapRect & rect) override; - void deselectRange(const MapRect & rect) override; - void selectAll() override; - void clearSelection() override; - void setSelection(const std::vector & vec); -}; - -/// Selection class to select objects. -class DLL_LINKAGE CObjectSelection: public CMapSelection -{ -public: - explicit CObjectSelection(CMap * map); -}; - -/// The abstract base class CMapOperation defines an operation that can be executed, undone and redone. -class DLL_LINKAGE CMapOperation : public boost::noncopyable -{ -public: - explicit CMapOperation(CMap * map); - virtual ~CMapOperation() { }; - - virtual void execute() = 0; - virtual void undo() = 0; - virtual void redo() = 0; - virtual std::string getLabel() const = 0; /// Returns a display-able name of the operation. - - static const int FLIP_PATTERN_HORIZONTAL = 1; - static const int FLIP_PATTERN_VERTICAL = 2; - static const int FLIP_PATTERN_BOTH = 3; - -protected: - MapRect extendTileAround(const int3 & centerPos) const; - MapRect extendTileAroundSafely(const int3 & centerPos) const; /// doesn't exceed map size - - CMap * map; -}; - /// The CMapUndoManager provides the functionality to save operations and undo/redo them. class DLL_LINKAGE CMapUndoManager : boost::noncopyable { @@ -186,8 +78,10 @@ public: void drawRiver(const std::string & riverType, CRandomGenerator * gen = nullptr); void insertObject(CGObjectInstance * obj); + void insertObjects(std::set& objects); void moveObject(CGObjectInstance * obj, const int3 & pos); void removeObject(CGObjectInstance * obj); + void removeObjects(std::set& objects); CTerrainSelection & getTerrainSelection(); CObjectSelection & getObjectSelection(); @@ -203,260 +97,3 @@ private: CTerrainSelection terrainSel; CObjectSelection objectSel; }; - -/* ---------------------------------------------------------------------------- */ -/* Implementation/Detail classes, Private API */ -/* ---------------------------------------------------------------------------- */ - -/// The CComposedOperation is an operation which consists of several operations. -class CComposedOperation : public CMapOperation -{ -public: - CComposedOperation(CMap * map); - - void execute() override; - void undo() override; - void redo() override; - - void addOperation(std::unique_ptr && operation); - -private: - std::list > operations; -}; - -/// The terrain view pattern describes a specific composition of terrain tiles -/// in a 3x3 matrix and notes which terrain view frame numbers can be used. -struct DLL_LINKAGE TerrainViewPattern -{ - struct WeightedRule - { - WeightedRule(std::string &Name); - /// Gets true if this rule is a standard rule which means that it has a value of one of the RULE_* constants. - inline bool isStandardRule() const - { - return standardRule; - } - inline bool isAnyRule() const - { - return anyRule; - } - inline bool isDirtRule() const - { - return dirtRule; - } - inline bool isSandRule() const - { - return sandRule; - } - inline bool isTransition() const - { - return transitionRule; - } - inline bool isNativeStrong() const - { - return nativeStrongRule; - } - inline bool isNativeRule() const - { - return nativeRule; - } - void setNative(); - - /// The name of the rule. Can be any value of the RULE_* constants or a ID of a another pattern. - //FIXME: remove string variable altogether, use only in constructor - std::string name; - /// Optional. A rule can have points. Patterns may have a minimum count of points to reach to be successful. - int points; - - private: - bool standardRule; - bool anyRule; - bool dirtRule; - bool sandRule; - bool transitionRule; - bool nativeStrongRule; - bool nativeRule; - - WeightedRule(); //only allow string constructor - }; - - 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. - static const std::string RULE_DIRT; - /// Constant for the rule sand, meaning a sandy border is required. - 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 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; - - TerrainViewPattern(); - - /// The pattern data can be visualized as a 3x3 matrix: - /// [ ][ ][ ] - /// [ ][ ][ ] - /// [ ][ ][ ] - /// - /// The box in the center belongs always to the native terrain type and - /// is the point of origin. Depending on the terrain type different rules - /// can be used. Their meaning differs also from type to type. - /// - /// std::vector -> several rules can be used in one cell - std::array, PATTERN_DATA_SIZE> data; - - /// The identifier of the pattern, if it's referenced from a another pattern. - std::string id; - - /// This describes the mapping between this pattern and the corresponding range of frames - /// which should be used for the ter view. - /// - /// std::vector -> size=1: typical, size=2: if this pattern should map to two different types of borders - /// std::pair -> 1st value: lower range, 2nd value: upper range - std::vector > mapping; - /// If diffImages is true, different images/frames are used to place a rotated terrain view. If it's false - /// the same frame will be used and rotated. - bool diffImages; - /// The rotationTypesCount is only used if diffImages is true and holds the number how many rotation types(horizontal, etc...) - /// are supported. - int rotationTypesCount; - - /// The minimum and maximum points to reach to validate the pattern successfully. - int minPoints, maxPoints; -}; - -/// The terrain view pattern config loads pattern data from the filesystem. -class DLL_LINKAGE CTerrainViewPatternConfig : public boost::noncopyable -{ -public: - typedef std::vector TVPVector; - - CTerrainViewPatternConfig(); - ~CTerrainViewPatternConfig(); - - const std::vector & getTerrainViewPatterns(const Terrain & terrain) const; - boost::optional getTerrainViewPatternById(std::string patternId, const std::string & id) const; - boost::optional getTerrainViewPatternsById(const Terrain & terrain, const std::string & id) const; - const TVPVector * getTerrainTypePatternById(const std::string & id) const; - void flipPattern(TerrainViewPattern & pattern, int flip) const; - -private: - std::map > terrainViewPatterns; - std::map terrainTypePatterns; -}; - -/// The CDrawTerrainOperation class draws a terrain area on the map. -class CDrawTerrainOperation : public CMapOperation -{ -public: - CDrawTerrainOperation(CMap * map, const CTerrainSelection & terrainSel, Terrain terType, CRandomGenerator * gen); - - void execute() override; - void undo() override; - void redo() override; - std::string getLabel() const override; - -private: - struct ValidationResult - { - ValidationResult(bool result, const std::string & transitionReplacement = ""); - - bool result; - /// The replacement of a T rule, either D or S. - std::string transitionReplacement; - int flip; - }; - - struct InvalidTiles - { - std::set foreignTiles, nativeTiles; - bool centerPosValid; - - InvalidTiles() : centerPosValid(false) { } - }; - - void updateTerrainTypes(); - void invalidateTerrainViews(const int3 & centerPos); - InvalidTiles getInvalidTiles(const int3 & centerPos) const; - - void updateTerrainViews(); - /// Validates the terrain view of the given position and with the given pattern. The first method wraps the - /// second method to validate the terrain view with the given pattern in all four flip directions(horizontal, vertical). - ValidationResult validateTerrainView(const int3 & pos, const std::vector * pattern, int recDepth = 0) const; - ValidationResult validateTerrainViewInner(const int3 & pos, const TerrainViewPattern & pattern, int recDepth = 0) const; - - CTerrainSelection terrainSel; - Terrain terType; - CRandomGenerator * gen; - std::set invalidatedTerViews; -}; - -class DLL_LINKAGE CTerrainViewPatternUtils -{ -public: - static void printDebuggingInfoAboutTile(const CMap * map, int3 pos); -}; - -/// The CClearTerrainOperation clears+initializes the terrain. -class CClearTerrainOperation : public CComposedOperation -{ -public: - CClearTerrainOperation(CMap * map, CRandomGenerator * gen); - - std::string getLabel() const override; - -private: - -}; - -/// The CInsertObjectOperation class inserts an object to the map. -class CInsertObjectOperation : public CMapOperation -{ -public: - CInsertObjectOperation(CMap * map, CGObjectInstance * obj); - - void execute() override; - void undo() override; - void redo() override; - std::string getLabel() const override; - -private: - CGObjectInstance * obj; -}; - -/// The CMoveObjectOperation class moves object to another position -class CMoveObjectOperation : public CMapOperation -{ -public: - CMoveObjectOperation(CMap * map, CGObjectInstance * obj, const int3 & targetPosition); - - void execute() override; - void undo() override; - void redo() override; - std::string getLabel() const override; - -private: - CGObjectInstance * obj; - int3 initialPos; - int3 targetPos; -}; - -/// The CRemoveObjectOperation class removes object from the map -class CRemoveObjectOperation : public CMapOperation -{ -public: - CRemoveObjectOperation(CMap * map, CGObjectInstance * obj); - - void execute() override; - void undo() override; - void redo() override; - std::string getLabel() const override; - -private: - CGObjectInstance * obj; -}; diff --git a/lib/mapping/CMapOperation.cpp b/lib/mapping/CMapOperation.cpp new file mode 100644 index 000000000..c2faf2fec --- /dev/null +++ b/lib/mapping/CMapOperation.cpp @@ -0,0 +1,671 @@ +/* + * CMapOperation.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 "CMapOperation.h" + +#include "../VCMI_Lib.h" +#include "CMap.h" +#include "MapEditUtils.h" + +CMapOperation::CMapOperation(CMap* map) : map(map) +{ + +} + +std::string CMapOperation::getLabel() const +{ + return ""; +} + + +MapRect CMapOperation::extendTileAround(const int3& centerPos) const +{ + return MapRect(int3(centerPos.x - 1, centerPos.y - 1, centerPos.z), 3, 3); +} + +MapRect CMapOperation::extendTileAroundSafely(const int3& centerPos) const +{ + return extendTileAround(centerPos) & MapRect(int3(0, 0, centerPos.z), map->width, map->height); +} + +CComposedOperation::CComposedOperation(CMap* map) : CMapOperation(map) +{ + +} + +void CComposedOperation::execute() +{ + for (auto& operation : operations) + { + operation->execute(); + } +} + +void CComposedOperation::undo() +{ + //reverse order + for (auto operation = operations.rbegin(); operation != operations.rend(); operation++) + { + operation->get()->undo(); + } +} + +void CComposedOperation::redo() +{ + for (auto& operation : operations) + { + operation->redo(); + } +} + +std::string CComposedOperation::getLabel() const +{ + std::string ret = "Composed operation: "; + for (auto& operation : operations) + { + ret.append(operation->getLabel() + ";"); + } + return ret; +} + +void CComposedOperation::addOperation(std::unique_ptr&& operation) +{ + operations.push_back(std::move(operation)); +} + +CDrawTerrainOperation::CDrawTerrainOperation(CMap* map, const CTerrainSelection& terrainSel, Terrain terType, CRandomGenerator* gen) + : CMapOperation(map), terrainSel(terrainSel), terType(terType), gen(gen) +{ + +} + +void CDrawTerrainOperation::execute() +{ + for (const auto& pos : terrainSel.getSelectedItems()) + { + auto& tile = map->getTile(pos); + tile.terType = terType; + invalidateTerrainViews(pos); + } + + updateTerrainTypes(); + updateTerrainViews(); +} + +void CDrawTerrainOperation::undo() +{ + //TODO +} + +void CDrawTerrainOperation::redo() +{ + //TODO +} + +std::string CDrawTerrainOperation::getLabel() const +{ + return "Draw Terrain"; +} + +void CDrawTerrainOperation::updateTerrainTypes() +{ + auto positions = terrainSel.getSelectedItems(); + while (!positions.empty()) + { + const auto& centerPos = *(positions.begin()); + auto centerTile = map->getTile(centerPos); + //logGlobal->debug("Set terrain tile at pos '%s' to type '%s'", centerPos, centerTile.terType); + auto tiles = getInvalidTiles(centerPos); + auto updateTerrainType = [&](const int3& pos) + { + map->getTile(pos).terType = centerTile.terType; + positions.insert(pos); + invalidateTerrainViews(pos); + //logGlobal->debug("Set additional terrain tile at pos '%s' to type '%s'", pos, centerTile.terType); + }; + + // Fill foreign invalid tiles + for (const auto& tile : tiles.foreignTiles) + { + updateTerrainType(tile); + } + + tiles = getInvalidTiles(centerPos); + if (tiles.nativeTiles.find(centerPos) != tiles.nativeTiles.end()) + { + // Blow up + auto rect = extendTileAroundSafely(centerPos); + std::set suitableTiles; + int invalidForeignTilesCnt = std::numeric_limits::max(), invalidNativeTilesCnt = 0; + bool centerPosValid = false; + rect.forEach([&](const int3& posToTest) + { + auto& terrainTile = map->getTile(posToTest); + if (centerTile.terType != terrainTile.terType) + { + auto formerTerType = terrainTile.terType; + terrainTile.terType = centerTile.terType; + auto testTile = getInvalidTiles(posToTest); + + int nativeTilesCntNorm = testTile.nativeTiles.empty() ? std::numeric_limits::max() : (int)testTile.nativeTiles.size(); + + bool putSuitableTile = false; + bool addToSuitableTiles = false; + if (testTile.centerPosValid) + { + if (!centerPosValid) + { + centerPosValid = true; + putSuitableTile = true; + } + else + { + if (testTile.foreignTiles.size() < invalidForeignTilesCnt) + { + putSuitableTile = true; + } + else + { + addToSuitableTiles = true; + } + } + } + else if (!centerPosValid) + { + if ((nativeTilesCntNorm > invalidNativeTilesCnt) || + (nativeTilesCntNorm == invalidNativeTilesCnt && testTile.foreignTiles.size() < invalidForeignTilesCnt)) + { + putSuitableTile = true; + } + else if (nativeTilesCntNorm == invalidNativeTilesCnt && testTile.foreignTiles.size() == invalidForeignTilesCnt) + { + addToSuitableTiles = true; + } + } + + if (putSuitableTile) + { + //if(!suitableTiles.empty()) + //{ + // logGlobal->debug("Clear suitables tiles."); + //} + + invalidNativeTilesCnt = nativeTilesCntNorm; + invalidForeignTilesCnt = static_cast(testTile.foreignTiles.size()); + suitableTiles.clear(); + addToSuitableTiles = true; + } + + if (addToSuitableTiles) + { + suitableTiles.insert(posToTest); + } + + terrainTile.terType = formerTerType; + } + }); + + if (suitableTiles.size() == 1) + { + updateTerrainType(*suitableTiles.begin()); + } + else + { + static const int3 directions[] = { int3(0, -1, 0), int3(-1, 0, 0), int3(0, 1, 0), int3(1, 0, 0), + int3(-1, -1, 0), int3(-1, 1, 0), int3(1, 1, 0), int3(1, -1, 0) }; + for (auto& direction : directions) + { + auto it = suitableTiles.find(centerPos + direction); + if (it != suitableTiles.end()) + { + updateTerrainType(*it); + break; + } + } + } + } + else + { + // add invalid native tiles which are not in the positions list + for (const auto& nativeTile : tiles.nativeTiles) + { + if (positions.find(nativeTile) == positions.end()) + { + positions.insert(nativeTile); + } + } + + positions.erase(centerPos); + } + } +} + +void CDrawTerrainOperation::updateTerrainViews() +{ + for (const auto& pos : invalidatedTerViews) + { + const auto& patterns = VLC->terviewh->getTerrainViewPatterns(map->getTile(pos).terType); + + // Detect a pattern which fits best + int bestPattern = -1; + ValidationResult valRslt(false); + for (int k = 0; k < patterns.size(); ++k) + { + const auto& pattern = patterns[k]; + //(ETerrainGroup::ETerrainGroup terGroup, const std::string & id) + valRslt = validateTerrainView(pos, &pattern); + if (valRslt.result) + { + bestPattern = k; + break; + } + } + //assert(bestPattern != -1); + if (bestPattern == -1) + { + // This shouldn't be the case + logGlobal->warn("No pattern detected at pos '%s'.", pos.toString()); + CTerrainViewPatternUtils::printDebuggingInfoAboutTile(map, pos); + continue; + } + + // Get mapping + const TerrainViewPattern& pattern = patterns[bestPattern][valRslt.flip]; + std::pair mapping; + if (valRslt.transitionReplacement.empty()) + { + mapping = pattern.mapping[0]; + } + else + { + mapping = valRslt.transitionReplacement == TerrainViewPattern::RULE_DIRT ? pattern.mapping[0] : pattern.mapping[1]; + } + + // Set terrain view + auto& tile = map->getTile(pos); + if (!pattern.diffImages) + { + tile.terView = gen->nextInt(mapping.first, mapping.second); + tile.extTileFlags = valRslt.flip; + } + else + { + const int framesPerRot = (mapping.second - mapping.first + 1) / pattern.rotationTypesCount; + int flip = (pattern.rotationTypesCount == 2 && valRslt.flip == 2) ? 1 : valRslt.flip; + int firstFrame = mapping.first + flip * framesPerRot; + tile.terView = gen->nextInt(firstFrame, firstFrame + framesPerRot - 1); + tile.extTileFlags = 0; + } + } +} + +CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainView(const int3& pos, const std::vector* pattern, int recDepth) const +{ + for (int flip = 0; flip < 4; ++flip) + { + auto valRslt = validateTerrainViewInner(pos, pattern->at(flip), recDepth); + if (valRslt.result) + { + valRslt.flip = flip; + return valRslt; + } + } + return ValidationResult(false); +} + +CDrawTerrainOperation::ValidationResult CDrawTerrainOperation::validateTerrainViewInner(const int3& pos, const TerrainViewPattern& pattern, int recDepth) const +{ + auto centerTerType = map->getTile(pos).terType; + int totalPoints = 0; + std::string transitionReplacement; + + for (int i = 0; i < 9; ++i) + { + // The center, middle cell can be skipped + if (i == 4) + { + continue; + } + + // Get terrain group of the current cell + int cx = pos.x + (i % 3) - 1; + int cy = pos.y + (i / 3) - 1; + int3 currentPos(cx, cy, pos.z); + bool isAlien = false; + Terrain terType; + if (!map->isInTheMap(currentPos)) + { + // position is not in the map, so take the ter type from the neighbor tile + bool widthTooHigh = currentPos.x >= map->width; + bool widthTooLess = currentPos.x < 0; + bool heightTooHigh = currentPos.y >= map->height; + bool heightTooLess = currentPos.y < 0; + + if ((widthTooHigh && heightTooHigh) || (widthTooHigh && heightTooLess) || (widthTooLess && heightTooHigh) || (widthTooLess && heightTooLess)) + { + terType = centerTerType; + } + else if (widthTooHigh) + { + terType = map->getTile(int3(currentPos.x - 1, currentPos.y, currentPos.z)).terType; + } + else if (heightTooHigh) + { + terType = map->getTile(int3(currentPos.x, currentPos.y - 1, currentPos.z)).terType; + } + else if (widthTooLess) + { + terType = map->getTile(int3(currentPos.x + 1, currentPos.y, currentPos.z)).terType; + } + else if (heightTooLess) + { + terType = map->getTile(int3(currentPos.x, currentPos.y + 1, currentPos.z)).terType; + } + } + else + { + terType = map->getTile(currentPos).terType; + if (terType != centerTerType && (terType.isPassable() || centerTerType.isPassable())) + { + isAlien = true; + } + } + + // Validate all rules per cell + int topPoints = -1; + for (auto& elem : pattern.data[i]) + { + TerrainViewPattern::WeightedRule rule = elem; + if (!rule.isStandardRule()) + { + if (recDepth == 0 && map->isInTheMap(currentPos)) + { + if (terType == centerTerType) + { + const auto& patternForRule = VLC->terviewh->getTerrainViewPatternsById(centerTerType, rule.name); + if (auto p = patternForRule) + { + auto rslt = validateTerrainView(currentPos, &(*p), 1); + if (rslt.result) topPoints = std::max(topPoints, rule.points); + } + } + continue; + } + else + { + rule.setNative(); + } + } + + auto applyValidationRslt = [&](bool rslt) + { + if (rslt) + { + topPoints = std::max(topPoints, rule.points); + } + }; + + // Validate cell with the ruleset of the pattern + bool nativeTestOk, nativeTestStrongOk; + nativeTestOk = nativeTestStrongOk = (rule.isNativeStrong() || rule.isNativeRule()) && !isAlien; + + if (centerTerType == Terrain("dirt")) + { + nativeTestOk = rule.isNativeRule() && !terType.isTransitionRequired(); + bool sandTestOk = (rule.isSandRule() || rule.isTransition()) + && terType.isTransitionRequired(); + applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk || nativeTestStrongOk); + } + else if (centerTerType == Terrain("sand")) + { + applyValidationRslt(true); + } + else if (centerTerType.isTransitionRequired()) //water, rock and some special terrains require sand transition + { + bool sandTestOk = (rule.isSandRule() || rule.isTransition()) + && isAlien; + applyValidationRslt(rule.isAnyRule() || sandTestOk || nativeTestOk); + } + else + { + bool dirtTestOk = (rule.isDirtRule() || rule.isTransition()) + && isAlien && !terType.isTransitionRequired(); + bool sandTestOk = (rule.isSandRule() || rule.isTransition()) + && terType.isTransitionRequired(); + + if (transitionReplacement.empty() && rule.isTransition() + && (dirtTestOk || sandTestOk)) + { + transitionReplacement = dirtTestOk ? TerrainViewPattern::RULE_DIRT : TerrainViewPattern::RULE_SAND; + } + if (rule.isTransition()) + { + applyValidationRslt((dirtTestOk && transitionReplacement != TerrainViewPattern::RULE_SAND) || + (sandTestOk && transitionReplacement != TerrainViewPattern::RULE_DIRT)); + } + else + { + applyValidationRslt(rule.isAnyRule() || dirtTestOk || sandTestOk || nativeTestOk); + } + } + } + + if (topPoints == -1) + { + return ValidationResult(false); + } + else + { + totalPoints += topPoints; + } + } + + if (totalPoints >= pattern.minPoints && totalPoints <= pattern.maxPoints) + { + return ValidationResult(true, transitionReplacement); + } + else + { + return ValidationResult(false); + } +} + +void CDrawTerrainOperation::invalidateTerrainViews(const int3& centerPos) +{ + auto rect = extendTileAroundSafely(centerPos); + rect.forEach([&](const int3& pos) + { + invalidatedTerViews.insert(pos); + }); +} + +CDrawTerrainOperation::InvalidTiles CDrawTerrainOperation::getInvalidTiles(const int3& centerPos) const +{ + //TODO: this is very expensive function for RMG, needs optimization + InvalidTiles tiles; + auto centerTerType = map->getTile(centerPos).terType; + auto rect = extendTileAround(centerPos); + rect.forEach([&](const int3& pos) + { + if (map->isInTheMap(pos)) + { + auto ptrConfig = VLC->terviewh; + auto terType = map->getTile(pos).terType; + auto valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById("n1")).result; + + // Special validity check for rock & water + if (valid && (terType.isWater() || !terType.isPassable())) + { + static const std::string patternIds[] = { "s1", "s2" }; + for (auto& patternId : patternIds) + { + valid = !validateTerrainView(pos, ptrConfig->getTerrainTypePatternById(patternId)).result; + if (!valid) break; + } + } + // Additional validity check for non rock OR water + else if (!valid && (terType.isLand() && terType.isPassable())) + { + static const std::string patternIds[] = { "n2", "n3" }; + for (auto& patternId : patternIds) + { + valid = validateTerrainView(pos, ptrConfig->getTerrainTypePatternById(patternId)).result; + if (valid) break; + } + } + + if (!valid) + { + if (terType == centerTerType) tiles.nativeTiles.insert(pos); + else tiles.foreignTiles.insert(pos); + } + else if (centerPos == pos) + { + tiles.centerPosValid = true; + } + } + }); + return tiles; +} + +CDrawTerrainOperation::ValidationResult::ValidationResult(bool result, const std::string& transitionReplacement) + : result(result), transitionReplacement(transitionReplacement), flip(0) +{ + +} + +CClearTerrainOperation::CClearTerrainOperation(CMap* map, CRandomGenerator* gen) : CComposedOperation(map) +{ + CTerrainSelection terrainSel(map); + terrainSel.selectRange(MapRect(int3(0, 0, 0), map->width, map->height)); + addOperation(make_unique(map, terrainSel, Terrain("water"), gen)); + if (map->twoLevel) + { + terrainSel.clearSelection(); + terrainSel.selectRange(MapRect(int3(0, 0, 1), map->width, map->height)); + addOperation(make_unique(map, terrainSel, Terrain("rock"), gen)); + } +} + +std::string CClearTerrainOperation::getLabel() const +{ + return "Clear Terrain"; +} + +CInsertObjectOperation::CInsertObjectOperation(CMap* map, CGObjectInstance* obj) + : CMapOperation(map), obj(obj) +{ + +} + +void CInsertObjectOperation::execute() +{ + obj->id = ObjectInstanceID(map->objects.size()); + + map->setUniqueInstanceName(obj); + + map->addNewObject(obj); +} + +void CInsertObjectOperation::undo() +{ + map->removeObject(obj); +} + +void CInsertObjectOperation::redo() +{ + execute(); +} + +std::string CInsertObjectOperation::getLabel() const +{ + return "Insert Object"; +} + +CMoveObjectOperation::CMoveObjectOperation(CMap* map, CGObjectInstance* obj, const int3& targetPosition) + : CMapOperation(map), + obj(obj), + initialPos(obj->pos), + targetPos(targetPosition) +{ +} + +void CMoveObjectOperation::execute() +{ + map->moveObject(obj, targetPos); +} + +void CMoveObjectOperation::undo() +{ + map->moveObject(obj, initialPos); +} + +void CMoveObjectOperation::redo() +{ + execute(); +} + +std::string CMoveObjectOperation::getLabel() const +{ + return "Move Object"; +} + +CRemoveObjectOperation::CRemoveObjectOperation(CMap* map, CGObjectInstance* obj) + : CMapOperation(map), obj(obj) +{ + +} + +CRemoveObjectOperation::~CRemoveObjectOperation() +{ + //when operation is destroyed and wasn't undone, the object is lost forever + + if (!obj) + { + return; + } + + //do not destroy an object that belongs to map + if (!vstd::contains(map->instanceNames, obj->instanceName)) + { + delete obj; + obj = nullptr; + } +} + +void CRemoveObjectOperation::execute() +{ + map->removeObject(obj); +} + +void CRemoveObjectOperation::undo() +{ + try + { + //set new id, but do not rename object + obj->id = ObjectInstanceID((si32)map->objects.size()); + map->addNewObject(obj); + } + catch (const std::exception& e) + { + logGlobal->error(e.what()); + } +} + +void CRemoveObjectOperation::redo() +{ + execute(); +} + +std::string CRemoveObjectOperation::getLabel() const +{ + return "Remove Object"; +} \ No newline at end of file diff --git a/lib/mapping/CMapOperation.h b/lib/mapping/CMapOperation.h new file mode 100644 index 000000000..5a47fe230 --- /dev/null +++ b/lib/mapping/CMapOperation.h @@ -0,0 +1,164 @@ +/* + * CMapOperation.h, 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 + * + */ + +#pragma once + +#include "../int3.h" +#include "MapEditUtils.h" + +class CGObjectInstance; +class CMap; +class CRandomGenerator; + +/// The abstract base class CMapOperation defines an operation that can be executed, undone and redone. +class DLL_LINKAGE CMapOperation : public boost::noncopyable +{ +public: + explicit CMapOperation(CMap* map); + virtual ~CMapOperation() { }; + + virtual void execute() = 0; + virtual void undo() = 0; + virtual void redo() = 0; + virtual std::string getLabel() const = 0; /// Returns a display-able name of the operation. + + static const int FLIP_PATTERN_HORIZONTAL = 1; + static const int FLIP_PATTERN_VERTICAL = 2; + static const int FLIP_PATTERN_BOTH = 3; + +protected: + MapRect extendTileAround(const int3& centerPos) const; + MapRect extendTileAroundSafely(const int3& centerPos) const; /// doesn't exceed map size + + CMap* map; +}; + +/// The CComposedOperation is an operation which consists of several operations. +class DLL_LINKAGE CComposedOperation : public CMapOperation +{ +public: + CComposedOperation(CMap* map); + + void execute() override; + void undo() override; + void redo() override; + std::string getLabel() const override; + + void addOperation(std::unique_ptr&& operation); + +private: + std::list > operations; +}; + +/// The CDrawTerrainOperation class draws a terrain area on the map. +class CDrawTerrainOperation : public CMapOperation +{ +public: + CDrawTerrainOperation(CMap* map, const CTerrainSelection& terrainSel, Terrain terType, CRandomGenerator* gen); + + void execute() override; + void undo() override; + void redo() override; + std::string getLabel() const override; + +private: + struct ValidationResult + { + ValidationResult(bool result, const std::string& transitionReplacement = ""); + + bool result; + /// The replacement of a T rule, either D or S. + std::string transitionReplacement; + int flip; + }; + + struct InvalidTiles + { + std::set foreignTiles, nativeTiles; + bool centerPosValid; + + InvalidTiles() : centerPosValid(false) { } + }; + + void updateTerrainTypes(); + void invalidateTerrainViews(const int3& centerPos); + InvalidTiles getInvalidTiles(const int3& centerPos) const; + + void updateTerrainViews(); + /// Validates the terrain view of the given position and with the given pattern. The first method wraps the + /// second method to validate the terrain view with the given pattern in all four flip directions(horizontal, vertical). + ValidationResult validateTerrainView(const int3& pos, const std::vector* pattern, int recDepth = 0) const; + ValidationResult validateTerrainViewInner(const int3& pos, const TerrainViewPattern& pattern, int recDepth = 0) const; + + CTerrainSelection terrainSel; + Terrain terType; + CRandomGenerator* gen; + std::set invalidatedTerViews; +}; + +/// The CClearTerrainOperation clears+initializes the terrain. +class CClearTerrainOperation : public CComposedOperation +{ +public: + CClearTerrainOperation(CMap* map, CRandomGenerator* gen); + + std::string getLabel() const override; + +private: + +}; + +/// The CInsertObjectOperation class inserts an object to the map. +class CInsertObjectOperation : public CMapOperation +{ +public: + CInsertObjectOperation(CMap* map, CGObjectInstance* obj); + + void execute() override; + void undo() override; + void redo() override; + std::string getLabel() const override; + +private: + CGObjectInstance* obj; +}; + +/// The CMoveObjectOperation class moves object to another position +class CMoveObjectOperation : public CMapOperation +{ +public: + CMoveObjectOperation(CMap* map, CGObjectInstance* obj, const int3& targetPosition); + + void execute() override; + void undo() override; + void redo() override; + std::string getLabel() const override; + +private: + CGObjectInstance* obj; + int3 initialPos; + int3 targetPos; +}; + +/// The CRemoveObjectOperation class removes object from the map +class CRemoveObjectOperation : public CMapOperation +{ +public: + CRemoveObjectOperation(CMap* map, CGObjectInstance* obj); + ~CRemoveObjectOperation(); + + void execute() override; + void undo() override; + void redo() override; + std::string getLabel() const override; + +private: + CGObjectInstance* obj; +}; \ No newline at end of file diff --git a/lib/mapping/MapEditUtils.cpp b/lib/mapping/MapEditUtils.cpp new file mode 100644 index 000000000..94d40704d --- /dev/null +++ b/lib/mapping/MapEditUtils.cpp @@ -0,0 +1,371 @@ +/* + * MapEditUtils.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 "MapEditUtils.h" + +#include "../filesystem/Filesystem.h" +#include "../JsonNode.h" +#include "CMap.h" +#include "CMapOperation.h" + +MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0) +{ + +} + +MapRect::MapRect(int3 pos, si32 width, si32 height) : x(pos.x), y(pos.y), z(pos.z), width(width), height(height) +{ + +} + +MapRect MapRect::operator&(const MapRect& rect) const +{ + bool intersect = right() > rect.left() && rect.right() > left() && + bottom() > rect.top() && rect.bottom() > top() && + z == rect.z; + if (intersect) + { + MapRect ret; + ret.x = std::max(left(), rect.left()); + ret.y = std::max(top(), rect.top()); + ret.z = rect.z; + ret.width = std::min(right(), rect.right()) - ret.x; + ret.height = std::min(bottom(), rect.bottom()) - ret.y; + return ret; + } + else + { + return MapRect(); + } +} + +si32 MapRect::left() const +{ + return x; +} + +si32 MapRect::right() const +{ + return x + width; +} + +si32 MapRect::top() const +{ + return y; +} + +si32 MapRect::bottom() const +{ + return y + height; +} + +int3 MapRect::topLeft() const +{ + return int3(x, y, z); +} + +int3 MapRect::topRight() const +{ + return int3(right(), y, z); +} + +int3 MapRect::bottomLeft() const +{ + return int3(x, bottom(), z); +} + +int3 MapRect::bottomRight() const +{ + return int3(right(), bottom(), z); +} + +CTerrainSelection::CTerrainSelection(CMap* map) : CMapSelection(map) +{ + +} + +void CTerrainSelection::selectRange(const MapRect& rect) +{ + rect.forEach([this](const int3 pos) + { + this->select(pos); + }); +} + +void CTerrainSelection::deselectRange(const MapRect& rect) +{ + rect.forEach([this](const int3 pos) + { + this->deselect(pos); + }); +} + +void CTerrainSelection::setSelection(const std::vector& vec) +{ + for (auto pos : vec) + this->select(pos); +} + +void CTerrainSelection::selectAll() +{ + selectRange(MapRect(int3(0, 0, 0), getMap()->width, getMap()->height)); + selectRange(MapRect(int3(0, 0, 1), getMap()->width, getMap()->height)); +} + +void CTerrainSelection::clearSelection() +{ + deselectRange(MapRect(int3(0, 0, 0), getMap()->width, getMap()->height)); + deselectRange(MapRect(int3(0, 0, 1), getMap()->width, getMap()->height)); +} + +CObjectSelection::CObjectSelection(CMap* map) : CMapSelection(map) +{ + +} + +const std::string TerrainViewPattern::FLIP_MODE_DIFF_IMAGES = "D"; + +const std::string TerrainViewPattern::RULE_DIRT = "D"; +const std::string TerrainViewPattern::RULE_SAND = "S"; +const std::string TerrainViewPattern::RULE_TRANSITION = "T"; +const std::string TerrainViewPattern::RULE_NATIVE = "N"; +const std::string TerrainViewPattern::RULE_NATIVE_STRONG = "N!"; +const std::string TerrainViewPattern::RULE_ANY = "?"; + +TerrainViewPattern::TerrainViewPattern() : diffImages(false), rotationTypesCount(0), minPoints(0) +{ + maxPoints = std::numeric_limits::max(); +} + +TerrainViewPattern::WeightedRule::WeightedRule(std::string& Name) : points(0), name(Name) +{ + standardRule = (TerrainViewPattern::RULE_ANY == Name || TerrainViewPattern::RULE_DIRT == Name + || TerrainViewPattern::RULE_NATIVE == Name || TerrainViewPattern::RULE_SAND == Name + || TerrainViewPattern::RULE_TRANSITION == Name || TerrainViewPattern::RULE_NATIVE_STRONG == Name); + anyRule = (Name == TerrainViewPattern::RULE_ANY); + dirtRule = (Name == TerrainViewPattern::RULE_DIRT); + sandRule = (Name == TerrainViewPattern::RULE_SAND); + transitionRule = (Name == TerrainViewPattern::RULE_TRANSITION); + nativeStrongRule = (Name == TerrainViewPattern::RULE_NATIVE_STRONG); + nativeRule = (Name == TerrainViewPattern::RULE_NATIVE); +} + +void TerrainViewPattern::WeightedRule::setNative() +{ + nativeRule = true; + standardRule = true; + //TODO: would look better as a bitfield + dirtRule = sandRule = transitionRule = nativeStrongRule = anyRule = false; //no idea what they mean, but look mutually exclusive +} + +CTerrainViewPatternConfig::CTerrainViewPatternConfig() +{ + const JsonNode config(ResourceID("config/terrainViewPatterns.json")); + static const std::string patternTypes[] = { "terrainView", "terrainType" }; + for (int i = 0; i < ARRAY_COUNT(patternTypes); ++i) + { + const auto& patternsVec = config[patternTypes[i]].Vector(); + for (const auto& ptrnNode : patternsVec) + { + TerrainViewPattern pattern; + + // Read pattern data + const JsonVector& data = ptrnNode["data"].Vector(); + assert(data.size() == 9); + for (int j = 0; j < data.size(); ++j) + { + std::string cell = data[j].String(); + boost::algorithm::erase_all(cell, " "); + std::vector rules; + boost::split(rules, cell, boost::is_any_of(",")); + for (std::string ruleStr : rules) + { + std::vector ruleParts; + boost::split(ruleParts, ruleStr, boost::is_any_of("-")); + TerrainViewPattern::WeightedRule rule(ruleParts[0]); + assert(!rule.name.empty()); + if (ruleParts.size() > 1) + { + rule.points = boost::lexical_cast(ruleParts[1]); + } + pattern.data[j].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 mapping + if (i == 0) + { + const auto& mappingStruct = ptrnNode["mapping"].Struct(); + for (const auto& mappingPair : mappingStruct) + { + TerrainViewPattern terGroupPattern = pattern; + auto mappingStr = mappingPair.second.String(); + boost::algorithm::erase_all(mappingStr, " "); + auto colonIndex = mappingStr.find_first_of(":"); + const auto& flipMode = mappingStr.substr(0, colonIndex); + terGroupPattern.diffImages = TerrainViewPattern::FLIP_MODE_DIFF_IMAGES == &(flipMode[flipMode.length() - 1]); + if (terGroupPattern.diffImages) + { + terGroupPattern.rotationTypesCount = boost::lexical_cast(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(",")); + for (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 + std::vector terrainViewPatternFlips; + terrainViewPatternFlips.push_back(terGroupPattern); + + for (int i = 1; i < 4; ++i) + { + //auto p = terGroupPattern; + flipPattern(terGroupPattern, i); //FIXME: we flip in place - doesn't make much sense now, but used to work + terrainViewPatternFlips.push_back(terGroupPattern); + } + + terrainViewPatterns[mappingPair.first].push_back(terrainViewPatternFlips); + } + } + else if (i == 1) + { + terrainTypePatterns[pattern.id].push_back(pattern); + for (int i = 1; i < 4; ++i) + { + //auto p = pattern; + flipPattern(pattern, i); ///FIXME: we flip in place - doesn't make much sense now + terrainTypePatterns[pattern.id].push_back(pattern); + } + } + } + } +} + +CTerrainViewPatternConfig::~CTerrainViewPatternConfig() +{ + +} + +const std::vector& CTerrainViewPatternConfig::getTerrainViewPatterns(const Terrain& terrain) const +{ + auto iter = terrainViewPatterns.find(Terrain::Manager::getInfo(terrain).terrainViewPatterns); + if (iter == terrainViewPatterns.end()) + return terrainViewPatterns.at("normal"); + return iter->second; +} + +boost::optional CTerrainViewPatternConfig::getTerrainViewPatternById(std::string patternId, const std::string& id) const +{ + auto iter = terrainViewPatterns.find(patternId); + const std::vector& groupPatterns = (iter == terrainViewPatterns.end()) ? terrainViewPatterns.at("normal") : iter->second; + + for (const TVPVector& patternFlips : groupPatterns) + { + const TerrainViewPattern& pattern = patternFlips.front(); + + if (id == pattern.id) + { + return boost::optional(pattern); + } + } + return boost::optional(); +} + +boost::optional CTerrainViewPatternConfig::getTerrainViewPatternsById(const Terrain& terrain, const std::string& id) const +{ + const std::vector& groupPatterns = getTerrainViewPatterns(terrain); + for (const TVPVector& patternFlips : groupPatterns) + { + const TerrainViewPattern& pattern = patternFlips.front(); + if (id == pattern.id) + { + return boost::optional(patternFlips); + } + } + return boost::optional(); +} + + +const CTerrainViewPatternConfig::TVPVector* CTerrainViewPatternConfig::getTerrainTypePatternById(const std::string& id) const +{ + auto it = terrainTypePatterns.find(id); + assert(it != terrainTypePatterns.end()); + return &(it->second); +} + +void CTerrainViewPatternConfig::flipPattern(TerrainViewPattern& pattern, int flip) const +{ + //flip in place to avoid expensive constructor. Seriously. + + if (flip == 0) + { + return; + } + + //always flip horizontal + for (int i = 0; i < 3; ++i) + { + int y = i * 3; + std::swap(pattern.data[y], pattern.data[y + 2]); + } + //flip vertical only at 2nd step + if (flip == CMapOperation::FLIP_PATTERN_VERTICAL) + { + for (int i = 0; i < 3; ++i) + { + std::swap(pattern.data[i], pattern.data[6 + i]); + } + } +} + + +void CTerrainViewPatternUtils::printDebuggingInfoAboutTile(const CMap* map, int3 pos) +{ + logGlobal->debug("Printing detailed info about nearby map tiles of pos '%s'", pos.toString()); + for (int y = pos.y - 2; y <= pos.y + 2; ++y) + { + std::string line; + const int PADDED_LENGTH = 10; + for (int x = pos.x - 2; x <= pos.x + 2; ++x) + { + auto debugPos = int3(x, y, pos.z); + if (map->isInTheMap(debugPos)) + { + auto debugTile = map->getTile(debugPos); + + std::string terType = static_cast(debugTile.terType).substr(0, 6); + line += terType; + line.insert(line.end(), PADDED_LENGTH - terType.size(), ' '); + } + else + { + line += "X"; + line.insert(line.end(), PADDED_LENGTH - 1, ' '); + } + } + + logGlobal->debug(line); + } +} diff --git a/lib/mapping/MapEditUtils.h b/lib/mapping/MapEditUtils.h new file mode 100644 index 000000000..31ab19751 --- /dev/null +++ b/lib/mapping/MapEditUtils.h @@ -0,0 +1,235 @@ +/* + * MapEditUtils.h, 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 + * + */ + +#pragma once + +#include "../int3.h" +#include "../CRandomGenerator.h" +#include "Terrain.h" + +class CGObjectInstance; +class CMap; + +/// Represents a map rectangle. +struct DLL_LINKAGE MapRect +{ + MapRect(); + MapRect(int3 pos, si32 width, si32 height); + si32 x, y, z; + si32 width, height; + + si32 left() const; + si32 right() const; + si32 top() const; + si32 bottom() const; + + int3 topLeft() const; /// Top left corner of this rect. + int3 topRight() const; /// Top right corner of this rect. + int3 bottomLeft() const; /// Bottom left corner of this rect. + int3 bottomRight() const; /// Bottom right corner of this rect. + + /// Returns a MapRect of the intersection of this rectangle and the given one. + MapRect operator&(const MapRect& rect) const; + + template + void forEach(Func f) const + { + for (int j = y; j < bottom(); ++j) + { + for (int i = x; i < right(); ++i) + { + f(int3(i, j, z)); + } + } + } +}; + +/// Generic selection class to select any type +template +class DLL_LINKAGE CMapSelection +{ +public: + explicit CMapSelection(CMap* map) : map(map) { } + virtual ~CMapSelection() { }; + void select(const T& item) + { + selectedItems.insert(item); + } + void deselect(const T& item) + { + selectedItems.erase(item); + } + std::set getSelectedItems() + { + return selectedItems; + } + CMap* getMap() { return map; } + virtual void selectRange(const MapRect& rect) { } + virtual void deselectRange(const MapRect& rect) { } + virtual void selectAll() { } + virtual void clearSelection() { } + +private: + std::set selectedItems; + CMap* map; +}; + +/// Selection class to select terrain. +class DLL_LINKAGE CTerrainSelection : public CMapSelection +{ +public: + explicit CTerrainSelection(CMap* map); + void selectRange(const MapRect& rect) override; + void deselectRange(const MapRect& rect) override; + void selectAll() override; + void clearSelection() override; + void setSelection(const std::vector& vec); +}; + +/// Selection class to select objects. +class DLL_LINKAGE CObjectSelection : public CMapSelection +{ +public: + explicit CObjectSelection(CMap* map); +}; + +/// The terrain view pattern describes a specific composition of terrain tiles +/// in a 3x3 matrix and notes which terrain view frame numbers can be used. +struct DLL_LINKAGE TerrainViewPattern +{ + struct WeightedRule + { + WeightedRule(std::string& Name); + /// Gets true if this rule is a standard rule which means that it has a value of one of the RULE_* constants. + inline bool isStandardRule() const + { + return standardRule; + } + inline bool isAnyRule() const + { + return anyRule; + } + inline bool isDirtRule() const + { + return dirtRule; + } + inline bool isSandRule() const + { + return sandRule; + } + inline bool isTransition() const + { + return transitionRule; + } + inline bool isNativeStrong() const + { + return nativeStrongRule; + } + inline bool isNativeRule() const + { + return nativeRule; + } + void setNative(); + + /// The name of the rule. Can be any value of the RULE_* constants or a ID of a another pattern. + //FIXME: remove string variable altogether, use only in constructor + std::string name; + /// Optional. A rule can have points. Patterns may have a minimum count of points to reach to be successful. + int points; + + private: + bool standardRule; + bool anyRule; + bool dirtRule; + bool sandRule; + bool transitionRule; + bool nativeStrongRule; + bool nativeRule; + + WeightedRule(); //only allow string constructor + }; + + 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. + static const std::string RULE_DIRT; + /// Constant for the rule sand, meaning a sandy border is required. + 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 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; + + TerrainViewPattern(); + + /// The pattern data can be visualized as a 3x3 matrix: + /// [ ][ ][ ] + /// [ ][ ][ ] + /// [ ][ ][ ] + /// + /// The box in the center belongs always to the native terrain type and + /// is the point of origin. Depending on the terrain type different rules + /// can be used. Their meaning differs also from type to type. + /// + /// std::vector -> several rules can be used in one cell + std::array, PATTERN_DATA_SIZE> data; + + /// The identifier of the pattern, if it's referenced from a another pattern. + std::string id; + + /// This describes the mapping between this pattern and the corresponding range of frames + /// which should be used for the ter view. + /// + /// std::vector -> size=1: typical, size=2: if this pattern should map to two different types of borders + /// std::pair -> 1st value: lower range, 2nd value: upper range + std::vector > mapping; + /// If diffImages is true, different images/frames are used to place a rotated terrain view. If it's false + /// the same frame will be used and rotated. + bool diffImages; + /// The rotationTypesCount is only used if diffImages is true and holds the number how many rotation types(horizontal, etc...) + /// are supported. + int rotationTypesCount; + + /// The minimum and maximum points to reach to validate the pattern successfully. + int minPoints, maxPoints; +}; + +/// The terrain view pattern config loads pattern data from the filesystem. +class DLL_LINKAGE CTerrainViewPatternConfig : public boost::noncopyable +{ +public: + typedef std::vector TVPVector; + + CTerrainViewPatternConfig(); + ~CTerrainViewPatternConfig(); + + const std::vector& getTerrainViewPatterns(const Terrain& terrain) const; + boost::optional getTerrainViewPatternById(std::string patternId, const std::string& id) const; + boost::optional getTerrainViewPatternsById(const Terrain& terrain, const std::string& id) const; + const TVPVector* getTerrainTypePatternById(const std::string& id) const; + void flipPattern(TerrainViewPattern& pattern, int flip) const; + +private: + std::map > terrainViewPatterns; + std::map terrainTypePatterns; +}; + + + +class DLL_LINKAGE CTerrainViewPatternUtils +{ +public: + static void printDebuggingInfoAboutTile(const CMap* map, int3 pos); +}; \ No newline at end of file diff --git a/lib/rmg/ObstaclePlacer.cpp b/lib/rmg/ObstaclePlacer.cpp index b4eac19f3..fe9ba8536 100644 --- a/lib/rmg/ObstaclePlacer.cpp +++ b/lib/rmg/ObstaclePlacer.cpp @@ -143,6 +143,8 @@ void ObstacleProxy::placeObstacles(CMap * map, CRandomGenerator & rand) //reverse order, since obstacles begin in bottom-right corner, while the map coordinates begin in top-left auto blockedTiles = blockedArea.getTilesVector(); int tilePos = 0; + std::set objs; + while(!blockedArea.empty() && tilePos < blockedArea.getTilesVector().size()) { auto tile = blockedArea.getTilesVector()[tilePos]; @@ -159,7 +161,11 @@ void ObstacleProxy::placeObstacles(CMap * map, CRandomGenerator & rand) auto objIter = RandomGeneratorUtil::nextItem(weightedObjects, rand); objIter->first->setPosition(objIter->second); - placeObject(map->getEditManager(), *objIter->first); + for (auto* instance : objIter->first->instances()) + { + objs.insert(&instance->object()); + } + blockedArea.subtract(objIter->first->getArea()); tilePos = 0; @@ -174,6 +180,8 @@ void ObstacleProxy::placeObstacles(CMap * map, CRandomGenerator & rand) o.clear(); } } + + map->getEditManager()->insertObjects(objs); //insert as one operation - for undo purposes } void ObstacleProxy::postProcess(const rmg::Object & object) diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index e07e48d04..cb1f6af17 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -12,7 +12,7 @@ #include "../ConstTransitivePtr.h" #include "../GameConstants.h" -const ui32 SERIALIZATION_VERSION = 803; +const ui32 SERIALIZATION_VERSION = 804; const ui32 MINIMAL_SERIALIZATION_VERSION = 803; const std::string SAVEGAME_MAGIC = "VCMISVG"; diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 445d9d212..1f013bd4a 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -679,6 +679,10 @@ void MainWindow::on_toolArea_clicked(bool checked) ui->mapView->selectionTool = MapView::SelectionTool::None; } +void MainWindow::on_actionErase_triggered() +{ + on_toolErase_clicked(); +} void MainWindow::on_toolErase_clicked() { @@ -825,6 +829,18 @@ void MainWindow::enableRedo(bool enable) ui->actionRedo->setEnabled(enable); } +void MainWindow::onSelectionMade(int level, bool anythingSelected) +{ + if (level == mapLevel) + { + auto info = QString::asprintf("Selection on layer %s: %s", level, anythingSelected ? "true" : "false"); + setStatusMessage(info); + + ui->actionErase->setEnabled(anythingSelected); + ui->toolErase->setEnabled(anythingSelected); + } +} + void MainWindow::on_actionValidate_triggered() { new Validator(controller.map(), this); diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index a9dda7025..fa41ff4a9 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -48,6 +48,8 @@ private slots: void on_actionLevel_triggered(); void on_actionSave_triggered(); + + void on_actionErase_triggered(); void on_actionUndo_triggered(); @@ -92,6 +94,7 @@ public slots: void mapChanged(); void enableUndo(bool enable); void enableRedo(bool enable); + void onSelectionMade(int level, bool anythingSelected); private: void preparePreview(const QModelIndex &index, bool createNew); diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index e87017401..73464d754 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -77,6 +77,7 @@ + @@ -107,6 +108,7 @@ + @@ -608,6 +610,9 @@ + + false + 0 @@ -663,7 +668,7 @@ 0 0 128 - 271 + 344 @@ -706,7 +711,7 @@ 0 0 128 - 271 + 344 @@ -725,7 +730,7 @@ 0 0 128 - 271 + 344 @@ -870,6 +875,17 @@ true + + + false + + + Erase + + + Del + + true diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 96df46f98..f7b4fddde 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -20,6 +20,19 @@ MapController::MapController(MainWindow * m): main(m) _scenes[1].reset(new MapScene(1)); _miniscenes[0].reset(new MinimapScene(0)); _miniscenes[1].reset(new MinimapScene(1)); + connectScenes(); +} + +void MapController::connectScenes() +{ + for (int level = 0; level <= 1; level++) + { + //selections for both layers will be handled separately + QObject::connect(_scenes[level].get(), &MapScene::selected, [this, level](bool anythingSelected) + { + main->onSelectionMade(level, anythingSelected); + }); + } } MapController::~MapController() @@ -61,6 +74,8 @@ void MapController::setMap(std::unique_ptr cmap) resetMapHandler(); sceneForceUpdate(); + connectScenes(); + _map->getEditManager()->getUndoManager().setUndoCallback([this](bool allowUndo, bool allowRedo) { main->enableUndo(allowUndo); @@ -120,13 +135,27 @@ void MapController::commitTerrainChange(int level, const Terrain & terrain) void MapController::commitObjectErase(int level) { - for(auto * obj : _scenes[level]->selectionObjectsView.getSelection()) + auto selectedObjects = _scenes[level]->selectionObjectsView.getSelection(); + if (selectedObjects.size() > 1) { - _map->getEditManager()->removeObject(obj); - //invalidate tiles under object - _mapHandler->invalidate(_mapHandler->geTilesUnderObject(obj)); - delete obj; + //mass erase => undo in one operation + _map->getEditManager()->removeObjects(selectedObjects); } + else if (selectedObjects.size() == 1) + { + _map->getEditManager()->removeObject(*selectedObjects.begin()); + } + else //nothing to erase - shouldn't be here + { + return; + } + + for (auto obj : selectedObjects) + { + //invalidate tiles under objects + _mapHandler->invalidate(_mapHandler->geTilesUnderObject(obj)); + } + _scenes[level]->selectionObjectsView.clear(); _scenes[level]->objectsView.draw(); _scenes[level]->selectionObjectsView.draw(); diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index a731a0634..4df0e93d6 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -49,6 +49,8 @@ private: MainWindow * main; mutable std::array, 2> _scenes; mutable std::array, 2> _miniscenes; + + void connectScenes(); }; #endif // MAPCONTROLLER_H diff --git a/mapeditor/mapview.cpp b/mapeditor/mapview.cpp index 304ce379a..0dbdde285 100644 --- a/mapeditor/mapview.cpp +++ b/mapeditor/mapview.cpp @@ -359,8 +359,12 @@ MapScene::MapScene(int lvl): selectionTerrainView(this), terrainView(this), objectsView(this), - selectionObjectsView(this) + selectionObjectsView(this), + isTerrainSelected(false), + isObjectSelected(false) { + connect(&selectionTerrainView, &SelectionTerrainLayer::selectionMade, this, &MapScene::terrainSelected); + connect(&selectionObjectsView, &SelectionObjectsLayer::selectionMade, this, &MapScene::objectSelected); } std::list MapScene::getAbstractLayers() @@ -386,6 +390,18 @@ void MapScene::updateViews() selectionObjectsView.show(true); } +void MapScene::terrainSelected(bool anythingSelected) +{ + isTerrainSelected = anythingSelected; + emit selected(isTerrainSelected || isObjectSelected); +} + +void MapScene::objectSelected(bool anythingSelected) +{ + isObjectSelected = anythingSelected; + emit selected(isTerrainSelected || isObjectSelected); +} + MinimapScene::MinimapScene(int lvl): MapSceneBase(lvl), minimapView(this), diff --git a/mapeditor/mapview.h b/mapeditor/mapview.h index 43012f9b4..eecc75559 100644 --- a/mapeditor/mapview.h +++ b/mapeditor/mapview.h @@ -13,6 +13,7 @@ class MapController; class MapSceneBase : public QGraphicsScene { + Q_OBJECT; public: MapSceneBase(int lvl); @@ -41,6 +42,7 @@ protected: class MapScene : public MapSceneBase { + Q_OBJECT public: MapScene(int lvl); @@ -52,9 +54,20 @@ public: TerrainLayer terrainView; ObjectsLayer objectsView; SelectionObjectsLayer selectionObjectsView; + +signals: + void selected(bool anything); + +public slots: + void terrainSelected(bool anything); + void objectSelected(bool anything); protected: std::list getAbstractLayers() override; + + bool isTerrainSelected; + bool isObjectSelected; + }; class MapView : public QGraphicsView diff --git a/mapeditor/scenelayer.cpp b/mapeditor/scenelayer.cpp index 6d55b2258..d514c4a89 100644 --- a/mapeditor/scenelayer.cpp +++ b/mapeditor/scenelayer.cpp @@ -113,6 +113,7 @@ void SelectionTerrainLayer::update() area.clear(); areaAdd.clear(); areaErase.clear(); + onSelection(); pixmap.reset(new QPixmap(map->width * 32, map->height * 32)); pixmap->fill(QColor(0, 0, 0, 0)); @@ -153,6 +154,7 @@ void SelectionTerrainLayer::select(const int3 & tile) areaAdd.insert(tile); areaErase.erase(tile); } + onSelection(); } void SelectionTerrainLayer::erase(const int3 & tile) @@ -166,6 +168,7 @@ void SelectionTerrainLayer::erase(const int3 & tile) areaErase.insert(tile); areaAdd.erase(tile); } + onSelection(); } void SelectionTerrainLayer::clear() @@ -173,6 +176,7 @@ void SelectionTerrainLayer::clear() areaErase = area; areaAdd.clear(); area.clear(); + onSelection(); } const std::set & SelectionTerrainLayer::selection() const @@ -180,6 +184,12 @@ const std::set & SelectionTerrainLayer::selection() const return area; } +void SelectionTerrainLayer::onSelection() +{ + emit selectionMade(!area.empty()); +} + + TerrainLayer::TerrainLayer(MapSceneBase * s): AbstractLayer(s) { } @@ -331,6 +341,7 @@ void SelectionObjectsLayer::update() return; selectedObjects.clear(); + onSelection(); shift = QPoint(); if(newObject) delete newObject; @@ -444,14 +455,19 @@ void SelectionObjectsLayer::selectObjects(int x1, int y1, int x2, int y2) for(int i = x1; i < x2; ++i) { for(auto & o : handler->getObjects(i, j, scene->level)) - selectObject(o.obj); + selectObject(o.obj, false); //do not inform about each object added } } + onSelection(); } -void SelectionObjectsLayer::selectObject(CGObjectInstance * obj) +void SelectionObjectsLayer::selectObject(CGObjectInstance * obj, bool inform /* = true */) { selectedObjects.insert(obj); + if (inform) + { + onSelection(); + } } bool SelectionObjectsLayer::isSelected(const CGObjectInstance * obj) const @@ -467,10 +483,16 @@ std::set SelectionObjectsLayer::getSelection() const void SelectionObjectsLayer::clear() { selectedObjects.clear(); + onSelection(); shift.setX(0); shift.setY(0); } +void SelectionObjectsLayer::onSelection() +{ + emit selectionMade(!selectedObjects.empty()); +} + MinimapLayer::MinimapLayer(MapSceneBase * s): AbstractLayer(s) { diff --git a/mapeditor/scenelayer.h b/mapeditor/scenelayer.h index 9e70be13c..1d785db44 100644 --- a/mapeditor/scenelayer.h +++ b/mapeditor/scenelayer.h @@ -4,13 +4,15 @@ #include "../lib/int3.h" class MapSceneBase; +class MapScene; class CGObjectInstance; class MapController; class CMap; class MapHandler; -class AbstractLayer +class AbstractLayer : public QObject { + Q_OBJECT public: AbstractLayer(MapSceneBase * s); @@ -36,6 +38,7 @@ private: class GridLayer: public AbstractLayer { + Q_OBJECT public: GridLayer(MapSceneBase * s); @@ -44,17 +47,18 @@ public: class PassabilityLayer: public AbstractLayer { + Q_OBJECT public: PassabilityLayer(MapSceneBase * s); void update() override; }; - class SelectionTerrainLayer: public AbstractLayer { + Q_OBJECT public: - SelectionTerrainLayer(MapSceneBase * s); + SelectionTerrainLayer(MapSceneBase* s); void update() override; @@ -64,14 +68,20 @@ public: void clear(); const std::set & selection() const; - + +signals: + void selectionMade(bool anythingSlected); + private: std::set area, areaAdd, areaErase; + + void onSelection(); }; class TerrainLayer: public AbstractLayer { + Q_OBJECT public: TerrainLayer(MapSceneBase * s); @@ -87,6 +97,7 @@ private: class ObjectsLayer: public AbstractLayer { + Q_OBJECT public: ObjectsLayer(MapSceneBase * s); @@ -105,8 +116,9 @@ private: class SelectionObjectsLayer: public AbstractLayer { + Q_OBJECT public: - SelectionObjectsLayer(MapSceneBase * s); + SelectionObjectsLayer(MapSceneBase* s); void update() override; @@ -114,7 +126,7 @@ public: CGObjectInstance * selectObjectAt(int x, int y) const; void selectObjects(int x1, int y1, int x2, int y2); - void selectObject(CGObjectInstance *); + void selectObject(CGObjectInstance *, bool inform = true); bool isSelected(const CGObjectInstance *) const; std::set getSelection() const; void moveSelection(int x, int y); @@ -122,10 +134,16 @@ public: QPoint shift; CGObjectInstance * newObject; + //FIXME: magic number int selectionMode = 0; //0 - nothing, 1 - selection, 2 - movement + +signals: + void selectionMade(bool anythingSlected); private: std::set selectedObjects; + + void onSelection(); }; class MinimapLayer: public AbstractLayer