Jump to content

[1.18.2] Custom Crafting


Skyriis

Recommended Posts

Hey Guys,

i'm trying to add a new recipe type (fletching) to my mod and i'm currenty struggling on implementing the crafting system.

The result item appears if one ingredient (of multiple) has been inserted and then removed.

My Menu:

Spoiler
public class FletchingTableMenu extends AbstractContainerMenu {

    private final ContainerLevelAccess levelAccess;
    private final InvWrapper playerInvWrapper;
    private final FletchingContainer craftingContainer = new FletchingContainer(this);
    private final ResultContainer resultContainer = new ResultContainer();
    private int lastHotBarIndex, lastInventoryIndex, lastCraftingIndex, resultIndex;
    private final Player player;


    public FletchingTableMenu(int pContainerId, ContainerLevelAccess levelAccess, Inventory inventory) {
        super(Vanilla_PlusMenuTypes.FLETCHING_TABLE_MENU, pContainerId);
        this.levelAccess = levelAccess;
        this.playerInvWrapper = new InvWrapper(inventory);
        this.player = inventory.player;

        setupFletchingTableSlots();
        setupPlayerSlots();
    }

    @Override
    public ItemStack quickMoveStack(Player pPlayer, int pIndex) {
        ItemStack slotItemStack = slots.get(pIndex).getItem();
        if (slotItemStack.isEmpty()) return ItemStack.EMPTY;

        if (pIndex < resultIndex) {
            //Handle Fletching Table Inventory Shift-Click
            slotItemStack = moveToPlayerInventory(slotItemStack);
        } else {
            //Handle Inventory Shift-Click
            slotItemStack = moveToFletchingTableInventory(slotItemStack);
        }

        Slot slot = slots.get(pIndex);
        slot.set(slotItemStack);
        slot.setChanged();
        return ItemStack.EMPTY;
    }

    private void setupFletchingTableSlots() {
        int slotIndex = 0;
        int baseY = 21;

        //left slots
        for (int i = 0; i < 3; i++) {
            addSlot(new FilteredSlot(this.craftingContainer, slotIndex++, 29, baseY + 21 * i));
        }

        //right slots
        for (int i = 0; i < 3; i++) {
            addSlot(new FilteredSlot(this.craftingContainer, slotIndex++, 50, baseY + 21 * i));
        }

        lastCraftingIndex = slots.size();

        //result
        this.addSlot(new ResultSlot(player, craftingContainer, this.resultContainer, slotIndex, 111, 41));
        resultIndex = slots.size();
    }

    private void setupPlayerSlots() {
        int index = 0;

        //Hot bar
        for (int col = 0; col < 9; col++) {
            addSlot(new SlotItemHandler(this.playerInvWrapper, index++, 8 + 18 * col, 100 + 18 * 3));
        }
        lastHotBarIndex = slots.size();

        //Inventory
        for (int row = 0; row < 3; row++) {
            for (int col = 0; col < 9; col++) {
                addSlot(new SlotItemHandler(this.playerInvWrapper, index++, 8 + 18 * col, 96 + 18 * row));
            }
        }
        lastInventoryIndex = slots.size();
    }

    @Override
    public boolean stillValid(Player pPlayer) {
        return stillValid(this.levelAccess, pPlayer, Blocks.FLETCHING_TABLE);
    }

    private ItemStack moveToPlayerInventory(int slotIndex, ItemStack itemStack) {
        Slot currentCurrentSlot = getSlot(slotIndex);
        //Check if slot exist
        if (currentCurrentSlot == null) return itemStack;
        //Try to place Item into the Player Slot
        if (currentCurrentSlot.mayPlace(itemStack)) {
            itemStack = currentCurrentSlot.safeInsert(itemStack);
            currentCurrentSlot.setChanged();
        }

        return itemStack;
    }

    private ItemStack moveToPlayerInventory(ItemStack stack) {
        //Check Space in hot-bar
        for (int i = resultIndex; i < lastHotBarIndex; i++) {
            stack = moveToPlayerInventory(i, stack);
            //Check if there are no items left
            if (stack.isEmpty()) {
                break;
            }
        }

        //Check Space in Inventory
        if (!stack.isEmpty()) {
            for (int i = lastHotBarIndex; i < lastInventoryIndex; i++) {
                stack = moveToPlayerInventory(i, stack);
                //Check if there are no items left
                if (stack.isEmpty()) {
                    break;
                }
            }
        }

        return stack;
    }

    private ItemStack moveToFletchingTableInventory(ItemStack stack) {
        //Handle Player Inventory Shift-Click
        for (int i = 0; i < lastCraftingIndex; i++) {
            Slot currentCurrentSlot = getSlot(i);
            //Check if slot exist
            if (currentCurrentSlot == null) break;

            //Try to place Item into the Player Slot
            if (currentCurrentSlot.mayPlace(stack)) {
                stack = currentCurrentSlot.safeInsert(stack);
                currentCurrentSlot.setChanged();

            }
        }
        return stack;
    }

