diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 8571e93ff..7020678d0 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -24,9 +24,9 @@ #include "../mwscript/globalscripts.hpp" -void MWState::StateManager::cleanup() +void MWState::StateManager::cleanup (bool force) { - if (mState!=State_NoGame) + if (mState!=State_NoGame || force) { MWBase::Environment::get().getSoundManager()->clear(); MWBase::Environment::get().getDialogueManager()->clear(); @@ -184,77 +184,86 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot void MWState::StateManager::loadGame (const Character *character, const Slot *slot) { - cleanup(); - - mTimePlayed = slot->mProfile.mTimePlayed; - - ESM::ESMReader reader; - reader.open (slot->mPath.string()); - - while (reader.hasMoreRecs()) + try { - ESM::NAME n = reader.getRecName(); - reader.getRecHeader(); + cleanup(); - switch (n.val) + mTimePlayed = slot->mProfile.mTimePlayed; + + ESM::ESMReader reader; + reader.open (slot->mPath.string()); + + while (reader.hasMoreRecs()) { - case ESM::REC_SAVE: + ESM::NAME n = reader.getRecName(); + reader.getRecHeader(); - // don't need to read that here - reader.skipRecord(); - break; + switch (n.val) + { + case ESM::REC_SAVE: - case ESM::REC_JOUR: - case ESM::REC_QUES: + // don't need to read that here + reader.skipRecord(); + break; - MWBase::Environment::get().getJournal()->readRecord (reader, n.val); - break; + case ESM::REC_JOUR: + case ESM::REC_QUES: - 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: + MWBase::Environment::get().getJournal()->readRecord (reader, n.val); + break; - MWBase::Environment::get().getWorld()->readRecord (reader, n.val); - break; + 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: - case ESM::REC_GSCR: + MWBase::Environment::get().getWorld()->readRecord (reader, n.val); + break; - MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); - break; + case ESM::REC_GSCR: - default: + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); + break; - // ignore invalid records - /// \todo log error - reader.skipRecord(); + default: + + // ignore invalid records + /// \todo log error + reader.skipRecord(); + } } + + mCharacterManager.setCurrentCharacter(character); + + mState = State_Running; + + Settings::Manager::setString ("character", "Saves", + slot->mPath.parent_path().filename().string()); + + MWBase::Environment::get().getWorld()->setupPlayer(); + MWBase::Environment::get().getWorld()->renderPlayer(); + MWBase::Environment::get().getWindowManager()->updatePlayer(); + MWBase::Environment::get().getMechanicsManager()->playerLoaded(); + + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); + + ESM::CellId cellId = ptr.getCell()->mCell->getCellId(); + + MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition()); + } + catch (const std::exception& e) + { + std::cerr << "failed to load saved game: " << e.what() << std::endl; + cleanup (true); } - - mCharacterManager.setCurrentCharacter(character); - - mState = State_Running; - - Settings::Manager::setString ("character", "Saves", - slot->mPath.parent_path().filename().string()); - - MWBase::Environment::get().getWorld()->setupPlayer(); - MWBase::Environment::get().getWorld()->renderPlayer(); - MWBase::Environment::get().getWindowManager()->updatePlayer(); - MWBase::Environment::get().getMechanicsManager()->playerLoaded(); - - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); - - ESM::CellId cellId = ptr.getCell()->mCell->getCellId(); - - MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition()); } MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index a2abcfd1b..d6bb7575d 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -19,7 +19,7 @@ namespace MWState private: - void cleanup(); + void cleanup (bool force = false); public: diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index c844b689e..ead12567f 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -1,5 +1,10 @@ #include "cells.hpp" +#include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -59,6 +64,30 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, CellStore& return ptr; } +void MWWorld::Cells::writeCell (ESM::ESMWriter& writer, const CellStore& cell) const +{ + ESM::CellState cellState; + + cell.saveState (cellState); + + writer.startRecord (ESM::REC_CSTA); + cellState.mId.save (writer); + cellState.save (writer); + /// \todo write references + writer.endRecord (ESM::REC_CSTA); +} + +bool MWWorld::Cells::hasState (const CellStore& cellStore) const +{ + if (cellStore.mState==CellStore::State_Loaded) + return true; + + if (cellStore.mCell->mData.mFlags & ESM::Cell::Interior) + return cellStore.mCell->mData.mFlags & ESM::Cell::HasWater; + else + return false; +} + MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector& reader) : mStore (store), mReader (reader), mIdCache (40, std::pair ("", (CellStore*)0)), /// \todo make cache size configurable @@ -121,6 +150,14 @@ MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name) return &result->second; } +MWWorld::CellStore *MWWorld::Cells::getCell (const ESM::CellId& id) +{ + if (id.mPaged) + return getExterior (id.mIndex.mX, id.mIndex.mY); + + return getInterior (id.mWorldspace); +} + MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, CellStore& cell, bool searchInContainers) { @@ -271,3 +308,62 @@ void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector::const_iterator iter (mInteriors.begin()); + iter!=mInteriors.end(); ++iter) + if (hasState (iter->second)) + ++count; + + for (std::map, CellStore>::const_iterator iter (mExteriors.begin()); + iter!=mExteriors.end(); ++iter) + if (hasState (iter->second)) + ++count; + + return count; +} + +void MWWorld::Cells::write (ESM::ESMWriter& writer) const +{ + for (std::map, CellStore>::const_iterator iter (mExteriors.begin()); + iter!=mExteriors.end(); ++iter) + if (hasState (iter->second)) + writeCell (writer, iter->second); + + for (std::map::const_iterator iter (mInteriors.begin()); + iter!=mInteriors.end(); ++iter) + if (hasState (iter->second)) + writeCell (writer, iter->second); +} + +bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type) +{ + if (type==ESM::REC_CSTA) + { + ESM::CellState state; + state.mId.load (reader); + + CellStore *cellStore = 0; + + try + { + cellStore = getCell (state.mId); + } + catch (...) + { + // silently drop cells that don't exist anymore + /// \todo log + } + + state.load (reader); + cellStore->loadState (state); + reader.skipRecord(); + + return true; + } + + return false; +} \ No newline at end of file diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index 31de2f60e..27e107646 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -10,6 +10,8 @@ namespace ESM { class ESMReader; + class ESMWriter; + struct CellId; } namespace MWWorld @@ -33,18 +35,23 @@ namespace MWWorld Ptr getPtrAndCache (const std::string& name, CellStore& cellStore); + void writeCell (ESM::ESMWriter& writer, const CellStore& cell) const; + + bool hasState (const CellStore& cellStore) const; + ///< Check if cell has state that needs to be included in a saved game file. + public: void clear(); Cells (const MWWorld::ESMStore& store, std::vector& reader); - ///< \todo pass the dynamic part of the ESMStore isntead (once it is written) of the whole - /// world CellStore *getExterior (int x, int y); CellStore *getInterior (const std::string& name); + CellStore *getCell (const ESM::CellId& id); + Ptr getPtr (const std::string& name, CellStore& cellStore, bool searchInContainers = false); ///< \param searchInContainers Only affect loaded cells. /// @note name must be lower case @@ -56,6 +63,12 @@ namespace MWWorld /// @note Due to the current implementation of getPtr this only supports one Ptr per cell. /// @note name must be lower case void getExteriorPtrs (const std::string& name, std::vector& out); + + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index cffa0537a..3cbf85e8e 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -2,6 +2,9 @@ #include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -230,4 +233,22 @@ namespace MWWorld << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; } } + + void CellStore::loadState (const ESM::CellState& state) + { + if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) + mWaterLevel = state.mWaterLevel; + + mWaterLevel = state.mWaterLevel; + } + + void CellStore::saveState (ESM::CellState& state) const + { + state.mId = mCell->getCellId(); + + if (mCell->mData.mFlags & ESM::Cell::Interior && mCell->mData.mFlags & ESM::Cell::HasWater) + state.mWaterLevel = mWaterLevel; + + state.mWaterLevel = mWaterLevel; + } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 1da219a9e..64843e7a4 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -7,6 +7,11 @@ #include "livecellref.hpp" #include "esmstore.hpp" +namespace ESM +{ + struct CellState; +} + namespace MWWorld { @@ -133,6 +138,10 @@ namespace MWWorld Ptr searchInContainer (const std::string& id); + void loadState (const ESM::CellState& state); + + void saveState (ESM::CellState& state) const; + private: template @@ -158,7 +167,6 @@ namespace MWWorld ///< Make case-adjustments to \a ref and insert it into the respective container. /// /// Invalid \a ref objects are silently dropped. - }; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 706701fa8..f8321d74e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -304,13 +304,16 @@ namespace MWWorld { return mStore.countSavedGameRecords() - +mGlobalVariables.countSavedGameRecords(); + +mGlobalVariables.countSavedGameRecords() + +1 // player record + +mCells.countSavedGameRecords(); } void World::write (ESM::ESMWriter& writer) const { mStore.write (writer); mGlobalVariables.write (writer); + mCells.write (writer); mPlayer->write (writer); } @@ -318,7 +321,8 @@ namespace MWWorld { if (!mStore.readRecord (reader, type) && !mGlobalVariables.readRecord (reader, type) && - !mPlayer->readRecord (reader, type)) + !mPlayer->readRecord (reader, type) && + !mCells.readRecord (reader, type)) { throw std::runtime_error ("unknown record in saved game"); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 4c0bff59d..d9ab8129d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -40,7 +40,7 @@ add_component_dir (esm loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc loadnpcc loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter - savedgame journalentry queststate locals globalscript player objectstate cellid + savedgame journalentry queststate locals globalscript player objectstate cellid cellstate ) add_component_dir (misc diff --git a/components/esm/cellstate.cpp b/components/esm/cellstate.cpp new file mode 100644 index 000000000..1f7e8197e --- /dev/null +++ b/components/esm/cellstate.cpp @@ -0,0 +1,17 @@ + +#include "cellstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::CellState::load (ESMReader &esm) +{ + mWaterLevel = 0; + esm.getHNOT (mWaterLevel, "WLVL"); +} + +void ESM::CellState::save (ESMWriter &esm) const +{ + if (!mId.mPaged) + esm.writeHNT ("WLVL", mWaterLevel); +} \ No newline at end of file diff --git a/components/esm/cellstate.hpp b/components/esm/cellstate.hpp new file mode 100644 index 000000000..cd0db3067 --- /dev/null +++ b/components/esm/cellstate.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_ESM_CELLSTATE_H +#define OPENMW_ESM_CELLSTATE_H + +#include "cellid.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + /// \note Does not include references + struct CellState + { + CellId mId; + + float mWaterLevel; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 2b956d216..1ca6e88fc 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -88,7 +88,8 @@ enum RecNameInts REC_JOUR = 0x524f55a4, REC_QUES = 0x53455551, REC_GSCR = 0x52435347, - REC_PLAY = 0x504c4159, + REC_PLAY = 0x59414c50, + REC_CSTA = 0x41545343, // format 1 REC_FILT = 0x544C4946