Merge pull request #467 from MihailRis/update-items

Items data
This commit is contained in:
MihailRis 2025-02-18 19:06:39 +03:00 committed by GitHub
commit 1584b07706
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 483 additions and 202 deletions

View File

@ -1,4 +1,5 @@
{ {
"icon-type": "sprite", "icon-type": "sprite",
"icon": "items:bazalt_breaker" "icon": "items:bazalt_breaker",
"uses": 100
} }

View File

@ -1,12 +1,13 @@
local util = {} 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 if itemid == 0 or not itemid then
return nil return nil
end end
return entities.spawn("base:drop", ppos, {base__drop={ return entities.spawn("base:drop", ppos, {base__drop={
id=itemid, id=itemid,
count=count, count=count,
data=data,
pickup_delay=pickup_delay pickup_delay=pickup_delay
}}) }})
end end

View File

@ -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) block.set(x, y, z, 0, 0)
if not player.is_infinite_items(pid) then
inventory.use(player.get_inventory(pid))
end
end end

View File

@ -14,6 +14,7 @@ end
if SAVED_DATA.item then if SAVED_DATA.item then
dropitem.id = item.index(SAVED_DATA.item) dropitem.id = item.index(SAVED_DATA.item)
dropitem.count = SAVED_DATA.count dropitem.count = SAVED_DATA.count
dropitem.data = SAVED_DATA.data
end end
local DROP_SCALE = 0.3 local DROP_SCALE = 0.3
@ -25,6 +26,7 @@ local rotation = mat4.rotate({
function on_save() function on_save()
SAVED_DATA.item = item.name(dropitem.id) SAVED_DATA.item = item.name(dropitem.id)
SAVED_DATA.count = dropitem.count SAVED_DATA.count = dropitem.count
SAVED_DATA.data = dropitem.data
end end
do -- setup visuals do -- setup visuals
@ -59,11 +61,10 @@ function on_sensor_enter(index, oid)
if pid == -1 then if pid == -1 then
-- other is base:drop too -- other is base:drop too
if index == 0 and other:def_index() == def_index then if index == 0 and other:def_index() == def_index then
local odrop = other:get_component("base:drop") local odrop = other:get_component("base:drop").dropitem
if odrop.dropitem.id == dropitem.id then if odrop.id == dropitem.id and not odrop.data then
-- // TODO: replace combination logic with item.* function
local stack = item.stack_size(dropitem.id) local stack = item.stack_size(dropitem.id)
local sum = dropitem.count + odrop.dropitem.count local sum = dropitem.count + odrop.count
if sum <= stack then if sum <= stack then
dropitem.count = sum dropitem.count = sum
other:despawn() other:despawn()
@ -75,7 +76,7 @@ function on_sensor_enter(index, oid)
if timer < 0.0 and index == 0 then if timer < 0.0 and index == 0 then
entity:despawn() 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") audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular")
end end
if index == 1 then if index == 1 then

View File

@ -14,12 +14,13 @@ function on_hud_open()
if itemid == 0 then if itemid == 0 then
return return
end end
local data = inventory.get_all_data(invid, slot)
inventory.set(invid, slot, itemid, itemcount-1) inventory.set(invid, slot, itemid, itemcount-1)
local pvel = {player.get_vel(pid)} local pvel = {player.get_vel(pid)}
local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0}) local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0})
local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE) 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)) local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL))
drop.rigidbody:set_vel(velocity) drop.rigidbody:set_vel(velocity)
end) end)

View File

@ -94,6 +94,31 @@ elseif __vc_app then
complete_app_lib(__vc_app) complete_app_lib(__vc_app)
end 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 --------------------- ------------------- Events ---------------------
------------------------------------------------ ------------------------------------------------

View File

@ -429,15 +429,29 @@ void ContentLoader::loadItem(
} else if (iconTypeStr == "sprite") { } else if (iconTypeStr == "sprite") {
def.iconType = ItemIconType::SPRITE; def.iconType = ItemIconType::SPRITE;
} else if (iconTypeStr.length()) { } 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("icon").get(def.icon);
root.at("placing-block").get(def.placingBlock); root.at("placing-block").get(def.placingBlock);
root.at("script-name").get(def.scriptName); root.at("script-name").get(def.scriptName);
root.at("model-name").get(def.modelName); root.at("model-name").get(def.modelName);
root.at("stack-size").get(def.stackSize); 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")) { if (auto found = root.at("emission")) {
const auto& emissionarr = *found; const auto& emissionarr = *found;
def.emission[0] = emissionarr[0].asNumber(); def.emission[0] = emissionarr[0].asNumber();

