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:
parent
b2bb12cd19
commit
d34aee6257
5 changed files with 61 additions and 49 deletions
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue