add LOOT support for OpenMwPluginDeployer

This commit is contained in:
Limo
2025-04-10 20:58:15 +02:00
parent 77638b2817
commit aa3933138f
16 changed files with 142 additions and 22 deletions

View File

@@ -934,6 +934,16 @@ bool Deployer::isCaseInvariant() const
return false;
}
bool Deployer::getEnableUnsafeSorting() const
{
return enable_unsafe_sorting_;
}
void Deployer::setEnableUnsafeSorting(bool enable)
{
enable_unsafe_sorting_ = enable;
}
void Deployer::removeManagedDirFile(const sfs::path& directory) const
{
sfs::remove(directory / managed_dir_file_name_);

View File

@@ -372,6 +372,20 @@ public:
* \return False.
*/
virtual bool isCaseInvariant() const;
/*!
* \brief Returns whether sorting mods is allowed affect overwrite behavior.
*
* If this is set to false, sorting will always be safe and only affect how mods are displayed.
* \return The safe sorting state.
*/
bool getEnableUnsafeSorting() const;
/*!
* \brief Sets whether sorting mods is allowed affect overwrite behavior.
*
* If this is set to false, sorting will always be safe and only affect how mods are displayed.
* \param The new safe sorting state.
*/
void setEnableUnsafeSorting(bool enable);
protected:
/*! \brief Type of this deployer, e.g. Simple Deployer. */
@@ -403,6 +417,8 @@ protected:
bool is_autonomous_ = false;
/*! \brief If true: Automatically update conflict groups when necessary. */
bool auto_update_conflict_groups_ = false;
/*! \brief Determines whether sorting mods can affect overwrite behavior. */
bool enable_unsafe_sorting_ = false;
/*!
* \brief Creates a pair of maps. One maps relative file paths to the mod id from which that

View File

@@ -56,4 +56,6 @@ struct DeployerInfo
std::vector<std::pair<std::string, std::string>> mod_actions = {};
/*! \brief For every mod: IDs of every valid mod_action which is valid for that mod. */
std::vector<std::vector<int>> valid_mod_actions = {};
/*! \brief Determines whether sorting mods can affect overwrite behavior. */
bool uses_unsafe_sorting = false;
};

View File

@@ -35,4 +35,6 @@ struct EditDeployerInfo
* deployer to the ignore list.
*/
bool update_ignore_list = false;
/*! \brief Determines whether sorting mods can affect overwrite behavior. */
bool enable_unsafe_sorting = true;
};

View File

