Skip to content

Commit

Permalink
Merge branch 'misisng_plugins' into 'master'
Browse files Browse the repository at this point in the history
Display missing plugins upon savegame loading

Closes #7608

See merge request OpenMW/openmw!3594
  • Loading branch information
Zackhasacat committed Nov 20, 2023
2 parents 5a1a54b + 9bbb89e commit 6fb6c7a
Show file tree
Hide file tree
Showing 20 changed files with 193 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions apps/openmw/mwbase/windowmanager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ namespace MWBase
= 0;
virtual void staticMessageBox(std::string_view message) = 0;
virtual void removeStaticMessageBox() = 0;
virtual void interactiveMessageBox(
std::string_view message, const std::vector<std::string>& buttons = {}, bool block = false)
virtual void interactiveMessageBox(std::string_view message, const std::vector<std::string>& buttons = {},
bool block = false, int defaultFocus = -1)
= 0;

/// returns the index of the pressed button or -1 if no button was pressed
Expand Down
34 changes: 30 additions & 4 deletions apps/openmw/mwgui/messagebox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ namespace MWGui
mLastButtonPressed = -1;
}

void MessageBoxManager::resetInteractiveMessageBox()
{
if (mInterMessageBoxe)
{
mInterMessageBoxe->setVisible(false);
mInterMessageBoxe.reset();
}
}

void MessageBoxManager::setLastButtonPressed(int index)
{
mLastButtonPressed = index;
}

void MessageBoxManager::onFrame(float frameDuration)
{
for (auto it = mMessageBoxes.begin(); it != mMessageBoxes.end();)
Expand Down Expand Up @@ -112,15 +126,16 @@ namespace MWGui
}

bool MessageBoxManager::createInteractiveMessageBox(
std::string_view message, const std::vector<std::string>& buttons)
std::string_view message, const std::vector<std::string>& buttons, bool immediate, int defaultFocus)
{
if (mInterMessageBoxe != nullptr)
{
Log(Debug::Warning) << "Warning: replacing an interactive message box that was not answered yet";
mInterMessageBoxe->setVisible(false);
}

mInterMessageBoxe = std::make_unique<InteractiveMessageBox>(*this, std::string{ message }, buttons);
mInterMessageBoxe
= std::make_unique<InteractiveMessageBox>(*this, std::string{ message }, buttons, immediate, defaultFocus);
mLastButtonPressed = -1;

return true;
Expand Down Expand Up @@ -200,13 +215,15 @@ namespace MWGui
mMainWidget->setVisible(value);
}

InteractiveMessageBox::InteractiveMessageBox(
MessageBoxManager& parMessageBoxManager, const std::string& message, const std::vector<std::string>& buttons)
InteractiveMessageBox::InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message,
const std::vector<std::string>& buttons, bool immediate, int defaultFocus)
: WindowModal(MWBase::Environment::get().getWindowManager()->isGuiMode()
? "openmw_interactive_messagebox_notransp.layout"
: "openmw_interactive_messagebox.layout")
, mMessageBoxManager(parMessageBoxManager)
, mButtonPressed(-1)
, mDefaultFocus(defaultFocus)
, mImmediate(immediate)
{
int textPadding = 10; // padding between text-widget and main-widget
int textButtonPadding = 10; // padding between the text-widget und the button-widget
Expand Down Expand Up @@ -363,6 +380,9 @@ namespace MWGui
MyGUI::Widget* InteractiveMessageBox::getDefaultKeyFocus()
{
std::vector<std::string> keywords{ "sOk", "sYes" };
if (mDefaultFocus >= 0 && mDefaultFocus < static_cast<int>(mButtons.size()))
return mButtons[mDefaultFocus];

for (MyGUI::Button* button : mButtons)
{
for (const std::string& keyword : keywords)
Expand Down Expand Up @@ -393,6 +413,12 @@ namespace MWGui
{
mButtonPressed = index;
mMessageBoxManager.onButtonPressed(mButtonPressed);
if (!mImmediate)
return;

mMessageBoxManager.setLastButtonPressed(mButtonPressed);
MWBase::Environment::get().getInputManager()->changeInputMode(
MWBase::Environment::get().getWindowManager()->isGuiMode());
return;
}
index++;
Expand Down
11 changes: 9 additions & 2 deletions apps/openmw/mwgui/messagebox.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ namespace MWGui
void onFrame(float frameDuration);
void createMessageBox(std::string_view message, bool stat = false);
void removeStaticMessageBox();
bool createInteractiveMessageBox(std::string_view message, const std::vector<std::string>& buttons);
bool createInteractiveMessageBox(std::string_view message, const std::vector<std::string>& buttons,
bool immediate = false, int defaultFocus = -1);
bool isInteractiveMessageBox();

int getMessagesCount();
Expand All @@ -40,6 +41,10 @@ namespace MWGui
/// @param reset Reset the pressed button to -1 after reading it.
int readPressedButton(bool reset = true);

void resetInteractiveMessageBox();

void setLastButtonPressed(int index);

typedef MyGUI::delegates::MultiDelegate<int> EventHandle_Int;

// Note: this delegate unassigns itself after it was fired, i.e. works once.
Expand Down Expand Up @@ -88,7 +93,7 @@ namespace MWGui
{
public:
InteractiveMessageBox(MessageBoxManager& parMessageBoxManager, const std::string& message,
const std::vector<std::string>& buttons);
const std::vector<std::string>& buttons, bool immediate, int defaultFocus);
void mousePressed(MyGUI::Widget* _widget);
int readPressedButton();

Expand All @@ -107,6 +112,8 @@ namespace MWGui
std::vector<MyGUI::Button*> mButtons;

int mButtonPressed;
int mDefaultFocus;
bool mImmediate;
};

}
Expand Down
6 changes: 4 additions & 2 deletions apps/openmw/mwgui/windowmanagerimp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -744,9 +744,9 @@ namespace MWGui
}

