Перейти к основному содержимому

Кастомный рецепт

Кастомные рецепты позволяют добавлять на сервер новые рецепты крафта, плавки и ковки. С их помощью вы можете дать игрокам возможность крафтить ванильные предметы новыми способами, создавать рецепты, дающие ваши кастомные предметы, или определять совершенно новые пути плавки и улучшения.

На этой странице рассматриваются следующие распространённые типы рецептов, поддерживаемые Nukkit-MOT:

Тип рецептаКлассОписание
Рецепт по форме (shaped)ShapedRecipeРецепт верстака с заданным шаблоном
Безформенный рецепт (shapeless)ShapelessRecipeРецепт верстака, где расположение не имеет значения
Рецепт печиFurnaceRecipeРецепт плавки в печи
Рецепт взрывной печиBlastFurnaceRecipeРецепт плавки во взрывной печи
Рецепт костраCampfireRecipeРецепт готовки на костре
Рецепт кузнечного столаSmithingRecipeРецепт улучшения на кузнечном столе
Рецепт камнерезаStonecutterRecipeРецепт резки на камнерезе

Процесс регистрации

Процесс регистрации описан в следующей диаграмме последовательности:

Регистрируйте все рецепты в методе onEnable вашего плагина:

ExamplePlugin.java
import cn.nukkit.Server;
import cn.nukkit.inventory.CraftingManager;
import cn.nukkit.plugin.PluginBase;

public class ExamplePlugin extends PluginBase {
@Override
public void onEnable() {
CraftingManager craftingManager = Server.getInstance().getCraftingManager();
// Register recipes here...
}
}
Удобный метод

Вы также можете использовать getServer().getCraftingManager() напрямую, поскольку PluginBase предоставляет метод getServer().

Рецепт по форме (shaped)

Рецепты по форме (shaped) задают определённый шаблон в данных рецептов крафта, отправляемых клиентам. Это самый распространённый тип рецептов, используемый для таких вещей, как инструменты, броня и блоки.

Базовое использование

Форма задаётся как массив строк (1–3 ряда, каждый по 1–3 символа). Каждый символ сопоставляется с предметом-ингредиентом. Пробелы обозначают пустые слоты.

Пример ShapedRecipe
import cn.nukkit.inventory.ShapedRecipe;
import cn.nukkit.item.Item;

// Craft a Diamond from 9 Diamond Ore
craftingManager.registerRecipe(new ShapedRecipe(
Item.get(Item.DIAMOND), // primaryResult
new String[]{ // shape (3x3 grid)
"DDD",
"DDD",
"DDD"
},
Map.of( // ingredient map
'D', Item.get(Item.DIAMOND_ORE)
),
List.of() // extraResults (empty buckets, etc.)
));

Это создаёт рецепт, по которому 9 алмазной руды, расположенной в квадрате 3×3, дают алмаз.

Понимание формы

Массив формы определяет раскладку сетки крафта:

Shape: {"AB ",
" C "}

Соответствует примерно такой сетке крафта:

[A] [B] [ ]
[ ] [C] [ ]
[ ] [ ] [ ]
Правила формы
  • Каждый ряд должен иметь одинаковую длину (1–3 символа)
  • Рядов должно быть от 1 до 3
  • Каждый символ, отличный от пробела, должен иметь соответствующий ингредиент
  • Карта ингредиентов не должна содержать символы, которые отсутствуют в форме
  • Сетка рецепта не обязана быть квадратной — дополнять пустые ряды или столбцы не нужно
Сопоставление на стороне сервера

CraftingDataPacket отправляет клиентам сетку с формой, однако CraftingManager.matchRecipe() проверяет рецепты по форме по хешу результата и совокупности ингредиентов, а не по точным позициям в сетке. Если вашему плагину нужны дополнительные позиционные правила сверх обычных данных рецептов для клиента, проверяйте их в CraftItemEvent.

Полный конструктор

