Add OpenMW commits up to 5 Sep 2019

# Conflicts:
#	apps/openmw/mwgui/container.cpp
#	apps/openmw/mwmechanics/actors.cpp
#	apps/openmw/mwworld/worldimp.hpp
pull/541/head
David Cernat 5 years ago
commit ca67587b89

@ -86,6 +86,7 @@ Programmers
Jacob Essex (Yacoby)
Jake Westrip (16bitint)
James Carty (MrTopCat)
James Stephens (james-h-stephens)
Jan-Peter Nilsson (peppe)
Jan Borsodi (am0s)
Jason Hooks (jhooks)

@ -18,6 +18,7 @@
Bug #3894: Hostile spell effects not detected/present on first frame of OnPCHitMe
Bug #4202: Open .omwaddon files without needing toopen openmw-cs first
Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect
Bug #4270: Closing doors while they are obstructed desyncs closing sfx
Bug #4276: Resizing character window differs from vanilla
Bug #4329: Removed birthsign abilities are restored after reloading the save
Bug #4341: Error message about missing GDB is too vague
@ -27,6 +28,7 @@
Bug #4540: Rain delay when exiting water
Bug #4600: Crash when no sound output is available or --no-sound is used.
Bug #4639: Black screen after completing first mages guild mission + training
Bug #4650: Focus is lost after pressing ESC in confirmation dialog inside savegame dialog
Bug #4701: PrisonMarker record is not hardcoded like other markers
Bug #4703: Editor: it's possible to preview levelled list records
Bug #4705: Editor: unable to open exterior cell views from Instances table
@ -128,12 +130,13 @@
Bug #5106: Still can jump even when encumbered
Bug #5110: ModRegion with a redundant numerical argument breaks script execution
Bug #5112: Insufficient magicka for current spell not reflected on HUD icon
Bug #5113: Unknown alchemy question mark not centered
Bug #5123: Script won't run on respawn
Bug #5124: Arrow remains attached to actor if pulling animation was cancelled
Bug #5126: Swimming creatures without RunForward animations are motionless during combat
Bug #5134: Doors rotation by "Lock" console command is inconsistent
Bug #5126: Swimming creatures without RunForward animations are motionless during combat
Bug #5137: Textures with Clamp Mode set to Clamp instead of Wrap are too dark outside the boundaries
Bug #5149: Failing lock pick attempts isn't always a crime
Feature #1774: Handle AvoidNode
Feature #2229: Improve pathfinding AI
Feature #3025: Analogue gamepad movement controls
@ -177,6 +180,8 @@
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
Feature #5147: Show spell magicka cost in spell buying window
Task #4686: Upgrade media decoder to a more current FFmpeg API
Task #4695: Optimize Distant Terrain memory consumption
Task #4789: Optimize cell transitions

@ -153,8 +153,8 @@ namespace MWBase
/// @param container The container the item is in; may be empty for an item in the world
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
int count, bool alarm = true) = 0;
/// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0;
/// Utility to check if unlocking this object is illegal and calling commitCrime if so
virtual void unlockAttempted (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0;
/// Attempt sleeping in a bed. If this is illegal, call commitCrime.
/// @return was it illegal, and someone saw you doing it?
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0;
@ -247,6 +247,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;

@ -9,6 +9,7 @@
#include <components/esm/cellid.hpp>
#include "../mwworld/ptr.hpp"
#include "../mwworld/doorstate.hpp"
#include "../mwrender/rendermode.hpp"
@ -571,14 +572,14 @@ namespace MWBase
/// update movement state of a non-teleport door as specified
/// @param state see MWClass::setDoorState
/// @note throws an exception when invoked on a teleport door
virtual void activateDoor(const MWWorld::Ptr& door, int state) = 0;
virtual void activateDoor(const MWWorld::Ptr& door, MWWorld::DoorState state) = 0;
/*
Start of tes3mp addition
Useful self-contained method for saving door states
*/
virtual void saveDoorState(const MWWorld::Ptr& door, int state) = 0;
virtual void saveDoorState(const MWWorld::Ptr& door, MWWorld::DoorState state) = 0;
/*
End of tes3mp addition
*/

@ -46,7 +46,7 @@ namespace MWClass
class DoorCustomData : public MWWorld::CustomData
{
public:
int mDoorState; // 0 = nothing, 1 = opening, 2 = closing
MWWorld::DoorState mDoorState;
virtual MWWorld::CustomData *clone() const;
@ -83,7 +83,7 @@ namespace MWClass
if (ptr.getRefData().getCustomData())
{
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
if (customData.mDoorState > 0)
if (customData.mDoorState != MWWorld::DoorState::Idle)
{
MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState);
}
@ -265,12 +265,12 @@ namespace MWClass
{
// animated door
std::shared_ptr<MWWorld::Action> action(new MWWorld::ActionDoor(ptr));
int doorstate = getDoorState(ptr);
const auto doorState = getDoorState(ptr);
bool opening = true;
float doorRot = ptr.getRefData().getPosition().rot[2] - ptr.getCellRef().getPosition().rot[2];
if (doorstate == 1)
if (doorState == MWWorld::DoorState::Opening)
opening = false;
if (doorstate == 0 && doorRot != 0)
if (doorState == MWWorld::DoorState::Idle && doorRot != 0)
opening = false;
if (opening)
@ -429,20 +429,20 @@ namespace MWClass
{
std::unique_ptr<DoorCustomData> data(new DoorCustomData);
data->mDoorState = 0;
data->mDoorState = MWWorld::DoorState::Idle;
ptr.getRefData().setCustomData(data.release());
}
}
int Door::getDoorState (const MWWorld::ConstPtr &ptr) const
MWWorld::DoorState Door::getDoorState (const MWWorld::ConstPtr &ptr) const
{
if (!ptr.getRefData().getCustomData())
return 0;
return MWWorld::DoorState::Idle;
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
return customData.mDoorState;
}
void Door::setDoorState (const MWWorld::Ptr &ptr, int state) const
void Door::setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const
{
if (ptr.getCellRef().getTeleport())
throw std::runtime_error("load doors can't be moved");
@ -460,7 +460,7 @@ namespace MWClass
DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
const ESM::DoorState& state2 = dynamic_cast<const ESM::DoorState&>(state);
customData.mDoorState = state2.mDoorState;
customData.mDoorState = static_cast<MWWorld::DoorState>(state2.mDoorState);
}
void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
@ -473,7 +473,7 @@ namespace MWClass
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
ESM::DoorState& state2 = dynamic_cast<ESM::DoorState&>(state);
state2.mDoorState = customData.mDoorState;
state2.mDoorState = static_cast<int>(customData.mDoorState);
}
}

