Stackeable and non-stackeable items on inventory system

(sorry for late reply)
(extra late reply, DevForum went read-only and deleted most of my progress)

Good question! The logic may seem a little daunting at first, but try your best to gain an intuition for it. It’s really easy once you know what’s going on.

Disclaimer: This isn’t a script you can copy+paste in your game and expect it to work. This is a basic expression; a framework, of a true inventory system. The design of your game is inherently yours, and it should stay that way.

In this example, the inventory is an array of 27 items. We can use a STACKS dictionary to define stack sizes for different items.

local STACKS = {
    stone = 64,
    ender_pearl = 16,
    diamond_sword = 1
}


local Inventory = {}

Inventory.Items = {}

for i = 1, 27 do
    table.insert(Inventory.Items, { Count = 0 })
end

Now, I’ll define a helper function to merge items:

-- merges source onto dest, swap arguments for the opposite effect
function Inventory.Merge(source, dest)
    if not dest.Item or (source.Item == dest.Item) then
        -- either no item in dest, or both types are the same

        local result = source.Count + dest.Count
        local max_stack = STACKS[dest.Item]

        dest.Count = math.min(result, max_stack)
        source.Count = result - max_stack

        if source.Count < 1 then
            -- source depleted; destroy the item
            -- handle this your own way, i'll just unassign its type
            source.Item = nil
        end
        
    end
end

If we test this function, you can see items get properly stacked just like they do in untitled block game.

-- for example, source could be the item held at the cursor
local source = {
    Item = "stone",
    Count = 48
}

local dest = {
    Item = "stone",
    Count = 32
}

Inventory.Merge(source, dest)

print(source.Count) -- 16
print(dest.Count) -- 64

image image

Now, items don’t get swapped yet; they always go to the inventory. But, you can implement that with a few extra checks. I’ll make a separate function for that, as swapping logic may be unintentional at times, such as when picking items off the ground.

function Inventory:Swap(slot, item)
    local current = self.Items[slot] -- get the item for later
    self.Items[slot] = item -- replace the item

    return current -- return the old item. this goes to the cursor
end

Finally, we can step back a bit, and simulate a cursor-to-inventory interaction, such as left-clicking on items.

local function Inventory:LMB(slot, cursor)
    -- cursor being the item held at the cursor, if any

    local inv = self.Items[slot]

    if not (inv.Item and cursor.Item) or inv.Item ~= cursor.Item then
        -- if there's one or more items missing, just swap
        -- if items aren't the same, swap
        return self:Swap(slot, cursor)
    else
        -- merge from cursor
        self.Merge(cursor, inv)
    end
end

And that’s it! Whether your stack size is 100, 64 or 1, the same logic will still apply.

The actual networking required to make this work is for you to do. This is a formulaic expression, and will hopefully give you the intuition to create a real, and awesome, inventory system. If it’s wrong by chance, I apologize. I wrote this on a touchscreen.

I also apologize if this isn’t exactly what you needed right from the gate. I don’t feel like writing an entire functioning system, networking and all, on my phone, while mentally debugging as best as I can. Inventory systems require at least some programming knowledge as a prerequisite, so I’m assuming you have that.

6 Likes