888cde79ef
makes carrying around shop objects easier might need to restrict the allowed interaction types more, as some may break due to the source entity being the player
482 lines
17 KiB
C++
482 lines
17 KiB
C++
#include "StarInventory.hpp"
|
|
#include "StarGuiReader.hpp"
|
|
#include "StarItemTooltip.hpp"
|
|
#include "StarSimpleTooltip.hpp"
|
|
#include "StarRoot.hpp"
|
|
#include "StarUniverseClient.hpp"
|
|
#include "StarItemGridWidget.hpp"
|
|
#include "StarButtonWidget.hpp"
|
|
#include "StarPortraitWidget.hpp"
|
|
#include "StarPaneManager.hpp"
|
|
#include "StarLabelWidget.hpp"
|
|
#include "StarImageWidget.hpp"
|
|
#include "StarPlayerInventory.hpp"
|
|
#include "StarPlayerCompanions.hpp"
|
|
#include "StarWorldClient.hpp"
|
|
#include "StarAssets.hpp"
|
|
#include "StarItem.hpp"
|
|
#include "StarMainInterface.hpp"
|
|
#include "StarMerchantInterface.hpp"
|
|
#include "StarJsonExtra.hpp"
|
|
#include "StarStatistics.hpp"
|
|
#include "StarAugmentItem.hpp"
|
|
#include "StarObjectItem.hpp"
|
|
#include "StarInteractionTypes.hpp"
|
|
|
|
namespace Star {
|
|
|
|
InventoryPane::InventoryPane(MainInterface* parent, PlayerPtr player, ContainerInteractorPtr containerInteractor) {
|
|
m_parent = parent;
|
|
m_player = std::move(player);
|
|
m_containerInteractor = std::move(containerInteractor);
|
|
|
|
GuiReader invWindowReader;
|
|
auto config = Root::singleton().assets()->json("/interface/windowconfig/playerinventory.config");
|
|
|
|
auto leftClickCallback = [this](String const& bagType, Widget* widget) {
|
|
auto itemGrid = convert<ItemGridWidget>(widget);
|
|
InventorySlot inventorySlot = BagSlot(bagType, itemGrid->selectedIndex());
|
|
|
|
auto inventory = m_player->inventory();
|
|
if (context()->shiftHeld()) {
|
|
if (auto sourceItem = itemGrid->selectedItem()) {
|
|
if (auto activeMerchantPane = m_parent->activeMerchantPane()) {
|
|
auto remainder = activeMerchantPane->addItems(inventory->takeSlot(inventorySlot));
|
|
if (remainder && !remainder->empty())
|
|
inventory->setItem(inventorySlot, remainder);
|
|
} else if (m_containerInteractor->containerOpen()) {
|
|
inventory->takeSlot(inventorySlot);
|
|
m_containerInteractor->addToContainer(sourceItem);
|
|
m_containerSource = inventorySlot;
|
|
m_expectingSwap = true;
|
|
}
|
|
}
|
|
} else {
|
|
inventory->shiftSwap(inventorySlot);
|
|
}
|
|
};
|
|
|
|
auto rightClickCallback = [this](InventorySlot slot) {
|
|
auto inventory = m_player->inventory();
|
|
if (ItemPtr slotItem = inventory->itemsAt(slot)) {
|
|
auto swapItem = inventory->swapSlotItem();
|
|
if (!swapItem || swapItem->empty() || swapItem->couldStack(slotItem)) {
|
|
uint64_t count = swapItem ? swapItem->couldStack(slotItem) : slotItem->maxStack();
|
|
if (context()->shiftHeld())
|
|
count = max(1, min<int>(count, slotItem->count() / 2));
|
|
else
|
|
count = 1;
|
|
|
|
if (auto taken = slotItem->take(count)) {
|
|
if (swapItem)
|
|
swapItem->stackWith(taken);
|
|
else
|
|
inventory->setSwapSlotItem(taken);
|
|
}
|
|
} else if (auto augment = as<AugmentItem>(swapItem)) {
|
|
if (auto augmented = augment->applyTo(slotItem))
|
|
inventory->setItem(slot, augmented);
|
|
}
|
|
}
|
|
else if (auto swapSlot = inventory->swapSlotItem()) {
|
|
if (auto es = slot.ptr<EquipmentSlot>()) {
|
|
if (inventory->itemAllowedAsEquipment(swapSlot, *es))
|
|
inventory->setItem(slot, swapSlot->take(1));
|
|
} else if (slot.is<TrashSlot>()) {
|
|
inventory->setItem(slot, swapSlot->take(1));
|
|
} else if (auto bs = slot.ptr<BagSlot>()) {
|
|
if (inventory->itemAllowedInBag(swapSlot, bs->first))
|
|
inventory->setItem(slot, swapSlot->take(1));
|
|
}
|
|
}
|
|
};
|
|
|
|
auto bagGridCallback = [rightClickCallback](String const& bagType, Widget* widget) {
|
|
auto slot = BagSlot(bagType, convert<ItemGridWidget>(widget)->selectedIndex());
|
|
rightClickCallback(slot);
|
|
};
|
|
|
|
auto middleClickCallback = [this](String const& bagType, Widget* widget) {
|
|
if (!m_player->inWorld())
|
|
return;
|
|
|
|
auto itemGrid = convert<ItemGridWidget>(widget);
|
|
InventorySlot inventorySlot = BagSlot(bagType, itemGrid->selectedIndex());
|
|
auto inventory = m_player->inventory();
|
|
if (auto sourceItem = as<ObjectItem>(itemGrid->selectedItem())) {
|
|
if (auto actionTypeName = sourceItem->instanceValue("interactAction")) {
|
|
auto actionType = InteractActionTypeNames.getLeft(actionTypeName.toString());
|
|
if (actionType >= InteractActionType::OpenCraftingInterface && actionType <= InteractActionType::ScriptPane) {
|
|
auto actionData = sourceItem->instanceValue("interactData", Json());
|
|
if (actionData.isType(Json::Type::Object))
|
|
actionData = actionData.set("openWithInventory", false);
|
|
InteractAction action(actionType, m_player->entityId(), actionData);
|
|
m_player->interact(action);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Json itemBagConfig = config.get("bagConfig");
|
|
auto bagOrder = itemBagConfig.toObject().keys().sorted([&itemBagConfig](String const& a, String const& b) {
|
|
return itemBagConfig.get(a).getInt("order", 0) < itemBagConfig.get(b).getInt("order", 0);
|
|
});
|
|
for (auto name : bagOrder) {
|
|
auto itemGrid = itemBagConfig.get(name).getString("itemGrid");
|
|
invWindowReader.registerCallback(itemGrid, bind(leftClickCallback, name, _1));
|
|
invWindowReader.registerCallback(strf("{}.right", itemGrid), bind(bagGridCallback, name, _1));
|
|
invWindowReader.registerCallback(strf("{}.middle", itemGrid), bind(middleClickCallback, name, _1));
|
|
}
|
|
|
|
invWindowReader.registerCallback("close", [=](Widget*) {
|
|
dismiss();
|
|
});
|
|
|
|
invWindowReader.registerCallback("sort", [=](Widget*) {
|
|
m_player->inventory()->condenseBagStacks(m_selectedTab);
|
|
m_player->inventory()->sortBag(m_selectedTab);
|
|
// Don't show sorted items as new items
|
|
m_itemGrids[m_selectedTab]->updateItemState();
|
|
m_itemGrids[m_selectedTab]->clearChangedSlots();
|
|
});
|
|
|
|
invWindowReader.registerCallback("gridModeSelector", [=](Widget* widget) {
|
|
auto selected = convert<ButtonWidget>(widget)->data().toString();
|
|
selectTab(m_tabButtonData.keyOf(selected));
|
|
});
|
|
|
|
auto registerSlotCallbacks = [&](String name, InventorySlot slot) {
|
|
invWindowReader.registerCallback(name, [=](Widget* paneObj) {
|
|
if (as<ItemSlotWidget>(paneObj))
|
|
m_player->inventory()->shiftSwap(slot);
|
|
else
|
|
throw GuiException("Invalid object type, expected ItemSlotWidget");
|
|
});
|
|
invWindowReader.registerCallback(name + ".right", [=](Widget* paneObj) {
|
|
if (as<ItemSlotWidget>(paneObj))
|
|
rightClickCallback(slot);
|
|
else
|
|
throw GuiException("Invalid object type, expected ItemSlotWidget");
|
|
});
|
|
};
|
|
|
|
for (auto const p : EquipmentSlotNames)
|
|
registerSlotCallbacks(p.second, p.first);
|
|
registerSlotCallbacks("trash", TrashSlot());
|
|
|
|
invWindowReader.construct(config.get("paneLayout"), this);
|
|
|
|
m_trashSlot = fetchChild<ItemSlotWidget>("trash");
|
|
m_trashBurn = GameTimer(config.get("trashBurnTimeout").toFloat());
|
|
|
|
m_disabledTechOverlays.append(fetchChild<ImageWidget>("techHeadDisabled"));
|
|
m_disabledTechOverlays.append(fetchChild<ImageWidget>("techBodyDisabled"));
|
|
m_disabledTechOverlays.append(fetchChild<ImageWidget>("techLegsDisabled"));
|
|
|
|
for (auto const p : EquipmentSlotNames) {
|
|
if (auto itemSlot = fetchChild<ItemSlotWidget>(p.second))
|
|
itemSlot->setItem(m_player->inventory()->itemsAt(p.first));
|
|
}
|
|
|
|
for (auto name : bagOrder) {
|
|
auto itemTab = itemBagConfig.get(name);
|
|
m_itemGrids[name] = fetchChild<ItemGridWidget>(itemTab.getString("itemGrid"));
|
|
m_itemGrids[name]->setItemBag(m_player->inventory()->bagContents(name));
|
|
m_itemGrids[name]->hide();
|
|
m_newItemMarkers[name] = fetchChild<Widget>(itemTab.getString("newItemMarker"));
|
|
m_tabButtonData[name] = itemTab.getString("tabButtonData");
|
|
}
|
|
selectTab(bagOrder[0]);
|
|
|
|
auto centralPortrait = fetchChild<PortraitWidget>("portrait");
|
|
centralPortrait->setEntity(m_player);
|
|
|
|
auto portrait = make_shared<PortraitWidget>(m_player, PortraitMode::Bust);
|
|
portrait->setIconMode();
|
|
setTitle(portrait, m_player->name(), config.getString("subtitle"));
|
|
|
|
m_expectingSwap = false;
|
|
|
|
if (auto item = m_player->inventory()->swapSlotItem())
|
|
m_currentSwapSlotItem = item->descriptor();
|
|
m_pickUpSounds = jsonToStringList(config.get("sounds").get("pickup"));
|
|
m_putDownSounds = jsonToStringList(config.get("sounds").get("putdown"));
|
|
m_someUpSounds = jsonToStringList(config.get("sounds").get("someup"));
|
|
m_someDownSounds = jsonToStringList(config.get("sounds").get("somedown"));
|
|
}
|
|
|
|
void InventoryPane::displayed() {
|
|
Pane::displayed();
|
|
m_expectingSwap = false;
|
|
|
|
for (auto grid : m_itemGrids)
|
|
grid.second->updateItemState();
|
|
m_itemGrids[m_selectedTab]->indicateChangedSlots();
|
|
}
|
|
|
|
PanePtr InventoryPane::createTooltip(Vec2I const& screenPosition) {
|
|
ItemPtr item;
|
|
if (auto child = getChildAt(screenPosition)) {
|
|
if (auto itemSlot = as<ItemSlotWidget>(child)) {
|
|
item = itemSlot->item();
|
|
if (!item) {
|
|
auto widgetData = itemSlot->data();
|
|
if (widgetData && widgetData.type() == Json::Type::Object) {
|
|
if (auto text = widgetData.optString("tooltipText"))
|
|
return SimpleTooltipBuilder::buildTooltip(*text);
|
|
}
|
|
}
|
|
}
|
|
if (auto itemGrid = as<ItemGridWidget>(child))
|
|
item = itemGrid->itemAt(screenPosition);
|
|
}
|
|
if (item)
|
|
return ItemTooltipBuilder::buildItemTooltip(item, m_player);
|
|
|
|
auto techDatabase = Root::singleton().techDatabase();
|
|
for (auto const& p : TechTypeNames) {
|
|
if (auto techIcon = fetchChild<ImageWidget>(strf("tech{}", p.second))) {
|
|
if (techIcon->screenBoundRect().contains(screenPosition)) {
|
|
if (auto techModule = m_player->techs()->equippedTechs().maybe(p.first))
|
|
return SimpleTooltipBuilder::buildTooltip(techDatabase->tech(*techModule).description);
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
bool InventoryPane::giveContainerResult(ContainerResult result) {
|
|
if (!m_expectingSwap)
|
|
return false;
|
|
|
|
for (auto& item : result) {
|
|
auto inv = m_player->inventory();
|
|
m_player->triggerPickupEvents(item);
|
|
|
|
auto remainder = inv->stackWith(m_containerSource, item);
|
|
if (remainder && !remainder->empty())
|
|
m_player->giveItem(remainder);
|
|
}
|
|
|
|
m_expectingSwap = false;
|
|
return true;
|
|
}
|
|
|
|
void InventoryPane::updateItems() {
|
|
for (auto& p : m_itemGrids)
|
|
p.second->updateItemState();
|
|
}
|
|
|
|
bool InventoryPane::containsNewItems() const {
|
|
for (auto& p : m_itemGrids) {
|
|
if (p.second->slotsChanged())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void InventoryPane::clearChangedSlots() {
|
|
for (auto& p : m_itemGrids) {
|
|
p.second->updateItemState();
|
|
p.second->clearChangedSlots();
|
|
}
|
|
}
|
|
|
|
void InventoryPane::update(float dt) {
|
|
auto inventory = m_player->inventory();
|
|
auto context = Widget::context();
|
|
|
|
HashSet<ItemPtr> customBarItems;
|
|
for (uint8_t i = 0; i < inventory->customBarIndexes(); ++i) {
|
|
if (auto primarySlot = inventory->customBarPrimarySlot(i)) {
|
|
if (auto primaryItem = inventory->itemsAt(*primarySlot))
|
|
customBarItems.add(primaryItem);
|
|
}
|
|
if (auto secondarySlot = inventory->customBarSecondarySlot(i)) {
|
|
if (auto secondaryItem = inventory->itemsAt(*secondarySlot))
|
|
customBarItems.add(secondaryItem);
|
|
}
|
|
}
|
|
|
|
m_trashSlot->setItem(inventory->itemsAt(TrashSlot()));
|
|
m_trashSlot->showLinkIndicator(customBarItems.contains(m_trashSlot->item()));
|
|
if (auto trashItem = m_trashSlot->item()) {
|
|
if (m_trashBurn.tick(dt) && trashItem->count() > 0) {
|
|
m_player->statistics()->recordEvent("trashItem", JsonObject{
|
|
{"itemName", trashItem->name()},
|
|
{"count", trashItem->count()},
|
|
{"category", trashItem->category()}
|
|
});
|
|
trashItem->take(trashItem->count());
|
|
}
|
|
} else {
|
|
m_trashBurn.reset();
|
|
}
|
|
m_trashSlot->setProgress(m_trashBurn.timer / m_trashBurn.time);
|
|
|
|
for (auto const& p : EquipmentSlotNames) {
|
|
if (auto itemSlot = fetchChild<ItemSlotWidget>(p.second)) {
|
|
itemSlot->setItem(inventory->itemsAt(p.first));
|
|
itemSlot->showLinkIndicator(customBarItems.contains(itemSlot->item()));
|
|
}
|
|
}
|
|
|
|
auto techDatabase = Root::singleton().techDatabase();
|
|
for (auto const& p : TechTypeNames) {
|
|
if (auto techIcon = fetchChild<ImageWidget>(strf("tech{}", p.second))) {
|
|
if (auto techModule = m_player->techs()->equippedTechs().maybe(p.first))
|
|
techIcon->setImage(techDatabase->tech(*techModule).icon);
|
|
else
|
|
techIcon->setImage("");
|
|
}
|
|
}
|
|
|
|
if (ItemPtr swapSlot = inventory->swapSlotItem()) {
|
|
if (!PlayerInventory::itemAllowedInBag(swapSlot, m_selectedTab)) {
|
|
for (auto& pair : m_itemGrids) {
|
|
if (pair.first != m_selectedTab && PlayerInventory::itemAllowedInBag(swapSlot, pair.first)) {
|
|
selectTab(pair.first);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto p : m_itemGrids) {
|
|
p.second->updateItemState();
|
|
for (size_t i = 0; i < p.second->itemSlots(); ++i) {
|
|
auto itemWidget = p.second->itemWidgetAt(i);
|
|
itemWidget->showLinkIndicator(customBarItems.contains(itemWidget->item()));
|
|
}
|
|
}
|
|
|
|
m_itemGrids[m_selectedTab]->clearChangedSlots();
|
|
|
|
for (auto& pair : m_newItemMarkers) {
|
|
if (m_itemGrids[pair.first]->slotsChanged())
|
|
pair.second->show();
|
|
else
|
|
pair.second->hide();
|
|
}
|
|
|
|
for (auto& techOverlay : m_disabledTechOverlays)
|
|
techOverlay->setVisibility(m_player->techOverridden());
|
|
|
|
auto healthLabel = fetchChild<LabelWidget>("healthtext");
|
|
healthLabel->setText(toString(m_player->maxHealth()));
|
|
auto energyLabel = fetchChild<LabelWidget>("energytext");
|
|
energyLabel->setText(toString(m_player->maxEnergy()));
|
|
auto weaponLabel = fetchChild<LabelWidget>("weapontext");
|
|
weaponLabel->setText(strf("{}%", ceil(m_player->powerMultiplier() * 100)));
|
|
auto defenseLabel = fetchChild<LabelWidget>("defensetext");
|
|
if (m_player->protection() == 0)
|
|
defenseLabel->setText("--");
|
|
else
|
|
defenseLabel->setText(toString(ceil(m_player->protection())));
|
|
|
|
auto moneyLabel = fetchChild<LabelWidget>("lblMoney");
|
|
moneyLabel->setText(toString(m_player->currency("money")));
|
|
|
|
if (m_player->currency("essence") > 0) {
|
|
fetchChild<ImageWidget>("imgEssenceIcon")->show();
|
|
auto essenceLabel = fetchChild<LabelWidget>("lblEssence");
|
|
essenceLabel->show();
|
|
essenceLabel->setText(toString(m_player->currency("essence")));
|
|
} else {
|
|
fetchChild<ImageWidget>("imgEssenceIcon")->hide();
|
|
fetchChild<LabelWidget>("lblEssence")->hide();
|
|
}
|
|
|
|
auto config = Root::singleton().assets()->json("/interface/windowconfig/playerinventory.config");
|
|
|
|
auto pets = m_player->companions()->getCompanions("pets");
|
|
if (pets.size() > 0) {
|
|
auto pet = pets.first();
|
|
auto companionImage = fetchChild<ImageWidget>("companionSlot");
|
|
companionImage->setVisibility(true);
|
|
|
|
companionImage->setDrawables(pet->portrait());
|
|
auto nameLabel = fetchChild<LabelWidget>("companionName");
|
|
if (auto name = pet->name()) {
|
|
nameLabel->setText(pet->name()->toUpper());
|
|
} else {
|
|
nameLabel->setText(config.getString("defaultPetNameLabel"));
|
|
}
|
|
|
|
auto attackLabel = fetchChild<LabelWidget>("companionAttackStat");
|
|
if (auto attack = pet->stat("attack")) {
|
|
attackLabel->setText(strf("{:.0f}", *attack));
|
|
} else {
|
|
attackLabel->setText("");
|
|
}
|
|
|
|
auto defenseLabel = fetchChild<LabelWidget>("companionDefenseStat");
|
|
if (auto defense = pet->stat("defense")) {
|
|
defenseLabel->setText(strf("{:.0f}", *defense));
|
|
} else {
|
|
defenseLabel->setText("");
|
|
}
|
|
|
|
if (containsChild("companionHealthBar")) {
|
|
auto healthBar = fetchChild<ProgressWidget>("companionHealthBar");
|
|
Maybe<float> health = pet->resource("health");
|
|
Maybe<float> healthMax = pet->resourceMax("health");
|
|
if (health && healthMax) {
|
|
healthBar->setCurrentProgressLevel(*health);
|
|
healthBar->setMaxProgressLevel(*healthMax);
|
|
} else {
|
|
healthBar->setCurrentProgressLevel(0);
|
|
healthBar->setMaxProgressLevel(1);
|
|
}
|
|
}
|
|
} else {
|
|
fetchChild<ImageWidget>("companionSlot")->setVisibility(false);
|
|
|
|
fetchChild<LabelWidget>("companionName")->setText(config.getString("defaultPetNameLabel"));
|
|
|
|
fetchChild<LabelWidget>("companionAttackStat")->setText("");
|
|
fetchChild<LabelWidget>("companionDefenseStat")->setText("");
|
|
|
|
if (containsChild("companionHealthBar")) {
|
|
auto healthBar = fetchChild<ProgressWidget>("companionHealthBar");
|
|
healthBar->setCurrentProgressLevel(0);
|
|
healthBar->setMaxProgressLevel(1);
|
|
}
|
|
}
|
|
|
|
if (auto item = inventory->swapSlotItem()) {
|
|
if (!m_currentSwapSlotItem || !item->matches(*m_currentSwapSlotItem, true))
|
|
context->playAudio(RandomSource().randFrom(m_pickUpSounds));
|
|
else if (item->count() > m_currentSwapSlotItem->count())
|
|
context->playAudio(RandomSource().randFrom(m_someUpSounds));
|
|
else if (item->count() < m_currentSwapSlotItem->count())
|
|
context->playAudio(RandomSource().randFrom(m_someDownSounds));
|
|
|
|
m_currentSwapSlotItem = item->descriptor();
|
|
} else {
|
|
if (m_currentSwapSlotItem)
|
|
context->playAudio(RandomSource().randFrom(m_putDownSounds));
|
|
m_currentSwapSlotItem = {};
|
|
}
|
|
|
|
m_title = m_player->name();
|
|
|
|
Pane::update(dt);
|
|
}
|
|
|
|
void InventoryPane::selectTab(String const& selected) {
|
|
for (auto grid : m_itemGrids)
|
|
grid.second->hide();
|
|
m_selectedTab = selected;
|
|
m_itemGrids[m_selectedTab]->show();
|
|
m_itemGrids[m_selectedTab]->indicateChangedSlots();
|
|
|
|
auto tabs = fetchChild<ButtonGroupWidget>("gridModeSelector");
|
|
for (auto button : tabs->buttons())
|
|
if (button->data().toString().equalsIgnoreCase(m_tabButtonData[selected]))
|
|
tabs->select(tabs->id(button));
|
|
}
|
|
|
|
}
|