Add a filter in the alchemy window.

A button allow to switch between ingredient name and magic effect.
Switching reset the filter.
The default filter can be set in the layout file.

The player can show only ingredients whose either name or effect
match the filter
Only effect that are known to the player (via alchemy skill) are
taken into account
pull/578/head
fredzio 6 years ago
parent d8e1a6b286
commit e1b5dd97b8

@ -74,6 +74,7 @@ Programmers
Fil Krynicki (filkry) Fil Krynicki (filkry)
Finbar Crago(finbar-crago) Finbar Crago(finbar-crago)
Florian Weber (Florianjw) Florian Weber (Florianjw)
Frédéric Chardon (fr3dz10)
Gaëtan Dezeiraud (Brouilles) Gaëtan Dezeiraud (Brouilles)
Gašper Sedej Gašper Sedej
Gijsbert ter Horst (Ghostbird) Gijsbert ter Horst (Ghostbird)

@ -264,6 +264,7 @@
Feature #5219: Impelement TestCells console command Feature #5219: Impelement TestCells console command
Feature #5224: Handle NiKeyframeController for NiTriShape Feature #5224: Handle NiKeyframeController for NiTriShape
Feature #5304: Morrowind-style bump-mapping Feature #5304: Morrowind-style bump-mapping
Feature #5314: Ingredient filter in the alchemy window
Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4686: Upgrade media decoder to a more current FFmpeg API
Task #4695: Optimize Distant Terrain memory consumption Task #4695: Optimize Distant Terrain memory consumption
Task #4789: Optimize cell transitions Task #4789: Optimize cell transitions

