diff --git a/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java b/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java index 553fbdf6..55094784 100644 --- a/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java +++ b/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java @@ -1,9 +1,30 @@ package dev.u9g.minecraftdatagenerator.generators; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import dev.u9g.minecraftdatagenerator.mixin.ShapedRecipeTypeAccessor; +import dev.u9g.minecraftdatagenerator.mixin.ShapelessRecipeTypeAccessor; +import dev.u9g.minecraftdatagenerator.util.Registries; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.itemgroup.ItemGroup; +import net.minecraft.recipe.RecipeDispatcher; +import net.minecraft.recipe.RecipeType; +import net.minecraft.recipe.ShapedRecipeType; +import net.minecraft.recipe.ShapelessRecipeType; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; public class RecipeDataGenerator implements IDataGenerator { + private static final int WILDCARD_DAMAGE = 32767; @Override public String getDataName() { @@ -12,113 +33,98 @@ public String getDataName() { @Override public JsonElement generateDataJson() { -// JsonObject finalObj = new JsonObject(); -// Multimap recipes = ArrayListMultimap.create(); -// for (Recipe recipe : Objects.requireNonNull(DGU.getWorld()).getRecipeManager().values()) { -// if (recipe instanceof ShapedRecipe sr) { -// var ingredients = sr.getIngredients(); -// List ingr = new ArrayList<>(); -// for (int i = 0; i < 9; i++) { -// if (i >= ingredients.size()) { -// ingr.add(-1); -// continue; -// } -// var stacks = ingredients.get(i); -//// var matching = stacks.getMatchingStacks(); -//// if (matching.length == 0) { -//// ingr.add(-1); -//// } else { -//// ingr.add(getRawIdFor(matching[0].getItem())); -//// } -// } -// Lists.reverse(ingr); -// -// JsonArray inShape = new JsonArray(); -// -// var iter = ingr.iterator(); -// for (int y = 0; y < 3; y++) { -// var jsonRow = new JsonArray(); -// int one = iter.next(); -// int two = iter.next(); -// int three = iter.next(); -// if (y > 0 && one == -1 && two == -1 && three == -1) continue; -// jsonRow.add(one); -// jsonRow.add(two); -// jsonRow.add(three); -// inShape.add(jsonRow); -// } -// -// JsonObject finalRecipe = new JsonObject(); -// finalRecipe.add("inShape", inShape); -// -// var resultObject = new JsonObject(); -// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem())); -// resultObject.addProperty("count", sr.getOutput().getCount()); -// finalRecipe.add("result", resultObject); -// -// String id = ((Integer) getRawIdFor(sr.getOutput().getItem())).toString(); -// -// if (!finalObj.has(id)) { -// finalObj.add(id, new JsonArray()); -// } -// finalObj.get(id).getAsJsonArray().add(finalRecipe); -//// var input = new JsonArray(); -//// var ingredients = sr.getIngredients().stream().toList(); -//// for (int y = 0; y < sr.getHeight(); y++) { -//// var arr = new JsonArray(); -//// for (int x = 0; x < sr.getWidth(); x++) { -//// if ((y*3)+x >= ingredients.size()) { -//// arr.add(JsonNull.INSTANCE); -//// continue; -//// } -//// var ingredient = ingredients.get((y*3)+x).getMatchingStacks(); // FIXME: fix when there are more than one matching stack -//// if (ingredient.length == 0) { -//// arr.add(JsonNull.INSTANCE); -//// } else { -//// arr.add(getRawIdFor(ingredient[0].getItem())); -//// } -//// } -//// input.add(arr); -//// } -//// var rootRecipeObject = new JsonObject(); -//// rootRecipeObject.add("inShape", input); -//// var resultObject = new JsonObject(); -//// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem())); -//// resultObject.addProperty("count", sr.getOutput().getCount()); -//// rootRecipeObject.add("result", resultObject); -//// recipes.put(getRawIdFor(sr.getOutput().getItem()), rootRecipeObject); -// } else if (recipe instanceof ShapelessRecipe sl) { + Map recipesByResult = new TreeMap<>(); + for (RecipeType recipe : RecipeDispatcher.getInstance().getAllRecipes()) { + ItemStack output = recipe.getOutput(); + if (isEmpty(output)) continue; + JsonObject json; + // Subclasses (map extending) compute their real output at craft time. + if (recipe.getClass() == ShapedRecipeType.class) { + json = generateShaped((ShapedRecipeType) recipe); + } else if (recipe.getClass() == ShapelessRecipeType.class) { + json = generateShapeless((ShapelessRecipeType) recipe); + } else { + continue; + } + int resultId = Registries.ITEMS.getRawId(output.getItem()); + recipesByResult.computeIfAbsent(resultId, k -> new JsonArray()).add(json); + } + JsonObject result = new JsonObject(); + recipesByResult.forEach((id, recipes) -> result.add(id.toString(), recipes)); + return result; + } -// var ingredients = new JsonArray(); -// for (Ingredient ingredient : sl.getIngredients()) { -// if (ingredient.isEmpty()) continue; -//// ingredients.add(getRawIdFor(ingredient.getMatchingStacks()[0].getItem())); -// } -// var rootRecipeObject = new JsonObject(); -// rootRecipeObject.add("ingredients", ingredients); -// var resultObject = new JsonObject(); -// resultObject.addProperty("id", getRawIdFor(sl.getOutput().getItem())); -// resultObject.addProperty("count", sl.getOutput().getCount()); -// rootRecipeObject.add("result", resultObject); -// recipes.put(getRawIdFor(sl.getOutput().getItem()), rootRecipeObject); -// } -// } -// recipes.forEach((a, b) -> { -// if (!finalObj.has(a.toString())) { -// finalObj.add(a.toString(), new JsonArray()); -// } -// finalObj.get(a.toString()).getAsJsonArray().add(b); -// }); -// return finalObj; - return JsonNull.INSTANCE; + private static JsonObject generateShaped(ShapedRecipeType recipe) { + ShapedRecipeTypeAccessor accessor = (ShapedRecipeTypeAccessor) recipe; + int width = accessor.getWidth(); + int height = accessor.getHeight(); + ItemStack[] ingredients = accessor.getIngredients(); + // Crafting leaves each ingredient's recipe remainder in its slot (milk bucket leaves a bucket). + JsonArray inShape = new JsonArray(); + JsonArray outShape = new JsonArray(); + boolean hasRemainder = false; + for (int y = 0; y < height; y++) { + JsonArray row = new JsonArray(); + JsonArray outRow = new JsonArray(); + for (int x = 0; x < width; x++) { + ItemStack stack = ingredients[y * width + x]; + row.add(cellFor(stack)); + Item remainder = isEmpty(stack) ? null : stack.getItem().getRecipeRemainder(); + hasRemainder |= remainder != null; + outRow.add(remainder == null ? JsonNull.INSTANCE : new JsonPrimitive(Registries.ITEMS.getRawId(remainder))); + } + inShape.add(row); + outShape.add(outRow); + } + JsonObject json = new JsonObject(); + json.add("inShape", inShape); + if (hasRemainder) json.add("outShape", outShape); + json.add("result", resultFor(recipe.getOutput())); + return json; } - @Override - public boolean isEnabled() { - return false; // TODO: Implement this + private static JsonObject generateShapeless(ShapelessRecipeType recipe) { + JsonArray ingredients = new JsonArray(); + for (ItemStack stack : ((ShapelessRecipeTypeAccessor) recipe).getStacks()) { + if (!isEmpty(stack)) ingredients.add(cellFor(stack)); + } + JsonObject json = new JsonObject(); + json.add("ingredients", ingredients); + json.add("result", resultFor(recipe.getOutput())); + return json; + } + + private static JsonObject resultFor(ItemStack output) { + JsonObject result = new JsonObject(); + result.addProperty("id", Registries.ITEMS.getRawId(output.getItem())); + result.addProperty("metadata", output.getDamage()); + result.addProperty("count", output.count); + return result; + } + + // A bare id means any metadata of that item: the vanilla wildcard damage, or an item + // that only exists with one metadata value. + private static JsonElement cellFor(ItemStack stack) { + if (isEmpty(stack)) return JsonNull.INSTANCE; + int id = Registries.ITEMS.getRawId(stack.getItem()); + if (stack.getDamage() == WILDCARD_DAMAGE || variantsOf(stack.getItem()).size() <= 1 && stack.getDamage() == 0) { + return new JsonPrimitive(id); + } + JsonObject cell = new JsonObject(); + cell.addProperty("id", id); + cell.addProperty("metadata", stack.getDamage()); + return cell; + } + + private static Set variantsOf(Item item) { + List stacks = new ArrayList<>(); + item.appendItemStacks(item, ItemGroup.SEARCH, stacks); + Set variants = new HashSet<>(); + for (ItemStack stack : stacks) variants.add(stack.getDamage()); + return variants; + } + + private static boolean isEmpty(ItemStack stack) { + return stack == null; } -// -// private static int getRawIdFor (Item item) { -// return Registry.ITEM.getRawId(item); -// } } diff --git a/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapedRecipeTypeAccessor.java b/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapedRecipeTypeAccessor.java new file mode 100644 index 00000000..680f81a0 --- /dev/null +++ b/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapedRecipeTypeAccessor.java @@ -0,0 +1,18 @@ +package dev.u9g.minecraftdatagenerator.mixin; + +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.ShapedRecipeType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ShapedRecipeType.class) +public interface ShapedRecipeTypeAccessor { + @Accessor("width") + int getWidth(); + + @Accessor("height") + int getHeight(); + + @Accessor("ingredients") + ItemStack[] getIngredients(); +} diff --git a/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapelessRecipeTypeAccessor.java b/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapelessRecipeTypeAccessor.java new file mode 100644 index 00000000..8cb567c1 --- /dev/null +++ b/mc/1.10.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapelessRecipeTypeAccessor.java @@ -0,0 +1,14 @@ +package dev.u9g.minecraftdatagenerator.mixin; + +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.ShapelessRecipeType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(ShapelessRecipeType.class) +public interface ShapelessRecipeTypeAccessor { + @Accessor("stacks") + List getStacks(); +} diff --git a/mc/1.10.2/src/main/resources/minecraft-data-generator.mixins.json b/mc/1.10.2/src/main/resources/minecraft-data-generator.mixins.json index 3ed82340..664c3843 100644 --- a/mc/1.10.2/src/main/resources/minecraft-data-generator.mixins.json +++ b/mc/1.10.2/src/main/resources/minecraft-data-generator.mixins.json @@ -14,6 +14,8 @@ "MiningToolItemAccessor", "NoteBlockAccessor", "ReadyMixin", + "ShapedRecipeTypeAccessor", + "ShapelessRecipeTypeAccessor", "StatusEffectAccessor" ], "injectors": { diff --git a/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java b/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java index 553fbdf6..6a0b8b5f 100644 --- a/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java +++ b/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java @@ -1,9 +1,31 @@ package dev.u9g.minecraftdatagenerator.generators; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import dev.u9g.minecraftdatagenerator.mixin.ShapedRecipeTypeAccessor; +import dev.u9g.minecraftdatagenerator.mixin.ShapelessRecipeTypeAccessor; +import dev.u9g.minecraftdatagenerator.util.Registries; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.itemgroup.ItemGroup; +import net.minecraft.recipe.RecipeDispatcher; +import net.minecraft.recipe.RecipeType; +import net.minecraft.recipe.ShapedRecipeType; +import net.minecraft.recipe.ShapelessRecipeType; +import net.minecraft.util.collection.DefaultedList; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; public class RecipeDataGenerator implements IDataGenerator { + private static final int WILDCARD_DAMAGE = 32767; @Override public String getDataName() { @@ -12,113 +34,98 @@ public String getDataName() { @Override public JsonElement generateDataJson() { -// JsonObject finalObj = new JsonObject(); -// Multimap recipes = ArrayListMultimap.create(); -// for (Recipe recipe : Objects.requireNonNull(DGU.getWorld()).getRecipeManager().values()) { -// if (recipe instanceof ShapedRecipe sr) { -// var ingredients = sr.getIngredients(); -// List ingr = new ArrayList<>(); -// for (int i = 0; i < 9; i++) { -// if (i >= ingredients.size()) { -// ingr.add(-1); -// continue; -// } -// var stacks = ingredients.get(i); -//// var matching = stacks.getMatchingStacks(); -//// if (matching.length == 0) { -//// ingr.add(-1); -//// } else { -//// ingr.add(getRawIdFor(matching[0].getItem())); -//// } -// } -// Lists.reverse(ingr); -// -// JsonArray inShape = new JsonArray(); -// -// var iter = ingr.iterator(); -// for (int y = 0; y < 3; y++) { -// var jsonRow = new JsonArray(); -// int one = iter.next(); -// int two = iter.next(); -// int three = iter.next(); -// if (y > 0 && one == -1 && two == -1 && three == -1) continue; -// jsonRow.add(one); -// jsonRow.add(two); -// jsonRow.add(three); -// inShape.add(jsonRow); -// } -// -// JsonObject finalRecipe = new JsonObject(); -// finalRecipe.add("inShape", inShape); -// -// var resultObject = new JsonObject(); -// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem())); -// resultObject.addProperty("count", sr.getOutput().getCount()); -// finalRecipe.add("result", resultObject); -// -// String id = ((Integer) getRawIdFor(sr.getOutput().getItem())).toString(); -// -// if (!finalObj.has(id)) { -// finalObj.add(id, new JsonArray()); -// } -// finalObj.get(id).getAsJsonArray().add(finalRecipe); -//// var input = new JsonArray(); -//// var ingredients = sr.getIngredients().stream().toList(); -//// for (int y = 0; y < sr.getHeight(); y++) { -//// var arr = new JsonArray(); -//// for (int x = 0; x < sr.getWidth(); x++) { -//// if ((y*3)+x >= ingredients.size()) { -//// arr.add(JsonNull.INSTANCE); -//// continue; -//// } -//// var ingredient = ingredients.get((y*3)+x).getMatchingStacks(); // FIXME: fix when there are more than one matching stack -//// if (ingredient.length == 0) { -//// arr.add(JsonNull.INSTANCE); -//// } else { -//// arr.add(getRawIdFor(ingredient[0].getItem())); -//// } -//// } -//// input.add(arr); -//// } -//// var rootRecipeObject = new JsonObject(); -//// rootRecipeObject.add("inShape", input); -//// var resultObject = new JsonObject(); -//// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem())); -//// resultObject.addProperty("count", sr.getOutput().getCount()); -//// rootRecipeObject.add("result", resultObject); -//// recipes.put(getRawIdFor(sr.getOutput().getItem()), rootRecipeObject); -// } else if (recipe instanceof ShapelessRecipe sl) { + Map recipesByResult = new TreeMap<>(); + for (RecipeType recipe : RecipeDispatcher.getInstance().getAllRecipes()) { + ItemStack output = recipe.getOutput(); + if (isEmpty(output)) continue; + JsonObject json; + // Subclasses (map extending) compute their real output at craft time. + if (recipe.getClass() == ShapedRecipeType.class) { + json = generateShaped((ShapedRecipeType) recipe); + } else if (recipe.getClass() == ShapelessRecipeType.class) { + json = generateShapeless((ShapelessRecipeType) recipe); + } else { + continue; + } + int resultId = Registries.ITEMS.getRawId(output.getItem()); + recipesByResult.computeIfAbsent(resultId, k -> new JsonArray()).add(json); + } + JsonObject result = new JsonObject(); + recipesByResult.forEach((id, recipes) -> result.add(id.toString(), recipes)); + return result; + } -// var ingredients = new JsonArray(); -// for (Ingredient ingredient : sl.getIngredients()) { -// if (ingredient.isEmpty()) continue; -//// ingredients.add(getRawIdFor(ingredient.getMatchingStacks()[0].getItem())); -// } -// var rootRecipeObject = new JsonObject(); -// rootRecipeObject.add("ingredients", ingredients); -// var resultObject = new JsonObject(); -// resultObject.addProperty("id", getRawIdFor(sl.getOutput().getItem())); -// resultObject.addProperty("count", sl.getOutput().getCount()); -// rootRecipeObject.add("result", resultObject); -// recipes.put(getRawIdFor(sl.getOutput().getItem()), rootRecipeObject); -// } -// } -// recipes.forEach((a, b) -> { -// if (!finalObj.has(a.toString())) { -// finalObj.add(a.toString(), new JsonArray()); -// } -// finalObj.get(a.toString()).getAsJsonArray().add(b); -// }); -// return finalObj; - return JsonNull.INSTANCE; + private static JsonObject generateShaped(ShapedRecipeType recipe) { + ShapedRecipeTypeAccessor accessor = (ShapedRecipeTypeAccessor) recipe; + int width = accessor.getWidth(); + int height = accessor.getHeight(); + ItemStack[] ingredients = accessor.getIngredients(); + // Crafting leaves each ingredient's recipe remainder in its slot (milk bucket leaves a bucket). + JsonArray inShape = new JsonArray(); + JsonArray outShape = new JsonArray(); + boolean hasRemainder = false; + for (int y = 0; y < height; y++) { + JsonArray row = new JsonArray(); + JsonArray outRow = new JsonArray(); + for (int x = 0; x < width; x++) { + ItemStack stack = ingredients[y * width + x]; + row.add(cellFor(stack)); + Item remainder = isEmpty(stack) ? null : stack.getItem().getRecipeRemainder(); + hasRemainder |= remainder != null; + outRow.add(remainder == null ? JsonNull.INSTANCE : new JsonPrimitive(Registries.ITEMS.getRawId(remainder))); + } + inShape.add(row); + outShape.add(outRow); + } + JsonObject json = new JsonObject(); + json.add("inShape", inShape); + if (hasRemainder) json.add("outShape", outShape); + json.add("result", resultFor(recipe.getOutput())); + return json; } - @Override - public boolean isEnabled() { - return false; // TODO: Implement this + private static JsonObject generateShapeless(ShapelessRecipeType recipe) { + JsonArray ingredients = new JsonArray(); + for (ItemStack stack : ((ShapelessRecipeTypeAccessor) recipe).getStacks()) { + if (!isEmpty(stack)) ingredients.add(cellFor(stack)); + } + JsonObject json = new JsonObject(); + json.add("ingredients", ingredients); + json.add("result", resultFor(recipe.getOutput())); + return json; + } + + private static JsonObject resultFor(ItemStack output) { + JsonObject result = new JsonObject(); + result.addProperty("id", Registries.ITEMS.getRawId(output.getItem())); + result.addProperty("metadata", output.getDamage()); + result.addProperty("count", output.getCount()); + return result; + } + + // A bare id means any metadata of that item: the vanilla wildcard damage, or an item + // that only exists with one metadata value. + private static JsonElement cellFor(ItemStack stack) { + if (isEmpty(stack)) return JsonNull.INSTANCE; + int id = Registries.ITEMS.getRawId(stack.getItem()); + if (stack.getDamage() == WILDCARD_DAMAGE || variantsOf(stack.getItem()).size() <= 1 && stack.getDamage() == 0) { + return new JsonPrimitive(id); + } + JsonObject cell = new JsonObject(); + cell.addProperty("id", id); + cell.addProperty("metadata", stack.getDamage()); + return cell; + } + + private static Set variantsOf(Item item) { + DefaultedList stacks = DefaultedList.of(); + item.method_13648(item, ItemGroup.SEARCH, stacks); + Set variants = new HashSet<>(); + for (ItemStack stack : stacks) variants.add(stack.getDamage()); + return variants; + } + + private static boolean isEmpty(ItemStack stack) { + return stack == null || stack.isEmpty(); } -// -// private static int getRawIdFor (Item item) { -// return Registry.ITEM.getRawId(item); -// } } diff --git a/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapedRecipeTypeAccessor.java b/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapedRecipeTypeAccessor.java new file mode 100644 index 00000000..680f81a0 --- /dev/null +++ b/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapedRecipeTypeAccessor.java @@ -0,0 +1,18 @@ +package dev.u9g.minecraftdatagenerator.mixin; + +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.ShapedRecipeType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(ShapedRecipeType.class) +public interface ShapedRecipeTypeAccessor { + @Accessor("width") + int getWidth(); + + @Accessor("height") + int getHeight(); + + @Accessor("ingredients") + ItemStack[] getIngredients(); +} diff --git a/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapelessRecipeTypeAccessor.java b/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapelessRecipeTypeAccessor.java new file mode 100644 index 00000000..8cb567c1 --- /dev/null +++ b/mc/1.11.2/src/main/java/dev/u9g/minecraftdatagenerator/mixin/ShapelessRecipeTypeAccessor.java @@ -0,0 +1,14 @@ +package dev.u9g.minecraftdatagenerator.mixin; + +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.ShapelessRecipeType; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(ShapelessRecipeType.class) +public interface ShapelessRecipeTypeAccessor { + @Accessor("stacks") + List getStacks(); +} diff --git a/mc/1.11.2/src/main/resources/minecraft-data-generator.mixins.json b/mc/1.11.2/src/main/resources/minecraft-data-generator.mixins.json index cd1d518d..97158201 100644 --- a/mc/1.11.2/src/main/resources/minecraft-data-generator.mixins.json +++ b/mc/1.11.2/src/main/resources/minecraft-data-generator.mixins.json @@ -12,6 +12,8 @@ "MiningToolItemAccessor", "NoteBlockAccessor", "ReadyMixin", + "ShapedRecipeTypeAccessor", + "ShapelessRecipeTypeAccessor", "StatusEffectAccessor" ], "injectors": { diff --git a/mc/1.12.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java b/mc/1.12.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java index 553fbdf6..bcf9695d 100644 --- a/mc/1.12.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java +++ b/mc/1.12.2/src/main/java/dev/u9g/minecraftdatagenerator/generators/RecipeDataGenerator.java @@ -1,9 +1,30 @@ package dev.u9g.minecraftdatagenerator.generators; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import dev.u9g.minecraftdatagenerator.util.Registries; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.itemgroup.ItemGroup; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeDispatcher; +import net.minecraft.recipe.RecipeType; +import net.minecraft.recipe.ShapedRecipeType; +import net.minecraft.recipe.ShapelessRecipeType; +import net.minecraft.util.collection.DefaultedList; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; public class RecipeDataGenerator implements IDataGenerator { + private static final int WILDCARD_DAMAGE = 32767; @Override public String getDataName() { @@ -12,113 +33,157 @@ public String getDataName() { @Override public JsonElement generateDataJson() { -// JsonObject finalObj = new JsonObject(); -// Multimap recipes = ArrayListMultimap.create(); -// for (Recipe recipe : Objects.requireNonNull(DGU.getWorld()).getRecipeManager().values()) { -// if (recipe instanceof ShapedRecipe sr) { -// var ingredients = sr.getIngredients(); -// List ingr = new ArrayList<>(); -// for (int i = 0; i < 9; i++) { -// if (i >= ingredients.size()) { -// ingr.add(-1); -// continue; -// } -// var stacks = ingredients.get(i); -//// var matching = stacks.getMatchingStacks(); -//// if (matching.length == 0) { -//// ingr.add(-1); -//// } else { -//// ingr.add(getRawIdFor(matching[0].getItem())); -//// } -// } -// Lists.reverse(ingr); -// -// JsonArray inShape = new JsonArray(); -// -// var iter = ingr.iterator(); -// for (int y = 0; y < 3; y++) { -// var jsonRow = new JsonArray(); -// int one = iter.next(); -// int two = iter.next(); -// int three = iter.next(); -// if (y > 0 && one == -1 && two == -1 && three == -1) continue; -// jsonRow.add(one); -// jsonRow.add(two); -// jsonRow.add(three); -// inShape.add(jsonRow); -// } -// -// JsonObject finalRecipe = new JsonObject(); -// finalRecipe.add("inShape", inShape); -// -// var resultObject = new JsonObject(); -// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem())); -// resultObject.addProperty("count", sr.getOutput().getCount()); -// finalRecipe.add("result", resultObject); -// -// String id = ((Integer) getRawIdFor(sr.getOutput().getItem())).toString(); -// -// if (!finalObj.has(id)) { -// finalObj.add(id, new JsonArray()); -// } -// finalObj.get(id).getAsJsonArray().add(finalRecipe); -//// var input = new JsonArray(); -//// var ingredients = sr.getIngredients().stream().toList(); -//// for (int y = 0; y < sr.getHeight(); y++) { -//// var arr = new JsonArray(); -//// for (int x = 0; x < sr.getWidth(); x++) { -//// if ((y*3)+x >= ingredients.size()) { -//// arr.add(JsonNull.INSTANCE); -//// continue; -//// } -//// var ingredient = ingredients.get((y*3)+x).getMatchingStacks(); // FIXME: fix when there are more than one matching stack -//// if (ingredient.length == 0) { -//// arr.add(JsonNull.INSTANCE); -//// } else { -//// arr.add(getRawIdFor(ingredient[0].getItem())); -//// } -//// } -//// input.add(arr); -//// } -//// var rootRecipeObject = new JsonObject(); -//// rootRecipeObject.add("inShape", input); -//// var resultObject = new JsonObject(); -//// resultObject.addProperty("id", getRawIdFor(sr.getOutput().getItem())); -//// resultObject.addProperty("count", sr.getOutput().getCount()); -//// rootRecipeObject.add("result", resultObject); -//// recipes.put(getRawIdFor(sr.getOutput().getItem()), rootRecipeObject); -// } else if (recipe instanceof ShapelessRecipe sl) { + Map recipesByResult = new TreeMap<>(); + for (RecipeType recipe : RecipeDispatcher.REGISTRY) { + ItemStack output = recipe.getOutput(); + if (output.isEmpty()) continue; + List generated; + // Subclasses (map extending) compute their real output at craft time. + if (recipe.getClass() == ShapedRecipeType.class) { + generated = generateShaped((ShapedRecipeType) recipe); + } else if (recipe.getClass() == ShapelessRecipeType.class) { + generated = generateShapeless((ShapelessRecipeType) recipe); + } else { + continue; + } + int resultId = Registries.ITEMS.getRawId(output.getItem()); + recipesByResult.computeIfAbsent(resultId, k -> new JsonArray()); + for (JsonObject json : generated) recipesByResult.get(resultId).add(json); + } + JsonObject result = new JsonObject(); + recipesByResult.forEach((id, recipes) -> result.add(id.toString(), recipes)); + return result; + } -// var ingredients = new JsonArray(); -// for (Ingredient ingredient : sl.getIngredients()) { -// if (ingredient.isEmpty()) continue; -//// ingredients.add(getRawIdFor(ingredient.getMatchingStacks()[0].getItem())); -// } -// var rootRecipeObject = new JsonObject(); -// rootRecipeObject.add("ingredients", ingredients); -// var resultObject = new JsonObject(); -// resultObject.addProperty("id", getRawIdFor(sl.getOutput().getItem())); -// resultObject.addProperty("count", sl.getOutput().getCount()); -// rootRecipeObject.add("result", resultObject); -// recipes.put(getRawIdFor(sl.getOutput().getItem()), rootRecipeObject); -// } -// } -// recipes.forEach((a, b) -> { -// if (!finalObj.has(a.toString())) { -// finalObj.add(a.toString(), new JsonArray()); -// } -// finalObj.get(a.toString()).getAsJsonArray().add(b); -// }); -// return finalObj; - return JsonNull.INSTANCE; + private static List generateShaped(ShapedRecipeType recipe) { + int width = recipe.method_14272(); + int height = recipe.method_14273(); + List> slots = new ArrayList<>(); + for (Ingredient ingredient : recipe.method_14252()) slots.add(alternativesFor(ingredient)); + List recipes = new ArrayList<>(); + for (List cells : cartesianProduct(slots)) { + // Crafting leaves each ingredient's recipe remainder in its slot (milk bucket leaves a bucket). + JsonArray inShape = new JsonArray(); + JsonArray outShape = new JsonArray(); + boolean hasRemainder = false; + for (int y = 0; y < height; y++) { + JsonArray row = new JsonArray(); + JsonArray outRow = new JsonArray(); + for (int x = 0; x < width; x++) { + Alternative cell = cells.get(y * width + x); + row.add(cell.json); + Item remainder = cell.item == null ? null : cell.item.getRecipeRemainder(); + hasRemainder |= remainder != null; + outRow.add(remainder == null ? JsonNull.INSTANCE : new JsonPrimitive(Registries.ITEMS.getRawId(remainder))); + } + inShape.add(row); + outShape.add(outRow); + } + JsonObject json = new JsonObject(); + json.add("inShape", inShape); + if (hasRemainder) json.add("outShape", outShape); + json.add("result", resultFor(recipe.getOutput())); + recipes.add(json); + } + return recipes; } - @Override - public boolean isEnabled() { - return false; // TODO: Implement this + private static List generateShapeless(ShapelessRecipeType recipe) { + List> slots = new ArrayList<>(); + for (Ingredient ingredient : recipe.method_14252()) { + List alternatives = alternativesFor(ingredient); + if (alternatives.get(0).item != null) slots.add(alternatives); + } + List recipes = new ArrayList<>(); + for (List cells : cartesianProduct(slots)) { + JsonArray ingredients = new JsonArray(); + for (Alternative cell : cells) ingredients.add(cell.json); + JsonObject json = new JsonObject(); + json.add("ingredients", ingredients); + json.add("result", resultFor(recipe.getOutput())); + recipes.add(json); + } + return recipes; + } + + private static JsonObject resultFor(ItemStack output) { + JsonObject result = new JsonObject(); + result.addProperty("id", Registries.ITEMS.getRawId(output.getItem())); + result.addProperty("metadata", output.getDamage()); + result.addProperty("count", output.getCount()); + return result; + } + + // An ingredient is a list of accepted stacks. A bare id means any metadata of that item; + // it is used when the stacks cover every variant of one item, matching how vanilla + // accepts each slot independently (mixed plank types craft a crafting table). + private static List alternativesFor(Ingredient ingredient) { + ItemStack[] stacks = ingredient.method_14244(); + List alternatives = new ArrayList<>(); + if (stacks.length == 0) { + alternatives.add(new Alternative(JsonNull.INSTANCE, null)); + return alternatives; + } + Item item = stacks[0].getItem(); + boolean sameItem = true; + Set damages = new HashSet<>(); + for (ItemStack stack : stacks) { + sameItem &= stack.getItem() == item; + damages.add(stack.getDamage()); + } + if (sameItem && (damages.contains(WILDCARD_DAMAGE) || damages.containsAll(variantsOf(item)))) { + alternatives.add(new Alternative(new JsonPrimitive(Registries.ITEMS.getRawId(item)), item)); + return alternatives; + } + for (ItemStack stack : stacks) alternatives.add(new Alternative(cellFor(stack), stack.getItem())); + return alternatives; + } + + // One accepted stack of an ingredient; item is null for an empty slot. + private static final class Alternative { + final JsonElement json; + final Item item; + + Alternative(JsonElement json, Item item) { + this.json = json; + this.item = item; + } + } + + private static JsonElement cellFor(ItemStack stack) { + int id = Registries.ITEMS.getRawId(stack.getItem()); + if (stack.getDamage() == WILDCARD_DAMAGE || variantsOf(stack.getItem()).size() <= 1 && stack.getDamage() == 0) { + return new JsonPrimitive(id); + } + JsonObject cell = new JsonObject(); + cell.addProperty("id", id); + cell.addProperty("metadata", stack.getDamage()); + return cell; + } + + // Metadata values the item exists with; an item without variants is matched by id alone. + private static Set variantsOf(Item item) { + DefaultedList stacks = DefaultedList.of(); + item.appendToItemGroup(ItemGroup.SEARCH, stacks); + Set variants = new HashSet<>(); + for (ItemStack stack : stacks) variants.add(stack.getDamage()); + return variants; + } + + private static List> cartesianProduct(List> slots) { + List> combinations = new ArrayList<>(); + combinations.add(new ArrayList<>()); + for (List alternatives : slots) { + List> next = new ArrayList<>(); + for (List prefix : combinations) { + for (T alternative : alternatives) { + List extended = new ArrayList<>(prefix); + extended.add(alternative); + next.add(extended); + } + } + combinations = next; + } + return combinations; } -// -// private static int getRawIdFor (Item item) { -// return Registry.ITEM.getRawId(item); -// } }