From 9e6b7fed1aa5b058f64a721fc0c0f7e4af9b1b9a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 13 Jun 2022 11:34:17 +0300 Subject: [PATCH 1/8] Consolidate refreshMovementAnims() --- apps/openmw/mwmechanics/character.cpp | 253 ++++++++++++-------------- 1 file changed, 121 insertions(+), 132 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c44b7d19d1..2da4f9ab1d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -491,149 +491,138 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup if (movement == mMovementState && idle == mIdleState && !force) return; + std::string movementAnimName = movementStateToAnimGroup(movement); + + if (movementAnimName.empty()) + { + if (!mCurrentMovement.empty()) + { + mAnimation->disable(mCurrentMovement); + mCurrentMovement.clear(); + } + mMovementState = CharState_None; + return; + } + + std::string::size_type swimpos = movementAnimName.find("swim"); + if (!mAnimation->hasAnimation(movementAnimName)) + { + if (swimpos != std::string::npos) + { + movementAnimName.erase(swimpos, 4); + swimpos = std::string::npos; + } + } + + MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; + + if (swimpos == std::string::npos && !weapShortGroup.empty()) + { + std::string weapMovementAnimName; + // Spellcasting stance turning is a special case + if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) + weapMovementAnimName = weapShortGroup + movementAnimName; + else + weapMovementAnimName = movementAnimName + weapShortGroup; + + if (!mAnimation->hasAnimation(weapMovementAnimName)) + { + weapMovementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); + // If we apply movement only for lower body, do not reset idle animations. + // For upper body there will be idle animation. + if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) + idle = CharState_Idle; + } + + movementAnimName = weapMovementAnimName; + } + + if (!force && movement == mMovementState) + return; + + if (!mAnimation->hasAnimation(movementAnimName)) + { + std::string::size_type runpos = movementAnimName.find("run"); + if (runpos != std::string::npos) + movementAnimName.replace(runpos, 3, "walk"); + + if (!mAnimation->hasAnimation(movementAnimName)) + { + if (!mCurrentMovement.empty()) + { + mAnimation->disable(mCurrentMovement); + mCurrentMovement.clear(); + } + mMovementState = CharState_None; + return; + } + } + + mMovementState = movement; + + // If we're playing the same animation, start it from the point it ended + float startpoint = 0.f; + if (!mCurrentMovement.empty() && movementAnimName == mCurrentMovement) + mAnimation->getInfo(mCurrentMovement, &startpoint); + + mMovementAnimationControlled = true; + + if (!mCurrentMovement.empty()) + mAnimation->disable(mCurrentMovement); + + mCurrentMovement = movementAnimName; + // Reset idle if we actually play movement animations excepts of these cases: // 1. When we play turning animations // 2. When we use a fallback animation for lower body since movement animation for given weapon is missing (e.g. for crossbows and spellcasting) - bool resetIdle = (movement != CharState_None && !isTurning()); - - std::string movementAnimName = movementStateToAnimGroup(movement); - MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; - - if (!movementAnimName.empty()) + if (!isTurning() && movemask == MWRender::Animation::BlendMask_All) { - if(!weapShortGroup.empty()) + if (!mCurrentIdle.empty()) { - std::string::size_type swimpos = movementAnimName.find("swim"); - if (swimpos == std::string::npos) - { - std::string weapMovementAnimName; - if (mWeaponType == ESM::Weapon::Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case - weapMovementAnimName = weapShortGroup + movementAnimName; - else - weapMovementAnimName = movementAnimName + weapShortGroup; + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); + } + mIdleState = CharState_None; + idle = CharState_None; + } - if (!mAnimation->hasAnimation(weapMovementAnimName)) - { - weapMovementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); - // If we apply movement only for lower body, do not reset idle animations. - // For upper body there will be idle animation. - if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) - idle = CharState_Idle; + // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity + // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. + mAdjustMovementAnimSpeed = true; + if (mPtr.getClass().getType() == ESM::Creature::sRecordId && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) + { + CharacterState walkState = runStateToWalkState(mMovementState); + std::string anim = movementStateToAnimGroup(walkState); - if (movemask == MWRender::Animation::BlendMask_LowerBody) - resetIdle = false; - } + mMovementAnimSpeed = mAnimation->getVelocity(anim); + if (mMovementAnimSpeed <= 1.0f) + { + // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), + // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist + // we will play without any scaling. + // Makes the speed attribute of most water creatures totally useless. + // And again, this can not be fixed without patching game data. + mAdjustMovementAnimSpeed = false; + mMovementAnimSpeed = 1.f; + } + } + else + { + mMovementAnimSpeed = mAnimation->getVelocity(mCurrentMovement); - movementAnimName = weapMovementAnimName; - } + if (mMovementAnimSpeed <= 1.0f) + { + // The first person anims don't have any velocity to calculate a speed multiplier from. + // We use the third person velocities instead. + // FIXME: should be pulled from the actual animation, but it is not presently loaded. + bool sneaking = mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack + || mMovementState == CharState_SneakLeft || mMovementState == CharState_SneakRight; + mMovementAnimSpeed = (sneaking ? 33.5452f : (isRunning() ? 222.857f : 154.064f)); + mMovementAnimationControlled = false; } } - if(force || movement != mMovementState) - { - mMovementState = movement; - if (!movementAnimName.empty()) - { - if(!mAnimation->hasAnimation(movementAnimName)) - { - std::string::size_type swimpos = movementAnimName.find("swim"); - if (swimpos != std::string::npos) - { - movementAnimName.erase(swimpos, 4); - if (!weapShortGroup.empty()) - { - std::string weapMovementAnimName = movementAnimName + weapShortGroup; - if(mAnimation->hasAnimation(weapMovementAnimName)) - movementAnimName = weapMovementAnimName; - else - { - movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); - if (movemask == MWRender::Animation::BlendMask_LowerBody) - resetIdle = false; - } - } - } - - if (swimpos == std::string::npos || !mAnimation->hasAnimation(movementAnimName)) - { - std::string::size_type runpos = movementAnimName.find("run"); - if (runpos != std::string::npos) - { - movementAnimName.replace(runpos, 3, "walk"); - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - } - else - movementAnimName.clear(); - } - } - } - - // If we're playing the same animation, start it from the point it ended - float startpoint = 0.f; - if (!mCurrentMovement.empty() && movementAnimName == mCurrentMovement) - mAnimation->getInfo(mCurrentMovement, &startpoint); - - mMovementAnimationControlled = true; - - mAnimation->disable(mCurrentMovement); - - if (!mAnimation->hasAnimation(movementAnimName)) - movementAnimName.clear(); - - mCurrentMovement = movementAnimName; - if(!mCurrentMovement.empty()) - { - if (resetIdle) - { - mAnimation->disable(mCurrentIdle); - mIdleState = CharState_None; - idle = CharState_None; - } - - // For non-flying creatures, MW uses the Walk animation to calculate the animation velocity - // even if we are running. This must be replicated, otherwise the observed speed would differ drastically. - mAdjustMovementAnimSpeed = true; - if (mPtr.getClass().getType() == ESM::Creature::sRecordId - && !(mPtr.get()->mBase->mFlags & ESM::Creature::Flies)) - { - CharacterState walkState = runStateToWalkState(mMovementState); - std::string anim = movementStateToAnimGroup(walkState); - - mMovementAnimSpeed = mAnimation->getVelocity(anim); - if (mMovementAnimSpeed <= 1.0f) - { - // Another bug: when using a fallback animation (e.g. RunForward as fallback to SwimRunForward), - // then the equivalent Walk animation will not use a fallback, and if that animation doesn't exist - // we will play without any scaling. - // Makes the speed attribute of most water creatures totally useless. - // And again, this can not be fixed without patching game data. - mAdjustMovementAnimSpeed = false; - mMovementAnimSpeed = 1.f; - } - } - else - { - mMovementAnimSpeed = mAnimation->getVelocity(mCurrentMovement); - - if (mMovementAnimSpeed <= 1.0f) - { - // The first person anims don't have any velocity to calculate a speed multiplier from. - // We use the third person velocities instead. - // FIXME: should be pulled from the actual animation, but it is not presently loaded. - bool sneaking = mMovementState == CharState_SneakForward || mMovementState == CharState_SneakBack - || mMovementState == CharState_SneakLeft || mMovementState == CharState_SneakRight; - mMovementAnimSpeed = (sneaking ? 33.5452f : (isRunning() ? 222.857f : 154.064f)); - mMovementAnimationControlled = false; - } - } - - mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, - 1.f, "start", "stop", startpoint, ~0ul, true); - } - else - mMovementState = CharState_None; - } + mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); } void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force) From ab46337c4131f85a0b422891d20c31a2646e2c99 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 13 Jun 2022 12:28:12 +0300 Subject: [PATCH 2/8] Rewrite handleTextKey using substring views --- apps/openmw/mwmechanics/character.cpp | 63 +++++++++++++-------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2da4f9ab1d..fbe53b94d9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -886,7 +886,7 @@ void CharacterController::handleTextKey(std::string_view groupname, SceneUtil::T { std::string_view evt = key->second; - if(evt.compare(0, 7, "sound: ") == 0) + if (evt.substr(0, 7) == "sound: ") { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); sndMgr->playSound3D(mPtr, evt.substr(7), 1.0f, 1.0f); @@ -894,7 +894,7 @@ void CharacterController::handleTextKey(std::string_view groupname, SceneUtil::T } auto& charClass = mPtr.getClass(); - if(evt.compare(0, 10, "soundgen: ") == 0) + if (evt.substr(0, 10) == "soundgen: ") { std::string soundgen = std::string(evt.substr(10)); @@ -936,30 +936,34 @@ void CharacterController::handleTextKey(std::string_view groupname, SceneUtil::T return; } - if(evt.compare(0, groupname.size(), groupname) != 0 || - evt.compare(groupname.size(), 2, ": ") != 0) + if (evt.substr(0, groupname.size()) != groupname || evt.substr(groupname.size(), 2) != ": ") { // Not ours, skip it return; } - const size_t off = groupname.size()+2; - const size_t len = evt.size() - off; - if(groupname == "shield" && evt.compare(off, len, "equip attach") == 0) - mAnimation->showCarriedLeft(true); - else if(groupname == "shield" && evt.compare(off, len, "unequip detach") == 0) - mAnimation->showCarriedLeft(false); - else if(evt.compare(off, len, "equip attach") == 0) - mAnimation->showWeapons(true); - else if(evt.compare(off, len, "unequip detach") == 0) - mAnimation->showWeapons(false); - else if(evt.compare(off, len, "chop hit") == 0) + std::string_view action = evt.substr(groupname.size() + 2); + if (action == "equip attach") + { + if (groupname == "shield") + mAnimation->showCarriedLeft(true); + else + mAnimation->showWeapons(true); + } + else if (action == "unequip detach") + { + if (groupname == "shield") + mAnimation->showCarriedLeft(false); + else + mAnimation->showWeapons(false); + } + else if (action == "chop hit") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); - else if(evt.compare(off, len, "slash hit") == 0) + else if (action == "slash hit") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Slash); - else if(evt.compare(off, len, "thrust hit") == 0) + else if (action == "thrust hit") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); - else if(evt.compare(off, len, "hit") == 0) + else if (action == "hit") { if (groupname == "attack1" || groupname == "swimattack1") charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Chop); @@ -970,9 +974,7 @@ void CharacterController::handleTextKey(std::string_view groupname, SceneUtil::T else charClass.hit(mPtr, mAttackStrength); } - else if (!groupname.empty() - && (groupname.compare(0, groupname.size()-1, "attack") == 0 || groupname.compare(0, groupname.size()-1, "swimattack") == 0) - && evt.compare(off, len, "start") == 0) + else if (isRandomAttackAnimation(groupname) && action == "start") { std::multimap::const_iterator hitKey = key; @@ -1001,25 +1003,22 @@ void CharacterController::handleTextKey(std::string_view groupname, SceneUtil::T charClass.hit(mPtr, mAttackStrength, ESM::Weapon::AT_Thrust); } } - else if (evt.compare(off, len, "shoot attach") == 0) + else if (action == "shoot attach") mAnimation->attachArrow(); - else if (evt.compare(off, len, "shoot release") == 0) + else if (action == "shoot release") mAnimation->releaseArrow(mAttackStrength); - else if (evt.compare(off, len, "shoot follow attach") == 0) + else if (action == "shoot follow attach") mAnimation->attachArrow(); - - else if (groupname == "spellcast" && evt.substr(evt.size()-7, 7) == "release" - // 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 type. - && evt.compare(off, len, mAttackType + " release") == 0) + // 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 type. + else if (groupname == "spellcast" && action == mAttackType + " release") { MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); mCastingManualSpell = false; } - - else if (groupname == "shield" && evt.compare(off, len, "block hit") == 0) + else if (groupname == "shield" && action == "block hit") charClass.block(mPtr); - else if (groupname == "containeropen" && evt.compare(off, len, "loot") == 0) + else if (groupname == "containeropen" && action == "loot") MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, mPtr); } From a105ba14e4ac952a1ecfb261186c561313b50371 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 13 Jun 2022 12:59:18 +0300 Subject: [PATCH 3/8] Consolidate refreshJumpAnims --- apps/openmw/mwmechanics/character.cpp | 55 ++++++++++++--------------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index fbe53b94d9..96aea48fa2 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -337,24 +337,27 @@ void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, Ju if (!force && jump == mJumpState && idle == CharState_None) return; - std::string jumpAnimName; - MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; - if (jump != JumpState_None) + if (jump == JumpState_None) { - jumpAnimName = "jump"; - if(!weapShortGroup.empty()) + if (!mCurrentJump.empty()) { - jumpAnimName += weapShortGroup; - if(!mAnimation->hasAnimation(jumpAnimName)) - { - jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); - - // If we apply jump only for lower body, do not reset idle animations. - // For upper body there will be idle animation. - if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) - idle = CharState_Idle; - } + mAnimation->disable(mCurrentJump); + mCurrentJump.clear(); } + mJumpState = JumpState_None; + return; + } + + std::string jumpAnimName = "jump" + weapShortGroup; + MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; + if (!weapShortGroup.empty() && !mAnimation->hasAnimation(jumpAnimName)) + { + jumpAnimName = fallbackShortWeaponGroup("jump", &jumpmask); + + // If we apply jump only for lower body, do not reset idle animations. + // For upper body there will be idle animation. + if (jumpmask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) + idle = CharState_Idle; } if (!force && jump == mJumpState) @@ -369,24 +372,14 @@ void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, Ju mCurrentJump.clear(); } + if (!mAnimation->hasAnimation(jumpAnimName)) + return; + + mCurrentJump = jumpAnimName; if(mJumpState == JumpState_InAir) - { - if (mAnimation->hasAnimation(jumpAnimName)) - { - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, - 1.0f, startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); - mCurrentJump = jumpAnimName; - } - } + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, false, 1.0f, startAtLoop ? "loop start" : "start", "stop", 0.f, ~0ul); else if (mJumpState == JumpState_Landing) - { - if (mAnimation->hasAnimation(jumpAnimName)) - { - mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, - 1.0f, "loop stop", "stop", 0.0f, 0); - mCurrentJump = jumpAnimName; - } - } + mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true, 1.0f, "loop stop", "stop", 0.0f, 0); } bool CharacterController::onOpen() const From 2c3d38567270e6df518bbc1200e7a3fe5b6a10a7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 13 Jun 2022 14:50:59 +0300 Subject: [PATCH 4/8] Consolidate refreshHitRecoilAnims --- apps/openmw/mwmechanics/character.cpp | 218 +++++++++++++------------- 1 file changed, 113 insertions(+), 105 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 96aea48fa2..ad786ca06f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -161,6 +161,26 @@ std::string deathStateToAnimGroup(MWMechanics::CharacterState state) } } +// Converts a hit state to its equivalent animation group as long as it is a hit state. +std::string hitStateToAnimGroup(MWMechanics::CharacterState state) +{ + using namespace MWMechanics; + switch (state) + { + case CharState_SwimHit: return "swimhit"; + case CharState_SwimKnockDown: return "swimknockdown"; + case CharState_SwimKnockOut: return "swimknockout"; + + case CharState_Hit: return "hit"; + case CharState_KnockDown: return "knockdown"; + case CharState_KnockOut: return "knockout"; + + case CharState_Block: return "shield"; + + default: return {}; + } +} + float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -211,125 +231,114 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { - const auto world = MWBase::Environment::get().getWorld(); auto& charClass = mPtr.getClass(); + if (!charClass.isActor()) + return; + const auto world = MWBase::Environment::get().getWorld(); auto& stats = charClass.getCreatureStats(mPtr); + bool knockout = stats.getFatigue().getCurrent() < 0 || stats.getFatigue().getBase() == 0; bool recovery = stats.getHitRecovery(); bool knockdown = stats.getKnockedDown(); bool block = stats.getBlock(); bool isSwimming = world->isSwimming(mPtr); auto& prng = world->getPrng(); - if(mHitState == CharState_None) - { - if (stats.getFatigue().getCurrent() < 0 || stats.getFatigue().getBase() == 0) - { - mTimeUntilWake = Misc::Rng::rollClosedProbability(prng) * 2 + 1; // Wake up after 1 to 3 seconds - if (isSwimming && mAnimation->hasAnimation("swimknockout")) - { - mHitState = CharState_SwimKnockOut; - mCurrentHit = "swimknockout"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); - } - else if (!isSwimming && mAnimation->hasAnimation("knockout")) - { - mHitState = CharState_KnockOut; - mCurrentHit = "knockout"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul); - } - else - { - // Knockout animations are missing. Fall back to idle animation, so target actor still can be killed via HtH. - mCurrentHit.erase(); - } - stats.setKnockedDown(true); - } - else if (knockdown) - { - if (isSwimming && mAnimation->hasAnimation("swimknockdown")) - { - mHitState = CharState_SwimKnockDown; - mCurrentHit = "swimknockdown"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else if (!isSwimming && mAnimation->hasAnimation("knockdown")) - { - mHitState = CharState_KnockDown; - mCurrentHit = "knockdown"; - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else - { - // Knockdown animation is missing. Cancel knockdown state. - stats.setKnockedDown(false); - } - } - else if (recovery) - { - std::string anim = chooseRandomGroup("swimhit"); - if (isSwimming && mAnimation->hasAnimation(anim)) - { - mHitState = CharState_SwimHit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - else - { - anim = chooseRandomGroup("hit"); - if (mAnimation->hasAnimation(anim)) - { - mHitState = CharState_Hit; - mCurrentHit = anim; - mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0); - } - } - } - else if (block && mAnimation->hasAnimation("shield")) - { - mHitState = CharState_Block; - mCurrentHit = "shield"; - MWRender::Animation::AnimPriority priorityBlock (Priority_Hit); - priorityBlock[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; - priorityBlock[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; - mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0); - } - - // Cancel upper body animations - if (isKnockedOut() || isKnockedDown()) - { - if (mUpperBodyState > UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; - if (mWeaponType > ESM::Weapon::None) - mAnimation->showWeapons(true); - } - else if (mUpperBodyState > UpperCharState_Nothing && mUpperBodyState < UpperCharState_WeapEquiped) - { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_Nothing; - } - } - if (mHitState != CharState_None) - idle = CharState_None; - } - else if(!mAnimation->isPlaying(mCurrentHit)) + if (mHitState != CharState_None) { - mCurrentHit.erase(); - if (knockdown) + if (!mAnimation->isPlaying(mCurrentHit)) + { + mHitState = CharState_None; + mCurrentHit.clear(); stats.setKnockedDown(false); - if (recovery) stats.setHitRecovery(false); - if (block) stats.setBlock(false); - mHitState = CharState_None; + } + else if (isKnockedOut() && !knockout && mTimeUntilWake <= 0) + { + mAnimation->disable(mCurrentHit); + mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); + } + return; } - else if (isKnockedOut() && stats.getFatigue().getCurrent() > 0 && mTimeUntilWake <= 0) + + if (!knockout && !knockdown && !recovery && !block) + return; + + if (knockout) + mTimeUntilWake = Misc::Rng::rollClosedProbability(prng) * 2 + 1; // Wake up after 1 to 3 seconds + + MWRender::Animation::AnimPriority priority(Priority_Knockdown); + bool autodisable = true; + std::string startKey = "start"; + std::string stopKey = "stop"; + if (knockout) + { + mHitState = isSwimming ? CharState_SwimKnockOut : CharState_KnockOut; + stats.setKnockedDown(true); + autodisable = false; + } + else if (knockdown) { mHitState = isSwimming ? CharState_SwimKnockDown : CharState_KnockDown; - mAnimation->disable(mCurrentHit); - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); } + else if (recovery) + { + mHitState = isSwimming ? CharState_SwimHit : CharState_Hit; + priority = Priority_Hit; + } + else if (block) + { + mHitState = CharState_Block; + priority = Priority_Block; + priority[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block; + priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; + startKey = "block start"; + stopKey = "block stop"; + } + + mCurrentHit = hitStateToAnimGroup(mHitState); + + if (isRecovery()) + { + mCurrentHit = chooseRandomGroup(mCurrentHit); + if (mHitState == CharState_SwimHit && !mAnimation->hasAnimation(mCurrentHit)) + mCurrentHit = chooseRandomGroup(hitStateToAnimGroup(CharState_Hit)); + } + + if (!mAnimation->hasAnimation(mCurrentHit)) + { + // The hit animation is missing. Reset the current hit state and immediately cancel all states as if the animation were instantaneous. + mHitState = CharState_None; + mCurrentHit.clear(); + stats.setKnockedDown(false); + stats.setHitRecovery(false); + stats.setBlock(false); + return; + } + + // Cancel upper body animations + if (isKnockedOut() || isKnockedDown()) + { + if (!mCurrentWeapon.empty()) + { + mAnimation->disable(mCurrentWeapon); + mCurrentWeapon.clear(); + } + if (mUpperBodyState > UpperCharState_WeapEquiped) + { + mUpperBodyState = UpperCharState_WeapEquiped; + if (mWeaponType > ESM::Weapon::None) + mAnimation->showWeapons(true); + } + else if (mUpperBodyState < UpperCharState_WeapEquiped) + { + mUpperBodyState = UpperCharState_Nothing; + } + } + + mAnimation->play(mCurrentHit, priority, MWRender::Animation::BlendMask_All, autodisable, 1, startKey, stopKey, 0.0f, ~0ul); + + idle = CharState_None; } void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force) @@ -689,8 +698,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat if (isPersistentAnimPlaying()) return; - if (mPtr.getClass().isActor()) - refreshHitRecoilAnims(idle); + refreshHitRecoilAnims(idle); std::string weap; if (mWeaponType != ESM::Weapon::HandToHand || mPtr.getClass().isBipedal(mPtr)) From dd42a69ca5f32ceeb393f9e5e4c6a994ff6dc2ea Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 13 Jun 2022 15:41:56 +0300 Subject: [PATCH 5/8] Consolidate refreshIdleAnims --- apps/openmw/mwmechanics/character.cpp | 149 +++++++++++++++++--------- 1 file changed, 100 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ad786ca06f..d39f04f456 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -181,6 +181,48 @@ std::string hitStateToAnimGroup(MWMechanics::CharacterState state) } } +// Converts an idle state to its equivalent animation group. +std::string idleStateToAnimGroup(MWMechanics::CharacterState state) +{ + using namespace MWMechanics; + switch (state) + { + case CharState_IdleSwim: + return "idleswim"; + case CharState_IdleSneak: + return "idlesneak"; + case CharState_Idle: + case CharState_Idle2: + case CharState_Idle3: + case CharState_Idle4: + case CharState_Idle5: + case CharState_Idle6: + case CharState_Idle7: + case CharState_Idle8: + case CharState_Idle9: + case CharState_SpecialIdle: + return "idle"; + default: + return {}; + } +} + +MWRender::Animation::AnimPriority getIdlePriority(MWMechanics::CharacterState state) +{ + using namespace MWMechanics; + MWRender::Animation::AnimPriority priority(Priority_Default); + switch (state) + { + case CharState_IdleSwim: + return Priority_SwimIdle; + case CharState_IdleSneak: + priority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; + [[fallthrough]]; + default: + return priority; + } +} + float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) { MWBase::World *world = MWBase::Environment::get().getWorld(); @@ -637,59 +679,68 @@ void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, Ch && !mPtr.getClass().isBipedal(mPtr)) idle = CharState_None; - if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) + if (!force && idle == mIdleState && (mAnimation->isPlaying(mCurrentIdle) || !mAnimQueue.empty())) + return; + + mIdleState = idle; + size_t numLoops = ~0ul; + + std::string idleGroup = idleStateToAnimGroup(mIdleState); + MWRender::Animation::AnimPriority priority = getIdlePriority(mIdleState); + + // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to + // "idle"+weapon or "idle". + if ((mIdleState == CharState_IdleSwim || mIdleState == CharState_IdleSneak) && !mAnimation->hasAnimation(idleGroup)) + idleGroup = idleStateToAnimGroup(CharState_Idle); + + if (idleGroup.empty()) { - mIdleState = idle; - size_t numLoops = ~0ul; - - std::string idleGroup; - MWRender::Animation::AnimPriority idlePriority (Priority_Default); - // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to - // "idle"+weapon or "idle". - if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) + if (mCurrentIdle.empty()) { - idleGroup = "idleswim"; - idlePriority = Priority_SwimIdle; - } - else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) - { - idleGroup = "idlesneak"; - idlePriority[MWRender::Animation::BoneGroup_LowerBody] = Priority_SneakIdleLowerBody; - } - else if(mIdleState != CharState_None) - { - idleGroup = "idle"; - if(!weapShortGroup.empty()) - { - idleGroup += weapShortGroup; - if(!mAnimation->hasAnimation(idleGroup)) - { - idleGroup = fallbackShortWeaponGroup("idle"); - } - - // play until the Loop Stop key 2 to 5 times, then play until the Stop key - // this replicates original engine behavior for the "Idle1h" 1st-person animation - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - numLoops = 1 + Misc::Rng::rollDice(4, prng); - } - } - - // There is no need to restart anim if the new and old anims are the same. - // Just update a number of loops. - float startPoint = 0; - if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup) - { - mAnimation->getInfo(mCurrentIdle, &startPoint); - } - - if(!mCurrentIdle.empty()) mAnimation->disable(mCurrentIdle); - - mCurrentIdle = idleGroup; - if(!mCurrentIdle.empty()) - mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", startPoint, numLoops, true); + mCurrentIdle.clear(); + } + mIdleState = CharState_None; + return; } + + if (mIdleState != CharState_IdleSwim && mIdleState != CharState_IdleSneak && mIdleState != CharState_None && !weapShortGroup.empty()) + { + std::string weapIdleGroup = idleGroup + weapShortGroup; + if (!mAnimation->hasAnimation(weapIdleGroup)) + weapIdleGroup = fallbackShortWeaponGroup(idleGroup); + idleGroup = weapIdleGroup; + + // play until the Loop Stop key 2 to 5 times, then play until the Stop key + // this replicates original engine behavior for the "Idle1h" 1st-person animation + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + numLoops = 1 + Misc::Rng::rollDice(4, prng); + } + + if (!mAnimation->hasAnimation(idleGroup)) + { + if (mCurrentIdle.empty()) + { + mAnimation->disable(mCurrentIdle); + mCurrentIdle.clear(); + } + mIdleState = CharState_None; + return; + } + + float startPoint = 0.f; + if (!mCurrentIdle.empty()) + { + // There is no need to restart anim if the new and old anims are the same. + // Just update the number of loops. + if (mCurrentIdle == idleGroup) + mAnimation->getInfo(mCurrentIdle, &startPoint); + + mAnimation->disable(mCurrentIdle); + } + + mCurrentIdle = idleGroup; + mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true); } void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force) From 0a38c3ab7847cc0a1c97d6f96111ce8b5bdf1b9d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 13 Jun 2022 16:14:19 +0300 Subject: [PATCH 6/8] Reset current animation states in a consistent way --- apps/openmw/mwmechanics/character.cpp | 174 ++++++++++++-------------- apps/openmw/mwmechanics/character.hpp | 8 ++ 2 files changed, 85 insertions(+), 97 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d39f04f456..2a975fa649 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -271,6 +271,52 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i return prefix + std::to_string(roll); } + +void CharacterController::clearStateAnimation(std::string &anim) const +{ + if (anim.empty()) + return; + if (mAnimation) + mAnimation->disable(anim); + anim.clear(); +} + +void CharacterController::resetCurrentJumpState() +{ + clearStateAnimation(mCurrentJump); + mJumpState = JumpState_None; +} + +void CharacterController::resetCurrentMovementState() +{ + clearStateAnimation(mCurrentMovement); + mMovementState = CharState_None; +} + +void CharacterController::resetCurrentIdleState() +{ + clearStateAnimation(mCurrentIdle); + mIdleState = CharState_None; +} + +void CharacterController::resetCurrentHitState() +{ + clearStateAnimation(mCurrentHit); + mHitState = CharState_None; +} + +void CharacterController::resetCurrentWeaponState() +{ + clearStateAnimation(mCurrentWeapon); + mUpperBodyState = UpperCharState_Nothing; +} + +void CharacterController::resetCurrentDeathState() +{ + clearStateAnimation(mCurrentDeath); + mDeathState = CharState_None; +} + void CharacterController::refreshHitRecoilAnims(CharacterState& idle) { auto& charClass = mPtr.getClass(); @@ -361,11 +407,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) // Cancel upper body animations if (isKnockedOut() || isKnockedDown()) { - if (!mCurrentWeapon.empty()) - { - mAnimation->disable(mCurrentWeapon); - mCurrentWeapon.clear(); - } + clearStateAnimation(mCurrentWeapon); if (mUpperBodyState > UpperCharState_WeapEquiped) { mUpperBodyState = UpperCharState_WeapEquiped; @@ -390,12 +432,7 @@ void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, Ju if (jump == JumpState_None) { - if (!mCurrentJump.empty()) - { - mAnimation->disable(mCurrentJump); - mCurrentJump.clear(); - } - mJumpState = JumpState_None; + resetCurrentJumpState(); return; } @@ -416,12 +453,7 @@ void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, Ju bool startAtLoop = (jump == mJumpState); mJumpState = jump; - - if (!mCurrentJump.empty()) - { - mAnimation->disable(mCurrentJump); - mCurrentJump.clear(); - } + clearStateAnimation(mCurrentJump); if (!mAnimation->hasAnimation(jumpAnimName)) return; @@ -539,12 +571,7 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup if (movementAnimName.empty()) { - if (!mCurrentMovement.empty()) - { - mAnimation->disable(mCurrentMovement); - mCurrentMovement.clear(); - } - mMovementState = CharState_None; + resetCurrentMovementState(); return; } @@ -592,12 +619,7 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup if (!mAnimation->hasAnimation(movementAnimName)) { - if (!mCurrentMovement.empty()) - { - mAnimation->disable(mCurrentMovement); - mCurrentMovement.clear(); - } - mMovementState = CharState_None; + resetCurrentMovementState(); return; } } @@ -611,9 +633,7 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup mMovementAnimationControlled = true; - if (!mCurrentMovement.empty()) - mAnimation->disable(mCurrentMovement); - + clearStateAnimation(mCurrentMovement); mCurrentMovement = movementAnimName; // Reset idle if we actually play movement animations excepts of these cases: @@ -621,12 +641,7 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup // 2. When we use a fallback animation for lower body since movement animation for given weapon is missing (e.g. for crossbows and spellcasting) if (!isTurning() && movemask == MWRender::Animation::BlendMask_All) { - if (!mCurrentIdle.empty()) - { - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); - } - mIdleState = CharState_None; + resetCurrentIdleState(); idle = CharState_None; } @@ -695,12 +710,7 @@ void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, Ch if (idleGroup.empty()) { - if (mCurrentIdle.empty()) - { - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); - } - mIdleState = CharState_None; + resetCurrentIdleState(); return; } @@ -719,26 +729,17 @@ void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, Ch if (!mAnimation->hasAnimation(idleGroup)) { - if (mCurrentIdle.empty()) - { - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); - } - mIdleState = CharState_None; + resetCurrentIdleState(); return; } float startPoint = 0.f; - if (!mCurrentIdle.empty()) - { - // There is no need to restart anim if the new and old anims are the same. - // Just update the number of loops. - if (mCurrentIdle == idleGroup) - mAnimation->getInfo(mCurrentIdle, &startPoint); - - mAnimation->disable(mCurrentIdle); - } + // There is no need to restart anim if the new and old anims are the same. + // Just update the number of loops. + if (mCurrentIdle == idleGroup) + mAnimation->getInfo(mCurrentIdle, &startPoint); + clearStateAnimation(mCurrentIdle); mCurrentIdle = idleGroup; mAnimation->play(mCurrentIdle, priority, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, numLoops, true); } @@ -781,21 +782,11 @@ void CharacterController::playDeath(float startpoint, CharacterState death) // For dead actors, refreshCurrentAnims is no longer called, so we need to disable the movement state manually. // Note that these animations wouldn't actually be visible (due to the Death animation's priority being higher). // However, they could still trigger text keys, such as Hit events, or sounds. - mMovementState = CharState_None; - mAnimation->disable(mCurrentMovement); - mCurrentMovement.clear(); - mUpperBodyState = UpperCharState_Nothing; - mAnimation->disable(mCurrentWeapon); - mCurrentWeapon.clear(); - mHitState = CharState_None; - mAnimation->disable(mCurrentHit); - mCurrentHit.clear(); - mIdleState = CharState_None; - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); - mJumpState = JumpState_None; - mAnimation->disable(mCurrentJump); - mCurrentJump.clear(); + resetCurrentMovementState(); + resetCurrentWeaponState(); + resetCurrentHitState(); + resetCurrentIdleState(); + resetCurrentJumpState(); mMovementAnimationControlled = true; mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All, @@ -1184,9 +1175,9 @@ bool CharacterController::updateState(CharacterState idle) if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped) { forcestateupdate = true; + clearStateAnimation(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; setAttackingOrSpell(false); - mAnimation->disable(mCurrentWeapon); mAnimation->showWeapons(true); stats.setAttackingOrSpell(false); } @@ -1256,7 +1247,7 @@ bool CharacterController::updateState(CharacterState idle) if (!isStillWeapon) { - mAnimation->disable(mCurrentWeapon); + clearStateAnimation(mCurrentWeapon); if (weaptype != ESM::Weapon::None) { mAnimation->showWeapons(false); @@ -1304,8 +1295,7 @@ bool CharacterController::updateState(CharacterState idle) // Make sure that we disabled unequipping animation if (mUpperBodyState == UpperCharState_UnEquipingWeap) { - mUpperBodyState = UpperCharState_Nothing; - mAnimation->disable(mCurrentWeapon); + resetCurrentWeaponState(); mWeaponType = ESM::Weapon::None; mCurrentWeapon = getWeaponAnimation(mWeaponType); } @@ -1347,7 +1337,7 @@ bool CharacterController::updateState(CharacterState idle) if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) { - mAnimation->disable(mCurrentWeapon); + clearStateAnimation(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; } } @@ -1572,8 +1562,7 @@ bool CharacterController::updateState(CharacterState idle) idle != CharState_IdleSneak && idle != CharState_IdleSwim && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) { - mAnimation->disable(mCurrentIdle); - mIdleState = CharState_None; + resetCurrentIdleState(); } animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); @@ -1629,7 +1618,7 @@ bool CharacterController::updateState(CharacterState idle) if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } - mAnimation->disable(mCurrentWeapon); + clearStateAnimation(mCurrentWeapon); } } @@ -1766,7 +1755,7 @@ bool CharacterController::updateState(CharacterState idle) } else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) { - mAnimation->disable(mCurrentWeapon); + clearStateAnimation(mCurrentWeapon); mUpperBodyState = UpperCharState_WeapEquiped; } @@ -2086,7 +2075,7 @@ void CharacterController::update(float duration) vec.z() = 0.0f; // We should reset idle animation during landing - mAnimation->disable(mCurrentIdle); + clearStateAnimation(mCurrentIdle); float height = cls.getCreatureStats(mPtr).land(isPlayer); float healthLost = getFallDamage(mPtr, height); @@ -2429,8 +2418,7 @@ void CharacterController::unpersistAnimationState() complete = (time - start) / (stop - start); } - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); + clearStateAnimation(mCurrentIdle); mIdleState = CharState_SpecialIdle; bool loopfallback = (mAnimQueue.front().mGroup.compare(0,4,"idle") == 0); @@ -2480,8 +2468,7 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int { clearAnimQueue(persist); - mAnimation->disable(mCurrentIdle); - mCurrentIdle.clear(); + clearStateAnimation(mCurrentIdle); mIdleState = CharState_SpecialIdle; bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0); @@ -2575,11 +2562,7 @@ CharacterController::KillResult CharacterController::kill() if (mDeathState == CharState_None) { playRandomDeath(); - - mAnimation->disable(mCurrentIdle); - - mIdleState = CharState_None; - mCurrentIdle.clear(); + resetCurrentIdleState(); return Result_DeathAnimStarted; } @@ -2599,10 +2582,7 @@ void CharacterController::resurrect() if(mDeathState == CharState_None) return; - if(mAnimation) - mAnimation->disable(mCurrentDeath); - mCurrentDeath.clear(); - mDeathState = CharState_None; + resetCurrentDeathState(); mWeaponType = ESM::Weapon::None; } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 1ae09c9d1a..1f1effed28 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -197,6 +197,14 @@ class CharacterController : public MWRender::Animation::TextKeyListener std::string getMovementBasedAttackType() const; + void clearStateAnimation(std::string &anim) const; + void resetCurrentJumpState(); + void resetCurrentMovementState(); + void resetCurrentIdleState(); + void resetCurrentHitState(); + void resetCurrentWeaponState(); + void resetCurrentDeathState(); + void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void refreshHitRecoilAnims(CharacterState& idle); void refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force=false); From b8018024a690da86c4a5cead5d316f576246e632 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 13 Jun 2022 16:21:48 +0300 Subject: [PATCH 7/8] Avoid passing weapon short group to refreshXAnims --- apps/openmw/mwmechanics/character.cpp | 31 ++++++++++++++++----------- apps/openmw/mwmechanics/character.hpp | 7 +++--- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2a975fa649..2e3777e5b0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -425,7 +425,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) idle = CharState_None; } -void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force) +void CharacterController::refreshJumpAnims(JumpingState jump, CharacterState& idle, bool force) { if (!force && jump == mJumpState && idle == CharState_None) return; @@ -436,6 +436,7 @@ void CharacterController::refreshJumpAnims(const std::string& weapShortGroup, Ju return; } + std::string weapShortGroup = getWeaponShortGroup(mWeaponType); std::string jumpAnimName = "jump" + weapShortGroup; MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All; if (!weapShortGroup.empty() && !mAnimation->hasAnimation(jumpAnimName)) @@ -525,6 +526,13 @@ std::string CharacterController::getWeaponAnimation(int weaponType) const return weaponGroup; } +std::string CharacterController::getWeaponShortGroup(int weaponType) const +{ + if (weaponType == ESM::Weapon::HandToHand && !mPtr.getClass().isBipedal(mPtr)) + return {}; + return getWeaponType(weaponType)->mShortGroup; +} + std::string CharacterController::fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask) const { bool isRealWeapon = mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; @@ -536,8 +544,8 @@ std::string CharacterController::fallbackShortWeaponGroup(const std::string& bas return baseGroupName; } - static const std::string oneHandFallback = getWeaponType(ESM::Weapon::LongBladeOneHand)->mShortGroup; - static const std::string twoHandFallback = getWeaponType(ESM::Weapon::LongBladeTwoHand)->mShortGroup; + static const std::string oneHandFallback = getWeaponShortGroup(ESM::Weapon::LongBladeOneHand); + static const std::string twoHandFallback = getWeaponShortGroup(ESM::Weapon::LongBladeTwoHand); std::string groupName = baseGroupName; const ESM::WeaponType* weapInfo = getWeaponType(mWeaponType); @@ -562,7 +570,7 @@ std::string CharacterController::fallbackShortWeaponGroup(const std::string& bas return groupName; } -void CharacterController::refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force) +void CharacterController::refreshMovementAnims(CharacterState movement, CharacterState& idle, bool force) { if (movement == mMovementState && idle == mIdleState && !force) return; @@ -587,6 +595,7 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; + std::string weapShortGroup = getWeaponShortGroup(mWeaponType); if (swimpos == std::string::npos && !weapShortGroup.empty()) { std::string weapMovementAnimName; @@ -684,7 +693,7 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); } -void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force) +void CharacterController::refreshIdleAnims(CharacterState idle, bool force) { // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // the idle animation should be displayed @@ -714,6 +723,7 @@ void CharacterController::refreshIdleAnims(const std::string& weapShortGroup, Ch return; } + std::string weapShortGroup = getWeaponShortGroup(mWeaponType); if (mIdleState != CharState_IdleSwim && mIdleState != CharState_IdleSneak && mIdleState != CharState_None && !weapShortGroup.empty()) { std::string weapIdleGroup = idleGroup + weapShortGroup; @@ -751,16 +761,11 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat return; refreshHitRecoilAnims(idle); - - std::string weap; - if (mWeaponType != ESM::Weapon::HandToHand || mPtr.getClass().isBipedal(mPtr)) - weap = getWeaponType(mWeaponType)->mShortGroup; - - refreshJumpAnims(weap, jump, idle, force); - refreshMovementAnims(weap, movement, idle, force); + refreshJumpAnims(jump, idle, force); + refreshMovementAnims(movement, idle, force); // idle handled last as it can depend on the other states - refreshIdleAnims(weap, idle, force); + refreshIdleAnims(idle, force); } void CharacterController::playDeath(float startpoint, CharacterState death) diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 1f1effed28..bd9fa70711 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -207,9 +207,9 @@ class CharacterController : public MWRender::Animation::TextKeyListener void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void refreshHitRecoilAnims(CharacterState& idle); - void refreshJumpAnims(const std::string& weapShortGroup, JumpingState jump, CharacterState& idle, bool force=false); - void refreshMovementAnims(const std::string& weapShortGroup, CharacterState movement, CharacterState& idle, bool force=false); - void refreshIdleAnims(const std::string& weapShortGroup, CharacterState idle, bool force=false); + void refreshJumpAnims(JumpingState jump, CharacterState& idle, bool force=false); + void refreshMovementAnims(CharacterState movement, CharacterState& idle, bool force=false); + void refreshIdleAnims(CharacterState idle, bool force=false); void clearAnimQueue(bool clearPersistAnims = false); @@ -240,6 +240,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener std::string fallbackShortWeaponGroup(const std::string& baseGroupName, MWRender::Animation::BlendMask* blendMask = nullptr) const; std::string getWeaponAnimation(int weaponType) const; + std::string getWeaponShortGroup(int weaponType) const; bool getAttackingOrSpell() const; void setAttackingOrSpell(bool attackingOrSpell) const; From 253de65d13f93dee18fd7cc3f027ca5bdb3c51d5 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 13 Jun 2022 17:14:12 +0300 Subject: [PATCH 8/8] Fix knockout animation interruption/looping --- apps/openmw/mwmechanics/character.cpp | 18 +++--------------- apps/openmw/mwmechanics/character.hpp | 2 -- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2e3777e5b0..1f1aa80856 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -329,7 +329,6 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) bool knockdown = stats.getKnockedDown(); bool block = stats.getBlock(); bool isSwimming = world->isSwimming(mPtr); - auto& prng = world->getPrng(); if (mHitState != CharState_None) { @@ -341,29 +340,21 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) stats.setHitRecovery(false); stats.setBlock(false); } - else if (isKnockedOut() && !knockout && mTimeUntilWake <= 0) - { - mAnimation->disable(mCurrentHit); - mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); - } + else if (isKnockedOut()) + mAnimation->setLoopingEnabled(mCurrentHit, knockout); return; } if (!knockout && !knockdown && !recovery && !block) return; - if (knockout) - mTimeUntilWake = Misc::Rng::rollClosedProbability(prng) * 2 + 1; // Wake up after 1 to 3 seconds - MWRender::Animation::AnimPriority priority(Priority_Knockdown); - bool autodisable = true; std::string startKey = "start"; std::string stopKey = "stop"; if (knockout) { mHitState = isSwimming ? CharState_SwimKnockOut : CharState_KnockOut; stats.setKnockedDown(true); - autodisable = false; } else if (knockdown) { @@ -420,7 +411,7 @@ void CharacterController::refreshHitRecoilAnims(CharacterState& idle) } } - mAnimation->play(mCurrentHit, priority, MWRender::Animation::BlendMask_All, autodisable, 1, startKey, stopKey, 0.0f, ~0ul); + mAnimation->play(mCurrentHit, priority, MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, ~0ul); idle = CharState_None; } @@ -1817,9 +1808,6 @@ void CharacterController::update(float duration) updateMagicEffects(); - if (isKnockedOut()) - mTimeUntilWake -= duration; - bool isPlayer = mPtr == MWMechanics::getPlayer(); bool isFirstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson(); bool godmode = isPlayer && MWBase::Environment::get().getWorld()->getGodModeState(); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index bd9fa70711..a15647d5be 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -190,8 +190,6 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool mCastingManualSpell{false}; - float mTimeUntilWake{0.f}; - bool mIsMovingBackward{false}; osg::Vec2f mSmoothedSpeed;