1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-03-31 14:36:39 +00:00

Merged pull request #1881

This commit is contained in:
Marc Zinnschlag 2018-09-14 11:43:11 +02:00
commit ea053d9f47
2 changed files with 91 additions and 48 deletions

View file

@ -243,7 +243,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i
return prefix + toString(roll); return prefix + toString(roll);
} }
void CharacterController::refreshHitRecoilAnims() void CharacterController::refreshHitRecoilAnims(CharacterState& idle)
{ {
bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery(); bool recovery = mPtr.getClass().getCreatureStats(mPtr).getHitRecovery();
bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown(); bool knockdown = mPtr.getClass().getCreatureStats(mPtr).getKnockedDown();
@ -348,14 +348,16 @@ void CharacterController::refreshHitRecoilAnims()
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0);
} }
if (mHitState != CharState_None) if (mHitState != CharState_None)
mIdleState = CharState_None; idle = CharState_None;
} }
void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force) void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force)
{ {
if(force || jump != mJumpState) if(force || jump != mJumpState)
{ {
mIdleState = CharState_None; if (jump != JumpState_None)
idle = CharState_None;
bool startAtLoop = (jump == mJumpState); bool startAtLoop = (jump == mJumpState);
mJumpState = jump; mJumpState = jump;
@ -372,6 +374,11 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState
jumpmask = MWRender::Animation::BlendMask_LowerBody; jumpmask = MWRender::Animation::BlendMask_LowerBody;
jumpAnimName = "jump"; jumpAnimName = "jump";
// Since we apply movement only for lower body, do not reset idle animations.
// For upper body there will be idle animation.
if (idle == CharState_None)
idle = CharState_Idle;
// For crossbow animations use 1h ones as fallback // For crossbow animations use 1h ones as fallback
if (mWeaponType == WeapType_Crossbow) if (mWeaponType == WeapType_Crossbow)
jumpAnimName += "1h"; jumpAnimName += "1h";
@ -406,34 +413,48 @@ void CharacterController::refreshJumpAnims(const WeaponInfo* weap, JumpingState
} }
} }
void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force) void CharacterController::refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force)
{ {
if(force || movement != mMovementState)
{
mMovementState = movement;
std::string movementAnimName; std::string movementAnimName;
MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All; MWRender::Animation::BlendMask movemask;
const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); const StateInfo *movestate;
if(force || movement != mMovementState || idle != mIdleState)
{
movemask = MWRender::Animation::BlendMask_All;
movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(movement));
if(movestate != sMovementListEnd) if(movestate != sMovementListEnd)
{ {
movementAnimName = movestate->groupname; movementAnimName = movestate->groupname;
if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos) if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos)
{ {
if (mWeaponType == WeapType_Spell && (mMovementState == CharState_TurnLeft || mMovementState == CharState_TurnRight)) // Spellcasting stance turning is a special case if (mWeaponType == WeapType_Spell && (movement == CharState_TurnLeft || movement == CharState_TurnRight)) // Spellcasting stance turning is a special case
movementAnimName = weap->shortgroup + movementAnimName; movementAnimName = weap->shortgroup + movementAnimName;
else else
movementAnimName += weap->shortgroup; movementAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(movementAnimName)) if(!mAnimation->hasAnimation(movementAnimName))
{ {
movemask = MWRender::Animation::BlendMask_LowerBody; movemask = MWRender::Animation::BlendMask_LowerBody;
movementAnimName = movestate->groupname; movementAnimName = movestate->groupname;
// Since we apply movement only for lower body, do not reset idle animations.
// For upper body there will be idle animation.
if (idle == CharState_None)
idle = CharState_Idle;
// For crossbow animations use 1h ones as fallback // For crossbow animations use 1h ones as fallback
if (mWeaponType == WeapType_Crossbow) if (mWeaponType == WeapType_Crossbow)
movementAnimName += "1h"; movementAnimName += "1h";
} }
} }
}
}
if(force || movement != mMovementState)
{
mMovementState = movement;
if(movestate != sMovementListEnd)
{
if(!mAnimation->hasAnimation(movementAnimName)) if(!mAnimation->hasAnimation(movementAnimName))
{ {
std::string::size_type swimpos = movementAnimName.find("swim"); std::string::size_type swimpos = movementAnimName.find("swim");
@ -451,6 +472,10 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
} }
else else
{ {
// For crossbow animations use 1h ones as fallback
if (mWeaponType == WeapType_Crossbow)
movementAnimName += "1h";
movementAnimName.erase(swimpos, 4); movementAnimName.erase(swimpos, 4);
if (weap != sWeaponTypeListEnd) if (weap != sWeaponTypeListEnd)
{ {
@ -527,7 +552,15 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force) void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force)
{ {
if(force || idle != mIdleState || mIdleState == CharState_None || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty())) // 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)
|| (mMovementState != CharState_None && !isTurning())
|| mHitState != CharState_None)
&& !mPtr.getClass().isBipedal(mPtr))
idle = CharState_None;
if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty()))
{ {
mIdleState = idle; mIdleState = idle;
size_t numLoops = ~0ul; size_t numLoops = ~0ul;
@ -586,24 +619,16 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
return; return;
if (mPtr.getClass().isActor()) if (mPtr.getClass().isActor())
refreshHitRecoilAnims(); refreshHitRecoilAnims(idle);
const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType));
if (!mPtr.getClass().hasInventoryStore(mPtr)) if (!mPtr.getClass().hasInventoryStore(mPtr))
weap = sWeaponTypeListEnd; weap = sWeaponTypeListEnd;
refreshJumpAnims(weap, jump, force); refreshJumpAnims(weap, jump, idle, force);
refreshMovementAnims(weap, movement, force); refreshMovementAnims(weap, movement, idle, force);
// idle handled last as it can depend on the other states // 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 && mUpperBodyState != UpperCharState_WeapEquiped)
|| (mMovementState != CharState_None && !isTurning())
|| mHitState != CharState_None)
&& !mPtr.getClass().isBipedal(mPtr))
idle = CharState_None;
refreshIdleAnims(weap, idle, force); refreshIdleAnims(weap, idle, force);
} }
@ -1195,7 +1220,7 @@ bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const
} }
} }
bool CharacterController::updateWeaponState() bool CharacterController::updateWeaponState(CharacterState& idle)
{ {
const MWWorld::Class &cls = mPtr.getClass(); const MWWorld::Class &cls = mPtr.getClass();
CreatureStats &stats = cls.getCreatureStats(mPtr); CreatureStats &stats = cls.getCreatureStats(mPtr);
@ -1394,6 +1419,14 @@ bool CharacterController::updateWeaponState()
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
mAttackStrength = 0; 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) if(mWeaponType == WeapType_Spell)
{ {
// Unset casting flag, otherwise pressing the mouse button down would // Unset casting flag, otherwise pressing the mouse button down would
@ -1557,11 +1590,11 @@ bool CharacterController::updateWeaponState()
// We should reset player's idle animation in the first-person mode. // We should reset player's idle animation in the first-person mode.
if (resetIdle && mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson()) if (resetIdle && mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson())
mIdleState = CharState_None; idle = CharState_None;
// In other cases we should not break swim and sneak animations // In other cases we should not break swim and sneak animations
if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim) if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
mIdleState = CharState_None; idle = CharState_None;
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete); animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown()) if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
@ -2072,7 +2105,12 @@ void CharacterController::update(float duration)
: (sneak ? CharState_SneakBack : (sneak ? CharState_SneakBack
: (isrunning ? CharState_RunBack : CharState_WalkBack))); : (isrunning ? CharState_RunBack : CharState_WalkBack)));
} }
else if(rot.z() != 0.0f && !sneak && !(mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson())) else if(rot.z() != 0.0f)
{
// It seems only bipedal actors use turning animations.
// Also do not use turning animations in the first-person view and when sneaking.
bool isFirstPlayer = mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson();
if (!sneak && !isFirstPlayer && mPtr.getClass().isBipedal(mPtr))
{ {
if(rot.z() > rotationThreshold) if(rot.z() > rotationThreshold)
movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight; movestate = inwater ? CharState_SwimTurnRight : CharState_TurnRight;
@ -2080,6 +2118,7 @@ void CharacterController::update(float duration)
movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft; movestate = inwater ? CharState_SwimTurnLeft : CharState_TurnLeft;
} }
} }
}
// Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering // Player can not use smooth turning as NPCs, so we play turning animation a bit to avoid jittering
if (mPtr == getPlayer()) if (mPtr == getPlayer())
@ -2095,7 +2134,11 @@ void CharacterController::update(float duration)
} }
else else
{ {
if (mPtr.getClass().isBipedal(mPtr))
{
if (mTurnAnimationThreshold > 0)
mTurnAnimationThreshold -= duration; mTurnAnimationThreshold -= duration;
if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft || if (movestate == CharState_TurnRight || movestate == CharState_TurnLeft ||
movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft) movestate == CharState_SwimTurnRight || movestate == CharState_SwimTurnLeft)
{ {
@ -2107,19 +2150,19 @@ void CharacterController::update(float duration)
movestate = mMovementState; movestate = mMovementState;
} }
} }
}
if(movestate != CharState_None && !isTurning()) if(movestate != CharState_None && !isTurning())
clearAnimQueue(); clearAnimQueue();
if(mAnimQueue.empty() || inwater || sneak) if(mAnimQueue.empty() || inwater || sneak)
{ {
// Note: turning animations should not interrupt idle ones. // Note: turning animations should not interrupt idle ones
// Also movement should not stop idle animation for spellcasting stance.
if (inwater) if (inwater)
idlestate = CharState_IdleSwim; idlestate = CharState_IdleSwim;
else if (sneak && !inJump) else if (sneak && !inJump)
idlestate = CharState_IdleSneak; idlestate = CharState_IdleSneak;
else if (movestate != CharState_None && !isTurning() && mWeaponType != WeapType_Spell) else if (movestate != CharState_None && !isTurning())
idlestate = CharState_None; idlestate = CharState_None;
else else
idlestate = CharState_Idle; idlestate = CharState_Idle;
@ -2131,7 +2174,7 @@ void CharacterController::update(float duration)
{ {
// bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used. // bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used.
if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr)) if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
forcestateupdate = updateWeaponState() || forcestateupdate; forcestateupdate = updateWeaponState(idlestate) || forcestateupdate;
else else
forcestateupdate = updateCreatureState() || forcestateupdate; forcestateupdate = updateCreatureState() || forcestateupdate;

View file

@ -213,14 +213,14 @@ class CharacterController : public MWRender::Animation::TextKeyListener
void setAttackTypeBasedOnMovement(); void setAttackTypeBasedOnMovement();
void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false);
void refreshHitRecoilAnims(); void refreshHitRecoilAnims(CharacterState& idle);
void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, bool force=false); void refreshJumpAnims(const WeaponInfo* weap, JumpingState jump, CharacterState& idle, bool force=false);
void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false); void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, CharacterState& idle, bool force=false);
void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false); void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false);
void clearAnimQueue(bool clearPersistAnims = false); void clearAnimQueue(bool clearPersistAnims = false);
bool updateWeaponState(); bool updateWeaponState(CharacterState& idle);
bool updateCreatureState(); bool updateCreatureState();
void updateIdleStormState(bool inwater); void updateIdleStormState(bool inwater);