From d3f902ae34a1cd724f4dfe61595748052a51c12b Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 22 Apr 2024 22:49:46 -0400 Subject: [PATCH] Banners --- .../geyser/inventory/item/BannerPattern.java | 104 +++++++++++++ .../geyser/inventory/item/DyeColor.java | 75 +++++++++ .../geysermc/geyser/item/type/BannerItem.java | 145 ++++++++++++------ .../geyser/session/cache/RegistryCache.java | 4 + .../inventory/LoomInventoryTranslator.java | 27 +--- .../entity/BannerBlockEntityTranslator.java | 4 +- .../bedrock/BedrockBookEditTranslator.java | 1 + 7 files changed, 295 insertions(+), 65 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/inventory/item/BannerPattern.java create mode 100644 core/src/main/java/org/geysermc/geyser/inventory/item/DyeColor.java diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/BannerPattern.java b/core/src/main/java/org/geysermc/geyser/inventory/item/BannerPattern.java new file mode 100644 index 000000000..442690d7d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/BannerPattern.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.item; + +import lombok.Getter; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Locale; + +@Getter +public enum BannerPattern { + BASE("b"), + SQUARE_BOTTOM_LEFT("bl"), + SQUARE_BOTTOM_RIGHT("br"), + SQUARE_TOP_LEFT("tl"), + SQUARE_TOP_RIGHT("tr"), + STRIPE_BOTTOM("bs"), + STRIPE_TOP("ts"), + STRIPE_LEFT("ls"), + STRIPE_RIGHT("rs"), + STRIPE_CENTER("cs"), + STRIPE_MIDDLE("ms"), + STRIPE_DOWNRIGHT("drs"), + STRIPE_DOWNLEFT("dls"), + SMALL_STRIPES("ss"), + CROSS("cr"), + STRAIGHT_CROSS("sc"), + TRIANGLE_BOTTOM("bt"), + TRIANGLE_TOP("tt"), + TRIANGLES_BOTTOM("bts"), + TRIANGLES_TOP("tts"), + DIAGONAL_LEFT("ld"), + DIAGONAL_UP_RIGHT("rd"), + DIAGONAL_UP_LEFT("lud"), + DIAGONAL_RIGHT("rud"), + CIRCLE("mc"), + RHOMBUS("mr"), + HALF_VERTICAL("vh"), + HALF_HORIZONTAL("hh"), + HALF_VERTICAL_RIGHT("vhr"), + HALF_HORIZONTAL_BOTTOM("hhb"), + BORDER("bo"), + CURLY_BORDER("cbo"), + GRADIENT("gra"), + GRADIENT_UP("gru"), + BRICKS("bri"), + GLOBE("glb"), + CREEPER("cre"), + SKULL("sku"), + FLOWER("flo"), + MOJANG("moj"), + PIGLIN("pig"); + + private static final BannerPattern[] VALUES = values(); + + private final String javaIdentifier; + private final String bedrockIdentifier; + + BannerPattern(String bedrockIdentifier) { + this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ROOT); + this.bedrockIdentifier = bedrockIdentifier; + } + + public static @Nullable BannerPattern getByJavaIdentifier(String javaIdentifier) { + for (BannerPattern bannerPattern : VALUES) { + if (bannerPattern.javaIdentifier.equals(javaIdentifier)) { + return bannerPattern; + } + } + return null; + } + + public static @Nullable BannerPattern getByBedrockIdentifier(String bedrockIdentifier) { + for (BannerPattern bannerPattern : VALUES) { + if (bannerPattern.bedrockIdentifier.equals(bedrockIdentifier)) { + return bannerPattern; + } + } + return null; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/DyeColor.java b/core/src/main/java/org/geysermc/geyser/inventory/item/DyeColor.java new file mode 100644 index 000000000..e2649a343 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/DyeColor.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.inventory.item; + +import lombok.Getter; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Locale; + +@Getter +public enum DyeColor { + WHITE, + ORANGE, + MAGENTA, + LIGHT_BLUE, + YELLOW, + LIME, + PINK, + GRAY, + LIGHT_GRAY, + CYAN, + PURPLE, + BLUE, + BROWN, + GREEN, + RED, + BLACK; + + private static final DyeColor[] VALUES = values(); + + private final String javaIdentifier; + + DyeColor() { + this.javaIdentifier = this.name().toLowerCase(Locale.ROOT); + } + + public static @Nullable DyeColor getById(int id) { + if (id >= 0 && id < VALUES.length) { + return VALUES[id]; + } + return null; + } + + public static @Nullable DyeColor getByJavaIdentifier(String javaIdentifier) { + for (DyeColor dyeColor : VALUES) { + if (dyeColor.javaIdentifier.equals(javaIdentifier)) { + return dyeColor; + } + } + return null; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java b/core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java index 32806c3c2..0d8050dec 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/BannerItem.java @@ -28,14 +28,14 @@ package org.geysermc.geyser.item.type; import com.github.steveice10.mc.protocol.data.game.item.component.BannerPatternLayer; import com.github.steveice10.mc.protocol.data.game.item.component.DataComponentType; import com.github.steveice10.mc.protocol.data.game.item.component.DataComponents; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.IntTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; -import com.github.steveice10.opennbt.tag.builtin.Tag; +import com.github.steveice10.opennbt.tag.builtin.*; +import it.unimi.dsi.fastutil.Pair; import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.nbt.NbtList; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; +import org.geysermc.geyser.inventory.item.BannerPattern; +import org.geysermc.geyser.inventory.item.DyeColor; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.item.BedrockItemBuilder; @@ -51,20 +51,48 @@ public class BannerItem extends BlockItem { * ominous banners that we set instead. This variable is used to detect Java ominous banner patterns, and apply * the correct ominous banner pattern if Bedrock pulls the item from creative. */ - public static final List OMINOUS_BANNER_PATTERN; + private static final List> OMINOUS_BANNER_PATTERN; + private static final ListTag OMINOUS_BANNER_PATTERN_BLOCK; static { // Construct what an ominous banner is supposed to look like OMINOUS_BANNER_PATTERN = List.of( -// new BannerPatternLayer("mr", 9), -// new BannerPatternLayer("bs", 8), -// new BannerPatternLayer("cs", 7), -// new BannerPatternLayer("bo", 8), -// new BannerPatternLayer("ms", 15), -// new BannerPatternLayer("hh", 8), -// new BannerPatternLayer("mc", 8), -// new BannerPatternLayer("bo", 15) + Pair.of(BannerPattern.RHOMBUS, DyeColor.CYAN), + Pair.of(BannerPattern.STRIPE_BOTTOM, DyeColor.LIGHT_GRAY), + Pair.of(BannerPattern.STRIPE_CENTER, DyeColor.GRAY), + Pair.of(BannerPattern.BORDER, DyeColor.LIGHT_GRAY), + Pair.of(BannerPattern.STRIPE_MIDDLE, DyeColor.BLACK), + Pair.of(BannerPattern.HALF_HORIZONTAL, DyeColor.LIGHT_GRAY), + Pair.of(BannerPattern.CIRCLE, DyeColor.LIGHT_GRAY), + Pair.of(BannerPattern.BORDER, DyeColor.BLACK) ); + + OMINOUS_BANNER_PATTERN_BLOCK = new ListTag("patterns"); + for (Pair pair : OMINOUS_BANNER_PATTERN) { + OMINOUS_BANNER_PATTERN_BLOCK.add(getJavaBannerPatternTag(pair.left(), pair.right())); + } + } + + public static boolean isOminous(GeyserSession session, List patternLayers) { + if (OMINOUS_BANNER_PATTERN.size() != patternLayers.size()) { + return false; + } + for (int i = 0; i < OMINOUS_BANNER_PATTERN.size(); i++) { + BannerPatternLayer patternLayer = patternLayers.get(i); + Pair pair = OMINOUS_BANNER_PATTERN.get(i); + if (!patternLayer.getPattern().isId() || patternLayer.getColorId() != pair.right().ordinal()) { + return false; + } + BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().get(patternLayer.getPattern().id()); + if (bannerPattern != pair.left()) { + return false; + } + } + return true; + } + + public static boolean isOminous(ListTag blockEntityPatterns) { + return OMINOUS_BANNER_PATTERN_BLOCK.equals(blockEntityPatterns); } /** @@ -76,7 +104,10 @@ public class BannerItem extends BlockItem { public static NbtList convertBannerPattern(ListTag patterns) { List tagsList = new ArrayList<>(); for (Tag patternTag : patterns.getValue()) { - tagsList.add(getBedrockBannerPattern((CompoundTag) patternTag)); + NbtMap bedrockBannerPattern = getBedrockBannerPattern((CompoundTag) patternTag); + if (bedrockBannerPattern != null) { + tagsList.add(bedrockBannerPattern); + } } return new NbtList<>(NbtType.COMPOUND, tagsList); @@ -88,35 +119,41 @@ public class BannerItem extends BlockItem { * @param pattern Java edition pattern nbt * @return The Bedrock edition format pattern nbt */ - @NonNull private static NbtMap getBedrockBannerPattern(CompoundTag pattern) { + BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier((String) pattern.get("pattern").getValue()); + DyeColor dyeColor = DyeColor.getByJavaIdentifier((String) pattern.get("color").getValue()); + if (bannerPattern == null || dyeColor == null) { + return null; + } + return NbtMap.builder() - .putInt("Color", 15 - (int) pattern.get("Color").getValue()) - .putString("Pattern", (String) pattern.get("Pattern").getValue()) + .putString("Pattern", bannerPattern.getBedrockIdentifier()) + .putInt("Color", 15 - dyeColor.ordinal()) .build(); } + public static CompoundTag getJavaBannerPatternTag(BannerPattern bannerPattern, DyeColor dyeColor) { + CompoundTag tag = new CompoundTag(""); + tag.put(new StringTag("pattern", bannerPattern.getJavaIdentifier())); + tag.put(new StringTag("color", dyeColor.getJavaIdentifier())); + return tag; + } + /** * Convert the Bedrock edition banner pattern nbt to Java edition * * @param pattern Bedrock edition pattern nbt - * @return The Java edition format pattern nbt + * @return The Java edition format pattern layer */ - public static CompoundTag getJavaBannerPattern(NbtMap pattern) { - //return new BannerPatternLayer(0/*pattern.getString("Pattern")*/, 15 - pattern.getInt("Color")); - return null; - } - - /** - * Convert a list of patterns from Java nbt to Bedrock nbt, or vice versa (we just need to invert the color) - * - * @param patterns The patterns to convert - */ - private void invertBannerColors(ListTag patterns) { - for (Tag patternTag : patterns.getValue()) { - IntTag color = ((CompoundTag) patternTag).get("Color"); - color.setValue(15 - color.getValue()); + public static BannerPatternLayer getJavaBannerPattern(GeyserSession session, NbtMap pattern) { + return null; // TODO + /*Int2ObjectBiMap registry = session.getRegistryCache().bannerPatterns(); + BannerPattern bannerPattern = BannerPattern.getByBedrockIdentifier(pattern.getString("Pattern")); + DyeColor dyeColor = DyeColor.getById(15 - pattern.getInt("Color")); + if (bannerPattern != null && dyeColor != null && registry.containsValue(bannerPattern)) { + return new BannerPatternLayer(Holder.ofId(registry.get(bannerPattern)), dyeColor.ordinal()); } + return null;*/ } public BannerItem(String javaIdentifier, Builder builder) { @@ -129,31 +166,51 @@ public class BannerItem extends BlockItem { List patterns = components.get(DataComponentType.BANNER_PATTERNS); if (patterns != null) { -// if (patterns.equals(OMINOUS_BANNER_PATTERN)) { -// // Remove the current patterns and set the ominous banner type -// builder.putInt("Type", 1); -// } else { -// invertBannerColors(patterns); -// tag.put(patterns); -// } + if (isOminous(session, patterns)) { + // Remove the current patterns and set the ominous banner type + builder.putInt("Type", 1); + } else { + List patternList = new ArrayList<>(patterns.size()); + for (BannerPatternLayer patternLayer : patterns) { + patternLayer.getPattern().ifId(holder -> { + BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().get(holder.id()); + if (bannerPattern != null) { + NbtMap tag = NbtMap.builder() + .putString("Pattern", bannerPattern.getBedrockIdentifier()) + .putInt("Color", 15 - patternLayer.getColorId()) + .build(); + patternList.add(tag); + } + }); + } + builder.putList("Patterns", NbtType.COMPOUND, patternList); + } } } @Override - public void translateNbtToJava(@NonNull CompoundTag tag, @NonNull ItemMapping mapping) { + public void translateNbtToJava(@NonNull CompoundTag tag, @NonNull ItemMapping mapping) { // TODO super.translateNbtToJava(tag, mapping); if (tag.get("Type") instanceof IntTag type && type.getValue() == 1) { // Ominous banner pattern tag.remove("Type"); CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); - //blockEntityTag.put(OMINOUS_BANNER_PATTERN); + blockEntityTag.put(OMINOUS_BANNER_PATTERN_BLOCK); tag.put(blockEntityTag); - } else if (tag.get("Patterns") instanceof ListTag patterns) { + } else if (tag.get("Patterns") instanceof ListTag patterns && patterns.getElementType() == CompoundTag.class) { CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); - invertBannerColors(patterns); - blockEntityTag.put(patterns); + + ListTag javaPatterns = new ListTag("patterns"); + for (Tag pattern : patterns.getValue()) { + BannerPattern bannerPattern = BannerPattern.getByBedrockIdentifier((String) ((CompoundTag) pattern).get("Pattern").getValue()); + DyeColor dyeColor = DyeColor.getById((int) ((CompoundTag) pattern).get("Color").getValue()); + if (bannerPattern != null && dyeColor != null) { + javaPatterns.add(getJavaBannerPatternTag(bannerPattern, dyeColor)); + } + } + blockEntityTag.put(javaPatterns); tag.put(blockEntityTag); tag.remove("Patterns"); // Remove the old Bedrock patterns list diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java index 44a5469b8..6f69574ed 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java @@ -37,6 +37,7 @@ import lombok.experimental.Accessors; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.cloudburstmc.protocol.bedrock.data.TrimPattern; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.inventory.item.BannerPattern; import org.geysermc.geyser.inventory.recipe.TrimRecipe; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.session.GeyserSession; @@ -68,6 +69,7 @@ public final class RegistryCache { register("trim_material", cache -> cache.trimMaterials, TrimRecipe::readTrimMaterial); register("trim_pattern", cache -> cache.trimPatterns, TrimRecipe::readTrimPattern); register("worldgen/biome", (cache, array) -> cache.biomeTranslations = array, BiomeTranslator::loadServerBiome); + register("banner_pattern", cache -> cache.bannerPatterns, ($, entry) -> BannerPattern.getByJavaIdentifier(entry.getId())); } @Getter(AccessLevel.NONE) @@ -85,6 +87,8 @@ public final class RegistryCache { private final Int2ObjectMap trimMaterials = new Int2ObjectOpenHashMap<>(); private final Int2ObjectMap trimPatterns = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectMap bannerPatterns = new Int2ObjectOpenHashMap<>(); + public RegistryCache(GeyserSession session) { this.session = session; } diff --git a/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java index 6c7a11ff2..636a7982a 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/inventory/LoomInventoryTranslator.java @@ -25,10 +25,10 @@ package org.geysermc.geyser.translator.inventory; +import com.github.steveice10.mc.protocol.data.game.item.component.BannerPatternLayer; +import com.github.steveice10.mc.protocol.data.game.item.component.DataComponentType; import com.github.steveice10.mc.protocol.data.game.item.component.DataComponents; import com.github.steveice10.mc.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.github.steveice10.opennbt.tag.builtin.ListTag; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.cloudburstmc.nbt.NbtMap; @@ -51,7 +51,7 @@ import org.geysermc.geyser.item.type.BannerItem; import org.geysermc.geyser.item.type.DyeItem; import org.geysermc.geyser.session.GeyserSession; -import java.util.Collections; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -159,23 +159,12 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { if (inputCopy.getComponents() == null) { inputCopy.setComponents(new DataComponents(new HashMap<>())); } - //TODO - CompoundTag blockEntityTag = inputCopy.getNbt().get("BlockEntityTag"); - CompoundTag javaBannerPattern = BannerItem.getJavaBannerPattern(pattern); - if (blockEntityTag != null) { - ListTag patternsList = blockEntityTag.get("Patterns"); - if (patternsList != null) { - patternsList.add(javaBannerPattern); - } else { - patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern)); - blockEntityTag.put(patternsList); - } - } else { - blockEntityTag = new CompoundTag("BlockEntityTag"); - ListTag patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern)); - blockEntityTag.put(patternsList); - inputCopy.getNbt().put(blockEntityTag); + BannerPatternLayer bannerPatternLayer = BannerItem.getJavaBannerPattern(session, pattern); // TODO + if (bannerPatternLayer != null) { + List patternsList = inputCopy.getComponents().getOrDefault(DataComponentType.BANNER_PATTERNS, new ArrayList<>()); + patternsList.add(bannerPatternLayer); + inputCopy.getComponents().put(DataComponentType.BANNER_PATTERNS, patternsList); } // Set the new item as the output diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java index f23433bbe..e0e0130c5 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/BannerBlockEntityTranslator.java @@ -46,8 +46,8 @@ public class BannerBlockEntityTranslator extends BlockEntityTranslator implement return; } - if (tag.get("Patterns") instanceof ListTag patterns) { - if (patterns.equals(BannerItem.OMINOUS_BANNER_PATTERN)) { + if (tag.get("patterns") instanceof ListTag patterns) { + if (BannerItem.isOminous(patterns)) { // This is an ominous banner; don't try to translate the raw patterns (it doesn't translate correctly) // and tell the Bedrock client that this is an ominous banner builder.putInt("Type", 1); diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBookEditTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBookEditTranslator.java index 4350f6a83..10024c02f 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBookEditTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockBookEditTranslator.java @@ -105,6 +105,7 @@ public class BedrockBookEditTranslator extends PacketTranslator break; } case SIGN_BOOK: { + // As of JE 1.20.5, client no longer adds title and author on its own break; } default: