2013-11-16 10:31:46 +01:00
|
|
|
#include "statemanagerimp.hpp"
|
|
|
|
|
2022-05-25 21:16:26 +02:00
|
|
|
#include <filesystem>
|
|
|
|
|
2018-08-14 23:05:43 +04:00
|
|
|
#include <components/debug/debuglog.hpp>
|
|
|
|
|
2022-01-22 15:58:41 +01:00
|
|
|
#include <components/esm3/cellid.hpp>
|
|
|
|
#include <components/esm3/esmreader.hpp>
|
|
|
|
#include <components/esm3/esmwriter.hpp>
|
|
|
|
#include <components/esm3/loadcell.hpp>
|
2022-09-05 19:35:15 +02:00
|
|
|
#include <components/esm3/loadclas.hpp>
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2015-07-09 19:22:04 +02:00
|
|
|
#include <components/loadinglistener/loadinglistener.hpp>
|
|
|
|
|
2022-07-03 00:02:29 +02:00
|
|
|
#include <components/files/conversion.hpp>
|
2013-11-26 10:37:58 +01:00
|
|
|
#include <components/settings/settings.hpp>
|
|
|
|
|
2015-06-03 16:40:16 +02:00
|
|
|
#include <osg/Image>
|
|
|
|
|
|
|
|
#include <osgDB/Registry>
|
2014-01-24 17:49:16 +01:00
|
|
|
|
2013-11-16 12:22:28 +01:00
|
|
|
#include "../mwbase/dialoguemanager.hpp"
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
#include "../mwbase/inputmanager.hpp"
|
|
|
|
#include "../mwbase/journal.hpp"
|
|
|
|
#include "../mwbase/luamanager.hpp"
|
2013-12-07 13:17:28 +01:00
|
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
2013-12-12 13:15:38 +01:00
|
|
|
#include "../mwbase/scriptmanager.hpp"
|
2014-01-21 14:13:13 +01:00
|
|
|
#include "../mwbase/soundmanager.hpp"
|
2014-03-09 03:34:49 +01:00
|
|
|
#include "../mwbase/windowmanager.hpp"
|
2020-12-19 00:02:31 +01:00
|
|
|
#include "../mwbase/world.hpp"
|
2013-11-16 12:22:28 +01:00
|
|
|
|
2014-02-23 20:11:05 +01:00
|
|
|
#include "../mwworld/cellstore.hpp"
|
2013-11-21 10:53:42 +01:00
|
|
|
#include "../mwworld/class.hpp"
|
2014-12-19 11:26:54 +01:00
|
|
|
#include "../mwworld/esmstore.hpp"
|
2013-11-21 10:53:42 +01:00
|
|
|
|
2015-08-21 21:12:39 +12:00
|
|
|
#include "../mwmechanics/actorutil.hpp"
|
2013-11-21 10:53:42 +01:00
|
|
|
#include "../mwmechanics/npcstats.hpp"
|
|
|
|
|
2013-12-12 13:15:38 +01:00
|
|
|
#include "../mwscript/globalscripts.hpp"
|
|
|
|
|
2018-01-12 20:02:43 -05:00
|
|
|
#include "quicksavemanager.hpp"
|
|
|
|
|
2014-01-23 11:29:40 +01:00
|
|
|
void MWState::StateManager::cleanup(bool force)
|
2013-11-28 11:22:34 +01:00
|
|
|
{
|
2014-01-23 11:29:40 +01:00
|
|
|
if (mState != State_NoGame || force)
|
2013-11-28 11:22:34 +01:00
|
|
|
{
|
2014-01-21 14:13:13 +01:00
|
|
|
MWBase::Environment::get().getSoundManager()->clear();
|
2013-11-28 11:22:34 +01:00
|
|
|
MWBase::Environment::get().getDialogueManager()->clear();
|
|
|
|
MWBase::Environment::get().getJournal()->clear();
|
2020-07-14 10:53:01 +04:00
|
|
|
MWBase::Environment::get().getScriptManager()->clear();
|
2014-01-25 13:34:56 +01:00
|
|
|
MWBase::Environment::get().getWindowManager()->clear();
|
2020-11-18 17:28:09 +01:00
|
|
|
MWBase::Environment::get().getWorld()->clear();
|
2014-03-09 03:34:49 +01:00
|
|
|
MWBase::Environment::get().getInputManager()->clear();
|
2014-06-13 01:24:58 +02:00
|
|
|
MWBase::Environment::get().getMechanicsManager()->clear();
|
2013-12-12 13:15:38 +01:00
|
|
|
|
2013-11-28 11:22:34 +01:00
|
|
|
mState = State_NoGame;
|
2018-10-09 10:21:12 +04:00
|
|
|
mCharacterManager.setCurrentCharacter(nullptr);
|
2013-11-28 11:22:34 +01:00
|
|
|
mTimePlayed = 0;
|
2014-04-29 19:56:33 +02:00
|
|
|
|
|
|
|
MWMechanics::CreatureStats::cleanup();
|
2013-11-28 11:22:34 +01:00
|
|
|
}
|
2020-12-19 00:02:31 +01:00
|
|
|
MWBase::Environment::get().getLuaManager()->clear();
|
2013-11-28 11:22:34 +01:00
|
|
|
}
|
|
|
|
|
2014-01-27 13:27:42 +01:00
|
|
|
std::map<int, int> MWState::StateManager::buildContentFileIndexMap(const ESM::ESMReader& reader) const
|
|
|
|
{
|
|
|
|
const std::vector<std::string>& current = MWBase::Environment::get().getWorld()->getContentFiles();
|
|
|
|
|
|
|
|
const std::vector<ESM::Header::MasterData>& prev = reader.getGameFiles();
|
|
|
|
|
|
|
|
std::map<int, int> map;
|
|
|
|
|
|
|
|
for (int iPrev = 0; iPrev < static_cast<int>(prev.size()); ++iPrev)
|
|
|
|
{
|
|
|
|
std::string id = Misc::StringUtils::lowerCase(prev[iPrev].name);
|
|
|
|
|
|
|
|
for (int iCurrent = 0; iCurrent < static_cast<int>(current.size()); ++iCurrent)
|
|
|
|
if (id == Misc::StringUtils::lowerCase(current[iCurrent]))
|
|
|
|
{
|
|
|
|
map.insert(std::make_pair(iPrev, iCurrent));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
|
2022-06-08 23:25:50 +02:00
|
|
|
MWState::StateManager::StateManager(const std::filesystem::path& saves, const std::vector<std::string>& contentFiles)
|
2021-10-17 10:29:10 +02:00
|
|
|
: mQuitRequest(false)
|
|
|
|
, mAskLoadRecent(false)
|
|
|
|
, mState(State_NoGame)
|
|
|
|
, mCharacterManager(saves, contentFiles)
|
|
|
|
, mTimePlayed(0)
|
2013-11-16 10:31:46 +01:00
|
|
|
{
|
2013-11-16 11:07:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void MWState::StateManager::requestQuit()
|
|
|
|
{
|
|
|
|
mQuitRequest = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MWState::StateManager::hasQuitRequest() const
|
|
|
|
{
|
|
|
|
return mQuitRequest;
|
2013-11-16 12:22:28 +01:00
|
|
|
}
|
|
|
|
|
2013-12-19 22:08:34 +02:00
|
|
|
void MWState::StateManager::askLoadRecent()
|
|
|
|
{
|
|
|
|
if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!mAskLoadRecent)
|
|
|
|
{
|
2016-03-14 00:00:11 +01:00
|
|
|
const MWState::Character* character = getCurrentCharacter();
|
2016-03-13 23:47:57 +01:00
|
|
|
if (!character || character->begin() == character->end()) // no saves
|
2013-12-19 22:08:34 +02:00
|
|
|
{
|
|
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-03-13 23:47:57 +01:00
|
|
|
MWState::Slot lastSave = *character->begin();
|
2013-12-19 22:08:34 +02:00
|
|
|
std::vector<std::string> buttons;
|
2020-10-17 12:26:35 +04:00
|
|
|
buttons.emplace_back("#{sYes}");
|
|
|
|
buttons.emplace_back("#{sNo}");
|
2022-08-24 22:16:03 +02:00
|
|
|
std::string_view tag = "%s";
|
|
|
|
std::string message{ MWBase::Environment::get().getWindowManager()->getGameSettingString(
|
|
|
|
"sLoadLastSaveMsg", tag) };
|
2013-12-20 14:04:59 +02:00
|
|
|
size_t pos = message.find(tag);
|
|
|
|
message.replace(pos, tag.length(), lastSave.mProfile.mDescription);
|
2015-01-10 23:21:39 +01:00
|
|
|
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons);
|
2013-12-19 22:08:34 +02:00
|
|
|
mAskLoadRecent = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-18 15:15:47 +01:00
|
|
|
MWState::StateManager::State MWState::StateManager::getState() const
|
2013-11-16 12:22:28 +01:00
|
|
|
{
|
2013-11-18 15:15:47 +01:00
|
|
|
return mState;
|
2013-11-16 12:22:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void MWState::StateManager::newGame(bool bypass)
|
|
|
|
{
|
2013-11-28 11:22:34 +01:00
|
|
|
cleanup();
|
2013-11-16 12:22:28 +01:00
|
|
|
|
|
|
|
if (!bypass)
|
|
|
|
MWBase::Environment::get().getWindowManager()->setNewGame(true);
|
2014-07-25 12:17:15 +02:00
|
|
|
|
2015-06-15 16:19:05 +02:00
|
|
|
try
|
|
|
|
{
|
2020-05-09 20:17:49 +03:00
|
|
|
Log(Debug::Info) << "Starting a new game";
|
2015-06-15 16:19:05 +02:00
|
|
|
MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup();
|
2020-12-19 00:02:31 +01:00
|
|
|
MWBase::Environment::get().getLuaManager()->newGameStarted();
|
2015-06-15 16:19:05 +02:00
|
|
|
MWBase::Environment::get().getWorld()->startNewGame(bypass);
|
|
|
|
|
|
|
|
mState = State_Running;
|
2017-07-23 11:04:58 +04:00
|
|
|
|
|
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0);
|
|
|
|
MWBase::Environment::get().getWindowManager()->fadeScreenIn(1);
|
2015-06-15 16:19:05 +02:00
|
|
|
}
|
|
|
|
catch (std::exception& e)
|
|
|
|
{
|
|
|
|
std::stringstream error;
|
|
|
|
error << "Failed to start new game: " << e.what();
|
2014-07-25 12:17:15 +02:00
|
|
|
|
2018-08-14 23:05:43 +04:00
|
|
|
Log(Debug::Error) << error.str();
|
2015-06-15 16:19:05 +02:00
|
|
|
cleanup(true);
|
2014-07-25 12:23:18 +02:00
|
|
|
|
2015-06-15 16:19:05 +02:00
|
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
|
|
|
|
|
|
|
|
std::vector<std::string> buttons;
|
2020-10-17 12:26:35 +04:00
|
|
|
buttons.emplace_back("#{sOk}");
|
2015-06-15 16:19:05 +02:00
|
|
|
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons);
|
|
|
|
}
|
2013-11-16 12:22:28 +01:00
|
|
|
}
|
2013-11-18 15:38:08 +01:00
|
|
|
|
|
|
|
void MWState::StateManager::endGame()
|
|
|
|
{
|
|
|
|
mState = State_Ended;
|
|
|
|
}
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2018-07-26 19:54:08 +03:00
|
|
|
void MWState::StateManager::resumeGame()
|
|
|
|
{
|
|
|
|
mState = State_Running;
|
|
|
|
}
|
|
|
|
|
2013-11-24 15:19:56 +01:00
|
|
|
void MWState::StateManager::saveGame(const std::string& description, const Slot* slot)
|
2013-11-19 15:38:26 +01:00
|
|
|
{
|
2016-03-14 00:00:11 +01:00
|
|
|
MWState::Character* character = getCurrentCharacter();
|
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
try
|
|
|
|
{
|
2022-02-13 20:13:14 +01:00
|
|
|
const auto start = std::chrono::steady_clock::now();
|
|
|
|
|
2022-02-13 20:45:05 +01:00
|
|
|
MWBase::Environment::get().getWindowManager()->asyncPrepareSaveMap();
|
|
|
|
|
2016-03-14 00:03:02 +01:00
|
|
|
if (!character)
|
|
|
|
{
|
|
|
|
MWWorld::ConstPtr player = MWMechanics::getPlayer();
|
2022-06-13 18:21:29 +02:00
|
|
|
const std::string& name = player.get<ESM::NPC>()->mBase->mName;
|
2016-03-14 00:03:02 +01:00
|
|
|
|
|
|
|
character = mCharacterManager.createCharacter(name);
|
|
|
|
mCharacterManager.setCurrentCharacter(character);
|
|
|
|
}
|
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
ESM::SavedGame profile;
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
MWBase::World& world = *MWBase::Environment::get().getWorld();
|
2013-11-21 10:53:42 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
MWWorld::Ptr player = world.getPlayerPtr();
|
2013-11-21 10:53:42 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
profile.mContentFiles = world.getContentFiles();
|
2013-11-25 13:00:05 +01:00
|
|
|
|
2014-09-13 02:58:01 +02:00
|
|
|
profile.mPlayerName = player.get<ESM::NPC>()->mBase->mName;
|
2014-06-02 14:46:41 +02:00
|
|
|
profile.mPlayerLevel = player.getClass().getNpcStats(player).getLevel();
|
2014-03-27 22:32:42 +01:00
|
|
|
|
2022-06-13 18:21:29 +02:00
|
|
|
const std::string& classId = player.get<ESM::NPC>()->mBase->mClass;
|
2014-06-02 14:46:41 +02:00
|
|
|
if (world.getStore().get<ESM::Class>().isDynamic(classId))
|
|
|
|
profile.mPlayerClassName = world.getStore().get<ESM::Class>().find(classId)->mName;
|
|
|
|
else
|
|
|
|
profile.mPlayerClassId = classId;
|
|
|
|
|
|
|
|
profile.mPlayerCell = world.getCellName();
|
2020-06-03 11:32:28 +04:00
|
|
|
profile.mInGameTime = world.getEpochTimeStamp();
|
2014-06-02 14:46:41 +02:00
|
|
|
profile.mTimePlayed = mTimePlayed;
|
|
|
|
profile.mDescription = description;
|
|
|
|
|
2021-07-05 13:11:54 +02:00
|
|
|
Log(Debug::Info) << "Making a screenshot for saved game '" << description << "'";
|
2015-06-03 16:40:16 +02:00
|
|
|
writeScreenshot(profile.mScreenshot);
|
2014-06-02 14:46:41 +02:00
|
|
|
|
|
|
|
if (!slot)
|
2016-03-14 00:00:11 +01:00
|
|
|
slot = character->createSlot(profile);
|
2014-06-02 14:46:41 +02:00
|
|
|
else
|
2016-03-14 00:00:11 +01:00
|
|
|
slot = character->updateSlot(slot, profile);
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2016-07-30 19:24:03 +02:00
|
|
|
// Make sure the animation state held by references is up to date before saving the game.
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->persistAnimationStates();
|
|
|
|
|
2020-05-12 13:51:07 +03:00
|
|
|
Log(Debug::Info) << "Writing saved game '" << description << "' for character '" << profile.mPlayerName << "'";
|
2020-05-09 20:17:49 +03:00
|
|
|
|
2015-12-05 01:05:24 +01:00
|
|
|
// Write to a memory stream first. If there is an exception during the save process, we don't want to trash the
|
|
|
|
// existing save file we are overwriting.
|
|
|
|
std::stringstream stream;
|
2014-01-27 13:27:42 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
ESM::ESMWriter writer;
|
2014-01-27 13:27:42 +01:00
|
|
|
|
2020-05-29 12:26:02 +04:00
|
|
|
for (const std::string& contentFile : MWBase::Environment::get().getWorld()->getContentFiles())
|
|
|
|
writer.addMaster(contentFile, 0); // not using the size information anyway -> use value of 0
|
2014-06-02 14:46:41 +02:00
|
|
|
|
2015-06-30 17:26:33 +02:00
|
|
|
writer.setFormat(ESM::SavedGame::sCurrentFormat);
|
2015-06-30 03:54:56 +02:00
|
|
|
|
2014-06-25 00:11:25 +02:00
|
|
|
// all unused
|
2015-06-30 17:26:33 +02:00
|
|
|
writer.setVersion(0);
|
2014-06-25 00:11:25 +02:00
|
|
|
writer.setType(0);
|
|
|
|
writer.setAuthor("");
|
|
|
|
writer.setDescription("");
|
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
int recordCount = 1 // saved game header
|
|
|
|
+ MWBase::Environment::get().getJournal()->countSavedGameRecords()
|
2021-01-29 02:38:09 +01:00
|
|
|
+ MWBase::Environment::get().getLuaManager()->countSavedGameRecords()
|
2014-06-02 14:46:41 +02:00
|
|
|
+ MWBase::Environment::get().getWorld()->countSavedGameRecords()
|
|
|
|
+ MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords()
|
|
|
|
+ MWBase::Environment::get().getDialogueManager()->countSavedGameRecords()
|
2016-10-20 02:12:01 +02:00
|
|
|
+ MWBase::Environment::get().getMechanicsManager()->countSavedGameRecords()
|
2022-02-13 20:45:05 +01:00
|
|
|
+ MWBase::Environment::get().getInputManager()->countSavedGameRecords()
|
|
|
|
+ MWBase::Environment::get().getWindowManager()->countSavedGameRecords();
|
2014-06-02 14:46:41 +02:00
|
|
|
writer.setRecordCount(recordCount);
|
2014-01-27 13:27:42 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
writer.save(stream);
|
2014-01-27 13:27:42 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
2015-01-11 18:01:06 +01:00
|
|
|
// Using only Cells for progress information, since they typically have the largest records by far
|
|
|
|
listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells());
|
2021-05-26 23:19:22 +02:00
|
|
|
listener.setLabel("#{sNotifyMessage4}", true);
|
2013-12-03 14:28:46 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
Loading::ScopedLoad load(&listener);
|
2013-12-03 14:28:46 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
writer.startRecord(ESM::REC_SAVE);
|
|
|
|
slot->mProfile.save(writer);
|
|
|
|
writer.endRecord(ESM::REC_SAVE);
|
2014-04-28 11:29:57 +02:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
MWBase::Environment::get().getJournal()->write(writer, listener);
|
|
|
|
MWBase::Environment::get().getDialogueManager()->write(writer, listener);
|
2021-01-29 02:38:09 +01:00
|
|
|
// LuaManager::write should be called before World::write because world also saves
|
|
|
|
// local scripts that depend on LuaManager.
|
|
|
|
MWBase::Environment::get().getLuaManager()->write(writer, listener);
|
2014-06-02 14:46:41 +02:00
|
|
|
MWBase::Environment::get().getWorld()->write(writer, listener);
|
|
|
|
MWBase::Environment::get().getScriptManager()->getGlobalScripts().write(writer, listener);
|
2014-06-13 01:24:58 +02:00
|
|
|
MWBase::Environment::get().getMechanicsManager()->write(writer, listener);
|
2016-10-20 02:12:01 +02:00
|
|
|
MWBase::Environment::get().getInputManager()->write(writer, listener);
|
2022-02-13 20:45:05 +01:00
|
|
|
MWBase::Environment::get().getWindowManager()->write(writer, listener);
|
2014-04-28 11:29:57 +02:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
// Ensure we have written the number of records that was estimated
|
|
|
|
if (writer.getRecordCount() != recordCount + 1) // 1 extra for TES3 record
|
2018-08-14 23:05:43 +04:00
|
|
|
Log(Debug::Warning) << "Warning: number of written savegame records does not match. Estimated: "
|
|
|
|
<< recordCount + 1 << ", written: " << writer.getRecordCount();
|
2014-04-28 11:29:57 +02:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
writer.close();
|
2013-11-28 11:22:34 +01:00
|
|
|
|
2014-06-07 17:48:40 +02:00
|
|
|
if (stream.fail())
|
2015-12-05 01:05:24 +01:00
|
|
|
throw std::runtime_error("Write operation failed (memory stream)");
|
|
|
|
|
|
|
|
// All good, write to file
|
2022-06-08 23:25:50 +02:00
|
|
|
std::ofstream filestream(slot->mPath, std::ios::binary);
|
2015-12-05 01:05:24 +01:00
|
|
|
filestream << stream.rdbuf();
|
|
|
|
|
|
|
|
if (filestream.fail())
|
|
|
|
throw std::runtime_error("Write operation failed (file stream)");
|
2014-06-07 17:48:40 +02:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
Settings::Manager::setString(
|
2022-07-03 00:02:29 +02:00
|
|
|
"character", "Saves", Files::pathToUnicodeString(slot->mPath.parent_path().filename()));
|
2022-02-13 20:13:14 +01:00
|
|
|
|
|
|
|
const auto finish = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
Log(Debug::Info) << '\'' << description << "' is saved in "
|
|
|
|
<< std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(finish - start).count()
|
|
|
|
<< "ms";
|
2014-06-02 14:46:41 +02:00
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
|
|
|
std::stringstream error;
|
|
|
|
error << "Failed to save game: " << e.what();
|
2013-11-28 11:22:34 +01:00
|
|
|
|
2018-08-14 23:05:43 +04:00
|
|
|
Log(Debug::Error) << error.str();
|
2013-11-26 10:37:58 +01:00
|
|
|
|
2014-06-02 14:46:41 +02:00
|
|
|
std::vector<std::string> buttons;
|
2020-10-17 12:26:35 +04:00
|
|
|
buttons.emplace_back("#{sOk}");
|
2015-01-10 23:21:39 +01:00
|
|
|
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons);
|
2014-06-07 17:48:40 +02:00
|
|
|
|
|
|
|
// If no file was written, clean up the slot
|
2022-06-08 23:25:50 +02:00
|
|
|
if (character && slot && !std::filesystem::exists(slot->mPath))
|
2016-03-14 00:00:11 +01:00
|
|
|
{
|
|
|
|
character->deleteSlot(slot);
|
|
|
|
character->cleanup();
|
|
|
|
}
|
2014-06-02 14:46:41 +02:00
|
|
|
}
|
2013-11-19 15:38:26 +01:00
|
|
|
}
|
|
|
|
|
2014-04-24 10:14:17 +02:00
|
|
|
void MWState::StateManager::quickSave(std::string name)
|
|
|
|
{
|
2014-05-02 11:20:43 +02:00
|
|
|
if (!(mState == State_Running
|
|
|
|
&& MWBase::Environment::get().getWorld()->getGlobalInt("chargenstate") == -1 // char gen
|
|
|
|
&& MWBase::Environment::get().getWindowManager()->isSavingAllowed()))
|
2014-04-25 08:39:40 +02:00
|
|
|
{
|
|
|
|
// You can not save your game right now
|
|
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sSaveGameDenied}");
|
2014-04-24 10:14:17 +02:00
|
|
|
return;
|
2014-04-24 03:06:36 -04:00
|
|
|
}
|
2014-04-24 09:54:47 +02:00
|
|
|
|
2018-01-12 20:02:43 -05:00
|
|
|
int maxSaves = Settings::Manager::getInt("max quicksaves", "Saves");
|
|
|
|
if (maxSaves < 1)
|
|
|
|
maxSaves = 1;
|
|
|
|
|
2016-03-14 00:00:11 +01:00
|
|
|
Character* currentCharacter = getCurrentCharacter(); // Get current character
|
2018-01-12 20:02:43 -05:00
|
|
|
QuickSaveManager saveFinder = QuickSaveManager(name, maxSaves);
|
2014-04-24 10:14:17 +02:00
|
|
|
|
2016-03-14 00:00:11 +01:00
|
|
|
if (currentCharacter)
|
2014-04-24 10:14:17 +02:00
|
|
|
{
|
2020-05-29 12:26:02 +04:00
|
|
|
for (auto& save : *currentCharacter)
|
2016-03-14 00:00:11 +01:00
|
|
|
{
|
2018-01-12 20:02:43 -05:00
|
|
|
// Visiting slots allows the quicksave finder to find the oldest quicksave
|
2020-05-29 12:26:02 +04:00
|
|
|
saveFinder.visitSave(&save);
|
2016-03-14 00:00:11 +01:00
|
|
|
}
|
2014-04-24 03:06:36 -04:00
|
|
|
}
|
2014-04-24 10:14:17 +02:00
|
|
|
|
2018-01-12 20:02:43 -05:00
|
|
|
// Once all the saves have been visited, the save finder can tell us which
|
|
|
|
// one to replace (or create)
|
2018-02-13 21:01:15 -05:00
|
|
|
saveGame(name, saveFinder.getNextQuickSaveSlot());
|
2014-04-24 03:06:36 -04:00
|
|
|
}
|
|
|
|
|
2022-06-19 13:28:33 +02:00
|
|
|
void MWState::StateManager::loadGame(const std::filesystem::path& filepath)
|
2015-01-07 03:03:56 +01:00
|
|
|
{
|
2020-05-29 12:26:02 +04:00
|
|
|
for (const auto& character : mCharacterManager)
|
2015-01-07 03:03:56 +01:00
|
|
|
{
|
2020-05-29 12:26:02 +04:00
|
|
|
for (const auto& slot : character)
|
2015-01-07 03:03:56 +01:00
|
|
|
{
|
2022-06-19 13:28:33 +02:00
|
|
|
if (slot.mPath == filepath)
|
2015-01-07 03:03:56 +01:00
|
|
|
{
|
2022-06-19 13:28:33 +02:00
|
|
|
loadGame(&character, slot.mPath);
|
2015-01-07 03:03:56 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-14 00:00:11 +01:00
|
|
|
MWState::Character* character = getCurrentCharacter();
|
2015-01-07 03:03:56 +01:00
|
|
|
loadGame(character, filepath);
|
|
|
|
}
|
|
|
|
|
2022-06-19 13:28:33 +02:00
|
|
|
void MWState::StateManager::loadGame(const Character* character, const std::filesystem::path& filepath)
|
2013-11-21 12:24:24 +01:00
|
|
|
{
|
2014-01-23 11:29:40 +01:00
|
|
|
try
|
2013-12-03 14:28:46 +01:00
|
|
|
{
|
2014-01-23 11:29:40 +01:00
|
|
|
cleanup();
|
2013-12-07 13:17:28 +01:00
|
|
|
|
2022-07-03 00:02:29 +02:00
|
|
|
Log(Debug::Info) << "Reading save file " << filepath.filename();
|
2020-05-09 20:17:49 +03:00
|
|
|
|
2014-01-23 11:29:40 +01:00
|
|
|
ESM::ESMReader reader;
|
2015-01-07 03:03:56 +01:00
|
|
|
reader.open(filepath);
|
2013-12-15 16:16:50 +01:00
|
|
|
|
2015-06-30 17:26:42 +02:00
|
|
|
if (reader.getFormat() > ESM::SavedGame::sCurrentFormat)
|
|
|
|
throw std::runtime_error(
|
|
|
|
"This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade "
|
|
|
|
"to the newest OpenMW version to load this file.");
|
|
|
|
|
2014-01-27 13:27:42 +01:00
|
|
|
std::map<int, int> contentFileMap = buildContentFileIndexMap(reader);
|
2021-01-29 02:38:09 +01:00
|
|
|
MWBase::Environment::get().getLuaManager()->setContentFileMapping(contentFileMap);
|
2014-01-27 13:27:42 +01:00
|
|
|
|
2014-04-28 11:29:57 +02:00
|
|
|
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
|
|
|
|
2015-01-11 17:54:15 +01:00
|
|
|
listener.setProgressRange(100);
|
2021-05-26 23:19:22 +02:00
|
|
|
listener.setLabel("#{sLoadingMessage14}");
|
2014-04-28 11:29:57 +02:00
|
|
|
|
|
|
|
Loading::ScopedLoad load(&listener);
|
|
|
|
|
2015-01-11 20:07:17 +01:00
|
|
|
bool firstPersonCam = false;
|
|
|
|
|
2015-01-11 17:54:15 +01:00
|
|
|
size_t total = reader.getFileSize();
|
|
|
|
int currentPercent = 0;
|
2014-01-23 11:29:40 +01:00
|
|
|
while (reader.hasMoreRecs())
|
|
|
|
{
|
|
|
|
ESM::NAME n = reader.getRecName();
|
|
|
|
reader.getRecHeader();
|
|
|
|
|
2021-10-17 02:52:22 +02:00
|
|
|
switch (n.toInt())
|
2014-01-23 11:29:40 +01:00
|
|
|
{
|
|
|
|
case ESM::REC_SAVE:
|
2022-09-22 21:26:05 +03:00
|
|
|
{
|
2015-01-07 03:03:56 +01:00
|
|
|
ESM::SavedGame profile;
|
|
|
|
profile.load(reader);
|
2015-01-10 23:58:55 +01:00
|
|
|
if (!verifyProfile(profile))
|
2015-01-07 03:03:56 +01:00
|
|
|
{
|
2015-01-10 23:58:55 +01:00
|
|
|
cleanup(true);
|
|
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
|
|
|
|
return;
|
2015-01-07 03:03:56 +01:00
|
|
|
}
|
|
|
|
mTimePlayed = profile.mTimePlayed;
|
2020-05-12 13:51:07 +03:00
|
|
|
Log(Debug::Info) << "Loading saved game '" << profile.mDescription << "' for character '"
|
|
|
|
<< profile.mPlayerName << "'";
|
2022-09-22 21:26:05 +03:00
|
|
|
}
|
2014-01-23 11:29:40 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ESM::REC_JOUR:
|
2015-01-22 17:47:00 +01:00
|
|
|
case ESM::REC_JOUR_LEGACY:
|
2014-01-23 11:29:40 +01:00
|
|
|
case ESM::REC_QUES:
|
|
|
|
|
2021-10-17 02:52:22 +02:00
|
|
|
MWBase::Environment::get().getJournal()->readRecord(reader, n.toInt());
|
2014-01-23 11:29:40 +01:00
|
|
|
break;
|
|
|
|
|
2014-02-16 12:54:27 +01:00
|
|
|
case ESM::REC_DIAS:
|
|
|
|
|
2021-10-17 02:52:22 +02:00
|
|
|
MWBase::Environment::get().getDialogueManager()->readRecord(reader, n.toInt());
|
2014-02-16 12:54:27 +01:00
|
|
|
break;
|
|
|
|
|
2014-01-23 11:29:40 +01:00
|
|
|
case ESM::REC_ALCH:
|
|
|
|
case ESM::REC_ARMO:
|
|
|
|
case ESM::REC_BOOK:
|
|
|
|
case ESM::REC_CLAS:
|
|
|
|
case ESM::REC_CLOT:
|
|
|
|
case ESM::REC_ENCH:
|
|
|
|
case ESM::REC_NPC_:
|
|
|
|
case ESM::REC_SPEL:
|
|
|
|
case ESM::REC_WEAP:
|
|
|
|
case ESM::REC_GLOB:
|
|
|
|
case ESM::REC_PLAY:
|
|
|
|
case ESM::REC_CSTA:
|
2014-03-20 01:25:52 -05:00
|
|
|
case ESM::REC_WTHR:
|
2014-05-11 00:32:22 +02:00
|
|
|
case ESM::REC_DYNA:
|
2014-05-14 09:47:49 +02:00
|
|
|
case ESM::REC_ACTC:
|
2014-05-17 05:21:17 +02:00
|
|
|
case ESM::REC_PROJ:
|
|
|
|
case ESM::REC_MPRJ:
|
2014-09-21 13:37:20 +02:00
|
|
|
case ESM::REC_ENAB:
|
2014-12-17 01:05:32 +01:00
|
|
|
case ESM::REC_LEVC:
|
|
|
|
case ESM::REC_LEVI:
|
2020-06-02 21:59:37 +02:00
|
|
|
case ESM::REC_CREA:
|
2020-10-29 13:55:24 +01:00
|
|
|
case ESM::REC_CONT:
|
2022-03-06 21:56:02 +02:00
|
|
|
case ESM::REC_RAND:
|
2021-10-17 02:52:22 +02:00
|
|
|
MWBase::Environment::get().getWorld()->readRecord(reader, n.toInt(), contentFileMap);
|
2015-01-11 20:07:17 +01:00
|
|
|
break;
|
2014-01-23 11:29:40 +01:00
|
|
|
|
2015-01-11 20:07:17 +01:00
|
|
|
case ESM::REC_CAM_:
|
|
|
|
reader.getHNT(firstPersonCam, "FIRS");
|
2014-01-23 11:29:40 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ESM::REC_GSCR:
|
|
|
|
|
2021-10-17 02:52:22 +02:00
|
|
|
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord(
|
|
|
|
reader, n.toInt(), contentFileMap);
|
2014-01-23 11:29:40 +01:00
|
|
|
break;
|
|
|
|
|
2014-01-25 18:20:17 +01:00
|
|
|
case ESM::REC_GMAP:
|
2014-05-01 21:16:32 +02:00
|
|
|
case ESM::REC_KEYS:
|
2014-05-12 21:04:02 +02:00
|
|
|
case ESM::REC_ASPL:
|
2014-08-17 05:45:35 +02:00
|
|
|
case ESM::REC_MARK:
|
2014-05-12 21:04:02 +02:00
|
|
|
|
2021-10-17 02:52:22 +02:00
|
|
|
MWBase::Environment::get().getWindowManager()->readRecord(reader, n.toInt());
|
2014-01-25 18:20:17 +01:00
|
|
|
break;
|
|
|
|
|
2014-06-13 01:24:58 +02:00
|
|
|
case ESM::REC_DCOU:
|
2015-02-04 21:18:43 +01:00
|
|
|
case ESM::REC_STLN:
|
2014-06-13 01:24:58 +02:00
|
|
|
|
2021-10-17 02:52:22 +02:00
|
|
|
MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.toInt());
|
2014-06-13 01:24:58 +02:00
|
|
|
break;
|
|
|
|
|
2016-10-20 02:12:01 +02:00
|
|
|
case ESM::REC_INPU:
|
2021-10-17 02:52:22 +02:00
|
|
|
MWBase::Environment::get().getInputManager()->readRecord(reader, n.toInt());
|
2016-10-20 02:12:01 +02:00
|
|
|
break;
|
|
|
|
|
2021-01-29 02:38:09 +01:00
|
|
|
case ESM::REC_LUAM:
|
2021-10-17 02:52:22 +02:00
|
|
|
MWBase::Environment::get().getLuaManager()->readRecord(reader, n.toInt());
|
2021-01-29 02:38:09 +01:00
|
|
|
break;
|
|
|
|
|
2014-01-23 11:29:40 +01:00
|
|
|
default:
|
|
|
|
|
|
|
|
// ignore invalid records
|
2022-01-27 23:53:09 +01:00
|
|
|
Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toStringView();
|
2014-01-23 11:29:40 +01:00
|
|
|
reader.skipRecord();
|
|
|
|
}
|
2015-01-11 17:54:15 +01:00
|
|
|
int progressPercent = static_cast<int>(float(reader.getFileOffset()) / total * 100);
|
|
|
|
if (progressPercent > currentPercent)
|
|
|
|
{
|
|
|
|
listener.increaseProgress(progressPercent - currentPercent);
|
|
|
|
currentPercent = progressPercent;
|
|
|
|
}
|
2013-12-03 14:28:46 +01:00
|
|
|
}
|
2013-11-21 12:24:24 +01:00
|
|
|
|
2014-01-23 11:29:40 +01:00
|
|
|
mCharacterManager.setCurrentCharacter(character);
|
2013-11-21 20:24:58 +01:00
|
|
|
|
2014-01-23 11:29:40 +01:00
|
|
|
mState = State_Running;
|
2013-11-26 10:37:58 +01:00
|
|
|
|
2016-03-13 23:37:14 +01:00
|
|
|
if (character)
|
|
|
|
Settings::Manager::setString(
|
2022-07-03 00:02:29 +02:00
|
|
|
"character", "Saves", Files::pathToUnicodeString(character->getPath().filename()));
|
2013-12-05 14:22:08 +01:00
|
|
|
|
2014-01-25 13:34:56 +01:00
|
|
|
MWBase::Environment::get().getWindowManager()->setNewGame(false);
|
2021-03-23 21:07:57 +01:00
|
|
|
MWBase::Environment::get().getWorld()->saveLoaded();
|
2014-01-23 11:29:40 +01:00
|
|
|
MWBase::Environment::get().getWorld()->setupPlayer();
|
|
|
|
MWBase::Environment::get().getWorld()->renderPlayer();
|
|
|
|
MWBase::Environment::get().getWindowManager()->updatePlayer();
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playerLoaded();
|
2013-12-07 13:17:28 +01:00
|
|
|
|
2015-01-11 20:07:17 +01:00
|
|
|
if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson())
|
|
|
|
MWBase::Environment::get().getWorld()->togglePOV();
|
|
|
|
|
2018-05-26 11:35:48 +00:00
|
|
|
MWWorld::ConstPtr ptr = MWMechanics::getPlayer();
|
2014-04-24 03:06:36 -04:00
|
|
|
|
2016-04-15 00:49:36 +02:00
|
|
|
if (ptr.isInCell())
|
|
|
|
{
|
|
|
|
const ESM::CellId& cellId = ptr.getCell()->getCell()->getCellId();
|
2014-01-16 12:03:23 +01:00
|
|
|
|
2016-04-15 00:49:36 +02:00
|
|
|
// Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again
|
|
|
|
MWBase::Environment::get().getWorld()->changeToCell(cellId, ptr.getRefData().getPosition(), false, false);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Cell no longer exists (i.e. changed game files), choose a default cell
|
2020-05-09 20:17:49 +03:00
|
|
|
Log(Debug::Warning) << "Warning: Player character's cell no longer exists, changing to the default cell";
|
2016-04-15 00:49:36 +02:00
|
|
|
MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(0, 0);
|
|
|
|
float x, y;
|
|
|
|
MWBase::Environment::get().getWorld()->indexToPosition(0, 0, x, y, false);
|
|
|
|
ESM::Position pos;
|
|
|
|
pos.pos[0] = x;
|
|
|
|
pos.pos[1] = y;
|
|
|
|
pos.pos[2] = 0; // should be adjusted automatically (adjustPlayerPos=true)
|
|
|
|
pos.rot[0] = 0;
|
|
|
|
pos.rot[1] = 0;
|
|
|
|
pos.rot[2] = 0;
|
|
|
|
MWBase::Environment::get().getWorld()->changeToCell(cell->getCell()->getCellId(), pos, true, false);
|
|
|
|
}
|
2014-05-17 05:21:17 +02:00
|
|
|
|
2021-02-25 23:12:14 +00:00
|
|
|
MWBase::Environment::get().getWorld()->updateProjectilesCasters();
|
|
|
|
|
2015-01-11 17:54:15 +01:00
|
|
|
// Vanilla MW will restart startup scripts when a save game is loaded. This is unintuitive,
|
2015-01-06 19:29:33 +01:00
|
|
|
// but some mods may be using it as a reload detector.
|
2014-07-25 12:17:15 +02:00
|
|
|
MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup();
|
|
|
|
|
2016-02-27 12:53:07 +01:00
|
|
|
// Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag.
|
|
|
|
// But make sure the flag is cleared anyway in case it was set from an earlier game.
|
2014-05-17 05:21:17 +02:00
|
|
|
MWBase::Environment::get().getWorld()->markCellAsUnchanged();
|
2021-09-25 10:46:47 +02:00
|
|
|
|
|
|
|
MWBase::Environment::get().getLuaManager()->gameLoaded();
|
2014-01-23 11:29:40 +01:00
|
|
|
}
|
|
|
|
catch (const std::exception& e)
|
|
|
|
{
|
2014-06-02 14:46:41 +02:00
|
|
|
std::stringstream error;
|
|
|
|
error << "Failed to load saved game: " << e.what();
|
|
|
|
|
2018-08-14 23:05:43 +04:00
|
|
|
Log(Debug::Error) << error.str();
|
2014-01-23 11:29:40 +01:00
|
|
|
cleanup(true);
|
2014-05-20 17:11:26 +02:00
|
|
|
|
|
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
|
2014-06-02 14:46:41 +02:00
|
|
|
|
|
|
|
std::vector<std::string> buttons;
|
2020-10-17 12:26:35 +04:00
|
|
|
buttons.emplace_back("#{sOk}");
|
2015-01-10 23:21:39 +01:00
|
|
|
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons);
|
2014-01-23 11:29:40 +01:00
|
|
|
}
|
2013-11-21 12:24:24 +01:00
|
|
|
}
|
|
|
|
|
2014-04-24 10:14:17 +02:00
|
|
|
void MWState::StateManager::quickLoad()
|
|
|
|
{
|
2016-03-14 00:00:11 +01:00
|
|
|
if (Character* currentCharacter = getCurrentCharacter())
|
2015-01-11 17:54:15 +01:00
|
|
|
{
|
2016-03-13 23:48:44 +01:00
|
|
|
if (currentCharacter->begin() == currentCharacter->end())
|
2015-01-11 17:54:15 +01:00
|
|
|
return;
|
2022-06-19 13:28:33 +02:00
|
|
|
loadGame(currentCharacter, currentCharacter->begin()->mPath); // Get newest save
|
2015-01-11 17:54:15 +01:00
|
|
|
}
|
2014-04-24 03:14:47 -04:00
|
|
|
}
|
|
|
|
|
2014-04-28 20:57:45 +02:00
|
|
|
void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot)
|
|
|
|
{
|
|
|
|
mCharacterManager.deleteSlot(character, slot);
|
|
|
|
}
|
|
|
|
|
2016-03-14 00:00:11 +01:00
|
|
|
MWState::Character* MWState::StateManager::getCurrentCharacter()
|
2013-11-19 15:38:26 +01:00
|
|
|
{
|
2016-03-14 00:00:11 +01:00
|
|
|
return mCharacterManager.getCurrentCharacter();
|
2013-11-21 11:10:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin()
|
|
|
|
{
|
|
|
|
return mCharacterManager.begin();
|
|
|
|
}
|
|
|
|
|
|
|
|
MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd()
|
|
|
|
{
|
|
|
|
return mCharacterManager.end();
|
|
|
|
}
|
2013-11-28 09:33:50 +01:00
|
|
|
|
|
|
|
void MWState::StateManager::update(float duration)
|
|
|
|
{
|
|
|
|
mTimePlayed += duration;
|
2014-01-25 22:20:46 +01:00
|
|
|
|
|
|
|
// Note: It would be nicer to trigger this from InputManager, i.e. the very beginning of the frame update.
|
|
|
|
if (mAskLoadRecent)
|
|
|
|
{
|
|
|
|
int iButton = MWBase::Environment::get().getWindowManager()->readPressedButton();
|
2016-03-14 00:00:11 +01:00
|
|
|
MWState::Character* curCharacter = getCurrentCharacter();
|
2014-06-10 00:22:00 +02:00
|
|
|
if (iButton == 0 && curCharacter)
|
2014-01-25 22:20:46 +01:00
|
|
|
{
|
|
|
|
mAskLoadRecent = false;
|
|
|
|
// Load last saved game for current character
|
2014-06-10 00:22:00 +02:00
|
|
|
|
2014-01-25 22:20:46 +01:00
|
|
|
MWState::Slot lastSave = *curCharacter->begin();
|
2022-06-19 13:28:33 +02:00
|
|
|
loadGame(curCharacter, lastSave.mPath);
|
2014-01-25 22:20:46 +01:00
|
|
|
}
|
|
|
|
else if (iButton == 1)
|
|
|
|
{
|
|
|
|
mAskLoadRecent = false;
|
|
|
|
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
|
|
|
|
}
|
|
|
|
}
|
2013-11-28 09:33:50 +01:00
|
|
|
}
|
2015-01-10 23:58:55 +01:00
|
|
|
|
|
|
|
bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const
|
|
|
|
{
|
|
|
|
const std::vector<std::string>& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles();
|
|
|
|
bool notFound = false;
|
2020-05-29 12:26:02 +04:00
|
|
|
for (const std::string& contentFile : profile.mContentFiles)
|
2015-01-10 23:58:55 +01:00
|
|
|
{
|
2020-05-29 12:26:02 +04:00
|
|
|
if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), contentFile)
|
2015-01-10 23:58:55 +01:00
|
|
|
== selectedContentFiles.end())
|
|
|
|
{
|
2020-05-29 12:26:02 +04:00
|
|
|
Log(Debug::Warning) << "Warning: Saved game dependency " << contentFile << " is missing.";
|
2015-01-10 23:58:55 +01:00
|
|
|
notFound = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (notFound)
|
|
|
|
{
|
|
|
|
std::vector<std::string> buttons;
|
2020-10-17 12:26:35 +04:00
|
|
|
buttons.emplace_back("#{sYes}");
|
|
|
|
buttons.emplace_back("#{sNo}");
|
2015-01-10 23:58:55 +01:00
|
|
|
MWBase::Environment::get().getWindowManager()->interactiveMessageBox("#{sMissingMastersMsg}", buttons, true);
|
|
|
|
int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton();
|
|
|
|
if (selectedButton == 1 || selectedButton == -1)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
2015-06-03 16:40:16 +02:00
|
|
|
|
|
|
|
void MWState::StateManager::writeScreenshot(std::vector<char>& imageData) const
|
|
|
|
{
|
|
|
|
int screenshotW = 259 * 2, screenshotH = 133 * 2; // *2 to get some nice antialiasing
|
|
|
|
|
|
|
|
osg::ref_ptr<osg::Image> screenshot(new osg::Image);
|
|
|
|
|
|
|
|
MWBase::Environment::get().getWorld()->screenshot(screenshot.get(), screenshotW, screenshotH);
|
|
|
|
|
|
|
|
osgDB::ReaderWriter* readerwriter = osgDB::Registry::instance()->getReaderWriterForExtension("jpg");
|
|
|
|
if (!readerwriter)
|
|
|
|
{
|
2018-08-14 23:05:43 +04:00
|
|
|
Log(Debug::Error) << "Error: Unable to write screenshot, can't find a jpg ReaderWriter";
|
2015-06-03 16:40:16 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::ostringstream ostream;
|
|
|
|
osgDB::ReaderWriter::WriteResult result = readerwriter->writeImage(*screenshot, ostream);
|
|
|
|
if (!result.success())
|
|
|
|
{
|
2018-08-14 23:05:43 +04:00
|
|
|
Log(Debug::Error) << "Error: Unable to write screenshot: " << result.message() << " code " << result.status();
|
2015-06-03 16:40:16 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string data = ostream.str();
|
|
|
|
imageData = std::vector<char>(data.begin(), data.end());
|
|
|
|
}
|