From e234dd2a36b1a66eb407b967b003f50b0cdce959 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jun 2018 17:18:51 +0400 Subject: [PATCH 01/15] Do not interrupt scripted animations --- apps/openmw/mwmechanics/character.cpp | 34 ++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 07e5fa7d6..71d02d59a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -561,6 +561,14 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) { + // If the current animation is persistent, do not touch it + if (!mAnimQueue.empty()) + { + AnimationQueueEntry& first = mAnimQueue.front(); + if (first.mPersist) + return; + } + if (mPtr.getClass().isActor()) refreshHitRecoilAnims(); @@ -2135,6 +2143,14 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int if(!mAnimation || !mAnimation->hasAnimation(groupname)) return false; + // We should not interrupt persistent animations by non-persistent ones + if (!mAnimQueue.empty()) + { + AnimationQueueEntry& first = mAnimQueue.front(); + if (first.mPersist && !persist) + return false; + } + // If this animation is a looped animation (has a "loop start" key) that is already playing // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count // and remove any other animations that were queued. @@ -2199,9 +2215,21 @@ bool CharacterController::isAnimPlaying(const std::string &groupName) void CharacterController::clearAnimQueue() { - if(!mAnimQueue.empty()) - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.clear(); + // Do not interrupt scripted animations + if (!mAnimQueue.empty()) + { + AnimationQueueEntry& first = mAnimQueue.front(); + if (!first.mPersist) + mAnimation->disable(mAnimQueue.front().mGroup); + } + + for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) + { + if (!it->mPersist) + it = mAnimQueue.erase(it); + else + ++it; + } } void CharacterController::forceStateUpdate() From 6099735c608e90b33a6bc2ab6ad74fd464b08099 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jun 2018 17:52:58 +0400 Subject: [PATCH 02/15] Early out only when scripted animation is playing --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 71d02d59a..795675fb3 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -565,7 +565,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (!mAnimQueue.empty()) { AnimationQueueEntry& first = mAnimQueue.front(); - if (first.mPersist) + if (first.mPersist && isAnimPlaying(first.mGroup)) return; } From d0619cfb3500a43bcf6019a0f2e7c206a360f447 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jun 2018 18:52:20 +0400 Subject: [PATCH 03/15] Play death animation for non-persisting actors with 0 health (bug #4291) --- apps/openmw/mwmechanics/character.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 795675fb3..daab8cdeb 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2023,7 +2023,11 @@ void CharacterController::update(float duration) // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) { - playDeath(1.f, mDeathState); + // Fast-forward death animation to end for persisting corpses + if (cls.isPersistent(mPtr)) + playDeath(1.f, mDeathState); + else + playDeath(0.f, mDeathState); } // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); @@ -2239,6 +2243,7 @@ void CharacterController::forceStateUpdate() clearAnimQueue(); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); + if(mDeathState != CharState_None) { playRandomDeath(); From a42c663fd70b0b44289fe7a00481d517204133d5 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jun 2018 18:53:25 +0400 Subject: [PATCH 04/15] Do not interrupt scripted animations by death animation (bug #4286) --- apps/openmw/mwmechanics/character.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index daab8cdeb..d0ffe742a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -728,6 +728,14 @@ void CharacterController::playRandomDeath(float startpoint) MWBase::Environment::get().getWorld()->useDeathCamera(); } + // Do not interrupt scripted animation by death + if (!mAnimQueue.empty()) + { + AnimationQueueEntry& first = mAnimQueue.front(); + if (first.mPersist && isAnimPlaying(first.mGroup)) + return; + } + if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) { mDeathState = CharState_SwimDeathKnockDown; From 977a27ecb76d9fe3da1194c6dc73b52cb5b82d45 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jun 2018 22:29:32 +0400 Subject: [PATCH 05/15] Do not clear corpses until end of death animation (bug #4307) --- apps/openmw/mwclass/creature.cpp | 5 ++++- apps/openmw/mwclass/npc.cpp | 5 ++++- apps/openmw/mwmechanics/character.cpp | 7 ++----- apps/openmw/mwworld/cellstore.cpp | 7 ++++++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2e16b13aa..27a20a0f5 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -136,7 +136,7 @@ namespace MWClass data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); if (data->mCreatureStats.isDead()) - data->mCreatureStats.setDeathAnimationFinished(true); + data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr)); // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); @@ -814,6 +814,9 @@ namespace MWClass if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) return; + if (!creatureStats.isDeathAnimationFinished()) + return; + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 8e8b5c3ad..172e4cc8f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -353,7 +353,7 @@ namespace MWClass data->mNpcStats.setNeedRecalcDynamicStats(true); } if (data->mNpcStats.isDead()) - data->mNpcStats.setDeathAnimationFinished(true); + data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr)); // race powers const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get().find(ref->mBase->mRace); @@ -1351,6 +1351,9 @@ namespace MWClass if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) return; + if (!creatureStats.isDeathAnimationFinished()) + return; + const MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat(); static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat(); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d0ffe742a..fd7abb571 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2029,13 +2029,10 @@ void CharacterController::update(float duration) { // initial start of death animation for actors that started the game as dead // not done in constructor since we need to give scripts a chance to set the mSkipAnim flag - if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty()) + if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty() && cls.isPersistent(mPtr)) { // Fast-forward death animation to end for persisting corpses - if (cls.isPersistent(mPtr)) - playDeath(1.f, mDeathState); - else - playDeath(0.f, mDeathState); + playDeath(1.f, mDeathState); } // We must always queue movement, even if there is none, to apply gravity. world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 1b6495c11..fc3c2e245 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -945,8 +945,13 @@ namespace MWWorld { const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get().find("fCorpseClearDelay")->getFloat(); - if (creatureStats.isDead() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + if (creatureStats.isDead() && + creatureStats.isDeathAnimationFinished() && + !ptr.getClass().isPersistent(ptr) && + creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp()) + { MWBase::Environment::get().getWorld()->deleteObject(ptr); + } } void CellStore::respawn() From 427be928d0501fd055706502e3746aa390908c9f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 11 Jun 2018 23:17:54 +0400 Subject: [PATCH 06/15] Do not update animation state for dead actors --- apps/openmw/mwmechanics/character.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index fd7abb571..eb89fe785 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -845,8 +845,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mIdleState = CharState_Idle; } - - if(mDeathState == CharState_None) + // Do not update animation status for dead actors + if(mDeathState == CharState_None && !cls.getCreatureStats(mPtr).isDead()) refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); From 0d3f535590f82b0f0b71657c8d9440b6e8dfb07d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 08:57:24 +0400 Subject: [PATCH 07/15] Warn about mod conflicts --- docs/source/reference/modding/settings/game.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 416f1bc1a..308a29546 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -77,6 +77,7 @@ This is how original Morrowind behaves. If this setting is false, player has to wait until end of death animation in all cases. This case is more safe, but makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. +Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation. This setting can only be configured by editing the settings configuration file. From ebaa6fb5a212817d3052b738998f5f4d19e6bb5b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 09:55:43 +0400 Subject: [PATCH 08/15] Play death scream only once --- apps/openmw/mwmechanics/character.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index eb89fe785..8439a4e4c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -728,14 +728,6 @@ void CharacterController::playRandomDeath(float startpoint) MWBase::Environment::get().getWorld()->useDeathCamera(); } - // Do not interrupt scripted animation by death - if (!mAnimQueue.empty()) - { - AnimationQueueEntry& first = mAnimQueue.front(); - if (first.mPersist && isAnimPlaying(first.mGroup)) - return; - } - if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) { mDeathState = CharState_SwimDeathKnockDown; @@ -760,6 +752,15 @@ void CharacterController::playRandomDeath(float startpoint) { mDeathState = chooseRandomDeathState(); } + + // Do not interrupt scripted animation by death + if (!mAnimQueue.empty()) + { + AnimationQueueEntry& first = mAnimQueue.front(); + if (first.mPersist && isAnimPlaying(first.mGroup)) + return; + } + playDeath(startpoint, mDeathState); } From b0a140e714272ac3d44bcb40198dc4c490aa120c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 10:00:38 +0400 Subject: [PATCH 09/15] Disable actor collision only after end of death animation --- apps/openmw/mwclass/actor.cpp | 2 +- apps/openmw/mwclass/creature.cpp | 1 + apps/openmw/mwclass/npc.cpp | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index 17af4725e..73a4d37d7 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -31,7 +31,7 @@ namespace MWClass if (!model.empty()) { physics.addActor(ptr, model); - if (getCreatureStats(ptr).isDead()) + if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished()) MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); } } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 27a20a0f5..a07a5c893 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -135,6 +135,7 @@ namespace MWClass data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee); data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm); + // Persistent actors with 0 health do not play death animation if (data->mCreatureStats.isDead()) data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr)); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 172e4cc8f..92e25baee 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -352,6 +352,8 @@ namespace MWClass data->mNpcStats.setNeedRecalcDynamicStats(true); } + + // Persistent actors with 0 health do not play death animation if (data->mNpcStats.isDead()) data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr)); From e3812f40753f0969557cf93361cb7ba4d27eaeed Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 11:27:18 +0400 Subject: [PATCH 10/15] Check creature stats only for actors --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 8439a4e4c..332cd225c 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -847,7 +847,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim } // Do not update animation status for dead actors - if(mDeathState == CharState_None && !cls.getCreatureStats(mPtr).isDead()) + if(mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead())) refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); From 0c926552502759ece98bdf14e76b7d01b6d7aaf7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 11:51:54 +0400 Subject: [PATCH 11/15] Avoid code duplication in character manager --- apps/openmw/mwmechanics/character.cpp | 44 ++++++++++++--------------- apps/openmw/mwmechanics/character.hpp | 2 ++ 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 332cd225c..6eaaa3def 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -562,12 +562,8 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) { // If the current animation is persistent, do not touch it - if (!mAnimQueue.empty()) - { - AnimationQueueEntry& first = mAnimQueue.front(); - if (first.mPersist && isAnimPlaying(first.mGroup)) - return; - } + if (isPersistentAnimPlaying()) + return; if (mPtr.getClass().isActor()) refreshHitRecoilAnims(); @@ -754,12 +750,8 @@ void CharacterController::playRandomDeath(float startpoint) } // Do not interrupt scripted animation by death - if (!mAnimQueue.empty()) - { - AnimationQueueEntry& first = mAnimQueue.front(); - if (first.mPersist && isAnimPlaying(first.mGroup)) - return; - } + if (isPersistentAnimPlaying()) + return; playDeath(startpoint, mDeathState); } @@ -2154,12 +2146,8 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int return false; // We should not interrupt persistent animations by non-persistent ones - if (!mAnimQueue.empty()) - { - AnimationQueueEntry& first = mAnimQueue.front(); - if (first.mPersist && !persist) - return false; - } + if (isPersistentAnimPlaying() && !persist) + return false; // If this animation is a looped animation (has a "loop start" key) that is already playing // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count @@ -2215,6 +2203,17 @@ void CharacterController::skipAnim() mSkipAnim = true; } +bool CharacterController::isPersistentAnimPlaying() +{ + if (!mAnimQueue.empty()) + { + AnimationQueueEntry& first = mAnimQueue.front(); + return first.mPersist && isAnimPlaying(first.mGroup); + } + + return false; +} + bool CharacterController::isAnimPlaying(const std::string &groupName) { if(mAnimation == NULL) @@ -2222,16 +2221,11 @@ bool CharacterController::isAnimPlaying(const std::string &groupName) return mAnimation->isPlaying(groupName); } - void CharacterController::clearAnimQueue() { // Do not interrupt scripted animations - if (!mAnimQueue.empty()) - { - AnimationQueueEntry& first = mAnimQueue.front(); - if (!first.mPersist) - mAnimation->disable(mAnimQueue.front().mGroup); - } + if (!isPersistentAnimPlaying() && !mAnimQueue.empty()) + mAnimation->disable(mAnimQueue.front().mGroup); for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) { diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index a172620b9..74910b3c3 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -221,6 +221,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool updateCreatureState(); void updateIdleStormState(bool inwater); + bool isPersistentAnimPlaying(); + void updateAnimQueue(); void updateHeadTracking(float duration); From 0e441d48ac78f11993df4fc630a258131a8eddc2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 12:55:28 +0400 Subject: [PATCH 12/15] Give scripted animations highest priority (bug #4286) --- apps/openmw/mwmechanics/character.cpp | 6 +++++- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6eaaa3def..9737ec355 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1308,6 +1308,10 @@ bool CharacterController::updateWeaponState() } } + // Combat for actors with persistent animations obviously will be buggy + if (isPersistentAnimPlaying()) + return forcestateupdate; + float complete; bool animPlaying; if(mAttackingOrSpell) @@ -2186,7 +2190,7 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int mIdleState = CharState_SpecialIdle; bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); - mAnimation->play(groupname, Priority_Default, + mAnimation->play(groupname, persist ? Priority_Persistent : Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 74910b3c3..791732c79 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -39,8 +39,8 @@ enum Priority { Priority_Knockdown, Priority_Torch, Priority_Storm, - Priority_Death, + Priority_Persistent, Num_Priorities }; From 25bb7c18266093f67bbadba73152f5a9a856a5a3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 14:04:03 +0400 Subject: [PATCH 13/15] Make 'PlayGroup idle' to cancel scripted animations --- apps/openmw/mwmechanics/character.cpp | 21 +++++++++++++-------- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9737ec355..5443bee81 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2182,23 +2182,28 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { - clearAnimQueue(); - mAnimQueue.push_back(entry); + clearAnimQueue(persist); mAnimation->disable(mCurrentIdle); mCurrentIdle.clear(); mIdleState = CharState_SpecialIdle; bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); - mAnimation->play(groupname, persist ? Priority_Persistent : Priority_Default, + mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback); } else { mAnimQueue.resize(1); - mAnimQueue.push_back(entry); } + + // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing + if (groupname == "idle") + entry.mPersist = false; + + mAnimQueue.push_back(entry); + return true; } @@ -2225,15 +2230,15 @@ bool CharacterController::isAnimPlaying(const std::string &groupName) return mAnimation->isPlaying(groupName); } -void CharacterController::clearAnimQueue() +void CharacterController::clearAnimQueue(bool clearPersistAnims) { - // Do not interrupt scripted animations - if (!isPersistentAnimPlaying() && !mAnimQueue.empty()) + // Do not interrupt scripted animations, if we want to keep them + if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) { - if (!it->mPersist) + if (clearPersistAnims || !it->mPersist) it = mAnimQueue.erase(it); else ++it; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 791732c79..381cf71a5 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -215,7 +215,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false); void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); - void clearAnimQueue(); + void clearAnimQueue(bool clearPersistAnims = false); bool updateWeaponState(); bool updateCreatureState(); From f299be8158b22dbe5d272b45707b9511c8f8ba41 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 16:07:36 +0400 Subject: [PATCH 14/15] Play scripted animations even if SkipAnim is used --- apps/openmw/mwmechanics/character.cpp | 3 ++- apps/openmw/mwrender/animation.cpp | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 5443bee81..fbdb19d5b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2035,7 +2035,8 @@ void CharacterController::update(float duration) world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f)); } - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + bool isPersist = isPersistentAnimPlaying(); + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); if(duration > 0.0f) moved /= duration; else diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3ccc06665..d96b9f809 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1089,11 +1089,28 @@ namespace MWRender osg::Vec3f Animation::runAnimation(float duration) { + // If we have scripted animations, play only them + bool hasScriptedAnims = false; + for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) + { + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) && stateiter->second.mPlaying) + { + hasScriptedAnims = true; + break; + } + } + osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while(stateiter != mStates.end()) { AnimState &state = stateiter->second; + if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) + { + ++stateiter; + continue; + } + const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys(); NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime())); From bce6d79ad37bb309918f63d48f1f520329fb540a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 12 Jun 2018 16:19:28 +0400 Subject: [PATCH 15/15] Add changelog entries, related to animations --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd1acff2a..6ca79a664 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,17 @@ Bug #2835: Player able to slowly move when overencumbered Bug #3374: Touch spells not hitting kwama foragers + Bug #3486: [Mod] NPC Commands does not work Bug #3591: Angled hit distance too low Bug #3897: Have Goodbye give all choices the effects of Goodbye Bug #3997: Almalexia doesn't pace Bug #4036: Weird behaviour of AI packages if package target has non-unique ID Bug #4221: Characters get stuck in V-shaped terrain Bug #4251: Stationary NPCs do not return to their position after combat + Bug #4286: Scripted animations can be interrupted + Bug #4291: Non-persistent actors that started the game as dead do not play death animations Bug #4293: Faction members are not aware of faction ownerships in barter + Bug #4307: World cleanup should remove dead bodies only if death animation is finished Bug #4327: Missing animations during spell/weapon stance switching Bug #4419: MRK NiStringExtraData is handled incorrectly Bug #4426: RotateWorld behavior is incorrect