diff --git a/AUTHORS.md b/AUTHORS.md index c64b83dab..a18fb42a1 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -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) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ad52cd7..7fd1e4d26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index ee80d919b..db1213ed7 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -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; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4592414a6..fff2926f4 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -9,6 +9,7 @@ #include #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 */ diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index d9e1bb599..9ecd8bbec 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -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 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 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(state); - customData.mDoorState = state2.mDoorState; + customData.mDoorState = static_cast(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(state); - state2.mDoorState = customData.mDoorState; + state2.mDoorState = static_cast(customData.mDoorState); } } diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 57e475382..b3e4e383c 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -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) diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index 65b079d85..9ebaf3919 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -40,14 +40,13 @@ namespace MWGui bool ConfirmationDialog::exit() { + setVisible(false); eventCancelClicked(); return true; } void ConfirmationDialog::onCancelButtonClicked(MyGUI::Widget* _sender) { - setVisible(false); - exit(); } diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 8c7084e8d..818946153 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -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) - */ } } diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 5bf89bc20..53abc5331 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -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)); } diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 7d8a07c21..3891cf8a2 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -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); } diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index 6e3e081a9..0568efeb4 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -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)); } diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index 6eab431d6..4b42b888a 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -268,7 +268,8 @@ namespace MWGui virtual void initialiseOverride(); private: - + static const int sIconOffset = 24; + void updateWidgets(); SpellEffectParams mEffectParams; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 44a508e89..7bf25ef6d 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -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); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index eaa081f99..cd7047824 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -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 }; }; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index eb455bac7..bed1985c0 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -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 diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 42147b63b..9db721d4c 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -71,6 +71,8 @@ namespace MWMechanics PtrActorMap::const_iterator begin() { return mActors.begin(); } PtrActorMap::const_iterator end() { return mActors.end(); } + void notifyDied(const MWWorld::Ptr &actor); + /// Check if the target actor was detected by an observer /// If the observer is a non-NPC, check all actors in AI processing distance as observers bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer); diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index 8fc35de49..793bd89ea 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -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 diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 598292fc3..646b37669 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -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 )) { diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d36ebcd18..7039a3b5a 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -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) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 24b06172b..c3e41af4e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -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)) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index e7d2e1834..311284273 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -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; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index b8676a883..c3da92e31 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -52,10 +52,10 @@ namespace MWMechanics // FIXME: cast const MWWorld::Ptr doorPtr = MWWorld::Ptr(&const_cast &>(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; diff --git a/apps/openmw/mwmechanics/security.cpp b/apps/openmw/mwmechanics/security.cpp index ad1e2b7cd..1a32366dc 100644 --- a/apps/openmw/mwmechanics/security.cpp +++ b/apps/openmw/mwmechanics/security.cpp @@ -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); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index a3a3b030a..4515c44bd 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -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; } } diff --git a/apps/openmw/mwmp/ObjectList.cpp b/apps/openmw/mwmp/ObjectList.cpp index 3601f9d47..c312e1408 100644 --- a/apps/openmw/mwmp/ObjectList.cpp +++ b/apps/openmw/mwmp/ObjectList.cpp @@ -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(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(state); addObject(baseObject); } diff --git a/apps/openmw/mwmp/ObjectList.hpp b/apps/openmw/mwmp/ObjectList.hpp index 449abaa39..f9ebcd3a2 100644 --- a/apps/openmw/mwmp/ObjectList.hpp +++ b/apps/openmw/mwmp/ObjectList.hpp @@ -2,7 +2,7 @@ #define OPENMW_OBJECTLIST_HPP #include -#include "../mwworld/cellstore.hpp" +#include "../mwworld/worldimp.hpp" #include 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); diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index abc2e4eb3..d05215b72 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -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) diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index f1f6f6ca8..038dcde6d 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -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; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index fa3dff9f1..2a3a66812 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -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); } } }; diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 1cb97c231..e01bbb18e 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -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"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index fd29acd79..25a1047d6 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -9,6 +9,7 @@ #include #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 {} diff --git a/apps/openmw/mwworld/doorstate.hpp b/apps/openmw/mwworld/doorstate.hpp new file mode 100644 index 000000000..aeafa7d8c --- /dev/null +++ b/apps/openmw/mwworld/doorstate.hpp @@ -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 diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ef01348fa..a6b0e3ce2 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -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 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()->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::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); } /* diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 7a2ed223e..33588d2e3 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -118,7 +118,7 @@ namespace MWWorld int mActivationDistanceOverride; - std::map mDoorStates; + std::map 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 */ diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 31dcd2a55..568286a4d 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1072,28 +1072,34 @@ namespace NifOsg if (nifNode->recType == Nif::RC_NiTriShape) { const Nif::NiTriShape* triShape = static_cast(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(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& 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& 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())); + } } } } diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 4e397e9c9..7bfa60c6c 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -72,13 +72,10 @@ can loot during death animation :Default: True If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, -if they are not in combat. However disposing corpses during death animation is not recommended - -death counter may not be incremented, and this behaviour can break quests. -This is how Morrowind behaves. +if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly. If this setting is false, player has to wait until end of death animation in all cases. -This case is more safe, but makes using of summoned creatures exploit -(looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. +Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation. This setting can be toggled in Advanced tab of the launcher. diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index f7575cd2b..152ab6411 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -19,7 +19,7 @@ - <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. However disposing corpses during death animation is not recommended - death counter may not be incremented, and this behaviour can break quests. This is how original Morrowind behaves.</p><p>If this setting is false, player has to wait until end of death animation in all cases. This case is more safe, but makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder.</p></body></html> + <html><head/><body><p>If this setting is true, the player is allowed to loot actors (e.g. summoned creatures) during death animation, if they are not in combat. In this case we have to increment death counter and run disposed actor's script instantly.</p><p>If this setting is false, player has to wait until end of death animation in all cases. Makes using of summoned creatures exploit (looting summoned Dremoras and Golden Saints for expensive weapons) a lot harder. Conflicts with mannequin mods, which use SkipAnim to prevent end of death animation.</p></body></html> Can loot during death animation