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

This commit is contained in:
scrawl 2016-06-12 00:04:50 +02:00
parent 37afe966cf
commit a825882c6b
10 changed files with 100 additions and 49 deletions

View file

@ -134,6 +134,9 @@ namespace MWClass
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
if (data->mCreatureStats.isDead())
data->mCreatureStats.setDeathAnimationFinished(true);
// spells // spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin()); for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
iter!=ref->mBase->mSpells.mList.end(); ++iter) iter!=ref->mBase->mSpells.mList.end(); ++iter)

View file

@ -351,6 +351,8 @@ namespace MWClass
data->mNpcStats.setNeedRecalcDynamicStats(true); data->mNpcStats.setNeedRecalcDynamicStats(true);
} }
if (data->mNpcStats.isDead())
data->mNpcStats.setDeathAnimationFinished(true);
// race powers // race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace); const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);

View file

@ -20,6 +20,7 @@
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spellcasting.hpp"
@ -1208,25 +1209,14 @@ namespace MWMechanics
continue; 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 // 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"); 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 // Apply soultrap
if (iter->first.getTypeName() == typeid(ESM::Creature).name()) if (iter->first.getTypeName() == typeid(ESM::Creature).name())
{ {
@ -1245,6 +1235,29 @@ namespace MWMechanics
if (cls.isEssential(iter->first)) if (cls.isEssential(iter->first))
MWBase::Environment::get().getWindowManager()->messageBox("#{sKilledEssential}"); 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");
}
} }
} }

View file

@ -40,7 +40,6 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/statemanager.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
@ -719,15 +718,20 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
mIdleState = CharState_Idle; mIdleState = CharState_Idle;
else else
{ {
// Set the death state, but don't play it yet const MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
// We will play it in the first frame, but only if no script set the skipAnim flag if (cStats.isDeathAnimationFinished())
signed char deathanim = mPtr.getClass().getCreatureStats(mPtr).getDeathAnimation(); {
if (deathanim == -1) // Set the death state, but don't play it yet
mDeathState = chooseRandomDeathState(); // We will play it in the first frame, but only if no script set the skipAnim flag
else signed char deathanim = cStats.getDeathAnimation();
mDeathState = static_cast<CharacterState>(CharState_Death1 + deathanim); 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 else
@ -2000,30 +2004,28 @@ void CharacterController::forceStateUpdate()
mAnimation->runAnimation(0.f); mAnimation->runAnimation(0.f);
} }
bool CharacterController::kill() CharacterController::KillResult CharacterController::kill()
{ {
if( isDead() ) if (mDeathState == CharState_None)
{ {
if( mPtr == getPlayer() && !isAnimPlaying(mCurrentDeath) ) playRandomDeath();
{
//player's death animation is over mAnimation->disable(mCurrentIdle);
MWBase::Environment::get().getStateManager()->askLoadRecent();
} mIdleState = CharState_None;
return false; mCurrentIdle.clear();
return Result_DeathAnimStarted;
} }
playRandomDeath(); MWMechanics::CreatureStats& cStats = mPtr.getClass().getCreatureStats(mPtr);
if (isAnimPlaying(mCurrentDeath))
mAnimation->disable(mCurrentIdle); return Result_DeathAnimPlaying;
if (!cStats.isDeathAnimationFinished())
mIdleState = CharState_None; {
mCurrentIdle.clear(); cStats.setDeathAnimationFinished(true);
return Result_DeathAnimJustFinished;
// Play Death Music if it was the player dying }
if(mPtr == getPlayer()) return Result_DeathAnimFinished;
MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3");
return true;
} }
void CharacterController::resurrect() void CharacterController::resurrect()

View file

@ -234,8 +234,14 @@ public:
void skipAnim(); void skipAnim();
bool isAnimPlaying(const std::string &groupName); bool isAnimPlaying(const std::string &groupName);
/// @return false if the character has already been killed before enum KillResult
bool kill(); {
Result_DeathAnimStarted,
Result_DeathAnimPlaying,
Result_DeathAnimJustFinished,
Result_DeathAnimFinished
};
KillResult kill();
void resurrect(); void resurrect();
bool isDead() const bool isDead() const

View file

@ -17,7 +17,7 @@ namespace MWMechanics
int CreatureStats::sActorId = 0; int CreatureStats::sActorId = 0;
CreatureStats::CreatureStats() 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), mTalkedTo (false), mAlarmed (false), mAttacked (false),
mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false), mKnockdown(false), mKnockdownOneFrame(false), mKnockdownOverOneFrame(false),
mHitRecovery(false), mBlock(false), mMovementFlags(0), mHitRecovery(false), mBlock(false), mMovementFlags(0),
@ -236,6 +236,16 @@ namespace MWMechanics
return mDead; return mDead;
} }
bool CreatureStats::isDeathAnimationFinished() const
{
return mDeathAnimationFinished;
}
void CreatureStats::setDeathAnimationFinished(bool finished)
{
mDeathAnimationFinished = finished;
}
void CreatureStats::notifyDied() void CreatureStats::notifyDied()
{ {
mDied = true; mDied = true;
@ -275,6 +285,7 @@ namespace MWMechanics
mDynamic[0].setCurrent(mDynamic[0].getModified()); mDynamic[0].setCurrent(mDynamic[0].getModified());
mDead = false; mDead = false;
mDeathAnimationFinished = false;
} }
} }
@ -482,6 +493,7 @@ namespace MWMechanics
state.mGoldPool = mGoldPool; state.mGoldPool = mGoldPool;
state.mDead = mDead; state.mDead = mDead;
state.mDeathAnimationFinished = mDeathAnimationFinished;
state.mDied = mDied; state.mDied = mDied;
state.mMurdered = mMurdered; state.mMurdered = mMurdered;
// The vanilla engine does not store friendly hits in the save file. Since there's no other mechanism // 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; mGoldPool = state.mGoldPool;
mDead = state.mDead; mDead = state.mDead;
mDeathAnimationFinished = state.mDeathAnimationFinished;
mDied = state.mDied; mDied = state.mDied;
mMurdered = state.mMurdered; mMurdered = state.mMurdered;
mTalkedTo = state.mTalkedTo; mTalkedTo = state.mTalkedTo;

