mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Tied in masterlist and userlist metadata with plugin data read, implemented merging and evaluation of conditions.
This commit is contained in:
@@ -42,13 +42,15 @@ BOSSv3 won't have a command line interface. Command line options may be added to
|
||||
Roadmap
|
||||
=======
|
||||
|
||||
1. Write new masterlist, userlist, settings file parsers. DONE
|
||||
2. Implement per-game handling. DONE
|
||||
3. Write API. DONE
|
||||
4. Develop sorting algorithm.
|
||||
5. Tie together automatic 'simple' sorting with masterlist, userlist data parsing and usage.
|
||||
6. Write masterlist updating code.
|
||||
7. Develop graphical user interface.
|
||||
- Write new masterlist, userlist, settings file parsers. DONE
|
||||
- Implement per-game handling. DONE
|
||||
- Write API. DONE
|
||||
- Develop sorting algorithm.
|
||||
- Tie together automatic 'simple' sorting with masterlist, userlist data parsing and usage. DONE
|
||||
- Write masterlist updating code.
|
||||
- Develop graphical user interface.
|
||||
- Error handling.
|
||||
- Initialisation and finishing routines (read settings file, write log file, etc.).
|
||||
|
||||
|
||||
Sorting Algorithm
|
||||
|
||||
@@ -234,11 +234,11 @@ condition: "file(\"Mart's Monster Mod for OOO.esm\") or file(\"FCOM_Convergence.
|
||||
<tr><td><code>name</code><td>string<td>✓<td>Can be an exact plugin filename or a regular expression plugin filename. If the period that precedes the file extension has been escaped (eg. <code>\.esp</code>, <code>\.esm</code>), the string is treated as a regular expression, otherwise it is treated as an exact filename.
|
||||
<tr><td><code>enabled</code><td>boolean<td>✗<td>Enables or disables use of the plugin object. Used for user rules, but no reason to use it in the masterlist. If unspecified, defaults to <code>true</code>.
|
||||
<tr><td><code>priority</code><td>integer<td>✗<td>??? If unspecified, defaults to <code>0</code>.
|
||||
<tr><td><code>after</code><td>file list<td>✗<td>Plugins that this plugin must load after, but which are not dependencies. Used for resolving specific compatibility issues and by user rules for specifying custom plugin positions.
|
||||
<tr><td><code>req</code><td>file list<td>✗<td>Files that this plugin requires to be present. If any of these files are missing, an error message will be displayed. The list can contain a mixture of plugins and other files, but all plugins must be given in their required load order relative to one another. Intended for use specifying implicit dependencies, as BOSS will detect a plugin's explicit masters itself.
|
||||
<tr><td><code>inc</code><td>file list<td>✗<td>Files that this plugin is incompatible with. If any of these files are present, an error message will be displayed.
|
||||
<tr><td><code>msg</code><td>message list<td>✗<td>The messages attached to this plugin.
|
||||
<tr><td><code>tag</code><td>tag list<td>✗<td>The Bash Tags suggested for this plugin.
|
||||
<tr><td><code>after</code><td>file list<td>✗<td>An unordered list of plugins that this plugin must load after, but which are not dependencies. Used for resolving specific compatibility issues and by user rules for specifying custom plugin positions.
|
||||
<tr><td><code>req</code><td>file list<td>✗<td>An unordered list of files that this plugin requires to be present. If any of these files are missing, an error message will be displayed. Intended for use specifying implicit dependencies, as BOSS will detect a plugin's explicit masters itself.
|
||||
<tr><td><code>inc</code><td>file list<td>✗<td>An unordered list of files that this plugin is incompatible with. If any of these files are present, an error message will be displayed.
|
||||
<tr><td><code>msg</code><td>message list<td>✗<td>The messages attached to this plugin. The messages will be displayed in the order that they are listed.
|
||||
<tr><td><code>tag</code><td>tag list<td>✗<td>An unordered list of Bash Tags suggested for this plugin.
|
||||
</table>
|
||||
<p>Example:
|
||||
<code class="box">name: "Oscuro's_Oblivion_Overhaul.esm"
|
||||
|
||||
+52
-13
@@ -169,7 +169,7 @@ namespace boss {
|
||||
}
|
||||
|
||||
bool Tag::operator < (const Tag& rhs) const {
|
||||
return Name() < rhs.Name();
|
||||
return (Name() < rhs.Name());
|
||||
}
|
||||
|
||||
bool Tag::IsAddition() const {
|
||||
@@ -216,6 +216,39 @@ namespace boss {
|
||||
}
|
||||
}
|
||||
|
||||
void Plugin::Merge(const Plugin& plugin) {
|
||||
//If 'name' differs or if 'enabled' is false for the given plugin, don't change anything.
|
||||
if (!boost::iequals(name, plugin.Name()) || !plugin.Enabled())
|
||||
return;
|
||||
|
||||
//The following should be replaced.
|
||||
priority = plugin.Priority();
|
||||
masters = plugin.Masters();
|
||||
formIDs = plugin.FormIDs();
|
||||
isMaster = plugin.IsMaster();
|
||||
|
||||
//Merge the following. If any files in the source already exist in the destination, they will be skipped. Files have display strings and condition strings which aren't considered when comparing them, so will be lost if the plugin being merged in has additional data in these strings.
|
||||
std::set<File> files = plugin.LoadAfter();
|
||||
loadAfter.insert(files.begin(), files.end());
|
||||
|
||||
files = plugin.Reqs();
|
||||
requirements.insert(files.begin(), files.end());
|
||||
|
||||
files = plugin.Incs();
|
||||
incompatibilities.insert(files.begin(), files.end());
|
||||
|
||||
//Merge Bash Tags too, but Tags can be added or removed, and BOSS should not be doing both for any one Tag, so whether a tag is added or removed is not considered during comparison. As such, the Tags from the plugin being merged in should override any equal-but-opposite Tags that already exist.
|
||||
std::set<Tag> bashTags = plugin.Tags();
|
||||
bashTags.insert(tags.begin(), tags.end());
|
||||
tags = bashTags;
|
||||
|
||||
//Messages are in an ordered list, and should be fully merged.
|
||||
std::list<Message> pMessages = plugin.Messages();
|
||||
messages.insert(messages.end(), pMessages.begin(), pMessages.end());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::string Plugin::Name() const {
|
||||
return name;
|
||||
}
|
||||
@@ -228,11 +261,11 @@ namespace boss {
|
||||
return priority;
|
||||
}
|
||||
|
||||
std::list<File> Plugin::LoadAfter() const {
|
||||
std::set<File> Plugin::LoadAfter() const {
|
||||
return loadAfter;
|
||||
}
|
||||
|
||||
std::list<File> Plugin::Reqs() const {
|
||||
std::set<File> Plugin::Reqs() const {
|
||||
return requirements;
|
||||
}
|
||||
|
||||
@@ -244,7 +277,7 @@ namespace boss {
|
||||
return messages;
|
||||
}
|
||||
|
||||
std::list<Tag> Plugin::Tags() const {
|
||||
std::set<Tag> Plugin::Tags() const {
|
||||
return tags;
|
||||
}
|
||||
|
||||
@@ -260,11 +293,11 @@ namespace boss {
|
||||
priority = p;
|
||||
}
|
||||
|
||||
void Plugin::LoadAfter(const std::list<File>& l) {
|
||||
void Plugin::LoadAfter(const std::set<File>& l) {
|
||||
loadAfter = l;
|
||||
}
|
||||
|
||||
void Plugin::Reqs(const std::list<File>& r) {
|
||||
void Plugin::Reqs(const std::set<File>& r) {
|
||||
requirements = r;
|
||||
}
|
||||
|
||||
@@ -276,21 +309,21 @@ namespace boss {
|
||||
messages = m;
|
||||
}
|
||||
|
||||
void Plugin::Tags(const std::list<Tag>& t) {
|
||||
void Plugin::Tags(const std::set<Tag>& t) {
|
||||
tags = t;
|
||||
}
|
||||
|
||||
void Plugin::EvalAllConditions(boss::Game& game) {
|
||||
for (list<File>::iterator it = loadAfter.begin(); it != loadAfter.end();) {
|
||||
for (set<File>::iterator it = loadAfter.begin(); it != loadAfter.end();) {
|
||||
if (!it->EvalCondition(game))
|
||||
it = loadAfter.erase(it);
|
||||
loadAfter.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
for (list<File>::iterator it = requirements.begin(); it != requirements.end();) {
|
||||
for (set<File>::iterator it = requirements.begin(); it != requirements.end();) {
|
||||
if (!it->EvalCondition(game))
|
||||
it = requirements.erase(it);
|
||||
requirements.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
@@ -309,9 +342,9 @@ namespace boss {
|
||||
++it;
|
||||
}
|
||||
|
||||
for (list<Tag>::iterator it = tags.begin(); it != tags.end();) {
|
||||
for (set<Tag>::iterator it = tags.begin(); it != tags.end();) {
|
||||
if (!it->EvalCondition(game))
|
||||
it = tags.erase(it++);
|
||||
tags.erase(it++);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
@@ -352,6 +385,12 @@ namespace boss {
|
||||
if (IsChildOf(rhs)) // Should probably also check for cyclic masters.
|
||||
return false;
|
||||
|
||||
if (Priority() > rhs.Priority())
|
||||
return true;
|
||||
|
||||
if (Priority() < rhs.Priority())
|
||||
return false;
|
||||
|
||||
if (!OverlapFormIDs(rhs).empty() && formIDs.size() != rhs.FormIDs().size())
|
||||
return formIDs.size() > rhs.FormIDs().size();
|
||||
|
||||
|
||||
+11
-10
@@ -125,23 +125,25 @@ namespace boss {
|
||||
Plugin(const std::string& name);
|
||||
Plugin(const std::string& name, const std::string& path);
|
||||
|
||||
void Merge(const Plugin& plugin);
|
||||
|
||||
std::string Name() const;
|
||||
bool Enabled() const;
|
||||
int Priority() const;
|
||||
std::list<File> LoadAfter() const;
|
||||
std::list<File> Reqs() const;
|
||||
std::set<File> LoadAfter() const;
|
||||
std::set<File> Reqs() const;
|
||||
std::set<File> Incs() const;
|
||||
std::list<Message> Messages() const;
|
||||
std::list<Tag> Tags() const;
|
||||
std::set<Tag> Tags() const;
|
||||
|
||||
void Name(const std::string& name);
|
||||
void Enabled(const bool enabled);
|
||||
void Priority(const int priority);
|
||||
void LoadAfter(const std::list<File>& after);
|
||||
void Reqs(const std::list<File>& reqs);
|
||||
void LoadAfter(const std::set<File>& after);
|
||||
void Reqs(const std::set<File>& reqs);
|
||||
void Incs(const std::set<File>& incs);
|
||||
void Messages(const std::list<Message>& messages);
|
||||
void Tags(const std::list<Tag>& tags);
|
||||
void Tags(const std::set<Tag>& tags);
|
||||
|
||||
void EvalAllConditions(boss::Game& game);
|
||||
bool HasNameOnly() const;
|
||||
@@ -167,17 +169,16 @@ namespace boss {
|
||||
std::string name;
|
||||
bool enabled; //Default to true.
|
||||
int priority; //Default to 0 : >0 is higher, <0 is lower priorities.
|
||||
std::list<File> loadAfter;
|
||||
std::list<File> requirements;
|
||||
std::set<File> loadAfter;
|
||||
std::set<File> requirements;
|
||||
std::set<File> incompatibilities;
|
||||
std::list<Message> messages;
|
||||
std::list<Tag> tags;
|
||||
std::set<Tag> tags;
|
||||
|
||||
std::vector<std::string> masters;
|
||||
std::set<FormID> formIDs;
|
||||
bool isMaster;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+3
-3
@@ -185,15 +185,15 @@ namespace YAML {
|
||||
rhs.Priority(node["priority"].as<int>());
|
||||
|
||||
if (node["after"])
|
||||
rhs.LoadAfter(node["after"].as< std::list<boss::File> >());
|
||||
rhs.LoadAfter(node["after"].as< std::set<boss::File> >());
|
||||
if (node["req"])
|
||||
rhs.Reqs(node["req"].as< std::list<boss::File> >());
|
||||
rhs.Reqs(node["req"].as< std::set<boss::File> >());
|
||||
if (node["inc"])
|
||||
rhs.Incs(node["inc"].as< std::set<boss::File> >());
|
||||
if (node["msg"])
|
||||
rhs.Messages(node["msg"].as< std::list<boss::Message> >());
|
||||
if (node["tag"])
|
||||
rhs.Tags(node["tag"].as< std::list<boss::Tag> >());
|
||||
rhs.Tags(node["tag"].as< std::set<boss::Tag> >());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
+90
-45
@@ -50,58 +50,26 @@ using namespace std;
|
||||
const char * const libespm_options_path = "libespm.cfg";
|
||||
const char * const libespm_game = "Skyrim";
|
||||
|
||||
const char * const masterlist_path = "masterlist.yaml";
|
||||
const char * const userlist_path = "userlist.yaml";
|
||||
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
/* cout << "Testing masterlist parser." << endl;
|
||||
/* Stuff still missing from a normal execution of BOSS:
|
||||
|
||||
YAML::Node test = YAML::LoadFile("masterlist-example.yaml");
|
||||
- Reading of settings file.
|
||||
- Detecting game to run for.
|
||||
- Error handling.
|
||||
- Masterlist updating.
|
||||
- Writing of log file.
|
||||
|
||||
list<boss::Message> globalMessages = test["globals"].as< list<boss::Message> >();
|
||||
list<boss::Plugin> pluginData = test["plugins"].as< list<boss::Plugin> >();
|
||||
Need to include libespm setup into game setup, once the former is made to be not global.
|
||||
*/
|
||||
|
||||
cout << "Testing masterlist generator." << endl;
|
||||
|
||||
// minimisePluginList(pluginData);
|
||||
|
||||
ofstream out("generated.yaml");
|
||||
YAML::Emitter yout;
|
||||
yout.SetIndent(2);
|
||||
yout << YAML::BeginMap
|
||||
<< YAML::Key << "globals" << YAML::Value << globalMessages
|
||||
<< YAML::Key << "plugins" << YAML::Value << pluginData
|
||||
<< YAML::EndMap;
|
||||
|
||||
out << yout.c_str();
|
||||
out.close();
|
||||
|
||||
cout << "Testing game settings structure." << endl;
|
||||
|
||||
boss::Game game(boss::GAME_TES5, "/media/oliver/6CF05918F058EA3A/Program Files (x86)/Steam/steamapps/common/skyrim");
|
||||
|
||||
for (list<boss::Plugin>::iterator it=pluginData.begin(), endIt=pluginData.end(); it != endIt; ++it) {
|
||||
try {
|
||||
it->EvalAllConditions(game);
|
||||
} catch (boss::error& e) {
|
||||
cout << e.what() << endl;
|
||||
}
|
||||
}
|
||||
|
||||
ofstream out2("evaled.yaml");
|
||||
YAML::Emitter yout2;
|
||||
yout2.SetIndent(2);
|
||||
yout2 << YAML::BeginMap
|
||||
<< YAML::Key << "globals" << YAML::Value << globalMessages
|
||||
<< YAML::Key << "plugins" << YAML::Value << pluginData
|
||||
<< YAML::EndMap;
|
||||
|
||||
out2 << yout2.c_str();
|
||||
out2.close();
|
||||
*/
|
||||
cout << "Setting up libespm and BOSS..." << endl;
|
||||
|
||||
|
||||
|
||||
// Set up libesm.
|
||||
common::options::setGame(libespm_game);
|
||||
ifstream input(libespm_options_path);
|
||||
@@ -136,6 +104,82 @@ int main(int argc, char *argv[]) {
|
||||
cout << "Time taken to read plugins: " << (end - start) << " seconds." << endl;
|
||||
start = time(NULL);
|
||||
|
||||
YAML::Node mlist, ulist;
|
||||
list<boss::Message> messages, mlist_messages, ulist_messages;
|
||||
list<boss::Plugin> mlist_plugins, ulist_plugins;
|
||||
|
||||
if (fs::exists(masterlist_path)) {
|
||||
cout << "Parsing masterlist..." << endl;
|
||||
|
||||
mlist = YAML::LoadFile(masterlist_path);
|
||||
if (mlist["globals"])
|
||||
mlist_messages = mlist["globals"].as< list<boss::Message> >();
|
||||
if (mlist["plugins"])
|
||||
mlist_plugins = mlist["plugins"].as< list<boss::Plugin> >();
|
||||
|
||||
end = time(NULL);
|
||||
cout << "Time taken to parse masterlist: " << (end - start) << " seconds." << endl;
|
||||
start = time(NULL);
|
||||
}
|
||||
|
||||
if (fs::exists(userlist_path)) {
|
||||
cout << "Parsing userlist..." << endl;
|
||||
|
||||
ulist = YAML::LoadFile(userlist_path);
|
||||
if (ulist["globals"])
|
||||
ulist_messages = ulist["globals"].as< list<boss::Message> >();
|
||||
if (ulist["plugins"])
|
||||
ulist_plugins = ulist["plugins"].as< list<boss::Plugin> >();
|
||||
|
||||
end = time(NULL);
|
||||
cout << "Time taken to parse userlist: " << (end - start) << " seconds." << endl;
|
||||
start = time(NULL);
|
||||
}
|
||||
|
||||
if (fs::exists(masterlist_path) || fs::exists(userlist_path)) {
|
||||
cout << "Merging plugin lists..." << endl;
|
||||
|
||||
//Merge all global message lists.
|
||||
messages = mlist_messages;
|
||||
messages.insert(messages.end(), ulist_messages.begin(), ulist_messages.end());
|
||||
|
||||
//Merge plugin list, masterlist and userlist plugin data.
|
||||
for (list<boss::Plugin>::iterator it=plugins.begin(), endIt=plugins.end(); it != endIt; ++it) {
|
||||
//Check if there is already a plugin in the 'plugins' list or not.
|
||||
list<boss::Plugin>::iterator pos = std::find(mlist_plugins.begin(), mlist_plugins.end(), *it);
|
||||
|
||||
if (pos != mlist_plugins.end()) {
|
||||
//Need to merge plugins.
|
||||
it->Merge(*pos);
|
||||
}
|
||||
|
||||
pos = std::find(ulist_plugins.begin(), ulist_plugins.end(), *it);
|
||||
|
||||
if (pos != ulist_plugins.end()) {
|
||||
//Need to merge plugins.
|
||||
it->Merge(*pos);
|
||||
}
|
||||
}
|
||||
|
||||
end = time(NULL);
|
||||
cout << "Time taken to merge lists: " << (end - start) << " seconds." << endl;
|
||||
start = time(NULL);
|
||||
}
|
||||
|
||||
cout << "Evaluating plugin list..." << endl;
|
||||
|
||||
for (list<boss::Plugin>::iterator it=plugins.begin(), endIt=plugins.end(); it != endIt; ++it) {
|
||||
try {
|
||||
it->EvalAllConditions(game);
|
||||
} catch (boss::error& e) {
|
||||
cout << e.what() << endl;
|
||||
}
|
||||
}
|
||||
|
||||
end = time(NULL);
|
||||
cout << "Time taken to evaluate plugin list: " << (end - start) << " seconds." << endl;
|
||||
start = time(NULL);
|
||||
|
||||
for (list<boss::Plugin>::iterator it=plugins.begin(), endIt = plugins.end(); it != endIt; ++it) {
|
||||
cout << it->Name() << endl
|
||||
<< '\t' << "Number of records: " << it->FormIDs().size() << endl
|
||||
@@ -163,7 +207,8 @@ int main(int argc, char *argv[]) {
|
||||
cout << "Sorting plugins..." << endl;
|
||||
|
||||
plugins.sort();
|
||||
|
||||
|
||||
end = time(NULL);
|
||||
cout << "Time taken to sort plugins: " << (end - start) << " seconds." << endl;
|
||||
|
||||
for (list<boss::Plugin>::iterator it=plugins.begin(), endIt = plugins.end(); it != endIt; ++it)
|
||||
|
||||
Reference in New Issue
Block a user