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": "items:bazalt_breaker"
"icon": "items:bazalt_breaker",
"uses": 100
}

View File

@ -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

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)
if not player.is_infinite_items(pid) then
inventory.use(player.get_inventory(pid))
end
end

View File

@ -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

View File

@ -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)

View File

@ -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 ---------------------
------------------------------------------------

View File

@ -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();

View File

@ -70,7 +70,7 @@ std::shared_ptr<UINode> create_debug_panel(
);
HudElement::HudElement(
hud_element_mode mode,
HudElementMode mode,
UiDocument* document,
std::shared_ptr<UINode> 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<UINode> HudElement::getNode() const {
std::shared_ptr<InventoryView> 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<Inventory>(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<Plotter>(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)
"<image src='"+DEBUG_WORLDGEN_IMAGE+
"' 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() {
@ -372,8 +372,8 @@ void Hud::openInventory() {
auto inventoryDocument = assets.get<UiDocument>("core:inventory");
inventoryView = std::dynamic_pointer_cast<InventoryView>(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<Inventory> Hud::openInventory(
@ -401,7 +401,7 @@ std::shared_ptr<Inventory> 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<InventoryView>(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();

View File

@ -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<gui::UINode> node;
bool debug;
bool removed = false;
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);
@ -57,7 +57,7 @@ public:
std::shared_ptr<gui::UINode> getNode() const;
bool isInventoryBound() const {
return mode == hud_element_mode::inventory_bound;
return mode == HudElementMode::INVENTORY;
}
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 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;
}

View File

@ -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<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;
}
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<Atlas>("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<Font>("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<Font>("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<float>(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);

View File

@ -4,15 +4,18 @@
#include "Container.hpp"
#include "typedefs.hpp"
#include "constants.hpp"
#include "items/ItemStack.hpp"
#include <vector>
#include <functional>
#include <glm/glm.hpp>
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);

View File

@ -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;

View File

@ -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;
};

View File

@ -15,4 +15,6 @@ void ItemDef::cloneTo(ItemDef& dst) {
dst.placingBlock = placingBlock;
dst.scriptName = scriptName;
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
};
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";

View File

@ -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;
}

View File

@ -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;
}
};

View File

@ -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<size_t>(slotid) >= inv.size()) {
throw std::runtime_error(
"slot index is out of range [0..inventory.size(invid)]"
);
}
}
using SlotFunc = int(lua::State*, ItemStack&);
template <SlotFunc func>
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<size_t>(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<l_get>},
{"set", lua::wrap<l_set>},
{"get", wrap_slot<l_get>},
{"set", wrap_slot<l_set>},
{"size", lua::wrap<l_size>},
{"add", lua::wrap<l_add>},
{"move", lua::wrap<l_move>},
@ -195,7 +234,12 @@ const luaL_Reg inventorylib[] = {
{"get_block", lua::wrap<l_get_block>},
{"bind_block", lua::wrap<l_bind_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>},
{"remove", lua::wrap<l_remove>},
{"clone", lua::wrap<l_clone>},
{NULL, NULL}};
{NULL, NULL}
};

View File

@ -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<l_index>},
{"name", lua::wrap<l_name>},
@ -90,4 +97,6 @@ const luaL_Reg itemlib[] = {
{"placing_block", lua::wrap<l_placing_block>},
{"model_name", lua::wrap<l_model_name>},
{"emission", lua::wrap<l_emission>},
{NULL, NULL}};
{"uses", lua::wrap<l_uses>},
{NULL, NULL}
};