From d34aee6257499b2a015ce0d2be4792794f3baa49 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 7 Nov 2024 16:37:28 +0100 Subject: [PATCH] Allow unsetting a character controller's animation --- apps/openmw/mwmechanics/actor.hpp | 19 ++++--- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/character.cpp | 72 +++++++++++++++------------ apps/openmw/mwmechanics/character.hpp | 15 +++--- apps/openmw/mwmechanics/objects.cpp | 2 +- 5 files changed, 61 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp index 6deaba3a95..69e370ec86 100644 --- a/apps/openmw/mwmechanics/actor.hpp +++ b/apps/openmw/mwmechanics/actor.hpp @@ -2,7 +2,6 @@ #define OPENMW_MECHANICS_ACTOR_H #include -#include #include "character.hpp" #include "creaturestats.hpp" @@ -29,19 +28,19 @@ namespace MWMechanics class Actor { public: - Actor(const MWWorld::Ptr& ptr, MWRender::Animation* animation) - : mPositionAdjusted(ptr.getClass().getCreatureStats(ptr).getFallHeight() > 0) + Actor(const MWWorld::Ptr& ptr, MWRender::Animation& animation) + : 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 - void updatePtr(const MWWorld::Ptr& newPtr) { mCharacterController->updatePtr(newPtr); } + void updatePtr(const MWWorld::Ptr& newPtr) { mCharacterController.updatePtr(newPtr); } - CharacterController& getCharacterController() { return *mCharacterController; } - const CharacterController& getCharacterController() const { return *mCharacterController; } + CharacterController& getCharacterController() { return mCharacterController; } + const CharacterController& getCharacterController() const { return mCharacterController; } int getGreetingTimer() const { return mGreetingTimer; } void setGreetingTimer(int timer) { mGreetingTimer = timer; } @@ -66,12 +65,12 @@ namespace MWMechanics void invalidate() { mInvalid = true; - mCharacterController.reset(); + mCharacterController.detachAnimation(); } bool isInvalid() const { return mInvalid; } private: - std::optional mCharacterController; + CharacterController mCharacterController; int mGreetingTimer{ 0 }; float mTargetAngleRadians{ 0.f }; GreetingState mGreetingState{ Greet_None }; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 6d1cdfcb3d..0df37875f6 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1197,7 +1197,7 @@ namespace MWMechanics MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (!anim) return; - const auto it = mActors.emplace(mActors.end(), ptr, anim); + const auto it = mActors.emplace(mActors.end(), ptr, *anim); mIndex.emplace(ptr.mRef, it); if (updateImmediately) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 87cc469eb4..651bff3999 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -535,7 +535,7 @@ namespace MWMechanics bool CharacterController::onOpen() const { - if (mPtr.getType() == ESM::Container::sRecordId) + if (mPtr.getType() == ESM::Container::sRecordId && mAnimation) { if (!mAnimation->hasAnimation("containeropen")) return true; @@ -559,7 +559,7 @@ namespace MWMechanics { if (mPtr.getType() == ESM::Container::sRecordId) { - if (!mAnimation->hasAnimation("containerclose")) + if (!mAnimation || !mAnimation->hasAnimation("containerclose")) return; float complete, startPoint = 0.f; @@ -883,14 +883,16 @@ namespace MWMechanics } mDeathState = hitStateToDeathState(mHitState); - if (mDeathState == CharState_None && MWBase::Environment::get().getWorld()->isSwimming(mPtr)) - mDeathState = CharState_SwimDeath; - - if (mDeathState == CharState_None || !mAnimation->hasAnimation(deathStateToAnimGroup(mDeathState))) - mDeathState = chooseRandomDeathState(); + if (mDeathState == CharState_None) + { + if (MWBase::Environment::get().getWorld()->isSwimming(mPtr)) + mDeathState = CharState_SwimDeath; + else if (mAnimation && !mAnimation->hasAnimation(deathStateToAnimGroup(mDeathState))) + mDeathState = chooseRandomDeathState(); + } // Do not interrupt scripted animation by death - if (isScriptedAnimPlaying()) + if (!mAnimation || isScriptedAnimPlaying()) return; playDeath(startpoint, mDeathState); @@ -910,13 +912,10 @@ namespace MWMechanics return result; } - CharacterController::CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim) + CharacterController::CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation& anim) : mPtr(ptr) - , mAnimation(anim) + , mAnimation(&anim) { - if (!mAnimation) - return; - mAnimation->setTextKeyListener(this); const MWWorld::Class& cls = mPtr.getClass(); @@ -992,11 +991,17 @@ namespace MWMechanics } CharacterController::~CharacterController() + { + detachAnimation(); + } + + void CharacterController::detachAnimation() { if (mAnimation) { persistAnimationState(); mAnimation->setTextKeyListener(nullptr); + mAnimation = nullptr; } } @@ -1066,7 +1071,7 @@ namespace MWMechanics std::string_view action = evt.substr(groupname.size() + 2); if (action == "equip attach") { - if (mUpperBodyState == UpperBodyState::Equipping) + if (mUpperBodyState == UpperBodyState::Equipping && mAnimation) { if (groupname == "shield") mAnimation->showCarriedLeft(true); @@ -1076,7 +1081,7 @@ namespace MWMechanics } else if (action == "unequip detach") { - if (mUpperBodyState == UpperBodyState::Unequipping) + if (mUpperBodyState == UpperBodyState::Unequipping && mAnimation) { if (groupname == "shield") mAnimation->showCarriedLeft(false); @@ -1148,18 +1153,18 @@ namespace MWMechanics mPtr, mAttackStrength, ESM::Weapon::AT_Thrust, mAttackVictim, mAttackHitPos, mAttackSuccess); } } - else if (action == "shoot attach") + else if (action == "shoot attach" && mAnimation) mAnimation->attachArrow(); else if (action == "shoot release") { // See notes for melee release above - if (mReadyToHit) + if (mReadyToHit && mAnimation) { mAnimation->releaseArrow(mAttackStrength); mReadyToHit = false; } } - else if (action == "shoot follow attach") + else if (action == "shoot follow attach" && mAnimation) mAnimation->attachArrow(); // 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 @@ -1232,7 +1237,8 @@ namespace MWMechanics 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; float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon + ": " + mAttackType + " min attack"); @@ -1950,6 +1956,8 @@ namespace MWMechanics void CharacterController::update(float duration) { + if (!mAnimation) + return; MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); const MWWorld::Class& cls = mPtr.getClass(); @@ -2528,7 +2536,7 @@ namespace MWMechanics ESM::AnimationState::ScriptedAnimation anim; anim.mGroup = iter->mGroup; - if (iter == mAnimQueue.begin()) + if (iter == mAnimQueue.begin() && mAnimation) { float complete; size_t loopcount; @@ -2741,23 +2749,18 @@ namespace MWMechanics void CharacterController::clearAnimQueue(bool clearScriptedAnims) { // 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); if (clearScriptedAnims) { - mAnimation->setPlayScriptedOnly(false); + if (mAnimation) + mAnimation->setPlayScriptedOnly(false); mAnimQueue.clear(); return; } - for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) - { - if (!it->mScripted) - it = mAnimQueue.erase(it); - else - ++it; - } + std::erase_if(mAnimQueue, [](const AnimationQueueEntry& entry) { return !entry.mScripted; }); } void CharacterController::forceStateUpdate() @@ -2866,6 +2869,8 @@ namespace MWMechanics void CharacterController::setVisibility(float visibility) const { + if (!mAnimation) + return; // We should take actor's invisibility in account if (mPtr.getClass().isActor()) { @@ -2926,7 +2931,7 @@ namespace MWMechanics bool CharacterController::isReadyToBlock() const { - return updateCarriedLeftVisible(mWeaponType); + return mAnimation && updateCarriedLeftVisible(mWeaponType); } bool CharacterController::isKnockedDown() const @@ -3030,7 +3035,8 @@ namespace MWMechanics void CharacterController::setActive(int active) const { - mAnimation->setActive(active); + if (mAnimation) + mAnimation->setActive(active); } void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr& target) @@ -3061,6 +3067,8 @@ namespace MWMechanics float CharacterController::getAnimationMovementDirection() const { + if (!mAnimation) + return 0.f; switch (mMovementState) { case CharState_RunLeft: @@ -3155,6 +3163,8 @@ namespace MWMechanics MWWorld::MovementDirectionFlags CharacterController::getSupportedMovementDirections() const { + if (!mAnimation) + return 0; using namespace std::string_view_literals; // There are fallbacks in the CharacterController::refreshMovementAnims for certain animations. Arrays below // represent them. diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index d5c642c883..2a1982c664 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -252,13 +252,21 @@ namespace MWMechanics 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: - CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim); + CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation& anim); virtual ~CharacterController(); CharacterController(const CharacterController&) = delete; CharacterController(CharacterController&&) = delete; + void detachAnimation(); + const MWWorld::Ptr& getPtr() const { return mPtr; } void handleTextKey(std::string_view groupname, SceneUtil::TextKeyMap::ConstIterator key, @@ -275,11 +283,6 @@ namespace MWMechanics void onClose() 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 playGroupLua(std::string_view groupname, float speed, std::string_view startKey, std::string_view stopKey, uint32_t loops, bool forceLoop); diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index 12d342666b..62f0df556d 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -20,7 +20,7 @@ namespace MWMechanics if (anim == nullptr) return; - const auto it = mObjects.emplace(mObjects.end(), ptr, anim); + const auto it = mObjects.emplace(mObjects.end(), ptr, *anim); mIndex.emplace(ptr.mRef, it); }