diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp index 8f090b3fc..5c2bcc402 100644 --- a/apps/essimporter/convertacdt.cpp +++ b/apps/essimporter/convertacdt.cpp @@ -1,3 +1,9 @@ +#include +#include +#include + +#include + #include "convertacdt.hpp" namespace ESSImport @@ -49,4 +55,49 @@ namespace ESSImport npcStats.mTimeToStartDrowning = actorData.mACDT.mBreathMeter; } + void convertANIS (const ANIS& anis, ESM::AnimationState& state) + { + static const char* animGroups[] = + { + "Idle", "Idle2", "Idle3", "Idle4", "Idle5", "Idle6", "Idle7", "Idle8", "Idle9", "Idlehh", "Idle1h", "Idle2c", + "Idle2w", "IdleSwim", "IdleSpell", "IdleCrossbow", "IdleSneak", "IdleStorm", "Torch", "Hit1", "Hit2", "Hit3", + "Hit4", "Hit5", "SwimHit1", "SwimHit2", "SwimHit3", "Death1", "Death2", "Death3", "Death4", "Death5", + "DeathKnockDown", "DeathKnockOut", "KnockDown", "KnockOut", "SwimDeath", "SwimDeath2", "SwimDeath3", + "SwimDeathKnockDown", "SwimDeathKnockOut", "SwimKnockOut", "SwimKnockDown", "SwimWalkForward", + "SwimWalkBack", "SwimWalkLeft", "SwimWalkRight", "SwimRunForward", "SwimRunBack", "SwimRunLeft", + "SwimRunRight", "SwimTurnLeft", "SwimTurnRight", "WalkForward", "WalkBack", "WalkLeft", "WalkRight", + "TurnLeft", "TurnRight", "RunForward", "RunBack", "RunLeft", "RunRight", "SneakForward", "SneakBack", + "SneakLeft", "SneakRight", "Jump", "WalkForwardhh", "WalkBackhh", "WalkLefthh", "WalkRighthh", + "TurnLefthh", "TurnRighthh", "RunForwardhh", "RunBackhh", "RunLefthh", "RunRighthh", "SneakForwardhh", + "SneakBackhh", "SneakLefthh", "SneakRighthh", "Jumphh", "WalkForward1h", "WalkBack1h", "WalkLeft1h", + "WalkRight1h", "TurnLeft1h", "TurnRight1h", "RunForward1h", "RunBack1h", "RunLeft1h", "RunRight1h", + "SneakForward1h", "SneakBack1h", "SneakLeft1h", "SneakRight1h", "Jump1h", "WalkForward2c", "WalkBack2c", + "WalkLeft2c", "WalkRight2c", "TurnLeft2c", "TurnRight2c", "RunForward2c", "RunBack2c", "RunLeft2c", + "RunRight2c", "SneakForward2c", "SneakBack2c", "SneakLeft2c", "SneakRight2c", "Jump2c", "WalkForward2w", + "WalkBack2w", "WalkLeft2w", "WalkRight2w", "TurnLeft2w", "TurnRight2w", "RunForward2w", "RunBack2w", + "RunLeft2w", "RunRight2w", "SneakForward2w", "SneakBack2w", "SneakLeft2w", "SneakRight2w", "Jump2w", + "SpellCast", "SpellTurnLeft", "SpellTurnRight", "Attack1", "Attack2", "Attack3", "SwimAttack1", + "SwimAttack2", "SwimAttack3", "HandToHand", "Crossbow", "BowAndArrow", "ThrowWeapon", "WeaponOneHand", + "WeaponTwoHand", "WeaponTwoWide", "Shield", "PickProbe", "InventoryHandToHand", "InventoryWeaponOneHand", + "InventoryWeaponTwoHand", "InventoryWeaponTwoWide" + }; + + if (anis.mGroupIndex < (sizeof(animGroups) / sizeof(*animGroups))) + { + std::string group(animGroups[anis.mGroupIndex]); + Misc::StringUtils::lowerCaseInPlace(group); + + ESM::AnimationState::ScriptedAnimation scriptedAnim; + scriptedAnim.mGroup = group; + scriptedAnim.mTime = anis.mTime; + scriptedAnim.mAbsolute = true; + // Neither loop count nor queueing seems to be supported by the ess format. + scriptedAnim.mLoopCount = std::numeric_limits::max(); + state.mScriptedAnims.push_back(scriptedAnim); + } + else + // TODO: Handle 0xFF index, which seems to be used for finished animations. + std::cerr << "unknown animation group index: " << static_cast(anis.mGroupIndex) << std::endl; + } + } diff --git a/apps/essimporter/convertacdt.hpp b/apps/essimporter/convertacdt.hpp index bc9a7bd00..4059dd1af 100644 --- a/apps/essimporter/convertacdt.hpp +++ b/apps/essimporter/convertacdt.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "importacdt.hpp" @@ -18,6 +19,8 @@ namespace ESSImport void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats); void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats); + + void convertANIS (const ANIS& anis, ESM::AnimationState& state); } #endif diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index afd0ef131..a428a8c71 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -34,6 +34,9 @@ namespace objstate.mCount = 0; convertSCRI(cellref.mSCRI, objstate.mLocals); objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); + + if (cellref.mHasANIS) + convertANIS(cellref.mANIS, objstate.mAnimationState); } bool isIndexedRefId(const std::string& indexedRefId) diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index fb0dccaf5..0ddd2eb64 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -123,8 +123,13 @@ namespace ESSImport if (esm.isNextSub("ND3D")) esm.skipHSub(); + + mHasANIS = false; if (esm.isNextSub("ANIS")) - esm.skipHSub(); + { + mHasANIS = true; + esm.getHT(mANIS); + } } } diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index eacb2edf1..bf48d1f78 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -55,6 +55,12 @@ namespace ESSImport unsigned char mCorpseClearCountdown; // hours? unsigned char mUnknown3[71]; }; + struct ANIS + { + unsigned char mGroupIndex; + unsigned char mUnknown[3]; + float mTime; + }; #pragma pack(pop) struct ActorData : public ESM::CellRef @@ -77,6 +83,9 @@ namespace ESSImport SCRI mSCRI; + bool mHasANIS; + ANIS mANIS; // scripted animation state + void load(ESM::ESMReader& esm); }; diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index a0e9da26a..0f414972f 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -159,12 +159,14 @@ namespace MWBase virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; ///< Forces an object to refresh its animation state. - virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1) = 0; + virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number=1, bool persist=false) = 0; ///< Run animation for a MW-reference. Calls to this function for references that are currently not /// in the scene should be ignored. /// /// \param mode 0 normal, 1 immediate start, 2 immediate loop /// \param count How many times the animation should be run + /// \param persist Whether the animation state should be stored in saved games + /// and persist after cell unload. /// \return Success or error virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; @@ -173,6 +175,9 @@ namespace MWBase virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; + /// Save the current animation state of managed references to their RefData. + virtual void persistAnimationStates() = 0; + /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index d12dde424..fc1c77caf 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1389,12 +1389,12 @@ namespace MWMechanics iter->second->getCharacterController()->forceStateUpdate(); } - bool Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) + bool Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) { - return iter->second->getCharacterController()->playGroup(groupName, mode, number); + return iter->second->getCharacterController()->playGroup(groupName, mode, number, persist); } else { @@ -1417,6 +1417,12 @@ namespace MWMechanics return false; } + void Actors::persistAnimationStates() + { + for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + iter->second->getCharacterController()->persistAnimationState(); + } + void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) { for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 0dc684c56..163995f6f 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -109,9 +109,10 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr &ptr); - bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); void skipAnimation(const MWWorld::Ptr& ptr); bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); + void persistAnimationStates(); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 081f288d5..07aba2f7d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -762,12 +762,17 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); mAnimation->runAnimation(0.f); + + unpersistAnimationState(); } CharacterController::~CharacterController() { if (mAnimation) + { + persistAnimationState(); mAnimation->setTextKeyListener(NULL); + } } void split(const std::string &s, char delim, std::vector &elems) { @@ -1557,14 +1562,14 @@ void CharacterController::update(float duration) { if(mAnimQueue.size() > 1) { - if(mAnimation->isPlaying(mAnimQueue.front().first) == false) + if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) { - mAnimation->disable(mAnimQueue.front().first); + mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); - mAnimation->play(mAnimQueue.front().first, Priority_Default, + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount); } } } @@ -1837,14 +1842,14 @@ void CharacterController::update(float duration) } else if(mAnimQueue.size() > 1) { - if(mAnimation->isPlaying(mAnimQueue.front().first) == false) + if(mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) { - mAnimation->disable(mAnimQueue.front().first); + mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); - mAnimation->play(mAnimQueue.front().first, Priority_Default, + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount); } } @@ -1951,8 +1956,74 @@ void CharacterController::update(float duration) mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); } +void CharacterController::persistAnimationState() +{ + ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); + + state.mScriptedAnims.clear(); + for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) + { + if (!iter->mPersist) + continue; + + ESM::AnimationState::ScriptedAnimation anim; + anim.mGroup = iter->mGroup; + + if (iter == mAnimQueue.begin()) + { + anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); + float complete; + mAnimation->getInfo(anim.mGroup, &complete, NULL); + anim.mTime = complete; + } + else + { + anim.mLoopCount = iter->mLoopCount; + anim.mTime = 0.f; + } + + state.mScriptedAnims.push_back(anim); + } +} + +void CharacterController::unpersistAnimationState() +{ + const ESM::AnimationState& state = mPtr.getRefData().getAnimationState(); + + if (!state.mScriptedAnims.empty()) + { + clearAnimQueue(); + for (ESM::AnimationState::ScriptedAnimations::const_iterator iter = state.mScriptedAnims.begin(); iter != state.mScriptedAnims.end(); ++iter) + { + AnimationQueueEntry entry; + entry.mGroup = iter->mGroup; + entry.mLoopCount = iter->mLoopCount; + entry.mPersist = true; + + mAnimQueue.push_back(entry); + } + + const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); + float complete = anim.mTime; + if (anim.mAbsolute) + { + float start = mAnimation->getTextKeyTime(anim.mGroup+": start"); + float stop = mAnimation->getTextKeyTime(anim.mGroup+": stop"); + float time = std::max(start, std::min(stop, anim.mTime)); + complete = (time - start) / (stop - start); + } -bool CharacterController::playGroup(const std::string &groupname, int mode, int count) + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); + mIdleState = CharState_SpecialIdle; + + mAnimation->play(anim.mGroup, + Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, + "start", "stop", complete, anim.mLoopCount); + } +} + +bool CharacterController::playGroup(const std::string &groupname, int mode, int count, bool persist) { if(!mAnimation || !mAnimation->hasAnimation(groupname)) { @@ -1962,10 +2033,16 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int else { count = std::max(count, 1); - if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().first)) + + AnimationQueueEntry entry; + entry.mGroup = groupname; + entry.mLoopCount = count-1; + entry.mPersist = persist; + + if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { clearAnimQueue(); - mAnimQueue.push_back(std::make_pair(groupname, count-1)); + mAnimQueue.push_back(entry); mAnimation->disable(mCurrentIdle); mCurrentIdle.clear(); @@ -1978,9 +2055,9 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int else if(mode == 0) { if (!mAnimQueue.empty()) - mAnimation->stopLooping(mAnimQueue.front().first); + mAnimation->stopLooping(mAnimQueue.front().mGroup); mAnimQueue.resize(1); - mAnimQueue.push_back(std::make_pair(groupname, count-1)); + mAnimQueue.push_back(entry); } } return true; @@ -2002,7 +2079,7 @@ bool CharacterController::isAnimPlaying(const std::string &groupName) void CharacterController::clearAnimQueue() { if(!mAnimQueue.empty()) - mAnimation->disable(mAnimQueue.front().first); + mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.clear(); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index b7e5feb6f..190e171b3 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -147,7 +147,13 @@ class CharacterController : public MWRender::Animation::TextKeyListener MWWorld::Ptr mPtr; MWRender::Animation *mAnimation; - typedef std::deque > AnimationQueue; + struct AnimationQueueEntry + { + std::string mGroup; + size_t mLoopCount; + bool mPersist; + }; + typedef std::deque AnimationQueue; AnimationQueue mAnimQueue; CharacterState mIdleState; @@ -236,7 +242,10 @@ public: void update(float duration); - bool playGroup(const std::string &groupname, int mode, int count); + void persistAnimationState(); + void unpersistAnimationState(); + + bool playGroup(const std::string &groupname, int mode, int count, bool persist=false); void skipAnim(); bool isAnimPlaying(const std::string &groupName); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 278a5749e..b10127f74 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -842,12 +842,12 @@ namespace MWMechanics mActors.forceStateUpdate(ptr); } - bool MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) + bool MechanicsManager::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) { if(ptr.getClass().isActor()) - return mActors.playAnimationGroup(ptr, groupName, mode, number); + return mActors.playAnimationGroup(ptr, groupName, mode, number, persist); else - return mObjects.playAnimationGroup(ptr, groupName, mode, number); + return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { @@ -864,6 +864,12 @@ namespace MWMechanics return false; } + void MechanicsManager::persistAnimationStates() + { + mActors.persistAnimationStates(); + mObjects.persistAnimationStates(); + } + void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr) { mActors.updateMagicEffects(ptr); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index b0a9380e5..04c67fcb6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -146,9 +146,10 @@ namespace MWMechanics /// Attempt to play an animation group /// @return Success or error - virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + virtual bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); virtual void skipAnimation(const MWWorld::Ptr& ptr); virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); + virtual void persistAnimationStates(); /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently /// paused we may want to do it manually (after equipping permanent enchantment) diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 7d7c0bfae..139825c21 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -79,12 +79,12 @@ void Objects::update(float duration, bool paused) } } -bool Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) +bool Objects::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist) { PtrControllerMap::iterator iter = mObjects.find(ptr); if(iter != mObjects.end()) { - return iter->second->playGroup(groupName, mode, number); + return iter->second->playGroup(groupName, mode, number, persist); } else { @@ -99,6 +99,12 @@ void Objects::skipAnimation(const MWWorld::Ptr& ptr) iter->second->skipAnim(); } +void Objects::persistAnimationStates() +{ + for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + iter->second->persistAnimationState(); +} + void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector& out) { for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 07e00675c..1efebafbe 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -38,8 +38,9 @@ namespace MWMechanics void update(float duration, bool paused); ///< Update object animations - bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); + bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); void skipAnimation(const MWWorld::Ptr& ptr); + void persistAnimationStates(); void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector& out); }; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index fa7542060..0412e1e41 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -843,6 +843,15 @@ namespace MWRender return iter->second.getTime(); } + size_t Animation::getCurrentLoopCount(const std::string& groupname) const + { + AnimStateMap::const_iterator iter = mStates.find(groupname); + if(iter == mStates.end()) + return 0; + + return iter->second.mLoopCount; + } + void Animation::disable(const std::string &groupname) { AnimStateMap::iterator iter = mStates.find(groupname); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index e9d79ced1..ad9d4ab4a 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -415,6 +415,8 @@ public: /// Get the current absolute position in the animation track for the animation that is currently playing from the given group. float getCurrentTime(const std::string& groupname) const; + size_t getCurrentLoopCount(const std::string& groupname) const; + /** Disables the specified animation group; * \param groupname Animation group to disable. */ diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 07a8a300a..44c4612ec 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -56,7 +56,7 @@ namespace MWScript throw std::runtime_error ("animation mode out of range"); } - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, std::numeric_limits::max()); + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, std::numeric_limits::max(), true); } }; @@ -89,7 +89,7 @@ namespace MWScript throw std::runtime_error ("animation mode out of range"); } - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, loops); + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, loops, true); } }; diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index a8639b94b..48cc37935 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -219,6 +219,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot else slot = character->updateSlot (slot, profile); + // Make sure the animation state held by references is up to date before saving the game. + MWBase::Environment::get().getMechanicsManager()->persistAnimationStates(); + // Write to a memory stream first. If there is an exception during the save process, we don't want to trash the // existing save file we are overwriting. std::stringstream stream; diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f85abcc32..ebc38ac9b 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -31,6 +31,8 @@ namespace MWWorld mDeletedByContentFile = refData.mDeletedByContentFile; mFlags = refData.mFlags; + mAnimationState = refData.mAnimationState; + mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; } @@ -65,6 +67,7 @@ namespace MWWorld mEnabled (objectState.mEnabled != 0), mCount (objectState.mCount), mPosition (objectState.mPosition), + mAnimationState(objectState.mAnimationState), mCustomData (0), mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed { @@ -96,6 +99,8 @@ namespace MWWorld objectState.mCount = mCount; objectState.mPosition = mPosition; objectState.mFlags = mFlags; + + objectState.mAnimationState = mAnimationState; } RefData& RefData::operator= (const RefData& refData) @@ -269,4 +274,15 @@ namespace MWWorld else return false; } + + const ESM::AnimationState& RefData::getAnimationState() const + { + return mAnimationState; + } + + ESM::AnimationState& RefData::getAnimationState() + { + return mAnimationState; + } + } diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index 9e662e430..75eec6742 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -2,6 +2,7 @@ #define GAME_MWWORLD_REFDATA_H #include +#include #include "../mwscript/locals.hpp" @@ -42,6 +43,8 @@ namespace MWWorld ESM::Position mPosition; + ESM::AnimationState mAnimationState; + CustomData *mCustomData; void copy (const RefData& refData); @@ -132,6 +135,9 @@ namespace MWWorld bool hasChanged() const; ///< Has this RefData changed since it was originally loaded? + + const ESM::AnimationState& getAnimationState() const; + ESM::AnimationState& getAnimationState(); }; } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f50f30fa6..e5292eb6b 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -77,7 +77,7 @@ add_component_dir (esm loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile - aisequence magiceffects util custommarkerstate stolenitems transport + aisequence magiceffects util custommarkerstate stolenitems transport animationstate ) add_component_dir (esmterrain diff --git a/components/esm/animationstate.cpp b/components/esm/animationstate.cpp new file mode 100644 index 000000000..52dde258f --- /dev/null +++ b/components/esm/animationstate.cpp @@ -0,0 +1,37 @@ +#include "animationstate.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + void AnimationState::load(ESMReader& esm) + { + mScriptedAnims.clear(); + + while (esm.isNextSub("ANIS")) + { + ScriptedAnimation anim; + + anim.mGroup = esm.getHString(); + esm.getHNOT(anim.mTime, "TIME"); + esm.getHNOT(anim.mAbsolute, "ABST"); + esm.getHNT(anim.mLoopCount, "COUN"); + + mScriptedAnims.push_back(anim); + } + } + + void AnimationState::save(ESMWriter& esm) const + { + for (ScriptedAnimations::const_iterator iter = mScriptedAnims.begin(); iter != mScriptedAnims.end(); ++iter) + { + esm.writeHNString("ANIS", iter->mGroup); + if (iter->mTime > 0) + esm.writeHNT("TIME", iter->mTime); + if (iter->mAbsolute) + esm.writeHNT("ABST", iter->mAbsolute); + esm.writeHNT("COUN", iter->mLoopCount); + } + } +} diff --git a/components/esm/animationstate.hpp b/components/esm/animationstate.hpp new file mode 100644 index 000000000..73b6a41d0 --- /dev/null +++ b/components/esm/animationstate.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_ESM_ANIMATIONSTATE_H +#define OPENMW_ESM_ANIMATIONSTATE_H + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + struct AnimationState + { + struct ScriptedAnimation + { + ScriptedAnimation() + : mTime(0.f), mAbsolute(false), mLoopCount(0) {} + + std::string mGroup; + float mTime; + bool mAbsolute; + size_t mLoopCount; + }; + + typedef std::vector ScriptedAnimations; + ScriptedAnimations mScriptedAnims; + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; + }; +} + +#endif diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index b80c72ffe..18c030256 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -34,6 +34,8 @@ void ESM::ObjectState::load (ESMReader &esm) int unused; esm.getHNOT(unused, "LTIM"); + mAnimationState.load(esm); + // FIXME: assuming "false" as default would make more sense, but also break compatibility with older save files mHasCustomState = true; esm.getHNOT (mHasCustomState, "HCUS"); @@ -61,6 +63,8 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const if (mFlags != 0) esm.writeHNT ("FLAG", mFlags); + mAnimationState.save(esm); + if (!mHasCustomState) esm.writeHNT ("HCUS", false); } diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index 5b78074af..b8eb138eb 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -6,6 +6,7 @@ #include "cellref.hpp" #include "locals.hpp" +#include "animationstate.hpp" namespace ESM { @@ -31,6 +32,8 @@ namespace ESM unsigned int mVersion; + ESM::AnimationState mAnimationState; + ObjectState() : mHasCustomState(true), mVersion(0) {}