@ -3,6 +3,7 @@
#include <MyGUI_Gui.h> #include <MyGUI_Gui.h>
#include <MyGUI_Button.h> #include <MyGUI_Button.h>
#include <MyGUI_EditBox.h> #include <MyGUI_EditBox.h>
#include <MyGUI_ComboBox.h>
#include <MyGUI_ControllerManager.h> #include <MyGUI_ControllerManager.h>
#include <MyGUI_ControllerRepeatClick.h> #include <MyGUI_ControllerRepeatClick.h>
@ -17,6 +18,7 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include <MyGUI_Macros.h>
#include <components/esm/records.hpp> #include <components/esm/records.hpp>
#include "inventoryitemmodel.hpp" #include "inventoryitemmodel.hpp"
@ -29,6 +31,7 @@ namespace MWGui
{ {
AlchemyWindow::AlchemyWindow() AlchemyWindow::AlchemyWindow()
: WindowBase("openmw_alchemy_window.layout") : WindowBase("openmw_alchemy_window.layout")
, mModel(nullptr)
, mSortModel(nullptr) , mSortModel(nullptr)
, mAlchemy(new MWMechanics::Alchemy()) , mAlchemy(new MWMechanics::Alchemy())
, mApparatus (4) , mApparatus (4)
@ -50,6 +53,8 @@ namespace MWGui
getWidget(mDecreaseButton, "DecreaseButton"); getWidget(mDecreaseButton, "DecreaseButton");
getWidget(mNameEdit, "NameEdit"); getWidget(mNameEdit, "NameEdit");
getWidget(mItemView, "ItemView"); getWidget(mItemView, "ItemView");
getWidget(mFilterValue, "FilterValue");
getWidget(mFilterType, "FilterType");
mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged); mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged);
mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept); mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
@ -72,6 +77,9 @@ namespace MWGui
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked);
mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept); mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
mFilterValue->eventComboChangePosition += MyGUI::newDelegate(this, &AlchemyWindow::onFilterChanged);
mFilterValue->eventEditTextChange += MyGUI::newDelegate(this, &AlchemyWindow::onFilterEdited);
mFilterType->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::switchFilterType);
center(); center();
} }
@ -136,16 +144,110 @@ namespace MWGui
removeIngredient(mIngredients[i]); removeIngredient(mIngredients[i]);
} }
updateFilters();
update(); update();
} }
void AlchemyWindow::initFilter()
{
auto const& wm = MWBase::Environment::get().getWindowManager();
auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients");
auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects");
if (mFilterType->getCaption() == ingredient)
mCurrentFilter = FilterType::ByName;
else
mCurrentFilter = FilterType::ByEffect;
updateFilters();
mFilterValue->clearIndexSelected();
updateFilters();
}
void AlchemyWindow::switchFilterType(MyGUI::Widget* _sender)
{
auto const& wm = MWBase::Environment::get().getWindowManager();
auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients");
auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects");
auto *button = _sender->castType<MyGUI::Button>();
if (button->getCaption() == ingredient)
{
button->setCaption(effect);
mCurrentFilter = FilterType::ByEffect;
}
else
{
button->setCaption(ingredient);
mCurrentFilter = FilterType::ByName;
}
mSortModel->setNameFilter({});
mSortModel->setEffectFilter({});
mFilterValue->clearIndexSelected();
updateFilters();
mItemView->update();
}
void AlchemyWindow::updateFilters()
{
std::set<std::string> itemNames, itemEffects;
for (size_t i = 0; i < mModel->getItemCount(); ++i)
{
auto const& base = mModel->getItem(i).mBase;
if (base.getTypeName() != typeid(ESM::Ingredient).name())
continue;
itemNames.insert(base.getClass().getName(base));
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
auto const alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy);
auto const effects = MWMechanics::Alchemy::effectsDescription(base, alchemySkill);
itemEffects.insert(effects.begin(), effects.end());
}
mFilterValue->removeAllItems();
auto const addItems = [&](auto const& container)
{
for (auto const& item : container)
mFilterValue->addItem(item);
};
switch (mCurrentFilter)
{
case FilterType::ByName: addItems(itemNames); break;
case FilterType::ByEffect: addItems(itemEffects); break;
}
}
void AlchemyWindow::applyFilter(const std::string& filter)
{
switch (mCurrentFilter)
{
case FilterType::ByName: mSortModel->setNameFilter(filter); break;
case FilterType::ByEffect: mSortModel->setEffectFilter(filter); break;
}
mItemView->update();
}
void AlchemyWindow::onFilterChanged(MyGUI::ComboBox* _sender, size_t _index)
{
// ignore spurious event fired when one edit the content after selection.
// onFilterEdited will handle it.
if (_index != MyGUI::ITEM_NONE)
applyFilter(_sender->getItemNameAt(_index));
}
void AlchemyWindow::onFilterEdited(MyGUI::EditBox* _sender)
{
applyFilter(_sender->getCaption());
}
void AlchemyWindow::onOpen() void AlchemyWindow::onOpen()
{ {
mAlchemy->clear(); mAlchemy->clear();
mAlchemy->setAlchemist (MWMechanics::getPlayer()); mAlchemy->setAlchemist (MWMechanics::getPlayer());
InventoryItemModel* model = new InventoryItemModel(MWMechanics::getPlayer()); mModel = new InventoryItemModel(MWMechanics::getPlayer());
mSortModel = new SortFilterItemModel(model); mSortModel = new SortFilterItemModel(mModel);
mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients); mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients);
mItemView->setModel (mSortModel); mItemView->setModel (mSortModel);
mItemView->resetScrollBars(); mItemView->resetScrollBars();
@ -167,6 +269,7 @@ namespace MWGui
} }
update(); update();
initFilter();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
} }

