diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index 67ff50dab..78f18b3a1 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -106,7 +106,7 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) /// \todo remove this label once we are feature complete and convinced that this thing is /// working properly. - QLabel *warning = new QLabel ("WARNING: OpenMW-CS is in alpha stage.

The editor is not feature complete and not sufficiently tested.
In theory your data should be safe. But we strongly advice to make backups regularly if you are working with live data.
"); + QLabel *warning = new QLabel ("WARNING: OpenMW-CS is in alpha stage.

The editor is not feature complete and not sufficiently tested.
In theory your data should be safe. But we strongly advise to make backups regularly if you are working with live data.
"); QFont font; font.setPointSize (12); diff --git a/apps/openmw-mp/Object.cpp b/apps/openmw-mp/Object.cpp index ae9bab454..631475364 100644 --- a/apps/openmw-mp/Object.cpp +++ b/apps/openmw-mp/Object.cpp @@ -228,6 +228,7 @@ void Object::setMasterState(bool state) } +// TODO: Make this actually reflect the capabilities offered by containers in 0.6.3 void Container::Init(LuaState &lua) { lua.getState()->new_usertype("Container", diff --git a/apps/openmw-mp/Settings.cpp b/apps/openmw-mp/Settings.cpp index ef9a823c5..5193d4e46 100644 --- a/apps/openmw-mp/Settings.cpp +++ b/apps/openmw-mp/Settings.cpp @@ -14,6 +14,7 @@ void GameSettings::Init(LuaState &lua) { lua.getState()->new_usertype("Settings", "setDifficulty", &GameSettings::setDifficulty, + "setEnforcedLogLevel", &GameSettings::setEnforcedLogLevel, "setPhysicsFramerate", &GameSettings::setPhysicsFramerate, "setConsoleAllowed", &GameSettings::setConsoleAllowed, "setBedRestAllowed", &GameSettings::setBedRestAllowed, @@ -39,6 +40,12 @@ void GameSettings::setDifficulty(int difficulty) setChanged(); } +void GameSettings::setEnforcedLogLevel(int logLevel) +{ + player->enforcedLogLevel = logLevel; + setChanged(); +} + void GameSettings::setPhysicsFramerate(double physicsFramerate) { player->physicsFramerate = physicsFramerate; diff --git a/apps/openmw-mp/Settings.hpp b/apps/openmw-mp/Settings.hpp index dda3230dc..ed6e9a11f 100644 --- a/apps/openmw-mp/Settings.hpp +++ b/apps/openmw-mp/Settings.hpp @@ -22,6 +22,8 @@ public: void setDifficulty(int difficulty); + void setEnforcedLogLevel(int logLevel); + void setPhysicsFramerate(double physicsFramerate); void setBedRestAllowed(bool state); @@ -33,4 +35,4 @@ public: private: void processUpdate() final; -}; \ No newline at end of file +}; diff --git a/apps/openmw-mp/processors/world/ProcessorContainer.hpp b/apps/openmw-mp/processors/world/ProcessorContainer.hpp index 9b12f5950..6a4939da7 100644 --- a/apps/openmw-mp/processors/world/ProcessorContainer.hpp +++ b/apps/openmw-mp/processors/world/ProcessorContainer.hpp @@ -30,8 +30,10 @@ namespace mwmp if (serverCell != nullptr) serverCell->sendToLoaded(&packet, &event); } - else - packet.Send(true); + + // Otherwise, don't have any hardcoded sync and expect Lua scripts to forward + // container packets to ensure their integrity based on what exists in the + // server data auto objCtrl = Networking::get().getState().getObjectCtrl(); auto containers = objCtrl.copyContainers(event); diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 11d96f16c..37e4d7050 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -145,6 +145,17 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; + /* + Start of tes3mp addition + + Make it possible to get the ContainerWindow from elsewhere + in the code + */ + virtual MWGui::ContainerWindow* getContainerWindow() = 0; + /* + End of tes3mp addition + */ + /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item) = 0; @@ -411,7 +422,7 @@ namespace MWBase /// Cycle to next or previous weapon virtual void cycleWeapon(bool next) = 0; - virtual void playSound(const std::string& soundId, float volume = 1.f, float pitch = 1.f) = 0; + virtual void playSound(const std::string& soundId, bool preventOverlapping = false, float volume = 1.f, float pitch = 1.f) = 0; // In WindowManager for now since there isn't a VFS singleton virtual std::string correctIconPath(const std::string& path) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8767b0f09..b37d2ce2e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -547,12 +547,14 @@ namespace MWBase virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) = 0; + virtual void applyLoopingParticles(const MWWorld::Ptr& ptr) = 0; + virtual const std::vector& getContentFiles() const = 0; virtual void breakInvisibility (const MWWorld::Ptr& actor) = 0; - // Are we in an exterior or pseudo-exterior cell and it's night? - virtual bool isDark() const = 0; + // Allow NPCs to use torches? + virtual bool useTorches() const = 0; virtual bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) = 0; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index f9056b75d..aeeb89a72 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -233,24 +233,6 @@ namespace MWClass if (!(ref->mBase->mData.mFlags & ESM::Light::Carry)) return std::make_pair(0,""); - const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc); - MWWorld::ConstContainerStoreIterator weapon = invStore.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - - if(weapon == invStore.end()) - return std::make_pair(1,""); - - /// \todo the 2h check is repeated many times; put it in a function - if(weapon->getTypeName() == typeid(ESM::Weapon).name() && - (weapon->get()->mBase->mData.mType == ESM::Weapon::LongBladeTwoHand || - weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoClose || - weapon->get()->mBase->mData.mType == ESM::Weapon::BluntTwoWide || - weapon->get()->mBase->mData.mType == ESM::Weapon::SpearTwoWide || - weapon->get()->mBase->mData.mType == ESM::Weapon::AxeTwoHand || - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanBow || - weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanCrossbow)) - { - return std::make_pair(3,""); - } return std::make_pair(1,""); } diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 5a9237cea..6f16cf076 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -892,7 +892,7 @@ protected: public: typedef TypesetBookImpl::StyleImpl Style; - typedef std::map ActiveTextFormats; + typedef std::map > ActiveTextFormats; int mViewTop; int mViewBottom; @@ -1048,7 +1048,7 @@ public: { if (mNode != NULL) i->second->destroyDrawItem (mNode); - delete i->second; + i->second.reset(); } mActiveTextFormats.clear (); @@ -1115,11 +1115,11 @@ public: if (j == this_->mActiveTextFormats.end ()) { - TextFormat * textFormat = new TextFormat (Font, this_); + std::unique_ptr textFormat(new TextFormat (Font, this_)); textFormat->mTexture = Font->getTextureFont (); - j = this_->mActiveTextFormats.insert (std::make_pair (Font, textFormat)).first; + j = this_->mActiveTextFormats.insert (std::make_pair (Font, std::move(textFormat))).first; } j->second->mCountVertex += run.mPrintableChars * 6; diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index c18548dad..cb902cadf 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -200,7 +200,7 @@ namespace MWGui { if ((mCurrentPage+1)*2 < mPages.size()) { - MWBase::Environment::get().getWindowManager()->playSound("book page2"); + MWBase::Environment::get().getWindowManager()->playSound("book page2", true); ++mCurrentPage; @@ -211,7 +211,7 @@ namespace MWGui { if (mCurrentPage > 0) { - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound("book page", true); --mCurrentPage; diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 4a66bd0e5..f6107c92e 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -115,35 +115,35 @@ namespace MWGui worldEvent->reset(); worldEvent->cell = *mPtr.getCell()->getCell(); worldEvent->action = mwmp::BaseEvent::Action::Remove; + worldEvent->containerSubAction = mwmp::BaseEvent::ContainerSubAction::Drag; - mwmp::WorldObject worldObject; - worldObject.refId = mPtr.getCellRef().getRefId(); - worldObject.refNumIndex = mPtr.getCellRef().getRefNum().mIndex; - worldObject.mpNum = mPtr.getCellRef().getMpNum(); - + mwmp::WorldObject worldObject = worldEvent->getWorldObject(mPtr); MWWorld::Ptr itemPtr = mModel->getItem(mSelectedItem).mBase; - mwmp::ContainerItem containerItem; - containerItem.refId =itemPtr.getCellRef().getRefId(); - containerItem.count = itemPtr.getRefData().getCount(); - containerItem.charge = itemPtr.getCellRef().getCharge(); - containerItem.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge(); - containerItem.actionCount = count; - - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s\n- item: %s, %i", - worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str(), - containerItem.refId.c_str(), containerItem.count); - - worldObject.containerItems.push_back(std::move(containerItem)); + worldEvent->addContainerItem(worldObject, itemPtr, count); worldEvent->worldObjects.push_back(std::move(worldObject)); mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->setEvent(worldEvent); mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->Send(); + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s\n- item: %s, %i", + worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str(), + itemPtr.getCellRef().getRefId().c_str(), itemPtr.getRefData().getCount()); /* End of tes3mp addition */ + /* + Start of tes3mp change (major) + + Avoid running any of the original code for dragging items, to prevent possibilities + for item duping or interaction with restricted containers + */ + return; + /* + End of tes3mp change (major) + */ + mDragAndDrop->startDrag(mSelectedItem, mSortModel, mModel, mItemView, count); } @@ -162,39 +162,47 @@ namespace MWGui worldEvent->reset(); worldEvent->cell = *mPtr.getCell()->getCell(); worldEvent->action = mwmp::BaseEvent::Action::Add; + worldEvent->containerSubAction = mwmp::BaseEvent::ContainerSubAction::Drop; - mwmp::WorldObject worldObject; - worldObject.refId = mPtr.getCellRef().getRefId(); - worldObject.refNumIndex = mPtr.getCellRef().getRefNum().mIndex; - worldObject.mpNum = mPtr.getCellRef().getMpNum(); - + mwmp::WorldObject worldObject = worldEvent->getWorldObject(mPtr); MWWorld::Ptr itemPtr = mDragAndDrop->mItem.mBase; - mwmp::ContainerItem containerItem; containerItem.refId = itemPtr.getCellRef().getRefId(); - + // Make sure we get the drag and drop count, not the count of the original item containerItem.count = mDragAndDrop->mDraggedCount; containerItem.charge = itemPtr.getCellRef().getCharge(); containerItem.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge(); - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s\n- item: %s, %i", - worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str(), - containerItem.refId.c_str(), containerItem.count); - worldObject.containerItems.push_back(std::move(containerItem)); worldEvent->worldObjects.push_back(std::move(worldObject)); mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->setEvent(worldEvent); mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->Send(); + + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s\n- item: %s, %i, %i", + worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str(), + containerItem.refId.c_str(), containerItem.count, containerItem.charge); } /* End of tes3mp addition */ - if (success) - mDragAndDrop->drop(mModel, mItemView); + /* + Start of tes3mp change (major) + + Avoid running any of the original code for dropping items, to prevent possibilities + for item duping or interaction with restricted containers + + Instead, finish the drag in a way that removes the items in it + */ + //if (success) + // mDragAndDrop->drop(mModel, mItemView); + mDragAndDrop->finish(true); + /* + End of tes3mp change (major) + */ } void ContainerWindow::onBackgroundSelected() @@ -283,6 +291,40 @@ namespace MWGui if(mDragAndDrop != NULL && mDragAndDrop->mIsOnDragAndDrop) return; + /* + Start of tes3mp addition + + Send an ID_CONTAINER packet every time the Take All button is used on + a container + */ + mwmp::WorldEvent *worldEvent = mwmp::Main::get().getNetworking()->getWorldEvent(); + worldEvent->reset(); + worldEvent->cell = *mPtr.getCell()->getCell(); + worldEvent->action = mwmp::BaseEvent::Action::Remove; + worldEvent->containerSubAction = mwmp::BaseEvent::ContainerSubAction::TakeAll; + worldEvent->addEntireContainer(mPtr); + + mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->setEvent(worldEvent); + mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->Send(); + + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i-%i\n- cell: %s", + mPtr.getCellRef().getRefId().c_str(), mPtr.getCellRef().getRefNum().mIndex, mPtr.getCellRef().getMpNum(), + worldEvent->cell.getDescription().c_str()); + /* + End of tes3mp addition + */ + + /* + Start of tes3mp change (major) + + Avoid running any of the original code for taking all items, to prevent + possibilities for item duping or interaction with restricted containers + */ + return; + /* + End of tes3mp change (major) + */ + // transfer everything into the player's inventory ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); mModel->update(); @@ -322,33 +364,6 @@ namespace MWGui } MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Container); - - /* - Start of tes3mp addition - - Send an ID_CONTAINER packet every time the Take All button is used on - a container - */ - mwmp::WorldEvent *worldEvent = mwmp::Main::get().getNetworking()->getWorldEvent(); - worldEvent->reset(); - worldEvent->cell = *mPtr.getCell()->getCell(); - worldEvent->action = mwmp::BaseEvent::Action::Set; - - mwmp::WorldObject worldObject; - worldObject.refId = mPtr.getCellRef().getRefId(); - worldObject.refNumIndex = mPtr.getCellRef().getRefNum().mIndex; - worldObject.mpNum = mPtr.getCellRef().getMpNum(); - - LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Sending ID_CONTAINER about\n- Ptr cellRef: %s, %i\n- cell: %s", - worldObject.refId.c_str(), worldObject.refNumIndex, worldEvent->cell.getDescription().c_str()); - - worldEvent->worldObjects.push_back(std::move(worldObject)); - - mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->setEvent(worldEvent); - mwmp::Main::get().getNetworking()->getWorldPacket(ID_CONTAINER)->Send(); - /* - End of tes3mp addition - */ } void ContainerWindow::onDisposeCorpseButtonClicked(MyGUI::Widget *sender) @@ -392,4 +407,48 @@ namespace MWGui return mModel->onTakeItem(item.mBase, count); } + /* + Start of tes3mp addition + + Make it possible to check from elsewhere whether there is currently an + item being dragged in the container window + */ + bool ContainerWindow::isOnDragAndDrop() + { + return mDragAndDrop->mIsOnDragAndDrop; + } + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to drag a specific item Ptr instead of having to rely + on an index that may have changed in the meantime, for drags that + require approval from the server + */ + bool ContainerWindow::dragItemByPtr(const MWWorld::Ptr& itemPtr, int dragCount) + { + ItemModel::ModelIndex newIndex = -1; + for (unsigned int i = 0; i < mModel->getItemCount(); ++i) + { + if (mModel->getItem(i).mBase == itemPtr) + { + newIndex = i; + break; + } + } + + if (newIndex != -1) + { + mDragAndDrop->startDrag(newIndex, mSortModel, mModel, mItemView, dragCount); + return true; + } + + return false; + } + /* + End of tes3mp addition + */ } diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index cf02d165d..427647fd6 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -41,6 +41,29 @@ namespace MWGui virtual void resetReference(); + /* + Start of tes3mp addition + + Make it possible to check from elsewhere whether there is currently an + item being dragged in the container window + */ + bool isOnDragAndDrop(); + /* + End of tes3mp addition + */ + + /* + Start of tes3mp addition + + Make it possible to drag a specific item Ptr instead of having to rely + on an index that may have changed in the meantime, for drags that + require approval from the server + */ + bool dragItemByPtr(const MWWorld::Ptr& itemPtr, int dragCount); + /* + End of tes3mp addition + */ + private: DragAndDrop* mDragAndDrop; diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp index d81b2ed00..075393dc6 100644 --- a/apps/openmw/mwgui/draganddrop.cpp +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -127,10 +127,35 @@ void DragAndDrop::onFrame() finish(); } -void DragAndDrop::finish() +/* + Start of tes3mp change (minor) + + Add a deleteDragItems argument that allows the deletion of the + items in the drag as oppposed to the regular behavior of returning + them to their source model + + This is required to reduce unpredictable behavior for drags approved + or rejected by the server +*/ +void DragAndDrop::finish(bool deleteDragItems) +/* + End of tes3mp change (minor) +*/ { mIsOnDragAndDrop = false; mSourceSortModel->clearDragItems(); + + /* + Start of tes3mp addition + + Make it possible to entirely delete the items in the drag + */ + if (deleteDragItems) + mSourceModel->removeItem(mItem, mDraggedCount); + /* + End of tes3mp addition + */ + // since mSourceView doesn't get updated in drag() MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); diff --git a/apps/openmw/mwgui/draganddrop.hpp b/apps/openmw/mwgui/draganddrop.hpp index dff8cd73c..20f753fbc 100644 --- a/apps/openmw/mwgui/draganddrop.hpp +++ b/apps/openmw/mwgui/draganddrop.hpp @@ -31,7 +31,20 @@ namespace MWGui void drop (ItemModel* targetModel, ItemView* targetView); void onFrame(); - void finish(); + /* + Start of tes3mp change (minor) + + Add a deleteDragItems argument that allows the deletion of the + items in the drag as oppposed to the regular behavior of returning + them to their source model + + This is required to reduce unpredictable behavior for drags approved + or rejected by the server + */ + void finish(bool deleteDragItems = false); + /* + End of tes3mp change (minor) + */ }; } diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 4b7a789e8..0776706d8 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -616,7 +616,7 @@ namespace if (page+2 < book->pageCount()) { - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound("book page", true); page += 2; updateShowingPages (); @@ -634,7 +634,7 @@ namespace if(page >= 2) { - MWBase::Environment::get().getWindowManager()->playSound("book page"); + MWBase::Environment::get().getWindowManager()->playSound("book page", true); page -= 2; updateShowingPages (); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 6b7bf2e9d..1e7edb0ae 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -384,10 +384,19 @@ namespace MWGui mGuiModeStates[GM_Dialogue] = GuiModeState(mDialogueWindow); mTradeWindow->eventTradeDone += MyGUI::newDelegate(mDialogueWindow, &DialogueWindow::onTradeComplete); - ContainerWindow* containerWindow = new ContainerWindow(mDragAndDrop); - mWindows.push_back(containerWindow); - trackWindow(containerWindow, "container"); - mGuiModeStates[GM_Container] = GuiModeState({containerWindow, mInventoryWindow}); + /* + Start of tes3mp change (major) + + Use a member variable (mContainerWIndow) instead of a local one so + we can access it from elsewhere + */ + mContainerWindow = new ContainerWindow(mDragAndDrop); + mWindows.push_back(mContainerWindow); + trackWindow(mContainerWindow, "container"); + mGuiModeStates[GM_Container] = GuiModeState({mContainerWindow, mInventoryWindow}); + /* + End of tes3mp change (major) + */ mHud = new HUD(mCustomMarkers, mDragAndDrop, mLocalMapRender); mWindows.push_back(mHud); @@ -1399,6 +1408,17 @@ namespace MWGui MWGui::ConfirmationDialog* WindowManager::getConfirmationDialog() { return mConfirmationDialog; } MWGui::TradeWindow* WindowManager::getTradeWindow() { return mTradeWindow; } + /* + Start of tes3mp addition + + Make it possible to get the ContainerWindow from elsewhere + in the code + */ + MWGui::ContainerWindow* WindowManager::getContainerWindow() { return mContainerWindow; } + /* + End of tes3mp addition + */ + void WindowManager::useItem(const MWWorld::Ptr &item) { if (mInventoryWindow) @@ -2022,11 +2042,16 @@ namespace MWGui mInventoryWindow->cycle(next); } - void WindowManager::playSound(const std::string& soundId, float volume, float pitch) + void WindowManager::playSound(const std::string& soundId, bool preventOverlapping, float volume, float pitch) { if (soundId.empty()) return; - MWBase::Environment::get().getSoundManager()->playSound(soundId, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); + + MWBase::SoundManager *sndmgr = MWBase::Environment::get().getSoundManager(); + if (preventOverlapping && sndmgr->getSoundPlaying(MWWorld::Ptr(), soundId)) + return; + + sndmgr->playSound(soundId, volume, pitch, MWSound::Type::Sfx, MWSound::PlayMode::NoEnv); } void WindowManager::updateSpellWindow() diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index f1fa3e7c5..60d03a7d5 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -184,6 +184,17 @@ namespace MWGui virtual MWGui::ConfirmationDialog* getConfirmationDialog(); virtual MWGui::TradeWindow* getTradeWindow(); + /* + Start of tes3mp addition + + Make it possible to get the ContainerWindow from elsewhere + in the code + */ + virtual MWGui::ContainerWindow* getContainerWindow(); + /* + End of tes3mp addition + */ + /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item); @@ -440,7 +451,7 @@ namespace MWGui /// Cycle to next or previous weapon virtual void cycleWeapon(bool next); - virtual void playSound(const std::string& soundId, float volume = 1.f, float pitch = 1.f); + virtual void playSound(const std::string& soundId, bool preventOverlapping = false, float volume = 1.f, float pitch = 1.f); // In WindowManager for now since there isn't a VFS singleton virtual std::string correctIconPath(const std::string& path); @@ -510,6 +521,17 @@ namespace MWGui DebugWindow* mDebugWindow; JailScreen* mJailScreen; + /* + Start of tes3mp addition + + Keep a pointer to the container window because of its usefulness + in multiplayer for container sync + */ + ContainerWindow* mContainerWindow; + /* + End of tes3mp addition + */ + std::vector mWindows; Translation::Storage& mTranslationDataStorage; diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index 5c203ab9b..4b6824cbb 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -1066,6 +1066,17 @@ namespace MWInput return; } + /* + Start of tes3mp addition + + Ignore attempts to rest if the player has not finished character generation yet + */ + if (!mwmp::Main::get().getLocalPlayer()->hasFinishedCharGen()) + return; + /* + End of tes3mp addition + */ + /* Start of tes3mp addition diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 9ef83459e..c97c0a202 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -949,7 +949,7 @@ namespace MWMechanics stats.setTimeToStartDrowning(fHoldBreathTime); } - void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration) + void Actors::updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip) { bool isPlayer = (ptr == getPlayer()); @@ -964,7 +964,7 @@ namespace MWMechanics Start of tes3mp change (major) We need DedicatedPlayers and DedicatedActors to not automatically - equip their light-emitting items, so additions conditions have been + equip their light-emitting items, so additional conditions have been added for them */ if (!isPlayer && !mwmp::PlayerList::isDedicatedPlayer(ptr) && !mwmp::Main::get().getCellController()->isDedicatedActor(ptr)) @@ -982,7 +982,7 @@ namespace MWMechanics } } - if (MWBase::Environment::get().getWorld()->isDark()) + if (mayEquip) { if (torch != inventoryStore.end()) { @@ -991,16 +991,11 @@ namespace MWMechanics // For non-hostile NPCs, unequip whatever is in the left slot in favor of a light. if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) inventoryStore.unequipItem(*heldIter, ptr); - - // Also unequip twohanded weapons which conflict with anything in CarriedLeft - if (torch->getClass().canBeEquipped(*torch, ptr).first == 3) - inventoryStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, ptr); } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - // If we have a torch and can equip it (left slot free, no - // twohanded weapon in right slot), then equip it now. + // If we have a torch and can equip it, then equip it now. if (heldIter == inventoryStore.end() && torch->getClass().canBeEquipped(*torch, ptr).first == 1) { @@ -1057,7 +1052,7 @@ namespace MWMechanics } } - void Actors::updateCrimePersuit(const MWWorld::Ptr& ptr, float duration) + void Actors::updateCrimePursuit(const MWWorld::Ptr& ptr, float duration) { MWWorld::Ptr player = getPlayer(); if (ptr != player && ptr.getClass().isNpc()) @@ -1293,6 +1288,9 @@ namespace MWMechanics if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0; if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0; + // show torches only when there are darkness and no precipitations + bool showTorches = MWBase::Environment::get().getWorld()->useTorches(); + MWWorld::Ptr player = getPlayer(); /// \todo move update logic to Actor class where appropriate @@ -1438,7 +1436,7 @@ namespace MWMechanics } if (iter->first.getClass().isNpc() && iter->first != player && (isLocalActor || isAIActive)) - updateCrimePersuit(iter->first, duration); + updateCrimePursuit(iter->first, duration); if (iter->first != player && (isLocalActor || isAIActive)) { @@ -1456,7 +1454,7 @@ namespace MWMechanics updateNpc(iter->first, duration); if (timerUpdateEquippedLight == 0) - updateEquippedLight(iter->first, updateEquippedLightInterval); + updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); } } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index c3f7b2b42..9410dea39 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -39,9 +39,9 @@ namespace MWMechanics void updateDrowning (const MWWorld::Ptr& ptr, float duration); - void updateEquippedLight (const MWWorld::Ptr& ptr, float duration); + void updateEquippedLight (const MWWorld::Ptr& ptr, float duration, bool mayEquip); - void updateCrimePersuit (const MWWorld::Ptr& ptr, float duration); + void updateCrimePursuit (const MWWorld::Ptr& ptr, float duration); void killDeadActors (); diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 398e84448..38f641b94 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -189,7 +189,7 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); MWWorld::Ptr door = getNearbyDoor(actor, distance); - if (door != MWWorld::Ptr()) + if (door != MWWorld::Ptr() && actor.getClass().isBipedal(actor)) { // note: AiWander currently does not open doors if (getTypeId() != TypeIdWander && !door.getCellRef().getTeleport() && door.getClass().getDoorState(door) == 0) @@ -224,7 +224,7 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur MWBase::Environment::get().getWorld()->activate(door, actor); } } - else // any other obstacle (NPC, crate, etc.) + else { mObstacleCheck.takeEvasiveAction(movement); } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 381a1047c..fde097a54 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -40,7 +40,6 @@ #include "../mwrender/animation.hpp" -#include "magiceffects.hpp" #include "npcstats.hpp" #include "actorutil.hpp" #include "aifollow.hpp" @@ -625,7 +624,6 @@ namespace MWMechanics std::string texture = magicEffect->mParticle; - // TODO: VFX are no longer active after saving/reloading the game bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; // Note: in case of non actor, a free effect should be fine as well MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); @@ -1383,4 +1381,24 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->getString(); } + void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key, + const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, + float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find(key.mId); + + const ESM::Static* castStatic; + if (!magicEffect->mHit.empty()) + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); + else + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); + + std::string texture = magicEffect->mParticle; + + bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor); + if (anim && loop) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture); + } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 1f5ef45bd..07c5b8477 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -6,6 +6,8 @@ #include "../mwworld/ptr.hpp" +#include "magiceffects.hpp" + namespace ESM { struct Spell; @@ -119,6 +121,21 @@ namespace MWMechanics bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; + class ApplyLoopingParticlesVisitor : public EffectSourceVisitor + { + private: + MWWorld::Ptr mActor; + + public: + ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor) + : mActor(actor) + { + } + + virtual void visit (MWMechanics::EffectKey key, + const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, + float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1); + }; } #endif diff --git a/apps/openmw/mwmp/GUIController.cpp b/apps/openmw/mwmp/GUIController.cpp index da1f0ce7f..a42a4e38e 100644 --- a/apps/openmw/mwmp/GUIController.cpp +++ b/apps/openmw/mwmp/GUIController.cpp @@ -218,7 +218,6 @@ void mwmp::GUIController::showCustomWindow(const mwmp::BasePlayer::GUIWindow &gu void mwmp::GUIController::onInputBoxDone(MWGui::WindowBase *parWindow) { //MWBase::WindowManager *windowManager = MWBase::Environment::get().getWindowManager(); - LOG_MESSAGE_SIMPLE(Log::LOG_VERBOSE, "GUIController::onInputBoxDone: %s.",mInputBox->getTextInput().c_str()); Main::get().getLocalPlayer()->guiMessageBox.data = mInputBox->getTextInput(); Main::get().getNetworking()->getPlayerPacket(ID_GUI_MESSAGEBOX)->setPlayer(Main::get().getLocalPlayer()); diff --git a/apps/openmw/mwmp/LocalActor.cpp b/apps/openmw/mwmp/LocalActor.cpp index 023b6b383..7cf1c542b 100644 --- a/apps/openmw/mwmp/LocalActor.cpp +++ b/apps/openmw/mwmp/LocalActor.cpp @@ -205,16 +205,8 @@ void LocalActor::updateEquipment(bool forceUpdate) item.refId = cellRef.getRefId(); item.charge = cellRef.getCharge(); - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - { - MWMechanics::WeaponType weaptype; - auto &_class = ptr.getClass(); - MWMechanics::getActiveWeapon(_class.getCreatureStats(ptr), _class.getInventoryStore(ptr), &weaptype); - if (weaptype != MWMechanics::WeapType_Thrown) - item.count = 1; - } - else - item.count = invStore.count(cellRef.getRefId()); + item.enchantmentCharge = it->getCellRef().getEnchantmentCharge(); + item.count = it->getRefData().getCount(); } } else if (!item.refId.empty()) @@ -222,7 +214,8 @@ void LocalActor::updateEquipment(bool forceUpdate) equipmentChanged = true; item.refId = ""; item.count = 0; - item.charge = 0; + item.charge = -1; + item.enchantmentCharge = -1; } } diff --git a/apps/openmw/mwmp/LocalPlayer.cpp b/apps/openmw/mwmp/LocalPlayer.cpp index d4c3e58fd..3537b8bb2 100644 --- a/apps/openmw/mwmp/LocalPlayer.cpp +++ b/apps/openmw/mwmp/LocalPlayer.cpp @@ -49,6 +49,7 @@ LocalPlayer::LocalPlayer() charGenState.isFinished = false; difficulty = 0; + enforcedLogLevel = -1; physicsFramerate = 60.0; consoleAllowed = false; bedRestAllowed = true; @@ -455,17 +456,7 @@ void LocalPlayer::updateEquipment(bool forceUpdate) item.refId = it->getCellRef().getRefId(); item.charge = it->getCellRef().getCharge(); item.enchantmentCharge = it->getCellRef().getEnchantmentCharge(); - - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - { - MWMechanics::WeaponType weaptype; - MWMechanics::getActiveWeapon(ptrPlayer.getClass().getCreatureStats(ptrPlayer), - ptrPlayer.getClass().getInventoryStore(ptrPlayer), &weaptype); - if (weaptype != MWMechanics::WeapType_Thrown) - item.count = 1; - } - else - item.count = invStore.count(it->getCellRef().getRefId()); + item.count = it->getRefData().getCount(); } } else if (!item.refId.empty()) @@ -688,6 +679,7 @@ void LocalPlayer::addItem(const Item &item) LOG_APPEND(Log::LOG_INFO, "- Ignored addition of invalid inventory item %s", item.refId.c_str()); } + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); } void LocalPlayer::addSpells() @@ -1080,6 +1072,8 @@ void LocalPlayer::setEquipment() else ptrInventory.unequipSlot(slot, ptrPlayer); } + + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updatePlayer(); } void LocalPlayer::setInventory() diff --git a/apps/openmw/mwmp/WorldEvent.cpp b/apps/openmw/mwmp/WorldEvent.cpp index 299b01448..c1b2cec31 100644 --- a/apps/openmw/mwmp/WorldEvent.cpp +++ b/apps/openmw/mwmp/WorldEvent.cpp @@ -15,6 +15,8 @@ #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwgui/container.hpp" + #include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/summoning.hpp" @@ -50,14 +52,20 @@ void WorldEvent::reset() cell.blank(); worldObjects.clear(); guid = mwmp::Main::get().getNetworking()->getLocalPlayer()->guid; + + action = BaseEvent::Action::Request; + containerSubAction = BaseEvent::ContainerSubAction::None; } void WorldEvent::editContainers(MWWorld::CellStore* cellStore) { + bool isLocalEvent = guid == Main::get().getLocalPlayer()->guid; + + LOG_APPEND(Log::LOG_VERBOSE, "- isLocalEvent? %s", isLocalEvent ? "true" : "false"); + for (const auto &worldObject : worldObjects) { - - //LOG_APPEND(Log::LOG_VERBOSE, "- cellRef: %s, %i, %i", worldObject.refId.c_str(), worldObject.refNumIndex, worldObject.mpNum); + //LOG_APPEND(Log::LOG_VERBOSE, "- container cellRef: %s %i-%i", worldObject.refId.c_str(), worldObject.refNumIndex, worldObject.mpNum); MWWorld::Ptr ptrFound = cellStore->searchExact(worldObject.refNumIndex, worldObject.mpNum); @@ -66,19 +74,41 @@ void WorldEvent::editContainers(MWWorld::CellStore* cellStore) //LOG_APPEND(Log::LOG_VERBOSE, "-- Found %s, %i, %i", ptrFound.getCellRef().getRefId().c_str(), // ptrFound.getCellRef().getRefNum().mIndex, ptrFound.getCellRef().getMpNum()); + bool isCurrentContainer = false; + + // If we are in a container, and it happens to be this container, keep track of that + if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Container)) + { + CurrentContainer *currentContainer = &mwmp::Main::get().getLocalPlayer()->currentContainer; + + if (currentContainer->refNumIndex == ptrFound.getCellRef().getRefNum().mIndex && + currentContainer->mpNum == ptrFound.getCellRef().getMpNum()) + { + isCurrentContainer = true; + } + } + MWWorld::ContainerStore& containerStore = ptrFound.getClass().getContainerStore(ptrFound); // If we are setting the entire contents, clear the current ones if (action == BaseEvent::Action::Set) containerStore.clear(); + bool isLocalDrop = isLocalEvent && containerSubAction == BaseEvent::ContainerSubAction::Drop; + bool isLocalDrag = isLocalEvent && containerSubAction == BaseEvent::ContainerSubAction::Drag; + bool isLocalTakeAll = isLocalEvent && containerSubAction == BaseEvent::ContainerSubAction::TakeAll; + std::string takeAllSound = ""; + MWWorld::Ptr ownerPtr = MWBase::Environment::get().getWorld()->getPlayerPtr(); for (const auto &containerItem : worldObject.containerItems) { + //LOG_APPEND(Log::LOG_VERBOSE, "-- containerItem cellRef: %s, count: %i, actionCount: %i", + // containerItem.refId.c_str(), containerItem.count, containerItem.actionCount); + if (containerItem.refId.find("$dynamic") != string::npos) continue; - if (action == BaseEvent::Action::Add || action == BaseEvent::Action::Set) + if (action == BaseEvent::Action::Set || action == BaseEvent::Action::Add) { // Create a ManualRef to be able to set item charge MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), containerItem.refId, 1); @@ -95,28 +125,53 @@ void WorldEvent::editContainers(MWWorld::CellStore* cellStore) containerStore.add(newPtr, containerItem.count, ownerPtr, true); } - else if (action == BaseEvent::Action::Remove) + else if (action == BaseEvent::Action::Remove && containerItem.actionCount > 0) { // We have to find the right item ourselves because ContainerStore has no method // accounting for charge - for (const auto ptr : containerStore) + for (const auto itemPtr : containerStore) { - if (Misc::StringUtils::ciEqual(ptr.getCellRef().getRefId(), containerItem.refId)) + if (Misc::StringUtils::ciEqual(itemPtr.getCellRef().getRefId(), containerItem.refId)) { - if (ptr.getRefData().getCount() == containerItem.count && - ptr.getCellRef().getCharge() == containerItem.charge && - ptr.getCellRef().getEnchantmentCharge() == containerItem.enchantmentCharge) + if (itemPtr.getCellRef().getCharge() == containerItem.charge && + itemPtr.getCellRef().getEnchantmentCharge() == containerItem.enchantmentCharge) { + // Store the sound of the first item in a TAKE_ALL + if (isLocalTakeAll && takeAllSound.empty()) + takeAllSound = itemPtr.getClass().getUpSoundId(itemPtr); + // Is this an actor's container? If so, unequip this item if it was equipped if (ptrFound.getClass().isActor()) { MWWorld::InventoryStore& invStore = ptrFound.getClass().getInventoryStore(ptrFound); - if (invStore.isEquipped(ptr)) - invStore.unequipItemQuantity(ptr, ptrFound, containerItem.count); + if (invStore.isEquipped(itemPtr)) + invStore.unequipItemQuantity(itemPtr, ptrFound, containerItem.count); } - containerStore.remove(ptr, containerItem.actionCount, ownerPtr); + bool isResolved = false; + + if (isLocalDrag && isCurrentContainer) + { + MWGui::ContainerWindow* containerWindow = MWBase::Environment::get().getWindowManager()->getContainerWindow(); + + if (!containerWindow->isOnDragAndDrop()) + { + isResolved = containerWindow->dragItemByPtr(itemPtr, containerItem.actionCount); + } + } + + if (!isResolved) + { + containerStore.remove(itemPtr, containerItem.actionCount, ownerPtr); + + if (isLocalDrag || isLocalTakeAll) + { + MWWorld::Ptr ptrPlayer = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::ContainerStore &playerStore = ptrPlayer.getClass().getContainerStore(ptrPlayer); + *playerStore.add(containerItem.refId, containerItem.actionCount, ptrPlayer); + } + } } } } @@ -132,16 +187,18 @@ void WorldEvent::editContainers(MWWorld::CellStore* cellStore) invStore.autoEquip(ptrFound); } - // If we are in a container, and it happens to be this container, update its view - if (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Container)) + // If this container was open for us, update its view + if (isCurrentContainer) { - CurrentContainer *currentContainer = &mwmp::Main::get().getLocalPlayer()->currentContainer; - - if (currentContainer->refNumIndex == ptrFound.getCellRef().getRefNum().mIndex && - currentContainer->mpNum == ptrFound.getCellRef().getMpNum()) + if (isLocalTakeAll) { MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); - MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_Container, ptrFound); + MWBase::Environment::get().getWindowManager()->playSound(takeAllSound); + } + else + { + MWGui::ContainerWindow* containerWindow = MWBase::Environment::get().getWindowManager()->getContainerWindow(); + containerWindow->setPtr(ptrFound); } } } @@ -610,6 +667,41 @@ void WorldEvent::playVideo() } } +WorldObject WorldEvent::getWorldObject(const MWWorld::Ptr& ptr) +{ + mwmp::WorldObject worldObject; + worldObject.refId = ptr.getCellRef().getRefId(); + worldObject.refNumIndex = ptr.getCellRef().getRefNum().mIndex; + worldObject.mpNum = ptr.getCellRef().getMpNum(); + return worldObject; +} + +void WorldEvent::addContainerItem(mwmp::WorldObject& worldObject, const MWWorld::Ptr& itemPtr, int actionCount) +{ + mwmp::ContainerItem containerItem; + containerItem.refId = itemPtr.getCellRef().getRefId(); + containerItem.count = itemPtr.getRefData().getCount(); + containerItem.charge = itemPtr.getCellRef().getCharge(); + containerItem.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge(); + containerItem.actionCount = actionCount; + + worldObject.containerItems.push_back(move(containerItem)); +} + +void WorldEvent::addEntireContainer(const MWWorld::Ptr& ptr) +{ + MWWorld::ContainerStore& containerStore = ptr.getClass().getContainerStore(ptr); + + mwmp::WorldObject worldObject = getWorldObject(ptr); + + for (const auto itemPtr : containerStore) + { + addContainerItem(worldObject, itemPtr, itemPtr.getRefData().getCount()); + } + + worldObjects.push_back(move(worldObject)); +} + void WorldEvent::addObjectPlace(const MWWorld::Ptr& ptr, bool droppedByPlayer) { if (ptr.getCellRef().getRefId().find("$dynamic") != string::npos) @@ -980,7 +1072,7 @@ void WorldEvent::sendScriptGlobalShort() mwmp::Main::get().getNetworking()->getWorldPacket(ID_SCRIPT_GLOBAL_SHORT)->Send(); } -void WorldEvent::sendContainers(MWWorld::CellStore* cellStore) +void WorldEvent::sendCellContainers(MWWorld::CellStore* cellStore) { reset(); cell = *cellStore->getCell(); @@ -999,13 +1091,7 @@ void WorldEvent::sendContainers(MWWorld::CellStore* cellStore) for (const auto &itemPtr : containerStore) { - mwmp::ContainerItem containerItem; - containerItem.refId = itemPtr.getCellRef().getRefId(); - containerItem.count = itemPtr.getRefData().getCount(); - containerItem.charge = itemPtr.getCellRef().getCharge(); - containerItem.enchantmentCharge = itemPtr.getCellRef().getEnchantmentCharge(); - - worldObject.containerItems.push_back(move(containerItem)); + addContainerItem(worldObject, itemPtr, 0); } worldObjects.push_back(move(worldObject)); diff --git a/apps/openmw/mwmp/WorldEvent.hpp b/apps/openmw/mwmp/WorldEvent.hpp index 1d7be2b36..86ddac8ee 100644 --- a/apps/openmw/mwmp/WorldEvent.hpp +++ b/apps/openmw/mwmp/WorldEvent.hpp @@ -40,6 +40,10 @@ namespace mwmp void playMusic(); void playVideo(); + WorldObject getWorldObject(const MWWorld::Ptr& ptr); + void addContainerItem(mwmp::WorldObject& worldObject, const MWWorld::Ptr& itemPtr, int actionCount); + void addEntireContainer(const MWWorld::Ptr& ptr); + void addObjectPlace(const MWWorld::Ptr& ptr, bool droppedByPlayer = false); void addObjectSpawn(const MWWorld::Ptr& ptr); void addObjectSpawn(const MWWorld::Ptr& ptr, const MWWorld::Ptr& master); @@ -49,6 +53,7 @@ namespace mwmp void addObjectScale(const MWWorld::Ptr& ptr, float scale); 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 addMusicPlay(std::string filename); void addVideoPlay(std::string filename, bool allowSkipping); @@ -73,7 +78,7 @@ namespace mwmp void sendScriptMemberShort(); void sendScriptGlobalShort(); - void sendContainers(MWWorld::CellStore* cellStore); + void sendCellContainers(MWWorld::CellStore* cellStore); private: Networking *getNetworking(); diff --git a/apps/openmw/mwmp/processors/ActorProcessor.cpp b/apps/openmw/mwmp/processors/ActorProcessor.cpp index 6f61d6766..42c46881c 100644 --- a/apps/openmw/mwmp/processors/ActorProcessor.cpp +++ b/apps/openmw/mwmp/processors/ActorProcessor.cpp @@ -15,6 +15,7 @@ bool ActorProcessor::Process(RakNet::Packet &packet, ActorList &actorList) { RakNet::BitStream bsIn(&packet.data[1], packet.length, false); bsIn.Read(guid); + actorList.guid = guid; ActorPacket *myPacket = Main::get().getNetworking()->getActorPacket(packet.data[0]); @@ -44,4 +45,4 @@ bool ActorProcessor::Process(RakNet::Packet &packet, ActorList &actorList) } } return false; -} \ No newline at end of file +} diff --git a/apps/openmw/mwmp/processors/WorldProcessor.cpp b/apps/openmw/mwmp/processors/WorldProcessor.cpp index a1fc95bb5..67689e55b 100644 --- a/apps/openmw/mwmp/processors/WorldProcessor.cpp +++ b/apps/openmw/mwmp/processors/WorldProcessor.cpp @@ -15,6 +15,7 @@ bool WorldProcessor::Process(RakNet::Packet &packet, WorldEvent &event) { RakNet::BitStream bsIn(&packet.data[1], packet.length, false); bsIn.Read(guid); + event.guid = guid; WorldPacket *myPacket = Main::get().getNetworking()->getWorldPacket(packet.data[0]); diff --git a/apps/openmw/mwmp/processors/player/ProcessorGameSettings.hpp b/apps/openmw/mwmp/processors/player/ProcessorGameSettings.hpp index f1a4ab88b..39be1eabd 100644 --- a/apps/openmw/mwmp/processors/player/ProcessorGameSettings.hpp +++ b/apps/openmw/mwmp/processors/player/ProcessorGameSettings.hpp @@ -19,8 +19,12 @@ namespace mwmp virtual void Do(PlayerPacket &packet, BasePlayer *player) { + static const int initialLogLevel = Log::GetLevel(); + if (isLocal()) { + LOG_MESSAGE_SIMPLE(Log::LOG_INFO, "Received ID_GAME_SETTINGS"); + if (MWBase::Environment::get().getWindowManager()->isGuiMode()) { if (MWBase::Environment::get().getWindowManager()->getMode() == MWGui::GM_Console && !player->consoleAllowed) @@ -30,6 +34,18 @@ namespace mwmp MWBase::Environment::get().getWindowManager()->popGuiMode(); } + if (player->enforcedLogLevel > -1) + { + LOG_APPEND(Log::LOG_INFO, "- server is enforcing log level %i", player->enforcedLogLevel); + + Log::Get().SetLevel(player->enforcedLogLevel); + } + else if (initialLogLevel != Log::GetLevel()) + { + LOG_APPEND(Log::LOG_INFO, "- log level has been reset to initial value %i", initialLogLevel); + Log::Get().SetLevel(initialLogLevel); + } + MWBase::Environment::get().getWorld()->setPhysicsFramerate(player->physicsFramerate); } } diff --git a/apps/openmw/mwmp/processors/player/ProcessorHandshake.hpp b/apps/openmw/mwmp/processors/player/ProcessorHandshake.hpp index d4ac8a708..6141f9405 100644 --- a/apps/openmw/mwmp/processors/player/ProcessorHandshake.hpp +++ b/apps/openmw/mwmp/processors/player/ProcessorHandshake.hpp @@ -19,7 +19,7 @@ namespace mwmp virtual void Do(PlayerPacket &packet, BasePlayer *player) { - packet.setPlayer(getLocalPlayer()); // player is 0 because myGuid will be setted after ProcessUserMyID + packet.setPlayer(getLocalPlayer()); // player is 0 because myGuid will be set after ProcessUserMyID packet.Send(serverAddr); } }; diff --git a/apps/openmw/mwmp/processors/world/ProcessorContainer.hpp b/apps/openmw/mwmp/processors/world/ProcessorContainer.hpp index ee5f261b5..684d9fdb4 100644 --- a/apps/openmw/mwmp/processors/world/ProcessorContainer.hpp +++ b/apps/openmw/mwmp/processors/world/ProcessorContainer.hpp @@ -17,12 +17,12 @@ namespace mwmp { BaseObjectProcessor::Do(packet, event); - LOG_APPEND(Log::LOG_VERBOSE, "- action: %i", (int)event.action); + LOG_APPEND(Log::LOG_VERBOSE, "- action: %i, containerSubAction: %i", (int) event.action, (int) event.containerSubAction); // If we've received a request for information, comply with it if (event.action == mwmp::BaseEvent::Action::Request) - event.sendContainers(ptrCellStore); - // Otherwise, edit containers based on the information received + event.sendCellContainers(ptrCellStore); + // Otherwise, edit containers based on the information received else event.editContainers(ptrCellStore); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 6efab6f32..5863897b9 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index b49f408d4..8283ef1d1 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -90,6 +90,9 @@ namespace if (ptr.getClass().isActor()) rendering.addWaterRippleEmitter(ptr); + + // Restore effect particles + MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 2f0a2f8cf..cf6cb1df8 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -9,7 +9,6 @@ #include #include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwmechanics/actorutil.hpp" @@ -520,6 +519,7 @@ WeatherManager::WeatherManager(MWRender::RenderingManager& rendering, const Fall , mSecunda("Secunda", fallback) , mWindSpeed(0.f) , mIsStorm(false) + , mPrecipitation(false) , mStormDirection(0,1,0) , mCurrentRegion() , mTimePassed(0) @@ -608,14 +608,11 @@ void WeatherManager::modRegion(const std::string& regionID, const std::vectorisCellExterior() || world->isCellQuasiExterior()) { - std::string playerRegion = Misc::StringUtils::lowerCase(world->getPlayerPtr().getCell()->getCell()->mRegion); std::map::iterator it = mRegions.find(playerRegion); if(it != mRegions.end() && playerRegion != mCurrentRegion) { @@ -625,11 +622,9 @@ void WeatherManager::playerTeleported() } } -void WeatherManager::update(float duration, bool paused) +void WeatherManager::update(float duration, bool paused, const TimeStamp& time, bool isExterior) { MWWorld::ConstPtr player = MWMechanics::getPlayer(); - MWBase::World& world = *MWBase::Environment::get().getWorld(); - TimeStamp time = world.getTimeStamp(); if(!paused || mFastForward) { @@ -647,8 +642,7 @@ void WeatherManager::update(float duration, bool paused) updateWeatherTransitions(duration); } - const bool exterior = (world.isCellExterior() || world.isCellQuasiExterior()); - if(!exterior) + if(!isExterior) { mRendering.setSkyEnabled(false); stopSounds(); @@ -660,6 +654,10 @@ void WeatherManager::update(float duration, bool paused) mWindSpeed = mResult.mWindSpeed; mIsStorm = mResult.mIsStorm; + // For some reason Ash Storm is not considered as a precipitation weather in game + mPrecipitation = !(mResult.mParticleEffect.empty() && mResult.mRainEffect.empty()) + && mResult.mParticleEffect != "meshes\\ashcloud.nif"; + if (mIsStorm) { osg::Vec3f playerPos (player.getRefData().getPosition().asVec3()); @@ -777,12 +775,11 @@ unsigned int WeatherManager::getWeatherID() const return mCurrentWeather; } -bool WeatherManager::isDark() const +bool WeatherManager::useTorches(float hour) const { - TimeStamp time = MWBase::Environment::get().getWorld()->getTimeStamp(); - bool exterior = (MWBase::Environment::get().getWorld()->isCellExterior() - || MWBase::Environment::get().getWorld()->isCellQuasiExterior()); - return exterior && (time.getHour() < mSunriseTime || time.getHour() > mTimeSettings.mNightStart - 1); + bool isDark = hour < mSunriseTime || hour > mTimeSettings.mNightStart - 1; + + return isDark && !mPrecipitation; } void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 5ca60c4cc..86bbf7d3c 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -224,14 +224,14 @@ namespace MWWorld */ void changeWeather(const std::string& regionID, const unsigned int weatherID); void modRegion(const std::string& regionID, const std::vector& chances); - void playerTeleported(); + void playerTeleported(const std::string& playerRegion, bool isExterior); /** * Per-frame update * @param duration * @param paused */ - void update(float duration, bool paused = false); + void update(float duration, bool paused, const TimeStamp& time, bool isExterior); void stopSounds(); @@ -246,8 +246,7 @@ namespace MWWorld unsigned int getWeatherID() const; - /// @see World::isDark - bool isDark() const; + bool useTorches(float hour) const; void write(ESM::ESMWriter& writer, Loading::Listener& progress); @@ -281,6 +280,7 @@ namespace MWWorld float mWindSpeed; bool mIsStorm; + bool mPrecipitation; osg::Vec3f mStormDirection; std::string mCurrentRegion; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 95819264e..0ce284ed8 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2359,6 +2359,8 @@ namespace MWWorld model = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); mPhysics->remove(getPlayerPtr()); mPhysics->addActor(getPlayerPtr(), model); + + applyLoopingParticles(player); } int World::canRest () @@ -3019,6 +3021,19 @@ namespace MWWorld mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); } + void World::applyLoopingParticles(const MWWorld::Ptr& ptr) + { + const MWWorld::Class &cls = ptr.getClass(); + if (cls.isActor()) + { + MWMechanics::ApplyLoopingParticlesVisitor visitor(ptr); + cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor); + cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor); + if (cls.hasInventoryStore(ptr)) + cls.getInventoryStore(ptr).visitEffectSources(visitor); + } + } + const std::vector& World::getContentFiles() const { return mContentFiles; @@ -3035,11 +3050,17 @@ namespace MWWorld MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); } - bool World::isDark() const + bool World::useTorches() const { + // If we are in exterior, check the weather manager. + // In interiors there are no precipitations and sun, so check the ambient + // Looks like pseudo-exteriors considered as interiors in this case MWWorld::CellStore* cell = mPlayer->getPlayer().getCell(); if (cell->isExterior()) - return mWeatherManager->isDark(); + { + float hour = getTimeStamp().getHour(); + return mWeatherManager->useTorches(hour); + } else { uint32_t ambient = cell->getCell()->mAmbi.mAmbient; @@ -3200,13 +3221,17 @@ namespace MWWorld void World::updateWeather(float duration, bool paused) { + bool isExterior = isCellExterior() || isCellQuasiExterior(); if (mPlayer->wasTeleported()) { mPlayer->setTeleported(false); - mWeatherManager->playerTeleported(); + + const std::string playerRegion = Misc::StringUtils::lowerCase(getPlayerPtr().getCell()->getCell()->mRegion); + mWeatherManager->playerTeleported(playerRegion, isExterior); } - mWeatherManager->update(duration, paused); + const TimeStamp time = getTimeStamp(); + mWeatherManager->update(duration, paused, time, isExterior); } struct AddDetectedReferenceVisitor diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 153c41c47..6b7c373bc 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -658,12 +658,13 @@ namespace MWWorld void launchProjectile (MWWorld::Ptr actor, MWWorld::ConstPtr projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr bow, float speed, float attackStrength) override; + void applyLoopingParticles(const MWWorld::Ptr& ptr); const std::vector& getContentFiles() const override; - void breakInvisibility (const MWWorld::Ptr& actor) override; - // Are we in an exterior or pseudo-exterior cell and it's night? - bool isDark() const override; + + // Allow NPCs to use torches? + bool useTorches() const override; bool findInteriorPositionInWorldSpace(const MWWorld::CellStore* cell, osg::Vec3f& result) override; diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 9b09bc41f..48c8be4d8 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -1,7 +1,7 @@ find_package(GTest REQUIRED) if (GTEST_FOUND) - include_directories(${GTEST_INCLUDE_DIRS}) + include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp diff --git a/components/fallback/fallback.cpp b/components/fallback/fallback.cpp index 11a577a45..ce6cba313 100644 --- a/components/fallback/fallback.cpp +++ b/components/fallback/fallback.cpp @@ -1,5 +1,7 @@ #include "fallback.hpp" +#include + #include @@ -17,6 +19,7 @@ namespace Fallback std::map::const_iterator it; if((it = mFallbackMap.find(fall)) == mFallbackMap.end()) { + std::cerr << "Warning: fallback value " << fall << " not found." << std::endl; return ""; } return it->second; @@ -25,7 +28,7 @@ namespace Fallback float Map::getFallbackFloat(const std::string& fall) const { std::string fallback=getFallbackString(fall); - if(fallback.empty()) + if (fallback.empty()) return 0; else return boost::lexical_cast(fallback); @@ -34,7 +37,7 @@ namespace Fallback int Map::getFallbackInt(const std::string& fall) const { std::string fallback=getFallbackString(fall); - if(fallback.empty()) + if (fallback.empty()) return 0; else return std::stoi(fallback); @@ -43,7 +46,7 @@ namespace Fallback bool Map::getFallbackBool(const std::string& fall) const { std::string fallback=getFallbackString(fall); - if(fallback.empty()) + if (fallback.empty()) return false; else return stob(fallback); @@ -52,8 +55,8 @@ namespace Fallback osg::Vec4f Map::getFallbackColour(const std::string& fall) const { std::string sum=getFallbackString(fall); - if(sum.empty()) - return osg::Vec4f(0.f,0.f,0.f,1.f); + if (sum.empty()) + return osg::Vec4f(0.5f,0.5f,0.5f,1.f); else { std::string ret[3]; diff --git a/components/openmw-mp/Base/BaseEvent.hpp b/components/openmw-mp/Base/BaseEvent.hpp index a4d580108..bf31f12f5 100644 --- a/components/openmw-mp/Base/BaseEvent.hpp +++ b/components/openmw-mp/Base/BaseEvent.hpp @@ -85,6 +85,14 @@ namespace mwmp Request }; + enum class ContainerSubAction : uint8_t + { + None = 0, + Drag, + Drop, + TakeAll + }; + RakNet::RakNetGUID guid; std::vector worldObjects; @@ -93,6 +101,7 @@ namespace mwmp std::string consoleCommand; Action action; + ContainerSubAction containerSubAction; bool isValid; }; diff --git a/components/openmw-mp/Base/BasePlayer.hpp b/components/openmw-mp/Base/BasePlayer.hpp index ce6810506..43ed28f27 100644 --- a/components/openmw-mp/Base/BasePlayer.hpp +++ b/components/openmw-mp/Base/BasePlayer.hpp @@ -303,6 +303,7 @@ namespace mwmp } weather; int difficulty; + int enforcedLogLevel; float physicsFramerate; bool consoleAllowed; bool bedRestAllowed; diff --git a/components/openmw-mp/Log.hpp b/components/openmw-mp/Log.hpp index 2d4731f6f..76415f716 100644 --- a/components/openmw-mp/Log.hpp +++ b/components/openmw-mp/Log.hpp @@ -72,6 +72,11 @@ public: return instance; } + static int GetLevel() + { + return Log::Get().logLevel; + } + void SetLevel(int level) { logLevel = level; diff --git a/components/openmw-mp/Packets/Player/PacketGameSettings.cpp b/components/openmw-mp/Packets/Player/PacketGameSettings.cpp index 24c275e71..e83031322 100644 --- a/components/openmw-mp/Packets/Player/PacketGameSettings.cpp +++ b/components/openmw-mp/Packets/Player/PacketGameSettings.cpp @@ -14,9 +14,10 @@ void PacketGameSettings::Packet(RakNet::BitStream *bs, bool send) PlayerPacket::Packet(bs, send); RW(player->difficulty, send); - RW(player->physicsFramerate, send); RW(player->consoleAllowed, send); RW(player->bedRestAllowed, send); RW(player->wildernessRestAllowed, send); RW(player->waitAllowed, send); + RW(player->enforcedLogLevel, send); + RW(player->physicsFramerate, send); } diff --git a/components/openmw-mp/Packets/World/PacketContainer.cpp b/components/openmw-mp/Packets/World/PacketContainer.cpp index 221333a18..dc5087bc2 100644 --- a/components/openmw-mp/Packets/World/PacketContainer.cpp +++ b/components/openmw-mp/Packets/World/PacketContainer.cpp @@ -16,6 +16,7 @@ void PacketContainer::Packet(RakNet::BitStream *bs, bool send) return; RW(event->action, send); + RW(event->containerSubAction, send); for (auto &&worldObject : event->worldObjects) {