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