Save scripted animation state (Fixes #1931, #2150, #3393)

coverity_scan^2
MiroslavR 9 years ago
parent 3374630e7b
commit b65f379b7f

@ -1,3 +1,9 @@
#include <string>
#include <iostream>
#include <limits>
#include <components/misc/stringops.hpp>
#include "convertacdt.hpp" #include "convertacdt.hpp"
namespace ESSImport namespace ESSImport
@ -49,4 +55,49 @@ namespace ESSImport
npcStats.mTimeToStartDrowning = actorData.mACDT.mBreathMeter; 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<size_t>::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<unsigned int>(anis.mGroupIndex) << std::endl;
}
} }

@ -4,6 +4,7 @@
#include <components/esm/creaturestats.hpp> #include <components/esm/creaturestats.hpp>
#include <components/esm/npcstats.hpp> #include <components/esm/npcstats.hpp>
#include <components/esm/loadskil.hpp> #include <components/esm/loadskil.hpp>
#include <components/esm/animationstate.hpp>
#include "importacdt.hpp" #include "importacdt.hpp"
@ -18,6 +19,8 @@ namespace ESSImport
void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats); void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats);
void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats); void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats);
void convertANIS (const ANIS& anis, ESM::AnimationState& state);
} }
#endif #endif

@ -34,6 +34,9 @@ namespace
objstate.mCount = 0; objstate.mCount = 0;
convertSCRI(cellref.mSCRI, objstate.mLocals); convertSCRI(cellref.mSCRI, objstate.mLocals);
objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); objstate.mHasLocals = !objstate.mLocals.mVariables.empty();
if (cellref.mHasANIS)
convertANIS(cellref.mANIS, objstate.mAnimationState);
} }
bool isIndexedRefId(const std::string& indexedRefId) bool isIndexedRefId(const std::string& indexedRefId)

@ -123,8 +123,13 @@ namespace ESSImport
if (esm.isNextSub("ND3D")) if (esm.isNextSub("ND3D"))
esm.skipHSub(); esm.skipHSub();
mHasANIS = false;
if (esm.isNextSub("ANIS")) if (esm.isNextSub("ANIS"))
esm.skipHSub(); {
mHasANIS = true;
esm.getHT(mANIS);
}
} }
} }

@ -55,6 +55,12 @@ namespace ESSImport
unsigned char mCorpseClearCountdown; // hours? unsigned char mCorpseClearCountdown; // hours?
unsigned char mUnknown3[71]; unsigned char mUnknown3[71];
}; };
struct ANIS
{
unsigned char mGroupIndex;
unsigned char mUnknown[3];
float mTime;
};
#pragma pack(pop) #pragma pack(pop)
struct ActorData : public ESM::CellRef struct ActorData : public ESM::CellRef
@ -77,6 +83,9 @@ namespace ESSImport
SCRI mSCRI; SCRI mSCRI;
bool mHasANIS;
ANIS mANIS; // scripted animation state
void load(ESM::ESMReader& esm); void load(ESM::ESMReader& esm);
}; };

@ -159,12 +159,14 @@ namespace MWBase
virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0; virtual void forceStateUpdate(const MWWorld::Ptr &ptr) = 0;
///< Forces an object to refresh its animation state. ///< 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 ///< Run animation for a MW-reference. Calls to this function for references that are currently not
/// in the scene should be ignored. /// in the scene should be ignored.
/// ///
/// \param mode 0 normal, 1 immediate start, 2 immediate loop /// \param mode 0 normal, 1 immediate start, 2 immediate loop
/// \param count How many times the animation should be run /// \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 /// \return Success or error
virtual void skipAnimation(const MWWorld::Ptr& ptr) = 0; 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; 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 /// 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) /// paused we may want to do it manually (after equipping permanent enchantment)
virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0;