Из класса cn.nukkit.inventory.ShapedRecipe:

Конструктор
new ShapedRecipe(
String recipeId, // Unique recipe identifier (can be null)
int priority, // Priority metadata
Item primaryResult, // Primary output item
String[] shape, // Shape array
Map<Character, Item> ingredients, // Character → Item mapping
List<Item> extraResults // Additional results (e.g., empty buckets)
);

Пример: крафт кастомного предмета

Использование кастомного предмета в качестве результата рецепта:

Крафт кастомного меча из кастомных предметов
import cn.nukkit.inventory.ShapedRecipe;
import cn.nukkit.item.Item;

// Craft the Candy Cane Sword: Candy Cane (C) + Stick (S)
craftingManager.registerRecipe(new ShapedRecipe(
"candy_cane_sword", // recipeId
1, // priority metadata
Item.fromString("nukkit:candy_cane_sword"), // custom item result
new String[]{
" C ",
" C ",
" S "
},
Map.of(
'C', Item.get(Item.DYE, 9), // Pink dye as "Candy Cane"
'S', Item.get(Item.STICK)
),
List.of()
));

Пример: рецепт с дополнительными результатами

Некоторые рецепты оставляют предметы в сетке крафта (например, пустые вёдра после крафта торта):

Рецепт с дополнительными результатами
import cn.nukkit.inventory.ShapedRecipe;
import cn.nukkit.item.Item;

craftingManager.registerRecipe(new ShapedRecipe(
Item.get(Item.CAKE),
new String[]{
"MMM",
"SES",
"WWW"
},
Map.of(
'M', Item.get(Item.BUCKET, 1), // Milk bucket
'S', Item.get(Item.SUGAR),
'E', Item.get(Item.EGG),
'W', Item.get(Item.WHEAT)
),
List.of(Item.get(Item.BUCKET, 0, 3)) // 3 empty buckets returned
));

Безформенный рецепт (shapeless)

Безформенные рецепты (shapeless) не требуют конкретного шаблона — ингредиенты можно располагать в любом месте сетки крафта. Они используются для таких рецептов, как красители, огненные заряды и другие комбинированные предметы.

Базовое использование

Пример ShapelessRecipe
import cn.nukkit.inventory.ShapelessRecipe;
import cn.nukkit.item.Item;

// Combine Iron Ingot + Flint = Flint and Steel
craftingManager.registerRecipe(new ShapelessRecipe(
Item.get(Item.FLINT_AND_STEEL), // result
List.of( // ingredients (order doesn't matter)
Item.get(Item.IRON_INGOT),
Item.get(Item.FLINT)
)
));

Полный конструктор

Из класса cn.nukkit.inventory.ShapelessRecipe:

Конструктор
new ShapelessRecipe(
String recipeId, // Unique recipe identifier (can be null)
int priority, // Priority metadata
Item result, // Output item
Collection<Item> ingredients // Ingredient list (max 9 items)
);
Ограничение на количество ингредиентов

ShapelessRecipe отклоняет коллекции ингредиентов с более чем 9 элементами. Количество у отдельного Item может быть больше 1, например Item.get(Item.COAL, 1, 2).

Пример: смешивание красителей

ShapelessRecipe - кастомное смешивание красителей
import cn.nukkit.inventory.ShapelessRecipe;
import cn.nukkit.item.Item;

// Mix Red + Yellow Dye = Orange Dye
craftingManager.registerRecipe(new ShapelessRecipe(
"orange_dye_mix",
0,
Item.get(Item.DYE, 14), // Orange dye
List.of(
Item.get(Item.DYE, 1), // Red dye
Item.get(Item.DYE, 11) // Yellow dye
)
));

Рецепт печи

Рецепты печи определяют, что происходит при переплавке предмета в печи.

Базовое использование

Пример FurnaceRecipe
import cn.nukkit.inventory.FurnaceRecipe;
import cn.nukkit.item.Item;