@ -59,10 +59,9 @@ namespace MWClass
virtual std::string getModel(const MWWorld::ConstPtr &ptr) const;
/// 0 = nothing, 1 = opening, 2 = closing
virtual int getDoorState (const MWWorld::ConstPtr &ptr) const;
virtual MWWorld::DoorState getDoorState (const MWWorld::ConstPtr &ptr) const;
/// This does not actually cause the door to move. Use World::activateDoor instead.
virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const;
virtual void setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const;
virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state)

@ -40,14 +40,13 @@ namespace MWGui
bool ConfirmationDialog::exit()
{
setVisible(false);
eventCancelClicked();
return true;
}
void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender)
{
setVisible(false);
exit();
}

@ -22,12 +22,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"
@ -384,23 +387,46 @@ namespace MWGui
if (mPtr.getClass().isPersistent(mPtr))
MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}");
/*
Start of tes3mp change (major)
Instead of deleting the corpse on this client, simply send an ID_OBJECT_DELETE
packet to the server as a request for the deletion
*/
else
{
/*
Start of tes3mp change (major)
Instead of deleting the corpse on this client, increasing the death count and
running the dead actor's sccript, simply send an ID_OBJECT_DELETE packet to the server
as a request for the deletion
*/
/*
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);
*/
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset();
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
objectList->addObjectDelete(mPtr);
objectList->sendObjectDelete();
/*
End of tes3mp change (major)
*/
}
/*
End of tes3mp change (major)
*/
}
}

@ -81,6 +81,7 @@ namespace MWGui
toAdd->eventMouseWheel += MyGUI::newDelegate(this, &SpellBuyingWindow::onMouseWheel);
toAdd->setUserString("ToolTipType", "Spell");
toAdd->setUserString("Spell", spell.mId);
toAdd->setUserString("SpellCost", std::to_string(spell.mData.mCost));
toAdd->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellBuyingWindow::onSpellButtonClick);
mSpellsWidgetMap.insert(std::make_pair (toAdd, spell.mId));
}

@ -249,6 +249,9 @@ namespace MWGui
int school = MWMechanics::getSpellSchool(spell, player);
info.text = "#{sSchool}: " + sSchoolNames[school];
}
std::string cost = focus->getUserString("SpellCost");
if (cost != "" && cost != "0")
info.text += MWGui::ToolTips::getValueString(spell->mData.mCost, "#{sCastCost}");
info.effects = effects;
tooltipSize = createToolTip(info);
}

@ -375,7 +375,8 @@ namespace MWGui
if (!mEffectParams.mKnown)
{
mTextWidget->setCaption ("?");
mRequestedWidth = mTextWidget->getTextSize().width + 24;
mTextWidget->setCoord(sIconOffset / 2, mTextWidget->getCoord().top, mTextWidget->getCoord().width, mTextWidget->getCoord().height); // Compensates for the missing image when effect is not known
mRequestedWidth = mTextWidget->getTextSize().width + sIconOffset;
mImageWidget->setImageTexture ("");
return;
}
@ -466,7 +467,7 @@ namespace MWGui
}
mTextWidget->setCaptionWithReplacing(spellLine);
mRequestedWidth = mTextWidget->getTextSize().width + 24;
mRequestedWidth = mTextWidget->getTextSize().width + sIconOffset;
mImageWidget->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(magicEffect->mIcon));
}

