mirror of
https://github.com/OpenMW/openmw.git
synced 2025-07-01 20:41:34 +00:00
Merged pull request #1749
This commit is contained in:
commit
a8ad530db9
9 changed files with 99 additions and 18 deletions
|
@ -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…
Reference in a new issue