View file

@ -34,7 +34,8 @@ namespace MWMechanics
Stat<int> mAiSettings[4]; Stat<int> mAiSettings[4];
AiSequence mAiSequence; AiSequence mAiSequence;
bool mDead; bool mDead;
bool mDied; bool mDeathAnimationFinished;
bool mDied; // flag for OnDeath script function
bool mMurdered; bool mMurdered;
int mFriendlyHits; int mFriendlyHits;
bool mTalkedTo; bool mTalkedTo;
@ -161,6 +162,9 @@ namespace MWMechanics
bool isDead() const; bool isDead() const;
bool isDeathAnimationFinished() const;
void setDeathAnimationFinished(bool finished);
void notifyDied(); void notifyDied();
bool hasDied() const; bool hasDied() const;

View file

@ -20,6 +20,9 @@ void ESM::CreatureStats::load (ESMReader &esm)
mDead = false; mDead = false;
esm.getHNOT (mDead, "DEAD"); esm.getHNOT (mDead, "DEAD");
mDeathAnimationFinished = false;
esm.getHNOT (mDeathAnimationFinished, "DFNT");
mDied = false; mDied = false;
esm.getHNOT (mDied, "DIED"); esm.getHNOT (mDied, "DIED");
@ -140,6 +143,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
if (mDead) if (mDead)
esm.writeHNT ("DEAD", mDead); esm.writeHNT ("DEAD", mDead);
if (mDeathAnimationFinished)
esm.writeHNT ("DFNT", mDeathAnimationFinished);
if (mDied) if (mDied)
esm.writeHNT ("DIED", mDied); esm.writeHNT ("DIED", mDied);
@ -233,6 +239,7 @@ void ESM::CreatureStats::blank()
mActorId = -1; mActorId = -1;
mHasAiSettings = false; mHasAiSettings = false;
mDead = false; mDead = false;
mDeathAnimationFinished = false;
mDied = false; mDied = false;
mMurdered = false; mMurdered = false;
mTalkedTo = false; mTalkedTo = false;

View file

@ -40,6 +40,7 @@ namespace ESM
int mActorId; int mActorId;
bool mDead; bool mDead;
bool mDeathAnimationFinished;
bool mDied; bool mDied;
bool mMurdered; bool mMurdered;
bool mTalkedTo; bool mTalkedTo;

View file

@ -5,7 +5,7 @@
#include "defs.hpp" #include "defs.hpp"
unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE;
int ESM::SavedGame::sCurrentFormat = 2; int ESM::SavedGame::sCurrentFormat = 3;
void ESM::SavedGame::load (ESMReader &esm) void ESM::SavedGame::load (ESMReader &esm)
{ {