@ -268,7 +268,8 @@ namespace MWGui
virtual void initialiseOverride();
private:
static const int sIconOffset = 24;
void updateWidgets();
SpellEffectParams mEffectParams;

@ -215,6 +215,11 @@ namespace MWInput
void InputManager::handleGuiArrowKey(int action)
{
// This is currently keyboard-specific code
// TODO: see if GUI controls can be refactored into a single function
if (mJoystickLastUsed)
return;
if (SDL_IsTextInputActive())
return;
@ -242,13 +247,10 @@ namespace MWInput
MWBase::Environment::get().getWindowManager()->injectKeyPress(key, 0, false);
}
bool InputManager::gamepadToGuiControl(const SDL_ControllerButtonEvent &arg, bool release=false)
bool InputManager::gamepadToGuiControl(const SDL_ControllerButtonEvent &arg)
{
// Presumption of GUI mode will be removed in the future.
// MyGUI KeyCodes *may* change.
// Currently button releases are ignored.
if (release)
return false;
MyGUI::KeyCode key = MyGUI::KeyCode::None;
switch (arg.button)
@ -399,9 +401,6 @@ namespace MWInput
case A_GameMenu:
toggleMainMenu ();
break;
case A_OptionsMenu:
toggleOptionsMenu();
break;
case A_Screenshot:
screenshot();
break;
@ -419,8 +418,7 @@ namespace MWInput
case A_MoveRight:
case A_MoveForward:
case A_MoveBackward:
// Temporary shut-down of this function until deemed necessary.
//handleGuiArrowKey(action);
handleGuiArrowKey(action);
break;
case A_Journal:
toggleJournal ();
@ -1025,9 +1023,9 @@ namespace MWInput
mJoystickLastUsed = true;
if (MWBase::Environment::get().getWindowManager()->isGuiMode())
{
if (gamepadToGuiControl(arg, false))
if (gamepadToGuiControl(arg))
return;
else if (mGamepadGuiCursorEnabled)
if (mGamepadGuiCursorEnabled)
{
// Temporary mouse binding until keyboard controls are available:
if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click.
@ -1068,9 +1066,7 @@ namespace MWInput
mJoystickLastUsed = true;
if (MWBase::Environment::get().getWindowManager()->isGuiMode())
{
if (gamepadToGuiControl(arg, true))
return;
else if (mGamepadGuiCursorEnabled)
if (mGamepadGuiCursorEnabled)
{
// Temporary mouse binding until keyboard controls are available:
if (arg.button == SDL_CONTROLLER_BUTTON_A) // We'll pretend that A is left click.
@ -1169,37 +1165,19 @@ namespace MWInput
}
if (MWBase::Environment::get().getWindowManager()->isConsoleMode())
return;
bool inGame = MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame;
MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode();
if ((inGame && mode == MWGui::GM_MainMenu) || mode == MWGui::GM_Settings)
MWBase::Environment::get().getWindowManager()->popGuiMode();
if (inGame && mode != MWGui::GM_MainMenu)
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu);
}
void InputManager::toggleOptionsMenu()
{
if (MyGUI::InputManager::getInstance().isModalAny())
{
MWBase::Environment::get().getWindowManager()->exitCurrentModal();
MWBase::Environment::get().getWindowManager()->toggleConsole();
return;
}
if (MWBase::Environment::get().getWindowManager()->isConsoleMode())
return;
MWGui::GuiMode mode = MWBase::Environment::get().getWindowManager()->getMode();
bool inGame = MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_NoGame;
if ((inGame && mode == MWGui::GM_MainMenu) || mode == MWGui::GM_Settings)
MWBase::Environment::get().getWindowManager()->popGuiMode();
if (inGame && mode != MWGui::GM_Settings)
MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Settings);
if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) //No open GUIs, open up the MainMenu
{
MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu);
}
else //Close current GUI
{
MWBase::Environment::get().getWindowManager()->exitCurrentGuiMode();
}
}
void InputManager::quickLoad() {
@ -1611,7 +1589,6 @@ namespace MWInput
defaultButtonBindings[A_TogglePOV] = SDL_CONTROLLER_BUTTON_RIGHTSTICK;
defaultButtonBindings[A_Inventory] = SDL_CONTROLLER_BUTTON_B;
defaultButtonBindings[A_GameMenu] = SDL_CONTROLLER_BUTTON_START;
defaultButtonBindings[A_OptionsMenu] = SDL_CONTROLLER_BUTTON_BACK;
defaultButtonBindings[A_QuickSave] = SDL_CONTROLLER_BUTTON_GUIDE;
defaultButtonBindings[A_MoveForward] = SDL_CONTROLLER_BUTTON_DPAD_UP;
defaultButtonBindings[A_MoveLeft] = SDL_CONTROLLER_BUTTON_DPAD_LEFT;
@ -1692,7 +1669,6 @@ namespace MWInput
descriptions[A_Journal] = "sJournal";
descriptions[A_Rest] = "sRestKey";
descriptions[A_Inventory] = "sInventory";
descriptions[A_OptionsMenu] = "sPreferences";
descriptions[A_TogglePOV] = "sTogglePOVCmd";
descriptions[A_QuickKeysMenu] = "sQuickMenu";
descriptions[A_QuickKey1] = "sQuick1Cmd";
@ -1830,7 +1806,6 @@ namespace MWInput
ret.push_back(A_Inventory);
ret.push_back(A_Journal);
ret.push_back(A_Rest);
ret.push_back(A_OptionsMenu);
ret.push_back(A_Console);
ret.push_back(A_QuickSave);
ret.push_back(A_QuickLoad);
@ -1863,7 +1838,6 @@ namespace MWInput
ret.push_back(A_Inventory);
ret.push_back(A_Journal);
ret.push_back(A_Rest);
ret.push_back(A_OptionsMenu);
ret.push_back(A_QuickSave);
ret.push_back(A_QuickLoad);
ret.push_back(A_Screenshot);

@ -226,7 +226,7 @@ namespace MWInput
void setPlayerControlsEnabled(bool enabled);
void handleGuiArrowKey(int action);
// Return true if GUI consumes input.
bool gamepadToGuiControl(const SDL_ControllerButtonEvent &arg, bool release);
bool gamepadToGuiControl(const SDL_ControllerButtonEvent &arg);
bool gamepadToGuiControl(const SDL_ControllerAxisEvent &arg);
void updateCursorMode();
@ -235,7 +235,6 @@ namespace MWInput
private:
void toggleMainMenu();
void toggleOptionsMenu();
void toggleSpell();
void toggleWeapon();
void toggleInventory();
@ -328,8 +327,6 @@ namespace MWInput
A_MoveForwardBackward,
A_MoveLeftRight,
A_OptionsMenu,
A_Last // Marker for the last item
};
};