View File

@ -70,7 +70,7 @@ std::shared_ptr<UINode> create_debug_panel(
); );
HudElement::HudElement( HudElement::HudElement(
hud_element_mode mode, HudElementMode mode,
UiDocument* document, UiDocument* document,
std::shared_ptr<UINode> node, std::shared_ptr<UINode> node,
bool debug bool debug
@ -83,16 +83,16 @@ void HudElement::update(bool pause, bool inventoryOpen, bool debugMode) {
return; return;
} }
switch (mode) { switch (mode) {
case hud_element_mode::permanent: case HudElementMode::PERMANENT:
node->setVisible(true); node->setVisible(true);
break; break;
case hud_element_mode::ingame: case HudElementMode::INGAME:
node->setVisible(!pause && !inventoryOpen); node->setVisible(!pause && !inventoryOpen);
break; break;
case hud_element_mode::inventory_any: case HudElementMode::INVENTORY_ANY:
node->setVisible(inventoryOpen); node->setVisible(inventoryOpen);
break; break;
case hud_element_mode::inventory_bound: case HudElementMode::INVENTORY:
removed = !inventoryOpen; removed = !inventoryOpen;
break; break;
} }
@ -108,21 +108,21 @@ std::shared_ptr<UINode> HudElement::getNode() const {
std::shared_ptr<InventoryView> Hud::createContentAccess() { std::shared_ptr<InventoryView> Hud::createContentAccess() {
auto& content = frontend.getLevel().content; auto& content = frontend.getLevel().content;
auto indices = content.getIndices(); auto& indices = *content.getIndices();
auto inventory = player.getInventory(); auto inventory = player.getInventory();
size_t itemsCount = indices->items.count(); size_t itemsCount = indices.items.count();
auto accessInventory = std::make_shared<Inventory>(0, itemsCount); auto accessInventory = std::make_shared<Inventory>(0, itemsCount);
for (size_t id = 1; id < itemsCount; id++) { for (size_t id = 1; id < itemsCount; id++) {
accessInventory->getSlot(id-1).set(ItemStack(id, 1)); accessInventory->getSlot(id-1).set(ItemStack(id, 1));
} }
SlotLayout slotLayout(-1, glm::vec2(), false, true, nullptr, SlotLayout slotLayout(-1, glm::vec2(), false, true, nullptr,
[=](uint, ItemStack& item) { [inventory, &indices](uint, ItemStack& item) {
auto copy = ItemStack(item); auto copy = ItemStack(item);
inventory->move(copy, indices); inventory->move(copy, indices);
}, },
[=](uint, ItemStack& item) { [this, inventory](uint, ItemStack& item) {
inventory->getSlot(player.getChosenSlot()).set(item); inventory->getSlot(player.getChosenSlot()).set(item);
}); });
@ -192,7 +192,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
auto dplotter = std::make_shared<Plotter>(350, 250, 2000, 16); auto dplotter = std::make_shared<Plotter>(350, 250, 2000, 16);
dplotter->setGravity(Gravity::bottom_right); dplotter->setGravity(Gravity::bottom_right);
dplotter->setInteractive(false); 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); assets.store(Texture::from(debugImgWorldGen.get()), DEBUG_WORLDGEN_IMAGE);
@ -200,7 +200,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
"<image src='"+DEBUG_WORLDGEN_IMAGE+ "<image src='"+DEBUG_WORLDGEN_IMAGE+
"' pos='0' size='256' gravity='top-right' margin='0,20,0,0'/>" "' pos='0' size='256' gravity='top-right' margin='0,20,0,0'/>"
); );
add(HudElement(hud_element_mode::permanent, nullptr, debugMinimap, true)); add(HudElement(HudElementMode::PERMANENT, nullptr, debugMinimap, true));
} }
Hud::~Hud() { Hud::~Hud() {
@ -372,8 +372,8 @@ void Hud::openInventory() {
auto inventoryDocument = assets.get<UiDocument>("core:inventory"); auto inventoryDocument = assets.get<UiDocument>("core:inventory");
inventoryView = std::dynamic_pointer_cast<InventoryView>(inventoryDocument->getRoot()); inventoryView = std::dynamic_pointer_cast<InventoryView>(inventoryDocument->getRoot());
inventoryView->bind(inventory, &content); inventoryView->bind(inventory, &content);
add(HudElement(hud_element_mode::inventory_bound, inventoryDocument, inventoryView, false)); add(HudElement(HudElementMode::INVENTORY, inventoryDocument, inventoryView, false));
add(HudElement(hud_element_mode::inventory_bound, nullptr, exchangeSlot, false)); add(HudElement(HudElementMode::INVENTORY, nullptr, exchangeSlot, false));
} }
std::shared_ptr<Inventory> Hud::openInventory( std::shared_ptr<Inventory> Hud::openInventory(
@ -401,7 +401,7 @@ std::shared_ptr<Inventory> Hud::openInventory(
inv = level.inventories->createVirtual(secondInvView->getSlotsCount()); inv = level.inventories->createVirtual(secondInvView->getSlotsCount());
} }
secondInvView->bind(inv, &content); 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); scripting::on_inventory_open(&player, *inv);
return inv; return inv;
} }
@ -436,7 +436,7 @@ void Hud::openInventory(
blockUI->bind(blockinv, &content); blockUI->bind(blockinv, &content);
blockPos = block; blockPos = block;
currentblockid = chunks.require(block.x, block.y, block.z).id; 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); scripting::on_inventory_open(&player, *blockinv);
} }
@ -468,8 +468,7 @@ void Hud::showOverlay(
showExchangeSlot(); showExchangeSlot();
inventoryOpen = true; inventoryOpen = true;
} }
add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false), add(HudElement(HudElementMode::INVENTORY, doc, secondUI, false), args);
args);
} }
void Hud::openPermanent(UiDocument* doc) { void Hud::openPermanent(UiDocument* doc) {
@ -480,7 +479,7 @@ void Hud::openPermanent(UiDocument* doc) {
if (invview) { if (invview) {
invview->bind(player.getInventory(), &frontend.getLevel().content); 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() { void Hud::dropExchangeSlot() {
@ -494,12 +493,12 @@ void Hud::dropExchangeSlot() {
auto indices = frontend.getLevel().content.getIndices(); auto indices = frontend.getLevel().content.getIndices();
if (auto invView = std::dynamic_pointer_cast<InventoryView>(blockUI)) { if (auto invView = std::dynamic_pointer_cast<InventoryView>(blockUI)) {
invView->getInventory()->move(stack, indices); invView->getInventory()->move(stack, *indices);
} }
if (stack.isEmpty()) { if (stack.isEmpty()) {
return; return;
} }
player.getInventory()->move(stack, indices); player.getInventory()->move(stack, *indices);
if (!stack.isEmpty()) { if (!stack.isEmpty()) {
logger.warning() << "discard item [" << stack.getItemId() << ":" logger.warning() << "discard item [" << stack.getItemId() << ":"
<< stack.getCount(); << stack.getCount();

View File

@ -30,26 +30,26 @@ namespace gui {
class SlotView; class SlotView;
} }
enum class hud_element_mode { enum class HudElementMode {
// element is hidden if menu or inventory open // element is hidden if menu or inventory open
ingame, INGAME,
// element is visible if hud is visible // element is visible if hud is visible
permanent, PERMANENT,
// element is visible in inventory mode // element is visible in inventory mode
inventory_any, INVENTORY_ANY,
// element will be removed on inventory close // element will be removed on inventory close
inventory_bound INVENTORY
}; };
class HudElement { class HudElement {
hud_element_mode mode; HudElementMode mode;
UiDocument* document; UiDocument* document;
std::shared_ptr<gui::UINode> node; std::shared_ptr<gui::UINode> node;
bool debug; bool debug;
bool removed = false; bool removed = false;
public: public:
HudElement(hud_element_mode mode, UiDocument* document, std::shared_ptr<gui::UINode> node, bool debug); HudElement(HudElementMode mode, UiDocument* document, std::shared_ptr<gui::UINode> node, bool debug);
void update(bool pause, bool inventoryOpen, bool debug); void update(bool pause, bool inventoryOpen, bool debug);
@ -57,7 +57,7 @@ public:
std::shared_ptr<gui::UINode> getNode() const; std::shared_ptr<gui::UINode> getNode() const;
bool isInventoryBound() const { bool isInventoryBound() const {
return mode == hud_element_mode::inventory_bound; return mode == HudElementMode::INVENTORY;
} }
void setRemoved() { void setRemoved() {

View File

@ -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 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); 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; this->color = color;
} }
inline glm::vec4 getColor() const {
void resetColor() {
this->color = glm::vec4(1.0f);
}
glm::vec4 getColor() const {
return color; return color;
} }

View File

@ -8,7 +8,6 @@
#include "items/Inventories.hpp" #include "items/Inventories.hpp"
#include "items/Inventory.hpp" #include "items/Inventory.hpp"
#include "items/ItemDef.hpp" #include "items/ItemDef.hpp"
#include "items/ItemStack.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "maths/voxmaths.hpp" #include "maths/voxmaths.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
@ -115,26 +114,73 @@ SlotView::SlotView(
setTooltipDelay(0.0f); setTooltipDelay(0.0f);
} }
void SlotView::draw(const DrawContext& pctx, const Assets& assets) { void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) {
if (bound == nullptr) { itemid_t itemid = stack.getItemId();
if (itemid == cache.stack.getItemId()) {
return; return;
} }
itemid_t itemid = bound->getItemId();
if (itemid != prevItem) {
if (itemid) { if (itemid) {
auto& def = content->getIndices()->items.require(itemid);
tooltip = util::pascal_case( tooltip = util::pascal_case(
langs::get(util::str2wstr_utf8(def.caption)) langs::get(util::str2wstr_utf8(item.caption))
); );
} else { } else {
tooltip.clear(); 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<Atlas>("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;
} }
prevItem = itemid; case ItemIconType::SPRITE: {
auto textureRegion =
util::get_texture_region(assets, item.icon, "blocks:notfound");
const int slotSize = InventoryView::SLOT_SIZE; 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 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::vec4 tint(1, 1, 1, isEnabled() ? 1 : 0.5f);
glm::vec2 pos = calcPos(); glm::vec2 pos = calcPos();
glm::vec4 color = getColor(); 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); color = glm::vec4(1, 1, 1, 0.2f);
} }
auto batch = pctx.getBatch2D(); auto& batch = *pctx.getBatch2D();
batch->setColor(color);
if (color.a > 0.0) { if (color.a > 0.0) {
batch->texture(nullptr); batch.setColor(color);
batch.texture(nullptr);
const int size = InventoryView::SLOT_SIZE;
if (highlighted) { 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 { } else {
batch->rect(pos.x, pos.y, slotSize, slotSize); batch.rect(pos.x, pos.y, size, size);
} }
} }
batch->setColor(glm::vec4(1.0f)); drawItemIcon(batch, stack, item, assets, tint, pos);
auto previews = assets.get<Atlas>("block-previews"); if (stack.getCount() > 1 || stack.getFields() != nullptr) {
auto indices = content->getIndices(); const auto& font = assets.require<Font>("normal");
drawItemInfo(batch, stack, item, font, pos);
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); static void draw_shaded_text(
batch->rect( Batch2D& batch, const Font& font, const std::wstring& text, int x, int y
pos.x, pos.y, slotSize, slotSize, ) {
0, 0, 0, textureRegion.region, false, true, tint); batch.setColor({0, 0, 0, 1.0f});
break; 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) { if (stack.getCount() > 1) {
auto font = assets.get<Font>("normal"); const auto& countStr = cache.countStr;
std::wstring text = std::to_wstring(stack.getCount()); 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; auto usesPtr = stack.getField("uses");
int y = pos.y+slotSize-16; 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<float>(uses) / item.uses;
batch->setColor({0, 0, 0, 1.0f}); int height = SLOT_SIZE * t;
font->draw(*batch, text, x+1, y+1, nullptr, 0); batch.setColor({(1.0f - t * 0.8f), 0.4f, t * 0.8f + 0.2f, 1.0f});
batch->setColor(glm::vec4(1.0f)); batch.rect(pos.x, pos.y + SLOT_SIZE - height, 2, height);
font->draw(*batch, text, x, y, nullptr, 0); break;
}
} }
} }
@ -219,7 +290,7 @@ void SlotView::performLeftClick(ItemStack& stack, ItemStack& grabbed) {
return; return;
} }
if (!layout.itemSource && stack.accepts(grabbed) && layout.placing) { if (!layout.itemSource && stack.accepts(grabbed) && layout.placing) {
stack.move(grabbed, content->getIndices()); stack.move(grabbed, *content->getIndices());
} else { } else {
if (layout.itemSource) { if (layout.itemSource) {
if (grabbed.isEmpty()) { if (grabbed.isEmpty()) {
@ -249,10 +320,11 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) {
return; return;
if (grabbed.isEmpty()) { if (grabbed.isEmpty()) {
if (!stack.isEmpty() && layout.taking) { if (!stack.isEmpty() && layout.taking) {
grabbed.set(stack); grabbed.set(std::move(stack));
int halfremain = stack.getCount() / 2; int halfremain = stack.getCount() / 2;
grabbed.setCount(stack.getCount() - halfremain); grabbed.setCount(stack.getCount() - halfremain);
stack.setCount(halfremain); // reset all data in the origin slot
stack = ItemStack(stack.getItemId(), halfremain);
} }
return; return;
} }
@ -261,9 +333,14 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) {
return; return;
} }
if (stack.isEmpty()) { if (stack.isEmpty()) {
stack.set(grabbed); itemcount_t count = grabbed.getCount();
stack.set(std::move(grabbed));
stack.setCount(1); 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) { } else if (stack.accepts(grabbed) && stack.getCount() < stackDef.stackSize) {
stack.setCount(stack.getCount() + 1); stack.setCount(stack.getCount() + 1);
grabbed.setCount(grabbed.getCount() - 1); grabbed.setCount(grabbed.getCount() - 1);

View File

@ -4,15 +4,18 @@
#include "Container.hpp" #include "Container.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "items/ItemStack.hpp"
#include <vector> #include <vector>
#include <functional> #include <functional>
#include <glm/glm.hpp> #include <glm/glm.hpp>
class Font;
class Assets; class Assets;
class ItemDef;
class Batch2D;
class DrawContext; class DrawContext;
class Content; class Content;
class ItemStack;
class ContentIndices; class ContentIndices;
class LevelFrontend; class LevelFrontend;
class Inventory; class Inventory;
@ -49,6 +52,11 @@ namespace gui {
}; };
class SlotView : public gui::UINode { class SlotView : public gui::UINode {
struct {
ItemStack stack {};
std::wstring countStr;
} cache;
const Content* content = nullptr; const Content* content = nullptr;
SlotLayout layout; SlotLayout layout;
bool highlighted = false; bool highlighted = false;
@ -56,11 +64,27 @@ namespace gui {
int64_t inventoryid = 0; int64_t inventoryid = 0;
ItemStack* bound = nullptr; ItemStack* bound = nullptr;
std::wstring tooltip;
itemid_t prevItem = 0;
void performLeftClick(ItemStack& stack, ItemStack& grabbed); void performLeftClick(ItemStack& stack, ItemStack& grabbed);
void performRightClick(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: public:
SlotView(SlotLayout layout); SlotView(SlotLayout layout);

View File

@ -37,7 +37,7 @@ size_t Inventory::findSlotByItem(
} }
void Inventory::move( 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); end = std::min(slots.size(), end);
for (size_t i = begin; i < end && !item.isEmpty(); i++) { 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")){ if (item.has("count")){
count = item["count"].asInteger(); count = item["count"].asInteger();
} }
dv::value fields = nullptr;
if (item.has("fields")) {
fields = item["fields"];
}
auto& slot = slots[i]; 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) { if (count) {
slotmap["count"] = count; slotmap["count"] = count;
} }
const auto& fields = item.getFields();
if (fields != nullptr) {
slotmap["fields"] = fields;
}
} }
return map; return map;
} }
@ -110,5 +118,3 @@ void Inventory::convert(dv::value& data, const ContentReport* report) {
inventory.convert(report); inventory.convert(report);
data = inventory.serialize(); data = inventory.serialize();
} }
const size_t Inventory::npos = -1;

View File

@ -26,13 +26,9 @@ public:
itemid_t id, size_t begin = 0, size_t end = -1, size_t minCount = 1 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( void move(
ItemStack& item, ItemStack& item,
const ContentIndices* indices, const ContentIndices& indices,
size_t begin = 0, size_t begin = 0,
size_t end = -1 size_t end = -1
); );
@ -46,17 +42,21 @@ public:
void convert(const ContentReport* report); void convert(const ContentReport* report);
static void convert(dv::value& data, 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; this->id = id;
} }
inline int64_t getId() const { int64_t getId() const {
return id; return id;
} }
inline bool isVirtual() const { bool isVirtual() const {
return id < 0; return id < 0;
} }
static const size_t npos; static constexpr size_t npos = -1;
}; };

View File

@ -15,4 +15,6 @@ void ItemDef::cloneTo(ItemDef& dst) {
dst.placingBlock = placingBlock; dst.placingBlock = placingBlock;
dst.scriptName = scriptName; dst.scriptName = scriptName;
dst.modelName = modelName; dst.modelName = modelName;
dst.uses = uses;
dst.usesDisplay = usesDisplay;
} }

View File

@ -19,6 +19,14 @@ enum class ItemIconType {
BLOCK, // block preview: icon is string block id 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 { struct ItemDef {
/// @brief Item string id (with prefix included) /// @brief Item string id (with prefix included)
std::string const name; std::string const name;
@ -28,10 +36,21 @@ struct ItemDef {
dv::value properties = nullptr; dv::value properties = nullptr;
/// @brief Item max stack size
itemcount_t stackSize = 64; itemcount_t stackSize = 64;
/// @brief Item is generated for other content unit (like block)
bool generated = false; 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}; 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; ItemIconType iconType = ItemIconType::SPRITE;
std::string icon = "blocks:notfound"; std::string icon = "blocks:notfound";

View File

@ -3,16 +3,18 @@
#include "content/Content.hpp" #include "content/Content.hpp"
#include "ItemDef.hpp" #include "ItemDef.hpp"
ItemStack::ItemStack() : item(ITEM_EMPTY), count(0) { ItemStack::ItemStack(itemid_t item, itemcount_t count, dv::value data)
} : item(item), count(count), fields(std::move(data)) {
ItemStack::ItemStack(itemid_t item, itemcount_t count)
: item(item), count(count) {
} }
void ItemStack::set(const ItemStack& item) { void ItemStack::set(const ItemStack& item) {
set(ItemStack(item));
}
void ItemStack::set(ItemStack&& item) {
this->item = item.item; this->item = item.item;
this->count = item.count; this->count = item.count;
this->fields = std::move(item.fields);
if (count == 0) { if (count == 0) {
this->item = 0; this->item = 0;
} }
@ -25,14 +27,14 @@ bool ItemStack::accepts(const ItemStack& other) const {
if (isEmpty()) { if (isEmpty()) {
return true; return true;
} }
return item == other.getItemId(); return item == other.getItemId() && other.fields == nullptr;
} }
void ItemStack::move(ItemStack& item, const ContentIndices* indices) { void ItemStack::move(ItemStack& item, const ContentIndices& indices) {
auto& def = indices->items.require(item.getItemId()); auto& def = indices.items.require(item.getItemId());
int count = std::min(item.count, def.stackSize - this->count); itemcount_t count = std::min(item.count, def.stackSize - this->count);
if (isEmpty()) { if (isEmpty()) {
set(ItemStack(item.getItemId(), count)); set(ItemStack(item.getItemId(), count, std::move(item.fields)));
} else { } else {
setCount(this->count + count); setCount(this->count + count);
} }
@ -42,6 +44,30 @@ void ItemStack::move(ItemStack& item, const ContentIndices* indices) {
void ItemStack::setCount(itemcount_t count) { void ItemStack::setCount(itemcount_t count) {
this->count = count; this->count = count;
if (count == 0) { 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;
}

View File

@ -2,36 +2,60 @@
#include "constants.hpp" #include "constants.hpp"
#include "typedefs.hpp" #include "typedefs.hpp"
#include "data/dv.hpp"
class ContentIndices; class ContentIndices;
class ItemStack { class ItemStack {
itemid_t item; itemid_t item = ITEM_EMPTY;
itemcount_t count; itemcount_t count = 0;
dv::value fields = nullptr;
public: 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(const ItemStack& item);
void set(ItemStack&& item);
void setCount(itemcount_t count); void setCount(itemcount_t count);
bool accepts(const ItemStack& item) const; /// @brief Set a field in the item stack data.
void move(ItemStack& item, const ContentIndices* indices); 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)); set(ItemStack(0, 0));
} }
inline bool isEmpty() const { bool isEmpty() const {
return item == ITEM_EMPTY; return item == ITEM_EMPTY;
} }
inline itemid_t getItemId() const { itemid_t getItemId() const {
return item; return item;
} }
inline itemcount_t getCount() const { itemcount_t getCount() const {
return count; return count;
} }
const dv::value& getFields() const {
return fields;
}
bool hasFields() const {
return fields != nullptr;
}
}; };

View File

@ -7,21 +7,22 @@
using namespace scripting; using namespace scripting;
static void validate_itemid(itemid_t id) { namespace {
void validate_itemid(itemid_t id) {
if (id >= indices->items.count()) { if (id >= indices->items.count()) {
throw std::runtime_error("invalid item id"); throw std::runtime_error("invalid item id");
} }
} }
static Inventory& get_inventory(int64_t id) { Inventory& get_inventory(int64_t id) {
auto inv = level->inventories->get(id); auto inv = level->inventories->get(id);
if (inv == nullptr) { if (inv == nullptr) {
throw std::runtime_error("inventory not found: " + std::to_string(id)); throw std::runtime_error("inventory not found: " + std::to_string(id));
} }
return *inv; return *inv;
} }
static Inventory& get_inventory(int64_t id, int arg) { Inventory& get_inventory(int64_t id, int arg) {
auto inv = level->inventories->get(id); auto inv = level->inventories->get(id);
if (inv == nullptr) { if (inv == nullptr) {
throw std::runtime_error( throw std::runtime_error(
@ -30,45 +31,49 @@ static Inventory& get_inventory(int64_t id, int arg) {
); );
} }
return *inv; return *inv;
} }
static void validate_slotid(int slotid, const Inventory& inv) { void validate_slotid(int slotid, const Inventory& inv) {
if (static_cast<size_t>(slotid) >= inv.size()) { if (static_cast<size_t>(slotid) >= inv.size()) {
throw std::runtime_error( throw std::runtime_error(
"slot index is out of range [0..inventory.size(invid)]" "slot index is out of range [0..inventory.size(invid)]"
); );
} }
} }
static int l_get(lua::State* L) { using SlotFunc = int(lua::State*, ItemStack&);
template <SlotFunc func>
int wrap_slot(lua::State* L) {
auto invid = lua::tointeger(L, 1); auto invid = lua::tointeger(L, 1);
auto slotid = lua::tointeger(L, 2); auto slotid = lua::tointeger(L, 2);
auto inv = get_inventory(invid); auto& inv = get_inventory(invid);
validate_slotid(slotid, inv); validate_slotid(slotid, inv);
const ItemStack& item = inv.getSlot(slotid); auto& item = inv.getSlot(slotid);
return func(L, item);
}
}
static int l_get(lua::State* L, ItemStack& item) {
lua::pushinteger(L, item.getItemId()); lua::pushinteger(L, item.getItemId());
lua::pushinteger(L, item.getCount()); lua::pushinteger(L, item.getCount());
return 2; return 2;
} }
static int l_set(lua::State* L) { static int l_set(lua::State* L, ItemStack& item) {
auto invid = lua::tointeger(L, 1);
auto slotid = lua::tointeger(L, 2);
auto itemid = lua::tointeger(L, 3); auto itemid = lua::tointeger(L, 3);
auto count = lua::tointeger(L, 4); auto count = lua::tointeger(L, 4);
validate_itemid(itemid); auto data = lua::tovalue(L, 5);
if (!data.isObject() && data != nullptr) {
auto& inv = get_inventory(invid); throw std::runtime_error("invalid data argument type (table expected)");
}
validate_slotid(slotid, inv); item.set(ItemStack(itemid, count, std::move(data)));
ItemStack& item = inv.getSlot(slotid);
item.set(ItemStack(itemid, count));
return 0; return 0;
} }
static int l_size(lua::State* L) { static int l_size(lua::State* L) {
auto invid = lua::tointeger(L, 1); auto invid = lua::tointeger(L, 1);
auto& inv = get_inventory(invid); const auto& inv = get_inventory(invid);
return lua::pushinteger(L, inv.size()); return lua::pushinteger(L, inv.size());
} }
@ -76,11 +81,16 @@ static int l_add(lua::State* L) {
auto invid = lua::tointeger(L, 1); auto invid = lua::tointeger(L, 1);
auto itemid = lua::tointeger(L, 2); auto itemid = lua::tointeger(L, 2);
auto count = lua::tointeger(L, 3); auto count = lua::tointeger(L, 3);
auto data = lua::tovalue(L, 4);
validate_itemid(itemid); validate_itemid(itemid);
if (!data.isObject() && data != nullptr) {
throw std::runtime_error("invalid data argument type (table expected)");
}
auto& inv = get_inventory(invid); auto& inv = get_inventory(invid);
ItemStack item(itemid, count); ItemStack item(itemid, count, std::move(data));
inv.move(item, indices); inv.move(item, *indices);
return lua::pushinteger(L, item.getCount()); return lua::pushinteger(L, item.getCount());
} }
@ -144,9 +154,9 @@ static int l_move(lua::State* L) {
auto& invB = get_inventory(invBid, 3); auto& invB = get_inventory(invBid, 3);
auto& slot = invA.getSlot(slotAid); auto& slot = invA.getSlot(slotAid);
if (slotBid == -1) { if (slotBid == -1) {
invB.move(slot, content->getIndices()); invB.move(slot, *content->getIndices());
} else { } else {
invB.move(slot, content->getIndices(), slotBid, slotBid + 1); invB.move(slot, *content->getIndices(), slotBid, slotBid + 1);
} }
return 0; return 0;
} }
@ -163,9 +173,9 @@ static int l_move_range(lua::State* L) {
auto invB = get_inventory(invBid, 3); auto invB = get_inventory(invBid, 3);
auto& slot = invA.getSlot(slotAid); auto& slot = invA.getSlot(slotAid);
if (slotBegin == -1) { if (slotBegin == -1) {
invB.move(slot, content->getIndices()); invB.move(slot, *content->getIndices());
} else { } else {
invB.move(slot, content->getIndices(), slotBegin, slotEnd); invB.move(slot, *content->getIndices(), slotBegin, slotEnd);
} }
return 0; return 0;
} }
@ -184,9 +194,38 @@ static int l_find_by_item(lua::State* L) {
return lua::pushinteger(L, index); 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[] = { const luaL_Reg inventorylib[] = {
{"get", lua::wrap<l_get>}, {"get", wrap_slot<l_get>},
{"set", lua::wrap<l_set>}, {"set", wrap_slot<l_set>},
{"size", lua::wrap<l_size>}, {"size", lua::wrap<l_size>},
{"add", lua::wrap<l_add>}, {"add", lua::wrap<l_add>},
{"move", lua::wrap<l_move>}, {"move", lua::wrap<l_move>},
@ -195,7 +234,12 @@ const luaL_Reg inventorylib[] = {
{"get_block", lua::wrap<l_get_block>}, {"get_block", lua::wrap<l_get_block>},
{"bind_block", lua::wrap<l_bind_block>}, {"bind_block", lua::wrap<l_bind_block>},
{"unbind_block", lua::wrap<l_unbind_block>}, {"unbind_block", lua::wrap<l_unbind_block>},
{"get_data", wrap_slot<l_get_data>},
{"set_data", wrap_slot<l_set_data>},
{"get_all_data", wrap_slot<l_get_all_data>},
{"has_data", wrap_slot<l_has_data>},
{"create", lua::wrap<l_create>}, {"create", lua::wrap<l_create>},
{"remove", lua::wrap<l_remove>}, {"remove", lua::wrap<l_remove>},
{"clone", lua::wrap<l_clone>}, {"clone", lua::wrap<l_clone>},
{NULL, NULL}}; {NULL, NULL}
};

View File

@ -80,6 +80,13 @@ static int l_emission(lua::State* L) {
return 0; 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[] = { const luaL_Reg itemlib[] = {
{"index", lua::wrap<l_index>}, {"index", lua::wrap<l_index>},
{"name", lua::wrap<l_name>}, {"name", lua::wrap<l_name>},
@ -90,4 +97,6 @@ const luaL_Reg itemlib[] = {
{"placing_block", lua::wrap<l_placing_block>}, {"placing_block", lua::wrap<l_placing_block>},
{"model_name", lua::wrap<l_model_name>}, {"model_name", lua::wrap<l_model_name>},
{"emission", lua::wrap<l_emission>}, {"emission", lua::wrap<l_emission>},
{NULL, NULL}}; {"uses", lua::wrap<l_uses>},
{NULL, NULL}
};