@ -1389,12 +1389,12 @@ namespace MWMechanics
iter->second->getCharacterController()->forceStateUpdate(); 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); PtrActorMap::iterator iter = mActors.find(ptr);
if(iter != mActors.end()) if(iter != mActors.end())
{ {
return iter->second->getCharacterController()->playGroup(groupName, mode, number); return iter->second->getCharacterController()->playGroup(groupName, mode, number, persist);
} }
else else
{ {
@ -1417,6 +1417,12 @@ namespace MWMechanics
return false; 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<MWWorld::Ptr>& out) void Actors::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out)
{ {
for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)

@ -109,9 +109,10 @@ namespace MWMechanics
void forceStateUpdate(const MWWorld::Ptr &ptr); 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); void skipAnimation(const MWWorld::Ptr& ptr);
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
void persistAnimationStates();
void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out);

@ -762,13 +762,18 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
mAnimation->runAnimation(0.f); mAnimation->runAnimation(0.f);
unpersistAnimationState();
} }
CharacterController::~CharacterController() CharacterController::~CharacterController()
{ {
if (mAnimation) if (mAnimation)
{
persistAnimationState();
mAnimation->setTextKeyListener(NULL); mAnimation->setTextKeyListener(NULL);
} }
}
void split(const std::string &s, char delim, std::vector<std::string> &elems) { void split(const std::string &s, char delim, std::vector<std::string> &elems) {
std::stringstream ss(s); std::stringstream ss(s);
@ -1557,14 +1562,14 @@ void CharacterController::update(float duration)
{ {
if(mAnimQueue.size() > 1) 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(); mAnimQueue.pop_front();
mAnimation->play(mAnimQueue.front().first, Priority_Default, mAnimation->play(mAnimQueue.front().mGroup, Priority_Default,
MWRender::Animation::BlendMask_All, false, 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) 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(); mAnimQueue.pop_front();
mAnimation->play(mAnimQueue.front().first, Priority_Default, mAnimation->play(mAnimQueue.front().mGroup, Priority_Default,
MWRender::Animation::BlendMask_All, false, 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()); 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();
bool CharacterController::playGroup(const std::string &groupname, int mode, int count) 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);
}
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)) if(!mAnimation || !mAnimation->hasAnimation(groupname))
{ {
@ -1962,10 +2033,16 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int
else else
{ {
count = std::max(count, 1); 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(); clearAnimQueue();
mAnimQueue.push_back(std::make_pair(groupname, count-1)); mAnimQueue.push_back(entry);
mAnimation->disable(mCurrentIdle); mAnimation->disable(mCurrentIdle);
mCurrentIdle.clear(); mCurrentIdle.clear();
@ -1978,9 +2055,9 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int
else if(mode == 0) else if(mode == 0)
{ {
if (!mAnimQueue.empty()) if (!mAnimQueue.empty())
mAnimation->stopLooping(mAnimQueue.front().first); mAnimation->stopLooping(mAnimQueue.front().mGroup);
mAnimQueue.resize(1); mAnimQueue.resize(1);
mAnimQueue.push_back(std::make_pair(groupname, count-1)); mAnimQueue.push_back(entry);
} }
} }
return true; return true;
@ -2002,7 +2079,7 @@ bool CharacterController::isAnimPlaying(const std::string &groupName)
void CharacterController::clearAnimQueue() void CharacterController::clearAnimQueue()
{ {
if(!mAnimQueue.empty()) if(!mAnimQueue.empty())
mAnimation->disable(mAnimQueue.front().first); mAnimation->disable(mAnimQueue.front().mGroup);
mAnimQueue.clear(); mAnimQueue.clear();
} }

@ -147,7 +147,13 @@ class CharacterController : public MWRender::Animation::TextKeyListener
MWWorld::Ptr mPtr; MWWorld::Ptr mPtr;
MWRender::Animation *mAnimation; MWRender::Animation *mAnimation;
typedef std::deque<std::pair<std::string,size_t> > AnimationQueue; struct AnimationQueueEntry
{
std::string mGroup;
size_t mLoopCount;
bool mPersist;
};
typedef std::deque<AnimationQueueEntry> AnimationQueue;
AnimationQueue mAnimQueue; AnimationQueue mAnimQueue;
CharacterState mIdleState; CharacterState mIdleState;
@ -236,7 +242,10 @@ public:
void update(float duration); 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(); void skipAnim();
bool isAnimPlaying(const std::string &groupName); bool isAnimPlaying(const std::string &groupName);

@ -842,12 +842,12 @@ namespace MWMechanics
mActors.forceStateUpdate(ptr); 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()) if(ptr.getClass().isActor())
return mActors.playAnimationGroup(ptr, groupName, mode, number); return mActors.playAnimationGroup(ptr, groupName, mode, number, persist);
else else
return mObjects.playAnimationGroup(ptr, groupName, mode, number); return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist);
} }
void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr)
{ {
@ -864,6 +864,12 @@ namespace MWMechanics
return false; return false;
} }
void MechanicsManager::persistAnimationStates()
{
mActors.persistAnimationStates();
mObjects.persistAnimationStates();
}
void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr) void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr)
{ {
mActors.updateMagicEffects(ptr); mActors.updateMagicEffects(ptr);

@ -146,9 +146,10 @@ namespace MWMechanics
/// Attempt to play an animation group /// Attempt to play an animation group
/// @return Success or error /// @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 void skipAnimation(const MWWorld::Ptr& ptr);
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); 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 /// 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) /// paused we may want to do it manually (after equipping permanent enchantment)

@ -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); PtrControllerMap::iterator iter = mObjects.find(ptr);
if(iter != mObjects.end()) if(iter != mObjects.end())
{ {
return iter->second->playGroup(groupName, mode, number); return iter->second->playGroup(groupName, mode, number, persist);
} }
else else
{ {
@ -99,6 +99,12 @@ void Objects::skipAnimation(const MWWorld::Ptr& ptr)
iter->second->skipAnim(); 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<MWWorld::Ptr>& out) void Objects::getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out)
{ {
for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter)

@ -38,8 +38,9 @@ namespace MWMechanics
void update(float duration, bool paused); void update(float duration, bool paused);
///< Update object animations ///< 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 skipAnimation(const MWWorld::Ptr& ptr);
void persistAnimationStates();
void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out); void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out);
}; };