// Smelt Diamond Ore → Diamond
craftingManager.registerRecipe(new FurnaceRecipe(
"diamond_ore_to_diamond",
Item.get(Item.DIAMOND), // result
Item.get(Item.DIAMOND_ORE) // ingredient
));

Полный конструктор

Из класса cn.nukkit.inventory.FurnaceRecipe:

Конструктор
new FurnaceRecipe(
String recipeId, // Unique recipe identifier (can be null)
Item result, // Smelting output
Item ingredient // Input item to smelt
);
Сопоставление по метаданным

CraftingManager хранит рецепты плавки по хешу входного предмета. Сопоставление для печи, взрывной печи и костра сначала проверяет точный хеш damage/meta входного предмета; если совпадения нет, выполняется откат к тому же id предмета с meta 0. Это значит, что рецепт с meta 0 может служить запасным вариантом для других вариантов предмета, если не зарегистрирован более специфичный рецепт.

Сетевой тип рецепта, например FURNACE или FURNACE_DATA, определяется на основе ingredient.hasMeta(), поэтому не судите о серверном сопоставлении только по имени типа.

Рецепт взрывной печи

Рецепты взрывной печи используют отдельную карту рецептов блока взрывной печи. Блочная сущность сопоставляет предметы с этой картой и обрабатывает совпавшие рецепты с множителем скорости 2.

Сейчас CraftingManager.packetFor() не перебирает getBlastFurnaceRecipes() при построении CraftingDataPacket, поэтому кастомные рецепты взрывной печи — это правила обработки на стороне сервера, и не следует полагаться на них для отображения рецептов в книге рецептов клиента.

Пример BlastFurnaceRecipe
import cn.nukkit.inventory.BlastFurnaceRecipe;
import cn.nukkit.item.Item;

craftingManager.registerRecipe(new BlastFurnaceRecipe(
Item.get(Item.IRON_INGOT), // result
Item.get(Item.IRON_ORE) // ingredient
));

Рецепт костра

Рецепты костра определяют правила готовки для блока костра. BlockEntityCampfire сопоставляет зарегистрированные рецепты костра при обработке предметов на блоке.

Сейчас CraftingManager.packetFor() не добавляет рецепты костра в CraftingDataPacket, поэтому эти рецепты влияют на обработку костра на стороне сервера, а не на отображение в книге рецептов клиента.

Пример CampfireRecipe
import cn.nukkit.inventory.CampfireRecipe;
import cn.nukkit.item.Item;

craftingManager.registerRecipe(new CampfireRecipe(
Item.get(Item.COOKED_PORKCHOP), // result
Item.get(Item.RAW_PORKCHOP) // ingredient
));

Рецепт кузнечного стола

Рецепты кузнечного стола используются в пути преобразования на кузнечном столе. Генерация обычного результата в текущем Nukkit-MOT реализована для потока незеритового улучшения: снаряжение + материал + minecraft:netherite_upgrade_smithing_template.

Базовое использование

Пример SmithingRecipe
import cn.nukkit.inventory.SmithingRecipe;
import cn.nukkit.item.Item;

// Upgrade: Diamond Sword + Netherite Ingot = Netherite Sword
craftingManager.registerRecipe(new SmithingRecipe(
"diamond_to_netherite_sword", // recipeId
0, // priority metadata
List.of( // ingredients (order matters!)
Item.get(Item.DIAMOND_SWORD), // [0] equipment
Item.get(Item.NETHERITE_INGOT), // [1] material
Item.fromString(Item.NETHERITE_UPGRADE_SMITHING_TEMPLATE) // [2] template
),
Item.get(Item.NETHERITE_SWORD) // result
));

Конструктор и порядок параметров

Из класса cn.nukkit.inventory.SmithingRecipe:

