diff --git a/CHANGELOG.md b/CHANGELOG.md index 65adf8add1..21ce44a2ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -176,6 +176,7 @@ Feature #5122: Use magic glow for enchanted arrows Feature #5131: Custom skeleton bones Feature #5132: Unique animations for different weapon types + Feature #5146: Safe Dispose corpse Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption Task #4789: Optimize cell transitions diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index 5c5821bca2..56386d4a1d 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -237,6 +237,8 @@ namespace MWBase virtual float getActorsProcessingRange() const = 0; + virtual void notifyDied(const MWWorld::Ptr& actor) = 0; + virtual bool onOpen(const MWWorld::Ptr& ptr) = 0; virtual void onClose(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index bfe93f6ed9..6a303dfbad 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -7,12 +7,15 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/scriptmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwscript/interpretercontext.hpp" + #include "countdialog.hpp" #include "inventorywindow.hpp" @@ -229,7 +232,26 @@ namespace MWGui if (mPtr.getClass().isPersistent(mPtr)) MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}"); else + { + MWMechanics::CreatureStats& creatureStats = mPtr.getClass().getCreatureStats(mPtr); + + // If we dispose corpse before end of death animation, we should update death counter counter manually. + // Also we should run actor's script - it may react on actor's death. + if (creatureStats.isDead() && !creatureStats.isDeathAnimationFinished()) + { + creatureStats.setDeathAnimationFinished(true); + MWBase::Environment::get().getMechanicsManager()->notifyDied(mPtr); + + const std::string script = mPtr.getClass().getScript(mPtr); + if (!script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled()) + { + MWScript::InterpreterContext interpreterContext (&mPtr.getRefData().getLocals(), mPtr); + MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + } + } + MWBase::Environment::get().getWorld()->deleteObject(mPtr); + } mPtr = MWWorld::Ptr(); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 929560ef1d..ff59ba4b91 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1630,6 +1630,13 @@ namespace MWMechanics updateCombatMusic(); } + void Actors::notifyDied(const MWWorld::Ptr &actor) + { + actor.getClass().getCreatureStats(actor).notifyDied(); + + ++mDeathCount[Misc::StringUtils::lowerCase(actor.getCellRef().getRefId())]; + } + void Actors::killDeadActors() { for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) @@ -1673,9 +1680,7 @@ namespace MWMechanics } else if (killResult == CharacterController::Result_DeathAnimJustFinished) { - iter->first.getClass().getCreatureStats(iter->first).notifyDied(); - - ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; + notifyDied(iter->first); // Reset magic effects and recalculate derived effects // One case where we need this is to make sure bound items are removed upon death diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 07e60e1d11..9621a8619b 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -71,6 +71,8 @@ namespace MWMechanics PtrActorMap::const_iterator begin() { return mActors.begin(); } PtrActorMap::const_iterator end() { return mActors.end(); } + void notifyDied(const MWWorld::Ptr &actor); + /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fe5f60ec4e..7f58ebc460 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -455,6 +455,11 @@ namespace MWMechanics } } + void MechanicsManager::notifyDied(const MWWorld::Ptr& actor) + { + mActors.notifyDied(actor); + } + float MechanicsManager::getActorsProcessingRange() const { return mActors.getProcessingRange(); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index a81a6bd75e..e0ab039c85 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -211,6 +211,8 @@ namespace MWMechanics virtual float getActorsProcessingRange() const override; + virtual void notifyDied(const MWWorld::Ptr& actor) override; + /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) override; diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4e397e9c9a..7bfa60c6c2 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -72,13 +72,10 @@ can loot during death animation :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 Morrowind behaves. +if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly. 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. +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 be toggled in Advanced tab of the launcher. diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index f7575cd2b8..152ab64115 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -19,7 +19,7 @@ - <html><head/><body><p>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.</p><p>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.</p></body></html> + <html><head/><body><p>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. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. 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.</p></body></html> Can loot during death animation