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) Jacob Essex (Yacoby)
Jake Westrip (16bitint) Jake Westrip (16bitint)
James Carty (MrTopCat) James Carty (MrTopCat)
James Stephens (james-h-stephens)
Jan-Peter Nilsson (peppe) Jan-Peter Nilsson (peppe)
Jan Borsodi (am0s) Jan Borsodi (am0s)
Jason Hooks (jhooks) Jason Hooks (jhooks)

@ -18,6 +18,7 @@
Bug #3894: Hostile spell effects not detected/present on first frame of OnPCHitMe 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 #4202: Open .omwaddon files without needing toopen openmw-cs first
Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect 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 #4276: Resizing character window differs from vanilla
Bug #4329: Removed birthsign abilities are restored after reloading the save Bug #4329: Removed birthsign abilities are restored after reloading the save
Bug #4341: Error message about missing GDB is too vague Bug #4341: Error message about missing GDB is too vague
@ -27,6 +28,7 @@
Bug #4540: Rain delay when exiting water Bug #4540: Rain delay when exiting water
Bug #4600: Crash when no sound output is available or --no-sound is used. 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 #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 #4701: PrisonMarker record is not hardcoded like other markers
Bug #4703: Editor: it's possible to preview levelled list records Bug #4703: Editor: it's possible to preview levelled list records
Bug #4705: Editor: unable to open exterior cell views from Instances table 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 #5106: Still can jump even when encumbered
Bug #5110: ModRegion with a redundant numerical argument breaks script execution Bug #5110: ModRegion with a redundant numerical argument breaks script execution
Bug #5112: Insufficient magicka for current spell not reflected on HUD icon 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 #5123: Script won't run on respawn
Bug #5124: Arrow remains attached to actor if pulling animation was cancelled Bug #5124: Arrow remains attached to actor if pulling animation was cancelled
Bug #5126: Swimming creatures without RunForward animations are motionless during combat Bug #5126: Swimming creatures without RunForward animations are motionless during combat
Bug #5134: Doors rotation by "Lock" console command is inconsistent 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 #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 #1774: Handle AvoidNode
Feature #2229: Improve pathfinding AI Feature #2229: Improve pathfinding AI
Feature #3025: Analogue gamepad movement controls Feature #3025: Analogue gamepad movement controls
@ -177,6 +180,8 @@
Feature #5122: Use magic glow for enchanted arrows Feature #5122: Use magic glow for enchanted arrows
Feature #5131: Custom skeleton bones Feature #5131: Custom skeleton bones
Feature #5132: Unique animations for different weapon types 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 #4686: Upgrade media decoder to a more current FFmpeg API
Task #4695: Optimize Distant Terrain memory consumption Task #4695: Optimize Distant Terrain memory consumption
Task #4789: Optimize cell transitions 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 /// @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, virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
int count, bool alarm = true) = 0; int count, bool alarm = true) = 0;
/// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so /// Utility to check if unlocking this object is illegal and calling commitCrime if so
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0; virtual void unlockAttempted (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) = 0;
/// Attempt sleeping in a bed. If this is illegal, call commitCrime. /// Attempt sleeping in a bed. If this is illegal, call commitCrime.
/// @return was it illegal, and someone saw you doing it? /// @return was it illegal, and someone saw you doing it?
virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0; virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) = 0;
@ -247,6 +247,8 @@ namespace MWBase
virtual float getActorsProcessingRange() const = 0; virtual float getActorsProcessingRange() const = 0;
virtual void notifyDied(const MWWorld::Ptr& actor) = 0;
virtual bool onOpen(const MWWorld::Ptr& ptr) = 0; virtual bool onOpen(const MWWorld::Ptr& ptr) = 0;
virtual void onClose(const MWWorld::Ptr& ptr) = 0; virtual void onClose(const MWWorld::Ptr& ptr) = 0;

