From 307e0103dc5ddcdc87d7c5ac5b7a8390b9b6a662 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 19 Jul 2018 16:38:32 +0400 Subject: [PATCH] Use fallbacks for missing weapon animations (bug #4470) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 123 ++++++++++++++++++++------ apps/openmw/mwmechanics/character.hpp | 3 + 3 files changed, 101 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 775f0545de..5b70649d05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ Bug #4461: "Open" spell from non-player caster isn't a crime Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal + Bug #4470: Non-bipedal creatures with Weapon & Shield flag have inconsistent behaviour Bug #4474: No fallback when getVampireHead fails Bug #4475: Scripted animations should not cause movement Bug #4479: "Game" category on Advanced page is getting too long diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index db9a53f827..6f9cb941d8 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -583,7 +583,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat refreshHitRecoilAnims(); const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if (!mPtr.getClass().isBipedal(mPtr)) + if (!mPtr.getClass().hasInventoryStore(mPtr)) weap = sWeaponTypeListEnd; refreshJumpAnims(weap, jump, force); @@ -592,7 +592,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat // idle handled last as it can depend on the other states // 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 + if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped) || (mMovementState != CharState_None && !isTurning()) || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr)) @@ -773,6 +773,20 @@ void CharacterController::playRandomDeath(float startpoint) playDeath(startpoint, mDeathState); } +std::string CharacterController::chooseRandomAttackAnimation() const +{ + std::string result; + bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); + + if (isSwimming) + result = chooseRandomGroup("swimattack"); + + if (!isSwimming || !mAnimation->hasAnimation(result)) + result = chooseRandomGroup("attack"); + + return result; +} + CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim) : mPtr(ptr) , mWeapon(MWWorld::Ptr()) @@ -1123,16 +1137,10 @@ bool CharacterController::updateCreatureState() else mCurrentWeapon = ""; } + if (weapType != WeapType_Spell || !mAnimation->hasAnimation("spellcast")) // Not all creatures have a dedicated spellcast animation { - bool isSwimming = MWBase::Environment::get().getWorld()->isSwimming(mPtr); - int roll = Misc::Rng::rollDice(3); // [0, 2] - if (roll == 0) - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack1") ? "swimattack1" : "attack1"; - else if (roll == 1) - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack2") ? "swimattack2" : "attack2"; - else - mCurrentWeapon = isSwimming && mAnimation->hasAnimation("swimattack3") ? "swimattack3" : "attack3"; + mCurrentWeapon = chooseRandomAttackAnimation(); } if (!mCurrentWeapon.empty()) @@ -1212,9 +1220,10 @@ bool CharacterController::updateWeaponState() mWeapon = weapon != inv.end() ? *weapon : MWWorld::Ptr(); } - // Apply 1st-person weapon animations only for upper body + // Use blending only with 3d-person movement animations for bipedal actors + bool firstPersonPlayer = (mPtr == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()); MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon); - if (mPtr != MWMechanics::getPlayer() || !MWBase::Environment::get().getWorld()->isFirstPerson()) + if (!firstPersonPlayer && mPtr.getClass().isBipedal(mPtr)) priorityWeapon[MWRender::Animation::BoneGroup_LowerBody] = Priority_WeaponLowerBody; bool forcestateupdate = false; @@ -1241,6 +1250,10 @@ bool CharacterController::updateWeaponState() MWRender::Animation::BlendMask_All, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); mUpperBodyState = UpperCharState_UnEquipingWeap; + + // If we do not have the "unequip detach" key, hide weapon manually. + if (mAnimation->getTextKeyTime(weapgroup+": unequip detach") < 0) + mAnimation->showWeapons(false); } if(!downSoundId.empty()) @@ -1252,7 +1265,6 @@ bool CharacterController::updateWeaponState() float complete; bool animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); - if (!animPlaying || complete >= 1.0f) { // Weapon is changed, no current animation (e.g. unequipping or attack). @@ -1275,6 +1287,13 @@ bool CharacterController::updateWeaponState() MWRender::Animation::BlendMask_All, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; + + // If we do not have the "equip attach" key, show weapon manually. + if (weaptype != WeapType_Spell) + { + if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0) + mAnimation->showWeapons(true); + } } } @@ -1365,6 +1384,15 @@ bool CharacterController::updateWeaponState() { MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); mAttackStrength = 0; + + // Randomize attacks for non-bipedal creatures with Weapon flag + if (mPtr.getClass().getTypeName() == typeid(ESM::Creature).name() && + !mPtr.getClass().isBipedal(mPtr) && + (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon))) + { + mCurrentWeapon = chooseRandomAttackAnimation(); + } + if(mWeaponType == WeapType_Spell) { // Unset casting flag, otherwise pressing the mouse button down would @@ -1412,16 +1440,31 @@ bool CharacterController::updateWeaponState() const ESM::ENAMstruct &firstEffect = spell->mEffects.mList.at(0); // first effect used for casting animation - switch(firstEffect.mRange) + std::string startKey; + std::string stopKey; + if (isRandomAttackAnimation(mCurrentWeapon)) { - case 0: mAttackType = "self"; break; - case 1: mAttackType = "touch"; break; - case 2: mAttackType = "target"; break; + startKey = "start"; + stopKey = "stop"; + MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately + mCastingManualSpell = false; + } + else + { + switch(firstEffect.mRange) + { + case 0: mAttackType = "self"; break; + case 1: mAttackType = "touch"; break; + case 2: mAttackType = "target"; break; + } + + startKey = mAttackType+" start"; + stopKey = mAttackType+" stop"; } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, true, - weapSpeed, mAttackType+" start", mAttackType+" stop", + 1, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; } @@ -1469,16 +1512,27 @@ bool CharacterController::updateWeaponState() } else if (ammunition) { - if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || - mWeaponType == WeapType_Thrown) + std::string startKey; + std::string stopKey; + if(mWeaponType == WeapType_Crossbow || mWeaponType == WeapType_BowAndArrow || mWeaponType == WeapType_Thrown) + { mAttackType = "shoot"; - else + startKey = mAttackType+" start"; + stopKey = mAttackType+" min attack"; + } + + if (isRandomAttackAnimation(mCurrentWeapon)) + { + startKey = "start"; + stopKey = "stop"; + } + else if (mAttackType != "shoot") { if(mPtr == getPlayer()) { if (isWeapon) { - if (Settings::Manager::getBool("best attack", "Game")) + if (Settings::Manager::getBool("best attack", "Game")) { MWWorld::ConstContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); mAttackType = getBestAttack(weapon->get()->mBase); @@ -1487,14 +1541,19 @@ bool CharacterController::updateWeaponState() setAttackTypeBasedOnMovement(); } else - setAttackTypeRandomly(mAttackType); + { + // There is no "best attack" for Hand-to-Hand + setAttackTypeRandomly(mAttackType); + } } // else if (mPtr != getPlayer()) use mAttackType set by AiCombat + startKey = mAttackType+" start"; + stopKey = mAttackType+" min attack"; } mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, - weapSpeed, mAttackType+" start", mAttackType+" min attack", + weapSpeed, startKey, stopKey, 0.0f, 0); mUpperBodyState = UpperCharState_StartToMinAttack; } @@ -1619,7 +1678,7 @@ bool CharacterController::updateWeaponState() else if(mUpperBodyState == UpperCharState_UnEquipingWeap) mUpperBodyState = UpperCharState_Nothing; } - else if(complete >= 1.0f) + else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon)) { std::string start, stop; switch(mUpperBodyState) @@ -1690,6 +1749,11 @@ bool CharacterController::updateWeaponState() weapSpeed, start, stop, 0.0f, 0); } } + else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon)) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } if (mPtr.getClass().hasInventoryStore(mPtr)) { @@ -2422,6 +2486,13 @@ void CharacterController::setAttackTypeBasedOnMovement() mAttackType = "chop"; } +bool CharacterController::isRandomAttackAnimation(const std::string& group) const +{ + return (group == "attack1" || group == "swimattack1" || + group == "attack2" || group == "swimattack2" || + group == "attack3" || group == "swimattack3"); +} + bool CharacterController::isAttackPrepairing() const { return mUpperBodyState == UpperCharState_StartToMinAttack || @@ -2523,7 +2594,7 @@ void CharacterController::setAttackTypeRandomly(std::string& attackType) bool CharacterController::readyToPrepareAttack() const { return (mHitState == CharState_None || mHitState == CharState_Block) - && mUpperBodyState <= UpperCharState_WeapEquiped; + && mUpperBodyState <= UpperCharState_WeapEquiped; } bool CharacterController::readyToStartAttack() const diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 754f551f94..631f78208a 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -224,6 +224,9 @@ class CharacterController : public MWRender::Animation::TextKeyListener bool updateCreatureState(); void updateIdleStormState(bool inwater); + std::string chooseRandomAttackAnimation() const; + bool isRandomAttackAnimation(const std::string& group) const; + bool isPersistentAnimPlaying(); void updateAnimQueue();