diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/AbstractShapelessPredicatedRecipe.java b/src/main/java/com/minelittlepony/unicopia/recipe/AbstractShapelessPredicatedRecipe.java deleted file mode 100644 index 5b8610d9..00000000 --- a/src/main/java/com/minelittlepony/unicopia/recipe/AbstractShapelessPredicatedRecipe.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.minelittlepony.unicopia.recipe; - -import java.util.List; -import com.google.common.collect.Lists; -import net.minecraft.inventory.Inventory; -import net.minecraft.item.ItemStack; -import net.minecraft.recipe.Recipe; -import net.minecraft.util.DefaultedList; -import net.minecraft.util.Identifier; -import net.minecraft.world.World; - -/** - * Basic crafting recipe that uses our custom ingredients. - */ -public abstract class AbstractShapelessPredicatedRecipe implements Recipe { - - protected final Ingredient output; - - protected final DefaultedList ingredients; - - private final Identifier id; - - public AbstractShapelessPredicatedRecipe(Identifier id, Ingredient output, DefaultedList ingredients) { - this.id = id; - this.output = output; - this.ingredients = ingredients; - } - - protected abstract int getInputMultiplier(C inv, World worldIn); - - @Override - public boolean matches(C inv, World worldIn) { - int materialMult = getInputMultiplier(inv, worldIn); - - if (materialMult == 0) { - return false; - } - - List toMatch = Lists.newArrayList(ingredients); - - for (int i = 0; i < inv.getInvSize(); i++) { - ItemStack stack = inv.getInvStack(i); - - if (!stack.isEmpty()) { - if (toMatch.isEmpty() || !removeMatch(toMatch, stack, materialMult)) { - return false; - } - } - } - - return toMatch.isEmpty(); - } - - private boolean removeMatch(List toMatch, ItemStack stack, int materialMult) { - return toMatch.stream() - .filter(s -> s.matches(stack, materialMult)) - .findFirst() - .filter(toMatch::remove) - .isPresent(); - } - - @Override - public Identifier getId() { - return id; - } - - @Override - public ItemStack craft(C inv) { - return getOutput(); - } - - @Override - public boolean fits(int width, int height) { - return width * height < ingredients.size(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/Pattern.java b/src/main/java/com/minelittlepony/unicopia/recipe/Pattern.java new file mode 100644 index 00000000..2b119e22 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/recipe/Pattern.java @@ -0,0 +1,220 @@ +package com.minelittlepony.unicopia.recipe; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Sets; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.minelittlepony.unicopia.recipe.ingredient.Ingredient; + +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.util.DefaultedList; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.Util; + +public class Pattern { + + public static Pattern read(PacketByteBuf buf) { + return new Pattern( + Utils.read(buf, Ingredient.EMPTY, Ingredient::read), + buf.readVarInt(), + buf.readVarInt() + ); + } + + public static Pattern read(JsonObject json) { + String[] patterns = combinePatternMatrix(getPattern(JsonHelper.getArray(json, "pattern"))); + Map ingredients = readIngredients(JsonHelper.getObject(json, "key")); + + return new Pattern(patterns, ingredients); + } + + public final DefaultedList matrix; + + public final int width; + public final int height; + + public Pattern(DefaultedList matrix, int width, int height) { + this.matrix = matrix; + this.width = width; + this.height = height; + } + + public Pattern(String[] pattern, Map ingredients) { + this(pattern, ingredients, pattern[0].length(), pattern.length); + } + + private Pattern(String[] pattern, Map ingredients, int width, int height) { + this(buildIngredientMatrix(pattern, ingredients, width, height), width, height); + } + + public boolean matches(CraftingInventory inv) { + for(int x = 0; x <= inv.getWidth() - width; x++) { + for(int y = 0; y <= inv.getHeight() - height; y++) { + if (matchesSmall(inv, x, y, true) || matchesSmall(inv, x, y, false)) { + return true; + } + } + } + + return false; + } + + public void write(PacketByteBuf buf) { + Utils.write(buf, matrix, Ingredient::write); + buf.writeVarInt(width); + buf.writeVarInt(height); + } + + public int size() { + return matrix.size(); + } + + private boolean matchesSmall(CraftingInventory inv, int offsetX, int offsetY, boolean reflected) { + for(int x = 0; x < inv.getWidth(); ++x) { + for(int y = 0; y < inv.getHeight(); ++y) { + int k = x - offsetX; + int l = y - offsetY; + Ingredient ingredient = Ingredient.EMPTY; + if (k >= 0 && l >= 0 && k < width && l < this.height) { + if (reflected) { + ingredient = matrix.get(width - k - 1 + l * this.width); + } else { + ingredient = matrix.get(k + l * this.width); + } + } + + if (!ingredient.matches(inv.getInvStack(x + y * inv.getWidth()), 1)) { + return false; + } + } + } + + return true; + } + + static DefaultedList buildIngredientMatrix(String[] pattern, Map ingredients, int width, int height) { + DefaultedList result = DefaultedList.ofSize(width * height, Ingredient.EMPTY); + + Set unsolved = Sets.newHashSet(ingredients.keySet()); + unsolved.remove(" "); + + for(int i = 0; i < pattern.length; ++i) { + for(int j = 0; j < pattern[i].length(); ++j) { + String key = pattern[i].substring(j, j + 1); + + Ingredient ingredient = ingredients.get(key); + + if (ingredient == null) { + throw new JsonSyntaxException("Pattern references symbol '" + key + "' but it's not defined in the key"); + } + + unsolved.remove(key); + result.set(j + width * i, ingredient); + } + } + + if (!unsolved.isEmpty()) { + throw new JsonSyntaxException("Key defines symbols that aren't used in pattern: " + unsolved); + } + + return result; + } + + static String[] combinePatternMatrix(String... lines) { + int i = Integer.MAX_VALUE; + int j = 0; + int k = 0; + int l = 0; + + for(int row = 0; row < lines.length; ++row) { + String line = lines[row]; + + i = Math.min(i, lookAhead(line)); + + int n = lookBack(line); + + j = Math.max(j, n); + if (n < 0) { + if (k == row) { + ++k; + } + + ++l; + } else { + l = 0; + } + } + + if (lines.length == l) { + return new String[0]; + } + + String[] strings = new String[lines.length - l - k]; + + for(int o = 0; o < strings.length; ++o) { + strings[o] = lines[o + k].substring(i, j + 1); + } + + return strings; + } + + private static int lookAhead(String pattern) { + int i; + for(i = 0; i < pattern.length() && pattern.charAt(i) == ' '; ++i) {} + + return i; + } + + private static int lookBack(String pattern) { + int i; + for(i = pattern.length() - 1; i >= 0 && pattern.charAt(i) == ' '; --i) {} + + return i; + } + + static String[] getPattern(JsonArray json) { + String[] strings = new String[json.size()]; + if (strings.length > 3) { + throw new JsonSyntaxException("Invalid pattern: too many rows, 3 is maximum"); + } else if (strings.length == 0) { + throw new JsonSyntaxException("Invalid pattern: empty pattern not allowed"); + } else { + for(int i = 0; i < strings.length; ++i) { + String string = JsonHelper.asString(json.get(i), "pattern[" + i + "]"); + if (string.length() > 3) { + throw new JsonSyntaxException("Invalid pattern: too many columns, 3 is maximum"); + } + + if (i > 0 && strings[0].length() != string.length()) { + throw new JsonSyntaxException("Invalid pattern: each row must be the same width"); + } + + strings[i] = string; + } + + return strings; + } + } + + static Map readIngredients(JsonObject json) { + return Util.make(json.entrySet().stream().collect(Collectors.toMap(e -> { + if (e.getKey().length() != 1) { + throw new JsonSyntaxException("Invalid key entry: '" + e.getKey() + "' is an invalid symbol (must be 1 character only)."); + } + + if (" ".equals(e.getKey())) { + throw new JsonSyntaxException("Invalid key entry: ' ' is a reserved symbol."); + } + + return e.getKey(); + }, + e -> Ingredient.one(e.getValue()) + )), m -> m.put(" ", Ingredient.EMPTY)); + } + +} diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/ShapedSpecialRecipe.java b/src/main/java/com/minelittlepony/unicopia/recipe/ShapedSpecialRecipe.java new file mode 100644 index 00000000..78b310f0 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ShapedSpecialRecipe.java @@ -0,0 +1,99 @@ +package com.minelittlepony.unicopia.recipe; + +import java.util.Random; + +import com.google.gson.JsonObject; +import com.minelittlepony.unicopia.recipe.ingredient.Ingredient; + +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.SpecialCraftingRecipe; +import net.minecraft.util.DefaultedList; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.world.World; + +public class ShapedSpecialRecipe extends SpecialCraftingRecipe { + + private static final Random RANDOM = new Random(); + + private final Pattern pattern; + private final Ingredient output; + private final String group; + + public ShapedSpecialRecipe(Identifier id, String group, Pattern pattern, Ingredient output) { + super(id); + this.group = group; + this.pattern = pattern; + this.output = output; + } + + @Override + public boolean matches(CraftingInventory inv, World world) { + return pattern.matches(inv); + } + + @Override + public ItemStack craft(CraftingInventory inv) { + return getOutput().copy(); + } + + @Override + public boolean fits(int width, int height) { + return width * height >= pattern.size(); + } + + @Override + public ItemStack getOutput() { + return output.getStack(RANDOM); + } + + @Override + public String getGroup() { + return this.group; + } + + @Override + public RecipeSerializer getSerializer() { + return null; + } + + @Override + public boolean isIgnoredInRecipeBook() { + return false; + } + + @Override + public DefaultedList getPreviewInputs() { + return Ingredient.preview(pattern.matrix); + } + + public static class Serializer implements RecipeSerializer { + @Override + public ShapedSpecialRecipe read(Identifier id, JsonObject json) { + return new ShapedSpecialRecipe(id, + JsonHelper.getString(json, "group", ""), + Pattern.read(json), + Ingredient.one(json.get("result")) + ); + } + + @Override + public ShapedSpecialRecipe read(Identifier id, PacketByteBuf buf) { + return new ShapedSpecialRecipe(id, + buf.readString(32767), + Pattern.read(buf), + Ingredient.read(buf) + ); + } + + @Override + public void write(PacketByteBuf buf, ShapedSpecialRecipe recipe) { + buf.writeString(recipe.group); + recipe.pattern.write(buf); + recipe.output.write(buf); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/ShapelessSpecialRecipe.java b/src/main/java/com/minelittlepony/unicopia/recipe/ShapelessSpecialRecipe.java new file mode 100644 index 00000000..49b1d86d --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ShapelessSpecialRecipe.java @@ -0,0 +1,104 @@ +package com.minelittlepony.unicopia.recipe; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.minelittlepony.unicopia.recipe.ingredient.Ingredient; + +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.SpecialCraftingRecipe; +import net.minecraft.util.DefaultedList; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.world.World; + +public class ShapelessSpecialRecipe extends SpecialCraftingRecipe { + + private final String group; + private final Ingredient output; + + private final DefaultedList input; + + public ShapelessSpecialRecipe(Identifier id, String group, Ingredient output, DefaultedList input) { + super(id); + this.group = group; + this.output = output; + this.input = input; + + if (input.isEmpty()) { + throw new JsonParseException("No ingredients for shapeless recipe"); + } + if (input.size() > 9) { + throw new JsonParseException("Too many ingredients for shapeless recipe"); + } + } + + @Override + public boolean matches(CraftingInventory inv, World world) { + return Utils.matchShapeless(input, inv, 1); + } + + @Override + public ItemStack craft(CraftingInventory inv) { + return getOutput().copy(); + } + + @Override + public boolean fits(int width, int height) { + return width * height >= input.size(); + } + + @Override + public ItemStack getOutput() { + return output.getStack(Utils.RANDOM); + } + + @Override + public String getGroup() { + return group; + } + + @Override + public RecipeSerializer getSerializer() { + return URecipes.CRAFTING_SHAPELESS; + } + + @Override + public DefaultedList getPreviewInputs() { + return Ingredient.preview(input); + } + + @Override + public boolean isIgnoredInRecipeBook() { + return false; + } + + public static class Serializer implements RecipeSerializer { + @Override + public ShapelessSpecialRecipe read(Identifier identifier, JsonObject jsonObject) { + return new ShapelessSpecialRecipe(identifier, + JsonHelper.getString(jsonObject, "group", ""), + Ingredient.one(jsonObject.get("output")), + Ingredient.many(JsonHelper.getArray(jsonObject, "ingredients")) + ); + } + + @Override + public ShapelessSpecialRecipe read(Identifier identifier, PacketByteBuf buf) { + return new ShapelessSpecialRecipe(identifier, + buf.readString(32767), + Ingredient.read(buf), + Utils.read(buf, Ingredient.EMPTY, Ingredient::read)); + } + + @Override + public void write(PacketByteBuf buf, ShapelessSpecialRecipe recipe) { + buf.writeString(recipe.group); + recipe.output.write(buf); + buf.writeVarInt(recipe.input.size()); + Utils.write(buf, recipe.input, Ingredient::write); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/SpellBookRecipe.java b/src/main/java/com/minelittlepony/unicopia/recipe/SpellBookRecipe.java index 08f16333..0df25635 100644 --- a/src/main/java/com/minelittlepony/unicopia/recipe/SpellBookRecipe.java +++ b/src/main/java/com/minelittlepony/unicopia/recipe/SpellBookRecipe.java @@ -1,52 +1,80 @@ package com.minelittlepony.unicopia.recipe; -import java.util.Random; - import com.google.gson.JsonObject; import com.minelittlepony.unicopia.container.SpellBookInventory; import com.minelittlepony.unicopia.item.UItems; +import com.minelittlepony.unicopia.recipe.ingredient.Ingredient; import net.minecraft.inventory.CraftingInventory; import net.minecraft.item.ItemStack; +import net.minecraft.recipe.Recipe; import net.minecraft.recipe.RecipeSerializer; import net.minecraft.recipe.RecipeType; import net.minecraft.util.DefaultedList; import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; import net.minecraft.util.PacketByteBuf; import net.minecraft.world.World; /** * Spellbook recipe accepting an item to enchant, a number of ingredients to use, an ingredient to compose the output. */ -public class SpellBookRecipe extends AbstractShapelessPredicatedRecipe { +public class SpellBookRecipe implements Recipe { - private static final Random RANDOM = new Random(); + private final Ingredient input; - protected final Ingredient input; + private final Ingredient output; + + private final DefaultedList ingredients; + + private final Identifier id; public SpellBookRecipe(Identifier id, Ingredient input, Ingredient output, DefaultedList ingredients) { - super(id, output, ingredients); + this.id = id; + this.output = output; + this.ingredients = ingredients; this.input = input; } @Override - protected int getInputMultiplier(CraftingInventory inv, World worldIn) { + public boolean matches(CraftingInventory inv, World world) { + ItemStack enchantedStack = ((SpellBookInventory)inv).getCraftResultMatrix().getInvStack(0); if (enchantedStack.isEmpty() || enchantedStack.getItem() == null) { - return 0; + return false; } if (!input.matches(enchantedStack, enchantedStack.getCount())) { - return 0; + return false; } - return enchantedStack.getCount(); + return Utils.matchShapeless(ingredients, inv, enchantedStack.getCount()); + } + + @Override + public Identifier getId() { + return id; + } + + @Override + public ItemStack craft(CraftingInventory inv) { + return getOutput(); + } + + @Override + public boolean fits(int width, int height) { + return width * height < ingredients.size(); + } + + @Override + public DefaultedList getPreviewInputs() { + return Ingredient.preview(ingredients, DefaultedList.copyOf(null, input.getPreview())); } @Override public ItemStack getOutput() { - return output.getStack(RANDOM); + return output.getStack(Utils.RANDOM); } @Override @@ -70,30 +98,24 @@ public class SpellBookRecipe extends AbstractShapelessPredicatedRecipe ingredients = DefaultedList.copyOf(Ingredient.EMPTY); - while (ingredients.size() < length) { - ingredients.add(Ingredient.read(buf)); - } - - return new SpellBookRecipe(id, input, output, ingredients); + return new SpellBookRecipe(id, + Ingredient.read(buf), + Ingredient.read(buf), + Utils.read(buf, Ingredient.EMPTY, Ingredient::read) + ); } @Override public void write(PacketByteBuf buf, SpellBookRecipe recipe) { recipe.input.write(buf); recipe.output.write(buf); - DefaultedList ingredients = recipe.ingredients; - buf.writeInt(ingredients.size()); - ingredients.forEach(i -> i.write(buf)); + Utils.write(buf, recipe.ingredients, Ingredient::write); } } } \ No newline at end of file diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/URecipes.java b/src/main/java/com/minelittlepony/unicopia/recipe/URecipes.java index 635f7928..c1c1ef01 100644 --- a/src/main/java/com/minelittlepony/unicopia/recipe/URecipes.java +++ b/src/main/java/com/minelittlepony/unicopia/recipe/URecipes.java @@ -10,7 +10,8 @@ public interface URecipes { RecipeType SPELL_BOOK = register("spell_book"); RecipeSerializer ENCHANTING_SPELL_SERIALIZER = register("enchanting_spell", new SpellBookRecipe.Serializer()); - RecipeSerializer CRAFTING_SPELL_SERIALIZER = register("crafting_spell", new SpellBookRecipe.Serializer()); + RecipeSerializer CRAFTING_SHAPELESS = register("crafting_shapeless", new ShapelessSpecialRecipe.Serializer()); + RecipeSerializer CRAFTING_SHAPED = register("crafting_shaped", new ShapedSpecialRecipe.Serializer()); static > RecipeType register(final String id) { return Registry.register(Registry.RECIPE_TYPE, new Identifier("unicopia", id), new RecipeType() { diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/Utils.java b/src/main/java/com/minelittlepony/unicopia/recipe/Utils.java new file mode 100644 index 00000000..4e2a0922 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/recipe/Utils.java @@ -0,0 +1,62 @@ +package com.minelittlepony.unicopia.recipe; + +import java.util.List; +import java.util.Random; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import com.google.common.collect.Lists; +import com.minelittlepony.unicopia.recipe.ingredient.Ingredient; + +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.DefaultedList; +import net.minecraft.util.PacketByteBuf; + +public class Utils { + + static final Random RANDOM = new Random(); + + static DefaultedList read(PacketByteBuf buf, T def, Function reader) { + DefaultedList list = DefaultedList.ofSize(buf.readInt(), def); + for (int i = 0; i < list.size(); i++) { + list.set(i, reader.apply(buf)); + } + return list; + } + + static void write(PacketByteBuf buf, DefaultedList list, BiConsumer writer) { + buf.writeInt(list.size()); + list.forEach(i -> writer.accept(i, buf)); + } + + + public static boolean matchShapeless(DefaultedList ingredients, C inv, int mult) { + if (mult == 0) { + return false; + } + + List toMatch = Lists.newArrayList(ingredients); + + for (int i = 0; i < inv.getInvSize(); i++) { + ItemStack stack = inv.getInvStack(i); + + if (!stack.isEmpty()) { + if (toMatch.isEmpty() || !removeMatch(toMatch, stack, mult)) { + return false; + } + } + } + + return toMatch.isEmpty(); + } + + private static boolean removeMatch(List toMatch, ItemStack stack, int materialMult) { + return toMatch.stream() + .filter(s -> s.matches(stack, materialMult)) + .findFirst() + .filter(toMatch::remove) + .isPresent(); + } + +} diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/ChoicePredicate.java b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/ChoicePredicate.java similarity index 85% rename from src/main/java/com/minelittlepony/unicopia/recipe/ChoicePredicate.java rename to src/main/java/com/minelittlepony/unicopia/recipe/ingredient/ChoicePredicate.java index 26b91647..ecbe95d4 100644 --- a/src/main/java/com/minelittlepony/unicopia/recipe/ChoicePredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/ChoicePredicate.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.recipe; +package com.minelittlepony.unicopia.recipe.ingredient; import java.util.List; import java.util.Random; @@ -8,8 +8,11 @@ import com.google.gson.JsonArray; import net.minecraft.item.ItemStack; import net.minecraft.util.PacketByteBuf; +/** + * Requires only one of the sub-ingredients to match when matching. + * Makes a random choice from a pool of alternatives when crafting. + */ class ChoicePredicate implements Ingredient.Predicate { - static Ingredient.Predicate read(JsonArray arr) { return new ChoicePredicate(Ingredient.many(arr)); } diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/DamagePredicate.java b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/DamagePredicate.java new file mode 100644 index 00000000..d10bbd35 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/DamagePredicate.java @@ -0,0 +1,84 @@ +package com.minelittlepony.unicopia.recipe.ingredient; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.function.BiPredicate; +import java.util.stream.Stream; + +import com.google.gson.JsonObject; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.Lazy; +import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.Util; + +/** + * Tests for whether a tag contains the input when matching. + * Supplies a random item from the tag as the output when crafting. + */ +class DamagePredicate implements Ingredient.Predicate { + private static final Map> OPERATIONS = Util.make(new HashMap<>(), map -> { + map.put("==", (a, b) -> a == b); + map.put("!=", (a, b) -> a != b); + map.put("<", (a, b) -> a < b); + map.put("<=", (a, b) -> a <= b); + map.put(">", (a, b) -> a > b); + map.put("<=", (a, b) -> a <= b); + }); + + static Ingredient.Predicate read(PacketByteBuf buf) { + int count = buf.readInt(); + if (count == 0) { + return EMPTY; + } + return new DamagePredicate(buf.readInt(), buf.readString(32767)); + } + + static Ingredient.Predicate read(JsonObject json) { + if (!json.has("damage")) { + return EMPTY; + } + + JsonObject o = JsonHelper.getObject(json, "damage"); + return new DamagePredicate( + JsonHelper.getInt(o, "damage"), + JsonHelper.getString(o, "op") + ); + } + + private final String op; + private final int damage; + + private final Lazy> operation; + + DamagePredicate(int damage, String op) { + this.op = op.trim(); + this.damage = damage; + this.operation = new Lazy<>(() -> OPERATIONS.get(op)); + } + + @Override + public ItemStack applyModifiers(ItemStack output, Random random) { + output.setDamage(damage); + return output; + } + + @Override + public Stream getMatchingStacks() { + return Stream.empty(); + } + + @Override + public boolean matches(ItemStack other, int materialMult) { + return !other.isEmpty() && operation.get().test(other.getDamage(), damage); + } + + @Override + public void write(PacketByteBuf buf) { + buf.writeInt(1); + buf.writeInt(damage); + buf.writeString(op); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/EnchantmentPredicate.java b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/EnchantmentPredicate.java similarity index 94% rename from src/main/java/com/minelittlepony/unicopia/recipe/EnchantmentPredicate.java rename to src/main/java/com/minelittlepony/unicopia/recipe/ingredient/EnchantmentPredicate.java index 7cc65184..f2c4aa48 100644 --- a/src/main/java/com/minelittlepony/unicopia/recipe/EnchantmentPredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/EnchantmentPredicate.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.recipe; +package com.minelittlepony.unicopia.recipe.ingredient; import java.util.Random; import java.util.stream.Stream; @@ -15,10 +15,10 @@ import net.minecraft.util.PacketByteBuf; import net.minecraft.util.registry.Registry; /** - * A predicate that tests for a specific enchantment on an input item. + * A predicate that tests for a specific enchantment on an input when matching. + * Appends that enchantment to the output when crafting. */ class EnchantmentPredicate implements Ingredient.Predicate { - public static Ingredient.Predicate read(PacketByteBuf buf) { int level = buf.readInt(); if (level == 0) { diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/Ingredient.java b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/Ingredient.java similarity index 83% rename from src/main/java/com/minelittlepony/unicopia/recipe/Ingredient.java rename to src/main/java/com/minelittlepony/unicopia/recipe/ingredient/Ingredient.java index b6a85576..ffed6241 100644 --- a/src/main/java/com/minelittlepony/unicopia/recipe/Ingredient.java +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/Ingredient.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.recipe; +package com.minelittlepony.unicopia.recipe.ingredient; import java.util.List; import java.util.Random; @@ -18,7 +18,7 @@ import net.minecraft.util.Lazy; import net.minecraft.util.PacketByteBuf; @Immutable -class Ingredient { +public class Ingredient { public static final Ingredient EMPTY = new Ingredient(Predicate.EMPTY, Predicate.EMPTY, Predicate.EMPTY, Predicate.EMPTY); private final Predicate stack; @@ -27,6 +27,7 @@ class Ingredient { private final Predicate enchantment; private final Lazy> matchingStacks; + private final Lazy preview; Ingredient(Predicate stack, Predicate tag, Predicate spell, Predicate enchantment) { this.stack = stack; @@ -42,12 +43,19 @@ class Ingredient { ).filter(s -> matches(s, 1)) .collect(Collectors.toList()); }); + this.preview = new Lazy<>(() -> { + return net.minecraft.recipe.Ingredient.ofStacks(getMatchingStacks().toArray(ItemStack[]::new)); + }); } public Stream getMatchingStacks() { return matchingStacks.get().stream(); } + public net.minecraft.recipe.Ingredient getPreview() { + return preview.get(); + } + public ItemStack getStack(Random random) { ItemStack output = ItemStack.EMPTY.copy(); stack.applyModifiers(output, random); @@ -64,7 +72,6 @@ class Ingredient { && enchantment.matches(other, materialMult); } - public void write(PacketByteBuf buf) { stack.write(buf); tag.write(buf); @@ -110,6 +117,15 @@ class Ingredient { return ingredients; } + public static DefaultedList preview(DefaultedList input) { + return preview(input, DefaultedList.of()); + } + + public static DefaultedList preview(DefaultedList input, DefaultedList output) { + input.stream().map(Ingredient::getPreview).forEach(output::add); + return output; + } + public interface Predicate { Predicate EMPTY = new Predicate() { @Override diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/SpellPredicate.java b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/SpellPredicate.java similarity index 87% rename from src/main/java/com/minelittlepony/unicopia/recipe/SpellPredicate.java rename to src/main/java/com/minelittlepony/unicopia/recipe/ingredient/SpellPredicate.java index cab4038b..a378c501 100644 --- a/src/main/java/com/minelittlepony/unicopia/recipe/SpellPredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/SpellPredicate.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.recipe; +package com.minelittlepony.unicopia.recipe.ingredient; import java.util.Random; import java.util.stream.Stream; @@ -9,6 +9,10 @@ import com.minelittlepony.unicopia.magic.spell.SpellRegistry; import net.minecraft.item.ItemStack; import net.minecraft.util.PacketByteBuf; +/** + * A predicate that tests for a specific spell on an input when matching. + * Appends that spell to the output when crafting. + */ class SpellPredicate implements Ingredient.Predicate { static Ingredient.Predicate read(PacketByteBuf buf) { int level = buf.readInt(); diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/StackPredicate.java b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/StackPredicate.java similarity index 92% rename from src/main/java/com/minelittlepony/unicopia/recipe/StackPredicate.java rename to src/main/java/com/minelittlepony/unicopia/recipe/ingredient/StackPredicate.java index 693185a1..ba84086c 100644 --- a/src/main/java/com/minelittlepony/unicopia/recipe/StackPredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/StackPredicate.java @@ -1,10 +1,8 @@ -package com.minelittlepony.unicopia.recipe; +package com.minelittlepony.unicopia.recipe.ingredient; -import java.util.Optional; import java.util.Random; import java.util.stream.Stream; -import com.google.common.collect.Streams; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException; @@ -17,6 +15,10 @@ import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; import net.minecraft.util.registry.Registry; +/** + * Tests for a specific item, stack size, and damage value when matching. + * Presents that item as the output when crafting. + */ class StackPredicate implements Ingredient.Predicate { static Ingredient.Predicate read(PacketByteBuf buf) { int count = buf.readInt(); @@ -81,7 +83,7 @@ class StackPredicate implements Ingredient.Predicate { return subItems.stream(); } - return Streams.stream(Optional.ofNullable(stack)); + return Stream.of(stack); } @Override diff --git a/src/main/java/com/minelittlepony/unicopia/recipe/TagPredicate.java b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/TagPredicate.java similarity index 92% rename from src/main/java/com/minelittlepony/unicopia/recipe/TagPredicate.java rename to src/main/java/com/minelittlepony/unicopia/recipe/ingredient/TagPredicate.java index ac7552e1..318245c1 100644 --- a/src/main/java/com/minelittlepony/unicopia/recipe/TagPredicate.java +++ b/src/main/java/com/minelittlepony/unicopia/recipe/ingredient/TagPredicate.java @@ -1,4 +1,4 @@ -package com.minelittlepony.unicopia.recipe; +package com.minelittlepony.unicopia.recipe.ingredient; import java.util.Random; import java.util.stream.Stream; @@ -13,6 +13,10 @@ import net.minecraft.tag.Tag; import net.minecraft.util.Identifier; import net.minecraft.util.PacketByteBuf; +/** + * Tests for whether a tag contains the input when matching. + * Supplies a random item from the tag as the output when crafting. + */ class TagPredicate implements Ingredient.Predicate { static Ingredient.Predicate read(PacketByteBuf buf) { int count = buf.readInt();