Vanilla-compatible creature/NPC respawning (Fixes #2369, Fixes #2467)

move
scrawl 9 years ago
parent 290da132b1
commit c3ef387208

@ -231,15 +231,16 @@ namespace MWBase
virtual float getTimeScaleFactor() const = 0; virtual float getTimeScaleFactor() const = 0;
virtual void changeToInteriorCell (const std::string& cellName, virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent=true) = 0;
const ESM::Position& position) = 0;
///< Move to interior cell. ///< 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. ///< 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; virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent=true) = 0;
///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes ///< @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; 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. ///< Return a cell matching the given name or a 0-pointer, if there is no such cell.

@ -759,7 +759,18 @@ namespace MWClass
void Creature::respawn(const MWWorld::Ptr &ptr) const 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<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
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()) if (ptr.getCellRef().hasContentFile())
{ {

@ -44,7 +44,28 @@ namespace MWClass
ensureCustomData(ptr); ensureCustomData(ptr);
CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData(); 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<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
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() void CreatureLevList::registerSelf()

@ -1292,7 +1292,18 @@ namespace MWClass
void Npc::respawn(const MWWorld::Ptr &ptr) const void Npc::respawn(const MWWorld::Ptr &ptr) const
{ {
if (ptr.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Respawn) const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return;
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
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<ESM::NPC>()->mBase->mFlags & ESM::NPC::Respawn
&& creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp())
{ {
if (ptr.getCellRef().hasContentFile()) if (ptr.getCellRef().hasContentFile())
{ {

@ -187,6 +187,9 @@ namespace MWMechanics
if (index==0 && mDynamic[index].getCurrent()<1) if (index==0 && mDynamic[index].getCurrent()<1)
{ {
if (!mDead)
mTimeOfDeath = MWBase::Environment::get().getWorld()->getTimeStamp();
mDead = true; mDead = true;
mDynamic[index].setModifier(0); mDynamic[index].setModifier(0);
@ -503,6 +506,7 @@ namespace MWMechanics
state.mLevel = mLevel; state.mLevel = mLevel;
state.mActorId = mActorId; state.mActorId = mActorId;
state.mDeathAnimation = mDeathAnimation; state.mDeathAnimation = mDeathAnimation;
state.mTimeOfDeath = mTimeOfDeath.toEsm();
mSpells.writeState(state.mSpells); mSpells.writeState(state.mSpells);
mActiveSpells.writeState(state.mActiveSpells); mActiveSpells.writeState(state.mActiveSpells);
@ -549,6 +553,7 @@ namespace MWMechanics
mLevel = state.mLevel; mLevel = state.mLevel;
mActorId = state.mActorId; mActorId = state.mActorId;
mDeathAnimation = state.mDeathAnimation; mDeathAnimation = state.mDeathAnimation;
mTimeOfDeath = MWWorld::TimeStamp(state.mTimeOfDeath);
mSpells.readState(state.mSpells); mSpells.readState(state.mSpells);
mActiveSpells.readState(state.mActiveSpells); mActiveSpells.readState(state.mActiveSpells);
@ -622,6 +627,11 @@ namespace MWMechanics
mDeathAnimation = index; mDeathAnimation = index;
} }
MWWorld::TimeStamp CreatureStats::getTimeOfDeath() const
{
return mTimeOfDeath;
}
std::map<CreatureStats::SummonKey, int>& CreatureStats::getSummonedCreatureMap() std::map<CreatureStats::SummonKey, int>& CreatureStats::getSummonedCreatureMap()
{ {
return mSummonedCreatures; return mSummonedCreatures;

@ -65,6 +65,8 @@ namespace MWMechanics
// The index of the death animation that was played // The index of the death animation that was played
unsigned char mDeathAnimation; unsigned char mDeathAnimation;
MWWorld::TimeStamp mTimeOfDeath;
public: public:
typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID> typedef std::pair<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
private: private:
@ -259,6 +261,8 @@ namespace MWMechanics
unsigned char getDeathAnimation() const; unsigned char getDeathAnimation() const;
void setDeathAnimation(unsigned char index); void setDeathAnimation(unsigned char index);
MWWorld::TimeStamp getTimeOfDeath() const;
int getActorId(); int getActorId();
///< Will generate an actor ID, if the actor does not have one yet. ///< Will generate an actor ID, if the actor does not have one yet.

@ -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. // but some mods may be using it as a reload detector.
MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); 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(); MWBase::Environment::get().getWorld()->markCellAsUnchanged();
} }
catch (const std::exception& e) catch (const std::exception& e)

@ -902,21 +902,22 @@ namespace MWWorld
Ptr ptr (&*it, this); Ptr ptr (&*it, this);
ptr.getClass().respawn(ptr); ptr.getClass().respawn(ptr);
} }
for (CellRefList<ESM::Creature>::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it) }
{
Ptr ptr (&*it, this); for (CellRefList<ESM::Creature>::List::iterator it (mCreatures.mList.begin()); it!=mCreatures.mList.end(); ++it)
ptr.getClass().respawn(ptr); {
} Ptr ptr (&*it, this);
for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it) ptr.getClass().respawn(ptr);
{ }
Ptr ptr (&*it, this); for (CellRefList<ESM::NPC>::List::iterator it (mNpcs.mList.begin()); it!=mNpcs.mList.end(); ++it)
ptr.getClass().respawn(ptr); {
} Ptr ptr (&*it, this);
for (CellRefList<ESM::CreatureLevList>::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it) ptr.getClass().respawn(ptr);
{ }
Ptr ptr (&*it, this); for (CellRefList<ESM::CreatureLevList>::List::iterator it (mCreatureLists.mList.begin()); it!=mCreatureLists.mList.end(); ++it)
ptr.getClass().respawn(ptr); {
} Ptr ptr (&*it, this);
ptr.getClass().respawn(ptr);
} }
} }
} }

