1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-27 21:35:24 +00:00
OpenMW/apps/openmw/mwstate/statemanagerimp.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

859 lines
30 KiB
C++
Raw Normal View History

2013-11-16 10:31:46 +01:00
#include "statemanagerimp.hpp"
2022-05-25 21:16:26 +02:00
#include <filesystem>
#include <SDL_clipboard.h>
2018-08-14 23:05:43 +04:00
#include <components/debug/debuglog.hpp>
#include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/esm3/loadclas.hpp>
#include <components/l10n/manager.hpp>
2015-07-09 19:22:04 +02:00
#include <components/loadinglistener/loadinglistener.hpp>
#include <components/files/conversion.hpp>
#include <components/misc/algorithm.hpp>
2023-10-08 13:55:17 +02:00
#include <components/settings/values.hpp>
2015-06-03 16:40:16 +02:00
#include <osg/Image>
#include <osgDB/Registry>
2014-01-24 17:49:16 +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"
#include "../mwbase/scriptmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
2014-02-23 20:11:05 +01:00
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/datetimemanager.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/globals.hpp"
#include "../mwworld/scene.hpp"
2022-12-06 00:11:19 +01:00
#include "../mwworld/worldmodel.hpp"
2015-08-21 21:12:39 +12:00
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwscript/globalscripts.hpp"
#include "quicksavemanager.hpp"
void MWState::StateManager::cleanup(bool force)
2013-11-28 11:22:34 +01:00
{
if (mState != State_NoGame || force)
2013-11-28 11:22:34 +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();
MWBase::Environment::get().getScriptManager()->clear();
MWBase::Environment::get().getWindowManager()->clear();
2020-11-18 17:28:09 +01:00
MWBase::Environment::get().getWorld()->clear();
MWBase::Environment::get().getInputManager()->clear();
MWBase::Environment::get().getMechanicsManager()->clear();
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;
mLastSavegame.clear();
2014-04-29 19:56:33 +02:00
MWMechanics::CreatureStats::cleanup();
2013-11-28 11:22:34 +01:00
}
MWBase::Environment::get().getLuaManager()->clear();
2013-11-28 11:22:34 +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)
{
for (int iCurrent = 0; iCurrent < static_cast<int>(current.size()); ++iCurrent)
if (Misc::StringUtils::ciEqual(prev[iPrev].name, current[iCurrent]))
{
map.insert(std::make_pair(iPrev, iCurrent));
break;
}
}
return map;
}
MWState::StateManager::StateManager(const std::filesystem::path& saves, const std::vector<std::string>& contentFiles)
: mQuitRequest(false)
, mAskLoadRecent(false)
, mState(State_NoGame)
, mCharacterManager(saves, contentFiles)
, mTimePlayed(0)
2013-11-16 10:31:46 +01:00
{
}
void MWState::StateManager::requestQuit()
{
mQuitRequest = true;
}
bool MWState::StateManager::hasQuitRequest() const
{
return mQuitRequest;
}
2013-12-19 22:08:34 +02:00
void MWState::StateManager::askLoadRecent()
{
if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu)
return;
if (!mAskLoadRecent)
{
if (mLastSavegame.empty()) // no saves
2013-12-19 22:08:34 +02:00
{
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
}
else
{
std::string saveName = Files::pathToUnicodeString(mLastSavegame.filename());
// Assume the last saved game belongs to the current character's slot list.
const Character* character = getCurrentCharacter();
if (character)
{
for (const auto& slot : *character)
{
if (slot.mPath == mLastSavegame)
{
saveName = slot.mProfile.mDescription;
break;
}
}
}
2013-12-19 22:08:34 +02:00
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:Yes}");
buttons.emplace_back("#{Interface:No}");
std::string message
= MWBase::Environment::get().getL10nManager()->getMessage("OMWEngine", "AskLoadLastSave");
std::string_view tag = "%s";
2013-12-20 14:04:59 +02:00
size_t pos = message.find(tag);
message.replace(pos, tag.length(), saveName);
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons);
2013-12-19 22:08:34 +02:00
mAskLoadRecent = true;
}
}
}
MWState::StateManager::State MWState::StateManager::getState() const
{
return mState;
}
void MWState::StateManager::newGame(bool bypass)
{
2013-11-28 11:22:34 +01:00
cleanup();
if (!bypass)
MWBase::Environment::get().getWindowManager()->setNewGame(true);
try
{
2020-05-09 20:17:49 +03:00
Log(Debug::Info) << "Starting a new game";
MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup();
MWBase::Environment::get().getWorld()->startNewGame(bypass);
mState = State_Running;
MWBase::Environment::get().getLuaManager()->newGameStarted();
2017-07-23 11:04:58 +04:00
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0);
MWBase::Environment::get().getWindowManager()->fadeScreenIn(1);
}
catch (std::exception& e)
{
std::stringstream error;
error << "Failed to start new game: " << e.what();
2018-08-14 23:05:43 +04:00
Log(Debug::Error) << error.str();
cleanup(true);
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:OK}");
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons);
}
}
2013-11-18 15:38:08 +01:00
void MWState::StateManager::endGame()
{
mState = State_Ended;
MWBase::Environment::get().getLuaManager()->gameEnded();
2013-11-18 15:38:08 +01:00
}
2018-07-26 19:54:08 +03:00
void MWState::StateManager::resumeGame()
{
mState = State_Running;
MWBase::Environment::get().getLuaManager()->gameLoaded();
2018-07-26 19:54:08 +03:00
}
void MWState::StateManager::saveGame(std::string_view description, const Slot* slot)
{
MWBase::Environment::get().getLuaManager()->applyDelayedActions();
MWState::Character* character = getCurrentCharacter();
try
{
2022-02-13 20:13:14 +01:00
const auto start = std::chrono::steady_clock::now();
MWBase::Environment::get().getWindowManager()->asyncPrepareSaveMap();
if (!character)
{
MWWorld::ConstPtr player = MWMechanics::getPlayer();
const std::string& name = player.get<ESM::NPC>()->mBase->mName;
character = mCharacterManager.createCharacter(name);
mCharacterManager.setCurrentCharacter(character);
}
ESM::SavedGame profile;
MWBase::World& world = *MWBase::Environment::get().getWorld();
MWWorld::Ptr player = world.getPlayerPtr();
profile.mContentFiles = world.getContentFiles();
2014-09-13 02:58:01 +02:00
profile.mPlayerName = player.get<ESM::NPC>()->mBase->mName;
profile.mPlayerLevel = player.getClass().getNpcStats(player).getLevel();
Initial commit: In ESM structures, replace the string members that are RefIds to other records, to a new strong type The strong type is actually just a string underneath, but this will help in the future to have a distinction so it's easier to search and replace when we use an integer ID Slowly going through all the changes to make, still hundreds of errors a lot of functions/structures use std::string or stringview to designate an ID. So it takes time Continues slowly replacing ids. There are technically more and more compilation errors I have good hope that there is a point where the amount of errors will dramatically go down as all the main functions use the ESM::RefId type Continue moving forward, changes to the stores slowly moving along Starting to see the fruit of those changes. still many many error, but more and more Irun into a situation where a function is sandwiched between two functions that use the RefId type. More replacements. Things are starting to get easier I can see more and more often the issue is that the function is awaiting a RefId, but is given a string there is less need to go down functions and to fix a long list of them. Still moving forward, and for the first time error count is going down! Good pace, not sure about topics though, mId and mName are actually the same thing and are used interchangeably Cells are back to using string for the name, haven't fixed everything yet. Many other changes Under the bar of 400 compilation errors. more good progress <100 compile errors! More progress Game settings store can use string for find, it was a bit absurd how every use of it required to create refId from string some more progress on other fronts Mostly game settings clean one error opened a lot of other errors. Down to 18, but more will prbably appear only link errors left?? Fixed link errors OpenMW compiles, and launches, with some issues, but still!
2022-09-25 13:17:09 +02:00
const ESM::RefId& classId = player.get<ESM::NPC>()->mBase->mClass;
if (world.getStore().get<ESM::Class>().isDynamic(classId))
profile.mPlayerClassName = world.getStore().get<ESM::Class>().find(classId)->mName;
else
profile.mPlayerClassId = classId;
const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
profile.mPlayerCellName = world.getCellName();
profile.mInGameTime = world.getTimeManager()->getEpochTimeStamp();
profile.mTimePlayed = mTimePlayed;
profile.mDescription = description;
profile.mCurrentDay = world.getTimeManager()->getTimeStamp().getDay();
profile.mCurrentHealth = stats.getHealth().getCurrent();
profile.mMaximumHealth = stats.getHealth().getModified();
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);
if (!slot)
slot = character->createSlot(profile);
else
slot = character->updateSlot(slot, profile);
// Make sure the animation state held by references is up to date before saving the game.
MWBase::Environment::get().getMechanicsManager()->persistAnimationStates();
Log(Debug::Info) << "Writing saved game '" << description << "' for character '" << profile.mPlayerName << "'";
2020-05-09 20:17:49 +03: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;
ESM::ESMWriter writer;
for (const std::string& contentFile : MWBase::Environment::get().getWorld()->getContentFiles())
writer.addMaster(contentFile, 0); // not using the size information anyway -> use value of 0
writer.setFormatVersion(ESM::CurrentSaveGameFormatVersion);
// all unused
writer.setVersion(0);
writer.setType(0);
writer.setAuthor("");
writer.setDescription("");
int recordCount = 1 // saved game header
+ MWBase::Environment::get().getJournal()->countSavedGameRecords()
+ MWBase::Environment::get().getLuaManager()->countSavedGameRecords()
+ 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()
+ MWBase::Environment::get().getInputManager()->countSavedGameRecords()
+ MWBase::Environment::get().getWindowManager()->countSavedGameRecords();
writer.setRecordCount(recordCount);
writer.save(stream);
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
// Using only Cells for progress information, since they typically have the largest records by far
listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells());
listener.setLabel("#{OMWEngine:SavingInProgress}", true);
2013-12-03 14:28:46 +01:00
Loading::ScopedLoad load(&listener);
2013-12-03 14:28:46 +01:00
writer.startRecord(ESM::REC_SAVE);
slot->mProfile.save(writer);
writer.endRecord(ESM::REC_SAVE);
MWBase::Environment::get().getJournal()->write(writer, listener);
MWBase::Environment::get().getDialogueManager()->write(writer, listener);
// 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);
MWBase::Environment::get().getWorld()->write(writer, listener);
MWBase::Environment::get().getScriptManager()->getGlobalScripts().write(writer, listener);
MWBase::Environment::get().getMechanicsManager()->write(writer, listener);
2016-10-20 02:12:01 +02:00
MWBase::Environment::get().getInputManager()->write(writer, listener);
MWBase::Environment::get().getWindowManager()->write(writer, listener);
// 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();
writer.close();
2013-11-28 11:22:34 +01:00
if (stream.fail())
throw std::runtime_error("Write operation failed (memory stream)");
// All good, write to file
std::ofstream filestream(slot->mPath, std::ios::binary);
filestream << stream.rdbuf();
if (filestream.fail())
throw std::runtime_error("Write operation failed (file stream)");
2023-10-08 13:55:17 +02:00
Settings::saves().mCharacter.set(Files::pathToUnicodeString(slot->mPath.parent_path().filename()));
mLastSavegame = slot->mPath;
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";
}
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();
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:OK}");
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons);
// If no file was written, clean up the slot
if (character && slot && !std::filesystem::exists(slot->mPath))
{
character->deleteSlot(slot);
character->cleanup();
}
}
}
void MWState::StateManager::quickSave(std::string name)
{
if (!(mState == State_Running
&& MWBase::Environment::get().getWorld()->getGlobalInt(MWWorld::Globals::sCharGenState) == -1 // char gen
&& MWBase::Environment::get().getWindowManager()->isSavingAllowed()))
{
// You can not save your game right now
MWBase::Environment::get().getWindowManager()->messageBox("#{OMWEngine:SaveGameDenied}");
return;
}
2014-04-24 09:54:47 +02:00
Character* currentCharacter = getCurrentCharacter(); // Get current character
2023-10-08 13:55:17 +02:00
QuickSaveManager saveFinder(name, Settings::saves().mMaxQuicksaves);
if (currentCharacter)
{
for (auto& save : *currentCharacter)
{
// Visiting slots allows the quicksave finder to find the oldest quicksave
saveFinder.visitSave(&save);
}
}
// Once all the saves have been visited, the save finder can tell us which
// one to replace (or create)
saveGame(name, saveFinder.getNextQuickSaveSlot());
}
void MWState::StateManager::loadGame(const std::filesystem::path& filepath)
{
for (const auto& character : mCharacterManager)
{
for (const auto& slot : character)
{
if (slot.mPath == filepath)
{
loadGame(&character, slot.mPath);
return;
}
}
}
MWState::Character* character = getCurrentCharacter();
loadGame(character, filepath);
}
struct SaveFormatVersionError : public std::exception
{
using std::exception::exception;
SaveFormatVersionError(ESM::FormatVersion savegameFormat, const std::string& message)
: mSavegameFormat(savegameFormat)
, mErrorMessage(message)
{
}
const char* what() const noexcept override { return mErrorMessage.c_str(); }
ESM::FormatVersion getFormatVersion() const { return mSavegameFormat; }
protected:
ESM::FormatVersion mSavegameFormat = ESM::DefaultFormatVersion;
std::string mErrorMessage;
};
struct SaveVersionTooOldError : SaveFormatVersionError
{
SaveVersionTooOldError(ESM::FormatVersion savegameFormat)
: SaveFormatVersionError(savegameFormat, "format version " + std::to_string(savegameFormat) + " is too old")
{
}
};
struct SaveVersionTooNewError : SaveFormatVersionError
{
SaveVersionTooNewError(ESM::FormatVersion savegameFormat)
: SaveFormatVersionError(savegameFormat, "format version " + std::to_string(savegameFormat) + " is too new")
{
}
};
void MWState::StateManager::loadGame(const Character* character, const std::filesystem::path& filepath)
2013-11-21 12:24:24 +01:00
{
try
2013-12-03 14:28:46 +01:00
{
if (mState != State_Ended)
{
// let menu scripts do cleanup
mState = State_Ended;
MWBase::Environment::get().getLuaManager()->gameEnded();
}
cleanup();
2013-12-07 13:17:28 +01:00
Log(Debug::Info) << "Reading save file " << filepath.filename();
2020-05-09 20:17:49 +03:00
ESM::ESMReader reader;
reader.open(filepath);
ESM::FormatVersion version = reader.getFormatVersion();
if (version > ESM::CurrentSaveGameFormatVersion)
throw SaveVersionTooNewError(version);
else if (version < ESM::MinSupportedSaveGameFormatVersion)
throw SaveVersionTooOldError(version);
std::map<int, int> contentFileMap = buildContentFileIndexMap(reader);
reader.setContentFileMapping(&contentFileMap);
MWBase::Environment::get().getLuaManager()->setContentFileMapping(contentFileMap);
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
listener.setProgressRange(100);
listener.setLabel("#{OMWEngine:LoadingInProgress}");
Loading::ScopedLoad load(&listener);
bool firstPersonCam = false;
size_t total = reader.getFileSize();
int currentPercent = 0;
while (reader.hasMoreRecs())
{
ESM::NAME n = reader.getRecName();
reader.getRecHeader();
switch (n.toInt())
{
case ESM::REC_SAVE:
2022-09-22 21:26:05 +03:00
{
ESM::SavedGame profile;
profile.load(reader);
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);
return;
}
mTimePlayed = profile.mTimePlayed;
Log(Debug::Info) << "Loading saved game '" << profile.mDescription << "' for character '"
<< profile.mPlayerName << "'";
2022-09-22 21:26:05 +03:00
}
break;
case ESM::REC_JOUR:
case ESM::REC_QUES:
MWBase::Environment::get().getJournal()->readRecord(reader, n.toInt());
break;
case ESM::REC_DIAS:
MWBase::Environment::get().getDialogueManager()->readRecord(reader, n.toInt());
break;
case ESM::REC_ALCH:
case ESM::REC_MISC:
case ESM::REC_ACTI:
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:
case ESM::REC_WTHR:
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:
case ESM::REC_ENAB:
case ESM::REC_LEVC:
case ESM::REC_LEVI:
case ESM::REC_CREA:
case ESM::REC_CONT:
case ESM::REC_RAND:
MWBase::Environment::get().getWorld()->readRecord(reader, n.toInt());
break;
case ESM::REC_CAM_:
reader.getHNT(firstPersonCam, "FIRS");
break;
case ESM::REC_GSCR:
MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord(reader, n.toInt());
break;
2014-01-25 18:20:17 +01:00
case ESM::REC_GMAP:
case ESM::REC_KEYS:
2014-05-12 21:04:02 +02:00
case ESM::REC_ASPL:
case ESM::REC_MARK:
2014-05-12 21:04:02 +02:00
MWBase::Environment::get().getWindowManager()->readRecord(reader, n.toInt());
2014-01-25 18:20:17 +01:00
break;
case ESM::REC_DCOU:
case ESM::REC_STLN:
MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.toInt());
break;
2016-10-20 02:12:01 +02:00
case ESM::REC_INPU:
MWBase::Environment::get().getInputManager()->readRecord(reader, n.toInt());
2016-10-20 02:12:01 +02:00
break;
case ESM::REC_LUAM:
MWBase::Environment::get().getLuaManager()->readRecord(reader, n.toInt());
break;
default:
// ignore invalid records
Log(Debug::Warning) << "Warning: Ignoring unknown record: " << n.toStringView();
reader.skipRecord();
}
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
mCharacterManager.setCurrentCharacter(character);
mState = State_Running;
if (character)
2023-10-08 13:55:17 +02:00
Settings::saves().mCharacter.set(Files::pathToUnicodeString(character->getPath().filename()));
mLastSavegame = filepath;
MWBase::Environment::get().getWindowManager()->setNewGame(false);
MWBase::Environment::get().getWorld()->saveLoaded();
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
if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson())
MWBase::Environment::get().getWorld()->togglePOV();
MWWorld::ConstPtr ptr = MWMechanics::getPlayer();
if (ptr.isInCell())
{
const ESM::RefId cellId = ptr.getCell()->getCell()->getId();
// 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
Log(Debug::Warning) << "Player character's cell no longer exists, changing to the default cell";
ESM::ExteriorCellLocation cellIndex(0, 0, ESM::Cell::sDefaultWorldspaceId);
MWWorld::CellStore& cell = MWBase::Environment::get().getWorldModel()->getExterior(cellIndex);
osg::Vec2 posFromIndex = ESM::indexToPosition(cellIndex, false);
ESM::Position pos;
pos.pos[0] = posFromIndex.x();
pos.pos[1] = posFromIndex.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()->getId(), pos, true, false);
}
2014-05-17 05:21:17 +02:00
MWBase::Environment::get().getWorld()->updateProjectilesCasters();
// Vanilla MW will restart startup scripts when a save game is loaded. This is unintuitive,
// but some mods may be using it as a reload detector.
MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup();
// 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.
MWBase::Environment::get().getWorldScene()->markCellAsUnchanged();
MWBase::Environment::get().getLuaManager()->gameLoaded();
}
catch (const SaveVersionTooNewError& e)
{
std::string error = "#{OMWEngine:LoadingRequiresNewVersionError}";
printSavegameFormatError(e.what(), error);
}
catch (const SaveVersionTooOldError& e)
{
const char* release;
// Report the last version still capable of reading this save
if (e.getFormatVersion() <= ESM::OpenMW0_48SaveGameFormatVersion)
release = "OpenMW 0.48.0";
else
{
// Insert additional else if statements above to cover future releases
static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion);
release = "OpenMW 0.49.0";
}
auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine");
std::string error = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release });
printSavegameFormatError(e.what(), error);
}
catch (const std::exception& e)
{
std::string error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what());
printSavegameFormatError(e.what(), error);
}
}
void MWState::StateManager::printSavegameFormatError(
const std::string& exceptionText, const std::string& messageBoxText)
{
Log(Debug::Error) << "Failed to load saved game: " << exceptionText;
cleanup(true);
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:OK}");
MWBase::Environment::get().getWindowManager()->interactiveMessageBox(messageBoxText, buttons);
2013-11-21 12:24:24 +01:00
}
void MWState::StateManager::quickLoad()
{
if (Character* currentCharacter = getCurrentCharacter())
{
if (currentCharacter->begin() == currentCharacter->end())
return;
loadGame(currentCharacter, currentCharacter->begin()->mPath); // Get newest save
}
}
void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot)
{
const std::filesystem::path savePath = slot->mPath;
mCharacterManager.deleteSlot(character, slot);
if (mLastSavegame == savePath)
{
if (character->begin() != character->end())
mLastSavegame = character->begin()->mPath;
else
mLastSavegame.clear();
}
}
MWState::Character* MWState::StateManager::getCurrentCharacter()
{
return mCharacterManager.getCurrentCharacter();
}
MWState::StateManager::CharacterIterator MWState::StateManager::characterBegin()
{
return mCharacterManager.begin();
}
MWState::StateManager::CharacterIterator MWState::StateManager::characterEnd()
{
return mCharacterManager.end();
}
void MWState::StateManager::update(float duration)
{
mTimePlayed += duration;
// 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();
MWState::Character* curCharacter = getCurrentCharacter();
if (iButton == 0 && curCharacter)
{
mAskLoadRecent = false;
// Load last saved game for current character
// loadGame resets the game state along with mLastSavegame so we want to preserve it
const std::filesystem::path filePath = std::move(mLastSavegame);
loadGame(curCharacter, filePath);
}
else if (iButton == 1)
{
mAskLoadRecent = false;
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
}
}
if (mNewGameRequest)
{
newGame();
mNewGameRequest = false;
}
if (mLoadRequest)
{
loadGame(*mLoadRequest);
mLoadRequest = std::nullopt;
}
}
bool MWState::StateManager::confirmLoading(const std::vector<std::string_view>& missingFiles) const
{
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
{
for (size_t i = 0; i < cappedSize - 1; ++i)
{
message += missingFiles[i];
message += "\n";
}
message += "...";
}
message
+= l10n->formatMessage("MissingContentFilesListCopy", { "files" }, { static_cast<int>(missingFiles.size()) });
int selectedButton = -1;
while (true)
{
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;
}
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());
}