@ -1830,6 +1830,43 @@ namespace MWMechanics
updateCombatMusic();
}
void Actors::notifyDied(const MWWorld::Ptr &actor)
{
actor.getClass().getCreatureStats(actor).notifyDied();
/*
Start of tes3mp change (major)
Only increment death count for an actor if we are its authority, to avoid
situations where we increment it locally after having already received an
ID_WORLD_KILL_COUNT packet about it
*/
bool isLocalActor = mwmp::Main::get().getCellController()->isLocalActor(actor);
if (isLocalActor)
++mDeathCount[Misc::StringUtils::lowerCase(actor.getCellRef().getRefId())];
/*
End of tes3mp change (major)
*/
/*
Start of tes3mp addition
Send an ID_WORLD_KILL_COUNT packet every time the kill count changes,
as long as we are the authority over the actor's cell
*/
if (isLocalActor)
{
std::string refId = Misc::StringUtils::lowerCase(actor.getCellRef().getRefId());
int number = mDeathCount[refId];
mwmp::Main::get().getLocalPlayer()->sendKill(refId, number);
}
/*
End of tes3mp addition
*/
}
void Actors::killDeadActors()
{
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
@ -1873,39 +1910,7 @@ namespace MWMechanics
}
else if (killResult == CharacterController::Result_DeathAnimJustFinished)
{
iter->first.getClass().getCreatureStats(iter->first).notifyDied();
/*
Start of tes3mp change (major)
Only increment death count for an actor if we are its authority, to avoid
situations where we increment it locally after having already received an
ID_WORLD_KILL_COUNT packet about it
*/
bool isLocalActor = mwmp::Main::get().getCellController()->isLocalActor(iter->first);
if (isLocalActor)
++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())];
/*
End of tes3mp change (major)
*/
/*
Start of tes3mp addition
Send an ID_WORLD_KILL_COUNT packet every time the kill count changes,
as long as we are the authority over the actor's cell
*/
if (isLocalActor)
{
std::string refId = Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId());
int number = mDeathCount[refId];
mwmp::Main::get().getLocalPlayer()->sendKill(refId, number);
}
/*
End of tes3mp addition
*/
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

@ -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);

@ -44,7 +44,7 @@ bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, CharacterCont
return true; // We have tried backing up for more than one second, we've probably cleared it
}
if (!mDoorPtr.getClass().getDoorState(mDoorPtr))
if (mDoorPtr.getClass().getDoorState(mDoorPtr) == MWWorld::DoorState::Idle)
return true; //Door is no longer opening
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door

