1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-12 09:39:58 +00:00

Display missing plugins upon savegame loading (feature 7608)

This commit is contained in:
Andrei Kortunov 2023-11-16 11:41:19 +04:00
parent edd69885ce
commit f88b99201a
15 changed files with 143 additions and 23 deletions

View File

@ -127,6 +127,7 @@
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
Feature #7546: Start the game on Fredas Feature #7546: Start the game on Fredas
Feature #7568: Uninterruptable scripted music 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 #7618: Show the player character's health in the save details
Feature #7625: Add some missing console error outputs Feature #7625: Add some missing console error outputs
Feature #7634: Support NiParticleBomb Feature #7634: Support NiParticleBomb

View File

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

View File

@ -21,7 +21,7 @@ namespace MWState
private: private:
void cleanup(bool force = false); 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; void writeScreenshot(std::vector<char>& imageData) const;

View File

@ -61,4 +61,18 @@ namespace ESM
esm.writeHNT("MHLT", mMaximumHealth); 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;
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -41,10 +41,22 @@ TimePlayed: "Spielzeit"
#DeleteGameConfirmation: "Are you sure you want to delete this saved game?" #DeleteGameConfirmation: "Are you sure you want to delete this saved game?"
#EmptySaveNameError: "Game can not be saved without a name!" #EmptySaveNameError: "Game can not be saved without a name!"
#LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" #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. # The currently selected content files do not match the ones used by this save game.
# Errors may occur during load or game play. # Errors may occur during load or game play.
# Do you wish to continue? # 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?" #OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?"

View File

@ -34,10 +34,22 @@ DeleteGame: "Delete Game"
DeleteGameConfirmation: "Are you sure you want to delete this saved game?" DeleteGameConfirmation: "Are you sure you want to delete this saved game?"
EmptySaveNameError: "Game can not be saved without a name!" EmptySaveNameError: "Game can not be saved without a name!"
LoadGameConfirmation: "Do you want to load a saved game and lose the current one?" 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. The currently selected content files do not match the ones used by this save game.
Errors may occur during load or game play. Errors may occur during load or game play.
Do you wish to continue? 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?" OverwriteGameConfirmation: "Are you sure you want to overwrite this saved game?"
SelectCharacter: "Select Character..." SelectCharacter: "Select Character..."
TimePlayed: "Time played" TimePlayed: "Time played"

View File

@ -37,10 +37,22 @@ DeleteGame: "Supprimer la partie"
DeleteGameConfirmation: "Voulez-vous réellement supprimer cette partie sauvegardée ?" DeleteGameConfirmation: "Voulez-vous réellement supprimer cette partie sauvegardée ?"
EmptySaveNameError: "Impossible de sauvegarder une partie lui donner un nom !" EmptySaveNameError: "Impossible de sauvegarder une partie lui donner un nom !"
LoadGameConfirmation: "Voulez-vous charger cette autre partie ? Toute progression non sauvegardée sera perdue." 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. 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. Cela peut entraîner des erreurs lors du chargement, mais aussi lors de votre partie.
Voulez-vous continuer ? 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 ?" OverwriteGameConfirmation: "Écraser la sauvegarde précédente ?"

View File

@ -34,10 +34,22 @@ DeleteGame: "Удалить игру"
DeleteGameConfirmation: "Вы уверены, что хотите удалить это сохранение?" DeleteGameConfirmation: "Вы уверены, что хотите удалить это сохранение?"
EmptySaveNameError: "Имя сохранения не может быть пустым!" EmptySaveNameError: "Имя сохранения не может быть пустым!"
LoadGameConfirmation: "Вы хотите загрузить сохранение? Текущая игра будет потеряна." LoadGameConfirmation: "Вы хотите загрузить сохранение? Текущая игра будет потеряна."
MissingContentFilesConfirmation: | MissingContentFilesConfirmation: |-
Выбранные ESM/ESP файлы не соответствуют тем, которые использовались для этого сохранения. Выбранные 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: "Вы уверены, что хотите перезаписать это сохранение?" OverwriteGameConfirmation: "Вы уверены, что хотите перезаписать это сохранение?"
SelectCharacter: "Выберите персонажа..." SelectCharacter: "Выберите персонажа..."
TimePlayed: "Время в игре" TimePlayed: "Время в игре"

View File

@ -35,10 +35,22 @@ DeleteGame: "Radera spel"
DeleteGameConfirmation: "Är du säker på att du vill radera sparfilen?" DeleteGameConfirmation: "Är du säker på att du vill radera sparfilen?"
EmptySaveNameError: "Spelet kan inte sparas utan ett namn!" EmptySaveNameError: "Spelet kan inte sparas utan ett namn!"
LoadGameConfirmation: "Vill du ladda ett sparat spel och förlora det pågående spelet?" 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. De valda innehållsfilerna matchar inte filerna som används av denna sparfil.
Fel kan uppstå vid laddning eller under spel. Fel kan uppstå vid laddning eller under spel.
Vill du fortsätta? 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?" OverwriteGameConfirmation: "Är du säker på att du vill skriva över det här sparade spelet?"
SelectCharacter: "Välj spelfigur..." SelectCharacter: "Välj spelfigur..."