Merge branch 'alchemyfilter' into 'master'

Filters for ingredients & effects in alchemy window

See merge request OpenMW/openmw!106
pull/2728/head
Andrei Kortunov 5 years ago
commit 6de1c0d0d3

@ -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())
return false;
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