@ -240,7 +240,7 @@ namespace MWWorld
mActiveCells.erase(*iter); mActiveCells.erase(*iter);
} }
void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener) void Scene::loadCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn)
{ {
std::pair<CellStoreCollection::iterator, bool> result = mActiveCells.insert(cell); std::pair<CellStoreCollection::iterator, bool> result = mActiveCells.insert(cell);
@ -270,12 +270,13 @@ namespace MWWorld
} }
} }
cell->respawn();
// register local scripts // register local scripts
// do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice // do this before insertCell, to make sure we don't add scripts from levelled creature spawning twice
MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell); MWBase::Environment::get().getWorld()->getLocalScripts().addCell (cell);
if (respawn)
cell->respawn();
// ... then references. This is important for adjustPosition to work correctly. // ... then references. This is important for adjustPosition to work correctly.
/// \todo rescale depending on the state of a new GMST /// \todo rescale depending on the state of a new GMST
insertCell (*cell, true, loadingListener); 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::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
Loading::ScopedLoad load(loadingListener); Loading::ScopedLoad load(loadingListener);
@ -403,7 +404,7 @@ namespace MWWorld
{ {
CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); 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); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(X,Y);
MWBase::Environment::get().getWindowManager()->changeCell(current); MWBase::Environment::get().getWindowManager()->changeCell(current);
mCellChanged = true; if (changeEvent)
mCellChanged = true;
mPreloader->updateCache(mRendering.getReferenceTime()); mPreloader->updateCache(mRendering.getReferenceTime());
} }
@ -484,7 +486,7 @@ namespace MWWorld
return mActiveCells; 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); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(cellName);
bool loadcell = (mCurrentCell == NULL); bool loadcell = (mCurrentCell == NULL);
@ -530,7 +532,7 @@ namespace MWWorld
loadingListener->setProgressRange(refsToLoad); loadingListener->setProgressRange(refsToLoad);
// Load cell. // Load cell.
loadCell (cell, loadingListener); loadCell (cell, loadingListener, changeEvent);
changePlayerCell(cell, position, true); changePlayerCell(cell, position, true);
@ -540,21 +542,24 @@ namespace MWWorld
// Sky system // Sky system
MWBase::Environment::get().getWorld()->adjustSky(); 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); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell);
mPreloader->updateCache(mRendering.getReferenceTime()); 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 x = 0;
int y = 0; int y = 0;
MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); 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); CellStore* current = MWBase::Environment::get().getWorld()->getExterior(x, y);
changePlayerCell(current, position, adjustPlayerPos); changePlayerCell(current, position, adjustPlayerPos);

@ -71,7 +71,7 @@ namespace MWWorld
void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener); 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 // 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); void getGridCenter(int& cellX, int& cellY);
@ -90,7 +90,7 @@ namespace MWWorld
void unloadCell (CellStoreCollection::iterator iter); 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); void playerMoved (const osg::Vec3f& pos);
@ -103,11 +103,13 @@ namespace MWWorld
bool hasCellChanged() const; bool hasCellChanged() const;
///< Has the set of active cells changed, since the last frame? ///< 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. ///< 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. ///< Move to exterior cell.
/// @param changeEvent Set cellChanged flag?
void changeToVoid(); void changeToVoid();
///< Change into a void ///< Change into a void

