Merged pull request #1749

pull/456/head
Marc Zinnschlag 7 years ago
commit a8ad530db9

@ -10,6 +10,7 @@
Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit
Bug #2971: Compiler did not reject lines with naked expressions beginning with x.y
Bug #3374: Touch spells not hitting kwama foragers
Bug #3486: [Mod] NPC Commands does not work
Bug #3591: Angled hit distance too low
Bug #3629: DB assassin attack never triggers creature spawning
Bug #3876: Landscape texture painting is misaligned
@ -23,7 +24,10 @@
Bug #4215: OpenMW shows book text after last <BR> tag
Bug #4221: Characters get stuck in V-shaped terrain
Bug #4251: Stationary NPCs do not return to their position after combat
Bug #4286: Scripted animations can be interrupted
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
Bug #4293: Faction members are not aware of faction ownerships in barter
Bug #4307: World cleanup should remove dead bodies only if death animation is finished
Bug #4327: Missing animations during spell/weapon stance switching
Bug #4368: Settings window ok button doesn't have key focus by default
Bug #4393: NPCs walk back to where they were after using ResetActors

@ -31,7 +31,7 @@ namespace MWClass
if (!model.empty())
{
physics.addActor(ptr, model);
if (getCreatureStats(ptr).isDead())
if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished())
MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false);
}
}

@ -135,8 +135,9 @@ namespace MWClass
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Flee, ref->mBase->mAiData.mFlee);
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
// Persistent actors with 0 health do not play death animation
if (data->mCreatureStats.isDead())
data->mCreatureStats.setDeathAnimationFinished(true);
data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
// spells
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
@ -814,6 +815,9 @@ namespace MWClass
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return;
if (!creatureStats.isDeathAnimationFinished())
return;
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat();
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat();

@ -352,8 +352,10 @@ namespace MWClass
data->mNpcStats.setNeedRecalcDynamicStats(true);
}
// Persistent actors with 0 health do not play death animation
if (data->mNpcStats.isDead())
data->mNpcStats.setDeathAnimationFinished(true);
data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
// race powers
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
@ -1351,6 +1353,9 @@ namespace MWClass
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
return;
if (!creatureStats.isDeathAnimationFinished())
return;
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
static const float fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat();
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat();

