From e5ef9f14646c05edd13f71c3f7dc4930233e3a95 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Aug 2022 19:46:02 +0300 Subject: [PATCH 1/7] Improve upper body character state naming --- apps/openmw/mwmechanics/character.cpp | 115 +++++++++++++------------- apps/openmw/mwmechanics/character.hpp | 25 +++--- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index bde2a8ce18..27b31181ad 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -307,7 +307,7 @@ void CharacterController::resetCurrentHitState() void CharacterController::resetCurrentWeaponState() { clearStateAnimation(mCurrentWeapon); - mUpperBodyState = UpperCharState_Nothing; + mUpperBodyState = UpperBodyState::None; } void CharacterController::resetCurrentDeathState() @@ -401,15 +401,15 @@ void CharacterController::refreshHitRecoilAnims() { if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); - if (mUpperBodyState > UpperCharState_WeapEquiped) + if (mUpperBodyState > UpperBodyState::WeaponEquipped) { - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } - else if (mUpperBodyState < UpperCharState_WeapEquiped) + else if (mUpperBodyState < UpperBodyState::WeaponEquipped) { - mUpperBodyState = UpperCharState_Nothing; + mUpperBodyState = UpperBodyState::None; } } @@ -680,7 +680,7 @@ 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 - if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped) + if (((mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped) || mMovementState != CharState_None || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr)) { resetCurrentIdleState(); @@ -855,7 +855,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim getActiveWeapon(mPtr, &mWeaponType); if (mWeaponType != ESM::Weapon::None) { - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; mCurrentWeapon = getWeaponAnimation(mWeaponType); } @@ -1071,7 +1071,7 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr) void CharacterController::updateIdleStormState(bool inwater) const { - if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperCharState_Nothing || inwater) + if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperBodyState::None || inwater) { mAnimation->disable("idlestorm"); return; @@ -1171,12 +1171,12 @@ bool CharacterController::updateWeaponState(CharacterState idle) // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires), // we should force actor to the "weapon equipped" state, interrupt attack and update animations. - if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped) + if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperBodyState::WeaponEquipped) { forcestateupdate = true; if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; setAttackingOrSpell(false); mAnimation->showWeapons(true); stats.setAttackingOrSpell(false); @@ -1187,7 +1187,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) std::string weapgroup; if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell) && weaptype != mWeaponType - && mUpperBodyState != UpperCharState_UnEquipingWeap + && mUpperBodyState != UpperBodyState::Unequipping && !isStillWeapon) { // We can not play un-equip animation if weapon changed since last update @@ -1209,7 +1209,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) mAnimation->play(weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_UnEquipingWeap; + mUpperBodyState = UpperBodyState::Unequipping; mAnimation->detachArrow(); @@ -1262,7 +1262,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) mAnimation->play(weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start", "equip stop", 0.0f, 0); - mUpperBodyState = UpperCharState_EquipingWeap; + mUpperBodyState = UpperBodyState::Equipping; // If we do not have the "equip attach" key, show weapon manually. if (weaptype != ESM::Weapon::Spell) @@ -1293,7 +1293,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) } // Make sure that we disabled unequipping animation - if (mUpperBodyState == UpperCharState_UnEquipingWeap) + if (mUpperBodyState == UpperBodyState::Unequipping) { resetCurrentWeaponState(); mWeaponType = ESM::Weapon::None; @@ -1335,11 +1335,11 @@ bool CharacterController::updateWeaponState(CharacterState idle) ammunition = false; } - if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + if (!ammunition && mUpperBodyState > UpperBodyState::WeaponEquipped) { if (!mCurrentWeapon.empty()) mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; } } @@ -1353,7 +1353,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) if(getAttackingOrSpell()) { bool resetIdle = ammunition; - if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) + if (mUpperBodyState == UpperBodyState::WeaponEquipped && (mHitState == CharState_None || mHitState == CharState_Block)) { mAttackStrength = 0; @@ -1411,7 +1411,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) resetIdle = false; // Spellcasting animation needs to "play" for at least one frame to reset the aiming factor animPlaying = true; - mUpperBodyState = UpperCharState_CastingSpell; + mUpperBodyState = UpperBodyState::Casting; } // Play the spellcasting animation/VFX if the spellcasting was successful or failed due to insufficient magicka. // Used up powers are exempt from this from some reason. @@ -1485,7 +1485,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) MWRender::Animation::BlendMask_All, true, 1, startKey, stopKey, 0.0f, 0); - mUpperBodyState = UpperCharState_CastingSpell; + mUpperBodyState = UpperBodyState::Casting; } else { @@ -1511,7 +1511,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, true, 1.0f, "start", "stop", 0.0, 0); - mUpperBodyState = UpperCharState_FollowStartToFollowStop; + mUpperBodyState = UpperBodyState::AttackEnd; if(!resultMessage.empty()) MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); @@ -1567,7 +1567,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) 0.0f, 0); if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) { - mUpperBodyState = UpperCharState_StartToMinAttack; + mUpperBodyState = UpperBodyState::AttackPreWindUp; if (isRandomAttackAnimation(mCurrentWeapon)) { mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); @@ -1587,13 +1587,13 @@ bool CharacterController::updateWeaponState(CharacterState idle) if (!animPlaying) animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) + if (mUpperBodyState == UpperBodyState::AttackWindUp && !isKnockedDown()) mAttackStrength = complete; } else { animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) + if (mUpperBodyState == UpperBodyState::AttackWindUp && !isKnockedDown()) { world->breakInvisibility(mPtr); float attackStrength = complete; @@ -1630,13 +1630,13 @@ bool CharacterController::updateWeaponState(CharacterState idle) 1.0f-complete, 0); complete = 0.f; - mUpperBodyState = UpperCharState_MaxAttackToMinHit; + mUpperBodyState = UpperBodyState::AttackRelease; } else if (isKnockedDown()) { - if (mUpperBodyState > UpperCharState_WeapEquiped) + if (mUpperBodyState > UpperBodyState::WeaponEquipped) { - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; if (mWeaponType > ESM::Weapon::None) mAnimation->showWeapons(true); } @@ -1650,15 +1650,15 @@ bool CharacterController::updateWeaponState(CharacterState idle) { switch (mUpperBodyState) { - case UpperCharState_StartToMinAttack: + case UpperBodyState::AttackPreWindUp: mAnimation->setPitchFactor(complete); break; - case UpperCharState_MinAttackToMaxAttack: - case UpperCharState_MaxAttackToMinHit: - case UpperCharState_MinHitToHit: + case UpperBodyState::AttackWindUp: + case UpperBodyState::AttackRelease: + case UpperBodyState::AttackHit: mAnimation->setPitchFactor(1.f); break; - case UpperCharState_FollowStartToFollowStop: + case UpperBodyState::AttackEnd: if (animPlaying) { // technically we do not need a pitch for crossbow reload animation, @@ -1676,39 +1676,39 @@ bool CharacterController::updateWeaponState(CharacterState idle) if(!animPlaying) { - if(mUpperBodyState == UpperCharState_EquipingWeap || - mUpperBodyState == UpperCharState_FollowStartToFollowStop || - mUpperBodyState == UpperCharState_CastingSpell) + if (mUpperBodyState == UpperBodyState::Equipping || + mUpperBodyState == UpperBodyState::AttackEnd || + mUpperBodyState == UpperBodyState::Casting) { if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow) mAnimation->attachArrow(); // Cancel stagger animation at the end of an attack to avoid abrupt transitions // in favor of a different abrupt transition, like Morrowind - if (mUpperBodyState != UpperCharState_EquipingWeap && isRecovery()) + if (mUpperBodyState != UpperBodyState::Equipping && isRecovery()) mAnimation->disable(mCurrentHit); - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; } - else if(mUpperBodyState == UpperCharState_UnEquipingWeap) - mUpperBodyState = UpperCharState_Nothing; + else if (mUpperBodyState == UpperBodyState::Unequipping) + mUpperBodyState = UpperBodyState::None; } else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon)) { std::string start, stop; switch(mUpperBodyState) { - case UpperCharState_MinAttackToMaxAttack: + case UpperBodyState::AttackWindUp: //hack to avoid body pos desync when jumping/sneaking in 'max attack' state if(!mAnimation->isPlaying(mCurrentWeapon)) mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); break; - case UpperCharState_StartToMinAttack: - case UpperCharState_MaxAttackToMinHit: + case UpperBodyState::AttackPreWindUp: + case UpperBodyState::AttackRelease: { - if (mUpperBodyState == UpperCharState_StartToMinAttack) + if (mUpperBodyState == UpperBodyState::AttackPreWindUp) { // If actor is already stopped preparing attack, do not play the "min attack -> max attack" part. // Happens if the player did not hold the attack button. @@ -1719,7 +1719,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) { start = mAttackType+" min attack"; stop = mAttackType+" max attack"; - mUpperBodyState = UpperCharState_MinAttackToMaxAttack; + mUpperBodyState = UpperBodyState::AttackWindUp; break; } @@ -1738,10 +1738,10 @@ bool CharacterController::updateWeaponState(CharacterState idle) start = mAttackType+" min hit"; stop = mAttackType+" hit"; } - mUpperBodyState = UpperCharState_MinHitToHit; + mUpperBodyState = UpperBodyState::AttackHit; break; } - case UpperCharState_MinHitToHit: + case UpperBodyState::AttackHit: if(mAttackType == "shoot") { start = mAttackType+" follow start"; @@ -1757,7 +1757,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) : (str < 1.0f) ? " medium follow stop" : " large follow stop"); } - mUpperBodyState = UpperCharState_FollowStartToFollowStop; + mUpperBodyState = UpperBodyState::AttackEnd; break; default: break; @@ -1772,7 +1772,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) mask = MWRender::Animation::BlendMask_UpperBody; mAnimation->disable(mCurrentWeapon); - if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) + if (mUpperBodyState == UpperBodyState::AttackEnd) mAnimation->play(mCurrentWeapon, priorityWeapon, mask, true, weapSpeed, start, stop, 0.0f, 0); @@ -1785,7 +1785,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) { clearStateAnimation(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + mUpperBodyState = UpperBodyState::WeaponEquipped; } if (cls.hasInventoryStore(mPtr)) @@ -1807,7 +1807,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) } } - mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped); + mAnimation->setAccurateAiming(mUpperBodyState > UpperBodyState::WeaponEquipped); return forcestateupdate; } @@ -2567,8 +2567,8 @@ void CharacterController::forceStateUpdate() mCanCast = false; mCastingManualSpell = false; setAttackingOrSpell(false); - if (mUpperBodyState != UpperCharState_Nothing) - mUpperBodyState = UpperCharState_WeapEquiped; + if (mUpperBodyState != UpperBodyState::None) + mUpperBodyState = UpperBodyState::WeaponEquipped; refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true); @@ -2687,13 +2687,13 @@ bool CharacterController::isRandomAttackAnimation(std::string_view group) bool CharacterController::isAttackPreparing() const { - return mUpperBodyState == UpperCharState_StartToMinAttack || - mUpperBodyState == UpperCharState_MinAttackToMaxAttack; + return mUpperBodyState == UpperBodyState::AttackPreWindUp || + mUpperBodyState == UpperBodyState::AttackWindUp; } bool CharacterController::isCastingSpell() const { - return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell; + return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting; } bool CharacterController::isReadyToBlock() const @@ -2729,8 +2729,7 @@ bool CharacterController::isRecovery() const bool CharacterController::isAttackingOrSpell() const { - return mUpperBodyState != UpperCharState_Nothing && - mUpperBodyState != UpperCharState_WeapEquiped; + return mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped; } bool CharacterController::isSneaking() const @@ -2786,7 +2785,7 @@ std::string_view CharacterController::getRandomAttackType() bool CharacterController::readyToPrepareAttack() const { return (mHitState == CharState_None || mHitState == CharState_Block) - && mUpperBodyState <= UpperCharState_WeapEquiped; + && mUpperBodyState <= UpperBodyState::WeaponEquipped; } bool CharacterController::readyToStartAttack() const @@ -2794,7 +2793,7 @@ bool CharacterController::readyToStartAttack() const if (mHitState != CharState_None && mHitState != CharState_Block) return false; - return mUpperBodyState == UpperCharState_WeapEquiped; + return mUpperBodyState == UpperBodyState::WeaponEquipped; } float CharacterController::getAttackStrength() const diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 42c60b5ffb..3988ac7d5e 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -103,17 +103,18 @@ enum CharacterState { CharState_Block }; -enum UpperBodyCharacterState { - UpperCharState_Nothing, - UpperCharState_EquipingWeap, - UpperCharState_UnEquipingWeap, - UpperCharState_WeapEquiped, - UpperCharState_StartToMinAttack, - UpperCharState_MinAttackToMaxAttack, - UpperCharState_MaxAttackToMinHit, - UpperCharState_MinHitToHit, - UpperCharState_FollowStartToFollowStop, - UpperCharState_CastingSpell +enum class UpperBodyState +{ + None, + Equipping, + Unequipping, + WeaponEquipped, + AttackPreWindUp, + AttackWindUp, + AttackRelease, + AttackHit, + AttackEnd, + Casting }; enum JumpingState { @@ -156,7 +157,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener CharacterState mHitState{CharState_None}; std::string mCurrentHit; - UpperBodyCharacterState mUpperBodyState{UpperCharState_Nothing}; + UpperBodyState mUpperBodyState{UpperBodyState::None}; JumpingState mJumpState{JumpState_None}; std::string mCurrentJump; From ac892f2bfd0eb8017bae51c50aebd3d1a185c6ac Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Aug 2022 20:17:02 +0300 Subject: [PATCH 2/7] Clean up updateWeaponState() --- apps/openmw/mwmechanics/character.cpp | 94 +++++++++++---------------- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 39 insertions(+), 57 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 27b31181ad..6a9cfd353e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1113,7 +1113,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const return mAnimation->updateCarriedLeftVisible(weaptype); } -bool CharacterController::updateWeaponState(CharacterState idle) +bool CharacterController::updateWeaponState() { const auto world = MWBase::Environment::get().getWorld(); auto& prng = world->getPrng(); @@ -1166,8 +1166,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) bool forcestateupdate = false; // We should not play equipping animation and sound during weapon->weapon transition - const bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None && - mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None; + const bool isStillWeapon = isRealWeapon(mWeaponType) && isRealWeapon(weaptype); // If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires), // we should force actor to the "weapon equipped" state, interrupt attack and update animations. @@ -1179,7 +1178,6 @@ bool CharacterController::updateWeaponState(CharacterState idle) mUpperBodyState = UpperBodyState::WeaponEquipped; setAttackingOrSpell(false); mAnimation->showWeapons(true); - stats.setAttackingOrSpell(false); } if(!isKnockedOut() && !isKnockedDown() && !isRecovery()) @@ -1247,7 +1245,8 @@ bool CharacterController::updateWeaponState(CharacterState idle) if (!isStillWeapon) { - clearStateAnimation(mCurrentWeapon); + if (animPlaying) + mAnimation->disable(mCurrentWeapon); if (weaptype != ESM::Weapon::None) { mAnimation->showWeapons(false); @@ -1265,12 +1264,15 @@ bool CharacterController::updateWeaponState(CharacterState idle) mUpperBodyState = UpperBodyState::Equipping; // If we do not have the "equip attach" key, show weapon manually. - if (weaptype != ESM::Weapon::Spell) + if (weaptype != ESM::Weapon::Spell && mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) { - if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) - mAnimation->showWeapons(true); + mAnimation->showWeapons(true); } } + if (!upSoundId.empty()) + { + sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); + } } if(isWerewolf) @@ -1286,10 +1288,6 @@ bool CharacterController::updateWeaponState(CharacterState idle) mWeaponType = weaptype; mCurrentWeapon = weapgroup; - if(!upSoundId.empty() && !isStillWeapon) - { - sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f); - } } // Make sure that we disabled unequipping animation @@ -1297,7 +1295,6 @@ bool CharacterController::updateWeaponState(CharacterState idle) { resetCurrentWeaponState(); mWeaponType = ESM::Weapon::None; - mCurrentWeapon = getWeaponAnimation(mWeaponType); } } } @@ -1319,20 +1316,17 @@ bool CharacterController::updateWeaponState(CharacterState idle) // Cancel attack if we no longer have ammunition bool ammunition = true; - bool isWeapon = false; float weapSpeed = 1.f; if (cls.hasInventoryStore(mPtr)) { MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype); - isWeapon = (weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId); - if (isWeapon) + if (stats.getDrawState() == DrawState::Weapon && !mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId) { - weapSpeed = weapon->get()->mBase->mData.mSpeed; + weapSpeed = mWeapon.get()->mBase->mData.mSpeed; MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); - int ammotype = getWeaponType(weapon->get()->mBase->mData.mType)->mAmmoType; - if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get()->mBase->mData.mType != ammotype)) - ammunition = false; + int ammotype = getWeaponType(mWeapon.get()->mBase->mData.mType)->mAmmoType; + if (ammotype != ESM::Weapon::None) + ammunition = ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype; } if (!ammunition && mUpperBodyState > UpperBodyState::WeaponEquipped) @@ -1341,6 +1335,20 @@ bool CharacterController::updateWeaponState(CharacterState idle) mAnimation->disable(mCurrentWeapon); mUpperBodyState = UpperBodyState::WeaponEquipped; } + + MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && updateCarriedLeftVisible(mWeaponType)) + { + if (mAnimation->isPlaying("shield")) + mAnimation->disable("shield"); + + mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, + false, 1.0f, "start", "stop", 0.0f, std::numeric_limits::max(), true); + } + else if (mAnimation->isPlaying("torch")) + { + mAnimation->disable("torch"); + } } // Combat for actors with persistent animations obviously will be buggy @@ -1358,9 +1366,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) mAttackStrength = 0; // Randomize attacks for non-bipedal creatures - if (cls.getType() == ESM::Creature::sRecordId && - !cls.isBipedal(mPtr) && - (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) + if (!cls.isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) { mCurrentWeapon = chooseRandomAttackAnimation(); } @@ -1495,18 +1501,16 @@ bool CharacterController::updateWeaponState(CharacterState idle) else if(mWeaponType == ESM::Weapon::PickProbe) { world->breakInvisibility(mPtr); - MWWorld::ContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - MWWorld::Ptr item = *weapon; // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. MWWorld::Ptr target = world->getFacedObject(); std::string resultMessage, resultSound; if(!target.isEmpty()) { - if(item.getType() == ESM::Lockpick::sRecordId) - Security(mPtr).pickLock(target, item, resultMessage, resultSound); - else if(item.getType() == ESM::Probe::sRecordId) - Security(mPtr).probeTrap(target, item, resultMessage, resultSound); + if (mWeapon.getType() == ESM::Lockpick::sRecordId) + Security(mPtr).pickLock(target, mWeapon, resultMessage, resultSound); + else if (mWeapon.getType() == ESM::Probe::sRecordId) + Security(mPtr).probeTrap(target, mWeapon, resultMessage, resultSound); } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, true, @@ -1540,10 +1544,9 @@ bool CharacterController::updateWeaponState(CharacterState idle) { if (Settings::Manager::getBool("best attack", "Game")) { - if (isWeapon) + if (!mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId) { - MWWorld::ConstContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - mAttackType = getBestAttack(weapon->get()->mBase); + mAttackType = getBestAttack(mWeapon.get()->mBase); } else { @@ -1578,9 +1581,7 @@ bool CharacterController::updateWeaponState(CharacterState idle) } // We should not break swim and sneak animations - if (resetIdle && - idle != CharState_IdleSneak && idle != CharState_IdleSwim && - mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) + if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) { resetCurrentIdleState(); } @@ -1788,25 +1789,6 @@ bool CharacterController::updateWeaponState(CharacterState idle) mUpperBodyState = UpperBodyState::WeaponEquipped; } - if (cls.hasInventoryStore(mPtr)) - { - const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); - MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId - && updateCarriedLeftVisible(mWeaponType)) - { - if (mAnimation->isPlaying("shield")) - mAnimation->disable("shield"); - - mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm, - false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); - } - else if (mAnimation->isPlaying("torch")) - { - mAnimation->disable("torch"); - } - } - mAnimation->setAccurateAiming(mUpperBodyState > UpperBodyState::WeaponEquipped); return forcestateupdate; @@ -2270,7 +2252,7 @@ void CharacterController::update(float duration) if (!mSkipAnim) { - refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState(idlestate)); + refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState()); updateIdleStormState(inwater); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 3988ac7d5e..3832bd9ee1 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -204,7 +204,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener void clearAnimQueue(bool clearPersistAnims = false); - bool updateWeaponState(CharacterState idle); + bool updateWeaponState(); void updateIdleStormState(bool inwater) const; std::string chooseRandomAttackAnimation() const; From d280a29b18c5e80788f610984e3f70cd52d98444 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Aug 2022 20:19:31 +0300 Subject: [PATCH 3/7] Re-enable lower body crossbow animation playback --- apps/openmw/mwmechanics/character.cpp | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6a9cfd353e..fa90cc1194 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1764,23 +1764,11 @@ bool CharacterController::updateWeaponState() break; } - // Note: apply crossbow reload animation only for upper body - // since blending with movement animations can give weird result. if(!start.empty()) { - int mask = MWRender::Animation::BlendMask_All; - if (mWeaponType == ESM::Weapon::MarksmanCrossbow) - mask = MWRender::Animation::BlendMask_UpperBody; - + bool autodisable = mUpperBodyState == UpperBodyState::AttackEnd; mAnimation->disable(mCurrentWeapon); - if (mUpperBodyState == UpperBodyState::AttackEnd) - mAnimation->play(mCurrentWeapon, priorityWeapon, - mask, true, - weapSpeed, start, stop, 0.0f, 0); - else - mAnimation->play(mCurrentWeapon, priorityWeapon, - mask, false, - weapSpeed, start, stop, 0.0f, 0); + mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, autodisable, weapSpeed, start, stop, 0.0f, 0); } } else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) From a4b5bfc051313c66b97a9a2c92646ca75f666539 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Aug 2022 20:21:11 +0300 Subject: [PATCH 4/7] Don't cancel the attack prematurely after running out of ammo --- apps/openmw/mwmechanics/character.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index fa90cc1194..7b7a3d2e58 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1314,7 +1314,6 @@ bool CharacterController::updateWeaponState() sndMgr->stopSound3D(mPtr, "WolfRun"); } - // Cancel attack if we no longer have ammunition bool ammunition = true; float weapSpeed = 1.f; if (cls.hasInventoryStore(mPtr)) @@ -1329,11 +1328,15 @@ bool CharacterController::updateWeaponState() ammunition = ammo != inv.end() && ammo->get()->mBase->mData.mType == ammotype; } - if (!ammunition && mUpperBodyState > UpperBodyState::WeaponEquipped) + // Cancel attack if we no longer have ammunition + if (!ammunition) { - if (!mCurrentWeapon.empty()) + if (mUpperBodyState == UpperBodyState::AttackPreWindUp || mUpperBodyState == UpperBodyState::AttackWindUp) + { mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperBodyState::WeaponEquipped; + mUpperBodyState = UpperBodyState::WeaponEquipped; + } + setAttackingOrSpell(false); } MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); @@ -1360,7 +1363,7 @@ bool CharacterController::updateWeaponState() ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; if(getAttackingOrSpell()) { - bool resetIdle = ammunition; + bool resetIdle = true; if (mUpperBodyState == UpperBodyState::WeaponEquipped && (mHitState == CharState_None || mHitState == CharState_Block)) { mAttackStrength = 0; @@ -1522,7 +1525,7 @@ bool CharacterController::updateWeaponState() if(!resultSound.empty()) sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f); } - else if (ammunition) + else { std::string startKey; std::string stopKey; From 8f280c521c26fe5a1ad57d61ef8c525d5d399624 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Aug 2022 20:41:25 +0300 Subject: [PATCH 5/7] Play pick/probe animation in attack start animation logic --- apps/openmw/mwmechanics/character.cpp | 69 ++++++++++++--------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 7b7a3d2e58..c04d2196b0 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1501,49 +1501,40 @@ bool CharacterController::updateWeaponState() resetIdle = false; } } - else if(mWeaponType == ESM::Weapon::PickProbe) - { - world->breakInvisibility(mPtr); - // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. - MWWorld::Ptr target = world->getFacedObject(); - std::string resultMessage, resultSound; - - if(!target.isEmpty()) - { - if (mWeapon.getType() == ESM::Lockpick::sRecordId) - Security(mPtr).pickLock(target, mWeapon, resultMessage, resultSound); - else if (mWeapon.getType() == ESM::Probe::sRecordId) - Security(mPtr).probeTrap(target, mWeapon, resultMessage, resultSound); - } - mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, true, - 1.0f, "start", "stop", 0.0, 0); - mUpperBodyState = UpperBodyState::AttackEnd; - - if(!resultMessage.empty()) - MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); - if(!resultSound.empty()) - sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f); - } else { - std::string startKey; - std::string stopKey; + std::string startKey = "start"; + std::string stopKey = "stop"; + bool autodisable = false; - if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) + if (mWeaponType == ESM::Weapon::PickProbe) { - mAttackType = "shoot"; - startKey = mAttackType+" start"; - stopKey = mAttackType+" min attack"; + autodisable = true; + mUpperBodyState = UpperBodyState::AttackEnd; + + world->breakInvisibility(mPtr); + // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. + MWWorld::Ptr target = world->getFacedObject(); + std::string resultMessage, resultSound; + + if(!target.isEmpty()) + { + if (mWeapon.getType() == ESM::Lockpick::sRecordId) + Security(mPtr).pickLock(target, mWeapon, resultMessage, resultSound); + else if (mWeapon.getType() == ESM::Probe::sRecordId) + Security(mPtr).probeTrap(target, mWeapon, resultMessage, resultSound); + } + + if (!resultMessage.empty()) + MWBase::Environment::get().getWindowManager()->messageBox(resultMessage); + if (!resultSound.empty()) + sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f); } - else if (isRandomAttackAnimation(mCurrentWeapon)) + else if (!isRandomAttackAnimation(mCurrentWeapon)) { - startKey = "start"; - stopKey = "stop"; - } - else - { - if(mPtr == getPlayer()) + if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) + mAttackType = "shoot"; + else if (mPtr == getPlayer()) { if (Settings::Manager::getBool("best attack", "Game")) { @@ -1568,10 +1559,10 @@ bool CharacterController::updateWeaponState() } mAnimation->play(mCurrentWeapon, priorityWeapon, - MWRender::Animation::BlendMask_All, false, + MWRender::Animation::BlendMask_All, autodisable, weapSpeed, startKey, stopKey, 0.0f, 0); - if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) + if (mWeaponType != ESM::Weapon::PickProbe && mAnimation->getCurrentTime(mCurrentWeapon) != -1.f) { mUpperBodyState = UpperBodyState::AttackPreWindUp; if (isRandomAttackAnimation(mCurrentWeapon)) From 09141388ad6004f2818f5d5e3c4867f3d2a1a9cd Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Aug 2022 21:14:58 +0300 Subject: [PATCH 6/7] Detangle attack start, knockdown attack cancel and on-going wind-up logic --- apps/openmw/mwmechanics/character.cpp | 50 +++++++++++++-------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c04d2196b0..6e10134346 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1579,19 +1579,30 @@ bool CharacterController::updateWeaponState() { resetCurrentIdleState(); } - - if (!animPlaying) - animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if (mUpperBodyState == UpperBodyState::AttackWindUp && !isKnockedDown()) - mAttackStrength = complete; } - else - { + + if (!animPlaying) animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if (mUpperBodyState == UpperBodyState::AttackWindUp && !isKnockedDown()) + + if (isKnockedDown()) + { + if (mUpperBodyState > UpperBodyState::WeaponEquipped) + { + mUpperBodyState = UpperBodyState::WeaponEquipped; + if (mWeaponType > ESM::Weapon::None) + mAnimation->showWeapons(true); + } + if (!mCurrentWeapon.empty()) + mAnimation->disable(mCurrentWeapon); + } + + if (mUpperBodyState == UpperBodyState::AttackWindUp) + { + mAttackStrength = complete; + + if (!getAttackingOrSpell()) { world->breakInvisibility(mPtr); - float attackStrength = complete; float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack"); float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack"); if (minAttackTime == maxAttackTime) @@ -1599,12 +1610,12 @@ bool CharacterController::updateWeaponState() // most creatures don't actually have an attack wind-up animation, so use a uniform random value // (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings) // Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far. - attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); + mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); } if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) { - if(isWerewolf) + if (isWerewolf) { const MWWorld::ESMStore &store = world->getStore(); const ESM::Sound *sound = store.get().searchRandom("WolfSwing", prng); @@ -1613,12 +1624,12 @@ bool CharacterController::updateWeaponState() } else { - playSwishSound(attackStrength); + playSwishSound(mAttackStrength); } } - mAttackStrength = attackStrength; - mAnimation->disable(mCurrentWeapon); + if (animPlaying) + mAnimation->disable(mCurrentWeapon); mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, weapSpeed, mAttackType+" max attack", mAttackType+" min hit", @@ -1627,17 +1638,6 @@ bool CharacterController::updateWeaponState() complete = 0.f; mUpperBodyState = UpperBodyState::AttackRelease; } - else if (isKnockedDown()) - { - if (mUpperBodyState > UpperBodyState::WeaponEquipped) - { - mUpperBodyState = UpperBodyState::WeaponEquipped; - if (mWeaponType > ESM::Weapon::None) - mAnimation->showWeapons(true); - } - if (!mCurrentWeapon.empty()) - mAnimation->disable(mCurrentWeapon); - } } mAnimation->setPitchFactor(0.f); From ad62f5cda324c85e4eac0f53e93909a738418fac Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Aug 2022 21:37:08 +0300 Subject: [PATCH 7/7] Move werewolf/ranged weapon swish logic to playSwishSound --- apps/openmw/mwmechanics/character.cpp | 50 +++++++++++++++------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6e10134346..e1c71209ca 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1613,20 +1613,7 @@ bool CharacterController::updateWeaponState() mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng)); } - if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) - { - if (isWerewolf) - { - const MWWorld::ESMStore &store = world->getStore(); - const ESM::Sound *sound = store.get().searchRandom("WolfSwing", prng); - if(sound) - sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f); - } - else - { - playSwishSound(mAttackStrength); - } - } + playSwishSound(mAttackStrength); if (animPlaying) mAnimation->disable(mCurrentWeapon); @@ -1719,8 +1706,7 @@ bool CharacterController::updateWeaponState() } world->breakInvisibility(mPtr); - if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown) - playSwishSound(0.0f); + playSwishSound(0.0f); } if(mAttackType == "shoot") @@ -2782,15 +2768,33 @@ void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target) void CharacterController::playSwishSound(float attackStrength) const { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; + if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown) + return; - std::string sound = "Weapon Swish"; - if(attackStrength < 0.5f) - sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack - else if(attackStrength < 1.0f) - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack + std::string soundId; + float pitch = 1.f; + + const MWWorld::Class &cls = mPtr.getClass(); + if (cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf()) + { + MWBase::World* world = MWBase::Environment::get().getWorld(); + const MWWorld::ESMStore &store = world->getStore(); + const ESM::Sound *sound = store.get().searchRandom("WolfSwing", world->getPrng()); + if (sound) + soundId = sound->mId; + } else - sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack + { + soundId = "Weapon Swish"; + if (attackStrength < 0.5f) + pitch = 0.8f; // Weak attack + else if (attackStrength >= 1.f) + pitch = 1.2f; // Strong attack + } + + if (!soundId.empty()) + MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, soundId, 1.0f, pitch); } void CharacterController::updateHeadTracking(float duration)