Конструктор
new SmithingRecipe(
String recipeId,
int priority,
Collection<Item> ingredients, // Must follow this order:
// [0] equipment (base item to upgrade)
// [1] ingredient (upgrade material)
// [2] template (use the netherite upgrade template for normal output)
Item result // Upgraded output item
);
Порядок ингредиентов важен

Коллекция ингредиентов должна содержать как минимум снаряжение → ингредиент именно в таком порядке. Третий предмет на уровне конструктора необязателен и по умолчанию равен Air, однако стандартный путь получения результата на кузнечном столе возвращает реальный результат, только когда фактическим предметом-шаблоном является minecraft:netherite_upgrade_smithing_template.

При использовании этого шаблона getFinalResult() копирует составной тег (compound tag) входного снаряжения в результат и переносит урон вплоть до максимальной прочности предмета-результата.

Рецепт камнереза

Рецепты камнереза позволяют разрезать блоки на варианты с помощью блока камнереза.

Пример StonecutterRecipe
import cn.nukkit.inventory.StonecutterRecipe;
import cn.nukkit.item.Item;

craftingManager.registerRecipe(new StonecutterRecipe(
"stone_to_stone_bricks", // recipeId
0, // priority metadata
Item.get(Item.STONE_BRICKS, 0, 4), // result (4 Stone Bricks)
Item.get(Item.STONE) // ingredient
));

Полный конструктор

Из класса cn.nukkit.inventory.StonecutterRecipe:

Конструктор
new StonecutterRecipe(
String recipeId, // Unique recipe identifier
int priority, // Priority metadata
Item result, // Output item
Item ingredient // Input item
);

Всё вместе

Вот полный пример регистрации нескольких типов рецептов в плагине:

RecipePlugin.java
package cn.nukkitmot.exampleplugin;

import cn.nukkit.inventory.*;
import cn.nukkit.item.Item;
import cn.nukkit.plugin.PluginBase;

import java.util.List;
import java.util.Map;

public class RecipePlugin extends PluginBase {
@Override
public void onEnable() {
CraftingManager craftingManager = getServer().getCraftingManager();

// Shaped: Craft a compass from 4 iron ingots + 1 redstone
craftingManager.registerRecipe(new ShapedRecipe(
"custom_compass",
1,
Item.get(Item.COMPASS),
new String[]{
" I ",
"IRI",
" I "
},
Map.of(
'I', Item.get(Item.IRON_INGOT),
'R', Item.get(Item.REDSTONE)
),
List.of()
));

// Shapeless: Combine 2 charcoal + 1 stick = 4 torches
craftingManager.registerRecipe(new ShapelessRecipe(
"custom_torch",
0,
Item.get(Item.TORCH, 0, 4),
List.of(
Item.get(Item.COAL, 1, 2), // Charcoal
Item.get(Item.STICK)
)
));

// Furnace: Smelt cobblestone back into stone
craftingManager.registerRecipe(new FurnaceRecipe(
"cobblestone_to_stone",
Item.get(Item.STONE),
Item.get(Item.COBBLESTONE)
));

// Campfire: Cook raw beef
craftingManager.registerRecipe(new CampfireRecipe(
Item.get(Item.COOKED_BEEF),
Item.get(Item.RAW_BEEF)
));

// Stonecutter: Stone → 4 Stone Bricks
craftingManager.registerRecipe(new StonecutterRecipe(
"stone_to_bricks",
0,
Item.get(Item.STONE_BRICKS, 0, 4),
Item.get(Item.STONE)
));

this.getLogger().info("Custom recipes registered!");
}
}

Дополнительные сведения

Приоритет рецепта

