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)
Finbar Crago(finbar-crago)
Florian Weber (Florianjw)
Frédéric Chardon (fr3dz10)
Gaëtan Dezeiraud (Brouilles)
Gašper Sedej
Gijsbert ter Horst (Ghostbird)

@ -264,6 +264,7 @@
Feature #5219: Impelement TestCells console command
Feature #5224: Handle NiKeyframeController for NiTriShape
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 #4695: Optimize Distant Terrain memory consumption
Task #4789: Optimize cell transitions

@ -3,6 +3,7 @@
#include <MyGUI_Gui.h>
#include <MyGUI_Button.h>
#include <MyGUI_EditBox.h>
#include <MyGUI_ComboBox.h>
#include <MyGUI_ControllerManager.h>
#include <MyGUI_ControllerRepeatClick.h>
@ -17,6 +18,7 @@
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include <MyGUI_Macros.h>
#include <components/esm/records.hpp>
#include "inventoryitemmodel.hpp"
@ -29,6 +31,7 @@ namespace MWGui
{
AlchemyWindow::AlchemyWindow()
: WindowBase("openmw_alchemy_window.layout")
, mModel(nullptr)
, mSortModel(nullptr)
, mAlchemy(new MWMechanics::Alchemy())
, mApparatus (4)
@ -50,6 +53,8 @@ namespace MWGui
getWidget(mDecreaseButton, "DecreaseButton");
getWidget(mNameEdit, "NameEdit");
getWidget(mItemView, "ItemView");
getWidget(mFilterValue, "FilterValue");
getWidget(mFilterType, "FilterType");
mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged);
mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
@ -72,6 +77,9 @@ namespace MWGui
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked);
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();
}
@ -136,16 +144,110 @@ namespace MWGui
removeIngredient(mIngredients[i]);
}
updateFilters();
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()
{
mAlchemy->clear();
mAlchemy->setAlchemist (MWMechanics::getPlayer());
InventoryItemModel* model = new InventoryItemModel(MWMechanics::getPlayer());
mSortModel = new SortFilterItemModel(model);
mModel = new InventoryItemModel(MWMechanics::getPlayer());
mSortModel = new SortFilterItemModel(mModel);
mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients);
mItemView->setModel (mSortModel);
mItemView->resetScrollBars();
@ -167,6 +269,7 @@ namespace MWGui
}
update();
initFilter();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
}

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

@ -23,6 +23,8 @@
#include "../mwworld/nullaction.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwmechanics/alchemy.hpp"
namespace
{
bool compareType(const std::string& type1, const std::string& type2)
@ -151,6 +153,8 @@ namespace MWGui
: mCategory(Category_All)
, mFilter(0)
, mSortByType(true)
, mNameFilter("")
, mEffectFilter("")
{
mSourceModel = sourceModel;
}
@ -199,8 +203,39 @@ namespace MWGui
if (!(category & mCategory))
return false;
if ((mFilter & Filter_OnlyIngredients) && base.getTypeName() != typeid(ESM::Ingredient).name())
if (mFilter & Filter_OnlyIngredients)
{
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))
return false;
if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name()
@ -286,6 +321,11 @@ namespace MWGui
mNameFilter = Misc::StringUtils::lowerCase(filter);
}
void SortFilterItemModel::setEffectFilter (const std::string& filter)
{
mEffectFilter = Misc::StringUtils::lowerCase(filter);
}
void SortFilterItemModel::update()
{
mSourceModel->update();

@ -26,6 +26,7 @@ namespace MWGui
void setCategory (int category);
void setFilter (int filter);
void setNameFilter (const std::string& filter);
void setEffectFilter (const std::string& filter);
/// Use ItemStack::Type for sorting?
void setSortByType(bool sort) { mSortByType = sort; }
@ -60,6 +61,7 @@ namespace MWGui
bool mSortByType;
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(
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
/// adjust the skills of the alchemist accordingly.
/// \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 -->
<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 -->
@ -71,6 +70,21 @@
<Widget type="Widget" skin="" position="4 4 316 122" name="CreatedEffects" align="HStretch"/>
</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 -->

Loading…
Cancel
Save