@ -5,7 +5,9 @@
#include <vector> #include <vector>
#include <MyGUI_ControllerItem.h> #include <MyGUI_ControllerItem.h>
#include <MyGUI_ComboBox.h>
#include <components/widgets/box.hpp>
#include <components/widgets/numericeditbox.hpp> #include <components/widgets/numericeditbox.hpp>
#include "windowbase.hpp" #include "windowbase.hpp"
@ -19,6 +21,7 @@ namespace MWGui
{ {
class ItemView; class ItemView;
class ItemWidget; class ItemWidget;
class InventoryItemModel;
class SortFilterItemModel; class SortFilterItemModel;
class AlchemyWindow : public WindowBase class AlchemyWindow : public WindowBase
@ -36,8 +39,11 @@ namespace MWGui
static const float sCountChangeInterval; // in seconds static const float sCountChangeInterval; // in seconds
std::string mSuggestedPotionName; std::string mSuggestedPotionName;
enum class FilterType { ByName, ByEffect };
FilterType mCurrentFilter;
ItemView* mItemView; ItemView* mItemView;
InventoryItemModel* mModel;
SortFilterItemModel* mSortModel; SortFilterItemModel* mSortModel;
MyGUI::Button* mCreateButton; MyGUI::Button* mCreateButton;
@ -47,6 +53,8 @@ namespace MWGui
MyGUI::Button* mIncreaseButton; MyGUI::Button* mIncreaseButton;
MyGUI::Button* mDecreaseButton; MyGUI::Button* mDecreaseButton;
Gui::AutoSizedButton* mFilterType;
MyGUI::ComboBox* mFilterValue;
MyGUI::EditBox* mNameEdit; MyGUI::EditBox* mNameEdit;
Gui::NumericEditBox* mBrewCountEdit; Gui::NumericEditBox* mBrewCountEdit;
@ -60,6 +68,13 @@ namespace MWGui
void onCountValueChanged(int value); void onCountValueChanged(int value);
void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller); void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller);
void applyFilter(const std::string& filter);
void initFilter();
void onFilterChanged(MyGUI::ComboBox* _sender, size_t _index);
void onFilterEdited(MyGUI::EditBox* _sender);
void switchFilterType(MyGUI::Widget* _sender);
void updateFilters();
void addRepeatController(MyGUI::Widget* widget); void addRepeatController(MyGUI::Widget* widget);
void onIncreaseButtonTriggered(); void onIncreaseButtonTriggered();

@ -23,6 +23,8 @@
#include "../mwworld/nullaction.hpp" #include "../mwworld/nullaction.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwmechanics/alchemy.hpp"
namespace namespace
{ {
bool compareType(const std::string& type1, const std::string& type2) bool compareType(const std::string& type1, const std::string& type2)
@ -151,6 +153,8 @@ namespace MWGui
: mCategory(Category_All) : mCategory(Category_All)
, mFilter(0) , mFilter(0)
, mSortByType(true) , mSortByType(true)
, mNameFilter("")
, mEffectFilter("")
{ {
mSourceModel = sourceModel; mSourceModel = sourceModel;
} }
@ -199,8 +203,39 @@ namespace MWGui
if (!(category & mCategory)) if (!(category & mCategory))
return false; return false;
if ((mFilter & Filter_OnlyIngredients) && base.getTypeName() != typeid(ESM::Ingredient).name()) if (mFilter & Filter_OnlyIngredients)
return false; {
if (base.getTypeName() != typeid(ESM::Ingredient).name())
return false;
if (!mNameFilter.empty() && !mEffectFilter.empty())
throw std::logic_error("name and magic effect filter are mutually exclusive");
if (!mNameFilter.empty())
{
const auto itemName = Misc::StringUtils::lowerCase(base.getClass().getName(base));
return itemName.find(mNameFilter) != std::string::npos;
}
if (!mEffectFilter.empty())
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
const auto alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy);
const auto effects = MWMechanics::Alchemy::effectsDescription(base, alchemySkill);
for (const auto& effect : effects)
{
const auto ciEffect = Misc::StringUtils::lowerCase(effect);
if (ciEffect.find(mEffectFilter) != std::string::npos)
return true;
}
return false;
}
return true;
}
if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted)) if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted))
return false; return false;
if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name() if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name()
@ -286,6 +321,11 @@ namespace MWGui
mNameFilter = Misc::StringUtils::lowerCase(filter); mNameFilter = Misc::StringUtils::lowerCase(filter);
} }
void SortFilterItemModel::setEffectFilter (const std::string& filter)
{
mEffectFilter = Misc::StringUtils::lowerCase(filter);
}
void SortFilterItemModel::update() void SortFilterItemModel::update()
{ {
mSourceModel->update(); mSourceModel->update();

@ -26,6 +26,7 @@ namespace MWGui
void setCategory (int category); void setCategory (int category);
void setFilter (int filter); void setFilter (int filter);
void setNameFilter (const std::string& filter); void setNameFilter (const std::string& filter);
void setEffectFilter (const std::string& filter);
/// Use ItemStack::Type for sorting? /// Use ItemStack::Type for sorting?
void setSortByType(bool sort) { mSortByType = sort; } void setSortByType(bool sort) { mSortByType = sort; }
@ -60,6 +61,7 @@ namespace MWGui
bool mSortByType; bool mSortByType;
std::string mNameFilter; // filter by item name std::string mNameFilter; // filter by item name
std::string mEffectFilter; // filter by magic effect
}; };
} }

