1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-13 19:13:06 +00:00

Allow unsetting a character controller's animation

This commit is contained in:
Evil Eye 2024-11-07 16:37:28 +01:00
parent b2bb12cd19
commit d34aee6257
5 changed files with 61 additions and 49 deletions

View file

@ -2,7 +2,6 @@
#define OPENMW_MECHANICS_ACTOR_H #define OPENMW_MECHANICS_ACTOR_H
#include <memory> #include <memory>
#include <optional>
#include "character.hpp" #include "character.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
@ -29,19 +28,19 @@ namespace MWMechanics
class Actor class Actor
{ {
public: public:
Actor(const MWWorld::Ptr& ptr, MWRender::Animation* animation) Actor(const MWWorld::Ptr& ptr, MWRender::Animation& animation)
: mPositionAdjusted(ptr.getClass().getCreatureStats(ptr).getFallHeight() > 0) : mCharacterController(ptr, animation)
, mPositionAdjusted(ptr.getClass().getCreatureStats(ptr).getFallHeight() > 0)
{ {
mCharacterController.emplace(ptr, animation);
} }
const MWWorld::Ptr& getPtr() const { return mCharacterController->getPtr(); } const MWWorld::Ptr& getPtr() const { return mCharacterController.getPtr(); }
/// Notify this actor of its new base object Ptr, use when the object changed cells /// Notify this actor of its new base object Ptr, use when the object changed cells
void updatePtr(const MWWorld::Ptr& newPtr) { mCharacterController->updatePtr(newPtr); } void updatePtr(const MWWorld::Ptr& newPtr) { mCharacterController.updatePtr(newPtr); }
CharacterController& getCharacterController() { return *mCharacterController; } CharacterController& getCharacterController() { return mCharacterController; }
const CharacterController& getCharacterController() const { return *mCharacterController; } const CharacterController& getCharacterController() const { return mCharacterController; }
int getGreetingTimer() const { return mGreetingTimer; } int getGreetingTimer() const { return mGreetingTimer; }
void setGreetingTimer(int timer) { mGreetingTimer = timer; } void setGreetingTimer(int timer) { mGreetingTimer = timer; }
@ -66,12 +65,12 @@ namespace MWMechanics
void invalidate() void invalidate()
{ {
mInvalid = true; mInvalid = true;
mCharacterController.reset(); mCharacterController.detachAnimation();
} }
bool isInvalid() const { return mInvalid; } bool isInvalid() const { return mInvalid; }
private: private:
std::optional<CharacterController> mCharacterController; CharacterController mCharacterController;
int mGreetingTimer{ 0 }; int mGreetingTimer{ 0 };
float mTargetAngleRadians{ 0.f }; float mTargetAngleRadians{ 0.f };
GreetingState mGreetingState{ Greet_None }; GreetingState mGreetingState{ Greet_None };

View file

@ -1197,7 +1197,7 @@ namespace MWMechanics
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(ptr);
if (!anim) if (!anim)
return; return;
const auto it = mActors.emplace(mActors.end(), ptr, anim); const auto it = mActors.emplace(mActors.end(), ptr, *anim);
mIndex.emplace(ptr.mRef, it); mIndex.emplace(ptr.mRef, it);
if (updateImmediately) if (updateImmediately)

View file

@ -535,7 +535,7 @@ namespace MWMechanics
bool CharacterController::onOpen() const bool CharacterController::onOpen() const
{ {
if (mPtr.getType() == ESM::Container::sRecordId) if (mPtr.getType() == ESM::Container::sRecordId && mAnimation)
{ {
if (!mAnimation->hasAnimation("containeropen")) if (!mAnimation->hasAnimation("containeropen"))
return true; return true;
@ -559,7 +559,7 @@ namespace MWMechanics
{ {
if (mPtr.getType() == ESM::Container::sRecordId) if (mPtr.getType() == ESM::Container::sRecordId)
{ {
if (!mAnimation->hasAnimation("containerclose")) if (!mAnimation || !mAnimation->hasAnimation("containerclose"))
return; return;
float complete, startPoint = 0.f; float complete, startPoint = 0.f;
@ -883,14 +883,16 @@ namespace MWMechanics
} }
mDeathState = hitStateToDeathState(mHitState); mDeathState = hitStateToDeathState(mHitState);
if (mDeathState == CharState_None && MWBase::Environment::get().getWorld()->isSwimming(mPtr)) if (mDeathState == CharState_None)
mDeathState = CharState_SwimDeath; {
if (MWBase::Environment::get().getWorld()->isSwimming(mPtr))
if (mDeathState == CharState_None || !mAnimation->hasAnimation(deathStateToAnimGroup(mDeathState))) mDeathState = CharState_SwimDeath;
mDeathState = chooseRandomDeathState(); else if (mAnimation && !mAnimation->hasAnimation(deathStateToAnimGroup(mDeathState)))
mDeathState = chooseRandomDeathState();
}
// Do not interrupt scripted animation by death // Do not interrupt scripted animation by death
if (isScriptedAnimPlaying()) if (!mAnimation || isScriptedAnimPlaying())
return; return;
playDeath(startpoint, mDeathState); playDeath(startpoint, mDeathState);
@ -910,13 +912,10 @@ namespace MWMechanics
return result; return result;
} }
CharacterController::CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim) CharacterController::CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation& anim)
: mPtr(ptr) : mPtr(ptr)
, mAnimation(anim) , mAnimation(&anim)
{ {
if (!mAnimation)
return;
mAnimation->setTextKeyListener(this); mAnimation->setTextKeyListener(this);
const MWWorld::Class& cls = mPtr.getClass(); const MWWorld::Class& cls = mPtr.getClass();
@ -992,11 +991,17 @@ namespace MWMechanics
} }
CharacterController::~CharacterController() CharacterController::~CharacterController()
{
detachAnimation();
}
void CharacterController::detachAnimation()
{ {
if (mAnimation) if (mAnimation)
{ {
persistAnimationState(); persistAnimationState();
mAnimation->setTextKeyListener(nullptr); mAnimation->setTextKeyListener(nullptr);
mAnimation = nullptr;
} }
} }
@ -1066,7 +1071,7 @@ namespace MWMechanics
std::string_view action = evt.substr(groupname.size() + 2); std::string_view action = evt.substr(groupname.size() + 2);
if (action == "equip attach") if (action == "equip attach")
{ {
if (mUpperBodyState == UpperBodyState::Equipping) if (mUpperBodyState == UpperBodyState::Equipping && mAnimation)
{ {
if (groupname == "shield") if (groupname == "shield")
mAnimation->showCarriedLeft(true); mAnimation->showCarriedLeft(true);
@ -1076,7 +1081,7 @@ namespace MWMechanics
} }
else if (action == "unequip detach") else if (action == "unequip detach")
{ {
if (mUpperBodyState == UpperBodyState::Unequipping) if (mUpperBodyState == UpperBodyState::Unequipping && mAnimation)
{ {
if (groupname == "shield") if (groupname == "shield")
mAnimation->showCarriedLeft(false); mAnimation->showCarriedLeft(false);
@ -1148,18 +1153,18 @@ namespace MWMechanics
mPtr, mAttackStrength, ESM::Weapon::AT_Thrust, mAttackVictim, mAttackHitPos, mAttackSuccess); mPtr, mAttackStrength, ESM::Weapon::AT_Thrust, mAttackVictim, mAttackHitPos, mAttackSuccess);
} }
} }
else if (action == "shoot attach") else if (action == "shoot attach" && mAnimation)
mAnimation->attachArrow(); mAnimation->attachArrow();
else if (action == "shoot release") else if (action == "shoot release")
{ {
// See notes for melee release above // See notes for melee release above
if (mReadyToHit) if (mReadyToHit && mAnimation)
{ {
mAnimation->releaseArrow(mAttackStrength); mAnimation->releaseArrow(mAttackStrength);
mReadyToHit = false; mReadyToHit = false;
} }
} }
else if (action == "shoot follow attach") else if (action == "shoot follow attach" && mAnimation)
mAnimation->attachArrow(); mAnimation->attachArrow();
// Make sure this key is actually for the RangeType we are casting. The flame atronach has // Make sure this key is actually for the RangeType we are casting. The flame atronach has
// the same animation for all range types, so there are 3 "release" keys on the same time, one for each range // the same animation for all range types, so there are 3 "release" keys on the same time, one for each range
@ -1232,7 +1237,8 @@ namespace MWMechanics
float CharacterController::calculateWindUp() const float CharacterController::calculateWindUp() const
{ {
if (mCurrentWeapon.empty() || mWeaponType == ESM::Weapon::PickProbe || isRandomAttackAnimation(mCurrentWeapon)) if (mCurrentWeapon.empty() || mWeaponType == ESM::Weapon::PickProbe || isRandomAttackAnimation(mCurrentWeapon)
|| !mAnimation)
return -1.f; return -1.f;
float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " min attack"); float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " min attack");
@ -1950,6 +1956,8 @@ namespace MWMechanics
void CharacterController::update(float duration) void CharacterController::update(float duration)
{ {
if (!mAnimation)
return;
MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::World* world = MWBase::Environment::get().getWorld();
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
const MWWorld::Class& cls = mPtr.getClass(); const MWWorld::Class& cls = mPtr.getClass();
@ -2528,7 +2536,7 @@ namespace MWMechanics
ESM::AnimationState::ScriptedAnimation anim; ESM::AnimationState::ScriptedAnimation anim;
anim.mGroup = iter->mGroup; anim.mGroup = iter->mGroup;
if (iter == mAnimQueue.begin()) if (iter == mAnimQueue.begin() && mAnimation)
{ {
float complete; float complete;
size_t loopcount; size_t loopcount;
@ -2741,23 +2749,18 @@ namespace MWMechanics
void CharacterController::clearAnimQueue(bool clearScriptedAnims) void CharacterController::clearAnimQueue(bool clearScriptedAnims)
{ {
// Do not interrupt scripted animations, if we want to keep them // Do not interrupt scripted animations, if we want to keep them
if ((!isScriptedAnimPlaying() || clearScriptedAnims) && !mAnimQueue.empty()) if (mAnimation && (!isScriptedAnimPlaying() || clearScriptedAnims) && !mAnimQueue.empty())
mAnimation->disable(mAnimQueue.front().mGroup); mAnimation->disable(mAnimQueue.front().mGroup);
if (clearScriptedAnims) if (clearScriptedAnims)
{ {
mAnimation->setPlayScriptedOnly(false); if (mAnimation)
mAnimation->setPlayScriptedOnly(false);
mAnimQueue.clear(); mAnimQueue.clear();
return; return;
} }
for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) std::erase_if(mAnimQueue, [](const AnimationQueueEntry& entry) { return !entry.mScripted; });
{
if (!it->mScripted)
it = mAnimQueue.erase(it);
else
++it;
}
} }
void CharacterController::forceStateUpdate() void CharacterController::forceStateUpdate()
@ -2866,6 +2869,8 @@ namespace MWMechanics
void CharacterController::setVisibility(float visibility) const void CharacterController::setVisibility(float visibility) const
{ {
if (!mAnimation)
return;
// We should take actor's invisibility in account // We should take actor's invisibility in account
if (mPtr.getClass().isActor()) if (mPtr.getClass().isActor())
{ {
@ -2926,7 +2931,7 @@ namespace MWMechanics
bool CharacterController::isReadyToBlock() const bool CharacterController::isReadyToBlock() const
{ {
return updateCarriedLeftVisible(mWeaponType); return mAnimation && updateCarriedLeftVisible(mWeaponType);
} }
bool CharacterController::isKnockedDown() const bool CharacterController::isKnockedDown() const
@ -3030,7 +3035,8 @@ namespace MWMechanics
void CharacterController::setActive(int active) const void CharacterController::setActive(int active) const
{ {
mAnimation->setActive(active); if (mAnimation)
mAnimation->setActive(active);
} }
void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr& target) void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr& target)
@ -3061,6 +3067,8 @@ namespace MWMechanics
float CharacterController::getAnimationMovementDirection() const float CharacterController::getAnimationMovementDirection() const
{ {
if (!mAnimation)
return 0.f;
switch (mMovementState) switch (mMovementState)
{ {
case CharState_RunLeft: case CharState_RunLeft:
@ -3155,6 +3163,8 @@ namespace MWMechanics
MWWorld::MovementDirectionFlags CharacterController::getSupportedMovementDirections() const MWWorld::MovementDirectionFlags CharacterController::getSupportedMovementDirections() const
{ {
if (!mAnimation)
return 0;
using namespace std::string_view_literals; using namespace std::string_view_literals;
// There are fallbacks in the CharacterController::refreshMovementAnims for certain animations. Arrays below // There are fallbacks in the CharacterController::refreshMovementAnims for certain animations. Arrays below
// represent them. // represent them.

View file

@ -252,13 +252,21 @@ namespace MWMechanics
void prepareHit(); void prepareHit();
void unpersistAnimationState();
void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask,
bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint,
uint32_t loops, bool loopfallback = false) const;
public: public:
CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim); CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation& anim);
virtual ~CharacterController(); virtual ~CharacterController();
CharacterController(const CharacterController&) = delete; CharacterController(const CharacterController&) = delete;
CharacterController(CharacterController&&) = delete; CharacterController(CharacterController&&) = delete;
void detachAnimation();
const MWWorld::Ptr& getPtr() const { return mPtr; } const MWWorld::Ptr& getPtr() const { return mPtr; }
void handleTextKey(std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key, void handleTextKey(std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key,
@ -275,11 +283,6 @@ namespace MWMechanics
void onClose() const; void onClose() const;
void persistAnimationState() const; void persistAnimationState() const;
void unpersistAnimationState();
void playBlendedAnimation(const std::string& groupname, const MWRender::AnimPriority& priority, int blendMask,
bool autodisable, float speedmult, std::string_view start, std::string_view stop, float startpoint,
uint32_t loops, bool loopfallback = false) const;
bool playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted = false); bool playGroup(std::string_view groupname, int mode, uint32_t count, bool scripted = false);
bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey, bool playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey,
uint32_t loops, bool forceLoop); uint32_t loops, bool forceLoop);

View file

@ -20,7 +20,7 @@ namespace MWMechanics
if (anim == nullptr) if (anim == nullptr)
return; return;
const auto it = mObjects.emplace(mObjects.end(), ptr, anim); const auto it = mObjects.emplace(mObjects.end(), ptr, *anim);
mIndex.emplace(ptr.mRef, it); mIndex.emplace(ptr.mRef, it);
} }