@ -232,7 +232,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
return;
// note: AiWander currently does not open doors
if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0)
if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == MWWorld::DoorState::Idle)
{
if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 ))
{

@ -2721,11 +2721,15 @@ void CharacterController::updateMagicEffects()
if (!mPtr.getClass().isActor())
return;
bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
mAnimation->setVampire(vampire);
float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude();
mAnimation->setLightEffect(light);
// If you're dead you don't care about whether you've started/stopped being a vampire or not
if (mPtr.getClass().getCreatureStats(mPtr).isDead())
return;
bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
mAnimation->setVampire(vampire);
}
void CharacterController::setVisibility(float visibility)

@ -468,6 +468,11 @@ namespace MWMechanics
}
}
void MechanicsManager::notifyDied(const MWWorld::Ptr& actor)
{
mActors.notifyDied(actor);
}
float MechanicsManager::getActorsProcessingRange() const
{
return mActors.getProcessingRange();
@ -1091,7 +1096,7 @@ namespace MWMechanics
return false;
}
void MechanicsManager::objectOpened(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item)
void MechanicsManager::unlockAttempted(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item)
{
MWWorld::Ptr victim;
if (isAllowedToUse(ptr, item, victim))

@ -152,8 +152,8 @@ namespace MWMechanics
/// @param container The container the item is in; may be empty for an item in the world
virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
int count, bool alarm = true) override;
/// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override;
/// Utility to check if unlocking this object is illegal and calling commitCrime if so
virtual void unlockAttempted (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override;
/// Attempt sleeping in a bed. If this is illegal, call commitCrime.
/// @return was it illegal, and someone saw you doing it? Also returns fail when enemies are nearby
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) override;
@ -221,6 +221,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;

@ -52,10 +52,10 @@ namespace MWMechanics
// FIXME: cast
const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast<MWWorld::LiveCellRef<ESM::Door> &>(ref), actor.getCell());
int doorState = doorPtr.getClass().getDoorState(doorPtr);
const auto doorState = doorPtr.getClass().getDoorState(doorPtr);
float doorRot = ref.mData.getPosition().rot[2] - doorPtr.getCellRef().getPosition().rot[2];
if (doorState != 0 || doorRot != 0)
if (doorState != MWWorld::DoorState::Idle || doorRot != 0)
continue; // the door is already opened/opening
doorPos.z() = 0;