@ -9,6 +9,7 @@
#include <components/esm/cellid.hpp> #include <components/esm/cellid.hpp>
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
#include "../mwworld/doorstate.hpp"
#include "../mwrender/rendermode.hpp" #include "../mwrender/rendermode.hpp"
@ -571,14 +572,14 @@ namespace MWBase
/// update movement state of a non-teleport door as specified /// update movement state of a non-teleport door as specified
/// @param state see MWClass::setDoorState /// @param state see MWClass::setDoorState
/// @note throws an exception when invoked on a teleport door /// @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 Start of tes3mp addition
Useful self-contained method for saving door states 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 End of tes3mp addition
*/ */

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

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

@ -22,12 +22,15 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwscript/interpretercontext.hpp"
#include "countdialog.hpp" #include "countdialog.hpp"
#include "inventorywindow.hpp" #include "inventorywindow.hpp"
@ -384,25 +387,48 @@ namespace MWGui
if (mPtr.getClass().isPersistent(mPtr)) if (mPtr.getClass().isPersistent(mPtr))
MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sDisposeCorpseFail}");
else
{
/* /*
Start of tes3mp change (major) Start of tes3mp change (major)
Instead of deleting the corpse on this client, simply send an ID_OBJECT_DELETE Instead of deleting the corpse on this client, increasing the death count and
packet to the server as a request for the deletion running the dead actor's sccript, simply send an ID_OBJECT_DELETE packet to the server
as a request for the deletion
*/ */
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);
*/
mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList(); mwmp::ObjectList *objectList = mwmp::Main::get().getNetworking()->getObjectList();
objectList->reset(); objectList->reset();
objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY; objectList->packetOrigin = mwmp::CLIENT_GAMEPLAY;
objectList->addObjectDelete(mPtr); objectList->addObjectDelete(mPtr);
objectList->sendObjectDelete(); objectList->sendObjectDelete();
}
/* /*
End of tes3mp change (major) End of tes3mp change (major)
*/ */
} }
} }
}
void ContainerWindow::onReferenceUnavailable() void ContainerWindow::onReferenceUnavailable()
{ {

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

@ -249,6 +249,9 @@ namespace MWGui
int school = MWMechanics::getSpellSchool(spell, player); int school = MWMechanics::getSpellSchool(spell, player);
info.text = "#{sSchool}: " + sSchoolNames[school]; 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; info.effects = effects;
tooltipSize = createToolTip(info); tooltipSize = createToolTip(info);
} }

@ -375,7 +375,8 @@ namespace MWGui
if (!mEffectParams.mKnown) if (!mEffectParams.mKnown)
{ {
mTextWidget->setCaption ("?"); 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 (""); mImageWidget->setImageTexture ("");
return; return;
} }
@ -466,7 +467,7 @@ namespace MWGui
} }
mTextWidget->setCaptionWithReplacing(spellLine); mTextWidget->setCaptionWithReplacing(spellLine);
mRequestedWidth = mTextWidget->getTextSize().width + 24; mRequestedWidth = mTextWidget->getTextSize().width + sIconOffset;
mImageWidget->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(magicEffect->mIcon)); mImageWidget->setImageTexture(MWBase::Environment::get().getWindowManager()->correctIconPath(magicEffect->mIcon));
} }

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

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

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

