diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ddd7df266..6ab1c0213a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking + Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders Feature #3537: Shader-based water ripples diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 1eb41a2d869..7208fce5f61 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -77,6 +78,11 @@ namespace MWGui mIngredients[2]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); mIngredients[3]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onIngredientSelected); + mApparatus[0]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onApparatusSelected); + mApparatus[1]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onApparatusSelected); + mApparatus[2]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onApparatusSelected); + mApparatus[3]->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onApparatusSelected); + mCreateButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCreateButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); @@ -141,12 +147,12 @@ namespace MWGui } // remove ingredient slots that have been fully used up - for (int i = 0; i < 4; ++i) + for (size_t i = 0; i < mIngredients.size(); ++i) if (mIngredients[i]->isUserString("ToolTipType")) { MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); if (ingred.getRefData().getCount() == 0) - removeIngredient(mIngredients[i]); + mAlchemy->removeIngredient(i); } updateFilters(); @@ -289,7 +295,85 @@ namespace MWGui void AlchemyWindow::onIngredientSelected(MyGUI::Widget* _sender) { - removeIngredient(_sender); + size_t i = std::distance(mIngredients.begin(), std::find(mIngredients.begin(), mIngredients.end(), _sender)); + mAlchemy->removeIngredient(i); + update(); + } + + void AlchemyWindow::onItemSelected(MWWorld::Ptr item) + { + mItemSelectionDialog->setVisible(false); + + int32_t index = item.get()->mBase->mData.mType; + const auto& widget = mApparatus[index]; + + widget->setItem(item); + + if (item.isEmpty()) + { + widget->clearUserStrings(); + return; + } + + mAlchemy->addApparatus(item); + + widget->setUserString("ToolTipType", "ItemPtr"); + widget->setUserData(MWWorld::Ptr(item)); + + MWBase::Environment::get().getWindowManager()->playSound(item.getClass().getDownSoundId(item)); + update(); + } + + void AlchemyWindow::onItemCancel() + { + mItemSelectionDialog->setVisible(false); + } + + void AlchemyWindow::onApparatusSelected(MyGUI::Widget* _sender) + { + size_t i = std::distance(mApparatus.begin(), std::find(mApparatus.begin(), mApparatus.end(), _sender)); + if (_sender->getUserData()->isEmpty()) // if this apparatus slot is empty + { + std::string title; + switch (i) + { + case ESM::Apparatus::AppaType::MortarPestle: + title = "#{sMortar}"; + break; + case ESM::Apparatus::AppaType::Alembic: + title = "#{sAlembic}"; + break; + case ESM::Apparatus::AppaType::Calcinator: + title = "#{sCalcinator}"; + break; + case ESM::Apparatus::AppaType::Retort: + title = "#{sRetort}"; + break; + default: + title = "#{sApparatus}"; + } + + mItemSelectionDialog = std::make_unique(title); + mItemSelectionDialog->eventItemSelected += MyGUI::newDelegate(this, &AlchemyWindow::onItemSelected); + mItemSelectionDialog->eventDialogCanceled += MyGUI::newDelegate(this, &AlchemyWindow::onItemCancel); + mItemSelectionDialog->setVisible(true); + mItemSelectionDialog->openContainer(MWMechanics::getPlayer()); + mItemSelectionDialog->getSortModel()->setApparatusTypeFilter(i); + mItemSelectionDialog->setFilter(SortFilterItemModel::Filter_OnlyAlchemyTools); + } + else + { + const auto& widget = mApparatus[i]; + mAlchemy->removeApparatus(i); + + if (widget->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(widget->getChildAt(0)); + + widget->clearUserStrings(); + widget->setItem(MWWorld::Ptr()); + widget->setUserData(MWWorld::Ptr()); + } + update(); } @@ -386,15 +470,6 @@ namespace MWGui effectsWidget->setCoord(coord); } - void AlchemyWindow::removeIngredient(MyGUI::Widget* ingredient) - { - for (int i = 0; i < 4; ++i) - if (mIngredients[i] == ingredient) - mAlchemy->removeIngredient(i); - - update(); - } - void AlchemyWindow::addRepeatController(MyGUI::Widget* widget) { MyGUI::ControllerItem* item diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index 39ea5ec9b33..82e5c3f583b 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -10,6 +10,7 @@ #include #include +#include "itemselection.hpp" #include "windowbase.hpp" #include "../mwmechanics/alchemy.hpp" @@ -44,6 +45,8 @@ namespace MWGui }; FilterType mCurrentFilter; + std::unique_ptr mItemSelectionDialog; + ItemView* mItemView; InventoryItemModel* mModel; SortFilterItemModel* mSortModel; @@ -63,6 +66,7 @@ namespace MWGui void onCancelButtonClicked(MyGUI::Widget* _sender); void onCreateButtonClicked(MyGUI::Widget* _sender); void onIngredientSelected(MyGUI::Widget* _sender); + void onApparatusSelected(MyGUI::Widget* _sender); void onAccept(MyGUI::EditBox*); void onIncreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onDecreaseButtonPressed(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); @@ -84,7 +88,8 @@ namespace MWGui void onSelectedItem(int index); - void removeIngredient(MyGUI::Widget* ingredient); + void onItemSelected(MWWorld::Ptr item); + void onItemCancel(); void createPotions(int count); diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp index 78f865bb556..fe87d7e38af 100644 --- a/apps/openmw/mwgui/itemselection.hpp +++ b/apps/openmw/mwgui/itemselection.hpp @@ -32,6 +32,8 @@ namespace MWGui void setCategory(int category); void setFilter(int filter); + SortFilterItemModel* getSortModel() { return mSortModel; } + private: ItemView* mItemView; SortFilterItemModel* mSortModel; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 4a5e02a8815..e5b87abff73 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -174,6 +174,7 @@ namespace MWGui : mCategory(Category_All) , mFilter(0) , mSortByType(true) + , mApparatusTypeFilter(-1) { mSourceModel = std::move(sourceModel); } @@ -311,6 +312,16 @@ namespace MWGui return false; } + if ((mFilter & Filter_OnlyAlchemyTools)) + { + if (base.getType() != ESM::Apparatus::sRecordId) + return false; + + int32_t apparatusType = base.get()->mBase->mData.mType; + if (mApparatusTypeFilter >= 0 && apparatusType != mApparatusTypeFilter) + return false; + } + std::string compare = Utf8Stream::lowerCaseUtf8(item.mBase.getClass().getName(item.mBase)); if (compare.find(mNameFilter) == std::string::npos) return false; @@ -352,6 +363,11 @@ namespace MWGui mEffectFilter = Utf8Stream::lowerCaseUtf8(filter); } + void SortFilterItemModel::setApparatusTypeFilter(const int32_t type) + { + mApparatusTypeFilter = type; + } + void SortFilterItemModel::update() { mSourceModel->update(); diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index 66a22b3afa8..d8490f7db12 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -27,6 +27,7 @@ namespace MWGui void setFilter(int filter); void setNameFilter(const std::string& filter); void setEffectFilter(const std::string& filter); + void setApparatusTypeFilter(const int32_t type); /// Use ItemStack::Type for sorting? void setSortByType(bool sort) { mSortByType = sort; } @@ -49,6 +50,7 @@ namespace MWGui static constexpr int Filter_OnlyRepairable = (1 << 5); static constexpr int Filter_OnlyRechargable = (1 << 6); static constexpr int Filter_OnlyRepairTools = (1 << 7); + static constexpr int Filter_OnlyAlchemyTools = (1 << 8); private: std::vector mItems; @@ -59,6 +61,7 @@ namespace MWGui int mFilter; bool mSortByType; + int32_t mApparatusTypeFilter; // filter by apparatus type std::string mNameFilter; // filter by item name std::string mEffectFilter; // filter by magic effect }; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 995d55759b2..5f8ffc17500 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -368,6 +368,8 @@ void MWMechanics::Alchemy::setAlchemist(const MWWorld::Ptr& npc) mTools.resize(4); + std::vector prevTools(mTools); + std::fill(mTools.begin(), mTools.end(), MWWorld::Ptr()); mEffects.clear(); @@ -384,6 +386,12 @@ void MWMechanics::Alchemy::setAlchemist(const MWWorld::Ptr& npc) if (type < 0 || type >= static_cast(mTools.size())) throw std::runtime_error("invalid apparatus type"); + if (prevTools[type] == *iter) + mTools[type] = *iter; // prefer the previous tool if still in the container + + if (!mTools[type].isEmpty() && !prevTools[type].isEmpty() && mTools[type] == prevTools[type]) + continue; + if (!mTools[type].isEmpty()) if (ref->mBase->mData.mQuality <= mTools[type].get()->mBase->mData.mQuality) continue; @@ -415,7 +423,6 @@ MWMechanics::Alchemy::TIngredientsIterator MWMechanics::Alchemy::endIngredients( void MWMechanics::Alchemy::clear() { mAlchemist = MWWorld::Ptr(); - mTools.clear(); mIngredients.clear(); mEffects.clear(); setPotionName(""); @@ -452,15 +459,33 @@ int MWMechanics::Alchemy::addIngredient(const MWWorld::Ptr& ingredient) return slot; } -void MWMechanics::Alchemy::removeIngredient(int index) +void MWMechanics::Alchemy::removeIngredient(size_t index) { - if (index >= 0 && index < static_cast(mIngredients.size())) + if (index < mIngredients.size()) { mIngredients[index] = MWWorld::Ptr(); updateEffects(); } } +void MWMechanics::Alchemy::addApparatus(const MWWorld::Ptr& apparatus) +{ + int32_t slot = apparatus.get()->mBase->mData.mType; + + mTools[slot] = apparatus; + + updateEffects(); +} + +void MWMechanics::Alchemy::removeApparatus(size_t index) +{ + if (index < mTools.size()) + { + mTools[index] = MWWorld::Ptr(); + updateEffects(); + } +} + MWMechanics::Alchemy::TEffectsIterator MWMechanics::Alchemy::beginEffects() const { return mEffects.begin(); diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index ab6225e544a..1b76e400f50 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -119,9 +119,15 @@ namespace MWMechanics /// \return Slot index or -1, if adding failed because of no free slot or the ingredient type being /// listed already. - void removeIngredient(int index); + void addApparatus(const MWWorld::Ptr& apparatus); + ///< Add apparatus into the appropriate slot. + + void removeIngredient(size_t index); ///< Remove ingredient from slot (calling this function on an empty slot is a no-op). + void removeApparatus(size_t index); + ///< Remove apparatus from slot. + std::string suggestPotionName(); ///< Suggest a name for the potion, based on the current effects