From cdb325f19ac55f66109769c6fa64aae8567f3b34 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:00:26 +0200 Subject: [PATCH 01/13] Rewrite CharacterController::updateAnimQueue() to properly maintain the animation queue and clean up finished animations. --- apps/openmw/mwmechanics/character.cpp | 30 +++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 48abac8b06..42611e1f9f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1852,17 +1852,39 @@ namespace MWMechanics void CharacterController::updateAnimQueue() { - if (mAnimQueue.size() > 1) - { + if (mAnimQueue.empty()) + return; + if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { + if (mAnimQueue.size() > 1) { mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } + else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) + { + // The last animation stopped playing when it shouldn't have. + // This is caused by a rebuild of the animation object, so we should restart the animation here + // Subtract 1 from mLoopCount to consider the current loop done. + bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount - 1, loopfallback); + } + else + { + // Animation is done, remove it from the queue. + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.pop_front(); + } + } + else + { + mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); } if (!mAnimQueue.empty()) From 8792e76f5d06b6c19c498e58ee17a0a40228f581 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:35:00 +0200 Subject: [PATCH 02/13] explicitly prevent movement whenever playing scripted animations --- 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 42611e1f9f..c83e72cef8 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2366,7 +2366,7 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled()) + if (!isMovementAnimationControlled() && mAnimQueue.empty()) world->queueMovement(mPtr, vec); } @@ -2435,7 +2435,7 @@ namespace MWMechanics } // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor()) + if (isMovementAnimationControlled() && mPtr.getClass().isActor() && mAnimQueue.empty()) world->queueMovement(mPtr, moved); mSkipAnim = false; From 24890a729bfa736d806154f40f62d859ba06e72f Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:59:05 +0200 Subject: [PATCH 03/13] clang'd --- apps/openmw/mwmechanics/character.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c83e72cef8..9c39b5b60d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1855,16 +1855,16 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { + if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { if (mAnimQueue.size() > 1) { mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) { @@ -1881,7 +1881,7 @@ namespace MWMechanics mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); } - } + } else { mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); From 92a5e524070a88d2cf972c4191b2cee9993914ea Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 19:53:34 +0200 Subject: [PATCH 04/13] rename "persisted" animations to "scripted", because that is what they actually are. And begin teaching the animation system to distinguish between a scripted and an unscripted animation. --- apps/openmw/mwmechanics/actors.cpp | 4 +- apps/openmw/mwmechanics/actors.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 59 +++++++++++-------- apps/openmw/mwmechanics/character.hpp | 8 +-- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +- .../mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwmechanics/objects.cpp | 4 +- apps/openmw/mwmechanics/objects.hpp | 2 +- apps/openmw/mwrender/animation.cpp | 12 +++- apps/openmw/mwrender/animation.hpp | 3 + 10 files changed, 60 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 743c5d5ab5..3e7b075e62 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1998,12 +1998,12 @@ namespace MWMechanics } bool Actors::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) const + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { - return iter->second->getCharacterController().playGroup(groupName, mode, number, persist); + return iter->second->getCharacterController().playGroup(groupName, mode, number, scripted); } else { diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 98c64397ab..15a39136a6 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -113,7 +113,7 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr& ptr) const; bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) const; + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; void skipAnimation(const MWWorld::Ptr& ptr) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; void persistAnimationStates() const; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9c39b5b60d..0d9cda9263 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -538,7 +538,7 @@ namespace MWMechanics if (mAnimation->isPlaying("containerclose")) return false; - mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + mAnimation->play("containeropen", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0); if (mAnimation->isPlaying("containeropen")) return false; @@ -559,7 +559,7 @@ namespace MWMechanics if (animPlaying) startPoint = 1.f - complete; - mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + mAnimation->play("containerclose", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, 0); } } @@ -828,7 +828,7 @@ namespace MWMechanics CharacterState idle, CharacterState movement, JumpingState jump, bool force) { // If the current animation is persistent, do not touch it - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return; refreshHitRecoilAnims(); @@ -882,7 +882,7 @@ namespace MWMechanics mDeathState = chooseRandomDeathState(); // Do not interrupt scripted animation by death - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return; playDeath(startpoint, mDeathState); @@ -1482,7 +1482,7 @@ namespace MWMechanics } // Combat for actors with persistent animations obviously will be buggy - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return forcestateupdate; float complete = 0.f; @@ -1855,10 +1855,18 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + bool isFrontAnimPlaying = false; + if (mAnimQueue.front().mScripted) + isFrontAnimPlaying = mAnimation->isPlayingScripted(mAnimQueue.front().mGroup); + else + isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); + + + if (!isFrontAnimPlaying) { if (mAnimQueue.size() > 1) { + // Curren animation finished, move to the next in queue mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); @@ -1866,14 +1874,13 @@ namespace MWMechanics mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) + else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mScripted) { - // The last animation stopped playing when it shouldn't have. - // This is caused by a rebuild of the animation object, so we should restart the animation here - // Subtract 1 from mLoopCount to consider the current loop done. + // A scripted animation stopped playing before time. + // This happens when the animation object is rebuilt, so we should restart the animation here. bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount - 1, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } else { @@ -2393,7 +2400,7 @@ namespace MWMechanics } } - bool isPersist = isPersistentAnimPlaying(); + bool isPersist = isScriptedAnimPlaying(); osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); if (duration > 0.0f) moved /= duration; @@ -2450,7 +2457,7 @@ namespace MWMechanics state.mScriptedAnims.clear(); for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) { - if (!iter->mPersist) + if (!iter->mScripted) continue; ESM::AnimationState::ScriptedAnimation anim; @@ -2486,7 +2493,7 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = iter->mGroup; entry.mLoopCount = iter->mLoopCount; - entry.mPersist = true; + entry.mScripted = true; mAnimQueue.push_back(entry); } @@ -2505,18 +2512,18 @@ namespace MWMechanics mIdleState = CharState_SpecialIdle; bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(anim.mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", + mAnimation->play(anim.mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", complete, anim.mLoopCount, loopfallback); } } - bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool persist) + bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted) { if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; // We should not interrupt persistent animations by non-persistent ones - if (isPersistentAnimPlaying() && !persist) + if (isScriptedAnimPlaying() && !scripted) return true; // If this animation is a looped animation (has a "loop start" key) that is already playing @@ -2545,17 +2552,17 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = groupname; entry.mLoopCount = count - 1; - entry.mPersist = persist; + entry.mScripted = scripted; if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { - clearAnimQueue(persist); + clearAnimQueue(scripted); clearStateAnimation(mCurrentIdle); mIdleState = CharState_SpecialIdle; bool loopfallback = entry.mGroup.starts_with("idle"); - mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default, + mAnimation->play(groupname, scripted && groupname != "idle" ? Priority_Scripted : Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, ((mode == 2) ? "loop start" : "start"), "stop", 0.0f, count - 1, loopfallback); } @@ -2566,7 +2573,7 @@ namespace MWMechanics // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing if (groupname == "idle") - entry.mPersist = false; + entry.mScripted = false; mAnimQueue.push_back(entry); @@ -2578,12 +2585,12 @@ namespace MWMechanics mSkipAnim = true; } - bool CharacterController::isPersistentAnimPlaying() const + bool CharacterController::isScriptedAnimPlaying() const { if (!mAnimQueue.empty()) { const AnimationQueueEntry& first = mAnimQueue.front(); - return first.mPersist && isAnimPlaying(first.mGroup); + return first.mScripted && isAnimPlaying(first.mGroup); } return false; @@ -2609,7 +2616,7 @@ namespace MWMechanics void CharacterController::clearAnimQueue(bool clearPersistAnims) { // Do not interrupt scripted animations, if we want to keep them - if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) + if ((!isScriptedAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); if (clearPersistAnims) @@ -2620,7 +2627,7 @@ namespace MWMechanics for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) { - if (!it->mPersist) + if (!it->mScripted) it = mAnimQueue.erase(it); else ++it; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 316a1cff0e..79bbe73538 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -40,7 +40,7 @@ namespace MWMechanics Priority_Torch, Priority_Storm, Priority_Death, - Priority_Persistent, + Priority_Scripted, Num_Priorities }; @@ -135,7 +135,7 @@ namespace MWMechanics { std::string mGroup; size_t mLoopCount; - bool mPersist; + bool mScripted; }; typedef std::deque AnimationQueue; AnimationQueue mAnimQueue; @@ -215,7 +215,7 @@ namespace MWMechanics std::string chooseRandomAttackAnimation() const; static bool isRandomAttackAnimation(std::string_view group); - bool isPersistentAnimPlaying() const; + bool isScriptedAnimPlaying() const; bool isMovementAnimationControlled() const; void updateAnimQueue(); @@ -270,7 +270,7 @@ namespace MWMechanics void persistAnimationState() const; void unpersistAnimationState(); - bool playGroup(std::string_view groupname, int mode, int count, bool persist = false); + bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 071ac164f3..f95df16855 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -760,12 +760,12 @@ namespace MWMechanics } bool MechanicsManager::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) { if (ptr.getClass().isActor()) - return mActors.playAnimationGroup(ptr, groupName, mode, number, persist); + return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted); else - return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist); + return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 36bb18e022..997636522e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -142,7 +142,7 @@ namespace MWMechanics /// Attempt to play an animation group /// @return Success or error bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) override; + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; void skipAnimation(const MWWorld::Ptr& ptr) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; void persistAnimationStates() override; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index ab981dd459..5bdfc91ac7 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -99,12 +99,12 @@ namespace MWMechanics } bool Objects::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { - return iter->second->playGroup(groupName, mode, number, persist); + return iter->second->playGroup(groupName, mode, number, scripted); } else { diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 8b5962109c..296f454e4f 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -46,7 +46,7 @@ namespace MWMechanics void onClose(const MWWorld::Ptr& ptr); bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false); + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false); void skipAnimation(const MWWorld::Ptr& ptr); void persistAnimationStates(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b49f382f66..cae3687afb 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1020,6 +1020,14 @@ namespace MWRender return false; } + bool Animation::isPlayingScripted(std::string_view groupname) const + { + AnimStateMap::const_iterator state(mStates.find(groupname)); + if (state != mStates.end()) + return state->second.mPlaying && state->second.mPriority.contains(MWMechanics::Priority::Priority_Scripted); + return false; + } + bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); @@ -1145,7 +1153,7 @@ namespace MWRender bool hasScriptedAnims = false; for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) { hasScriptedAnims = true; @@ -1158,7 +1166,7 @@ namespace MWRender while (stateiter != mStates.end()) { AnimState& state = stateiter->second; - if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) + if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Scripted))) { ++stateiter; continue; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8615811cc3..9a55c91fc4 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -432,6 +432,9 @@ namespace MWRender /** Returns true if the named animation group is playing. */ bool isPlaying(std::string_view groupname) const; + /** Returns true if the named, scripted animation group is playing. */ + bool isPlayingScripted(std::string_view groupname) const; + /// Returns true if no important animations are currently playing on the upper body. bool upperBodyReady() const; From a9ade184d8f2dda41db4bb51fc2df4d5cdfead60 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 19:55:10 +0200 Subject: [PATCH 05/13] mLoopCount is the number of *remaining* loops. So we should be checking for >0, not >1. --- 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 0d9cda9263..ad201997bd 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1874,7 +1874,7 @@ namespace MWMechanics mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mScripted) + else if (mAnimQueue.front().mLoopCount > 0 && mAnimQueue.front().mScripted) { // A scripted animation stopped playing before time. // This happens when the animation object is rebuilt, so we should restart the animation here. From 426f5952a5bfbdf3c6d2114164a4176b85c6db3e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:02:17 +0200 Subject: [PATCH 06/13] Prevent movement only when *scripted* animations are playing. Otherwise idle animations of wandering NPCs will slide. --- apps/openmw/mwmechanics/character.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ad201997bd..08c035ce31 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1861,7 +1861,6 @@ namespace MWMechanics else isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); - if (!isFrontAnimPlaying) { if (mAnimQueue.size() > 1) @@ -2373,7 +2372,7 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled() && mAnimQueue.empty()) + if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) world->queueMovement(mPtr, vec); } @@ -2442,7 +2441,7 @@ namespace MWMechanics } // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor() && mAnimQueue.empty()) + if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying()) world->queueMovement(mPtr, moved); mSkipAnim = false; @@ -2522,7 +2521,7 @@ namespace MWMechanics if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; - // We should not interrupt persistent animations by non-persistent ones + // We should not interrupt persistent animations by non-scripted ones if (isScriptedAnimPlaying() && !scripted) return true; From cf5b782c761a0cbded399f3ef97b451f166bece0 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:05:07 +0200 Subject: [PATCH 07/13] More persist -> script language. --- apps/openmw/mwmechanics/character.cpp | 15 +++++++-------- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 08c035ce31..b3661c8f96 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -827,7 +827,7 @@ namespace MWMechanics void CharacterController::refreshCurrentAnims( CharacterState idle, CharacterState movement, JumpingState jump, bool force) { - // If the current animation is persistent, do not touch it + // If the current animation is scripted, do not touch it if (isScriptedAnimPlaying()) return; @@ -1481,7 +1481,7 @@ namespace MWMechanics sndMgr->stopSound3D(mPtr, wolfRun); } - // Combat for actors with persistent animations obviously will be buggy + // Combat for actors with scripted animations obviously will be buggy if (isScriptedAnimPlaying()) return forcestateupdate; @@ -2399,8 +2399,7 @@ namespace MWMechanics } } - bool isPersist = isScriptedAnimPlaying(); - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); if (duration > 0.0f) moved /= duration; else @@ -2521,7 +2520,7 @@ namespace MWMechanics if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; - // We should not interrupt persistent animations by non-scripted ones + // We should not interrupt scripted animations with non-scripted ones if (isScriptedAnimPlaying() && !scripted) return true; @@ -2612,13 +2611,13 @@ namespace MWMechanics return movementAnimationControlled; } - void CharacterController::clearAnimQueue(bool clearPersistAnims) + void CharacterController::clearAnimQueue(bool clearScriptedAnims) { // Do not interrupt scripted animations, if we want to keep them - if ((!isScriptedAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) + if ((!isScriptedAnimPlaying() || clearScriptedAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); - if (clearPersistAnims) + if (clearScriptedAnims) { mAnimQueue.clear(); return; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 79bbe73538..c3d45fe0fb 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -207,7 +207,7 @@ namespace MWMechanics void refreshMovementAnims(CharacterState movement, bool force = false); void refreshIdleAnims(CharacterState idle, bool force = false); - void clearAnimQueue(bool clearPersistAnims = false); + void clearAnimQueue(bool clearScriptedAnims = false); bool updateWeaponState(); void updateIdleStormState(bool inwater) const; From 70f41b22b657aba178e747320535e251efdaa684 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:25:12 +0200 Subject: [PATCH 08/13] Based on the reason for closing !1845, we *should* let a scripted animation get stuck forever if the animation was already playing. --- apps/openmw/mwmechanics/character.cpp | 8 +------- apps/openmw/mwrender/animation.cpp | 8 -------- apps/openmw/mwrender/animation.hpp | 3 --- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b3661c8f96..aa045edff6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1855,13 +1855,7 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - bool isFrontAnimPlaying = false; - if (mAnimQueue.front().mScripted) - isFrontAnimPlaying = mAnimation->isPlayingScripted(mAnimQueue.front().mGroup); - else - isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); - - if (!isFrontAnimPlaying) + if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { if (mAnimQueue.size() > 1) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cae3687afb..4ce589f8b9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1020,14 +1020,6 @@ namespace MWRender return false; } - bool Animation::isPlayingScripted(std::string_view groupname) const - { - AnimStateMap::const_iterator state(mStates.find(groupname)); - if (state != mStates.end()) - return state->second.mPlaying && state->second.mPriority.contains(MWMechanics::Priority::Priority_Scripted); - return false; - } - bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 9a55c91fc4..8615811cc3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -432,9 +432,6 @@ namespace MWRender /** Returns true if the named animation group is playing. */ bool isPlaying(std::string_view groupname) const; - /** Returns true if the named, scripted animation group is playing. */ - bool isPlayingScripted(std::string_view groupname) const; - /// Returns true if no important animations are currently playing on the upper body. bool upperBodyReady() const; From c25c6872b03b4ba0c0df3ded2779b01cc1ca5d67 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 22:20:37 +0200 Subject: [PATCH 09/13] changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e122f0a9..6641f41ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,8 @@ Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing + Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation + Bug #7637: Actors can sometimes move while playing scripted animations Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7647: NPC walk cycle bugs after greeting player Feature #3537: Shader-based water ripples From afb9fa06d7279ce90890f3ceba4b9be555412ddf Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 22:23:29 +0200 Subject: [PATCH 10/13] clang format --- apps/openmw/mwrender/animation.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 4ce589f8b9..0741e24a69 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1145,8 +1145,7 @@ namespace MWRender bool hasScriptedAnims = false; for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) - && stateiter->second.mPlaying) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) { hasScriptedAnims = true; break; From 85f104fefe0f4bf4a29bca465ae05621f3531e6a Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 15:33:07 +0100 Subject: [PATCH 11/13] Comments from Capo --- apps/openmw/mwmechanics/character.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index aa045edff6..a21e5fbf36 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1857,30 +1857,22 @@ namespace MWMechanics if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { - if (mAnimQueue.size() > 1) + // Remove the finished animation, unless it's a scripted animation that was interrupted by e.g. a rebuild of + // the animation object. + if (mAnimQueue.size() > 1 || !mAnimQueue.front().mScripted || mAnimQueue.front().mLoopCount == 0) { - // Curren animation finished, move to the next in queue mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); + } - bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - else if (mAnimQueue.front().mLoopCount > 0 && mAnimQueue.front().mScripted) + if (!mAnimQueue.empty()) { - // A scripted animation stopped playing before time. - // This happens when the animation object is rebuilt, so we should restart the animation here. + // Move on to the remaining items of the queue bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, + mAnimation->play(mAnimQueue.front().mGroup, mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - else - { - // Animation is done, remove it from the queue. - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - } } else { From 58e3fdac3661d2f796bfd11e388301eea524435d Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 16:32:35 +0100 Subject: [PATCH 12/13] Clang format --- apps/openmw/mwmechanics/character.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index a21e5fbf36..068d91ab42 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1869,9 +1869,10 @@ namespace MWMechanics { // Move on to the remaining items of the queue bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, - MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, + mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, + MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, + mAnimQueue.front().mLoopCount, loopfallback); } } else From d2836748fd708feba645def1b9f7c43ee98672e9 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 20:27:02 +0100 Subject: [PATCH 13/13] Changelog entry for #4742 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6641f41ac5..c7756fd437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Bug #4207: RestoreHealth/Fatigue spells have a huge priority even if a success chance is near 0 Bug #4382: Sound output device does not change when it should Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely + Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses