#include "InventoryView.hpp" #include "assets/Assets.hpp" #include "assets/assets_util.hpp" #include "content/Content.hpp" #include "frontend/LevelFrontend.hpp" #include "frontend/locale.hpp" #include "items/Inventories.hpp" #include "items/Inventory.hpp" #include "items/ItemDef.hpp" #include "logic/scripting/scripting.hpp" #include "maths/voxmaths.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" #include "voxels/Block.hpp" #include "window/input.hpp" #include "world/Level.hpp" #include "graphics/core/Atlas.hpp" #include "graphics/core/Batch2D.hpp" #include "graphics/core/Font.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/Texture.hpp" #include "graphics/render/BlocksPreview.hpp" #include "graphics/ui/GUI.hpp" #include #include using namespace gui; SlotLayout::SlotLayout( int index, glm::vec2 position, bool background, bool itemSource, slotcallback updateFunc, slotcallback shareFunc, slotcallback rightClick ) : index(index), position(position), background(background), itemSource(itemSource), updateFunc(std::move(updateFunc)), shareFunc(std::move(shareFunc)), rightClick(std::move(rightClick)) {} InventoryBuilder::InventoryBuilder(GUI& gui) : gui(gui) { view = std::make_shared(gui); } void InventoryBuilder::addGrid( int cols, int count, glm::vec2 pos, glm::vec4 padding, bool addpanel, const SlotLayout& slotLayout ) { const int slotSize = InventoryView::SLOT_SIZE; const int interval = InventoryView::SLOT_INTERVAL; int rows = ceildiv(count, cols); uint width = cols * (slotSize + interval) - interval + padding.x + padding.z; uint height = rows * (slotSize + interval) - interval + padding.y + padding.w; glm::vec2 vsize = view->getSize(); if (pos.x + width > vsize.x) { vsize.x = pos.x + width; } if (pos.y + height > vsize.y) { vsize.y = pos.y + height; } view->setSize(vsize); if (addpanel) { auto panel = std::make_shared(gui, glm::vec2(width, height)); view->setColor(glm::vec4(0.122f, 0.122f, 0.122f, 0.878f)); view->add(panel, pos); } for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { if (row * cols + col >= count) { break; } glm::vec2 position ( col * (slotSize + interval) + padding.x, row * (slotSize + interval) + padding.y ); auto builtSlot = slotLayout; builtSlot.index = row * cols + col; builtSlot.position = position; add(builtSlot); } } } void InventoryBuilder::add(const SlotLayout& layout) { view->add(view->addSlot(layout), layout.position); } std::shared_ptr InventoryBuilder::build() { return view; } SlotView::SlotView( GUI& gui, SlotLayout layout ) : UINode(gui, glm::vec2(InventoryView::SLOT_SIZE)), layout(std::move(layout)) { setColor(glm::vec4(0, 0, 0, 0.2f)); setTooltipDelay(0.0f); } void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) { itemid_t itemid = stack.getItemId(); if (itemid == cache.stack.getItemId()) { return; } if (itemid) { tooltip = util::pascal_case( langs::get(util::str2wstr_utf8(item.caption)) ); } else { tooltip.clear(); } } void SlotView::drawItemIcon( Batch2D& batch, const ItemStack& stack, const ItemDef& item, const Assets& assets, const glm::vec4& tint, const glm::vec2& pos ) { const int SLOT_SIZE = InventoryView::SLOT_SIZE; const auto& previews = assets.require("block-previews"); batch.setColor(glm::vec4(1.0f)); switch (item.iconType) { case ItemIconType::NONE: break; case ItemIconType::BLOCK: { const Block& block = content->blocks.require(item.icon); batch.texture(previews.getTexture()); UVRegion region = previews.get(block.name); batch.rect( pos.x, pos.y, SLOT_SIZE, SLOT_SIZE, 0, 0, 0, region, false, true, tint ); break; } case ItemIconType::SPRITE: { auto textureRegion = util::get_texture_region(assets, item.icon, "blocks:notfound"); batch.texture(textureRegion.texture); batch.rect( pos.x, pos.y, SLOT_SIZE, SLOT_SIZE, 0, 0, 0, textureRegion.region, false, true, tint ); break; } } } void SlotView::draw(const DrawContext& pctx, const Assets& assets) { if (bound == nullptr) { return; } const auto& indices = *content->getIndices(); const ItemStack& stack = *bound; const ItemDef& item = indices.items.require(stack.getItemId()); if (cache.stack.getCount() != stack.getCount()) { cache.countStr = std::to_wstring(stack.getCount()); } refreshTooltip(stack, item); cache.stack.set(ItemStack(stack.getItemId(), stack.getCount())); glm::vec4 tint(1, 1, 1, isEnabled() ? 1 : 0.5f); glm::vec2 pos = calcPos(); glm::vec4 color = getColor(); if (hover || highlighted) { tint *= 1.333f; color = glm::vec4(1, 1, 1, 0.2f); } auto& batch = *pctx.getBatch2D(); if (color.a > 0.0) { batch.setColor(color); batch.texture(nullptr); const int size = InventoryView::SLOT_SIZE; if (highlighted) { batch.rect(pos.x - 4, pos.y - 4, size + 8, size + 8); } else { batch.rect(pos.x, pos.y, size, size); } } drawItemIcon(batch, stack, item, assets, tint, pos); if (stack.getCount() > 1 || stack.getFields() != nullptr) { const auto& font = assets.require(FONT_DEFAULT); drawItemInfo(batch, stack, item, font, pos); } } static void draw_shaded_text( Batch2D& batch, const Font& font, const std::wstring& text, int x, int y ) { batch.setColor({0, 0, 0, 1.0f}); font.draw(batch, text, x + 1, y + 1, nullptr, 0); batch.resetColor(); font.draw(batch, text, x, y, nullptr, 0); } void SlotView::drawItemInfo( Batch2D& batch, const ItemStack& stack, const ItemDef& item, const Font& font, const glm::vec2& pos ) { const int SLOT_SIZE = InventoryView::SLOT_SIZE; if (stack.getCount() > 1) { const auto& countStr = cache.countStr; int x = pos.x + SLOT_SIZE - countStr.length() * 8; int y = pos.y + SLOT_SIZE - 16; draw_shaded_text(batch, font, countStr, x, y); } auto usesPtr = stack.getField("uses"); if (usesPtr == nullptr || !usesPtr->isInteger()) { return; } int16_t uses = usesPtr->asInteger(); if (uses < 0) { return; } switch (item.usesDisplay) { case ItemUsesDisplay::NONE: break; case ItemUsesDisplay::RELATION: draw_shaded_text( batch, font, std::to_wstring(item.uses), pos.x - 3, pos.y + 9 ); [[fallthrough]]; case ItemUsesDisplay::NUMBER: draw_shaded_text( batch, font, std::to_wstring(uses), pos.x - 3, pos.y - 3 ); break; case ItemUsesDisplay::VBAR: { batch.untexture(); batch.setColor({0, 0, 0, 0.75f}); batch.rect(pos.x - 2, pos.y - 2, 6, SLOT_SIZE + 4); float t = static_cast(uses) / item.uses; int height = SLOT_SIZE * t; batch.setColor({(1.0f - t * 0.8f), 0.4f, t * 0.8f + 0.2f, 1.0f}); batch.rect(pos.x, pos.y + SLOT_SIZE - height, 2, height); break; } } } void SlotView::setHighlighted(bool flag) { highlighted = flag; } bool SlotView::isHighlighted() const { return highlighted; } void SlotView::performLeftClick(ItemStack& stack, ItemStack& grabbed) { const auto& input = gui.getInput(); if (layout.taking && input.pressed(Keycode::LEFT_SHIFT)) { if (layout.shareFunc) { layout.shareFunc(layout.index, stack); } if (layout.updateFunc) { layout.updateFunc(layout.index, stack); } return; } if (!layout.itemSource && stack.accepts(grabbed) && layout.placing) { stack.move(grabbed, *content->getIndices()); } else { if (layout.itemSource) { if (grabbed.isEmpty()) { grabbed.set(stack); } else { grabbed.clear(); } } else if (grabbed.isEmpty()) { if (layout.taking) { std::swap(grabbed, stack); } } else if (layout.taking && layout.placing) { std::swap(grabbed, stack); } } } void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) { if (layout.rightClick) { layout.rightClick(inventoryid, stack); if (layout.updateFunc) { layout.updateFunc(layout.index, stack); } return; } if (layout.itemSource) return; if (grabbed.isEmpty()) { if (!stack.isEmpty() && layout.taking) { grabbed.set(std::move(stack)); int halfremain = stack.getCount() / 2; grabbed.setCount(stack.getCount() - halfremain); // reset all data in the origin slot stack = ItemStack(stack.getItemId(), halfremain); } return; } auto& stackDef = content->getIndices()->items.require(stack.getItemId()); if (!layout.placing) { return; } if (stack.isEmpty()) { itemcount_t count = grabbed.getCount(); stack.set(std::move(grabbed)); stack.setCount(1); if (count == 1) { grabbed = {}; } else { grabbed = ItemStack(stack.getItemId(), count - 1); } } else if (stack.accepts(grabbed) && stack.getCount() < stackDef.stackSize) { stack.setCount(stack.getCount() + 1); grabbed.setCount(grabbed.getCount() - 1); } } void SlotView::clicked(Mousecode button) { if (bound == nullptr) return; auto exchangeSlot = std::dynamic_pointer_cast(gui.get(EXCHANGE_SLOT_NAME)); if (exchangeSlot == nullptr) { return; } ItemStack& grabbed = exchangeSlot->getStack(); ItemStack& stack = *bound; if (button == Mousecode::BUTTON_1) { performLeftClick(stack, grabbed); } else if (button == Mousecode::BUTTON_2) { performRightClick(stack, grabbed); } if (layout.updateFunc) { layout.updateFunc(layout.index, stack); } } void SlotView::onFocus() { clicked(Mousecode::BUTTON_1); } const std::wstring& SlotView::getTooltip() const { const auto& str = UINode::getTooltip(); if (!str.empty() || tooltip.empty()) { return str; } return tooltip; } void SlotView::bind( int64_t inventoryid, ItemStack& stack, const Content* content ) { this->inventoryid = inventoryid; bound = &stack; this->content = content; } const SlotLayout& SlotView::getLayout() const { return layout; } ItemStack& SlotView::getStack() { return *bound; } InventoryView::InventoryView(GUI& gui) : Container(gui, glm::vec2()) { setColor(glm::vec4(0, 0, 0, 0.0f)); } InventoryView::~InventoryView() {} std::shared_ptr InventoryView::addSlot(const SlotLayout& layout) { uint width = InventoryView::SLOT_SIZE + layout.padding; uint height = InventoryView::SLOT_SIZE + layout.padding; auto pos = layout.position; auto vsize = getSize(); if (pos.x + width > vsize.x) { vsize.x = pos.x + width; } if (pos.y + height > vsize.y) { vsize.y = pos.y + height; } setSize(vsize); auto slot = std::make_shared(gui, layout); if (!layout.background) { slot->setColor(glm::vec4()); } slots.push_back(slot.get()); return slot; } std::shared_ptr InventoryView::getInventory() const { return inventory; } size_t InventoryView::getSlotsCount() const { return slots.size(); } void InventoryView::bind( const std::shared_ptr& inventory, const Content* content ) { this->inventory = inventory; this->content = content; for (auto slot : slots) { slot->bind( inventory->getId(), inventory->getSlot(slot->getLayout().index), content ); } } void InventoryView::unbind() { inventory = nullptr; } void InventoryView::setSelected(int index) { for (size_t i = 0; i < slots.size(); i++) { slots[i]->setHighlighted(static_cast(i) == index); } } void InventoryView::setPos(glm::vec2 pos) { Container::setPos(pos - origin); } void InventoryView::setOrigin(glm::vec2 origin) { this->origin = origin; } glm::vec2 InventoryView::getOrigin() const { return origin; }