@ -62,7 +62,6 @@ namespace MWMechanics
resultMessage = "#{sLockImpossible}";
else
{
MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, lock);
if (Misc::Rng::roll0to99() <= x)
{
/*
@ -98,6 +97,7 @@ namespace MWMechanics
resultMessage = "#{sLockFail}";
}
MWBase::Environment::get().getMechanicsManager()->unlockAttempted(mActor, lock);
int uses = lockpick.getClass().getItemHealth(lockpick);
--uses;
lockpick.getCellRef().setCharge(uses);
@ -127,7 +127,6 @@ namespace MWMechanics
resultMessage = "#{sTrapImpossible}";
else
{
MWBase::Environment::get().getMechanicsManager()->objectOpened(mActor, trap);
if (Misc::Rng::roll0to99() <= x)
{
/*
@ -163,6 +162,7 @@ namespace MWMechanics
resultMessage = "#{sTrapFail}";
}
MWBase::Environment::get().getMechanicsManager()->unlockAttempted(mActor, trap);
int uses = probe.getClass().getItemHealth(probe);
--uses;
probe.getCellRef().setCharge(uses);

@ -790,9 +790,6 @@ namespace MWMechanics
if (target.getCellRef().getLockLevel() > 0)
{
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
if (!caster.isEmpty())
MWBase::Environment::get().getMechanicsManager()->objectOpened(getPlayer(), target);
// Use the player instead of the caster for vanilla crime compatibility
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}");
@ -825,6 +822,10 @@ namespace MWMechanics
}
else
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
if (!caster.isEmpty())
MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target);
// Use the player instead of the caster for vanilla crime compatibility
return true;
}
}

@ -13,6 +13,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
@ -26,6 +27,8 @@
#include "../mwrender/animation.hpp"
#include "../mwscript/interpretercontext.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"
@ -483,6 +486,28 @@ void ObjectList::deleteObjects(MWWorld::CellStore* cellStore)
}
}
// Is this a dying actor being deleted before its death animation has finished? If so,
// increase the death count for the actor if applicable and run the actor's script,
// which is the same as what happens in OpenMW's ContainerWindow::onDisposeCorpseButtonClicked()
// if an actor's corpse is disposed of before its death animation is finished
if (ptrFound.getClass().isActor())
{
MWMechanics::CreatureStats& creatureStats = ptrFound.getClass().getCreatureStats(ptrFound);
if (creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())
{
creatureStats.setDeathAnimationFinished(true);
MWBase::Environment::get().getMechanicsManager()->notifyDied(ptrFound);
const std::string script = ptrFound.getClass().getScript(ptrFound);
if (!script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled())
{
MWScript::InterpreterContext interpreterContext(&ptrFound.getRefData().getLocals(), ptrFound);
MWBase::Environment::get().getScriptManager()->run(script, interpreterContext);
}
}
}
MWBase::Environment::get().getWorld()->deleteObject(ptrFound);
}
}
@ -646,8 +671,10 @@ void ObjectList::activateDoors(MWWorld::CellStore* cellStore)
LOG_APPEND(TimedLog::LOG_VERBOSE, "-- Found %s %i-%i", ptrFound.getCellRef().getRefId().c_str(),
ptrFound.getCellRef().getRefNum(), ptrFound.getCellRef().getMpNum());
ptrFound.getClass().setDoorState(ptrFound, baseObject.doorState);
MWBase::Environment::get().getWorld()->saveDoorState(ptrFound, baseObject.doorState);
MWWorld::DoorState doorState = static_cast<MWWorld::DoorState>(baseObject.doorState);
ptrFound.getClass().setDoorState(ptrFound, doorState);
MWBase::Environment::get().getWorld()->saveDoorState(ptrFound, doorState);
}
}
}
@ -1054,7 +1081,7 @@ void ObjectList::addObjectAnimPlay(const MWWorld::Ptr& ptr, std::string group, i
addObject(baseObject);
}
void ObjectList::addDoorState(const MWWorld::Ptr& ptr, int state)
void ObjectList::addDoorState(const MWWorld::Ptr& ptr, MWWorld::DoorState state)
{
cell = *ptr.getCell()->getCell();
@ -1062,7 +1089,7 @@ void ObjectList::addDoorState(const MWWorld::Ptr& ptr, int state)
baseObject.refId = ptr.getCellRef().getRefId();
baseObject.refNum = ptr.getCellRef().getRefNum().mIndex;
baseObject.mpNum = ptr.getCellRef().getMpNum();
baseObject.doorState = state;
baseObject.doorState = static_cast<int>(state);
addObject(baseObject);
}

@ -2,7 +2,7 @@
#define OPENMW_OBJECTLIST_HPP
#include <components/openmw-mp/Base/BaseObject.hpp>
#include "../mwworld/cellstore.hpp"
#include "../mwworld/worldimp.hpp"
#include <RakNetTypes.h>
namespace mwmp
@ -61,7 +61,7 @@ namespace mwmp
void addObjectState(const MWWorld::Ptr& ptr, bool objectState);
void addObjectAnimPlay(const MWWorld::Ptr& ptr, std::string group, int mode);
void addDoorState(const MWWorld::Ptr& ptr, int state);
void addDoorState(const MWWorld::Ptr& ptr, MWWorld::DoorState state);
void addMusicPlay(std::string filename);
void addVideoPlay(std::string filename, bool allowSkipping);
void addScriptLocalShort(const MWWorld::Ptr& ptr, int index, int shortVal);

@ -64,7 +64,7 @@ ActorAnimation::~ActorAnimation()
mScabbard.reset();
}
PartHolderPtr ActorAnimation::getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor)
PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor)
{
osg::Group* parent = getBoneByName(bonename);
if (!parent)
@ -160,7 +160,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
if (showHolsteredWeapons)
{
osg::Vec4f glowColor = weapon->getClass().getEnchantmentColor(*weapon);
mScabbard = getWeaponPart(mesh, boneName, isEnchanted, &glowColor);
mScabbard = attachMesh(mesh, boneName, isEnchanted, &glowColor);
if (mScabbard)
resetControllers(mScabbard->getNode());
}
@ -168,7 +168,7 @@ void ActorAnimation::updateHolsteredWeapon(bool showHolsteredWeapons)
return;
}
mScabbard = getWeaponPart(scabbardName, boneName);
mScabbard = attachMesh(scabbardName, boneName);
osg::Group* weaponNode = getBoneByName("Bip01 Weapon");
if (!weaponNode)

@ -44,11 +44,11 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener
virtual void updateHolsteredWeapon(bool showHolsteredWeapons);
virtual void updateQuiver();
virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon);
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor);
virtual PartHolderPtr getWeaponPart(const std::string& model, const std::string& bonename)
virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor);
virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename)
{
osg::Vec4f stubColor = osg::Vec4f(0,0,0,0);
return getWeaponPart(model, bonename, false, &stubColor);
return attachMesh(model, bonename, false, &stubColor);
};
PartHolderPtr mScabbard;

@ -247,7 +247,7 @@ namespace MWScript
// This is done when using Lock in scripts, but not when using Lock spells.
if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport())
{
MWBase::Environment::get().getWorld()->activateDoor(ptr, 0);
MWBase::Environment::get().getWorld()->activateDoor(ptr, MWWorld::DoorState::Idle);
}
}
};

@ -473,12 +473,12 @@ namespace MWWorld
return false;
}
int Class::getDoorState (const MWWorld::ConstPtr &ptr) const
MWWorld::DoorState Class::getDoorState (const MWWorld::ConstPtr &ptr) const
{
throw std::runtime_error("this is not a door");
}
void Class::setDoorState (const MWWorld::Ptr &ptr, int state) const
void Class::setDoorState (const MWWorld::Ptr &ptr, MWWorld::DoorState state) const
{
throw std::runtime_error("this is not a door");
}

@ -9,6 +9,7 @@
#include <osg/Vec4f>
#include "ptr.hpp"
#include "doorstate.hpp"
namespace ESM
{
@ -309,7 +310,7 @@ namespace MWWorld
virtual bool allowTelekinesis(const MWWorld::ConstPtr& ptr) const { return true; }
///< Return whether this class of object can be activated with telekinesis
/// Get a blood texture suitable for \a ptr (see Blood Texture 0-2 in Morrowind.ini)
virtual int getBloodTexture (const MWWorld::ConstPtr& ptr) const;
@ -361,10 +362,9 @@ namespace MWWorld
virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const;
/// 0 = nothing, 1 = opening, 2 = closing
virtual int getDoorState (const MWWorld::ConstPtr &ptr) const;
virtual DoorState getDoorState (const MWWorld::ConstPtr &ptr) const;
/// This does not actually cause the door to move. Use World::activateDoor instead.
virtual void setDoorState (const MWWorld::Ptr &ptr, int state) const;
virtual void setDoorState (const MWWorld::Ptr &ptr, DoorState state) const;
virtual void respawn (const MWWorld::Ptr& ptr) const {}

@ -0,0 +1,14 @@
#ifndef GAME_MWWORLD_DOORSTATE_H
#define GAME_MWWORLD_DOORSTATE_H
namespace MWWorld
{
enum class DoorState
{
Idle = 0,
Opening = 1,
Closing = 2,
};
}
#endif

@ -1847,7 +1847,7 @@ namespace MWWorld
return result.mHit;
}
bool World::rotateDoor(const Ptr door, int state, float duration)
bool World::rotateDoor(const Ptr door, MWWorld::DoorState state, float duration)
{
const ESM::Position& objPos = door.getRefData().getPosition();
float oldRot = objPos.rot[2];
@ -1856,17 +1856,20 @@ namespace MWWorld
float maxRot = minRot + osg::DegreesToRadians(90.f);
float diff = duration * osg::DegreesToRadians(90.f);
float targetRot = std::min(std::max(minRot, oldRot + diff * (state == 1 ? 1 : -1)), maxRot);
float targetRot = std::min(std::max(minRot, oldRot + diff * (state == MWWorld::DoorState::Opening ? 1 : -1)), maxRot);
rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot);
bool reached = (targetRot == maxRot && state) || targetRot == minRot;
bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot;
/// \todo should use convexSweepTest here
bool collisionWithActor = false;
std::vector<MWWorld::Ptr> collisions = mPhysics->getCollisions(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor);
for (MWWorld::Ptr& ptr : collisions)
{
if (ptr.getClass().isActor())
{
collisionWithActor = true;
// Collided with actor, ask actor to try to avoid door
if(ptr != getPlayerPtr() )
{
@ -1881,6 +1884,25 @@ namespace MWWorld
}
}
// Cancel door closing sound if collision with actor is detected
if (collisionWithActor)
{
const ESM::Door* ref = door.get<ESM::Door>()->mBase;
if (state == MWWorld::DoorState::Opening)
{
const std::string& openSound = ref->mOpenSound;
if (!openSound.empty() && MWBase::Environment::get().getSoundManager()->getSoundPlaying(door, openSound))
MWBase::Environment::get().getSoundManager()->stopSound3D(door, openSound);
}
else if (state == MWWorld::DoorState::Closing)
{
const std::string& closeSound = ref->mCloseSound;
if (!closeSound.empty() && MWBase::Environment::get().getSoundManager()->getSoundPlaying(door, closeSound))
MWBase::Environment::get().getSoundManager()->stopSound3D(door, closeSound);
}
}
// the rotation order we want to use
mWorldScene->updateObjectRotation(door, false);
return reached;
@ -1888,7 +1910,7 @@ namespace MWWorld
void World::processDoors(float duration)
{
std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin();
auto it = mDoorStates.begin();
while (it != mDoorStates.end())
{
if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode())
@ -1905,7 +1927,7 @@ namespace MWWorld
if (reached)
{
// Mark as non-moving
it->first.getClass().setDoorState(it->first, 0);
it->first.getClass().setDoorState(it->first, MWWorld::DoorState::Idle);
mDoorStates.erase(it++);
}
else
@ -2802,21 +2824,21 @@ namespace MWWorld
void World::activateDoor(const MWWorld::Ptr& door)
{
int state = door.getClass().getDoorState(door);
auto state = door.getClass().getDoorState(door);
switch (state)
{
case 0:
case MWWorld::DoorState::Idle:
if (door.getRefData().getPosition().rot[2] == door.getCellRef().getPosition().rot[2])
state = 1; // if closed, then open
state = MWWorld::DoorState::Opening; // if closed, then open
else
state = 2; // if open, then close
state = MWWorld::DoorState::Closing; // if open, then close
break;
case 2:
state = 1; // if closing, then open
case MWWorld::DoorState::Closing:
state = MWWorld::DoorState::Opening; // if closing, then open
break;
case 1:
case MWWorld::DoorState::Opening:
default:
state = 2; // if opening, then close
state = MWWorld::DoorState::Closing; // if opening, then close
break;
}
@ -2838,7 +2860,7 @@ namespace MWWorld
mDoorStates[door] = state;
}
void World::activateDoor(const Ptr &door, int state)
void World::activateDoor(const Ptr &door, MWWorld::DoorState state)
{
/*
Start of tes3mp addition
@ -2856,7 +2878,7 @@ namespace MWWorld
door.getClass().setDoorState(door, state);
mDoorStates[door] = state;
if (state == 0)
if (state == MWWorld::DoorState::Idle)
{
mDoorStates.erase(door);
rotateDoor(door, state, 1);
@ -2868,10 +2890,10 @@ namespace MWWorld
Allow the saving of door states without going through World::activateDoor()
*/
void World::saveDoorState(const Ptr &door, int state)
void World::saveDoorState(const Ptr &door, MWWorld::DoorState state)
{
mDoorStates[door] = state;
if (state == 0)
if (state == MWWorld::DoorState::Idle)
mDoorStates.erase(door);
}
/*

@ -118,7 +118,7 @@ namespace MWWorld
int mActivationDistanceOverride;
std::map<MWWorld::Ptr, int> mDoorStates;
std::map<MWWorld::Ptr, MWWorld::DoorState> mDoorStates;
///< only holds doors that are currently moving. 1 = opening, 2 = closing
std::string mStartCell;
@ -155,7 +155,7 @@ namespace MWWorld
void addContainerScripts(const Ptr& reference, CellStore* cell) override;
void removeContainerScripts(const Ptr& reference) override;
private:
bool rotateDoor(const Ptr door, int state, float duration);
bool rotateDoor(const Ptr door, DoorState state, float duration);
void processDoors(float duration);
///< Run physics simulation and modify \a world accordingly.
@ -680,14 +680,14 @@ namespace MWWorld
/// update movement state of a non-teleport door as specified
/// @param state see MWClass::setDoorState
/// @note throws an exception when invoked on a teleport door
void activateDoor(const MWWorld::Ptr& door, int state) override;
void activateDoor(const MWWorld::Ptr& door, MWWorld::DoorState state) override;
/*
Start of tes3mp addition
Useful self-contained method for saving door states
*/
void saveDoorState(const MWWorld::Ptr& door, int state) override;
void saveDoorState(const MWWorld::Ptr& door, MWWorld::DoorState state) override;
/*
End of tes3mp addition
*/

@ -1072,28 +1072,34 @@ namespace NifOsg
if (nifNode->recType == Nif::RC_NiTriShape)
{
const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(nifNode);
const Nif::NiTriShapeData* data = triShape->data.getPtr();
vertexColorsPresent = !data->colors.empty();
triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triShape->name);
if (!data->triangles.empty())
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(),
(unsigned short*)data->triangles.data()));
if (!triShape->data.empty())
{
const Nif::NiTriShapeData* data = triShape->data.getPtr();
vertexColorsPresent = !data->colors.empty();
triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triShape->name);
if (!data->triangles.empty())
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(),
(unsigned short*)data->triangles.data()));
}
}
else
{
const Nif::NiTriStrips* triStrips = static_cast<const Nif::NiTriStrips*>(nifNode);
const Nif::NiTriStripsData* data = triStrips->data.getPtr();
vertexColorsPresent = !data->colors.empty();
triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triStrips->name);
if (!data->strips.empty())
if (!triStrips->data.empty())
{
for (const std::vector<unsigned short>& strip : data->strips)
const Nif::NiTriStripsData* data = triStrips->data.getPtr();
vertexColorsPresent = !data->colors.empty();
triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triStrips->name);
if (!data->strips.empty())
{
// Can't make a triangle from less than three vertices.
if (strip.size() < 3)
continue;
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(),
(unsigned short*)strip.data()));
for (const std::vector<unsigned short>& strip : data->strips)
{
// Can't make a triangle from less than three vertices.
if (strip.size() < 3)
continue;
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLE_STRIP, strip.size(),
(unsigned short*)strip.data()));
}
}
}
}

@ -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.

@ -19,7 +19,7 @@
<item>
<widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Can loot during death animation</string>

Loading…
Cancel
Save