@ -843,6 +843,15 @@ namespace MWRender
return iter->second.getTime(); 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) void Animation::disable(const std::string &groupname)
{ {
AnimStateMap::iterator iter = mStates.find(groupname); AnimStateMap::iterator iter = mStates.find(groupname);

@ -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. /// 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; float getCurrentTime(const std::string& groupname) const;
size_t getCurrentLoopCount(const std::string& groupname) const;
/** Disables the specified animation group; /** Disables the specified animation group;
* \param groupname Animation group to disable. * \param groupname Animation group to disable.
*/ */

@ -56,7 +56,7 @@ namespace MWScript
throw std::runtime_error ("animation mode out of range"); throw std::runtime_error ("animation mode out of range");
} }
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, std::numeric_limits<int>::max()); MWBase::Environment::get().getMechanicsManager()->playAnimationGroup (ptr, group, mode, std::numeric_limits<int>::max(), true);
} }
}; };
@ -89,7 +89,7 @@ namespace MWScript
throw std::runtime_error ("animation mode out of range"); 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);
} }
}; };

@ -219,6 +219,9 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
else else
slot = character->updateSlot (slot, profile); 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 // 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. // existing save file we are overwriting.
std::stringstream stream; std::stringstream stream;

@ -31,6 +31,8 @@ namespace MWWorld
mDeletedByContentFile = refData.mDeletedByContentFile; mDeletedByContentFile = refData.mDeletedByContentFile;
mFlags = refData.mFlags; mFlags = refData.mFlags;
mAnimationState = refData.mAnimationState;
mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0; mCustomData = refData.mCustomData ? refData.mCustomData->clone() : 0;
} }
@ -65,6 +67,7 @@ namespace MWWorld
mEnabled (objectState.mEnabled != 0), mEnabled (objectState.mEnabled != 0),
mCount (objectState.mCount), mCount (objectState.mCount),
mPosition (objectState.mPosition), mPosition (objectState.mPosition),
mAnimationState(objectState.mAnimationState),
mCustomData (0), mCustomData (0),
mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed
{ {
@ -96,6 +99,8 @@ namespace MWWorld
objectState.mCount = mCount; objectState.mCount = mCount;
objectState.mPosition = mPosition; objectState.mPosition = mPosition;
objectState.mFlags = mFlags; objectState.mFlags = mFlags;
objectState.mAnimationState = mAnimationState;
} }
RefData& RefData::operator= (const RefData& refData) RefData& RefData::operator= (const RefData& refData)
@ -269,4 +274,15 @@ namespace MWWorld
else else
return false; return false;
} }
const ESM::AnimationState& RefData::getAnimationState() const
{
return mAnimationState;
}
ESM::AnimationState& RefData::getAnimationState()
{
return mAnimationState;
}
} }

