diff --git a/resources/report/js/script.js b/resources/report/js/script.js index 9e88444b..1147d89b 100644 --- a/resources/report/js/script.js +++ b/resources/report/js/script.js @@ -253,27 +253,23 @@ function updateSelectedGame() { } function changeGame(evt) { /* First store current game info in loot.games object. - This object may not exist, so initialise it using the corresponding - loot.settings.games object. */ + This object may not exist, so initialise it if not. */ var index = undefined; if (loot.games) { for (var i = 0; i < loot.games.length; ++i) { if (loot.games[i].folder == loot.game.folder) { index = i; + break; } } + } else { + loot.games = []; } if (index == undefined) { - for (var i = 0; i < loot.settings.games.length; ++i) { - if (loot.settings.games[i].folder == loot.game.folder) { - index = i; - } - } - - loot.games = []; - loot.games.push(loot.settings.games[index]); - index = 0; - + loot.games.push({ + folder: loot.game.folder + }); + index = loot.games.length - 1; } loot.games[index].globalMessages = loot.game.globalMessages; loot.games[index].masterlist = loot.game.masterlist; @@ -321,8 +317,67 @@ function changeGame(evt) { globalMessages.removeChild(globalMessages.firstElementChild); } + /* Parse the data sent from C++. */ + try { + var gameInfo = JSON.parse(result, jsonToPlugin); + } catch (e) { + console.log(e); + console.log('getGameData response: ' + result); + } + + /* This may not be the first time loading this game this instance of + LOOT. Restore cached data if it exists. */ + index = undefined; + if (loot.games) { + for (var i = 0; i < loot.games.length; ++i) { + if (loot.games[i].folder == gameInfo.folder) { + index = i; + break; + } + } + } + if (index == undefined) { + /* Game data not cached, simply set what was sent. */ + loot.game = gameInfo; + } else { + /* Game data cache exists. */ + loot.game = loot.games[i]; + /* Now overwrite plugin data with the newly sent data. Also update + card and li vars as they were unset when the game was switched + from before. */ + gameInfo.plugins.forEach(function(plugin){ + var foundPlugin = false; + for (var i = 0; i < loot.game.plugins.length; ++i) { + if (loot.game.plugins[i].name == plugin.name) { + + loot.game.plugins[i].isActive = plugin.isActive; + loot.game.plugins[i].isDummy = plugin.isDummy; + loot.game.plugins[i].loadsBSA = plugin.loadsBSA; + loot.game.plugins[i].crc = plugin.crc; + loot.game.plugins[i].version = plugin.version; + + loot.game.plugins[i].modPriority = plugin.modPriority; + loot.game.plugins[i].isGlobalPriority = plugin.isGlobalPriority; + loot.game.plugins[i].messages = plugin.messages; + loot.game.plugins[i].tags = plugin.tags; + loot.game.plugins[i].isDirty = plugin.isDirty; + + loot.game.plugins[i].card = plugin.card; + loot.game.plugins[i].li = plugin.li; + + foundPlugin = true; + break; + } + } + if (!foundPlugin) { + /* A new plugin. */ + loot.game.plugins.push(plugin); + } + }); + } + /* Now update interface for new data. */ - updateInterfaceWithGameInfo(result); + updateInterfaceWithGameInfo(); /* Now update game menu to highlight the newly selected game. */ updateSelectedGame(); @@ -987,7 +1042,13 @@ function initVars() { } if (results.length > 3) { - updateInterfaceWithGameInfo(results[3]); + try { + loot.game = JSON.parse(results[3], jsonToPlugin); + } catch (e) { + console.log(e); + console.log('getGameData response: ' + results[3]); + } + updateInterfaceWithGameInfo(); } try { @@ -1020,14 +1081,7 @@ function masterlistObserver(changes) { } }); } -function updateInterfaceWithGameInfo(response) { - - try { - loot.game = JSON.parse(response, jsonToPlugin); - } catch (e) { - console.log(e); - console.log('getGameData response: ' + response); - } +function updateInterfaceWithGameInfo() { var totalMessageNo = 0; var warnMessageNo = 0; diff --git a/src/backend/game.cpp b/src/backend/game.cpp index 82424e45..78fef913 100644 --- a/src/backend/game.cpp +++ b/src/backend/game.cpp @@ -722,6 +722,17 @@ namespace loot { group.join_all(); } + bool Game::HasBeenLoaded() { + // Easy way to check is by checking the game's master file, + // which definitely shouldn't be empty. + auto pairIt = plugins.find(_masterFile); + + if (pairIt != plugins.end()) + return pairIt->second.FormIDs().size() > 0; + + return false; + } + void Game::CreateLOOTGameFolder() { //Make sure that the LOOT game path exists. try { diff --git a/src/backend/game.h b/src/backend/game.h index 0ba14b27..69b2cfc7 100644 --- a/src/backend/game.h +++ b/src/backend/game.h @@ -123,6 +123,7 @@ namespace loot { void RefreshActivePluginsList(); void RedatePlugins(); //Change timestamps to match load order (Skyrim only). void LoadPlugins(bool headersOnly); //Loads all installed plugins. + bool HasBeenLoaded(); // Checks if the game's plugins have already been loaded. std::list Sort(const unsigned int language, std::function progressCallback); diff --git a/src/gui/handler.cpp b/src/gui/handler.cpp index 75192a62..35a0eb76 100644 --- a/src/gui/handler.cpp +++ b/src/gui/handler.cpp @@ -356,8 +356,8 @@ namespace loot { auto pluginIt = g_app_state.CurrentGame().plugins.find(boost::locale::to_lower(pluginName)); // Checking for FormID overlap will only work if the plugins have been loaded, so check if - // the first plugin has any FormIDs in memory, and if not load all plugins. - if (g_app_state.CurrentGame().plugins.begin()->second.FormIDs().size() == 0) + // the plugins have been fully loaded, and if not load all plugins. + if (!g_app_state.CurrentGame().HasBeenLoaded()) g_app_state.CurrentGame().LoadPlugins(false); YAML::Node node; @@ -569,9 +569,16 @@ namespace loot { } std::string Handler::GetGameData() { + /* GetGameData() can be called for initialising the UI for a game for the first time + in a session, or it can be called when changing to a game that has previously been + active. In the first case, all data should be loaded, but in the second, only load + order and plugin header info should be re-loaded. + Determine which case it is by checking to see if the game's plugins object is empty. + */ BOOST_LOG_TRIVIAL(info) << "Getting data specific to LOOT's active game."; // Get masterlist revision info and parse if it exists. Also get plugin headers info and parse userlist if it exists. + bool isFirstLoad = g_app_state.CurrentGame().plugins.empty(); g_app_state.CurrentGame().LoadPlugins(true); //Sort plugins into their load order. @@ -585,48 +592,44 @@ namespace loot { installed.push_back(pos->second); } - //Parse masterlist, don't update it. - if (fs::exists(g_app_state.CurrentGame().MasterlistPath())) { - BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; - try { - g_app_state.CurrentGame().masterlist.MetadataList::Load(g_app_state.CurrentGame().MasterlistPath()); + if (isFirstLoad) { + //Parse masterlist, don't update it. + if (fs::exists(g_app_state.CurrentGame().MasterlistPath())) { + BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; + try { + g_app_state.CurrentGame().masterlist.MetadataList::Load(g_app_state.CurrentGame().MasterlistPath()); + } + catch (exception &e) { + g_app_state.CurrentGame().masterlist.messages.push_back(Message(Message::error, string("An error occurred while parsing the masterlist: ") + e.what())); + } } - catch (exception &e) { - g_app_state.CurrentGame().masterlist.messages.push_back(Message(Message::error, string("An error occurred while parsing the masterlist: ") + e.what())); - } - } - //Parse userlist. - if (fs::exists(g_app_state.CurrentGame().UserlistPath())) { - BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; - try { - g_app_state.CurrentGame().userlist.Load(g_app_state.CurrentGame().UserlistPath()); - } - catch (exception &e) { - g_app_state.CurrentGame().userlist.messages.push_back(Message(Message::error, string("An error occurred while parsing the userlist: ") + e.what())); + //Parse userlist. + if (fs::exists(g_app_state.CurrentGame().UserlistPath())) { + BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; + try { + g_app_state.CurrentGame().userlist.Load(g_app_state.CurrentGame().UserlistPath()); + } + catch (exception &e) { + g_app_state.CurrentGame().userlist.messages.push_back(Message(Message::error, string("An error occurred while parsing the userlist: ") + e.what())); + } } } // Now convert to a single object that can be turned into a JSON string //--------------------------------------------------------------------- - //Set language. - unsigned int language; - if (g_app_state.GetSettings()["language"]) - language = Language(g_app_state.GetSettings()["language"].as()).Code(); - else - language = Language::any; - BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name(); - // The data structure is to be set as 'loot.game'. YAML::Node gameNode; // ID the game using its folder value. gameNode["folder"] = g_app_state.CurrentGame().FolderName(); - // Store the masterlist revision and date. - gameNode["masterlist"]["revision"] = g_app_state.CurrentGame().masterlist.GetRevision(g_app_state.CurrentGame().MasterlistPath()); - gameNode["masterlist"]["date"] = g_app_state.CurrentGame().masterlist.GetDate(g_app_state.CurrentGame().MasterlistPath()); + if (isFirstLoad) { + // Store the masterlist revision and date. + gameNode["masterlist"]["revision"] = g_app_state.CurrentGame().masterlist.GetRevision(g_app_state.CurrentGame().MasterlistPath()); + gameNode["masterlist"]["date"] = g_app_state.CurrentGame().masterlist.GetDate(g_app_state.CurrentGame().MasterlistPath()); + } // Now store plugin data. for (const auto& plugin : installed) { @@ -634,6 +637,16 @@ namespace loot { the editor, and also processed data for the main display. */ YAML::Node pluginNode; + // Find the masterlist metadata for this plugin. Treat Bash Tags from the plugin + // description as part of it. + BOOST_LOG_TRIVIAL(trace) << "Getting masterlist metadata for: " << plugin.Name(); + Plugin mlistPlugin(plugin); + mlistPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(plugin.Name())); + + // Now do the same again for any userlist data. + BOOST_LOG_TRIVIAL(trace) << "Getting userlist metadata for: " << plugin.Name(); + Plugin ulistPlugin(g_app_state.CurrentGame().userlist.FindPlugin(plugin.Name())); + pluginNode["__type"] = "Plugin"; // For conversion back into a JS typed object. pluginNode["name"] = plugin.Name(); pluginNode["isActive"] = g_app_state.CurrentGame().IsActive(plugin.Name()); @@ -642,37 +655,27 @@ namespace loot { pluginNode["crc"] = IntToHexString(plugin.Crc()); pluginNode["version"] = plugin.Version(); - // Find the masterlist metadata for this plugin. - BOOST_LOG_TRIVIAL(trace) << "Getting masterlist metadata for: " << plugin.Name(); - Plugin mlistPlugin(plugin); - mlistPlugin.MergeMetadata(g_app_state.CurrentGame().masterlist.FindPlugin(plugin.Name())); + if (isFirstLoad) { + if (!mlistPlugin.HasNameOnly()) { + // Now add the masterlist metadata to the pluginNode. + pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); + pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); + pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); + pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); + pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); + pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); + } - if (!mlistPlugin.HasNameOnly()) { - // Now add the masterlist metadata to the pluginNode. - pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); - pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); - pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); - pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); - pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); - pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); - } - - // Now do the same again for any userlist data. - BOOST_LOG_TRIVIAL(trace) << "Getting userlist metadata for: " << plugin.Name(); - Plugin ulistPlugin(plugin); - // Clear Bash Tags to prevent false positives. - ulistPlugin.Tags(set()); - ulistPlugin.MergeMetadata(g_app_state.CurrentGame().userlist.FindPlugin(plugin.Name())); - - if (!ulistPlugin.HasNameOnly()) { - // Now add the userlist metadata to the pluginNode. - pluginNode["userlist"]["enabled"] = ulistPlugin.Enabled(); - pluginNode["userlist"]["after"] = ulistPlugin.LoadAfter(); - pluginNode["userlist"]["req"] = ulistPlugin.Reqs(); - pluginNode["userlist"]["inc"] = ulistPlugin.Incs(); - pluginNode["userlist"]["msg"] = ulistPlugin.Messages(); - pluginNode["userlist"]["tag"] = ulistPlugin.Tags(); - pluginNode["userlist"]["dirty"] = ulistPlugin.DirtyInfo(); + if (!ulistPlugin.HasNameOnly()) { + // Now add the userlist metadata to the pluginNode. + pluginNode["userlist"]["enabled"] = ulistPlugin.Enabled(); + pluginNode["userlist"]["after"] = ulistPlugin.LoadAfter(); + pluginNode["userlist"]["req"] = ulistPlugin.Reqs(); + pluginNode["userlist"]["inc"] = ulistPlugin.Incs(); + pluginNode["userlist"]["msg"] = ulistPlugin.Messages(); + pluginNode["userlist"]["tag"] = ulistPlugin.Tags(); + pluginNode["userlist"]["dirty"] = ulistPlugin.DirtyInfo(); + } } // Now merge masterlist and userlist metadata and evaluate, @@ -687,26 +690,36 @@ namespace loot { gameNode["plugins"].push_back(pluginNode); } - //Evaluate any conditions in the global messages. - BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; - list messages = g_app_state.CurrentGame().masterlist.messages; - messages.insert(messages.end(), g_app_state.CurrentGame().userlist.messages.begin(), g_app_state.CurrentGame().userlist.messages.end()); - try { - list::iterator it = messages.begin(); - while (it != messages.end()) { - if (!it->EvalCondition(g_app_state.CurrentGame(), language)) - it = messages.erase(it); - else - ++it; - } - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "A global message contains a condition that could not be evaluated. Details: " << e.what(); - messages.push_back(Message(Message::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str())); - } + if (isFirstLoad) { + //Set language. + unsigned int language; + if (g_app_state.GetSettings()["language"]) + language = Language(g_app_state.GetSettings()["language"].as()).Code(); + else + language = Language::any; + BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name(); - // Now store global messages from masterlist. - gameNode["globalMessages"] = messages; + //Evaluate any conditions in the global messages. + BOOST_LOG_TRIVIAL(debug) << "Evaluating global message conditions."; + list messages = g_app_state.CurrentGame().masterlist.messages; + messages.insert(messages.end(), g_app_state.CurrentGame().userlist.messages.begin(), g_app_state.CurrentGame().userlist.messages.end()); + try { + list::iterator it = messages.begin(); + while (it != messages.end()) { + if (!it->EvalCondition(g_app_state.CurrentGame(), language)) + it = messages.erase(it); + else + ++it; + } + } + catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "A global message contains a condition that could not be evaluated. Details: " << e.what(); + messages.push_back(Message(Message::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str())); + } + + // Now store global messages from masterlist. + gameNode["globalMessages"] = messages; + } return JSON::stringify(gameNode); } @@ -835,8 +848,8 @@ namespace loot { language = Language::any; BOOST_LOG_TRIVIAL(info) << "Using message language: " << Language(language).Name(); - // Check if the first plugin has any FormIDs in memory, and if not load all plugins. - if (g_app_state.CurrentGame().plugins.begin()->second.FormIDs().size() == 0) + // Check if the plugins have been fully loaded, and if not load all plugins. + if (!g_app_state.CurrentGame().HasBeenLoaded()) g_app_state.CurrentGame().LoadPlugins(false); //Sort plugins into their load order.