Merge pull request #666 from scrawl/animation

Animation refactoring & fixes
This commit is contained in:
scrawl 2015-07-18 13:33:41 +02:00
commit 111cf5462b
5 changed files with 204 additions and 187 deletions

View file

@ -43,6 +43,7 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
namespace namespace
{ {
@ -236,7 +237,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i
return prefix + toString(roll); return prefix + toString(roll);
} }
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force) void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force)
{ {
// hit recoils/knockdown animations handling // hit recoils/knockdown animations handling
if(mPtr.getClass().isActor()) if(mPtr.getClass().isActor())
@ -251,26 +252,28 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{ {
mHitState = CharState_KnockOut; mHitState = CharState_KnockOut;
mCurrentHit = "knockout"; mCurrentHit = "knockout";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true); mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
} }
else if(knockdown) else if(knockdown)
{ {
mHitState = CharState_KnockDown; mHitState = CharState_KnockDown;
mCurrentHit = "knockdown"; mCurrentHit = "knockdown";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else if (recovery) else if (recovery)
{ {
mHitState = CharState_Hit; mHitState = CharState_Hit;
mCurrentHit = chooseRandomGroup("hit"); mCurrentHit = chooseRandomGroup("hit");
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0); mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
} }
else if (block) else if (block)
{ {
mHitState = CharState_Block; mHitState = CharState_Block;
mCurrentHit = "shield"; mCurrentHit = "shield";
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0); MWRender::Animation::AnimPriority priorityBlock (Priority_Hit);
priorityBlock.mPriority[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block;
mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0);
} }
// Cancel upper body animations // Cancel upper body animations
@ -303,7 +306,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{ {
mHitState = CharState_KnockDown; mHitState = CharState_KnockDown;
mAnimation->disable(mCurrentHit); mAnimation->disable(mCurrentHit);
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_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);
} }
} }
@ -311,40 +314,41 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
if (!mPtr.getClass().isBipedal(mPtr)) if (!mPtr.getClass().isBipedal(mPtr))
weap = sWeaponTypeListEnd; weap = sWeaponTypeListEnd;
if(force && mJumpState != JumpState_None) if(force || jump != mJumpState)
{ {
std::string jump; bool startAtLoop = (jump == mJumpState);
MWRender::Animation::Group jumpgroup = MWRender::Animation::Group_All; mJumpState = jump;
std::string jumpAnimName;
MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All;
if(mJumpState != JumpState_None) if(mJumpState != JumpState_None)
{ {
jump = "jump"; jumpAnimName = "jump";
if(weap != sWeaponTypeListEnd) if(weap != sWeaponTypeListEnd)
{ {
jump += weap->shortgroup; jumpAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(jump)) if(!mAnimation->hasAnimation(jumpAnimName))
{ {
jumpgroup = MWRender::Animation::Group_LowerBody; jumpmask = MWRender::Animation::BlendMask_LowerBody;
jump = "jump"; jumpAnimName = "jump";
} }
} }
} }
if(mJumpState == JumpState_InAir) if(mJumpState == JumpState_InAir)
{ {
int mode = ((jump == mCurrentJump) ? 2 : 1);
mAnimation->disable(mCurrentJump); mAnimation->disable(mCurrentJump);
mCurrentJump = jump; mCurrentJump = jumpAnimName;
if (mAnimation->hasAnimation("jump")) if (mAnimation->hasAnimation("jump"))
mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false, mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false,
1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); 1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul);
} }
else else
{ {
mAnimation->disable(mCurrentJump); mAnimation->disable(mCurrentJump);
mCurrentJump.clear(); mCurrentJump.clear();
if (mAnimation->hasAnimation("jump")) if (mAnimation->hasAnimation("jump"))
mAnimation->play(jump, Priority_Jump, jumpgroup, true, mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true,
1.0f, "loop stop", "stop", 0.0f, 0); 1.0f, "loop stop", "stop", 0.0f, 0);
} }
} }
@ -353,55 +357,55 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{ {
mMovementState = movement; mMovementState = movement;
std::string movement; std::string movementAnimName;
MWRender::Animation::Group movegroup = MWRender::Animation::Group_All; MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All;
const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState)); const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState));
if(movestate != sMovementListEnd) if(movestate != sMovementListEnd)
{ {
movement = movestate->groupname; movementAnimName = movestate->groupname;
if(weap != sWeaponTypeListEnd && movement.find("swim") == std::string::npos) if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos)
{ {
movement += weap->shortgroup; movementAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(movement)) if(!mAnimation->hasAnimation(movementAnimName))
{ {
movegroup = MWRender::Animation::Group_LowerBody; movemask = MWRender::Animation::BlendMask_LowerBody;
movement = movestate->groupname; movementAnimName = movestate->groupname;
} }
} }
if(!mAnimation->hasAnimation(movement)) if(!mAnimation->hasAnimation(movementAnimName))
{ {
std::string::size_type swimpos = movement.find("swim"); std::string::size_type swimpos = movementAnimName.find("swim");
if(swimpos == std::string::npos) if(swimpos == std::string::npos)
{ {
std::string::size_type runpos = movement.find("run"); std::string::size_type runpos = movementAnimName.find("run");
if (runpos != std::string::npos) if (runpos != std::string::npos)
{ {
movement.replace(runpos, runpos+3, "walk"); movementAnimName.replace(runpos, runpos+3, "walk");
if (!mAnimation->hasAnimation(movement)) if (!mAnimation->hasAnimation(movementAnimName))
movement.clear(); movementAnimName.clear();
} }
else else
movement.clear(); movementAnimName.clear();
} }
else else
{ {
movegroup = MWRender::Animation::Group_LowerBody; movemask = MWRender::Animation::BlendMask_LowerBody;
movement.erase(swimpos, 4); movementAnimName.erase(swimpos, 4);
if(!mAnimation->hasAnimation(movement)) if(!mAnimation->hasAnimation(movementAnimName))
movement.clear(); movementAnimName.clear();
} }
} }
} }
/* If we're playing the same animation, restart from the loop start instead of the /* If we're playing the same animation, restart from the loop start instead of the
* beginning. */ * beginning. */
int mode = ((movement == mCurrentMovement) ? 2 : 1); int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1);
mMovementAnimationControlled = true; mMovementAnimationControlled = true;
mAnimation->disable(mCurrentMovement); mAnimation->disable(mCurrentMovement);
mCurrentMovement = movement; mCurrentMovement = movementAnimName;
if(!mCurrentMovement.empty()) if(!mCurrentMovement.empty())
{ {
float vel, speedmult = 1.0f; float vel, speedmult = 1.0f;
@ -447,7 +451,17 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
} }
} }
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false, MWRender::Animation::AnimPriority priorityMovement (Priority_Movement);
if ((movement == CharState_TurnLeft || movement == CharState_TurnRight)
&& mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()
&& MWBase::Environment::get().getWorld()->isFirstPerson())
{
priorityMovement.mPriority[MWRender::Animation::BoneGroup_Torso] = 0;
priorityMovement.mPriority[MWRender::Animation::BoneGroup_LeftArm] = 0;
priorityMovement.mPriority[MWRender::Animation::BoneGroup_RightArm] = 0;
}
mAnimation->play(mCurrentMovement, priorityMovement, movemask, false,
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
} }
} }
@ -456,7 +470,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
// FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), // 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 // the idle animation should be displayed
if ((mUpperBodyState != UpperCharState_Nothing if ((mUpperBodyState != UpperCharState_Nothing
|| mMovementState != CharState_None || (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight)
|| mHitState != CharState_None) || mHitState != CharState_None)
&& !mPtr.getClass().isBipedal(mPtr)) && !mPtr.getClass().isBipedal(mPtr))
idle = CharState_None; idle = CharState_None;
@ -486,7 +500,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mAnimation->disable(mCurrentIdle); mAnimation->disable(mCurrentIdle);
mCurrentIdle = idle; mCurrentIdle = idle;
if(!mCurrentIdle.empty()) if(!mCurrentIdle.empty())
mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false, mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, ~0ul, true); 1.0f, "start", "stop", 0.0f, ~0ul, true);
} }
@ -602,7 +616,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death)
mCurrentJump = ""; mCurrentJump = "";
mMovementAnimationControlled = true; mMovementAnimationControlled = true;
mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All, mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All,
false, 1.0f, "start", "stop", startpoint, 0); false, 1.0f, "start", "stop", startpoint, 0);
} }
@ -704,7 +718,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
if(mDeathState == CharState_None) if(mDeathState == CharState_None)
refreshCurrentAnims(mIdleState, mMovementState, true); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
mAnimation->runAnimation(0.f); mAnimation->runAnimation(0.f);
} }
@ -868,10 +882,10 @@ void CharacterController::updateIdleStormState()
mAnimation->getInfo("idlestorm", &complete); mAnimation->getInfo("idlestorm", &complete);
if (complete == 0) if (complete == 0)
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false,
1.0f, "start", "loop start", 0.0f, 0); 1.0f, "start", "loop start", 0.0f, 0);
else if (complete == 1) else if (complete == 1)
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false, mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false,
1.0f, "loop start", "loop stop", 0.0f, ~0ul); 1.0f, "loop start", "loop stop", 0.0f, ~0ul);
} }
else else
@ -882,7 +896,7 @@ void CharacterController::updateIdleStormState()
{ {
if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop")) if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop"))
{ {
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, true, mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true,
1.0f, "loop stop", "stop", 0.0f, 0); 1.0f, "loop stop", "stop", 0.0f, 0);
} }
} }
@ -989,7 +1003,7 @@ bool CharacterController::updateCreatureState()
if (!mCurrentWeapon.empty()) if (!mCurrentWeapon.empty())
{ {
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_All, true, MWRender::Animation::BlendMask_All, true,
1, startKey, stopKey, 1, startKey, stopKey,
0.0f, 0); 0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack; mUpperBodyState = UpperCharState_StartToMinAttack;
@ -1051,6 +1065,9 @@ bool CharacterController::updateWeaponState()
} }
} }
MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon);
priorityWeapon.mPriority[MWRender::Animation::BoneGroup_LowerBody] = 0;
bool forcestateupdate = false; bool forcestateupdate = false;
if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut
&& mHitState != CharState_Hit) && mHitState != CharState_Hit)
@ -1063,8 +1080,8 @@ bool CharacterController::updateWeaponState()
if(weaptype == WeapType_None) if(weaptype == WeapType_None)
{ {
getWeaponGroup(mWeaponType, weapgroup); getWeaponGroup(mWeaponType, weapgroup);
mAnimation->play(weapgroup, Priority_Weapon, mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
1.0f, "unequip start", "unequip stop", 0.0f, 0); 1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap; mUpperBodyState = UpperCharState_UnEquipingWeap;
} }
@ -1074,8 +1091,8 @@ bool CharacterController::updateWeaponState()
mAnimation->showWeapons(false); mAnimation->showWeapons(false);
mAnimation->setWeaponGroup(weapgroup); mAnimation->setWeaponGroup(weapgroup);
mAnimation->play(weapgroup, Priority_Weapon, mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
1.0f, "equip start", "equip stop", 0.0f, 0); 1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap; mUpperBodyState = UpperCharState_EquipingWeap;
@ -1145,7 +1162,7 @@ bool CharacterController::updateWeaponState()
bool animPlaying; bool animPlaying;
if(mAttackingOrSpell) if(mAttackingOrSpell)
{ {
if(mUpperBodyState == UpperCharState_WeapEquiped && mHitState == CharState_None) if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
{ {
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
mAttackType.clear(); mAttackType.clear();
@ -1154,6 +1171,10 @@ bool CharacterController::updateWeaponState()
// Unset casting flag, otherwise pressing the mouse button down would // Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation // continue casting every frame if there is no animation
mAttackingOrSpell = false; mAttackingOrSpell = false;
if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr())
{
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
}
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -1191,8 +1212,8 @@ bool CharacterController::updateWeaponState()
case 2: mAttackType = "target"; break; case 2: mAttackType = "target"; break;
} }
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
weapSpeed, mAttackType+" start", mAttackType+" stop", weapSpeed, mAttackType+" start", mAttackType+" stop",
0.0f, 0); 0.0f, 0);
mUpperBodyState = UpperCharState_CastingSpell; mUpperBodyState = UpperCharState_CastingSpell;
@ -1223,8 +1244,8 @@ bool CharacterController::updateWeaponState()
else if(item.getTypeName() == typeid(ESM::Probe).name()) else if(item.getTypeName() == typeid(ESM::Probe).name())
Security(mPtr).probeTrap(target, item, resultMessage, resultSound); Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
} }
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
1.0f, "start", "stop", 0.0, 0); 1.0f, "start", "stop", 0.0, 0);
mUpperBodyState = UpperCharState_FollowStartToFollowStop; mUpperBodyState = UpperCharState_FollowStartToFollowStop;
@ -1250,8 +1271,8 @@ bool CharacterController::updateWeaponState()
determineAttackType(); determineAttackType();
} }
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, false, MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" start", mAttackType+" min attack", weapSpeed, mAttackType+" start", mAttackType+" min attack",
0.0f, 0); 0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack; mUpperBodyState = UpperCharState_StartToMinAttack;
@ -1301,8 +1322,8 @@ bool CharacterController::updateWeaponState()
mAttackStrength = attackStrength; mAttackStrength = attackStrength;
mAnimation->disable(mCurrentWeapon); mAnimation->disable(mCurrentWeapon);
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, false, MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" max attack", mAttackType+" min hit", weapSpeed, mAttackType+" max attack", mAttackType+" min hit",
1.0f-complete, 0); 1.0f-complete, 0);
@ -1371,15 +1392,6 @@ bool CharacterController::updateWeaponState()
mAnimation->attachArrow(); mAnimation->attachArrow();
mUpperBodyState = UpperCharState_WeapEquiped; mUpperBodyState = UpperCharState_WeapEquiped;
//don't allow to continue playing hit animation on UpperBody after actor had attacked during it
if(mHitState == CharState_Hit)
{
mAnimation->changeGroups(mCurrentHit, MWRender::Animation::Group_LowerBody);
//commenting out following 2 lines will give a bit different combat dynamics(slower)
mHitState = CharState_None;
mCurrentHit.clear();
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
}
} }
else if(mUpperBodyState == UpperCharState_UnEquipingWeap) else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
mUpperBodyState = UpperCharState_Nothing; mUpperBodyState = UpperCharState_Nothing;
@ -1397,8 +1409,8 @@ bool CharacterController::updateWeaponState()
case UpperCharState_MinAttackToMaxAttack: case UpperCharState_MinAttackToMaxAttack:
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state //hack to avoid body pos desync when jumping/sneaking in 'max attack' state
if(!mAnimation->isPlaying(mCurrentWeapon)) if(!mAnimation->isPlaying(mCurrentWeapon))
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, false, MWRender::Animation::BlendMask_All, false,
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0); 0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
break; break;
case UpperCharState_MaxAttackToMinHit: case UpperCharState_MaxAttackToMinHit:
@ -1440,33 +1452,16 @@ bool CharacterController::updateWeaponState()
{ {
mAnimation->disable(mCurrentWeapon); mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop) if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, true, MWRender::Animation::BlendMask_All, true,
weapSpeed, start, stop, 0.0f, 0); weapSpeed, start, stop, 0.0f, 0);
else else
mAnimation->play(mCurrentWeapon, Priority_Weapon, mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::Group_UpperBody, false, MWRender::Animation::BlendMask_All, false,
weapSpeed, start, stop, 0.0f, 0); weapSpeed, start, stop, 0.0f, 0);
} }
} }
//if playing combat animation and lowerbody is not busy switch to whole body animation
if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap) && animPlaying)
{
if( mMovementState != CharState_None ||
mJumpState != JumpState_None ||
mHitState != CharState_None ||
MWBase::Environment::get().getWorld()->isSwimming(mPtr) ||
cls.getCreatureStats(mPtr).getMovementFlag(CreatureStats::Flag_Sneak))
{
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody);
}
else
{
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All);
}
}
if (mPtr.getClass().hasInventoryStore(mPtr)) if (mPtr.getClass().hasInventoryStore(mPtr))
{ {
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
@ -1475,7 +1470,7 @@ bool CharacterController::updateWeaponState()
&& updateCarriedLeftVisible(mWeaponType)) && updateCarriedLeftVisible(mWeaponType))
{ {
mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true);
} }
else if (mAnimation->isPlaying("torch")) else if (mAnimation->isPlaying("torch"))
@ -1505,7 +1500,7 @@ void CharacterController::update(float duration)
mAnimQueue.pop_front(); mAnimQueue.pop_front();
mAnimation->play(mAnimQueue.front().first, Priority_Default, mAnimation->play(mAnimQueue.front().first, Priority_Default,
MWRender::Animation::Group_All, false, MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second);
} }
} }
@ -1562,6 +1557,8 @@ void CharacterController::update(float duration)
CharacterState movestate = CharState_None; CharacterState movestate = CharState_None;
CharacterState idlestate = CharState_SpecialIdle; CharacterState idlestate = CharState_SpecialIdle;
JumpingState jumpstate = JumpState_None;
bool forcestateupdate = false; bool forcestateupdate = false;
mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f; mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f;
@ -1644,7 +1641,7 @@ void CharacterController::update(float duration)
} }
forcestateupdate = (mJumpState != JumpState_InAir); forcestateupdate = (mJumpState != JumpState_InAir);
mJumpState = JumpState_InAir; jumpstate = JumpState_InAir;
static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat();
static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat();
@ -1689,7 +1686,7 @@ void CharacterController::update(float duration)
else if(mJumpState == JumpState_InAir) else if(mJumpState == JumpState_InAir)
{ {
forcestateupdate = true; forcestateupdate = true;
mJumpState = JumpState_Landing; jumpstate = JumpState_Landing;
vec.z() = 0.0f; vec.z() = 0.0f;
float height = cls.getCreatureStats(mPtr).land(); float height = cls.getCreatureStats(mPtr).land();
@ -1720,7 +1717,7 @@ void CharacterController::update(float duration)
} }
else else
{ {
mJumpState = JumpState_None; jumpstate = JumpState_None;
vec.z() = 0.0f; vec.z() = 0.0f;
inJump = false; inJump = false;
@ -1786,19 +1783,22 @@ void CharacterController::update(float duration)
mAnimQueue.pop_front(); mAnimQueue.pop_front();
mAnimation->play(mAnimQueue.front().first, Priority_Default, mAnimation->play(mAnimQueue.front().first, Priority_Default,
MWRender::Animation::Group_All, false, MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, mAnimQueue.front().second); 1.0f, "start", "stop", 0.0f, mAnimQueue.front().second);
} }
} }
if (!mSkipAnim)
{
// 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() || forcestateupdate;
else else
forcestateupdate = updateCreatureState() || forcestateupdate; forcestateupdate = updateCreatureState() || forcestateupdate;
if (!mSkipAnim) refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate);
refreshCurrentAnims(idlestate, movestate, forcestateupdate); }
if (inJump) if (inJump)
mMovementAnimationControlled = false; mMovementAnimationControlled = false;
@ -1894,7 +1894,7 @@ void CharacterController::playGroup(const std::string &groupname, int mode, int
mIdleState = CharState_SpecialIdle; mIdleState = CharState_SpecialIdle;
mAnimation->play(groupname, Priority_Default, mAnimation->play(groupname, Priority_Default,
MWRender::Animation::Group_All, false, 1.0f, MWRender::Animation::BlendMask_All, false, 1.0f,
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1); ((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1);
} }
else if(mode == 0) else if(mode == 0)
@ -1934,7 +1934,7 @@ void CharacterController::forceStateUpdate()
return; return;
clearAnimQueue(); clearAnimQueue();
refreshCurrentAnims(mIdleState, mMovementState, true); refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
if(mDeathState != CharState_None) if(mDeathState != CharState_None)
{ {
playRandomDeath(); playRandomDeath();
@ -2052,12 +2052,13 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
bool CharacterController::readyToPrepareAttack() const bool CharacterController::readyToPrepareAttack() const
{ {
return mHitState == CharState_None && mUpperBodyState <= UpperCharState_WeapEquiped; return (mHitState == CharState_None || mHitState == CharState_Block)
&& mUpperBodyState <= UpperCharState_WeapEquiped;
} }
bool CharacterController::readyToStartAttack() const bool CharacterController::readyToStartAttack() const
{ {
if (mHitState != CharState_None) if (mHitState != CharState_None && mHitState != CharState_Block)
return false; return false;
if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr)) if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr))

View file

@ -32,6 +32,7 @@ enum Priority {
Priority_Movement, Priority_Movement,
Priority_Hit, Priority_Hit,
Priority_Weapon, Priority_Weapon,
Priority_Block,
Priority_Knockdown, Priority_Knockdown,
Priority_Torch, Priority_Torch,
Priority_Storm, Priority_Storm,
@ -185,7 +186,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
void determineAttackType(); void determineAttackType();
void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false); void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false);
void clearAnimQueue(); void clearAnimQueue();