@ -1830,6 +1830,43 @@ namespace MWMechanics
updateCombatMusic(); 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() void Actors::killDeadActors()
{ {
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
@ -1873,39 +1910,7 @@ namespace MWMechanics
} }
else if (killResult == CharacterController::Result_DeathAnimJustFinished) else if (killResult == CharacterController::Result_DeathAnimJustFinished)
{ {
iter->first.getClass().getCreatureStats(iter->first).notifyDied(); notifyDied(iter->first);
/*
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
*/
// Reset magic effects and recalculate derived effects // Reset magic effects and recalculate derived effects
// One case where we need this is to make sure bound items are removed upon death // 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 begin() { return mActors.begin(); }
PtrActorMap::const_iterator end() { return mActors.end(); } PtrActorMap::const_iterator end() { return mActors.end(); }
void notifyDied(const MWWorld::Ptr &actor);
/// Check if the target actor was detected by an observer /// 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 /// 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); 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 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 return true; //Door is no longer opening
ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door ESM::Position tPos = mDoorPtr.getRefData().getPosition(); //Position of the door

@ -232,7 +232,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
return; return;
// note: AiWander currently does not open doors // 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 )) if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 ))
{ {

@ -2721,11 +2721,15 @@ void CharacterController::updateMagicEffects()
if (!mPtr.getClass().isActor()) if (!mPtr.getClass().isActor())
return; 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(); float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude();
mAnimation->setLightEffect(light); 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) 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 float MechanicsManager::getActorsProcessingRange() const
{ {
return mActors.getProcessingRange(); return mActors.getProcessingRange();
@ -1091,7 +1096,7 @@ namespace MWMechanics
return false; 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; MWWorld::Ptr victim;
if (isAllowedToUse(ptr, item, 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 /// @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, virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container,
int count, bool alarm = true) override; int count, bool alarm = true) override;
/// Utility to check if opening (i.e. unlocking) this object is illegal and calling commitCrime if so /// Utility to check if unlocking this object is illegal and calling commitCrime if so
virtual void objectOpened (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override; virtual void unlockAttempted (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item) override;
/// Attempt sleeping in a bed. If this is illegal, call commitCrime. /// 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 /// @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; virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed) override;
@ -221,6 +221,8 @@ namespace MWMechanics
virtual float getActorsProcessingRange() const override; virtual float getActorsProcessingRange() const override;
virtual void notifyDied(const MWWorld::Ptr& actor) override;
/// Check if the target actor was detected by an observer /// 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 /// 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; virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) override;

@ -52,10 +52,10 @@ namespace MWMechanics
// FIXME: cast // FIXME: cast
const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast<MWWorld::LiveCellRef<ESM::Door> &>(ref), actor.getCell()); 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]; 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 continue; // the door is already opened/opening
doorPos.z() = 0; doorPos.z() = 0;

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

@ -790,9 +790,6 @@ namespace MWMechanics
if (target.getCellRef().getLockLevel() > 0) if (target.getCellRef().getLockLevel() > 0)
{ {
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); 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()) if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}");
@ -825,6 +822,10 @@ namespace MWMechanics
} }
else else
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); 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; return true;
} }
} }

@ -13,6 +13,7 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/scriptmanager.hpp"
#include "../mwbase/soundmanager.hpp" #include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
@ -26,6 +27,8 @@
#include "../mwrender/animation.hpp" #include "../mwrender/animation.hpp"
#include "../mwscript/interpretercontext.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp" #include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.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); 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(), LOG_APPEND(TimedLog::LOG_VERBOSE, "-- Found %s %i-%i", ptrFound.getCellRef().getRefId().c_str(),
ptrFound.getCellRef().getRefNum(), ptrFound.getCellRef().getMpNum()); ptrFound.getCellRef().getRefNum(), ptrFound.getCellRef().getMpNum());
ptrFound.getClass().setDoorState(ptrFound, baseObject.doorState); MWWorld::DoorState doorState = static_cast<MWWorld::DoorState>(baseObject.doorState);
MWBase::Environment::get().getWorld()->saveDoorState(ptrFound, 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); 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(); cell = *ptr.getCell()->getCell();
@ -1062,7 +1089,7 @@ void ObjectList::addDoorState(const MWWorld::Ptr& ptr, int state)
baseObject.refId = ptr.getCellRef().getRefId(); baseObject.refId = ptr.getCellRef().getRefId();
baseObject.refNum = ptr.getCellRef().getRefNum().mIndex; baseObject.refNum = ptr.getCellRef().getRefNum().mIndex;
baseObject.mpNum = ptr.getCellRef().getMpNum(); baseObject.mpNum = ptr.getCellRef().getMpNum();
baseObject.doorState = state; baseObject.doorState = static_cast<int>(state);
addObject(baseObject); addObject(baseObject);
} }

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

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

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

