Документация
Скрипты
К скриптам
Данные

Инвентарь и действия

Как найти предмет, взять его в руку, переложить, выбросить или забрать из сундука — и не попасть на рассинхрон с сервером.

Две системы номеров

В API встречаются два разных номера слота, и путать их — главная причина скриптов, которые «кликают не туда».

hotbar — 0…8позиция в хотбаре слева направо. Её принимают select_slot и второй аргумент swap_slot, её отдают hotbar_slot() и hotbar()
slot — id слота окнаномер внутри открытого контейнера. Его принимают все click-функции, его отдают inventory(), find_item() и slot()

Id слота зависит от того, что открыто. В собственном инвентаре раскладка всегда одна и та же:

0результат крафта
1–4сетка крафта 2×2
5–8броня: шлем, нагрудник, поножи, ботинки
9–35основной инвентарь, три ряда
36–44хотбар: id = 36 + hotbar
45левая рука

В сундуке сначала идут его слоты, а инвентарь игрока — после них, поэтому жёстко прописанные числа ломаются. Надёжнее не считать id самому, а брать их из inventory() или find_item(). У каждого предмета оттуда есть поля player (слот принадлежит игроку, а не сундуку), index (номер внутри своего инвентаря) и hotbar (0–8 или nil).

Поиск

player.find_item(id)slot и hotbar. Хотбар в приоритете; если предмет только в инвентаре — hotbar = nil
player.count_item(id)сколько штук во всём окне
player.inventory()все непустые слоты окна
player.slot(slotId)один слот, в том числе пустой
player.held_item(hand)что в руке сейчас: main или off
local slot, hotbar = player.find_item("minecraft:golden_apple")

if not slot then
    client.notify("яблок нет")
elseif hotbar then
    print("в хотбаре, позиция", hotbar)
else
    print("в инвентаре, id слота", slot)
end

Id предмета — полный, с неймспейсом: minecraft:trident, а не trident. Узнать его проще всего так: print(player.held_item().id).

Взять предмет в руку

player.select_slot(hotbar)переключить слот хотбара, как колесом мыши
player.swap_slot(slot, hotbar)поменять слот окна со слотом хотбара. hotbar = 40 — левая рука

Если предмет в хотбаре — достаточно select_slot. Если в инвентаре — swap_slot кладёт его в текущий слот хотбара, а то, что там лежало, уходит на его место. Инвентарь открывать не нужно: это та же операция, что и клавиша 1–9 над слотом.

local function take(id)
    if player.held_item().id == id then return true end

    local slot, hotbar = player.find_item(id)
    if not slot then return false end

    if hotbar then
        player.select_slot(hotbar)
    else
        player.swap_slot(slot, player.hotbar_slot())
    end
    return false
end

Функция возвращает true, только когда предмет уже в руке. Вызывайте её каждый тик, пока не получите true, — и только потом действуйте.

Почему нужна пауза в тиках

Смена слота и клик уходят на сервер пакетом, а команда в чат — другим пакетом. Если отправить команду в том же тике, сервер может увидеть в руке старый предмет: продать не то, выставить воздух или ответить «возьмите предмет в руку». Поэтому после каждого действия с инвентарём ждите 1–2 тика и проверяйте результат через held_item().

Удобнее всего строить такие скрипты как машину состояний: переменная stage, счётчик wait и один колбэк на tick. Пример — ниже.

Пример: продать предмет при включении

Скрипт срабатывает один раз: берёт трезубец из хотбара или инвентаря, выставляет его командой и сам выключает свой модуль. Верхний уровень файла выполняется при каждом включении модуля, поэтому переменные сбрасываются сами.

local ITEM  = "minecraft:trident"
local PRICE = 500

local stage, wait, tries = "find", 0, 0

local function finish(text)
    client.notify(text)
    stage = "done"
    modules.get(client.name).set(false)
end

events.on("tick", function()
    if stage == "done" or not player.exists() then return end
    if wait > 0 then wait = wait - 1 return end

    if stage == "find" then
        if player.held_item().id == ITEM then stage = "sell" return end

        local slot, hotbar = player.find_item(ITEM)
        if not slot then return finish("трезубца нет") end

        if hotbar then
            player.select_slot(hotbar)
        else
            tries = tries + 1
            if tries > 3 then return finish("не удалось взять") end
            player.swap_slot(slot, player.hotbar_slot())
        end
        wait = 2
    elseif stage == "sell" then
        player.send_command("market sell " .. PRICE)
        finish("выставлен за " .. PRICE)
    end
end)

Счётчик tries — страховка: если сервер отклонил клик (античит, открытое меню), скрипт не будет свапать бесконечно.

Перекладывание и выброс

player.quick_move(slot)shift-клик: из сундука в инвентарь и обратно
player.drop_slot(slot, all)выбросить один предмет или весь стак
player.click_slot(slot, button, action)любой клик окна, если готовых функций не хватает

Действия для click_slot: pickup (обычный клик, button 0 — левая, 1 — правая), quick_move, swap (button — позиция хотбара), clone, throw, quick_craft и pickup_all (двойной клик). Слот -999 — клик за пределами окна: так выбрасывают предмет с курсора.

-- переложить стак в другой слот двумя кликами
player.click_slot(from, 0, "pickup")
player.click_slot(to, 0, "pickup")

-- выкинуть весь мусор
for _, item in ipairs(player.inventory()) do
    if item.player and item.id == "minecraft:rotten_flesh" then
        player.drop_slot(item.slot, true)
    end
end

Серверы с античитом не любят десятки кликов за тик. Делайте одно действие за тик или раз в несколько тиков — как в примере с сундуком.

Сундуки и меню сервера

player.container().openоткрыто ли чужое окно
player.container().titleзаголовок: так отличают аукцион от обычного сундука
player.container().container_sizeсколько слотов у самого контейнера, без инвентаря игрока
player.container().cursorпредмет на курсоре
player.close_screen()закрыть окно с уведомлением сервера
local WANT = { ["minecraft:diamond"] = true }
local cooldown = 0

events.on("tick", function()
    if cooldown > 0 then cooldown = cooldown - 1 return end

    local box = player.container()
    if not box or not box.open then return end

    for _, item in ipairs(player.inventory()) do
        if not item.player and WANT[item.id] then
            player.quick_move(item.slot)
            cooldown = 2
            return
        end
    end
end)

Серверные меню (аукцион, магазины, варпы) — это тоже контейнеры. Открытие отслеживайте событием screen_open, а кликайте не раньше следующего тика: содержимое приходит отдельным пакетом, и в момент открытия окно ещё пустое.

Частые ошибки

hotbar() вместо inventory() для кликову hotbar() поле slot — это 0–8, а не id слота окна
команда в том же тике, что и свапсервер видит старый предмет — ждите 1–2 тика
жёсткие id слотовв сундуке они сдвигаются — берите slot из find_item / inventory
бесконечный цикл без лимита попытокесли клик отклонён, скрипт спамит — считайте попытки
send_command("/market ...")слеш не нужен: send_command("market sell 500")