From c3ef387208614527937f5b54d50ec864c59dba39 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 27 Feb 2016 12:53:07 +0100 Subject: [PATCH] Vanilla-compatible creature/NPC respawning (Fixes #2369, Fixes #2467) --- apps/openmw/mwbase/world.hpp | 11 ++++---- apps/openmw/mwclass/creature.cpp | 13 +++++++++- apps/openmw/mwclass/creaturelevlist.cpp | 23 ++++++++++++++++- apps/openmw/mwclass/npc.cpp | 13 +++++++++- apps/openmw/mwmechanics/creaturestats.cpp | 10 ++++++++ apps/openmw/mwmechanics/creaturestats.hpp | 4 +++ apps/openmw/mwstate/statemanagerimp.cpp | 3 ++- apps/openmw/mwworld/cellstore.cpp | 31 ++++++++++++----------- apps/openmw/mwworld/scene.cpp | 27 ++++++++++++-------- apps/openmw/mwworld/scene.hpp | 10 +++++--- apps/openmw/mwworld/worldimp.cpp | 20 +++++++-------- apps/openmw/mwworld/worldimp.hpp | 11 ++++---- components/esm/creaturestats.cpp | 5 ++++ components/esm/creaturestats.hpp | 1 + 14 files changed, 128 insertions(+), 54 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 52697d670..99cbe9654 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -231,15 +231,16 @@ namespace MWBase virtual float getTimeScaleFactor() const = 0; - virtual void changeToInteriorCell (const std::string& cellName, - const ESM::Position& position) = 0; + virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent=true) = 0; ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position) = 0; + virtual void changeToExteriorCell (const ESM::Position& position, bool changeEvent=true) = 0; ///< Move to exterior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true) = 0; - ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent=true) = 0; + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes 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. diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4a7af1099..182177d52 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -759,7 +759,18 @@ namespace MWClass void Creature::respawn(const MWWorld::Ptr &ptr) const { - if (isFlagBitSet(ptr, ESM::Creature::Respawn)) + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + return; + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + + if (isFlagBitSet(ptr, ESM::Creature::Respawn) + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 0223fb634..cd55d31a1 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -44,7 +44,28 @@ namespace MWClass ensureCustomData(ptr); CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); - customData.mSpawn = true; + if (customData.mSpawn) + return; + + MWWorld::Ptr creature = (customData.mSpawnActorId == -1) ? MWWorld::Ptr() : MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (!creature.isEmpty()) + { + const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); + if (creature.getRefData().getCount() == 0) + customData.mSpawn = true; + else if (creatureStats.isDead()) + { + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = std::min(fCorpseRespawnDelay, fCorpseClearDelay); + if (creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + customData.mSpawn = true; + } + } + else + customData.mSpawn = true; } void CreatureLevList::registerSelf() diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 4c825f63f..ee0112ac9 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1292,7 +1292,18 @@ namespace MWClass void Npc::respawn(const MWWorld::Ptr &ptr) const { - if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) + const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); + if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + return; + + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); + static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); + + float delay = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + + if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn + && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 6933c40a3..50b1a89b3 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -187,6 +187,9 @@ namespace MWMechanics if (index==0 && mDynamic[index].getCurrent()<1) { + if (!mDead) + mTimeOfDeath = MWBase::Environment::get().getWorld()->getTimeStamp(); + mDead = true; mDynamic[index].setModifier(0); @@ -503,6 +506,7 @@ namespace MWMechanics state.mLevel = mLevel; state.mActorId = mActorId; state.mDeathAnimation = mDeathAnimation; + state.mTimeOfDeath = mTimeOfDeath.toEsm(); mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); @@ -549,6 +553,7 @@ namespace MWMechanics mLevel = state.mLevel; mActorId = state.mActorId; mDeathAnimation = state.mDeathAnimation; + mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath); mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); @@ -622,6 +627,11 @@ namespace MWMechanics mDeathAnimation = index; } + MWWorld::TimeStamp CreatureStats::getTimeOfDeath() const + { + return mTimeOfDeath; + } + std::map& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 46c5bab31..734e87319 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -65,6 +65,8 @@ namespace MWMechanics // The index of the death animation that was played unsigned char mDeathAnimation; + MWWorld::TimeStamp mTimeOfDeath; + public: typedef std::pair SummonKey; // private: @@ -259,6 +261,8 @@ namespace MWMechanics unsigned char getDeathAnimation() const; void setDeathAnimation(unsigned char index); + MWWorld::TimeStamp getTimeOfDeath() const; + int getActorId(); ///< Will generate an actor ID, if the actor does not have one yet. diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 89d31c485..1bf97cb7e 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -493,7 +493,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str // but some mods may be using it as a reload detector. MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); - // Do not trigger erroneous cellChanged events + // Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag. + // But make sure the flag is cleared anyway in case it was set from an earlier game. MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } catch (const std::exception& e) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index dcaa73e93..1572ae1b0 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -902,21 +902,22 @@ namespace MWWorld Ptr ptr (&*it, this); ptr.getClass().respawn(ptr); } - for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } - for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } - for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) - { - Ptr ptr (&*it, this); - ptr.getClass().respawn(ptr); - } + } + + for (CellRefList::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); + } + for (CellRefList::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) + { + Ptr ptr (&*it, this); + ptr.getClass().respawn(ptr); } } } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index cd0954260..63bb4758a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -240,7 +240,7 @@ namespace MWWorld mActiveCells.erase(*iter); } - void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener) + void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn) { std::pair result = mActiveCells.insert(cell); @@ -270,12 +270,13 @@ namespace MWWorld } } - cell->respawn(); - // register local scripts // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); + if (respawn) + cell->respawn(); + // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST insertCell (*cell, true, loadingListener); @@ -329,7 +330,7 @@ namespace MWWorld } } - void Scene::changeCellGrid (int X, int Y) + void Scene::changeCellGrid (int X, int Y, bool changeEvent) { Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -403,7 +404,7 @@ namespace MWWorld { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadCell (cell, loadingListener); + loadCell (cell, loadingListener, changeEvent); } } } @@ -411,7 +412,8 @@ namespace MWWorld CellStore* current = MWBase::Environment::get().getWorld()->getExterior(X,Y); MWBase::Environment::get().getWindowManager()->changeCell(current); - mCellChanged = true; + if (changeEvent) + mCellChanged = true; mPreloader->updateCache(mRendering.getReferenceTime()); } @@ -484,7 +486,7 @@ namespace MWWorld return mActiveCells; } - void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) + void Scene::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent) { CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName); bool loadcell = (mCurrentCell == NULL); @@ -530,7 +532,7 @@ namespace MWWorld loadingListener->setProgressRange(refsToLoad); // Load cell. - loadCell (cell, loadingListener); + loadCell (cell, loadingListener, changeEvent); changePlayerCell(cell, position, true); @@ -540,21 +542,24 @@ namespace MWWorld // Sky system MWBase::Environment::get().getWorld()->adjustSky(); - mCellChanged = true; MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); + if (changeEvent) + mCellChanged = true; + + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); mPreloader->updateCache(mRendering.getReferenceTime()); } - void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos) + void Scene::changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent) { int x = 0; int y = 0; MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); - changeCellGrid(x, y); + changeCellGrid(x, y, changeEvent); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y); changePlayerCell(current, position, adjustPlayerPos); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 5c429f7c7..6cba9c3be 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -71,7 +71,7 @@ namespace MWWorld void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center - void changeCellGrid (int X, int Y); + void changeCellGrid (int X, int Y, bool changeEvent = true); void getGridCenter(int& cellX, int& cellY); @@ -90,7 +90,7 @@ namespace MWWorld void unloadCell (CellStoreCollection::iterator iter); - void loadCell (CellStore *cell, Loading::Listener* loadingListener); + void loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn); void playerMoved (const osg::Vec3f& pos); @@ -103,11 +103,13 @@ namespace MWWorld bool hasCellChanged() const; ///< Has the set of active cells changed, since the last frame? - void changeToInteriorCell (const std::string& cellName, const ESM::Position& position); + void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent=true); ///< Move to interior cell. + /// @param changeEvent Set cellChanged flag? - void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos); + void changeToExteriorCell (const ESM::Position& position, bool adjustPlayerPos, bool changeEvent=true); ///< Move to exterior cell. + /// @param changeEvent Set cellChanged flag? void changeToVoid(); ///< Change into a void diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ea2069bd2..e6b8f25a2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -962,11 +962,11 @@ namespace MWWorld return mTimeScale->getFloat(); } - void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position) + void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent) { mPhysics->clearQueuedMovement(); - if (mCurrentWorldSpace != cellName) + if (changeEvent && mCurrentWorldSpace != cellName) { // changed worldspace mProjectileManager->clear(); @@ -976,34 +976,34 @@ namespace MWWorld } removeContainerScripts(getPlayerPtr()); - mWorldScene->changeToInteriorCell(cellName, position); + mWorldScene->changeToInteriorCell(cellName, position, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToExteriorCell (const ESM::Position& position) + void World::changeToExteriorCell (const ESM::Position& position, bool changeEvent) { mPhysics->clearQueuedMovement(); - if (mCurrentWorldSpace != "sys::default") // FIXME + if (changeEvent && mCurrentWorldSpace != "sys::default") // FIXME { // changed worldspace mProjectileManager->clear(); mRendering->notifyWorldSpaceChanged(); } removeContainerScripts(getPlayerPtr()); - mWorldScene->changeToExteriorCell(position, true); + mWorldScene->changeToExteriorCell(position, true, changeEvent); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); } - void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange) + void World::changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent) { - if (!detectWorldSpaceChange) + if (!changeEvent) mCurrentWorldSpace = cellId.mWorldspace; if (cellId.mPaged) - changeToExteriorCell (position); + changeToExteriorCell (position, changeEvent); else - changeToInteriorCell (cellId.mWorldspace, position); + changeToInteriorCell (cellId.mWorldspace, position, changeEvent); } void World::markCellAsUnchanged() diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6cc5cdc11..ce5aa417e 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -324,15 +324,16 @@ namespace MWWorld virtual float getTimeScaleFactor() const; - virtual void changeToInteriorCell (const std::string& cellName, - const ESM::Position& position); + virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent = true); ///< Move to interior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToExteriorCell (const ESM::Position& position); + virtual void changeToExteriorCell (const ESM::Position& position, bool changeEvent = true); ///< Move to exterior cell. + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes - virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool detectWorldSpaceChange=true); - ///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes + virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent=true); + ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes 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. diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 9bdbf9668..2200297e2 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -87,6 +87,8 @@ void ESM::CreatureStats::load (ESMReader &esm) mDeathAnimation = 0; esm.getHNOT (mDeathAnimation, "DANM"); + esm.getHNOT (mTimeOfDeath, "DTIM"); + mSpells.load(esm); mActiveSpells.load(esm); mAiSequence.load(esm); @@ -193,6 +195,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mDeathAnimation) esm.writeHNT ("DANM", mDeathAnimation); + if (mTimeOfDeath.mHour != 0 && mTimeOfDeath.mDay != 0) + esm.writeHNT ("DTIM", mTimeOfDeath); + mSpells.save(esm); mActiveSpells.save(esm); mAiSequence.save(esm); diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 426e89055..66b708e27 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -57,6 +57,7 @@ namespace ESM bool mRecalcDynamicStats; int mDrawState; unsigned char mDeathAnimation; + ESM::TimeStamp mTimeOfDeath; int mLevel;