mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-29 18:32:36 +00:00
Merge pull request #1874 from akortunov/combat_anims
Fix some issues with attack animations
This commit is contained in:
commit
7f3769d5fc
@ -39,6 +39,7 @@
|
||||
Bug #4230: AiTravel package issues break some Tribunal quests
|
||||
Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality
|
||||
Bug #4251: Stationary NPCs do not return to their position after combat
|
||||
Bug #4271: Scamp flickers when attacking
|
||||
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
|
||||
Bug #4286: Scripted animations can be interrupted
|
||||
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
|
||||
@ -83,6 +84,7 @@
|
||||
Bug #4503: Cast and ExplodeSpell commands increase alteration skill
|
||||
Bug #4510: Division by zero in MWMechanics::CreatureStats::setAttribute
|
||||
Bug #4519: Knockdown does not discard movement in the 1st-person mode
|
||||
Bug #4531: Movement does not reset idle animations
|
||||
Bug #4539: Paper Doll is affected by GUI scaling
|
||||
Bug #4545: Creatures flee from werewolves
|
||||
Bug #4551: Replace 0 sound range with default range separately
|
||||
@ -95,6 +97,7 @@
|
||||
Bug #4574: Player turning animations are twitchy
|
||||
Bug #4575: Weird result of attack animation blending with movement animations
|
||||
Bug #4576: Reset of idle animations when attack can not be started
|
||||
Bug #4591: Attack strength should be 0 if player did not hold the attack button
|
||||
Feature #1645: Casting effects from objects
|
||||
Feature #2606: Editor: Implemented (optional) case sensitive global search
|
||||
Feature #3083: Play animation when NPC is casting spell via script
|
||||
|
@ -257,7 +257,7 @@ namespace MWBase
|
||||
virtual void cleanupSummonedCreature(const MWWorld::Ptr& caster, int creatureActorId) = 0;
|
||||
|
||||
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count) = 0;
|
||||
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual bool isRunning(const MWWorld::Ptr& ptr) = 0;
|
||||
virtual bool isSneaking(const MWWorld::Ptr& ptr) = 0;
|
||||
};
|
||||
|
@ -1004,9 +1004,9 @@ namespace MWInput
|
||||
if (!mControlSwitch["playerfighting"] || !mControlSwitch["playercontrols"])
|
||||
return;
|
||||
|
||||
// We want to interrupt animation only if attack is prepairing, but still is not triggered
|
||||
// We want to interrupt animation only if attack is preparing, but still is not triggered
|
||||
// Otherwise we will get a "speedshooting" exploit, when player can skip reload animation by hitting "Toggle Weapon" key twice
|
||||
if (MWBase::Environment::get().getMechanicsManager()->isAttackPrepairing(mPlayer->getPlayer()))
|
||||
if (MWBase::Environment::get().getMechanicsManager()->isAttackPreparing(mPlayer->getPlayer()))
|
||||
mPlayer->setAttackingOrSpell(false);
|
||||
else if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(mPlayer->getPlayer()))
|
||||
return;
|
||||
|
@ -896,14 +896,14 @@ namespace MWMechanics
|
||||
}
|
||||
}
|
||||
|
||||
bool Actors::isAttackPrepairing(const MWWorld::Ptr& ptr)
|
||||
bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
PtrActorMap::iterator it = mActors.find(ptr);
|
||||
if (it == mActors.end())
|
||||
return false;
|
||||
CharacterController* ctrl = it->second->getCharacterController();
|
||||
|
||||
return ctrl->isAttackPrepairing();
|
||||
return ctrl->isAttackPreparing();
|
||||
}
|
||||
|
||||
bool Actors::isRunning(const MWWorld::Ptr& ptr)
|
||||
|
@ -118,7 +118,7 @@ namespace MWMechanics
|
||||
int countDeaths (const std::string& id) const;
|
||||
///< Return the number of deaths for actors with the given ID.
|
||||
|
||||
bool isAttackPrepairing(const MWWorld::Ptr& ptr);
|
||||
bool isAttackPreparing(const MWWorld::Ptr& ptr);
|
||||
bool isRunning(const MWWorld::Ptr& ptr);
|
||||
bool isSneaking(const MWWorld::Ptr& ptr);
|
||||
|
||||
|
@ -372,21 +372,32 @@ namespace MWMechanics
|
||||
actorMovementSettings.mPosition[1] = storage.mMovement.mPosition[1];
|
||||
actorMovementSettings.mPosition[2] = storage.mMovement.mPosition[2];
|
||||
|
||||
rotateActorOnAxis(actor, 2, actorMovementSettings, storage.mMovement);
|
||||
rotateActorOnAxis(actor, 0, actorMovementSettings, storage.mMovement);
|
||||
rotateActorOnAxis(actor, 2, actorMovementSettings, storage);
|
||||
rotateActorOnAxis(actor, 0, actorMovementSettings, storage);
|
||||
}
|
||||
|
||||
void AiCombat::rotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
||||
MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement)
|
||||
MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage)
|
||||
{
|
||||
actorMovementSettings.mRotation[axis] = 0;
|
||||
float& targetAngleRadians = desiredMovement.mRotation[axis];
|
||||
float& targetAngleRadians = storage.mMovement.mRotation[axis];
|
||||
if (targetAngleRadians != 0)
|
||||
{
|
||||
if (smoothTurn(actor, targetAngleRadians, axis))
|
||||
// Some attack animations contain small amount of movement.
|
||||
// Since we use cone shapes for melee, we can use a threshold to avoid jittering
|
||||
std::shared_ptr<Action>& currentAction = storage.mCurrentAction;
|
||||
bool isRangedCombat = false;
|
||||
currentAction->getCombatRange(isRangedCombat);
|
||||
// Check if the actor now facing desired direction, no need to turn any more
|
||||
if (isRangedCombat)
|
||||
{
|
||||
// actor now facing desired direction, no need to turn any more
|
||||
targetAngleRadians = 0;
|
||||
if (smoothTurn(actor, targetAngleRadians, axis))
|
||||
targetAngleRadians = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (smoothTurn(actor, targetAngleRadians, axis, osg::DegreesToRadians(3.f)))
|
||||
targetAngleRadians = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -453,7 +464,7 @@ namespace MWMechanics
|
||||
if (distToTarget <= rangeAttackOfTarget && Misc::Rng::rollClosedProbability() < 0.25)
|
||||
{
|
||||
mMovement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f; // to the left/right
|
||||
mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability();
|
||||
mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
||||
mCombatMove = true;
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ namespace MWMechanics
|
||||
/// Transfer desired movement (from AiCombatStorage) to Actor
|
||||
void updateActorsMovement(const MWWorld::Ptr& actor, float duration, AiCombatStorage& storage);
|
||||
void rotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
||||
MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement);
|
||||
MWMechanics::Movement& actorMovementSettings, AiCombatStorage& storage);
|
||||
};
|
||||
|
||||
|
||||
|
@ -411,10 +411,6 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
||||
if(force || movement != mMovementState)
|
||||
{
|
||||
mMovementState = movement;
|
||||
|
||||
if (movement != CharState_None)
|
||||
mIdleState = CharState_None;
|
||||
|
||||
std::string movementAnimName;
|
||||
MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All;
|
||||
const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState));
|
||||
@ -531,7 +527,7 @@ void CharacterController::refreshMovementAnims(const WeaponInfo* weap, Character
|
||||
|
||||
void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force)
|
||||
{
|
||||
if(force || idle != mIdleState || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty()))
|
||||
if(force || idle != mIdleState || mIdleState == CharState_None || (!mAnimation->isPlaying(mCurrentIdle) && mAnimQueue.empty()))
|
||||
{
|
||||
mIdleState = idle;
|
||||
size_t numLoops = ~0ul;
|
||||
@ -562,14 +558,24 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat
|
||||
// play until the Loop Stop key 2 to 5 times, then play until the Stop key
|
||||
// this replicates original engine behavior for the "Idle1h" 1st-person animation
|
||||
numLoops = 1 + Misc::Rng::rollDice(4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mAnimation->disable(mCurrentIdle);
|
||||
// There is no need to restart anim if the new and old anims are the same.
|
||||
// Just update a number of loops.
|
||||
float startPoint = 0;
|
||||
if (!mCurrentIdle.empty() && mCurrentIdle == idleGroup)
|
||||
{
|
||||
mAnimation->getInfo(mCurrentIdle, &startPoint);
|
||||
}
|
||||
|
||||
if(!mCurrentIdle.empty())
|
||||
mAnimation->disable(mCurrentIdle);
|
||||
|
||||
mCurrentIdle = idleGroup;
|
||||
if(!mCurrentIdle.empty())
|
||||
mAnimation->play(mCurrentIdle, idlePriority, MWRender::Animation::BlendMask_All, false,
|
||||
1.0f, "start", "stop", 0.0f, numLoops, true);
|
||||
1.0f, "start", "stop", startPoint, numLoops, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1385,14 +1391,6 @@ 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
|
||||
@ -1401,15 +1399,10 @@ bool CharacterController::updateWeaponState()
|
||||
if (mPtr == player)
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
|
||||
}
|
||||
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
|
||||
// For the player, set the spell we want to cast
|
||||
// This has to be done at the start of the casting animation,
|
||||
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
|
||||
if (mPtr == player)
|
||||
{
|
||||
// For the player, set the spell we want to cast
|
||||
// This has to be done at the start of the casting animation,
|
||||
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
|
||||
std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
|
||||
stats.getSpells().setSelectedSpell(selectedSpell);
|
||||
}
|
||||
@ -1421,6 +1414,7 @@ bool CharacterController::updateWeaponState()
|
||||
MWMechanics::CastSpell cast(mPtr, NULL, false, mCastingManualSpell);
|
||||
cast.playSpellCastingEffects(spellid);
|
||||
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
||||
const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back();
|
||||
const ESM::MagicEffect *effect;
|
||||
@ -1520,13 +1514,12 @@ bool CharacterController::updateWeaponState()
|
||||
startKey = mAttackType+" start";
|
||||
stopKey = mAttackType+" min attack";
|
||||
}
|
||||
|
||||
if (isRandomAttackAnimation(mCurrentWeapon))
|
||||
else if (isRandomAttackAnimation(mCurrentWeapon))
|
||||
{
|
||||
startKey = "start";
|
||||
stopKey = "stop";
|
||||
}
|
||||
else if (mAttackType != "shoot")
|
||||
else
|
||||
{
|
||||
if(mPtr == getPlayer())
|
||||
{
|
||||
@ -1683,11 +1676,6 @@ bool CharacterController::updateWeaponState()
|
||||
std::string start, stop;
|
||||
switch(mUpperBodyState)
|
||||
{
|
||||
case UpperCharState_StartToMinAttack:
|
||||
start = mAttackType+" min attack";
|
||||
stop = mAttackType+" max attack";
|
||||
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
||||
break;
|
||||
case UpperCharState_MinAttackToMaxAttack:
|
||||
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
|
||||
if(!mAnimation->isPlaying(mCurrentWeapon))
|
||||
@ -1695,6 +1683,23 @@ bool CharacterController::updateWeaponState()
|
||||
MWRender::Animation::BlendMask_All, false,
|
||||
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
|
||||
break;
|
||||
case UpperCharState_StartToMinAttack:
|
||||
{
|
||||
// If actor is already stopped preparing attack, do not play the "min attack -> max attack" part.
|
||||
// Happens if the player did not hold the attack button.
|
||||
// Note: if the "min attack"->"max attack" is a stub, "play" it anyway. Attack strength will be 1.
|
||||
float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
|
||||
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
|
||||
if (mAttackingOrSpell || minAttackTime == maxAttackTime)
|
||||
{
|
||||
start = mAttackType+" min attack";
|
||||
stop = mAttackType+" max attack";
|
||||
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
|
||||
break;
|
||||
}
|
||||
playSwishSound(0.0f);
|
||||
}
|
||||
// Fall-through
|
||||
case UpperCharState_MaxAttackToMinHit:
|
||||
if(mAttackType == "shoot")
|
||||
{
|
||||
@ -2094,7 +2099,16 @@ void CharacterController::update(float duration)
|
||||
|
||||
if(mAnimQueue.empty() || inwater || sneak)
|
||||
{
|
||||
idlestate = (inwater ? CharState_IdleSwim : (sneak && !inJump ? CharState_IdleSneak : CharState_Idle));
|
||||
// Note: turning animations should not interrupt idle ones.
|
||||
// Also movement should not stop idle animation for spellcasting stance.
|
||||
if (inwater)
|
||||
idlestate = CharState_IdleSwim;
|
||||
else if (sneak && !inJump)
|
||||
idlestate = CharState_IdleSneak;
|
||||
else if (movestate != CharState_None && !isTurning() && mWeaponType != WeapType_Spell)
|
||||
idlestate = CharState_None;
|
||||
else
|
||||
idlestate = CharState_Idle;
|
||||
}
|
||||
else
|
||||
updateAnimQueue();
|
||||
@ -2493,7 +2507,7 @@ bool CharacterController::isRandomAttackAnimation(const std::string& group) cons
|
||||
group == "attack3" || group == "swimattack3");
|
||||
}
|
||||
|
||||
bool CharacterController::isAttackPrepairing() const
|
||||
bool CharacterController::isAttackPreparing() const
|
||||
{
|
||||
return mUpperBodyState == UpperCharState_StartToMinAttack ||
|
||||
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
|
||||
|
@ -281,7 +281,7 @@ public:
|
||||
|
||||
void forceStateUpdate();
|
||||
|
||||
bool isAttackPrepairing() const;
|
||||
bool isAttackPreparing() const;
|
||||
bool isCastingSpell() const;
|
||||
bool isReadyToBlock() const;
|
||||
bool isKnockedDown() const;
|
||||
|
@ -436,9 +436,9 @@ namespace MWMechanics
|
||||
return mActors.isActorDetected(actor, observer);
|
||||
}
|
||||
|
||||
bool MechanicsManager::isAttackPrepairing(const MWWorld::Ptr& ptr)
|
||||
bool MechanicsManager::isAttackPreparing(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
return mActors.isAttackPrepairing(ptr);
|
||||
return mActors.isAttackPreparing(ptr);
|
||||
}
|
||||
|
||||
bool MechanicsManager::isRunning(const MWWorld::Ptr& ptr)
|
||||
|
@ -229,7 +229,7 @@ namespace MWMechanics
|
||||
|
||||
virtual void confiscateStolenItemToOwner(const MWWorld::Ptr &player, const MWWorld::Ptr &item, const MWWorld::Ptr& victim, int count);
|
||||
|
||||
virtual bool isAttackPrepairing(const MWWorld::Ptr& ptr);
|
||||
virtual bool isAttackPreparing(const MWWorld::Ptr& ptr);
|
||||
virtual bool isRunning(const MWWorld::Ptr& ptr);
|
||||
virtual bool isSneaking(const MWWorld::Ptr& ptr);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user