diff options
Diffstat (limited to 'source/frontend/StarCraftingInterface.cpp')
-rw-r--r-- | source/frontend/StarCraftingInterface.cpp | 773 |
1 files changed, 773 insertions, 0 deletions
diff --git a/source/frontend/StarCraftingInterface.cpp b/source/frontend/StarCraftingInterface.cpp new file mode 100644 index 0000000..01f7e3c --- /dev/null +++ b/source/frontend/StarCraftingInterface.cpp @@ -0,0 +1,773 @@ +#include "StarCraftingInterface.hpp" +#include "StarJsonExtra.hpp" +#include "StarGuiReader.hpp" +#include "StarLexicalCast.hpp" +#include "StarRoot.hpp" +#include "StarItemTooltip.hpp" +#include "StarPlayer.hpp" +#include "StarContainerEntity.hpp" +#include "StarWorldClient.hpp" +#include "StarPlayerBlueprints.hpp" +#include "StarButtonWidget.hpp" +#include "StarPaneManager.hpp" +#include "StarPortraitWidget.hpp" +#include "StarLabelWidget.hpp" +#include "StarTextBoxWidget.hpp" +#include "StarImageWidget.hpp" +#include "StarListWidget.hpp" +#include "StarImageStretchWidget.hpp" +#include "StarItemSlotWidget.hpp" +#include "StarConfiguration.hpp" +#include "StarObjectItem.hpp" +#include "StarAssets.hpp" +#include "StarItemDatabase.hpp" +#include "StarObjectDatabase.hpp" +#include "StarPlayerInventory.hpp" +#include "StarPlayerLog.hpp" +#include "StarMixer.hpp" + +namespace Star { + +CraftingPane::CraftingPane(WorldClientPtr worldClient, PlayerPtr player, Json const& settings, EntityId sourceEntityId) { + m_worldClient = move(worldClient); + m_player = move(player); + m_blueprints = m_player->blueprints(); + m_recipeAutorefreshCooldown = 0; + m_sourceEntityId = sourceEntityId; + + auto assets = Root::singleton().assets(); + // get the config data for this crafting pane, default to "bare hands" crafting + auto baseConfig = settings.get("config", "/interface/windowconfig/crafting.config"); + m_settings = jsonMerge(assets->fetchJson(baseConfig), settings); + m_filter = StringSet::from(jsonToStringList(m_settings.get("filter", JsonArray()))); + + GuiReader reader; + reader.registerCallback("spinCount.up", [=](Widget*) { + if (m_count < maxCraft()) + m_count++; + else + m_count = 1; + countChanged(); + }); + + reader.registerCallback("spinCount.down", [=](Widget*) { + if (m_count > 1) + m_count--; + else + m_count = std::max(maxCraft(), 1); + countChanged(); + }); + + reader.registerCallback("tbSpinCount", [=](Widget*) { countTextChanged(); }); + + reader.registerCallback("close", [=](Widget*) { dismiss(); }); + + reader.registerCallback("btnCraft", [=](Widget*) { toggleCraft(); }); + reader.registerCallback("btnStopCraft", [=](Widget*) { toggleCraft(); }); + + reader.registerCallback("btnFilterHaveMaterials", [=](Widget*) { + Root::singleton().configuration()->setPath("crafting.filterHaveMaterials", m_filterHaveMaterials->isChecked()); + updateAvailableRecipes(); + }); + + reader.registerCallback("filter", [=](Widget*) { updateAvailableRecipes(); }); + + reader.registerCallback("categories", [=](Widget*) { updateAvailableRecipes(); }); + + reader.registerCallback("rarities", [=](Widget*) { updateAvailableRecipes(); }); + + reader.registerCallback("btnUpgrade", [=](Widget*) { upgradeTable(); }); + + // this is where the GUI gets built and the buttons begin to have existence. + // all possible callbacks must exist by this point + + Json paneLayout = m_settings.get("paneLayout"); + paneLayout = jsonMerge(paneLayout, m_settings.get("paneLayoutOverride", {})); + reader.construct(paneLayout, this); + + if (auto upgradeButton = fetchChild<ButtonWidget>("btnUpgrade")) { + upgradeButton->disable(); + Maybe<JsonArray> recipeData = m_settings.optArray("upgradeMaterials"); + + // create a recipe out of the listed upgrade materials. + // for ease of creating a tooltip later. + if (recipeData) { + m_upgradeRecipe = ItemRecipe(); + for (auto ingredient : *recipeData) + m_upgradeRecipe->inputs.append(ItemDescriptor(ingredient.getString("item"), ingredient.getUInt("count"), {})); + upgradeButton->setVisibility(true); + } else { + upgradeButton->setVisibility(false); + } + } + + m_guiList = fetchChild<ListWidget>("scrollArea.itemList"); + m_textBox = fetchChild<TextBoxWidget>("tbSpinCount"); + + m_filterHaveMaterials = fetchChild<ButtonWidget>("btnFilterHaveMaterials"); + if (m_filterHaveMaterials) + m_filterHaveMaterials->setChecked(Root::singleton().configuration()->getPath("crafting.filterHaveMaterials").toBool()); + + fetchChild<ButtonWidget>("btnCraft")->disable(); + if (auto spinCountUp = fetchChild<ButtonWidget>("spinCount.up")) + spinCountUp->disable(); + if (auto spinCountDown = fetchChild<ButtonWidget>("spinCount.down")) + spinCountDown->disable(); + + m_displayedRecipe = NPos; + updateAvailableRecipes(); + + m_crafting = false; + m_count = 1; + countChanged(); + + if (m_settings.getBool("titleFromEntity", false) && sourceEntityId != NullEntityId) { + auto entity = m_worldClient->entity(sourceEntityId); + + if (auto container = as<ContainerEntity>(entity)) { + if (container->iconItem()) { + auto itemDatabase = Root::singleton().itemDatabase(); + auto iconItem = itemDatabase->item(container->iconItem()); + auto icon = make_shared<ItemSlotWidget>(iconItem, "/interface/inventory/portrait.png"); + String title = this->title(); + if (title.empty()) + title = container->containerDescription(); + String subTitle = this->subTitle(); + if (subTitle.empty()) + subTitle = container->containerSubTitle(); + icon->showRarity(false); + setTitle(icon, title, subTitle); + } + } + if (auto portaitEntity = as<PortraitEntity>(entity)) { + auto portrait = make_shared<PortraitWidget>(portaitEntity, PortraitMode::Bust); + portrait->setIconMode(); + String title = this->title(); + if (title.empty()) + title = portaitEntity->name(); + String subTitle = this->subTitle(); + setTitle(portrait, title, subTitle); + } + } +} + +void CraftingPane::displayed() { + Pane::displayed(); + + if (auto filterWidget = fetchChild<TextBoxWidget>("filter")) { + filterWidget->setText(""); + filterWidget->blur(); + } + + updateAvailableRecipes(); + + // unlock any recipes specified + if (auto recipeUnlocks = m_settings.opt("initialRecipeUnlocks")) { + for (String itemName : jsonToStringList(*recipeUnlocks)) + m_player->addBlueprint(ItemDescriptor(itemName)); + } +} + +void CraftingPane::dismissed() { + stopCrafting(); + Pane::dismissed(); + m_itemCache.clear(); +} + +PanePtr CraftingPane::createTooltip(Vec2I const& screenPosition) { + for (size_t i = 0; i < m_guiList->numChildren(); ++i) { + auto entry = m_guiList->itemAt(i); + if (entry->getChildAt(screenPosition)) { + auto& recipe = m_recipesWidgetMap.getLeft(entry); + return setupTooltip(recipe); + } + } + + if (WidgetPtr child = getChildAt(screenPosition)) { + if (child->name() == "btnUpgrade") { + if (m_upgradeRecipe) + return setupTooltip(*m_upgradeRecipe); + } + } + + return {}; +} + +EntityId CraftingPane::sourceEntityId() const { + return m_sourceEntityId; +} + +void CraftingPane::upgradeTable() { + if (m_sourceEntityId != NullEntityId) { + // Checks that the upgrade path exists + if (m_upgradeRecipe) { + if (m_player->isAdmin() || ItemDatabase::canMakeRecipe(*m_upgradeRecipe, m_player->inventory()->availableItems(), m_player->inventory()->availableCurrencies())) { + if (!m_player->isAdmin()) + consumeIngredients(*m_upgradeRecipe, 1); + + // upgrade the old table + m_worldClient->sendEntityMessage(m_sourceEntityId, "requestUpgrade"); + + // unlock any recipes specified + if (auto recipeUnlocks = m_settings.opt("upgradeRecipeUnlocks")) { + for (String itemName : jsonToStringList(*recipeUnlocks)) + m_player->addBlueprint(ItemDescriptor(itemName)); + } + + // this closes the interface window + dismiss(); + } + } + } +} + +size_t CraftingPane::itemCount(List<ItemPtr> const& store, ItemDescriptor const& item) { + auto itemDb = Root::singleton().itemDatabase(); + return itemDb->getCountOfItem(store, item); +} + +void CraftingPane::update() { + // shut down if we can't reach the table anymore. + if (m_sourceEntityId != NullEntityId) { + auto sourceEntity = as<TileEntity>(m_worldClient->entity(m_sourceEntityId)); + if (!sourceEntity || !m_worldClient->playerCanReachEntity(m_sourceEntityId) || !sourceEntity->isInteractive()) { + dismiss(); + return; + } + } + + // similarly if the player is dead + if (m_player->isDead()) { + dismiss(); + return; + } + + // has the selected recipe changed ? + bool changedHighlight = (m_displayedRecipe != m_guiList->selectedItem()); + + if (changedHighlight) { + stopCrafting(); // TODO: allow viewing other recipes without interrupting crafting + + m_displayedRecipe = m_guiList->selectedItem(); + countTextChanged(); + + auto recipe = recipeFromSelectedWidget(); + + if (recipe.isNull()) { + fetchChild<Widget>("description")->removeAllChildren(); + } else { + auto description = fetchChild<Widget>("description"); + description->removeAllChildren(); + + auto item = Root::singleton().itemDatabase()->item(recipe.output); + ItemTooltipBuilder::buildItemDescription(description, item); + } + } + + // crafters gonna craft + while (m_crafting && m_craftTimer.wrapTick()) { + craft(1); + } + + // update crafting icon, progress and buttons + if (auto currentRecipeIcon = fetchChild<ItemSlotWidget>("currentRecipeIcon")) { + auto recipe = recipeFromSelectedWidget(); + if (recipe.isNull()) { + currentRecipeIcon->setItem(nullptr); + } else { + auto single = recipe.output.singular(); + ItemPtr item = m_itemCache[single]; + currentRecipeIcon->setItem(item); + + if (m_crafting) + currentRecipeIcon->setProgress(1.0f - m_craftTimer.percent()); + else + currentRecipeIcon->setProgress(1.0f); + } + } + + --m_recipeAutorefreshCooldown; + + // changed recipe or auto update time + if (changedHighlight || (m_recipeAutorefreshCooldown <= 0)) { + updateAvailableRecipes(); + updateCraftButtons(); + } + + setLabel("lblPlayerMoney", strf("%s", (int)m_player->currency("money"))); + + Pane::update(); +} + +void CraftingPane::updateCraftButtons() { + auto normalizedBag = m_player->inventory()->availableItems(); + auto availableCurrencies = m_player->inventory()->availableCurrencies(); + + auto recipe = recipeFromSelectedWidget(); + bool recipeAvailable = !recipe.isNull() && (m_player->isAdmin() || ItemDatabase::canMakeRecipe(recipe, normalizedBag, availableCurrencies)); + + fetchChild<ButtonWidget>("btnCraft")->setEnabled(recipeAvailable); + if (auto spinCountUp = fetchChild<ButtonWidget>("spinCount.up")) + spinCountUp->setEnabled(recipeAvailable); + if (auto spinCountDown = fetchChild<ButtonWidget>("spinCount.down")) + spinCountDown->setEnabled(recipeAvailable); + + if (auto stopCraftButton = fetchChild<ButtonWidget>("btnStopCraft")) { + stopCraftButton->setVisibility(m_crafting); + fetchChild<ButtonWidget>("btnCraft")->setVisibility(!m_crafting); + } + + if (auto upgradeButton = fetchChild<ButtonWidget>("btnUpgrade")) { + bool canUpgrade = (m_upgradeRecipe && (m_player->isAdmin() || ItemDatabase::canMakeRecipe(*m_upgradeRecipe, normalizedBag, availableCurrencies))); + upgradeButton->setEnabled(canUpgrade); + } +} + +void CraftingPane::updateAvailableRecipes() { + m_recipeAutorefreshCooldown = 30; + + StringSet categoryFilter; + if (auto categoriesGroup = fetchChild<ButtonGroupWidget>("categories")) { + if (auto selectedCategories = categoriesGroup->checkedButton()) { + for (auto group : selectedCategories->data().getArray("filter")) + categoryFilter.add(group.toString()); + } + } + + HashSet<Rarity> rarityFilter; + if (auto raritiesGroup = fetchChild<ButtonGroupWidget>("rarities")) { + if (auto selectedRarities = raritiesGroup->checkedButton()) { + for (auto entry : jsonToStringSet(selectedRarities->data().getArray("rarity"))) + rarityFilter.add(RarityNames.getLeft(entry)); + } + } + + String filterText; + if (auto filterWidget = fetchChild<TextBoxWidget>("filter")) + filterText = filterWidget->getText(); + + m_recipes = determineRecipes(); + + size_t currentOffset = 0; + + ItemRecipe selectedRecipe; + if (m_guiList->selectedWidget()) + selectedRecipe = m_recipesWidgetMap.getLeft(m_guiList->selectedWidget()); + + HashMap<ItemDescriptor, uint64_t> normalizedBag = m_player->inventory()->availableItems(); + + m_guiList->clear(); + + for (auto const& recipe : m_recipes) { + auto widget = m_recipesWidgetMap.valueRight(recipe); + if (widget) { + m_guiList->addItem(widget); + } else { + widget = m_guiList->addItem(); + m_recipesWidgetMap.add(recipe, widget); + } + + setupWidget(widget, recipe, normalizedBag); + + if (selectedRecipe == recipe) { + m_guiList->setSelected(currentOffset); + } + + currentOffset++; + } +} + +void CraftingPane::setupWidget(WidgetPtr const& widget, ItemRecipe const& recipe, HashMap<ItemDescriptor, uint64_t> const& normalizedBag) { + auto& root = Root::singleton(); + + auto single = recipe.output.singular(); + ItemPtr item = m_itemCache[single]; + if (!item) { + item = root.itemDatabase()->item(single); + m_itemCache[single] = item; + } + + bool unavailable = false; + size_t price = recipe.currencyInputs.value("money", 0); + + if (!m_player->isAdmin()) { + for (auto const& p : recipe.currencyInputs) { + if (m_player->currency(p.first) < p.second) + unavailable = true; + } + + auto itemDb = Root::singleton().itemDatabase(); + for (auto const& input : recipe.inputs) { + if (itemDb->getCountOfItem(normalizedBag, input, recipe.matchInputParameters) < input.count()) + unavailable = true; + } + } + + String name = item->friendlyName(); + if (recipe.output.count() > 1) + name = strf("%s (x%s)", name, recipe.output.count()); + + auto itemName = widget->fetchChild<LabelWidget>("itemName"); + auto notcraftableoverlay = widget->fetchChild<ImageWidget>("notcraftableoverlay"); + + itemName->setText(name); + + if (unavailable) { + itemName->setColor(Color::Gray); + notcraftableoverlay->show(); + } else { + itemName->setColor(Color::White); + notcraftableoverlay->hide(); + } + + if (price > 0) { + widget->setLabel("priceLabel", strf("%s", price)); + if (auto icon = widget->fetchChild<ImageWidget>("moneyIcon")) + icon->setVisibility(true); + } else { + widget->setLabel("priceLabel", ""); + if (auto icon = widget->fetchChild<ImageWidget>("moneyIcon")) + icon->setVisibility(false); + } + + if (auto newIndicator = widget->fetchChild<ImageWidget>("newIcon")) { + if (m_blueprints->isNew(recipe.output.singular())) { + newIndicator->show(); + widget->setLabel("priceLabel", ""); + if (auto icon = widget->fetchChild<ImageWidget>("moneyIcon")) + icon->setVisibility(false); + } else { + newIndicator->hide(); + } + } + + widget->fetchChild<ItemSlotWidget>("itemIcon")->setItem(item); + widget->show(); +} + +PanePtr CraftingPane::setupTooltip(ItemRecipe const& recipe) { + auto& root = Root::singleton(); + + auto tooltip = make_shared<Pane>(); + GuiReader reader; + reader.construct(root.assets()->json("/interface/craftingtooltip/craftingtooltip.config"), tooltip.get()); + + auto guiList = tooltip->fetchChild<ListWidget>("itemList"); + guiList->clear(); + + auto normalizedBag = m_player->inventory()->availableItems(); + + auto itemDb = root.itemDatabase(); + + auto addIngredient = [guiList](ItemPtr const& item, size_t availableCount, size_t requiredCount) { + auto widget = guiList->addItem(); + widget->fetchChild<LabelWidget>("itemName")->setText(item->friendlyName()); + auto countWidget = widget->fetchChild<LabelWidget>("count"); + countWidget->setText(strf("%s/%s", availableCount, requiredCount)); + if (availableCount < requiredCount) + countWidget->setColor(Color::Red); + else + countWidget->setColor(Color::Green); + widget->fetchChild<ItemSlotWidget>("itemIcon")->setItem(item); + widget->show(); + }; + + auto currenciesConfig = root.assets()->json("/currencies.config"); + for (auto const& p : recipe.currencyInputs) { + if (p.second > 0) { + auto currencyItem = root.itemDatabase()->item(ItemDescriptor(currenciesConfig.get(p.first).getString("representativeItem"))); + addIngredient(currencyItem, m_player->currency(p.first), p.second); + } + } + + for (auto const& input : recipe.inputs) { + auto item = root.itemDatabase()->item(input.singular()); + size_t itemCount = itemDb->getCountOfItem(normalizedBag, input, recipe.matchInputParameters); + addIngredient(item, itemCount, input.count()); + } + + auto background = tooltip->fetchChild<ImageStretchWidget>("background"); + background->setSize(background->size() + Vec2I(0, guiList->size()[1])); + + auto title = tooltip->fetchChild<LabelWidget>("title"); + title->setPosition(title->position() + Vec2I(0, guiList->size()[1])); + + tooltip->setSize(background->size()); + + return tooltip; +} + +bool CraftingPane::consumeIngredients(ItemRecipe& recipe, int count) { + auto itemDb = Root::singleton().itemDatabase(); + + auto normalizedBag = m_player->inventory()->availableItems(); + auto availableCurrencies = m_player->inventory()->availableCurrencies(); + + // make sure we still have the currencies and items avaialable + for (auto const& p : recipe.currencyInputs) { + uint64_t countRequired = p.second * count; + if (availableCurrencies.value(p.first) < countRequired) { + updateAvailableRecipes(); + return false; + } + } + for (auto input : recipe.inputs) { + size_t countRequired = input.count() * count; + if (itemDb->getCountOfItem(normalizedBag, input, recipe.matchInputParameters) < countRequired) { + updateAvailableRecipes(); + return false; + } + } + + // actually consume the currencies and items + for (auto const& p : recipe.currencyInputs) { + if (p.second > 0) + m_player->inventory()->consumeCurrency(p.first, p.second * count); + } + for (auto input : recipe.inputs) { + if (count > 0) + m_player->inventory()->consumeItems(ItemDescriptor(input.name(), input.count() * count, input.parameters()), recipe.matchInputParameters); + } + return true; +} + +void CraftingPane::stopCrafting() { + if (m_craftingSound) + m_craftingSound->stop(); + m_crafting = false; +} + +void CraftingPane::toggleCraft() { + if (m_crafting) { + stopCrafting(); + } else { + auto recipe = recipeFromSelectedWidget(); + if (recipe.duration > 0 && !m_settings.getBool("disableTimer", false)) { + m_crafting = true; + m_craftTimer = GameTimer(recipe.duration); + + if (auto craftingSound = m_settings.optString("craftingSound")) { + auto assets = Root::singleton().assets(); + m_craftingSound = make_shared<AudioInstance>(*assets->audio(*craftingSound)); + m_craftingSound->setLoops(-1); + GuiContext::singleton().playAudio(m_craftingSound); + } + } else { + craft(m_count); + } + } +} + +void CraftingPane::craft(int count) { + auto& root = Root::singleton(); + + if (m_guiList->selectedItem() != NPos) { + auto recipe = recipeFromSelectedWidget(); + + if (!m_player->isAdmin() && !consumeIngredients(recipe, count)) { + stopCrafting(); + return; + } + + ItemDescriptor itemDescriptor = recipe.output; + int remainingItemCount = itemDescriptor.count() * count; + while (remainingItemCount > 0) { + auto craftedItem = root.itemDatabase()->item(itemDescriptor.singular().multiply(remainingItemCount)); + remainingItemCount -= craftedItem->count(); + m_player->giveItem(craftedItem); + + for (auto collectable : recipe.collectables) + m_player->addCollectable(collectable.first, collectable.second); + } + + m_blueprints->markAsRead(recipe.output.singular()); + } + + updateAvailableRecipes(); + + m_count -= count; + if (m_count <= 0) { + m_count = 1; + stopCrafting(); + } + countChanged(); + + updateCraftButtons(); +} + +void CraftingPane::countTextChanged() { + if (m_textBox) { + int appropriateDefaultCount = 1; + try { + if (!m_textBox->getText().replace("x", "").size()) { + m_count = appropriateDefaultCount; + } else { + m_count = clamp<int>(lexicalCast<int>(m_textBox->getText().replace("x", "")), appropriateDefaultCount, maxCraft()); + countChanged(); + } + } catch (BadLexicalCast const&) { + m_count = appropriateDefaultCount; + countChanged(); + } + } else { + m_count = 1; + } +} + +void CraftingPane::countChanged() { + if (m_textBox) + m_textBox->setText(strf("x%s", m_count), false); +} + +List<ItemRecipe> CraftingPane::determineRecipes() { + HashSet<ItemRecipe> recipes; + auto itemDb = Root::singleton().itemDatabase(); + + StringSet categoryFilter; + if (auto categoriesGroup = fetchChild<ButtonGroupWidget>("categories")) { + if (auto selectedCategories = categoriesGroup->checkedButton()) { + for (auto group : selectedCategories->data().getArray("filter")) + categoryFilter.add(group.toString()); + } + } + + HashSet<Rarity> rarityFilter; + if (auto raritiesGroup = fetchChild<ButtonGroupWidget>("rarities")) { + if (auto selectedRarities = raritiesGroup->checkedButton()) { + for (auto entry : jsonToStringSet(selectedRarities->data().getArray("rarity"))) + rarityFilter.add(RarityNames.getLeft(entry)); + } + } + + String filterText; + if (auto filterWidget = fetchChild<TextBoxWidget>("filter")) + filterText = filterWidget->getText(); + + bool filterHaveMaterials = false; + if (m_filterHaveMaterials) + filterHaveMaterials = m_filterHaveMaterials->isChecked(); + + if (m_settings.getBool("printer", false)) { + auto objectDatabase = Root::singleton().objectDatabase(); + + StringList itemList; + if (m_player->isAdmin()) + itemList = objectDatabase->allObjects(); + else + itemList = StringList::from(m_player->log()->scannedObjects()); + + filter(itemList, [objectDatabase, itemDb](String const& itemName) { + if (objectDatabase->isObject(itemName)) { + if (auto objectConfig = objectDatabase->getConfig(itemName)) + return objectConfig->printable && itemDb->hasItem(itemName); + } + return false; + }); + + float printTime = m_settings.getFloat("printTime", 0); + float printFactor = m_settings.getFloat("printCostFactor", 1.0); + for (auto itemName : itemList) { + ItemRecipe recipe; + recipe.output = ItemDescriptor(itemName, 1); + auto recipeItem = itemDb->item(recipe.output); + int itemPrice = int(recipeItem->price() * printFactor); + recipe.currencyInputs["money"] = itemPrice; + recipe.outputRarity = recipeItem->rarity(); + recipe.duration = printTime; + recipe.guiFilterString = ItemDatabase::guiFilterString(recipeItem); + recipe.groups = StringSet{objectDatabase->getConfig(itemName)->category}; + recipes.add(recipe); + } + } else if (m_settings.contains("recipes")) { + for (auto entry : m_settings.getArray("recipes")) { + if (entry.type() == Json::Type::String) + recipes.addAll(itemDb->recipesForOutputItem(entry.toString())); + else + recipes.add(itemDb->parseRecipe(entry)); + } + + if (filterHaveMaterials) + recipes.addAll(itemDb->recipesFromSubset(m_player->inventory()->availableItems(), m_player->inventory()->availableCurrencies(), take(recipes), m_filter)); + } else { + if (filterHaveMaterials) + recipes.addAll(itemDb->recipesFromBagContents(m_player->inventory()->availableItems(), m_player->inventory()->availableCurrencies(), m_filter)); + else + recipes.addAll(itemDb->allRecipes(m_filter)); + } + + if (!m_player->isAdmin() && m_settings.getBool("requiresBlueprint", true)) { + auto tempRecipes = take(recipes); + for (auto const& recipe : tempRecipes) { + if (m_blueprints->isKnown(recipe.output)) + recipes.add(recipe); + } + } + + if (!categoryFilter.empty()) { + auto temprecipes = take(recipes); + for (auto const& recipe : temprecipes) { + if (recipe.groups.hasIntersection(categoryFilter)) + recipes.add(recipe); + } + } + + if (!rarityFilter.empty()) { + auto temprecipes = take(recipes); + for (auto const& recipe : temprecipes) { + if (recipe.output) { + if (rarityFilter.contains(recipe.outputRarity)) + recipes.add(recipe); + } + } + } + + if (!filterText.empty()) { + auto bits = filterText.toLower().splitAny(" ,.?*\\+/|\t"); + auto temprecipes = take(recipes); + for (auto const& recipe : temprecipes) { + if (recipe.output) { + bool match = true; + auto guiFilterString = recipe.guiFilterString; + for (auto const& bit : bits) { + match &= guiFilterString.contains(bit); + if (!match) + break; + } + if (match) + recipes.add(recipe); + } + } + } + + List<ItemRecipe> sortedRecipes = recipes.values(); + auto itemDatabase = Root::singleton().itemDatabase(); + sortByComputedValue(sortedRecipes, [itemDatabase](ItemRecipe const& recipe) { + return make_tuple(itemDatabase->itemFriendlyName(recipe.output.name()).trim().toLower(), recipe.output.name()); + }); + + return sortedRecipes; +} + +int CraftingPane::maxCraft() { + if (m_player->isAdmin()) + return 1000; + auto itemDb = Root::singleton().itemDatabase(); + int res = 0; + if (m_guiList->selectedItem() != NPos && m_guiList->selectedItem() < m_recipes.size()) { + HashMap<ItemDescriptor, uint64_t> normalizedBag = m_player->inventory()->availableItems(); + auto selectedRecipe = recipeFromSelectedWidget(); + res = itemDb->maxCraftableInBag(normalizedBag, m_player->inventory()->availableCurrencies(), selectedRecipe); + res = std::min(res, 1000); + } + return res; +} + +ItemRecipe CraftingPane::recipeFromSelectedWidget() const { + auto pane = m_guiList->selectedWidget(); + if (pane && m_recipesWidgetMap.hasRightValue(pane)) { + return m_recipesWidgetMap.getLeft(pane); + } + return ItemRecipe(); +} + +} |