Begin to implement the Uncrafter. (WIP)

This commit is contained in:
Zontreck 2024-03-04 02:29:39 -07:00
parent 09e93001b5
commit 9f36566fa6
4 changed files with 315 additions and 0 deletions

View file

@ -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<Item> 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))
{

View file

@ -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<Item> getRemainingIngredients(ItemStack stack)
{
List<Item> 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));
}
}

View file

@ -12,6 +12,8 @@ public class ModRecipes {
public static final RegistryObject<RecipeSerializer<CompressionChamberRecipe>> COMPRESSING_SERIALIZER = SERIALIZERS.register("compressing", ()->CompressionChamberRecipe.Serializer.INSTANCE);
public static final RegistryObject<RecipeSerializer<UncraftingRecipe>> UNCRAFTING_SERIALIZER = SERIALIZERS.register("uncrafting", UncraftingRecipe.Serializer::new);
public static void register(IEventBus bus)
{
SERIALIZERS.register(bus);

View file

@ -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<Ingredient> resultItems) implements CraftingRecipe, IShapedRecipe<CraftingContainer> {
@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<Ingredient> getIngredients() {
return this.resultItems();
}
public static class Serializer implements RecipeSerializer<UncraftingRecipe> {
/**
* 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<String, Ingredient> key = keyFromJson(GsonHelper.getAsJsonObject(json, "key"));
String[] pattern = shrink(patternFromJson(GsonHelper.getAsJsonArray(json, "pattern")));
int width = pattern[0].length();
int height = pattern.length;
NonNullList<Ingredient> ingredients = dissolvePattern(pattern, key, width, height);
return new UncraftingRecipe(id, width, height, ingredient, count, ingredients);
}
private static Map<String, Ingredient> keyFromJson(JsonObject json) {
Map<String, Ingredient> map = Maps.newHashMap();
for (Map.Entry<String, JsonElement> 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<Ingredient> dissolvePattern(String[] pattern, Map<String, Ingredient> key, int width, int height) {
NonNullList<Ingredient> results = NonNullList.withSize(width * height, Ingredient.EMPTY);
Set<String> 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<Ingredient> 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<UncraftingRecipe> {
private Type(){}
public static final UncraftingRecipe.Type INSTANCE = new UncraftingRecipe.Type();
public static final String ID = "uncrafting";
}
}