From 9f36566fa6d644555304577f7a5a479ef212605f Mon Sep 17 00:00:00 2001 From: Zontreck Date: Mon, 4 Mar 2024 02:29:39 -0700 Subject: [PATCH] Begin to implement the Uncrafter. (WIP) --- .../blocks/entity/UncrafterBlockEntity.java | 40 +++ .../zontreck/otemod/items/PartialItem.java | 35 +++ .../zontreck/otemod/recipe/ModRecipes.java | 2 + .../otemod/recipe/UncraftingRecipe.java | 238 ++++++++++++++++++ 4 files changed, 315 insertions(+) create mode 100644 src/main/java/dev/zontreck/otemod/recipe/UncraftingRecipe.java diff --git a/src/main/java/dev/zontreck/otemod/blocks/entity/UncrafterBlockEntity.java b/src/main/java/dev/zontreck/otemod/blocks/entity/UncrafterBlockEntity.java index eb431f6..29e2408 100644 --- a/src/main/java/dev/zontreck/otemod/blocks/entity/UncrafterBlockEntity.java +++ b/src/main/java/dev/zontreck/otemod/blocks/entity/UncrafterBlockEntity.java @@ -3,6 +3,7 @@ package dev.zontreck.otemod.blocks.entity; import dev.zontreck.otemod.implementation.OutputItemStackHandler; import dev.zontreck.otemod.implementation.energy.OTEEnergy; import dev.zontreck.otemod.implementation.uncrafting.UncrafterMenu; +import dev.zontreck.otemod.items.PartialItem; import dev.zontreck.otemod.networking.ModMessages; import dev.zontreck.otemod.networking.packets.EnergySyncS2CPacket; import net.minecraft.core.BlockPos; @@ -16,8 +17,13 @@ import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.ContainerData; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.CraftingRecipe; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeType; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -30,6 +36,10 @@ import net.minecraftforge.items.ItemStackHandler; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class UncrafterBlockEntity extends BlockEntity implements MenuProvider { public UncrafterBlockEntity(BlockPos position, BlockState state) { @@ -216,10 +226,40 @@ public class UncrafterBlockEntity extends BlockEntity implements MenuProvider return (entity.ENERGY_STORAGE.getEnergyStored() >= ENERGY_REQUIREMENT); } + + private ItemStack[] getIngredients(Recipe recipe) { + ItemStack[] stacks = new ItemStack[recipe.getIngredients().size()]; + + for (int i = 0; i < recipe.getIngredients().size(); i++) { + ItemStack[] matchingStacks = Arrays.stream(recipe.getIngredients().get(i).getItems()).toArray(ItemStack[]::new); + + stacks[i] = matchingStacks.length > 0 ? matchingStacks[Math.floorMod(this.ingredientsInCycle, matchingStacks.length)] : ItemStack.EMPTY; + } + + + return stacks; + } + + private int ingredientsInCycle=0; + + + private static CraftingRecipe[] getRecipesFor(CraftingContainer matrix, Level world) { + return world.getRecipeManager().getRecipesFor(RecipeType.CRAFTING, matrix, world).toArray(new CraftingRecipe[0]); + } + private static void uncraftItem(UncrafterBlockEntity entity) { if(hasRecipe(entity)) { ItemStack existing = entity.outputItems.getStackInSlot(0); + List INGREDIENTS = new ArrayList<>(); + if(existing.getItem() instanceof PartialItem pi) + { + INGREDIENTS = PartialItem.getRemainingIngredients(existing); + + } else { + // Reverse recipe + + } existing.setCount(existing.getCount()+1); if(existing.is(Items.AIR)) { diff --git a/src/main/java/dev/zontreck/otemod/items/PartialItem.java b/src/main/java/dev/zontreck/otemod/items/PartialItem.java index 6959ced..c0d519d 100644 --- a/src/main/java/dev/zontreck/otemod/items/PartialItem.java +++ b/src/main/java/dev/zontreck/otemod/items/PartialItem.java @@ -1,18 +1,26 @@ package dev.zontreck.otemod.items; import dev.zontreck.libzontreck.util.ChatHelpers; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.*; import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.commands.GiveCommand; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.TooltipFlag; import net.minecraft.world.level.Level; +import net.minecraftforge.registries.ForgeRegistries; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; public class PartialItem extends Item { private static final String TAG_UNCRAFT_REMAIN = "remaining"; + private static final String TAG_UNCRAFT_LIST = "Items"; + public PartialItem() { super (new Properties().fireResistant()); @@ -32,4 +40,31 @@ public class PartialItem extends Item tooltip.add(ChatHelpers.macro("!Dark_Red!This partial item appears to be invalid, and contains no item fragments.")); } } + + public static List getRemainingIngredients(ItemStack stack) + { + List itx = new ArrayList<>(); + if(stack.getTag()!=null) + { + if(stack.getTag().contains(TAG_UNCRAFT_LIST)) + { + ListTag lst = stack.getTag().getList(TAG_UNCRAFT_LIST, ListTag.TAG_STRING); + + for (Tag tag : + lst) { + StringTag st = (StringTag)tag; + itx.add(deserializeItemType(st.getAsString())); + } + + } + + } + + return itx; + } + + private static Item deserializeItemType(String item) + { + return ForgeRegistries.ITEMS.getValue(new ResourceLocation(item)); + } } diff --git a/src/main/java/dev/zontreck/otemod/recipe/ModRecipes.java b/src/main/java/dev/zontreck/otemod/recipe/ModRecipes.java index c14c726..ed3c5c2 100644 --- a/src/main/java/dev/zontreck/otemod/recipe/ModRecipes.java +++ b/src/main/java/dev/zontreck/otemod/recipe/ModRecipes.java @@ -12,6 +12,8 @@ public class ModRecipes { public static final RegistryObject> COMPRESSING_SERIALIZER = SERIALIZERS.register("compressing", ()->CompressionChamberRecipe.Serializer.INSTANCE); + public static final RegistryObject> UNCRAFTING_SERIALIZER = SERIALIZERS.register("uncrafting", UncraftingRecipe.Serializer::new); + public static void register(IEventBus bus) { SERIALIZERS.register(bus); diff --git a/src/main/java/dev/zontreck/otemod/recipe/UncraftingRecipe.java b/src/main/java/dev/zontreck/otemod/recipe/UncraftingRecipe.java new file mode 100644 index 0000000..1acbd4b --- /dev/null +++ b/src/main/java/dev/zontreck/otemod/recipe/UncraftingRecipe.java @@ -0,0 +1,238 @@ +package dev.zontreck.otemod.recipe; + +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.core.NonNullList; +import net.minecraft.core.RegistryAccess; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.level.Level; +import net.minecraftforge.common.crafting.IShapedRecipe; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; + + +public record UncraftingRecipe(ResourceLocation recipeID, int width, int height, Ingredient input, int count, NonNullList resultItems) implements CraftingRecipe, IShapedRecipe { + + @Override //This method is never used, but it has to be implemented + public boolean matches(CraftingContainer container, Level level) { + return false; + } + + @Override //We have to implement this method, can't really be used since we have multiple outputs + public ItemStack assemble(CraftingContainer container, RegistryAccess access) { + return ItemStack.EMPTY; + } + + @Override //We have to implement this method, returns the count just in case + public ItemStack getResultItem(RegistryAccess access) { + return new ItemStack(Items.AIR, this.count); + } + + @Override //Could probably be set to return true, since the recipe serializer doesn't let a bigger number through. + public boolean canCraftInDimensions(int width, int height) { + return (width >= this.width && height >= this.height); + } + + //Checks if the itemStack is a part of the ingredient when UncraftingMenu's getRecipesFor() method iterates through all recipes. + public boolean isItemStackAnIngredient(ItemStack stack) { + return Arrays.stream(this.input().getItems()).anyMatch(i -> (stack.getItem() == i.getItem() && stack.getCount() >= this.count())); + } + + @Override + public ResourceLocation getId() { + return this.recipeID; + } + + @Override + public RecipeSerializer getSerializer() { + return ModRecipes.UNCRAFTING_SERIALIZER.get(); + } + + @Override + public RecipeType getType() { + return Type.INSTANCE; + } + + @Override + public CraftingBookCategory category() { + return CraftingBookCategory.MISC; + } + + @Override + public int getRecipeWidth() { + return this.width(); + } + + @Override + public int getRecipeHeight() { + return this.height(); + } + + @Override + public NonNullList getIngredients() { + return this.resultItems(); + } + + public static class Serializer implements RecipeSerializer { + /** + * This is mostly vanilla's shaped recipe serializer, with some changes made to make it work with the slightly different recipe type. + * The recipe json has inputs for "cost", which determines how many levels the recipe will cost. + * "input", which is made to be an ingredient instead of an itemStack, so the recipe can have multiple input options, such as any member of an item tag. + * "count" is how many of the same item are required by the recipe, since we're dealing with ingredients and not itemStacks, we get this separately. + * "key" and "pattern", which work just like vanilla except this is output and not input, since we're uncrafting. + * Width and height get assigned automatically. + */ + @Override + public UncraftingRecipe fromJson(ResourceLocation id, JsonObject json) { + JsonObject input = GsonHelper.getAsJsonObject(json, "input"); + + JsonElement jsonelement = (GsonHelper.isArrayNode(input, "ingredient") ? GsonHelper.getAsJsonArray(input, "ingredient") : GsonHelper.getAsJsonObject(input, "ingredient")); + Ingredient ingredient = Ingredient.fromJson(jsonelement); + + int count = GsonHelper.getAsInt(input, "count", 1); + + Map key = keyFromJson(GsonHelper.getAsJsonObject(json, "key")); + String[] pattern = shrink(patternFromJson(GsonHelper.getAsJsonArray(json, "pattern"))); + + int width = pattern[0].length(); + int height = pattern.length; + + NonNullList ingredients = dissolvePattern(pattern, key, width, height); + + return new UncraftingRecipe(id, width, height, ingredient, count, ingredients); + } + + private static Map keyFromJson(JsonObject json) { + Map map = Maps.newHashMap(); + for (Map.Entry entry : json.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[] shrink(String... prePattern) { + int i = Integer.MAX_VALUE; + int j = 0; + int k = 0; + int l = 0; + + for (int i1 = 0; i1 < prePattern.length; ++i1) { + String s = prePattern[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 (prePattern.length == l) return new String[0]; + else { + String[] shrunk = new String[prePattern.length - l - k]; + for (int k1 = 0; k1 < shrunk.length; ++k1) shrunk[k1] = prePattern[k1 + k].substring(i, j + 1); + return shrunk; + } + } + + private static int firstNonSpace(String first) { + int i; + i = 0; + while (i < first.length() && first.charAt(i) == ' ') ++i; + return i; + } + + private static int lastNonSpace(String last) { + int i; + i = last.length() - 1; + while (i >= 0 && last.charAt(i) == ' ') --i; + return i; + } + + private static String[] patternFromJson(JsonArray pattern) { + String[] stringPattern = new String[pattern.size()]; + if (stringPattern.length > 3) { + throw new JsonSyntaxException("Invalid pattern: too many rows, 3 is maximum"); + } else if (stringPattern.length == 0) { + throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed"); + } else { + for (int i = 0; i < stringPattern.length; ++i) { + String s = GsonHelper.convertToString(pattern.get(i), "pattern[" + i + "]"); + if (s.length() > 3) + throw new JsonSyntaxException("Invalid pattern: too many columns, 3 is maximum"); + if (i > 0 && stringPattern[0].length() != s.length()) + throw new JsonSyntaxException("Invalid pattern: each row must be the same width"); + stringPattern[i] = s; + } + return stringPattern; + } + } + + private static NonNullList dissolvePattern(String[] pattern, Map key, int width, int height) { + NonNullList results = NonNullList.withSize(width * height, Ingredient.EMPTY); + Set set = Sets.newHashSet(key.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 = key.get(s); + if (ingredient == null) + throw new JsonSyntaxException("Pattern references symbol '" + s + "' but it's not defined in the key"); + set.remove(s); + results.set(j + width * i, ingredient); + } + } + + if (!set.isEmpty()) + throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + set); + else return results; + } + + @Nullable + @Override + public UncraftingRecipe fromNetwork(ResourceLocation id, FriendlyByteBuf buffer) { + int width = buffer.readVarInt(); + int height = buffer.readVarInt(); + Ingredient result = Ingredient.fromNetwork(buffer); + int count = buffer.readVarInt(); + NonNullList ingredients = NonNullList.withSize(width * height, Ingredient.EMPTY); + ingredients.replaceAll(ignored -> Ingredient.fromNetwork(buffer)); + return new UncraftingRecipe(id, width, height, result, count, ingredients); + } + + @Override + public void toNetwork(FriendlyByteBuf buffer, UncraftingRecipe recipe) { + buffer.writeVarInt(recipe.width()); + buffer.writeVarInt(recipe.height()); + recipe.input().toNetwork(buffer); + buffer.writeVarInt(recipe.count()); + for (Ingredient i : recipe.resultItems()) i.toNetwork(buffer); + } + } + + public static class Type implements RecipeType { + private Type(){} + + public static final UncraftingRecipe.Type INSTANCE = new UncraftingRecipe.Type(); + public static final String ID = "uncrafting"; + } +} \ No newline at end of file