diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c925302599..637c243007 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1524,6 +1524,26 @@ bool CharacterController::updateWeaponState() return forcestateupdate; } +void CharacterController::updateAnimQueue() +{ + if(mAnimQueue.size() > 1) + { + if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.pop_front(); + + bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, + MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + } + } + + if(!mAnimQueue.empty()) + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); +} + void CharacterController::update(float duration) { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -1534,21 +1554,7 @@ void CharacterController::update(float duration) updateMagicEffects(); if(!cls.isActor()) - { - if(mAnimQueue.size() > 1) - { - if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, - MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - } - } + updateAnimQueue(); else if(!cls.getCreatureStats(mPtr).isDead()) { bool onground = world->isOnGround(mPtr); @@ -1816,19 +1822,8 @@ void CharacterController::update(float duration) { idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle)); } - else if(mAnimQueue.size() > 1) - { - if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - - bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, - MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - } + else + updateAnimQueue(); if (!mSkipAnim) { @@ -1994,9 +1989,10 @@ void CharacterController::unpersistAnimationState() mCurrentIdle.clear(); mIdleState = CharState_SpecialIdle; + bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); mAnimation->play(anim.mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, - "start", "stop", complete, anim.mLoopCount); + "start", "stop", complete, anim.mLoopCount, loopfallback); } } @@ -2009,6 +2005,27 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int } else { + // If the given animation is a looped animation, is already playing + // and has not yet reached its Loop Stop key, make it the only animation + // in the queue, and retain the loop count from the animation that was + // already playing. This emulates observed behavior from the original + // engine and allows banners to animate correctly. + if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && + mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop start") >= 0) + { + float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": loop stop"); + + if (endOfLoop < 0) // if no Loop Stop key was found, use the Stop key + endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup+": stop"); + + if (endOfLoop > 0 && (mAnimation->getCurrentTime(mAnimQueue.front().mGroup) < endOfLoop)) + { + mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, true); + mAnimQueue.resize(1); + return true; + } + } + count = std::max(count, 1); AnimationQueueEntry entry; @@ -2032,8 +2049,6 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int } else if(mode == 0) { - if (!mAnimQueue.empty()) - mAnimation->stopLooping(mAnimQueue.front().mGroup); mAnimQueue.resize(1); mAnimQueue.push_back(entry); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index d5dc5fe284..4661d6983a 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -212,6 +212,8 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool updateCreatureState(); void updateIdleStormState(bool inwater); + void updateAnimQueue(); + void updateHeadTracking(float duration); void updateMagicEffects(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e5614f3f8f..32fa5e6009 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -873,16 +873,6 @@ namespace MWRender addControllers(); } - void Animation::stopLooping(const std::string& groupname) - { - AnimStateMap::iterator stateiter = mStates.find(groupname); - if(stateiter != mStates.end()) - { - stateiter->second.mLoopCount = 0; - return; - } - } - void Animation::adjustSpeedMult(const std::string &groupname, float speedmult) { AnimStateMap::iterator state(mStates.find(groupname)); @@ -1023,35 +1013,33 @@ namespace MWRender { float targetTime; - if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) - goto handle_loop; - - targetTime = state.getTime() + timepassed; - if(textkey == textkeys.end() || textkey->first > targetTime) + if (!state.shouldLoop()) { - if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), targetTime, movement); - state.setTime(std::min(targetTime, state.mStopTime)); + targetTime = state.getTime() + timepassed; + if(textkey == textkeys.end() || textkey->first > targetTime) + { + if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), targetTime, movement); + state.setTime(std::min(targetTime, state.mStopTime)); + } + else + { + if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), textkey->first, movement); + state.setTime(textkey->first); + } + + state.mPlaying = (state.getTime() < state.mStopTime); + timepassed = targetTime - state.getTime(); + + while(textkey != textkeys.end() && textkey->first <= state.getTime()) + { + handleTextKey(state, stateiter->first, textkey, textkeys); + ++textkey; + } } - else + if(state.shouldLoop()) { - if(mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), textkey->first, movement); - state.setTime(textkey->first); - } - - state.mPlaying = (state.getTime() < state.mStopTime); - timepassed = targetTime - state.getTime(); - - while(textkey != textkeys.end() && textkey->first <= state.getTime()) - { - handleTextKey(state, stateiter->first, textkey, textkeys); - ++textkey; - } - - if(state.getTime() >= state.mLoopStopTime && state.mLoopCount > 0) - { - handle_loop: state.mLoopCount--; state.setTime(state.mLoopStartTime); state.mPlaying = true; @@ -1065,7 +1053,7 @@ namespace MWRender if(state.getTime() >= state.mLoopStopTime) break; - } + } if(timepassed <= 0.0f) break; @@ -1095,6 +1083,13 @@ namespace MWRender return movement; } + void Animation::setLoopingEnabled(const std::string &groupname, bool enabled) + { + AnimStateMap::iterator state(mStates.find(groupname)); + if(state != mStates.end()) + state->second.mLoopingEnabled = enabled; + } + void Animation::setObjectRoot(const std::string &model, bool forceskeleton, bool baseonly, bool isCreature) { osg::ref_ptr previousStateset; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a837a26aeb..d0191d172c 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -183,6 +183,7 @@ protected: float mSpeedMult; bool mPlaying; + bool mLoopingEnabled; size_t mLoopCount; AnimPriority mPriority; @@ -190,8 +191,8 @@ protected: bool mAutoDisable; AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f), - mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0), - mPriority(0), mBlendMask(0), mAutoDisable(true) + mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopingEnabled(true), + mLoopCount(0), mPriority(0), mBlendMask(0), mAutoDisable(true) { } ~AnimState(); @@ -204,6 +205,11 @@ protected: { *mTime = time; } + + bool shouldLoop() const + { + return getTime() >= mLoopStopTime && mLoopingEnabled && mLoopCount > 0; + } }; typedef std::map AnimStateMap; AnimStateMap mStates; @@ -389,10 +395,6 @@ public: float speedmult, const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback=false); - /** If the given animation group is currently playing, set its remaining loop count to '0'. - */ - void stopLooping(const std::string& groupName); - /** Adjust the speed multiplier of an already playing animation. */ void adjustSpeedMult (const std::string& groupname, float speedmult); @@ -432,6 +434,8 @@ public: virtual osg::Vec3f runAnimation(float duration); + void setLoopingEnabled(const std::string &groupname, bool enabled); + /// This is typically called as part of runAnimation, but may be called manually if needed. void updateEffects(float duration);