@ -2,6 +2,7 @@
#define GAME_MWWORLD_REFDATA_H #define GAME_MWWORLD_REFDATA_H
#include <components/esm/defs.hpp> #include <components/esm/defs.hpp>
#include <components/esm/animationstate.hpp>
#include "../mwscript/locals.hpp" #include "../mwscript/locals.hpp"
@ -42,6 +43,8 @@ namespace MWWorld
ESM::Position mPosition; ESM::Position mPosition;
ESM::AnimationState mAnimationState;
CustomData *mCustomData; CustomData *mCustomData;
void copy (const RefData& refData); void copy (const RefData& refData);
@ -132,6 +135,9 @@ namespace MWWorld
bool hasChanged() const; bool hasChanged() const;
///< Has this RefData changed since it was originally loaded? ///< Has this RefData changed since it was originally loaded?
const ESM::AnimationState& getAnimationState() const;
ESM::AnimationState& getAnimationState();
}; };
} }

@ -77,7 +77,7 @@ add_component_dir (esm
loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter 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 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 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 add_component_dir (esmterrain

@ -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);
}
}
}

@ -0,0 +1,34 @@
#ifndef OPENMW_ESM_ANIMATIONSTATE_H
#define OPENMW_ESM_ANIMATIONSTATE_H
#include <string>
#include <vector>
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<ScriptedAnimation> ScriptedAnimations;
ScriptedAnimations mScriptedAnims;
void load(ESMReader& esm);
void save(ESMWriter& esm) const;
};
}
#endif

@ -34,6 +34,8 @@ void ESM::ObjectState::load (ESMReader &esm)
int unused; int unused;
esm.getHNOT(unused, "LTIM"); esm.getHNOT(unused, "LTIM");
mAnimationState.load(esm);
// FIXME: assuming "false" as default would make more sense, but also break compatibility with older save files // FIXME: assuming "false" as default would make more sense, but also break compatibility with older save files
mHasCustomState = true; mHasCustomState = true;
esm.getHNOT (mHasCustomState, "HCUS"); esm.getHNOT (mHasCustomState, "HCUS");
@ -61,6 +63,8 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const
if (mFlags != 0) if (mFlags != 0)
esm.writeHNT ("FLAG", mFlags); esm.writeHNT ("FLAG", mFlags);
mAnimationState.save(esm);
if (!mHasCustomState) if (!mHasCustomState)
esm.writeHNT ("HCUS", false); esm.writeHNT ("HCUS", false);
} }

@ -6,6 +6,7 @@
#include "cellref.hpp" #include "cellref.hpp"
#include "locals.hpp" #include "locals.hpp"
#include "animationstate.hpp"
namespace ESM namespace ESM
{ {
@ -31,6 +32,8 @@ namespace ESM
unsigned int mVersion; unsigned int mVersion;
ESM::AnimationState mAnimationState;
ObjectState() : mHasCustomState(true), mVersion(0) ObjectState() : mHasCustomState(true), mVersion(0)
{} {}

Loading…
Cancel
Save