Choose a language en

ItemStacks and Inventories

Introduction

In this chapter, you will learn how to use and manipulate inventories, whether that be a player inventory, a node inventory, or a detached inventory.

What are ItemStacks and Inventories?

An ItemStack is the data behind a single cell in an inventory.

An inventory is a collection of inventory lists, each of which is a 2D grid of ItemStacks. Inventory lists are referred to as lists in the context of inventories.

Players and nodes only have a single inventory; lists enable you to have multiple grids within that inventory. By default, the player has the “main” list for the bulk of its inventory and a few lists for the crafting system.

ItemStacks

ItemStacks have four components to them: name, count, wear, and metadata.

The item name may be the item name of a registered item, an alias, or an unknown item name. Unknown items are common when users uninstall mods, or when mods remove items without precautions, such as registering aliases.

print(stack:get_name())
stack:set_name("default:dirt")

if not stack:is_known() then
    print("Is an unknown item!")
end

The count will always be 0 or greater. Through normal gameplay, the count should be no more than the maximum stack size of the item - stack_max. However, admin commands and buggy mods may result in stacks exceeding the maximum size.

print(stack:get_stack_max())

An ItemStack can be empty, in which case the count will be 0.

print(stack:get_count())
stack:set_count(10)

ItemStacks can be constructed in multiple ways using the ItemStack function:

ItemStack() -- name="", count=0
ItemStack("default:pick_stone") -- count=1
ItemStack("default:stone 30")
ItemStack({ name = "default:wood", count = 10 })

Item metadata is an unlimited key-value store for data about the item. Key-value means that you use a name (called the key) to access the data (called the value). Some keys have special meaning, such as description which is used to have a per-stack item description. This will be covered in more detail in the Storage and Metadata chapter.

Inventory Locations

An Inventory Location is where and how the inventory is stored. There are three types of inventory location: player, node, and detached. An inventory is directly tied to one and only one location - updating the inventory will cause it to update immediately.

Node Inventories

Node inventories are related to the position of a specific node, such as a chest. The node must be loaded because it is stored in node metadata.

on_punch = function(pos, node)
    local inv = core.get_inventory({ type="node", pos=pos })
    -- now use the inventory
end,

The above obtains an inventory reference, commonly referred to as InvRef. Inventory references are used to manipulate an inventory. Reference means that the data isn’t actually stored inside that object, but the object instead directly updates the data in-place.

The location of an inventory reference can be found like so:

local location = inv:get_location()

Player Inventories

Player inventories can be obtained similarly or using a player reference. The player must be online to access their inventory.

local inv = core.get_inventory({ type="player", name="player1" })
-- or
local inv = player:get_inventory()

Detached Inventories

A detached inventory is one that is independent of players or nodes. Detached inventories also don’t save over a restart.

local inv = core.get_inventory({
    type="detached", name="inventory_name" })

Unlike the other types of inventory, you must first create a detached inventory before accessing it:

core.create_detached_inventory("inventory_name")

The create_detached_inventory function accepts 3 arguments, where only the first - the inventory name - is required. The second argument takes a table of callbacks, which can be used to control how players interact with the inventory:

-- Input only detached inventory
core.create_detached_inventory("inventory_name", {
    allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
        return count -- allow moving
    end,

    allow_put = function(inv, listname, index, stack, player)
        return stack:get_count() -- allow putting
    end,

    allow_take = function(inv, listname, index, stack, player)
        return 0 -- don't allow taking
    end,

    on_put = function(inv, listname, index, stack, player)
        core.chat_send_all(player:get_player_name() ..
            " gave " .. stack:to_string() ..
            " to the donation chest from " .. core.pos_to_string(player:get_pos()))
    end,
})

Permission callbacks - ie: those starting with allow_ - return the number of items to transfer, with 0 being used to prevent transfer completely.

On the contrary, action callbacks - starting with on_ - don’t have a return value.

Lists

Inventory Lists are a concept used to allow multiple grids to be stored inside a single location. This is especially useful for the player as there are several common lists that all games have, such as the main inventory and craft slots.

Size and Width

Lists have a size, which is the total number of cells in the grid, and a width, which is only used within the engine. The width of the list is not used when drawing the inventory in a window, because the code behind the window determines the width to use.

if inv:set_size("main", 32) then
    inv:set_width("main", 8)
    print("size:  " .. inv:get_size("main"))
    print("width: " .. inv:get_width("main"))
else
    print("Error! Invalid itemname or size to set_size()")
end

set_size will fail and return false if the listname or size is invalid. For example, the new size may be too small to fit all the current items in the inventory.

Checking Contents

is_empty can be used to see if a list contains any items:

if inv:is_empty("main") then
    print("The list is empty!")
end

contains_item can be used to see if a list contains a specific item:

if inv:contains_item("main", "default:stone") then
    print("I've found some stone!")
end

Modifying Inventories and ItemStacks

Adding to a List

add_item adds items to a list (in this case "main"). In the example below, the maximum stack size is also respected:

local stack    = ItemStack("default:stone 99")
local leftover = inv:add_item("main", stack)
if leftover:get_count() > 0 then
    print("Inventory is full! " ..
            leftover:get_count() .. " items weren't added")
end

Taking Items

To remove items from a list:

local taken = inv:remove_item("main", stack)
print("Took " .. taken:get_count())

Manipulating Stacks

You can modify individual stacks by first getting them:

local stack = inv:get_stack(listname, 0)

Then modifying them by setting properties or by using the methods which respect stack_size:

local stack    = ItemStack("default:stone 50")
local to_add   = ItemStack("default:stone 100")
local leftover = stack:add_item(to_add)
local taken    = stack:take_item(19)

print("Could not add"  .. leftover:get_count() .. " of the items.")
-- ^ will be 51

print("Have " .. stack:get_count() .. " items")
-- ^ will be 80
--   min(50+100, stack_max) - 19 = 80
--     where stack_max = 99

add_item will add items to an ItemStack and return any that could not be added. take_item will take up to the number of items but may take less, and returns the stack taken.

Finally, set the item stack:

inv:set_stack(listname, 0, stack)

Wear

Tools can have wear; wear shows a progress bar and makes the tool break when completely worn. Wear is a number out of 65535; the higher it is, the more worn the tool is.

Wear can be manipulated using add_wear(), get_wear(), and set_wear(wear).

local stack = ItemStack("default:pick_mese")
local max_uses = 10

-- This is done automatically when you use a tool that digs things
-- It increases the wear of an item by one use.
stack:add_wear(65535 / (max_uses - 1))

When digging a node, the amount of wear a tool gets may depend on the node being dug. So max_uses varies depending on what is being dug.

Lua Tables

ItemStacks and Inventories can be converted to and from tables. This is useful for copying and bulk operations.

-- Entire inventory
local data = inv1:get_lists()
inv2:set_lists(data)

-- One list
local listdata = inv1:get_list("main")
inv2:set_list("main", listdata)

The table of lists returned by get_lists() will be in this form:

{
    list_one = {
        ItemStack,
        ItemStack,
        ItemStack,
        ItemStack,
        -- inv:get_size("list_one") elements
    },
    list_two = {
        ItemStack,
        ItemStack,
        ItemStack,
        ItemStack,
        -- inv:get_size("list_two") elements
    }
}

get_list() will return a single list as just a list of ItemStacks.

One important thing to note is that the set methods above don’t change the size of the lists. This means that you can clear a list by setting it to an empty table and it won’t decrease in size:

inv:set_list("main", {})