Commit a825882c authored by scrawl's avatar scrawl

Process death events at the end of the death animation (Fixes #1873)

parent 37afe966
......@@ -134,6 +134,9 @@ namespace MWClass
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
if (data->mCreatureStats.isDead())
data->mCreatureStats.setDeathAnimationFinished(true);
// spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
iter!=ref->mBase->mSpells.mList.end(); ++iter)
......
......@@ -351,6 +351,8 @@ namespace MWClass
data->mNpcStats.setNeedRecalcDynamicStats(true);
}
if (data->mNpcStats.isDead())
data->mNpcStats.setDeathAnimationFinished(true);
// race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
......
......@@ -20,6 +20,7 @@
#include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwmechanics/spellcasting.hpp"
......@@ -1208,25 +1209,14 @@ namespace MWMechanics
continue;
}
if (iter->second->getCharacterController()->kill())
CharacterController::KillResult killResult = iter->second->getCharacterController()->kill();
if (killResult == CharacterController::Result_DeathAnimStarted)
{
// TODO: It's not known whether the soundgen tags scream, roar, and moan are reliable
// for NPCs since some of the npc death animation files are missing them.
// Play dying words
// Note: It's not known whether the soundgen tags scream, roar, and moan are reliable
// for NPCs since some of the npc death animation files are missing them.
MWBase::Environment::get().getDialogueManager()->say(iter->first, "hit");
iter->first.getClass().getCreatureStats(iter->first).notifyDied();
++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())];
// Make sure spell effects are removed
for (PtrActorMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2)
{
MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells();
spells.purge(stats.getActorId());
}
// Apply soultrap
if (iter->first.getTypeName() == typeid(ESM::Creature).name())
{
......@@ -1245,6 +1235,29 @@ namespace MWMechanics
if (cls.isEssential(iter->first))
MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}");
}
else if (killResult == CharacterController::Result_DeathAnimJustFinished)
{
iter->first.getClass().getCreatureStats(iter->first).notifyDied();
++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())];
// Make sure spell effects are removed
for (PtrActorMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2)
{
MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells();
spells.purge(stats.getActorId());
}
if( iter->first == getPlayer())
{
//player's death animation is over
MWBase::Environment::get().getStateManager()->askLoadRecent();
}
// Play Death Music if it was the player dying
if(iter->first == getPlayer())
MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3");
}
}
}
......
......@@ -40,7 +40,6 @@
#include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
......@@ -719,15 +718,20 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
mIdleState = CharState_Idle;
else
{
// Set the death state, but don't play it yet
// We will play it in the first frame, but only if no script set the skipAnim flag
signed char deathanim = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation();
if (deathanim == -1)
mDeathState = chooseRandomDeathState();
else
mDeathState = static_cast<CharacterState>(CharState_Death1 + deathanim);
const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
if (cStats.isDeathAnimationFinished())
{
// Set the death state, but don't play it yet
// We will play it in the first frame, but only if no script set the skipAnim flag
signed char deathanim = cStats.getDeathAnimation();
if (deathanim == -1)
mDeathState = chooseRandomDeathState();
else
mDeathState = static_cast<CharacterState>(CharState_Death1 + deathanim);
mFloatToSurface = false;
mFloatToSurface = false;
}
// else: nothing to do, will detect death in the next frame and start playing death animation
}
}
else
......@@ -2000,30 +2004,28 @@ void CharacterController::forceStateUpdate()
mAnimation->runAnimation(0.f);
}
bool CharacterController::kill()
CharacterController::KillResult CharacterController::kill()
{
if( isDead() )
if (mDeathState == CharState_None)
{
if( mPtr == getPlayer() && !isAnimPlaying(mCurrentDeath) )
{
//player's death animation is over
MWBase::Environment::get().getStateManager()->askLoadRecent();
}
return false;
}
playRandomDeath();
mAnimation->disable(mCurrentIdle);
playRandomDeath();
mIdleState = CharState_None;
mCurrentIdle.clear();
mAnimation->disable(mCurrentIdle);
// Play Death Music if it was the player dying
if(mPtr == getPlayer())
MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3");
mIdleState = CharState_None;
mCurrentIdle.clear();
return Result_DeathAnimStarted;
}
return true;
MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
if (isAnimPlaying(mCurrentDeath))
return Result_DeathAnimPlaying;
if (!cStats.isDeathAnimationFinished())
{
cStats.setDeathAnimationFinished(true);
return Result_DeathAnimJustFinished;
}
return Result_DeathAnimFinished;
}
void CharacterController::resurrect()
......
......@@ -234,8 +234,14 @@ public:
void skipAnim();
bool isAnimPlaying(const std::string &groupName);
/// @return false if the character has already been killed before
bool kill();
enum KillResult
{
Result_DeathAnimStarted,
Result_DeathAnimPlaying,
Result_DeathAnimJustFinished,
Result_DeathAnimFinished
};
KillResult kill();
void resurrect();
bool isDead() const
......
......@@ -17,7 +17,7 @@ namespace MWMechanics
int CreatureStats::sActorId = 0;
CreatureStats::CreatureStats()
: mDrawState (DrawState_Nothing), mDead (false), mDied (false), mMurdered(false), mFriendlyHits (0),
: mDrawState (DrawState_Nothing), mDead (false), mDeathAnimationFinished(false), mDied (false), mMurdered(false), mFriendlyHits (0),
mTalkedTo (false), mAlarmed (false), mAttacked (false),
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
mHitRecovery(false), mBlock(false), mMovementFlags(0),
......@@ -236,6 +236,16 @@ namespace MWMechanics
return mDead;
}
bool CreatureStats::isDeathAnimationFinished() const
{
return mDeathAnimationFinished;
}
void CreatureStats::setDeathAnimationFinished(bool finished)
{
mDeathAnimationFinished = finished;
}
void CreatureStats::notifyDied()
{
mDied = true;
......@@ -275,6 +285,7 @@ namespace MWMechanics
mDynamic[0].setCurrent(mDynamic[0].getModified());
mDead = false;
mDeathAnimationFinished = false;
}
}
......@@ -482,6 +493,7 @@ namespace MWMechanics
state.mGoldPool = mGoldPool;
state.mDead = mDead;
state.mDeathAnimationFinished = mDeathAnimationFinished;
state.mDied = mDied;
state.mMurdered = mMurdered;
// The vanilla engine does not store friendly hits in the save file. Since there's no other mechanism
......@@ -533,6 +545,7 @@ namespace MWMechanics
mGoldPool = state.mGoldPool;
mDead = state.mDead;
mDeathAnimationFinished = state.mDeathAnimationFinished;
mDied = state.mDied;
mMurdered = state.mMurdered;
mTalkedTo = state.mTalkedTo;
......
......@@ -34,7 +34,8 @@ namespace MWMechanics
Stat<int> mAiSettings[4];
AiSequence mAiSequence;
bool mDead;
bool mDied;
bool mDeathAnimationFinished;
bool mDied; // flag for OnDeath script function
bool mMurdered;
int mFriendlyHits;
bool mTalkedTo;
......@@ -161,6 +162,9 @@ namespace MWMechanics
bool isDead() const;
bool isDeathAnimationFinished() const;
void setDeathAnimationFinished(bool finished);
void notifyDied();
bool hasDied() const;
......
......@@ -20,6 +20,9 @@ void ESM::CreatureStats::load (ESMReader &esm)
mDead = false;
esm.getHNOT (mDead, "DEAD");
mDeathAnimationFinished = false;
esm.getHNOT (mDeathAnimationFinished, "DFNT");
mDied = false;
esm.getHNOT (mDied, "DIED");
......@@ -140,6 +143,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
if (mDead)
esm.writeHNT ("DEAD", mDead);
if (mDeathAnimationFinished)
esm.writeHNT ("DFNT", mDeathAnimationFinished);
if (mDied)
esm.writeHNT ("DIED", mDied);
......@@ -233,6 +239,7 @@ void ESM::CreatureStats::blank()
mActorId = -1;
mHasAiSettings = false;
mDead = false;
mDeathAnimationFinished = false;
mDied = false;
mMurdered = false;
mTalkedTo = false;
......
......@@ -40,6 +40,7 @@ namespace ESM
int mActorId;
bool mDead;
bool mDeathAnimationFinished;
bool mDied;
bool mMurdered;
bool mTalkedTo;
......
......@@ -5,7 +5,7 @@
#include "defs.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 2;
int ESM::SavedGame::sCurrentFormat = 3;
void ESM::SavedGame::load (ESMReader &esm)
{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment