From a825882c6b552a606d6f2b34d07f97af51e680d8 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sun, 12 Jun 2016 00:04:50 +0200 Subject: [PATCH] Process death events at the end of the death animation (Fixes #1873) --- apps/openmw/mwclass/creature.cpp | 3 ++ apps/openmw/mwclass/npc.cpp | 2 + apps/openmw/mwmechanics/actors.cpp | 43 ++++++++++------ apps/openmw/mwmechanics/character.cpp | 60 ++++++++++++----------- apps/openmw/mwmechanics/character.hpp | 10 +++- apps/openmw/mwmechanics/creaturestats.cpp | 15 +++++- apps/openmw/mwmechanics/creaturestats.hpp | 6 ++- components/esm/creaturestats.cpp | 7 +++ components/esm/creaturestats.hpp | 1 + components/esm/savedgame.cpp | 2 +- 10 files changed, 100 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index c111fce36..51c83eee1 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -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::const_iterator iter (ref->mBase->mSpells.mList.begin()); iter!=ref->mBase->mSpells.mList.end(); ++iter) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index ee0112ac9..52cd562d1 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -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().find(ref->mBase->mRace); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 92f5fc5fd..ce3297d61 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -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"); + } } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 708065024..eeeea37ee 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -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(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(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); + + mIdleState = CharState_None; + mCurrentIdle.clear(); + return Result_DeathAnimStarted; } - playRandomDeath(); - - mAnimation->disable(mCurrentIdle); - - mIdleState = CharState_None; - mCurrentIdle.clear(); - - // Play Death Music if it was the player dying - if(mPtr == getPlayer()) - MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); - - 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() diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 757e2a19f..9bea4b78c 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -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 diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 7d1dc4d2f..e33a6f7bd 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -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; diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 8965dedac..171857603 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -34,7 +34,8 @@ namespace MWMechanics Stat 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; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index a48ce5472..4c795a532 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -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; diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index a99cca944..55af9d509 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -40,6 +40,7 @@ namespace ESM int mActorId; bool mDead; + bool mDeathAnimationFinished; bool mDied; bool mMurdered; bool mTalkedTo; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index cf9f68c9a..3220f496e 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -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) {