    @Override
    public void slotsChanged(Container pInventory) {
        levelAccess.execute((level, pos) -> {
            slotChangedCraftingGrid(this, level, this.player, craftingContainer, resultContainer);
        });
    }

    protected static void slotChangedCraftingGrid(FletchingTableMenu menu, Level level, Player player, FletchingContainer craftingContainer, ResultContainer resultContainer) {
        if(player instanceof ServerPlayer serverPlayer){
            ItemStack itemstack = ItemStack.EMPTY;

            Optional<FletchingRecipe> optional = level.getServer().getRecipeManager().getRecipeFor(Vanilla_PlusRecipeTypeRegistry.FLETCHING_RECIPE.get(), craftingContainer, level);
            if (optional.isPresent()) {
                FletchingRecipe fletchingRecipe = optional.get();
                if (resultContainer.setRecipeUsed(level, serverPlayer, fletchingRecipe)) {
                    itemstack = fletchingRecipe.assemble(craftingContainer);
                }
            }

            resultContainer.setItem(0, itemstack);
            menu.setRemoteSlot(0, itemstack);
            serverPlayer.connection.send(new ClientboundContainerSetSlotPacket(menu.containerId, menu.incrementStateId(), 0, itemstack));
        }
    }
}

Recipe Serializer:

Spoiler
public class FletchingRecipeSerializer implements RecipeSerializer<FletchingRecipe> {
    private ResourceLocation registryName;

    @Override
    public FletchingRecipe fromJson(ResourceLocation pRecipeId, JsonObject pSerializedRecipe) {
        Map<String, Ingredient> ingredientMap = keyFromJson(GsonHelper.getAsJsonObject(pSerializedRecipe, "key"));
        String[] patternArray = shrink(patternFromJson(GsonHelper.getAsJsonArray(pSerializedRecipe, "pattern")));
        int i = patternArray[0].length();
        int j = patternArray.length;
        NonNullList<Ingredient> dissolvePattern = dissolvePattern(patternArray, ingredientMap, i, j);
        ItemStack result = ShapedRecipe.itemStackFromJson(GsonHelper.getAsJsonObject(pSerializedRecipe, "result"));

        return new FletchingRecipe(dissolvePattern, result, pRecipeId);
    }

    @Nullable
    @Override
    public FletchingRecipe fromNetwork(ResourceLocation pRecipeId, FriendlyByteBuf pBuffer) {
        int i = pBuffer.readVarInt();
        int j = pBuffer.readVarInt();
        String s = pBuffer.readUtf();
        NonNullList<Ingredient> dissolvePattern = NonNullList.withSize(i * j, Ingredient.EMPTY);

        for (int k = 0; k < dissolvePattern.size(); ++k) {
            dissolvePattern.set(k, Ingredient.fromNetwork(pBuffer));
        }

        ItemStack result = pBuffer.readItem();
        return new FletchingRecipe(dissolvePattern, result, pRecipeId);
    }

    @Override
    public void toNetwork(FriendlyByteBuf pBuffer, FletchingRecipe pRecipe) {
        pBuffer.writeVarInt(2);
        pBuffer.writeVarInt(3);

        for (Ingredient ingredient : pRecipe.getIngredients()) {
            ingredient.toNetwork(pBuffer);
        }

        pBuffer.writeItem(pRecipe.getResultItem());
    }

    @Override
    public RecipeSerializer<?> setRegistryName(ResourceLocation name) {
        registryName=name;
        return this;
    }

    @Nullable
    @Override
    public ResourceLocation getRegistryName() {
        return registryName;
    }

    @Override
    public Class<RecipeSerializer<?>> getRegistryType() {
        return ForgeRegistries.RECIPE_SERIALIZERS.getRegistrySuperType();
    }

    static Map<String, Ingredient> keyFromJson(JsonObject pKeyEntry) {
        Map<String, Ingredient> map = Maps.newHashMap();

        for (Map.Entry<String, JsonElement> entry : pKeyEntry.entrySet()) {
            if (entry.getKey().length() != 1) {
                throw new JsonSyntaxException("Invalid key entry: '" + entry.getKey() + "' is an invalid symbol (must be 1 character only).");
            }

            if (" ".equals(entry.getKey())) {
                throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol.");
            }

            map.put(entry.getKey(), Ingredient.fromJson(entry.getValue()));
        }

        map.put(" ", Ingredient.EMPTY);
        return map;
    }

    static String[] patternFromJson(JsonArray pPatternArray) {
        String[] astring = new String[pPatternArray.size()];
        if (astring.length > 3) {
            throw new JsonSyntaxException("Invalid pattern: too many rows, " + 3 + " is maximum");
        } else if (astring.length == 0) {
            throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed");
        } else {
            for (int i = 0; i < astring.length; ++i) {
                String s = GsonHelper.convertToString(pPatternArray.get(i), "pattern[" + i + "]");
                if (s.length() > 2) {
                    throw new JsonSyntaxException("Invalid pattern: too many columns, " + 2 + " is maximum");
                }

                if (i > 0 && astring[0].length() != s.length()) {
                    throw new JsonSyntaxException("Invalid pattern: each row must be the same width");
                }

                astring[i] = s;
            }

            return astring;
        }
    }

