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"
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<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/npcstats.hpp>
#include <components/esm/loadskil.hpp>
#include <components/esm/animationstate.hpp>
#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

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

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

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

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

@ -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<MWWorld::Ptr>& out)
{
for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)

@ -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<MWWorld::Ptr>& out);

@ -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<std::string> &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();
}

@ -147,7 +147,13 @@ class CharacterController : public MWRender::Animation::TextKeyListener
MWWorld::Ptr mPtr;
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;
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);

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

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

@ -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<MWWorld::Ptr>& out)
{
for (PtrControllerMap::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter)

@ -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<MWWorld::Ptr>& out);
};

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

@ -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.
*/

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

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

@ -2,6 +2,7 @@
#define GAME_MWWORLD_REFDATA_H
#include <components/esm/defs.hpp>
#include <components/esm/animationstate.hpp>
#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();
};
}

@ -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

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

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

Loading…
Cancel
Save