Параметр priority хранится в объектах рецептов по форме, безформенных рецептов, кузнечных рецептов и рецептов камнереза. Для рецептов по форме, безформенных рецептов и рецептов камнереза он также записывается в данные крафта. Текущее сопоставление на стороне сервера не сортирует рецепты по этому значению. Для рецептов по форме и безформенных рецептов CraftingManager группирует рецепты по хешу предмета-результата, индексирует их по совокупному хешу ингредиентов и может перебирать соответствующую группу результатов.

  • Короткие конструкторы ShapedRecipe и ShapelessRecipe используют приоритеты по умолчанию 1 и 10 соответственно
  • Значение по умолчанию из ванильных JSON-файлов рецептов — 0
  • Более поздний рецепт по форме или безформенный рецепт с тем же хешем результата и тем же совокупным хешем ингредиентов заменяет предыдущую запись в серверной карте сопоставления; рецепты с разными ингредиентами сосуществуют
  • Не полагайтесь на priority для переопределения ванильных рецептов

Использование предметов с метаданными

Многие ванильные предметы используют значения damage/meta для различения вариантов. Используйте двухаргументную форму Item.get():

// Bone meal / legacy white dye (meta 15)
Item.get(Item.DYE, 15);

// Coal (meta 0) vs Charcoal (meta 1)
Item.get(Item.COAL, 0); // Coal
Item.get(Item.COAL, 1); // Charcoal

// Items with count
Item.get(Item.TORCH, 0, 4); // 4 torches

Требование разблокировки рецепта

У ShapedRecipe и ShapelessRecipe есть расширенные конструкторы, принимающие RecipeUnlockingRequirement. Для протокола v1_21_0 и новее Nukkit-MOT записывает требования к рецептам в CraftingDataPacket, что определяет, когда рецепты становятся видимы для игроков. Короткие конструкторы используют RecipeUnlockingRequirement.ALWAYS_UNLOCKED.

import cn.nukkit.inventory.data.RecipeUnlockingRequirement;

// Always unlocked (default)
RecipeUnlockingRequirement.ALWAYS_UNLOCKED

// Based on context
new RecipeUnlockingRequirement(
RecipeUnlockingRequirement.UnlockingContext.PLAYER_IN_WATER
)

Рецепт с кастомными предметами

Вы можете использовать Item.fromString() для ссылок на кастомные предметы, зарегистрированные вашим плагином или другими плагинами:

// Use a custom item as ingredient
Item.fromString("nukkit:candy_cane_sword")

// Use a custom item as result
Item.fromString("myplugin:magic_dust")
Порядок регистрации

Кастомные предметы должны быть зарегистрированы до рецептов, которые на них ссылаются. Поскольку Item.registerCustomItem() обычно вызывается в onEnable(), убедитесь, что он выполняется до кода регистрации рецептов в том же методе onEnable().

Замена или удаление ванильных рецептов

В Nukkit-MOT нет высокоуровневого вызова «удалить ванильный рецепт». Регистрация рецепта с более высоким priority не делает его победителем над ванильным рецептом.

Для рецептов по форме и безформенных рецептов замена происходит, только когда регистрация записывает тот же ключ карты CraftingManager: тот же хеш предмета-результата и тот же совокупный хеш ингредиентов. Если ваш кастомный рецепт использует другие входные предметы, оба рецепта остаются доступными.

Удаление записи из предоставляемого серверного хранилища сопоставления влияет на сопоставление: используйте getShapedRecipes(), getShapelessRecipes(), getFurnaceRecipes(), getBlastFurnaceRecipes(), getSmithingRecipes(), getStonecutterRecipes() или campfireRecipes в зависимости от типа рецепта.

Для рецептов, отображаемых в книге рецептов клиента, также удалите запись из коллекции, используемой CraftingManager.packetFor(), и вызовите rebuildPacket(), чтобы кэшированные данные CraftingDataPacket обновились. Рецепты по форме и безформенные рецепты рекламируются из getRecipes(), рецепты печи — из getFurnaceRecipes(), рецепты камнереза — из getStonecutterRecipes(), а кузнечные рецепты — из getSmithingRecipes(). Сейчас packetFor() не рекламирует карты рецептов взрывной печи и костра. Для блокировки на основе политик проверяйте/отменяйте транзакцию крафта в вашем плагине.