mirror of
https://github.com/TES3MP/TES3MP.git
synced 2025-09-27 06:55:54 -04:00
Merged pull request #1749
This commit is contained in:
commit
a8ad530db9
@ -10,6 +10,7 @@
|
|||||||
Bug #2862: [macOS] Can't quit launcher using Command-Q or OpenMW->Quit
|
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 #2971: Compiler did not reject lines with naked expressions beginning with x.y
|
||||||
Bug #3374: Touch spells not hitting kwama foragers
|
Bug #3374: Touch spells not hitting kwama foragers
|
||||||
|
Bug #3486: [Mod] NPC Commands does not work
|
||||||
Bug #3591: Angled hit distance too low
|
Bug #3591: Angled hit distance too low
|
||||||
Bug #3629: DB assassin attack never triggers creature spawning
|
Bug #3629: DB assassin attack never triggers creature spawning
|
||||||
Bug #3876: Landscape texture painting is misaligned
|
Bug #3876: Landscape texture painting is misaligned
|
||||||
@ -23,7 +24,10 @@
|
|||||||
Bug #4215: OpenMW shows book text after last <BR> tag
|
Bug #4215: OpenMW shows book text after last <BR> tag
|
||||||
Bug #4221: Characters get stuck in V-shaped terrain
|
Bug #4221: Characters get stuck in V-shaped terrain
|
||||||
Bug #4251: Stationary NPCs do not return to their position after combat
|
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 #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 #4327: Missing animations during spell/weapon stance switching
|
||||||
Bug #4368: Settings window ok button doesn't have key focus by default
|
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
|
Bug #4393: NPCs walk back to where they were after using ResetActors
|
||||||
|
@ -31,7 +31,7 @@ namespace MWClass
|
|||||||
if (!model.empty())
|
if (!model.empty())
|
||||||
{
|
{
|
||||||
physics.addActor(ptr, model);
|
physics.addActor(ptr, model);
|
||||||
if (getCreatureStats(ptr).isDead())
|
if (getCreatureStats(ptr).isDead() && getCreatureStats(ptr).isDeathAnimationFinished())
|
||||||
MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false);
|
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_Flee, ref->mBase->mAiData.mFlee);
|
||||||
data->mCreatureStats.setAiSetting (MWMechanics::CreatureStats::AI_Alarm, ref->mBase->mAiData.mAlarm);
|
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())
|
if (data->mCreatureStats.isDead())
|
||||||
data->mCreatureStats.setDeathAnimationFinished(true);
|
data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
|
||||||
|
|
||||||
// spells
|
// spells
|
||||||
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
|
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())
|
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!creatureStats.isDeathAnimationFinished())
|
||||||
|
return;
|
||||||
|
|
||||||
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
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 fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat();
|
||||||
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat();
|
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->getFloat();
|
||||||
|
@ -352,8 +352,10 @@ namespace MWClass
|
|||||||
|
|
||||||
data->mNpcStats.setNeedRecalcDynamicStats(true);
|
data->mNpcStats.setNeedRecalcDynamicStats(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Persistent actors with 0 health do not play death animation
|
||||||
if (data->mNpcStats.isDead())
|
if (data->mNpcStats.isDead())
|
||||||
data->mNpcStats.setDeathAnimationFinished(true);
|
data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
|
||||||
|
|
||||||
// race powers
|
// race powers
|
||||||
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
|
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())
|
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!creatureStats.isDeathAnimationFinished())
|
||||||
|
return;
|
||||||
|
|
||||||
const MWWorld::Store<ESM::GameSetting>& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
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 fCorpseRespawnDelay = gmst.find("fCorpseRespawnDelay")->getFloat();
|
||||||
static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->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)
|
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())
|
if (mPtr.getClass().isActor())
|
||||||
refreshHitRecoilAnims();
|
refreshHitRecoilAnims();
|
||||||
|
|
||||||
@ -744,6 +748,11 @@ void CharacterController::playRandomDeath(float startpoint)
|
|||||||
{
|
{
|
||||||
mDeathState = chooseRandomDeathState();
|
mDeathState = chooseRandomDeathState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not interrupt scripted animation by death
|
||||||
|
if (isPersistentAnimPlaying())
|
||||||
|
return;
|
||||||
|
|
||||||
playDeath(startpoint, mDeathState);
|
playDeath(startpoint, mDeathState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -829,8 +838,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
|
|||||||
mIdleState = CharState_Idle;
|
mIdleState = CharState_Idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not update animation status for dead actors
|
||||||
if(mDeathState == CharState_None)
|
if(mDeathState == CharState_None && (!cls.isActor() || !cls.getCreatureStats(mPtr).isDead()))
|
||||||
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
|
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
|
||||||
|
|
||||||
mAnimation->runAnimation(0.f);
|
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;
|
float complete;
|
||||||
bool animPlaying;
|
bool animPlaying;
|
||||||
if(mAttackingOrSpell)
|
if(mAttackingOrSpell)
|
||||||
@ -2013,15 +2026,17 @@ void CharacterController::update(float duration)
|
|||||||
{
|
{
|
||||||
// initial start of death animation for actors that started the game as dead
|
// 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
|
// 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);
|
playDeath(1.f, mDeathState);
|
||||||
}
|
}
|
||||||
// We must always queue movement, even if there is none, to apply gravity.
|
// We must always queue movement, even if there is none, to apply gravity.
|
||||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
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)
|
if(duration > 0.0f)
|
||||||
moved /= duration;
|
moved /= duration;
|
||||||
else
|
else
|
||||||
@ -2135,6 +2150,10 @@ bool CharacterController::playGroup(const std::string &groupname, int mode, int
|
|||||||
if(!mAnimation || !mAnimation->hasAnimation(groupname))
|
if(!mAnimation || !mAnimation->hasAnimation(groupname))
|
||||||
return false;
|
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
|
// 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 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.
|
// 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))
|
if(mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup))
|
||||||
{
|
{
|
||||||
clearAnimQueue();
|
clearAnimQueue(persist);
|
||||||
mAnimQueue.push_back(entry);
|
|
||||||
|
|
||||||
mAnimation->disable(mCurrentIdle);
|
mAnimation->disable(mCurrentIdle);
|
||||||
mCurrentIdle.clear();
|
mCurrentIdle.clear();
|
||||||
|
|
||||||
mIdleState = CharState_SpecialIdle;
|
mIdleState = CharState_SpecialIdle;
|
||||||
bool loopfallback = (entry.mGroup.compare(0,4,"idle") == 0);
|
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,
|
MWRender::Animation::BlendMask_All, false, 1.0f,
|
||||||
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback);
|
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1, loopfallback);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mAnimQueue.resize(1);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2189,6 +2213,17 @@ void CharacterController::skipAnim()
|
|||||||
mSkipAnim = true;
|
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)
|
bool CharacterController::isAnimPlaying(const std::string &groupName)
|
||||||
{
|
{
|
||||||
if(mAnimation == NULL)
|
if(mAnimation == NULL)
|
||||||
@ -2196,12 +2231,19 @@ bool CharacterController::isAnimPlaying(const std::string &groupName)
|
|||||||
return mAnimation->isPlaying(groupName);
|
return mAnimation->isPlaying(groupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CharacterController::clearAnimQueue(bool clearPersistAnims)
|
||||||
void CharacterController::clearAnimQueue()
|
|
||||||
{
|
{
|
||||||
if(!mAnimQueue.empty())
|
// Do not interrupt scripted animations, if we want to keep them
|
||||||
|
if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty())
|
||||||
mAnimation->disable(mAnimQueue.front().mGroup);
|
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()
|
void CharacterController::forceStateUpdate()
|
||||||
@ -2211,6 +2253,7 @@ void CharacterController::forceStateUpdate()
|
|||||||
clearAnimQueue();
|
clearAnimQueue();
|
||||||
|
|
||||||
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
|
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
|
||||||
|
|
||||||
if(mDeathState != CharState_None)
|
if(mDeathState != CharState_None)
|
||||||
{
|
{
|
||||||
playRandomDeath();
|
playRandomDeath();
|
||||||
|
@ -39,8 +39,8 @@ enum Priority {
|
|||||||
Priority_Knockdown,
|
Priority_Knockdown,
|
||||||
Priority_Torch,
|
Priority_Torch,
|
||||||
Priority_Storm,
|
Priority_Storm,
|
||||||
|
|
||||||
Priority_Death,
|
Priority_Death,
|
||||||
|
Priority_Persistent,
|
||||||
|
|
||||||
Num_Priorities
|
Num_Priorities
|
||||||
};
|
};
|
||||||
@ -215,12 +215,14 @@ class CharacterController : public MWRender::Animation::TextKeyListener
|
|||||||
void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false);
|
void refreshMovementAnims(const WeaponInfo* weap, CharacterState movement, bool force=false);
|
||||||
void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false);
|
void refreshIdleAnims(const WeaponInfo* weap, CharacterState idle, bool force=false);
|
||||||
|
|
||||||
void clearAnimQueue();
|
void clearAnimQueue(bool clearPersistAnims = false);
|
||||||
|
|
||||||
bool updateWeaponState();
|
bool updateWeaponState();
|
||||||
bool updateCreatureState();
|
bool updateCreatureState();
|
||||||
void updateIdleStormState(bool inwater);
|
void updateIdleStormState(bool inwater);
|
||||||
|
|
||||||
|
bool isPersistentAnimPlaying();
|
||||||
|
|
||||||
void updateAnimQueue();
|
void updateAnimQueue();
|
||||||
|
|
||||||
void updateHeadTracking(float duration);
|
void updateHeadTracking(float duration);
|
||||||
|
@ -1089,11 +1089,28 @@ namespace MWRender
|
|||||||
|
|
||||||
osg::Vec3f Animation::runAnimation(float duration)
|
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);
|
osg::Vec3f movement(0.f, 0.f, 0.f);
|
||||||
AnimStateMap::iterator stateiter = mStates.begin();
|
AnimStateMap::iterator stateiter = mStates.begin();
|
||||||
while(stateiter != mStates.end())
|
while(stateiter != mStates.end())
|
||||||
{
|
{
|
||||||
AnimState &state = stateiter->second;
|
AnimState &state = stateiter->second;
|
||||||
|
if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent)))
|
||||||
|
{
|
||||||
|
++stateiter;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys();
|
const NifOsg::TextKeyMap &textkeys = state.mSource->getTextKeys();
|
||||||
NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime()));
|
NifOsg::TextKeyMap::const_iterator textkey(textkeys.upper_bound(state.getTime()));
|
||||||
|
|
||||||
|
@ -945,9 +945,14 @@ namespace MWWorld
|
|||||||
{
|
{
|
||||||
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
|
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
|
||||||
static const float fCorpseClearDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fCorpseClearDelay")->getFloat();
|
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);
|
MWBase::Environment::get().getWorld()->deleteObject(ptr);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CellStore::respawn()
|
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.
|
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.
|
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.
|
This setting can only be configured by editing the settings configuration file.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user