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 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.

@ -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<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())
{

@ -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<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()

@ -1292,7 +1292,18 @@ namespace MWClass
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())
{

@ -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::SummonKey, int>& CreatureStats::getSummonedCreatureMap()
{
return mSummonedCreatures;

@ -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<int, std::string> SummonKey; // <ESM::MagicEffect index, spell ID>
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.

@ -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)

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

@ -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<CellStoreCollection::iterator, bool> 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);

@ -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

@ -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()

@ -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.

@ -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);

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

Loading…
Cancel
Save