@@ -22,6 +22,7 @@ LootDeployer::LootDeployer(const sfs::path& source_path,
LIST_URLS = DEFAULT_LIST_URLS;
// make sure no hard link related checks are performed
deploy_mode_ = copy;
enable_unsafe_sorting_ = true;
if(!perform_init)
return;
type_ = "Loot Deployer";
@@ -169,6 +170,7 @@ void LootDeployer::sortModsByConflicts(std::optional<ProgressNode*> progress_nod
updateMasterList();
if(progress_node)
(*progress_node)->child(0).advance();
sfs::path master_list_path = dest_path_ / "masterlist.yaml";
if(!sfs::exists(master_list_path))
throw std::runtime_error("Could not find masterlist.yaml at '" + master_list_path.string() +
@@ -187,6 +189,7 @@ void LootDeployer::sortModsByConflicts(std::optional<ProgressNode*> progress_nod
loot_handle->GetDatabase().LoadLists(master_list_path, user_list_path, prelude_path);
if(progress_node)
(*progress_node)->child(1).advance();
std::vector<sfs::path> plugin_paths;
std::vector<std::string> plugin_file_names;
plugin_paths.reserve(plugins_.size());
@@ -200,7 +203,9 @@ void LootDeployer::sortModsByConflicts(std::optional<ProgressNode*> progress_nod
auto sorted_plugins = loot_handle->SortPlugins(plugin_file_names);
if(progress_node)
(*progress_node)->child(2).advance();
std::vector<std::pair<std::string, bool>> new_plugins;
new_plugins.reserve(plugins_.size());
std::set<std::string> conflicting;
int num_light_plugins = 0;
int num_master_plugins = 0;
@@ -253,7 +258,8 @@ void LootDeployer::sortModsByConflicts(std::optional<ProgressNode*> progress_nod
num_master_plugins,
num_standard_plugins,
num_light_plugins));
plugins_ = new_plugins;
if(enable_unsafe_sorting_)
plugins_ = new_plugins;
writePluginTags();
writePlugins();
if(progress_node)

View File

@@ -43,13 +43,17 @@ public:
"https://raw.githubusercontent.com/loot/fallout4vr/v0.21/masterlist.yaml" },
{ loot::GameType::fonv,
"https://raw.githubusercontent.com/loot/falloutnv/v0.21/masterlist.yaml" },
// there is no dedicated OpenMW masterlist, so we use the morrowind one
{ loot::GameType::openmw,
"https://raw.githubusercontent.com/loot/morrowind/v0.21/masterlist.yaml" },
{ loot::GameType::starfield,
"https://raw.githubusercontent.com/loot/starfield/v0.21/masterlist.yaml" },
{ loot::GameType::tes3,
"https://raw.githubusercontent.com/loot/morrowind/v0.21/masterlist.yaml" },
{ loot::GameType::tes4,
"https://raw.githubusercontent.com/loot/oblivion/v0.21/masterlist.yaml" },
{ loot::GameType::tes5, "https://raw.githubusercontent.com/loot/skyrim/v0.21/masterlist.yaml" },
{ loot::GameType::tes5,
"https://raw.githubusercontent.com/loot/skyrim/v0.21/masterlist.yaml" },
{ loot::GameType::tes5se,
"https://raw.githubusercontent.com/loot/skyrimse/v0.21/masterlist.yaml" },
{ loot::GameType::tes5vr,

View File

@@ -307,6 +307,7 @@ void ModdedApplication::addDeployer(const EditDeployerInfo& info)
info.deploy_mode,
info.separate_profile_dirs,
info.update_ignore_list));
deployers_.back()->setEnableUnsafeSorting(info.enable_unsafe_sorting);
for(int i = 0; i < profile_names_.size(); i++)
deployers_.back()->addProfile();
deployers_.back()->setProfile(current_profile_);
@@ -532,6 +533,7 @@ void ModdedApplication::editDeployer(int deployer, const EditDeployerInfo& info)
deployers_[deployer]->setName(info.name);
deployers_[deployer]->setDestPath(info.target_dir);
deployers_[deployer]->setDeployMode(info.deploy_mode);
deployers_[deployer]->setEnableUnsafeSorting(info.enable_unsafe_sorting);
}
else
{
@@ -550,6 +552,7 @@ void ModdedApplication::editDeployer(int deployer, const EditDeployerInfo& info)
json_settings_["deployers"][deployer]["dest_path"] = info.target_dir;
json_settings_["deployers"][deployer]["type"] = info.type;
json_settings_["deployers"][deployer]["deploy_mode"] = info.deploy_mode;
json_settings_["deployers"][deployer]["enable_unsafe_sorting"] = info.enable_unsafe_sorting;
updateState();
}
if(deployers_[deployer]->isAutonomous() && info.type != DeployerFactory::REVERSEDEPLOYER)
@@ -914,7 +917,8 @@ DeployerInfo ModdedApplication::getDeployerInfo(int deployer)
deployers_[deployer]->idsAreSourceReferences(),
{},
deployers_[deployer]->getModActions(),
deployers_[deployer]->getValidModActions() };
deployers_[deployer]->getValidModActions(),
deployers_[deployer]->getEnableUnsafeSorting() };
}
else
{
@@ -964,7 +968,8 @@ DeployerInfo ModdedApplication::getDeployerInfo(int deployer)
deployers_[deployer]->idsAreSourceReferences(),
mod_names,
deployers_[deployer]->getModActions(),
deployers_[deployer]->getValidModActions() };
deployers_[deployer]->getValidModActions(),
deployers_[deployer]->getEnableUnsafeSorting() };
}
}
@@ -1721,6 +1726,8 @@ void ModdedApplication::updateSettings(bool write)
json_settings_["deployers"][depl]["name"] = deployers_[depl]->getName();
json_settings_["deployers"][depl]["type"] = deployers_[depl]->getType();
json_settings_["deployers"][depl]["deploy_mode"] = deployers_[depl]->getDeployMode();
json_settings_["deployers"][depl]["enable_unsafe_sorting"] =
deployers_[depl]->getEnableUnsafeSorting();
if(!deployers_[depl]->isAutonomous())
{
@@ -1908,6 +1915,8 @@ void ModdedApplication::updateState(bool read)
sfs::path(deployers[depl]["dest_path"].asString()),
deployers[depl]["name"].asString(),
deploy_mode));
if(deployers[depl].isMember("enable_unsafe_sorting"))
deployers_.back()->setEnableUnsafeSorting(deployers[depl]["enable_unsafe_sorting"].asBool());
if(!deployers_[depl]->isAutonomous())
{

View File

@@ -13,10 +13,11 @@ namespace pu = path_utils;
OpenMwPluginDeployer::OpenMwPluginDeployer(const sfs::path& source_path,
const sfs::path& dest_path,
const std::string& name) :
PluginDeployer(source_path, dest_path, name)
LootDeployer(source_path, dest_path, name, false, false)
{
type_ = "OpenMW Plugin Deployer";
is_autonomous_ = true;
app_type_ = loot::GameType::openmw;
plugin_regex_ =
std::regex(R"(.*\.(?:es[pm]|omwscripts|omwaddon|omwgame)$)", std::regex_constants::icase);
plugin_file_line_regex_ = std::regex(
@@ -80,8 +81,9 @@ std::map<std::string, int> OpenMwPluginDeployer::getAutoTagMap()
void OpenMwPluginDeployer::sortModsByConflicts(std::optional<ProgressNode*> progress_node)
{
LootDeployer::sortModsByConflicts(progress_node);
auto groups = getConflictGroups();
;
std::vector<std::pair<std::string, bool>> new_plugins;
new_plugins.reserve(plugins_.size());
for(const auto& group : groups)
@@ -94,11 +96,6 @@ void OpenMwPluginDeployer::sortModsByConflicts(std::optional<ProgressNode*> prog
writePlugins();
}
bool OpenMwPluginDeployer::supportsModConflicts() const
{
return false;
}
std::vector<std::pair<std::string, std::string>> OpenMwPluginDeployer::getModActions() const
{
return { { "Add Groundcover Tag", "tag-new" }, { "Remove Groundcover Tag", "tag-delete" } };
@@ -140,6 +137,24 @@ void OpenMwPluginDeployer::applyModAction(int action, int mod_id)
writePlugins();
}
void OpenMwPluginDeployer::addProfile(int source)
{
// we don't want the changes made to profiles by LootDeployer
PluginDeployer::addProfile(source);
}
void OpenMwPluginDeployer::removeProfile(int profile)
{
// we don't want the changes made to profiles by LootDeployer
PluginDeployer::removeProfile(profile);
}
void OpenMwPluginDeployer::setProfile(int profile)
{
// we don't want the changes made to profiles by LootDeployer
PluginDeployer::setProfile(profile);
}
void OpenMwPluginDeployer::writePlugins() const
{
writePluginsPrivate();

View File

@@ -5,14 +5,14 @@
#pragma once
#include "plugindeployer.h"
#include "lootdeployer.h"
#include <set>
/*!
* \brief Autonomous deployer which handles plugin files for OpenMW using LOOT.
*/
class OpenMwPluginDeployer : public PluginDeployer
class OpenMwPluginDeployer : public LootDeployer
{
public:
/*!
@@ -52,11 +52,6 @@ public:
* \param progress_node Used to inform about the current progress.
*/
virtual void sortModsByConflicts(std::optional<ProgressNode*> progress_node = {}) override;
/*!
* \brief Returns whether or not this deployer type supports showing mod conflicts.
* \return False.
*/
virtual bool supportsModConflicts() const override;
/*!
* \brief Returns names and icon names for additional actions which can be applied to a mod.
* \return The actions.
@@ -73,6 +68,23 @@ public:
* \param mod_id Target mod.
*/
virtual void applyModAction(int action, int mod_id) override;
/*!
* \brief Adds a new profile and optionally copies it's load order from an existing profile.
* Profiles are stored in the target directory.
* \param source The profile to be copied. A value of -1 indicates no copy.
*/
virtual void addProfile(int source = -1) override;
/*!
* \brief Removes a profile.
* \param profile The profile to be removed.
*/
virtual void removeProfile(int profile) override;
/*!
* \brief Setter for the active profile. Changes the currently active plugin files
* to the ones saved in the new profile.
* \param profile The new profile.
*/
virtual void setProfile(int profile) override;
private:
/*! \brief Name of the OpenMW config file. */

View File

@@ -77,6 +77,7 @@ void AddDeployerDialog::setAddMode(int app_id)
ui->rev_depl_ignore_cb->setCheckState(Qt::Unchecked);
ui->rev_depl_separate_cb->setCheckState(Qt::Unchecked);
disable_confirmation_boxes_ = false;
ui->unsafe_sorting_box->setCheckState(Qt::Checked);
}
void AddDeployerDialog::setEditMode(const QString& type,
@@ -86,6 +87,7 @@ void AddDeployerDialog::setEditMode(const QString& type,
Deployer::DeployMode deploy_mode,
int app_id,
int deployer_id,
bool uses_unsafe_sorting,
bool has_separate_dirs,
bool has_ignored_files)
{
@@ -95,6 +97,7 @@ void AddDeployerDialog::setEditMode(const QString& type,
type_ = type;
app_id_ = app_id;
deployer_id_ = deployer_id;
ui->unsafe_sorting_box->setCheckState(uses_unsafe_sorting ? Qt::Checked : Qt::Unchecked);
has_separate_dirs_ = has_separate_dirs;
has_ignored_files_ = has_ignored_files;
ui->deploy_mode_box->setCurrentIndex(deploy_mode);
@@ -132,6 +135,7 @@ void AddDeployerDialog::updateSourceFields()
if(cur_text.empty())
return;
const bool is_reverse_deployer = cur_text == DeployerFactory::REVERSEDEPLOYER;
const bool is_openmw_plugin_deployer = cur_text == DeployerFactory::OPENMWPLUGINDEPLOYER;
const bool hide_source =
!DeployerFactory::AUTONOMOUS_DEPLOYERS.at(cur_text) || is_reverse_deployer;
const bool hide_mode = !hide_source && !is_reverse_deployer;
@@ -149,6 +153,7 @@ void AddDeployerDialog::updateSourceFields()
ui->rev_depl_ignore_button->setHidden(!is_reverse_deployer || !edit_mode_ ||
type_.toStdString() != DeployerFactory::REVERSEDEPLOYER ||
!has_ignored_files_);
ui->unsafe_sorting_box->setHidden(!is_openmw_plugin_deployer);
updateOkButton();
}
@@ -194,9 +199,20 @@ void AddDeployerDialog::on_buttonBox_accepted()
info.separate_profile_dirs = ui->rev_depl_separate_cb->checkState() == Qt::Checked;
info.update_ignore_list = ui->rev_depl_ignore_cb->checkState() == Qt::Checked;
if(edit_mode_)
{
info.enable_unsafe_sorting = ui->unsafe_sorting_box->checkState() == Qt::Checked;
emit deployerEdited(info, app_id_, deployer_id_);
}
else
{
if(ui->type_box->currentText().toStdString() == DeployerFactory::LOOTDEPLOYER)
info.enable_unsafe_sorting = true;
else if(ui->type_box->currentText().toStdString() == DeployerFactory::OPENMWPLUGINDEPLOYER)
info.enable_unsafe_sorting = ui->unsafe_sorting_box->checkState() == Qt::Checked;
else
info.enable_unsafe_sorting = false;
emit deployerAdded(info, app_id_);
}
}
void AddDeployerDialog::onFileDialogAccepted(const QString& path)

View File

@@ -43,6 +43,7 @@ public:
* \param deploy_mode Determines how files are deployed to the target directory.
* \param app_id Id of the ModdedApplication owning the edited Deployer.
* \param deployer_id Id of the edited Deployer.
* \param uses_unsafe_sorting Determines whether sorting mods can affect overwrite behavior.
* \param has_separate_dirs Used by ReverseDeployers: If true: Store files on a per profile basis.
* Else: All profiles use the same files.
* \param has_ignored_files Used by ReverseDeployers: If true: Deployer has files on the ignore list.
@@ -54,6 +55,7 @@ public:
Deployer::DeployMode deploy_mode,
int app_id,
int deployer_id,
bool uses_unsafe_sorting,
bool has_separate_dirs = false,
bool has_ignored_files = false);
/*! \brief Enables/ Disables the ui elements responsible for setting a source directory. */

View File

@@ -119,6 +119,17 @@
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="unsafe_sorting_box">
<property name="toolTip">
<string>When enabled: Sorting plugins uses LOOT. This will affect override order.
When disabled: Sorting only groups plugins by types. Does not affect override order.</string>
</property>
<property name="text">
<string>Use LOOT for sorting</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="rev_depl_ignore_cb">
<property name="toolTip">
@@ -165,7 +176,7 @@ li.checked::marker { content: &quot;\2612&quot;; }
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse</set>
<set>Qt::TextInteractionFlag::LinksAccessibleByMouse</set>
</property>
</widget>
</item>
@@ -182,7 +193,7 @@ li.checked::marker { content: &quot;\2612&quot;; }
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
@@ -195,10 +206,10 @@ li.checked::marker { content: &quot;\2612&quot;; }
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>

View File

@@ -169,3 +169,8 @@ bool DeployerListModel::hasIgnoredFiles() const
{
return deployer_info_.has_ignored_files;
}
bool DeployerListModel::usesUnsafeSorting() const
{
return deployer_info_.uses_unsafe_sorting;
}

View File

@@ -92,6 +92,13 @@ public:
* \return True if at least one file is ignored.
*/
bool hasIgnoredFiles() const;
/*!
* \brief Returns whether sorting mods is allowed affect overwrite behavior.
*
* If this is set to false, sorting will always be safe and only affect how mods are displayed.
* \return The safe sorting state.
*/
bool usesUnsafeSorting() const;
private:
/*! \brief Contains all mods managed by this model. */

View File

@@ -844,6 +844,7 @@ void MainWindow::showEditDeployerDialog(int deployer)
deploy_mode,
currentApp(),
deployer,
deployer_model_->usesUnsafeSorting(),
deployer_model_->hasSeparateDirs(),
deployer_model_->hasIgnoredFiles());
setBusyStatus(true, false);

View File

@@ -1,4 +1,6 @@
{
"auto_update_master_list" : true,
"current_profile" : 1,
"list_download_time" : 0,
"num_profiles" : 3
}