diff --git a/.gitignore b/.gitignore index c061ca637..3975c4521 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ resources ## generated objects apps/openmw/config.hpp +components/version/version.hpp Docs/mainpage.hpp moc_*.cxx *.cxx_parameters diff --git a/CMakeLists.txt b/CMakeLists.txt index 8eb8b44c2..01ac9b23f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,15 +14,30 @@ endif (APPLE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) -include (OpenMWMacros) +include(OpenMWMacros) # Version -set (OPENMW_VERSION_MAJOR 0) -set (OPENMW_VERSION_MINOR 27) -set (OPENMW_VERSION_RELEASE 0) +include(GetGitRevisionDescription) -set (OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") +get_git_tag_revision(TAGHASH --tags --max-count=1) +get_git_head_revision(REFSPEC COMMITHASH) +git_describe(VERSION --tags ${TAGHASH}) + +string(REGEX MATCH "^openmw-[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" MATCH "${VERSION}") +if (MATCH) + string(REGEX REPLACE "^openmw-([0-9]+)\\..*" "\\1" OPENMW_VERSION_MAJOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_MINOR "${VERSION}") + string(REGEX REPLACE "^openmw-[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" OPENMW_VERSION_RELEASE "${VERSION}") + + set(OPENMW_VERSION "${OPENMW_VERSION_MAJOR}.${OPENMW_VERSION_MINOR}.${OPENMW_VERSION_RELEASE}") + set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") + set(OPENMW_VERSION_TAGHASH "${TAGHASH}") + + message(STATUS "Configuring OpenMW ${OPENMW_VERSION}...") +else (MATCH) + message(FATAL_ERROR "Failed to get valid version information from Git") +endif (MATCH) # doxygen main page @@ -319,7 +334,7 @@ configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg configure_file(${OpenMW_SOURCE_DIR}/files/opencs.cfg "${OpenMW_BINARY_DIR}/opencs.cfg") - + configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters "${OpenMW_BINARY_DIR}/resources/defaultfilters" COPYONLY) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 27980096e..e9731d626 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -236,7 +236,9 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) // Loop through all the references ESM::CellRef ref; if(!quiet) std::cout << " References:\n"; - while(cell.getNextRef(esm, ref)) + + bool deleted = false; + while(cell.getNextRef(esm, ref, deleted)) { if (save) { info.data.mCellRefs[&cell].push_back(ref); @@ -244,13 +246,14 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) if(quiet) continue; - std::cout << " Refnum: " << ref.mRefnum << std::endl; + std::cout << " Refnum: " << ref.mRefNum.mIndex << std::endl; std::cout << " ID: '" << ref.mRefID << "'\n"; std::cout << " Owner: '" << ref.mOwner << "'\n"; std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n"; std::cout << " Uses/health: '" << ref.mCharge << "'\n"; std::cout << " Gold value: '" << ref.mGoldValue << "'\n"; std::cout << " Blocked: '" << static_cast(ref.mReferenceBlocked) << "'" << std::endl; + std::cout << " Deleted: " << deleted << std::endl; } } diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 9b3c4e1b0..56b3186ff 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,5 +1,10 @@ #include "maindialog.hpp" +#include + +#include +#include +#include #include #include #include @@ -67,6 +72,22 @@ Launcher::MainDialog::MainDialog(QWidget *parent) // Remove what's this? button setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); + // Add version information to bottom of the window + QString revision(OPENMW_VERSION_COMMITHASH); + QString tag(OPENMW_VERSION_TAGHASH); + + if (revision == tag) { + versionLabel->setText(tr("OpenMW %0 release").arg(OPENMW_VERSION)); + } else { + versionLabel->setText(tr("OpenMW development (%0)").arg(revision.left(10))); + } + + // Add the compile date and time + versionLabel->setToolTip(tr("Compiled on %0 %1").arg(QLocale(QLocale::C).toDate(QString(__DATE__).simplified(), + QLatin1String("MMM d yyyy")).toString(Qt::SystemLocaleLongDate), + QLocale(QLocale::C).toTime(QString(__TIME__).simplified(), + QLatin1String("hh:mm:ss")).toString(Qt::SystemLocaleShortDate))); + createIcons(); } diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 8e9bcfc0d..d7df2117d 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -133,16 +133,10 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vector (&topic.mModified.sRecordId)[i]; - - mState.getWriter().startRecord (type); + mState.getWriter().startRecord (topic.mModified.sRecordId); mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); topic.mModified.save (mState.getWriter()); - mState.getWriter().endRecord (type); + mState.getWriter().endRecord (topic.mModified.sRecordId); // write modified selected info records for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; @@ -178,15 +172,10 @@ void CSMDoc::WriteDialogueCollectionStage::perform (int stage, std::vectormModified.mId.substr (next->mModified.mId.find_last_of ('#')+1); } - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic (change ESMWriter interface?) - type += reinterpret_cast (&info.sRecordId)[i]; - - mState.getWriter().startRecord (type); + mState.getWriter().startRecord (info.sRecordId); mState.getWriter().writeHNCString ("INAM", info.mId); info.save (mState.getWriter()); - mState.getWriter().endRecord (type); + mState.getWriter().endRecord (info.sRecordId); } } } diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index ca5586511..b8eb0a3b3 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -104,10 +104,10 @@ namespace CSMDoc /// \todo make endianess agnostic (change ESMWriter interface?) type += reinterpret_cast (&mCollection.getRecord (stage).mModified.sRecordId)[i]; - mState.getWriter().startRecord (type); + mState.getWriter().startRecord (mCollection.getRecord (stage).mModified.sRecordId); mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); mCollection.getRecord (stage).mModified.save (mState.getWriter()); - mState.getWriter().endRecord (type); + mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId); } else if (state==CSMWorld::RecordBase::State_Deleted) { diff --git a/apps/opencs/model/world/ref.cpp b/apps/opencs/model/world/ref.cpp index 74f60419b..cf9e496ee 100644 --- a/apps/opencs/model/world/ref.cpp +++ b/apps/opencs/model/world/ref.cpp @@ -8,6 +8,5 @@ void CSMWorld::CellRef::load (ESM::ESMReader &esm, Cell& cell, const std::string mId = id; mCell = cell.mId; - if (!mDeleted) - cell.addRef (mId); + cell.addRef (mId); } \ No newline at end of file diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 6b0081258..9ee59bd1e 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -16,7 +16,8 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool CellRef ref; - while (cell2.getNextRef (reader, ref)) + bool deleted = false; + while (cell2.getNextRef (reader, ref, deleted)) { /// \todo handle deleted and moved references ref.load (reader, cell2, getNewId()); diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 9fb143e81..cf98fee21 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -147,15 +147,10 @@ namespace CSMWorld if (state==CSMWorld::RecordBase::State_Modified || state==CSMWorld::RecordBase::State_ModifiedOnly) { - std::string type; - for (int i=0; i<4; ++i) - /// \todo make endianess agnostic (change ESMWriter interface?) - type += reinterpret_cast (&mContainer.at (index).mModified.sRecordId)[i]; - - writer.startRecord (type); + writer.startRecord (mContainer.at (index).mModified.sRecordId); writer.writeHNCString ("NAME", getId (index)); mContainer.at (index).mModified.save (writer); - writer.endRecord (type); + writer.endRecord (mContainer.at (index).mModified.sRecordId); } else if (state==CSMWorld::RecordBase::State_Deleted) { diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 41cc320ad..84d116848 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -1,7 +1,3 @@ - -# config file -configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/config.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/config.hpp") - # local files set(GAME main.cpp @@ -12,7 +8,6 @@ if(NOT WIN32) endif() set(GAME_HEADER engine.hpp - config.hpp ) source_group(game FILES ${GAME} ${GAME_HEADER}) @@ -74,12 +69,16 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting - disease pickpocket levelledlist combat + disease pickpocket levelledlist combat steering + ) + +add_openmw_dir (mwstate + statemanagerimp charactermanager character ) add_openmw_dir (mwbase environment world scriptmanager dialoguemanager journal soundmanager mechanicsmanager - inputmanager windowmanager + inputmanager windowmanager statemanager ) # Main executable diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index f18c8a8b8..bf1cca5e0 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -7,6 +7,8 @@ #include +#include + #include #include @@ -41,8 +43,7 @@ #include "mwmechanics/mechanicsmanagerimp.hpp" - -#include +#include "mwstate/statemanagerimp.hpp" void OMW::Engine::executeLocalScripts() { @@ -88,31 +89,47 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) if (mUseSound) MWBase::Environment::get().getSoundManager()->update(frametime); - // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); + + // update game state + MWBase::Environment::get().getStateManager()->update (frametime); - bool changed = MWBase::Environment::get().getWorld()->hasCellChanged(); + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_Running) + { + // global scripts + MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + + bool changed = MWBase::Environment::get().getWorld()->hasCellChanged(); - // local scripts - executeLocalScripts(); // This does not handle the case where a global script causes a cell - // change, followed by a cell change in a local script during the same - // frame. + // local scripts + executeLocalScripts(); // This does not handle the case where a global script causes a + // cell change, followed by a cell change in a local script during + // the same frame. - // passing of time - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - MWBase::Environment::get().getWorld()->advanceTime( - frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); + if (changed) // keep change flag for another frame, if cell changed happened in local script + MWBase::Environment::get().getWorld()->markCellAsUnchanged(); + if (!paused) + MWBase::Environment::get().getWorld()->advanceTime( + frametime*MWBase::Environment::get().getWorld()->getTimeScaleFactor()/3600); + } - if (changed) // keep change flag for another frame, if cell changed happend in local script - MWBase::Environment::get().getWorld()->markCellAsUnchanged(); // update actors MWBase::Environment::get().getMechanicsManager()->update(frametime, - MWBase::Environment::get().getWindowManager()->isGuiMode()); + paused); + + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_Running) + { + MWWorld::Ptr player = mEnvironment.getWorld()->getPlayerPtr(); + if(!paused && player.getClass().getCreatureStats(player).isDead()) + MWBase::Environment::get().getStateManager()->endGame(); + } // update world - MWBase::Environment::get().getWorld()->update(frametime, MWBase::Environment::get().getWindowManager()->isGuiMode()); + MWBase::Environment::get().getWorld()->update(frametime, paused); // update GUI Ogre::RenderWindow* window = mOgre->getWindow(); @@ -135,7 +152,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mOgre (0) , mFpsLevel(0) , mVerboseScripts (false) - , mNewGame (false) + , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) , mScriptContext (0) @@ -265,12 +282,7 @@ void OMW::Engine::setCell (const std::string& cellName) void OMW::Engine::addContentFile(const std::string& file) { - if (file.find_last_of(".") == std::string::npos) - { - throw std::runtime_error("Missing extension in content file!"); - } - - mContentFiles.push_back(file); + mContentFiles.push_back(file); } void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity) @@ -278,9 +290,9 @@ void OMW::Engine::setScriptsVerbosity(bool scriptsVerbosity) mVerboseScripts = scriptsVerbosity; } -void OMW::Engine::setNewGame(bool newGame) +void OMW::Engine::setSkipMenu (bool skipMenu) { - mNewGame = newGame; + mSkipMenu = skipMenu; } std::string OMW::Engine::loadSettings (Settings::Manager & settings) @@ -326,6 +338,9 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) void OMW::Engine::prepareEngine (Settings::Manager & settings) { + mEnvironment.setStateManager ( + new MWState::StateManager (mCfgMgr.getUserDataPath() / "saves", mContentFiles.at (0))); + Nif::NIFFile::CacheLock cachelock; std::string renderSystem = settings.getString("render system", "Video"); @@ -392,10 +407,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) input->setPlayer(&mEnvironment.getWorld()->getPlayer()); window->initUI(); - if (mNewGame) - // still redundant work here: recreate CharacterCreation(), - // double update visibility etc. - window->setNewGame(true); window->renderWorldMap(); //Load translation data @@ -403,7 +414,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) for (size_t i = 0; i < mContentFiles.size(); i++) mTranslationDataStorage.loadTranslationData(mFileCollections, mContentFiles[i]); - Compiler::registerExtensions (mExtensions); + Compiler::registerExtensions (mExtensions); // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); @@ -427,12 +438,12 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mechanics->buildPlayer(); window->updatePlayer(); - if (!mNewGame) - { - // load cell - ESM::Position pos; - MWBase::World *world = MWBase::Environment::get().getWorld(); + // load cell + ESM::Position pos; + MWBase::World *world = MWBase::Environment::get().getWorld(); + if (!mCellName.empty()) + { if (world->findExteriorPosition(mCellName, pos)) { world->changeToExteriorCell (pos); } @@ -442,7 +453,11 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) } } else - mEnvironment.getWorld()->startNewGame(); + { + pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; + pos.rot[0] = pos.rot[1] = pos.pos[2] = 0; + world->changeToExteriorCell (pos); + } Ogre::FrameEvent event; event.timeSinceLastEvent = 0; @@ -468,7 +483,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) void OMW::Engine::go() { - assert (!mCellName.empty()); assert (!mContentFiles.empty()); assert (!mOgre); @@ -489,8 +503,14 @@ void OMW::Engine::go() if (!mStartupScript.empty()) MWBase::Environment::get().getWindowManager()->executeInConsole (mStartupScript); + // start in main menu + if (!mSkipMenu) + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + else + MWBase::Environment::get().getStateManager()->newGame (true); + // Start the main rendering loop - while (!mEnvironment.getRequestExit()) + while (!mEnvironment.get().getStateManager()->hasQuitRequest()) Ogre::Root::getSingleton().renderOneFrame(); // Save user settings diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 9292e81bb..8b2a65b7e 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -71,7 +71,7 @@ namespace OMW std::vector mContentFiles; int mFpsLevel; bool mVerboseScripts; - bool mNewGame; + bool mSkipMenu; bool mUseSound; bool mCompileAll; std::string mFocusName; @@ -151,8 +151,7 @@ namespace OMW /// Disable or enable all sounds void setSoundUsage(bool soundUsage); - /// Start as a new game. - void setNewGame(bool newGame); + void setSkipMenu (bool skipMenu); void setGrabMouse(bool grab) { mGrab = grab; } diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 3129e6bd3..67578a214 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -30,8 +31,6 @@ extern int is_debugger_attached(void); #include #endif -#include "config.hpp" - #include /** * Workaround for problems with whitespaces in paths in older versions of Boost library @@ -116,7 +115,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("resources", bpo::value()->default_value("resources"), "set resources directory") - ("start", bpo::value()->default_value("Beshara"), + ("start", bpo::value()->default_value(""), "set initial cell") ("content", bpo::value()->default_value(StringsVector(), "") @@ -137,8 +136,8 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-run", bpo::value()->default_value(""), "select a file containing a list of console commands that is executed on startup") - ("new-game", bpo::value()->implicit_value(true) - ->default_value(false), "activate char gen/new game mechanics") + ("skip-menu", bpo::value()->implicit_value(true) + ->default_value(false), "skip main menu on game startup") ("fs-strict", bpo::value()->implicit_value(true) ->default_value(false), "strict file system handling (no case folding)") @@ -232,7 +231,7 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // startup-settings engine.setCell(variables["start"].as()); - engine.setNewGame(variables["new-game"].as()); + engine.setSkipMenu (variables["skip-menu"].as()); // other settings engine.setSoundUsage(!variables["no-sound"].as()); diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 4db0b45b9..3bc15746e 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -11,13 +11,14 @@ #include "mechanicsmanager.hpp" #include "inputmanager.hpp" #include "windowmanager.hpp" +#include "statemanager.hpp" MWBase::Environment *MWBase::Environment::sThis = 0; -bool MWBase::Environment::sExit = false; MWBase::Environment::Environment() : mWorld (0), mSoundManager (0), mScriptManager (0), mWindowManager (0), - mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mFrameDuration (0) + mMechanicsManager (0), mDialogueManager (0), mJournal (0), mInputManager (0), mFrameDuration (0), + mStateManager (0) { assert (!sThis); sThis = this; @@ -69,6 +70,11 @@ void MWBase::Environment::setInputManager (InputManager *inputManager) mInputManager = inputManager; } +void MWBase::Environment::setStateManager (StateManager *stateManager) +{ + mStateManager = stateManager; +} + void MWBase::Environment::setFrameDuration (float duration) { mFrameDuration = duration; @@ -122,6 +128,12 @@ MWBase::InputManager *MWBase::Environment::getInputManager() const return mInputManager; } +MWBase::StateManager *MWBase::Environment::getStateManager() const +{ + assert (mStateManager); + return mStateManager; +} + float MWBase::Environment::getFrameDuration() const { return mFrameDuration; @@ -152,6 +164,9 @@ void MWBase::Environment::cleanup() delete mInputManager; mInputManager = 0; + + delete mStateManager; + mStateManager = 0; } const MWBase::Environment& MWBase::Environment::get() diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 466302907..eb636ea2f 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -11,6 +11,7 @@ namespace MWBase class MechanicsManager; class InputManager; class WindowManager; + class StateManager; /// \brief Central hub for mw-subsystems /// @@ -30,10 +31,9 @@ namespace MWBase DialogueManager *mDialogueManager; Journal *mJournal; InputManager *mInputManager; + StateManager *mStateManager; float mFrameDuration; - static bool sExit; - Environment (const Environment&); ///< not implemented @@ -46,9 +46,6 @@ namespace MWBase ~Environment(); - static void setRequestExit () { sExit = true; } - static bool getRequestExit () { return sExit; } - void setWorld (World *world); void setSoundManager (SoundManager *soundManager); @@ -65,6 +62,8 @@ namespace MWBase void setInputManager (InputManager *inputManager); + void setStateManager (StateManager *stateManager); + void setFrameDuration (float duration); ///< Set length of current frame in seconds. @@ -84,6 +83,8 @@ namespace MWBase InputManager *getInputManager() const; + StateManager *getStateManager() const; + float getFrameDuration() const; void cleanup(); diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 51e51edda..56d9601fc 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -5,10 +5,18 @@ #include #include +#include + #include "../mwdialogue/journalentry.hpp" #include "../mwdialogue/topic.hpp" #include "../mwdialogue/quest.hpp" +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + namespace MWBase { /// \brief Interface for the player's journal (implemented in MWDialogue) @@ -46,7 +54,7 @@ namespace MWBase virtual int getJournalIndex (const std::string& id) const = 0; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId) = 0; + virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) = 0; virtual TEntryIter begin() const = 0; ///< Iterator pointing to the begin of the main journal. @@ -69,6 +77,12 @@ namespace MWBase virtual TTopicIter topicEnd() const = 0; ///< Iterator pointing past the last topic. + + virtual int countSavedGameRecords() const = 0; + + virtual void write (ESM::ESMWriter& writer) const = 0; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; }; } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 9c298b9c1..22dda0ce0 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -136,33 +136,35 @@ namespace MWBase float currentTemporaryDispositionDelta, bool& success, float& tempChange, float& permChange) = 0; ///< Perform a persuasion action on NPC - virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; - ///< Forces an object to refresh its animation state. + virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; + ///< Forces an object to refresh its animation state. - virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1) = 0; - ///< Run animation for a MW-reference. Calls to this function for references that are currently not - /// in the scene should be ignored. - /// - /// \param mode 0 normal, 1 immediate start, 2 immediate loop - /// \param count How many times the animation should be run + virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1) = 0; + ///< Run animation for a MW-reference. Calls to this function for references that are currently not + /// in the scene should be ignored. + /// + /// \param mode 0 normal, 1 immediate start, 2 immediate loop + /// \param count How many times the animation should be run - virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; - ///< Skip the animation for the given MW-reference for one frame. Calls to this function for - /// references that are currently not in the scene should be ignored. + virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; + ///< Skip the animation for the given MW-reference for one frame. Calls to this function for + /// references that are currently not in the scene should be ignored. - virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; - /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently - /// paused we may want to do it manually (after equipping permanent enchantment) - virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently + /// paused we may want to do it manually (after equipping permanent enchantment) + virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; - virtual void toggleAI() = 0; - virtual bool isAIActive() = 0; + virtual void toggleAI() = 0; + virtual bool isAIActive() = 0; virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects) = 0; ///return the list of actors which are following the given actor (ie AiFollow is active and the target is the actor) virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; + + virtual void playerLoaded() = 0; }; } diff --git a/apps/openmw/mwbase/scriptmanager.hpp b/apps/openmw/mwbase/scriptmanager.hpp index 32df2bfa3..ae146e064 100644 --- a/apps/openmw/mwbase/scriptmanager.hpp +++ b/apps/openmw/mwbase/scriptmanager.hpp @@ -35,8 +35,6 @@ namespace MWBase virtual ~ScriptManager() {} - virtual void resetGlobalScripts() = 0; - virtual void run (const std::string& name, Interpreter::Context& interpreterContext) = 0; ///< Run the script with the given name (compile first, if not compiled yet) diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index 98b733c41..f3973153a 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -149,6 +149,8 @@ namespace MWBase virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up) = 0; virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated) = 0; + + virtual void clear() = 0; }; } diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp new file mode 100644 index 000000000..cd907408a --- /dev/null +++ b/apps/openmw/mwbase/statemanager.hpp @@ -0,0 +1,81 @@ +#ifndef GAME_MWSTATE_STATEMANAGER_H +#define GAME_MWSTATE_STATEMANAGER_H + +#include +#include + +namespace MWState +{ + struct Slot; + class Character; +} + +namespace MWBase +{ + /// \brief Interface for game state manager (implemented in MWState) + class StateManager + { + public: + + enum State + { + State_NoGame, + State_Ended, + State_Running + }; + + typedef std::vector::const_iterator CharacterIterator; + + private: + + StateManager (const StateManager&); + ///< not implemented + + StateManager& operator= (const StateManager&); + ///< not implemented + + public: + + StateManager() {} + + virtual ~StateManager() {} + + virtual void requestQuit() = 0; + + virtual bool hasQuitRequest() const = 0; + + virtual void askLoadRecent() = 0; + + virtual State getState() const = 0; + + virtual void newGame (bool bypass = false) = 0; + ///< Start a new game. + /// + /// \param bypass Skip new game mechanics. + + virtual void endGame() = 0; + + virtual void saveGame (const std::string& description, const MWState::Slot *slot = 0) = 0; + ///< Write a saved game to \a slot or create a new slot if \a slot == 0. + /// + /// \note Slot must belong to the current character. + + virtual void loadGame (const MWState::Character *character, const MWState::Slot *slot) = 0; + ///< Load a saved game file from \a slot. + /// + /// \note \a slot must belong to \a character. + + virtual MWState::Character *getCurrentCharacter (bool create = true) = 0; + ///< \param create Create a new character, if there is no current character. + + virtual CharacterIterator characterBegin() = 0; + ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned + /// iterator. + + virtual CharacterIterator characterEnd() = 0; + + virtual void update (float duration) = 0; + }; +} + +#endif diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index c39de4400..069e6311a 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -33,6 +33,8 @@ namespace OEngine namespace ESM { struct Class; + class ESMReader; + class ESMWriter; } namespace MWWorld @@ -279,12 +281,19 @@ namespace MWBase virtual const Translation::Storage& getTranslationDataStorage() const = 0; + /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. virtual void setKeyFocusWidget (MyGUI::Widget* widget) = 0; virtual Loading::Listener* getLoadingScreen() = 0; /// Should the cursor be visible? virtual bool getCursorVisible() = 0; + + /// Clear all savegame-specific data + virtual void clear() = 0; + + virtual void write (ESM::ESMWriter& writer) = 0; + virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 92a1a4143..3d033838c 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -2,6 +2,7 @@ #define GAME_MWBASE_WORLD_H #include +#include #include @@ -30,12 +31,14 @@ namespace OEngine namespace ESM { class ESMReader; + class ESMWriter; struct Position; struct Cell; struct Class; struct Potion; struct Spell; struct NPC; + struct CellId; } namespace MWRender @@ -94,13 +97,26 @@ namespace MWBase virtual void startNewGame() = 0; + virtual void clear() = 0; + + virtual int countSavedGameRecords() const = 0; + + virtual void write (ESM::ESMWriter& writer) const = 0; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap) = 0; + virtual OEngine::Render::Fader* getFader() = 0; - ///< \ŧodo remove this function. Rendering details should not be exposed. + ///< \todo remove this function. Rendering details should not be exposed. virtual MWWorld::CellStore *getExterior (int x, int y) = 0; virtual MWWorld::CellStore *getInterior (const std::string& name) = 0; + virtual MWWorld::CellStore *getCell (const ESM::CellId& id) = 0; + + virtual void useDeathCamera() = 0; + virtual void setWaterHeight(const float height) = 0; virtual void toggleWater() = 0; @@ -139,16 +155,26 @@ namespace MWBase virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior) = 0; ///< see MWRender::LocalMap::isPositionExplored - virtual MWWorld::Globals::Data& getGlobalVariable (const std::string& name) = 0; + virtual void setGlobalInt (const std::string& name, int value) = 0; + ///< Set value independently from real type. - virtual MWWorld::Globals::Data getGlobalVariable (const std::string& name) const = 0; + virtual void setGlobalFloat (const std::string& name, float value) = 0; + ///< Set value independently from real type. + + virtual int getGlobalInt (const std::string& name) const = 0; + ///< Get value independently from real type. + + virtual float getGlobalFloat (const std::string& name) const = 0; + ///< Get value independently from real type. virtual char getGlobalVariableType (const std::string& name) const = 0; ///< Return ' ', if there is no global variable with this name. - virtual std::vector getGlobals () const = 0; - - virtual std::string getCurrentCellName() const = 0; + virtual std::string getCellName (const MWWorld::CellStore *cell = 0) const = 0; + ///< Return name of the cell. + /// + /// \note If cell==0, the cell the player is currently in will be used instead to + /// generate a name. virtual void removeRefScript (MWWorld::RefData *ref) = 0; //< Remove the script attached to ref from mLocalScripts @@ -185,8 +211,12 @@ namespace MWBase virtual void setDay (int day) = 0; ///< Set in-game time day. - virtual int getDay() = 0; - virtual int getMonth() = 0; + virtual int getDay() const = 0; + virtual int getMonth() const = 0; + virtual int getYear() const = 0; + + virtual std::string getMonthName (int month = -1) const = 0; + ///< Return name of month (-1: current month) virtual MWWorld::TimeStamp getTimeStamp() const = 0; ///< Return current in-game time stamp. @@ -215,6 +245,8 @@ namespace MWBase virtual void changeToExteriorCell (const ESM::Position& position) = 0; ///< Move to exterior cell. + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position) = 0; + virtual const ESM::Cell *getExterior (const std::string& cellName) const = 0; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -336,7 +368,7 @@ namespace MWBase virtual bool isSwimming(const MWWorld::Ptr &object) const = 0; ///Is the head of the creature underwater? virtual bool isSubmerged(const MWWorld::Ptr &object) const = 0; - virtual bool isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const = 0; + virtual bool isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const = 0; virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; virtual void togglePOV() = 0; @@ -384,6 +416,7 @@ namespace MWBase virtual void playVideo(const std::string& name, bool allowSkipping) = 0; virtual void stopVideo() = 0; virtual void frameStarted (float dt, bool paused) = 0; + virtual void screenshot (Ogre::Image& image, int w, int h) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise @@ -428,6 +461,8 @@ namespace MWBase virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName) = 0; + virtual const std::vector& getContentFiles() const = 0; + virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; // Are we in an exterior or pseudo-exterior cell and it's night? diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 83bda25d1..e3974f243 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -366,12 +366,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mArmors.insert(*ref), &cell); } - float Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Armor::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mEnchant/10.f; + return ref->mBase->mData.mEnchant; } bool Armor::canSell (const MWWorld::Ptr& item, int npcServices) const diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index d8d09d5bb..17cfca453 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -79,7 +79,7 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; }; diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 429d91259..0e6506514 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -189,12 +189,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mBooks.insert(*ref), &cell); } - float Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Book::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mEnchant/10.f; + return ref->mBase->mData.mEnchant; } bool Book::canSell (const MWWorld::Ptr& item, int npcServices) const diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 7fb8a9507..79b823fa9 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -58,7 +58,7 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; virtual float getWeight (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index a135585eb..ab98d05ae 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -279,12 +279,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mClothes.insert(*ref), &cell); } - float Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Clothing::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mEnchant/10.f; + return ref->mBase->mData.mEnchant; } bool Clothing::canSell (const MWWorld::Ptr& item, int npcServices) const diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index e2e1188a1..a73b2c190 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -71,7 +71,7 @@ namespace MWClass virtual std::string getModel(const MWWorld::Ptr &ptr) const; - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; virtual float getWeight (const MWWorld::Ptr& ptr) const; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index f89a6bce0..546d6538c 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -2,6 +2,7 @@ #include "container.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -258,4 +259,26 @@ namespace MWClass return MWWorld::Ptr(&cell.mContainers.insert(*ref), &cell); } + + void Container::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::ContainerState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. + readState (state2.mInventory); + } + + void Container::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::ContainerState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore. + writeState (state2.mInventory); + } } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 006e4bd22..c97867d35 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -54,6 +54,14 @@ namespace MWClass virtual void unlock (const MWWorld::Ptr& ptr) const; ///< Unlock object + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. + static void registerSelf(); virtual std::string getModel(const MWWorld::Ptr &ptr) const; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 812cd16f7..6af8373c5 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -2,6 +2,7 @@ #include "creature.hpp" #include +#include #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/magiceffects.hpp" @@ -332,7 +333,7 @@ namespace MWClass if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); - victim.getClass().onHit(victim, damage, true, MWWorld::Ptr(), ptr, true); + victim.getClass().onHit(victim, damage, true, weapon, ptr, true); } void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, bool successful) const @@ -528,8 +529,8 @@ namespace MWClass float moveSpeed; if(normalizedEncumbrance >= 1.0f) moveSpeed = 0.0f; - else if(mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && - world->isLevitationEnabled()) + else if(isFlying(ptr) || (mageffects.get(ESM::MagicEffect::Levitate).mMagnitude > 0 && + world->isLevitationEnabled())) { float flySpeed = 0.01f*(stats.getAttribute(ESM::Attribute::Speed).getModified() + mageffects.get(ESM::MagicEffect::Levitate).mMagnitude); @@ -758,6 +759,28 @@ namespace MWClass return 0; } + void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::CreatureState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore-> + readState (state2.mInventory); + } + + void Creature::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::CreatureState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mContainerStore-> + writeState (state2.mInventory); + } + const ESM::GameSetting* Creature::fMinWalkSpeedCreature; const ESM::GameSetting* Creature::fMaxWalkSpeedCreature; const ESM::GameSetting *Creature::fEncumberedMoveEffect; diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index ca8abc040..adaf62a96 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -91,7 +91,7 @@ namespace MWClass virtual bool isEssential (const MWWorld::Ptr& ptr) const; ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) - + virtual int getServices (const MWWorld::Ptr& actor) const; virtual bool isPersistent (const MWWorld::Ptr& ptr) const; @@ -125,6 +125,14 @@ namespace MWClass /// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini) virtual int getBloodTexture (const MWWorld::Ptr& ptr) const; + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index cc56ec4c8..ddb2c16d6 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -2,6 +2,7 @@ #include "light.hpp" #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -269,4 +270,24 @@ namespace MWClass } return std::make_pair(1,""); } + + void Light::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::LightState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mTime = state2.mTime; + } + + void Light::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::LightState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + state2.mTime = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + } } diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index c15228a6a..5568e1727 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -71,6 +71,14 @@ namespace MWClass virtual bool canSell (const MWWorld::Ptr& item, int npcServices) const; std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index a8a6c55ec..e58716f1c 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -155,11 +155,8 @@ namespace MWClass int count = ptr.getRefData().getCount(); bool gold = isGold(ptr); - - if (gold && ptr.getCellRef().mGoldValue != 1) - count = ptr.getCellRef().mGoldValue; - else if (gold) - count *= ref->mBase->mData.mValue; + if (gold) + count *= getValue(ptr); std::string countString; if (!gold) @@ -204,7 +201,7 @@ namespace MWClass MWBase::Environment::get().getWorld()->getStore(); if (isGold(ptr)) { - int goldAmount = ptr.getRefData().getCount(); + int goldAmount = getValue(ptr) * ptr.getRefData().getCount(); std::string base = "Gold_001"; if (goldAmount >= 100) @@ -223,6 +220,7 @@ namespace MWClass newRef.getPtr().get(); newPtr = MWWorld::Ptr(&cell.mMiscItems.insert(*ref), &cell); newPtr.getCellRef().mGoldValue = goldAmount; + newPtr.getRefData().setCount(1); } else { MWWorld::LiveCellRef *ref = ptr.get(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4ee02d73e..34ea515ba 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -536,7 +537,7 @@ namespace MWClass weapon.getCellRef().mCharge = weapmaxhealth; damage *= float(weapon.getCellRef().mCharge) / weapmaxhealth; } - + if (!MWBase::Environment::get().getWorld()->getGodModeState()) weapon.getCellRef().mCharge -= std::min(std::max(1, (int)(damage * gmst.find("fWeaponDamageMult")->getFloat())), weapon.getCellRef().mCharge); @@ -603,10 +604,7 @@ namespace MWClass { MWMechanics::CastSpell cast(ptr, victim); cast.mHitPosition = hitPosition; - bool success = cast.cast(weapon); - - if (ptr.getRefData().getHandle() == "player" && success) - skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3); + cast.cast(weapon); } } @@ -726,6 +724,9 @@ namespace MWClass if (armorref.mCharge == 0) inv.unequipItem(armor, ptr); + if (ptr.getRefData().getHandle() == "player") + skillUsageSucceeded(ptr, get(armor).getEquipmentSkill(armor), 0); + switch(get(armor).getEquipmentSkill(armor)) { case ESM::Skill::LightArmor: @@ -739,6 +740,8 @@ namespace MWClass break; } } + else if(ptr.getRefData().getHandle() == "player") + skillUsageSucceeded(ptr, ESM::Skill::Unarmored, 0); } } @@ -999,7 +1002,7 @@ namespace MWClass return ref->mBase->mFlags & ESM::NPC::Essential; } - + void Npc::registerSelf() { boost::shared_ptr instance (new Npc); @@ -1268,6 +1271,28 @@ namespace MWClass return 0; } + void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const + { + const ESM::NpcState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore. + readState (state2.mInventory); + } + + void Npc::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const + { + ESM::NpcState& state2 = dynamic_cast (state); + + ensureCustomData (ptr); + + dynamic_cast (*ptr.getRefData().getCustomData()).mInventoryStore. + writeState (state2.mInventory); + } + const ESM::GameSetting *Npc::fMinWalkSpeed; const ESM::GameSetting *Npc::fMaxWalkSpeed; const ESM::GameSetting *Npc::fEncumberedMoveEffect; diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index 2bd91d198..c54dd339a 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -137,7 +137,7 @@ namespace MWClass ///< Is \a ptr essential? (i.e. may losing \a ptr make the game unwinnable) virtual int getServices (const MWWorld::Ptr& actor) const; - + virtual bool isPersistent (const MWWorld::Ptr& ptr) const; virtual std::string getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const; @@ -158,6 +158,14 @@ namespace MWClass virtual bool isNpc() const { return true; } + + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. }; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 82935673c..af0234cd5 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -429,12 +429,12 @@ namespace MWClass return MWWorld::Ptr(&cell.mWeapons.insert(*ref), &cell); } - float Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Weapon::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { MWWorld::LiveCellRef *ref = ptr.get(); - return ref->mBase->mData.mEnchant/10.f; + return ref->mBase->mData.mEnchant; } bool Weapon::canSell (const MWWorld::Ptr& item, int npcServices) const diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 181c637db..db44cd2b7 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -84,7 +84,7 @@ namespace MWClass virtual float getWeight (const MWWorld::Ptr& ptr) const; - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index 7fbebb9d7..845c3c07b 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -286,7 +286,7 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); win->addResponse (Interpreter::fixDefinesDialog(info->mResponse, interpreterContext), title); - MWBase::Environment::get().getJournal()->addTopic (topic, info->mId); + MWBase::Environment::get().getJournal()->addTopic (topic, info->mId, mActor.getClass().getName(mActor)); executeScript (info->mResultScript); @@ -451,7 +451,7 @@ namespace MWDialogue MWScript::InterpreterContext interpreterContext(&mActor.getRefData().getLocals(),mActor); MWBase::Environment::get().getWindowManager()->getDialogueWindow()->addResponse (Interpreter::fixDefinesDialog(text, interpreterContext)); - MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId); + MWBase::Environment::get().getJournal()->addTopic (mLastTopic, info->mId, mActor.getClass().getName(mActor)); executeScript (info->mResultScript); } } diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 731fafbf6..3d45b85ce 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -171,7 +171,7 @@ bool MWDialogue::Filter::testSelectStructNumeric (const SelectWrapper& select) c // internally all globals are float :( return select.selectCompare ( - MWBase::Environment::get().getWorld()->getGlobalVariable (select.getName()).mFloat); + MWBase::Environment::get().getWorld()->getGlobalFloat (select.getName())); case SelectWrapper::Function_Local: { diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index 5ffde5499..9463e4c45 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -3,6 +3,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -10,23 +12,55 @@ namespace MWDialogue { - JournalEntry::JournalEntry() {} - - JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId) - : mTopic (topic), mInfoId (infoId) - {} + Entry::Entry() {} - std::string JournalEntry::getText (const MWWorld::ESMStore& store) const + Entry::Entry (const std::string& topic, const std::string& infoId) + : mInfoId (infoId) { const ESM::Dialogue *dialogue = - store.get().find (mTopic); + MWBase::Environment::get().getWorld()->getStore().get().find (topic); for (std::vector::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == mInfoId) - return iter->mResponse; + { + /// \todo text replacement + mText = iter->mResponse; + return; + } + + throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + topic); + } + + Entry::Entry (const ESM::JournalEntry& record) : mInfoId (record.mInfo), mText (record.mText), mActorName(record.mActorName) {} + + std::string Entry::getText() const + { + return mText; + } + + void Entry::write (ESM::JournalEntry& entry) const + { + entry.mInfo = mInfoId; + entry.mText = mText; + entry.mActorName = mActorName; + } + + + JournalEntry::JournalEntry() {} + + JournalEntry::JournalEntry (const std::string& topic, const std::string& infoId) + : Entry (topic, infoId), mTopic (topic) + {} + + JournalEntry::JournalEntry (const ESM::JournalEntry& record) + : Entry (record), mTopic (record.mTopic) + {} - throw std::runtime_error ("unknown info ID " + mInfoId + " for topic " + mTopic); + void JournalEntry::write (ESM::JournalEntry& entry) const + { + Entry::write (entry); + entry.mTopic = mTopic; } JournalEntry JournalEntry::makeFromQuest (const std::string& topic, int index) @@ -49,6 +83,7 @@ namespace MWDialogue throw std::runtime_error ("unknown journal index for topic " + topic); } + StampedJournalEntry::StampedJournalEntry() : mDay (0), mMonth (0), mDayOfMonth (0) {} @@ -58,11 +93,24 @@ namespace MWDialogue : JournalEntry (topic, infoId), mDay (day), mMonth (month), mDayOfMonth (dayOfMonth) {} + StampedJournalEntry::StampedJournalEntry (const ESM::JournalEntry& record) + : JournalEntry (record), mDay (record.mDay), mMonth (record.mMonth), + mDayOfMonth (record.mDayOfMonth) + {} + + void StampedJournalEntry::write (ESM::JournalEntry& entry) const + { + JournalEntry::write (entry); + entry.mDay = mDay; + entry.mMonth = mMonth; + entry.mDayOfMonth = mDayOfMonth; + } + StampedJournalEntry StampedJournalEntry::makeFromQuest (const std::string& topic, int index) { - int day = MWBase::Environment::get().getWorld()->getGlobalVariable ("dayspassed").mLong; - int month = MWBase::Environment::get().getWorld()->getGlobalVariable ("month").mLong; - int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalVariable ("day").mLong; + int day = MWBase::Environment::get().getWorld()->getGlobalInt ("dayspassed"); + int month = MWBase::Environment::get().getWorld()->getGlobalInt ("month"); + int dayOfMonth = MWBase::Environment::get().getWorld()->getGlobalInt ("day"); return StampedJournalEntry (topic, idFromIndex (topic, index), day, month, dayOfMonth); } diff --git a/apps/openmw/mwdialogue/journalentry.hpp b/apps/openmw/mwdialogue/journalentry.hpp index 9d009b48b..a77ba4f7c 100644 --- a/apps/openmw/mwdialogue/journalentry.hpp +++ b/apps/openmw/mwdialogue/journalentry.hpp @@ -3,24 +3,45 @@ #include -namespace MWWorld +namespace ESM { - struct ESMStore; + struct JournalEntry; } namespace MWDialogue { - /// \brief A quest or dialogue entry - struct JournalEntry + /// \brief Basic quest/dialogue/topic entry + struct Entry { - std::string mTopic; std::string mInfoId; + std::string mText; + std::string mActorName; // optional + + Entry(); + + Entry (const std::string& topic, const std::string& infoId); + + Entry (const ESM::JournalEntry& record); + + std::string getText() const; + + void write (ESM::JournalEntry& entry) const; + }; + + /// \brief A dialogue entry + /// + /// Same as entry, but store TopicID + struct JournalEntry : public Entry + { + std::string mTopic; JournalEntry(); JournalEntry (const std::string& topic, const std::string& infoId); - std::string getText (const MWWorld::ESMStore& store) const; + JournalEntry (const ESM::JournalEntry& record); + + void write (ESM::JournalEntry& entry) const; static JournalEntry makeFromQuest (const std::string& topic, int index); @@ -39,6 +60,10 @@ namespace MWDialogue StampedJournalEntry (const std::string& topic, const std::string& infoId, int day, int month, int dayOfMonth); + StampedJournalEntry (const ESM::JournalEntry& record); + + void write (ESM::JournalEntry& entry) const; + static StampedJournalEntry makeFromQuest (const std::string& topic, int index); }; } diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 23cfb5fdd..26383b3a7 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -1,6 +1,13 @@ #include "journalimp.hpp" +#include + +#include +#include +#include +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -26,6 +33,38 @@ namespace MWDialogue return iter->second; } + Topic& Journal::getTopic (const std::string& id) + { + TTopicContainer::iterator iter = mTopics.find (id); + + if (iter==mTopics.end()) + { + std::pair result + = mTopics.insert (std::make_pair (id, Topic (id))); + + iter = result.first; + } + + return iter->second; + } + + bool Journal::isThere (const std::string& topicId, const std::string& infoId) const + { + if (const ESM::Dialogue *dialogue = + MWBase::Environment::get().getWorld()->getStore().get().search (topicId)) + { + if (infoId.empty()) + return true; + + for (std::vector::const_iterator iter (dialogue->mInfo.begin()); + iter!=dialogue->mInfo.end(); ++iter) + if (iter->mId == infoId) + return true; + } + + return false; + } + Journal::Journal() {} @@ -64,19 +103,13 @@ namespace MWDialogue quest.setIndex (index); } - void Journal::addTopic (const std::string& topicId, const std::string& infoId) + void Journal::addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName) { - TTopicContainer::iterator iter = mTopics.find (topicId); - - if (iter==mTopics.end()) - { - std::pair result - = mTopics.insert (std::make_pair (topicId, Topic (topicId))); - - iter = result.first; - } + Topic& topic = getTopic (topicId); - iter->second.addEntry (JournalEntry (topicId, infoId)); + JournalEntry entry(topicId, infoId); + entry.mActorName = actorName; + topic.addEntry (entry); } int Journal::getJournalIndex (const std::string& id) const @@ -118,4 +151,106 @@ namespace MWDialogue { return mTopics.end(); } + + int Journal::countSavedGameRecords() const + { + int count = static_cast (mQuests.size()); + + for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) + count += std::distance (iter->second.begin(), iter->second.end()); + + count += std::distance (mJournal.begin(), mJournal.end()); + + for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) + count += std::distance (iter->second.begin(), iter->second.end()); + + return count; + } + + void Journal::write (ESM::ESMWriter& writer) const + { + for (TQuestIter iter (mQuests.begin()); iter!=mQuests.end(); ++iter) + { + const Quest& quest = iter->second; + + ESM::QuestState state; + quest.write (state); + writer.startRecord (ESM::REC_QUES); + state.save (writer); + writer.endRecord (ESM::REC_QUES); + + for (Topic::TEntryIter iter (quest.begin()); iter!=quest.end(); ++iter) + { + ESM::JournalEntry entry; + entry.mType = ESM::JournalEntry::Type_Quest; + entry.mTopic = quest.getTopic(); + iter->write (entry); + writer.startRecord (ESM::REC_JOUR); + entry.save (writer); + writer.endRecord (ESM::REC_JOUR); + } + } + + for (TEntryIter iter (mJournal.begin()); iter!=mJournal.end(); ++iter) + { + ESM::JournalEntry entry; + entry.mType = ESM::JournalEntry::Type_Journal; + iter->write (entry); + writer.startRecord (ESM::REC_JOUR); + entry.save (writer); + writer.endRecord (ESM::REC_JOUR); + } + + for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) + { + const Topic& topic = iter->second; + + for (Topic::TEntryIter iter (topic.begin()); iter!=topic.end(); ++iter) + { + ESM::JournalEntry entry; + entry.mType = ESM::JournalEntry::Type_Topic; + entry.mTopic = topic.getTopic(); + iter->write (entry); + writer.startRecord (ESM::REC_JOUR); + entry.save (writer); + writer.endRecord (ESM::REC_JOUR); + } + } + } + + void Journal::readRecord (ESM::ESMReader& reader, int32_t type) + { + if (type==ESM::REC_JOUR) + { + ESM::JournalEntry record; + record.load (reader); + + if (isThere (record.mTopic, record.mInfo)) + switch (record.mType) + { + case ESM::JournalEntry::Type_Quest: + + getQuest (record.mTopic).insertEntry (record); + break; + + case ESM::JournalEntry::Type_Journal: + + mJournal.push_back (record); + break; + + case ESM::JournalEntry::Type_Topic: + + getTopic (record.mTopic).insertEntry (record); + break; + } + } + else if (type==ESM::REC_QUES) + { + ESM::QuestState record; + record.load (reader); + + if (isThere (record.mTopic)) + mQuests.insert (std::make_pair (record.mTopic, record)); + } + } } diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index f4f8eb1c2..1b4803ba2 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -15,8 +15,14 @@ namespace MWDialogue TQuestContainer mQuests; TTopicContainer mTopics; + private: + Quest& getQuest (const std::string& id); + Topic& getTopic (const std::string& id); + + bool isThere (const std::string& topicId, const std::string& infoId = "") const; + public: Journal(); @@ -32,7 +38,7 @@ namespace MWDialogue virtual int getJournalIndex (const std::string& id) const; ///< Get the journal index. - virtual void addTopic (const std::string& topicId, const std::string& infoId); + virtual void addTopic (const std::string& topicId, const std::string& infoId, const std::string& actorName); virtual TEntryIter begin() const; ///< Iterator pointing to the begin of the main journal. @@ -55,6 +61,12 @@ namespace MWDialogue virtual TTopicIter topicEnd() const; ///< Iterator pointing past the last topic. + + virtual int countSavedGameRecords() const; + + virtual void write (ESM::ESMWriter& writer) const; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type); }; } diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index 520331bc1..a5411b747 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -1,6 +1,8 @@ #include "quest.hpp" +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -16,7 +18,11 @@ namespace MWDialogue : Topic (topic), mIndex (0), mFinished (false) {} - const std::string Quest::getName() const + Quest::Quest (const ESM::QuestState& state) + : Topic (state.mTopic), mIndex (state.mState), mFinished (state.mFinished!=0) + {} + + std::string Quest::getName() const { const ESM::Dialogue *dialogue = MWBase::Environment::get().getWorld()->getStore().get().find (mTopic); @@ -82,12 +88,20 @@ namespace MWDialogue if (index==-1) throw std::runtime_error ("unknown journal entry for topic " + mTopic); - setIndex (index); + if (index > mIndex) + setIndex (index); for (TEntryIter iter (mEntries.begin()); iter!=mEntries.end(); ++iter) - if (*iter==entry.mInfoId) + if (iter->mInfoId==entry.mInfoId) return; - mEntries.push_back (entry.mInfoId); + mEntries.push_back (entry); // we want slicing here + } + + void Quest::write (ESM::QuestState& state) const + { + state.mTopic = getTopic(); + state.mState = mIndex; + state.mFinished = mFinished; } } diff --git a/apps/openmw/mwdialogue/quest.hpp b/apps/openmw/mwdialogue/quest.hpp index 3afa81fac..40824f398 100644 --- a/apps/openmw/mwdialogue/quest.hpp +++ b/apps/openmw/mwdialogue/quest.hpp @@ -3,9 +3,14 @@ #include "topic.hpp" +namespace ESM +{ + struct QuestState; +} + namespace MWDialogue { - /// \brief A quest in progress or a compelted quest + /// \brief A quest in progress or a completed quest class Quest : public Topic { int mIndex; @@ -17,13 +22,15 @@ namespace MWDialogue Quest (const std::string& topic); - const std::string getName() const; + Quest (const ESM::QuestState& state); + + virtual std::string getName() const; ///< May be an empty string int getIndex() const; void setIndex (int index); - ///< Calling this function with a non-existant index while throw an exception. + ///< Calling this function with a non-existent index will throw an exception. bool isFinished() const; @@ -31,6 +38,8 @@ namespace MWDialogue ///< Add entry and adjust index accordingly. /// /// \note Redundant entries are ignored, but the index is still adjusted. + + void write (ESM::QuestState& state) const; }; } diff --git a/apps/openmw/mwdialogue/topic.cpp b/apps/openmw/mwdialogue/topic.cpp index 3253b20d6..f7df305c7 100644 --- a/apps/openmw/mwdialogue/topic.cpp +++ b/apps/openmw/mwdialogue/topic.cpp @@ -1,6 +1,9 @@ #include "topic.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + #include "../mwworld/esmstore.hpp" namespace MWDialogue @@ -9,7 +12,8 @@ namespace MWDialogue {} Topic::Topic (const std::string& topic) - : mTopic (topic) + : mTopic (topic), mName ( + MWBase::Environment::get().getWorld()->getStore().get().find (topic)->mId) {} Topic::~Topic() @@ -20,11 +24,29 @@ namespace MWDialogue if (entry.mTopic!=mTopic) throw std::runtime_error ("topic does not match: " + mTopic); - for (TEntryIter iter = begin(); iter!=end(); ++iter) - if (*iter==entry.mInfoId) + // bail out if we already have heard this + for (Topic::TEntryIter it = mEntries.begin(); it != mEntries.end(); ++it) + { + if (it->mInfoId == entry.mInfoId) return; + } + + mEntries.push_back (entry); // we want slicing here + } + + void Topic::insertEntry (const ESM::JournalEntry& entry) + { + mEntries.push_back (entry); + } - mEntries.push_back (entry.mInfoId); + std::string Topic::getTopic() const + { + return mTopic; + } + + std::string Topic::getName() const + { + return mName; } Topic::TEntryIter Topic::begin() const diff --git a/apps/openmw/mwdialogue/topic.hpp b/apps/openmw/mwdialogue/topic.hpp index c3f0baabc..02fa6d524 100644 --- a/apps/openmw/mwdialogue/topic.hpp +++ b/apps/openmw/mwdialogue/topic.hpp @@ -6,6 +6,11 @@ #include "journalentry.hpp" +namespace ESM +{ + struct JournalEntry; +} + namespace MWDialogue { /// \brief Collection of seen responses for a topic @@ -13,13 +18,14 @@ namespace MWDialogue { public: - typedef std::vector TEntryContainer; + typedef std::vector TEntryContainer; typedef TEntryContainer::const_iterator TEntryIter; protected: std::string mTopic; - TEntryContainer mEntries; // info-IDs + std::string mName; + TEntryContainer mEntries; public: @@ -34,7 +40,13 @@ namespace MWDialogue /// /// \note Redundant entries are ignored. - std::string const & getName () const { return mTopic; } + void insertEntry (const ESM::JournalEntry& entry); + ///< Add entry without checking for redundant entries or modifying the state of the + /// topic otherwise + + std::string getTopic() const; + + virtual std::string getName() const; TEntryIter begin() const; ///< Iterator pointing to the begin of the journal for this topic. diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index 9c8e07f3e..7f58309ba 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -33,8 +33,7 @@ namespace MWGui getWidget(mBirthList, "BirthsignList"); mBirthList->setScrollVisible(true); - mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); - mBirthList->eventListMouseItemActivate += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); + mBirthList->eventListSelectAccept += MyGUI::newDelegate(this, &BirthDialog::onAccept); mBirthList->eventListChangePosition += MyGUI::newDelegate(this, &BirthDialog::onSelectBirth); MyGUI::Button* backButton; @@ -97,6 +96,14 @@ namespace MWGui eventDone(this); } + void BirthDialog::onAccept(MyGUI::ListBox *_sender, size_t _index) + { + onSelectBirth(_sender, _index); + if(mBirthList->getIndexSelected() == MyGUI::ITEM_NONE) + return; + eventDone(this); + } + void BirthDialog::onBackClicked(MyGUI::Widget* _sender) { eventBack(); diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index cc958ddca..20a64c78c 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -38,6 +38,7 @@ namespace MWGui protected: void onSelectBirth(MyGUI::ListBox* _sender, size_t _index); + void onAccept(MyGUI::ListBox* _sender, size_t index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 1a3226074..5526bd26d 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -10,7 +10,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" -#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" #include "../mwworld/fallback.hpp" @@ -47,9 +47,8 @@ namespace void updatePlayerHealth() { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats(player); - - creatureStats.updateHealth(); + MWMechanics::NpcStats& npcStats = MWWorld::Class::get(player).getNpcStats(player); + npcStats.updateHealth(); } } diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 1e1aebd95..1c8cc7840 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -82,8 +82,7 @@ namespace MWGui getWidget(mClassList, "ClassList"); mClassList->setScrollVisible(true); - mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); - mClassList->eventListMouseItemActivate += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); + mClassList->eventListSelectAccept += MyGUI::newDelegate(this, &PickClassDialog::onAccept); mClassList->eventListChangePosition += MyGUI::newDelegate(this, &PickClassDialog::onSelectClass); getWidget(mClassImage, "ClassImage"); @@ -152,6 +151,14 @@ namespace MWGui eventBack(); } + void PickClassDialog::onAccept(MyGUI::ListBox* _sender, size_t _index) + { + onSelectClass(_sender, _index); + if(mClassList->getIndexSelected() == MyGUI::ITEM_NONE) + return; + eventDone(this); + } + void PickClassDialog::onSelectClass(MyGUI::ListBox* _sender, size_t _index) { if (_index == MyGUI::ITEM_NONE) diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index e74370a4c..f78f7541b 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -111,6 +111,7 @@ namespace MWGui protected: void onSelectClass(MyGUI::ListBox* _sender, size_t _index); + void onAccept(MyGUI::ListBox* _sender, size_t _index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 858378c03..7e7ad5ec2 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -235,7 +235,7 @@ namespace MWGui mItemView->setModel (mSortModel); - MyGUI::InputManager::getInstance().setKeyFocusWidget(mCloseButton); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mCloseButton); // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last // or we end up using a possibly invalid model. diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index bd75c078c..4d3d04ced 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -12,6 +12,8 @@ #include #include +#include + namespace { int convertFromHex(std::string hex) @@ -288,6 +290,16 @@ namespace MWGui MyGUI::ImageBox* box = mParent->createWidget ("ImageBox", MyGUI::IntCoord(0, mHeight, width, height), MyGUI::Align::Left | MyGUI::Align::Top, mParent->getName() + boost::lexical_cast(mParent->getChildCount())); + + // Apparently a bug with some morrowind versions, they reference the image without the size suffix. + // So if the image isn't found, try appending the size. + if (!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("bookart\\"+image)) + { + std::stringstream str; + str << image.substr(0, image.rfind(".")) << "_" << width << "_" << height << image.substr(image.rfind(".")); + image = str.str(); + } + box->setImageTexture("bookart\\" + image); box->setProperty("NeedMouse", "false"); } diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 7139c1b2c..2ea09db61 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -55,7 +55,7 @@ namespace MWGui getWidget(mRightPane, "RightPane"); getWidget(mArmorRating, "ArmorRating"); - mAvatar->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); + mAvatarImage->eventMouseButtonClick += MyGUI::newDelegate(this, &InventoryWindow::onAvatarClicked); getWidget(mItemView, "ItemView"); mItemView->eventItemClicked += MyGUI::newDelegate(this, &InventoryWindow::onItemSelected); @@ -76,11 +76,12 @@ namespace MWGui void InventoryWindow::adjustPanes() { - const float aspect = 0.5; // fixed aspect ratio for the left pane - mLeftPane->setSize( (mMainWidget->getSize().height-44) * aspect, mMainWidget->getSize().height-44 ); - mRightPane->setCoord( mLeftPane->getPosition().left + (mMainWidget->getSize().height-44) * aspect + 4, + const float aspect = 0.5; // fixed aspect ratio for the avatar image + float leftPaneWidth = (mMainWidget->getSize().height-44-mArmorRating->getHeight()) * aspect; + mLeftPane->setSize( leftPaneWidth, mMainWidget->getSize().height-44 ); + mRightPane->setCoord( mLeftPane->getPosition().left + leftPaneWidth + 4, mRightPane->getPosition().top, - mMainWidget->getSize().width - 12 - (mMainWidget->getSize().height-44) * aspect - 15, + mMainWidget->getSize().width - 12 - leftPaneWidth - 15, mMainWidget->getSize().height-44 ); } @@ -418,9 +419,9 @@ namespace MWGui else { MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance ().getLastPressedPosition (MyGUI::MouseButton::Left); - MyGUI::IntPoint relPos = mousePos - mAvatar->getAbsolutePosition (); - int realX = int(float(relPos.left) / float(mAvatar->getSize().width) * 512.f ); - int realY = int(float(relPos.top) / float(mAvatar->getSize().height) * 1024.f ); + MyGUI::IntPoint relPos = mousePos - mAvatarImage->getAbsolutePosition (); + int realX = int(float(relPos.left) / float(mAvatarImage->getSize().width) * 512.f ); + int realY = int(float(relPos.top) / float(mAvatarImage->getSize().height) * 1024.f ); MWWorld::Ptr itemSelected = getAvatarSelectedItem (realX, realY); if (itemSelected.isEmpty ()) @@ -487,11 +488,18 @@ namespace MWGui if (mPreviewDirty) { mPreviewDirty = false; - MyGUI::IntSize size = mAvatar->getSize(); + MyGUI::IntSize size = mAvatarImage->getSize(); mPreview.update (size.width, size.height); - mAvatarImage->setSize(MyGUI::IntSize(std::max(mAvatar->getSize().width, 512), std::max(mAvatar->getSize().height, 1024))); + mAvatarImage->setImageTexture("CharacterPreview"); + mAvatarImage->setImageCoord(MyGUI::IntCoord(0, 0, std::min(512, size.width), std::min(1024, size.height))); + mAvatarImage->setImageTile(MyGUI::IntSize(std::min(512, size.width), std::min(1024, size.height))); + + mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + + boost::lexical_cast(static_cast(MWWorld::Class::get(mPtr).getArmorRating(mPtr)))); + if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) + mArmorRating->setCaptionWithReplacing (boost::lexical_cast(static_cast(MWWorld::Class::get(mPtr).getArmorRating(mPtr)))); } } @@ -502,9 +510,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); mPreviewDirty = true; - - mArmorRating->setCaptionWithReplacing ("#{sArmor}: " - + boost::lexical_cast(static_cast(MWWorld::Class::get(mPtr).getArmorRating(mPtr)))); } void InventoryWindow::pickUpObject (MWWorld::Ptr object) @@ -551,9 +556,4 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count); } - - MyGUI::IntCoord InventoryWindow::getAvatarScreenCoord () - { - return mAvatar->getAbsoluteCoord (); - } } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 7e5a0fe10..7ef168e98 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -31,8 +31,6 @@ namespace MWGui void pickUpObject (MWWorld::Ptr object); - MyGUI::IntCoord getAvatarScreenCoord(); - MWWorld::Ptr getAvatarSelectedItem(int x, int y); void rebuildAvatar() { diff --git a/apps/openmw/mwgui/journalbooks.cpp b/apps/openmw/mwgui/journalbooks.cpp index dbea10e77..8caea770e 100644 --- a/apps/openmw/mwgui/journalbooks.cpp +++ b/apps/openmw/mwgui/journalbooks.cpp @@ -196,34 +196,6 @@ book JournalBooks::createEmptyJournalBook () typesetter->lineBreak (); typesetter->write (body, to_utf8_span ("You should have gone though the starting quest and got an initial quest.")); - BookTypesetter::Style* big = typesetter->createStyle ("", MyGUI::Colour::Black); - BookTypesetter::Style* test = typesetter->createStyle ("MonoFont", MyGUI::Colour::Blue); - - typesetter->sectionBreak (20); - typesetter->write (body, to_utf8_span ( - "The layout engine doesn't currently support aligning fonts to " - "their baseline within a single line so the following text looks " - "funny. In order to properly implement it, a stupidly simple " - "change is needed in MyGUI to report the where the baseline is for " - "a particular font" - )); - - typesetter->sectionBreak (20); - typesetter->write (big, to_utf8_span ("big text g")); - typesetter->write (body, to_utf8_span (" проверяем только в дебаге")); - typesetter->write (body, to_utf8_span (" normal g")); - typesetter->write (big, to_utf8_span (" done g")); - - typesetter->sectionBreak (20); - typesetter->write (test, to_utf8_span ( - "int main (int argc,\n" - " char ** argv)\n" - "{\n" - " print (\"hello world!\\n\");\n" - " return 0;\n" - "}\n" - )); - return typesetter->complete (); } diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index ba6552225..a0d67b025 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -20,8 +20,6 @@ namespace MWGui { struct JournalViewModelImpl; -static void injectMonthName (std::ostream & os, int month); - struct JournalViewModelImpl : JournalViewModel { typedef KeywordSearch KeywordSearchT; @@ -237,21 +235,21 @@ struct JournalViewModelImpl : JournalViewModel std::string getText () const { - return itr->getText(MWBase::Environment::get().getWorld()->getStore()); + return itr->getText(); } Utf8Span timestamp () const { if (timestamp_buffer.empty ()) { - std::ostringstream os; - - os << itr->mDayOfMonth << ' '; + std::string dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}"); - injectMonthName (os, itr->mMonth); + std::ostringstream os; - const std::string& dayStr = MyGUI::LanguageManager::getInstance().replaceTags("#{sDay}"); - os << " (" << dayStr << " " << (itr->mDay + 1) << ')'; + os + << itr->mDayOfMonth << ' ' + << MWBase::Environment::get().getWorld()->getMonthName (itr->mMonth) + << " (" << dayStr << " " << (itr->mDay + 1) << ')'; timestamp_buffer = os.str (); } @@ -272,7 +270,7 @@ struct JournalViewModelImpl : JournalViewModel { for (MWDialogue::Topic::TEntryIter j = quest->begin (); j != quest->end (); ++j) { - if (i->mInfoId == *j) + if (i->mInfoId == j->mInfoId) visitor (JournalEntryImpl (this, i)); } } @@ -292,9 +290,7 @@ struct JournalViewModelImpl : JournalViewModel void visitTopicName (TopicId topicId, boost::function visitor) const { MWDialogue::Topic const & topic = * reinterpret_cast (topicId); - // This is to get the correct case for the topic - const std::string& name = MWBase::Environment::get().getWorld()->getStore().get().find(topic.getName())->mId; - visitor (toUtf8Span (name)); + visitor (toUtf8Span (topic.getName())); } void visitTopicNamesStartingWith (char character, boost::function < void (TopicId , Utf8Span) > visitor) const @@ -306,10 +302,7 @@ struct JournalViewModelImpl : JournalViewModel if (i->first [0] != std::tolower (character, mLocale)) continue; - // This is to get the correct case for the topic - const std::string& name = MWBase::Environment::get().getWorld()->getStore().get().find(i->first)->mId; - - visitor (TopicId (&i->second), toUtf8Span (name)); + visitor (TopicId (&i->second), toUtf8Span (i->second.getName())); } } @@ -318,24 +311,18 @@ struct JournalViewModelImpl : JournalViewModel { MWDialogue::Topic const & mTopic; - mutable std::string source_buffer; - TopicEntryImpl (JournalViewModelImpl const * model, MWDialogue::Topic const & topic, iterator_t itr) : BaseEntry (model, itr), mTopic (topic) {} std::string getText () const { - /// \todo defines are not replaced (%PCName etc). should probably be done elsewhere though since we need the actor - return mTopic.getEntry (*itr).getText(MWBase::Environment::get().getWorld()->getStore()); - + return itr->getText(); } Utf8Span source () const { - if (source_buffer.empty ()) - source_buffer = "someone"; - return toUtf8Span (source_buffer); + return toUtf8Span (itr->mActorName); } }; @@ -351,38 +338,6 @@ struct JournalViewModelImpl : JournalViewModel } }; -static void injectMonthName (std::ostream & os, int month) -{ - MyGUI::LanguageManager& lm = MyGUI::LanguageManager::getInstance(); - - if (month == 0) - os << lm.replaceTags ("#{sMonthMorningstar}"); - else if (month == 1) - os << lm.replaceTags ("#{sMonthSunsdawn}"); - else if (month == 2) - os << lm.replaceTags ("#{sMonthFirstseed}"); - else if (month == 3) - os << lm.replaceTags ("#{sMonthRainshand}"); - else if (month == 4) - os << lm.replaceTags ("#{sMonthSecondseed}"); - else if (month == 5) - os << lm.replaceTags ("#{sMonthMidyear}"); - else if (month == 6) - os << lm.replaceTags ("#{sMonthSunsheight}"); - else if (month == 7) - os << lm.replaceTags ("#{sMonthLastseed}"); - else if (month == 8) - os << lm.replaceTags ("#{sMonthHeartfire}"); - else if (month == 9) - os << lm.replaceTags ("#{sMonthFrostfall}"); - else if (month == 10) - os << lm.replaceTags ("#{sMonthSunsdusk}"); - else if (month == 11) - os << lm.replaceTags ("#{sMonthEveningstar}"); - else - os << month; -} - JournalViewModel::Ptr JournalViewModel::create () { return boost::make_shared (); diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index e55d9d8ca..f56d80883 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -155,7 +155,6 @@ namespace MWGui void LevelupDialog::onOkButtonClicked (MyGUI::Widget* sender) { MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get(player).getCreatureStats (player); MWMechanics::NpcStats& pcStats = MWWorld::Class::get(player).getNpcStats (player); if (mSpentAttributes.size() < 3) @@ -165,15 +164,14 @@ namespace MWGui // increase attributes for (int i=0; i<3; ++i) { - MWMechanics::AttributeValue attribute = creatureStats.getAttribute(mSpentAttributes[i]); + MWMechanics::AttributeValue attribute = pcStats.getAttribute(mSpentAttributes[i]); attribute.setBase (attribute.getBase () + pcStats.getLevelupAttributeMultiplier (mSpentAttributes[i])); if (attribute.getBase() >= 100) attribute.setBase(100); - creatureStats.setAttribute(mSpentAttributes[i], attribute); + pcStats.setAttribute(mSpentAttributes[i], attribute); } - creatureStats.levelUp(); pcStats.levelUp (); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Levelup); diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index fa7ed2ace..da1992474 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -1,13 +1,14 @@ #include "mainmenu.hpp" -#include - #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/statemanager.hpp" + +#include "../mwstate/character.hpp" #include "savegamedialog.hpp" @@ -16,90 +17,132 @@ namespace MWGui MainMenu::MainMenu(int w, int h) : OEngine::GUI::Layout("openmw_mainmenu.layout") - , mButtonBox(0) + , mButtonBox(0), mWidth (w), mHeight (h) + , mSaveGameDialog(NULL) + { + updateMenu(); + } + + MainMenu::~MainMenu() { - onResChange(w,h); + delete mSaveGameDialog; } void MainMenu::onResChange(int w, int h) { - setCoord(0,0,w,h); + mWidth = w; + mHeight = h; + + updateMenu(); + } + + void MainMenu::setVisible (bool visible) + { + if (visible) + updateMenu(); + + OEngine::GUI::Layout::setVisible (visible); + } + + void MainMenu::onButtonClicked(MyGUI::Widget *sender) + { + std::string name = *sender->getUserData(); + MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f); + if (name == "return") + { + MWBase::Environment::get().getSoundManager ()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); + MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu); + } + else if (name == "options") + MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings); + else if (name == "exitgame") + MWBase::Environment::get().getStateManager()->requestQuit(); + else if (name == "newgame") + { + MWBase::Environment::get().getStateManager()->newGame(); + } + + else + { + if (!mSaveGameDialog) + mSaveGameDialog = new SaveGameDialog(); + if (name == "loadgame") + mSaveGameDialog->setLoadOrSave(true); + else if (name == "savegame") + mSaveGameDialog->setLoadOrSave(false); + mSaveGameDialog->setVisible(true); + } + } + + void MainMenu::updateMenu() + { + setCoord(0,0, mWidth, mHeight); - if (mButtonBox) - MyGUI::Gui::getInstance ().destroyWidget(mButtonBox); + if (!mButtonBox) + mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); - mButtonBox = mMainWidget->createWidget("", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); int curH = 0; + MWBase::StateManager::State state = MWBase::Environment::get().getStateManager()->getState(); + std::vector buttons; - buttons.push_back("return"); + + if (state==MWBase::StateManager::State_Running) + buttons.push_back("return"); + buttons.push_back("newgame"); - //buttons.push_back("loadgame"); - //buttons.push_back("savegame"); + + if (MWBase::Environment::get().getStateManager()->characterBegin()!= + MWBase::Environment::get().getStateManager()->characterEnd()) + buttons.push_back("loadgame"); + + if (state==MWBase::StateManager::State_Running && + MWBase::Environment::get().getWorld()->getGlobalInt ("chargenstate")==-1) + buttons.push_back("savegame"); + buttons.push_back("options"); //buttons.push_back("credits"); buttons.push_back("exitgame"); - int maxwidth = 0; - - mButtons.clear(); + // Create new buttons if needed for (std::vector::iterator it = buttons.begin(); it != buttons.end(); ++it) { - MWGui::ImageButton* button = mButtonBox->createWidget - ("ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default); - button->setProperty("ImageHighlighted", "textures\\menu_" + *it + "_over.dds"); - button->setProperty("ImageNormal", "textures\\menu_" + *it + ".dds"); - button->setProperty("ImagePushed", "textures\\menu_" + *it + "_pressed.dds"); - MyGUI::IntSize requested = button->getRequestedSize(); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked); - mButtons[*it] = button; - curH += requested.height; - - if (requested.width > maxwidth) - maxwidth = requested.width; + if (mButtons.find(*it) == mButtons.end()) + { + MWGui::ImageButton* button = mButtonBox->createWidget + ("ImageBox", MyGUI::IntCoord(0, curH, 0, 0), MyGUI::Align::Default); + button->setProperty("ImageHighlighted", "textures\\menu_" + *it + "_over.dds"); + button->setProperty("ImageNormal", "textures\\menu_" + *it + ".dds"); + button->setProperty("ImagePushed", "textures\\menu_" + *it + "_pressed.dds"); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MainMenu::onButtonClicked); + button->setUserData(std::string(*it)); + mButtons[*it] = button; + } } + + // Start by hiding all buttons + int maxwidth = 0; for (std::map::iterator it = mButtons.begin(); it != mButtons.end(); ++it) { + it->second->setVisible(false); MyGUI::IntSize requested = it->second->getRequestedSize(); - it->second->setCoord((maxwidth-requested.width) / 2, it->second->getTop(), requested.width, requested.height); + if (requested.width > maxwidth) + maxwidth = requested.width; } - mButtonBox->setCoord (w/2 - maxwidth/2, h/2 - curH/2, maxwidth, curH); - } - - void MainMenu::onButtonClicked(MyGUI::Widget *sender) - { - MWBase::Environment::get().getSoundManager()->playSound("Menu Click", 1.f, 1.f); - if (sender == mButtons["return"]) - { - MWBase::Environment::get().getSoundManager ()->resumeSounds (MWBase::SoundManager::Play_TypeSfx); - MWBase::Environment::get().getWindowManager ()->removeGuiMode (GM_MainMenu); - } - else if (sender == mButtons["options"]) - MWBase::Environment::get().getWindowManager ()->pushGuiMode (GM_Settings); - else if (sender == mButtons["exitgame"]) - MWBase::Environment::get().setRequestExit(); - else if (sender == mButtons["newgame"]) + // Now show and position the ones we want + for (std::vector::iterator it = buttons.begin(); it != buttons.end(); ++it) { - MWBase::Environment::get().getWorld()->startNewGame(); - MWBase::Environment::get().getWindowManager()->setNewGame(true); - MWBase::Environment::get().getDialogueManager()->clear(); - MWBase::Environment::get().getJournal()->clear(); + assert(mButtons.find(*it) != mButtons.end()); + MWGui::ImageButton* button = mButtons[*it]; + button->setVisible(true); + MyGUI::IntSize requested = button->getRequestedSize(); + button->setCoord((maxwidth-requested.width) / 2, curH, requested.width, requested.height); + curH += requested.height; } - else if (sender == mButtons["loadgame"]) - { - MWGui::SaveGameDialog* dialog = new MWGui::SaveGameDialog(); - dialog->setLoadOrSave(true); - dialog->setVisible(true); - } - else if (sender == mButtons["savegame"]) - { - MWGui::SaveGameDialog* dialog = new MWGui::SaveGameDialog(); - dialog->setLoadOrSave(false); - dialog->setVisible(true); - } - } + mButtonBox->setCoord (mWidth/2 - maxwidth/2, mHeight/2 - curH/2, maxwidth, curH); + } } diff --git a/apps/openmw/mwgui/mainmenu.hpp b/apps/openmw/mwgui/mainmenu.hpp index 4e76a64df..6d52f26d5 100644 --- a/apps/openmw/mwgui/mainmenu.hpp +++ b/apps/openmw/mwgui/mainmenu.hpp @@ -5,19 +5,33 @@ namespace MWGui { + class SaveGameDialog; + class MainMenu : public OEngine::GUI::Layout { - public: - MainMenu(int w, int h); + int mWidth; + int mHeight; + + public: + + MainMenu(int w, int h); + ~MainMenu(); + + void onResChange(int w, int h); + + virtual void setVisible (bool visible); + + private: + + MyGUI::Widget* mButtonBox; - void onResChange(int w, int h); + std::map mButtons; - private: - MyGUI::Widget* mButtonBox; + void onButtonClicked (MyGUI::Widget* sender); - std::map mButtons; + void updateMenu(); - void onButtonClicked (MyGUI::Widget* sender); + SaveGameDialog* mSaveGameDialog; }; } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index ba6114262..1b4af6d82 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -373,8 +373,9 @@ namespace MWGui // ------------------------------------------------------------------------------------------ - MapWindow::MapWindow(const std::string& cacheDir) - : MWGui::WindowPinnableBase("openmw_map_window.layout") + MapWindow::MapWindow(DragAndDrop* drag, const std::string& cacheDir) + : WindowPinnableBase("openmw_map_window.layout") + , NoDrop(drag, mMainWidget) , mGlobal(false) , mGlobalMap(0) , mGlobalMapRender(0) @@ -434,7 +435,7 @@ namespace MWGui static int _counter=0; - MyGUI::Button* markerWidget = mGlobalMapImage->createWidget("ButtonImage", + MyGUI::Button* markerWidget = mGlobalMapOverlay->createWidget("ButtonImage", widgetCoord, MyGUI::Align::Default, "Door" + boost::lexical_cast(_counter)); markerWidget->setImageResource("DoorMarker"); markerWidget->setUserString("ToolTipType", "Layout"); @@ -499,10 +500,11 @@ namespace MWGui mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - for (unsigned int i=0; igetChildCount (); ++i) + // force markers to foreground + for (unsigned int i=0; igetChildCount (); ++i) { - if (mGlobalMapImage->getChildAt (i)->getName().substr(0,4) == "Door") - mGlobalMapImage->getChildAt (i)->castType()->setImageResource("DoorMarker"); + if (mGlobalMapOverlay->getChildAt (i)->getName().substr(0,4) == "Door") + mGlobalMapOverlay->getChildAt (i)->castType()->setImageResource("DoorMarker"); } globalMapUpdatePlayer(); @@ -573,4 +575,31 @@ namespace MWGui mGlobalMap->setViewOffset(viewoffs); } + void MapWindow::clear() + { + mGlobalMapRender->clear(); + + while (mEventBoxGlobal->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mEventBoxGlobal->getChildAt(0)); + while (mGlobalMapOverlay->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mGlobalMapOverlay->getChildAt(0)); + } + + void MapWindow::write(ESM::ESMWriter &writer) + { + mGlobalMapRender->write(writer); + } + + void MapWindow::readRecord(ESM::ESMReader &reader, int32_t type) + { + std::vector > exploredCells; + mGlobalMapRender->readRecord(reader, type, exploredCells); + + for (std::vector >::iterator it = exploredCells.begin(); it != exploredCells.end(); ++it) + { + const ESM::Cell* cell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first, it->second); + if (cell && !cell->mName.empty()) + addVisitedLocation(cell->mName, it->first, it->second); + } + } } diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 7df2105dc..1e52ff26a 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_MAPWINDOW_H #define MWGUI_MAPWINDOW_H +#include + #include "windowpinnablebase.hpp" namespace MWRender @@ -8,6 +10,12 @@ namespace MWRender class GlobalMap; } +namespace ESM +{ + class ESMReader; + class ESMWriter; +} + namespace Loading { class Listener; @@ -75,10 +83,10 @@ namespace MWGui float mLastDirectionY; }; - class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase + class MapWindow : public MWGui::WindowPinnableBase, public LocalMapBase, public NoDrop { public: - MapWindow(const std::string& cacheDir); + MapWindow(DragAndDrop* drag, const std::string& cacheDir); virtual ~MapWindow(); void setCellName(const std::string& cellName); @@ -92,6 +100,14 @@ namespace MWGui virtual void open(); + void onFrame(float dt) { NoDrop::onFrame(dt); } + + /// Clear all savegame-specific data + void clear(); + + void write (ESM::ESMWriter& writer); + void readRecord (ESM::ESMReader& reader, int32_t type); + private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index 74231c008..1ce167c33 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -245,11 +245,11 @@ namespace MWGui } mainWidgetSize.height = textSize.height + textButtonPadding + buttonHeight + buttonMainPadding; - MyGUI::IntCoord absCoord; - absCoord.left = (gameWindowSize.width - mainWidgetSize.width)/2; - absCoord.top = (gameWindowSize.height - mainWidgetSize.height)/2; + MyGUI::IntPoint absPos; + absPos.left = (gameWindowSize.width - mainWidgetSize.width)/2; + absPos.top = (gameWindowSize.height - mainWidgetSize.height)/2; - mMainWidget->setCoord(absCoord); + mMainWidget->setPosition(absPos); mMainWidget->setSize(mainWidgetSize); MyGUI::IntCoord messageWidgetCoord; @@ -332,7 +332,7 @@ namespace MWGui { if(Misc::StringUtils::ciEqual((*button)->getCaption(), ok)) { - MyGUI::InputManager::getInstance().setKeyFocusWidget(*button); + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(*button); (*button)->eventKeyButtonPressed += MyGUI::newDelegate(this, &InteractiveMessageBox::onKeyPressed); break; } diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index 299b34b51..3dff1b7e4 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -70,8 +70,7 @@ namespace MWGui setText("RaceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu5", "Race")); getWidget(mRaceList, "RaceList"); mRaceList->setScrollVisible(true); - mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); - mRaceList->eventListMouseItemActivate += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); + mRaceList->eventListSelectAccept += MyGUI::newDelegate(this, &RaceDialog::onAccept); mRaceList->eventListChangePosition += MyGUI::newDelegate(this, &RaceDialog::onSelectRace); setText("SkillsT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sBonusSkillTitle", "Skill Bonus")); @@ -241,6 +240,14 @@ namespace MWGui updateSpellPowers(); } + void RaceDialog::onAccept(MyGUI::ListBox *_sender, size_t _index) + { + onSelectRace(_sender, _index); + if(mRaceList->getIndexSelected() == MyGUI::ITEM_NONE) + return; + eventDone(this); + } + void RaceDialog::getBodyParts (int part, std::vector& out) { out.clear(); diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 914ae8096..340dcfa27 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -67,6 +67,7 @@ namespace MWGui void onSelectNextHair(MyGUI::Widget* _sender); void onSelectRace(MyGUI::ListBox* _sender, size_t _index); + void onAccept(MyGUI::ListBox* _sender, size_t _index); void onOkClicked(MyGUI::Widget* _sender); void onBackClicked(MyGUI::Widget* _sender); diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index 756cd5736..2ea0db64a 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -16,7 +16,7 @@ namespace MWGui void ReferenceInterface::checkReferenceAvailable() { - MWWorld::Ptr::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore* playerCell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); // check if player has changed cell, or count of the reference has become 0 if ((playerCell != mCurrentPlayerCell && mCurrentPlayerCell != NULL) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index a1acd3588..77ad98121 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -1,12 +1,28 @@ #include "savegamedialog.hpp" #include "widgets.hpp" +#include +#include + +#include + +#include + +#include "../mwbase/statemanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwstate/character.hpp" + +#include "confirmationdialog.hpp" namespace MWGui { - SaveGameDialog::SaveGameDialog() : WindowModal("openmw_savegame_dialog.layout") + , mSaving(true) + , mCurrentCharacter(NULL) { getWidget(mScreenshot, "Screenshot"); getWidget(mCharacterSelection, "SelectCharacter"); @@ -18,21 +34,89 @@ namespace MWGui getWidget(mSpacer, "Spacer"); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onOkButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SaveGameDialog::onCancelButtonClicked); + mCharacterSelection->eventComboChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onCharacterSelected); + mSaveList->eventListChangePosition += MyGUI::newDelegate(this, &SaveGameDialog::onSlotSelected); + mSaveList->eventListSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onSlotActivated); + mSaveNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &SaveGameDialog::onEditSelectAccept); + mSaveNameEdit->eventEditTextChange += MyGUI::newDelegate(this, &SaveGameDialog::onSaveNameChanged); + } + void SaveGameDialog::onSlotActivated(MyGUI::ListBox *sender, size_t pos) + { + onSlotSelected(sender, pos); + accept(); + } + + void SaveGameDialog::onSaveNameChanged(MyGUI::EditBox *sender) + { + // This might have previously been a save slot from the list. If so, that is no longer the case + mSaveList->setIndexSelected(MyGUI::ITEM_NONE); + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); + } + + void SaveGameDialog::onEditSelectAccept(MyGUI::EditBox *sender) + { + accept(); } void SaveGameDialog::open() { + WindowModal::open(); + + mSaveNameEdit->setCaption (""); + if (mSaving) + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mSaveNameEdit); + center(); + + MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); + if (mgr->characterBegin() == mgr->characterEnd()) + return; + + mCurrentCharacter = mgr->getCurrentCharacter (false); + + std::string directory = + Misc::StringUtils::lowerCase (Settings::Manager::getString ("character", "Saves")); + + mCharacterSelection->removeAllItems(); + + for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it) + { + if (it->begin()!=it->end()) + { + std::stringstream title; + title << it->getSignature().mPlayerName; + title << " (Level " << it->getSignature().mPlayerLevel << " " << it->getSignature().mPlayerClass << ")"; + + mCharacterSelection->addItem (title.str()); + + if (mCurrentCharacter == &*it || + (!mCurrentCharacter && !mSaving && directory==Misc::StringUtils::lowerCase ( + it->begin()->mPath.parent_path().filename().string()))) + { + mCurrentCharacter = &*it; + mCharacterSelection->setIndexSelected(mCharacterSelection->getItemCount()-1); + } + } + } + + fillSaveList(); + } void SaveGameDialog::setLoadOrSave(bool load) { + mSaving = !load; mSaveNameEdit->setVisible(!load); mCharacterSelection->setUserString("Hidden", load ? "false" : "true"); mCharacterSelection->setVisible(load); mSpacer->setUserString("Hidden", load ? "false" : "true"); + if (!load) + { + mCurrentCharacter = MWBase::Environment::get().getStateManager()->getCurrentCharacter (false); + } + center(); } @@ -41,9 +125,161 @@ namespace MWGui setVisible(false); } - void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender) + void SaveGameDialog::onConfirmationGiven() + { + accept(true); + } + + void SaveGameDialog::accept(bool reallySure) { + MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(NULL); + + // Get the selected slot, if any + unsigned int i=0; + const MWState::Slot* slot = NULL; + + if (mCurrentCharacter) + { + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it,++i) + { + if (i == mSaveList->getIndexSelected()) + slot = &*it; + } + } + + if (mSaving) + { + // If overwriting an existing slot, ask for confirmation first + if (slot != NULL && !reallySure) + { + ConfirmationDialog* dialog = MWBase::Environment::get().getWindowManager()->getConfirmationDialog(); + dialog->open("#{sMessage4}"); + dialog->eventOkClicked.clear(); + dialog->eventOkClicked += MyGUI::newDelegate(this, &SaveGameDialog::onConfirmationGiven); + dialog->eventCancelClicked.clear(); + return; + } + if (mSaveNameEdit->getCaption().empty()) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage65}"); + return; + } + MWBase::Environment::get().getStateManager()->saveGame (mSaveNameEdit->getCaption(), slot); + } + else + { + if (mCurrentCharacter && slot) + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, slot); + } + setVisible(false); + + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_NoGame) + { + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } } + void SaveGameDialog::onOkButtonClicked(MyGUI::Widget *sender) + { + accept(); + } + + void SaveGameDialog::onCharacterSelected(MyGUI::ComboBox *sender, size_t pos) + { + MWBase::StateManager* mgr = MWBase::Environment::get().getStateManager(); + + unsigned int i=0; + const MWState::Character* character = NULL; + for (MWBase::StateManager::CharacterIterator it = mgr->characterBegin(); it != mgr->characterEnd(); ++it, ++i) + { + if (i == pos) + character = &*it; + } + assert(character && "Can't find selected character"); + + mCurrentCharacter = character; + fillSaveList(); + } + + void SaveGameDialog::fillSaveList() + { + mSaveList->removeAllItems(); + if (!mCurrentCharacter) + return; + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it) + { + mSaveList->addItem(it->mProfile.mDescription); + } + onSlotSelected(mSaveList, MyGUI::ITEM_NONE); + } + + void SaveGameDialog::onSlotSelected(MyGUI::ListBox *sender, size_t pos) + { + if (pos == MyGUI::ITEM_NONE) + { + mInfoText->setCaption(""); + mScreenshot->setImageTexture(""); + return; + } + + if (mSaving) + mSaveNameEdit->setCaption(sender->getItemNameAt(pos)); + + const MWState::Slot* slot = NULL; + unsigned int i=0; + for (MWState::Character::SlotIterator it = mCurrentCharacter->begin(); it != mCurrentCharacter->end(); ++it, ++i) + { + if (i == pos) + slot = &*it; + } + assert(slot && "Can't find selected slot"); + + std::stringstream text; + time_t time = slot->mTimeStamp; + struct tm* timeinfo; + timeinfo = localtime(&time); + + text << asctime(timeinfo) << "\n"; + text << "Level " << slot->mProfile.mPlayerLevel << "\n"; + text << slot->mProfile.mPlayerCell << "\n"; + // text << "Time played: " << slot->mProfile.mTimePlayed << "\n"; + + int hour = int(slot->mProfile.mInGameTime.mGameHour); + bool pm = hour >= 12; + if (hour >= 13) hour -= 12; + if (hour == 0) hour = 12; + + text + << slot->mProfile.mInGameTime.mDay << " " + << MWBase::Environment::get().getWorld()->getMonthName(slot->mProfile.mInGameTime.mMonth) + << " " << hour << " " << (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); + + mInfoText->setCaptionWithReplacing(text.str()); + + // Decode screenshot + std::vector data = slot->mProfile.mScreenshot; // MemoryDataStream doesn't work with const data :( + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); + Ogre::Image image; + image.load(stream, "jpg"); + + const std::string textureName = "@savegame_screenshot"; + Ogre::TexturePtr texture; + texture = Ogre::TextureManager::getSingleton().getByName(textureName); + mScreenshot->setImageTexture(""); + if (texture.isNull()) + { + texture = Ogre::TextureManager::getSingleton().createManual(textureName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, + image.getWidth(), image.getHeight(), 0, Ogre::PF_BYTE_RGBA, Ogre::TU_DYNAMIC_WRITE_ONLY); + } + texture->unload(); + texture->setWidth(image.getWidth()); + texture->setHeight(image.getHeight()); + texture->loadImage(image); + + mScreenshot->setImageTexture(textureName); + } } diff --git a/apps/openmw/mwgui/savegamedialog.hpp b/apps/openmw/mwgui/savegamedialog.hpp index 1a3178ef3..8d09a1cbc 100644 --- a/apps/openmw/mwgui/savegamedialog.hpp +++ b/apps/openmw/mwgui/savegamedialog.hpp @@ -3,6 +3,11 @@ #include "windowbase.hpp" +namespace MWState +{ + class Character; +} + namespace MWGui { @@ -15,12 +20,22 @@ namespace MWGui void setLoadOrSave(bool load); + private: void onCancelButtonClicked (MyGUI::Widget* sender); void onOkButtonClicked (MyGUI::Widget* sender); + void onCharacterSelected (MyGUI::ComboBox* sender, size_t pos); + void onSlotSelected (MyGUI::ListBox* sender, size_t pos); + void onSlotActivated (MyGUI::ListBox* sender, size_t pos); + void onEditSelectAccept (MyGUI::EditBox* sender); + void onSaveNameChanged (MyGUI::EditBox* sender); + void onConfirmationGiven(); + void accept(bool reallySure=false); + + void fillSaveList(); - private: MyGUI::ImageBox* mScreenshot; + bool mSaving; MyGUI::ComboBox* mCharacterSelection; MyGUI::EditBox* mInfoText; @@ -30,6 +45,8 @@ namespace MWGui MyGUI::EditBox* mSaveNameEdit; MyGUI::Widget* mSpacer; + const MWState::Character* mCurrentCharacter; + }; } diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 07076159c..6b261a799 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -42,8 +42,9 @@ namespace namespace MWGui { - SpellWindow::SpellWindow() + SpellWindow::SpellWindow(DragAndDrop* drag) : WindowPinnableBase("openmw_spell_window.layout") + , NoDrop(drag, mMainWidget) , mHeight(0) , mWidth(0) { diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index 521e73d76..38a761931 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -7,14 +7,16 @@ namespace MWGui { class SpellIcons; - class SpellWindow : public WindowPinnableBase + class SpellWindow : public WindowPinnableBase, public NoDrop { public: - SpellWindow(); + SpellWindow(DragAndDrop* drag); virtual ~SpellWindow(); void updateSpells(); + void onFrame(float dt) { NoDrop::onFrame(dt); } + protected: MyGUI::ScrollView* mSpellView; MyGUI::Widget* mEffectBox; diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index 40eb2d3b1..3d4c741a3 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -18,8 +18,9 @@ namespace MWGui const int StatsWindow::sLineHeight = 18; - StatsWindow::StatsWindow () + StatsWindow::StatsWindow (DragAndDrop* drag) : WindowPinnableBase("openmw_stats_window.layout") + , NoDrop(drag, mMainWidget) , mSkillView(NULL) , mMajorSkills() , mMinorSkills() @@ -219,11 +220,13 @@ namespace MWGui updateSkillArea(); } - void StatsWindow::onFrame () + void StatsWindow::onFrame (float dt) { if (!mMainWidget->getVisible()) return; + NoDrop::onFrame(dt); + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWMechanics::NpcStats &PCstats = MWWorld::Class::get(player).getNpcStats(player); @@ -231,9 +234,12 @@ namespace MWGui MyGUI::Widget* levelWidget; for (int i=0; i<2; ++i) { + int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->getInt(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); levelWidget->setUserString("RangePosition_LevelProgress", boost::lexical_cast(PCstats.getLevelProgress())); - levelWidget->setUserString("Caption_LevelProgressText", boost::lexical_cast(PCstats.getLevelProgress()) + "/10"); + levelWidget->setUserString("Range_LevelProgress", boost::lexical_cast(max)); + levelWidget->setUserString("Caption_LevelProgressText", boost::lexical_cast(PCstats.getLevelProgress()) + "/" + + boost::lexical_cast(max)); } setFactions(PCstats.getFactionRanks()); diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index 28d96ca90..d90c16be9 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -10,17 +10,17 @@ namespace MWGui { class WindowManager; - class StatsWindow : public WindowPinnableBase + class StatsWindow : public WindowPinnableBase, public NoDrop { public: typedef std::map FactionList; typedef std::vector SkillList; - StatsWindow(); + StatsWindow(DragAndDrop* drag); /// automatically updates all the data in the stats window, but only if it has changed. - void onFrame(); + void onFrame(float dt); void setBar(const std::string& name, const std::string& tname, int val, int max); void setPlayerName(const std::string& playerName); diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 0fe500879..8716c4dea 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -182,7 +182,7 @@ namespace MWGui } else if (type == "AvatarItemSelection") { - MyGUI::IntCoord avatarPos = MWBase::Environment::get().getWindowManager()->getInventoryWindow ()->getAvatarScreenCoord (); + MyGUI::IntCoord avatarPos = focus->getAbsoluteCoord(); MyGUI::IntPoint relMousePos = MyGUI::InputManager::getInstance ().getMousePosition () - MyGUI::IntPoint(avatarPos.left, avatarPos.top); int realX = int(float(relMousePos.left) / float(avatarPos.width) * 512.f ); int realY = int(float(relMousePos.top) / float(avatarPos.height) * 1024.f ); diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index 0ead54d9d..3c1a4b3fa 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -87,49 +87,7 @@ namespace MWGui onHourSliderChangedPosition(mHourSlider, 0); mHourSlider->setScrollPosition (0); - // http://www.uesp.net/wiki/Lore:Calendar - std::string month; - int m = MWBase::Environment::get().getWorld ()->getMonth (); - switch (m) { - case 0: - month = "#{sMonthMorningstar}"; - break; - case 1: - month = "#{sMonthSunsdawn}"; - break; - case 2: - month = "#{sMonthFirstseed}"; - break; - case 3: - month = "#{sMonthRainshand}"; - break; - case 4: - month = "#{sMonthSecondseed}"; - break; - case 5: - month = "#{sMonthMidyear}"; - break; - case 6: - month = "#{sMonthSunsheight}"; - break; - case 7: - month = "#{sMonthLastseed}"; - break; - case 8: - month = "#{sMonthHeartfire}"; - break; - case 9: - month = "#{sMonthFrostfall}"; - break; - case 10: - month = "#{sMonthSunsdusk}"; - break; - case 11: - month = "#{sMonthEveningstar}"; - break; - default: - break; - } + std::string month = MWBase::Environment::get().getWorld ()->getMonthName(); int hour = MWBase::Environment::get().getWorld ()->getTimeStamp ().getHour (); bool pm = hour >= 12; if (hour >= 13) hour -= 12; @@ -271,7 +229,9 @@ namespace MWGui const MWMechanics::NpcStats &pcstats = MWWorld::Class::get(player).getNpcStats(player); // trigger levelup if possible - if (mSleeping && pcstats.getLevelProgress () >= 10) + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->getInt()) { MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); } diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index cc74579ab..87b26b814 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -1,6 +1,7 @@ #include "windowbase.hpp" #include "../mwbase/windowmanager.hpp" +#include "container.hpp" using namespace MWGui; @@ -50,3 +51,36 @@ void WindowModal::close() { MyGUI::InputManager::getInstance ().removeWidgetModal (mMainWidget); } + +NoDrop::NoDrop(DragAndDrop *drag, MyGUI::Widget *widget) + : mDrag(drag), mWidget(widget), mTransparent(false) +{ +} + +void NoDrop::onFrame(float dt) +{ + MyGUI::IntPoint mousePos = MyGUI::InputManager::getInstance().getMousePosition(); + + if (mDrag->mIsOnDragAndDrop) + { + MyGUI::Widget* focus = MyGUI::InputManager::getInstance().getMouseFocusWidget(); + while (focus && focus != mWidget) + focus = focus->getParent(); + + if (focus == mWidget) + mTransparent = true; + } + if (!mWidget->getAbsoluteCoord().inside(mousePos)) + mTransparent = false; + + if (mTransparent) + { + mWidget->setNeedMouseFocus(false); // Allow click-through + mWidget->setAlpha(std::max(0.13f, mWidget->getAlpha() - dt*5)); + } + else + { + mWidget->setNeedMouseFocus(true); + mWidget->setAlpha(std::min(1.0f, mWidget->getAlpha() + dt*5)); + } +} diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 2c014baf0..48de9ea87 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -11,6 +11,7 @@ namespace MWBase namespace MWGui { class WindowManager; + class DragAndDrop; class WindowBase: public OEngine::GUI::Layout { @@ -42,6 +43,21 @@ namespace MWGui virtual void open(); virtual void close(); }; + + /// A window that cannot be the target of a drag&drop action. + /// When hovered with a drag item, the window will become transparent and allow click-through. + class NoDrop + { + public: + NoDrop(DragAndDrop* drag, MyGUI::Widget* widget); + + void onFrame(float dt); + + private: + MyGUI::Widget* mWidget; + DragAndDrop* mDrag; + bool mTransparent; + }; } #endif diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index cda146e8c..bd17cb317 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -14,6 +14,7 @@ #include #include "../mwbase/inputmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -200,9 +201,9 @@ namespace MWGui mRecharge = new Recharge(); mMenu = new MainMenu(w,h); - mMap = new MapWindow(""); + mMap = new MapWindow(mDragAndDrop, ""); trackWindow(mMap, "map"); - mStatsWindow = new StatsWindow(); + mStatsWindow = new StatsWindow(mDragAndDrop); trackWindow(mStatsWindow, "stats"); mConsole = new Console(w,h, mConsoleOnlyScripts); trackWindow(mConsole, "console"); @@ -227,7 +228,7 @@ namespace MWGui mConfirmationDialog = new ConfirmationDialog(); mAlchemyWindow = new AlchemyWindow(); trackWindow(mAlchemyWindow, "alchemy"); - mSpellWindow = new SpellWindow(); + mSpellWindow = new SpellWindow(mDragAndDrop); trackWindow(mSpellWindow, "spells"); mQuickKeysMenu = new QuickKeysMenu(); mLevelupDialog = new LevelupDialog(); @@ -699,6 +700,10 @@ namespace MWGui mToolTips->onFrame(frameDuration); + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_NoGame) + return; + if (mDragAndDrop->mIsOnDragAndDrop) { assert(mDragAndDrop->mDraggedWidget); @@ -709,7 +714,9 @@ namespace MWGui mInventoryWindow->onFrame(); - mStatsWindow->onFrame(); + mStatsWindow->onFrame(frameDuration); + mMap->onFrame(frameDuration); + mSpellWindow->onFrame(frameDuration); mWaitDialog->onFrame(frameDuration); @@ -730,31 +737,20 @@ namespace MWGui mCompanionWindow->onFrame(); } - void WindowManager::changeCell(MWWorld::Ptr::CellStore* cell) + void WindowManager::changeCell(MWWorld::CellStore* cell) { + std::string name = MWBase::Environment::get().getWorld()->getCellName (cell); + + mMap->setCellName( name ); + mHud->setCellName( name ); + if (cell->mCell->isExterior()) { - std::string name; - if (cell->mCell->mName != "") - { - name = cell->mCell->mName; + if (!cell->mCell->mName.empty()) mMap->addVisitedLocation ("#{sCell=" + name + "}", cell->mCell->getGridX (), cell->mCell->getGridY ()); - } - else - { - const ESM::Region* region = - MWBase::Environment::get().getWorld()->getStore().get().search(cell->mCell->mRegion); - if (region) - name = region->mName; - else - name = getGameSettingString("sDefaultCellname", "Wilderness"); - } mMap->cellExplored(cell->mCell->getGridX(), cell->mCell->getGridY()); - mMap->setCellName( name ); - mHud->setCellName( name ); - mMap->setCellPrefix("Cell"); mHud->setCellPrefix("Cell"); mMap->setActiveCell( cell->mCell->getGridX(), cell->mCell->getGridY() ); @@ -762,8 +758,6 @@ namespace MWGui } else { - mMap->setCellName( cell->mCell->mName ); - mHud->setCellName( cell->mCell->mName ); mMap->setCellPrefix( cell->mCell->mName ); mHud->setCellPrefix( cell->mCell->mName ); @@ -774,7 +768,6 @@ namespace MWGui MWBase::Environment::get().getWorld()->getPlayer().setLastKnownExteriorPosition(worldPos); mMap->setGlobalMapPlayerPosition(worldPos.x, worldPos.y); } - } void WindowManager::setInteriorMapTexture(const int x, const int y) @@ -1237,7 +1230,7 @@ namespace MWGui bool WindowManager::getRestEnabled() { //Enable rest dialogue if character creation finished - if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalVariable ("chargenstate").mFloat==-1) + if(mRestAllowed==false && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) mRestAllowed=true; return mRestAllowed; } @@ -1390,4 +1383,19 @@ namespace MWGui Settings::Manager::setFloat(setting + " h", "Windows", h); } + void WindowManager::clear() + { + mMap->clear(); + } + + void WindowManager::write(ESM::ESMWriter &writer) + { + mMap->write(writer); + } + + void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type) + { + mMap->readRecord(reader, type); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 9838a667f..db52d9f79 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -105,6 +105,7 @@ namespace MWGui */ virtual void update(); + /// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this. virtual void setKeyFocusWidget (MyGUI::Widget* widget); virtual void setNewGame(bool newgame); @@ -280,6 +281,12 @@ namespace MWGui virtual bool getCursorVisible(); + /// Clear all savegame-specific data + virtual void clear(); + + virtual void write (ESM::ESMWriter& writer); + virtual void readRecord (ESM::ESMReader& reader, int32_t type); + private: bool mConsoleOnlyScripts; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 2ad667d3f..c4b8d0a89 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -20,6 +20,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwmechanics/creaturestats.hpp" using namespace ICS; @@ -167,7 +168,9 @@ namespace MWInput switch (action) { case A_GameMenu: - toggleMainMenu (); + if(!(MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running + && MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu)) + toggleMainMenu (); break; case A_Screenshot: screenshot(); @@ -248,8 +251,6 @@ namespace MWInput mInputManager->capture(loading); // inject some fake mouse movement to force updating MyGUI's widget states - // this shouldn't do any harm since we're moving back to the original position afterwards - MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX+1), int(mMouseY+1), mMouseWheel); MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); // update values of channels (as a result of pressed keys) @@ -280,7 +281,9 @@ namespace MWInput return; // Disable movement in Gui mode - if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; + if (MWBase::Environment::get().getWindowManager()->isGuiMode() + || MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running) + return; // Configure player movement according to keyboard input. Actual movement will @@ -574,8 +577,6 @@ namespace MWInput double x = arg.xrel * mCameraSensitivity * (1.0f/256.f); double y = arg.yrel * mCameraSensitivity * (1.0f/256.f) * (mInvertY ? -1 : 1) * mCameraYMultiplier; - float scale = MWBase::Environment::get().getFrameDuration(); - if(scale <= 0.0f) scale = 1.0f; float rot[3]; rot[0] = -y; @@ -585,8 +586,8 @@ namespace MWInput // Only actually turn player when we're not in vanity mode if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot)) { - mPlayer->yaw(x/scale); - mPlayer->pitch(-y/scale); + mPlayer->yaw(x); + mPlayer->pitch(-y); } if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change @@ -615,7 +616,7 @@ namespace MWInput void InputManager::windowClosed() { - MWBase::Environment::setRequestExit(); + MWBase::Environment::get().getStateManager()->requestQuit(); } void InputManager::toggleMainMenu() @@ -747,7 +748,8 @@ namespace MWInput void InputManager::showQuickKeysMenu() { - if (!MWBase::Environment::get().getWindowManager()->isGuiMode ()) + if (!MWBase::Environment::get().getWindowManager()->isGuiMode () + && MWBase::Environment::get().getWorld()->getGlobalFloat ("chargenstate")==-1) MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_QuickKeysMenu); else if (MWBase::Environment::get().getWindowManager()->getMode () == MWGui::GM_QuickKeysMenu) MWBase::Environment::get().getWindowManager()->removeGuiMode (MWGui::GM_QuickKeysMenu); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b80fa9d7c..1fb22ce63 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -757,7 +757,7 @@ namespace MWMechanics } } - void Actors::dropActors (const MWWorld::Ptr::CellStore *cellStore, const MWWorld::Ptr& ignore) + void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore) { PtrControllerMap::iterator iter = mActors.begin(); while(iter != mActors.end()) @@ -774,23 +774,56 @@ namespace MWMechanics void Actors::update (float duration, bool paused) { - if (!paused) + if(!paused) { + // Reset data from previous frame + for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + { + // Reset last hit object, which is only valid for one frame + // Note, the new hit object for this frame may be set by CharacterController::update -> Animation::runAnimation + // (below) + iter->first.getClass().getCreatureStats(iter->first).setLastHitObject(std::string()); + } + + // AI and magic effects update + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) + { + updateActor(iter->first, duration); + if(iter->first.getTypeName() == typeid(ESM::NPC).name()) + updateNpc(iter->first, duration, paused); + } + } + + // Looping magic VFX update + // Note: we need to do this before any of the animations are updated. + // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), + // so updating VFX immediately after that would just remove the particle effects instantly. + // There needs to be a magic effect update in between. + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + iter->second->updateContinuousVfx(); + + // Animation/movement update + for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( + ESM::MagicEffect::Paralyze).mMagnitude > 0) + iter->second->skipAnim(); + iter->second->update(duration); + } + + // Kill dead actors for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();iter++) { const MWWorld::Class &cls = MWWorld::Class::get(iter->first); CreatureStats &stats = cls.getCreatureStats(iter->first); - stats.setLastHitObject(std::string()); if(!stats.isDead()) { if(iter->second->isDead()) iter->second->resurrect(); - updateActor(iter->first, duration); - if(iter->first.getTypeName() == typeid(ESM::NPC).name()) - updateNpc(iter->first, duration, paused); - if(!stats.isDead()) continue; } @@ -799,65 +832,47 @@ namespace MWMechanics if(iter->first.getRefData().getHandle()=="player" && MWBase::Environment::get().getWorld()->getGodModeState()) { - MWMechanics::DynamicStat stat(stats.getHealth()); + MWMechanics::DynamicStat stat (stats.getHealth()); - if(stat.getModified()<1) + if (stat.getModified()<1) { stat.setModified(1, 0); stats.setHealth(stat); } - stats.resurrect(); continue; } - if(iter->second->isDead()) - continue; - - iter->second->kill(); - - // Apply soultrap - if (iter->first.getTypeName() == typeid(ESM::Creature).name()) - { - SoulTrap soulTrap (iter->first); - stats.getActiveSpells().visitEffectSources(soulTrap); - } - - // Reset magic effects and recalculate derived effects - // One case where we need this is to make sure bound items are removed upon death - stats.setMagicEffects(MWMechanics::MagicEffects()); - calculateCreatureStatModifiers(iter->first, 0); - // Make sure spell effects with CasterLinked flag are removed + // TODO: would be nice not to do this all the time... for(PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) { MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); spells.purge(iter->first.getRefData().getHandle()); } - ++mDeathCount[cls.getId(iter->first)]; + // FIXME: see http://bugs.openmw.org/issues/869 + MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); - if(cls.isEssential(iter->first)) - MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); + if (iter->second->kill()) + { + ++mDeathCount[cls.getId(iter->first)]; - } - } + // Apply soultrap + if (iter->first.getTypeName() == typeid(ESM::Creature).name()) + { + SoulTrap soulTrap (iter->first); + stats.getActiveSpells().visitEffectSources(soulTrap); + } - if(!paused) - { - // Note: we need to do this before any of the animations are updated. - // Reaching the text keys may trigger Hit / Spellcast (and as such, particles), - // so updating VFX immediately after that would just remove the particle effects instantly. - // There needs to be a magic effect update in between. - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) - iter->second->updateContinuousVfx(); + // Reset magic effects and recalculate derived effects + // One case where we need this is to make sure bound items are removed upon death + stats.setMagicEffects(MWMechanics::MagicEffects()); + calculateCreatureStatModifiers(iter->first, 0); - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) - { - if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( - ESM::MagicEffect::Paralyze).mMagnitude > 0) - iter->second->skipAnim(); - iter->second->update(duration); + if(cls.isEssential(iter->first)) + MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); + } } } } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index ab39e8f0f..3653587f8 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -1,23 +1,22 @@ #include "aicombat.hpp" -#include "aifollow.hpp" -#include "movement.hpp" +#include +#include + #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" +#include "../mwworld/inventorystore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" -#include "character.hpp" -#include "../mwworld/inventorystore.hpp" -#include "creaturestats.hpp" #include "npcstats.hpp" - -#include -#include +#include "steering.hpp" +#include "movement.hpp" +#include "character.hpp" // fixme: for getActiveWeapon namespace { @@ -43,7 +42,9 @@ namespace MWMechanics mReadyToAttack(false), mStrike(false), mCombatMove(false), - mMovement() + mRotate(false), + mMovement(), + mTargetAngle(0) { } @@ -68,10 +69,16 @@ namespace MWMechanics mCombatMove = false; } } + actor.getClass().getMovementSettings(actor) = mMovement; + + if (mRotate) + { + if (zTurn(actor, Ogre::Degree(mTargetAngle))) + mRotate = false; + } - //actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mReadyToAttack); mTimerAttack -= duration; actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); @@ -156,17 +163,12 @@ namespace MWMechanics weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) } - //MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false); - ESM::Position pos = actor.getRefData().getPosition(); - - float zAngle; - float rangeMelee; float rangeCloseUp; bool distantCombat = false; - if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_ThowWeapon) + if (weaptype==WeapType_BowAndArrow || weaptype==WeapType_Crossbow || weaptype==WeapType_Thrown) { rangeMelee = 1000; // TODO: should depend on archer skill rangeCloseUp = 0; //doesn't needed when attacking from distance @@ -189,12 +191,8 @@ namespace MWMechanics //Melee and Close-up combat vDir.z = 0; float dirLen = vDir.length(); - zAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees(); - - // TODO: use movement settings instead of rotating directly - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - - //MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees(); + mRotate = true; //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); if (mFollowTarget && distBetween > rangeMelee) @@ -206,7 +204,10 @@ namespace MWMechanics { //Melee: stop running and attack mMovement.mPosition[1] = 0; - chooseBestAttack(weapon, mMovement); + + // When attacking with a weapon, choose between slash, thrust or chop + if (actor.getClass().hasInventoryStore(actor)) + chooseBestAttack(weapon, mMovement); if(mMovement.mPosition[0] || mMovement.mPosition[1]) { @@ -237,12 +238,6 @@ namespace MWMechanics else { //target is at far distance: build path to target OR follow target (if previously actor had reached it once) - - /* - //apply when AIFOLLOW package implementation will be existent - if(mFollowTarget) - actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiFollow(mTarget));*/ - mFollowTarget = false; buildNewPath(actor); @@ -252,13 +247,10 @@ namespace MWMechanics //try shortcut if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) - zAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); + mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); else - zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - - // TODO: use movement settings instead of rotating directly - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - //mMovement.mRotation[2] = 10*(Ogre::Degree(zAngle).valueRadians()-pos.rot[2]); + mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); + mRotate = true; mMovement.mPosition[1] = 1; mReadyToAttack = false; @@ -294,6 +286,8 @@ namespace MWMechanics } } + actor.getClass().getMovementSettings(actor) = mMovement; + return false; } @@ -374,7 +368,7 @@ void chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement { if (weapon == NULL) { - //hand-to-hand and creatures' attacks deal equal damage for each type + //hand-to-hand deal equal damage for each type float roll = static_cast(rand())/RAND_MAX; if(roll <= 0.333f) //side punch { diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 27f7f5d95..767a36292 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -29,16 +29,21 @@ namespace MWMechanics private: PathFinder mPathFinder; - //controls duration of the actual strike + // controls duration of the actual strike float mTimerAttack; float mTimerReact; - //controls duration of the sideway & forward moves - //when mCombatMove is true + // controls duration of the sideway & forward moves + // when mCombatMove is true float mTimerCombatMove; + // the z rotation angle (degrees) we want to reach + // used every frame when mRotate is true + float mTargetAngle; + bool mReadyToAttack, mStrike; bool mFollowTarget; bool mCombatMove; + bool mRotate; MWMechanics::Movement mMovement; MWWorld::Ptr mTarget; diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 901b8c31d..bac258425 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -8,6 +8,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "steering.hpp" + namespace { float sgn(float a) @@ -33,7 +35,7 @@ namespace MWMechanics { mMaxDist = 470; - // The CS Help File states that if a duration is givin, the AI package will run for that long + // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) mDuration = 0; @@ -52,7 +54,7 @@ namespace MWMechanics { mMaxDist = 470; - // The CS Help File states that if a duration is givin, the AI package will run for that long + // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) mDuration = 0; @@ -89,25 +91,23 @@ namespace MWMechanics if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX) { int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX); - // Check if actor is near the border of an inactive cell. If so, disable AiEscort. - // FIXME: This *should* pause the AiEscort package instead of terminating it. + // Check if actor is near the border of an inactive cell. If so, pause walking. if(sideX * (pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / 2.0 - 200)) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return true; + return false; } } if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY) { int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY); - // Check if actor is near the border of an inactive cell. If so, disable AiEscort. - // FIXME: This *should* pause the AiEscort package instead of terminating it. + // Check if actor is near the border of an inactive cell. If so, pause walking. if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / 2.0 - 200)) { MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; - return true; + return false; } } @@ -151,8 +151,7 @@ namespace MWMechanics if(distanceBetweenResult <= mMaxDist * mMaxDist) { float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - // TODO: use movement settings instead of rotating directly - MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); + zTurn(actor, Ogre::Degree(zAngle)); MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; mMaxDist = 470; } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 10bff8356..cf5291fd3 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -6,15 +6,17 @@ #include "movement.hpp" #include - + +#include "steering.hpp" + MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) : mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) -{ -} -MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) -{ -} +{ +} +MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) +: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) +{ +} bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) { @@ -45,14 +47,14 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) } } - ESM::Pathgrid::Point dest; - dest.mX = target.getRefData().getPosition().pos[0]; - dest.mY = target.getRefData().getPosition().pos[1]; + ESM::Pathgrid::Point dest; + dest.mX = target.getRefData().getPosition().pos[0]; + dest.mY = target.getRefData().getPosition().pos[1]; dest.mZ = target.getRefData().getPosition().pos[2]; - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; + ESM::Pathgrid::Point start; + start.mX = pos.pos[0]; + start.mY = pos.pos[1]; start.mZ = pos.pos[2]; if(mPathFinder.getPath().empty()) @@ -88,18 +90,14 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) { - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - //MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mRotation[2] = 10*(Ogre::Degree(zAngle).valueRadians()-pos.rot[2]); - //std::cout << Ogre::Degree(zAngle).valueDegrees()-Ogre::Radian(actor.getRefData().getPosition().rot[2]).valueDegrees() << " "<< pos.rot[2] << " " << zAngle << "\n"; - //MWWorld::Class::get(actor).get - } - - if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2]) - < 100*100) - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + } + + if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2]) + < 100*100) + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; else - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; return false; } @@ -109,12 +107,12 @@ std::string MWMechanics::AiFollow::getFollowedActor() return mActorId; } -MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const -{ - return new AiFollow(*this); -} - - int MWMechanics::AiFollow::getTypeId() const -{ - return TypeIdFollow; +MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const +{ + return new AiFollow(*this); +} + + int MWMechanics::AiFollow::getTypeId() const +{ + return TypeIdFollow; } diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 989d9c6a2..2110393fd 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -23,7 +23,7 @@ void MWMechanics::AiSequence::copy (const AiSequence& sequence) mPackages.push_back ((*iter)->clone()); } -MWMechanics::AiSequence::AiSequence() : mDone (false) {} +MWMechanics::AiSequence::AiSequence() : mDone (false), mLastAiPackage(-1) {} MWMechanics::AiSequence::AiSequence (const AiSequence& sequence) : mDone (false) { @@ -84,6 +84,7 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration) { if (!mPackages.empty()) { + mLastAiPackage = mPackages.front()->getTypeId(); if (mPackages.front()->execute (actor,duration)) { delete *mPackages.begin(); @@ -91,7 +92,9 @@ void MWMechanics::AiSequence::execute (const MWWorld::Ptr& actor,float duration) mDone = true; } else + { mDone = false; + } } } } diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 351e04480..62f48f981 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -23,6 +23,9 @@ namespace MWMechanics void copy (const AiSequence& sequence); + // The type of AI package that ran last + int mLastAiPackage; + public: AiSequence(); @@ -36,6 +39,10 @@ namespace MWMechanics int getTypeId() const; ///< @see enum AiPackage::TypeId + int getLastRunTypeId() const { return mLastAiPackage; } + ///< Get the typeid of the Ai package that ran last, NOT the currently "active" Ai package that will be run in the next frame. + /// This difference is important when an Ai package has just finished and been removed. + bool getCombatTarget (std::string &targetActorId) const; ///< Return true and assign target if combat package is currently /// active, return false otherwise diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 9a1b98d8f..8a0b2ebd0 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,11 +1,12 @@ #include "aitravel.hpp" -#include "movement.hpp" - #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" +#include "steering.hpp" +#include "movement.hpp" + namespace { float sgn(float a) @@ -86,9 +87,7 @@ namespace MWMechanics return true; } - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - // TODO: use movement settings instead of rotating directly - world->rotateObject(actor, 0, 0, zAngle, false); + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); movement.mPosition[1] = 1; return false; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 2f8f1cbd4..77316fedf 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -11,6 +11,8 @@ #include "creaturestats.hpp" #include +#include "steering.hpp" + namespace { float sgn(float a) @@ -68,6 +70,7 @@ namespace MWMechanics bool AiWander::execute (const MWWorld::Ptr& actor,float duration) { actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false); MWBase::World *world = MWBase::Environment::get().getWorld(); if(mDuration) { @@ -188,15 +191,18 @@ namespace MWMechanics mIdleNow = true; // Play idle voiced dialogue entries randomly - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - float chance = store.get().find("fVoiceIdleOdds")->getFloat(); - int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - // Don't bother if the player is out of hearing range - if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + if (hello > 0) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + float chance = store.get().find("fVoiceIdleOdds")->getFloat(); + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 100; // [0, 99] + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + // Don't bother if the player is out of hearing range + if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } } } @@ -205,7 +211,7 @@ namespace MWMechanics // Play a random voice greeting if the player gets too close const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - float hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); + int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified(); float helloDistance = hello; int iGreetDistanceMultiplier = store.get().find("iGreetDistanceMultiplier")->getInt(); helloDistance *= iGreetDistanceMultiplier; @@ -281,11 +287,6 @@ namespace MWMechanics if(mWalking) { - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - // TODO: use movement settings instead of rotating directly - world->rotateObject(actor, 0, 0, zAngle, false); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { stopWalking(actor); @@ -293,6 +294,12 @@ namespace MWMechanics mWalking = false; mChooseAction = true; } + else + { + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; + } } return false; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7647a940e..c2a26ced3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -32,6 +32,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" @@ -118,7 +119,7 @@ static const struct WeaponInfo { { WeapType_TwoWide, "2w", "weapontwowide" }, { WeapType_BowAndArrow, "1h", "bowandarrow" }, { WeapType_Crossbow, "crossbow", "crossbow" }, - { WeapType_ThowWeapon, "1h", "throwweapon" }, + { WeapType_Thrown, "1h", "throwweapon" }, { WeapType_PickProbe, "1h", "pickprobe" }, { WeapType_Spell, "spell", "spellcast" }, }; @@ -156,7 +157,14 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat bool block = mPtr.getClass().getCreatureStats(mPtr).getBlock(); if(mHitState == CharState_None) { - if(knockdown) + if (mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() < 0) + { + mHitState = CharState_KnockOut; + mCurrentHit = "knockout"; + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul); + mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); + } + else if(knockdown) { mHitState = CharState_KnockDown; mCurrentHit = "knockdown"; @@ -186,6 +194,12 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat mPtr.getClass().getCreatureStats(mPtr).setBlock(false); mHitState = CharState_None; } + else if (mHitState == CharState_KnockOut && mPtr.getClass().getCreatureStats(mPtr).getFatigue().getCurrent() > 0) + { + mHitState = CharState_KnockDown; + mAnimation->disable(mCurrentHit); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0); + } } const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); @@ -301,12 +315,24 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if(!mCurrentMovement.empty()) { float vel, speedmult = 1.0f; + + bool isrunning = mPtr.getClass().getCreatureStats(mPtr).getStance(MWMechanics::CreatureStats::Stance_Run); + if(mMovementSpeed > 0.0f && (vel=mAnimation->getVelocity(mCurrentMovement)) > 1.0f) speedmult = mMovementSpeed / vel; - + else if (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight) + speedmult = 1.f; // TODO: should get a speed mult depending on the current turning speed + else if (mMovementSpeed > 0.0f) + // The first person anims don't have any velocity to calculate a speed multiplier from. + // We use the third person velocities instead. + // FIXME: should be pulled from the actual animation, but it is not presently loaded. + speedmult = mMovementSpeed / (isrunning ? 222.857f : 154.064f); mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); + + mMovementAnimVelocity = vel; } + else mMovementAnimVelocity = 0.0f; } } @@ -367,7 +393,7 @@ MWWorld::ContainerStoreIterator getActiveWeapon(CreatureStats &stats, MWWorld::I *weaptype = WeapType_Crossbow; break; case ESM::Weapon::MarksmanThrown: - *weaptype = WeapType_ThowWeapon; + *weaptype = WeapType_Thrown; break; } } @@ -403,6 +429,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim , mIdleState(CharState_None) , mMovementState(CharState_None) , mMovementSpeed(0.0f) + , mMovementAnimVelocity(0.0f) , mDeathState(CharState_None) , mHitState(CharState_None) , mUpperBodyState(UpperCharState_Nothing) @@ -479,7 +506,14 @@ bool CharacterController::updateCreatureState() { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); - determineAttackType(); + // These are unique animations and not linked to movement type. Just pick one randomly. + int roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 3; // [0, 2] + if (roll == 0) + mCurrentWeapon = "attack1"; + else if (roll == 1) + mCurrentWeapon = "attack2"; + else + mCurrentWeapon = "attack3"; mAnimation->play(mCurrentWeapon, Priority_Weapon, MWRender::Animation::Group_All, true, @@ -527,6 +561,7 @@ bool CharacterController::updateWeaponState() { getWeaponGroup(weaptype, weapgroup); mAnimation->showWeapons(false); + mAnimation->setWeaponGroup(weapgroup); mAnimation->play(weapgroup, Priority_Weapon, MWRender::Animation::Group_UpperBody, true, @@ -581,6 +616,19 @@ bool CharacterController::updateWeaponState() if(isWeapon) weapSpeed = weapon->get()->mBase->mData.mSpeed; + // Cancel attack if we no longer have ammunition + bool ammunition = true; + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (mWeaponType == WeapType_Crossbow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); + else if (mWeaponType == WeapType_BowAndArrow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); + if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } + float complete; bool animPlaying; if(stats.getAttackingOrSpell()) @@ -685,14 +733,15 @@ bool CharacterController::updateWeaponState() if(item.getRefData().getCount()) MWBase::Environment::get().getWindowManager()->setSelectedWeapon(item); } - else + else if (ammunition) { if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || - mWeaponType == WeapType_ThowWeapon) + mWeaponType == WeapType_Thrown) mAttackType = "shoot"; else { - if(isWeapon && Settings::Manager::getBool("best attack", "Game")) + if(isWeapon && mPtr.getRefData().getHandle() == "player" && + Settings::Manager::getBool("best attack", "Game")) mAttackType = getBestAttack(weapon->get()->mBase); else determineAttackType(); @@ -751,12 +800,59 @@ bool CharacterController::updateWeaponState() } } + mAnimation->setPitchFactor(0.f); + if (mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + { + switch (mUpperBodyState) + { + case UpperCharState_StartToMinAttack: + mAnimation->setPitchFactor(complete); + break; + case UpperCharState_MinAttackToMaxAttack: + case UpperCharState_MaxAttackToMinHit: + case UpperCharState_MinHitToHit: + mAnimation->setPitchFactor(1.f); + break; + case UpperCharState_FollowStartToFollowStop: + if (animPlaying) + mAnimation->setPitchFactor(1.f-complete); + break; + default: + break; + } + } + else if (mWeaponType == WeapType_Crossbow) + { + switch (mUpperBodyState) + { + case UpperCharState_EquipingWeap: + mAnimation->setPitchFactor(complete); + break; + case UpperCharState_UnEquipingWeap: + mAnimation->setPitchFactor(1.f-complete); + break; + case UpperCharState_WeapEquiped: + case UpperCharState_StartToMinAttack: + case UpperCharState_MinAttackToMaxAttack: + case UpperCharState_MaxAttackToMinHit: + case UpperCharState_MinHitToHit: + case UpperCharState_FollowStartToFollowStop: + mAnimation->setPitchFactor(1.f); + break; + default: + break; + } + } + if(!animPlaying) { if(mUpperBodyState == UpperCharState_EquipingWeap || mUpperBodyState == UpperCharState_FollowStartToFollowStop || mUpperBodyState == UpperCharState_CastingSpell) { + if (ammunition && mWeaponType == WeapType_Crossbow) + mAnimation->attachArrow(); + mUpperBodyState = UpperCharState_WeapEquiped; //don't allow to continue playing hit animation on UpperBody after actor had attacked during it if(mHitState == CharState_Hit) @@ -1136,18 +1232,17 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - if(mHitState != CharState_KnockDown) + rot *= Ogre::Math::RadiansToDegrees(1.0f); + if(mHitState != CharState_KnockDown && mHitState != CharState_KnockOut) { - rot *= duration * Ogre::Math::RadiansToDegrees(1.0f); world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); } else //avoid z-rotating for knockdown world->rotateObject(mPtr, rot.x, rot.y, 0.0f, true); - // all actual movement in 3rd person controlled by animations, except for jump - // !mAnimation->hasAnimation("death1") identifies 1st person mode - if(mJumpState != JumpState_None || vec.z > 0 - || (mPtr.getRefData().getHandle() == "player" && !mAnimation->hasAnimation("death1"))) + // always control actual movement by animation unless this: + // FIXME: actor falling/landing should be controlled by physics engine + if(mMovementAnimVelocity == 0.0f && (vec.length() > 0.0f || mJumpState != JumpState_None)) { world->queueMovement(mPtr, vec); } @@ -1158,7 +1253,6 @@ void CharacterController::update(float duration) } else if(cls.getCreatureStats(mPtr).isDead()) { - MWBase::Environment::get().getWorld()->enableActorCollision(mPtr, false); world->queueMovement(mPtr, Ogre::Vector3(0.0f)); } @@ -1260,10 +1354,17 @@ void CharacterController::forceStateUpdate() } } -void CharacterController::kill() +bool CharacterController::kill() { - if(mDeathState != CharState_None) - return; + if( isDead() ) + { + //player's death animation is over + if( mPtr.getRefData().getHandle()=="player" && !isAnimPlaying(mCurrentDeath) ) + { + MWBase::Environment::get().getStateManager()->askLoadRecent(); + } + return false; + } playRandomDeath(); @@ -1274,6 +1375,8 @@ void CharacterController::kill() mIdleState = CharState_None; mCurrentIdle.clear(); + + return true; } void CharacterController::resurrect() @@ -1338,15 +1441,6 @@ void CharacterController::determineAttackType() else mAttackType = "chop"; } - else - { - if (move[0] && !move[1]) //sideway - mCurrentWeapon = "attack2"; - else if (move[1]) //forward - mCurrentWeapon = "attack3"; - else - mCurrentWeapon = "attack1"; - } } } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index b89cb0c12..4009744ef 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -93,6 +93,7 @@ enum CharacterState { CharState_Hit, CharState_KnockDown, + CharState_KnockOut, CharState_Block }; @@ -105,7 +106,7 @@ enum WeaponType { WeapType_TwoWide, WeapType_BowAndArrow, WeapType_Crossbow, - WeapType_ThowWeapon, + WeapType_Thrown, WeapType_PickProbe, WeapType_Spell @@ -144,6 +145,7 @@ class CharacterController CharacterState mMovementState; std::string mCurrentMovement; float mMovementSpeed; + float mMovementAnimVelocity; CharacterState mDeathState; std::string mCurrentDeath; @@ -198,7 +200,7 @@ public: void skipAnim(); bool isAnimPlaying(const std::string &groupName); - void kill(); + bool kill(); void resurrect(); bool isDead() const { return mDeathState != CharState_None; } diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 30db59311..8f890befb 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -10,7 +10,7 @@ namespace MWMechanics { CreatureStats::CreatureStats() - : mLevel (0), mLevelHealthBonus(0.f), mDead (false), mDied (false), mFriendlyHits (0), + : mLevel (0), mDead (false), mDied (false), mFriendlyHits (0), mTalkedTo (false), mAlarmed (false), mAttacked (false), mHostile (false), mAttackingOrSpell(false), @@ -22,35 +22,6 @@ namespace MWMechanics mAiSettings[i] = 0; } - float CreatureStats::getLevelHealthBonus () const - { - return mLevelHealthBonus; - } - - void CreatureStats::levelUp() - { - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - - const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); - - // "When you gain a level, in addition to increasing three primary attributes, your Health - // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, - // the Health increase is calculated from the increased Endurance" - mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat(); - updateHealth(); - - mLevel++; - } - - void CreatureStats::updateHealth() - { - const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); - const int strength = getAttribute(ESM::Attribute::Strength).getBase(); - - setHealth(static_cast (0.5 * (strength + endurance)) + mLevelHealthBonus); - } - const AiSequence& CreatureStats::getAiSequence() const { return mAiSequence; @@ -208,9 +179,6 @@ namespace MWMechanics mDynamic[index] = value; - if (index == 2 && value.getCurrent() < 0) - setKnockedDown(true); - if (index==0 && mDynamic[index].getCurrent()<1) { if (!mDead) diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 67d925a19..bb9583301 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -22,13 +22,11 @@ namespace MWMechanics DrawState_ mDrawState; AttributeValue mAttributes[8]; DynamicStat mDynamic[3]; // health, magicka, fatigue - int mLevel; Spells mSpells; ActiveSpells mActiveSpells; MagicEffects mMagicEffects; Stat mAiSettings[4]; AiSequence mAiSequence; - float mLevelHealthBonus; bool mDead; bool mDied; int mFriendlyHits; @@ -54,6 +52,7 @@ namespace MWMechanics protected: bool mIsWerewolf; AttributeValue mWerewolfAttributes[8]; + int mLevel; public: CreatureStats(); @@ -142,14 +141,6 @@ namespace MWMechanics float getFatigueTerm() const; ///< Return effective fatigue - float getLevelHealthBonus() const; - - void levelUp(); - - void updateHealth(); - ///< Calculate health based on endurance and strength. - /// Called at character creation and at level up. - bool isDead() const; bool hasDied() const; diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 6c765aa41..87337cdd7 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -250,7 +250,10 @@ namespace MWMechanics { if (itemEmpty()) return 0; - return MWWorld::Class::get(mOldItemPtr).getEnchantmentPoints(mOldItemPtr); + + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + + return mOldItemPtr.getClass().getEnchantmentPoints(mOldItemPtr) * store.get().find("fEnchantmentMult")->getFloat(); } bool Enchanting::soulEmpty() const { @@ -274,22 +277,18 @@ namespace MWMechanics float Enchanting::getEnchantChance() const { - /* - Formula from http://www.uesp.net/wiki/Morrowind:Enchant - */ - const CreatureStats& creatureStats = MWWorld::Class::get (mEnchanter).getCreatureStats (mEnchanter); const NpcStats& npcStats = MWWorld::Class::get (mEnchanter).getNpcStats (mEnchanter); float chance1 = (npcStats.getSkill (ESM::Skill::Enchant).getModified() + - (0.25 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified()) - + (0.125 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified())); + (0.25 * npcStats.getAttribute (ESM::Attribute::Intelligence).getModified()) + + (0.125 * npcStats.getAttribute (ESM::Attribute::Luck).getModified())); + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + + float chance2 = 7.5 / (gmst.find("fEnchantmentChanceMult")->getFloat() * ((mCastStyle == ESM::Enchantment::ConstantEffect) ? + gmst.find("fEnchantmentConstantChanceMult")->getFloat() : 1 )) + * getEnchantPoints(); - float chance2 = 2.5 * getEnchantPoints(); - if(mCastStyle==ESM::Enchantment::ConstantEffect) - { - float constantChance = MWBase::Environment::get().getWorld()->getStore().get().find ("fEnchantmentConstantChanceMult")->getFloat(); - chance2 /= constantChance; - } return (chance1-chance2); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fc9ea08b9..4c8f35edb 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -760,6 +760,14 @@ namespace MWMechanics return mAI; } + void MechanicsManager::playerLoaded() + { + mUpdatePlayer = true; + mClassSelected = true; + mRaceSelected = true; + mAI = true; + } + bool MechanicsManager::sleepInBed(const MWWorld::Ptr &ptr, const MWWorld::Ptr &bed) { MWWorld::Ptr victim; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 4cfbd0134..761caf586 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -123,22 +123,24 @@ namespace MWMechanics /// @return was it illegal, and someone saw you doing it? virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed); - virtual void forceStateUpdate(const MWWorld::Ptr &ptr); + virtual void forceStateUpdate(const MWWorld::Ptr &ptr); - virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); - virtual void skipAnimation(const MWWorld::Ptr& ptr); - virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); + virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + virtual void skipAnimation(const MWWorld::Ptr& ptr); + virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) virtual void updateMagicEffects (const MWWorld::Ptr& ptr); - virtual void toggleAI(); - virtual bool isAIActive(); + virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects); - virtual void getObjectsInRange (const Ogre::Vector3& position, float radius, std::vector& objects); + virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); - virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); + virtual void toggleAI(); + virtual bool isAIActive(); + + virtual void playerLoaded(); }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index e41ce2078..e642ffc5a 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -30,6 +30,7 @@ MWMechanics::NpcStats::NpcStats() , mProfit(0) , mTimeToStartDrowning(20.0) , mLastDrowningHit(0) +, mLevelHealthBonus(0) { mSkillIncreases.resize (ESM::Attribute::Length, 0); } @@ -189,22 +190,31 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas base += 1; - // if this is a major or minor skill of the class, increase level progress - bool levelProgress = false; - for (int i=0; i<2; ++i) - for (int j=0; j<5; ++j) + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + // is this a minor or major skill? + int increase = gmst.find("iLevelupMiscMultAttriubte")->getInt(); // Note: GMST has a typo + for (int k=0; k<5; ++k) + { + if (class_.mData.mSkills[k][0] == skillIndex) { - int skill = class_.mData.mSkills[j][i]; - if (skill == skillIndex) - levelProgress = true; + mLevelProgress += gmst.find("iLevelUpMinorMult")->getInt(); + increase = gmst.find("iLevelUpMajorMultAttribute")->getInt(); } + } + for (int k=0; k<5; ++k) + { + if (class_.mData.mSkills[k][1] == skillIndex) + { + mLevelProgress += gmst.find("iLevelUpMajorMult")->getInt(); + increase = gmst.find("iLevelUpMinorMultAttribute")->getInt(); + } + } - mLevelProgress += levelProgress; - - // check the attribute this skill belongs to const ESM::Skill* skill = MWBase::Environment::get().getWorld ()->getStore ().get().find(skillIndex); - ++mSkillIncreases[skill->mData.mAttribute]; + mSkillIncreases[skill->mData.mAttribute] += increase; // Play sound & skill progress notification /// \todo check if character is the player, if levelling is ever implemented for NPCs @@ -216,7 +226,7 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas % static_cast (base); MWBase::Environment::get().getWindowManager ()->messageBox(message.str()); - if (mLevelProgress >= 10) + if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt()) { // levelup is possible now MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}"); @@ -237,22 +247,43 @@ void MWMechanics::NpcStats::levelUp() mLevelProgress -= 10; for (int i=0; i &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + + const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); + + // "When you gain a level, in addition to increasing three primary attributes, your Health + // will automatically increase by 10% of your Endurance attribute. If you increased Endurance this level, + // the Health increase is calculated from the increased Endurance" + mLevelHealthBonus += endurance * gmst.find("fLevelUpHealthEndMult")->getFloat(); + updateHealth(); + + setLevel(getLevel()+1); +} + +void MWMechanics::NpcStats::updateHealth() +{ + const int endurance = getAttribute(ESM::Attribute::Endurance).getBase(); + const int strength = getAttribute(ESM::Attribute::Strength).getBase(); + + setHealth(static_cast (0.5 * (strength + endurance)) + mLevelHealthBonus); } int MWMechanics::NpcStats::getLevelupAttributeMultiplier(int attribute) const { - // Source: http://www.uesp.net/wiki/Morrowind:Level#How_to_Level_Up int num = mSkillIncreases[attribute]; - if (num <= 1) + + if (num == 0) return 1; - else if (num <= 4) - return 2; - else if (num <= 7) - return 3; - else if (num <= 9) - return 4; - else - return 5; + + num = std::min(10, num); + + // iLevelUp01Mult - iLevelUp10Mult + std::stringstream gmst; + gmst << "iLevelUp" << std::setfill('0') << std::setw(2) << num << "Mult"; + + return MWBase::Environment::get().getWorld()->getStore().get().find(gmst.str())->getInt(); } void MWMechanics::NpcStats::flagAsUsed (const std::string& id) diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 8cdeeea5d..d7db999e4 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -51,6 +51,8 @@ namespace MWMechanics /// time since last hit from drowning float mLastDrowningHit; + float mLevelHealthBonus; + public: NpcStats(); @@ -98,6 +100,10 @@ namespace MWMechanics void levelUp(); + void updateHealth(); + ///< Calculate health based on endurance and strength. + /// Called at character creation and at level up. + void flagAsUsed (const std::string& id); bool hasBeenUsed (const std::string& id) const; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index b09574923..ba35af777 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -55,7 +55,7 @@ void Objects::updateObject(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) } } -void Objects::dropObjects (const MWWorld::Ptr::CellStore *cellStore) +void Objects::dropObjects (const MWWorld::CellStore *cellStore) { PtrControllerMap::iterator iter = mObjects.begin(); while(iter != mObjects.end()) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 70fa197d0..4407363a6 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -158,7 +158,9 @@ namespace namespace MWMechanics { PathFinder::PathFinder() - :mIsPathConstructed(false),mIsGraphConstructed(false) + : mIsPathConstructed(false), + mIsGraphConstructed(false), + mCell(NULL) { } diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index a3ac22012..8771ef0ca 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -3,7 +3,6 @@ #include #include -#include namespace MWWorld { @@ -26,8 +25,10 @@ namespace MWMechanics bool checkPathCompleted(float x, float y, float z); ///< \Returns true if the last point of the path has been reached. + bool checkWaypoint(float x, float y, float z); ///< \Returns true if a way point was reached + float getZAngleToNext(float x, float y) const; float getDistToNext(float x, float y, float z); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 749a5d7b1..0dec49f13 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -554,7 +554,10 @@ namespace MWMechanics else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes) { if (mCaster.getRefData().getHandle() == "player") + { MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge + mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3); + } } inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); diff --git a/apps/openmw/mwmechanics/steering.cpp b/apps/openmw/mwmechanics/steering.cpp new file mode 100644 index 000000000..d911fd81b --- /dev/null +++ b/apps/openmw/mwmechanics/steering.cpp @@ -0,0 +1,43 @@ +#include "steering.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/ptr.hpp" + +#include "../mwbase/environment.hpp" + +#include "movement.hpp" + +namespace MWMechanics +{ + +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle) +{ + Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]); + Ogre::Radian diff (targetAngle - currentAngle); + if (diff >= Ogre::Degree(180)) + { + // Turning the other way would be a better idea + diff = diff-Ogre::Degree(360); + } + else if (diff <= Ogre::Degree(-180)) + { + diff = Ogre::Degree(360)-diff; + } + Ogre::Radian absDiff = Ogre::Math::Abs(diff); + + // The turning animation actually moves you slightly, so the angle will be wrong again. + // Use epsilon to prevent jerkiness. + const Ogre::Degree epsilon (0.5); + if (absDiff < epsilon) + return true; + + // Max. speed of 10 radian per sec + Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration(); + if (absDiff > limit) + diff = Ogre::Math::Sign(diff) * limit; + + actor.getClass().getMovementSettings(actor).mRotation[2] = diff.valueRadians(); + return false; +} + +} diff --git a/apps/openmw/mwmechanics/steering.hpp b/apps/openmw/mwmechanics/steering.hpp new file mode 100644 index 000000000..504dc3ac3 --- /dev/null +++ b/apps/openmw/mwmechanics/steering.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_MECHANICS_STEERING_H + +#include + +namespace MWWorld +{ +class Ptr; +} + +namespace MWMechanics +{ + +/// configure rotation settings for an actor to reach this target angle (eventually) +/// @return have we reached the target angle? +bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle); + +} + +#endif diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index 4955dd6cb..820ba8acc 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -127,7 +127,7 @@ bool Actors::deleteObject (const MWWorld::Ptr& ptr) return true; } -void Actors::removeCell(MWWorld::Ptr::CellStore* store) +void Actors::removeCell(MWWorld::CellStore* store) { for(PtrAnimationMap::iterator iter = mAllActors.begin();iter != mAllActors.end();) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index c62515c8c..44bba90d0 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -53,7 +53,6 @@ void Animation::EffectAnimationTime::setValue(Ogre::Real) Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) : mPtr(ptr) - , mCamera(NULL) , mInsert(node) , mSkelBase(NULL) , mAccumRoot(NULL) @@ -532,12 +531,6 @@ static void updateBoneTree(const Ogre::SkeletonInstance *skelsrc, Ogre::Bone *bo bone->setScale(Ogre::Vector3::UNIT_SCALE); } } - else - { - // No matching bone in the source. Make sure it stays properly offset - // from its parent. - bone->resetToInitialState(); - } Ogre::Node::ChildNodeIterator boneiter = bone->getChildIterator(); while(boneiter.hasMoreElements()) @@ -702,6 +695,12 @@ void Animation::handleTextKey(AnimState &state, const std::string &groupname, co else mPtr.getClass().hit(mPtr); } + else if (evt.compare(off, len, "shoot attach") == 0) + attachArrow(); + else if (evt.compare(off, len, "shoot release") == 0) + releaseArrow(); + else if (evt.compare(off, len, "shoot follow attach") == 0) + attachArrow(); else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release") MWBase::Environment::get().getWorld()->castSpell(mPtr); @@ -876,6 +875,27 @@ bool Animation::getInfo(const std::string &groupname, float *complete, float *sp return true; } +float Animation::getStartTime(const std::string &groupname) const +{ + AnimSourceList::const_iterator iter(mAnimSources.begin()); + for(;iter != mAnimSources.end();iter++) + { + const NifOgre::TextKeyMap &keys = (*iter)->mTextKeys; + NifOgre::TextKeyMap::const_iterator found = findGroupStart(keys, groupname); + if(found != keys.end()) + return found->first; + } + return -1.f; +} + +float Animation::getCurrentTime(const std::string &groupname) const +{ + AnimStateMap::const_iterator iter = mStates.find(groupname); + if(iter == mStates.end()) + return -1.f; + + return iter->second.mTime; +} void Animation::disable(const std::string &groupname) { diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index da1c1628c..c0cb18010 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -121,7 +121,6 @@ protected: std::vector mEffects; MWWorld::Ptr mPtr; - Camera *mCamera; Ogre::SceneNode *mInsert; Ogre::Entity *mSkelBase; @@ -271,27 +270,37 @@ public: */ bool getInfo(const std::string &groupname, float *complete=NULL, float *speedmult=NULL) const; + /// Get the absolute position in the animation track of the first text key with the given group. + float getStartTime(const std::string &groupname) const; + + /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. + float getCurrentTime(const std::string& groupname) const; + /** Disables the specified animation group; * \param groupname Animation group to disable. */ void disable(const std::string &groupname); void changeGroups(const std::string &groupname, int group); + virtual void setWeaponGroup(const std::string& group) {} + /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(const std::string &groupname) const; + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + virtual void setPitchFactor(float factor) {} + virtual Ogre::Vector3 runAnimation(float duration); virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool show) {} - + virtual void attachArrow() {} + virtual void releaseArrow() {} void enableLights(bool enable); Ogre::AxisAlignedBox getWorldBounds(); - void setCamera(Camera *cam) - { mCamera = cam; } - Ogre::Node *getNode(const std::string &name); // Attaches the given object to a bone on this object's base skeleton. If the bone doesn't diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 9c8387b83..9ae9c5878 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -338,11 +338,9 @@ namespace MWRender if(mAnimation && mAnimation != anim) { mAnimation->setViewMode(NpcAnimation::VM_Normal); - mAnimation->setCamera(NULL); mAnimation->detachObjectFromBone(mCamera); } mAnimation = anim; - mAnimation->setCamera(this); processViewChange(); } diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 32145928e..280828652 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -128,7 +128,7 @@ namespace MWRender InventoryPreview::InventoryPreview(MWWorld::Ptr character) - : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 62, -200), Ogre::Vector3(0, 62, 0)) + : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0)) , mSelectionBuffer(NULL) { } @@ -160,7 +160,10 @@ namespace MWRender if(type == ESM::Weapon::ShortBladeOneHand || type == ESM::Weapon::LongBladeOneHand || type == ESM::Weapon::BluntOneHand || - type == ESM::Weapon::AxeOneHand) + type == ESM::Weapon::AxeOneHand || + type == ESM::Weapon::MarksmanThrown || + type == ESM::Weapon::MarksmanCrossbow || + type == ESM::Weapon::MarksmanBow) groupname = "inventoryweapononehand"; else if(type == ESM::Weapon::LongBladeTwoHand || type == ESM::Weapon::BluntTwoClose || diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index b318c2d56..2b61e109b 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -185,14 +185,14 @@ bool Debugging::toggleRenderMode (int mode){ return false; } -void Debugging::cellAdded(MWWorld::Ptr::CellStore *store) +void Debugging::cellAdded(MWWorld::CellStore *store) { mActiveCells.push_back(store); if (mPathgridEnabled) enableCellPathgrid(store); } -void Debugging::cellRemoved(MWWorld::Ptr::CellStore *store) +void Debugging::cellRemoved(MWWorld::CellStore *store) { mActiveCells.erase(std::remove(mActiveCells.begin(), mActiveCells.end(), store), mActiveCells.end()); if (mPathgridEnabled) @@ -227,7 +227,7 @@ void Debugging::togglePathgrid() } } -void Debugging::enableCellPathgrid(MWWorld::Ptr::CellStore *store) +void Debugging::enableCellPathgrid(MWWorld::CellStore *store) { const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*store->mCell); @@ -254,7 +254,7 @@ void Debugging::enableCellPathgrid(MWWorld::Ptr::CellStore *store) } } -void Debugging::disableCellPathgrid(MWWorld::Ptr::CellStore *store) +void Debugging::disableCellPathgrid(MWWorld::CellStore *store) { if (store->mCell->isExterior()) { diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 120a83fae..018dc082a 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -12,6 +12,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -60,8 +62,6 @@ namespace MWRender loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1)); loadingListener->setProgress(0); - mExploredBuffer.resize((mMaxX-mMinX+1) * (mMaxY-mMinY+1) * 4); - //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png")) if (1) { @@ -170,21 +170,10 @@ namespace MWRender tex->load(); - - mOverlayTexture = Ogre::TextureManager::getSingleton().createManual("GlobalMapOverlay", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_A8B8G8R8, Ogre::TU_DYNAMIC_WRITE_ONLY); - - std::vector buffer; - buffer.resize(mWidth * mHeight); - - // initialize to (0, 0, 0, 0) - for (int p=0; pgetBuffer()->lock(Ogre::HardwareBuffer::HBL_DISCARD), &buffer[0], mWidth*mHeight*4); - mOverlayTexture->getBuffer()->unlock(); + clear(); loadingListener->loadingOff(); } @@ -227,9 +216,124 @@ namespace MWRender if (!localMapTexture.isNull()) { - mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,512,512), Ogre::Image::Box(originX,originY,originX+24,originY+24)); } } + + void GlobalMap::clear() + { + std::vector buffer; + // initialize to (0,0,0,0) + buffer.resize(mWidth * mHeight, 0); + + Ogre::PixelBox pb(mWidth, mHeight, 1, Ogre::PF_A8B8G8R8, &buffer[0]); + + mOverlayTexture->getBuffer()->blitFromMemory(pb); + } + + void GlobalMap::write(ESM::ESMWriter &writer) + { + ESM::GlobalMap map; + map.mBounds.mMinX = mMinX; + map.mBounds.mMaxX = mMaxX; + map.mBounds.mMinY = mMinY; + map.mBounds.mMaxY = mMaxY; + + Ogre::Image image; + mOverlayTexture->convertToImage(image); + Ogre::DataStreamPtr encoded = image.encode("png"); + map.mImageData.resize(encoded->size()); + encoded->read(&map.mImageData[0], encoded->size()); + + writer.startRecord(ESM::REC_GMAP); + map.save(writer); + writer.endRecord(ESM::REC_GMAP); + } + + void GlobalMap::readRecord(ESM::ESMReader &reader, int32_t type, std::vector >& exploredCells) + { + if (type == ESM::REC_GMAP) + { + ESM::GlobalMap map; + map.load(reader); + + const ESM::GlobalMap::Bounds& bounds = map.mBounds; + + if (bounds.mMaxX-bounds.mMinX <= 0) + return; + if (bounds.mMaxY-bounds.mMinY <= 0) + return; + + if (bounds.mMinX > bounds.mMaxX + || bounds.mMinY > bounds.mMaxY) + throw std::runtime_error("invalid map bounds"); + + Ogre::Image image; + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&map.mImageData[0], map.mImageData.size())); + image.load(stream, "png"); + + int xLength = (bounds.mMaxX-bounds.mMinX+1); + int yLength = (bounds.mMaxY-bounds.mMinY+1); + + // Size of one cell in image space + int cellImageSizeSrc = image.getWidth() / xLength; + if (int(image.getHeight() / yLength) != cellImageSizeSrc) + throw std::runtime_error("cell size must be quadratic"); + + // Determine which cells were explored by reading the image data + for (int x=0; x < xLength; ++x) + { + for (int y=0; y < yLength; ++y) + { + unsigned int imageX = (x) * cellImageSizeSrc; + // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is + unsigned int imageY = (yLength - (y + 1)) * cellImageSizeSrc; + + assert(imageX < image.getWidth()); + assert(imageY < image.getHeight()); + + if (image.getColourAt(imageX, imageY, 0).a > 0) + exploredCells.push_back(std::make_pair(x+bounds.mMinX,y+bounds.mMinY)); + } + } + + // If cell bounds of the currently loaded content and the loaded savegame do not match, + // we need to resize source/dest boxes to accommodate + // This means nonexisting cells will be dropped silently + int cellImageSizeDst = 24; + + // Completely off-screen? -> no need to blit anything + if (bounds.mMaxX < mMinX + || bounds.mMaxY < mMinY + || bounds.mMinX > mMaxX + || bounds.mMinY > mMaxY) + return; + + int leftDiff = (mMinX - bounds.mMinX); + int topDiff = (bounds.mMaxY - mMaxY); + int rightDiff = (bounds.mMaxX - mMaxX); + int bottomDiff = (mMinY - bounds.mMinY); + Ogre::Image::Box srcBox ( std::max(0, leftDiff * cellImageSizeSrc), + std::max(0, topDiff * cellImageSizeSrc), + std::min(image.getWidth(), image.getWidth() - rightDiff * cellImageSizeSrc), + std::min(image.getHeight(), image.getHeight() - bottomDiff * cellImageSizeSrc)); + + Ogre::Image::Box destBox ( std::max(0, -leftDiff * cellImageSizeDst), + std::max(0, -topDiff * cellImageSizeDst), + std::min(mOverlayTexture->getWidth(), mOverlayTexture->getWidth() + rightDiff * cellImageSizeDst), + std::min(mOverlayTexture->getHeight(), mOverlayTexture->getHeight() + bottomDiff * cellImageSizeDst)); + + // Looks like there is no interface for blitting from memory with src/dst boxes. + // So we create a temporary texture for blitting. + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().createManual("@temp", + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, image.getWidth(), + image.getHeight(), 0, Ogre::PF_A8B8G8R8); + tex->loadImage(image); + + mOverlayTexture->getBuffer()->blit(tex->getBuffer(), srcBox, destBox); + + Ogre::TextureManager::getSingleton().remove("@temp"); + } + } } diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index aad9adcc4..5fe878cd4 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -10,6 +10,12 @@ namespace Loading class Listener; } +namespace ESM +{ + class ESMWriter; + class ESMReader; +} + namespace MWRender { @@ -31,13 +37,18 @@ namespace MWRender void exploreCell (int cellX, int cellY); + /// Clears the overlay + void clear(); + + void write (ESM::ESMWriter& writer); + void readRecord (ESM::ESMReader& reader, int32_t type, std::vector >& exploredCells); + private: std::string mCacheDir; std::vector< std::pair > mExploredCells; Ogre::TexturePtr mOverlayTexture; - std::vector mExploredBuffer; int mWidth; int mHeight; diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 3ea3380e8..f147ae7b7 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -79,7 +79,7 @@ std::string LocalMap::coordStr(const int x, const int y) return StringConverter::toString(x) + "_" + StringConverter::toString(y); } -void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) +void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { if (!mInterior) { @@ -108,7 +108,7 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) } } -void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax) +void LocalMap::requestMap(MWWorld::CellStore* cell, float zMin, float zMax) { mInterior = false; @@ -125,7 +125,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax) render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name); } -void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, +void LocalMap::requestMap(MWWorld::CellStore* cell, AxisAlignedBox bounds) { // if we're in an empty cell, don't bother rendering anything diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index bcb6a374c..bcbcbc737 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -71,6 +71,27 @@ float HeadAnimationTime::getValue() const return 1; } +float WeaponAnimationTime::getValue() const +{ + if (mWeaponGroup.empty()) + return 0; + float current = mAnimation->getCurrentTime(mWeaponGroup); + if (current == -1) + return 0; + return current - mStartTime; +} + +void WeaponAnimationTime::setGroup(const std::string &group) +{ + mWeaponGroup = group; + mStartTime = mAnimation->getStartTime(mWeaponGroup); +} + +void WeaponAnimationTime::updateStartTime() +{ + setGroup(mWeaponGroup); +} + static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; @@ -121,11 +142,13 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mShowCarriedLeft(true), mFirstPersonOffset(0.f, 0.f, 0.f), mAlpha(1.f), - mNpcType(Type_Normal) + mNpcType(Type_Normal), + mPitchFactor(0) { mNpc = mPtr.get()->mBase; mHeadAnimationTime = Ogre::SharedPtr(new HeadAnimationTime(mPtr)); + mWeaponAnimationTime = Ogre::SharedPtr(new WeaponAnimationTime(this)); for(size_t i = 0;i < ESM::PRT_Count;i++) { @@ -223,6 +246,8 @@ void NpcAnimation::updateNpcBase() for(size_t i = 0;i < ESM::PRT_Count;i++) removeIndividualPart((ESM::PartReferenceType)i); updateParts(); + + mWeaponAnimationTime->updateStartTime(); } void NpcAnimation::updateParts() @@ -498,16 +523,25 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) Ogre::Vector3 ret = Animation::runAnimation(timepassed); Ogre::SkeletonInstance *baseinst = mSkelBase->getSkeleton(); - if(mViewMode == VM_FirstPerson && mCamera) + if(mViewMode == VM_FirstPerson) { - float pitch = mCamera->getPitch(); + float pitch = mPtr.getRefData().getPosition().rot[0]; Ogre::Node *node = baseinst->getBone("Bip01 Neck"); - node->pitch(Ogre::Radian(pitch*0.75f), Ogre::Node::TS_WORLD); + node->pitch(Ogre::Radian(pitch), Ogre::Node::TS_WORLD); // This has to be done before this function ends; // updateSkeletonInstance, below, touches the hands. node->translate(mFirstPersonOffset, Ogre::Node::TS_WORLD); } + else if (mPitchFactor > 0) + { + // In third person mode we may still need pitch for ranged weapon targeting + float pitch = mPtr.getRefData().getPosition().rot[0] * mPitchFactor; + Ogre::Node *node = baseinst->getBone("Bip01 Spine2"); + node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD); + node = baseinst->getBone("Bip01 Spine1"); + node->pitch(Ogre::Radian(pitch/2), Ogre::Node::TS_WORLD); + } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. for(size_t i = 0;i < ESM::PRT_Count;i++) @@ -588,9 +622,6 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g updateSkeletonInstance(mSkelBase->getSkeleton(), skel); } - // TODO: - // type == ESM::PRT_Weapon should get an animation source based on the current offset - // of the weapon attack animation (from its beginning, or start marker?) std::vector >::iterator ctrl(mObjectParts[type]->mControllers.begin()); for(;ctrl != mObjectParts[type]->mControllers.end();ctrl++) { @@ -600,6 +631,8 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g if (type == ESM::PRT_Head) ctrl->setSource(mHeadAnimationTime); + else if (type == ESM::PRT_Weapon) + ctrl->setSource(mWeaponAnimationTime); } } @@ -663,6 +696,17 @@ void NpcAnimation::showWeapons(bool showWeapon) std::string mesh = MWWorld::Class::get(*weapon).getModel(*weapon); addOrReplaceIndividualPart(ESM::PRT_Weapon, MWWorld::InventoryStore::Slot_CarriedRight, 1, mesh, !weapon->getClass().getEnchantment(*weapon).empty(), &glowColor); + + if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow) + { + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt) + attachArrow(); + else + mAmmunition.setNull(); + } + else + mAmmunition.setNull(); } } else @@ -693,6 +737,52 @@ void NpcAnimation::showCarriedLeft(bool show) removeIndividualPart(ESM::PRT_Shield); } +void NpcAnimation::attachArrow() +{ + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + showWeapons(true); + else + { + NifOgre::ObjectScenePtr weapon = mObjectParts[ESM::PRT_Weapon]; + + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + std::string model = ammo->getClass().getModel(*ammo); + + mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", mInsert, model); + Ogre::Vector3 glowColor = getEnchantmentColor(*ammo); + setRenderProperties(mAmmunition, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, + !ammo->getClass().getEnchantment(*ammo).empty(), &glowColor); + + std::for_each(mAmmunition->mEntities.begin(), mAmmunition->mEntities.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); + std::for_each(mAmmunition->mParticles.begin(), mAmmunition->mParticles.end(), SetObjectGroup(MWWorld::InventoryStore::Slot_Ammunition)); + } +} + +void NpcAnimation::releaseArrow() +{ + // Thrown weapons get detached now + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if (weapon != inv.end() && weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + { + showWeapons(false); + inv.remove(*weapon, 1, mPtr); + } + else + { + // With bows and crossbows only the used arrow/bolt gets detached + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (ammo == inv.end()) + return; + inv.remove(*ammo, 1, mPtr); + mAmmunition.setNull(); + } +} + void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew, bool playSound) { // During first auto equip, we don't play any sounds. diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index e86ec7d4e..725fde01d 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -25,6 +25,23 @@ public: { } }; +class WeaponAnimationTime : public Ogre::ControllerValue +{ +private: + Animation* mAnimation; + std::string mWeaponGroup; + float mStartTime; +public: + WeaponAnimationTime(Animation* animation) : mAnimation(animation), mStartTime(0) {} + void setGroup(const std::string& group); + void updateStartTime(); + + virtual Ogre::Real getValue() const; + virtual void setValue(Ogre::Real value) + { } +}; + + class NpcAnimation : public Animation, public MWWorld::InventoryStoreListener { public: @@ -71,8 +88,10 @@ private: Ogre::Vector3 mFirstPersonOffset; Ogre::SharedPtr mHeadAnimationTime; + Ogre::SharedPtr mWeaponAnimationTime; float mAlpha; + float mPitchFactor; void updateNpcBase(); @@ -105,11 +124,22 @@ public: ViewMode viewMode=VM_Normal); virtual ~NpcAnimation(); + virtual void setWeaponGroup(const std::string& group) { mWeaponAnimationTime->setGroup(group); } + virtual Ogre::Vector3 runAnimation(float timepassed); + /// A relative factor (0-1) that decides if and how much the skeleton should be pitched + /// to indicate the facing orientation of the character. + virtual void setPitchFactor(float factor) { mPitchFactor = factor; } + virtual void showWeapons(bool showWeapon); virtual void showCarriedLeft(bool showa); + virtual void attachArrow(); + virtual void releaseArrow(); + + NifOgre::ObjectScenePtr mAmmunition; + void setViewMode(ViewMode viewMode); void updateParts(); diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 267320713..e721477ee 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -172,7 +172,7 @@ bool Objects::deleteObject (const MWWorld::Ptr& ptr) } -void Objects::removeCell(MWWorld::Ptr::CellStore* store) +void Objects::removeCell(MWWorld::CellStore* store) { for(PtrAnimationMap::iterator iter = mObjects.begin();iter != mObjects.end();) { @@ -212,7 +212,7 @@ void Objects::removeCell(MWWorld::Ptr::CellStore* store) } } -void Objects::buildStaticGeometry(MWWorld::Ptr::CellStore& cell) +void Objects::buildStaticGeometry(MWWorld::CellStore& cell) { if(mStaticGeometry.find(&cell) != mStaticGeometry.end()) { @@ -226,7 +226,7 @@ void Objects::buildStaticGeometry(MWWorld::Ptr::CellStore& cell) } } -Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::Ptr::CellStore* cell) +Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::CellStore* cell) { return mBounds[cell]; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 515112668..0a82d67fb 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -29,6 +29,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" // FIXME #include "../mwbase/windowmanager.hpp" // FIXME +#include "../mwbase/statemanager.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -217,7 +218,12 @@ OEngine::Render::Fader* RenderingManager::getFader() return mRendering.getFader(); } -void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) + MWRender::Camera* RenderingManager::getCamera() const +{ + return mCamera; +} + +void RenderingManager::removeCell (MWWorld::CellStore *store) { mObjects->removeCell(store); mActors->removeCell(store); @@ -234,7 +240,7 @@ void RenderingManager::toggleWater() mWater->toggle(); } -void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) +void RenderingManager::cellAdded (MWWorld::CellStore *store) { mObjects->buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); @@ -325,6 +331,12 @@ void RenderingManager::rebuildPtr(const MWWorld::Ptr &ptr) void RenderingManager::update (float duration, bool paused) { + mVideoPlayer->update (); + + if (MWBase::Environment::get().getStateManager()->getState()== + MWBase::StateManager::State_NoGame) + return; + MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); @@ -363,8 +375,6 @@ void RenderingManager::update (float duration, bool paused) mOcclusionQuery->update(duration); - mVideoPlayer->update (); - mRendering.update(duration); Ogre::ControllerManager::getSingleton().setTimeFactor(paused ? 0.f : 1.f); @@ -410,7 +420,7 @@ void RenderingManager::postRenderTargetUpdate(const RenderTargetEvent &evt) mOcclusionQuery->setActive(false); } -void RenderingManager::waterAdded (MWWorld::Ptr::CellStore *store) +void RenderingManager::waterAdded (MWWorld::CellStore *store) { if(store->mCell->mData.mFlags & ESM::Cell::HasWater) { @@ -488,7 +498,7 @@ bool RenderingManager::toggleRenderMode(int mode) } } -void RenderingManager::configureFog(MWWorld::Ptr::CellStore &mCell) +void RenderingManager::configureFog(MWWorld::CellStore &mCell) { Ogre::ColourValue color; color.setAsABGR (mCell.mCell->mAmbi.mFog); @@ -541,7 +551,7 @@ void RenderingManager::setAmbientMode() } } -void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell) +void RenderingManager::configureAmbient(MWWorld::CellStore &mCell) { if (mCell.mCell->mData.mFlags & ESM::Cell::Interior) mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient); @@ -638,7 +648,7 @@ void RenderingManager::setGlare(bool glare) mSkyManager->setGlare(glare); } -void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) +void RenderingManager::requestMap(MWWorld::CellStore* cell) { if (cell->mCell->isExterior()) { @@ -657,7 +667,7 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) mLocalMap->requestMap(cell, mObjects->getDimensions(cell)); } -void RenderingManager::preCellChange(MWWorld::Ptr::CellStore* cell) +void RenderingManager::preCellChange(MWWorld::CellStore* cell) { mLocalMap->saveFogOfWar(cell); } @@ -949,6 +959,38 @@ Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) return anim; } +void RenderingManager::screenshot(Image &image, int w, int h) +{ + // Create a temporary render target. We do not use the RenderWindow since we want a specific size. + // Also, the GUI should not be visible (and it is only rendered on the RenderWindow's primary viewport) + const std::string tempName = "@temp"; + Ogre::TexturePtr texture = Ogre::TextureManager::getSingleton().createManual(tempName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, w, h, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); + + float oldAspect = mRendering.getCamera()->getAspectRatio(); + + mRendering.getCamera()->setAspectRatio(w / static_cast(h)); + + Ogre::RenderTarget* rt = texture->getBuffer()->getRenderTarget(); + Ogre::Viewport* vp = rt->addViewport(mRendering.getCamera()); + vp->setBackgroundColour(mRendering.getViewport()->getBackgroundColour()); + vp->setOverlaysEnabled(false); + vp->setVisibilityMask(mRendering.getViewport()->getVisibilityMask()); + rt->update(); + + Ogre::PixelFormat pf = rt->suggestPixelFormat(); + + std::vector data; + data.resize(w * h * Ogre::PixelUtil::getNumElemBytes(pf)); + + Ogre::PixelBox pb(w, h, 1, pf, &data[0]); + rt->copyContentsToMemory(pb); + + image.loadDynamicImage(&data[0], w, h, pf); + + Ogre::TextureManager::getSingleton().remove(tempName); + mRendering.getCamera()->setAspectRatio(oldAspect); +} void RenderingManager::playVideo(const std::string& name, bool allowSkipping) { diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index ea9a64120..64ec029ce 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -94,6 +94,8 @@ public: SkyManager* getSkyManager(); + MWRender::Camera* getCamera() const; + void toggleLight(); bool toggleRenderMode(int mode); @@ -206,6 +208,7 @@ public: void playVideo(const std::string& name, bool allowSkipping); void stopVideo(); void frameStarted(float dt, bool paused); + void screenshot(Ogre::Image& image, int w, int h); void spawnEffect (const std::string& model, const std::string& texture, const Ogre::Vector3& worldPosition, float scale=1.f); diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 05886c51c..8314d011a 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -352,7 +352,7 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().getTypeId (); + Interpreter::Type_Integer value = MWWorld::Class::get (ptr).getCreatureStats (ptr).getAiSequence().getLastRunTypeId(); runtime.push (value); } diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 1834c5651..a0acfa4da 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -130,7 +130,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime& runtime) { - MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); if (cell->mCell->hasWater()) runtime.push (cell->mWaterLevel); else @@ -146,7 +146,7 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; - MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); if (cell->mCell->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); @@ -164,7 +164,7 @@ namespace MWScript { Interpreter::Type_Float level = runtime[0].mFloat; - MWWorld::Ptr::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); + MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); if (cell->mCell->isExterior()) throw std::runtime_error("Can't set water level in exterior cell"); diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index 608725ae6..8f269a015 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -3,6 +3,9 @@ #include +#include +#include + #include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" @@ -15,25 +18,16 @@ namespace MWScript GlobalScripts::GlobalScripts (const MWWorld::ESMStore& store) : mStore (store) { - reset(); - } - - void GlobalScripts::reset() - { - mScripts.clear(); - addScript ("Main"); - - MWWorld::Store::iterator iter = - mStore.get().begin(); - - for (; iter != mStore.get().end(); ++iter) { - addScript (iter->mScript); - } + addStartup(); } void GlobalScripts::addScript (const std::string& name) { - if (mScripts.find (name)==mScripts.end()) + std::map >::iterator iter = + mScripts.find (Misc::StringUtils::lowerCase (name)); + + if (iter==mScripts.end()) + { if (const ESM::Script *script = mStore.get().find (name)) { Locals locals; @@ -42,11 +36,15 @@ namespace MWScript mScripts.insert (std::make_pair (name, std::make_pair (true, locals))); } + } + else + iter->second.first = true; } void GlobalScripts::removeScript (const std::string& name) { - std::map >::iterator iter = mScripts.find (name); + std::map >::iterator iter = + mScripts.find (Misc::StringUtils::lowerCase (name)); if (iter!=mScripts.end()) iter->second.first = false; @@ -55,7 +53,7 @@ namespace MWScript bool GlobalScripts::isRunning (const std::string& name) const { std::map >::const_iterator iter = - mScripts.find (name); + mScripts.find (Misc::StringUtils::lowerCase (name)); if (iter==mScripts.end()) return false; @@ -76,4 +74,78 @@ namespace MWScript } } } + + void GlobalScripts::clear() + { + mScripts.clear(); + } + + void GlobalScripts::addStartup() + { + addScript ("main"); + + for (MWWorld::Store::iterator iter = + mStore.get().begin(); + iter != mStore.get().end(); ++iter) + { + addScript (iter->mScript); + } + } + + int GlobalScripts::countSavedGameRecords() const + { + return mScripts.size(); + } + + void GlobalScripts::write (ESM::ESMWriter& writer) const + { + for (std::map >::const_iterator iter (mScripts.begin()); + iter!=mScripts.end(); ++iter) + { + ESM::GlobalScript script; + + script.mId = iter->first; + + iter->second.second.write (script.mLocals, iter->first); + + script.mRunning = iter->second.first ? 1 : 0; + + writer.startRecord (ESM::REC_GSCR); + script.save (writer); + writer.endRecord (ESM::REC_GSCR); + } + } + + bool GlobalScripts::readRecord (ESM::ESMReader& reader, int32_t type) + { + if (type==ESM::REC_GSCR) + { + ESM::GlobalScript script; + script.load (reader); + + std::map >::iterator iter = + mScripts.find (script.mId); + + if (iter==mScripts.end()) + { + if (const ESM::Script *scriptRecord = mStore.get().search (script.mId)) + { + std::pair data (false, Locals()); + + data.second.configure (*scriptRecord); + + iter = mScripts.insert (std::make_pair (script.mId, data)).first; + } + else // script does not exist anymore + return true; + } + + iter->second.first = script.mRunning!=0; + iter->second.second.read (script.mLocals, script.mId); + + return true; + } + + return false; + } } diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 628919d1d..cf716c8e4 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -4,9 +4,17 @@ #include #include +#include + #include "locals.hpp" -namespace MWWorld +namespace ESM +{ + class ESMWriter; + class ESMReader; +} + +namespace MWWorld { struct ESMStore; } @@ -22,8 +30,6 @@ namespace MWScript GlobalScripts (const MWWorld::ESMStore& store); - void reset(); - void addScript (const std::string& name); void removeScript (const std::string& name); @@ -32,6 +38,20 @@ namespace MWScript void run(); ///< run all active global scripts + + void clear(); + + void addStartup(); + ///< Add startup script + + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); + ///< Records for variables that do not exist are dropped silently. + /// + /// \return Known type? }; } diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 18baf0bda..10e98e398 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -126,61 +126,49 @@ namespace MWScript int InterpreterContext::getGlobalShort (const std::string& name) const { - return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mShort; + return MWBase::Environment::get().getWorld()->getGlobalInt (name); } int InterpreterContext::getGlobalLong (const std::string& name) const { // a global long is internally a float. - return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mLong; + return MWBase::Environment::get().getWorld()->getGlobalInt (name); } float InterpreterContext::getGlobalFloat (const std::string& name) const { - return MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat; + return MWBase::Environment::get().getWorld()->getGlobalFloat (name); } void InterpreterContext::setGlobalShort (const std::string& name, int value) { - if (name=="gamehour") - MWBase::Environment::get().getWorld()->setHour (value); - else if (name=="day") - MWBase::Environment::get().getWorld()->setDay (value); - else if (name=="month") - MWBase::Environment::get().getWorld()->setMonth (value); - else - MWBase::Environment::get().getWorld()->getGlobalVariable (name).mShort = value; + MWBase::Environment::get().getWorld()->setGlobalInt (name, value); } void InterpreterContext::setGlobalLong (const std::string& name, int value) { - if (name=="gamehour") - MWBase::Environment::get().getWorld()->setHour (value); - else if (name=="day") - MWBase::Environment::get().getWorld()->setDay (value); - else if (name=="month") - MWBase::Environment::get().getWorld()->setMonth (value); - else - MWBase::Environment::get().getWorld()->getGlobalVariable (name).mLong = value; + MWBase::Environment::get().getWorld()->setGlobalInt (name, value); } void InterpreterContext::setGlobalFloat (const std::string& name, float value) { - if (name=="gamehour") - MWBase::Environment::get().getWorld()->setHour (value); - else if (name=="day") - MWBase::Environment::get().getWorld()->setDay (value); - else if (name=="month") - MWBase::Environment::get().getWorld()->setMonth (value); - else - MWBase::Environment::get().getWorld()->getGlobalVariable (name).mFloat = value; + MWBase::Environment::get().getWorld()->setGlobalFloat (name, value); } - std::vector InterpreterContext::getGlobals () const + std::vector InterpreterContext::getGlobals() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); - return world->getGlobals(); + std::vector ids; + + const MWWorld::Store& globals = + MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); + ++iter) + { + ids.push_back (iter->mId); + } + + return ids; } char InterpreterContext::getGlobalType (const std::string& name) const @@ -321,8 +309,7 @@ namespace MWScript std::string InterpreterContext::getCurrentCellName() const { - MWBase::World *world = MWBase::Environment::get().getWorld(); - return world->getCurrentCellName(); + return MWBase::Environment::get().getWorld()->getCellName(); } bool InterpreterContext::isScriptRunning (const std::string& name) const diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index 180a2791b..094fe085a 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -1,6 +1,8 @@ #include "locals.hpp" #include +#include +#include #include @@ -65,4 +67,68 @@ namespace MWScript } return false; } + + void Locals::write (ESM::Locals& locals, const std::string& script) const + { + const Compiler::Locals& declarations = + MWBase::Environment::get().getScriptManager()->getLocals(script); + + for (int i=0; i<3; ++i) + { + char type = 0; + + switch (i) + { + case 0: type = 's'; break; + case 1: type = 'l'; break; + case 2: type = 'f'; break; + } + + const std::vector& names = declarations.get (type); + + for (int i2=0; i2 (names.size()); ++i2) + { + ESM::Variant value; + + switch (i) + { + case 0: value.setType (ESM::VT_Int); value.setInteger (mShorts.at (i2)); break; + case 1: value.setType (ESM::VT_Int); value.setInteger (mLongs.at (i2)); break; + case 2: value.setType (ESM::VT_Float); value.setFloat (mFloats.at (i2)); break; + } + + locals.mVariables.push_back (std::make_pair (names[i2], value)); + } + } + } + + void Locals::read (const ESM::Locals& locals, const std::string& script) + { + const Compiler::Locals& declarations = + MWBase::Environment::get().getScriptManager()->getLocals(script); + + for (std::vector >::const_iterator iter + = locals.mVariables.begin(); iter!=locals.mVariables.end(); ++iter) + { + char type = declarations.getType (iter->first); + char index = declarations.getIndex (iter->first); + + try + { + switch (type) + { + case 's': mShorts.at (index) = iter->second.getInteger(); break; + case 'l': mLongs.at (index) = iter->second.getInteger(); break; + case 'f': mFloats.at (index) = iter->second.getFloat(); break; + + // silently ignore locals that don't exist anymore + } + } + catch (...) + { + // ignore type changes + /// \todo write to log + } + } + } } diff --git a/apps/openmw/mwscript/locals.hpp b/apps/openmw/mwscript/locals.hpp index deae0d44e..d17d1be2d 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -8,6 +8,7 @@ namespace ESM { struct Script; + struct Locals; } namespace MWScript @@ -23,6 +24,9 @@ namespace MWScript bool setVarByInt(const std::string& script, const std::string& var, int val); int getIntVar (const std::string& script, const std::string& var); ///< if var does not exist, returns 0 + void write (ESM::Locals& locals, const std::string& script) const; + + void read (const ESM::Locals& locals, const std::string& script); }; } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 27730767f..d117c8ded 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -682,23 +682,43 @@ namespace MWScript void printGlobalVars(Interpreter::Runtime &runtime) { + InterpreterContext& context = + static_cast (runtime.getContext()); + std::stringstream str; str<< "Global variables:"; MWBase::World *world = MWBase::Environment::get().getWorld(); - std::vector names = world->getGlobals(); + std::vector names = context.getGlobals(); for(size_t i = 0;i < names.size();++i) { - char type = world->getGlobalVariableType(names[i]); - if(type == 's') - str<getGlobalVariableType (names[i]); + str << std::endl << " " << names[i] << " = "; + + switch (type) + { + case 's': + + str << context.getGlobalShort (names[i]) << " (short)"; + break; + + case 'l': + + str << context.getGlobalLong (names[i]) << " (long)"; + break; + + case 'f': + + str << context.getGlobalFloat (names[i]) << " (float)"; + break; + + default: + + str << ""; + } } - runtime.getContext().report(str.str()); + context.report (str.str()); } public: diff --git a/apps/openmw/mwscript/scriptmanagerimp.cpp b/apps/openmw/mwscript/scriptmanagerimp.cpp index 8b6b7ed2a..a3e061546 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.cpp +++ b/apps/openmw/mwscript/scriptmanagerimp.cpp @@ -220,9 +220,4 @@ namespace MWScript throw std::runtime_error ("unable to access local variable " + variable + " of " + scriptId); } - - void ScriptManager::resetGlobalScripts() - { - mGlobalScripts.reset(); - } } diff --git a/apps/openmw/mwscript/scriptmanagerimp.hpp b/apps/openmw/mwscript/scriptmanagerimp.hpp index 7bb98ffbd..1a856e0c5 100644 --- a/apps/openmw/mwscript/scriptmanagerimp.hpp +++ b/apps/openmw/mwscript/scriptmanagerimp.hpp @@ -61,8 +61,6 @@ namespace MWScript ///< Compile script with the given namen /// \return Success? - virtual void resetGlobalScripts(); - virtual std::pair compileAll(); ///< Compile all scripts /// \return count, success diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index d39602516..fd3fe4c0a 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -6,6 +6,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwbase/statemanager.hpp" #include "../mwworld/esmstore.hpp" @@ -398,7 +399,7 @@ namespace MWSound } } - void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell) + void SoundManager::stopSound(const MWWorld::CellStore *cell) { SoundMap::iterator snditer = mActiveSounds.begin(); while(snditer != mActiveSounds.end()) @@ -594,7 +595,7 @@ namespace MWSound soundDuration=snditer->first->mFadeOutTime; snditer->first->setVolume(snditer->first->mVolume - soundDuration / snditer->first->mFadeOutTime * snditer->first->mVolume); - snditer->first->mFadeOutTime -= soundDuration; + snditer->first->mFadeOutTime -= soundDuration; } snditer->first->update(); ++snditer; @@ -606,8 +607,13 @@ namespace MWSound { if(!mOutput->isInitialized()) return; - updateSounds(duration); - updateRegionSound(duration); + + if (MWBase::Environment::get().getStateManager()->getState()!= + MWBase::StateManager::State_NoGame) + { + updateSounds(duration); + updateRegionSound(duration); + } } @@ -713,4 +719,13 @@ namespace MWSound { return bytes / framesToBytes(1, config, type); } + + void SoundManager::clear() + { + for (SoundMap::iterator iter (mActiveSounds.begin()); iter!=mActiveSounds.end(); ++iter) + iter->first->stop(); + + mActiveSounds.clear(); + stopMusic(); + } } diff --git a/apps/openmw/mwsound/soundmanagerimp.hpp b/apps/openmw/mwsound/soundmanagerimp.hpp index 4fd1d3d48..1454240b4 100644 --- a/apps/openmw/mwsound/soundmanagerimp.hpp +++ b/apps/openmw/mwsound/soundmanagerimp.hpp @@ -150,6 +150,8 @@ namespace MWSound virtual void setListenerPosDir(const Ogre::Vector3 &pos, const Ogre::Vector3 &dir, const Ogre::Vector3 &up); virtual void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated); + + virtual void clear(); }; } diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp new file mode 100644 index 000000000..304eaddd3 --- /dev/null +++ b/apps/openmw/mwstate/character.cpp @@ -0,0 +1,153 @@ + +#include "character.hpp" + +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +bool MWState::operator< (const Slot& left, const Slot& right) +{ + return left.mTimeStampESM::Header::CurrentFormat) + return; // format is too new -> ignore + + if (reader.getRecName()!=ESM::REC_SAVE) + return; // invalid save file -> ignore + + reader.getRecHeader(); + + slot.mProfile.load (reader); + + if (Misc::StringUtils::lowerCase (slot.mProfile.mContentFiles.at (0))!= + Misc::StringUtils::lowerCase (game)) + return; // this file is for a different game -> ignore + + mSlots.push_back (slot); +} + +void MWState::Character::addSlot (const ESM::SavedGame& profile) +{ + Slot slot; + + std::ostringstream stream; + stream << mNext++; + + slot.mPath = mPath / stream.str(); + slot.mProfile = profile; + slot.mTimeStamp = std::time (0); + + mSlots.push_back (slot); +} + +MWState::Character::Character (const boost::filesystem::path& saves, const std::string& game) +: mPath (saves), mNext (0) +{ + if (!boost::filesystem::is_directory (mPath)) + { + boost::filesystem::create_directories (mPath); + } + else + { + for (boost::filesystem::directory_iterator iter (mPath); + iter!=boost::filesystem::directory_iterator(); ++iter) + { + boost::filesystem::path slotPath = *iter; + + try + { + addSlot (slotPath, game); + } + catch (...) {} // ignoring bad saved game files for now + + std::istringstream stream (slotPath.filename().string()); + + int index = 0; + + if ((stream >> index) && index>=mNext) + mNext = index+1; + } + + std::sort (mSlots.begin(), mSlots.end()); + } +} + +const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile) +{ + addSlot (profile); + + return &mSlots.back(); +} + +const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile) +{ + int index = slot - &mSlots[0]; + + if (index<0 || index>=static_cast (mSlots.size())) + { + // sanity check; not entirely reliable + throw std::logic_error ("slot not found"); + } + + Slot newSlot = *slot; + newSlot.mProfile = profile; + newSlot.mTimeStamp = std::time (0); + + mSlots.erase (mSlots.begin()+index); + + mSlots.push_back (newSlot); + + return &mSlots.back(); +} + +MWState::Character::SlotIterator MWState::Character::begin() const +{ + return mSlots.rbegin(); +} + +MWState::Character::SlotIterator MWState::Character::end() const +{ + return mSlots.rend(); +} + +ESM::SavedGame MWState::Character::getSignature() const +{ + if (mSlots.empty()) + throw std::logic_error ("character signature not available"); + + std::vector::const_iterator iter (mSlots.begin()); + + Slot slot = *iter; + + for (++iter; iter!=mSlots.end(); ++iter) + if (iter->mProfile.mPlayerLevel>slot.mProfile.mPlayerLevel) + slot = *iter; + else if (iter->mProfile.mPlayerLevel==slot.mProfile.mPlayerLevel && + iter->mTimeStamp>slot.mTimeStamp) + slot = *iter; + + return slot.mProfile; +} \ No newline at end of file diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp new file mode 100644 index 000000000..61e4e5b25 --- /dev/null +++ b/apps/openmw/mwstate/character.hpp @@ -0,0 +1,63 @@ +#ifndef GAME_STATE_CHARACTER_H +#define GAME_STATE_CHARACTER_H + +#include + +#include + +namespace MWState +{ + struct Slot + { + boost::filesystem::path mPath; + ESM::SavedGame mProfile; + std::time_t mTimeStamp; + }; + + bool operator< (const Slot& left, const Slot& right); + + class Character + { + public: + + typedef std::vector::const_reverse_iterator SlotIterator; + + private: + + boost::filesystem::path mPath; + std::vector mSlots; + int mNext; + + void addSlot (const boost::filesystem::path& path, const std::string& game); + + void addSlot (const ESM::SavedGame& profile); + + public: + + Character (const boost::filesystem::path& saves, const std::string& game); + + const Slot *createSlot (const ESM::SavedGame& profile); + ///< Create new slot. + /// + /// \attention The ownership of the slot is not transferred. + + const Slot *updateSlot (const Slot *slot, const ESM::SavedGame& profile); + /// \note Slot must belong to this character. + /// + /// \attention The \a slot pointer will be invalidated by this call. + + SlotIterator begin() const; + ///< First slot is the most recent. Other slots follow in descending order of save date. + /// + /// Any call to createSlot and updateSlot can invalidate the returned iterator. + + SlotIterator end() const; + + ESM::SavedGame getSignature() const; + ///< Return signature information for this character. + /// + /// \attention This function must not be called if there are no slots. + }; +} + +#endif diff --git a/apps/openmw/mwstate/charactermanager.cpp b/apps/openmw/mwstate/charactermanager.cpp new file mode 100644 index 000000000..2a40fb1cc --- /dev/null +++ b/apps/openmw/mwstate/charactermanager.cpp @@ -0,0 +1,85 @@ + +#include "charactermanager.hpp" + +#include +#include + +#include + +MWState::CharacterManager::CharacterManager (const boost::filesystem::path& saves, + const std::string& game) +: mPath (saves), mNext (0), mCurrent (0), mGame (game) +{ + if (!boost::filesystem::is_directory (mPath)) + { + boost::filesystem::create_directories (mPath); + } + else + { + for (boost::filesystem::directory_iterator iter (mPath); + iter!=boost::filesystem::directory_iterator(); ++iter) + { + boost::filesystem::path characterDir = *iter; + + if (boost::filesystem::is_directory (characterDir)) + { + Character character (characterDir, mGame); + + if (character.begin()!=character.end()) + mCharacters.push_back (character); + } + + std::istringstream stream (characterDir.filename().string()); + + int index = 0; + + if ((stream >> index) && index>=mNext) + mNext = index+1; + } + } +} + +MWState::Character *MWState::CharacterManager::getCurrentCharacter (bool create) +{ + if (!mCurrent && create) + createCharacter(); + + return mCurrent; +} + +void MWState::CharacterManager::createCharacter() +{ + std::ostringstream stream; + stream << mNext++; + + boost::filesystem::path path = mPath / stream.str(); + + mCharacters.push_back (Character (path, mGame)); + + mCurrent = &mCharacters.back(); +} + +void MWState::CharacterManager::setCurrentCharacter (const Character *character) +{ + int index = character - &mCharacters[0]; + + if (index<0 || index>=static_cast (mCharacters.size())) + throw std::logic_error ("invalid character"); + + mCurrent = &mCharacters[index]; +} + +void MWState::CharacterManager::clearCurrentCharacter() +{ + mCurrent = 0; +} + +std::vector::const_iterator MWState::CharacterManager::begin() const +{ + return mCharacters.begin(); +} + +std::vector::const_iterator MWState::CharacterManager::end() const +{ + return mCharacters.end(); +} diff --git a/apps/openmw/mwstate/charactermanager.hpp b/apps/openmw/mwstate/charactermanager.hpp new file mode 100644 index 000000000..bc2e23f89 --- /dev/null +++ b/apps/openmw/mwstate/charactermanager.hpp @@ -0,0 +1,46 @@ +#ifndef GAME_STATE_CHARACTERMANAGER_H +#define GAME_STATE_CHARACTERMANAGER_H + +#include + +#include "character.hpp" + +namespace MWState +{ + class CharacterManager + { + boost::filesystem::path mPath; + int mNext; + std::vector mCharacters; + Character *mCurrent; + std::string mGame; + + private: + + CharacterManager (const CharacterManager&); + ///< Not implemented + + CharacterManager& operator= (const CharacterManager&); + ///< Not implemented + + public: + + CharacterManager (const boost::filesystem::path& saves, const std::string& game); + + Character *getCurrentCharacter (bool create = true); + ///< \param create Create a new character, if there is no current character. + + void createCharacter(); + ///< Create new character within saved game management + + void setCurrentCharacter (const Character *character); + + void clearCurrentCharacter(); + + std::vector::const_iterator begin() const; + + std::vector::const_iterator end() const; + }; +} + +#endif diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp new file mode 100644 index 000000000..f68a01bf4 --- /dev/null +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -0,0 +1,345 @@ + +#include "statemanagerimp.hpp" + +#include +#include +#include +#include + +#include + +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/journal.hpp" +#include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/scriptmanager.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwworld/player.hpp" +#include "../mwworld/class.hpp" + +#include "../mwmechanics/npcstats.hpp" + +#include "../mwscript/globalscripts.hpp" + +void MWState::StateManager::cleanup (bool force) +{ + if (mState!=State_NoGame || force) + { + MWBase::Environment::get().getSoundManager()->clear(); + MWBase::Environment::get().getDialogueManager()->clear(); + MWBase::Environment::get().getJournal()->clear(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().clear(); + MWBase::Environment::get().getWorld()->clear(); + MWBase::Environment::get().getWindowManager()->clear(); + + mState = State_NoGame; + mCharacterManager.clearCurrentCharacter(); + mTimePlayed = 0; + } +} + +std::map MWState::StateManager::buildContentFileIndexMap (const ESM::ESMReader& reader) + const +{ + const std::vector& current = + MWBase::Environment::get().getWorld()->getContentFiles(); + + const std::vector& prev = reader.getGameFiles(); + + std::map map; + + for (int iPrev = 0; iPrev (prev.size()); ++iPrev) + { + std::string id = Misc::StringUtils::lowerCase (prev[iPrev].name); + + for (int iCurrent = 0; iCurrent (current.size()); ++iCurrent) + if (id==Misc::StringUtils::lowerCase (current[iCurrent])) + { + map.insert (std::make_pair (iPrev, iCurrent)); + break; + } + } + + return map; +} + +MWState::StateManager::StateManager (const boost::filesystem::path& saves, const std::string& game) +: mQuitRequest (false), mAskLoadRecent(false), mState (State_NoGame), mCharacterManager (saves, game), mTimePlayed (0) +{ + +} + +void MWState::StateManager::requestQuit() +{ + mQuitRequest = true; +} + +bool MWState::StateManager::hasQuitRequest() const +{ + return mQuitRequest; +} + +void MWState::StateManager::askLoadRecent() +{ + if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_MainMenu) + return; + + if( !mAskLoadRecent ) + { + if(getCurrentCharacter()->begin() == getCurrentCharacter()->end() )//no saves + { + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + else + { + MWState::Slot lastSave = *getCurrentCharacter()->begin(); + std::vector buttons; + buttons.push_back("#{sYes}"); + buttons.push_back("#{sNo}"); + std::string tag("%s"); + std::string message = MWBase::Environment::get().getWindowManager()->getGameSettingString("sLoadLastSaveMsg", tag); + size_t pos = message.find(tag); + message.replace(pos, tag.length(), lastSave.mProfile.mDescription); + MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); + mAskLoadRecent = true; + } + } +} + +MWState::StateManager::State MWState::StateManager::getState() const +{ + return mState; +} + +void MWState::StateManager::newGame (bool bypass) +{ + cleanup(); + + if (!bypass) + { + MWBase::Environment::get().getWorld()->startNewGame(); + MWBase::Environment::get().getWindowManager()->setNewGame (true); + } + else + MWBase::Environment::get().getWorld()->setGlobalInt ("chargenstate", -1); + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); + + mState = State_Running; +} + +void MWState::StateManager::endGame() +{ + mState = State_Ended; + MWBase::Environment::get().getWorld()->useDeathCamera(); +} + +void MWState::StateManager::saveGame (const std::string& description, const Slot *slot) +{ + ESM::SavedGame profile; + + MWBase::World& world = *MWBase::Environment::get().getWorld(); + + MWWorld::Ptr player = world.getPlayer().getPlayer(); + + profile.mContentFiles = world.getContentFiles(); + + profile.mPlayerName = player.getClass().getName (player); + profile.mPlayerLevel = player.getClass().getNpcStats (player).getLevel(); + profile.mPlayerClass = player.get()->mBase->mClass; + + profile.mPlayerCell = world.getCellName(); + + profile.mInGameTime.mGameHour = world.getTimeStamp().getHour(); + profile.mInGameTime.mDay = world.getDay(); + profile.mInGameTime.mMonth = world.getMonth(); + profile.mInGameTime.mYear = world.getYear(); + profile.mTimePlayed = mTimePlayed; + profile.mDescription = description; + + int screenshotW = 259*2, screenshotH = 133*2; // *2 to get some nice antialiasing + Ogre::Image screenshot; + world.screenshot(screenshot, screenshotW, screenshotH); + Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); + profile.mScreenshot.resize(encoded->size()); + encoded->read(&profile.mScreenshot[0], encoded->size()); + + if (!slot) + slot = mCharacterManager.getCurrentCharacter()->createSlot (profile); + else + slot = mCharacterManager.getCurrentCharacter()->updateSlot (slot, profile); + + std::ofstream stream (slot->mPath.string().c_str()); + + ESM::ESMWriter writer; + + const std::vector& current = + MWBase::Environment::get().getWorld()->getContentFiles(); + + for (std::vector::const_iterator iter (current.begin()); iter!=current.end(); + ++iter) + writer.addMaster (*iter, 0); // not using the size information anyway -> use value of 0 + + writer.setFormat (ESM::Header::CurrentFormat); + writer.setRecordCount ( + 1 // saved game header + +MWBase::Environment::get().getJournal()->countSavedGameRecords() + +MWBase::Environment::get().getWorld()->countSavedGameRecords() + +MWBase::Environment::get().getScriptManager()->getGlobalScripts().countSavedGameRecords() + + 1 // global map + ); + + writer.save (stream); + + writer.startRecord (ESM::REC_SAVE); + slot->mProfile.save (writer); + writer.endRecord (ESM::REC_SAVE); + + MWBase::Environment::get().getJournal()->write (writer); + MWBase::Environment::get().getWorld()->write (writer); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().write (writer); + MWBase::Environment::get().getWindowManager()->write(writer); + + writer.close(); + + Settings::Manager::setString ("character", "Saves", + slot->mPath.parent_path().filename().string()); +} + +void MWState::StateManager::loadGame (const Character *character, const Slot *slot) +{ + try + { + cleanup(); + + mTimePlayed = slot->mProfile.mTimePlayed; + + ESM::ESMReader reader; + reader.open (slot->mPath.string()); + + std::map contentFileMap = buildContentFileIndexMap (reader); + + while (reader.hasMoreRecs()) + { + ESM::NAME n = reader.getRecName(); + reader.getRecHeader(); + + switch (n.val) + { + case ESM::REC_SAVE: + + // don't need to read that here + reader.skipRecord(); + break; + + case ESM::REC_JOUR: + case ESM::REC_QUES: + + MWBase::Environment::get().getJournal()->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: + + MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); + break; + + case ESM::REC_GSCR: + + MWBase::Environment::get().getScriptManager()->getGlobalScripts().readRecord (reader, n.val); + break; + + case ESM::REC_GMAP: + + MWBase::Environment::get().getWindowManager()->readRecord(reader, n.val); + break; + + 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().getWindowManager()->setNewGame(false); + 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); + } +} + +MWState::Character *MWState::StateManager::getCurrentCharacter (bool create) +{ + return mCharacterManager.getCurrentCharacter (create); +} + +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(); + if(iButton==0) + { + mAskLoadRecent = false; + //Load last saved game for current character + MWState::Character *curCharacter = getCurrentCharacter(); + MWState::Slot lastSave = *curCharacter->begin(); + loadGame(curCharacter, &lastSave); + } + else if(iButton==1) + { + mAskLoadRecent = false; + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + } + } +} diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp new file mode 100644 index 000000000..46ade236b --- /dev/null +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -0,0 +1,70 @@ +#ifndef GAME_STATE_STATEMANAGER_H +#define GAME_STATE_STATEMANAGER_H + +#include + +#include "../mwbase/statemanager.hpp" + +#include + +#include "charactermanager.hpp" + +namespace MWState +{ + class StateManager : public MWBase::StateManager + { + bool mQuitRequest; + bool mAskLoadRecent; + State mState; + CharacterManager mCharacterManager; + double mTimePlayed; + + private: + + void cleanup (bool force = false); + + std::map buildContentFileIndexMap (const ESM::ESMReader& reader) const; + + public: + + StateManager (const boost::filesystem::path& saves, const std::string& game); + + virtual void requestQuit(); + + virtual bool hasQuitRequest() const; + + virtual void askLoadRecent(); + + virtual State getState() const; + + virtual void newGame (bool bypass = false); + ///< Start a new game. + /// + /// \param bypass Skip new game mechanics. + + virtual void endGame(); + + virtual void saveGame (const std::string& description, const Slot *slot = 0); + ///< Write a saved game to \a slot or create a new slot if \a slot == 0. + /// + /// \note Slot must belong to the current character. + + virtual void loadGame (const Character *character, const Slot *slot); + ///< Load a saved game file from \a slot. + /// + /// \note \a slot must belong to \a character. + + virtual Character *getCurrentCharacter (bool create = true); + ///< \param create Create a new character, if there is no current character. + + virtual CharacterIterator characterBegin(); + ///< Any call to SaveGame and getCurrentCharacter can invalidate the returned + /// iterator. + + virtual CharacterIterator characterEnd(); + + virtual void update (float duration); + }; +} + +#endif diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index c8a070e21..965c9fc5d 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" @@ -7,29 +12,29 @@ #include "esmstore.hpp" #include "containerstore.hpp" -MWWorld::Ptr::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) +MWWorld::CellStore *MWWorld::Cells::getCellStore (const ESM::Cell *cell) { if (cell->mData.mFlags & ESM::Cell::Interior) { std::string lowerName(Misc::StringUtils::lowerCase(cell->mName)); - std::map::iterator result = mInteriors.find (lowerName); + std::map::iterator result = mInteriors.find (lowerName); if (result==mInteriors.end()) { - result = mInteriors.insert (std::make_pair (lowerName, Ptr::CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first; } return &result->second; } else { - std::map, Ptr::CellStore>::iterator result = + std::map, CellStore>::iterator result = mExteriors.find (std::make_pair (cell->getGridX(), cell->getGridY())); if (result==mExteriors.end()) { result = mExteriors.insert (std::make_pair ( - std::make_pair (cell->getGridX(), cell->getGridY()), Ptr::CellStore (cell))).first; + std::make_pair (cell->getGridX(), cell->getGridY()), CellStore (cell))).first; } @@ -41,11 +46,11 @@ void MWWorld::Cells::clear() { mInteriors.clear(); mExteriors.clear(); - std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::Ptr::CellStore*)0)); + std::fill(mIdCache.begin(), mIdCache.end(), std::make_pair("", (MWWorld::CellStore*)0)); mIdCacheIndex = 0; } -MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellStore& cellStore) +MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, CellStore& cellStore) { Ptr ptr = getPtr (name, cellStore); @@ -60,15 +65,39 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellS 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); + cell.writeReferences (writer); + 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 ("", (Ptr::CellStore*)0)), /// \todo make cache size configurable + mIdCache (40, std::pair ("", (CellStore*)0)), /// \todo make cache size configurable mIdCacheIndex (0) {} -MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) +MWWorld::CellStore *MWWorld::Cells::getExterior (int x, int y) { - std::map, Ptr::CellStore>::iterator result = + std::map, CellStore>::iterator result = mExteriors.find (std::make_pair (x, y)); if (result==mExteriors.end()) @@ -93,7 +122,7 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) std::make_pair (x, y), CellStore (cell))).first; } - if (result->second.mState!=Ptr::CellStore::State_Loaded) + if (result->second.mState!=CellStore::State_Loaded) { // Multiple plugin support for landscape data is much easier than for references. The last plugin wins. result->second.load (mStore, mReader); @@ -102,19 +131,19 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y) return &result->second; } -MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name) +MWWorld::CellStore *MWWorld::Cells::getInterior (const std::string& name) { std::string lowerName = Misc::StringUtils::lowerCase(name); - std::map::iterator result = mInteriors.find (lowerName); + std::map::iterator result = mInteriors.find (lowerName); if (result==mInteriors.end()) { const ESM::Cell *cell = mStore.get().find(lowerName); - result = mInteriors.insert (std::make_pair (lowerName, Ptr::CellStore (cell))).first; + result = mInteriors.insert (std::make_pair (lowerName, CellStore (cell))).first; } - if (result->second.mState!=Ptr::CellStore::State_Loaded) + if (result->second.mState!=CellStore::State_Loaded) { result->second.load (mStore, mReader); } @@ -122,13 +151,21 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getInterior (const std::string& name) return &result->second; } -MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& cell, +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) { - if (cell.mState==Ptr::CellStore::State_Unloaded) + if (cell.mState==CellStore::State_Unloaded) cell.preload (mStore, mReader); - if (cell.mState==Ptr::CellStore::State_Preloaded) + if (cell.mState==CellStore::State_Preloaded) { if (std::binary_search (cell.mIds.begin(), cell.mIds.end(), name)) { @@ -207,7 +244,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name, Ptr::CellStore& ce MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) { // First check the cache - for (std::vector >::iterator iter (mIdCache.begin()); + for (std::vector >::iterator iter (mIdCache.begin()); iter!=mIdCache.end(); ++iter) if (iter->first==name && iter->second) { @@ -217,7 +254,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) } // Then check cells that are already listed - for (std::map, Ptr::CellStore>::iterator iter = mExteriors.begin(); + for (std::map, CellStore>::iterator iter = mExteriors.begin(); iter!=mExteriors.end(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); @@ -225,7 +262,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) return ptr; } - for (std::map::iterator iter = mInteriors.begin(); + for (std::map::iterator iter = mInteriors.begin(); iter!=mInteriors.end(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); @@ -239,7 +276,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) for (iter = cells.extBegin(); iter != cells.extEnd(); ++iter) { - Ptr::CellStore *cellStore = getCellStore (&(*iter)); + CellStore *cellStore = getCellStore (&(*iter)); Ptr ptr = getPtrAndCache (name, *cellStore); @@ -249,7 +286,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) for (iter = cells.intBegin(); iter != cells.intEnd(); ++iter) { - Ptr::CellStore *cellStore = getCellStore (&(*iter)); + CellStore *cellStore = getCellStore (&(*iter)); Ptr ptr = getPtrAndCache (name, *cellStore); @@ -263,7 +300,7 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector &out) { - for (std::map, Ptr::CellStore>::iterator iter = mExteriors.begin(); + for (std::map, CellStore>::iterator iter = mExteriors.begin(); iter!=mExteriors.end(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); @@ -275,7 +312,7 @@ void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector &out) { - for (std::map::iterator iter = mInteriors.begin(); + for (std::map::iterator iter = mInteriors.begin(); iter!=mInteriors.end(); ++iter) { Ptr ptr = getPtrAndCache (name, iter->second); @@ -284,3 +321,67 @@ void MWWorld::Cells::getInteriorPtrs(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, + const std::map& contentFileMap) +{ + 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); + + if (cellStore->mState!=CellStore::State_Loaded) + cellStore->load (mStore, mReader); + + cellStore->readReferences (reader, contentFileMap); + + return true; + } + + return false; +} diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index afa7c03f2..7ee8a3f6c 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 @@ -62,6 +69,12 @@ namespace MWWorld /// @note name must be lower case void getInteriorPtrs (const std::string& name, std::vector& out); + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap); }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b492c6f32..42c954afb 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -2,6 +2,15 @@ #include +#include +#include +#include +#include +#include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -29,38 +38,99 @@ namespace return MWWorld::Ptr(); } + + template + void writeReferenceCollection (ESM::ESMWriter& writer, + const MWWorld::CellRefList& collection) + { + if (!collection.mList.empty()) + { + // references + for (typename MWWorld::CellRefList::List::const_iterator + iter (collection.mList.begin()); + iter!=collection.mList.end(); ++iter) + { + if (iter->mData.getCount()==0 && iter->mRef.mRefNum.mContentFile==-1) + continue; // deleted file that did not came from a content file -> ignore + + RecordType state; + iter->save (state); + + writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId); + state.save (writer); + } + } + } + + template + void readReferenceCollection (ESM::ESMReader& reader, + MWWorld::CellRefList& collection, const std::map& contentFileMap) + { + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); + + RecordType state; + state.load (reader); + + std::map::const_iterator iter = + contentFileMap.find (state.mRef.mRefNum.mContentFile); + + if (iter==contentFileMap.end()) + return; // content file has been removed -> skip + + state.mRef.mRefNum.mContentFile = iter->second; + + if (!MWWorld::LiveCellRef::checkState (state)) + return; // not valid anymore with current content files -> skip + + const T *record = esmStore.get().search (state.mRef.mRefID); + + if (!record) + return; + + for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); + iter!=collection.mList.end(); ++iter) + if (iter->mRef.mRefNum==state.mRef.mRefNum) + { + // overwrite existing reference + iter->load (state); + return; + } + + // new reference + MWWorld::LiveCellRef ref (record); + ref.load (state); + collection.mList.push_back (ref); + } } namespace MWWorld { template - void CellRefList::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore) + void CellRefList::load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore) { - // Get existing reference, in case we need to overwrite it. - typename std::list::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefnum); + const MWWorld::Store &store = esmStore.get(); + + if (const X *ptr = store.search (ref.mRefID)) + { + typename std::list::iterator iter = + std::find(mList.begin(), mList.end(), ref.mRefNum); + + LiveRef liveCellRef (ref, ptr); + + if (deleted) + liveCellRef.mData.setCount (0); - // Skip this when reference was deleted. - // TODO: Support respawning references, in this case, we need to track it somehow. - if (ref.mDeleted) { if (iter != mList.end()) - mList.erase(iter); - return; + *iter = liveCellRef; + else + mList.push_back (liveCellRef); } - - // for throwing exception on unhandled record type - const MWWorld::Store &store = esmStore.get(); - const X *ptr = store.search(ref.mRefID); - - /// \note no longer redundant - changed to Store::search(), don't throw - /// an exception on miss, try to continue (that's how MW does it, anyway) - if (ptr == NULL) { - std::cout << "Warning: could not resolve cell reference " << ref.mRefID << ", trying to continue anyway" << std::endl; - } else { - if (iter != mList.end()) - *iter = LiveRef(ref, ptr); - else - mList.push_back(LiveRef(ref, ptr)); + else + { + std::cerr + << "Error: could not resolve cell reference " << ref.mRefID + << " (dropping reference)" << std::endl; } } @@ -117,16 +187,13 @@ namespace MWWorld ESM::CellRef ref; // Get each reference in turn - while (mCell->getNextRef (esm[index], ref)) + bool deleted = false; + while (mCell->getNextRef (esm[index], ref, deleted)) { - std::string lowerCase = Misc::StringUtils::lowerCase (ref.mRefID); - if (ref.mDeleted) { - // Right now, don't do anything. Where is "listRefs" actually used, anyway? - // Skipping for now... + if (deleted) continue; - } - mIds.push_back (lowerCase); + mIds.push_back (Misc::StringUtils::lowerCase (ref.mRefID)); } } @@ -135,7 +202,7 @@ namespace MWWorld void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector &esm) { - assert (mCell); + assert (mCell); if (mCell->mContextList.empty()) return; // this is a dynamically generated cell -> skipping. @@ -148,98 +215,30 @@ namespace MWWorld mCell->restore (esm[index], i); ESM::CellRef ref; + ref.mRefNum.mContentFile = -1; // Get each reference in turn - while(mCell->getNextRef(esm[index], ref)) + bool deleted = false; + while(mCell->getNextRef(esm[index], ref, deleted)) { // Don't load reference if it was moved to a different cell. std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID); - ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefnum); + ESM::MovedCellRefTracker::const_iterator iter = + std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefNum); if (iter != mCell->mMovedRefs.end()) { continue; } - int rec = store.find(ref.mRefID); - - ref.mRefID = lowerCase; - - /* We can optimize this further by storing the pointer to the - record itself in store.all, so that we don't need to look it - up again here. However, never optimize. There are infinite - opportunities to do that later. - */ - switch(rec) - { - case ESM::REC_ACTI: mActivators.load(ref, store); break; - case ESM::REC_ALCH: mPotions.load(ref, store); break; - case ESM::REC_APPA: mAppas.load(ref, store); break; - case ESM::REC_ARMO: mArmors.load(ref, store); break; - case ESM::REC_BOOK: mBooks.load(ref, store); break; - case ESM::REC_CLOT: mClothes.load(ref, store); break; - case ESM::REC_CONT: mContainers.load(ref, store); break; - case ESM::REC_CREA: mCreatures.load(ref, store); break; - case ESM::REC_DOOR: mDoors.load(ref, store); break; - case ESM::REC_INGR: mIngreds.load(ref, store); break; - case ESM::REC_LEVC: mCreatureLists.load(ref, store); break; - case ESM::REC_LEVI: mItemLists.load(ref, store); break; - case ESM::REC_LIGH: mLights.load(ref, store); break; - case ESM::REC_LOCK: mLockpicks.load(ref, store); break; - case ESM::REC_MISC: mMiscItems.load(ref, store); break; - case ESM::REC_NPC_: mNpcs.load(ref, store); break; - case ESM::REC_PROB: mProbes.load(ref, store); break; - case ESM::REC_REPA: mRepairs.load(ref, store); break; - case ESM::REC_STAT: mStatics.load(ref, store); break; - case ESM::REC_WEAP: mWeapons.load(ref, store); break; - - case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break; - default: - std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; - } + + loadRef (ref, deleted, store); } } // Load moved references, from separately tracked list. for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) { - // Doesn't seem to work in one line... huh? Too sleepy to check... ESM::CellRef &ref = const_cast(*it); - //ESM::CellRef &ref = const_cast(it->second); - - int rec = store.find(ref.mRefID); - Misc::StringUtils::toLower(ref.mRefID); - - /* We can optimize this further by storing the pointer to the - record itself in store.all, so that we don't need to look it - up again here. However, never optimize. There are infinite - opportunities to do that later. - */ - switch(rec) - { - case ESM::REC_ACTI: mActivators.load(ref, store); break; - case ESM::REC_ALCH: mPotions.load(ref, store); break; - case ESM::REC_APPA: mAppas.load(ref, store); break; - case ESM::REC_ARMO: mArmors.load(ref, store); break; - case ESM::REC_BOOK: mBooks.load(ref, store); break; - case ESM::REC_CLOT: mClothes.load(ref, store); break; - case ESM::REC_CONT: mContainers.load(ref, store); break; - case ESM::REC_CREA: mCreatures.load(ref, store); break; - case ESM::REC_DOOR: mDoors.load(ref, store); break; - case ESM::REC_INGR: mIngreds.load(ref, store); break; - case ESM::REC_LEVC: mCreatureLists.load(ref, store); break; - case ESM::REC_LEVI: mItemLists.load(ref, store); break; - case ESM::REC_LIGH: mLights.load(ref, store); break; - case ESM::REC_LOCK: mLockpicks.load(ref, store); break; - case ESM::REC_MISC: mMiscItems.load(ref, store); break; - case ESM::REC_NPC_: mNpcs.load(ref, store); break; - case ESM::REC_PROB: mProbes.load(ref, store); break; - case ESM::REC_REPA: mRepairs.load(ref, store); break; - case ESM::REC_STAT: mStatics.load(ref, store); break; - case ESM::REC_WEAP: mWeapons.load(ref, store); break; - - case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break; - default: - std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n"; - } + loadRef (ref, false, store); } } @@ -268,4 +267,198 @@ namespace MWWorld return Ptr(); } + + void CellStore::loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store) + { + Misc::StringUtils::toLower (ref.mRefID); + + switch (store.find (ref.mRefID)) + { + case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; + case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break; + case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; + case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; + case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; + case ESM::REC_CLOT: mClothes.load(ref, deleted, store); break; + case ESM::REC_CONT: mContainers.load(ref, deleted, store); break; + case ESM::REC_CREA: mCreatures.load(ref, deleted, store); break; + case ESM::REC_DOOR: mDoors.load(ref, deleted, store); break; + case ESM::REC_INGR: mIngreds.load(ref, deleted, store); break; + case ESM::REC_LEVC: mCreatureLists.load(ref, deleted, store); break; + case ESM::REC_LEVI: mItemLists.load(ref, deleted, store); break; + case ESM::REC_LIGH: mLights.load(ref, deleted, store); break; + case ESM::REC_LOCK: mLockpicks.load(ref, deleted, store); break; + case ESM::REC_MISC: mMiscItems.load(ref, deleted, store); break; + case ESM::REC_NPC_: mNpcs.load(ref, deleted, store); break; + case ESM::REC_PROB: mProbes.load(ref, deleted, store); break; + case ESM::REC_REPA: mRepairs.load(ref, deleted, store); break; + case ESM::REC_STAT: mStatics.load(ref, deleted, store); break; + case ESM::REC_WEAP: mWeapons.load(ref, deleted, store); break; + + case 0: std::cerr << "Cell reference " + ref.mRefID + " not found!\n"; break; + + default: + std::cerr + << "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; + } + + void CellStore::writeReferences (ESM::ESMWriter& writer) const + { + writeReferenceCollection (writer, mActivators); + writeReferenceCollection (writer, mPotions); + writeReferenceCollection (writer, mAppas); + writeReferenceCollection (writer, mArmors); + writeReferenceCollection (writer, mBooks); + writeReferenceCollection (writer, mClothes); + writeReferenceCollection (writer, mContainers); + writeReferenceCollection (writer, mCreatures); + writeReferenceCollection (writer, mDoors); + writeReferenceCollection (writer, mIngreds); + writeReferenceCollection (writer, mCreatureLists); + writeReferenceCollection (writer, mItemLists); + writeReferenceCollection (writer, mLights); + writeReferenceCollection (writer, mLockpicks); + writeReferenceCollection (writer, mMiscItems); + writeReferenceCollection (writer, mNpcs); + writeReferenceCollection (writer, mProbes); + writeReferenceCollection (writer, mRepairs); + writeReferenceCollection (writer, mStatics); + writeReferenceCollection (writer, mWeapons); + } + + void CellStore::readReferences (ESM::ESMReader& reader, + const std::map& contentFileMap) + { + while (reader.isNextSub ("OBJE")) + { + unsigned int id = 0; + reader.getHT (id); + + switch (id) + { + case ESM::REC_ACTI: + + readReferenceCollection (reader, mActivators, contentFileMap); + break; + + case ESM::REC_ALCH: + + readReferenceCollection (reader, mPotions, contentFileMap); + break; + + case ESM::REC_APPA: + + readReferenceCollection (reader, mAppas, contentFileMap); + break; + + case ESM::REC_ARMO: + + readReferenceCollection (reader, mArmors, contentFileMap); + break; + + case ESM::REC_BOOK: + + readReferenceCollection (reader, mBooks, contentFileMap); + break; + + case ESM::REC_CLOT: + + readReferenceCollection (reader, mClothes, contentFileMap); + break; + + case ESM::REC_CONT: + + readReferenceCollection (reader, mContainers, contentFileMap); + break; + + case ESM::REC_CREA: + + readReferenceCollection (reader, mCreatures, contentFileMap); + break; + + case ESM::REC_DOOR: + + readReferenceCollection (reader, mDoors, contentFileMap); + break; + + case ESM::REC_INGR: + + readReferenceCollection (reader, mIngreds, contentFileMap); + break; + + case ESM::REC_LEVC: + + readReferenceCollection (reader, mCreatureLists, contentFileMap); + break; + + case ESM::REC_LEVI: + + readReferenceCollection (reader, mItemLists, contentFileMap); + break; + + case ESM::REC_LIGH: + + readReferenceCollection (reader, mLights, contentFileMap); + break; + + case ESM::REC_LOCK: + + readReferenceCollection (reader, mLockpicks, contentFileMap); + break; + + case ESM::REC_MISC: + + readReferenceCollection (reader, mMiscItems, contentFileMap); + break; + + case ESM::REC_NPC_: + + readReferenceCollection (reader, mNpcs, contentFileMap); + break; + + case ESM::REC_PROB: + + readReferenceCollection (reader, mProbes, contentFileMap); + break; + + case ESM::REC_REPA: + + readReferenceCollection (reader, mRepairs, contentFileMap); + break; + + case ESM::REC_STAT: + + readReferenceCollection (reader, mStatics, contentFileMap); + break; + + case ESM::REC_WEAP: + + readReferenceCollection (reader, mWeapons, contentFileMap); + break; + + default: + + throw std::runtime_error ("unknown type in cell reference section"); + } + } + } } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 8731c42dc..a4f219013 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 { @@ -25,7 +30,7 @@ namespace MWWorld // and the build will fail with an ugly three-way cyclic header dependence // so we need to pass the instantiation of the method to the lnker, when // all methods are known. - void load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore); + void load(ESM::CellRef &ref, bool deleted, const MWWorld::ESMStore &esmStore); LiveRef *find (const std::string& name) { @@ -133,6 +138,14 @@ namespace MWWorld Ptr searchInContainer (const std::string& id); + void loadState (const ESM::CellState& state); + + void saveState (ESM::CellState& state) const; + + void writeReferences (ESM::ESMWriter& writer) const; + + void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap); + private: template @@ -154,6 +167,10 @@ namespace MWWorld void loadRefs(const MWWorld::ESMStore &store, std::vector &esm); + void loadRef (ESM::CellRef& ref, bool deleted, const ESMStore& store); + ///< 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/class.cpp b/apps/openmw/mwworld/class.cpp index 9771ffde3..2110086d3 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -167,7 +167,7 @@ namespace MWWorld return 0; } - float Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const + int Class::getEnchantmentPoints (const MWWorld::Ptr& ptr) const { throw std::runtime_error ("class does not support enchanting"); } @@ -377,4 +377,8 @@ namespace MWWorld { throw std::runtime_error("class does not support gore"); } + + void Class::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const {} + + void Class::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const {} } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 0dee8b292..ad2cc3af4 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -9,6 +9,11 @@ #include "ptr.hpp" +namespace ESM +{ + struct ObjectState; +} + namespace Ogre { class Vector3; @@ -259,7 +264,7 @@ namespace MWWorld ///< @return the enchantment ID if the object is enchanted, otherwise an empty string /// (default implementation: return empty string) - virtual float getEnchantmentPoints (const MWWorld::Ptr& ptr) const; + virtual int getEnchantmentPoints (const MWWorld::Ptr& ptr) const; ///< @return the number of enchantment points available for possible enchanting virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; @@ -306,6 +311,14 @@ namespace MWWorld virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; + virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) + const; + ///< Read additional state from \a state into \a ptr. + + virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) + const; + ///< Write additional state from \a ptr into \a state. + static const Class& get (const std::string& key); ///< If there is no class for this \a key, an exception is thrown. diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 0c4226f9b..bd0704724 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -57,6 +59,54 @@ namespace } } +template +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList& collection, + const ESM::ObjectState& state) +{ + if (!LiveCellRef::checkState (state)) + return ContainerStoreIterator (this); // not valid anymore with current content files -> skip + + const T *record = MWBase::Environment::get().getWorld()->getStore(). + get().search (state.mRef.mRefID); + + if (!record) + return ContainerStoreIterator (this); + + LiveCellRef ref (record); + ref.load (state); + ref.mRef.mRefNum.mContentFile = -1; + collection.mList.push_back (ref); + + return ContainerStoreIterator (this, --collection.mList.end()); +} + +template +void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::ObjectState& state) const +{ + ref.save (state); +} + +template +void MWWorld::ContainerStore::storeStates (const CellRefList& collection, + std::vector > >& states, bool equipable) const +{ + for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); + iter!=collection.mList.end(); ++iter) + { + ESM::ObjectState state; + storeState (*iter, state); + int slot = equipable ? getSlot (*iter) : -1; + states.push_back (std::make_pair (state, std::make_pair (T::sRecordId, slot))); + } +} + +int MWWorld::ContainerStore::getSlot (const MWWorld::LiveCellRefBase& ref) const +{ + return -1; +} + +void MWWorld::ContainerStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot) {} + const std::string MWWorld::ContainerStore::sGoldId = "gold_001"; MWWorld::ContainerStore::ContainerStore() : mCachedWeight (0), mWeightUpToDate (false) {} @@ -192,13 +242,11 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_025") || Misc::StringUtils::ciEqual(ptr.getCellRef().mRefID, "gold_100")) { - int realCount = ptr.getRefData().getCount(); - if (ptr.getCellRef().mGoldValue > 1 && realCount == 1) - realCount = ptr.getCellRef().mGoldValue; + int realCount = count * ptr.getClass().getValue(ptr); for (MWWorld::ContainerStoreIterator iter (begin(type)); iter!=end(); ++iter) { - if (Misc::StringUtils::ciEqual((*iter).get()->mRef.mRefID, MWWorld::ContainerStore::sGoldId)) + if (Misc::StringUtils::ciEqual((*iter).getCellRef().mRefID, MWWorld::ContainerStore::sGoldId)) { iter->getRefData().setCount(iter->getRefData().getCount() + realCount); flagAsModified(); @@ -206,8 +254,8 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp (const Ptr& ptr, } } - MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, count); - return addNewStack(ref.getPtr(), count); + MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, realCount); + return addNewStack(ref.getPtr(), realCount); } // determine whether to stack or not @@ -495,6 +543,69 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) return Ptr(); } +void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const +{ + state.mItems.clear(); + + storeStates (potions, state.mItems); + storeStates (appas, state.mItems); + storeStates (armors, state.mItems, true); + storeStates (books, state.mItems); + storeStates (clothes, state.mItems, true); + storeStates (ingreds, state.mItems); + storeStates (lockpicks, state.mItems, true); + storeStates (miscItems, state.mItems); + storeStates (probes, state.mItems, true); + storeStates (repairs, state.mItems); + storeStates (weapons, state.mItems, true); + + state.mLights.clear(); + + for (MWWorld::CellRefList::List::const_iterator iter (lights.mList.begin()); + iter!=lights.mList.end(); ++iter) + { + ESM::LightState objectState; + storeState (*iter, objectState); + state.mLights.push_back (std::make_pair (objectState, getSlot (*iter))); + } +} + +void MWWorld::ContainerStore::readState (const ESM::InventoryState& state) +{ + clear(); + + for (std::vector > >::const_iterator + iter (state.mItems.begin()); iter!=state.mItems.end(); ++iter) + { + int slot = iter->second.second; + + switch (iter->second.first) + { + case ESM::REC_ALCH: getState (potions, iter->first); break; + case ESM::REC_APPA: getState (appas, iter->first); break; + case ESM::REC_ARMO: setSlot (getState (armors, iter->first), slot); break; + case ESM::REC_BOOK: getState (books, iter->first); break; + case ESM::REC_CLOT: setSlot (getState (clothes, iter->first), slot); break; + case ESM::REC_INGR: getState (ingreds, iter->first); break; + case ESM::REC_LOCK: setSlot (getState (lockpicks, iter->first), slot); break; + case ESM::REC_MISC: getState (miscItems, iter->first); break; + case ESM::REC_PROB: setSlot (getState (probes, iter->first), slot); break; + case ESM::REC_REPA: getState (repairs, iter->first); break; + case ESM::REC_WEAP: setSlot (getState (weapons, iter->first), slot); break; + + default: + + std::cerr << "invalid item type in inventory state" << std::endl; + } + } + + for (std::vector >::const_iterator iter (state.mLights.begin()); + iter!=state.mLights.end(); ++iter) + { + getState (lights, iter->first); + } +} + MWWorld::ContainerStoreIterator::ContainerStoreIterator (ContainerStore *container) : mType (-1), mMask (0), mContainer (container) diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 913ec544a..acf429891 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -8,6 +8,7 @@ namespace ESM { struct InventoryList; + struct InventoryState; } namespace MWWorld @@ -56,6 +57,24 @@ namespace MWWorld ContainerStoreIterator addImp (const Ptr& ptr, int count); void addInitialItem (const std::string& id, const std::string& owner, const std::string& faction, int count, bool topLevel=true); + template + ContainerStoreIterator getState (CellRefList& collection, + const ESM::ObjectState& state); + + template + void storeState (const LiveCellRef& ref, ESM::ObjectState& state) const; + + template + void storeStates (const CellRefList& collection, + std::vector > >& states, + bool equipable = false) const; + + virtual int getSlot (const MWWorld::LiveCellRefBase& ref) const; + ///< Return inventory slot that \a ref is in or -1 (if \a ref is not in a slot). + + virtual void setSlot (const MWWorld::ContainerStoreIterator& iter, int slot); + ///< Set slot for \a iter. Ignored if \a iter is an end iterator or if slot==-1. + public: ContainerStore(); @@ -113,7 +132,7 @@ namespace MWWorld void fill (const ESM::InventoryList& items, const std::string& owner, const std::string& faction, const MWWorld::ESMStore& store); ///< Insert items into *this. - void clear(); + virtual void clear(); ///< Empty container. float getWeight() const; @@ -125,6 +144,10 @@ namespace MWWorld Ptr search (const std::string& id); + void writeState (ESM::InventoryState& state) const; + + void readState (const ESM::InventoryState& state); + friend class ContainerStoreIterator; }; @@ -174,7 +197,7 @@ namespace MWWorld ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList::List::iterator); ContainerStoreIterator (ContainerStore *container, MWWorld::CellRefList::List::iterator); - void copy (const ContainerStoreIterator& src); + void copy (const ContainerStoreIterator& src); void incType(); @@ -202,7 +225,7 @@ namespace MWWorld ContainerStoreIterator operator++ (int); - ContainerStoreIterator& operator= (const ContainerStoreIterator& rhs); + ContainerStoreIterator& operator= (const ContainerStoreIterator& rhs); bool isEqual (const ContainerStoreIterator& iter) const; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index f1bff11a2..c5c826d47 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -52,7 +52,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) if (index == (int)~0) { // Tried to load a parent file that has not been loaded yet. This is bad, // the launcher should have taken care of this. - std::string fstring = "File " + fname + " asks for parent file " + masters[j].name + std::string fstring = "File " + esm.getName() + " asks for parent file " + masters[j].name + ", but it has not been loaded yet. Please check your load order."; esm.fail(fstring); } @@ -108,7 +108,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } // Insert the reference into the global lookup if (!id.empty() && isCacheableRecord(n.val)) { - mIds[id] = n.val; + mIds[Misc::StringUtils::lowerCase (id)] = n.val; } } listener->setProgress(esm.getFileOffset() / (float)esm.getFileSize() * 1000); @@ -139,4 +139,68 @@ void ESMStore::setUp() mAttributes.setUp(); } + int ESMStore::countSavedGameRecords() const + { + return + mPotions.getDynamicSize() + +mArmors.getDynamicSize() + +mBooks.getDynamicSize() + +mClasses.getDynamicSize() + +mClothes.getDynamicSize() + +mEnchants.getDynamicSize() + +mNpcs.getDynamicSize() + +mSpells.getDynamicSize() + +mWeapons.getDynamicSize(); + } + + void ESMStore::write (ESM::ESMWriter& writer) const + { + mPotions.write (writer); + mArmors.write (writer); + mBooks.write (writer); + mClasses.write (writer); + mClothes.write (writer); + mEnchants.write (writer); + mSpells.write (writer); + mWeapons.write (writer); + mNpcs.write (writer); + } + + bool ESMStore::readRecord (ESM::ESMReader& reader, int32_t type) + { + switch (type) + { + 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_SPEL: + case ESM::REC_WEAP: + case ESM::REC_NPC_: + + mStores[type]->read (reader); + + if (type==ESM::REC_NPC_) + { + // NPC record will always be last and we know that there can be only one + // dynamic NPC record (player) -> We are done here with dynamic record laoding + setUp(); + + const ESM::NPC *player = mNpcs.find ("player"); + + if (!mRaces.find (player->mRace) || + !mClasses.find (player->mClass)) + throw std::runtime_error ("Invalid player record (race or class unavilable"); + } + + return true; + + default: + + return false; + } + } + } // end namespace diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index ebb086cee..e6730c307 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -24,10 +24,8 @@ namespace MWWorld Store mBirthSigns; Store mClasses; Store mClothes; - Store mContChange; Store mContainers; Store mCreatures; - Store mCreaChange; Store mDialogs; Store mDoors; Store mEnchants; @@ -40,7 +38,6 @@ namespace MWWorld Store mLockpicks; Store mMiscItems; Store mNpcs; - Store mNpcChange; Store mProbes; Store mRaces; Store mRegions; @@ -103,7 +100,7 @@ namespace MWWorld { // Cell store needs access to this for tracking moved references mCells.mEsmStore = this; - + mStores[ESM::REC_ACTI] = &mActivators; mStores[ESM::REC_ALCH] = &mPotions; mStores[ESM::REC_APPA] = &mAppas; @@ -114,10 +111,8 @@ namespace MWWorld mStores[ESM::REC_CELL] = &mCells; mStores[ESM::REC_CLAS] = &mClasses; mStores[ESM::REC_CLOT] = &mClothes; - mStores[ESM::REC_CNTC] = &mContChange; mStores[ESM::REC_CONT] = &mContainers; mStores[ESM::REC_CREA] = &mCreatures; - mStores[ESM::REC_CREC] = &mCreaChange; mStores[ESM::REC_DIAL] = &mDialogs; mStores[ESM::REC_DOOR] = &mDoors; mStores[ESM::REC_ENCH] = &mEnchants; @@ -133,7 +128,6 @@ namespace MWWorld mStores[ESM::REC_LTEX] = &mLandTextures; mStores[ESM::REC_MISC] = &mMiscItems; mStores[ESM::REC_NPC_] = &mNpcs; - mStores[ESM::REC_NPCC] = &mNpcChange; mStores[ESM::REC_PGRD] = &mPathgrids; mStores[ESM::REC_PROB] = &mProbes; mStores[ESM::REC_RACE] = &mRaces; @@ -215,6 +209,13 @@ namespace MWWorld // This method must be called once, after loading all master/plugin files. This can only be done // from the outside, so it must be public. void setUp(); + + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); + ///< \return Known type? }; template <> @@ -287,11 +288,6 @@ namespace MWWorld return mClothes; } - template <> - inline const Store &ESMStore::get() const { - return mContChange; - } - template <> inline const Store &ESMStore::get() const { return mContainers; @@ -302,11 +298,6 @@ namespace MWWorld return mCreatures; } - template <> - inline const Store &ESMStore::get() const { - return mCreaChange; - } - template <> inline const Store &ESMStore::get() const { return mDialogs; @@ -367,11 +358,6 @@ namespace MWWorld return mNpcs; } - template <> - inline const Store &ESMStore::get() const { - return mNpcChange; - } - template <> inline const Store &ESMStore::get() const { return mProbes; diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index a905f8aae..879ffa8e3 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -3,21 +3,15 @@ #include +#include + +#include +#include + #include "esmstore.hpp" namespace MWWorld { - std::vector Globals::getGlobals () const - { - std::vector retval; - Collection::const_iterator it; - for(it = mVariables.begin(); it != mVariables.end(); ++it){ - retval.push_back(it->first); - } - - return retval; - } - Globals::Collection::const_iterator Globals::find (const std::string& name) const { Collection::const_iterator iter = mVariables.find (name); @@ -38,122 +32,78 @@ namespace MWWorld return iter; } - Globals::Globals (const MWWorld::ESMStore& store) + void Globals::fill (const MWWorld::ESMStore& store) { - const MWWorld::Store &globals = store.get(); - MWWorld::Store::iterator iter = globals.begin(); - for (; iter != globals.end(); ++iter) - { - char type = ' '; - Data value; - - switch (iter->mValue.getType()) - { - case ESM::VT_Short: - - type = 's'; - value.mShort = iter->mValue.getInteger(); - break; + mVariables.clear(); - case ESM::VT_Long: + const MWWorld::Store& globals = store.get(); - type = 'l'; - value.mLong = iter->mValue.getInteger(); - break; - - case ESM::VT_Float: - - type = 'f'; - value.mFloat = iter->mValue.getFloat(); - break; - - default: - - throw std::runtime_error ("unsupported global variable type"); - } - - mVariables.insert (std::make_pair (iter->mId, std::make_pair (type, value))); + for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); + ++iter) + { + mVariables.insert (std::make_pair (iter->mId, iter->mValue)); } } - const Globals::Data& Globals::operator[] (const std::string& name) const + const ESM::Variant& Globals::operator[] (const std::string& name) const { - Collection::const_iterator iter = find (name); - - return iter->second.second; + return find (name)->second; } - Globals::Data& Globals::operator[] (const std::string& name) + ESM::Variant& Globals::operator[] (const std::string& name) { - Collection::iterator iter = find (name); - - return iter->second.second; + return find (name)->second; } - void Globals::setInt (const std::string& name, int value) + char Globals::getType (const std::string& name) const { - Collection::iterator iter = find (name); + Collection::const_iterator iter = mVariables.find (name); - switch (iter->second.first) + if (iter==mVariables.end()) + return ' '; + + switch (iter->second.getType()) { - case 's': iter->second.second.mShort = value; break; - case 'l': iter->second.second.mLong = value; break; - case 'f': iter->second.second.mFloat = value; break; + case ESM::VT_Short: return 's'; + case ESM::VT_Long: return 'l'; + case ESM::VT_Float: return 'f'; - default: throw std::runtime_error ("unsupported global variable type"); + default: return ' '; } } - void Globals::setFloat (const std::string& name, float value) + int Globals::countSavedGameRecords() const { - Collection::iterator iter = find (name); - - switch (iter->second.first) - { - case 's': iter->second.second.mShort = value; break; - case 'l': iter->second.second.mLong = value; break; - case 'f': iter->second.second.mFloat = value; break; - - default: throw std::runtime_error ("unsupported global variable type"); - } + return mVariables.size(); } - int Globals::getInt (const std::string& name) const + void Globals::write (ESM::ESMWriter& writer) const { - Collection::const_iterator iter = find (name); - - switch (iter->second.first) + for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { - case 's': return iter->second.second.mShort; - case 'l': return iter->second.second.mLong; - case 'f': return iter->second.second.mFloat; - - default: throw std::runtime_error ("unsupported global variable type"); + writer.startRecord (ESM::REC_GLOB); + writer.writeHNString ("NAME", iter->first); + iter->second.write (writer, ESM::Variant::Format_Global); + writer.endRecord (ESM::REC_GLOB); } } - float Globals::getFloat (const std::string& name) const + bool Globals::readRecord (ESM::ESMReader& reader, int32_t type) { - Collection::const_iterator iter = find (name); - - switch (iter->second.first) + if (type==ESM::REC_GLOB) { - case 's': return iter->second.second.mShort; - case 'l': return iter->second.second.mLong; - case 'f': return iter->second.second.mFloat; + std::string id = reader.getHNString ("NAME"); - default: throw std::runtime_error ("unsupported global variable type"); - } - } + Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (id)); - char Globals::getType (const std::string& name) const - { - Collection::const_iterator iter = mVariables.find (name); + if (iter!=mVariables.end()) + iter->second.read (reader, ESM::Variant::Format_Global); + else + reader.skipHRecord(); - if (iter==mVariables.end()) - return ' '; + return true; + } - return iter->second.first; + return false; } } - diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index 681bd560e..8f521c8a6 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -5,7 +5,16 @@ #include #include +#include + #include +#include + +namespace ESM +{ + class ESMWriter; + class ESMReader; +} namespace MWWorld { @@ -13,49 +22,37 @@ namespace MWWorld class Globals { - public: - - union Data - { - Interpreter::Type_Float mFloat; - Interpreter::Type_Float mLong; // Why Morrowind, why? :( - Interpreter::Type_Float mShort; - }; - - typedef std::map > Collection; - private: - + + typedef std::map Collection; + Collection mVariables; // type, value - + Collection::const_iterator find (const std::string& name) const; Collection::iterator find (const std::string& name); - + public: - - Globals (const MWWorld::ESMStore& store); - - const Data& operator[] (const std::string& name) const; - - Data& operator[] (const std::string& name); - - void setInt (const std::string& name, int value); - ///< Set value independently from real type. - - void setFloat (const std::string& name, float value); - ///< Set value independently from real type. - - int getInt (const std::string& name) const; - ///< Get value independently from real type. - - float getFloat (const std::string& name) const; - ///< Get value independently from real type. - + + const ESM::Variant& operator[] (const std::string& name) const; + + ESM::Variant& operator[] (const std::string& name); + char getType (const std::string& name) const; ///< If there is no global variable with this name, ' ' is returned. - std::vector getGlobals () const; + void fill (const MWWorld::ESMStore& store); + ///< Replace variables with variables from \a store with default values. + + int countSavedGameRecords() const; + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); + ///< Records for variables that do not exist are dropped silently. + /// + /// \return Known type? + }; } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index ea43314e6..e00276293 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -34,6 +34,13 @@ void MWWorld::InventoryStore::copySlots (const InventoryStore& store) mSlots.push_back (slot); } + + // some const-trickery, required because of a flaw in the handling of MW-references and the + // resulting workarounds + std::size_t distance = std::distance (const_cast (store).begin(), const_cast (store).mSelectedEnchantItem); + ContainerStoreIterator slot = begin(); + std::advance (slot, distance); + mSelectedEnchantItem = slot; } void MWWorld::InventoryStore::initSlots (TSlots& slots_) @@ -42,6 +49,21 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots_) slots_.push_back (end()); } +int MWWorld::InventoryStore::getSlot (const MWWorld::LiveCellRefBase& ref) const +{ + for (int i = 0; i (mSlots.size()); ++i) + if (mSlots[i].getType()!=-1 && mSlots[i]->getBase()==&ref) + return i; + + return -1; +} + +void MWWorld::InventoryStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot) +{ + if (iter!=end() && slot>=0 && slotgetRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); - it->getRefData().setCount(0); - retval = iter; - break; - } + iter->getRefData().setCount(iter->getRefData().getCount() + it->getRefData().getCount()); + it->getRefData().setCount(0); + retval = iter; + break; } } @@ -629,3 +652,10 @@ void MWWorld::InventoryStore::purgeEffect(short effectId) { mMagicEffects.add(MWMechanics::EffectKey(effectId), -mMagicEffects.get(MWMechanics::EffectKey(effectId)).mMagnitude); } + +void MWWorld::InventoryStore::clear() +{ + mSlots.clear(); + initSlots (mSlots); + ContainerStore::clear(); +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index d97bdf365..714ba47da 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -105,6 +105,12 @@ namespace MWWorld void fireEquipmentChangedEvent(); + virtual int getSlot (const MWWorld::LiveCellRefBase& ref) const; + ///< Return inventory slot that \a ref is in or -1 (if \a ref is not in a slot). + + virtual void setSlot (const MWWorld::ContainerStoreIterator& iter, int slot); + ///< Set slot for \a iter. Ignored if \a iter is an end iterator or if slot==-1. + public: InventoryStore(); @@ -162,12 +168,10 @@ namespace MWWorld /// /// @return the number of items actually removed - ContainerStoreIterator unequipSlot(int slot, const Ptr& actor, bool restack = true); + ContainerStoreIterator unequipSlot(int slot, const Ptr& actor); ///< Unequip \a slot. /// /// @return an iterator to the item that was previously in the slot - /// (if \a restack is true, the item can be re-stacked so its count - /// may differ from when it was equipped). ContainerStoreIterator unequipItem(const Ptr& item, const Ptr& actor); ///< Unequip an item identified by its Ptr. An exception is thrown @@ -187,6 +191,9 @@ namespace MWWorld void purgeEffect (short effectId); ///< Remove a magic effect + + virtual void clear(); + ///< Empty container. }; } diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp new file mode 100644 index 000000000..d71704fd7 --- /dev/null +++ b/apps/openmw/mwworld/livecellref.cpp @@ -0,0 +1,29 @@ + +#include "livecellref.hpp" + +#include + +#include "ptr.hpp" +#include "class.hpp" + +void MWWorld::LiveCellRefBase::loadImp (const ESM::ObjectState& state) +{ + mRef = state.mRef; + mData = RefData (state); + Ptr ptr (this); + mClass->readAdditionalState (ptr, state); +} + +void MWWorld::LiveCellRefBase::saveImp (ESM::ObjectState& state) const +{ + state.mRef = mRef; + mData.write (state); + /// \todo get rid of this cast once const-correct Ptr are available + Ptr ptr (const_cast (this)); + mClass->writeAdditionalState (ptr, state); +} + +bool MWWorld::LiveCellRefBase::checkStateImp (const ESM::ObjectState& state) +{ + return true; +} \ No newline at end of file diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 415351e78..b2e4d6d56 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -7,6 +7,11 @@ #include "refdata.hpp" +namespace ESM +{ + struct ObjectState; +} + namespace MWWorld { class Ptr; @@ -29,8 +34,39 @@ namespace MWWorld LiveCellRefBase(std::string type, const ESM::CellRef &cref=ESM::CellRef()); /* Need this for the class to be recognized as polymorphic */ virtual ~LiveCellRefBase() { } + + virtual void load (const ESM::ObjectState& state) = 0; + ///< Load state into a LiveCellRef, that has already been initialised with base and class. + /// + /// \attention Must not be called with an invalid \a state. + + virtual void save (ESM::ObjectState& state) const = 0; + ///< Save LiveCellRef state into \a state. + + protected: + + void loadImp (const ESM::ObjectState& state); + ///< Load state into a LiveCellRef, that has already been initialised with base and + /// class. + /// + /// \attention Must not be called with an invalid \a state. + + void saveImp (ESM::ObjectState& state) const; + ///< Save LiveCellRef state into \a state. + + static bool checkStateImp (const ESM::ObjectState& state); + ///< Check if state is valid and report errors. + /// + /// \return Valid? + /// + /// \note Does not check if the RefId exists. }; + inline bool operator== (const LiveCellRefBase& cellRef, const ESM::CellRef::RefNum refNum) + { + return cellRef.mRef.mRefNum==refNum; + } + /// A reference to one object (of any type) in a cell. /// /// Constructing this with a CellRef instance in the constructor means that @@ -50,9 +86,41 @@ namespace MWWorld // The object that this instance is based on. const X* mBase; + + virtual void load (const ESM::ObjectState& state); + ///< Load state into a LiveCellRef, that has already been initialised with base and class. + /// + /// \attention Must not be called with an invalid \a state. + + virtual void save (ESM::ObjectState& state) const; + ///< Save LiveCellRef state into \a state. + + static bool checkState (const ESM::ObjectState& state); + ///< Check if state is valid and report errors. + /// + /// \return Valid? + /// + /// \note Does not check if the RefId exists. }; -// template bool operator==(const LiveCellRef& ref, int pRefnum); + template + void LiveCellRef::load (const ESM::ObjectState& state) + { + loadImp (state); + } + + template + void LiveCellRef::save (ESM::ObjectState& state) const + { + saveImp (state); + } + + template + bool LiveCellRef::checkState (const ESM::ObjectState& state) + { + return checkStateImp (state); + } + } #endif diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 5ec5ca9b5..997e9e32c 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -11,7 +11,7 @@ namespace { template void listCellScripts (MWWorld::LocalScripts& localScripts, - MWWorld::CellRefList& cellRefList, MWWorld::Ptr::CellStore *cell) + MWWorld::CellRefList& cellRefList, MWWorld::CellStore *cell) { for (typename MWWorld::CellRefList::List::iterator iter ( cellRefList.mList.begin()); @@ -27,15 +27,15 @@ namespace // Adds scripts for items in containers (containers/npcs/creatures) template void listCellScriptsCont (MWWorld::LocalScripts& localScripts, - MWWorld::CellRefList& cellRefList, MWWorld::Ptr::CellStore *cell) + MWWorld::CellRefList& cellRefList, MWWorld::CellStore *cell) { for (typename MWWorld::CellRefList::List::iterator iter ( cellRefList.mList.begin()); iter!=cellRefList.mList.end(); ++iter) { - - MWWorld::Ptr containerPtr (&*iter, cell); - + + MWWorld::Ptr containerPtr (&*iter, cell); + MWWorld::ContainerStore& container = MWWorld::Class::get(containerPtr).getContainerStore(containerPtr); for(MWWorld::ContainerStoreIterator it3 = container.begin(); it3 != container.end(); ++it3) { @@ -99,7 +99,7 @@ void MWWorld::LocalScripts::add (const std::string& scriptName, const Ptr& ptr) } } -void MWWorld::LocalScripts::addCell (Ptr::CellStore *cell) +void MWWorld::LocalScripts::addCell (CellStore *cell) { listCellScripts (*this, cell->mActivators, cell); listCellScripts (*this, cell->mPotions, cell); @@ -128,7 +128,7 @@ void MWWorld::LocalScripts::clear() mScripts.clear(); } -void MWWorld::LocalScripts::clearCell (Ptr::CellStore *cell) +void MWWorld::LocalScripts::clearCell (CellStore *cell) { std::list >::iterator iter = mScripts.begin(); diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 0b21fd78b..0e21c55ac 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -25,6 +25,8 @@ namespace MWWorld { LiveCellRef ref; ref.mBase = instance; + ref.mRef.mRefNum.mIndex = 0; + ref.mRef.mRefNum.mContentFile = -1; mRef = ref; mPtr = Ptr (&boost::any_cast&> (mRef), 0); @@ -64,8 +66,9 @@ namespace MWWorld // initialise ESM::CellRef& cellRef = mPtr.getCellRef(); - cellRef.mRefID = Misc::StringUtils::lowerCase(name); - cellRef.mRefnum = -1; + cellRef.mRefID = Misc::StringUtils::lowerCase (name); + cellRef.mRefNum.mIndex = 0; + cellRef.mRefNum.mContentFile = -1; cellRef.mScale = 1; cellRef.mFactIndex = 0; cellRef.mCharge = -1; diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 3ea6c62f8..c1cce84fc 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -1,6 +1,13 @@ #include "player.hpp" +#include + +#include +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" @@ -34,9 +41,6 @@ namespace MWWorld void Player::set(const ESM::NPC *player) { mPlayer.mBase = player; - - float* playerPos = mPlayer.mData.getPosition().pos; - playerPos[0] = playerPos[1] = playerPos[2] = 0; } void Player::setCell (MWWorld::CellStore *cellStore) @@ -171,4 +175,102 @@ namespace MWWorld if (mMarkedCell) markedPosition = mMarkedPosition; } + + void Player::clear() + { + mCellStore = 0; + mSign.clear(); + mMarkedCell = 0; + mAutoMove = false; + mForwardBackward = 0; + mTeleported = false; + } + + void Player::write (ESM::ESMWriter& writer) const + { + ESM::Player player; + + mPlayer.save (player.mObject); + player.mCellId = mCellStore->mCell->getCellId(); + + player.mBirthsign = mSign; + + player.mLastKnownExteriorPosition[0] = mLastKnownExteriorPosition.x; + player.mLastKnownExteriorPosition[1] = mLastKnownExteriorPosition.y; + player.mLastKnownExteriorPosition[2] = mLastKnownExteriorPosition.z; + + if (mMarkedCell) + { + player.mHasMark = true; + player.mMarkedPosition = mMarkedPosition; + player.mMarkedCell = mMarkedCell->mCell->getCellId(); + } + else + player.mHasMark = false; + + player.mAutoMove = mAutoMove ? 1 : 0; + + writer.startRecord (ESM::REC_PLAY); + player.save (writer); + writer.endRecord (ESM::REC_PLAY); + } + + bool Player::readRecord (ESM::ESMReader& reader, int32_t type) + { + if (type==ESM::REC_PLAY) + { + ESM::Player player; + player.load (reader); + + if (!mPlayer.checkState (player.mObject)) + { + // this is the one object we can not silently drop. + throw std::runtime_error ("invalid player state record (object state)"); + } + + mPlayer.load (player.mObject); + + MWBase::World& world = *MWBase::Environment::get().getWorld(); + + mCellStore = world.getCell (player.mCellId); + + if (!player.mBirthsign.empty() && + !world.getStore().get().search (player.mBirthsign)) + throw std::runtime_error ("invalid player state record (birthsign)"); + + mSign = player.mBirthsign; + + mLastKnownExteriorPosition.x = player.mLastKnownExteriorPosition[0]; + mLastKnownExteriorPosition.y = player.mLastKnownExteriorPosition[1]; + mLastKnownExteriorPosition.z = player.mLastKnownExteriorPosition[2]; + + if (player.mHasMark && !player.mMarkedCell.mPaged) + { + // interior cell -> need to check if it exists (exterior cell will be + // generated on the fly) + + if (!world.getStore().get().search (player.mMarkedCell.mWorldspace)) + player.mHasMark = false; // drop mark silently + } + + if (player.mHasMark) + { + mMarkedPosition = player.mMarkedPosition; + mMarkedCell = world.getCell (player.mMarkedCell); + } + else + { + mMarkedCell = 0; + } + + mAutoMove = player.mAutoMove!=0; + + mForwardBackward = 0; + mTeleported = false; + + return true; + } + + return false; + } } diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index 1df848111..7eb023a2b 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -11,6 +11,8 @@ namespace ESM { struct NPC; + class ESMWriter; + class ESMReader; } namespace MWBase @@ -86,6 +88,12 @@ namespace MWWorld bool wasTeleported() const; void setTeleported(bool teleported); + + void clear(); + + void write (ESM::ESMWriter& writer) const; + + bool readRecord (ESM::ESMReader& reader, int32_t type); }; } #endif diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 384bd71b1..67bfe4900 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -21,6 +21,14 @@ const std::string& MWWorld::Ptr::getTypeName() const throw std::runtime_error("Can't get type name from an empty object."); } +MWWorld::LiveCellRefBase *MWWorld::Ptr::getBase() const +{ + if (!mRef) + throw std::runtime_error ("Can't access cell ref pointed to by null Ptr"); + + return mRef; +} + ESM::CellRef& MWWorld::Ptr::getCellRef() const { assert(mRef); diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index e5352da28..1212619d0 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -14,9 +14,6 @@ namespace MWWorld { public: - typedef MWWorld::CellStore CellStore; - ///< \deprecated - MWWorld::LiveCellRefBase *mRef; CellStore *mCell; ContainerStore *mContainerStore; @@ -55,11 +52,13 @@ namespace MWWorld throw std::runtime_error(str.str()); } + MWWorld::LiveCellRefBase *getBase() const; + ESM::CellRef& getCellRef() const; RefData& getRefData() const; - Ptr::CellStore *getCell() const + CellStore *getCell() const { assert(mCell); return mCell; diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index c1a3ae785..8d48078b1 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -3,6 +3,8 @@ #include +#include + #include "customdata.hpp" #include "cellstore.hpp" @@ -32,6 +34,17 @@ namespace MWWorld mCustomData = 0; } + RefData::RefData() + : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mCustomData (0) + { + for (int i=0; i<3; ++i) + { + mLocalRotation.rot[i] = 0; + mPosition.pos[i] = 0; + mPosition.rot[i] = 0; + } + } + RefData::RefData (const ESM::CellRef& cellRef) : mBaseNode(0), mHasLocals (false), mEnabled (true), mCount (1), mPosition (cellRef.mPos), mCustomData (0) @@ -41,6 +54,14 @@ namespace MWWorld mLocalRotation.rot[2]=0; } + RefData::RefData (const ESM::ObjectState& objectState) + : mBaseNode (0), mHasLocals (false), mEnabled (objectState.mEnabled), + mCount (objectState.mCount), mPosition (objectState.mPosition), mCustomData (0) + { + for (int i=0; i<3; ++i) + mLocalRotation.rot[i] = objectState.mLocalRotation[i]; + } + RefData::RefData (const RefData& refData) : mBaseNode(0), mCustomData (0) { @@ -55,6 +76,17 @@ namespace MWWorld } } + void RefData::write (ESM::ObjectState& objectState) const + { + objectState.mHasLocals = false; + objectState.mEnabled = mEnabled; + objectState.mCount = mCount; + objectState.mPosition = mPosition; + + for (int i=0; i<3; ++i) + objectState.mLocalRotation[i] = mLocalRotation.rot[i]; + } + RefData& RefData::operator= (const RefData& refData) { try @@ -88,7 +120,7 @@ namespace MWWorld static const std::string empty; return empty; } - + return mBaseNode->getName(); } @@ -120,7 +152,7 @@ namespace MWWorld { if(count == 0) MWBase::Environment::get().getWorld()->removeRefScript(this); - + mCount = count; } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index d5701efc5..19e3d4882 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -14,6 +14,7 @@ namespace ESM { class Script; class CellRef; + struct ObjectState; } namespace MWWorld @@ -48,15 +49,25 @@ namespace MWWorld public: + RefData(); + /// @param cellRef Used to copy constant data such as position into this class where it can /// be altered without effecting the original data. This makes it possible /// to reset the position as the orignal data is still held in the CellRef RefData (const ESM::CellRef& cellRef); + RefData (const ESM::ObjectState& objectState); + ///< Ignores local variables and custom data (not enough context available here to + /// perform these operations). + RefData (const RefData& refData); ~RefData(); + void write (ESM::ObjectState& objectState) const; + ///< Ignores local variables and custom data (not enough context available here to + /// perform these operations). + RefData& operator= (const RefData& refData); /// Return OGRE handle (may be empty). diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 0d5fb0560..167adf301 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -118,7 +118,7 @@ namespace MWWorld mActiveCells.erase(*iter); } - void Scene::loadCell (Ptr::CellStore *cell, Loading::Listener* loadingListener) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener) { std::pair result = mActiveCells.insert(cell); @@ -163,7 +163,7 @@ namespace MWWorld MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); } - void Scene::playerCellChange(MWWorld::CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos) + void Scene::playerCellChange(CellStore *cell, const ESM::Position& pos, bool adjustPlayerPos) { MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr old = world->getPlayerPtr(); @@ -441,7 +441,7 @@ namespace MWWorld changeCell (x, y, position, true); } - Ptr::CellStore* Scene::getCurrentCell () + CellStore* Scene::getCurrentCell () { return mCurrentCell; } @@ -451,7 +451,7 @@ namespace MWWorld mCellChanged = false; } - int Scene::countRefs (const Ptr::CellStore& cell) + int Scene::countRefs (const CellStore& cell) { return cell.mActivators.mList.size() + cell.mPotions.mList.size() @@ -475,7 +475,7 @@ namespace MWWorld + cell.mNpcs.mList.size(); } - void Scene::insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener) + void Scene::insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener) { // Loop through all references in the cell insertCellRefList(mRendering, cell.mActivators, cell, *mPhysics, rescale, loadingListener); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 73c3c4b12..665274831 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -56,9 +56,9 @@ namespace MWWorld void playerCellChange (CellStore *cell, const ESM::Position& position, bool adjustPlayerPos = true); - void insertCell (Ptr::CellStore &cell, bool rescale, Loading::Listener* loadingListener); + void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); - int countRefs (const Ptr::CellStore& cell); + int countRefs (const CellStore& cell); public: diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 8e4c5ecef..1156cbc15 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -10,7 +10,7 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // and we merge all this data into one Cell object. However, we can't simply search for the cell id, // as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they // are not available until both cells have been loaded! So first, proceed as usual. - + // All cells have a name record, even nameless exterior cells. std::string idLower = Misc::StringUtils::lowerCase(id); ESM::Cell *cell = new ESM::Cell; @@ -30,13 +30,14 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following // implementation when the oher implementation works as well. - cell->getNextRef(esm, ref); + bool deleted = false; + cell->getNextRef(esm, ref, deleted); // Add data required to make reference appear in the correct cell. // We should not need to test for duplicates, as this part of the code is pre-cell merge. cell->mMovedRefs.push_back(cMRef); // But there may be duplicates here! - ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefnum); + ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefNum); if (iter == cellAlt->mLeasedRefs.end()) cellAlt->mLeasedRefs.push_back(ref); else @@ -72,11 +73,11 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // merge lists of leased references, use newer data in case of conflict for (ESM::MovedCellRefTracker::const_iterator it = cell->mMovedRefs.begin(); it != cell->mMovedRefs.end(); ++it) { // remove reference from current leased ref tracker and add it to new cell - ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefnum); + ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefNum); if (itold != oldcell->mMovedRefs.end()) { ESM::MovedCellRef target0 = *itold; ESM::Cell *wipecell = const_cast(search(target0.mTarget[0], target0.mTarget[1])); - ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefnum); + ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefNum); wipecell->mLeasedRefs.erase(it_lease); *itold = *it; } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index c25197319..7bd00d6bf 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -6,6 +6,8 @@ #include #include +#include + #include "recordcmp.hpp" namespace MWWorld @@ -18,10 +20,16 @@ namespace MWWorld virtual void listIdentifier(std::vector &list) const {} virtual size_t getSize() const = 0; + virtual int getDynamicSize() const { return 0; } virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} + + virtual void write (ESM::ESMWriter& writer) const {} + + virtual void read (ESM::ESMReader& reader) {} + ///< Read into dynamic storage }; template @@ -193,6 +201,7 @@ namespace MWWorld void setUp() { //std::sort(mStatic.begin(), mStatic.end(), RecordCmp()); + mShared.clear(); mShared.reserve(mStatic.size()); typename std::map::iterator it = mStatic.begin(); for (; it != mStatic.end(); ++it) { @@ -212,6 +221,11 @@ namespace MWWorld return mShared.size(); } + int getDynamicSize() const + { + return mDynamic.size(); + } + void listIdentifier(std::vector &list) const { list.reserve(list.size() + getSize()); typename std::vector::const_iterator it = mShared.begin(); @@ -290,8 +304,42 @@ namespace MWWorld bool erase(const T &item) { return erase(item.mId); } + + void write (ESM::ESMWriter& writer) const + { + for (typename Dynamic::const_iterator iter (mDynamic.begin()); iter!=mDynamic.end(); + ++iter) + { + writer.startRecord (T::sRecordId); + writer.writeHNString ("NAME", iter->second.mId); + iter->second.save (writer); + writer.endRecord (T::sRecordId); + } + } + + void read (ESM::ESMReader& reader) + { + T record; + record.mId = reader.getHNString ("NAME"); + record.load (reader); + insert (record); + } }; + template <> + inline void Store::clearDynamic() + { + std::map::iterator iter = mDynamic.begin(); + + while (iter!=mDynamic.end()) + if (iter->first=="player") + ++iter; + else + mDynamic.erase (iter++); + + mShared.clear(); + } + template <> inline void Store::load(ESM::ESMReader &esm, const std::string &id) { std::string idLower = Misc::StringUtils::lowerCase(id); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 0394a2edd..4e240195a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -21,7 +22,6 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/scriptmanager.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" @@ -50,30 +50,6 @@ using namespace Ogre; namespace { -/* // NOTE this code is never instantiated (proper copy in localscripts.cpp), - // so this commented out to not produce syntactic errors - - template - void listCellScripts (const MWWorld::ESMStore& store, - MWWorld::CellRefList& cellRefList, MWWorld::LocalScripts& localScripts, - MWWorld::Ptr::CellStore *cell) - { - for (typename MWWorld::CellRefList::List::iterator iter ( - cellRefList.mList.begin()); - iter!=cellRefList.mList.end(); ++iter) - { - if (!iter->mBase->mScript.empty() && iter->mData.getCount()) - { - if (const ESM::Script *script = store.get().find (iter->mBase->mScript)) - { - iter->mData.setLocals (*script); - - localScripts.add (iter->mBase->mScript, MWWorld::Ptr (&*iter, cell)); - } - } - } - } -*/ template MWWorld::LiveCellRef *searchViaHandle (const std::string& handle, MWWorld::CellRefList& refList) @@ -127,7 +103,7 @@ namespace MWWorld LoadersContainer mLoaders; }; - Ptr World::getPtrViaHandle (const std::string& handle, Ptr::CellStore& cell) + Ptr World::getPtrViaHandle (const std::string& handle, CellStore& cell) { if (MWWorld::LiveCellRef *ref = searchViaHandle (handle, cell.mActivators)) @@ -198,9 +174,9 @@ namespace MWWorld { if (mSky && (isCellExterior() || isCellQuasiExterior())) { - mRendering->skySetHour (mGlobalVariables->getFloat ("gamehour")); - mRendering->skySetDate (mGlobalVariables->getInt ("day"), - mGlobalVariables->getInt ("month")); + mRendering->skySetHour (mGlobalVariables["gamehour"].getFloat()); + mRendering->skySetDate (mGlobalVariables["day"].getInteger(), + mGlobalVariables["month"].getInteger()); mRendering->skyEnable(); } @@ -213,11 +189,12 @@ namespace MWWorld const std::vector& contentFiles, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, ToUTF8::Utf8Encoder* encoder, const std::map& fallbackMap, int mActivationDistanceOverride) - : mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0), + : mPlayer (0), mLocalScripts (mStore), mSky (true), mCells (mStore, mEsm), mActivationDistanceOverride (mActivationDistanceOverride), - mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(false), - mFacedDistance(FLT_MAX), mGodMode(false), mGoToJail(false) + mFallback(fallbackMap), mPlayIntro(0), mTeleportEnabled(true), mLevitationEnabled(true), + mFacedDistance(FLT_MAX), mGodMode(false), mContentFiles (contentFiles), + mGoToJail(false) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -253,7 +230,7 @@ namespace MWWorld mStore.setUp(); mStore.movePlayerRecord(); - mGlobalVariables = new Globals (mStore); + mGlobalVariables.fill (mStore); mWorldScene = new Scene(*mRendering, mPhysics); } @@ -263,29 +240,15 @@ namespace MWWorld mGoToJail = false; mLevitationEnabled = true; mTeleportEnabled = true; - mWorldScene->changeToVoid(); - - mStore.clearDynamic(); - mStore.setUp(); - - mCells.clear(); // Rebuild player setupPlayer(); - mPlayer->setCell(NULL); - MWWorld::Ptr player = mPlayer->getPlayer(); - - // removes NpcStats, ContainerStore etc - player.getRefData().setCustomData(NULL); renderPlayer(); - mRendering->resetCamera(); - - // make sure to do this so that local scripts from items that were in the players inventory are removed - mLocalScripts.clear(); MWBase::Environment::get().getWindowManager()->updatePlayer(); + // FIXME: this will add cell 0,0 as visible on the global map ESM::Position pos; const int cellSize = 8192; pos.pos[0] = cellSize/2; @@ -296,29 +259,82 @@ namespace MWWorld pos.rot[2] = 0; mWorldScene->changeToExteriorCell(pos); - - // enable collision - if(!mPhysics->toggleCollisionMode()) - mPhysics->toggleCollisionMode(); - // FIXME: should be set to 1, but the sound manager won't pause newly started sounds mPlayIntro = 2; - // global variables - delete mGlobalVariables; - mGlobalVariables = new Globals (mStore); - // set new game mark - mGlobalVariables->setInt ("chargenstate", 1); - mGlobalVariables->setInt ("pcrace", 3); + mGlobalVariables["chargenstate"].setInteger (1); + mGlobalVariables["pcrace"].setInteger (3); // we don't want old weather to persist on a new game delete mWeatherManager; + mWeatherManager = 0; mWeatherManager = new MWWorld::WeatherManager(mRendering,&mFallback); + } - MWBase::Environment::get().getScriptManager()->resetGlobalScripts(); + void World::clear() + { + mLocalScripts.clear(); + mPlayer->clear(); + + // enable collision + if (!mPhysics->toggleCollisionMode()) + mPhysics->toggleCollisionMode(); + + mWorldScene->changeToVoid(); + + mStore.clearDynamic(); + mStore.setUp(); + + if (mPlayer) + { + mPlayer->setCell (0); + mPlayer->getPlayer().getRefData() = RefData(); + mPlayer->set (mStore.get().find ("player")); + } + + mCells.clear(); + + mProjectiles.clear(); + mDoorStates.clear(); + + mGodMode = false; + mSky = true; + mTeleportEnabled = true; + mPlayIntro = 0; + mFacedDistance = FLT_MAX; + + mGlobalVariables.fill (mStore); } + int World::countSavedGameRecords() const + { + return + mStore.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); + } + + void World::readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap) + { + if (!mStore.readRecord (reader, type) && + !mGlobalVariables.readRecord (reader, type) && + !mPlayer->readRecord (reader, type) && + !mCells.readRecord (reader, type, contentFileMap)) + { + throw std::runtime_error ("unknown record in saved game"); + } + } void World::ensureNeededRecords() { @@ -359,7 +375,6 @@ namespace MWWorld { delete mWeatherManager; delete mWorldScene; - delete mGlobalVariables; delete mRendering; delete mPhysics; @@ -393,16 +408,35 @@ namespace MWWorld return &mFallback; } - Ptr::CellStore *World::getExterior (int x, int y) + CellStore *World::getExterior (int x, int y) { return mCells.getExterior (x, y); } - Ptr::CellStore *World::getInterior (const std::string& name) + CellStore *World::getInterior (const std::string& name) { return mCells.getInterior (name); } + CellStore *World::getCell (const ESM::CellId& id) + { + if (id.mPaged) + return getExterior (id.mIndex.mX, id.mIndex.mY); + else + return getInterior (id.mWorldspace); + } + + void World::useDeathCamera() + { + if(mRendering->getCamera()->isVanityOrPreviewModeEnabled() ) + { + mRendering->getCamera()->togglePreviewMode(false); + mRendering->getCamera()->toggleVanityMode(false); + } + if(mRendering->getCamera()->isFirstPerson()) + togglePOV(); + } + MWWorld::Player& World::getPlayer() { return *mPlayer; @@ -428,60 +462,57 @@ namespace MWWorld return mWorldScene->hasCellChanged(); } - Globals::Data& World::getGlobalVariable (const std::string& name) + void World::setGlobalInt (const std::string& name, int value) { - return (*mGlobalVariables)[name]; + if (name=="gamehour") + setHour (value); + else if (name=="day") + setDay (value); + else if (name=="month") + setMonth (value); + else + mGlobalVariables[name].setInteger (value); } - Globals::Data World::getGlobalVariable (const std::string& name) const + void World::setGlobalFloat (const std::string& name, float value) { - return (*mGlobalVariables)[name]; + if (name=="gamehour") + setHour (value); + else if (name=="day") + setDay (value); + else if (name=="month") + setMonth (value); + else + mGlobalVariables[name].setFloat (value); } - char World::getGlobalVariableType (const std::string& name) const + int World::getGlobalInt (const std::string& name) const { - return mGlobalVariables->getType (name); + return mGlobalVariables[name].getInteger(); } - std::vector World::getGlobals () const + float World::getGlobalFloat (const std::string& name) const { - return mGlobalVariables->getGlobals(); + return mGlobalVariables[name].getFloat(); } - std::string World::getCurrentCellName () const + char World::getGlobalVariableType (const std::string& name) const { - std::string name; + return mGlobalVariables.getType (name); + } - Ptr::CellStore *cell = mWorldScene->getCurrentCell(); - if (cell->mCell->isExterior()) - { - if (cell->mCell->mName != "") - { - name = cell->mCell->mName; - } - else - { - const ESM::Region* region = - MWBase::Environment::get().getWorld()->getStore().get().search(cell->mCell->mRegion); - if (region) - name = region->mName; - else - { - const ESM::GameSetting *setting = - MWBase::Environment::get().getWorld()->getStore().get().search("sDefaultCellname"); + std::string World::getCellName (const MWWorld::CellStore *cell) const + { + if (!cell) + cell = mWorldScene->getCurrentCell(); - if (setting && setting->mValue.getType()==ESM::VT_String) - name = setting->mValue.getString(); - } + if (!cell->mCell->isExterior() || !cell->mCell->mName.empty()) + return cell->mCell->mName; - } - } - else - { - name = cell->mCell->mName; - } + if (const ESM::Region* region = getStore().get().search (cell->mCell->mRegion)) + return region->mName; - return name; + return getStore().get().find ("sDefaultCellname")->mValue.getString(); } void World::removeRefScript (MWWorld::RefData *ref) @@ -510,7 +541,7 @@ namespace MWWorld for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); iter!=mWorldScene->getActiveCells().end(); ++iter) { - Ptr::CellStore* cellstore = *iter; + CellStore* cellstore = *iter; Ptr ptr = mCells.getPtr (lowerCaseName, *cellstore, true); if (!ptr.isEmpty()) @@ -547,7 +578,7 @@ namespace MWWorld for (Scene::CellStoreCollection::const_iterator iter (mWorldScene->getActiveCells().begin()); iter!=mWorldScene->getActiveCells().end(); ++iter) { - Ptr::CellStore* cellstore = *iter; + CellStore* cellstore = *iter; Ptr ptr = getPtrViaHandle (handle, *cellstore); if (!ptr.isEmpty()) @@ -557,7 +588,7 @@ namespace MWWorld return MWWorld::Ptr(); } - void World::addContainerScripts(const Ptr& reference, Ptr::CellStore * cell) + void World::addContainerScripts(const Ptr& reference, CellStore * cell) { if( reference.getTypeName()==typeid (ESM::Container).name() || reference.getTypeName()==typeid (ESM::NPC).name() || @@ -624,14 +655,15 @@ namespace MWWorld mWeatherManager->advanceTime (hours); - hours += mGlobalVariables->getFloat ("gamehour"); + hours += mGlobalVariables["gamehour"].getFloat(); setHour (hours); int days = hours / 24; if (days>0) - mGlobalVariables->setInt ("dayspassed", days + mGlobalVariables->getInt ("dayspassed")); + mGlobalVariables["dayspassed"].setInteger ( + days + mGlobalVariables["dayspassed"].getInteger()); } void World::setHour (double hour) @@ -643,14 +675,14 @@ namespace MWWorld hour = std::fmod (hour, 24); - mGlobalVariables->setFloat ("gamehour", hour); + mGlobalVariables["gamehour"].setFloat (hour); mRendering->skySetHour (hour); mWeatherManager->setHour (hour); if (days>0) - setDay (days + mGlobalVariables->getInt ("day")); + setDay (days + mGlobalVariables["day"].getInteger()); } void World::setDay (int day) @@ -658,7 +690,7 @@ namespace MWWorld if (day<1) day = 1; - int month = mGlobalVariables->getInt ("month"); + int month = mGlobalVariables["month"].getInteger(); while (true) { @@ -673,14 +705,14 @@ namespace MWWorld else { month = 0; - mGlobalVariables->setInt ("year", mGlobalVariables->getInt ("year")+1); + mGlobalVariables["year"].setInteger (mGlobalVariables["year"].getInteger()+1); } day -= days; } - mGlobalVariables->setInt ("day", day); - mGlobalVariables->setInt ("month", month); + mGlobalVariables["day"].setInteger (day); + mGlobalVariables["month"].setInteger (month); mRendering->skySetDate (day, month); @@ -697,31 +729,56 @@ namespace MWWorld int days = getDaysPerMonth (month); - if (mGlobalVariables->getInt ("day")>days) - mGlobalVariables->setInt ("day", days); + if (mGlobalVariables["day"].getInteger()>days) + mGlobalVariables["day"].setInteger (days); - mGlobalVariables->setInt ("month", month); + mGlobalVariables["month"].setInteger (month); if (years>0) - mGlobalVariables->setInt ("year", years+mGlobalVariables->getInt ("year")); + mGlobalVariables["year"].setInteger (years+mGlobalVariables["year"].getInteger()); + + mRendering->skySetDate (mGlobalVariables["day"].getInteger(), month); + } + + int World::getDay() const + { + return mGlobalVariables["day"].getInteger(); + } - mRendering->skySetDate (mGlobalVariables->getInt ("day"), month); + int World::getMonth() const + { + return mGlobalVariables["month"].getInteger(); } - int World::getDay() + int World::getYear() const { - return mGlobalVariables->getInt("day"); + return mGlobalVariables["year"].getInteger(); } - int World::getMonth() + std::string World::getMonthName (int month) const { - return mGlobalVariables->getInt("month"); + if (month==-1) + month = getMonth(); + + const int months = 12; + + if (month<0 || month>=months) + return ""; + + static const char *monthNames[months] = + { + "sMonthMorningstar", "sMonthSunsdawn", "sMonthFirstseed", "sMonthRainshand", + "sMonthSecondseed", "sMonthMidyear", "sMonthSunsheight", "sMonthLastseed", + "sMonthHeartfire", "sMonthFrostfall", "sMonthSunsdusk", "sMonthEveningstar" + }; + + return getStore().get().find (monthNames[month])->mValue.getString(); } TimeStamp World::getTimeStamp() const { - return TimeStamp (mGlobalVariables->getFloat ("gamehour"), - mGlobalVariables->getInt ("dayspassed")); + return TimeStamp (mGlobalVariables["gamehour"].getFloat(), + mGlobalVariables["dayspassed"].getInteger()); } bool World::toggleSky() @@ -757,7 +814,7 @@ namespace MWWorld float World::getTimeScaleFactor() const { - return mGlobalVariables->getFloat ("timescale"); + return mGlobalVariables["timescale"].getFloat(); } void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) @@ -774,6 +831,14 @@ namespace MWWorld addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } + void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position) + { + if (cellId.mPaged) + changeToExteriorCell (position); + else + changeToInteriorCell (cellId.mWorldspace, position); + } + void World::markCellAsUnchanged() { return mWorldScene->markCellAsUnchanged(); @@ -1153,7 +1218,7 @@ namespace MWWorld std::map::iterator it = mDoorStates.begin(); while (it != mDoorStates.end()) { - if (!mWorldScene->isCellActive(*it->first.getCell())) + if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode()) mDoorStates.erase(it++); else { @@ -1228,7 +1293,7 @@ namespace MWWorld if (Misc::StringUtils::ciEqual (ids[i], record.mRace)) break; - mGlobalVariables->setInt ("pcrace", (i == ids.size()) ? 0 : i+1); + mGlobalVariables["pcrace"].setInteger (i == ids.size() ? 0 : i+1); const ESM::NPC *player = mPlayer->getPlayer().get()->mBase; @@ -1293,7 +1358,7 @@ namespace MWWorld updateWindowManager (); - if (mPlayer->getPlayer().getCell()->isExterior()) + if (!paused && mPlayer->getPlayer().getCell()->isExterior()) { ESM::Position pos = mPlayer->getPlayer().getRefData().getPosition(); mPlayer->setLastKnownExteriorPosition(Ogre::Vector3(pos.pos)); @@ -1389,7 +1454,7 @@ namespace MWWorld bool World::isCellExterior() const { - Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); + CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { return currentCell->mCell->isExterior(); @@ -1399,7 +1464,7 @@ namespace MWWorld bool World::isCellQuasiExterior() const { - Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); + CellStore *currentCell = mWorldScene->getCurrentCell(); if (currentCell) { if (!(currentCell->mCell->mData.mFlags & ESM::Cell::QuasiEx)) @@ -1580,7 +1645,7 @@ namespace MWWorld void World::dropObjectOnGround (const Ptr& actor, const Ptr& object, int amount) { - MWWorld::Ptr::CellStore* cell = actor.getCell(); + MWWorld::CellStore* cell = actor.getCell(); ESM::Position pos = actor.getRefData().getPosition(); @@ -1682,7 +1747,7 @@ namespace MWWorld } bool - World::isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const + World::isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const { if (!(cell->mCell->mData.mFlags & ESM::Cell::HasWater)) { return false; @@ -1702,9 +1767,9 @@ namespace MWWorld return mRendering->vanityRotateCamera(rot); } - void World::setCameraDistance(float dist, bool adjust, bool override) + void World::setCameraDistance(float dist, bool adjust, bool override_) { - return mRendering->setCameraDistance(dist, adjust, override);; + return mRendering->setCameraDistance(dist, adjust, override_); } void World::setupPlayer() @@ -1722,12 +1787,19 @@ namespace MWWorld void World::renderPlayer() { mRendering->renderPlayer(mPlayer->getPlayer()); + + // At this point the Animation object is live, and the CharacterController associated with it must be created. + // It has to be done at this point: resetCamera below does animation->setViewMode -> CharacterController::forceStateUpdate + // so we should make sure not to use a "stale" controller for that. + MWBase::Environment::get().getMechanicsManager()->add(mPlayer->getPlayer()); + mPhysics->addActor(mPlayer->getPlayer()); + mRendering->resetCamera(); } int World::canRest () { - Ptr::CellStore *currentCell = mWorldScene->getCurrentCell(); + CellStore *currentCell = mWorldScene->getCurrentCell(); Ptr player = mPlayer->getPlayer(); RefData &refdata = player.getRefData(); @@ -1763,6 +1835,11 @@ namespace MWWorld mRendering->frameStarted(dt, paused); } + void World::screenshot(Ogre::Image &image, int w, int h) + { + mRendering->screenshot(image, w, h); + } + void World::activateDoor(const MWWorld::Ptr& door) { if (mDoorStates.find(door) != mDoorStates.end()) @@ -2296,6 +2373,11 @@ namespace MWWorld } } + const std::vector& World::getContentFiles() const + { + return mContentFiles; + } + void World::breakInvisibility(const Ptr &actor) { actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); @@ -2476,13 +2558,13 @@ namespace MWWorld int discount = bounty*fCrimeGoldDiscountMult; int turnIn = bounty * fCrimeGoldTurnInMult; - mGlobalVariables->setInt("pchascrimegold", (bounty <= playerGold) ? 1 : 0); + mGlobalVariables["pchascrimegold"].setInteger((bounty <= playerGold) ? 1 : 0); - mGlobalVariables->setInt("pchasgolddiscount", (discount <= playerGold) ? 1 : 0); - mGlobalVariables->setInt("crimegolddiscount", discount); + mGlobalVariables["pchasgolddiscount"].setInteger((discount <= playerGold) ? 1 : 0); + mGlobalVariables["crimegolddiscount"].setInteger(discount); - mGlobalVariables->setInt("crimegoldturnin", turnIn); - mGlobalVariables->setInt("pchasturnin", (turnIn <= playerGold) ? 1 : 0); + mGlobalVariables["crimegoldturnin"].setInteger(turnIn); + mGlobalVariables["pchasturnin"].setInteger((turnIn <= playerGold) ? 1 : 0); } void World::confiscateStolenItems(const Ptr &ptr) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 8b1bd9538..036cafe2d 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -11,6 +11,7 @@ #include "localscripts.hpp" #include "timestamp.hpp" #include "fallback.hpp" +#include "globals.hpp" #include "../mwbase/world.hpp" @@ -41,6 +42,7 @@ namespace MWRender class SkyManager; class CellRender; class Animation; + class Camera; } struct ContentLoader; @@ -64,7 +66,7 @@ namespace MWWorld std::vector mEsm; MWWorld::ESMStore mStore; LocalScripts mLocalScripts; - MWWorld::Globals *mGlobalVariables; + MWWorld::Globals mGlobalVariables; MWWorld::PhysicsSystem *mPhysics; bool mSky; @@ -73,12 +75,13 @@ namespace MWWorld OEngine::Physic::PhysicEngine* mPhysEngine; bool mGodMode; + std::vector mContentFiles; // not implemented World (const World&); World& operator= (const World&); - Ptr getPtrViaHandle (const std::string& handle, Ptr::CellStore& cellStore); + Ptr getPtrViaHandle (const std::string& handle, CellStore& cellStore); int mActivationDistanceOverride; std::string mFacedHandle; @@ -125,7 +128,7 @@ namespace MWWorld float getObjectActivationDistance (); void removeContainerScripts(const Ptr& reference); - void addContainerScripts(const Ptr& reference, Ptr::CellStore* cell); + void addContainerScripts(const Ptr& reference, CellStore* cell); void PCDropped (const Ptr& item); void processDoors(float duration); @@ -170,13 +173,27 @@ namespace MWWorld virtual void startNewGame(); + virtual void clear(); + + virtual int countSavedGameRecords() const; + + virtual void write (ESM::ESMWriter& writer) const; + + virtual void readRecord (ESM::ESMReader& reader, int32_t type, + const std::map& contentFileMap); + virtual OEngine::Render::Fader* getFader(); - ///< \ŧodo remove this function. Rendering details should not be exposed. + ///< \todo remove this function. Rendering details should not be exposed. virtual CellStore *getExterior (int x, int y); virtual CellStore *getInterior (const std::string& name); + virtual CellStore *getCell (const ESM::CellId& id); + + //switch to POV before showing player's death animation + virtual void useDeathCamera(); + virtual void setWaterHeight(const float height); virtual void toggleWater(); @@ -215,16 +232,26 @@ namespace MWWorld virtual bool isPositionExplored (float nX, float nY, int x, int y, bool interior); ///< see MWRender::LocalMap::isPositionExplored - virtual Globals::Data& getGlobalVariable (const std::string& name); + virtual void setGlobalInt (const std::string& name, int value); + ///< Set value independently from real type. + + virtual void setGlobalFloat (const std::string& name, float value); + ///< Set value independently from real type. - virtual Globals::Data getGlobalVariable (const std::string& name) const; + virtual int getGlobalInt (const std::string& name) const; + ///< Get value independently from real type. + + virtual float getGlobalFloat (const std::string& name) const; + ///< Get value independently from real type. virtual char getGlobalVariableType (const std::string& name) const; ///< Return ' ', if there is no global variable with this name. - virtual std::vector getGlobals () const; - - virtual std::string getCurrentCellName () const; + virtual std::string getCellName (const MWWorld::CellStore *cell = 0) const; + ///< Return name of the cell. + /// + /// \note If cell==0, the cell the player is currently in will be used instead to + /// generate a name. virtual void removeRefScript (MWWorld::RefData *ref); //< Remove the script attached to ref from mLocalScripts @@ -262,8 +289,12 @@ namespace MWWorld virtual void setDay (int day); ///< Set in-game time day. - virtual int getDay(); - virtual int getMonth(); + virtual int getDay() const; + virtual int getMonth() const; + virtual int getYear() const; + + virtual std::string getMonthName (int month = -1) const; + ///< Return name of month (-1: current month) virtual TimeStamp getTimeStamp() const; ///< Return current in-game time stamp. @@ -292,6 +323,8 @@ namespace MWWorld virtual void changeToExteriorCell (const ESM::Position& position); ///< Move to exterior cell. + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position); + virtual const ESM::Cell *getExterior (const std::string& cellName) const; ///< Return a cell matching the given name or a 0-pointer, if there is no such cell. @@ -410,7 +443,7 @@ namespace MWWorld ///Is the head of the creature underwater? virtual bool isSubmerged(const MWWorld::Ptr &object) const; virtual bool isSwimming(const MWWorld::Ptr &object) const; - virtual bool isUnderwater(const MWWorld::Ptr::CellStore* cell, const Ogre::Vector3 &pos) const; + virtual bool isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const; virtual bool isOnGround(const MWWorld::Ptr &ptr) const; virtual void togglePOV() { @@ -476,6 +509,7 @@ namespace MWWorld virtual void playVideo(const std::string& name, bool allowSkipping); virtual void stopVideo(); virtual void frameStarted (float dt, bool paused); + virtual void screenshot (Ogre::Image& image, int w, int h); /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise @@ -521,6 +555,9 @@ namespace MWWorld virtual void launchProjectile (const std::string& id, bool stack, const ESM::EffectList& effects, const MWWorld::Ptr& actor, const std::string& sourceName); + + virtual const std::vector& getContentFiles() const; + virtual void breakInvisibility (const MWWorld::Ptr& actor); // Are we in an exterior or pseudo-exterior cell and it's night? virtual bool isDark() const; diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake new file mode 100644 index 000000000..f70f64261 --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake @@ -0,0 +1,159 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ ...]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +function(get_git_head_revision _refspecvar _hashvar) + set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + + # check if this is a submodule + if(NOT IS_DIRECTORY ${GIT_DIR}) + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + + #get_git_head_revision(refspec hash) + + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + + #if(NOT hash) + # set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + # return() + #endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + #${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(get_git_tag_revision _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + rev-list + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + + diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in new file mode 100644 index 000000000..888ce13aa --- /dev/null +++ b/cmake/GetGitRevisionDescription.cmake.in @@ -0,0 +1,38 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") + configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + set(HEAD_HASH "${HEAD_REF}") + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index a037fd5fa..e27955684 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,5 +1,9 @@ project (Components) set (CMAKE_BUILD_TYPE DEBUG) + +# Version file +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp") + # source files add_component_dir (settings @@ -40,6 +44,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 cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate ) add_component_dir (misc @@ -75,8 +80,12 @@ add_component_dir (loadinglistener ) add_component_dir (ogreinit - ogreinit ogreplugin - ) + ogreinit ogreplugin + ) + +add_component_dir (version + version + ) set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui ) diff --git a/components/compiler/locals.cpp b/components/compiler/locals.cpp index d93e73849..e2b1c5c96 100644 --- a/components/compiler/locals.cpp +++ b/components/compiler/locals.cpp @@ -15,27 +15,27 @@ namespace Compiler { case 's': return mShorts; case 'l': return mLongs; - case 'f': return mFloats; + case 'f': return mFloats; } - + throw std::logic_error ("unknown variable type"); } - + int Locals::searchIndex (char type, const std::string& name) const { const std::vector& collection = get (type); - + std::vector::const_iterator iter = std::find (collection.begin(), collection.end(), name); - + if (iter==collection.end()) return -1; - + return iter-collection.begin(); } - + bool Locals::search (char type, const std::string& name) const - { + { return searchIndex (type, name)!=-1; } @@ -45,10 +45,10 @@ namespace Compiler { case 's': return mShorts; case 'l': return mLongs; - case 'f': return mFloats; + case 'f': return mFloats; } - - throw std::logic_error ("unknown variable type"); + + throw std::logic_error ("unknown variable type"); } char Locals::getType (const std::string& name) const @@ -58,35 +58,35 @@ namespace Compiler if (search ('l', name)) return 'l'; - + if (search ('f', name)) return 'f'; - + return ' '; } - + int Locals::getIndex (const std::string& name) const { int index = searchIndex ('s', name); - + if (index!=-1) return index; - + index = searchIndex ('l', name); if (index!=-1) return index; - return searchIndex ('f', name); + return searchIndex ('f', name); } - + void Locals::write (std::ostream& localFile) const { localFile << get ('s').size() << ' ' << get ('l').size() << ' ' << get ('f').size() << std::endl; - + std::copy (get ('s').begin(), get ('s').end(), std::ostream_iterator (localFile, " ")); std::copy (get ('l').begin(), get ('l').end(), @@ -94,12 +94,12 @@ namespace Compiler std::copy (get ('f').begin(), get ('f').end(), std::ostream_iterator (localFile, " ")); } - + void Locals::declare (char type, const std::string& name) { get (type).push_back (name); } - + void Locals::clear() { get ('s').clear(); diff --git a/components/compiler/locals.hpp b/components/compiler/locals.hpp index e54b7798c..d5bf05253 100644 --- a/components/compiler/locals.hpp +++ b/components/compiler/locals.hpp @@ -8,35 +8,35 @@ namespace Compiler { /// \brief Local variable declarations - + class Locals { std::vector mShorts; std::vector mLongs; std::vector mFloats; - + int searchIndex (char type, const std::string& name) const; bool search (char type, const std::string& name) const; - - std::vector& get (char type); - + + std::vector& get (char type); + public: - + char getType (const std::string& name) const; ///< 's': short, 'l': long, 'f': float, ' ': does not exist. - + int getIndex (const std::string& name) const; ///< return index for local variable \a name (-1: does not exist). - + const std::vector& get (char type) const; void write (std::ostream& localFile) const; ///< write declarations to file. - + void declare (char type, const std::string& name); ///< declares a variable. - + void clear(); ///< remove all declarations. }; diff --git a/components/esm/cellid.cpp b/components/esm/cellid.cpp new file mode 100644 index 000000000..5bc8b7aef --- /dev/null +++ b/components/esm/cellid.cpp @@ -0,0 +1,26 @@ + +#include "cellid.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::CellId::load (ESMReader &esm) +{ + mWorldspace = esm.getHNString ("SPAC"); + + if (esm.isNextSub ("CIDX")) + { + esm.getHT (mIndex, 8); + mPaged = true; + } + else + mPaged = false; +} + +void ESM::CellId::save (ESMWriter &esm) const +{ + esm.writeHNString ("SPAC", mWorldspace); + + if (mPaged) + esm.writeHNT ("CIDX", mIndex, 8); +} \ No newline at end of file diff --git a/components/esm/cellid.hpp b/components/esm/cellid.hpp new file mode 100644 index 000000000..54dbdae78 --- /dev/null +++ b/components/esm/cellid.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_ESM_CELLID_H +#define OPENMW_ESM_CELLID_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct CellId + { + struct CellIndex + { + int mX; + int mY; + }; + + std::string mWorldspace; + CellIndex mIndex; + bool mPaged; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index e91059b26..00b15f4a3 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -1,11 +1,87 @@ #include "cellref.hpp" +#include "esmreader.hpp" #include "esmwriter.hpp" -void ESM::CellRef::save(ESMWriter &esm) const +void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) { - esm.writeHNT("FRMR", mRefnum); + // NAM0 sometimes appears here, sometimes further on + mNam0 = 0; + if (esm.isNextSub ("NAM0")) + esm.getHT (mNam0); + + if (wideRefNum) + esm.getHNT (mRefNum, "FRMR", 8); + else + esm.getHNT (mRefNum.mIndex, "FRMR"); + + mRefID = esm.getHNString ("NAME"); + + // Again, UNAM sometimes appears after NAME and sometimes later. + // Or perhaps this UNAM means something different? + mReferenceBlocked = -1; + esm.getHNOT (mReferenceBlocked, "UNAM"); + + mScale = 1.0; + esm.getHNOT (mScale, "XSCL"); + + mOwner = esm.getHNOString ("ANAM"); + mGlob = esm.getHNOString ("BNAM"); + mSoul = esm.getHNOString ("XSOL"); + + mFaction = esm.getHNOString ("CNAM"); + mFactIndex = -2; + esm.getHNOT (mFactIndex, "INDX"); + + mGoldValue = 1; + mCharge = -1; + mEnchantmentCharge = -1; + + esm.getHNOT (mEnchantmentCharge, "XCHG"); + + esm.getHNOT (mCharge, "INTV"); + + esm.getHNOT (mGoldValue, "NAM9"); + + // Present for doors that teleport you to another cell. + if (esm.isNextSub ("DODT")) + { + mTeleport = true; + esm.getHT (mDoorDest); + mDestCell = esm.getHNOString ("DNAM"); + } + else + mTeleport = false; + + mLockLevel = -1; + esm.getHNOT (mLockLevel, "FLTV"); + mKey = esm.getHNOString ("KNAM"); + mTrap = esm.getHNOString ("TNAM"); + + mFltv = 0; + esm.getHNOT (mReferenceBlocked, "UNAM"); + esm.getHNOT (mFltv, "FLTV"); + + esm.getHNOT(mPos, "DATA", 24); + + // Number of references in the cell? Maximum once in each cell, + // but not always at the beginning, and not always right. In other + // words, completely useless. + // Update: Well, maybe not completely useless. This might actually be + // number_of_references + number_of_references_moved_here_Across_boundaries, + // and could be helpful for collecting these weird moved references. + if (esm.isNextSub ("NAM0")) + esm.getHT (mNam0); +} + +void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const +{ + if (wideRefNum) + esm.writeHNT ("FRMR", mRefNum, 8); + else + esm.writeHNT ("FRMR", mRefNum.mIndex, 4); + esm.writeHNCString("NAME", mRefID); if (mScale != 1.0) { @@ -31,34 +107,38 @@ void ESM::CellRef::save(ESMWriter &esm) const esm.writeHNT("NAM9", mGoldValue); } - if (mTeleport) + if (mTeleport && !inInventory) { esm.writeHNT("DODT", mDoorDest); esm.writeHNOCString("DNAM", mDestCell); } - if (mLockLevel != -1) { + if (mLockLevel != -1 && !inInventory) esm.writeHNT("FLTV", mLockLevel); - } - esm.writeHNOCString("KNAM", mKey); - esm.writeHNOCString("TNAM", mTrap); - if (mReferenceBlocked != -1) { + if (!inInventory) + esm.writeHNOCString ("KNAM", mKey); + + if (!inInventory) + esm.writeHNOCString ("TNAM", mTrap); + + if (mReferenceBlocked != -1) esm.writeHNT("UNAM", mReferenceBlocked); - } - if (mFltv != 0) { + + if (mFltv != 0 && !inInventory) esm.writeHNT("FLTV", mFltv); - } - esm.writeHNT("DATA", mPos, 24); - if (mNam0 != 0) { + if (!inInventory) + esm.writeHNT("DATA", mPos, 24); + + if (mNam0 != 0 && !inInventory) esm.writeHNT("NAM0", mNam0); - } } void ESM::CellRef::blank() { - mRefnum = 0; + mRefNum.mIndex = 0; + mRefNum.mContentFile = -1; mRefID.clear(); mScale = 1; mOwner.clear(); @@ -84,4 +164,9 @@ void ESM::CellRef::blank() mPos.pos[i] = 0; mPos.rot[i] = 0; } -} \ No newline at end of file +} + +bool ESM::operator== (const CellRef::RefNum& left, const CellRef::RefNum& right) +{ + return left.mIndex==right.mIndex && left.mContentFile==right.mContentFile; +} diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 4d9ebd046..16f6603a2 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -8,6 +8,7 @@ namespace ESM { class ESMWriter; + class ESMReader; /* Cell reference. This represents ONE object (of many) inside the cell. The cell references are not loaded as part of the normal @@ -19,8 +20,14 @@ namespace ESM { public: - int mRefnum; // Reference number - std::string mRefID; // ID of object being referenced (must be lowercase) + struct RefNum + { + int mIndex; + int mContentFile; // -1 no content file + }; + + RefNum mRefNum; // Reference number + std::string mRefID; // ID of object being referenced float mScale; // Scale applied to mesh @@ -71,9 +78,6 @@ namespace ESM // -1 is not blocked, otherwise it is blocked. signed char mReferenceBlocked; - // Track deleted references. 0 - not deleted, 1 - deleted, but respawns, 2 - deleted and does not respawn. - int mDeleted; - // Occurs in Tribunal.esm, eg. in the cell "Mournhold, Plaza // Brindisi Dorom", where it has the value 100. Also only for // activators. @@ -83,10 +87,14 @@ namespace ESM // Position and rotation of this object within the cell Position mPos; - void save(ESMWriter &esm) const; + void load (ESMReader& esm, bool wideRefNum = false); + + void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const; void blank(); }; + + bool operator== (const CellRef::RefNum& left, const CellRef::RefNum& right); } #endif 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/containerstate.cpp b/components/esm/containerstate.cpp new file mode 100644 index 000000000..5dcf17733 --- /dev/null +++ b/components/esm/containerstate.cpp @@ -0,0 +1,16 @@ + +#include "containerstate.hpp" + +void ESM::ContainerState::load (ESMReader &esm) +{ + ObjectState::load (esm); + + mInventory.load (esm); +} + +void ESM::ContainerState::save (ESMWriter &esm, bool inInventory) const +{ + ObjectState::save (esm, inInventory); + + mInventory.save (esm); +} \ No newline at end of file diff --git a/components/esm/containerstate.hpp b/components/esm/containerstate.hpp new file mode 100644 index 000000000..1ecf2b46e --- /dev/null +++ b/components/esm/containerstate.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_ESM_CONTAINERSTATE_H +#define OPENMW_ESM_CONTAINERSTATE_H + +#include "objectstate.hpp" +#include "inventorystate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct ContainerState : public ObjectState + { + InventoryState mInventory; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp new file mode 100644 index 000000000..43cde3025 --- /dev/null +++ b/components/esm/creaturestate.cpp @@ -0,0 +1,16 @@ + +#include "creaturestate.hpp" + +void ESM::CreatureState::load (ESMReader &esm) +{ + ObjectState::load (esm); + + mInventory.load (esm); +} + +void ESM::CreatureState::save (ESMWriter &esm, bool inInventory) const +{ + ObjectState::save (esm, inInventory); + + mInventory.save (esm); +} \ No newline at end of file diff --git a/components/esm/creaturestate.hpp b/components/esm/creaturestate.hpp new file mode 100644 index 000000000..f7f9b8038 --- /dev/null +++ b/components/esm/creaturestate.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_ESM_CREATURESTATE_H +#define OPENMW_ESM_CREATURESTATE_H + +#include "objectstate.hpp" +#include "inventorystate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct CreatureState : public ObjectState + { + InventoryState mInventory; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index dd7ebfe93..1b0125e78 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -83,6 +83,15 @@ enum RecNameInts REC_STAT = 0x54415453, REC_WEAP = 0x50414557, + // format 0 - saved games + REC_SAVE = 0x45564153, + REC_JOUR = 0x524f55a4, + REC_QUES = 0x53455551, + REC_GSCR = 0x52435347, + REC_PLAY = 0x59414c50, + REC_CSTA = 0x41545343, + REC_GMAP = 0x50414d47, + // format 1 REC_FILT = 0x544C4946 }; diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 51d86a2ee..ebdb1e41f 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -8,6 +8,11 @@ namespace ESM using namespace Misc; + std::string ESMReader::getName() const + { + return mCtx.filename; + } + ESM_Context ESMReader::getContext() { // Update the file position before returning @@ -302,8 +307,14 @@ std::string ESMReader::getString(int size) char *ptr = &mBuffer[0]; getExact(ptr, size); + if (size>0 && ptr[size-1]==0) + --size; + // Convert to UTF8 and return - return mEncoder->getUtf8(ptr, size); + if (mEncoder) + return mEncoder->getUtf8(ptr, size); + + return std::string (ptr, size); } void ESMReader::fail(const std::string &msg) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 3bf194c4e..897c8fe73 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -38,6 +38,7 @@ public: int getFormat() const; const NAME &retSubName() const { return mCtx.subName; } uint32_t getSubSize() const { return mCtx.leftSub; } + std::string getName() const; /************************************************************************* * diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index f39aa2b89..f38591b7b 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -6,7 +6,7 @@ namespace ESM { - ESMWriter::ESMWriter() : mRecordCount (0), mCounting (true) {} + ESMWriter::ESMWriter() : mEncoder (0), mRecordCount (0), mCounting (true) {} unsigned int ESMWriter::getVersion() const { @@ -51,12 +51,6 @@ namespace ESM mHeader.mMaster.push_back(d); } - void ESMWriter::save(const std::string& file) - { - std::ofstream fs(file.c_str(), std::ios_base::out | std::ios_base::trunc); - save(fs); - } - void ESMWriter::save(std::ostream& file) { mRecordCount = 0; @@ -94,6 +88,16 @@ namespace ESM assert(mRecords.back().size == 0); } + void ESMWriter::startRecord (uint32_t name, uint32_t flags) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic + type += reinterpret_cast (&name)[i]; + + startRecord (type, flags); + } + void ESMWriter::startSubRecord(const std::string& name) { writeName(name); @@ -123,6 +127,16 @@ namespace ESM } + void ESMWriter::endRecord (uint32_t name) + { + std::string type; + for (int i=0; i<4; ++i) + /// \todo make endianess agnostic + type += reinterpret_cast (&name)[i]; + + endRecord (type); + } + void ESMWriter::writeHNString(const std::string& name, const std::string& data) { startSubRecord(name); @@ -152,9 +166,9 @@ namespace ESM else { // Convert to UTF8 and return - std::string ascii = mEncoder->getLegacyEnc(data); + std::string string = mEncoder ? mEncoder->getLegacyEnc(data) : data; - write(ascii.c_str(), ascii.size()); + write(string.c_str(), string.size()); } } diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index 104f97f90..94f0a1004 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -36,9 +36,6 @@ class ESMWriter void addMaster(const std::string& name, uint64_t size); - void save(const std::string& file); - ///< Start saving a file by writing the TES3 header. - void save(std::ostream& file); ///< Start saving a file by writing the TES3 header. @@ -93,8 +90,10 @@ class ESMWriter } void startRecord(const std::string& name, uint32_t flags = 0); + void startRecord(uint32_t name, uint32_t flags = 0); void startSubRecord(const std::string& name); void endRecord(const std::string& name); + void endRecord(uint32_t name); void writeHString(const std::string& data); void writeHCString(const std::string& data); void writeName(const std::string& data); diff --git a/components/esm/globalmap.cpp b/components/esm/globalmap.cpp new file mode 100644 index 000000000..1fa5f907e --- /dev/null +++ b/components/esm/globalmap.cpp @@ -0,0 +1,26 @@ +#include "globalmap.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "defs.hpp" + +unsigned int ESM::GlobalMap::sRecordId = ESM::REC_GMAP; + +void ESM::GlobalMap::load (ESMReader &esm) +{ + esm.getHNT(mBounds, "BNDS"); + + esm.getSubNameIs("DATA"); + esm.getSubHeader(); + mImageData.resize(esm.getSubSize()); + esm.getExact(&mImageData[0], mImageData.size()); +} + +void ESM::GlobalMap::save (ESMWriter &esm) const +{ + esm.writeHNT("BNDS", mBounds); + + esm.startSubRecord("DATA"); + esm.write(&mImageData[0], mImageData.size()); + esm.endRecord("DATA"); +} diff --git a/components/esm/globalmap.hpp b/components/esm/globalmap.hpp new file mode 100644 index 000000000..5d036c736 --- /dev/null +++ b/components/esm/globalmap.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_COMPONENTS_ESM_GLOBALMAP_H +#define OPENMW_COMPONENTS_ESM_GLOBALMAP_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + ///< \brief An image containing the explored areas on the global map. + struct GlobalMap + { + static unsigned int sRecordId; + + // The minimum and maximum cell coordinates + struct Bounds + { + int mMinX, mMaxX, mMinY, mMaxY; + }; + + Bounds mBounds; + + std::vector mImageData; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; + +} + +#endif diff --git a/components/esm/globalscript.cpp b/components/esm/globalscript.cpp new file mode 100644 index 000000000..dcbd91140 --- /dev/null +++ b/components/esm/globalscript.cpp @@ -0,0 +1,25 @@ + +#include "globalscript.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::GlobalScript::load (ESMReader &esm) +{ + mId = esm.getHNString ("NAME"); + + mLocals.load (esm); + + mRunning = 0; + esm.getHNOT (mRunning, "RUN_"); +} + +void ESM::GlobalScript::save (ESMWriter &esm) const +{ + esm.writeHNString ("NAME", mId); + + mLocals.save (esm); + + if (mRunning) + esm.writeHNT ("RUN_", mRunning); +} \ No newline at end of file diff --git a/components/esm/globalscript.hpp b/components/esm/globalscript.hpp new file mode 100644 index 000000000..4fb8b7c48 --- /dev/null +++ b/components/esm/globalscript.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_ESM_GLOBALSCRIPT_H +#define OPENMW_ESM_GLOBALSCRIPT_H + +#include "locals.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + /// \brief Storage structure for global script state (only used in saved games) + + struct GlobalScript + { + std::string mId; + Locals mLocals; + int mRunning; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp new file mode 100644 index 000000000..4d8cbc622 --- /dev/null +++ b/components/esm/inventorystate.cpp @@ -0,0 +1,60 @@ + +#include "inventorystate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace +{ + void read (ESM::ESMReader &esm, ESM::ObjectState& state, int& slot) + { + slot = -1; + esm.getHNOT (slot, "SLOT"); + + state.load (esm); + } + + void write (ESM::ESMWriter &esm, const ESM::ObjectState& state, unsigned int type, int slot) + { + esm.writeHNT ("IOBJ", type); + + if (slot!=-1) + esm.writeHNT ("SLOT", slot); + + state.save (esm, true); + } +} + +void ESM::InventoryState::load (ESMReader &esm) +{ + while (esm.isNextSub ("IOBJ")) + { + unsigned int id = 0; + esm.getHT (id); + + if (id==ESM::REC_LIGH) + { + LightState state; + int slot; + read (esm, state, slot); + mLights.push_back (std::make_pair (state, slot)); + } + else + { + ObjectState state; + int slot; + read (esm, state, slot); + mItems.push_back (std::make_pair (state, std::make_pair (id, slot))); + } + } +} + +void ESM::InventoryState::save (ESMWriter &esm) const +{ + for (std::vector > >::const_iterator iter (mItems.begin()); iter!=mItems.end(); ++iter) + write (esm, iter->first, iter->second.first, iter->second.second); + + for (std::vector >::const_iterator iter (mLights.begin()); + iter!=mLights.end(); ++iter) + write (esm, iter->first, ESM::REC_LIGH, iter->second); +} \ No newline at end of file diff --git a/components/esm/inventorystate.hpp b/components/esm/inventorystate.hpp new file mode 100644 index 000000000..3cfffbccc --- /dev/null +++ b/components/esm/inventorystate.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_ESM_INVENTORYSTATE_H +#define OPENMW_ESM_INVENTORYSTATE_H + +#include "objectstate.hpp" +#include "lightstate.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + /// \brief State for inventories and containers + struct InventoryState + { + // anything but lights (type, slot) + std::vector > > mItems; + + // lights (slot) + std::vector > mLights; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/journalentry.cpp b/components/esm/journalentry.cpp new file mode 100644 index 000000000..445213de4 --- /dev/null +++ b/components/esm/journalentry.cpp @@ -0,0 +1,39 @@ + +#include "journalentry.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::JournalEntry::load (ESMReader &esm) +{ + esm.getHNOT (mType, "JETY"); + mTopic = esm.getHNString ("YETO"); + mInfo = esm.getHNString ("YEIN"); + mText = esm.getHNString ("TEXT"); + + if (mType==Type_Journal) + { + esm.getHNT (mDay, "JEDA"); + esm.getHNT (mMonth, "JEMO"); + esm.getHNT (mDayOfMonth, "JEDM"); + } + else if (mType==Type_Topic) + mActorName = esm.getHNOString("ACT_"); +} + +void ESM::JournalEntry::save (ESMWriter &esm) const +{ + esm.writeHNT ("JETY", mType); + esm.writeHNString ("YETO", mTopic); + esm.writeHNString ("YEIN", mInfo); + esm.writeHNString ("TEXT", mText); + + if (mType==Type_Journal) + { + esm.writeHNT ("JEDA", mDay); + esm.writeHNT ("JEMO", mMonth); + esm.writeHNT ("JEDM", mDayOfMonth); + } + else if (mType==Type_Topic) + esm.writeHNString ("ACT_", mActorName); +} diff --git a/components/esm/journalentry.hpp b/components/esm/journalentry.hpp new file mode 100644 index 000000000..76901a4b6 --- /dev/null +++ b/components/esm/journalentry.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_ESM_JOURNALENTRY_H +#define OPENMW_ESM_JOURNALENTRY_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + struct JournalEntry + { + enum Type + { + Type_Journal = 0, + Type_Topic = 1, + Type_Quest = 2 + }; + + int mType; + std::string mTopic; + std::string mInfo; + std::string mText; + std::string mActorName; // Could also be Actor ID to allow switching of localisation, but since mText is plaintext anyway... + int mDay; // time stamp + int mMonth; + int mDayOfMonth; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/lightstate.cpp b/components/esm/lightstate.cpp new file mode 100644 index 000000000..1ef040823 --- /dev/null +++ b/components/esm/lightstate.cpp @@ -0,0 +1,21 @@ + +#include "lightstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::LightState::load (ESMReader &esm) +{ + ObjectState::load (esm); + + mTime = 0; + esm.getHNOT (mTime, "LTIM"); +} + +void ESM::LightState::save (ESMWriter &esm, bool inInventory) const +{ + ObjectState::save (esm, inInventory); + + if (mTime) + esm.writeHNT ("LTIM", mTime); +} \ No newline at end of file diff --git a/components/esm/lightstate.hpp b/components/esm/lightstate.hpp new file mode 100644 index 000000000..a22735e07 --- /dev/null +++ b/components/esm/lightstate.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_ESM_LIGHTSTATE_H +#define OPENMW_ESM_LIGHTSTATE_H + +#include "objectstate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct LightState : public ObjectState + { + float mTime; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index f4bba7f19..dd7bf3e42 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -3,26 +3,52 @@ #include #include #include + #include +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" +#include "cellid.hpp" -namespace ESM +namespace { - unsigned int Cell::sRecordId = REC_CELL; + ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum + void adjustRefNum (ESM::CellRef::RefNum& refNum, ESM::ESMReader& reader) + { + int local = (refNum.mIndex & 0xff000000) >> 24; -/// Some overloaded compare operators. -bool operator==(const MovedCellRef& ref, int pRefnum) -{ - return (ref.mRefnum == pRefnum); + if (local) + { + // If the most significant 8 bits are used, then this reference already exists. + // In this case, do not spawn a new reference, but overwrite the old one. + refNum.mIndex &= 0x00ffffff; // delete old plugin ID + refNum.mContentFile = reader.getGameFiles()[local-1].index; + } + else + { + // This is an addition by the present plugin. Set the corresponding plugin index. + refNum.mContentFile = reader.getIndex(); + } + } } -bool operator==(const CellRef& ref, int pRefnum) +namespace ESM { - return (ref.mRefnum == pRefnum); -} + unsigned int Cell::sRecordId = REC_CELL; + + // Some overloaded compare operators. + bool operator== (const MovedCellRef& ref, const CellRef::RefNum& refNum) + { + return ref.mRefNum == refNum; + } + + bool operator== (const CellRef& ref, const CellRef::RefNum& refNum) + { + return ref.mRefNum == refNum; + } void Cell::load(ESMReader &esm, bool saveContext) @@ -143,7 +169,7 @@ std::string Cell::getDescription() const } } -bool Cell::getNextRef(ESMReader &esm, CellRef &ref) +bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted) { // TODO: Try and document reference numbering, I don't think this has been done anywhere else. if (!esm.hasMoreSubs()) @@ -157,132 +183,28 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref) // That should be it, I haven't seen any other fields yet. } - // NAM0 sometimes appears here, sometimes further on - ref.mNam0 = 0; - if (esm.isNextSub("NAM0")) - { - esm.getHT(ref.mNam0); - //esm.getHNOT(NAM0, "NAM0"); - } - - esm.getHNT(ref.mRefnum, "FRMR"); - ref.mRefID = esm.getHNString("NAME"); + ref.load (esm); // Identify references belonging to a parent file and adapt the ID accordingly. - int local = (ref.mRefnum & 0xff000000) >> 24; - size_t global = esm.getIndex() + 1; - if (local) - { - // If the most significant 8 bits are used, then this reference already exists. - // In this case, do not spawn a new reference, but overwrite the old one. - ref.mRefnum &= 0x00ffffff; // delete old plugin ID - const std::vector &masters = esm.getGameFiles(); - global = masters[local-1].index + 1; - ref.mRefnum |= global << 24; // insert global plugin ID - } - else - { - // This is an addition by the present plugin. Set the corresponding plugin index. - ref.mRefnum |= global << 24; // insert global plugin ID - } - - // getHNOT will not change the existing value if the subrecord is - // missing - ref.mScale = 1.0; - esm.getHNOT(ref.mScale, "XSCL"); - - // TODO: support loading references from saves, there are tons of keys not recognized yet. - // The following is just an incomplete list. - if (esm.isNextSub("ACTN")) - esm.skipHSub(); - if (esm.isNextSub("STPR")) - esm.skipHSub(); - if (esm.isNextSub("ACDT")) - esm.skipHSub(); - if (esm.isNextSub("ACSC")) - esm.skipHSub(); - if (esm.isNextSub("ACSL")) - esm.skipHSub(); - if (esm.isNextSub("CHRD")) - esm.skipHSub(); - else if (esm.isNextSub("CRED")) // ??? - esm.skipHSub(); - - ref.mOwner = esm.getHNOString("ANAM"); - ref.mGlob = esm.getHNOString("BNAM"); - ref.mSoul = esm.getHNOString("XSOL"); - - ref.mFaction = esm.getHNOString("CNAM"); - ref.mFactIndex = -2; - esm.getHNOT(ref.mFactIndex, "INDX"); + adjustRefNum (ref.mRefNum, esm); - ref.mGoldValue = 1; - ref.mCharge = -1; - ref.mEnchantmentCharge = -1; - - esm.getHNOT(ref.mEnchantmentCharge, "XCHG"); - - esm.getHNOT(ref.mCharge, "INTV"); - - esm.getHNOT(ref.mGoldValue, "NAM9"); - - // Present for doors that teleport you to another cell. - if (esm.isNextSub("DODT")) + if (esm.isNextSub("DELE")) { - ref.mTeleport = true; - esm.getHT(ref.mDoorDest); - ref.mDestCell = esm.getHNOString("DNAM"); - } else { - ref.mTeleport = false; - } - - // Integer, despite the name suggesting otherwise - ref.mLockLevel = -1; - esm.getHNOT(ref.mLockLevel, "FLTV"); - ref.mKey = esm.getHNOString("KNAM"); - ref.mTrap = esm.getHNOString("TNAM"); - - ref.mReferenceBlocked = -1; - ref.mFltv = 0; - esm.getHNOT(ref.mReferenceBlocked, "UNAM"); - esm.getHNOT(ref.mFltv, "FLTV"); - - esm.getHNOT(ref.mPos, "DATA", 24); - - // Number of references in the cell? Maximum once in each cell, - // but not always at the beginning, and not always right. In other - // words, completely useless. - // Update: Well, maybe not completely useless. This might actually be - // number_of_references + number_of_references_moved_here_Across_boundaries, - // and could be helpful for collecting these weird moved references. - if (esm.isNextSub("NAM0")) - { - esm.getHT(ref.mNam0); - //esm.getHNOT(NAM0, "NAM0"); - } - - if (esm.isNextSub("DELE")) { esm.skipHSub(); - ref.mDeleted = 2; // Deleted, will not respawn. - // TODO: find out when references do respawn. - } else - ref.mDeleted = 0; + deleted = true; + } + else + deleted = false; return true; } bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) { - esm.getHT(mref.mRefnum); + esm.getHT(mref.mRefNum.mIndex); esm.getHNOT(mref.mTarget, "CNDT"); - // Identify references belonging to a parent file and adapt the ID accordingly. - int local = (mref.mRefnum & 0xff000000) >> 24; - size_t global = esm.getIndex() + 1; - mref.mRefnum &= 0x00ffffff; // delete old plugin ID - const std::vector &masters = esm.getGameFiles(); - global = masters[local-1].index + 1; - mref.mRefnum |= global << 24; // insert global plugin ID + adjustRefNum (mref.mRefNum, esm); return true; } @@ -318,4 +240,24 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) *original = *modified; original->mWater = waterLevel; } + + CellId Cell::getCellId() const + { + CellId id; + + id.mPaged = !(mData.mFlags & Interior); + + if (id.mPaged) + { + id.mWorldspace = "default"; + id.mIndex.mX = mData.mX; + id.mIndex.mY = mData.mY; + } + else + { + id.mWorldspace = Misc::StringUtils::lowerCase (mName); + } + + return id; + } } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 71478946f..f01c88c65 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -18,6 +18,7 @@ namespace ESM { class ESMReader; class ESMWriter; + class CellId; /* Moved cell reference tracking object. This mainly stores the target cell of the reference, so we can easily know where it has been moved when another @@ -27,7 +28,7 @@ class ESMWriter; class MovedCellRef { public: - int mRefnum; + CellRef::RefNum mRefNum; // Target cell (if exterior) int mTarget[2]; @@ -37,9 +38,9 @@ public: // introduces a henchman (which no one uses), so we may need this as well. }; -/// Overloaded copare operator used to search inside a list of cell refs. -bool operator==(const MovedCellRef& ref, int pRefnum); -bool operator==(const CellRef& ref, int pRefnum); +/// Overloaded compare operator used to search inside a list of cell refs. +bool operator==(const MovedCellRef& ref, const CellRef::RefNum& refNum); +bool operator==(const CellRef& ref, const CellRef::RefNum& refNum); typedef std::list MovedCellRefTracker; typedef std::list CellRefTracker; @@ -147,7 +148,7 @@ struct Cell All fields of the CellRef struct are overwritten. You can safely reuse one memory location without blanking it between calls. */ - static bool getNextRef(ESMReader &esm, CellRef &ref); + static bool getNextRef(ESMReader &esm, CellRef &ref, bool& deleted); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. @@ -156,6 +157,8 @@ struct Cell void blank(); ///< Set record to default state (does not touch the ID/index). + + CellId getCellId() const; }; } #endif diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 9c97eaa4d..1b701229e 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -14,7 +14,7 @@ void Land::LandData::save(ESMWriter &esm) esm.writeHNT("VNML", mNormals, sizeof(VNML)); } if (mDataTypes & Land::DATA_VHGT) { - static VHGT offsets; + VHGT offsets; offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; offsets.mUnk1 = mUnk1; offsets.mUnk2 = mUnk2; diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 30460c17a..de679e815 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -26,24 +26,24 @@ void Script::load(ESMReader &esm) if (esm.isNextSub("SCVR")) { int s = mData.mStringTableSize; - char* tmp = new char[s]; - esm.getHExact(tmp, s); + + std::vector tmp (s); + esm.getHExact (&tmp[0], s); // Set up the list of variable names mVarNames.resize(mData.mNumShorts + mData.mNumLongs + mData.mNumFloats); // The tmp buffer is a null-byte separated string list, we // just have to pick out one string at a time. - char* str = tmp; + char* str = &tmp[0]; for (size_t i = 0; i < mVarNames.size(); i++) { mVarNames[i] = std::string(str); str += mVarNames[i].size() + 1; - if (str - tmp > s) + if (str - &tmp[0] > s) esm.fail("String table overflow"); } - delete[] tmp; } // Script mData diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index 262d4f6fa..d3d525049 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -19,6 +19,15 @@ void ESM::Header::blank() void ESM::Header::load (ESMReader &esm) { + if (esm.isNextSub ("FORM")) + { + esm.getHT (mFormat); + if (mFormat<0) + esm.fail ("invalid format code"); + } + else + mFormat = 0; + if (esm.isNextSub("HEDR")) { esm.getSubHeader(); @@ -29,15 +38,6 @@ void ESM::Header::load (ESMReader &esm) esm.getT(mData.records); } - if (esm.isNextSub ("FORM")) - { - esm.getHT (mFormat); - if (mFormat<0) - esm.fail ("invalid format code"); - } - else - mFormat = 0; - while (esm.isNextSub ("MAST")) { MasterData m; @@ -49,11 +49,11 @@ void ESM::Header::load (ESMReader &esm) void ESM::Header::save (ESMWriter &esm) { - esm.writeHNT ("HEDR", mData, 300); - if (mFormat>0) esm.writeHNT ("FORM", mFormat); + esm.writeHNT ("HEDR", mData, 300); + for (std::vector::iterator iter = mMaster.begin(); iter != mMaster.end(); ++iter) { diff --git a/components/esm/locals.cpp b/components/esm/locals.cpp new file mode 100644 index 000000000..9c470a025 --- /dev/null +++ b/components/esm/locals.cpp @@ -0,0 +1,28 @@ + +#include "locals.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::Locals::load (ESMReader &esm) +{ + while (esm.isNextSub ("LOCA")) + { + std::string id = esm.getHString(); + + Variant value; + value.read (esm, Variant::Format_Info); + + mVariables.push_back (std::make_pair (id, value)); + } +} + +void ESM::Locals::save (ESMWriter &esm) const +{ + for (std::vector >::const_iterator iter (mVariables.begin()); + iter!=mVariables.end(); ++iter) + { + esm.writeHNString ("LOCA", iter->first); + iter->second.write (esm, Variant::Format_Info); + } +} \ No newline at end of file diff --git a/components/esm/locals.hpp b/components/esm/locals.hpp new file mode 100644 index 000000000..af5afb23b --- /dev/null +++ b/components/esm/locals.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_ESM_LOCALS_H +#define OPENMW_ESM_LOCALS_H + +#include +#include + +#include "variant.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + /// \brief Storage structure for local variables (only used in saved games) + /// + /// \note This is not a top-level record. + + struct Locals + { + std::vector > mVariables; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp new file mode 100644 index 000000000..c452611a0 --- /dev/null +++ b/components/esm/npcstate.cpp @@ -0,0 +1,16 @@ + +#include "npcstate.hpp" + +void ESM::NpcState::load (ESMReader &esm) +{ + ObjectState::load (esm); + + mInventory.load (esm); +} + +void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const +{ + ObjectState::save (esm, inInventory); + + mInventory.save (esm); +} \ No newline at end of file diff --git a/components/esm/npcstate.hpp b/components/esm/npcstate.hpp new file mode 100644 index 000000000..ceb18b88b --- /dev/null +++ b/components/esm/npcstate.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_ESM_NPCSTATE_H +#define OPENMW_ESM_NPCSTATE_H + +#include "objectstate.hpp" +#include "inventorystate.hpp" + +namespace ESM +{ + // format 0, saved games only + + struct NpcState : public ObjectState + { + InventoryState mInventory; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + }; +} + +#endif diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp new file mode 100644 index 000000000..be00f3ef6 --- /dev/null +++ b/components/esm/objectstate.cpp @@ -0,0 +1,51 @@ + +#include "objectstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::ObjectState::load (ESMReader &esm) +{ + mRef.load (esm, true); + + mHasLocals = 0; + esm.getHNOT (mHasLocals, "HLOC"); + + if (mHasLocals) + mLocals.load (esm); + + mEnabled = 1; + esm.getHNOT (mEnabled, "ENAB"); + + mCount = 1; + esm.getHNOT (mCount, "COUN"); + + esm.getHNOT (mPosition, "POS_", 24); + + esm.getHNOT (mLocalRotation, "LROT", 12); +} + +void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const +{ + mRef.save (esm, true, inInventory); + + if (mHasLocals) + { + esm.writeHNT ("HLOC", mHasLocals); + mLocals.save (esm); + } + + if (!mEnabled && !inInventory) + esm.writeHNT ("ENAB", mEnabled); + + if (mCount!=1) + esm.writeHNT ("COUN", mCount); + + if (!inInventory) + { + esm.writeHNT ("POS_", mPosition, 24); + esm.writeHNT ("LROT", mLocalRotation, 12); + } +} + +ESM::ObjectState::~ObjectState() {} \ No newline at end of file diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp new file mode 100644 index 000000000..9c9ca5f2e --- /dev/null +++ b/components/esm/objectstate.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_ESM_OBJECTSTATE_H +#define OPENMW_ESM_OBJECTSTATE_H + +#include +#include + +#include "cellref.hpp" +#include "locals.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + ///< \brief Save state for objects, that do not use custom data + struct ObjectState + { + CellRef mRef; + + unsigned char mHasLocals; + Locals mLocals; + unsigned char mEnabled; + int mCount; + ESM::Position mPosition; + float mLocalRotation[3]; + + virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + + virtual ~ObjectState(); + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/player.cpp b/components/esm/player.cpp new file mode 100644 index 000000000..d5ddc74d0 --- /dev/null +++ b/components/esm/player.cpp @@ -0,0 +1,48 @@ + +#include "player.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::Player::load (ESMReader &esm) +{ + mObject.load (esm); + + mCellId.load (esm); + + esm.getHNT (mLastKnownExteriorPosition, "LKEP", 12); + + if (esm.isNextSub ("MARK")) + { + mHasMark = true; + esm.getHT (mMarkedPosition, 24); + mMarkedCell.load (esm); + } + else + mHasMark = false; + + mAutoMove = 0; + esm.getHNOT (mAutoMove, "AMOV"); + + mBirthsign = esm.getHNString ("SIGN"); +} + +void ESM::Player::save (ESMWriter &esm) const +{ + mObject.save (esm); + + mCellId.save (esm); + + esm.writeHNT ("LKEP", mLastKnownExteriorPosition, 12); + + if (mHasMark) + { + esm.writeHNT ("MARK", mMarkedPosition, 24); + mMarkedCell.save (esm); + } + + if (mAutoMove) + esm.writeHNT ("AMOV", mAutoMove); + + esm.writeHNString ("SIGN", mBirthsign); +} \ No newline at end of file diff --git a/components/esm/player.hpp b/components/esm/player.hpp new file mode 100644 index 000000000..0d70ee090 --- /dev/null +++ b/components/esm/player.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_ESM_PLAYER_H +#define OPENMW_ESM_PLAYER_H + +#include + +#include "npcstate.hpp" +#include "cellid.hpp" +#include "defs.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + struct Player + { + NpcState mObject; + CellId mCellId; + float mLastKnownExteriorPosition[3]; + unsigned char mHasMark; + ESM::Position mMarkedPosition; + CellId mMarkedCell; + unsigned char mAutoMove; + std::string mBirthsign; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/queststate.cpp b/components/esm/queststate.cpp new file mode 100644 index 000000000..e93826725 --- /dev/null +++ b/components/esm/queststate.cpp @@ -0,0 +1,19 @@ + +#include "queststate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +void ESM::QuestState::load (ESMReader &esm) +{ + mTopic = esm.getHNString ("YETO"); + esm.getHNOT (mState, "QSTA"); + esm.getHNOT (mFinished, "QFIN"); +} + +void ESM::QuestState::save (ESMWriter &esm) const +{ + esm.writeHNString ("YETO", mTopic); + esm.writeHNT ("QSTA", mState); + esm.writeHNT ("QFIN", mFinished); +} \ No newline at end of file diff --git a/components/esm/queststate.hpp b/components/esm/queststate.hpp new file mode 100644 index 000000000..1769336f2 --- /dev/null +++ b/components/esm/queststate.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_ESM_QUESTSTATE_H +#define OPENMW_ESM_QUESTSTATE_H + +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + struct QuestState + { + std::string mTopic; + int mState; + unsigned char mFinished; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif \ No newline at end of file diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp new file mode 100644 index 000000000..d6887f170 --- /dev/null +++ b/components/esm/savedgame.cpp @@ -0,0 +1,46 @@ + +#include "savedgame.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" +#include "defs.hpp" + +unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; + +void ESM::SavedGame::load (ESMReader &esm) +{ + mPlayerName = esm.getHNString("PLNA"); + esm.getHNOT (mPlayerLevel, "PLLE"); + mPlayerClass = esm.getHNString("PLCL"); + mPlayerCell = esm.getHNString("PLCE"); + esm.getHNT (mInGameTime, "TSTM", 16); + esm.getHNT (mTimePlayed, "TIME"); + mDescription = esm.getHNString ("DESC"); + + while (esm.isNextSub ("DEPE")) + mContentFiles.push_back (esm.getHString()); + + esm.getSubNameIs("SCRN"); + esm.getSubHeader(); + mScreenshot.resize(esm.getSubSize()); + esm.getExact(&mScreenshot[0], mScreenshot.size()); +} + +void ESM::SavedGame::save (ESMWriter &esm) const +{ + esm.writeHNString ("PLNA", mPlayerName); + esm.writeHNT ("PLLE", mPlayerLevel); + esm.writeHNString ("PLCL", mPlayerClass); + esm.writeHNString ("PLCE", mPlayerCell); + esm.writeHNT ("TSTM", mInGameTime, 16); + esm.writeHNT ("TIME", mTimePlayed); + esm.writeHNString ("DESC", mDescription); + + for (std::vector::const_iterator iter (mContentFiles.begin()); + iter!=mContentFiles.end(); ++iter) + esm.writeHNString ("DEPE", *iter); + + esm.startSubRecord("SCRN"); + esm.write(&mScreenshot[0], mScreenshot.size()); + esm.endRecord("SCRN"); +} diff --git a/components/esm/savedgame.hpp b/components/esm/savedgame.hpp new file mode 100644 index 000000000..9c7bf551d --- /dev/null +++ b/components/esm/savedgame.hpp @@ -0,0 +1,41 @@ +#ifndef OPENMW_ESM_SAVEDGAME_H +#define OPENMW_ESM_SAVEDGAME_H + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + + struct SavedGame + { + static unsigned int sRecordId; + + struct TimeStamp + { + float mGameHour; + int mDay; + int mMonth; + int mYear; + }; + + std::vector mContentFiles; + std::string mPlayerName; + int mPlayerLevel; + std::string mPlayerClass; // this is the ID and not the name of the class + std::string mPlayerCell; + TimeStamp mInGameTime; + double mTimePlayed; + std::string mDescription; + std::vector mScreenshot; // raw jpg-encoded data + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + }; +} + +#endif diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp index 2bba60a15..8ba9bb34f 100644 --- a/components/esm/variant.hpp +++ b/components/esm/variant.hpp @@ -33,7 +33,7 @@ namespace ESM { Format_Global, Format_Gmst, - Format_Info + Format_Info // also used for local variables in saved game files }; Variant(); diff --git a/components/interpreter/miscopcodes.hpp b/components/interpreter/miscopcodes.hpp index 1b4c823a0..1da8cf695 100644 --- a/components/interpreter/miscopcodes.hpp +++ b/components/interpreter/miscopcodes.hpp @@ -48,9 +48,10 @@ namespace Interpreter } else if (c=='f' || c=='F' || c=='.') { - while (c!='f' && icontroller; do { // Load GeomMorpherController into an Ogre::Pose and Animation - if(ctrl->recType == Nif::RC_NiGeomMorpherController) + if(ctrl->recType == Nif::RC_NiGeomMorpherController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) { const Nif::NiGeomMorpherController *geom = static_cast(ctrl.getPtr()); diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index d036844fc..43e8934c8 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -311,25 +311,26 @@ public: static void setVisible(Ogre::Node *node, int vis) { + // Skinned meshes are attached to the scene node, not the bone. + // We use the Node's user data to connect it with the mesh. + Ogre::Any customData = node->getUserObjectBindings().getUserAny(); + + if (!customData.isEmpty()) + Ogre::any_cast(customData)->setVisible(vis); + + Ogre::TagPoint *tag = dynamic_cast(node); + if(tag != NULL) + { + Ogre::MovableObject *obj = tag->getChildObject(); + if(obj != NULL) + obj->setVisible(vis); + } + Ogre::Node::ChildNodeIterator iter = node->getChildIterator(); while(iter.hasMoreElements()) { node = iter.getNext(); setVisible(node, vis); - - // Skinned meshes and particle systems are attached to the scene node, not the bone. - // We use the Node's user data to connect it with the mesh / particle system. - Ogre::Any customData = node->getUserObjectBindings().getUserAny(); - if (!customData.isEmpty()) - Ogre::any_cast(customData)->setVisible(vis); - - Ogre::TagPoint *tag = dynamic_cast(node); - if(tag != NULL) - { - Ogre::MovableObject *obj = tag->getChildObject(); - if(obj != NULL) - obj->setVisible(vis); - } } } @@ -622,50 +623,52 @@ class NIFObjectLoader scene->mEntities.push_back(entity); if(scene->mSkelBase) { + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast(entity))); + if(entity->hasSkeleton()) entity->shareSkeletonInstanceWith(scene->mSkelBase); else - { - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, shape->recIndex); - Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - trgtbone->getUserObjectBindings().setUserAny(Ogre::Any(static_cast(entity))); scene->mSkelBase->attachObjectToBone(trgtbone->getName(), entity); - } } Nif::ControllerPtr ctrl = node->controller; while(!ctrl.empty()) { - if(ctrl->recType == Nif::RC_NiUVController) + if (ctrl->flags & Nif::NiNode::ControllerFlag_Active) { - const Nif::NiUVController *uv = static_cast(ctrl.getPtr()); + if(ctrl->recType == Nif::RC_NiUVController) + { + const Nif::NiUVController *uv = static_cast(ctrl.getPtr()); - Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr)); + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW UVController::Value(entity, uv->data.getPtr(), &scene->mMaterialControllerMgr)); - UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); + UVController::Function* function = OGRE_NEW UVController::Function(uv, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - else if(ctrl->recType == Nif::RC_NiGeomMorpherController) - { - const Nif::NiGeomMorpherController *geom = static_cast(ctrl.getPtr()); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + else if(ctrl->recType == Nif::RC_NiGeomMorpherController) + { + const Nif::NiGeomMorpherController *geom = static_cast(ctrl.getPtr()); - Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value( - entity, geom->data.getPtr(), geom->recIndex)); + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW GeomMorpherController::Value( + entity, geom->data.getPtr(), geom->recIndex)); - GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); + GeomMorpherController::Function* function = OGRE_NEW GeomMorpherController::Function(geom, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } } ctrl = ctrl->next; } @@ -746,8 +749,8 @@ class NIFObjectLoader emitter->setParticleVelocity(partctrl->velocity - partctrl->velocityRandom*0.5f, partctrl->velocity + partctrl->velocityRandom*0.5f); emitter->setEmissionRate(partctrl->emitRate); - emitter->setTimeToLive(partctrl->lifetime - partctrl->lifetimeRandom*0.5f, - partctrl->lifetime + partctrl->lifetimeRandom*0.5f); + emitter->setTimeToLive(partctrl->lifetime, + partctrl->lifetime + partctrl->lifetimeRandom); emitter->setParameter("width", Ogre::StringConverter::toString(partctrl->offsetRandom.x)); emitter->setParameter("height", Ogre::StringConverter::toString(partctrl->offsetRandom.y)); emitter->setParameter("depth", Ogre::StringConverter::toString(partctrl->offsetRandom.z)); @@ -852,7 +855,7 @@ class NIFObjectLoader Nif::ControllerPtr ctrl = partnode->controller; while(!ctrl.empty()) { - if(ctrl->recType == Nif::RC_NiParticleSystemController) + if(ctrl->recType == Nif::RC_NiParticleSystemController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) { const Nif::NiParticleSystemController *partctrl = static_cast(ctrl.getPtr()); @@ -879,6 +882,9 @@ class NIFObjectLoader Ogre::ControllerFunctionRealPtr func(function); scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + + if (partflags&Nif::NiNode::ParticleFlag_AutoPlay) + partsys->fastForward(1, 0.1); } ctrl = ctrl->next; } @@ -893,42 +899,45 @@ class NIFObjectLoader static void createNodeControllers(const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) { do { - if(ctrl->recType == Nif::RC_NiVisController) + if (ctrl->flags & Nif::NiNode::ControllerFlag_Active) { - const Nif::NiVisController *vis = static_cast(ctrl.getPtr()); - - int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); - Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? - Ogre::ControllerManager::getSingleton().getFrameTimeSource() : - Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr())); - - VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); - scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); - Ogre::ControllerFunctionRealPtr func(function); - - scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); - } - else if(ctrl->recType == Nif::RC_NiKeyframeController) - { - const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); - if(!key->data.empty()) + if(ctrl->recType == Nif::RC_NiVisController) { + const Nif::NiVisController *vis = static_cast(ctrl.getPtr()); + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); - // The keyframe controller will control this bone manually - trgtbone->setManuallyControlled(true); Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? Ogre::ControllerManager::getSingleton().getFrameTimeSource() : Ogre::ControllerValueRealPtr()); - Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); - KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW VisController::Value(trgtbone, vis->data.getPtr())); + + VisController::Function* function = OGRE_NEW VisController::Function(vis, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); Ogre::ControllerFunctionRealPtr func(function); scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); } + else if(ctrl->recType == Nif::RC_NiKeyframeController) + { + const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); + if(!key->data.empty()) + { + int trgtid = NIFSkeletonLoader::lookupOgreBoneHandle(name, ctrl->target->recIndex); + Ogre::Bone *trgtbone = scene->mSkelBase->getSkeleton()->getBone(trgtid); + // The keyframe controller will control this bone manually + trgtbone->setManuallyControlled(true); + Ogre::ControllerValueRealPtr srcval((animflags&Nif::NiNode::AnimFlag_AutoPlay) ? + Ogre::ControllerManager::getSingleton().getFrameTimeSource() : + Ogre::ControllerValueRealPtr()); + Ogre::ControllerValueRealPtr dstval(OGRE_NEW KeyframeController::Value(trgtbone, key->data.getPtr())); + KeyframeController::Function* function = OGRE_NEW KeyframeController::Function(key, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); + scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); + Ogre::ControllerFunctionRealPtr func(function); + + scene->mControllers.push_back(Ogre::Controller(srcval, dstval, func)); + } + } } ctrl = ctrl->next; } while(!ctrl.empty()); @@ -1034,7 +1043,7 @@ class NIFObjectLoader e = e->extra; } - if(!node->controller.empty() && (node->parent || node->recType != Nif::RC_NiNode)) + if(!node->controller.empty()) createNodeControllers(name, node->controller, scene, animflags); if(node->recType == Nif::RC_NiCamera) @@ -1151,6 +1160,9 @@ public: continue; } + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + const Nif::NiStringExtraData *strdata = static_cast(extra.getPtr()); const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); diff --git a/components/nifogre/skeleton.cpp b/components/nifogre/skeleton.cpp index e01ae22ef..c0482cf5e 100644 --- a/components/nifogre/skeleton.cpp +++ b/components/nifogre/skeleton.cpp @@ -97,7 +97,7 @@ bool NIFSkeletonLoader::needSkeleton(const Nif::Node *node) { Nif::ControllerPtr ctrl = node->controller; do { - if(ctrl->recType == Nif::RC_NiKeyframeController) + if(ctrl->recType == Nif::RC_NiKeyframeController && ctrl->flags & Nif::NiNode::ControllerFlag_Active) return true; } while(!(ctrl=ctrl->next).empty()); } diff --git a/apps/openmw/config.hpp.cmake b/components/version/version.hpp.cmake similarity index 52% rename from apps/openmw/config.hpp.cmake rename to components/version/version.hpp.cmake index 848fbe0eb..4cdfa32f0 100644 --- a/apps/openmw/config.hpp.cmake +++ b/components/version/version.hpp.cmake @@ -1,9 +1,13 @@ -#ifndef CONFIG_H -#define CONFIG_H +#ifndef VERSION_HPP +#define VERSION_HPP #define OPENMW_VERSION_MAJOR @OPENMW_VERSION_MAJOR@ #define OPENMW_VERSION_MINOR @OPENMW_VERSION_MINOR@ #define OPENMW_VERSION_RELEASE @OPENMW_VERSION_RELEASE@ #define OPENMW_VERSION "@OPENMW_VERSION@" -#endif +#define OPENMW_VERSION_COMMITHASH "@OPENMW_VERSION_COMMITHASH@" +#define OPENMW_VERSION_TAGHASH "@OPENMW_VERSION_TAGHASH@" + +#endif // VERSION_HPP + diff --git a/files/mygui/openmw_inventory_window.layout b/files/mygui/openmw_inventory_window.layout index ecccd995b..09e5ed9c7 100644 --- a/files/mygui/openmw_inventory_window.layout +++ b/files/mygui/openmw_inventory_window.layout @@ -2,7 +2,7 @@ - + @@ -12,11 +12,10 @@ - - - + + - + diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout index 232f31b75..6e0efce7e 100644 --- a/files/mygui/openmw_map_window.layout +++ b/files/mygui/openmw_map_window.layout @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_savegame_dialog.layout b/files/mygui/openmw_savegame_dialog.layout index 18de6a239..ceb1a8428 100644 --- a/files/mygui/openmw_savegame_dialog.layout +++ b/files/mygui/openmw_savegame_dialog.layout @@ -17,8 +17,6 @@ - - @@ -26,11 +24,6 @@ - - - - - @@ -51,8 +44,6 @@ - - diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout index ab924da6d..ec655ace8 100644 --- a/files/mygui/openmw_spell_window.layout +++ b/files/mygui/openmw_spell_window.layout @@ -2,7 +2,7 @@ - + diff --git a/files/mygui/openmw_stats_window.layout b/files/mygui/openmw_stats_window.layout index 36c28c450..6cdd4c02a 100644 --- a/files/mygui/openmw_stats_window.layout +++ b/files/mygui/openmw_stats_window.layout @@ -2,7 +2,7 @@ - + @@ -100,7 +100,9 @@ - + + + @@ -114,7 +116,7 @@ - + @@ -128,7 +130,7 @@ - + @@ -142,7 +144,7 @@ - + @@ -156,7 +158,7 @@ - + @@ -170,7 +172,7 @@ - + @@ -184,7 +186,7 @@ - + @@ -198,7 +200,7 @@ - + @@ -212,6 +214,7 @@ + diff --git a/files/mygui/openmw_tooltips.layout b/files/mygui/openmw_tooltips.layout index 624c133f2..3f4fec59f 100644 --- a/files/mygui/openmw_tooltips.layout +++ b/files/mygui/openmw_tooltips.layout @@ -184,11 +184,8 @@ - - - diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 22391fe93..4fb7097f8 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1,4 +1,4 @@ -# WARNING: Editing this file might have no effect, as these +# WARNING: Editing this file might have no effect, as these # settings are overwritten by your user settings file. [Video] @@ -13,7 +13,7 @@ screen = 0 # Valid values: # OpenGL Rendering Subsystem # Direct3D9 Rendering Subsystem -render system = +render system = # Valid values: # none @@ -171,6 +171,9 @@ ui y multiplier = 1.0 # Always use the most powerful attack when striking with a weapon (chop, slash or thrust) best attack = false +[Saves] +character = + [Windows] inventory x = 0 inventory y = 0.4275 diff --git a/files/ui/mainwindow.ui b/files/ui/mainwindow.ui index a1dfb172b..5f2be05a2 100644 --- a/files/ui/mainwindow.ui +++ b/files/ui/mainwindow.ui @@ -2,6 +2,14 @@ MainWindow + + + 0 + 0 + 575 + 535 + + 575 @@ -56,11 +64,35 @@ - - - QDialogButtonBox::Close - - + + + + + OpenMW version + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QDialogButtonBox::Close + + + +