mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-21 07:53:53 +00:00
Merge pull request #1810 from akortunov/weaponfix
Use fallbacks for missing weapon animations
This commit is contained in:
commit
6a32a4aed0
3 changed files with 101 additions and 26 deletions
|
@ -65,6 +65,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))
|
||||
{
|
||||
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<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…
Reference in a new issue