diff --git a/README.md b/README.md index e3b5a496a..14bdb17a9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here! -### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.1 and Minecraft Java 1.21 +### Currently supporting Minecraft Bedrock 1.20.80 - 1.21.2 and Minecraft Java 1.21 ## Setting Up Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser. diff --git a/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java b/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java index 8289813a4..b5e76a296 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/recipe/TrimRecipe.java @@ -32,9 +32,8 @@ import org.cloudburstmc.protocol.bedrock.data.TrimPattern; import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescriptorWithCount; import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemTagDescriptor; import org.geysermc.geyser.registry.type.ItemMapping; -import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; /** * Stores information on trim materials and patterns, including smithing armor hacks for pre-1.20. @@ -46,18 +45,18 @@ public final class TrimRecipe { public static final ItemDescriptorWithCount ADDITION = tagDescriptor("minecraft:trim_materials"); public static final ItemDescriptorWithCount TEMPLATE = tagDescriptor("minecraft:trim_templates"); - public static TrimMaterial readTrimMaterial(GeyserSession session, RegistryEntry entry) { - String key = entry.getId().asMinimalString(); + public static TrimMaterial readTrimMaterial(RegistryEntryContext context) { + String key = context.id().asMinimalString(); // Color is used when hovering over the item // Find the nearest legacy color from the RGB Java gives us to work with // Also yes this is a COMPLETE hack but it works ok!!!!! - String colorTag = entry.getData().getCompound("description").getString("color"); + String colorTag = context.data().getCompound("description").getString("color"); TextColor color = TextColor.fromHexString(colorTag); String legacy = MessageTranslator.convertMessage(Component.space().color(color)); - String itemIdentifier = entry.getData().getString("ingredient"); - ItemMapping itemMapping = session.getItemMappings().getMapping(itemIdentifier); + String itemIdentifier = context.data().getString("ingredient"); + ItemMapping itemMapping = context.session().getItemMappings().getMapping(itemIdentifier); if (itemMapping == null) { // This should never happen so not sure what to do here. itemMapping = ItemMapping.AIR; @@ -66,11 +65,11 @@ public final class TrimRecipe { return new TrimMaterial(key, legacy.substring(2).trim(), itemMapping.getBedrockIdentifier()); } - public static TrimPattern readTrimPattern(GeyserSession session, RegistryEntry entry) { - String key = entry.getId().asMinimalString(); + public static TrimPattern readTrimPattern(RegistryEntryContext context) { + String key = context.id().asMinimalString(); - String itemIdentifier = entry.getData().getString("template_item"); - ItemMapping itemMapping = session.getItemMappings().getMapping(itemIdentifier); + String itemIdentifier = context.data().getString("template_item"); + ItemMapping itemMapping = context.session().getItemMappings().getMapping(itemIdentifier); if (itemMapping == null) { // This should never happen so not sure what to do here. itemMapping = ItemMapping.AIR; diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java index c3ac73372..7afd31cc9 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.inventory.updater; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.stream.IntStream; import net.kyori.adventure.text.Component; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtMapBuilder; @@ -41,11 +42,14 @@ import org.geysermc.geyser.inventory.item.BedrockEnchantment; import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.tags.EnchantmentTag; +import org.geysermc.geyser.session.cache.tags.ItemTag; import org.geysermc.geyser.translator.inventory.InventoryTranslator; import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.util.ItemUtils; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet; import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments; import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundRenameItemPacket; @@ -310,17 +314,18 @@ public class AnvilInventoryUpdater extends InventoryUpdater { for (Object2IntMap.Entry entry : getEnchantments(session, material).object2IntEntrySet()) { Enchantment enchantment = entry.getKey(); - boolean canApply = isEnchantedBook(input) || session.getTagCache().is(enchantment.supportedItems(), input); - var exclusiveSet = enchantment.exclusiveSet(); - if (exclusiveSet != null) { - int[] incompatibleEnchantments = session.getTagCache().get(exclusiveSet); - for (int i : incompatibleEnchantments) { - Enchantment incompatible = session.getRegistryCache().enchantments().byId(i); - if (combinedEnchantments.containsKey(incompatible)) { - canApply = false; - if (!bedrock) { - cost++; - } + HolderSet supportedItems = enchantment.supportedItems(); + int[] supportedItemIds = supportedItems.resolve(tagId -> session.getTagCache().get(ItemTag.ALL_ITEM_TAGS.get(tagId))); + boolean canApply = isEnchantedBook(input) || IntStream.of(supportedItemIds).anyMatch(id -> id == input.getJavaId()); + + HolderSet exclusiveSet = enchantment.exclusiveSet(); + int[] incompatibleEnchantments = exclusiveSet.resolve(tagId -> session.getTagCache().get(EnchantmentTag.ALL_ENCHANTMENT_TAGS.get(tagId))); + for (int i : incompatibleEnchantments) { + Enchantment incompatible = session.getRegistryCache().enchantments().byId(i); + if (combinedEnchantments.containsKey(incompatible)) { + canApply = false; + if (!bedrock) { + cost++; } } } diff --git a/core/src/main/java/org/geysermc/geyser/item/Items.java b/core/src/main/java/org/geysermc/geyser/item/Items.java index 1ddd14982..462e98d19 100644 --- a/core/src/main/java/org/geysermc/geyser/item/Items.java +++ b/core/src/main/java/org/geysermc/geyser/item/Items.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.item; +import org.geysermc.geyser.item.components.Rarity; import org.geysermc.geyser.item.components.ToolTier; import org.geysermc.geyser.item.type.*; import org.geysermc.geyser.level.block.Blocks; @@ -122,7 +123,7 @@ public final class Items { public static final Item RAW_IRON_BLOCK = register(new BlockItem(builder(), Blocks.RAW_IRON_BLOCK)); public static final Item RAW_COPPER_BLOCK = register(new BlockItem(builder(), Blocks.RAW_COPPER_BLOCK)); public static final Item RAW_GOLD_BLOCK = register(new BlockItem(builder(), Blocks.RAW_GOLD_BLOCK)); - public static final Item HEAVY_CORE = register(new BlockItem(builder(), Blocks.HEAVY_CORE)); + public static final Item HEAVY_CORE = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.HEAVY_CORE)); public static final Item AMETHYST_BLOCK = register(new BlockItem(builder(), Blocks.AMETHYST_BLOCK)); public static final Item BUDDING_AMETHYST = register(new BlockItem(builder(), Blocks.BUDDING_AMETHYST)); public static final Item IRON_BLOCK = register(new BlockItem(builder(), Blocks.IRON_BLOCK)); @@ -416,7 +417,7 @@ public final class Items { public static final Item END_PORTAL_FRAME = register(new BlockItem(builder(), Blocks.END_PORTAL_FRAME)); public static final Item END_STONE = register(new BlockItem(builder(), Blocks.END_STONE)); public static final Item END_STONE_BRICKS = register(new BlockItem(builder(), Blocks.END_STONE_BRICKS)); - public static final Item DRAGON_EGG = register(new BlockItem(builder(), Blocks.DRAGON_EGG)); + public static final Item DRAGON_EGG = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.DRAGON_EGG)); public static final Item SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.SANDSTONE_STAIRS)); public static final Item ENDER_CHEST = register(new BlockItem(builder(), Blocks.ENDER_CHEST)); public static final Item EMERALD_BLOCK = register(new BlockItem(builder(), Blocks.EMERALD_BLOCK)); @@ -432,8 +433,8 @@ public final class Items { public static final Item BAMBOO_MOSAIC_STAIRS = register(new BlockItem(builder(), Blocks.BAMBOO_MOSAIC_STAIRS)); public static final Item CRIMSON_STAIRS = register(new BlockItem(builder(), Blocks.CRIMSON_STAIRS)); public static final Item WARPED_STAIRS = register(new BlockItem(builder(), Blocks.WARPED_STAIRS)); - public static final Item COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.COMMAND_BLOCK)); - public static final Item BEACON = register(new BlockItem(builder(), Blocks.BEACON)); + public static final Item COMMAND_BLOCK = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.COMMAND_BLOCK)); + public static final Item BEACON = register(new BlockItem(builder().rarity(Rarity.RARE), Blocks.BEACON)); public static final Item COBBLESTONE_WALL = register(new BlockItem(builder(), Blocks.COBBLESTONE_WALL)); public static final Item MOSSY_COBBLESTONE_WALL = register(new BlockItem(builder(), Blocks.MOSSY_COBBLESTONE_WALL)); public static final Item BRICK_WALL = register(new BlockItem(builder(), Blocks.BRICK_WALL)); @@ -480,8 +481,8 @@ public final class Items { public static final Item GREEN_TERRACOTTA = register(new BlockItem(builder(), Blocks.GREEN_TERRACOTTA)); public static final Item RED_TERRACOTTA = register(new BlockItem(builder(), Blocks.RED_TERRACOTTA)); public static final Item BLACK_TERRACOTTA = register(new BlockItem(builder(), Blocks.BLACK_TERRACOTTA)); - public static final Item BARRIER = register(new BlockItem(builder(), Blocks.BARRIER)); - public static final Item LIGHT = register(new BlockItem(builder(), Blocks.LIGHT)); + public static final Item BARRIER = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.BARRIER)); + public static final Item LIGHT = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.LIGHT)); public static final Item HAY_BLOCK = register(new BlockItem(builder(), Blocks.HAY_BLOCK)); public static final Item WHITE_CARPET = register(new BlockItem(builder(), Blocks.WHITE_CARPET)); public static final Item ORANGE_CARPET = register(new BlockItem(builder(), Blocks.ORANGE_CARPET)); @@ -551,14 +552,14 @@ public final class Items { public static final Item CHISELED_RED_SANDSTONE = register(new BlockItem(builder(), Blocks.CHISELED_RED_SANDSTONE)); public static final Item CUT_RED_SANDSTONE = register(new BlockItem(builder(), Blocks.CUT_RED_SANDSTONE)); public static final Item RED_SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.RED_SANDSTONE_STAIRS)); - public static final Item REPEATING_COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.REPEATING_COMMAND_BLOCK)); - public static final Item CHAIN_COMMAND_BLOCK = register(new BlockItem(builder(), Blocks.CHAIN_COMMAND_BLOCK)); + public static final Item REPEATING_COMMAND_BLOCK = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.REPEATING_COMMAND_BLOCK)); + public static final Item CHAIN_COMMAND_BLOCK = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.CHAIN_COMMAND_BLOCK)); public static final Item MAGMA_BLOCK = register(new BlockItem(builder(), Blocks.MAGMA_BLOCK)); public static final Item NETHER_WART_BLOCK = register(new BlockItem(builder(), Blocks.NETHER_WART_BLOCK)); public static final Item WARPED_WART_BLOCK = register(new BlockItem(builder(), Blocks.WARPED_WART_BLOCK)); public static final Item RED_NETHER_BRICKS = register(new BlockItem(builder(), Blocks.RED_NETHER_BRICKS)); public static final Item BONE_BLOCK = register(new BlockItem(builder(), Blocks.BONE_BLOCK)); - public static final Item STRUCTURE_VOID = register(new BlockItem(builder(), Blocks.STRUCTURE_VOID)); + public static final Item STRUCTURE_VOID = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.STRUCTURE_VOID)); public static final Item SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.SHULKER_BOX)); public static final Item WHITE_SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.WHITE_SHULKER_BOX)); public static final Item ORANGE_SHULKER_BOX = register(new ShulkerBoxItem(builder().stackSize(1), Blocks.ORANGE_SHULKER_BOX)); @@ -657,7 +658,7 @@ public final class Items { public static final Item DEAD_FIRE_CORAL_FAN = register(new BlockItem(builder(), Blocks.DEAD_FIRE_CORAL_FAN, Blocks.DEAD_FIRE_CORAL_WALL_FAN)); public static final Item DEAD_HORN_CORAL_FAN = register(new BlockItem(builder(), Blocks.DEAD_HORN_CORAL_FAN, Blocks.DEAD_HORN_CORAL_WALL_FAN)); public static final Item BLUE_ICE = register(new BlockItem(builder(), Blocks.BLUE_ICE)); - public static final Item CONDUIT = register(new BlockItem(builder(), Blocks.CONDUIT)); + public static final Item CONDUIT = register(new BlockItem(builder().rarity(Rarity.RARE), Blocks.CONDUIT)); public static final Item POLISHED_GRANITE_STAIRS = register(new BlockItem(builder(), Blocks.POLISHED_GRANITE_STAIRS)); public static final Item SMOOTH_RED_SANDSTONE_STAIRS = register(new BlockItem(builder(), Blocks.SMOOTH_RED_SANDSTONE_STAIRS)); public static final Item MOSSY_STONE_BRICK_STAIRS = register(new BlockItem(builder(), Blocks.MOSSY_STONE_BRICK_STAIRS)); @@ -810,7 +811,7 @@ public final class Items { public static final Item HOPPER_MINECART = register(new Item("hopper_minecart", builder().stackSize(1))); public static final Item CARROT_ON_A_STICK = register(new Item("carrot_on_a_stick", builder().stackSize(1).maxDamage(25))); public static final Item WARPED_FUNGUS_ON_A_STICK = register(new Item("warped_fungus_on_a_stick", builder().stackSize(1).maxDamage(100))); - public static final Item ELYTRA = register(new ElytraItem("elytra", builder().stackSize(1).maxDamage(432))); + public static final Item ELYTRA = register(new ElytraItem("elytra", builder().stackSize(1).maxDamage(432).rarity(Rarity.UNCOMMON))); public static final Item OAK_BOAT = register(new BoatItem("oak_boat", builder().stackSize(1))); public static final Item OAK_CHEST_BOAT = register(new BoatItem("oak_chest_boat", builder().stackSize(1))); public static final Item SPRUCE_BOAT = register(new BoatItem("spruce_boat", builder().stackSize(1))); @@ -829,8 +830,8 @@ public final class Items { public static final Item MANGROVE_CHEST_BOAT = register(new BoatItem("mangrove_chest_boat", builder().stackSize(1))); public static final Item BAMBOO_RAFT = register(new BoatItem("bamboo_raft", builder().stackSize(1))); public static final Item BAMBOO_CHEST_RAFT = register(new BoatItem("bamboo_chest_raft", builder().stackSize(1))); - public static final Item STRUCTURE_BLOCK = register(new BlockItem(builder(), Blocks.STRUCTURE_BLOCK)); - public static final Item JIGSAW = register(new BlockItem(builder(), Blocks.JIGSAW)); + public static final Item STRUCTURE_BLOCK = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.STRUCTURE_BLOCK)); + public static final Item JIGSAW = register(new BlockItem(builder().rarity(Rarity.EPIC), Blocks.JIGSAW)); public static final Item TURTLE_HELMET = register(new ArmorItem("turtle_helmet", ArmorMaterial.TURTLE, builder().stackSize(1).maxDamage(275))); public static final Item TURTLE_SCUTE = register(new Item("turtle_scute", builder())); public static final Item ARMADILLO_SCUTE = register(new Item("armadillo_scute", builder())); @@ -921,8 +922,8 @@ public final class Items { public static final Item PORKCHOP = register(new Item("porkchop", builder())); public static final Item COOKED_PORKCHOP = register(new Item("cooked_porkchop", builder())); public static final Item PAINTING = register(new Item("painting", builder())); - public static final Item GOLDEN_APPLE = register(new Item("golden_apple", builder())); - public static final Item ENCHANTED_GOLDEN_APPLE = register(new Item("enchanted_golden_apple", builder())); + public static final Item GOLDEN_APPLE = register(new Item("golden_apple", builder().rarity(Rarity.RARE))); + public static final Item ENCHANTED_GOLDEN_APPLE = register(new Item("enchanted_golden_apple", builder().rarity(Rarity.EPIC))); public static final Item OAK_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.OAK_SIGN, Blocks.OAK_WALL_SIGN)); public static final Item SPRUCE_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.SPRUCE_SIGN, Blocks.SPRUCE_WALL_SIGN)); public static final Item BIRCH_SIGN = register(new BlockItem(builder().stackSize(16), Blocks.BIRCH_SIGN, Blocks.BIRCH_WALL_SIGN)); @@ -1042,7 +1043,7 @@ public final class Items { public static final Item BLAZE_POWDER = register(new Item("blaze_powder", builder())); public static final Item MAGMA_CREAM = register(new Item("magma_cream", builder())); public static final Item BREWING_STAND = register(new BlockItem(builder(), Blocks.BREWING_STAND)); - public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.WATER_CAULDRON, Blocks.LAVA_CAULDRON, Blocks.POWDER_SNOW_CAULDRON)); + public static final Item CAULDRON = register(new BlockItem(builder(), Blocks.CAULDRON, Blocks.WATER_CAULDRON, Blocks.POWDER_SNOW_CAULDRON, Blocks.LAVA_CAULDRON)); public static final Item ENDER_EYE = register(new Item("ender_eye", builder())); public static final Item GLISTERING_MELON_SLICE = register(new Item("glistering_melon_slice", builder())); public static final Item ARMADILLO_SPAWN_EGG = register(new SpawnEggItem("armadillo_spawn_egg", builder())); @@ -1125,12 +1126,12 @@ public final class Items { public static final Item ZOMBIE_HORSE_SPAWN_EGG = register(new SpawnEggItem("zombie_horse_spawn_egg", builder())); public static final Item ZOMBIE_VILLAGER_SPAWN_EGG = register(new SpawnEggItem("zombie_villager_spawn_egg", builder())); public static final Item ZOMBIFIED_PIGLIN_SPAWN_EGG = register(new SpawnEggItem("zombified_piglin_spawn_egg", builder())); - public static final Item EXPERIENCE_BOTTLE = register(new Item("experience_bottle", builder())); + public static final Item EXPERIENCE_BOTTLE = register(new Item("experience_bottle", builder().rarity(Rarity.UNCOMMON))); public static final Item FIRE_CHARGE = register(new Item("fire_charge", builder())); public static final Item WIND_CHARGE = register(new Item("wind_charge", builder())); public static final Item WRITABLE_BOOK = register(new WritableBookItem("writable_book", builder().stackSize(1))); public static final Item WRITTEN_BOOK = register(new WrittenBookItem("written_book", builder().stackSize(16))); - public static final Item MACE = register(new MaceItem("mace", builder().stackSize(1).maxDamage(500))); + public static final Item MACE = register(new MaceItem("mace", builder().stackSize(1).maxDamage(500).rarity(Rarity.EPIC))); public static final Item ITEM_FRAME = register(new Item("item_frame", builder())); public static final Item GLOW_ITEM_FRAME = register(new Item("glow_item_frame", builder())); public static final Item FLOWER_POT = register(new BlockItem(builder(), Blocks.FLOWER_POT)); @@ -1140,18 +1141,18 @@ public final class Items { public static final Item POISONOUS_POTATO = register(new Item("poisonous_potato", builder())); public static final Item MAP = register(new MapItem("map", builder())); public static final Item GOLDEN_CARROT = register(new Item("golden_carrot", builder())); - public static final Item SKELETON_SKULL = register(new BlockItem(builder(), Blocks.SKELETON_SKULL, Blocks.SKELETON_WALL_SKULL)); - public static final Item WITHER_SKELETON_SKULL = register(new BlockItem(builder(), Blocks.WITHER_SKELETON_SKULL, Blocks.WITHER_SKELETON_WALL_SKULL)); - public static final Item PLAYER_HEAD = register(new PlayerHeadItem(builder(), Blocks.PLAYER_HEAD, Blocks.PLAYER_WALL_HEAD)); - public static final Item ZOMBIE_HEAD = register(new BlockItem(builder(), Blocks.ZOMBIE_HEAD, Blocks.ZOMBIE_WALL_HEAD)); - public static final Item CREEPER_HEAD = register(new BlockItem(builder(), Blocks.CREEPER_HEAD, Blocks.CREEPER_WALL_HEAD)); - public static final Item DRAGON_HEAD = register(new BlockItem(builder(), Blocks.DRAGON_HEAD, Blocks.DRAGON_WALL_HEAD)); - public static final Item PIGLIN_HEAD = register(new BlockItem(builder(), Blocks.PIGLIN_HEAD, Blocks.PIGLIN_WALL_HEAD)); - public static final Item NETHER_STAR = register(new Item("nether_star", builder())); + public static final Item SKELETON_SKULL = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.SKELETON_SKULL, Blocks.SKELETON_WALL_SKULL)); + public static final Item WITHER_SKELETON_SKULL = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.WITHER_SKELETON_SKULL, Blocks.WITHER_SKELETON_WALL_SKULL)); + public static final Item PLAYER_HEAD = register(new PlayerHeadItem(builder().rarity(Rarity.UNCOMMON), Blocks.PLAYER_HEAD, Blocks.PLAYER_WALL_HEAD)); + public static final Item ZOMBIE_HEAD = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.ZOMBIE_HEAD, Blocks.ZOMBIE_WALL_HEAD)); + public static final Item CREEPER_HEAD = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.CREEPER_HEAD, Blocks.CREEPER_WALL_HEAD)); + public static final Item DRAGON_HEAD = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.DRAGON_HEAD, Blocks.DRAGON_WALL_HEAD)); + public static final Item PIGLIN_HEAD = register(new BlockItem(builder().rarity(Rarity.UNCOMMON), Blocks.PIGLIN_HEAD, Blocks.PIGLIN_WALL_HEAD)); + public static final Item NETHER_STAR = register(new Item("nether_star", builder().rarity(Rarity.UNCOMMON))); public static final Item PUMPKIN_PIE = register(new Item("pumpkin_pie", builder())); public static final Item FIREWORK_ROCKET = register(new FireworkRocketItem("firework_rocket", builder())); public static final Item FIREWORK_STAR = register(new FireworkStarItem("firework_star", builder())); - public static final Item ENCHANTED_BOOK = register(new EnchantedBookItem("enchanted_book", builder().stackSize(1))); + public static final Item ENCHANTED_BOOK = register(new EnchantedBookItem("enchanted_book", builder().stackSize(1).rarity(Rarity.UNCOMMON))); public static final Item NETHER_BRICK = register(new Item("nether_brick", builder())); public static final Item PRISMARINE_SHARD = register(new Item("prismarine_shard", builder())); public static final Item PRISMARINE_CRYSTALS = register(new Item("prismarine_crystals", builder())); @@ -1167,7 +1168,7 @@ public final class Items { public static final Item LEATHER_HORSE_ARMOR = register(new DyeableArmorItem("leather_horse_armor", ArmorMaterial.LEATHER, builder().stackSize(1))); public static final Item LEAD = register(new Item("lead", builder())); public static final Item NAME_TAG = register(new Item("name_tag", builder())); - public static final Item COMMAND_BLOCK_MINECART = register(new Item("command_block_minecart", builder().stackSize(1))); + public static final Item COMMAND_BLOCK_MINECART = register(new Item("command_block_minecart", builder().stackSize(1).rarity(Rarity.EPIC))); public static final Item MUTTON = register(new Item("mutton", builder())); public static final Item COOKED_MUTTON = register(new Item("cooked_mutton", builder())); public static final Item WHITE_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.WHITE_BANNER, Blocks.WHITE_WALL_BANNER)); @@ -1186,7 +1187,7 @@ public final class Items { public static final Item GREEN_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.GREEN_BANNER, Blocks.GREEN_WALL_BANNER)); public static final Item RED_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.RED_BANNER, Blocks.RED_WALL_BANNER)); public static final Item BLACK_BANNER = register(new BannerItem(builder().stackSize(16), Blocks.BLACK_BANNER, Blocks.BLACK_WALL_BANNER)); - public static final Item END_CRYSTAL = register(new Item("end_crystal", builder())); + public static final Item END_CRYSTAL = register(new Item("end_crystal", builder().rarity(Rarity.RARE))); public static final Item CHORUS_FRUIT = register(new Item("chorus_fruit", builder())); public static final Item POPPED_CHORUS_FRUIT = register(new Item("popped_chorus_fruit", builder())); public static final Item TORCHFLOWER_SEEDS = register(new BlockItem("torchflower_seeds", builder(), Blocks.TORCHFLOWER_CROP)); @@ -1194,52 +1195,52 @@ public final class Items { public static final Item BEETROOT = register(new Item("beetroot", builder())); public static final Item BEETROOT_SEEDS = register(new BlockItem("beetroot_seeds", builder(), Blocks.BEETROOTS)); public static final Item BEETROOT_SOUP = register(new Item("beetroot_soup", builder().stackSize(1))); - public static final Item DRAGON_BREATH = register(new Item("dragon_breath", builder())); + public static final Item DRAGON_BREATH = register(new Item("dragon_breath", builder().rarity(Rarity.UNCOMMON))); public static final Item SPLASH_POTION = register(new PotionItem("splash_potion", builder().stackSize(1))); public static final Item SPECTRAL_ARROW = register(new Item("spectral_arrow", builder())); public static final Item TIPPED_ARROW = register(new TippedArrowItem("tipped_arrow", builder())); public static final Item LINGERING_POTION = register(new PotionItem("lingering_potion", builder().stackSize(1))); public static final Item SHIELD = register(new ShieldItem("shield", builder().stackSize(1).maxDamage(336))); - public static final Item TOTEM_OF_UNDYING = register(new Item("totem_of_undying", builder().stackSize(1))); + public static final Item TOTEM_OF_UNDYING = register(new Item("totem_of_undying", builder().stackSize(1).rarity(Rarity.UNCOMMON))); public static final Item SHULKER_SHELL = register(new Item("shulker_shell", builder())); public static final Item IRON_NUGGET = register(new Item("iron_nugget", builder())); - public static final Item KNOWLEDGE_BOOK = register(new Item("knowledge_book", builder().stackSize(1))); - public static final Item DEBUG_STICK = register(new Item("debug_stick", builder().stackSize(1))); - public static final Item MUSIC_DISC_13 = register(new Item("music_disc_13", builder().stackSize(1))); - public static final Item MUSIC_DISC_CAT = register(new Item("music_disc_cat", builder().stackSize(1))); - public static final Item MUSIC_DISC_BLOCKS = register(new Item("music_disc_blocks", builder().stackSize(1))); - public static final Item MUSIC_DISC_CHIRP = register(new Item("music_disc_chirp", builder().stackSize(1))); - public static final Item MUSIC_DISC_CREATOR = register(new Item("music_disc_creator", builder().stackSize(1))); - public static final Item MUSIC_DISC_CREATOR_MUSIC_BOX = register(new Item("music_disc_creator_music_box", builder().stackSize(1))); - public static final Item MUSIC_DISC_FAR = register(new Item("music_disc_far", builder().stackSize(1))); - public static final Item MUSIC_DISC_MALL = register(new Item("music_disc_mall", builder().stackSize(1))); - public static final Item MUSIC_DISC_MELLOHI = register(new Item("music_disc_mellohi", builder().stackSize(1))); - public static final Item MUSIC_DISC_STAL = register(new Item("music_disc_stal", builder().stackSize(1))); - public static final Item MUSIC_DISC_STRAD = register(new Item("music_disc_strad", builder().stackSize(1))); - public static final Item MUSIC_DISC_WARD = register(new Item("music_disc_ward", builder().stackSize(1))); - public static final Item MUSIC_DISC_11 = register(new Item("music_disc_11", builder().stackSize(1))); - public static final Item MUSIC_DISC_WAIT = register(new Item("music_disc_wait", builder().stackSize(1))); - public static final Item MUSIC_DISC_OTHERSIDE = register(new Item("music_disc_otherside", builder().stackSize(1))); - public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1))); - public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1))); - public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1))); - public static final Item MUSIC_DISC_PRECIPICE = register(new Item("music_disc_precipice", builder().stackSize(1))); + public static final Item KNOWLEDGE_BOOK = register(new Item("knowledge_book", builder().stackSize(1).rarity(Rarity.EPIC))); + public static final Item DEBUG_STICK = register(new Item("debug_stick", builder().stackSize(1).rarity(Rarity.EPIC).glint(true))); + public static final Item MUSIC_DISC_13 = register(new Item("music_disc_13", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_CAT = register(new Item("music_disc_cat", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_BLOCKS = register(new Item("music_disc_blocks", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_CHIRP = register(new Item("music_disc_chirp", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_CREATOR = register(new Item("music_disc_creator", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_CREATOR_MUSIC_BOX = register(new Item("music_disc_creator_music_box", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_FAR = register(new Item("music_disc_far", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_MALL = register(new Item("music_disc_mall", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_MELLOHI = register(new Item("music_disc_mellohi", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_STAL = register(new Item("music_disc_stal", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_STRAD = register(new Item("music_disc_strad", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_WARD = register(new Item("music_disc_ward", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_11 = register(new Item("music_disc_11", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_WAIT = register(new Item("music_disc_wait", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_OTHERSIDE = register(new Item("music_disc_otherside", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_RELIC = register(new Item("music_disc_relic", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_5 = register(new Item("music_disc_5", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_PIGSTEP = register(new Item("music_disc_pigstep", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item MUSIC_DISC_PRECIPICE = register(new Item("music_disc_precipice", builder().stackSize(1).rarity(Rarity.RARE))); public static final Item DISC_FRAGMENT_5 = register(new Item("disc_fragment_5", builder())); - public static final Item TRIDENT = register(new Item("trident", builder().stackSize(1).maxDamage(250).attackDamage(9.0))); + public static final Item TRIDENT = register(new Item("trident", builder().stackSize(1).maxDamage(250).attackDamage(9.0).rarity(Rarity.EPIC))); public static final Item PHANTOM_MEMBRANE = register(new Item("phantom_membrane", builder())); public static final Item NAUTILUS_SHELL = register(new Item("nautilus_shell", builder())); - public static final Item HEART_OF_THE_SEA = register(new Item("heart_of_the_sea", builder())); + public static final Item HEART_OF_THE_SEA = register(new Item("heart_of_the_sea", builder().rarity(Rarity.UNCOMMON))); public static final Item CROSSBOW = register(new CrossbowItem("crossbow", builder().stackSize(1).maxDamage(465))); public static final Item SUSPICIOUS_STEW = register(new Item("suspicious_stew", builder().stackSize(1))); public static final Item LOOM = register(new BlockItem(builder(), Blocks.LOOM)); public static final Item FLOWER_BANNER_PATTERN = register(new Item("flower_banner_pattern", builder().stackSize(1))); - public static final Item CREEPER_BANNER_PATTERN = register(new Item("creeper_banner_pattern", builder().stackSize(1))); - public static final Item SKULL_BANNER_PATTERN = register(new Item("skull_banner_pattern", builder().stackSize(1))); - public static final Item MOJANG_BANNER_PATTERN = register(new Item("mojang_banner_pattern", builder().stackSize(1))); + public static final Item CREEPER_BANNER_PATTERN = register(new Item("creeper_banner_pattern", builder().stackSize(1).rarity(Rarity.UNCOMMON))); + public static final Item SKULL_BANNER_PATTERN = register(new Item("skull_banner_pattern", builder().stackSize(1).rarity(Rarity.UNCOMMON))); + public static final Item MOJANG_BANNER_PATTERN = register(new Item("mojang_banner_pattern", builder().stackSize(1).rarity(Rarity.EPIC))); public static final Item GLOBE_BANNER_PATTERN = register(new Item("globe_banner_pattern", builder().stackSize(1))); - public static final Item PIGLIN_BANNER_PATTERN = register(new Item("piglin_banner_pattern", builder().stackSize(1))); - public static final Item FLOW_BANNER_PATTERN = register(new Item("flow_banner_pattern", builder().stackSize(1))); - public static final Item GUSTER_BANNER_PATTERN = register(new Item("guster_banner_pattern", builder().stackSize(1))); + public static final Item PIGLIN_BANNER_PATTERN = register(new Item("piglin_banner_pattern", builder().stackSize(1).rarity(Rarity.UNCOMMON))); + public static final Item FLOW_BANNER_PATTERN = register(new Item("flow_banner_pattern", builder().stackSize(1).rarity(Rarity.RARE))); + public static final Item GUSTER_BANNER_PATTERN = register(new Item("guster_banner_pattern", builder().stackSize(1).rarity(Rarity.RARE))); public static final Item GOAT_HORN = register(new GoatHornItem("goat_horn", builder().stackSize(1))); public static final Item COMPOSTER = register(new BlockItem(builder(), Blocks.COMPOSTER)); public static final Item BARREL = register(new BlockItem(builder(), Blocks.BARREL)); diff --git a/core/src/main/java/org/geysermc/geyser/item/components/Rarity.java b/core/src/main/java/org/geysermc/geyser/item/components/Rarity.java new file mode 100644 index 000000000..6fa74ea35 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/item/components/Rarity.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 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.item.components; + +import lombok.Getter; + +@Getter +public enum Rarity { + COMMON("common", 'f'), + UNCOMMON("uncommon", 'e'), + RARE("rare", 'b'), + EPIC("epic", 'd'); + + private final String name; + private final char color; + + Rarity(final String name, char chatColor) { + this.name = name; + this.color = chatColor; + } + + private static final Rarity[] VALUES = values(); + + public static Rarity fromId(int id) { + return VALUES.length > id ? VALUES[id] : VALUES[0]; + } + +} diff --git a/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java b/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java index c5c0d2611..3c0caa60c 100644 --- a/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java +++ b/core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java @@ -25,18 +25,21 @@ package org.geysermc.geyser.item.enchantment; +import java.util.List; +import java.util.function.Function; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.nbt.NbtMap; import org.geysermc.geyser.inventory.item.BedrockEnchantment; -import org.geysermc.geyser.session.cache.tags.EnchantmentTag; -import org.geysermc.geyser.session.cache.tags.ItemTag; +import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.geyser.util.MinecraftKey; -import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; import java.util.HashSet; import java.util.Map; import java.util.Set; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.HolderSet; /** * @param description only populated if {@link #bedrockEnchantment()} is not null. @@ -44,28 +47,32 @@ import java.util.Set; */ public record Enchantment(String identifier, Set effects, - ItemTag supportedItems, + HolderSet supportedItems, int maxLevel, String description, int anvilCost, - @Nullable EnchantmentTag exclusiveSet, + HolderSet exclusiveSet, @Nullable BedrockEnchantment bedrockEnchantment) { - // Implementation note: I have a feeling the tags can be a list of items, because in vanilla they're HolderSet classes. - // I'm not sure how that's wired over the network, so we'll put it off. - public static Enchantment read(RegistryEntry entry) { - NbtMap data = entry.getData(); + public static Enchantment read(RegistryEntryContext context) { + NbtMap data = context.data(); Set effects = readEnchantmentComponents(data.getCompound("effects")); - String supportedItems = data.getString("supported_items").substring(1); // Remove '#' at beginning that indicates tag + + HolderSet supportedItems = readHolderSet(data.get("supported_items"), itemId -> Registries.JAVA_ITEM_IDENTIFIERS.getOrDefault(itemId.asString(), Items.AIR).javaId()); + int maxLevel = data.getInt("max_level"); int anvilCost = data.getInt("anvil_cost"); - String exclusiveSet = data.getString("exclusive_set", null); - EnchantmentTag exclusiveSetTag = exclusiveSet == null ? null : EnchantmentTag.ALL_ENCHANTMENT_TAGS.get(MinecraftKey.key(exclusiveSet.substring(1))); - BedrockEnchantment bedrockEnchantment = BedrockEnchantment.getByJavaIdentifier(entry.getId().asString()); + + HolderSet exclusiveSet = readHolderSet(data.getOrDefault("exclusive_set", null), context::getNetworkId); + + BedrockEnchantment bedrockEnchantment = BedrockEnchantment.getByJavaIdentifier(context.id().asString()); + + // TODO - description is a component. So if a hardcoded literal string is given, this will display normally on Java, + // but Geyser will attempt to lookup the literal string as translation - and will fail, displaying an empty string as enchantment name. String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(data) : null; - return new Enchantment(entry.getId().asString(), effects, ItemTag.ALL_ITEM_TAGS.get(MinecraftKey.key(supportedItems)), maxLevel, - description, anvilCost, exclusiveSetTag, bedrockEnchantment); + return new Enchantment(context.id().asString(), effects, supportedItems, maxLevel, + description, anvilCost, exclusiveSet, bedrockEnchantment); } private static Set readEnchantmentComponents(NbtMap effects) { @@ -77,4 +84,24 @@ public record Enchantment(String identifier, } return Set.copyOf(components); // Also ensures any empty sets are consolidated } + + // TODO holder set util? + private static HolderSet readHolderSet(@Nullable Object holderSet, Function keyIdMapping) { + if (holderSet == null) { + return new HolderSet(new int[]{}); + } + + if (holderSet instanceof String stringTag) { + // Tag + if (stringTag.startsWith("#")) { + return new HolderSet(Key.key(stringTag.substring(1))); // Remove '#' at beginning that indicates tag + } else { + return new HolderSet(new int[]{keyIdMapping.apply(Key.key(stringTag))}); + } + } else if (holderSet instanceof List list) { + // Assume the list is a list of strings + return new HolderSet(list.stream().map(o -> (String) o).map(Key::key).map(keyIdMapping).mapToInt(Integer::intValue).toArray()); + } + throw new IllegalArgumentException("Holder set must either be a tag, a string ID or a list of string IDs"); + } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/Item.java b/core/src/main/java/org/geysermc/geyser/item/type/Item.java index 362b760c7..57538565a 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/Item.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/Item.java @@ -35,6 +35,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.inventory.item.BedrockEnchantment; import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.item.components.Rarity; import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.type.ItemMapping; @@ -63,12 +64,16 @@ public class Item { private final int stackSize; private final int attackDamage; private final int maxDamage; + private final Rarity rarity; + private final boolean glint; public Item(String javaIdentifier, Builder builder) { this.javaIdentifier = MinecraftKey.key(javaIdentifier).asString().intern(); this.stackSize = builder.stackSize; this.maxDamage = builder.maxDamage; this.attackDamage = builder.attackDamage; + this.rarity = builder.rarity; + this.glint = builder.glint; } public String javaIdentifier() { @@ -91,6 +96,14 @@ public class Item { return stackSize; } + public Rarity rarity() { + return rarity; + } + + public boolean glint() { + return glint; + } + public boolean isValidRepairItem(Item other) { return false; } @@ -275,6 +288,8 @@ public class Item { private int stackSize = 64; private int maxDamage; private int attackDamage; + private Rarity rarity = Rarity.COMMON; + private boolean glint = false; public Builder stackSize(int stackSize) { this.stackSize = stackSize; @@ -292,6 +307,16 @@ public class Item { return this; } + public Builder rarity(Rarity rarity) { + this.rarity = rarity; + return this; + } + + public Builder glint(boolean glintOverride) { + this.glint = glintOverride; + return this; + } + private Builder() { } } diff --git a/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java b/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java index 6112dc6cf..dd0f4215e 100644 --- a/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java +++ b/core/src/main/java/org/geysermc/geyser/level/JavaDimension.java @@ -26,7 +26,7 @@ package org.geysermc.geyser.level; import org.cloudburstmc.nbt.NbtMap; -import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; +import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; /** * Represents the information we store from the current Java dimension @@ -35,8 +35,8 @@ import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; */ public record JavaDimension(int minY, int maxY, boolean piglinSafe, double worldCoordinateScale) { - public static JavaDimension read(RegistryEntry entry) { - NbtMap dimension = entry.getData(); + public static JavaDimension read(RegistryEntryContext entry) { + NbtMap dimension = entry.data(); int minY = dimension.getInt("min_y"); int maxY = dimension.getInt("height"); // Logical height can be ignored probably - seems to be for artificial limits like the Nether. diff --git a/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java b/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java index 156a62cd1..b00dc9f98 100644 --- a/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java +++ b/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java @@ -27,13 +27,13 @@ package org.geysermc.geyser.level; import org.cloudburstmc.nbt.NbtMap; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.geyser.translator.text.MessageTranslator; -import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; public record JukeboxSong(String soundEvent, String description) { - public static JukeboxSong read(RegistryEntry entry) { - NbtMap data = entry.getData(); + public static JukeboxSong read(RegistryEntryContext context) { + NbtMap data = context.data(); Object soundEventObject = data.get("sound_event"); String soundEvent; if (soundEventObject instanceof NbtMap map) { @@ -42,7 +42,7 @@ public record JukeboxSong(String soundEvent, String description) { soundEvent = string; } else { soundEvent = ""; - GeyserImpl.getInstance().getLogger().debug("Sound event for " + entry.getId() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject); + GeyserImpl.getInstance().getLogger().debug("Sound event for " + context.id() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject); } String description = MessageTranslator.deserializeDescription(data); return new JukeboxSong(soundEvent, description); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java index c536d739c..2c033edc7 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/CreativeItemRegistryPopulator.java @@ -49,8 +49,6 @@ import java.util.function.Consumer; public class CreativeItemRegistryPopulator { private static final List> JAVA_ONLY_ITEM_FILTER = List.of( - // Just shows an empty texture; either way it doesn't exist in the creative menu on Java - (identifier, data) -> identifier.equals("minecraft:debug_stick"), // Bedrock-only as its own item (identifier, data) -> identifier.equals("minecraft:empty_map") && data == 2, // Bedrock-only banner patterns @@ -103,16 +101,8 @@ public class CreativeItemRegistryPopulator { } GeyserBedrockBlock blockDefinition = null; - JsonNode blockRuntimeIdNode = itemNode.get("blockRuntimeId"); JsonNode blockStateNode; - if (blockRuntimeIdNode != null) { - bedrockBlockRuntimeId = blockRuntimeIdNode.asInt(); - if (bedrockBlockRuntimeId == 0 && !identifier.equals("minecraft:blue_candle")) { // FIXME - bedrockBlockRuntimeId = -1; - } - - blockDefinition = bedrockBlockRuntimeId == -1 ? null : blockMappings.getDefinition(bedrockBlockRuntimeId); - } else if ((blockStateNode = itemNode.get("block_state_b64")) != null) { + if ((blockStateNode = itemNode.get("block_state_b64")) != null) { byte[] bytes = Base64.getDecoder().decode(blockStateNode.asText()); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); try { diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java index e19066462..aad5e494d 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/ItemRegistryPopulator.java @@ -58,6 +58,8 @@ import org.geysermc.geyser.api.item.custom.NonVanillaCustomItemData; import org.geysermc.geyser.inventory.item.StoredItemMappings; import org.geysermc.geyser.item.GeyserCustomMappingData; import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.item.components.Rarity; +import org.geysermc.geyser.item.type.BlockItem; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.Registries; @@ -252,10 +254,7 @@ public class ItemRegistryPopulator { } else { // Try to get an example block runtime ID from the creative contents packet, for Bedrock identifier obtaining int aValidBedrockBlockId = blacklistedIdentifiers.getOrDefault(bedrockIdentifier, customBlockItemOverride != null ? customBlockItemOverride.getRuntimeId() : -1); - if (aValidBedrockBlockId == -1 && customBlockItemOverride == null) { - // Fallback - bedrockBlock = blockMappings.getBedrockBlock(firstBlockRuntimeId); - } else { + if (aValidBedrockBlockId != -1 || customBlockItemOverride != null) { // As of 1.16.220, every item requires a block runtime ID attached to it. // This is mostly for identifying different blocks with the same item ID - wool, slabs, some walls. // However, in order for some visuals and crafting to work, we need to send the first matching block state @@ -266,7 +265,7 @@ public class ItemRegistryPopulator { boolean firstPass = true; // Block states are all grouped together. In the mappings, we store the first block runtime ID in order, // and the last, if relevant. We then iterate over all those values and get their Bedrock equivalents - Integer lastBlockRuntimeId = entry.getValue().getLastBlockRuntimeId() == null ? firstBlockRuntimeId : entry.getValue().getLastBlockRuntimeId(); + int lastBlockRuntimeId = entry.getValue().getLastBlockRuntimeId() == null ? firstBlockRuntimeId : entry.getValue().getLastBlockRuntimeId(); for (int i = firstBlockRuntimeId; i <= lastBlockRuntimeId; i++) { GeyserBedrockBlock bedrockBlockRuntimeId = blockMappings.getVanillaBedrockBlock(i); NbtMap blockTag = bedrockBlockRuntimeId.getState(); @@ -402,9 +401,10 @@ public class ItemRegistryPopulator { } } - if (javaOnlyItems.contains(javaItem)) { + if (javaOnlyItems.contains(javaItem) || javaItem.rarity() != Rarity.COMMON) { // These items don't exist on Bedrock, so set up a variable that indicates they should have custom names - mappingBuilder = mappingBuilder.translationString((bedrockBlock != null ? "block." : "item.") + entry.getKey().replace(":", ".")); + // Or, ensure that we are translating these at all times to account for rarity colouring + mappingBuilder = mappingBuilder.translationString((javaItem instanceof BlockItem ? "block." : "item.") + entry.getKey().replace(":", ".")); GeyserImpl.getInstance().getLogger().debug("Adding " + entry.getKey() + " as an item that needs to be translated."); } 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 3121af369..a393d461d 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,6 +27,8 @@ 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.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; @@ -45,6 +47,7 @@ import org.geysermc.geyser.level.JukeboxSong; import org.geysermc.geyser.level.PaintingType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.cache.registry.JavaRegistry; +import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry; import org.geysermc.geyser.text.TextDecoration; import org.geysermc.geyser.translator.level.BiomeTranslator; @@ -59,7 +62,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.ToIntFunction; @@ -76,16 +78,16 @@ public final class RegistryCache { private static final Map>> REGISTRIES = new HashMap<>(); static { - register("chat_type", cache -> cache.chatTypes, ($, entry) -> TextDecoration.readChatType(entry)); - register("dimension_type", cache -> cache.dimensions, ($, entry) -> JavaDimension.read(entry)); - register("enchantment", cache -> cache.enchantments, ($, entry) -> Enchantment.read(entry)); - register("jukebox_song", cache -> cache.jukeboxSongs, ($, entry) -> JukeboxSong.read(entry)); - register("painting_variant", cache -> cache.paintings, ($, entry) -> PaintingType.getByName(entry.getId())); + register("chat_type", cache -> cache.chatTypes, TextDecoration::readChatType); + register("dimension_type", cache -> cache.dimensions, JavaDimension::read); + register("enchantment", cache -> cache.enchantments, Enchantment::read); + register("jukebox_song", cache -> cache.jukeboxSongs, JukeboxSong::read); + register("painting_variant", cache -> cache.paintings, context -> PaintingType.getByName(context.id())); 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())); - register("wolf_variant", cache -> cache.wolfVariants, ($, entry) -> WolfEntity.BuiltInWolfVariant.getByJavaIdentifier(entry.getId().asString())); + register("banner_pattern", cache -> cache.bannerPatterns, context -> BannerPattern.getByJavaIdentifier(context.id())); + register("wolf_variant", cache -> cache.wolfVariants, context -> WolfEntity.BuiltInWolfVariant.getByJavaIdentifier(context.id().asString())); // Load from MCProtocolLib's classloader NbtMap tag = MinecraftProtocol.loadNetworkCodec(); @@ -149,25 +151,35 @@ public final class RegistryCache { * @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) { - Key key = MinecraftKey.key(registry); - REGISTRIES.put(key, (registryCache, entries) -> { + private static void register(String registry, Function> localCacheFunction, Function reader) { + Key registryKey = MinecraftKey.key(registry); + REGISTRIES.put(registryKey, (registryCache, entries) -> { Map localRegistry = null; JavaRegistry localCache = localCacheFunction.apply(registryCache); // Clear each local cache every time a new registry entry is given to us // (e.g. proxy server switches) + + // Store each of the entries resource location IDs and their respective network ID, + // used for the key mapper that's currently only used by the Enchantment class + Object2IntMap entryIdMap = new Object2IntOpenHashMap<>(); + for (int i = 0; i < entries.size(); i++) { + entryIdMap.put(entries.get(i).getId(), i); + } + List builder = new ArrayList<>(entries.size()); for (int i = 0; i < entries.size(); i++) { RegistryEntry entry = entries.get(i); // If the data is null, that's the server telling us we need to use our default values. if (entry.getData() == null) { if (localRegistry == null) { // Lazy initialize - localRegistry = DEFAULTS.get(key); + localRegistry = DEFAULTS.get(registryKey); } entry = new RegistryEntry(entry.getId(), localRegistry.get(entry.getId())); } + + RegistryEntryContext context = new RegistryEntryContext(entry, entryIdMap, registryCache.session); // This is what Geyser wants to keep as a value for this registry. - T cacheEntry = reader.apply(registryCache.session, entry); + T cacheEntry = reader.apply(context); builder.add(i, cacheEntry); } localCache.reset(builder); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java index c8bfc7eed..f4d69dcdb 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/TagCache.java @@ -130,8 +130,12 @@ public final class TagCache { return contains(values, item.javaId()); } - public int[] get(EnchantmentTag tag) { - return this.enchantments[tag.ordinal()]; + public int[] get(ItemTag itemTag) { + return this.items[itemTag.ordinal()]; + } + + public int[] get(EnchantmentTag enchantmentTag) { + return this.enchantments[enchantmentTag.ordinal()]; } private static boolean contains(int[] array, int i) { diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/RegistryEntryContext.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/RegistryEntryContext.java new file mode 100644 index 000000000..415890d95 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/RegistryEntryContext.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 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.Object2IntMap; +import java.util.Map; +import net.kyori.adventure.key.Key; +import org.cloudburstmc.nbt.NbtMap; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; + +/** + * Used to store context around a single registry entry when reading said entry's NBT. + * + * @param entry the registry entry being read. + * @param keyIdMap a map for each of the resource location's in the registry and their respective network IDs. + * @param session the Geyser session. + */ +public record RegistryEntryContext(RegistryEntry entry, Object2IntMap keyIdMap, GeyserSession session) { + + public int getNetworkId(Key registryKey) { + return keyIdMap.getOrDefault(registryKey, 0); + } + + public Key id() { + return entry.getId(); + } + + // Not annotated as nullable because data should never be null here + public NbtMap data() { + return entry.getData(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java b/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java index ab9e2b5ed..94aec22ef 100644 --- a/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java +++ b/core/src/main/java/org/geysermc/geyser/text/TextDecoration.java @@ -29,7 +29,7 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; import org.cloudburstmc.nbt.NbtMap; import org.cloudburstmc.nbt.NbtType; -import org.geysermc.mcprotocollib.protocol.data.game.RegistryEntry; +import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType; import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatTypeDecoration; @@ -43,11 +43,11 @@ public record TextDecoration(String translationKey, List parameters, throw new UnsupportedOperationException(); } - public static ChatType readChatType(RegistryEntry entry) { + public static ChatType readChatType(RegistryEntryContext context) { // Note: The ID is NOT ALWAYS THE SAME! ViaVersion as of 1.19 adds two registry entries that do NOT match vanilla. // (This note has been passed around through several classes and iterations. It stays as a warning // to anyone that dares to try and hardcode registry IDs.) - NbtMap tag = entry.getData(); + NbtMap tag = context.data(); NbtMap chat = tag.getCompound("chat", null); if (chat != null) { String translationKey = chat.getString("translation_key"); diff --git a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java index abe39f177..6a781dcb8 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/item/ItemTranslator.java @@ -33,7 +33,9 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtList; import org.cloudburstmc.nbt.NbtMap; +import org.cloudburstmc.nbt.NbtMapBuilder; import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition; import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; @@ -41,6 +43,7 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.block.custom.CustomBlockData; import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.item.Items; +import org.geysermc.geyser.item.components.Rarity; import org.geysermc.geyser.item.type.Item; import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.BlockRegistries; @@ -145,7 +148,20 @@ public final class ItemTranslator { if (components.get(DataComponentType.HIDE_TOOLTIP) != null) hideTooltips = true; } - String customName = getCustomName(session, components, bedrockItem); + Rarity rarity = javaItem.rarity(); + boolean enchantmentGlint = javaItem.glint(); + if (components != null) { + Integer rarityIndex = components.get(DataComponentType.RARITY); + if (rarityIndex != null) { + rarity = Rarity.fromId(rarityIndex); + } + Boolean enchantmentGlintOverride = components.get(DataComponentType.ENCHANTMENT_GLINT_OVERRIDE); + if (enchantmentGlintOverride != null) { + enchantmentGlint = enchantmentGlintOverride; + } + } + + String customName = getCustomName(session, components, bedrockItem, rarity.getColor()); if (customName != null) { nbtBuilder.setCustomName(customName); } @@ -162,6 +178,12 @@ public final class ItemTranslator { addAdvancedTooltips(components, nbtBuilder, javaItem, session.locale()); } + // Add enchantment override. We can't remove it - enchantments would stop showing - but we can add it. + if (enchantmentGlint) { + NbtMapBuilder nbtMapBuilder = nbtBuilder.getOrCreateNbt(); + nbtMapBuilder.putIfAbsent("ench", NbtList.EMPTY); + } + ItemData.Builder builder = javaItem.translateToBedrock(count, components, bedrockItem, session.getItemMappings()); // Finalize the Bedrock NBT builder.tag(nbtBuilder.build()); @@ -401,16 +423,6 @@ public final class ItemTranslator { } } - /** - * Translates the display name of the item - * @param session the Bedrock client's session - * @param components the components to translate - * @param mapping the item entry, in case it requires translation - */ - public static String getCustomName(GeyserSession session, DataComponents components, ItemMapping mapping) { - return getCustomName(session, components, mapping, 'f'); - } - /** * @param translationColor if this item is not available on Java, the color that the new name should be. * Normally, this should just be white, but for shulker boxes this should be gray.