@ -962,11 +962,11 @@ namespace MWWorld
return mTimeScale->getFloat(); 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(); mPhysics->clearQueuedMovement();
if (mCurrentWorldSpace != cellName) if (changeEvent && mCurrentWorldSpace != cellName)
{ {
// changed worldspace // changed worldspace
mProjectileManager->clear(); mProjectileManager->clear();
@ -976,34 +976,34 @@ namespace MWWorld
} }
removeContainerScripts(getPlayerPtr()); removeContainerScripts(getPlayerPtr());
mWorldScene->changeToInteriorCell(cellName, position); mWorldScene->changeToInteriorCell(cellName, position, changeEvent);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
} }
void World::changeToExteriorCell (const ESM::Position& position) void World::changeToExteriorCell (const ESM::Position& position, bool changeEvent)
{ {
mPhysics->clearQueuedMovement(); mPhysics->clearQueuedMovement();
if (mCurrentWorldSpace != "sys::default") // FIXME if (changeEvent && mCurrentWorldSpace != "sys::default") // FIXME
{ {
// changed worldspace // changed worldspace
mProjectileManager->clear(); mProjectileManager->clear();
mRendering->notifyWorldSpaceChanged(); mRendering->notifyWorldSpaceChanged();
} }
removeContainerScripts(getPlayerPtr()); removeContainerScripts(getPlayerPtr());
mWorldScene->changeToExteriorCell(position, true); mWorldScene->changeToExteriorCell(position, true, changeEvent);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell()); 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; mCurrentWorldSpace = cellId.mWorldspace;
if (cellId.mPaged) if (cellId.mPaged)
changeToExteriorCell (position); changeToExteriorCell (position, changeEvent);
else else
changeToInteriorCell (cellId.mWorldspace, position); changeToInteriorCell (cellId.mWorldspace, position, changeEvent);
} }
void World::markCellAsUnchanged() void World::markCellAsUnchanged()

@ -324,15 +324,16 @@ namespace MWWorld
virtual float getTimeScaleFactor() const; virtual float getTimeScaleFactor() const;
virtual void changeToInteriorCell (const std::string& cellName, virtual void changeToInteriorCell (const std::string& cellName, const ESM::Position& position, bool changeEvent = true);
const ESM::Position& position);
///< Move to interior cell. ///< 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. ///< 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); virtual void changeToCell (const ESM::CellId& cellId, const ESM::Position& position, bool changeEvent=true);
///< @param detectWorldSpaceChange if true, clean up worldspace-specific data when the world space changes ///< @param changeEvent If false, do not trigger cell change flag or detect worldspace changes
virtual const ESM::Cell *getExterior (const std::string& cellName) const; 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. ///< Return a cell matching the given name or a 0-pointer, if there is no such cell.

@ -87,6 +87,8 @@ void ESM::CreatureStats::load (ESMReader &esm)
mDeathAnimation = 0; mDeathAnimation = 0;
esm.getHNOT (mDeathAnimation, "DANM"); esm.getHNOT (mDeathAnimation, "DANM");
esm.getHNOT (mTimeOfDeath, "DTIM");
mSpells.load(esm); mSpells.load(esm);
mActiveSpells.load(esm); mActiveSpells.load(esm);
mAiSequence.load(esm); mAiSequence.load(esm);
@ -193,6 +195,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
if (mDeathAnimation) if (mDeathAnimation)
esm.writeHNT ("DANM", mDeathAnimation); esm.writeHNT ("DANM", mDeathAnimation);
if (mTimeOfDeath.mHour != 0 && mTimeOfDeath.mDay != 0)
esm.writeHNT ("DTIM", mTimeOfDeath);
mSpells.save(esm); mSpells.save(esm);
mActiveSpells.save(esm); mActiveSpells.save(esm);
mAiSequence.save(esm); mAiSequence.save(esm);

@ -57,6 +57,7 @@ namespace ESM
bool mRecalcDynamicStats; bool mRecalcDynamicStats;
int mDrawState; int mDrawState;
unsigned char mDeathAnimation; unsigned char mDeathAnimation;
ESM::TimeStamp mTimeOfDeath;
int mLevel; int mLevel;

Loading…
Cancel
Save