Merge pull request #1350 from akortunov/deathanimationfix

Do not allow to loot fighting actors during death animation (bug #3528)
experimental
scrawl 7 years ago committed by GitHub
commit 2611377081

@ -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…
Cancel
Save