mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-21 18:40:01 +00:00
More cleanup of scripted animations
This commit is contained in:
parent
7cdf702a14
commit
f6a6c278dd
@ -11,10 +11,12 @@
|
|||||||
Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel
|
Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel
|
||||||
Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely
|
Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely
|
||||||
Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward
|
Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward
|
||||||
|
Bug #4743: PlayGroup doesn't play non-looping animations correctly
|
||||||
Bug #4754: Stack of ammunition cannot be equipped partially
|
Bug #4754: Stack of ammunition cannot be equipped partially
|
||||||
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
|
Bug #4816: GetWeaponDrawn returns 1 before weapon is attached
|
||||||
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses
|
Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses
|
||||||
Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation
|
Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation
|
||||||
|
Bug #5066: Quirks with starting and stopping scripted animations
|
||||||
Bug #5129: Stuttering animation on Centurion Archer
|
Bug #5129: Stuttering animation on Centurion Archer
|
||||||
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place
|
Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place
|
||||||
Bug #5371: Keyframe animation tracks are used for any file that begins with an X
|
Bug #5371: Keyframe animation tracks are used for any file that begins with an X
|
||||||
@ -91,6 +93,7 @@
|
|||||||
Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation
|
Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation
|
||||||
Bug #7637: Actors can sometimes move while playing scripted animations
|
Bug #7637: Actors can sometimes move while playing scripted animations
|
||||||
Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat
|
Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat
|
||||||
|
Bug #7641: loopgroup loops the animation one time too many for actors
|
||||||
Bug #7642: Items in repair and recharge menus aren't sorted alphabetically
|
Bug #7642: Items in repair and recharge menus aren't sorted alphabetically
|
||||||
Bug #7647: NPC walk cycle bugs after greeting player
|
Bug #7647: NPC walk cycle bugs after greeting player
|
||||||
Bug #7654: Tooltips for enchantments with invalid effects cause crashes
|
Bug #7654: Tooltips for enchantments with invalid effects cause crashes
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "character.hpp"
|
#include "character.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include <components/esm/records.hpp>
|
#include <components/esm/records.hpp>
|
||||||
#include <components/misc/mathutil.hpp>
|
#include <components/misc/mathutil.hpp>
|
||||||
@ -1189,7 +1190,7 @@ namespace MWMechanics
|
|||||||
if (!animPlaying)
|
if (!animPlaying)
|
||||||
{
|
{
|
||||||
int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm;
|
int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm;
|
||||||
mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul);
|
mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1246,8 +1247,47 @@ namespace MWMechanics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CharacterController::isLoopingAnimation(std::string_view group) const
|
||||||
|
{
|
||||||
|
// In Morrowind, a some animation groups are always considered looping, regardless
|
||||||
|
// of loop start/stop keys.
|
||||||
|
// To be match vanilla behavior we probably only need to check this list, but we don't
|
||||||
|
// want to prevent modded animations with custom group names from looping either.
|
||||||
|
static const std::unordered_set<std::string_view> loopingAnimations = { "walkforward", "walkback", "walkleft",
|
||||||
|
"walkright", "swimwalkforward", "swimwalkback", "swimwalkleft", "swimwalkright", "runforward", "runback",
|
||||||
|
"runleft", "runright", "swimrunforward", "swimrunback", "swimrunleft", "swimrunright", "sneakforward",
|
||||||
|
"sneakback", "sneakleft", "sneakright", "turnleft", "turnright", "swimturnleft", "swimturnright",
|
||||||
|
"spellturnleft", "spellturnright", "torch", "idle", "idle2", "idle3", "idle4", "idle5", "idle6", "idle7",
|
||||||
|
"idle8", "idle9", "idlesneak", "idlestorm", "idleswim", "jump", "inventoryhandtohand",
|
||||||
|
"inventoryweapononehand", "inventoryweapontwohand", "inventoryweapontwowide" };
|
||||||
|
static const std::vector<std::string_view> shortGroups = getAllWeaponTypeShortGroups();
|
||||||
|
|
||||||
|
if (mAnimation && mAnimation->getTextKeyTime(std::string(group) + ": loop start") >= 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Most looping animations have variants for each weapon type shortgroup.
|
||||||
|
// Just remove the shortgroup instead of enumerating all of the possible animation groupnames.
|
||||||
|
// Make sure we pick the longest shortgroup so e.g. "bow" doesn't get picked over "crossbow"
|
||||||
|
// when the shortgroup is crossbow.
|
||||||
|
std::size_t suffixLength = 0;
|
||||||
|
for (std::string_view suffix : shortGroups)
|
||||||
|
{
|
||||||
|
if (suffix.length() > suffixLength && group.ends_with(suffix))
|
||||||
|
{
|
||||||
|
suffixLength = suffix.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group.remove_suffix(suffixLength);
|
||||||
|
|
||||||
|
return loopingAnimations.count(group) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool CharacterController::updateWeaponState()
|
bool CharacterController::updateWeaponState()
|
||||||
{
|
{
|
||||||
|
// If the current animation is scripted, we can't do anything here.
|
||||||
|
if (isScriptedAnimPlaying())
|
||||||
|
return false;
|
||||||
|
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
auto& prng = world->getPrng();
|
auto& prng = world->getPrng();
|
||||||
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
@ -1481,10 +1521,6 @@ namespace MWMechanics
|
|||||||
sndMgr->stopSound3D(mPtr, wolfRun);
|
sndMgr->stopSound3D(mPtr, wolfRun);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combat for actors with scripted animations obviously will be buggy
|
|
||||||
if (isScriptedAnimPlaying())
|
|
||||||
return forcestateupdate;
|
|
||||||
|
|
||||||
float complete = 0.f;
|
float complete = 0.f;
|
||||||
bool animPlaying = false;
|
bool animPlaying = false;
|
||||||
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
|
||||||
@ -1857,33 +1893,58 @@ namespace MWMechanics
|
|||||||
|
|
||||||
if (!mAnimation->isPlaying(mAnimQueue.front().mGroup))
|
if (!mAnimation->isPlaying(mAnimQueue.front().mGroup))
|
||||||
{
|
{
|
||||||
// Remove the finished animation, unless it's a scripted animation that was interrupted by e.g. a rebuild of
|
// Playing animations through mwscript is weird. If an animation is
|
||||||
// the animation object.
|
// a looping animation (idle or other cyclical animations), then they
|
||||||
if (mAnimQueue.size() > 1 || !mAnimQueue.front().mScripted || mAnimQueue.front().mLoopCount == 0)
|
// will end as expected. However, if they are non-looping animations, they
|
||||||
|
// will stick around forever or until another animation appears in the queue.
|
||||||
|
bool shouldPlayOrRestart = mAnimQueue.size() > 1;
|
||||||
|
if (shouldPlayOrRestart || !mAnimQueue.front().mScripted
|
||||||
|
|| (mAnimQueue.front().mLoopCount == 0 && mAnimQueue.front().mLooping))
|
||||||
{
|
{
|
||||||
|
mAnimation->setPlayScriptedOnly(false);
|
||||||
mAnimation->disable(mAnimQueue.front().mGroup);
|
mAnimation->disable(mAnimQueue.front().mGroup);
|
||||||
mAnimQueue.pop_front();
|
mAnimQueue.pop_front();
|
||||||
|
shouldPlayOrRestart = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
// A non-looping animation will stick around forever, so only restart if the animation
|
||||||
|
// actually was removed for some reason.
|
||||||
|
shouldPlayOrRestart = !mAnimation->getInfo(mAnimQueue.front().mGroup)
|
||||||
|
&& mAnimation->hasAnimation(mAnimQueue.front().mGroup);
|
||||||
|
|
||||||
if (!mAnimQueue.empty())
|
if (shouldPlayOrRestart)
|
||||||
{
|
{
|
||||||
// Move on to the remaining items of the queue
|
// Move on to the remaining items of the queue
|
||||||
bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle");
|
playAnimQueue();
|
||||||
mAnimation->play(mAnimQueue.front().mGroup,
|
|
||||||
mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default,
|
|
||||||
MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f,
|
|
||||||
mAnimQueue.front().mLoopCount, loopfallback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup);
|
float complete;
|
||||||
|
size_t loopcount;
|
||||||
|
mAnimation->getInfo(mAnimQueue.front().mGroup, &complete, nullptr, &loopcount);
|
||||||
|
mAnimQueue.front().mLoopCount = loopcount;
|
||||||
|
mAnimQueue.front().mTime = complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mAnimQueue.empty())
|
if (!mAnimQueue.empty())
|
||||||
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
|
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CharacterController::playAnimQueue(bool loopStart)
|
||||||
|
{
|
||||||
|
if (!mAnimQueue.empty())
|
||||||
|
{
|
||||||
|
clearStateAnimation(mCurrentIdle);
|
||||||
|
mIdleState = CharState_SpecialIdle;
|
||||||
|
auto priority = mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default;
|
||||||
|
mAnimation->setPlayScriptedOnly(mAnimQueue.front().mScripted);
|
||||||
|
mAnimation->play(mAnimQueue.front().mGroup, priority, MWRender::Animation::BlendMask_All, false, 1.0f,
|
||||||
|
(loopStart ? "loop start" : "start"), "stop", mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount,
|
||||||
|
mAnimQueue.front().mLooping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CharacterController::update(float duration)
|
void CharacterController::update(float duration)
|
||||||
{
|
{
|
||||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
@ -2455,10 +2516,11 @@ namespace MWMechanics
|
|||||||
|
|
||||||
if (iter == mAnimQueue.begin())
|
if (iter == mAnimQueue.begin())
|
||||||
{
|
{
|
||||||
anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup);
|
|
||||||
float complete;
|
float complete;
|
||||||
mAnimation->getInfo(anim.mGroup, &complete, nullptr);
|
size_t loopcount;
|
||||||
|
mAnimation->getInfo(anim.mGroup, &complete, nullptr, &loopcount);
|
||||||
anim.mTime = complete;
|
anim.mTime = complete;
|
||||||
|
anim.mLoopCount = loopcount;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2484,26 +2546,20 @@ namespace MWMechanics
|
|||||||
entry.mGroup = iter->mGroup;
|
entry.mGroup = iter->mGroup;
|
||||||
entry.mLoopCount = iter->mLoopCount;
|
entry.mLoopCount = iter->mLoopCount;
|
||||||
entry.mScripted = true;
|
entry.mScripted = true;
|
||||||
|
entry.mLooping = isLoopingAnimation(entry.mGroup);
|
||||||
|
entry.mTime = iter->mTime;
|
||||||
|
if (iter->mAbsolute)
|
||||||
|
{
|
||||||
|
float start = mAnimation->getTextKeyTime(iter->mGroup + ": start");
|
||||||
|
float stop = mAnimation->getTextKeyTime(iter->mGroup + ": stop");
|
||||||
|
float time = std::clamp(iter->mTime, start, stop);
|
||||||
|
entry.mTime = (time - start) / (stop - start);
|
||||||
|
}
|
||||||
|
|
||||||
mAnimQueue.push_back(entry);
|
mAnimQueue.push_back(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front();
|
playAnimQueue();
|
||||||
float complete = anim.mTime;
|
|
||||||
if (anim.mAbsolute)
|
|
||||||
{
|
|
||||||
float start = mAnimation->getTextKeyTime(anim.mGroup + ": start");
|
|
||||||
float stop = mAnimation->getTextKeyTime(anim.mGroup + ": stop");
|
|
||||||
float time = std::clamp(anim.mTime, start, stop);
|
|
||||||
complete = (time - start) / (stop - start);
|
|
||||||
}
|
|
||||||
|
|
||||||
clearStateAnimation(mCurrentIdle);
|
|
||||||
mIdleState = CharState_SpecialIdle;
|
|
||||||
|
|
||||||
bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle");
|
|
||||||
mAnimation->play(anim.mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start",
|
|
||||||
"stop", complete, anim.mLoopCount, loopfallback);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2516,13 +2572,14 @@ namespace MWMechanics
|
|||||||
if (isScriptedAnimPlaying() && !scripted)
|
if (isScriptedAnimPlaying() && !scripted)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// If this animation is a looped animation (has a "loop start" key) that is already playing
|
bool looping = isLoopingAnimation(groupname);
|
||||||
|
|
||||||
|
// If this animation is a looped animation that is already playing
|
||||||
// and has not yet reached the end of the loop, allow it to continue animating with its existing loop count
|
// and has not yet reached the end of the loop, allow it to continue animating with its existing loop count
|
||||||
// and remove any other animations that were queued.
|
// and remove any other animations that were queued.
|
||||||
// This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners
|
// This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners
|
||||||
// correctly.
|
// correctly.
|
||||||
if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname
|
if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && looping
|
||||||
&& mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0
|
|
||||||
&& mAnimation->isPlaying(groupname))
|
&& mAnimation->isPlaying(groupname))
|
||||||
{
|
{
|
||||||
float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop stop");
|
float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop stop");
|
||||||
@ -2537,36 +2594,43 @@ namespace MWMechanics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count = std::max(count, 1);
|
// The loop count in vanilla is weird.
|
||||||
|
// if played with a count of 0, all objects play exactly once from start to stop.
|
||||||
|
// But if the count is x > 0, actors and non-actors behave differently. actors will loop
|
||||||
|
// exactly x times, while non-actors will loop x+1 instead.
|
||||||
|
if (mPtr.getClass().isActor())
|
||||||
|
count--;
|
||||||
|
count = std::max(count, 0);
|
||||||
|
|
||||||
AnimationQueueEntry entry;
|
AnimationQueueEntry entry;
|
||||||
entry.mGroup = groupname;
|
entry.mGroup = groupname;
|
||||||
entry.mLoopCount = count - 1;
|
entry.mLoopCount = count;
|
||||||
|
entry.mTime = 0.f;
|
||||||
entry.mScripted = scripted;
|
entry.mScripted = scripted;
|
||||||
|
entry.mLooping = looping;
|
||||||
|
|
||||||
|
bool playImmediately = false;
|
||||||
|
|
||||||
if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
|
if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
|
||||||
{
|
{
|
||||||
clearAnimQueue(scripted);
|
clearAnimQueue(scripted);
|
||||||
|
|
||||||
clearStateAnimation(mCurrentIdle);
|
playImmediately = true;
|
||||||
|
|
||||||
mIdleState = CharState_SpecialIdle;
|
|
||||||
bool loopfallback = entry.mGroup.starts_with("idle");
|
|
||||||
mAnimation->play(groupname, scripted && groupname != "idle" ? Priority_Scripted : Priority_Default,
|
|
||||||
MWRender::Animation::BlendMask_All, false, 1.0f, ((mode == 2) ? "loop start" : "start"), "stop", 0.0f,
|
|
||||||
count - 1, loopfallback);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mAnimQueue.resize(1);
|
mAnimQueue.resize(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// "PlayGroup idle" is a special case, used to remove to stop scripted animations playing
|
// "PlayGroup idle" is a special case, used to stop and remove scripted animations playing
|
||||||
if (groupname == "idle")
|
if (groupname == "idle")
|
||||||
entry.mScripted = false;
|
entry.mScripted = false;
|
||||||
|
|
||||||
mAnimQueue.push_back(entry);
|
mAnimQueue.push_back(entry);
|
||||||
|
|
||||||
|
if (playImmediately)
|
||||||
|
playAnimQueue(mode == 2);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2577,11 +2641,10 @@ namespace MWMechanics
|
|||||||
|
|
||||||
bool CharacterController::isScriptedAnimPlaying() const
|
bool CharacterController::isScriptedAnimPlaying() const
|
||||||
{
|
{
|
||||||
|
// If the front of the anim queue is scripted, morrowind treats it as if it's
|
||||||
|
// still playing even if it's actually done.
|
||||||
if (!mAnimQueue.empty())
|
if (!mAnimQueue.empty())
|
||||||
{
|
return mAnimQueue.front().mScripted;
|
||||||
const AnimationQueueEntry& first = mAnimQueue.front();
|
|
||||||
return first.mScripted && isAnimPlaying(first.mGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -2611,6 +2674,7 @@ namespace MWMechanics
|
|||||||
|
|
||||||
if (clearScriptedAnims)
|
if (clearScriptedAnims)
|
||||||
{
|
{
|
||||||
|
mAnimation->setPlayScriptedOnly(false);
|
||||||
mAnimQueue.clear();
|
mAnimQueue.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2645,6 +2709,8 @@ namespace MWMechanics
|
|||||||
playRandomDeath();
|
playRandomDeath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateAnimQueue();
|
||||||
|
|
||||||
mAnimation->runAnimation(0.f);
|
mAnimation->runAnimation(0.f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +135,8 @@ namespace MWMechanics
|
|||||||
{
|
{
|
||||||
std::string mGroup;
|
std::string mGroup;
|
||||||
size_t mLoopCount;
|
size_t mLoopCount;
|
||||||
|
float mTime;
|
||||||
|
bool mLooping;
|
||||||
bool mScripted;
|
bool mScripted;
|
||||||
};
|
};
|
||||||
typedef std::deque<AnimationQueueEntry> AnimationQueue;
|
typedef std::deque<AnimationQueueEntry> AnimationQueue;
|
||||||
@ -219,6 +221,7 @@ namespace MWMechanics
|
|||||||
bool isMovementAnimationControlled() const;
|
bool isMovementAnimationControlled() const;
|
||||||
|
|
||||||
void updateAnimQueue();
|
void updateAnimQueue();
|
||||||
|
void playAnimQueue(bool useLoopStart = false);
|
||||||
|
|
||||||
void updateHeadTracking(float duration);
|
void updateHeadTracking(float duration);
|
||||||
|
|
||||||
@ -245,6 +248,8 @@ namespace MWMechanics
|
|||||||
|
|
||||||
void prepareHit();
|
void prepareHit();
|
||||||
|
|
||||||
|
bool isLoopingAnimation(std::string_view group) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim);
|
CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim);
|
||||||
virtual ~CharacterController();
|
virtual ~CharacterController();
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
#include <components/esm3/loadweap.hpp>
|
#include <components/esm3/loadweap.hpp>
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
template <enum ESM::Weapon::Type>
|
template <enum ESM::Weapon::Type>
|
||||||
@ -416,4 +418,18 @@ namespace MWMechanics
|
|||||||
|
|
||||||
return &Weapon<ESM::Weapon::ShortBladeOneHand>::getValue();
|
return &Weapon<ESM::Weapon::ShortBladeOneHand>::getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string_view> getAllWeaponTypeShortGroups()
|
||||||
|
{
|
||||||
|
// Go via a set to eliminate duplicates.
|
||||||
|
std::set<std::string_view> shortGroupSet;
|
||||||
|
for (int type = ESM::Weapon::Type::First; type <= ESM::Weapon::Type::Last; type++)
|
||||||
|
{
|
||||||
|
std::string_view shortGroup = getWeaponType(type)->mShortGroup;
|
||||||
|
if (!shortGroup.empty())
|
||||||
|
shortGroupSet.insert(shortGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::vector<std::string_view>(shortGroupSet.begin(), shortGroupSet.end());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
#ifndef GAME_MWMECHANICS_WEAPONTYPE_H
|
#ifndef GAME_MWMECHANICS_WEAPONTYPE_H
|
||||||
#define GAME_MWMECHANICS_WEAPONTYPE_H
|
#define GAME_MWMECHANICS_WEAPONTYPE_H
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
struct WeaponType;
|
struct WeaponType;
|
||||||
@ -21,6 +24,8 @@ namespace MWMechanics
|
|||||||
MWWorld::ContainerStoreIterator getActiveWeapon(const MWWorld::Ptr& actor, int* weaptype);
|
MWWorld::ContainerStoreIterator getActiveWeapon(const MWWorld::Ptr& actor, int* weaptype);
|
||||||
|
|
||||||
const ESM::WeaponType* getWeaponType(const int weaponType);
|
const ESM::WeaponType* getWeaponType(const int weaponType);
|
||||||
|
|
||||||
|
std::vector<std::string_view> getAllWeaponTypeShortGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -529,6 +529,7 @@ namespace MWRender
|
|||||||
, mBodyPitchRadians(0.f)
|
, mBodyPitchRadians(0.f)
|
||||||
, mHasMagicEffects(false)
|
, mHasMagicEffects(false)
|
||||||
, mAlpha(1.f)
|
, mAlpha(1.f)
|
||||||
|
, mPlayScriptedOnly(false)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < sNumBlendMasks; i++)
|
for (size_t i = 0; i < sNumBlendMasks; i++)
|
||||||
mAnimationTimePtr[i] = std::make_shared<AnimationTime>();
|
mAnimationTimePtr[i] = std::make_shared<AnimationTime>();
|
||||||
@ -1020,7 +1021,7 @@ namespace MWRender
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const
|
bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult, size_t* loopcount) const
|
||||||
{
|
{
|
||||||
AnimStateMap::const_iterator iter = mStates.find(groupname);
|
AnimStateMap::const_iterator iter = mStates.find(groupname);
|
||||||
if (iter == mStates.end())
|
if (iter == mStates.end())
|
||||||
@ -1029,6 +1030,8 @@ namespace MWRender
|
|||||||
*complete = 0.0f;
|
*complete = 0.0f;
|
||||||
if (speedmult)
|
if (speedmult)
|
||||||
*speedmult = 0.0f;
|
*speedmult = 0.0f;
|
||||||
|
if (loopcount)
|
||||||
|
*loopcount = 0;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1042,6 +1045,9 @@ namespace MWRender
|
|||||||
}
|
}
|
||||||
if (speedmult)
|
if (speedmult)
|
||||||
*speedmult = iter->second.mSpeedMult;
|
*speedmult = iter->second.mSpeedMult;
|
||||||
|
|
||||||
|
if (loopcount)
|
||||||
|
*loopcount = iter->second.mLoopCount;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1054,15 +1060,6 @@ 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(std::string_view groupname)
|
void Animation::disable(std::string_view groupname)
|
||||||
{
|
{
|
||||||
AnimStateMap::iterator iter = mStates.find(groupname);
|
AnimStateMap::iterator iter = mStates.find(groupname);
|
||||||
@ -1141,23 +1138,12 @@ namespace MWRender
|
|||||||
|
|
||||||
osg::Vec3f Animation::runAnimation(float duration)
|
osg::Vec3f Animation::runAnimation(float duration)
|
||||||
{
|
{
|
||||||
// If we have scripted animations, play only them
|
|
||||||
bool hasScriptedAnims = false;
|
|
||||||
for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++)
|
|
||||||
{
|
|
||||||
if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying)
|
|
||||||
{
|
|
||||||
hasScriptedAnims = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
osg::Vec3f movement(0.f, 0.f, 0.f);
|
osg::Vec3f movement(0.f, 0.f, 0.f);
|
||||||
AnimStateMap::iterator stateiter = mStates.begin();
|
AnimStateMap::iterator stateiter = mStates.begin();
|
||||||
while (stateiter != mStates.end())
|
while (stateiter != mStates.end())
|
||||||
{
|
{
|
||||||
AnimState& state = stateiter->second;
|
AnimState& state = stateiter->second;
|
||||||
if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Scripted)))
|
if (mPlayScriptedOnly && !state.mPriority.contains(MWMechanics::Priority_Scripted))
|
||||||
{
|
{
|
||||||
++stateiter;
|
++stateiter;
|
||||||
continue;
|
continue;
|
||||||
@ -1263,10 +1249,6 @@ namespace MWRender
|
|||||||
osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1)));
|
osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scripted animations should not cause movement
|
|
||||||
if (hasScriptedAnims)
|
|
||||||
return osg::Vec3f(0, 0, 0);
|
|
||||||
|
|
||||||
return movement;
|
return movement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,6 +292,8 @@ namespace MWRender
|
|||||||
|
|
||||||
osg::ref_ptr<SceneUtil::LightListCallback> mLightListCallback;
|
osg::ref_ptr<SceneUtil::LightListCallback> mLightListCallback;
|
||||||
|
|
||||||
|
bool mPlayScriptedOnly;
|
||||||
|
|
||||||
const NodeMap& getNodeMap() const;
|
const NodeMap& getNodeMap() const;
|
||||||
|
|
||||||
/* Sets the appropriate animations on the bone groups based on priority.
|
/* Sets the appropriate animations on the bone groups based on priority.
|
||||||
@ -441,7 +443,8 @@ namespace MWRender
|
|||||||
* \param speedmult Stores the animation speed multiplier
|
* \param speedmult Stores the animation speed multiplier
|
||||||
* \return True if the animation is active, false otherwise.
|
* \return True if the animation is active, false otherwise.
|
||||||
*/
|
*/
|
||||||
bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr) const;
|
bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr,
|
||||||
|
size_t* loopcount = nullptr) const;
|
||||||
|
|
||||||
/// Get the absolute position in the animation track of the first text key with the given group.
|
/// Get the absolute position in the animation track of the first text key with the given group.
|
||||||
float getStartTime(const std::string& groupname) const;
|
float getStartTime(const std::string& groupname) const;
|
||||||
@ -453,8 +456,6 @@ namespace MWRender
|
|||||||
/// the given group.
|
/// 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.
|
||||||
*/
|
*/
|
||||||
@ -477,6 +478,9 @@ namespace MWRender
|
|||||||
MWWorld::MovementDirectionFlags getSupportedMovementDirections(
|
MWWorld::MovementDirectionFlags getSupportedMovementDirections(
|
||||||
std::span<const std::string_view> prefixes) const;
|
std::span<const std::string_view> prefixes) const;
|
||||||
|
|
||||||
|
bool getPlayScriptedOnly() const { return mPlayScriptedOnly; }
|
||||||
|
void setPlayScriptedOnly(bool playScriptedOnly) { mPlayScriptedOnly = playScriptedOnly; }
|
||||||
|
|
||||||
virtual bool useShieldAnimations() const { return false; }
|
virtual bool useShieldAnimations() const { return false; }
|
||||||
virtual bool getWeaponsShown() const { return false; }
|
virtual bool getWeaponsShown() const { return false; }
|
||||||
virtual void showWeapons(bool showWeapon) {}
|
virtual void showWeapons(bool showWeapon) {}
|
||||||
|
@ -923,13 +923,18 @@ namespace MWRender
|
|||||||
|
|
||||||
if (mViewMode == VM_FirstPerson)
|
if (mViewMode == VM_FirstPerson)
|
||||||
{
|
{
|
||||||
NodeMap::iterator found = mNodeMap.find("bip01 neck");
|
// If there is no active animation, then the bip01 neck node will not be updated each frame, and the
|
||||||
if (found != mNodeMap.end())
|
// RotateController will accumulate rotations.
|
||||||
|
if (mStates.size() > 0)
|
||||||
{
|
{
|
||||||
osg::MatrixTransform* node = found->second.get();
|
NodeMap::iterator found = mNodeMap.find("bip01 neck");
|
||||||
mFirstPersonNeckController = new RotateController(mObjectRoot.get());
|
if (found != mNodeMap.end())
|
||||||
node->addUpdateCallback(mFirstPersonNeckController);
|
{
|
||||||
mActiveControllers.emplace_back(node, mFirstPersonNeckController);
|
osg::MatrixTransform* node = found->second.get();
|
||||||
|
mFirstPersonNeckController = new RotateController(mObjectRoot.get());
|
||||||
|
node->addUpdateCallback(mFirstPersonNeckController);
|
||||||
|
mActiveControllers.emplace_back(node, mFirstPersonNeckController);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (mViewMode == VM_Normal)
|
else if (mViewMode == VM_Normal)
|
||||||
|
@ -91,7 +91,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 + 1, true);
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(ptr, group, mode, loops, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ namespace ESM
|
|||||||
|
|
||||||
enum Type
|
enum Type
|
||||||
{
|
{
|
||||||
|
First = -4,
|
||||||
PickProbe = -4,
|
PickProbe = -4,
|
||||||
HandToHand = -3,
|
HandToHand = -3,
|
||||||
Spell = -2,
|
Spell = -2,
|
||||||
@ -41,7 +42,8 @@ namespace ESM
|
|||||||
MarksmanCrossbow = 10,
|
MarksmanCrossbow = 10,
|
||||||
MarksmanThrown = 11,
|
MarksmanThrown = 11,
|
||||||
Arrow = 12,
|
Arrow = 12,
|
||||||
Bolt = 13
|
Bolt = 13,
|
||||||
|
Last = 13
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AttackType
|
enum AttackType
|
||||||
|
Loading…
x
Reference in New Issue
Block a user