Создание своего типа рецепта

Версия Minecraft
1.20.2
API
Forge
22
1
0
День добрый, я пытаюсь создать кастомный тип рецепта на Forge версии 1.20.2, но все мои попытки создать его или найти какой-либо более-менее рабочий гайд приводят к тому, что ничего не работает. Что именно не работает - понятия не имею, и прошу помочь хотя бы определиться, что мне нужно фиксить.
Код рецепта крафта:
Java:
public class CoalExtruderRecipe implements Recipe<Container> {
    private final NonNullList<Ingredient> inputs;
    private final ItemStack output;

    public CoalExtruderRecipe(List<Ingredient> inputs, ItemStack output) {
        this.inputs = NonNullList.withSize(2, Ingredient.EMPTY);
        for (int index = 0; index < inputs.size(); index++) {
            this.inputs.set(index, inputs.get(index));
        }
        this.output = output;
    }

    public NonNullList<Ingredient> getInputs() {
        return this.inputs;
    }

    public ItemStack getOutput() {
        return this.output;
    }

    @Override
    public boolean matches(Container pContainer, Level pLevel) {
        ItemStack input0 = pContainer.getItem(0);
        ItemStack input1 = pContainer.getItem(1);

        return matches(input0, input1);
    }
    public boolean matches(ItemStack... inputs) {
        List<Boolean> matched = new ArrayList<>(inputs.length);

        for (int i = 0; i < inputs.length; i++) {
            matched.add(false);
        }

        for (Ingredient recipeIngredient : this.inputs) {
            for (int inputIndex = 0; inputIndex < inputs.length; inputIndex++) {
                ItemStack input = inputs[inputIndex];

                if (!matched.get(inputIndex) && recipeIngredient.test(input)) {
                    matched.set(inputIndex, true);
                    break; // Move to the next recipe ingredient
                }
            }
        }

        // Check if all ingredients were matched
        return matched.stream().allMatch(Boolean::valueOf);
    }

    @Override
    public ItemStack assemble(Container pContainer, RegistryAccess pRegistryAccess) {
        ItemStack input0 = pContainer.getItem(0);
        ItemStack input1 = pContainer.getItem(1);

        return matches(input0, input1) ? this.output.copy() : ItemStack.EMPTY;
    }

    @Override
    public boolean canCraftInDimensions(int pWidth, int pHeight) {
        return true;
    }

    @Override
    public ItemStack getResultItem(RegistryAccess pRegistryAccess) {
        return this.output;
    }

    @Override
    public RecipeSerializer<?> getSerializer() {
        return Serializer.INSTANCE;
    }

    @Override
    public RecipeType<?> getType() {
        return Type.INSTANCE;
    }

    public void assemble(CoalExtruderBlockEntity coalExtruderBlockEntity) {
        ItemStack input0 = coalExtruderBlockEntity.getInventory().getStackInSlot(0);
        ItemStack input1 = coalExtruderBlockEntity.getInventory().getStackInSlot(1);

        if (matches(input0, input1)) {
            coalExtruderBlockEntity.getInventory().insertItem(2, this.output.copy(), false);

            // Remove the items from the inputs that were used
            for (Ingredient input : this.inputs) {
                if(input.test(input0))
                    coalExtruderBlockEntity.getInventory().extractItem(0, 1, false);
                else if(input.test(input1))
                    coalExtruderBlockEntity.getInventory().extractItem(1, 1, false);
            }
        }
    }

    public static class Serializer implements RecipeSerializer<CoalExtruderRecipe> {
        public static final Serializer INSTANCE  = new Serializer();

        private static final Codec<CoalExtruderRecipe> CODEC = RecordCodecBuilder.create(instance -> instance.group(
                Ingredient.CODEC_NONEMPTY.listOf().fieldOf("inputs").forGetter(recipe -> recipe.inputs),
                ItemStack.CODEC.fieldOf("output").forGetter(recipe -> recipe.output)).apply(instance, CoalExtruderRecipe::new));

        @Override
        public Codec<CoalExtruderRecipe> codec() {
            return CODEC;
        }

        @Override
        public @Nullable CoalExtruderRecipe fromNetwork(FriendlyByteBuf pBuffer) {
            int inputCount = pBuffer.readInt();
            NonNullList<Ingredient> inputs = NonNullList.withSize(2, Ingredient.EMPTY);
            for (int index = 0; index < inputCount; index++) {
                inputs.add(Ingredient.fromNetwork(pBuffer));
            }

            ItemStack output = pBuffer.readItem();
            int processTime = pBuffer.readInt();
            return new CoalExtruderRecipe(inputs, output);
        }

        @Override
        public void toNetwork(FriendlyByteBuf pBuffer, CoalExtruderRecipe pRecipe) {
            pBuffer.writeInt(pRecipe.inputs.size());
            for (Ingredient input : pRecipe.inputs) {
                input.toNetwork(pBuffer);
            }

            pBuffer.writeItem(pRecipe.output);
        }
    }

    public static class Type implements RecipeType<CoalExtruderRecipe> {
        public static final Type INSTANCE = new Type();

        private static final String ID = new ResourceLocation(TutorialMod.MOD_ID, "coal_extruding").toString();