@ -561,6 +561,10 @@ void CharacterController::refreshIdleAnims(const WeaponInfo* weap, CharacterStat
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force)
{
// If the current animation is persistent, do not touch it
if (isPersistentAnimPlaying())
return;
if (mPtr.getClass().isActor())
refreshHitRecoilAnims();
@ -744,6 +748,11 @@ void CharacterController::playRandomDeath(float startpoint)
{
mDeathState = chooseRandomDeathState();
}
// Do not interrupt scripted animation by death
if (isPersistentAnimPlaying())
return;
playDeath(startpoint, mDeathState);
}
@ -829,8 +838,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
mIdleState = CharState_Idle;
}
if(mDeathState == CharState_None)
// Do not update animation status for dead actors
if(mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead()))
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
mAnimation->runAnimation(0.f);
@ -1299,6 +1308,10 @@ bool CharacterController::updateWeaponState()
}
}
// Combat for actors with persistent animations obviously will be buggy
if (isPersistentAnimPlaying())
return forcestateupdate;
float complete;
bool animPlaying;
if(mAttackingOrSpell)
@ -2013,15 +2026,17 @@ void CharacterController::update(float duration)
{
// initial start of death animation for actors that started the game as dead
// not done in constructor since we need to give scripts a chance to set the mSkipAnim flag
if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty())
if (!mSkipAnim && mDeathState != CharState_None && mCurrentDeath.empty() && cls.isPersistent(mPtr))
{
// Fast-forward death animation to end for persisting corpses
playDeath(1.f, mDeathState);
}
// We must always queue movement, even if there is none, to apply gravity.
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
}
osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration);
bool isPersist = isPersistentAnimPlaying();
osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration);
if(duration > 0.0f)
moved /= duration;
else
@ -2135,6 +2150,10 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int
if(!mAnimation || !mAnimation->hasAnimation(groupname))
return false;
// We should not interrupt persistent animations by non-persistent ones
if (isPersistentAnimPlaying() && !persist)
return false;
// If this animation is a looped animation (has a "loop start" key) that is already playing
// and has not yet reached the end of the loop, allow it to continue animating with its existing loop count
// and remove any other animations that were queued.
@ -2164,23 +2183,28 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int
if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
{
clearAnimQueue();
mAnimQueue.push_back(entry);
clearAnimQueue(persist);
mAnimation->disable(mCurrentIdle);
mCurrentIdle.clear();
mIdleState = CharState_SpecialIdle;
bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0);
mAnimation->play(groupname, Priority_Default,
mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default,
MWRender::Animation::BlendMask_All, false, 1.0f,
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback);
}
else
{
mAnimQueue.resize(1);
mAnimQueue.push_back(entry);
}
// "PlayGroup idle" is a special case, used to remove to stop scripted animations playing
if (groupname == "idle")
entry.mPersist = false;
mAnimQueue.push_back(entry);
return true;
}
@ -2189,6 +2213,17 @@ void CharacterController::skipAnim()
mSkipAnim = true;
}
bool CharacterController::isPersistentAnimPlaying()
{
if (!mAnimQueue.empty())
{
AnimationQueueEntry& first = mAnimQueue.front();
return first.mPersist && isAnimPlaying(first.mGroup);
}
return false;
}
bool CharacterController::isAnimPlaying(const std::string &groupName)
{
if(mAnimation == NULL)
@ -2196,12 +2231,19 @@ bool CharacterController::isAnimPlaying(const std::string &groupName)
return mAnimation->isPlaying(groupName);
}
void CharacterController::clearAnimQueue()
void CharacterController::clearAnimQueue(bool clearPersistAnims)
{
if(!mAnimQueue.empty())
// Do not interrupt scripted animations, if we want to keep them
if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty())
mAnimation->disable(mAnimQueue.front().mGroup);
mAnimQueue.clear();
for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();)
{
if (clearPersistAnims || !it->mPersist)
it = mAnimQueue.erase(it);
else
++it;
}
}
void CharacterController::forceStateUpdate()
@ -2211,6 +2253,7 @@ void CharacterController::forceStateUpdate()
clearAnimQueue();
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
if(mDeathState != CharState_None)
{
playRandomDeath();

@ -39,8 +39,8 @@ enum Priority {
Priority_Knockdown,
Priority_Torch,
Priority_Storm,
Priority_Death,
Priority_Persistent,
Num_Priorities
};
@ -215,12 +215,14 @@ class CharacterController : public MWRender::Animation::TextKeyListener
void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false);
void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false);
void clearAnimQueue();
void clearAnimQueue(bool clearPersistAnims = false);
bool updateWeaponState();
bool updateCreatureState();
void updateIdleStormState(bool inwater);
bool isPersistentAnimPlaying();
void updateAnimQueue();
void updateHeadTracking(float duration);

@ -1089,11 +1089,28 @@ namespace MWRender
osg::Vec3f Animation::runAnimation(float duration)
{
// If we have scripted animations, play only them
bool hasScriptedAnims = false;
for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++)
{
if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) && stateiter->second.mPlaying)
{
hasScriptedAnims = true;
break;
}
}
osg::Vec3f movement(0.f, 0.f, 0.f);
AnimStateMap::iterator stateiter = mStates.begin();
while(stateiter != mStates.end())
{
AnimState &state = stateiter->second;
if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent)))
{
++stateiter;
continue;
}
const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys();
NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime()));

@ -945,8 +945,13 @@ namespace MWWorld
{
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fCorpseClearDelay")->getFloat();
if (creatureStats.isDead() && !ptr.getClass().isPersistent(ptr) && creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp())
if (creatureStats.isDead() &&
creatureStats.isDeathAnimationFinished() &&
!ptr.getClass().isPersistent(ptr) &&
creatureStats.getTimeOfDeath() + fCorpseClearDelay <= MWBase::Environment::get().getWorld()->getTimeStamp())
{
MWBase::Environment::get().getWorld()->deleteObject(ptr);
}
}
void CellStore::respawn()

@ -77,6 +77,7 @@ This is how original Morrowind behaves.
If this setting is false, player has to wait until end of death animation in all cases.
This case is more safe, but makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder.
Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.
This setting can only be configured by editing the settings configuration file.

Loading…
Cancel
Save