    static String[] shrink(String... pToShrink) {
        int i = Integer.MAX_VALUE;
        int j = 0;
        int k = 0;
        int l = 0;

        for (int i1 = 0; i1 < pToShrink.length; ++i1) {
            String s = pToShrink[i1];
            i = Math.min(i, firstNonSpace(s));
            int j1 = lastNonSpace(s);
            j = Math.max(j, j1);
            if (j1 < 0) {
                if (k == i1) {
                    ++k;
                }

                ++l;
            } else {
                l = 0;
            }
        }

        if (pToShrink.length == l) {
            return new String[0];
        } else {
            String[] astring = new String[pToShrink.length - l - k];

            for (int k1 = 0; k1 < astring.length; ++k1) {
                astring[k1] = pToShrink[k1 + k].substring(i, j + 1);
            }

            return astring;
        }
    }

    private static int firstNonSpace(String pEntry) {
        int i;
        for (i = 0; i < pEntry.length() && pEntry.charAt(i) == ' '; ++i) {
        }

        return i;
    }

    private static int lastNonSpace(String pEntry) {
        int i;
        for (i = pEntry.length() - 1; i >= 0 && pEntry.charAt(i) == ' '; --i) {
        }

        return i;
    }

    static NonNullList<Ingredient> dissolvePattern(String[] pPattern, Map<String, Ingredient> pKeys, int pPatternWidth, int pPatternHeight) {
        NonNullList<Ingredient> nonnulllist = NonNullList.withSize(pPatternWidth * pPatternHeight, Ingredient.EMPTY);
        Set<String> set = Sets.newHashSet(pKeys.keySet());
        set.remove(" ");

        for (int i = 0; i < pPattern.length; ++i) {
            for (int j = 0; j < pPattern[i].length(); ++j) {
                String s = pPattern[i].substring(j, j + 1);
                Ingredient ingredient = pKeys.get(s);
                if (ingredient == null) {
                    throw new JsonSyntaxException("Pattern references symbol '" + s + "' but it's not defined in the key");
                }

                set.remove(s);
                nonnulllist.set(j + pPatternWidth * i, ingredient);
            }
        }

        if (!set.isEmpty()) {
            throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + set);
        } else {
            return nonnulllist;
        }
    }

Recipe:

Spoiler
@RequiredArgsConstructor
public class FletchingRecipe implements Recipe<FletchingContainer> {
    final NonNullList<Ingredient> recipeItems;
    final ItemStack result;
    @Getter
    private final ResourceLocation id;

    @Override
    public boolean matches(FletchingContainer pCraftingInventory, Level pLevel) {
        for (int i = 0; i < pCraftingInventory.getWidth(); ++i) {
            for (int j = 0; j < pCraftingInventory.getHeight(); ++j) {
                int k = i - 2;
                int l = j - 3;
                Ingredient ingredient = Ingredient.EMPTY;
                if (k >= 0 && l >= 0 && k < 2 && l < 3) {
                    ingredient = this.recipeItems.get(k + l * 2);
                }

                if (!ingredient.test(pCraftingInventory.getItem(i + j * pCraftingInventory.getWidth()))) {
                    return false;
                }
            }
        }

        return true;
    }

    @Override
    public ItemStack assemble(FletchingContainer pContainer) {
        return this.getResultItem().copy();
    }

    @Override
    public boolean canCraftInDimensions(int pWidth, int pHeight) {
        return pWidth >= 2 && pHeight >= 3;
    }

    @Override
    public ItemStack getResultItem() {
        return result;
    }


    @Override
    public RecipeSerializer<?> getSerializer() {
        return Vanilla_PlusRecipeSerializers.FLETCHING_RECIPE;
    }

    @Override
    public RecipeType<?> getType() {
        return Vanilla_PlusRecipeTypeRegistry.FLETCHING_RECIPE.get();
    }
}

test recipe json:

Spoiler
{
  "type": "vanilla_plus:fletching_recipe",
  "pattern": [
    "F ",
    "S ",
    "A "
  ],
  "key": {
    "F": {
      "item": "minecraft:flint"
    },
    "S": {
      "tag": "forge:rods/wooden"
    },
    "A": {
      "tag": "forge:feathers"
    }
  },
  "result": {
    "item": "minecraft:arrow",
    "count": 8
  }
}

 

Link to comment
Share on other sites

On 5/11/2022 at 11:04 AM, diesieben07 said:

Why? The client knows its dimension already.

Well, good point.

On 5/11/2022 at 11:04 AM, diesieben07 said:

You completely overwriting the jukebox BlockEntity with your own using mixins is not a good idea. You should instead modify the existing one to your needs.

I'm gonna do that in an update. I'm happy if something works somehow ^^"

On 5/11/2022 at 11:04 AM, diesieben07 said:

I'll have to look at the actual issue later when I am off work.

Nice :) thank you

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.