diff --git a/res/content/base/items/bazalt_breaker.json b/res/content/base/items/bazalt_breaker.json index 59aeabdb..9e70fa55 100644 --- a/res/content/base/items/bazalt_breaker.json +++ b/res/content/base/items/bazalt_breaker.json @@ -1,4 +1,5 @@ { "icon-type": "sprite", - "icon": "items:bazalt_breaker" + "icon": "items:bazalt_breaker", + "uses": 100 } diff --git a/res/content/base/modules/util.lua b/res/content/base/modules/util.lua index d12337fb..b1a97ad0 100644 --- a/res/content/base/modules/util.lua +++ b/res/content/base/modules/util.lua @@ -1,12 +1,13 @@ local util = {} -function util.drop(ppos, itemid, count, pickup_delay) +function util.drop(ppos, itemid, count, data, pickup_delay) if itemid == 0 or not itemid then return nil end return entities.spawn("base:drop", ppos, {base__drop={ id=itemid, count=count, + data=data, pickup_delay=pickup_delay }}) end diff --git a/res/content/base/scripts/bazalt_breaker.lua b/res/content/base/scripts/bazalt_breaker.lua index 78b872b2..8869eca5 100644 --- a/res/content/base/scripts/bazalt_breaker.lua +++ b/res/content/base/scripts/bazalt_breaker.lua @@ -1,3 +1,6 @@ -function on_block_break_by(x, y, z, p) +function on_block_break_by(x, y, z, pid) block.set(x, y, z, 0, 0) + if not player.is_infinite_items(pid) then + inventory.use(player.get_inventory(pid)) + end end diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index 7aa8babb..cf57f72f 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -14,6 +14,7 @@ end if SAVED_DATA.item then dropitem.id = item.index(SAVED_DATA.item) dropitem.count = SAVED_DATA.count + dropitem.data = SAVED_DATA.data end local DROP_SCALE = 0.3 @@ -25,6 +26,7 @@ local rotation = mat4.rotate({ function on_save() SAVED_DATA.item = item.name(dropitem.id) SAVED_DATA.count = dropitem.count + SAVED_DATA.data = dropitem.data end do -- setup visuals @@ -59,11 +61,10 @@ function on_sensor_enter(index, oid) if pid == -1 then -- other is base:drop too if index == 0 and other:def_index() == def_index then - local odrop = other:get_component("base:drop") - if odrop.dropitem.id == dropitem.id then - -- // TODO: replace combination logic with item.* function + local odrop = other:get_component("base:drop").dropitem + if odrop.id == dropitem.id and not odrop.data then local stack = item.stack_size(dropitem.id) - local sum = dropitem.count + odrop.dropitem.count + local sum = dropitem.count + odrop.count if sum <= stack then dropitem.count = sum other:despawn() @@ -75,7 +76,7 @@ function on_sensor_enter(index, oid) if timer < 0.0 and index == 0 then entity:despawn() - inventory.add(player.get_inventory(pid), dropitem.id, dropitem.count) + inventory.add(player.get_inventory(pid), dropitem.id, dropitem.count, dropitem.data) audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") end if index == 1 then diff --git a/res/content/base/scripts/hud.lua b/res/content/base/scripts/hud.lua index 31125631..00f6bdc6 100644 --- a/res/content/base/scripts/hud.lua +++ b/res/content/base/scripts/hud.lua @@ -14,12 +14,13 @@ function on_hud_open() if itemid == 0 then return end + local data = inventory.get_all_data(invid, slot) inventory.set(invid, slot, itemid, itemcount-1) local pvel = {player.get_vel(pid)} local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0}) local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE) - local drop = base_util.drop(ppos, itemid, 1, 1.5) + local drop = base_util.drop(ppos, itemid, 1, data, 1.5) local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL)) drop.rigidbody:set_vel(velocity) end) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 6d458584..8a5f66eb 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -94,6 +94,31 @@ elseif __vc_app then complete_app_lib(__vc_app) end +function inventory.get_uses(invid, slot) + local uses = inventory.get_data(invid, slot, "uses") + if uses == nil then + return item.uses(inventory.get(invid, slot)) + end + return uses +end + + +function inventory.use(invid, slot) + local itemid, count = inventory.get(invid, slot) + if itemid == nil then + return + end + local item_uses = inventory.get_uses(invid, slot) + if item_uses == nil then + return + end + if item_uses == 1 then + inventory.set(invid, slot, itemid, count - 1) + elseif item_uses > 1 then + inventory.set_data(invid, slot, "uses", item_uses - 1) + end +end + ------------------------------------------------ ------------------- Events --------------------- ------------------------------------------------ diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 64d4f69b..b2457cf4 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -429,15 +429,29 @@ void ContentLoader::loadItem( } else if (iconTypeStr == "sprite") { def.iconType = ItemIconType::SPRITE; } else if (iconTypeStr.length()) { - logger.error() << name << ": unknown icon type" << iconTypeStr; + logger.error() << name << ": unknown icon type - " << iconTypeStr; } root.at("icon").get(def.icon); root.at("placing-block").get(def.placingBlock); root.at("script-name").get(def.scriptName); root.at("model-name").get(def.modelName); root.at("stack-size").get(def.stackSize); + root.at("uses").get(def.uses); + + std::string usesDisplayStr = ""; + root.at("uses-display").get(usesDisplayStr); + if (usesDisplayStr == "none") { + def.usesDisplay = ItemUsesDisplay::NONE; + } else if (usesDisplayStr == "number") { + def.usesDisplay = ItemUsesDisplay::NUMBER; + } else if (usesDisplayStr == "relation") { + def.usesDisplay = ItemUsesDisplay::RELATION; + } else if (usesDisplayStr == "vbar") { + def.usesDisplay = ItemUsesDisplay::VBAR; + } else if (usesDisplayStr.length()) { + logger.error() << name << ": unknown uses display mode - " << usesDisplayStr; + } - // item light emission [r, g, b] where r,g,b in range [0..15] if (auto found = root.at("emission")) { const auto& emissionarr = *found; def.emission[0] = emissionarr[0].asNumber(); diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index 1f062240..1684799f 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -70,7 +70,7 @@ std::shared_ptr create_debug_panel( ); HudElement::HudElement( - hud_element_mode mode, + HudElementMode mode, UiDocument* document, std::shared_ptr node, bool debug @@ -83,16 +83,16 @@ void HudElement::update(bool pause, bool inventoryOpen, bool debugMode) { return; } switch (mode) { - case hud_element_mode::permanent: + case HudElementMode::PERMANENT: node->setVisible(true); break; - case hud_element_mode::ingame: + case HudElementMode::INGAME: node->setVisible(!pause && !inventoryOpen); break; - case hud_element_mode::inventory_any: + case HudElementMode::INVENTORY_ANY: node->setVisible(inventoryOpen); break; - case hud_element_mode::inventory_bound: + case HudElementMode::INVENTORY: removed = !inventoryOpen; break; } @@ -108,21 +108,21 @@ std::shared_ptr HudElement::getNode() const { std::shared_ptr Hud::createContentAccess() { auto& content = frontend.getLevel().content; - auto indices = content.getIndices(); + auto& indices = *content.getIndices(); auto inventory = player.getInventory(); - size_t itemsCount = indices->items.count(); + size_t itemsCount = indices.items.count(); auto accessInventory = std::make_shared(0, itemsCount); for (size_t id = 1; id < itemsCount; id++) { accessInventory->getSlot(id-1).set(ItemStack(id, 1)); } SlotLayout slotLayout(-1, glm::vec2(), false, true, nullptr, - [=](uint, ItemStack& item) { + [inventory, &indices](uint, ItemStack& item) { auto copy = ItemStack(item); inventory->move(copy, indices); }, - [=](uint, ItemStack& item) { + [this, inventory](uint, ItemStack& item) { inventory->getSlot(player.getChosenSlot()).set(item); }); @@ -192,7 +192,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player) auto dplotter = std::make_shared(350, 250, 2000, 16); dplotter->setGravity(Gravity::bottom_right); dplotter->setInteractive(false); - add(HudElement(hud_element_mode::permanent, nullptr, dplotter, true)); + add(HudElement(HudElementMode::PERMANENT, nullptr, dplotter, true)); assets.store(Texture::from(debugImgWorldGen.get()), DEBUG_WORLDGEN_IMAGE); @@ -200,7 +200,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player) "" ); - add(HudElement(hud_element_mode::permanent, nullptr, debugMinimap, true)); + add(HudElement(HudElementMode::PERMANENT, nullptr, debugMinimap, true)); } Hud::~Hud() { @@ -372,8 +372,8 @@ void Hud::openInventory() { auto inventoryDocument = assets.get("core:inventory"); inventoryView = std::dynamic_pointer_cast(inventoryDocument->getRoot()); inventoryView->bind(inventory, &content); - add(HudElement(hud_element_mode::inventory_bound, inventoryDocument, inventoryView, false)); - add(HudElement(hud_element_mode::inventory_bound, nullptr, exchangeSlot, false)); + add(HudElement(HudElementMode::INVENTORY, inventoryDocument, inventoryView, false)); + add(HudElement(HudElementMode::INVENTORY, nullptr, exchangeSlot, false)); } std::shared_ptr Hud::openInventory( @@ -401,7 +401,7 @@ std::shared_ptr Hud::openInventory( inv = level.inventories->createVirtual(secondInvView->getSlotsCount()); } secondInvView->bind(inv, &content); - add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false)); + add(HudElement(HudElementMode::INVENTORY, doc, secondUI, false)); scripting::on_inventory_open(&player, *inv); return inv; } @@ -436,7 +436,7 @@ void Hud::openInventory( blockUI->bind(blockinv, &content); blockPos = block; currentblockid = chunks.require(block.x, block.y, block.z).id; - add(HudElement(hud_element_mode::inventory_bound, doc, blockUI, false)); + add(HudElement(HudElementMode::INVENTORY, doc, blockUI, false)); scripting::on_inventory_open(&player, *blockinv); } @@ -468,8 +468,7 @@ void Hud::showOverlay( showExchangeSlot(); inventoryOpen = true; } - add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false), - args); + add(HudElement(HudElementMode::INVENTORY, doc, secondUI, false), args); } void Hud::openPermanent(UiDocument* doc) { @@ -480,7 +479,7 @@ void Hud::openPermanent(UiDocument* doc) { if (invview) { invview->bind(player.getInventory(), &frontend.getLevel().content); } - add(HudElement(hud_element_mode::permanent, doc, doc->getRoot(), false)); + add(HudElement(HudElementMode::PERMANENT, doc, doc->getRoot(), false)); } void Hud::dropExchangeSlot() { @@ -494,12 +493,12 @@ void Hud::dropExchangeSlot() { auto indices = frontend.getLevel().content.getIndices(); if (auto invView = std::dynamic_pointer_cast(blockUI)) { - invView->getInventory()->move(stack, indices); + invView->getInventory()->move(stack, *indices); } if (stack.isEmpty()) { return; } - player.getInventory()->move(stack, indices); + player.getInventory()->move(stack, *indices); if (!stack.isEmpty()) { logger.warning() << "discard item [" << stack.getItemId() << ":" << stack.getCount(); diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp index 07affc66..a978e282 100644 --- a/src/frontend/hud.hpp +++ b/src/frontend/hud.hpp @@ -30,26 +30,26 @@ namespace gui { class SlotView; } -enum class hud_element_mode { +enum class HudElementMode { // element is hidden if menu or inventory open - ingame, + INGAME, // element is visible if hud is visible - permanent, + PERMANENT, // element is visible in inventory mode - inventory_any, + INVENTORY_ANY, // element will be removed on inventory close - inventory_bound + INVENTORY }; class HudElement { - hud_element_mode mode; + HudElementMode mode; UiDocument* document; std::shared_ptr node; bool debug; bool removed = false; public: - HudElement(hud_element_mode mode, UiDocument* document, std::shared_ptr node, bool debug); + HudElement(HudElementMode mode, UiDocument* document, std::shared_ptr node, bool debug); void update(bool pause, bool inventoryOpen, bool debug); @@ -57,7 +57,7 @@ public: std::shared_ptr getNode() const; bool isInventoryBound() const { - return mode == hud_element_mode::inventory_bound; + return mode == HudElementMode::INVENTORY; } void setRemoved() { diff --git a/src/graphics/core/Batch2D.hpp b/src/graphics/core/Batch2D.hpp index 6bfdb14d..5610f603 100644 --- a/src/graphics/core/Batch2D.hpp +++ b/src/graphics/core/Batch2D.hpp @@ -48,10 +48,15 @@ public: void sprite(float x, float y, float w, float h, float skew, int atlasRes, int index, glm::vec4 tint); void point(float x, float y, float r, float g, float b, float a); - inline void setColor(glm::vec4 color) { + void setColor(glm::vec4 color) { this->color = color; } - inline glm::vec4 getColor() const { + + void resetColor() { + this->color = glm::vec4(1.0f); + } + + glm::vec4 getColor() const { return color; } diff --git a/src/graphics/ui/elements/InventoryView.cpp b/src/graphics/ui/elements/InventoryView.cpp index 22e1b82a..a5a2c8a5 100644 --- a/src/graphics/ui/elements/InventoryView.cpp +++ b/src/graphics/ui/elements/InventoryView.cpp @@ -8,7 +8,6 @@ #include "items/Inventories.hpp" #include "items/Inventory.hpp" #include "items/ItemDef.hpp" -#include "items/ItemStack.hpp" #include "logic/scripting/scripting.hpp" #include "maths/voxmaths.hpp" #include "objects/Player.hpp" @@ -115,26 +114,73 @@ SlotView::SlotView( 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; } - itemid_t itemid = bound->getItemId(); - if (itemid != prevItem) { - if (itemid) { - auto& def = content->getIndices()->items.require(itemid); - tooltip = util::pascal_case( - langs::get(util::str2wstr_utf8(def.caption)) - ); - } else { - tooltip.clear(); - } - } - prevItem = itemid; - - const int slotSize = InventoryView::SLOT_SIZE; - + 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(); @@ -144,59 +190,84 @@ void SlotView::draw(const DrawContext& pctx, const Assets& assets) { color = glm::vec4(1, 1, 1, 0.2f); } - auto batch = pctx.getBatch2D(); - batch->setColor(color); + auto& batch = *pctx.getBatch2D(); + if (color.a > 0.0) { - batch->texture(nullptr); + batch.setColor(color); + batch.texture(nullptr); + + const int size = InventoryView::SLOT_SIZE; if (highlighted) { - batch->rect(pos.x-4, pos.y-4, slotSize+8, slotSize+8); + batch.rect(pos.x - 4, pos.y - 4, size + 8, size + 8); } else { - batch->rect(pos.x, pos.y, slotSize, slotSize); - } - } - - batch->setColor(glm::vec4(1.0f)); - - auto previews = assets.get("block-previews"); - auto indices = content->getIndices(); - - auto& item = indices->items.require(stack.getItemId()); - switch (item.iconType) { - case ItemIconType::NONE: - break; - case ItemIconType::BLOCK: { - const Block& cblock = content->blocks.require(item.icon); - batch->texture(previews->getTexture()); - - UVRegion region = previews->get(cblock.name); - batch->rect( - pos.x, pos.y, slotSize, slotSize, - 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, slotSize, slotSize, - 0, 0, 0, textureRegion.region, false, true, tint); - break; + 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("normal"); + 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) { - auto font = assets.get("normal"); - std::wstring text = std::to_wstring(stack.getCount()); + 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); + } - int x = pos.x+slotSize-text.length()*8; - int y = pos.y+slotSize-16; - - batch->setColor({0, 0, 0, 1.0f}); - font->draw(*batch, text, x+1, y+1, nullptr, 0); - batch->setColor(glm::vec4(1.0f)); - font->draw(*batch, text, x, y, nullptr, 0); + 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; + } } } @@ -219,7 +290,7 @@ void SlotView::performLeftClick(ItemStack& stack, ItemStack& grabbed) { return; } if (!layout.itemSource && stack.accepts(grabbed) && layout.placing) { - stack.move(grabbed, content->getIndices()); + stack.move(grabbed, *content->getIndices()); } else { if (layout.itemSource) { if (grabbed.isEmpty()) { @@ -249,10 +320,11 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) { return; if (grabbed.isEmpty()) { if (!stack.isEmpty() && layout.taking) { - grabbed.set(stack); + grabbed.set(std::move(stack)); int halfremain = stack.getCount() / 2; grabbed.setCount(stack.getCount() - halfremain); - stack.setCount(halfremain); + // reset all data in the origin slot + stack = ItemStack(stack.getItemId(), halfremain); } return; } @@ -261,9 +333,14 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) { return; } if (stack.isEmpty()) { - stack.set(grabbed); + itemcount_t count = grabbed.getCount(); + stack.set(std::move(grabbed)); stack.setCount(1); - grabbed.setCount(grabbed.getCount() - 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); diff --git a/src/graphics/ui/elements/InventoryView.hpp b/src/graphics/ui/elements/InventoryView.hpp index f98bb20c..c43d48fe 100644 --- a/src/graphics/ui/elements/InventoryView.hpp +++ b/src/graphics/ui/elements/InventoryView.hpp @@ -4,15 +4,18 @@ #include "Container.hpp" #include "typedefs.hpp" #include "constants.hpp" +#include "items/ItemStack.hpp" #include #include #include +class Font; class Assets; +class ItemDef; +class Batch2D; class DrawContext; class Content; -class ItemStack; class ContentIndices; class LevelFrontend; class Inventory; @@ -49,6 +52,11 @@ namespace gui { }; class SlotView : public gui::UINode { + struct { + ItemStack stack {}; + std::wstring countStr; + } cache; + const Content* content = nullptr; SlotLayout layout; bool highlighted = false; @@ -56,11 +64,27 @@ namespace gui { int64_t inventoryid = 0; ItemStack* bound = nullptr; - std::wstring tooltip; - itemid_t prevItem = 0; - void performLeftClick(ItemStack& stack, ItemStack& grabbed); void performRightClick(ItemStack& stack, ItemStack& grabbed); + + void drawItemIcon( + Batch2D& batch, + const ItemStack& stack, + const ItemDef& item, + const Assets& assets, + const glm::vec4& tint, + const glm::vec2& pos + ); + + void drawItemInfo( + Batch2D& batch, + const ItemStack& stack, + const ItemDef& item, + const Font& font, + const glm::vec2& pos + ); + + void refreshTooltip(const ItemStack& stack, const ItemDef& item); public: SlotView(SlotLayout layout); diff --git a/src/items/Inventory.cpp b/src/items/Inventory.cpp index 9797a15e..5978f283 100644 --- a/src/items/Inventory.cpp +++ b/src/items/Inventory.cpp @@ -37,7 +37,7 @@ size_t Inventory::findSlotByItem( } void Inventory::move( - ItemStack& item, const ContentIndices* indices, size_t begin, size_t end + 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++) { @@ -72,8 +72,12 @@ void Inventory::deserialize(const dv::value& src) { if (item.has("count")){ count = item["count"].asInteger(); } + dv::value fields = nullptr; + if (item.has("fields")) { + fields = item["fields"]; + } auto& slot = slots[i]; - slot.set(ItemStack(id, count)); + slot.set(ItemStack(id, count, fields)); } } @@ -92,6 +96,10 @@ dv::value Inventory::serialize() const { if (count) { slotmap["count"] = count; } + const auto& fields = item.getFields(); + if (fields != nullptr) { + slotmap["fields"] = fields; + } } return map; } @@ -110,5 +118,3 @@ void Inventory::convert(dv::value& data, const ContentReport* report) { inventory.convert(report); data = inventory.serialize(); } - -const size_t Inventory::npos = -1; diff --git a/src/items/Inventory.hpp b/src/items/Inventory.hpp index 49560523..29205640 100644 --- a/src/items/Inventory.hpp +++ b/src/items/Inventory.hpp @@ -26,13 +26,9 @@ public: itemid_t id, size_t begin = 0, size_t end = -1, size_t minCount = 1 ); - inline size_t size() const { - return slots.size(); - } - void move( ItemStack& item, - const ContentIndices* indices, + const ContentIndices& indices, size_t begin = 0, size_t end = -1 ); @@ -46,17 +42,21 @@ public: void convert(const ContentReport* report); static void convert(dv::value& data, const ContentReport* report); - inline void setId(int64_t id) { + size_t size() const { + return slots.size(); + } + + void setId(int64_t id) { this->id = id; } - inline int64_t getId() const { + int64_t getId() const { return id; } - inline bool isVirtual() const { + bool isVirtual() const { return id < 0; } - static const size_t npos; + static constexpr size_t npos = -1; }; diff --git a/src/items/ItemDef.cpp b/src/items/ItemDef.cpp index 8d0cdd66..e2b54b81 100644 --- a/src/items/ItemDef.cpp +++ b/src/items/ItemDef.cpp @@ -15,4 +15,6 @@ void ItemDef::cloneTo(ItemDef& dst) { dst.placingBlock = placingBlock; dst.scriptName = scriptName; dst.modelName = modelName; + dst.uses = uses; + dst.usesDisplay = usesDisplay; } diff --git a/src/items/ItemDef.hpp b/src/items/ItemDef.hpp index f6d61208..db4ff9f3 100644 --- a/src/items/ItemDef.hpp +++ b/src/items/ItemDef.hpp @@ -19,6 +19,14 @@ enum class ItemIconType { BLOCK, // block preview: icon is string block id }; +enum class ItemUsesDisplay { + NONE, // uses count is not displayed + NUMBER, // uses count is displayed as number + RELATION, // uses count is displayed as `remain/default` relation + VBAR, // uses count is displayed as vertical bar without counter + DEFAULT = VBAR, +}; + struct ItemDef { /// @brief Item string id (with prefix included) std::string const name; @@ -28,10 +36,21 @@ struct ItemDef { dv::value properties = nullptr; + /// @brief Item max stack size itemcount_t stackSize = 64; + + /// @brief Item is generated for other content unit (like block) bool generated = false; + + /// @brief Item light emission [r, g, b] where r,g,b in range [0..15] uint8_t emission[4] {0, 0, 0, 0}; + /// @brief Default item uses count + int16_t uses = -1; + + /// @brief Item uses count display mode + ItemUsesDisplay usesDisplay = ItemUsesDisplay::DEFAULT; + ItemIconType iconType = ItemIconType::SPRITE; std::string icon = "blocks:notfound"; diff --git a/src/items/ItemStack.cpp b/src/items/ItemStack.cpp index 99cb0a0e..f99d8700 100644 --- a/src/items/ItemStack.cpp +++ b/src/items/ItemStack.cpp @@ -3,16 +3,18 @@ #include "content/Content.hpp" #include "ItemDef.hpp" -ItemStack::ItemStack() : item(ITEM_EMPTY), count(0) { -} - -ItemStack::ItemStack(itemid_t item, itemcount_t count) - : item(item), count(count) { +ItemStack::ItemStack(itemid_t item, itemcount_t count, dv::value data) + : item(item), count(count), fields(std::move(data)) { } void ItemStack::set(const ItemStack& item) { + set(ItemStack(item)); +} + +void ItemStack::set(ItemStack&& item) { this->item = item.item; this->count = item.count; + this->fields = std::move(item.fields); if (count == 0) { this->item = 0; } @@ -25,14 +27,14 @@ bool ItemStack::accepts(const ItemStack& other) const { if (isEmpty()) { return true; } - return item == other.getItemId(); + return item == other.getItemId() && other.fields == nullptr; } -void ItemStack::move(ItemStack& item, const ContentIndices* indices) { - auto& def = indices->items.require(item.getItemId()); - int count = std::min(item.count, def.stackSize - this->count); +void ItemStack::move(ItemStack& item, const ContentIndices& indices) { + auto& def = indices.items.require(item.getItemId()); + itemcount_t count = std::min(item.count, def.stackSize - this->count); if (isEmpty()) { - set(ItemStack(item.getItemId(), count)); + set(ItemStack(item.getItemId(), count, std::move(item.fields))); } else { setCount(this->count + count); } @@ -42,6 +44,30 @@ void ItemStack::move(ItemStack& item, const ContentIndices* indices) { void ItemStack::setCount(itemcount_t count) { this->count = count; if (count == 0) { - item = 0; + clear(); } } + +void ItemStack::setField(std::string_view name, dv::value value) { + if (fields == nullptr) { + if (value == nullptr) { + return; + } + fields = dv::object(); + } + if (value == nullptr) { + fields.erase(std::string(name)); + if (fields.empty()) { + fields = nullptr; + } + return; + } + fields[std::string(name)] = std::move(value); +} + +dv::value* ItemStack::getField(const std::string& name) const { + if (fields == nullptr) { + return nullptr; + } + return fields.at(name).ptr; +} diff --git a/src/items/ItemStack.hpp b/src/items/ItemStack.hpp index 1a8e191d..91f2d3e7 100644 --- a/src/items/ItemStack.hpp +++ b/src/items/ItemStack.hpp @@ -2,36 +2,60 @@ #include "constants.hpp" #include "typedefs.hpp" +#include "data/dv.hpp" class ContentIndices; class ItemStack { - itemid_t item; - itemcount_t count; + itemid_t item = ITEM_EMPTY; + itemcount_t count = 0; + dv::value fields = nullptr; public: - ItemStack(); + ItemStack() = default; - ItemStack(itemid_t item, itemcount_t count); + ItemStack(itemid_t item, itemcount_t count, dv::value data=nullptr); void set(const ItemStack& item); + void set(ItemStack&& item); void setCount(itemcount_t count); - bool accepts(const ItemStack& item) const; - void move(ItemStack& item, const ContentIndices* indices); + /// @brief Set a field in the item stack data. + void setField(std::string_view name, dv::value value); - inline void clear() { + /// @brief Get a field from the item stack data. + /// @param name field name + /// @return value pointer or nullptr if the field does not exist. + dv::value* getField(const std::string& name) const; + + bool accepts(const ItemStack& item) const; + + /// @brief Move items from one stack to another. + /// If the target stack is completely filled, the source stack will be reduced. + /// @param item source stack + /// @param indices content indices + void move(ItemStack& item, const ContentIndices& indices); + + void clear() { set(ItemStack(0, 0)); } - inline bool isEmpty() const { + bool isEmpty() const { return item == ITEM_EMPTY; } - inline itemid_t getItemId() const { + itemid_t getItemId() const { return item; } - inline itemcount_t getCount() const { + itemcount_t getCount() const { return count; } + + const dv::value& getFields() const { + return fields; + } + + bool hasFields() const { + return fields != nullptr; + } }; diff --git a/src/logic/scripting/lua/libs/libinventory.cpp b/src/logic/scripting/lua/libs/libinventory.cpp index 65f0dd7c..3e2d390e 100644 --- a/src/logic/scripting/lua/libs/libinventory.cpp +++ b/src/logic/scripting/lua/libs/libinventory.cpp @@ -7,68 +7,73 @@ using namespace scripting; -static void validate_itemid(itemid_t id) { - if (id >= indices->items.count()) { - throw std::runtime_error("invalid item id"); +namespace { + void validate_itemid(itemid_t id) { + if (id >= indices->items.count()) { + throw std::runtime_error("invalid item id"); + } + } + + Inventory& get_inventory(int64_t id) { + auto inv = level->inventories->get(id); + if (inv == nullptr) { + throw std::runtime_error("inventory not found: " + std::to_string(id)); + } + return *inv; + } + + Inventory& get_inventory(int64_t id, int arg) { + auto inv = level->inventories->get(id); + if (inv == nullptr) { + throw std::runtime_error( + "inventory not found: " + std::to_string(id) + " argument " + + std::to_string(arg) + ); + } + return *inv; + } + + void validate_slotid(int slotid, const Inventory& inv) { + if (static_cast(slotid) >= inv.size()) { + throw std::runtime_error( + "slot index is out of range [0..inventory.size(invid)]" + ); + } + } + + using SlotFunc = int(lua::State*, ItemStack&); + + template + int wrap_slot(lua::State* L) { + auto invid = lua::tointeger(L, 1); + auto slotid = lua::tointeger(L, 2); + auto& inv = get_inventory(invid); + validate_slotid(slotid, inv); + auto& item = inv.getSlot(slotid); + return func(L, item); } } -static Inventory& get_inventory(int64_t id) { - auto inv = level->inventories->get(id); - if (inv == nullptr) { - throw std::runtime_error("inventory not found: " + std::to_string(id)); - } - return *inv; -} - -static Inventory& get_inventory(int64_t id, int arg) { - auto inv = level->inventories->get(id); - if (inv == nullptr) { - throw std::runtime_error( - "inventory not found: " + std::to_string(id) + " argument " + - std::to_string(arg) - ); - } - return *inv; -} - -static void validate_slotid(int slotid, const Inventory& inv) { - if (static_cast(slotid) >= inv.size()) { - throw std::runtime_error( - "slot index is out of range [0..inventory.size(invid)]" - ); - } -} - -static int l_get(lua::State* L) { - auto invid = lua::tointeger(L, 1); - auto slotid = lua::tointeger(L, 2); - auto inv = get_inventory(invid); - validate_slotid(slotid, inv); - const ItemStack& item = inv.getSlot(slotid); +static int l_get(lua::State* L, ItemStack& item) { lua::pushinteger(L, item.getItemId()); lua::pushinteger(L, item.getCount()); return 2; } -static int l_set(lua::State* L) { - auto invid = lua::tointeger(L, 1); - auto slotid = lua::tointeger(L, 2); +static int l_set(lua::State* L, ItemStack& item) { auto itemid = lua::tointeger(L, 3); auto count = lua::tointeger(L, 4); - validate_itemid(itemid); - - auto& inv = get_inventory(invid); - - validate_slotid(slotid, inv); - ItemStack& item = inv.getSlot(slotid); - item.set(ItemStack(itemid, count)); + auto data = lua::tovalue(L, 5); + if (!data.isObject() && data != nullptr) { + throw std::runtime_error("invalid data argument type (table expected)"); + } + item.set(ItemStack(itemid, count, std::move(data))); return 0; } static int l_size(lua::State* L) { auto invid = lua::tointeger(L, 1); - auto& inv = get_inventory(invid); + const auto& inv = get_inventory(invid); return lua::pushinteger(L, inv.size()); } @@ -76,11 +81,16 @@ static int l_add(lua::State* L) { auto invid = lua::tointeger(L, 1); auto itemid = lua::tointeger(L, 2); auto count = lua::tointeger(L, 3); + auto data = lua::tovalue(L, 4); + validate_itemid(itemid); + if (!data.isObject() && data != nullptr) { + throw std::runtime_error("invalid data argument type (table expected)"); + } auto& inv = get_inventory(invid); - ItemStack item(itemid, count); - inv.move(item, indices); + ItemStack item(itemid, count, std::move(data)); + inv.move(item, *indices); return lua::pushinteger(L, item.getCount()); } @@ -144,9 +154,9 @@ static int l_move(lua::State* L) { auto& invB = get_inventory(invBid, 3); auto& slot = invA.getSlot(slotAid); if (slotBid == -1) { - invB.move(slot, content->getIndices()); + invB.move(slot, *content->getIndices()); } else { - invB.move(slot, content->getIndices(), slotBid, slotBid + 1); + invB.move(slot, *content->getIndices(), slotBid, slotBid + 1); } return 0; } @@ -163,9 +173,9 @@ static int l_move_range(lua::State* L) { auto invB = get_inventory(invBid, 3); auto& slot = invA.getSlot(slotAid); if (slotBegin == -1) { - invB.move(slot, content->getIndices()); + invB.move(slot, *content->getIndices()); } else { - invB.move(slot, content->getIndices(), slotBegin, slotEnd); + invB.move(slot, *content->getIndices(), slotBegin, slotEnd); } return 0; } @@ -184,9 +194,38 @@ static int l_find_by_item(lua::State* L) { return lua::pushinteger(L, index); } +static int l_get_data(lua::State* L, ItemStack& stack) { + auto key = lua::require_string(L, 3); + auto value = stack.getField(key); + if (value == nullptr) { + return 0; + } + return lua::pushvalue(L, *value); +} + +static int l_get_all_data(lua::State* L, ItemStack& stack) { + return lua::pushvalue(L, stack.getFields()); +} + +static int l_has_data(lua::State* L, ItemStack& stack) { + auto key = lua::tostring(L, 3); + if (key == nullptr) { + return lua::pushboolean(L, stack.hasFields()); + } + return lua::pushboolean(L, stack.getField(key) != nullptr); +} + +static int l_set_data(lua::State* L, ItemStack& stack) { + auto key = lua::require_string(L, 3); + auto value = lua::tovalue(L, 4); + auto& fields = stack.getFields(); + stack.setField(key, std::move(value)); + return 0; +} + const luaL_Reg inventorylib[] = { - {"get", lua::wrap}, - {"set", lua::wrap}, + {"get", wrap_slot}, + {"set", wrap_slot}, {"size", lua::wrap}, {"add", lua::wrap}, {"move", lua::wrap}, @@ -195,7 +234,12 @@ const luaL_Reg inventorylib[] = { {"get_block", lua::wrap}, {"bind_block", lua::wrap}, {"unbind_block", lua::wrap}, + {"get_data", wrap_slot}, + {"set_data", wrap_slot}, + {"get_all_data", wrap_slot}, + {"has_data", wrap_slot}, {"create", lua::wrap}, {"remove", lua::wrap}, {"clone", lua::wrap}, - {NULL, NULL}}; + {NULL, NULL} +}; diff --git a/src/logic/scripting/lua/libs/libitem.cpp b/src/logic/scripting/lua/libs/libitem.cpp index c4ac49ec..b905f1d9 100644 --- a/src/logic/scripting/lua/libs/libitem.cpp +++ b/src/logic/scripting/lua/libs/libitem.cpp @@ -80,6 +80,13 @@ static int l_emission(lua::State* L) { return 0; } +static int l_uses(lua::State* L) { + if (auto def = get_item_def(L, 1)) { + return lua::pushinteger(L, def->uses); + } + return 0; +} + const luaL_Reg itemlib[] = { {"index", lua::wrap}, {"name", lua::wrap}, @@ -90,4 +97,6 @@ const luaL_Reg itemlib[] = { {"placing_block", lua::wrap}, {"model_name", lua::wrap}, {"emission", lua::wrap}, - {NULL, NULL}}; + {"uses", lua::wrap}, + {NULL, NULL} +};