diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 03e4a2dc6..f6101b453 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -266,41 +266,34 @@ QStringList CModListView::getModNames(QString queryingModID, QStringList input) for(const auto & modID : input) { - // Missing mod - use mod ID - // TODO: for submods show parent ID instead - if (!modStateModel->isModExists(modID)) + if (modStateModel->isModExists(modID) && modStateModel->getMod(modID).isHidden()) + continue; + + QString parentModID = modStateModel->getTopParent(modID); + QString displayName; + + if (modStateModel->isSubmod(modID) && queryingMod.getParentID() != parentModID ) { - result += modID; - continue; + // show in form "parent mod (submod)" + + QString parentDisplayName = parentModID; + QString submodDisplayName = modID; + + if (modStateModel->isModExists(parentModID)) + parentDisplayName = modStateModel->getMod(parentModID).getName(); + + if (modStateModel->isModExists(modID)) + submodDisplayName = modStateModel->getMod(modID).getName(); + + displayName = QString("%1 (%2)").arg(submodDisplayName, parentDisplayName); } - - auto mod = modStateModel->getMod(modID); - - if (queryingMod.getParentID() == modID) - continue; - - if (mod.isHidden()) - continue; - - QString displayName = mod.getName(); - if (displayName.isEmpty()) - displayName = modID.toLower(); - - if (mod.isSubmod() && queryingMod.getParentID() != mod.getParentID() ) // FIXME: recheck this block + else { - auto parentModID = mod.getTopParentID(); - auto parentMod = modStateModel->getMod(parentModID); - QString parentDisplayName = parentMod.getName(); - if (parentDisplayName.isEmpty()) - parentDisplayName = parentModID.toLower(); - - if (modStateModel->isModInstalled(modID)) - displayName = QString("%1 (%2)").arg(displayName, parentDisplayName); - else - displayName = parentDisplayName; + // show simply as mod name + displayName = modID; + if (modStateModel->isModExists(modID)) + displayName = modStateModel->getMod(modID).getName(); } - - // TODO: show active mods in bold? result += displayName; } return result; @@ -457,7 +450,6 @@ void CModListView::selectMod(const QModelIndex & index) //FIXME: this function should be recursive //FIXME: ensure that this is also reflected correctly in "Notes" section of mod description bool hasInvalidDeps = !findInvalidDependencies(modName).empty(); - bool hasDependentMods = !findDependentMods(modName, true).empty(); ui->disableButton->setVisible(modStateModel->isModEnabled(mod.getID())); ui->enableButton->setVisible(!modStateModel->isModEnabled(mod.getID())); @@ -466,13 +458,11 @@ void CModListView::selectMod(const QModelIndex & index) ui->updateButton->setVisible(mod.isUpdateAvailable()); // Block buttons if action is not allowed at this time - // TODO: automate handling of some of these cases instead of forcing player - // to resolve all conflicts manually. ui->disableButton->setEnabled(true); ui->enableButton->setEnabled(!hasInvalidDeps); ui->installButton->setEnabled(!hasInvalidDeps); ui->uninstallButton->setEnabled(true); - ui->updateButton->setEnabled(!hasInvalidDeps && !hasDependentMods); + ui->updateButton->setEnabled(!hasInvalidDeps); loadScreenshots(); } @@ -514,27 +504,18 @@ QStringList CModListView::findInvalidDependencies(QString mod) QStringList ret; for(QString requirement : modStateModel->getMod(mod).getDependencies()) { - if(!modStateModel->isModExists(requirement)) - ret += requirement; - } - return ret; -} - -QStringList CModListView::findDependentMods(QString mod, bool excludeDisabled) -{ - QStringList ret; - for(QString modName : modStateModel->getAllMods()) - { - auto current = modStateModel->getMod(modName); - - if(!current.isInstalled() || !current.isVisible()) + if(modStateModel->isModExists(requirement)) continue; - if(current.getDependencies().contains(mod, Qt::CaseInsensitive)) + if(modStateModel->isSubmod(requirement)) { - if(!(modStateModel->isModEnabled(modName) && excludeDisabled)) - ret += modName; + QString parentModID = modStateModel->getTopParent(requirement); + + if (modStateModel->isModExists(parentModID) && !modStateModel->isModInstalled(parentModID)) + continue; } + + ret += requirement; } return ret; } @@ -569,13 +550,47 @@ void CModListView::disableModByName(QString modName) modModel->reloadRepositories(); } +QStringList CModListView::getModsToInstall(QString mod) +{ + QStringList result; + QStringList candidates; + QStringList processed; + + candidates.push_back(mod); + while (!candidates.empty()) + { + QString potentialToInstall = candidates.back(); + candidates.pop_back(); + processed.push_back(potentialToInstall); + + if (modStateModel->isSubmod(potentialToInstall)) + potentialToInstall = modStateModel->getTopParent(potentialToInstall); + + if (!modStateModel->isModExists(potentialToInstall)) + throw std::runtime_error("Attempt to install non-existing mod! Mod name:" + potentialToInstall.toStdString()); + + if (modStateModel->isModInstalled(potentialToInstall)) + continue; + + result.push_back(potentialToInstall); + + QStringList dependencies = modStateModel->getMod(potentialToInstall).getDependencies(); + for (const auto & dependency : dependencies) + { + if (!processed.contains(dependency)) + candidates.push_back(dependency); + } + } + return result; +} + void CModListView::on_updateButton_clicked() { QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); assert(findInvalidDependencies(modName).empty()); - for(const auto & name : modStateModel->getMod(modName).getDependencies()) + for(const auto & name : getModsToInstall(modName)) { auto mod = modStateModel->getMod(name); // update required mod, install missing (can be new dependency) @@ -604,8 +619,8 @@ void CModListView::on_installButton_clicked() QString modName = ui->allModsView->currentIndex().data(ModRoles::ModNameRole).toString(); assert(findInvalidDependencies(modName).empty()); - - for(const auto & name : modStateModel->getMod(modName).getDependencies()) + + for(const auto & name : getModsToInstall(modName)) { auto mod = modStateModel->getMod(name); if(mod.isAvailable()) @@ -1058,7 +1073,6 @@ void CModListView::on_allModsView_doubleClicked(const QModelIndex &index) auto mod = modStateModel->getMod(modName); bool hasInvalidDeps = !findInvalidDependencies(modName).empty(); - bool hasDependentMods = !findDependentMods(modName, true).empty(); if(!hasInvalidDeps && mod.isAvailable() && !mod.isSubmod()) { @@ -1066,7 +1080,7 @@ void CModListView::on_allModsView_doubleClicked(const QModelIndex &index) return; } - if(!hasInvalidDeps && !hasDependentMods && mod.isUpdateAvailable() && index.column() == ModFields::STATUS_UPDATE) + if(!hasInvalidDeps && mod.isUpdateAvailable() && index.column() == ModFields::STATUS_UPDATE) { on_updateButton_clicked(); return; @@ -1088,7 +1102,7 @@ void CModListView::on_allModsView_doubleClicked(const QModelIndex &index) return; } - if(!hasDependentMods && !mod.isVisible() && modStateModel->isModEnabled(modName)) + if(modStateModel->isModEnabled(modName)) { on_disableButton_clicked(); return; diff --git a/launcher/modManager/cmodlistview_moc.h b/launcher/modManager/cmodlistview_moc.h index 9e9bf14c6..c64f6b13c 100644 --- a/launcher/modManager/cmodlistview_moc.h +++ b/launcher/modManager/cmodlistview_moc.h @@ -46,10 +46,11 @@ class CModListView : public QWidget /// replace mod ID's with proper human-readable mod names QStringList getModNames(QString queryingMod, QStringList input); + /// returns list of mods that are needed for install of this mod (potentially including this mod itself) + QStringList getModsToInstall(QString mod); + // find mods unknown to mod list (not present in repo and not installed) QStringList findInvalidDependencies(QString mod); - // find mods that depend on this one - QStringList findDependentMods(QString mod, bool excludeDisabled); void manualInstallFile(QString filePath); void downloadFile(QString file, QString url, QString description, qint64 size = 0); diff --git a/launcher/modManager/modstatemodel.cpp b/launcher/modManager/modstatemodel.cpp index e320be196..81e8a8c84 100644 --- a/launcher/modManager/modstatemodel.cpp +++ b/launcher/modManager/modstatemodel.cpp @@ -104,3 +104,17 @@ void ModStateModel::doDisableMod(QString modname) { modManager->tryDisableMod(modname.toStdString()); } + +bool ModStateModel::isSubmod(QString modname) +{ + return modname.contains('.'); +} + +QString ModStateModel::getTopParent(QString modname) const +{ + QStringList components = modname.split('.'); + if (components.size() > 1) + return components.front(); + else + return ""; +} diff --git a/launcher/modManager/modstatemodel.h b/launcher/modManager/modstatemodel.h index d20f73d6c..8d883aacf 100644 --- a/launcher/modManager/modstatemodel.h +++ b/launcher/modManager/modstatemodel.h @@ -45,4 +45,7 @@ public: void doEnableMod(QString modname); void doDisableMod(QString modname); + + bool isSubmod(QString modname); + QString getTopParent(QString modname) const; }; diff --git a/lib/modding/CModHandler.cpp b/lib/modding/CModHandler.cpp index 726727f58..09dddd8a2 100644 --- a/lib/modding/CModHandler.cpp +++ b/lib/modding/CModHandler.cpp @@ -93,7 +93,8 @@ void CModHandler::loadModFilesystems() modFilesystems[modName] = genModFilesystem(modName, getModInfo(modName).getFilesystemConfig()); for(const TModID & modName : activeMods) - CResourceHandler::addFilesystem("data", modName, modFilesystems[modName]); + if (modName != "core") // FIXME: remove + CResourceHandler::addFilesystem("data", modName, modFilesystems[modName]); if (settings["mods"]["validation"].String() == "full") checkModFilesystemsConflicts(modFilesystems);