void WindowManager::interactiveMessageBox(
std::string_view message, const std::vector<std::string>& buttons, bool block)
std::string_view message, const std::vector<std::string>& buttons, bool block, int defaultFocus)
{
mMessageBoxManager->createInteractiveMessageBox(message, buttons);
mMessageBoxManager->createInteractiveMessageBox(message, buttons, block, defaultFocus);
updateVisible();

if (block)
Expand Down Expand Up @@ -779,6 +779,8 @@ namespace MWGui

frameRateLimiter.limit();
}

mMessageBoxManager->resetInteractiveMessageBox();
}
}

Expand Down
4 changes: 2 additions & 2 deletions apps/openmw/mwgui/windowmanagerimp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ namespace MWGui
enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) override;
void staticMessageBox(std::string_view message) override;
void removeStaticMessageBox() override;
void interactiveMessageBox(
std::string_view message, const std::vector<std::string>& buttons = {}, bool block = false) override;
void interactiveMessageBox(std::string_view message, const std::vector<std::string>& buttons = {},
bool block = false, int defaultFocus = -1) override;

int readPressedButton() override; ///< returns the index of the pressed button or -1 if no button was pressed
///< (->MessageBoxmanager->InteractiveMessageBox)
Expand Down
76 changes: 58 additions & 18 deletions apps/openmw/mwstate/statemanagerimp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <filesystem>

#include <SDL_clipboard.h>

#include <components/debug/debuglog.hpp>

#include <components/esm3/esmreader.hpp>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -668,30 +672,66 @@ 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()) });

int selectedButton = -1;
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);
int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton();
if (selectedButton == 1 || selectedButton == -1)
return false;
auto windowManager = MWBase::Environment::get().getWindowManager();
windowManager->interactiveMessageBox(message, buttons, true, selectedButton);
selectedButton = windowManager->readPressedButton();
if (selectedButton == 0)
break;

if (selectedButton == 1)
{
SDL_SetClipboardText(fullList.c_str());
continue;
}

return false;
}

return true;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/openmw/mwstate/statemanagerimp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
14 changes: 14 additions & 0 deletions components/esm3/savedgame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
2 changes: 2 additions & 0 deletions components/esm3/savedgame.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}

Expand Down
1 change: 1 addition & 0 deletions files/data/l10n/Interface/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Yes: "Ja"
#OK: "OK"
#Off: "Off"
#On: "On"
#Copy: "Copy"
1 change: 1 addition & 0 deletions files/data/l10n/Interface/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ None: "None"
OK: "OK"
Cancel: "Cancel"
Close: "Close"
Copy: "Copy"
1 change: 1 addition & 0 deletions files/data/l10n/Interface/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ None: "Aucun"
OK: "Valider"
Cancel: "Annuler"
Close: "Fermer"
#Copy: "Copy"
1 change: 1 addition & 0 deletions files/data/l10n/Interface/ru.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Cancel: "Отмена"
Close: "Закрыть"
Copy: "Скопировать"
DurationDay: "{days} д "
DurationHour: "{hours} ч "
DurationMinute: "{minutes} мин "
Expand Down
1 change: 1 addition & 0 deletions files/data/l10n/Interface/sv.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ Off: "Av"
On: ""
Reset: "Återställ"
Yes: "Ja"
#Copy: "Copy"
14 changes: 13 additions & 1 deletion files/data/l10n/OMWEngine/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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?"


Expand Down
14 changes: 13 additions & 1 deletion files/data/l10n/OMWEngine/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading

0 comments on commit 6fb6c7a

Please sign in to comment.