diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java index ceb29695a..4573f0e7a 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/animal/tameable/WolfEntity.java @@ -113,7 +113,7 @@ public class WolfEntity extends TameableEntity { // 1.20.5+ public void setWolfVariant(IntEntityMetadata entityMetadata) { - WolfVariant wolfVariant = session.getRegistryCache().wolfVariants().get(entityMetadata.getPrimitiveValue()); + WolfVariant wolfVariant = session.getRegistryCache().wolfVariants().byId(entityMetadata.getPrimitiveValue()); if (wolfVariant == null) { wolfVariant = WolfVariant.PALE; } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/ArmorItem.java b/core/src/main/java/org/geysermc/geyser/item/type/ArmorItem.java index 76f951c66..fc0dd3ba4 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/ArmorItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/ArmorItem.java @@ -56,8 +56,8 @@ public class ArmorItem extends Item { return; } - TrimMaterial material = session.getRegistryCache().trimMaterials().get(trim.material().id()); - TrimPattern pattern = session.getRegistryCache().trimPatterns().get(trim.pattern().id()); + TrimMaterial material = session.getRegistryCache().trimMaterials().byId(trim.material().id()); + TrimPattern pattern = session.getRegistryCache().trimPatterns().byId(trim.pattern().id()); NbtMapBuilder trimBuilder = NbtMap.builder(); // bedrock has an uppercase first letter key, and the value is not namespaced 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 2f5d96b5a..6c2678db9 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 @@ -33,13 +33,14 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.nbt.NbtList; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; -import org.cloudburstmc.protocol.common.util.Int2ObjectBiMap; 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.session.cache.registry.JavaRegistry; import org.geysermc.geyser.translator.item.BedrockItemBuilder; import org.geysermc.mcprotocollib.protocol.data.game.Holder; +import org.geysermc.mcprotocollib.protocol.data.game.Identifier; import org.geysermc.mcprotocollib.protocol.data.game.item.component.BannerPatternLayer; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -57,7 +58,6 @@ public class BannerItem extends BlockItem { * the correct ominous banner pattern if Bedrock pulls the item from creative. */ private static final List> OMINOUS_BANNER_PATTERN; - private static final List OMINOUS_BANNER_PATTERN_BLOCK; // TODO fix - we somehow need to be able to get the sessions banner pattern registry, which we don't have where we need this :/ private static final int[] ominousBannerPattern = new int[] { 21, 29, 30, 1, 34, 15, 3, 1 }; @@ -74,11 +74,6 @@ public class BannerItem extends BlockItem { Pair.of(BannerPattern.CIRCLE, DyeColor.LIGHT_GRAY), Pair.of(BannerPattern.BORDER, DyeColor.BLACK) ); - - OMINOUS_BANNER_PATTERN_BLOCK = new ArrayList<>(); - for (Pair pair : OMINOUS_BANNER_PATTERN) { - OMINOUS_BANNER_PATTERN_BLOCK.add(getJavaBannerPatternTag(pair.left(), pair.right())); - } } public static boolean isOminous(GeyserSession session, List patternLayers) { @@ -92,7 +87,7 @@ public class BannerItem extends BlockItem { !patternLayer.getPattern().isId()) { return false; } - BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().get(patternLayer.getPattern().id()); + BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().byId(patternLayer.getPattern().id()); if (bannerPattern != pair.left()) { return false; } @@ -101,7 +96,25 @@ public class BannerItem extends BlockItem { } public static boolean isOminous(List blockEntityPatterns) { - return OMINOUS_BANNER_PATTERN_BLOCK.equals(blockEntityPatterns); + // Cannot do a simple NBT equals check here because the IDs may not be full resource locations + // ViaVersion's fault, 1.20.4 -> 1.20.5, but it works on Java so we need to support it. + if (OMINOUS_BANNER_PATTERN.size() != blockEntityPatterns.size()) { + return false; + } + for (int i = 0; i < OMINOUS_BANNER_PATTERN.size(); i++) { + NbtMap patternLayer = blockEntityPatterns.get(i); + Pair pair = OMINOUS_BANNER_PATTERN.get(i); + DyeColor color = DyeColor.getByJavaIdentifier(patternLayer.getString("color")); + if (color != pair.right()) { + return false; + } + String id = Identifier.formalize(patternLayer.getString("pattern")); // Ouch + BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(id); + if (bannerPattern != pair.left()) { + return false; + } + } + return true; } /** @@ -133,7 +146,7 @@ public class BannerItem extends BlockItem { List patternList = new ArrayList<>(patterns.size()); for (BannerPatternLayer patternLayer : patterns) { patternLayer.getPattern().ifId(holder -> { - BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().get(holder.id()); + BannerPattern bannerPattern = session.getRegistryCache().bannerPatterns().byId(holder.id()); if (bannerPattern != null) { NbtMap tag = NbtMap.builder() .putString("Pattern", bannerPattern.getBedrockIdentifier()) @@ -154,7 +167,8 @@ public class BannerItem extends BlockItem { * @return The Bedrock edition format pattern nbt */ private static NbtMap getBedrockBannerPattern(NbtMap pattern) { - BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(pattern.getString("pattern")); + // ViaVersion 1.20.4 -> 1.20.5 can send without the namespace + BannerPattern bannerPattern = BannerPattern.getByJavaIdentifier(Identifier.formalize(pattern.getString("pattern"))); DyeColor dyeColor = DyeColor.getByJavaIdentifier(pattern.getString("color")); if (bannerPattern == null || dyeColor == null) { return null; @@ -166,13 +180,6 @@ public class BannerItem extends BlockItem { .build(); } - public static NbtMap getJavaBannerPatternTag(BannerPattern bannerPattern, DyeColor dyeColor) { - return NbtMap.builder() - .putString("pattern", bannerPattern.getJavaIdentifier()) - .putString("color", dyeColor.getJavaIdentifier()) - .build(); - } - /** * Convert the Bedrock edition banner pattern nbt to Java edition * @@ -180,11 +187,14 @@ public class BannerItem extends BlockItem { * @return The Java edition format pattern layer */ public static BannerPatternLayer getJavaBannerPattern(GeyserSession session, NbtMap pattern) { - Int2ObjectBiMap registry = session.getRegistryCache().bannerPatterns(); + JavaRegistry 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()); + if (bannerPattern != null && dyeColor != null) { + int id = registry.byValue(bannerPattern); + if (id != -1) { + return new BannerPatternLayer(Holder.ofId(id), dyeColor.ordinal()); + } } return null; } 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 4dfc603ee..9581df253 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 @@ -27,25 +27,25 @@ package org.geysermc.geyser.session.cache; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; import org.cloudburstmc.protocol.bedrock.data.TrimMaterial; import org.cloudburstmc.protocol.bedrock.data.TrimPattern; -import org.cloudburstmc.protocol.common.util.Int2ObjectBiMap; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity; 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; +import org.geysermc.geyser.session.cache.registry.JavaRegistry; +import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry; import org.geysermc.geyser.text.TextDecoration; import org.geysermc.geyser.translator.level.BiomeTranslator; import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; import org.geysermc.mcprotocollib.protocol.packet.configuration.clientbound.ClientboundRegistryDataPacket; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -71,7 +71,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); - registerBannerRegistry(($, entry) -> BannerPattern.getByJavaIdentifier(entry.getId())); + register("banner_pattern", cache -> cache.bannerPatterns, ($, entry) -> BannerPattern.getByJavaIdentifier(entry.getId())); register("wolf_variant", cache -> cache.wolfVariants, ($, entry) -> WolfEntity.WolfVariant.getByJavaIdentifier(entry.getId())); } @@ -82,16 +82,16 @@ public final class RegistryCache { * Java -> Bedrock biome network IDs. */ private int[] biomeTranslations; - private final Int2ObjectMap chatTypes = new Int2ObjectOpenHashMap<>(7); + private final JavaRegistry chatTypes = new SimpleJavaRegistry<>(); /** * All dimensions that the client could possibly connect to. */ - private final Int2ObjectMap dimensions = new Int2ObjectOpenHashMap<>(4); - private final Int2ObjectMap trimMaterials = new Int2ObjectOpenHashMap<>(); - private final Int2ObjectMap trimPatterns = new Int2ObjectOpenHashMap<>(); + private final JavaRegistry dimensions = new SimpleJavaRegistry<>(); + private final JavaRegistry trimMaterials = new SimpleJavaRegistry<>(); + private final JavaRegistry trimPatterns = new SimpleJavaRegistry<>(); - private Int2ObjectBiMap bannerPatterns = new Int2ObjectBiMap<>(); - private final Int2ObjectMap wolfVariants = new Int2ObjectOpenHashMap<>(); + private final JavaRegistry bannerPatterns = new SimpleJavaRegistry<>(); + private final JavaRegistry wolfVariants = new SimpleJavaRegistry<>(); public RegistryCache(GeyserSession session) { this.session = session; @@ -109,47 +109,25 @@ public final class RegistryCache { } } - private static void registerBannerRegistry(BiFunction reader) { - REGISTRIES.put("minecraft:banner_pattern", ((registryCache, entries) -> { - // Clear each local cache every time a new registry entry is given to us - // (e.g. proxy server switches) - registryCache.bannerPatterns = new Int2ObjectBiMap<>(); // Cannot clear it, must re-create :( - for (int i = 0; i < entries.size(); i++) { - RegistryEntry entry = entries.get(i); - // This is what Geyser wants to keep as a value for this registry. - T cacheEntry = reader.apply(registryCache.session, entry); - if (cacheEntry != null) { - registryCache.bannerPatterns.put(i, (BannerPattern) cacheEntry); - } else { - // TODO - seems to be possible with viaversion :/ - GeyserImpl.getInstance().getLogger().warning("Was not able to translate entry: "); - } - } - })); - } - /** * @param registry the Java registry resource location, without the "minecraft:" prefix. * @param localCacheFunction which local field in RegistryCache are we caching entries for this registry? * @param reader converts the RegistryEntry NBT into a class file * @param the class that represents these entries. */ - private static void register(String registry, Function> localCacheFunction, BiFunction reader) { + private static void register(String registry, Function> localCacheFunction, BiFunction reader) { REGISTRIES.put("minecraft:" + registry, (registryCache, entries) -> { - Int2ObjectMap localCache = localCacheFunction.apply(registryCache); + JavaRegistry localCache = localCacheFunction.apply(registryCache); // Clear each local cache every time a new registry entry is given to us // (e.g. proxy server switches) - localCache.clear(); + List builder = new ArrayList<>(entries.size()); for (int i = 0; i < entries.size(); i++) { RegistryEntry entry = entries.get(i); // This is what Geyser wants to keep as a value for this registry. T cacheEntry = reader.apply(registryCache.session, entry); - localCache.put(i, cacheEntry); - } - // Trim registry down to needed size - if (localCache instanceof Int2ObjectOpenHashMap hashMap) { - hashMap.trim(); + builder.add(i, cacheEntry); } + localCache.reset(builder); }); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistry.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistry.java new file mode 100644 index 000000000..d7c7782ea --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistry.java @@ -0,0 +1,56 @@ +/* + * 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.session.cache.registry; + +import org.checkerframework.checker.index.qual.NonNegative; + +import java.util.List; + +/** + * A wrapper for a list, holding Java registry values. + */ +public interface JavaRegistry { + + /** + * Looks up a registry entry by its ID. The object can be null, or not present. + */ + T byId(@NonNegative int id); + + /** + * Reverse looks-up an object to return its network ID, or -1. + */ + int byValue(T value); + + /** + * Resets the objects by these IDs. + */ + void reset(List values); + + /** + * All values of this registry, as a list. + */ + List values(); +} diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java new file mode 100644 index 000000000..633e5fbd1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/SimpleJavaRegistry.java @@ -0,0 +1,59 @@ +/* + * 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.session.cache.registry; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.checkerframework.checker.index.qual.NonNegative; + +import java.util.List; + +public class SimpleJavaRegistry implements JavaRegistry { + protected final ObjectArrayList values = new ObjectArrayList<>(); + + @Override + public T byId(@NonNegative int id) { + if (id < 0 || id >= this.values.size()) { + return null; + } + return this.values.get(id); + } + + @Override + public int byValue(T value) { + return this.values.indexOf(value); + } + + @Override + public void reset(List values) { + this.values.addAll(values); + this.values.trim(); + } + + @Override + public List values() { + return this.values; + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java index 884f05256..5a0121039 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java @@ -330,7 +330,7 @@ public class MessageTranslator { textPacket.setNeedsTranslation(false); - TextDecoration decoration = session.getRegistryCache().chatTypes().get(chatType); + TextDecoration decoration = session.getRegistryCache().chatTypes().byId(chatType); if (decoration != null) { // As of 1.19 - do this to apply all the styling for signed messages // Though, Bedrock cannot care about the signed stuff. diff --git a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java index a542f7431..379162540 100644 --- a/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/ChunkUtils.java @@ -280,7 +280,7 @@ public class ChunkUtils { * This must be done after the player has switched dimensions so we know what their dimension is */ public static void loadDimension(GeyserSession session) { - JavaDimension dimension = session.getRegistryCache().dimensions().get(session.getDimension()); + JavaDimension dimension = session.getRegistryCache().dimensions().byId(session.getDimension()); session.setDimensionType(dimension); int minY = dimension.minY(); int maxY = dimension.maxY();