Display missing plugins upon savegame loading (feature 7608)

macos_ci_fix
Andrei Kortunov 6 months ago
parent edd69885ce
commit f88b99201a

@ -127,6 +127,7 @@
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
Feature #7546: Start the game on Fredas
Feature #7568: Uninterruptable scripted music
Feature #7608: Make the missing dependencies warning when loading a savegame more helpful
Feature #7618: Show the player character's health in the save details
Feature #7625: Add some missing console error outputs
Feature #7634: Support NiParticleBomb

@ -2,6 +2,8 @@
#include <filesystem>
#include <SDL_clipboard.h>
#include <components/debug/debuglog.hpp>
#include <components/esm3/esmreader.hpp>
@ -440,7 +442,9 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
{
ESM::SavedGame profile;
profile.load(reader);
if (!verifyProfile(profile))
const auto& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles();
auto missingFiles = profile.getMissingContentFiles(selectedContentFiles);
if (!missingFiles.empty() && !confirmLoading(missingFiles))
{
cleanup(true);
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
@ -668,30 +672,64 @@ void MWState::StateManager::update(float duration)
}
}
bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const
bool MWState::StateManager::confirmLoading(const std::vector<std::string_view>& missingFiles) const
{
const std::vector<std::string>& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles();
bool notFound = false;
for (const std::string& contentFile : profile.mContentFiles)
std::ostringstream stream;
for (auto& contentFile : missingFiles)
{
Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing.";
stream << contentFile << "\n";
}
auto fullList = stream.str();
if (!fullList.empty())
fullList.pop_back();
constexpr size_t missingPluginsDisplayLimit = 12;
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:Yes}");
buttons.emplace_back("#{Interface:Copy}");
buttons.emplace_back("#{Interface:No}");
std::string message = "#{OMWEngine:MissingContentFilesConfirmation}";
auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine");
message += l10n->formatMessage("MissingContentFilesList", { "files" }, { static_cast<int>(missingFiles.size()) });
auto cappedSize = std::min(missingFiles.size(), missingPluginsDisplayLimit);
if (cappedSize == missingFiles.size())
{
message += fullList;
}
else
{
if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), contentFile)
== selectedContentFiles.end())
for (size_t i = 0; i < cappedSize - 1; ++i)
{
Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing.";
notFound = true;
message += missingFiles[i];
message += "\n";
}
message += "...";
}
if (notFound)
message
+= l10n->formatMessage("MissingContentFilesListCopy", { "files" }, { static_cast<int>(missingFiles.size()) });
while (true)
{
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:Yes}");
buttons.emplace_back("#{Interface:No}");
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(
"#{OMWEngine:MissingContentFilesConfirmation}", buttons, true);
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons, true);
int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton();
if (selectedButton == 1 || selectedButton == -1)
return false;
if (selectedButton == 0)
break;
if (selectedButton == 1)
{
SDL_SetClipboardText(fullList.c_str());
continue;
}
return false;
}
return true;
}

@ -21,7 +21,7 @@ namespace MWState
private:
void cleanup(bool force = false);
bool verifyProfile(const ESM::SavedGame& profile) const;
bool confirmLoading(const std::vector<std::string_view>& missingFiles) const;
void writeScreenshot(std::vector<char>& imageData) const;

@ -61,4 +61,18 @@ namespace ESM
esm.writeHNT("MHLT", mMaximumHealth);
}
std::vector<std::string_view> SavedGame::getMissingContentFiles(
const std::vector<std::string>& allContentFiles) const
{
std::vector<std::string_view> missingFiles;
for (const std::string& contentFile : mContentFiles)
{
if (std::find(allContentFiles.begin(), allContentFiles.end(), contentFile) == allContentFiles.end())
{
missingFiles.emplace_back(contentFile);
}
}
return missingFiles;
}
}

@ -40,6 +40,8 @@ namespace ESM
void load(ESMReader& esm);
void save(ESMWriter& esm) const;
std::vector<std::string_view> getMissingContentFiles(const std::vector<std::string>& allContentFiles) const;
};
}

@ -25,3 +25,4 @@ Yes: "Ja"
#OK: "OK"
#Off: "Off"
#On: "On"
#Copy: "Copy"

@ -22,3 +22,4 @@ None: "None"
OK: "OK"
Cancel: "Cancel"
Close: "Close"
Copy: "Copy"

@ -22,3 +22,4 @@ None: "Aucun"
OK: "Valider"
Cancel: "Annuler"
Close: "Fermer"
#Copy: "Copy"

@ -1,5 +1,6 @@
Cancel: "Отмена"
Close: "Закрыть"
Copy: "Скопировать"
DurationDay: "{days} д "
DurationHour: "{hours} ч "
DurationMinute: "{minutes} мин "

@ -14,3 +14,4 @@ Off: "Av"
On: "På"
Reset: "Återställ"
Yes: "Ja"
#Copy: "Copy"

