From 5142f3b4e7712a5eaefe6c8fd3e4ff4d68cbcce7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 21 Jan 2024 15:23:46 +0300 Subject: [PATCH] Inventory (WIP part I) --- src/frontend/InventoryView.cpp | 422 ++++++++++++++++++++++++--------- src/frontend/InventoryView.h | 138 +++++++++-- src/frontend/WorldRenderer.cpp | 8 +- src/frontend/gui/UINode.cpp | 4 +- src/frontend/hud.cpp | 207 ++++++++++++++-- src/frontend/hud.h | 15 +- src/items/Inventory.cpp | 74 ++++++ src/items/Inventory.h | 41 ++++ src/items/ItemStack.cpp | 40 ++++ src/items/ItemStack.h | 40 ++++ src/logic/PlayerController.cpp | 19 +- src/objects/Player.cpp | 21 +- src/objects/Player.h | 12 +- 13 files changed, 865 insertions(+), 176 deletions(-) create mode 100644 src/items/Inventory.cpp create mode 100644 src/items/Inventory.h create mode 100644 src/items/ItemStack.cpp create mode 100644 src/items/ItemStack.h diff --git a/src/frontend/InventoryView.cpp b/src/frontend/InventoryView.cpp index 2b898c32..723af338 100644 --- a/src/frontend/InventoryView.cpp +++ b/src/frontend/InventoryView.cpp @@ -1,146 +1,334 @@ #include "InventoryView.h" +#include #include #include "BlocksPreview.h" #include "LevelFrontend.h" #include "../window/Events.h" +#include "../window/input.h" #include "../assets/Assets.h" #include "../graphics/Atlas.h" #include "../graphics/Shader.h" #include "../graphics/Batch2D.h" #include "../graphics/GfxContext.h" +#include "../graphics/Font.h" #include "../content/Content.h" #include "../items/ItemDef.h" +#include "../items/Inventory.h" #include "../maths/voxmaths.h" #include "../objects/Player.h" #include "../voxels/Block.h" +#include "../frontend/gui/controls.h" +#include "../util/stringutil.h" + +InventoryLayout::InventoryLayout(glm::vec2 size) : size(size) {} + +void InventoryLayout::add(SlotLayout slot) { + slots.push_back(slot); +} + +void InventoryLayout::setSize(glm::vec2 size) { + this->size = size; +} + +void InventoryLayout::setOrigin(glm::vec2 origin) { + this->origin = origin; +} + +glm::vec2 InventoryLayout::getSize() const { + return size; +} + +glm::vec2 InventoryLayout::getOrigin() const { + return origin; +} + +std::vector& InventoryLayout::getSlots() { + return slots; +} + +SlotLayout::SlotLayout( + glm::vec2 position, + bool background, + bool itemSource, + itemsharefunc shareFunc, + slotcallback rightClick +) + : position(position), + background(background), + itemSource(itemSource), + shareFunc(shareFunc), + rightClick(rightClick) {} + +InventoryPanel::InventoryPanel( + glm::vec2 position, + glm::vec2 size, + glm::vec4 color) + : position(position), size(size), color(color) {} + +InventoryBuilder::InventoryBuilder() + : layout(std::make_unique(glm::vec2())) +{} + +void InventoryBuilder::addGrid( + int cols, int rows, + glm::vec2 coord, + int padding, + SlotLayout slotLayout) +{ + const int slotSize = InventoryView::SLOT_SIZE; + const int interval = InventoryView::SLOT_INTERVAL; + + uint width = cols * (slotSize + interval) - interval + padding*2; + uint height = rows * (slotSize + interval) - interval + padding*2; + + auto lsize = layout->getSize(); + if (coord.x + width > lsize.x) { + lsize.x = coord.x + width; + } + if (coord.y + height > lsize.y) { + lsize.y = coord.y + height; + } + layout->setSize(lsize); + + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + glm::vec2 position ( + col * (slotSize + interval) + padding, + row * (slotSize + interval) + padding + ); + auto builtSlot = slotLayout; + builtSlot.position = position; + layout->add(builtSlot); + } + } +} + +std::unique_ptr InventoryBuilder::build() { + return std::unique_ptr(layout.release()); +} + +SlotView::SlotView( + ItemStack& stack, + LevelFrontend* frontend, + InventoryInteraction* interaction, + const Content* content, + SlotLayout layout) + : UINode(glm::vec2(), glm::vec2(InventoryView::SLOT_SIZE)), + frontend(frontend), + interaction(interaction), + content(content), + stack(stack), + layout(layout) { + color(glm::vec4(0, 0, 0, 0.2f)); +} + +// performance disaster +void SlotView::draw(Batch2D* batch, Assets* assets) { + glm::vec2 coord = calcCoord(); + + int slotSize = InventoryView::SLOT_SIZE; + + glm::vec4 tint(1.0f); + glm::vec4 color = color_; + if (hover_ || highlighted) { + tint *= 1.333f; + color = glm::vec4(1, 1, 1, 0.2f); + } + + batch->color = color; + if (color.a > 0.0) { + batch->texture(nullptr); + if (highlighted) { + batch->rect(coord.x-4, coord.y-4, slotSize+8, slotSize+8); + } else { + batch->rect(coord.x, coord.y, slotSize, slotSize); + } + } + + batch->color = glm::vec4(1.0f); + + Shader* uiShader = assets->getShader("ui"); + Viewport viewport(Window::width, Window::height); + GfxContext ctx(nullptr, viewport, batch); + + auto preview = frontend->getBlocksPreview(); + auto indices = content->getIndices(); + + ItemDef* item = indices->getItemDef(stack.getItemId()); + switch (item->iconType) { + case item_icon_type::none: + break; + case item_icon_type::block: + batch->render(); + { + GfxContext subctx = ctx.sub(); + subctx.depthTest(true); + subctx.cullFace(true); + + Block* cblock = content->requireBlock(item->icon); + preview->begin(&subctx.getViewport()); + preview->draw(cblock, coord.x, coord.y, slotSize, tint); + } + uiShader->use(); + batch->begin(); + break; + case item_icon_type::sprite: { + size_t index = item->icon.find(':'); + std::string name = item->icon.substr(index+1); + UVRegion region(0.0f, 0.0, 1.0f, 1.0f); + if (index == std::string::npos) { + batch->texture(assets->getTexture(name)); + } else { + std::string atlasname = item->icon.substr(0, index); + Atlas* atlas = assets->getAtlas(atlasname); + if (atlas && atlas->has(name)) { + region = atlas->get(name); + batch->texture(atlas->getTexture()); + } + } + batch->rect( + coord.x, coord.y, slotSize, slotSize, + 0, 0, 0, region, false, true, tint); + break; + } + } + + if (stack.getCount() > 1) { + auto font = assets->getFont("normal"); + std::wstring text = std::to_wstring(stack.getCount()); + + int x = coord.x+slotSize-text.length()*8; + int y = coord.y+slotSize-16; + + batch->color = glm::vec4(0, 0, 0, 1.0f); + font->draw(batch, text, x+1, y+1); + batch->color = glm::vec4(1.0f); + font->draw(batch, text, x, y); + } +} + +void SlotView::setHighlighted(bool flag) { + highlighted = flag; +} + +bool SlotView::isHighlighted() const { + return highlighted; +} + +void SlotView::clicked(gui::GUI* gui, int button) { + ItemStack& grabbed = interaction->getGrabbedItem(); + if (button == mousecode::BUTTON_1) { + if (Events::pressed(keycode::LEFT_SHIFT)) { + if (layout.shareFunc) { + layout.shareFunc(stack); + } + return; + } + if (!layout.itemSource && stack.accepts(grabbed)) { + stack.move(grabbed, content->getIndices()); + } else { + if (layout.itemSource) { + if (grabbed.isEmpty()) { + grabbed.set(stack); + } else { + grabbed.clear(); + } + } else { + std::swap(grabbed, stack); + } + } + } else if (button == mousecode::BUTTON_2) { + if (layout.rightClick) { + layout.rightClick(stack, grabbed); + return; + } + if (layout.itemSource) + return; + if (grabbed.isEmpty()) { + if (!stack.isEmpty()) { + grabbed.set(stack); + int halfremain = stack.getCount() / 2; + grabbed.setCount(stack.getCount() - halfremain); + stack.setCount(halfremain); + } + } else { + if (stack.isEmpty()) { + stack.set(grabbed); + stack.setCount(1); + } else { + stack.setCount(stack.getCount()+1); + } + grabbed.setCount(grabbed.getCount()-1); + } + } +} InventoryView::InventoryView( - int columns, const Content* content, LevelFrontend* frontend, - std::vector items) - : content(content), + InventoryInteraction* interaction, + std::shared_ptr inventory, + std::unique_ptr layout) + : Container(glm::vec2(), glm::vec2()), + content(content), indices(content->getIndices()), - items(items), + inventory(inventory), + layout(std::move(layout)), frontend(frontend), - columns(columns) { + interaction(interaction) { + size(this->layout->getSize()); + color(glm::vec4(0, 0, 0, 0.5f)); } -InventoryView::~InventoryView() { -} +InventoryView::~InventoryView() {} -void InventoryView::setPosition(int x, int y) { - position.x = x; - position.y = y; -} +void InventoryView::build() { + int index = 0; + for (auto& slot : layout->getSlots()) { + ItemStack& item = inventory->getSlot(index); -int InventoryView::getWidth() const { - return columns * iconSize + (columns-1) * interval + padding.x * 2; -} - -int InventoryView::getHeight() const { - uint inv_rows = ceildiv(items.size(), columns); - return inv_rows * iconSize + (inv_rows-1) * interval + padding.y * 2; -} - -void InventoryView::setSlotConsumer(slotconsumer consumer) { - this->consumer = consumer; -} - -void InventoryView::setItems(std::vector items) { - this->items = items; -} - -void InventoryView::actAndDraw(const GfxContext* ctx) { - Assets* assets = frontend->getAssets(); - Shader* uiShader = assets->getShader("ui"); - - auto viewport = ctx->getViewport(); - uint inv_w = getWidth(); - uint inv_h = getHeight(); - int xs = position.x + padding.x; - int ys = position.y + padding.y; - - glm::vec4 tint (1.0f); - int mx = Events::cursor.x; - int my = Events::cursor.y; - - // background - auto batch = ctx->getBatch2D(); - batch->texture(nullptr); - batch->color = glm::vec4(0.0f, 0.0f, 0.0f, 0.5f); - batch->rect(position.x, position.y, inv_w, inv_h); - batch->render(); - - // blocks & items - if (Events::scroll) { - scroll -= Events::scroll * (iconSize+interval); + auto view = std::make_shared( + item, frontend, interaction, content, slot + ); + if (!slot.background) { + view->color(glm::vec4()); + } + slots.push_back(view.get()); + add(view, slot.position); + index++; } - scroll = std::min(scroll, int(inv_h-viewport.getHeight())); - scroll = std::max(scroll, 0); - - auto blocksPreview = frontend->getBlocksPreview(); - // todo: optimize - { - Window::clearDepth(); - GfxContext subctx = ctx->sub(); - subctx.depthTest(true); - subctx.cullFace(true); - uint index = 0; - for (uint i = 0; i < items.size(); i++) { - ItemDef* item = indices->getItemDef(items[i]); - int x = xs + (iconSize+interval) * (index % columns); - int y = ys + (iconSize+interval) * (index / columns) - scroll; - if (y < -int(iconSize+interval) || y >= int(viewport.getHeight())) { - index++; - continue; - } - if (mx > x && mx < x + (int)iconSize && my > y && my < y + (int)iconSize) { - tint.r *= 1.2f; - tint.g *= 1.2f; - tint.b *= 1.2f; - if (Events::jclicked(mousecode::BUTTON_1)) { - if (consumer) { - consumer(items[i]); - } - } - } else { - tint = glm::vec4(1.0f); - } - switch (item->iconType) { - case item_icon_type::none: - break; - case item_icon_type::block: { - Block* cblock = content->requireBlock(item->icon); - blocksPreview->begin(&ctx->getViewport()); - blocksPreview->draw(cblock, x, y, iconSize, tint); - break; - } - case item_icon_type::sprite: { - batch->begin(); - uiShader->use(); - size_t index = item->icon.find(':'); - std::string name = item->icon.substr(index+1); - UVRegion region(0.0f, 0.0, 1.0f, 1.0f); - if (index == std::string::npos) { - batch->texture(assets->getTexture(name)); - } else { - std::string atlasname = item->icon.substr(0, index); - Atlas* atlas = assets->getAtlas(atlasname); - if (atlas && atlas->has(name)) { - region = atlas->get(name); - batch->texture(atlas->getTexture()); - } - } - batch->rect(x, y, 48, 48, 0, 0, 0, region, false, true, glm::vec4(1.0f)); - batch->render(); - break; - } - } - index++; - } - } - uiShader->use(); +} + +void InventoryView::setSelected(int index) { + for (int i = 0; i < int(slots.size()); i++) { + auto slot = slots[i]; + slot->setHighlighted(i == index); + } +} + +void InventoryView::setCoord(glm::vec2 coord) { + Container::setCoord(coord - layout->getOrigin()); +} + +void InventoryView::setInventory(std::shared_ptr inventory) { + this->inventory = inventory; +} + +InventoryLayout* InventoryView::getLayout() const { + return layout.get(); +} + +// performance disaster x2 +void InventoryView::draw(Batch2D* batch, Assets* assets) { + Container::draw(batch, assets); + Window::clearDepth(); +} + +void InventoryView::drawBackground(Batch2D* batch, Assets* assets) { + glm::vec2 coord = calcCoord(); + batch->texture(nullptr); + batch->color = color_; + batch->rect(coord.x-1, coord.y-1, size_.x+2, size_.y+2); } diff --git a/src/frontend/InventoryView.h b/src/frontend/InventoryView.h index af7f7990..1115428e 100644 --- a/src/frontend/InventoryView.h +++ b/src/frontend/InventoryView.h @@ -5,46 +5,150 @@ #include #include +#include "../frontend/gui/UINode.h" +#include "../frontend/gui/panels.h" +#include "../frontend/gui/controls.h" +#include "../items/ItemStack.h" #include "../typedefs.h" +class Batch2D; class Assets; class GfxContext; class Content; class ContentIndices; class LevelFrontend; +class Inventory; -typedef std::function slotconsumer; +typedef std::function itemsharefunc; +typedef std::function slotcallback; -class InventoryView { +class InventoryInteraction; + +struct SlotLayout { + glm::vec2 position; + bool background; + bool itemSource; + itemsharefunc shareFunc; + slotcallback rightClick; + + SlotLayout(glm::vec2 position, + bool background, + bool itemSource, + itemsharefunc shareFunc, + slotcallback rightClick); +}; + +// temporary unused +struct InventoryPanel { + glm::vec2 position; + glm::vec2 size; + glm::vec4 color; + + InventoryPanel(glm::vec2 position, + glm::vec2 size, + glm::vec4 color); +}; + +class InventoryLayout { + glm::vec2 size; + glm::vec2 origin; + std::vector slots; +public: + InventoryLayout(glm::vec2 size); + + void add(SlotLayout slot); + void setSize(glm::vec2 size); + void setOrigin(glm::vec2 origin); + + glm::vec2 getSize() const; + glm::vec2 getOrigin() const; + + std::vector& getSlots(); +}; + +class InventoryBuilder { + std::unique_ptr layout; +public: + InventoryBuilder(); + + void addGrid( + int cols, int rows, + glm::vec2 coord, + int padding, + SlotLayout slotLayout); + std::unique_ptr build(); +}; + +class SlotView : public gui::UINode { + LevelFrontend* frontend; + InventoryInteraction* interaction; + const Content* const content; + ItemStack& stack; + bool highlighted = false; + + SlotLayout layout; +public: + SlotView(ItemStack& stack, + LevelFrontend* frontend, + InventoryInteraction* interaction, + const Content* content, + SlotLayout layout); + + virtual void draw(Batch2D* batch, Assets* assets) override; + + void setHighlighted(bool flag); + bool isHighlighted() const; + + virtual void clicked(gui::GUI*, int) override; +}; + +class InventoryView : public gui::Container { const Content* content; const ContentIndices* indices; - std::vector items; - slotconsumer consumer = nullptr; + + std::shared_ptr inventory; + std::unique_ptr layout; LevelFrontend* frontend; + InventoryInteraction* interaction; + + std::vector slots; int scroll = 0; - int columns; - uint iconSize = 48; - uint interval = 4; - glm::ivec2 padding {interval, interval}; - glm::ivec2 position {0, 0}; public: InventoryView( - int columns, const Content* content, LevelFrontend* frontend, - std::vector items); + InventoryInteraction* interaction, + std::shared_ptr inventory, + std::unique_ptr layout); virtual ~InventoryView(); - virtual void actAndDraw(const GfxContext* ctx); + void build(); - void setItems(std::vector items); + virtual void draw(Batch2D* batch, Assets* assets) override; + virtual void drawBackground(Batch2D* batch, Assets* assets) override; - void setPosition(int x, int y); - int getWidth() const; - int getHeight() const; - void setSlotConsumer(slotconsumer consumer); + void setInventory(std::shared_ptr inventory); + + virtual void setCoord(glm::vec2 coord) override; + + InventoryLayout* getLayout() const; + + void setSelected(int index); + + static const int SLOT_INTERVAL = 4; + static const int SLOT_SIZE = 48; +}; + +class InventoryInteraction { + ItemStack grabbedItem; +public: + InventoryInteraction() = default; + + ItemStack& getGrabbedItem() { + return grabbedItem; + } }; #endif // FRONTEND_INVENTORY_VIEW_H_ diff --git a/src/frontend/WorldRenderer.cpp b/src/frontend/WorldRenderer.cpp index 0e517951..8ae83df0 100644 --- a/src/frontend/WorldRenderer.cpp +++ b/src/frontend/WorldRenderer.cpp @@ -28,6 +28,8 @@ #include "../settings.h" #include "../engine.h" #include "../items/ItemDef.h" +#include "../items/ItemStack.h" +#include "../items/Inventory.h" #include "LevelFrontend.h" #include "graphics/Skybox.h" @@ -169,8 +171,10 @@ void WorldRenderer::draw(const GfxContext& pctx, Camera* camera, bool hudVisible shader->uniform3f("u_cameraPos", camera->position); shader->uniform1i("u_cubemap", 1); { - itemid_t id = level->player->getChosenItem(); - ItemDef* item = indices->getItemDef(id); + auto player = level->player; + auto inventory = player->getInventory(); + ItemStack& stack = inventory->getSlot(player->getChosenSlot()); + ItemDef* item = indices->getItemDef(stack.getItemId()); assert(item != nullptr); float multiplier = 0.5f; shader->uniform3f("u_torchlightColor", diff --git a/src/frontend/gui/UINode.cpp b/src/frontend/gui/UINode.cpp index 205347f3..239da047 100644 --- a/src/frontend/gui/UINode.cpp +++ b/src/frontend/gui/UINode.cpp @@ -10,8 +10,6 @@ using gui::Align; using glm::vec2; using glm::vec4; -#include - UINode::UINode(vec2 coord, vec2 size) : coord(coord), size_(size) { } @@ -85,7 +83,7 @@ shared_ptr UINode::getAt(vec2 pos, shared_ptr self) { } bool UINode::isInteractive() const { - return interactive; + return interactive && visible(); } void UINode::setInteractive(bool flag) { diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index aa7fd378..8a4c3a0c 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -44,6 +44,7 @@ #include "../engine.h" #include "../core_defs.h" #include "../items/ItemDef.h" +#include "../items/Inventory.h" using glm::vec2; using glm::vec3; @@ -163,26 +164,142 @@ void HudRenderer::createDebugPanel(Engine* engine) { panel->refresh(); } +std::shared_ptr HudRenderer::createContentAccess() { + auto level = frontend->getLevel(); + auto content = level->content; + auto indices = content->getIndices(); + auto player = level->player; + auto inventory = player->getInventory(); + + int itemsCount = indices->countItemDefs(); + auto accessInventory = std::make_shared(itemsCount); + for (int id = 1; id < itemsCount; id++) { + accessInventory->getSlot(id-1).set(ItemStack(id, 1)); + } + + const int slotSize = InventoryView::SLOT_SIZE; + const int interval = InventoryView::SLOT_INTERVAL; + int padding = 8; + + int columns = 8; + int rows = ceildiv(itemsCount-1, columns); + uint cawidth = columns * (slotSize + interval) - interval + padding; + uint caheight = rows * (slotSize + interval) - interval + padding*2; + auto layout = std::make_unique(glm::vec2(cawidth, caheight)); + for (int i = 0; i < itemsCount-1; i++) { + int row = i / columns; + int col = i % columns; + glm::vec2 position ( + col * slotSize + (col-1) * interval + padding, + row * slotSize + (row-1) * interval + padding + ); + layout->add(SlotLayout(position, false, true, + [=](ItemStack& item) { + auto copy = ItemStack(item); + inventory->move(copy, indices); + }, + [=](ItemStack& item, ItemStack& grabbed) { + inventory->getSlot(player->getChosenSlot()).set(item); + })); + } + auto contentAccess = std::make_shared( + content, + frontend, + interaction.get(), + accessInventory, + std::move(layout) + ); + contentAccess->build(); + return contentAccess; +} + +std::shared_ptr HudRenderer::createHotbar() { + auto level = frontend->getLevel(); + auto player = level->player; + auto inventory = player->getInventory(); + auto content = level->content; + + const int slotSize = InventoryView::SLOT_SIZE; + const int interval = InventoryView::SLOT_INTERVAL; + + int padding = 4; + uint width = 10 * (slotSize + interval) - interval + padding*2; + uint height = slotSize + padding * 2; + auto layout = std::make_unique(glm::vec2(width, height)); + for (int i = 0; i < 10; i++) { + glm::vec2 position (i * (slotSize + interval) + padding, padding); + layout->add(SlotLayout(position, false, false, nullptr, nullptr)); + } + layout->setOrigin(glm::vec2(width / 2, 0)); + auto view = std::make_shared( + content, + frontend, + interaction.get(), + inventory, + std::move(layout) + ); + view->build(); + view->setInteractive(false); + return view; +} + +std::shared_ptr HudRenderer::createInventory() { + auto level = frontend->getLevel(); + auto player = level->player; + auto inventory = player->getInventory(); + auto content = level->content; + + SlotLayout slotLayout(glm::vec2(), true, false, [=](ItemStack& stack) { + stack.clear(); + }, nullptr); + + int columns = 10; + int rows = ceildiv(inventory->size(), columns); + int padding = 4; + + InventoryBuilder builder; + builder.addGrid(columns, rows, glm::vec2(), padding, slotLayout); + auto layout = builder.build(); + + auto view = std::make_shared( + content, + frontend, + interaction.get(), + inventory, + std::move(layout) + ); + view->build(); + return view; +} + HudRenderer::HudRenderer(Engine* engine, LevelFrontend* frontend) : assets(engine->getAssets()), gui(engine->getGUI()), - frontend(frontend) { - - auto level = frontend->getLevel(); + frontend(frontend) +{ auto menu = gui->getMenu(); - auto content = level->content; - auto indices = content->getIndices(); - std::vector items; - for (itemid_t id = 1; id < indices->countItemDefs(); id++) { - items.push_back(id); - } - contentAccess.reset(new InventoryView(8, content, frontend, items)); - contentAccess->setSlotConsumer([=](blockid_t id) { - level->player->setChosenItem(id); - }); + interaction = std::make_unique(); + grabbedItemView = std::make_shared( + interaction->getGrabbedItem(), + frontend, + interaction.get(), + frontend->getLevel()->content, + SlotLayout(glm::vec2(), false, false, nullptr, nullptr) + ); + grabbedItemView->color(glm::vec4()); + grabbedItemView->setInteractive(false); - hotbarView.reset(new InventoryView(1, content, frontend, std::vector {0})); + contentAccess = createContentAccess(); + contentAccessPanel = std::make_shared( + contentAccess->size(), vec4(0.0f), 0.0f + ); + contentAccessPanel->color(glm::vec4()); + contentAccessPanel->add(contentAccess); + contentAccessPanel->scrollable(true); + + hotbarView = createHotbar(); + inventoryView = createInventory(); uicamera = new Camera(vec3(), 1); uicamera->perspective = false; @@ -191,10 +308,18 @@ HudRenderer::HudRenderer(Engine* engine, LevelFrontend* frontend) createDebugPanel(engine); menu->reset(); - gui->add(this->debugPanel); + gui->add(debugPanel); + gui->add(contentAccessPanel); + gui->add(hotbarView); + gui->add(inventoryView); + gui->add(grabbedItemView); } HudRenderer::~HudRenderer() { + gui->remove(grabbedItemView); + gui->remove(inventoryView); + gui->remove(hotbarView); + gui->remove(contentAccessPanel); gui->remove(debugPanel); delete uicamera; } @@ -206,7 +331,12 @@ void HudRenderer::drawDebug(int fps){ } void HudRenderer::update(bool visible) { + auto level = frontend->getLevel(); + auto player = level->player; auto menu = gui->getMenu(); + + menu->visible(pause); + if (!visible && inventoryOpen) { inventoryOpen = false; } @@ -232,13 +362,35 @@ void HudRenderer::update(bool visible) { if ((pause || inventoryOpen) == Events::_cursor_locked) { Events::toggleCursor(); } + + glm::vec2 invSize = contentAccessPanel->size(); + inventoryView->visible(inventoryOpen); + contentAccessPanel->visible(inventoryOpen); + contentAccessPanel->size(glm::vec2(invSize.x, Window::height)); + + for (int i = keycode::NUM_1; i <= keycode::NUM_9; i++) { + if (Events::jpressed(i)) { + player->setChosenSlot(i - keycode::NUM_1); + } + } + if (Events::jpressed(keycode::NUM_0)) { + player->setChosenSlot(9); + } + if (!inventoryOpen && Events::scroll) { + int slot = player->getChosenSlot(); + slot = (slot + Events::scroll) % 10; + if (slot < 0) { + slot += 10; + } + player->setChosenSlot(slot); + } } void HudRenderer::drawOverlay(const GfxContext& ctx) { if (pause) { Shader* uishader = assets->getShader("ui"); uishader->use(); - uishader->uniformMatrix("u_projview", uicamera->getProjection()*uicamera->getView()); + uishader->uniformMatrix("u_projview", uicamera->getProjView()); const Viewport& viewport = ctx.getViewport(); const uint width = viewport.getWidth(); @@ -271,12 +423,11 @@ void HudRenderer::draw(const GfxContext& ctx){ Shader* uishader = assets->getShader("ui"); uishader->use(); - uishader->uniformMatrix("u_projview", uicamera->getProjection()*uicamera->getView()); + uishader->uniformMatrix("u_projview", uicamera->getProjView()); // Draw selected item preview - hotbarView->setPosition(width-60, height-60); - hotbarView->setItems({player->getChosenItem()}); - hotbarView->actAndDraw(&ctx); + hotbarView->setCoord(glm::vec2(width/2, height-65)); + hotbarView->setSelected(player->getChosenSlot()); // Crosshair batch->begin(); @@ -289,10 +440,20 @@ void HudRenderer::draw(const GfxContext& ctx){ } if (inventoryOpen) { - // draw content access panel (all available items) - contentAccess->setPosition(viewport.getWidth()-contentAccess->getWidth(), 0); - contentAccess->actAndDraw(&ctx); + auto caLayout = contentAccess->getLayout(); + auto invLayout = inventoryView->getLayout(); + float caWidth = caLayout->getSize().x; + glm::vec2 invSize = invLayout->getSize(); + + float width = viewport.getWidth(); + + inventoryView->setCoord(glm::vec2( + glm::min(width/2-invSize.x/2, width-caWidth-10-invSize.x), + height/2-invSize.y/2 + )); + contentAccessPanel->setCoord(glm::vec2(width-caWidth, 0)); } + grabbedItemView->setCoord(glm::vec2(Events::cursor)); batch->render(); } diff --git a/src/frontend/hud.h b/src/frontend/hud.h index 2cf2cdfe..05515e0c 100644 --- a/src/frontend/hud.h +++ b/src/frontend/hud.h @@ -15,12 +15,15 @@ class Assets; class Player; class Level; class Engine; +class SlotView; class InventoryView; class LevelFrontend; +class InventoryInteraction; namespace gui { class GUI; class UINode; + class Panel; } class HudRenderer { @@ -34,13 +37,21 @@ class HudRenderer { bool inventoryOpen = false; bool pause = false; - std::unique_ptr contentAccess; - std::unique_ptr hotbarView; + std::shared_ptr contentAccessPanel; + std::shared_ptr contentAccess; + std::shared_ptr hotbarView; + std::shared_ptr inventoryView; std::shared_ptr debugPanel; + std::unique_ptr interaction; + std::shared_ptr grabbedItemView; gui::GUI* gui; LevelFrontend* frontend; void createDebugPanel(Engine* engine); + + std::shared_ptr createContentAccess(); + std::shared_ptr createHotbar(); + std::shared_ptr createInventory(); public: HudRenderer(Engine* engine, LevelFrontend* frontend); ~HudRenderer(); diff --git a/src/items/Inventory.cpp b/src/items/Inventory.cpp new file mode 100644 index 00000000..ba2025ad --- /dev/null +++ b/src/items/Inventory.cpp @@ -0,0 +1,74 @@ +#include "Inventory.h" + +Inventory::Inventory(size_t size) : slots(size) { +} + +ItemStack& Inventory::getSlot(size_t index) { + return slots[index]; +} + +size_t Inventory::findEmptySlot(size_t begin, size_t end) const { + end = std::min(slots.size(), end); + for (size_t i = begin; i < end; i++) { + if (slots[i].isEmpty()) { + return i; + } + } + return npos; +} + +size_t Inventory::findSlotByItem(itemid_t id, size_t begin, size_t end) { + end = std::min(slots.size(), end); + for (size_t i = begin; i < end; i++) { + if (slots[i].getItemId() == id) { + return i; + } + } + return npos; +} + +void Inventory::move( + ItemStack& item, + const ContentIndices* indices, + size_t begin, + size_t end) +{ + end = std::min(slots.size(), end); + for (size_t i = begin; i < end && !item.isEmpty(); i++) { + ItemStack& slot = slots[i]; + if (slot.accepts(item)) { + slot.move(item, indices); + } + } +} + +void Inventory::read(const dynamic::Map* src) { + auto slotsarr = src->list("slots"); + size_t slotscount = std::min(slotsarr->size(), slots.size()); + for (size_t i = 0; i < slotscount; i++) { + auto item = slotsarr->map(i); + itemid_t id = item->getInt("id", ITEM_EMPTY); + itemcount_t count = item->getInt("count", 0); + auto& slot = slots[i]; + slot.set(ItemStack(id, count)); + } +} + +std::unique_ptr Inventory::write() const { + auto map = std::make_unique(); + auto& slotsarr = map->putList("slots"); + for (size_t i = 0; i < slots.size(); i++) { + auto& item = slots[i]; + itemid_t id = item.getItemId(); + itemcount_t count = item.getCount(); + + auto& slotmap = slotsarr.putMap(); + slotmap.put("id", id); + if (count) { + slotmap.put("count", count); + } + } + return map; +} + +const size_t Inventory::npos = -1; diff --git a/src/items/Inventory.h b/src/items/Inventory.h new file mode 100644 index 00000000..34a38b44 --- /dev/null +++ b/src/items/Inventory.h @@ -0,0 +1,41 @@ +#ifndef ITEMS_INVENTORY_H_ +#define ITEMS_INVENTORY_H_ + +#include +#include + +#include "ItemStack.h" + +#include "../typedefs.h" +#include "../data/dynamic.h" + +class ContentIndices; + +class Inventory { + std::vector slots; +public: + Inventory(size_t size); + + ItemStack& getSlot(size_t index); + size_t findEmptySlot(size_t begin=0, size_t end=-1) const; + size_t findSlotByItem(itemid_t id, size_t begin=0, size_t end=-1); + + inline size_t size() const { + return slots.size(); + } + + void move( + ItemStack& item, + const ContentIndices* indices, + size_t begin=0, + size_t end=-1); + + /* deserializing inventory */ + void read(const dynamic::Map* src); + /* serializing inventory */ + std::unique_ptr write() const; + + static const size_t npos; +}; + +#endif // ITEMS_INVENTORY_H_ diff --git a/src/items/ItemStack.cpp b/src/items/ItemStack.cpp new file mode 100644 index 00000000..f43b7546 --- /dev/null +++ b/src/items/ItemStack.cpp @@ -0,0 +1,40 @@ +#include "ItemStack.h" + +#include "ItemDef.h" +#include "../content/Content.h" + +ItemStack::ItemStack() : item(ITEM_EMPTY), count(0) { +} + +ItemStack::ItemStack(itemid_t item, itemcount_t count) : item(item), count(count) { +} + +void ItemStack::set(const ItemStack& item) { + this->item = item.item; + this->count = item.count; +} + +bool ItemStack::accepts(const ItemStack& other) const { + if (isEmpty()) { + return true; + } + return item == other.getItemId(); +} + +void ItemStack::move(ItemStack& item, const ContentIndices* indices) { + auto def = indices->getItemDef(item.getItemId()); + int count = std::min(item.count, def->stackSize-this->count); + if (isEmpty()) { + set(ItemStack(item.getItemId(), count)); + } else { + setCount(this->count + count); + } + item.setCount(item.count-count); +} + +void ItemStack::setCount(itemcount_t count) { + this->count = count; + if (count == 0) { + item = 0; + } +} diff --git a/src/items/ItemStack.h b/src/items/ItemStack.h new file mode 100644 index 00000000..7cc51e55 --- /dev/null +++ b/src/items/ItemStack.h @@ -0,0 +1,40 @@ +#ifndef ITEMS_ITEM_STACK_H_ +#define ITEMS_ITEM_STACK_H_ + +#include "../typedefs.h" +#include "../constants.h" + +class ContentIndices; + +class ItemStack { + itemid_t item; + itemcount_t count; +public: + ItemStack(); + + ItemStack(itemid_t item, itemcount_t count); + + void set(const ItemStack& item); + void setCount(itemcount_t count); + + bool accepts(const ItemStack& item) const; + void move(ItemStack& item, const ContentIndices* indices); + + inline void clear() { + set(ItemStack(0, 0)); + } + + inline bool isEmpty() const { + return item == ITEM_EMPTY; + } + + inline itemid_t getItemId() const { + return item; + } + + inline itemcount_t getCount() const { + return count; + } +}; + +#endif // ITEMS_ITEM_STACK_H_ diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index a4b717f0..1b0ba530 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -13,6 +13,8 @@ #include "../window/Events.h" #include "../window/input.h" #include "../items/ItemDef.h" +#include "../items/ItemStack.h" +#include "../items/Inventory.h" #include "scripting/scripting.h" #include "BlocksController.h" @@ -239,7 +241,9 @@ void PlayerController::updateInteraction(){ int z = iend.z; uint8_t states = 0; - ItemDef* item = indices->getItemDef(player->getChosenItem()); + auto inventory = player->getInventory(); + ItemStack& stack = inventory->getSlot(player->getChosenSlot()); + ItemDef* item = indices->getItemDef(stack.getItemId()); Block* def = indices->getBlockDef(item->rt.placingBlock); if (def && def->rotatable){ const std::string& name = def->rotations.name; @@ -311,7 +315,18 @@ void PlayerController::updateInteraction(){ } if (Events::jactive(BIND_PLAYER_PICK)){ Block* block = indices->getBlockDef(chunks->get(x,y,z)->id); - player->setChosenItem(block->rt.pickingItem); + itemid_t id = block->rt.pickingItem; + auto inventory = player->getInventory(); + size_t slotid = inventory->findSlotByItem(id); + if (slotid == Inventory::npos) { + slotid = player->getChosenSlot(); + } else { + player->setChosenSlot(slotid); + } + ItemStack& stack = inventory->getSlot(slotid); + if (stack.getItemId() != id) { + stack.set(ItemStack(id, 1)); + } } } else { selectedBlockId = -1; diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index d61b667a..6e32a2fd 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -5,6 +5,7 @@ #include "../world/Level.h" #include "../window/Events.h" #include "../window/Camera.h" +#include "../items/Inventory.h" #include @@ -18,12 +19,16 @@ const float JUMP_FORCE = 8.0f; Player::Player(glm::vec3 position, float speed) : speed(speed), - chosenItem(0), + chosenSlot(0), camera(new Camera(position, glm::radians(90.0f))), spCamera(new Camera(position, glm::radians(90.0f))), tpCamera(new Camera(position, glm::radians(90.0f))), currentCamera(camera), - hitbox(new Hitbox(position, glm::vec3(0.3f,0.9f,0.3f))) { + hitbox(new Hitbox(position, glm::vec3(0.3f,0.9f,0.3f))), + inventory(new Inventory(40)) { +} + +Player::~Player() { } void Player::update( @@ -118,14 +123,18 @@ void Player::teleport(glm::vec3 position) { hitbox->position = position; } -void Player::setChosenItem(itemid_t id) { - chosenItem = id; +void Player::setChosenSlot(int index) { + chosenSlot = index; } -itemid_t Player::getChosenItem() const { - return chosenItem; +int Player::getChosenSlot() const { + return chosenSlot; } float Player::getSpeed() const { return speed; } + +std::shared_ptr Player::getInventory() const { + return inventory; +} diff --git a/src/objects/Player.h b/src/objects/Player.h index 46f39aa6..b58b2c4b 100644 --- a/src/objects/Player.h +++ b/src/objects/Player.h @@ -9,6 +9,7 @@ class Camera; class Hitbox; +class Inventory; class PhysicsSolver; class Chunks; class Level; @@ -30,11 +31,12 @@ struct PlayerInput { class Player { float speed; - itemid_t chosenItem; + int chosenSlot; public: std::shared_ptr camera, spCamera, tpCamera; std::shared_ptr currentCamera; std::unique_ptr hitbox; + std::shared_ptr inventory; bool flight = false; bool noclip = false; bool debug = false; @@ -43,15 +45,17 @@ public: glm::vec2 cam = {}; Player(glm::vec3 position, float speed); - ~Player() = default; + ~Player(); void teleport(glm::vec3 position); void update(Level* level, PlayerInput& input, float delta); - void setChosenItem(itemid_t id); + void setChosenSlot(int index); - itemid_t getChosenItem() const; + int getChosenSlot() const; float getSpeed() const; + + std::shared_ptr getInventory() const; }; #endif /* SRC_OBJECTS_PLAYER_H_ */