@ -554,3 +554,37 @@ std::string MWMechanics::Alchemy::suggestPotionName()
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find( return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
ESM::MagicEffect::effectIdToString(effectId))->mValue.getString(); ESM::MagicEffect::effectIdToString(effectId))->mValue.getString();
} }
std::vector<std::string> MWMechanics::Alchemy::effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySkill)
{
std::vector<std::string> effects;
const auto& item = ptr.get<ESM::Ingredient>()->mBase;
const auto& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
const static auto fWortChanceValue = gmst.find("fWortChanceValue")->mValue.getFloat();
const auto& data = item->mData;
for (auto i = 0; i < 4; ++i)
{
const auto effectID = data.mEffectID[i];
const auto skillID = data.mSkills[i];
const auto attributeID = data.mAttributes[i];
if (alchemySkill < fWortChanceValue * (i + 1))
break;
if (effectID != -1)
{
std::string effect = gmst.find(ESM::MagicEffect::effectIdToString(effectID))->mValue.getString();
if (skillID != -1)
effect += " " + gmst.find(ESM::Skill::sSkillNameIds[skillID])->mValue.getString();
else if (attributeID != -1)
effect += " " + gmst.find(ESM::Attribute::sGmstAttributeIds[attributeID])->mValue.getString();
effects.push_back(effect);
}
}
return effects;
}

@ -131,6 +131,8 @@ namespace MWMechanics
///< Try to create potions from the ingredients, place them in the inventory of the alchemist and ///< Try to create potions from the ingredients, place them in the inventory of the alchemist and
/// adjust the skills of the alchemist accordingly. /// adjust the skills of the alchemist accordingly.
/// \param name must not be an empty string, or Result_NoName is returned /// \param name must not be an empty string, or Result_NoName is returned
static std::vector<std::string> effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySKill);
}; };
} }

@ -57,8 +57,7 @@
<!-- Available Ingredients --> <!-- Available Ingredients -->
<Widget type="ItemView" skin="MW_ItemView" position="10 206 552 158" name="ItemView" align="Left Top Stretch"/> <Widget type="ItemView" skin="MW_ItemView" position="10 206 552 132" name="ItemView" align="Left Top Stretch"/>
<!-- Created Effects --> <!-- Created Effects -->
@ -71,6 +70,21 @@
<Widget type="Widget" skin="" position="4 4 316 122" name="CreatedEffects" align="HStretch"/> <Widget type="Widget" skin="" position="4 4 316 122" name="CreatedEffects" align="HStretch"/>
</Widget> </Widget>
<!-- Filters -->
<Widget type="HBox" skin="MW_Box" position="10 333 552 39" align="Bottom HStretch">
<Property key="Padding" value="5"/>
<Property key="Spacing" value="10"/>
<Widget type="AutoSizedButton" skin="MW_Button" position="10 2 1 1" name="FilterType">
<Property key="Caption" value="#{sIngredients}"/> <!-- default value, can be either sIngredients of sMagicEffects -->
</Widget>
<Widget type="ComboBox" skin="MW_ComboBox" position="0 2 0 24" name="FilterValue">
<UserString key="HStretch" value="true"/>
<Property key="ModeDrop" value="false"/>
</Widget>
</Widget>
<!-- Buttons --> <!-- Buttons -->

Loading…
Cancel
Save