diff --git a/core/src/main/java/org/geysermc/geyser/inventory/item/GeyserInstrument.java b/core/src/main/java/org/geysermc/geyser/inventory/item/GeyserInstrument.java new file mode 100644 index 00000000000..9983a8e902e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/inventory/item/GeyserInstrument.java @@ -0,0 +1,166 @@ +/* + * 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.inventory.item; + +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.cloudburstmc.nbt.NbtMap; +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.translator.text.MessageTranslator; +import org.geysermc.geyser.util.MinecraftKey; +import org.geysermc.geyser.util.SoundUtils; +import org.geysermc.mcprotocollib.protocol.data.game.Holder; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Instrument; +import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; + +import java.util.Locale; + +public interface GeyserInstrument { + + static GeyserInstrument read(RegistryEntryContext context) { + NbtMap data = context.data(); + String soundEvent = SoundUtils.readSoundEvent(data, "instrument " + context.id()); + float range = data.getFloat("range"); + String description = MessageTranslator.deserializeDescriptionForTooltip(context.session(), data); + BedrockInstrument bedrockInstrument = BedrockInstrument.getByJavaIdentifier(context.id()); + return new GeyserInstrument.Impl(soundEvent, range, description, bedrockInstrument); + } + + String soundEvent(); + + float range(); + + /** + * In Bedrock format + */ + String description(); + + BedrockInstrument bedrockInstrument(); + + /** + * @return the ID of the Bedrock counterpart for this instrument. If there is none ({@link #bedrockInstrument()} is null), then -1 is returned. + */ + default int bedrockId() { + BedrockInstrument bedrockInstrument = bedrockInstrument(); + if (bedrockInstrument != null) { + return bedrockInstrument.ordinal(); + } + return -1; + } + + /** + * @return the ID of the Java counterpart for the given Bedrock ID. If an invalid Bedrock ID was given, or there is no counterpart, -1 is returned. + */ + static int bedrockIdToJava(GeyserSession session, int id) { + JavaRegistry instruments = session.getRegistryCache().instruments(); + BedrockInstrument bedrockInstrument = BedrockInstrument.getByBedrockId(id); + if (bedrockInstrument != null) { + for (int i = 0; i < instruments.values().size(); i++) { + GeyserInstrument instrument = instruments.byId(i); + if (instrument.bedrockInstrument() == bedrockInstrument) { + return i; + } + } + } + return -1; + } + + static GeyserInstrument fromHolder(GeyserSession session, Holder holder) { + if (holder.isId()) { + return session.getRegistryCache().instruments().byId(holder.id()); + } + Instrument custom = holder.custom(); + return new Wrapper(custom, session.locale()); + } + + record Wrapper(Instrument instrument, String locale) implements GeyserInstrument { + @Override + public String soundEvent() { + return instrument.getSoundEvent().getName(); + } + + @Override + public float range() { + return instrument.getRange(); + } + + @Override + public String description() { + return MessageTranslator.convertMessageForTooltip(instrument.getDescription(), locale); + } + + @Override + public BedrockInstrument bedrockInstrument() { + if (instrument.getSoundEvent() instanceof BuiltinSound) { + return BedrockInstrument.getByJavaIdentifier(MinecraftKey.key(instrument.getSoundEvent().getName())); + } + // Probably custom + return null; + } + } + + record Impl(String soundEvent, float range, String description, @Nullable BedrockInstrument bedrockInstrument) implements GeyserInstrument { + } + + /** + * Each vanilla instrument on Bedrock, ordered in their network IDs. + */ + enum BedrockInstrument { + PONDER, + SING, + SEEK, + FEEL, + ADMIRE, + CALL, + YEARN, + DREAM; + + private static final BedrockInstrument[] VALUES = values(); + private final Key javaIdentifier; + + BedrockInstrument() { + this.javaIdentifier = MinecraftKey.key(this.name().toLowerCase(Locale.ENGLISH) + "_goat_horn"); + } + + public static @Nullable BedrockInstrument getByJavaIdentifier(Key javaIdentifier) { + for (BedrockInstrument instrument : VALUES) { + if (instrument.javaIdentifier.equals(javaIdentifier)) { + return instrument; + } + } + return null; + } + + public static @Nullable BedrockInstrument getByBedrockId(int bedrockId) { + if (bedrockId >= 0 && bedrockId < VALUES.length) { + return VALUES[bedrockId]; + } + return null; + } + } +} 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 9088d962648..e0b4f6e0fdc 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 @@ -41,7 +41,7 @@ import java.util.Set; /** - * @param description only populated if {@link #bedrockEnchantment()} is not null. + * @param description only populated if {@link #bedrockEnchantment()} is null. * @param anvilCost also as a rarity multiplier */ public record Enchantment(String identifier, @@ -66,8 +66,6 @@ public static Enchantment read(RegistryEntryContext context) { 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(context.session(), data) : null; return new Enchantment(context.id().asString(), effects, supportedItems, maxLevel, diff --git a/core/src/main/java/org/geysermc/geyser/item/type/ArrowItem.java b/core/src/main/java/org/geysermc/geyser/item/type/ArrowItem.java index 4e4f1830e0d..b2d3737d8ab 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/ArrowItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/ArrowItem.java @@ -32,6 +32,7 @@ import org.geysermc.geyser.item.Items; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.PotionContents; @@ -41,9 +42,9 @@ public ArrowItem(String javaIdentifier, Builder builder) { } @Override - public @NonNull GeyserItemStack translateToJava(@NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { + public @NonNull GeyserItemStack translateToJava(GeyserSession session, @NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { Potion potion = Potion.getByTippedArrowDamage(itemData.getDamage()); - GeyserItemStack itemStack = super.translateToJava(itemData, mapping, mappings); + GeyserItemStack itemStack = super.translateToJava(session, itemData, mapping, mappings); if (potion != null) { itemStack = Items.TIPPED_ARROW.newItemStack(itemStack.getAmount(), itemStack.getComponents()); PotionContents contents = potion.toComponent(); diff --git a/core/src/main/java/org/geysermc/geyser/item/type/CompassItem.java b/core/src/main/java/org/geysermc/geyser/item/type/CompassItem.java index 712e75a238b..1c0ec0d5fa5 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/CompassItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/CompassItem.java @@ -43,11 +43,11 @@ public CompassItem(String javaIdentifier, Builder builder) { } @Override - public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { + public ItemData.Builder translateToBedrock(GeyserSession session, int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { if (isLodestoneCompass(components)) { - return super.translateToBedrock(count, components, mappings.getLodestoneCompass(), mappings); + return super.translateToBedrock(session, count, components, mappings.getLodestoneCompass(), mappings); } - return super.translateToBedrock(count, components, mapping, mappings); + return super.translateToBedrock(session, count, components, mapping, mappings); } @Override @@ -78,12 +78,12 @@ private boolean isLodestoneCompass(@Nullable DataComponents components) { } @Override - public @NonNull GeyserItemStack translateToJava(@NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { + public @NonNull GeyserItemStack translateToJava(GeyserSession session, @NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { if (mapping.getBedrockIdentifier().equals("minecraft:lodestone_compass")) { // Revert the entry back to the compass mapping = mappings.getStoredItems().compass(); } - return super.translateToJava(itemData, mapping, mappings); + return super.translateToJava(session, itemData, mapping, mappings); } } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/FilledMapItem.java b/core/src/main/java/org/geysermc/geyser/item/type/FilledMapItem.java index e571a796ae3..07a0ad13359 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/FilledMapItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/FilledMapItem.java @@ -28,6 +28,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -37,8 +38,8 @@ public FilledMapItem(String javaIdentifier, Builder builder) { } @Override - public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { - ItemData.Builder builder = super.translateToBedrock(count, components, mapping, mappings); + public ItemData.Builder translateToBedrock(GeyserSession session, int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { + ItemData.Builder builder = super.translateToBedrock(session, count, components, mapping, mappings); if (components == null) { // This is a fallback for maps with no nbt (Change added back in June 2020; is it needed in 2023?) //return builder.tag(NbtMap.builder().putInt("map", 0).build()); TODO if this is *still* broken, let's move it to translateComponentsToBedrock diff --git a/core/src/main/java/org/geysermc/geyser/item/type/GoatHornItem.java b/core/src/main/java/org/geysermc/geyser/item/type/GoatHornItem.java index d0e85ec5292..9af07a40e68 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/GoatHornItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/GoatHornItem.java @@ -28,8 +28,11 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData; import org.geysermc.geyser.inventory.GeyserItemStack; +import org.geysermc.geyser.inventory.item.GeyserInstrument; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.translator.item.BedrockItemBuilder; import org.geysermc.mcprotocollib.protocol.data.game.Holder; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -41,24 +44,45 @@ public GoatHornItem(String javaIdentifier, Builder builder) { } @Override - public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { - ItemData.Builder builder = super.translateToBedrock(count, components, mapping, mappings); + public ItemData.Builder translateToBedrock(GeyserSession session, int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { + ItemData.Builder builder = super.translateToBedrock(session, count, components, mapping, mappings); if (components == null) { return builder; } - Holder instrument = components.get(DataComponentType.INSTRUMENT); - if (instrument != null && instrument.isId()) { - builder.damage(instrument.id()); + + Holder holder = components.get(DataComponentType.INSTRUMENT); + if (holder != null) { + GeyserInstrument instrument = GeyserInstrument.fromHolder(session, holder); + int bedrockId = instrument.bedrockId(); + if (bedrockId >= 0) { + builder.damage(bedrockId); + } } + return builder; } @Override - public @NonNull GeyserItemStack translateToJava(@NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { - GeyserItemStack itemStack = super.translateToJava(itemData, mapping, mappings); + public void translateComponentsToBedrock(@NonNull GeyserSession session, @NonNull DataComponents components, @NonNull BedrockItemBuilder builder) { + super.translateComponentsToBedrock(session, components, builder); + + Holder holder = components.get(DataComponentType.INSTRUMENT); + if (holder != null && components.get(DataComponentType.HIDE_TOOLTIP) == null + && components.get(DataComponentType.HIDE_ADDITIONAL_TOOLTIP) == null) { + GeyserInstrument instrument = GeyserInstrument.fromHolder(session, holder); + if (instrument.bedrockInstrument() == null) { + builder.getOrCreateLore().add(instrument.description()); + } + } + } + + @Override + public @NonNull GeyserItemStack translateToJava(GeyserSession session, @NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { + GeyserItemStack itemStack = super.translateToJava(session, itemData, mapping, mappings); int damage = itemData.getDamage(); - itemStack.getOrCreateComponents().put(DataComponentType.INSTRUMENT, Holder.ofId(damage)); + // This could cause an issue since -1 is returned for non-vanilla goat horns + itemStack.getOrCreateComponents().put(DataComponentType.INSTRUMENT, Holder.ofId(GeyserInstrument.bedrockIdToJava(session, damage))); return itemStack; } 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 a8a477025bb..249936e5afa 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 @@ -115,7 +115,7 @@ public String translationKey() { /* Translation methods to Bedrock and back */ - public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { + public ItemData.Builder translateToBedrock(GeyserSession session, int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { if (this == Items.AIR || count <= 0) { // Return, essentially, air return ItemData.builder(); @@ -130,7 +130,7 @@ public ItemData.Builder translateToBedrock(int count, DataComponents components, return builder; } - public @NonNull GeyserItemStack translateToJava(@NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { + public @NonNull GeyserItemStack translateToJava(GeyserSession session, @NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { return GeyserItemStack.of(javaId, itemData.getCount()); } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/OminousBottleItem.java b/core/src/main/java/org/geysermc/geyser/item/type/OminousBottleItem.java index 815f7141933..92a8d726d89 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/OminousBottleItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/OminousBottleItem.java @@ -31,6 +31,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -40,8 +41,8 @@ public OminousBottleItem(String javaIdentifier, Builder builder) { } @Override - public ItemData.Builder translateToBedrock(int count, @Nullable DataComponents components, ItemMapping mapping, ItemMappings mappings) { - var builder = super.translateToBedrock(count, components, mapping, mappings); + public ItemData.Builder translateToBedrock(GeyserSession session, int count, @Nullable DataComponents components, ItemMapping mapping, ItemMappings mappings) { + var builder = super.translateToBedrock(session, count, components, mapping, mappings); if (components == null) { // Level 1 ominous bottle is null components - Java 1.21. return builder; @@ -54,9 +55,9 @@ public ItemData.Builder translateToBedrock(int count, @Nullable DataComponents c } @Override - public @NonNull GeyserItemStack translateToJava(@NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { + public @NonNull GeyserItemStack translateToJava(GeyserSession session, @NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { // This item can be pulled from the creative inventory with amplifiers. - GeyserItemStack itemStack = super.translateToJava(itemData, mapping, mappings); + GeyserItemStack itemStack = super.translateToJava(session, itemData, mapping, mappings); int damage = itemData.getDamage(); if (damage == 0) { return itemStack; diff --git a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java index f8fe2b4ee3b..89e60b32506 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/PotionItem.java @@ -33,6 +33,7 @@ import org.geysermc.geyser.inventory.item.Potion; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.translator.item.CustomItemTranslator; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents; @@ -44,8 +45,8 @@ public PotionItem(String javaIdentifier, Builder builder) { } @Override - public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { - if (components == null) return super.translateToBedrock(count, components, mapping, mappings); + public ItemData.Builder translateToBedrock(GeyserSession session, int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { + if (components == null) return super.translateToBedrock(session, count, components, mapping, mappings); PotionContents potionContents = components.get(DataComponentType.POTION_CONTENTS); if (potionContents != null) { ItemDefinition customItemDefinition = CustomItemTranslator.getCustomItem(components, mapping); @@ -64,13 +65,13 @@ public ItemData.Builder translateToBedrock(int count, DataComponents components, .count(count); } } - return super.translateToBedrock(count, components, mapping, mappings); + return super.translateToBedrock(session, count, components, mapping, mappings); } @Override - public @NonNull GeyserItemStack translateToJava(@NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { + public @NonNull GeyserItemStack translateToJava(GeyserSession session, @NonNull ItemData itemData, @NonNull ItemMapping mapping, @NonNull ItemMappings mappings) { Potion potion = Potion.getByBedrockId(itemData.getDamage()); - GeyserItemStack itemStack = super.translateToJava(itemData, mapping, mappings); + GeyserItemStack itemStack = super.translateToJava(session, itemData, mapping, mappings); if (potion != null) { itemStack.getOrCreateComponents().put(DataComponentType.POTION_CONTENTS, potion.toComponent()); } diff --git a/core/src/main/java/org/geysermc/geyser/item/type/TippedArrowItem.java b/core/src/main/java/org/geysermc/geyser/item/type/TippedArrowItem.java index d9e58eaf97b..09e4ee21f9b 100644 --- a/core/src/main/java/org/geysermc/geyser/item/type/TippedArrowItem.java +++ b/core/src/main/java/org/geysermc/geyser/item/type/TippedArrowItem.java @@ -30,6 +30,7 @@ import org.geysermc.geyser.inventory.item.Potion; import org.geysermc.geyser.registry.type.ItemMapping; import org.geysermc.geyser.registry.type.ItemMappings; +import org.geysermc.geyser.session.GeyserSession; 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.PotionContents; @@ -40,7 +41,7 @@ public TippedArrowItem(String javaIdentifier, Builder builder) { } @Override - public ItemData.Builder translateToBedrock(int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { + public ItemData.Builder translateToBedrock(GeyserSession session, int count, DataComponents components, ItemMapping mapping, ItemMappings mappings) { if (components != null) { PotionContents potionContents = components.get(DataComponentType.POTION_CONTENTS); if (potionContents != null) { @@ -54,6 +55,6 @@ public ItemData.Builder translateToBedrock(int count, DataComponents components, GeyserImpl.getInstance().getLogger().debug("Unknown Java potion (tipped arrow): " + potionContents.getPotionId()); } } - return super.translateToBedrock(count, components, mapping, mappings); + return super.translateToBedrock(session, count, components, mapping, mappings); } } 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 86d66e209fd..fad13d1bb68 100644 --- a/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java +++ b/core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java @@ -29,21 +29,13 @@ import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.SoundUtils; public record JukeboxSong(String soundEvent, String description) { public static JukeboxSong read(RegistryEntryContext context) { NbtMap data = context.data(); - Object soundEventObject = data.get("sound_event"); - String soundEvent; - if (soundEventObject instanceof NbtMap map) { - soundEvent = map.getString("sound_id"); - } else if (soundEventObject instanceof String string) { - soundEvent = string; - } else { - soundEvent = ""; - GeyserImpl.getInstance().getLogger().debug("Sound event for " + context.id() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject); - } + String soundEvent = SoundUtils.readSoundEvent(data, "jukebox song " + context.id());; String description = MessageTranslator.deserializeDescription(context.session(), data); return new JukeboxSong(soundEvent, description); } 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 fcbc7c64c1d..2cc7bd5a66b 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 @@ -42,6 +42,7 @@ import org.geysermc.geyser.inventory.item.BannerPattern; import org.geysermc.geyser.inventory.recipe.TrimRecipe; import org.geysermc.geyser.item.enchantment.Enchantment; +import org.geysermc.geyser.inventory.item.GeyserInstrument; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.JukeboxSong; import org.geysermc.geyser.level.PaintingType; @@ -90,6 +91,7 @@ public final class RegistryCache { register(JavaRegistries.BIOME, (cache, array) -> cache.biomeTranslations = array, BiomeTranslator::loadServerBiome); register(JavaRegistries.BANNER_PATTERN, cache -> cache.bannerPatterns, context -> BannerPattern.getByJavaIdentifier(context.id())); register(JavaRegistries.WOLF_VARIANT, cache -> cache.wolfVariants, context -> WolfEntity.BuiltInWolfVariant.getByJavaIdentifier(context.id().asString())); + register(JavaRegistries.INSTRUMENT, cache -> cache.instruments, GeyserInstrument::read); // Load from MCProtocolLib's classloader NbtMap tag = MinecraftProtocol.loadNetworkCodec(); @@ -129,6 +131,7 @@ public final class RegistryCache { private final JavaRegistry bannerPatterns = new SimpleJavaRegistry<>(); private final JavaRegistry wolfVariants = new SimpleJavaRegistry<>(); + private final JavaRegistry instruments = new SimpleJavaRegistry<>(); public RegistryCache(GeyserSession session) { this.session = session; diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java index 5927963c04a..2663fc51185 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/WorldCache.java @@ -31,15 +31,18 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import lombok.Setter; +import net.kyori.adventure.key.Key; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.vector.Vector3i; import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket; -import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.inventory.GeyserItemStack; import org.geysermc.geyser.scoreboard.Scoreboard; import org.geysermc.geyser.scoreboard.ScoreboardUpdater.ScoreboardSession; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.ChunkUtils; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentType; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.UseCooldown; import org.geysermc.mcprotocollib.protocol.data.game.setting.Difficulty; import java.util.Iterator; @@ -72,7 +75,7 @@ public final class WorldCache { @Setter private boolean editingSignOnFront; - private final Object2IntMap activeCooldowns = new Object2IntOpenHashMap<>(2); + private final Object2IntMap activeCooldowns = new Object2IntOpenHashMap<>(2); public WorldCache(GeyserSession session) { this.session = session; @@ -204,17 +207,24 @@ public String removeActiveRecord(Vector3i pos) { return this.activeRecords.remove(pos); } - public void setCooldown(Item item, int ticks) { + public void setCooldown(Key cooldownGroup, int ticks) { if (ticks == 0) { // As of Java 1.21 - this.activeCooldowns.removeInt(item); + this.activeCooldowns.removeInt(cooldownGroup.asString()); return; } - this.activeCooldowns.put(item, session.getTicks() + ticks); + this.activeCooldowns.put(cooldownGroup.asString(), session.getTicks() + ticks); } - public boolean hasCooldown(Item item) { - return this.activeCooldowns.containsKey(item); + public boolean hasCooldown(GeyserItemStack item) { + UseCooldown cooldown = item.getComponent(DataComponentType.USE_COOLDOWN); + String cooldownGroup; + if (cooldown != null && cooldown.cooldownGroup() != null) { + cooldownGroup = cooldown.cooldownGroup().asString(); + } else { + cooldownGroup = item.asItem().javaIdentifier(); + } + return this.activeCooldowns.containsKey(cooldownGroup); } public void tick() { @@ -222,9 +232,9 @@ public void tick() { // but we don't want the cooldown field to balloon in size from overuse. if (!this.activeCooldowns.isEmpty()) { int ticks = session.getTicks(); - Iterator> it = Object2IntMaps.fastIterator(this.activeCooldowns); + Iterator> it = Object2IntMaps.fastIterator(this.activeCooldowns); while (it.hasNext()) { - Object2IntMap.Entry entry = it.next(); + Object2IntMap.Entry entry = it.next(); if (entry.getIntValue() <= ticks) { it.remove(); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistries.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistries.java index cb51f488ead..f0cd3afde4e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistries.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistries.java @@ -33,6 +33,7 @@ import org.geysermc.geyser.inventory.item.BannerPattern; import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.item.type.Item; +import org.geysermc.geyser.inventory.item.GeyserInstrument; import org.geysermc.geyser.level.JavaDimension; import org.geysermc.geyser.level.JukeboxSong; import org.geysermc.geyser.level.PaintingType; @@ -62,6 +63,7 @@ public class JavaRegistries { public static final JavaRegistryKey PAINTING_VARIANT = create("painting_variant", RegistryCache::paintings); public static final JavaRegistryKey TRIM_MATERIAL = create("trim_material", RegistryCache::trimMaterials); public static final JavaRegistryKey TRIM_PATTERN = create("trim_pattern", RegistryCache::trimPatterns); + public static final JavaRegistryKey INSTRUMENT = create("instrument", RegistryCache::instruments); /** * This registry should not be used in holder sets, tags, etc. It's simply used as a mapping from Java biomes to Bedrock ones. */ 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 163eef20b32..3cfd00233f5 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 @@ -108,7 +108,7 @@ public static ItemStack translateToJava(GeyserSession session, ItemData data) { ItemMapping bedrockItem = session.getItemMappings().getMapping(data); Item javaItem = bedrockItem.getJavaItem(); - GeyserItemStack itemStack = javaItem.translateToJava(data, bedrockItem, session.getItemMappings()); + GeyserItemStack itemStack = javaItem.translateToJava(session, data, bedrockItem, session.getItemMappings()); NbtMap nbt = data.getTag(); if (nbt != null && !nbt.isEmpty()) { @@ -198,7 +198,7 @@ public static ItemData translateToBedrock(GeyserSession session, ItemStack stack nbtMapBuilder.putIfAbsent("ench", NbtList.EMPTY); } - ItemData.Builder builder = javaItem.translateToBedrock(count, components, bedrockItem, session.getItemMappings()); + ItemData.Builder builder = javaItem.translateToBedrock(session, count, components, bedrockItem, session.getItemMappings()); // Finalize the Bedrock NBT builder.tag(nbtBuilder.build()); if (bedrockItem.isBlock()) { diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java index 421e082b165..422c45b9bfd 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/bedrock/BedrockInventoryTransactionTranslator.java @@ -42,6 +42,7 @@ import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; import org.cloudburstmc.protocol.bedrock.packet.InventoryTransactionPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEventPacket; +import org.cloudburstmc.protocol.bedrock.packet.PlaySoundPacket; import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.geyser.entity.EntityDefinitions; import org.geysermc.geyser.entity.type.Entity; @@ -51,6 +52,7 @@ import org.geysermc.geyser.inventory.Inventory; import org.geysermc.geyser.inventory.PlayerInventory; import org.geysermc.geyser.inventory.click.Click; +import org.geysermc.geyser.inventory.item.GeyserInstrument; import org.geysermc.geyser.item.Items; import org.geysermc.geyser.item.type.BlockItem; import org.geysermc.geyser.item.type.BoatItem; @@ -75,6 +77,7 @@ import org.geysermc.geyser.util.EntityUtils; import org.geysermc.geyser.util.InteractionResult; import org.geysermc.geyser.util.InventoryUtils; +import org.geysermc.geyser.util.SoundUtils; import org.geysermc.mcprotocollib.protocol.data.game.Holder; import org.geysermc.mcprotocollib.protocol.data.game.entity.object.Direction; import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode; @@ -379,18 +382,28 @@ public void translate(GeyserSession session, InventoryTransactionPacket packet) session.setCurrentBook(packet.getItemInHand()); } else if (session.getPlayerInventory().getItemInHand().asItem() == Items.GOAT_HORN) { // Temporary workaround while we don't have full item/block use tracking. - if (!session.getWorldCache().hasCooldown(Items.GOAT_HORN)) { - Holder instrument = session.getPlayerInventory() + if (!session.getWorldCache().hasCooldown(session.getPlayerInventory().getItemInHand())) { + Holder holder = session.getPlayerInventory() .getItemInHand() .getComponent(DataComponentType.INSTRUMENT); - if (instrument != null && instrument.isId()) { - // BDS uses a LevelSoundEvent2Packet, but that doesn't work here... (as of 1.21.20) - LevelSoundEventPacket soundPacket = new LevelSoundEventPacket(); - soundPacket.setSound(SoundEvent.valueOf("GOAT_CALL_" + instrument.id())); - soundPacket.setPosition(session.getPlayerEntity().getPosition()); - soundPacket.setIdentifier("minecraft:player"); - soundPacket.setExtraData(-1); - session.sendUpstreamPacket(soundPacket); + if (holder != null) { + GeyserInstrument instrument = GeyserInstrument.fromHolder(session, holder); + if (instrument.bedrockInstrument() != null) { + // BDS uses a LevelSoundEvent2Packet, but that doesn't work here... (as of 1.21.20) + LevelSoundEventPacket soundPacket = new LevelSoundEventPacket(); + soundPacket.setSound(SoundEvent.valueOf("GOAT_CALL_" + instrument.bedrockInstrument().ordinal())); + soundPacket.setPosition(session.getPlayerEntity().getPosition()); + soundPacket.setIdentifier("minecraft:player"); + soundPacket.setExtraData(-1); + session.sendUpstreamPacket(soundPacket); + } else { + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setPosition(session.getPlayerEntity().position()); + playSoundPacket.setSound(SoundUtils.translatePlaySound(instrument.soundEvent())); + playSoundPacket.setPitch(1.0F); + playSoundPacket.setVolume(instrument.range() / 16.0F); + session.sendUpstreamPacket(playSoundPacket); + } } } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java index 168992dd42f..2b14f015fde 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/level/JavaCooldownTranslator.java @@ -25,6 +25,7 @@ package org.geysermc.geyser.translator.protocol.java.level; +import net.kyori.adventure.key.Key; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundCooldownPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerStartItemCooldownPacket; import org.geysermc.geyser.item.Items; @@ -39,7 +40,11 @@ public class JavaCooldownTranslator extends PacketTranslator