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 98450f476..664c956c3 100644 --- a/core/src/main/java/org/geysermc/geyser/item/Items.java +++ b/core/src/main/java/org/geysermc/geyser/item/Items.java @@ -66,11 +66,6 @@ import static org.geysermc.geyser.item.type.Item.builder; */ @SuppressWarnings("unused") public final class Items { - - static { - // Load data components here - } - public static final Item AIR = register(new Item("air", builder())); public static final Item STONE = register(new BlockItem(builder(), Blocks.STONE)); public static final Item GRANITE = register(new BlockItem(builder(), Blocks.GRANITE)); 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 fde742efa..9b2603284 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 @@ -40,6 +40,7 @@ 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.Registries; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; import org.geysermc.geyser.session.GeyserSession; @@ -65,12 +66,9 @@ public class Item { private final int attackDamage; private final DataComponents baseComponents; // unmodifiable - private final List enchantmentGlintPresent = List.of(Items.ENCHANTED_GOLDEN_APPLE, Items.EXPERIENCE_BOTTLE, Items.WRITTEN_BOOK, - Items.NETHER_STAR, Items.ENCHANTED_BOOK, Items.END_CRYSTAL); - public Item(String javaIdentifier, Builder builder) { this.javaIdentifier = MinecraftKey.key(javaIdentifier); - this.baseComponents = builder.components; + this.baseComponents = builder.components == null ? Registries.DEFAULT_DATA_COMPONENTS.get(javaId) : builder.components; this.attackDamage = builder.attackDamage; } @@ -297,7 +295,7 @@ public class Item { } public static Builder builder() { - return new Builder().components(new DataComponents(ImmutableMap.of())); // TODO actually set components here + return new Builder(); } public static final class Builder { @@ -315,10 +313,6 @@ public class Item { return this; } - public DataComponents components() { - return new DataComponents(ImmutableMap.copyOf(components.getDataComponents())); - } - private Builder() { } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index 61bb42454..fc41275ae 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -47,6 +47,7 @@ import org.geysermc.geyser.registry.loader.RegistryLoaders; import org.geysermc.geyser.registry.loader.SoundEventsRegistryLoader; import org.geysermc.geyser.registry.loader.SoundRegistryLoader; import org.geysermc.geyser.registry.loader.SoundTranslatorRegistryLoader; +import org.geysermc.geyser.registry.populator.DataComponentRegistryPopulator; import org.geysermc.geyser.registry.populator.ItemRegistryPopulator; import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; import org.geysermc.geyser.registry.populator.TagRegistryPopulator; @@ -60,6 +61,7 @@ import org.geysermc.geyser.translator.sound.SoundInteractionTranslator; import org.geysermc.geyser.translator.sound.SoundTranslator; import org.geysermc.mcprotocollib.network.packet.Packet; import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType; import org.geysermc.mcprotocollib.protocol.data.game.level.event.LevelEvent; import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType; @@ -139,6 +141,8 @@ public final class Registries { */ public static final SimpleMappedRegistry JAVA_ITEM_IDENTIFIERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); + public static final ListRegistry DEFAULT_DATA_COMPONENTS = ListRegistry.create(RegistryLoaders.empty(ArrayList::new)); + /** * A versioned registry which holds {@link ItemMappings} for each version. These item mappings contain * primarily Bedrock version-specific data. @@ -209,6 +213,7 @@ public final class Registries { public static void populate() { PacketRegistryPopulator.populate(); + DataComponentRegistryPopulator.populate(); ItemRegistryPopulator.populate(); TagRegistryPopulator.populate(); diff --git a/core/src/main/java/org/geysermc/geyser/registry/populator/DataComponentRegistryPopulator.java b/core/src/main/java/org/geysermc/geyser/registry/populator/DataComponentRegistryPopulator.java new file mode 100644 index 000000000..0c89760b1 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/registry/populator/DataComponentRegistryPopulator.java @@ -0,0 +1,89 @@ +/* + * 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.registry.populator; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.geysermc.geyser.GeyserBootstrap; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.registry.Registries; +import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponent; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemCodecHelper; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class DataComponentRegistryPopulator { + + public static void populate() { + GeyserBootstrap bootstrap = GeyserImpl.getInstance().getBootstrap(); + List defaultComponents; + try (InputStream stream = bootstrap.getResourceOrThrow("mappings/item_data_components.json")) { + JsonElement rootElement = JsonParser.parseReader(new InputStreamReader(stream)); + JsonArray jsonArray = rootElement.getAsJsonArray(); + + defaultComponents = new ObjectArrayList<>(jsonArray.size()); + + for (JsonElement element : jsonArray) { + JsonObject entryObject = element.getAsJsonObject(); + JsonObject components = entryObject.getAsJsonObject("components"); + + Map, DataComponent> map = new HashMap<>(); + + for (Map.Entry componentEntry : components.entrySet()) { + String encodedValue = componentEntry.getValue().getAsString(); + byte[] bytes = Base64.getDecoder().decode(encodedValue); + ByteBuf buf = Unpooled.wrappedBuffer(bytes); + MinecraftCodecHelper helper = new MinecraftCodecHelper(); + int varInt = helper.readVarInt(buf); + DataComponentType dataComponentType = DataComponentType.from(varInt); + DataComponent dataComponent = dataComponentType.readDataComponent(ItemCodecHelper.INSTANCE, buf); + + map.put(dataComponentType, dataComponent); + } + + defaultComponents.add(new DataComponents(ImmutableMap.copyOf(map))); + } + } catch (Exception e) { + throw new AssertionError("Unable to load or parse components", e); + } + + Registries.DEFAULT_DATA_COMPONENTS.set(defaultComponents); + } +} 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 376957d67..54bbe086c 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 @@ -102,6 +102,9 @@ public final class ItemTranslator { SLOT_NAMES.put(ItemAttributeModifiers.EquipmentSlotGroup.BODY, "body"); } + private final static List GLINT_PRESENT = List.of(Items.ENCHANTED_GOLDEN_APPLE, Items.EXPERIENCE_BOTTLE, Items.WRITTEN_BOOK, + Items.NETHER_STAR, Items.ENCHANTED_BOOK, Items.END_CRYSTAL); + private ItemTranslator() { } @@ -180,7 +183,7 @@ public final class ItemTranslator { } // Add enchantment override. We can't remove it - enchantments would stop showing - but we can add it. - if (components.getOrDefault(DataComponentType.ENCHANTMENT_GLINT_OVERRIDE, false)) { + if (components.getOrDefault(DataComponentType.ENCHANTMENT_GLINT_OVERRIDE, false) && !GLINT_PRESENT.contains(javaItem)) { NbtMapBuilder nbtMapBuilder = nbtBuilder.getOrCreateNbt(); nbtMapBuilder.putIfAbsent("ench", NbtList.EMPTY); }