diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c79780e..1df2655c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,7 +89,7 @@ set(PSEUDOSEM_INCLUDE_DIRS "${SOURCE_DIR}/include") ExternalProject_Add(testing-metadata PREFIX "external" - URL "https://github.com/loot/testing-metadata/archive/1.0.0.tar.gz" + URL "https://github.com/loot/testing-metadata/archive/1.1.0.tar.gz" CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "") diff --git a/docs/LOOT Metadata Syntax.html b/docs/LOOT Metadata Syntax.html index f708e1ce..0963db15 100644 --- a/docs/LOOT Metadata Syntax.html +++ b/docs/LOOT Metadata Syntax.html @@ -240,13 +240,17 @@ var {
| Key Name | Value Type | Notes |
|---|---|---|
bash_tags | string list | A list of Bash Tags that are supported by the masterlist's game. These Bash Tags are used to provide autocomplete suggestions in LOOT's metadata editor. |
globals | message list | A list of message data structures for messages that are displayed independently of any plugin. |
plugins | plugin list and plugin set | The plugin data structures that hold all the plugin metadata within the file. It is a mixture of a list and a set because no non-regex plugin value may be equal to any other non-regex plugin value, but there may be any number of equal regex plugin values, and non-regex plugin values may be equal to regex plugin values.
If multiple plugin values match a single plugin, their metadata is merged in the order the values are listed, and as defined later in this document. |
Other keys may also be present, but are not processed by LOOT. The message and plugin data structures are detailed in the next section.
An example metadata file: -
globals:
+bash_tags:
+ - 'C.Climate'
+ - 'Relev'
+globals:
- type: say
content: 'You are using the latest version of LOOT.'
condition: 'version("LOOT", "0.5.0.0", ==)'
diff --git a/src/backend/game/game_settings.cpp b/src/backend/game/game_settings.cpp
index 5d33b284..12001ede 100644
--- a/src/backend/game/game_settings.cpp
+++ b/src/backend/game/game_settings.cpp
@@ -54,73 +54,6 @@ namespace loot {
_masterFile = "Oblivion.esm";
_repositoryURL = "https://github.com/loot/oblivion.git";
_repositoryBranch = "v0.8";
-
- bashTags = {
- "Actors.ACBS",
- "Actors.AIData",
- "Actors.AIPackages",
- "Actors.AIPackagesForceAdd",
- "Actors.Animations",
- "Actors.CombatStyle",
- "Actors.DeathItem",
- "Actors.Skeleton",
- "Actors.Spells",
- "Actors.SpellsForceAdd",
- "Actors.Stats",
- "Body-F",
- "Body-M",
- "Body-Size-F",
- "Body-Size-M",
- "C.Climate",
- "C.Light",
- "C.Music",
- "C.Name",
- "C.Owner",
- "C.RecordFlags",
- "C.Water",
- "Creatures.Blood",
- "Deactivate",
- "Delev",
- "Eyes",
- "Factions",
- "Filter",
- "Graphics",
- "Hair",
- "IIM",
- "Invent",
- "InventOnly",
- "Merge",
- "MustBeActiveIfImported",
- "Names",
- "NoMerge",
- "NpcFaces",
- "NpcFacesForceFullImport",
- "NPC.Class",
- "Npc.EyesOnly",
- "Npc.HairOnly",
- "NPC.Race",
- "Relations",
- "Relev",
- "Roads",
- "R.AddSpells",
- "R.Attributes-F",
- "R.Attributes-M",
- "R.ChangeSpells",
- "R.Description",
- "R.Ears",
- "R.Head",
- "R.Mouth",
- "R.Relations",
- "R.Skills",
- "R.Teeth",
- "ScriptContents",
- "Scripts",
- "Sound",
- "SpellStats",
- "Stats",
- "Voice-F",
- "Voice-M",
- };
}
else if (Id() == GameSettings::tes5) {
_name = "TES V: Skyrim";
@@ -129,78 +62,6 @@ namespace loot {
_masterFile = "Skyrim.esm";
_repositoryURL = "https://github.com/loot/skyrim.git";
_repositoryBranch = "v0.8";
-
- bashTags = {
- "Actors.ACBS",
- "Actors.AIData",
- "Actors.AIPackages",
- "Actors.AIPackagesForceAdd",
- "Actors.Animations",
- "Actors.CombatStyle",
- "Actors.DeathItem",
- "Actors.Skeleton",
- "Actors.Spells",
- "Actors.SpellsForceAdd",
- "Actors.Stats",
- "Body-F",
- "Body-M",
- "Body-Size-F",
- "Body-Size-M",
- "C.Acoustic",
- "C.Climate",
- "C.ImageSpace",
- "C.Light",
- "C.Location",
- "C.Music",
- "C.Name",
- "C.Owner",
- "C.RecordFlags",
- "C.Regions",
- "C.SkyLighting",
- "C.Water",
- "Creatures.Blood",
- "Deactivate",
- "Delev",
- "Eyes",
- "Factions",
- "Filter",
- "Graphics",
- "Hair",
- "IIM",
- "Invent",
- "InventOnly",
- "Merge",
- "MustBeActiveIfImported",
- "Names",
- "NoMerge",
- "NpcFaces",
- "NpcFacesForceFullImport",
- "NPC.Class",
- "Npc.EyesOnly",
- "Npc.HairOnly",
- "NPC.Race",
- "Relations",
- "Relev",
- "Roads",
- "R.AddSpells",
- "R.Attributes-F",
- "R.Attributes-M",
- "R.ChangeSpells",
- "R.Description",
- "R.Ears",
- "R.Head",
- "R.Mouth",
- "R.Relations",
- "R.Skills",
- "R.Teeth",
- "ScriptContents",
- "Scripts",
- "Sound",
- "SpellStats",
- "Stats",
- "Voice-F",
- "Voice-M",
- };
}
else if (Id() == GameSettings::fo3) {
_name = "Fallout 3";
@@ -209,11 +70,6 @@ namespace loot {
_masterFile = "Fallout3.esm";
_repositoryURL = "https://github.com/loot/fallout3.git";
_repositoryBranch = "v0.8";
-
- bashTags = {
- "Deflst",
- "Destructible",
- };
}
else if (Id() == GameSettings::fonv) {
_name = "Fallout: New Vegas";
@@ -222,12 +78,6 @@ namespace loot {
_masterFile = "FalloutNV.esm";
_repositoryURL = "https://github.com/loot/falloutnv.git";
_repositoryBranch = "v0.8";
-
- bashTags = {
- "Deflst",
- "Destructible",
- "WeaponMods",
- };
}
else if (Id() == GameSettings::fo4) {
_name = "Fallout 4";
@@ -341,10 +191,6 @@ namespace loot {
return LootPaths::getLootDataPath() / _lootFolderName / "userlist.yaml";
}
- std::vector GameSettings::getSupportedBashTags() const {
- return bashTags;
- }
-
GameSettings& GameSettings::SetName(const std::string& name) {
BOOST_LOG_TRIVIAL(trace) << "Setting \"" << _name << "\" name to: " << name;
_name = name;
diff --git a/src/backend/game/game_settings.h b/src/backend/game/game_settings.h
index 9fa767d5..2d14310e 100644
--- a/src/backend/game/game_settings.h
+++ b/src/backend/game/game_settings.h
@@ -59,8 +59,6 @@ namespace loot {
boost::filesystem::path MasterlistPath() const;
boost::filesystem::path UserlistPath() const;
- std::vector getSupportedBashTags() const;
-
GameSettings& SetName(const std::string& name);
GameSettings& SetMaster(const std::string& masterFile);
GameSettings& SetRegistryKey(const std::string& registry);
@@ -86,8 +84,6 @@ namespace loot {
std::string _repositoryBranch;
boost::filesystem::path _gamePath; //Path to the game's folder.
-
- std::vector bashTags;
};
}
diff --git a/src/backend/metadata_list.cpp b/src/backend/metadata_list.cpp
index 48c6c341..c063e62d 100644
--- a/src/backend/metadata_list.cpp
+++ b/src/backend/metadata_list.cpp
@@ -33,9 +33,7 @@ using namespace std;
namespace loot {
void MetadataList::Load(const boost::filesystem::path& filepath) {
- plugins.clear();
- regexPlugins.clear();
- messages.clear();
+ clear();
BOOST_LOG_TRIVIAL(debug) << "Loading file: " << filepath;
@@ -58,7 +56,10 @@ namespace loot {
}
}
if (metadataList["globals"])
- messages = metadataList["globals"].as< list >();
+ messages = metadataList["globals"].as>();
+
+ if (metadataList["bash_tags"])
+ bashTags_ = metadataList["bash_tags"].as>();
BOOST_LOG_TRIVIAL(debug) << "File loaded successfully.";
}
@@ -68,6 +69,7 @@ namespace loot {
YAML::Emitter yout;
yout.SetIndent(2);
yout << YAML::BeginMap
+ << YAML::Key << "bash_tags" << YAML::Value << bashTags_
<< YAML::Key << "plugins" << YAML::Value << Plugins()
<< YAML::Key << "globals" << YAML::Value << messages
<< YAML::EndMap;
@@ -78,6 +80,7 @@ namespace loot {
}
void MetadataList::clear() {
+ bashTags_.clear();
plugins.clear();
regexPlugins.clear();
messages.clear();
@@ -95,6 +98,10 @@ namespace loot {
return messages;
}
+ std::set MetadataList::BashTags() const {
+ return bashTags_;
+ }
+
// Merges multiple matching regex entries if any are found.
PluginMetadata MetadataList::FindPlugin(const PluginMetadata& plugin) const {
PluginMetadata match(plugin.Name());
diff --git a/src/backend/metadata_list.h b/src/backend/metadata_list.h
index 8ecfd9ee..f6400a3f 100644
--- a/src/backend/metadata_list.h
+++ b/src/backend/metadata_list.h
@@ -54,6 +54,7 @@ namespace loot {
std::list Plugins() const;
std::list Messages() const;
+ std::set BashTags() const;
// Merges multiple matching regex entries if any are found.
PluginMetadata FindPlugin(const PluginMetadata& plugin) const;
@@ -69,6 +70,7 @@ namespace loot {
void EvalAllConditions(Game& game, const unsigned int language);
protected:
+ std::set bashTags_;
std::unordered_set plugins;
std::list regexPlugins;
std::list messages;
diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp
index 7f15450a..4c9226d0 100644
--- a/src/gui/handler.cpp
+++ b/src/gui/handler.cpp
@@ -701,7 +701,7 @@ namespace loot {
// Now store global messages.
gameNode["globalMessages"] = GetGeneralMessages();
- gameNode["bashTags"] = _lootState.CurrentGame().getSupportedBashTags();
+ gameNode["bashTags"] = _lootState.CurrentGame().GetMasterlist().BashTags();
// Now store plugin data.
for (const auto& plugin : installed) {
@@ -822,6 +822,9 @@ namespace loot {
gameNode["masterlist"]["date"] = e.what();
}
+ // Store bash tags in case they have changed.
+ gameNode["bashTags"] = _lootState.CurrentGame().GetMasterlist().BashTags();
+
// Store global messages in case they have changed.
gameNode["globalMessages"] = GetGeneralMessages();
diff --git a/src/gui/html/js/events.js b/src/gui/html/js/events.js
index 16d8c2be..a83a7a3c 100644
--- a/src/gui/html/js/events.js
+++ b/src/gui/html/js/events.js
@@ -80,6 +80,9 @@ function updateMasterlist() {
loot.game.masterlist = result.masterlist;
loot.game.globalMessages = result.globalMessages;
+ /* Update Bash Tag autocomplete suggestions. */
+ loot.DOM.initialiseAutocompleteBashTags(result.bashTags);
+
result.plugins.forEach((resultPlugin) => {
const existingPlugin = loot.game.plugins.find(plugin => plugin.name === resultPlugin.name);
if (existingPlugin) {
diff --git a/src/tests/backend/game/game_settings_test.h b/src/tests/backend/game/game_settings_test.h
index 1e2e8c67..69c7a780 100644
--- a/src/tests/backend/game/game_settings_test.h
+++ b/src/tests/backend/game/game_settings_test.h
@@ -59,8 +59,6 @@ namespace loot {
EXPECT_EQ("", game.DataPath());
EXPECT_EQ("", game.MasterlistPath());
EXPECT_EQ("", game.UserlistPath());
-
- EXPECT_TRUE(game.getSupportedBashTags().empty());
}
TEST_P(GameSettingsTest, idConstructorShouldInitialiseSettingsToDefaultsForThatGame) {
@@ -89,180 +87,6 @@ namespace loot {
EXPECT_EQ(LootPaths::getLootDataPath() / "folder" / "userlist.yaml", game.UserlistPath());
}
- TEST_P(GameSettingsTest, idConstructorShouldSetCorrectBashTagsForTES4) {
- game = GameSettings(GameSettings::tes4);
-
- std::vector expectedBashTags = {
- "Actors.ACBS",
- "Actors.AIData",
- "Actors.AIPackages",
- "Actors.AIPackagesForceAdd",
- "Actors.Animations",
- "Actors.CombatStyle",
- "Actors.DeathItem",
- "Actors.Skeleton",
- "Actors.Spells",
- "Actors.SpellsForceAdd",
- "Actors.Stats",
- "Body-F",
- "Body-M",
- "Body-Size-F",
- "Body-Size-M",
- "C.Climate",
- "C.Light",
- "C.Music",
- "C.Name",
- "C.Owner",
- "C.RecordFlags",
- "C.Water",
- "Creatures.Blood",
- "Deactivate",
- "Delev",
- "Eyes",
- "Factions",
- "Filter",
- "Graphics",
- "Hair",
- "IIM",
- "Invent",
- "InventOnly",
- "Merge",
- "MustBeActiveIfImported",
- "Names",
- "NoMerge",
- "NpcFaces",
- "NpcFacesForceFullImport",
- "NPC.Class",
- "Npc.EyesOnly",
- "Npc.HairOnly",
- "NPC.Race",
- "Relations",
- "Relev",
- "Roads",
- "R.AddSpells",
- "R.Attributes-F",
- "R.Attributes-M",
- "R.ChangeSpells",
- "R.Description",
- "R.Ears",
- "R.Head",
- "R.Mouth",
- "R.Relations",
- "R.Skills",
- "R.Teeth",
- "ScriptContents",
- "Scripts",
- "Sound",
- "SpellStats",
- "Stats",
- "Voice-F",
- "Voice-M",
- };
- EXPECT_EQ(expectedBashTags, game.getSupportedBashTags());
- }
-
- TEST_P(GameSettingsTest, idConstructorShouldSetCorrectBashTagsForTES5) {
- game = GameSettings(GameSettings::tes5);
-
- std::vector expectedBashTags = {
- "Actors.ACBS",
- "Actors.AIData",
- "Actors.AIPackages",
- "Actors.AIPackagesForceAdd",
- "Actors.Animations",
- "Actors.CombatStyle",
- "Actors.DeathItem",
- "Actors.Skeleton",
- "Actors.Spells",
- "Actors.SpellsForceAdd",
- "Actors.Stats",
- "Body-F",
- "Body-M",
- "Body-Size-F",
- "Body-Size-M",
- "C.Acoustic",
- "C.Climate",
- "C.ImageSpace",
- "C.Light",
- "C.Location",
- "C.Music",
- "C.Name",
- "C.Owner",
- "C.RecordFlags",
- "C.Regions",
- "C.SkyLighting",
- "C.Water",
- "Creatures.Blood",
- "Deactivate",
- "Delev",
- "Eyes",
- "Factions",
- "Filter",
- "Graphics",
- "Hair",
- "IIM",
- "Invent",
- "InventOnly",
- "Merge",
- "MustBeActiveIfImported",
- "Names",
- "NoMerge",
- "NpcFaces",
- "NpcFacesForceFullImport",
- "NPC.Class",
- "Npc.EyesOnly",
- "Npc.HairOnly",
- "NPC.Race",
- "Relations",
- "Relev",
- "Roads",
- "R.AddSpells",
- "R.Attributes-F",
- "R.Attributes-M",
- "R.ChangeSpells",
- "R.Description",
- "R.Ears",
- "R.Head",
- "R.Mouth",
- "R.Relations",
- "R.Skills",
- "R.Teeth",
- "ScriptContents",
- "Scripts",
- "Sound",
- "SpellStats",
- "Stats",
- "Voice-F",
- "Voice-M",
- };
- EXPECT_EQ(expectedBashTags, game.getSupportedBashTags());
- }
-
- TEST_P(GameSettingsTest, idConstructorShouldSetNoBashTagsForFO3) {
- game = GameSettings(GameSettings::fo3);
-
- std::vector expectedBashTags = {
- "Deflst",
- "Destructible",
- };
- EXPECT_EQ(expectedBashTags, game.getSupportedBashTags());
- }
-
- TEST_P(GameSettingsTest, idConstructorShouldSetNoBashTagsForFONV) {
- game = GameSettings(GameSettings::fonv);
-
- std::vector expectedBashTags = {
- "Deflst",
- "Destructible",
- "WeaponMods",
- };
- EXPECT_EQ(expectedBashTags, game.getSupportedBashTags());
- }
-
- TEST_P(GameSettingsTest, idConstructorShouldSetNoBashTagsForFO4) {
- EXPECT_TRUE(GameSettings(GameSettings::fo4).getSupportedBashTags().empty());
- }
-
TEST_P(GameSettingsTest, isInstalledShouldBeFalseIfGamePathIsNotSet) {
GameSettings game;
EXPECT_FALSE(game.IsInstalled());
diff --git a/src/tests/backend/metadata_list_test.h b/src/tests/backend/metadata_list_test.h
index 1f66edde..7ef81c93 100644
--- a/src/tests/backend/metadata_list_test.h
+++ b/src/tests/backend/metadata_list_test.h
@@ -26,6 +26,8 @@ along with LOOT. If not, see
#define LOOT_TEST_BACKEND_METADATA_LIST
#include "backend/metadata_list.h"
+
+#include "backend/game/game.h"
#include "tests/base_game_test.h"
namespace loot {
@@ -110,6 +112,16 @@ namespace loot {
}), names);
}
+ TEST_P(MetadataListTest, loadShouldLoadBashTags) {
+ MetadataList metadataList;
+ ASSERT_NO_THROW(metadataList.Load(metadataPath));
+
+ EXPECT_EQ(std::set({
+ "C.Climate",
+ "Relev"
+ }), metadataList.BashTags());
+ }
+
TEST_P(MetadataListTest, loadShouldThrowIfAnInvalidMetadataFileIsGiven) {
MetadataList ml;
for (const auto& path : invalidMetadataPaths) {
@@ -123,10 +135,12 @@ namespace loot {
ASSERT_NO_THROW(metadataList.Load(metadataPath));
ASSERT_FALSE(metadataList.Messages().empty());
ASSERT_FALSE(metadataList.Plugins().empty());
+ ASSERT_FALSE(metadataList.BashTags().empty());
EXPECT_ANY_THROW(metadataList.Load(blankEsm));
EXPECT_TRUE(metadataList.Messages().empty());
EXPECT_TRUE(metadataList.Plugins().empty());
+ EXPECT_TRUE(metadataList.BashTags().empty());
}
TEST_P(MetadataListTest, loadShouldClearExistingDataIfAMissingMetadataFileIsGiven) {
@@ -135,10 +149,12 @@ namespace loot {
ASSERT_NO_THROW(metadataList.Load(metadataPath));
ASSERT_FALSE(metadataList.Messages().empty());
ASSERT_FALSE(metadataList.Plugins().empty());
+ ASSERT_FALSE(metadataList.BashTags().empty());
EXPECT_ANY_THROW(metadataList.Load(missingMetadataPath));
EXPECT_TRUE(metadataList.Messages().empty());
EXPECT_TRUE(metadataList.Plugins().empty());
+ EXPECT_TRUE(metadataList.BashTags().empty());
}
TEST_P(MetadataListTest, saveShouldWriteTheLoadedMetadataToTheGivenFilePath) {
@@ -152,6 +168,11 @@ namespace loot {
// Check the new file contains the same metadata.
EXPECT_NO_THROW(metadataList.Load(savedMetadataPath));
+ EXPECT_EQ(std::set({
+ "C.Climate",
+ "Relev"
+ }), metadataList.BashTags());
+
EXPECT_EQ(std::list({
Message(Message::say, "A global message."),
}), metadataList.Messages());
@@ -173,15 +194,17 @@ namespace loot {
}), names);
}
- TEST_P(MetadataListTest, clearShouldClearLoadedMessagesAndPlugins) {
+ TEST_P(MetadataListTest, clearShouldClearLoadedData) {
MetadataList metadataList;
ASSERT_NO_THROW(metadataList.Load(metadataPath));
ASSERT_FALSE(metadataList.Messages().empty());
ASSERT_FALSE(metadataList.Plugins().empty());
+ ASSERT_FALSE(metadataList.BashTags().empty());
metadataList.clear();
EXPECT_TRUE(metadataList.Messages().empty());
EXPECT_TRUE(metadataList.Plugins().empty());
+ EXPECT_TRUE(metadataList.BashTags().empty());
}
TEST_P(MetadataListTest, findPluginShouldReturnAnEmptyPluginObjectIfTheGivenPluginIsNotInTheMetadataList) {