View file

@ -219,7 +219,7 @@ namespace MWRender
typedef std::map<std::string, osg::ref_ptr<NifOsg::KeyframeController> > ControllerMap; typedef std::map<std::string, osg::ref_ptr<NifOsg::KeyframeController> > ControllerMap;
ControllerMap mControllerMap[Animation::sNumGroups]; ControllerMap mControllerMap[Animation::sNumBlendMasks];
const std::multimap<float, std::string>& getTextKeys(); const std::multimap<float, std::string>& getTextKeys();
}; };
@ -261,7 +261,7 @@ namespace MWRender
, mHeadYawRadians(0.f) , mHeadYawRadians(0.f)
, mHeadPitchRadians(0.f) , mHeadPitchRadians(0.f)
{ {
for(size_t i = 0;i < sNumGroups;i++) for(size_t i = 0;i < sNumBlendMasks;i++)
mAnimationTimePtr[i].reset(new AnimationTime); mAnimationTimePtr[i].reset(new AnimationTime);
} }
@ -299,9 +299,9 @@ namespace MWRender
mResetAccumRootCallback->setAccumulate(mAccumulate); mResetAccumRootCallback->setAccumulate(mAccumulate);
} }
size_t Animation::detectAnimGroup(osg::Node* node) size_t Animation::detectBlendMask(osg::Node* node)
{ {
static const char sGroupRoots[sNumGroups][32] = { static const char sBlendMaskRoots[sNumBlendMasks][32] = {
"", /* Lower body / character root */ "", /* Lower body / character root */
"Bip01 Spine1", /* Torso */ "Bip01 Spine1", /* Torso */
"Bip01 L Clavicle", /* Left arm */ "Bip01 L Clavicle", /* Left arm */
@ -311,9 +311,9 @@ namespace MWRender
while(node != mObjectRoot) while(node != mObjectRoot)
{ {
const std::string &name = node->getName(); const std::string &name = node->getName();
for(size_t i = 1;i < sNumGroups;i++) for(size_t i = 1;i < sNumBlendMasks;i++)
{ {
if(name == sGroupRoots[i]) if(name == sBlendMaskRoots[i])
return i; return i;
} }
@ -361,13 +361,13 @@ namespace MWRender
osg::Node* node = found->second; osg::Node* node = found->second;
size_t group = detectAnimGroup(node); size_t blendMask = detectBlendMask(node);
// clone the controller, because each Animation needs its own ControllerSource // clone the controller, because each Animation needs its own ControllerSource
osg::ref_ptr<NifOsg::KeyframeController> cloned = osg::clone(it->second.get(), osg::CopyOp::DEEP_COPY_ALL); osg::ref_ptr<NifOsg::KeyframeController> cloned = osg::clone(it->second.get(), osg::CopyOp::DEEP_COPY_ALL);
cloned->setSource(mAnimationTimePtr[group]); cloned->setSource(mAnimationTimePtr[blendMask]);
animsrc->mControllerMap[group].insert(std::make_pair(bonename, cloned)); animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned));
} }
mAnimSources.push_back(animsrc); mAnimSources.push_back(animsrc);
@ -390,7 +390,7 @@ namespace MWRender
{ {
mStates.clear(); mStates.clear();
for(size_t i = 0;i < sNumGroups;i++) for(size_t i = 0;i < sNumBlendMasks;i++)
mAnimationTimePtr[i]->setTimePtr(boost::shared_ptr<float>()); mAnimationTimePtr[i]->setTimePtr(boost::shared_ptr<float>());
mAccumCtrl = NULL; mAccumCtrl = NULL;
@ -461,7 +461,7 @@ namespace MWRender
mTextKeyListener->handleTextKey(groupname, key, map); mTextKeyListener->handleTextKey(groupname, key, map);
} }
void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, void Animation::play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback) const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback)
{ {
if(!mObjectRoot || mAnimSources.empty()) if(!mObjectRoot || mAnimSources.empty())
@ -473,8 +473,6 @@ namespace MWRender
return; return;
} }
priority = std::max(0, priority);
AnimStateMap::iterator stateiter = mStates.begin(); AnimStateMap::iterator stateiter = mStates.begin();
while(stateiter != mStates.end()) while(stateiter != mStates.end())
{ {
@ -505,7 +503,7 @@ namespace MWRender
state.mLoopCount = loops; state.mLoopCount = loops;
state.mPlaying = (state.getTime() < state.mStopTime); state.mPlaying = (state.getTime() < state.mStopTime);
state.mPriority = priority; state.mPriority = priority;
state.mGroups = groups; state.mBlendMask = blendMask;
state.mAutoDisable = autodisable; state.mAutoDisable = autodisable;
mStates[groupname] = state; mStates[groupname] = state;
@ -600,9 +598,7 @@ namespace MWRender
state.setTime(state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint)); state.setTime(state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint));
// mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation // mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation
// (see handleTextKey). But if startpoint is already past these keys, we need to assign them now. // (see handleTextKey). But if startpoint is already past these keys, or start time is == stop time, we need to assign them now.
if(state.getTime() > state.mStartTime)
{
const std::string loopstarttag = groupname+": loop start"; const std::string loopstarttag = groupname+": loop start";
const std::string loopstoptag = groupname+": loop stop"; const std::string loopstoptag = groupname+": loop stop";
@ -617,7 +613,6 @@ namespace MWRender
else if (key->second == loopstoptag) else if (key->second == loopstoptag)
state.mLoopStopTime = key->first; state.mLoopStopTime = key->first;
} }
}
return true; return true;
} }
@ -643,35 +638,35 @@ namespace MWRender
mAccumCtrl = NULL; mAccumCtrl = NULL;
for(size_t grp = 0;grp < sNumGroups;grp++) for(size_t blendMask = 0;blendMask < sNumBlendMasks;blendMask++)
{ {
AnimStateMap::const_iterator active = mStates.end(); AnimStateMap::const_iterator active = mStates.end();
AnimStateMap::const_iterator state = mStates.begin(); AnimStateMap::const_iterator state = mStates.begin();
for(;state != mStates.end();++state) for(;state != mStates.end();++state)
{ {
if(!(state->second.mGroups&(1<<grp))) if(!(state->second.mBlendMask&(1<<blendMask)))
continue; continue;
if(active == mStates.end() || active->second.mPriority < state->second.mPriority) if(active == mStates.end() || active->second.mPriority.mPriority[blendMask] < state->second.mPriority.mPriority[blendMask])
active = state; active = state;
} }
mAnimationTimePtr[grp]->setTimePtr(active == mStates.end() ? boost::shared_ptr<float>() : active->second.mTime); mAnimationTimePtr[blendMask]->setTimePtr(active == mStates.end() ? boost::shared_ptr<float>() : active->second.mTime);
// add external controllers for the AnimSource active in this group // add external controllers for the AnimSource active in this blend mask
if (active != mStates.end()) if (active != mStates.end())
{ {
boost::shared_ptr<AnimSource> animsrc = active->second.mSource; boost::shared_ptr<AnimSource> animsrc = active->second.mSource;
for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[grp].begin(); it != animsrc->mControllerMap[grp].end(); ++it) for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it)
{ {
osg::ref_ptr<osg::Node> node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource osg::ref_ptr<osg::Node> node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
node->addUpdateCallback(it->second); node->addUpdateCallback(it->second);
mActiveControllers.insert(std::make_pair(node, it->second)); mActiveControllers.insert(std::make_pair(node, it->second));
if (grp == 0 && node == mAccumRoot) if (blendMask == 0 && node == mAccumRoot)
{ {
mAccumCtrl = it->second; mAccumCtrl = it->second;
@ -690,20 +685,6 @@ namespace MWRender
addControllers(); addControllers();
} }
void Animation::changeGroups(const std::string &groupname, int groups)
{
AnimStateMap::iterator stateiter = mStates.find(groupname);
if(stateiter != mStates.end())
{
if(stateiter->second.mGroups != groups)
{
stateiter->second.mGroups = groups;
resetActiveGroups();
}
return;
}
}
void Animation::stopLooping(const std::string& groupname) void Animation::stopLooping(const std::string& groupname)
{ {
AnimStateMap::iterator stateiter = mStates.find(groupname); AnimStateMap::iterator stateiter = mStates.find(groupname);
@ -1208,9 +1189,10 @@ namespace MWRender
{ {
for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter) for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter)
{ {
if((stateiter->second.mPriority > MWMechanics::Priority_Movement if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Hit))
&& stateiter->second.mPriority < MWMechanics::Priority_Torch) || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Weapon))
|| stateiter->second.mPriority == MWMechanics::Priority_Death) || stateiter->second.mPriority.contains(int(MWMechanics::Priority_Knockdown))
|| stateiter->second.mPriority.contains(int(MWMechanics::Priority_Death)))
return false; return false;
} }
return true; return true;

View file

@ -67,16 +67,53 @@ typedef boost::shared_ptr<PartHolder> PartHolderPtr;
class Animation class Animation
{ {
public: public:
enum Group { enum BoneGroup {
Group_LowerBody = 1<<0, BoneGroup_LowerBody = 0,
BoneGroup_Torso,
BoneGroup_LeftArm,
BoneGroup_RightArm
};
Group_Torso = 1<<1, enum BlendMask {
Group_LeftArm = 1<<2, BlendMask_LowerBody = 1<<0,
Group_RightArm = 1<<3, BlendMask_Torso = 1<<1,
BlendMask_LeftArm = 1<<2,
BlendMask_RightArm = 1<<3,
Group_UpperBody = Group_Torso | Group_LeftArm | Group_RightArm, BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm,
Group_All = Group_LowerBody | Group_UpperBody BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
};
/* This is the number of *discrete* blend masks. */
static const size_t sNumBlendMasks = 4;
/// Holds an animation priority value for each BoneGroup.
struct AnimPriority
{
/// Convenience constructor, initialises all priorities to the same value.
AnimPriority(int priority)
{
for (unsigned int i=0; i<sNumBlendMasks; ++i)
mPriority[i] = priority;
}
bool operator == (const AnimPriority& other) const
{
for (unsigned int i=0; i<sNumBlendMasks; ++i)
if (other.mPriority[i] != mPriority[i])
return false;
return true;
}
bool contains(int priority) const
{
for (unsigned int i=0; i<sNumBlendMasks; ++i)
if (priority == mPriority[i])
return true;
return false;
}
int mPriority[sNumBlendMasks];
}; };
class TextKeyListener class TextKeyListener
@ -89,9 +126,6 @@ public:
void setTextKeyListener(TextKeyListener* listener); void setTextKeyListener(TextKeyListener* listener);
protected: protected:
/* This is the number of *discrete* groups. */
static const size_t sNumGroups = 4;
class AnimationTime : public SceneUtil::ControllerSource class AnimationTime : public SceneUtil::ControllerSource
{ {
private: private:
@ -132,13 +166,13 @@ protected:
bool mPlaying; bool mPlaying;
size_t mLoopCount; size_t mLoopCount;
int mPriority; AnimPriority mPriority;
int mGroups; int mBlendMask;
bool mAutoDisable; bool mAutoDisable;
AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f), AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f),
mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0), mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0),
mPriority(0), mGroups(0), mAutoDisable(true) mPriority(0), mBlendMask(0), mAutoDisable(true)
{ {
} }
~AnimState(); ~AnimState();
@ -176,7 +210,7 @@ protected:
typedef std::multimap<osg::ref_ptr<osg::Node>, osg::ref_ptr<osg::NodeCallback> > ControllerMap; typedef std::multimap<osg::ref_ptr<osg::Node>, osg::ref_ptr<osg::NodeCallback> > ControllerMap;
ControllerMap mActiveControllers; ControllerMap mActiveControllers;
boost::shared_ptr<AnimationTime> mAnimationTimePtr[sNumGroups]; boost::shared_ptr<AnimationTime> mAnimationTimePtr[sNumBlendMasks];
// Stored in all lowercase for a case-insensitive lookup // Stored in all lowercase for a case-insensitive lookup
typedef std::map<std::string, osg::ref_ptr<osg::MatrixTransform> > NodeMap; typedef std::map<std::string, osg::ref_ptr<osg::MatrixTransform> > NodeMap;
@ -213,7 +247,7 @@ protected:
*/ */
void resetActiveGroups(); void resetActiveGroups();
size_t detectAnimGroup(osg::Node* node); size_t detectBlendMask(osg::Node* node);
/* Updates the position of the accum root node for the given time, and /* Updates the position of the accum root node for the given time, and
* returns the wanted movement vector from the previous time. */ * returns the wanted movement vector from the previous time. */
@ -304,7 +338,7 @@ public:
* \param priority Priority of the animation. The animation will play on * \param priority Priority of the animation. The animation will play on
* bone groups that don't have another animation set of a * bone groups that don't have another animation set of a
* higher priority. * higher priority.
* \param groups Bone groups to play the animation on. * \param blendMask Bone groups to play the animation on.
* \param autodisable Automatically disable the animation when it stops * \param autodisable Automatically disable the animation when it stops
* playing. * playing.
* \param speedmult Speed multiplier for the animation. * \param speedmult Speed multiplier for the animation.
@ -319,7 +353,7 @@ public:
* \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use * \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use
* the "start" and "stop" keys for looping? * the "start" and "stop" keys for looping?
*/ */
void play(const std::string &groupname, int priority, int groups, bool autodisable, void play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable,
float speedmult, const std::string &start, const std::string &stop, float speedmult, const std::string &start, const std::string &stop,
float startpoint, size_t loops, bool loopfallback=false); float startpoint, size_t loops, bool loopfallback=false);
@ -358,7 +392,6 @@ public:
* \param groupname Animation group to disable. * \param groupname Animation group to disable.
*/ */
void disable(const std::string &groupname); void disable(const std::string &groupname);
void changeGroups(const std::string &groupname, int group);
/** Retrieves the velocity (in units per second) that the animation will move. */ /** Retrieves the velocity (in units per second) that the animation will move. */
float getVelocity(const std::string &groupname) const; float getVelocity(const std::string &groupname) const;

View file

@ -241,13 +241,13 @@ namespace MWRender
mAnimation->showCarriedLeft(showCarriedLeft); mAnimation->showCarriedLeft(showCarriedLeft);
mCurrentAnimGroup = groupname; mCurrentAnimGroup = groupname;
mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0);
MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && showCarriedLeft) if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && showCarriedLeft)
{ {
if(!mAnimation->getInfo("torch")) if(!mAnimation->getInfo("torch"))
mAnimation->play("torch", 2, MWRender::Animation::Group_LeftArm, false, mAnimation->play("torch", 2, Animation::BlendMask_LeftArm, false,
1.0f, "start", "stop", 0.0f, ~0ul, true); 1.0f, "start", "stop", 0.0f, ~0ul, true);
} }
else if(mAnimation->getInfo("torch")) else if(mAnimation->getInfo("torch"))
@ -357,7 +357,7 @@ namespace MWRender
void RaceSelectionPreview::onSetup () void RaceSelectionPreview::onSetup ()
{ {
mAnimation->play("idle", 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0);
mAnimation->runAnimation(0.f); mAnimation->runAnimation(0.f);
// attach camera to follow the head node // attach camera to follow the head node