From ae66d28c87d9331f0a42aca806c46232b9a6aa06 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 17 May 2014 09:05:41 +0200 Subject: [PATCH] Feature #32: Implement respawn for containers, creatures and NPCs --- apps/openmw/mwclass/container.cpp | 10 +++++++ apps/openmw/mwclass/container.hpp | 2 ++ apps/openmw/mwclass/creature.cpp | 20 ++++++++++++++ apps/openmw/mwclass/creature.hpp | 2 ++ apps/openmw/mwclass/creaturelevlist.cpp | 27 ++++++++++++++++--- apps/openmw/mwclass/creaturelevlist.hpp | 2 ++ apps/openmw/mwclass/npc.cpp | 20 ++++++++++++++ apps/openmw/mwclass/npc.hpp | 2 ++ apps/openmw/mwworld/cellstore.cpp | 36 ++++++++++++++++++++++++- apps/openmw/mwworld/cellstore.hpp | 7 +++++ apps/openmw/mwworld/class.hpp | 2 ++ apps/openmw/mwworld/scene.cpp | 2 ++ components/esm/cellstate.cpp | 8 +++++- components/esm/cellstate.hpp | 4 +++ components/esm/creaturelevliststate.cpp | 6 +++++ components/esm/creaturelevliststate.hpp | 1 + 16 files changed, 146 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index be76bd0b4..f0c71bf53 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -60,6 +60,16 @@ namespace MWClass } } + void Container::respawn(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = + ptr.get(); + if (ref->mBase->mFlags & ESM::Container::Respawn) + { + ptr.getRefData().setCustomData(NULL); + } + } + void Container::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const { const std::string model = getModel(ptr); diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index f012d675c..79a801248 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -64,6 +64,8 @@ namespace MWClass static void registerSelf(); + virtual void respawn (const MWWorld::Ptr& ptr) const; + virtual std::string getModel(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index dc23b63f3..4abc9ea71 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -822,6 +822,26 @@ namespace MWClass return ptr.get()->mBase->mData.mGold; } + void Creature::respawn(const MWWorld::Ptr &ptr) const + { + if (ptr.get()->mBase->mFlags & ESM::Creature::Respawn) + { + // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. + // This also means we cannot respawn dynamically placed references with no content file connection. + if (ptr.getCellRef().mRefNum.mContentFile != -1) + { + if (ptr.getRefData().getCount() == 0) + ptr.getRefData().setCount(1); + + // Reset to original position + ESM::Position& pos = ptr.getRefData().getPosition(); + pos = ptr.getCellRef().mPos; + + ptr.getRefData().setCustomData(NULL); + } + } + } + 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 04c010c83..d3ffb321e 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -143,6 +143,8 @@ namespace MWClass ///< Write additional state from \a ptr into \a state. virtual int getBaseGold(const MWWorld::Ptr& ptr) const; + + virtual void respawn (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index be01b848a..fea30735c 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -14,6 +14,7 @@ namespace { // actorId of the creature we spawned int mSpawnActorId; + bool mSpawn; // Should a new creature be spawned? virtual MWWorld::CustomData *clone() const; }; @@ -31,6 +32,14 @@ namespace MWClass return ""; } + void CreatureLevList::respawn(const MWWorld::Ptr &ptr) const + { + ensureCustomData(ptr); + + CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); + customData.mSpawn = true; + } + void CreatureLevList::registerSelf() { boost::shared_ptr instance (new CreatureLevList); @@ -43,9 +52,8 @@ namespace MWClass ensureCustomData(ptr); CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); - if (customData.mSpawnActorId != -1) - return; // TODO: handle respawning - + if (!customData.mSpawn) + return; MWWorld::LiveCellRef *ref = ptr.get(); @@ -54,11 +62,21 @@ namespace MWClass if (!id.empty()) { + // Delete the previous creature + if (customData.mSpawnActorId != -1) + { + MWWorld::Ptr creature = MWBase::Environment::get().getWorld()->searchPtrViaActorId(customData.mSpawnActorId); + if (!creature.isEmpty()) + MWBase::Environment::get().getWorld()->deleteObject(creature); + customData.mSpawnActorId = -1; + } + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); MWWorld::ManualRef ref(store, id); ref.getPtr().getCellRef().mPos = ptr.getCellRef().mPos; MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), ptr.getCell() , ptr.getCellRef().mPos); customData.mSpawnActorId = placed.getClass().getCreatureStats(placed).getActorId(); + customData.mSpawn = false; } } @@ -68,6 +86,7 @@ namespace MWClass { std::auto_ptr data (new CreatureLevListCustomData); data->mSpawnActorId = -1; + data->mSpawn = true; ptr.getRefData().setCustomData(data.release()); } @@ -81,6 +100,7 @@ namespace MWClass ensureCustomData(ptr); CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); customData.mSpawnActorId = state2.mSpawnActorId; + customData.mSpawn = state2.mSpawn; } void CreatureLevList::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) @@ -91,5 +111,6 @@ namespace MWClass ensureCustomData(ptr); CreatureLevListCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); state2.mSpawnActorId = customData.mSpawnActorId; + state2.mSpawn = customData.mSpawn; } } diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index b67fb5523..6c51a3189 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -27,6 +27,8 @@ namespace MWClass virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) const; ///< Write additional state from \a ptr into \a state. + + virtual void respawn (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6d2eea6af..5b9e26f26 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1310,6 +1310,26 @@ namespace MWClass return Misc::StringUtils::ciEqual(ptr.get()->mBase->mClass, className); } + void Npc::respawn(const MWWorld::Ptr &ptr) const + { + if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn) + { + // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. + // This also means we cannot respawn dynamically placed references with no content file connection. + if (ptr.getCellRef().mRefNum.mContentFile != -1) + { + if (ptr.getRefData().getCount() == 0) + ptr.getRefData().setCount(1); + + // Reset to original position + ESM::Position& pos = ptr.getRefData().getPosition(); + pos = ptr.getCellRef().mPos; + + ptr.getRefData().setCustomData(NULL); + } + } + } + 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 4b9c8988e..03c6e234a 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -178,6 +178,8 @@ namespace MWClass virtual bool canWalk (const MWWorld::Ptr &ptr) const { return true; } + + virtual void respawn (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 5d2d8a517..aa247f0b9 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -169,7 +169,7 @@ namespace MWWorld } CellStore::CellStore (const ESM::Cell *cell) - : mCell (cell), mState (State_Unloaded), mHasState (false) + : mCell (cell), mState (State_Unloaded), mHasState (false), mLastRespawn(0,0) { mWaterLevel = cell->mWater; } @@ -555,6 +555,7 @@ namespace MWWorld mWaterLevel = state.mWaterLevel; mWaterLevel = state.mWaterLevel; + mLastRespawn = MWWorld::TimeStamp(state.mLastRespawn); } void CellStore::saveState (ESM::CellState& state) const @@ -566,6 +567,7 @@ namespace MWWorld state.mWaterLevel = mWaterLevel; state.mHasFogOfWar = (mFogState.get() ? 1 : 0); + state.mLastRespawn = mLastRespawn.toEsm(); } void CellStore::writeFog(ESM::ESMWriter &writer) const @@ -754,4 +756,36 @@ namespace MWWorld { return mFogState.get(); } + + void CellStore::respawn() + { + if (mState == State_Loaded) + { + static int iMonthsToRespawn = MWBase::Environment::get().getWorld()->getStore().get().find("iMonthsToRespawn")->getInt(); + if (MWBase::Environment::get().getWorld()->getTimeStamp() - mLastRespawn > 24*30*iMonthsToRespawn) + { + mLastRespawn = MWBase::Environment::get().getWorld()->getTimeStamp(); + for (CellRefList::List::iterator it (mContainers.mList.begin()); it!=mContainers.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/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 91c536b43..ba6fad7ba 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -13,6 +13,8 @@ #include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld +#include "timestamp.hpp" + namespace ESM { struct CellState; @@ -48,6 +50,8 @@ namespace MWWorld std::vector mIds; float mWaterLevel; + MWWorld::TimeStamp mLastRespawn; + CellRefList mActivators; CellRefList mPotions; CellRefList mAppas; @@ -161,6 +165,9 @@ namespace MWWorld void readReferences (ESM::ESMReader& reader, const std::map& contentFileMap); + void respawn (); + ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. + template CellRefList& get() { throw std::runtime_error ("Storage for this type not exist in cells"); diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 057bc906e..f47b8ace3 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -341,6 +341,8 @@ namespace MWWorld virtual int getDoorState (const MWWorld::Ptr &ptr) const; /// This does not actually cause the door to move. Use World::activateDoor instead. virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const; + + virtual void respawn (const MWWorld::Ptr& ptr) const {} }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index ecc8bdd02..25ee0c2e8 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -165,6 +165,8 @@ namespace MWWorld } } + 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); diff --git a/components/esm/cellstate.cpp b/components/esm/cellstate.cpp index 541a359c6..4df04d0e5 100644 --- a/components/esm/cellstate.cpp +++ b/components/esm/cellstate.cpp @@ -11,6 +11,10 @@ void ESM::CellState::load (ESMReader &esm) mHasFogOfWar = false; esm.getHNOT (mHasFogOfWar, "HFOW"); + + mLastRespawn.mDay = 0; + mLastRespawn.mHour = 0; + esm.getHNOT (mLastRespawn, "RESP"); } void ESM::CellState::save (ESMWriter &esm) const @@ -18,5 +22,7 @@ void ESM::CellState::save (ESMWriter &esm) const if (!mId.mPaged) esm.writeHNT ("WLVL", mWaterLevel); - esm.writeHNT("HFOW", mHasFogOfWar); + esm.writeHNT ("HFOW", mHasFogOfWar); + + esm.writeHNT ("RESP", mLastRespawn); } diff --git a/components/esm/cellstate.hpp b/components/esm/cellstate.hpp index 88918a3ab..55c1e5155 100644 --- a/components/esm/cellstate.hpp +++ b/components/esm/cellstate.hpp @@ -3,6 +3,8 @@ #include "cellid.hpp" +#include "defs.hpp" + namespace ESM { class ESMReader; @@ -19,6 +21,8 @@ namespace ESM int mHasFogOfWar; // Do we have fog of war state (0 or 1)? (see fogstate.hpp) + ESM::TimeStamp mLastRespawn; + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/creaturelevliststate.cpp b/components/esm/creaturelevliststate.cpp index 164dae96e..21cc73b89 100644 --- a/components/esm/creaturelevliststate.cpp +++ b/components/esm/creaturelevliststate.cpp @@ -12,6 +12,9 @@ namespace ESM mSpawnActorId = -1; esm.getHNOT (mSpawnActorId, "SPAW"); + + mSpawn = false; + esm.getHNOT (mSpawn, "RESP"); } void CreatureLevListState::save(ESMWriter &esm, bool inInventory) const @@ -20,6 +23,9 @@ namespace ESM if (mSpawnActorId != -1) esm.writeHNT ("SPAW", mSpawnActorId); + + if (mSpawn) + esm.writeHNT ("RESP", mSpawn); } } diff --git a/components/esm/creaturelevliststate.hpp b/components/esm/creaturelevliststate.hpp index 99b5a7fa2..da64cd7c2 100644 --- a/components/esm/creaturelevliststate.hpp +++ b/components/esm/creaturelevliststate.hpp @@ -10,6 +10,7 @@ namespace ESM struct CreatureLevListState : public ObjectState { int mSpawnActorId; + bool mSpawn; virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const;