@ -247,7 +247,7 @@ namespace MWScript
// This is done when using Lock in scripts, but not when using Lock spells. // This is done when using Lock in scripts, but not when using Lock spells.
if (ptr.getTypeName() == typeid(ESM::Door).name() && !ptr.getCellRef().getTeleport()) 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; 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"); 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"); throw std::runtime_error("this is not a door");
} }

@ -9,6 +9,7 @@
#include <osg/Vec4f> #include <osg/Vec4f>
#include "ptr.hpp" #include "ptr.hpp"
#include "doorstate.hpp"
namespace ESM namespace ESM
{ {
@ -361,10 +362,9 @@ namespace MWWorld
virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const; virtual bool isClass(const MWWorld::ConstPtr& ptr, const std::string &className) const;
/// 0 = nothing, 1 = opening, 2 = closing virtual DoorState getDoorState (const MWWorld::ConstPtr &ptr) const;
virtual int getDoorState (const MWWorld::ConstPtr &ptr) const;
/// This does not actually cause the door to move. Use World::activateDoor instead. /// 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 {} 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; 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(); const ESM::Position& objPos = door.getRefData().getPosition();
float oldRot = objPos.rot[2]; float oldRot = objPos.rot[2];
@ -1856,17 +1856,20 @@ namespace MWWorld
float maxRot = minRot + osg::DegreesToRadians(90.f); float maxRot = minRot + osg::DegreesToRadians(90.f);
float diff = duration * 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); 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 /// \todo should use convexSweepTest here
bool collisionWithActor = false;
std::vector<MWWorld::Ptr> collisions = mPhysics->getCollisions(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor); std::vector<MWWorld::Ptr> collisions = mPhysics->getCollisions(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor);
for (MWWorld::Ptr& ptr : collisions) for (MWWorld::Ptr& ptr : collisions)
{ {
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())
{ {
collisionWithActor = true;
// Collided with actor, ask actor to try to avoid door // Collided with actor, ask actor to try to avoid door
if(ptr != getPlayerPtr() ) 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 // the rotation order we want to use
mWorldScene->updateObjectRotation(door, false); mWorldScene->updateObjectRotation(door, false);
return reached; return reached;
@ -1888,7 +1910,7 @@ namespace MWWorld
void World::processDoors(float duration) void World::processDoors(float duration)
{ {
std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin(); auto it = mDoorStates.begin();
while (it != mDoorStates.end()) while (it != mDoorStates.end())
{ {
if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode()) if (!mWorldScene->isCellActive(*it->first.getCell()) || !it->first.getRefData().getBaseNode())
@ -1905,7 +1927,7 @@ namespace MWWorld
if (reached) if (reached)
{ {
// Mark as non-moving // Mark as non-moving
it->first.getClass().setDoorState(it->first, 0); it->first.getClass().setDoorState(it->first, MWWorld::DoorState::Idle);
mDoorStates.erase(it++); mDoorStates.erase(it++);
} }
else else
@ -2802,21 +2824,21 @@ namespace MWWorld
void World::activateDoor(const MWWorld::Ptr& door) void World::activateDoor(const MWWorld::Ptr& door)
{ {
int state = door.getClass().getDoorState(door); auto state = door.getClass().getDoorState(door);
switch (state) switch (state)
{ {
case 0: case MWWorld::DoorState::Idle:
if (door.getRefData().getPosition().rot[2] == door.getCellRef().getPosition().rot[2]) 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 else
state = 2; // if open, then close state = MWWorld::DoorState::Closing; // if open, then close
break; break;
case 2: case MWWorld::DoorState::Closing:
state = 1; // if closing, then open state = MWWorld::DoorState::Opening; // if closing, then open
break; break;
case 1: case MWWorld::DoorState::Opening:
default: default:
state = 2; // if opening, then close state = MWWorld::DoorState::Closing; // if opening, then close
break; break;
} }
@ -2838,7 +2860,7 @@ namespace MWWorld
mDoorStates[door] = state; mDoorStates[door] = state;
} }
void World::activateDoor(const Ptr &door, int state) void World::activateDoor(const Ptr &door, MWWorld::DoorState state)
{ {
/* /*
Start of tes3mp addition Start of tes3mp addition
@ -2856,7 +2878,7 @@ namespace MWWorld
door.getClass().setDoorState(door, state); door.getClass().setDoorState(door, state);
mDoorStates[door] = state; mDoorStates[door] = state;
if (state == 0) if (state == MWWorld::DoorState::Idle)
{ {
mDoorStates.erase(door); mDoorStates.erase(door);
rotateDoor(door, state, 1); rotateDoor(door, state, 1);
@ -2868,10 +2890,10 @@ namespace MWWorld
Allow the saving of door states without going through World::activateDoor() 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; mDoorStates[door] = state;
if (state == 0) if (state == MWWorld::DoorState::Idle)
mDoorStates.erase(door); mDoorStates.erase(door);
} }
/* /*

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

@ -1072,6 +1072,8 @@ namespace NifOsg
if (nifNode->recType == Nif::RC_NiTriShape) if (nifNode->recType == Nif::RC_NiTriShape)
{ {
const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(nifNode); const Nif::NiTriShape* triShape = static_cast<const Nif::NiTriShape*>(nifNode);
if (!triShape->data.empty())
{
const Nif::NiTriShapeData* data = triShape->data.getPtr(); const Nif::NiTriShapeData* data = triShape->data.getPtr();
vertexColorsPresent = !data->colors.empty(); vertexColorsPresent = !data->colors.empty();
triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triShape->name); triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triShape->name);
@ -1079,9 +1081,12 @@ namespace NifOsg
geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(), geometry->addPrimitiveSet(new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, data->triangles.size(),
(unsigned short*)data->triangles.data())); (unsigned short*)data->triangles.data()));
} }
}
else else
{ {
const Nif::NiTriStrips* triStrips = static_cast<const Nif::NiTriStrips*>(nifNode); const Nif::NiTriStrips* triStrips = static_cast<const Nif::NiTriStrips*>(nifNode);
if (!triStrips->data.empty())
{
const Nif::NiTriStripsData* data = triStrips->data.getPtr(); const Nif::NiTriStripsData* data = triStrips->data.getPtr();
vertexColorsPresent = !data->colors.empty(); vertexColorsPresent = !data->colors.empty();
triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triStrips->name); triCommonToGeometry(geometry, data->vertices, data->normals, data->uvlist, data->colors, boundTextures, triStrips->name);
@ -1097,6 +1102,7 @@ namespace NifOsg
} }
} }
} }
}
// osg::Material properties are handled here for two reasons: // osg::Material properties are handled here for two reasons:
// - if there are no vertex colors, we need to disable colorMode. // - if there are no vertex colors, we need to disable colorMode.

@ -72,13 +72,10 @@ can loot during death animation
:Default: True :Default: True
If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, 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 - if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.
death counter may not be incremented, and this behaviour can break quests.
This is how 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 Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder.
(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. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.
This setting can be toggled in Advanced tab of the launcher. This setting can be toggled in Advanced tab of the launcher.

@ -19,7 +19,7 @@
<item> <item>
<widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox"> <widget class="QCheckBox" name="canLootDuringDeathAnimationCheckBox">
<property name="toolTip"> <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>
<property name="text"> <property name="text">
<string>Can loot during death animation</string> <string>Can loot during death animation</string>

Loading…
Cancel
Save