@ -41,10 +41,22 @@ TimePlayed: "Spielzeit"
#DeleteGameConfirmation: "Are you sure you want to delete this saved game?"
#EmptySaveNameError: "Game can not be saved without a name!"
#LoadGameConfirmation: "Do you want to load a saved game and lose the current one?"
#MissingContentFilesConfirmation: |
#MissingContentFilesConfirmation: |-
# The currently selected content files do not match the ones used by this save game.
# Errors may occur during load or game play.
# Do you wish to continue?
#MissingContentFilesList: |-
# {files, plural,
# one{\n\nFound missing file: }
# few{\n\nFound {files} missing files:\n}
# other{\n\nFound {files} missing files:\n}
# }
#MissingContentFilesListCopy: |-
# {files, plural,
# one{\n\nPress Copy to place its name to the clipboard.}
# few{\n\nPress Copy to place their names to the clipboard.}
# other{\n\nPress Copy to place their names to the clipboard.}
# }
#OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?"

@ -34,10 +34,22 @@ DeleteGame: "Delete Game"
DeleteGameConfirmation: "Are you sure you want to delete this saved game?"
EmptySaveNameError: "Game can not be saved without a name!"
LoadGameConfirmation: "Do you want to load a saved game and lose the current one?"
MissingContentFilesConfirmation: |
MissingContentFilesConfirmation: |-
The currently selected content files do not match the ones used by this save game.
Errors may occur during load or game play.
Do you wish to continue?
MissingContentFilesList: |-
{files, plural,
one{\n\nFound missing file: }
few{\n\nFound {files} missing files:\n}
other{\n\nFound {files} missing files:\n}
}
MissingContentFilesListCopy: |-
{files, plural,
one{\n\nPress Copy to place its name to the clipboard.}
few{\n\nPress Copy to place their names to the clipboard.}
other{\n\nPress Copy to place their names to the clipboard.}
}
OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?"
SelectCharacter: "Select Character..."
TimePlayed: "Time played"

@ -37,10 +37,22 @@ DeleteGame: "Supprimer la partie"
DeleteGameConfirmation: "Voulez-vous réellement supprimer cette partie sauvegardée ?"
EmptySaveNameError: "Impossible de sauvegarder une partie lui donner un nom !"
LoadGameConfirmation: "Voulez-vous charger cette autre partie ? Toute progression non sauvegardée sera perdue."
MissingContentFilesConfirmation: |
MissingContentFilesConfirmation: |-
Les données de jeu actuellement sélectionnées ne correspondent pas à celle indiquée dans cette sauvegarde.
Cela peut entraîner des erreurs lors du chargement, mais aussi lors de votre partie.
Voulez-vous continuer ?
#MissingContentFilesList: |-
# {files, plural,
# one{\n\nFound missing file: }
# few{\n\nFound {files} missing files:\n}
# other{\n\nFound {files} missing files:\n}
# }
#MissingContentFilesListCopy: |-
# {files, plural,
# one{\n\nPress Copy to place its name to the clipboard.}
# few{\n\nPress Copy to place their names to the clipboard.}
# other{\n\nPress Copy to place their names to the clipboard.}
# }
OverwriteGameConfirmation: "Écraser la sauvegarde précédente ?"

@ -34,10 +34,22 @@ DeleteGame: "Удалить игру"
DeleteGameConfirmation: "Вы уверены, что хотите удалить это сохранение?"
EmptySaveNameError: "Имя сохранения не может быть пустым!"
LoadGameConfirmation: "Вы хотите загрузить сохранение? Текущая игра будет потеряна."
MissingContentFilesConfirmation: |
MissingContentFilesConfirmation: |-
Выбранные ESM/ESP файлы не соответствуют тем, которые использовались для этого сохранения.
Во время загрузки или в процессе игры могут возникнуть ошибки.
Вы хотите продолжить?
MissingContentFilesList: |-
{files, plural,
one{\n\nОтсутствует файл }
few{\n\nОтсутствуют {files} файла:\n}
other{\n\nОтсутствуют {files} файлов:\n}
}
MissingContentFilesListCopy: |-
{files, plural,
one{\n\nНажмите Скопировать, чтобы поместить его название в буфер обмена.}
few{\n\nНажмите Скопировать, чтобы поместить их названия в буфер обмена.}
other{\n\nНажмите Скопировать, чтобы поместить их названия в буфер обмена.}
}
OverwriteGameConfirmation: "Вы уверены, что хотите перезаписать это сохранение?"
SelectCharacter: "Выберите персонажа..."
TimePlayed: "Время в игре"

@ -35,10 +35,22 @@ DeleteGame: "Radera spel"
DeleteGameConfirmation: "Är du säker på att du vill radera sparfilen?"
EmptySaveNameError: "Spelet kan inte sparas utan ett namn!"
LoadGameConfirmation: "Vill du ladda ett sparat spel och förlora det pågående spelet?"
MissingContentFilesConfirmation: |
MissingContentFilesConfirmation: |-
De valda innehållsfilerna matchar inte filerna som används av denna sparfil.
Fel kan uppstå vid laddning eller under spel.
Vill du fortsätta?
#MissingContentFilesList: |-
# {files, plural,
# one{\n\nFound missing file: }
# few{\n\nFound {files} missing files:\n}
# other{\n\nFound {files} missing files:\n}
# }
#MissingContentFilesListCopy: |-
# {files, plural,
# one{\n\nPress Copy to place its name to the clipboard.}
# few{\n\nPress Copy to place their names to the clipboard.}
# other{\n\nPress Copy to place their names to the clipboard.}
# }
OverwriteGameConfirmation: "Är du säker på att du vill skriva över det här sparade spelet?"
SelectCharacter: "Välj spelfigur..."

Loading…
Cancel
Save