Enforce hardcoded plugin positions during sorting

It's very verbose to enforce this through masterlist metadata, and the data is available through libloadorder. Don't treat Update.esm for the original Skyrim as having a hardcoded position, as it doesn't even though it's implicitly active.
This commit is contained in:
Oliver Hamlet
2018-06-02 22:22:42 +01:00
parent f093a9c7e2
commit c36eb4b0a6
8 changed files with 184 additions and 3 deletions
+1 -1
View File
@@ -100,7 +100,7 @@ set(LIBGIT2_LIBRARIES "${BINARY_DIR}/${CMAKE_CFG_INTDIR}/${CMAKE_STATIC_LIBRARY_
ExternalProject_Add(libloadorder
PREFIX "external"
URL "https://github.com/WrinklyNinja/libloadorder/archive/11.2.2.tar.gz"
URL "https://github.com/WrinklyNinja/libloadorder/archive/11.3.0.tar.gz"
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
BUILD_COMMAND cargo build --release --manifest-path ffi/Cargo.toml --features ffi-headers --target ${RUST_TARGET}
+20
View File
@@ -129,6 +129,26 @@ std::vector<std::string> LoadOrderHandler::GetLoadOrder() const {
return loadOrder;
}
std::vector<std::string> LoadOrderHandler::GetImplicitlyActivePlugins() const {
auto logger = getLogger();
if (logger) {
logger->debug("Getting implicitly active plugins.");
}
char** pluginArr;
size_t pluginArrSize;
unsigned int ret =
lo_get_implicitly_active_plugins(gh_, &pluginArr, &pluginArrSize);
HandleError("get implicitly active plugins", ret);
std::vector<string> loadOrder(pluginArr, pluginArr + pluginArrSize);
lo_free_string_array(pluginArr, pluginArrSize);
return loadOrder;
}
void LoadOrderHandler::SetLoadOrder(
const std::vector<std::string>& loadOrder) const {
auto logger = getLogger();
+2
View File
@@ -48,6 +48,8 @@ public:
std::vector<std::string> GetLoadOrder() const;
std::vector<std::string> GetImplicitlyActivePlugins() const;
bool IsPluginActive(const std::string& pluginName) const;
void SetLoadOrder(const std::vector<std::string>& loadOrder) const;
+47
View File
@@ -132,6 +132,8 @@ std::vector<std::string> PluginSorter::Sort(Game& game) {
}
AddSpecificEdges();
AddHardcodedPluginEdges(game);
AddGroupEdges();
if (logger_) {
@@ -328,6 +330,51 @@ void PluginSorter::AddEdge(const vertex_t& fromVertex,
}
}
void PluginSorter::AddHardcodedPluginEdges(Game& game) {
if (logger_) {
logger_->trace("Adding hardcoded plugin edges.");
}
auto implicitlyActivePlugins =
game.GetLoadOrderHandler()->GetImplicitlyActivePlugins();
std::set<std::string> processedPlugins;
for (const auto& plugin : implicitlyActivePlugins) {
auto lowercasedName = boost::locale::to_lower(plugin);
processedPlugins.insert(lowercasedName);
if (game.Type() == GameType::tes5 && lowercasedName == "update.esm") {
if (logger_) {
logger_->trace(
"Skipping adding hardcoded plugin edges for Update.esm as it does "
"not have a hardcoded position for Skyrim.");
continue;
}
}
vertex_t pluginVertex;
if (!GetVertexByName(plugin, pluginVertex)) {
if (logger_) {
logger_->trace(
"Skipping adding harcoded plugin edges for \"{}\" as it is not "
"installed.",
plugin);
}
continue;
}
vertex_it vit, vitend;
for (tie(vit, vitend) = boost::vertices(graph_); vit != vitend; ++vit) {
auto& graphPlugin = graph_[*vit];
if (processedPlugins.count(graphPlugin.GetLowercasedName()) == 0) {
AddEdge(pluginVertex, *vit);
}
}
}
}
void PluginSorter::AddSpecificEdges() {
// Add edges for all relationships that aren't overlaps.
vertex_it vit, vitend;
+1
View File
@@ -59,6 +59,7 @@ private:
void AddPluginVertices(Game& game);
void AddSpecificEdges();
void AddHardcodedPluginEdges(Game& game);
void AddGroupEdges();
void AddOverlapEdges();
void AddTieBreakEdges();
+2 -1
View File
@@ -42,7 +42,8 @@ public:
std::unordered_set<std::string> GetAfterGroupPlugins() const;
void SetAfterGroupPlugins(std::unordered_set<std::string> plugins);
using PluginMetadata::GetLowercasedName;
using PluginMetadata::GetGroup;
using PluginMetadata::GetLoadAfterFiles;
using PluginMetadata::GetRequirements;
@@ -59,6 +59,38 @@ protected:
loadOrderHandler_.Init(GetParam(), dataPath.parent_path(), localPath));
}
std::vector<std::string> getImplicitlyActivePlugins() {
switch (GetParam()) {
case GameType::tes5:
return {"Skyrim.esm", "Update.esm"};
case GameType::tes5se:
return {"Skyrim.esm",
"Update.esm",
"Dawnguard.esm",
"Hearthfires.esm",
"Dragonborn.esm"};
case GameType::tes5vr:
return {"Skyrim.esm",
"Update.esm",
"Dawnguard.esm",
"Hearthfires.esm",
"Dragonborn.esm",
"SkyrimVR.esm"};
case GameType::fo4:
return {"Fallout4.esm",
"DLCRobot.esm",
"DLCworkshop01.esm",
"DLCCoast.esm",
"DLCworkshop02.esm",
"DLCworkshop03.esm",
"DLCNukaWorld.esm"};
case GameType::fo4vr:
return {"Fallout4.esm", "Fallout4_VR.esm"};
default:
return {};
}
}
LoadOrderHandler loadOrderHandler_;
std::vector<std::string> loadOrderToSet_;
};
@@ -139,6 +171,25 @@ TEST_P(LoadOrderHandlerTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
ASSERT_EQ(getLoadOrder(), loadOrderHandler_.GetLoadOrder());
}
TEST_P(LoadOrderHandlerTest,
getImplicitlyActivePluginsShouldThrowIfTheHandlerHasNotBeenInitialised) {
EXPECT_THROW(loadOrderHandler_.GetImplicitlyActivePlugins(),
std::system_error);
}
TEST_P(
LoadOrderHandlerTest,
getImplicitlyActivePluginsShouldReturnValidDataEvenIfStateHasNotBeenLoaded) {
initialiseHandler();
ASSERT_EQ(getImplicitlyActivePlugins(),
loadOrderHandler_.GetImplicitlyActivePlugins());
loadOrderHandler_.LoadCurrentState();
ASSERT_EQ(getImplicitlyActivePlugins(),
loadOrderHandler_.GetImplicitlyActivePlugins());
}
TEST_P(LoadOrderHandlerTest,
setLoadOrderShouldThrowIfTheHandlerHasNotBeenInitialised) {
EXPECT_THROW(loadOrderHandler_.SetLoadOrder(loadOrderToSet_),
@@ -38,6 +38,7 @@ protected:
PluginSorterTest() :
game_(GetParam(), dataPath.parent_path(), localPath),
masterlistPath_("./userlist.yaml"),
cccPath_(dataPath.parent_path() / getCCCFilename()),
blankEslEsp("Blank.esl.esp") {}
void TearDown() {
@@ -48,6 +49,7 @@ protected:
}
boost::filesystem::remove(masterlistPath_);
boost::filesystem::remove(cccPath_);
}
void loadInstalledPlugins(Game &game_, bool headersOnly) {
@@ -104,9 +106,32 @@ protected:
masterlist.close();
}
std::string getCCCFilename() {
if (GetParam() == GameType::fo4) {
return "Fallout4.ccc";
}
else {
// Not every game has a .ccc file, but Skyrim SE does, so just assume that.
return "Skyrim.ccc";
}
}
void GenerateCCCFile() {
using std::endl;
if (GetParam() == GameType::fo4) {
boost::filesystem::ofstream ccc(cccPath_);
ccc << blankDifferentEsm << endl
<< blankDifferentMasterDependentEsm << endl;
ccc.close();
}
}
Game game_;
const std::string blankEslEsp;
const boost::filesystem::path masterlistPath_;
const boost::filesystem::path cccPath_;
};
// Pass an empty first argument, as it's a prefix for the test instantation,
@@ -319,7 +344,6 @@ TEST_P(
blankEsm,
blankMasterDependentEsm,
blankDifferentEsm,
masterFile,
blankDifferentMasterDependentEsm,
blankEsp,
blankDifferentEsp,
@@ -330,7 +354,10 @@ TEST_P(
});
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
expectedSortedOrder.insert(expectedSortedOrder.begin(), masterFile);
expectedSortedOrder.insert(expectedSortedOrder.begin() + 5, blankEsl);
} else {
expectedSortedOrder.insert(expectedSortedOrder.begin() + 3, masterFile);
}
std::vector<std::string> sorted = ps.Sort(game_);
@@ -466,6 +493,38 @@ TEST_P(PluginSorterTest,
EXPECT_EQ(expectedSortedOrder, sorted);
}
TEST_P(PluginSorterTest,
sortingShouldUseTheGameCCCFileToEnforceHardcodedLoadOrderPositions) {
if (GetParam() != GameType::fo4) {
return;
}
// Need to generate CCC file then recreate game object as the file is only
// read during intialisation.
GenerateCCCFile();
Game newGame(GetParam(), dataPath.parent_path(), localPath);
ASSERT_NO_THROW(loadInstalledPlugins(newGame, false));
PluginSorter ps;
std::vector<std::string> expectedSortedOrder({
masterFile,
blankDifferentEsm,
blankDifferentMasterDependentEsm,
blankEsm,
blankMasterDependentEsm,
blankEsl,
blankEsp,
blankDifferentEsp,
blankMasterDependentEsp,
blankDifferentMasterDependentEsp,
blankPluginDependentEsp,
blankDifferentPluginDependentEsp,
});
std::vector<std::string> sorted = ps.Sort(newGame);
EXPECT_EQ(expectedSortedOrder, sorted);
}
TEST_P(PluginSorterTest, sortingShouldThrowIfACyclicInteractionIsEncountered) {
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
PluginMetadata plugin(blankEsm);