Browse Source

Merge pull request #5108 from IvanSavenko/preset_import

[1.6.1?] Mod preset import/export
Ivan Savenko 10 months ago
parent
commit
e612fbb0c1

+ 28 - 0
launcher/modManager/cmodlistview_moc.cpp

@@ -857,6 +857,12 @@ void CModListView::installMods(QStringList archives)
 		modNames.push_back(modName);
 	}
 
+	if (!activatingPreset.isEmpty())
+	{
+		modStateModel->activatePreset(activatingPreset);
+		activatingPreset.clear();
+	}
+
 	// uninstall old version of mod, if installed
 	for(QString mod : modNames)
 	{
@@ -1147,3 +1153,25 @@ QString CModListView::getActivePreset() const
 {
 	return modStateModel->getActivePreset();
 }
+
+JsonNode CModListView::exportCurrentPreset() const
+{
+	return modStateModel->exportCurrentPreset();
+}
+
+void CModListView::importPreset(const JsonNode & data)
+{
+	const auto & [presetName, modList] = modStateModel->importPreset(data);
+
+	if (modList.empty())
+	{
+		modStateModel->activatePreset(presetName);
+		modStateModel->reloadLocalState();
+	}
+	else
+	{
+		activatingPreset = presetName;
+		for (const auto & modID : modList)
+			doInstallMod(modID);
+	}
+}

+ 4 - 4
launcher/modManager/cmodlistview_moc.h

@@ -37,6 +37,7 @@ class CModListView : public QWidget
 	CModFilterModel * filterModel;
 	CDownloadManager * dlManager;
 	JsonNode accumulatedRepositoryData;
+	QString activatingPreset;
 
 	QStringList enqueuedModDownloads;
 
@@ -97,17 +98,16 @@ public:
 	QStringList getUpdateableMods();
 
 	void createNewPreset(const QString & presetName);
-
 	void deletePreset(const QString & presetName);
-
 	void activatePreset(const QString & presetName);
-
 	void renamePreset(const QString & oldPresetName, const QString & newPresetName);
 
 	QStringList getAllPresets() const;
-
 	QString getActivePreset() const;
 
+	JsonNode exportCurrentPreset() const;
+	void importPreset(const JsonNode & data);
+
 	/// returns true if mod is currently enabled
 	bool isModEnabled(const QString & modName);
 

+ 13 - 0
launcher/modManager/modstatemodel.cpp

@@ -157,3 +157,16 @@ QString ModStateModel::getActivePreset() const
 {
 	return QString::fromStdString(modManager->getActivePreset());
 }
+
+JsonNode ModStateModel::exportCurrentPreset() const
+{
+	return modManager->exportCurrentPreset();
+}
+
+std::tuple<QString, QStringList> ModStateModel::importPreset(const JsonNode & data)
+{
+	std::tuple<QString, QStringList> result;
+	const auto & [presetName, modList] = modManager->importPreset(data);
+
+	return {QString::fromStdString(presetName), stringListStdToQt(modList)};
+}

+ 3 - 0
launcher/modManager/modstatemodel.h

@@ -57,4 +57,7 @@ public:
 
 	QStringList getAllPresets() const;
 	QString getActivePreset() const;
+
+	JsonNode exportCurrentPreset() const;
+	std::tuple<QString, QStringList> importPreset(const JsonNode & data);
 };

+ 45 - 4
launcher/startGame/StartGameTab.cpp

@@ -44,12 +44,41 @@ StartGameTab::StartGameTab(QWidget * parent)
 	refreshState();
 
 	ui->buttonGameResume->setVisible(false); // TODO: implement
-	ui->buttonPresetExport->setVisible(false); // TODO: implement
-	ui->buttonPresetImport->setVisible(false); // TODO: implement
 
 #ifndef ENABLE_EDITOR
 	ui->buttonGameEditor->hide();
 #endif
+
+	auto clipboard = QGuiApplication::clipboard();
+
+	connect(clipboard, SIGNAL(dataChanged()), this, SLOT(clipboardDataChanged()));
+}
+
+void StartGameTab::clipboardDataChanged()
+{
+	ui->buttonPresetExport->setIcon(QIcon{});// reset icon, if any
+
+	auto clipboard = QGuiApplication::clipboard();
+	QString clipboardText = clipboard->text().trimmed();
+
+	if (clipboardText.isEmpty())
+	{
+		ui->buttonPresetImport->setEnabled(false);
+	}
+	else
+	{
+		// this *may* be json, try parsing it
+		if (clipboardText.startsWith('{'))
+		{
+			QByteArray presetBytes(clipboardText.toUtf8());
+			const JsonNode presetJson(reinterpret_cast<const std::byte*>(presetBytes.data()), presetBytes.size(), "preset in clipboard");
+			bool presetValid = !presetJson["name"].String().empty() && !presetJson["mods"].Vector().empty();
+
+			ui->buttonPresetImport->setEnabled(presetValid);
+		}
+		else
+			ui->buttonPresetImport->setEnabled(false);
+	}
 }
 
 StartGameTab::~StartGameTab()
@@ -72,6 +101,8 @@ void StartGameTab::refreshState()
 	refreshTranslation(getMainWindow()->getTranslationStatus());
 	refreshPresets();
 	refreshMods();
+
+	clipboardDataChanged();
 }
 
 void StartGameTab::refreshPresets()
