/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.annotations.VisibleForTesting * com.mojang.datafixers.kinds.App * com.mojang.datafixers.kinds.Applicative * com.mojang.serialization.Codec * com.mojang.serialization.DataResult * com.mojang.serialization.MapCodec * com.mojang.serialization.codecs.RecordCodecBuilder * it.unimi.dsi.fastutil.chars.CharArraySet * it.unimi.dsi.fastutil.chars.CharSet */ package net.minecraft.world.item.crafting; import com.google.common.annotations.VisibleForTesting; import com.mojang.datafixers.kinds.App; import com.mojang.datafixers.kinds.Applicative; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import it.unimi.dsi.fastutil.chars.CharArraySet; import it.unimi.dsi.fastutil.chars.CharSet; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.Util; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.CraftingInput; import net.minecraft.world.item.crafting.Ingredient; public final class ShapedRecipePattern { private static final int MAX_SIZE = 3; public static final char EMPTY_SLOT = ' '; public static final MapCodec MAP_CODEC = Data.MAP_CODEC.flatXmap(ShapedRecipePattern::unpack, pattern -> pattern.data.map(DataResult::success).orElseGet(() -> DataResult.error(() -> "Cannot encode unpacked recipe"))); public static final StreamCodec STREAM_CODEC = StreamCodec.composite(ByteBufCodecs.VAR_INT, e -> e.width, ByteBufCodecs.VAR_INT, e -> e.height, Ingredient.OPTIONAL_CONTENTS_STREAM_CODEC.apply(ByteBufCodecs.list()), e -> e.ingredients, ShapedRecipePattern::createFromNetwork); private final int width; private final int height; private final List> ingredients; private final Optional data; private final int ingredientCount; private final boolean symmetrical; public ShapedRecipePattern(int width, int height, List> ingredients, Optional data) { this.width = width; this.height = height; this.ingredients = ingredients; this.data = data; this.ingredientCount = (int)ingredients.stream().flatMap(Optional::stream).count(); this.symmetrical = Util.isSymmetrical(width, height, ingredients); } private static ShapedRecipePattern createFromNetwork(Integer width, Integer height, List> ingredients) { return new ShapedRecipePattern(width, height, ingredients, Optional.empty()); } public static ShapedRecipePattern of(Map key, String ... pattern) { return ShapedRecipePattern.of(key, List.of(pattern)); } public static ShapedRecipePattern of(Map key, List pattern) { Data data = new Data(key, pattern); return (ShapedRecipePattern)ShapedRecipePattern.unpack(data).getOrThrow(); } private static DataResult unpack(Data data) { String[] shrunkPattern = ShapedRecipePattern.shrink(data.pattern); int width = shrunkPattern[0].length(); int height = shrunkPattern.length; ArrayList> ingredients = new ArrayList>(width * height); CharArraySet unusedSymbols = new CharArraySet(data.key.keySet()); for (String line : shrunkPattern) { for (int x = 0; x < line.length(); ++x) { Optional ingredient; char symbol = line.charAt(x); if (symbol == ' ') { ingredient = Optional.empty(); } else { Ingredient ingredientForSymbol = data.key.get(Character.valueOf(symbol)); if (ingredientForSymbol == null) { return DataResult.error(() -> "Pattern references symbol '" + symbol + "' but it's not defined in the key"); } ingredient = Optional.of(ingredientForSymbol); } unusedSymbols.remove(symbol); ingredients.add(ingredient); } } if (!unusedSymbols.isEmpty()) { return DataResult.error(() -> ShapedRecipePattern.lambda$unpack$7((CharSet)unusedSymbols)); } return DataResult.success((Object)new ShapedRecipePattern(width, height, ingredients, Optional.of(data))); } @VisibleForTesting static String[] shrink(List pattern) { int left = Integer.MAX_VALUE; int right = 0; int top = 0; int bottom = 0; for (int i = 0; i < pattern.size(); ++i) { String line = pattern.get(i); left = Math.min(left, ShapedRecipePattern.firstNonEmpty(line)); int lastNonSpace = ShapedRecipePattern.lastNonEmpty(line); right = Math.max(right, lastNonSpace); if (lastNonSpace < 0) { if (top == i) { ++top; } ++bottom; continue; } bottom = 0; } if (pattern.size() == bottom) { return new String[0]; } String[] result = new String[pattern.size() - bottom - top]; for (int line = 0; line < result.length; ++line) { result[line] = pattern.get(line + top).substring(left, right + 1); } return result; } private static int firstNonEmpty(String line) { int index; for (index = 0; index < line.length() && line.charAt(index) == ' '; ++index) { } return index; } private static int lastNonEmpty(String line) { int index; for (index = line.length() - 1; index >= 0 && line.charAt(index) == ' '; --index) { } return index; } public boolean matches(CraftingInput input) { if (input.ingredientCount() != this.ingredientCount) { return false; } if (input.width() == this.width && input.height() == this.height) { if (!this.symmetrical && this.matches(input, true)) { return true; } if (this.matches(input, false)) { return true; } } return false; } private boolean matches(CraftingInput input, boolean xFlip) { for (int y = 0; y < this.height; ++y) { for (int x = 0; x < this.width; ++x) { ItemStack actual; Optional expected = xFlip ? this.ingredients.get(this.width - x - 1 + y * this.width) : this.ingredients.get(x + y * this.width); if (Ingredient.testOptionalIngredient(expected, actual = input.getItem(x, y))) continue; return false; } } return true; } public int width() { return this.width; } public int height() { return this.height; } public List> ingredients() { return this.ingredients; } private static /* synthetic */ String lambda$unpack$7(CharSet unusedSymbols) { return "Key defines symbols that aren't used in pattern: " + String.valueOf(unusedSymbols); } public record Data(Map key, List pattern) { private static final Codec> PATTERN_CODEC = Codec.STRING.listOf().comapFlatMap(strings -> { if (strings.size() > 3) { return DataResult.error(() -> "Invalid pattern: too many rows, 3 is maximum"); } if (strings.isEmpty()) { return DataResult.error(() -> "Invalid pattern: empty pattern not allowed"); } int firstLength = ((String)strings.getFirst()).length(); for (String line : strings) { if (line.length() > 3) { return DataResult.error(() -> "Invalid pattern: too many columns, 3 is maximum"); } if (firstLength == line.length()) continue; return DataResult.error(() -> "Invalid pattern: each row must be the same width"); } return DataResult.success((Object)strings); }, Function.identity()); private static final Codec SYMBOL_CODEC = Codec.STRING.comapFlatMap(symbol -> { if (symbol.length() != 1) { return DataResult.error(() -> "Invalid key entry: '" + symbol + "' is an invalid symbol (must be 1 character only)."); } if (" ".equals(symbol)) { return DataResult.error(() -> "Invalid key entry: ' ' is a reserved symbol."); } return DataResult.success((Object)Character.valueOf(symbol.charAt(0))); }, String::valueOf); public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec(i -> i.group((App)ExtraCodecs.strictUnboundedMap(SYMBOL_CODEC, Ingredient.CODEC).fieldOf("key").forGetter(d -> d.key), (App)PATTERN_CODEC.fieldOf("pattern").forGetter(d -> d.pattern)).apply((Applicative)i, Data::new)); } }