Posted November 3, 20205 yr Hello, i created a special crafting table and recipe. If i craft something, then the ingredients get decreased by one, but then they double themselves. For example: a stack has initally 3 items, then after a single craft it becomes 4, then 6, 10, 18 and so on. If the recipeType is changed to the normal vanilla one in the container, then it functions properly. Consequently it is probably the custom recipe responsible for the error. This, however, contradicts my findings, the CraftingResultSlot should be responsible for handling the ingredients slots. But as i said, the container class works fine with the vanilla recipes and i didn't find anything related in the classes responsible for the recipes. Container class: package com.thoriuslight.professionsmod.inventory.container; import java.util.Optional; import com.thoriuslight.professionsmod.init.BlockInit; import com.thoriuslight.professionsmod.init.ModContainerTypes; import com.thoriuslight.professionsmod.init.RecipeSerializerInit; import com.thoriuslight.professionsmod.item.crafting.ISmithRecipe; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.entity.player.ServerPlayerEntity; import net.minecraft.inventory.CraftResultInventory; import net.minecraft.inventory.CraftingInventory; import net.minecraft.inventory.IInventory; import net.minecraft.inventory.container.CraftingResultSlot; import net.minecraft.inventory.container.RecipeBookContainer; import net.minecraft.inventory.container.Slot; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.IRecipe; import net.minecraft.item.crafting.RecipeItemHelper; import net.minecraft.network.PacketBuffer; import net.minecraft.network.play.server.SSetSlotPacket; import net.minecraft.util.IWorldPosCallable; import net.minecraft.world.World; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; public class SmithCraftingContainer extends RecipeBookContainer<CraftingInventory>{ private final CraftingInventory craftMatrix = new CraftingInventory(this, 3, 3); private final CraftResultInventory craftResult = new CraftResultInventory(); private final IWorldPosCallable canInteractWithCallable; private final PlayerEntity player; public SmithCraftingContainer(int windowId, final PlayerInventory playerInventory, final PacketBuffer data) { this(windowId, playerInventory, IWorldPosCallable.DUMMY); } public SmithCraftingContainer(int windowId, final PlayerInventory playerInventory) { this(windowId, playerInventory, IWorldPosCallable.DUMMY); } public SmithCraftingContainer(int windowId, final PlayerInventory playerInventory, IWorldPosCallable worldPosCallable) { super(ModContainerTypes.SMITH_CRAFTING.get(), windowId); this.canInteractWithCallable = worldPosCallable; this.player = playerInventory.player; this.addSlot(new CraftingResultSlot(playerInventory.player, this.craftMatrix, this.craftResult, 0, 124, 35)); for(int i = 0; i < 3; ++i) { for(int j = 0; j < 3; ++j) { this.addSlot(new Slot(this.craftMatrix, j + i * 3, 30 + j * 18, 17 + i * 18)); } } for(int k = 0; k < 3; ++k) { for(int i1 = 0; i1 < 9; ++i1) { this.addSlot(new Slot(playerInventory, i1 + k * 9 + 9, 8 + i1 * 18, 84 + k * 18)); } } for(int l = 0; l < 9; ++l) { this.addSlot(new Slot(playerInventory, l, 8 + l * 18, 142)); } } protected static void func_217066_a(int p_217066_0_, World world, PlayerEntity p_217066_2_, CraftingInventory craftInv, CraftResultInventory p_217066_4_) { if (!world.isRemote) { ServerPlayerEntity serverplayerentity = (ServerPlayerEntity)p_217066_2_; ItemStack itemstack = ItemStack.EMPTY; Optional<ISmithRecipe> optional = world.getServer().getRecipeManager().getRecipe(RecipeSerializerInit.SMITH_TYPE, craftInv, world); if (optional.isPresent()) { ISmithRecipe ismithrecipe = optional.get(); if (p_217066_4_.canUseRecipe(world, serverplayerentity, ismithrecipe)) { itemstack = ismithrecipe.getCraftingResult(craftInv); } } p_217066_4_.setInventorySlotContents(0, itemstack); serverplayerentity.connection.sendPacket(new SSetSlotPacket(p_217066_0_, 0, itemstack)); } } @Override public void onCraftMatrixChanged(IInventory inventoryIn) { this.canInteractWithCallable.consume((world, p_217069_2_) -> { func_217066_a(this.windowId, world, this.player, this.craftMatrix, this.craftResult); }); } @Override public void fillStackedContents(RecipeItemHelper itemHelperIn) { this.craftMatrix.fillStackedContents(itemHelperIn); } @Override public void clear() { this.craftMatrix.clear(); this.craftResult.clear(); } @Override public boolean matches(IRecipe<? super CraftingInventory> recipeIn) { return recipeIn.matches(this.craftMatrix, this.player.world); } @Override public void onContainerClosed(PlayerEntity playerIn) { super.onContainerClosed(playerIn); this.canInteractWithCallable.consume((p_217068_2_, p_217068_3_) -> { this.clearContainer(playerIn, p_217068_2_, this.craftMatrix); }); } @Override public boolean canInteractWith(PlayerEntity playerIn) { return isWithinUsableDistance(this.canInteractWithCallable, playerIn, BlockInit.SMITHCRAFTINGTABLE_BLOCK.get()); } @Override public ItemStack transferStackInSlot(PlayerEntity playerIn, int index) { ItemStack itemstack = ItemStack.EMPTY; Slot slot = this.inventorySlots.get(index); if (slot != null && slot.getHasStack()) { ItemStack itemstack1 = slot.getStack(); itemstack = itemstack1.copy(); if (index == 0) { this.canInteractWithCallable.consume((p_217067_2_, p_217067_3_) -> { itemstack1.getItem().onCreated(itemstack1, p_217067_2_, playerIn); }); if (!this.mergeItemStack(itemstack1, 10, 46, true)) { return ItemStack.EMPTY; } slot.onSlotChange(itemstack1, itemstack); } else if (index >= 10 && index < 46) { if (!this.mergeItemStack(itemstack1, 1, 10, false)) { if (index < 37) { if (!this.mergeItemStack(itemstack1, 37, 46, false)) { return ItemStack.EMPTY; } } else if (!this.mergeItemStack(itemstack1, 10, 37, false)) { return ItemStack.EMPTY; } } } else if (!this.mergeItemStack(itemstack1, 10, 46, false)) { return ItemStack.EMPTY; } if (itemstack1.isEmpty()) { slot.putStack(ItemStack.EMPTY); } else { slot.onSlotChanged(); } if (itemstack1.getCount() == itemstack.getCount()) { return ItemStack.EMPTY; } ItemStack itemstack2 = slot.onTake(playerIn, itemstack1); if (index == 0) { playerIn.dropItem(itemstack2, false); } } return itemstack; } @Override public boolean canMergeSlot(ItemStack stack, Slot slotIn) { return slotIn.inventory != this.craftResult && super.canMergeSlot(stack, slotIn); } @Override public int getOutputSlot() { return 0; } @Override public int getWidth() { return this.craftMatrix.getWidth(); } @Override public int getHeight() { return this.craftMatrix.getHeight(); } @OnlyIn(Dist.CLIENT) @Override public int getSize() { return 10; } } Recipe class: package com.thoriuslight.professionsmod.item.crafting; import com.thoriuslight.professionsmod.init.RecipeSerializerInit; import net.minecraft.inventory.CraftingInventory; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraft.item.crafting.Ingredient; import net.minecraft.util.NonNullList; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; public class SmithRecipe implements ISmithRecipe{ private final int recipeWidth; private final int recipeHeight; private final NonNullList<Ingredient> recipeItems; private final ItemStack recipeOutput; private final ResourceLocation id; public SmithRecipe(ResourceLocation id, int recipeWidthIn, int recipeHeightIn, NonNullList<Ingredient> recipeItemsIn, ItemStack output){ this.recipeWidth = recipeWidthIn; this.recipeHeight = recipeHeightIn; this.id = id; this.recipeItems = recipeItemsIn; this.recipeOutput = output; } @Override public boolean matches(CraftingInventory inv, World worldIn) { //Horizontal shift for(int x = 0; x <= inv.getWidth() - this.recipeWidth; ++x) { //Vertical shift for(int y = 0; y <= inv.getHeight() - this.recipeHeight; ++y) { if (this.checkMatch(inv, x, y, true)) { return true; } if (this.checkMatch(inv, x, y, false)) { return true; } } } return false; } private boolean checkMatch(CraftingInventory craftingInventory, int p_77573_2_, int p_77573_3_, boolean p_77573_4_) { for(int i = 0; i < craftingInventory.getWidth(); ++i) { for(int j = 0; j < craftingInventory.getHeight(); ++j) { int k = i - p_77573_2_; int l = j - p_77573_3_; Ingredient ingredient = Ingredient.EMPTY; if (k >= 0 && l >= 0 && k < this.recipeWidth && l < this.recipeHeight) { if (p_77573_4_) { ingredient = this.recipeItems.get(this.recipeWidth - k - 1 + l * this.recipeWidth); } else { ingredient = this.recipeItems.get(k + l * this.recipeWidth); } } if (!ingredient.test(craftingInventory.getStackInSlot(i + j * craftingInventory.getWidth()))) { return false; } } } return true; } @Override public ItemStack getCraftingResult(CraftingInventory inv) { return this.recipeOutput.copy(); } @Override public boolean canFit(int width, int height) { return width >= this.recipeWidth && height >= this.recipeHeight; } @Override public ItemStack getRecipeOutput() { return this.recipeOutput; } @Override public ResourceLocation getId() { return this.id; } @Override public IRecipeSerializer<?> getSerializer() { return RecipeSerializerInit.SMITH_SERIALIZER.get(); } @Override public Ingredient getInput() { return null; } public int getRecipeHeight() { return this.recipeHeight; } public int getRecipeWidth() { return this.recipeWidth; } @Override public NonNullList<Ingredient> getIngredients() { return this.recipeItems; } } IRecipe class: package com.thoriuslight.professionsmod.item.crafting; import javax.annotation.Nonnull; import com.thoriuslight.professionsmod.ProfessionsMod; import net.minecraft.inventory.CraftingInventory; import net.minecraft.item.crafting.IRecipe; import net.minecraft.item.crafting.IRecipeType; import net.minecraft.item.crafting.Ingredient; import net.minecraft.util.ResourceLocation; import net.minecraft.util.registry.Registry; public interface ISmithRecipe extends IRecipe<CraftingInventory>{ ResourceLocation RECIPE_TYPE_ID = new ResourceLocation(ProfessionsMod.MODID, "smith"); @Nonnull @Override default IRecipeType<?> getType() { return Registry.RECIPE_TYPE.getValue(RECIPE_TYPE_ID).get(); } @Override default boolean canFit(int width, int height) { return false; } Ingredient getInput(); } RecipeSerializer class: package com.thoriuslight.professionsmod.item.crafting; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraft.item.crafting.Ingredient; import net.minecraft.network.PacketBuffer; import net.minecraft.util.JSONUtils; import net.minecraft.util.NonNullList; import net.minecraft.util.ResourceLocation; import net.minecraftforge.common.crafting.CraftingHelper; import net.minecraftforge.registries.ForgeRegistryEntry; public class SmithRecipeSerializer extends ForgeRegistryEntry<IRecipeSerializer<?>> implements IRecipeSerializer<SmithRecipe>{ @Override public SmithRecipe read(ResourceLocation recipeId, JsonObject json) { Map<String, Ingredient> map = SmithRecipeSerializer.deserializeKey(JSONUtils.getJsonObject(json, "key")); String[] astring = SmithRecipeSerializer.shrink(SmithRecipeSerializer.patternFromJson(JSONUtils.getJsonArray(json, "pattern"))); int i = astring[0].length(); int j = astring.length; NonNullList<Ingredient> input = deserializeIngredients(astring, map, i, j); ItemStack ouput = CraftingHelper.getItemStack(JSONUtils.getJsonObject(json, "result"), true); return new SmithRecipe(recipeId, i, j, input, ouput); } private static NonNullList<Ingredient> deserializeIngredients(String[] pattern, Map<String, Ingredient> keys, int patternWidth, int patternHeight) { NonNullList<Ingredient> nonnulllist = NonNullList.withSize(patternWidth * patternHeight, Ingredient.EMPTY); Set<String> set = Sets.newHashSet(keys.keySet()); set.remove(" "); for(int i = 0; i < pattern.length; ++i) { for(int j = 0; j < pattern[i].length(); ++j) { String s = pattern[i].substring(j, j + 1); Ingredient ingredient = keys.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 + patternWidth * i, ingredient); } } if (!set.isEmpty()) { throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + set); } else { return nonnulllist; } } private static Map<String, Ingredient> deserializeKey(JsonObject json) { Map<String, Ingredient> map = Maps.newHashMap(); for(Entry<String, JsonElement> entry : json.entrySet()) { if (entry.getKey().length() != 1) { throw new JsonSyntaxException("Invalid key entry: '" + (String)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.deserialize(entry.getValue())); } map.put(" ", Ingredient.EMPTY); return map; } @VisibleForTesting static String[] shrink(String... toShrink) { int i = Integer.MAX_VALUE; int j = 0; int k = 0; int l = 0; for(int i1 = 0; i1 < toShrink.length; ++i1) { String s = toShrink[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 (toShrink.length == l) { return new String[0]; } else { String[] astring = new String[toShrink.length - l - k]; for(int k1 = 0; k1 < astring.length; ++k1) { astring[k1] = toShrink[k1 + k].substring(i, j + 1); } return astring; } } private static int firstNonSpace(String str) { int i; for(i = 0; i < str.length() && str.charAt(i) == ' '; ++i) { ; } return i; } private static int lastNonSpace(String str) { int i; for(i = str.length() - 1; i >= 0 && str.charAt(i) == ' '; --i) { ; } return i; } private static String[] patternFromJson(JsonArray jsonArr) { String[] astring = new String[jsonArr.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 = JSONUtils.getString(jsonArr.get(i), "pattern[" + i + "]"); if (s.length() > 3) { throw new JsonSyntaxException("Invalid pattern: too many columns, " + 3 + " 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; } } @Override public SmithRecipe read(ResourceLocation recipeId, PacketBuffer buffer) { int i = buffer.readVarInt(); int j = buffer.readVarInt(); NonNullList<Ingredient> nonnulllist = NonNullList.withSize(i * j, Ingredient.EMPTY); for(int k = 0; k < nonnulllist.size(); ++k) { nonnulllist.set(k, Ingredient.read(buffer)); } ItemStack itemstack = buffer.readItemStack(); return new SmithRecipe(recipeId, i, j, nonnulllist, itemstack); } @Override public void write(PacketBuffer buffer, SmithRecipe recipe) { buffer.writeVarInt(recipe.getRecipeWidth()); buffer.writeVarInt(recipe.getRecipeHeight()); for(Ingredient ingredient : recipe.getIngredients()) { ingredient.write(buffer); } buffer.writeItemStack(recipe.getRecipeOutput()); } } Recipe Init class: package com.thoriuslight.professionsmod.init; import com.thoriuslight.professionsmod.ProfessionsMod; import com.thoriuslight.professionsmod.item.crafting.ISmithRecipe; import com.thoriuslight.professionsmod.item.crafting.SmithRecipe; import com.thoriuslight.professionsmod.item.crafting.SmithRecipeSerializer; import net.minecraft.item.crafting.IRecipe; import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraft.item.crafting.IRecipeType; import net.minecraft.util.ResourceLocation; import net.minecraft.util.registry.Registry; import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; public class RecipeSerializerInit { public static final IRecipeSerializer<SmithRecipe> SMITH_RECIPE_SERIALIZER = new SmithRecipeSerializer(); public static final IRecipeType<ISmithRecipe> SMITH_TYPE = registerType(ISmithRecipe.RECIPE_TYPE_ID); public static final DeferredRegister<IRecipeSerializer<?>> RECIPE_SERIALIZERS = new DeferredRegister<>(ForgeRegistries.RECIPE_SERIALIZERS, ProfessionsMod.MODID); public static final RegistryObject<IRecipeSerializer<?>> SMITH_SERIALIZER = RECIPE_SERIALIZERS.register("smith", () -> SMITH_RECIPE_SERIALIZER); private static IRecipeType<ISmithRecipe> registerType(ResourceLocation recipeTypeId) { return Registry.register(Registry.RECIPE_TYPE, recipeTypeId, new RecipeType<>()); } private static class RecipeType<T extends IRecipe<?>> implements IRecipeType<T> { @Override public String toString() { return Registry.RECIPE_TYPE.getKey(this).toString(); } } } Thank you for taking your time to help. I really appreciate it. Edited November 4, 20204 yr by Thorius
November 4, 20205 yr 5 hours ago, Thorius said: If i craft something, then the ingredients get decreased by one, but then they double themselves. Then that's probably a desynchronisation across what you are changing. You are probably handling something that is not isolated and directly affects a field directly. As for what, I can't tell as most of this code seems to be from the shaped crafting recipe. You should rehandle your code such that it extends directly from the shaped crafting recipe if you are going to do it this way. Quite literally, I see no different between crafting tables other than a different recipe type. Therefore, you could reuse everything except the container and screen which could just be simply transferred as needed. This is not to mention a little bit of problematic logic during synchronization and how containers are handled. Just so I understand, are you saying the output is duplicating or the input?
November 4, 20204 yr Author The input gets duplicated. However i found the problem. I extended the CraftResultSlot for debug reasons and i noticed that it uses IRecipeType.CRAFTING to get the ingredients. After changing it to my type it works fine. Thanks for the help anyways.
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.