forked from teamnwah/openmw-tes3coop
Merge pull request #1350 from akortunov/deathanimationfix
Do not allow to loot fighting actors during death animation (bug #3528)
This commit is contained in:
commit
2611377081
8 changed files with 111 additions and 40 deletions
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <components/esm/loadcrea.hpp>
|
#include <components/esm/loadcrea.hpp>
|
||||||
#include <components/esm/creaturestate.hpp>
|
#include <components/esm/creaturestate.hpp>
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/magiceffects.hpp"
|
#include "../mwmechanics/magiceffects.hpp"
|
||||||
|
@ -448,10 +449,27 @@ namespace MWClass
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(getCreatureStats(ptr).isDead())
|
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
|
|
||||||
if(ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat())
|
if(stats.isDead())
|
||||||
|
{
|
||||||
|
bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game");
|
||||||
|
|
||||||
|
// by default user can loot friendly actors during death animation
|
||||||
|
if (canLoot && !stats.getAiSequence().isInCombat())
|
||||||
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
|
||||||
|
|
||||||
|
// otherwise wait until death animation
|
||||||
|
if(stats.isDeathAnimationFinished())
|
||||||
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
|
||||||
|
|
||||||
|
// death animation is not finished, do nothing
|
||||||
|
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stats.getAiSequence().isInCombat())
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
|
||||||
|
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,7 +576,11 @@ namespace MWClass
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
||||||
return !customData.mCreatureStats.getAiSequence().isInCombat() || customData.mCreatureStats.isDead();
|
|
||||||
|
if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return !customData.mCreatureStats.getAiSequence().isInCombat();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
MWGui::ToolTipInfo Creature::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <components/esm/loadmgef.hpp>
|
#include <components/esm/loadmgef.hpp>
|
||||||
#include <components/esm/loadnpc.hpp>
|
#include <components/esm/loadnpc.hpp>
|
||||||
#include <components/esm/npcstate.hpp>
|
#include <components/esm/npcstate.hpp>
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
@ -863,16 +864,35 @@ namespace MWClass
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(getCreatureStats(ptr).isDead())
|
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
|
|
||||||
if(ptr.getClass().getCreatureStats(ptr).getAiSequence().isInCombat())
|
if(stats.isDead())
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction("#{sActorInCombat}"));
|
{
|
||||||
|
bool canLoot = Settings::Manager::getBool ("can loot during death animation", "Game");
|
||||||
|
|
||||||
|
// by default user can loot friendly actors during death animation
|
||||||
|
if (canLoot && !stats.getAiSequence().isInCombat())
|
||||||
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
|
||||||
|
|
||||||
|
// otherwise wait until death animation
|
||||||
|
if(stats.isDeathAnimationFinished())
|
||||||
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr, true));
|
||||||
|
|
||||||
|
// death animation is not finished, do nothing
|
||||||
|
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stats.getAiSequence().isInCombat())
|
||||||
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
|
||||||
|
|
||||||
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)
|
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)
|
||||||
|| ptr.getClass().getCreatureStats(ptr).getKnockedDown())
|
|| ptr.getClass().getCreatureStats(ptr).getKnockedDown())
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
|
||||||
|
|
||||||
// Can't talk to werewolfs
|
// Can't talk to werewolfs
|
||||||
if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf())
|
if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf())
|
||||||
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
|
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
|
||||||
|
|
||||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1019,7 +1039,11 @@ namespace MWClass
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
||||||
return !customData.mNpcStats.getAiSequence().isInCombat() || customData.mNpcStats.isDead();
|
|
||||||
|
if (customData.mNpcStats.isDead() && customData.mNpcStats.isDeathAnimationFinished())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return !customData.mNpcStats.getAiSequence().isInCombat();
|
||||||
}
|
}
|
||||||
|
|
||||||
MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
MWGui::ToolTipInfo Npc::getToolTipInfo (const MWWorld::ConstPtr& ptr, int count) const
|
||||||
|
|
|
@ -783,7 +783,7 @@ namespace MWMechanics
|
||||||
creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures);
|
creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures);
|
||||||
if (ptr.getClass().hasInventoryStore(ptr))
|
if (ptr.getClass().hasInventoryStore(ptr))
|
||||||
ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures);
|
ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures);
|
||||||
updateSummonedCreatures.process();
|
updateSummonedCreatures.process(mTimerDisposeSummonsCorpses == 0.f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1040,7 +1040,9 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Actors::Actors() {}
|
Actors::Actors() {
|
||||||
|
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
|
||||||
|
}
|
||||||
|
|
||||||
Actors::~Actors()
|
Actors::~Actors()
|
||||||
{
|
{
|
||||||
|
@ -1109,6 +1111,7 @@ namespace MWMechanics
|
||||||
// target lists get updated once every 1.0 sec
|
// target lists get updated once every 1.0 sec
|
||||||
if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
|
if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
|
||||||
if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
|
if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
|
||||||
|
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
|
||||||
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
|
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
|
||||||
|
|
||||||
MWWorld::Ptr player = getPlayer();
|
MWWorld::Ptr player = getPlayer();
|
||||||
|
@ -1213,6 +1216,7 @@ namespace MWMechanics
|
||||||
timerUpdateAITargets += duration;
|
timerUpdateAITargets += duration;
|
||||||
timerUpdateHeadTrack += duration;
|
timerUpdateHeadTrack += duration;
|
||||||
timerUpdateEquippedLight += duration;
|
timerUpdateEquippedLight += duration;
|
||||||
|
mTimerDisposeSummonsCorpses += duration;
|
||||||
|
|
||||||
// Looping magic VFX update
|
// Looping magic VFX update
|
||||||
// Note: we need to do this before any of the animations are updated.
|
// Note: we need to do this before any of the animations are updated.
|
||||||
|
|
|
@ -153,6 +153,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PtrActorMap mActors;
|
PtrActorMap mActors;
|
||||||
|
float mTimerDisposeSummonsCorpses;
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,26 +38,10 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateSummonedCreatures::process()
|
void UpdateSummonedCreatures::process(bool cleanup)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor);
|
MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor);
|
||||||
|
|
||||||
// Update summon effects
|
|
||||||
std::map<CreatureStats::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
|
std::map<CreatureStats::SummonKey, int>& creatureMap = creatureStats.getSummonedCreatureMap();
|
||||||
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
|
||||||
{
|
|
||||||
bool found = mActiveEffects.find(it->first) != mActiveEffects.end();
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
// Effect has ended
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second);
|
|
||||||
creatureMap.erase(it++);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::set<std::pair<int, std::string> >::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it)
|
for (std::set<std::pair<int, std::string> >::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it)
|
||||||
{
|
{
|
||||||
|
@ -101,21 +85,18 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update summon effects
|
||||||
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||||
{
|
{
|
||||||
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
|
bool found = mActiveEffects.find(it->first) != mActiveEffects.end();
|
||||||
if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished())
|
if (!found)
|
||||||
{
|
{
|
||||||
// Purge the magic effect so a new creature can be summoned if desired
|
// Effect has ended
|
||||||
creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second);
|
|
||||||
if (mActor.getClass().hasInventoryStore(ptr))
|
|
||||||
mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second);
|
|
||||||
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second);
|
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second);
|
||||||
creatureMap.erase(it++);
|
creatureMap.erase(it++);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else
|
++it;
|
||||||
++it;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<int>& graveyard = creatureStats.getSummonedCreatureGraveyard();
|
std::vector<int>& graveyard = creatureStats.getSummonedCreatureGraveyard();
|
||||||
|
@ -137,6 +118,26 @@ namespace MWMechanics
|
||||||
else
|
else
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cleanup)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (std::map<CreatureStats::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
|
||||||
|
{
|
||||||
|
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
|
||||||
|
if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()))
|
||||||
|
{
|
||||||
|
// Purge the magic effect so a new creature can be summoned if desired
|
||||||
|
creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second);
|
||||||
|
if (mActor.getClass().hasInventoryStore(mActor))
|
||||||
|
mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second);
|
||||||
|
|
||||||
|
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second);
|
||||||
|
creatureMap.erase(it++);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace MWMechanics
|
||||||
float magnitude, float remainingTime = -1, float totalTime = -1);
|
float magnitude, float remainingTime = -1, float totalTime = -1);
|
||||||
|
|
||||||
/// To call after all effect sources have been visited
|
/// To call after all effect sources have been visited
|
||||||
void process();
|
void process(bool cleanup);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MWWorld::Ptr mActor;
|
MWWorld::Ptr mActor;
|
||||||
|
|
|
@ -65,6 +65,22 @@ the type of attack is determined by the direction that the character is moving a
|
||||||
The default value is false.
|
The default value is false.
|
||||||
This setting can be toggled with the Always Use Best Attack button in the Prefs panel of the Options menu.
|
This setting can be toggled with the Always Use Best Attack button in the Prefs panel of the Options menu.
|
||||||
|
|
||||||
|
can loot during death animation
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: True
|
||||||
|
|
||||||
|
If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat.
|
||||||
|
However disposing corpses during death animation is not recommended - death counter may not be incremented, and this behaviour can break quests.
|
||||||
|
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.
|
||||||
|
|
||||||
|
The default value is true. This setting can only be configured by editing the settings configuration file.
|
||||||
|
|
||||||
difficulty
|
difficulty
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -110,4 +126,4 @@ followers attack on sight
|
||||||
:Default: False
|
:Default: False
|
||||||
|
|
||||||
Makes player followers and escorters start combat with enemies who have started combat with them or the player.
|
Makes player followers and escorters start combat with enemies who have started combat with them or the player.
|
||||||
Otherwise they wait for the enemies or the player to do an attack first.
|
Otherwise they wait for the enemies or the player to do an attack first.
|
||||||
|
|
|
@ -180,6 +180,9 @@ prevent merchant equipping = false
|
||||||
# or the player. Otherwise they wait for the enemies or the player to do an attack first.
|
# or the player. Otherwise they wait for the enemies or the player to do an attack first.
|
||||||
followers attack on sight = false
|
followers attack on sight = false
|
||||||
|
|
||||||
|
# Can loot non-fighting actors during death animation
|
||||||
|
can loot during death animation = true
|
||||||
|
|
||||||
[General]
|
[General]
|
||||||
|
|
||||||
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
|
# Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16).
|
||||||
|
|
Loading…
Reference in a new issue