diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 329cb9176..5f3a141c5 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -333,7 +333,23 @@ bool MainWindow::openMap(const QString & filenameSelect) CMapService mapService; try { - controller.setMap(mapService.loadMap(resId)); + if(auto header = mapService.loadMapHeader(resId)) + { + auto missingMods = CMapService::verifyMapHeaderMods(*header); + CModHandler::Incompatibility::ModList modList; + for(const auto & m : missingMods) + modList.push_back({m.first, m.second.toString()}); + + if(!modList.empty()) + throw CModHandler::Incompatibility(std::move(modList)); + + controller.setMap(mapService.loadMap(resId)); + } + } + catch(const CModHandler::Incompatibility & e) + { + QMessageBox::warning(this, "Mods requiered", e.what()); + return false; } catch(const std::exception & e) { diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index e992e40c3..af14216f9 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -554,3 +554,33 @@ void MapController::redo() sceneForceUpdate(); //TODO: use smart invalidation (setDirty) main->mapChanged(); } + +ModCompatibilityInfo MapController::modAssessmentAll() +{ + ModCompatibilityInfo result; + for(auto primaryID : VLC->objtypeh->knownObjects()) + { + for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID)) + { + auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID); + auto modName = QString::fromStdString(handler->getJsonKey()).split(":").at(0).toStdString(); + if(modName != "core") + result[modName] = VLC->modh->getModInfo(modName).version; + } + } + return result; +} + +ModCompatibilityInfo MapController::modAssessmentMap(const CMap & map) +{ + ModCompatibilityInfo result; + for(auto obj : map.objects) + { + auto handler = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID); + auto modName = QString::fromStdString(handler->getJsonKey()).split(":").at(0).toStdString(); + if(modName != "core") + result[modName] = VLC->modh->getModInfo(modName).version; + } + //TODO: terrains? + return result; +} diff --git a/mapeditor/mapcontroller.h b/mapeditor/mapcontroller.h index 338798201..892a89a1a 100644 --- a/mapeditor/mapcontroller.h +++ b/mapeditor/mapcontroller.h @@ -54,6 +54,9 @@ public: bool discardObject(int level) const; void createObject(int level, CGObjectInstance * obj) const; bool canPlaceObject(int level, CGObjectInstance * obj, QString & error) const; + + static ModCompatibilityInfo modAssessmentAll(); + static ModCompatibilityInfo modAssessmentMap(const CMap & map); void undo(); void redo(); diff --git a/mapeditor/mapsettings.cpp b/mapeditor/mapsettings.cpp index 3f2f2922e..c904bb5cb 100644 --- a/mapeditor/mapsettings.cpp +++ b/mapeditor/mapsettings.cpp @@ -17,8 +17,10 @@ #include "../lib/CArtHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CGeneralTextHandler.h" +#include "../lib/CModHandler.h" #include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/MiscObjects.h" +#include "../lib/mapping/CMapService.h" #include "../lib/StringConstants.h" #include "inspector/townbulidingswidget.h" //to convert BuildingID to string @@ -82,6 +84,14 @@ std::vector linearJsonArray(const JsonNode & json) return result; } +void traverseNode(QTreeWidgetItem * item, std::function action) +{ + // Do something with item + action(item); + for (int i = 0; i < item->childCount(); ++i) + traverseNode(item->child(i), action); +} + MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : QDialog(parent), ui(new Ui::MapSettings), @@ -98,7 +108,6 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : show(); - for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i) { auto * item = new QListWidgetItem(QString::fromStdString(VLC->skillh->objects[i]->getNameTranslated())); @@ -387,6 +396,61 @@ MapSettings::MapSettings(MapController & ctrl, QWidget *parent) : } } } + + //mods management + //collect all active mods + QMap addedMods; + QSet modsToProcess; + ui->treeMods->blockSignals(true); + + auto createModTreeWidgetItem = [&](QTreeWidgetItem * parent, const CModInfo & modInfo) + { + auto item = new QTreeWidgetItem(parent, {QString::fromStdString(modInfo.name), QString::fromStdString(modInfo.version.toString())}); + item->setData(0, Qt::UserRole, QVariant(QString::fromStdString(modInfo.identifier))); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(0, controller.map()->mods.count(modInfo.identifier) ? Qt::Checked : Qt::Unchecked); + //set parent check + if(parent && item->checkState(0) == Qt::Checked) + parent->setCheckState(0, Qt::Checked); + return item; + }; + + for(const auto & modName : VLC->modh->getActiveMods()) + { + QString qmodName = QString::fromStdString(modName); + if(qmodName.split(".").size() == 1) + { + const auto & modInfo = VLC->modh->getModInfo(modName); + addedMods[qmodName] = createModTreeWidgetItem(nullptr, modInfo); + ui->treeMods->addTopLevelItem(addedMods[qmodName]); + } + else + { + modsToProcess.insert(qmodName); + } + } + + for(auto qmodIter = modsToProcess.begin(); qmodIter != modsToProcess.end();) + { + auto qmodName = *qmodIter; + auto pieces = qmodName.split("."); + assert(pieces.size() > 1); + + QString qs; + for(int i = 0; i < pieces.size() - 1; ++i) + qs += pieces[i]; + + if(addedMods.count(qs)) + { + const auto & modInfo = VLC->modh->getModInfo(qmodName.toStdString()); + addedMods[qmodName] = createModTreeWidgetItem(addedMods[qs], modInfo); + modsToProcess.erase(qmodIter); + qmodIter = modsToProcess.begin(); + } + else + ++qmodIter; + } + ui->treeMods->blockSignals(false); } MapSettings::~MapSettings() @@ -430,6 +494,22 @@ std::string MapSettings::getMonsterName(int monsterObjectIdx) return name; } +void MapSettings::updateModWidgetBasedOnMods(const ModCompatibilityInfo & mods) +{ + //Mod management + auto widgetAction = [&](QTreeWidgetItem * item) + { + auto modName = item->data(0, Qt::UserRole).toString().toStdString(); + item->setCheckState(0, mods.count(modName) ? Qt::Checked : Qt::Unchecked); + }; + + for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i) + { + QTreeWidgetItem *item = ui->treeMods->topLevelItem(i); + traverseNode(item, widgetAction); + } +} + void MapSettings::on_pushButton_clicked() { controller.map()->name = ui->mapNameEdit->text().toStdString(); @@ -705,6 +785,23 @@ void MapSettings::on_pushButton_clicked() controller.map()->triggeredEvents.push_back(specialDefeat); } + //Mod management + auto widgetAction = [&](QTreeWidgetItem * item) + { + if(item->checkState(0) == Qt::Checked) + { + auto modName = item->data(0, Qt::UserRole).toString().toStdString(); + controller.map()->mods[modName] = VLC->modh->getModInfo(modName).version; + } + }; + + controller.map()->mods.clear(); + for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i) + { + QTreeWidgetItem *item = ui->treeMods->topLevelItem(i); + traverseNode(item, widgetAction); + } + close(); } @@ -887,3 +984,33 @@ void MapSettings::on_heroLevelLimitCheck_toggled(bool checked) ui->heroLevelLimit->setEnabled(checked); } +void MapSettings::on_modResolution_map_clicked() +{ + updateModWidgetBasedOnMods(MapController::modAssessmentMap(*controller.map())); +} + + +void MapSettings::on_modResolution_full_clicked() +{ + updateModWidgetBasedOnMods(MapController::modAssessmentAll()); +} + +void MapSettings::on_treeMods_itemChanged(QTreeWidgetItem *item, int column) +{ + //set state for children + for (int i = 0; i < item->childCount(); ++i) + item->child(i)->setCheckState(0, item->checkState(0)); + + //set state for parent + ui->treeMods->blockSignals(true); + if(item->checkState(0) == Qt::Checked) + { + while(item->parent()) + { + item->parent()->setCheckState(0, Qt::Checked); + item = item->parent(); + } + } + ui->treeMods->blockSignals(false); +} + diff --git a/mapeditor/mapsettings.h b/mapeditor/mapsettings.h index aff47e069..bd7a79826 100644 --- a/mapeditor/mapsettings.h +++ b/mapeditor/mapsettings.h @@ -34,12 +34,20 @@ private slots: void on_heroLevelLimitCheck_toggled(bool checked); + void on_modResolution_map_clicked(); + + void on_modResolution_full_clicked(); + + void on_treeMods_itemChanged(QTreeWidgetItem *item, int column); + private: std::string getTownName(int townObjectIdx); std::string getHeroName(int townObjectIdx); std::string getMonsterName(int townObjectIdx); + void updateModWidgetBasedOnMods(const ModCompatibilityInfo & mods); + template std::vector getObjectIndexes() const { diff --git a/mapeditor/mapsettings.ui b/mapeditor/mapsettings.ui index fe830d3b5..5a314b917 100644 --- a/mapeditor/mapsettings.ui +++ b/mapeditor/mapsettings.ui @@ -9,7 +9,7 @@ 0 0 - 470 + 543 494 @@ -23,14 +23,7 @@ Map settings - - - - Ok - - - - + 0 @@ -139,6 +132,77 @@ + + + Mods + + + + + + Mandatory mods for playing this map + + + + + + + QAbstractScrollArea::AdjustIgnored + + + 320 + + + + Mod name + + + + + Version + + + + + + + + + + Automatic assignment + + + + + + + Set required mods based on objects placed on the map. This method may cause problems if you have customized rewards, garrisons, etc from mods + + + Map objects mods + + + false + + + + + + + Set all mods having a game content as mandatory + + + Full content mods + + + false + + + + + + + Events @@ -369,6 +433,13 @@ + + + + Ok + + + diff --git a/mapeditor/validator.cpp b/mapeditor/validator.cpp index 2fd622d69..c7a342542 100644 --- a/mapeditor/validator.cpp +++ b/mapeditor/validator.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "validator.h" +#include "mapcontroller.h" #include "ui_validator.h" #include "../lib/mapObjects/MapObjects.h" #include "../lib/CHeroHandler.h" @@ -158,6 +159,15 @@ std::list Validator::validate(const CMap * map) issues.emplace_back("Map name is not specified", false); if(map->description.empty()) issues.emplace_back("Map description is not specified", false); + + //verificationfor mods + for(auto & mod : MapController::modAssessmentMap(*map)) + { + if(!map->mods.count(mod.first)) + { + issues.emplace_back(QString("Map contains object from mod \"%1\", but doesn't require it").arg(QString::fromStdString(VLC->modh->getModInfo(mod.first).name)), true); + } + } } catch(const std::exception & e) { diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 59fe1b2be..714012d9a 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -264,7 +264,7 @@ void WindowNewMap::on_okButton_clicked() nmap = f.get(); } - + nmap->mods = MapController::modAssessmentAll(); static_cast(parent())->controller.setMap(std::move(nmap)); static_cast(parent())->initializeMap(true); close();