431a9c00a5
On Linux and macOS, using Clang to compile OpenStarbound produces about 400 MB worth of warnings during the build, making the compiler output unreadable and slowing the build down considerably. 99% of the warnings were unqualified uses of std::move and std::forward, which are now all properly qualified. Fixed a few other minor warnings about non-virtual destructors and some uses of std::move preventing copy elision on temporary objects. Most remaining warnings are now unused parameters.
776 lines
25 KiB
C++
776 lines
25 KiB
C++
#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 = std::move(worldClient);
|
|
m_player = std::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->json("/interface/windowconfig/crafting.config:default"),
|
|
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->itemShared(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(float dt) {
|
|
// 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()->itemShared(recipe.output);
|
|
ItemTooltipBuilder::buildItemDescription(description, item);
|
|
}
|
|
}
|
|
|
|
// crafters gonna craft
|
|
while (m_crafting && m_craftTimer.wrapTick()) {
|
|
craft(min(m_count, (int)m_settings.getInt("craftCount", 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", toString((int)m_player->currency("money")));
|
|
|
|
Pane::update(dt);
|
|
}
|
|
|
|
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()->itemShared(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("{} (x{})", 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", toString(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("{}/{}", 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()->itemShared(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()->itemShared(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{}", 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->itemShared(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();
|
|
}
|
|
|
|
}
|