Jump to content

[1.15.2] Recipe input doubles - custom crafting table + recipe [Solved]


Thorius

Recommended Posts

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 by Thorius
Link to comment
Share on other sites

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?

Link to comment
Share on other sites

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.

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.