Skip to content

A shop block (block + menu)

Status: Implemented for a block that opens a chest-style menu its mod controls. Block entities (per-position inventories and progress that save with the world), custom menu layouts and their screens are Planned; this guide keeps the shop’s stock in the mod.

Goal: a Ruby Trader block. Right-click it and a one-row chest menu opens with offers; clicking an offer pays rubies and gives the item. It works for vanilla clients too, because the menu is a vanilla chest.

Language: Lua, with C++ at the end. APIs: Inventories and menus, Blocks and block states.

1. The offers

local OFFERS = {
  [0] = { item = "minecraft:diamond",     price = 3 },
  [1] = { item = "minecraft:golden_apple", price = 5 },
  [2] = { item = "minecraft:ender_pearl",  price = 2 },
}
local RUBY = "my_mod:ruby"

2. The menu

local function open_trader(player)
  local menu = cl.menu.open(player, {
    kind = "chest_9x1",
    title = "Ruby Trader",
    -- locked (the default): every click is cancelled unless on_click returns true
    on_click = function(m, who, slot)
      local offer = OFFERS[slot]
      if not offer then return end
      if who:count_item(RUBY) < offer.price then
        who:send_action_bar("You need " .. offer.price .. " rubies.")
        return
      end
      who:take(RUBY, offer.price)
      who:give(offer.item)
      who:send_action_bar("Thanks!")
    end,
  })
  for slot, offer in pairs(OFFERS) do menu:set_item(slot, offer.item) end
end

Count first: take removes whatever it finds, even when it is less than asked.

3. The block

cl.on("lifecycle.register", function()
  cl.blocks.register{
    id = "ruby_trader",
    copy_properties_from = "minecraft:barrel",
    vanilla_fallback = "minecraft:barrel",
    item = "my_mod:ruby_trader",
    behavior = {
      use_without_item = function(ctx, player)
        open_trader(player)
        return "success"
      end,
    },
  }
end)

The behaviour runs on the server, so the menu opens for that player. Add the block’s assets as in A custom block.

4. C++

void open_trader(clight::Entity player) {
    auto menu = clight::menu::open(player, {
        .kind = CL_MENU_CHEST_9X1,
        .title = "Ruby Trader",
        .on_click = [](clight::Menu, clight::Entity who, int32_t slot, int32_t, cl_click_type) {
            if (slot == 0 && who.count_item("my_mod:ruby") >= 3) {
                who.take("my_mod:ruby", 3);
                who.give("minecraft:diamond");
            }
            return false;   // keep the menu locked
        },
    });
    if (menu) menu->set_item(0, clight::item("minecraft:diamond"));
}

Checklist

  • [ ] The block registers in lifecycle.register and has its assets.
  • [ ] Offers check the price before taking.
  • [ ] Slot numbers start at 0 in the menu’s top row.

Planned

A block entity type with its own inventory that saves with the chunk, hoppers moving items in and out, progress bars synced to the menu, and a custom menu screen (RmlUi with slot elements).