Use fallbacks for missing weapon animations (bug #4470)

pull/1810/head
Andrei Kortunov 6 years ago
parent 1cfc1f9bdb
commit 307e0103dc

@ -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

@ -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))
{
startKey = "start";
stopKey = "stop";
MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately
mCastingManualSpell = false;
}
else
{
case 0: mAttackType = "self"; break;
case 1: mAttackType = "touch"; break;
case 2: mAttackType = "target"; break;
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<ESM::Weapon>()->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

@ -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();

Loading…
Cancel
Save