@@ -363,12 +394,22 @@ void StartGameTab::on_buttonMissingCampaignsHelp_clicked()
 
 void StartGameTab::on_buttonPresetExport_clicked()
 {
-	// TODO
+	JsonNode presetJson = getMainWindow()->getModView()->exportCurrentPreset();
+	QString presetString = QString::fromStdString(presetJson.toCompactString());
+	QGuiApplication::clipboard()->setText(presetString);
+
+	ui->buttonPresetExport->setIcon(QIcon{":/icons/mod-enabled.png"});
 }
 
 void StartGameTab::on_buttonPresetImport_clicked()
 {
-	// TODO
+	QString presetString = QGuiApplication::clipboard()->text();
+	QByteArray presetBytes(presetString.toUtf8());
+	JsonNode presetJson(reinterpret_cast<const std::byte*>(presetBytes.data()), presetBytes.size(), "imported preset");
+
+	getMainWindow()->getModView()->importPreset(presetJson);
+	getMainWindow()->switchToModsTab();
+	refreshPresets();
 }
 
 void StartGameTab::on_buttonPresetNew_clicked()

+ 1 - 6
launcher/startGame/StartGameTab.h

@@ -65,19 +65,14 @@ private slots:
 	void on_buttonMissingVideoHelp_clicked();
 	void on_buttonMissingFilesHelp_clicked();
 	void on_buttonMissingCampaignsHelp_clicked();
-
 	void on_buttonPresetExport_clicked();
-
 	void on_buttonPresetImport_clicked();
-
 	void on_buttonPresetNew_clicked();
-
 	void on_buttonPresetDelete_clicked();
-
 	void on_comboBoxModPresets_currentTextChanged(const QString &arg1);
-
 	void on_buttonPresetRename_clicked();
 
+	void clipboardDataChanged();
 private:
 	Ui::StartGameTab * ui;
 };

+ 57 - 1
lib/modding/ModManager.cpp

@@ -207,7 +207,12 @@ const JsonNode & ModsPresetState::getActivePresetConfig() const
 
 TModList ModsPresetState::getActiveRootMods() const
 {
-	const JsonNode & modsToActivateJson = getActivePresetConfig()["mods"];
+	return getRootMods(getActivePreset());
+}
+
+TModList ModsPresetState::getRootMods(const std::string & presetName) const
+{
+	const JsonNode & modsToActivateJson = modConfig["presets"][presetName]["mods"];
 	auto modsToActivate = modsToActivateJson.convertTo<std::vector<TModID>>();
 	if (!vstd::contains(modsToActivate, ModScope::scopeBuiltin()))
 		modsToActivate.push_back(ModScope::scopeBuiltin());
@@ -385,6 +390,33 @@ std::string ModsPresetState::getActivePreset() const
 	return modConfig["activePreset"].String();
 }
 
+JsonNode ModsPresetState::exportCurrentPreset() const
+{
+	JsonNode data = getActivePresetConfig();
+	std::string presetName = getActivePreset();
+
+	data["name"] = JsonNode(presetName);
+
+	vstd::erase_if(data["settings"].Struct(), [&](const auto & pair){
+		return !vstd::contains(data["mods"].Vector(), JsonNode(pair.first));
+	});
+
+	return data;
+}
+
+std::string ModsPresetState::importPreset(const JsonNode & newConfig)
+{
+	std::string importedPresetName = newConfig["name"].String();
+
+	if (importedPresetName.empty())
+		throw std::runtime_error("Attempt to import invalid preset");
+
+	modConfig["presets"][importedPresetName] = newConfig;
+	modConfig["presets"][importedPresetName].Struct().erase("name");
+
+	return importedPresetName;
+}
+
 ModsStorage::ModsStorage(const std::vector<TModID> & modsToLoad, const JsonNode & repositoryList)
 {
 	JsonNode coreModConfig(JsonPath::builtin("config/gameConfig.json"));
@@ -796,4 +828,28 @@ std::string ModManager::getActivePreset() const
 	return modsPreset->getActivePreset();
 }
 
+JsonNode ModManager::exportCurrentPreset() const
+{
+	return modsPreset->exportCurrentPreset();
+}
+
+std::tuple<std::string, TModList> ModManager::importPreset(const JsonNode & data)
+{
+	std::string presetName = modsPreset->importPreset(data);
+
+	TModList requiredMods = modsPreset->getRootMods(presetName);
+	TModList installedMods = modsState->getInstalledMods();
+
+	TModList missingMods;
+	for (const auto & modID : requiredMods)
+	{
+		if (!vstd::contains(installedMods, modID))
+			missingMods.push_back(modID);
+	}
+
+	modsPreset->saveConfigurationState();
+
+	return {presetName, missingMods};
+}
+
 VCMI_LIB_NAMESPACE_END

+ 14 - 0
lib/modding/ModManager.h

@@ -58,6 +58,12 @@ public:
 	std::vector<std::string> getAllPresets() const;
 	std::string getActivePreset() const;
 
+	JsonNode exportCurrentPreset() const;
+
+	/// Imports preset from provided json
+	/// Returns name of imported preset on success
+	std::string importPreset(const JsonNode & data);
+
 	void setModActive(const TModID & modName, bool isActive);
 
 	void addRootMod(const TModID & modName);
@@ -72,6 +78,8 @@ public:
 
 	/// Returns list of currently active root mods (non-submod)
 	TModList getActiveRootMods() const;
+	/// Returns list of root mods present in specified preset
+	TModList getRootMods(const std::string & presetName) const;
 
 	/// Returns list of all known settings (submods) for a specified mod
 	std::map<TModID, bool> getModSettings(const TModID & modID) const;
@@ -155,6 +163,12 @@ public:
 
 	std::vector<std::string> getAllPresets() const;
 	std::string getActivePreset() const;
+
+	JsonNode exportCurrentPreset() const;
+
+	/// Imports preset from provided json
+	/// Returns name of imported preset and list of mods that must be installed to activate preset
+	std::tuple<std::string, TModList> importPreset(const JsonNode & data);
 };
 
 VCMI_LIB_NAMESPACE_END