        @Override
        public String toString() {
            return ID;
        }
    }
}
Использование в BlockEntity:
Java:
public void tick() {
        if(this.level.isClientSide() || this.level == null) {
            return;
        }
        if(hasRecipe()) {
            increaseCraftingProgress();
            setChanged(this.level, this.worldPosition, this.getBlockState());

            if(hasProgressFinished()) {
                craftItem();
                resetProgress();
            }
        } else {
            resetProgress();
        }
    }

private boolean hasRecipe() {
        Optional<RecipeHolder<CoalExtruderRecipe>> recipe = getCurrentRecipe();
        if(recipe.isEmpty()) {
            return false;
        }
        ItemStack result = recipe.get().value().getResultItem(null);
        return canInsertAmountInSlot(result.getCount()) && canInsertItemInSlot(result.getItem());
    }

    private boolean canInsertItemInSlot(Item item) {
        return this.inventory.getStackInSlot(2).isEmpty() || this.inventory.getStackInSlot(2).is(item);
    }

    private boolean canInsertAmountInSlot(int count) {
        return this.inventory.getStackInSlot(2).getCount() + count <= this.inventory.getStackInSlot(2).getMaxStackSize();
    }

    private Optional<RecipeHolder<CoalExtruderRecipe>> getCurrentRecipe() {
        SimpleContainer Inventory = new SimpleContainer(this.inventory.getSlots());
        for (int i = 0; i < inventory.getSlots(); i++) {
            Inventory.setItem(i, this.inventory.getStackInSlot(i));
        }

        return this.level.getRecipeManager().getRecipeFor(CoalExtruderRecipe.Type.INSTANCE, Inventory, level);
    }
Ни единой ошибки я не получаю (Если бы получал, может быть, пофиксил бы), прошу помочь хоть чем-нибудь
 
Решение
Вот сейчас я на самом деле посмеялся от души. Оказывается, он (наверное) все это время парсил все json'ы, причем парсил успешно, но искал другие ключи, выблевывая нужные рецепты.

Сейчас я заменил в "output" ключи "count" на "Count" и "item" на "id", и все заработало в штатном порядке. Пойду смотреть, где определяются названия этих ключей.

Огромное спасибо за помощь и уделенное мне время
Проблема в методе getCurrentRecipe()

Последний тестовый вариант:
//Вместо BlockEntity наследуй BaseContainerBlockEntity.   
private Optional<RecipeHolder<CoalExtruderRecipe>> getCurrentRecipe() {
   return this.level.getRecipeManager().getRecipeFor(ModRecipeTypes.COAL_EXTRUDER.get(), this, level); 
}

Могу ещё посоветовать посмотреть у печки (сериализатор и тип оставив тот же)
 
Java:
//Вместо BlockEntity наследуй BaseContainerBlockEntity.
private Optional<RecipeHolder<CoalExtruderRecipe>> getCurrentRecipe() {
return this.level.getRecipeManager().getRecipeFor(ModRecipeTypes.COAL_EXTRUDER.get(), this, level);
}
Сделал наследование от этого класса, имплементировал все, optional все равно пустая. Не совсем понял, в чем смысл этого

1732359931715.png
 
Ладно, кажется, я все же понял, почему люди пишут в большинстве своем на 1.20.1, а не на 1.20.2. Может, нужно все перенести на версию постарше и не морочить голову никому? Ну и так, вскользь, на какой (или каких) версиях сейчас лучше писать моды?
 
Не совсем понял, в чем смысл этого
Смысл в том чтобы избежать ItemStackHandler'a + не создавать SimpleContainer

optional все равно пустая.
Проверь ещё раз matches, а также путь к файлу рецепта и сам файл.

на какой (или каких) версиях сейчас лучше писать моды?
1.20.1.
 
Последнее редактирование:
Может, нужно все перенести на версию постарше и не морочить голову никому?
Если ручки кривые, то перенос кода не версию ниже ничем не поможет.

RecipeManager#getRecipeFor хз как работает, я этот метод вообще не помню. Вместо macthes я делаю проверку на подходящий рецепт вручную.

UPD. Посмотрел как оно работает. Ставь брейкпоинт на matches в своём рецепте, и смотри где накосячил.
 
Последнее редактирование:
Хм)

Java:
[19:27:41] [Render thread/ERROR] [minecraft/RecipeManager]: Parsing error loading recipe tutorialmod:iron_ingot_from_coal_extruding
com.google.gson.JsonParseException: No key Count in MapLike[{"count":1,"item":"minecraft:iron_ingot"}]; No key id in MapLike[{"count":1,"item":"minecraft:iron_ingot"}]

Java:
{
  "type": "tutorialmod:coal_extruding",
  "inputs":[
    {
      "item": "minecraft:iron_nugget"
    },
    {
      "item": "minecraft:coal"
    }
  ],
  "output":{
    "count": 1,
    "item": "minecraft:iron_ingot"
  }
}
 
Вот сейчас я на самом деле посмеялся от души. Оказывается, он (наверное) все это время парсил все json'ы, причем парсил успешно, но искал другие ключи, выблевывая нужные рецепты.

Сейчас я заменил в "output" ключи "count" на "Count" и "item" на "id", и все заработало в штатном порядке. Пойду смотреть, где определяются названия этих ключей.

Огромное спасибо за помощь и уделенное мне время
 
Назад
Сверху