Goat horns and item cooldowns for 1.21.2 (#5102)

Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com>
This commit is contained in:
Eclipse 2024-10-31 22:52:26 +00:00 committed by GitHub
parent 4588f341ec
commit 6cc2aa3697
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 320 additions and 66 deletions

View file

@ -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<GeyserInstrument> 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<Instrument> 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;
}
}
}

View file

@ -41,7 +41,7 @@ import java.util.Map;
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 record Enchantment(String identifier,
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,

View file

@ -32,6 +32,7 @@ import org.geysermc.geyser.inventory.item.Potion;
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 class ArrowItem extends Item {
}
@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();

View file

@ -43,11 +43,11 @@ public class CompassItem extends Item {
}
@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 @@ public class CompassItem extends Item {
}
@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);
}
}

View file

@ -28,6 +28,7 @@ package org.geysermc.geyser.item.type;
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 class FilledMapItem extends MapItem {
}
@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

View file

@ -28,8 +28,11 @@ package org.geysermc.geyser.item.type;
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 class GoatHornItem extends Item {
}
@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> instrument = components.get(DataComponentType.INSTRUMENT);
if (instrument != null && instrument.isId()) {
builder.damage(instrument.id());
Holder<Instrument> 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<Instrument> 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;
}

View file

@ -115,7 +115,7 @@ public class Item {
/* 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 class Item {
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());
}

View file

@ -31,6 +31,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
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 class OminousBottleItem extends Item {
}
@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 class OminousBottleItem extends Item {
}
@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;

View file

@ -33,6 +33,7 @@ import org.geysermc.geyser.inventory.GeyserItemStack;
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 class PotionItem extends Item {
}
@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 class PotionItem extends Item {
.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());
}

View file

@ -30,6 +30,7 @@ import org.geysermc.geyser.GeyserImpl;
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 class TippedArrowItem extends ArrowItem {
}
@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 class TippedArrowItem extends ArrowItem {
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);
}
}

View file

@ -29,21 +29,13 @@ 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.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);
}

View file

@ -42,6 +42,7 @@ import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity;
import org.geysermc.geyser.inventory.item.BannerPattern;
import org.geysermc.geyser.inventory.recipe.TrimRecipe;
import org.geysermc.geyser.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<BannerPattern> bannerPatterns = new SimpleJavaRegistry<>();
private final JavaRegistry<WolfEntity.BuiltInWolfVariant> wolfVariants = new SimpleJavaRegistry<>();
private final JavaRegistry<GeyserInstrument> instruments = new SimpleJavaRegistry<>();
public RegistryCache(GeyserSession session) {
this.session = session;

View file

@ -31,15 +31,18 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
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<Item> activeCooldowns = new Object2IntOpenHashMap<>(2);
private final Object2IntMap<String> activeCooldowns = new Object2IntOpenHashMap<>(2);
public WorldCache(GeyserSession session) {
this.session = session;
@ -204,17 +207,24 @@ public final class WorldCache {
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 final class WorldCache {
// but we don't want the cooldown field to balloon in size from overuse.
if (!this.activeCooldowns.isEmpty()) {
int ticks = session.getTicks();
Iterator<Object2IntMap.Entry<Item>> it = Object2IntMaps.fastIterator(this.activeCooldowns);
Iterator<Object2IntMap.Entry<String>> it = Object2IntMaps.fastIterator(this.activeCooldowns);
while (it.hasNext()) {
Object2IntMap.Entry<Item> entry = it.next();
Object2IntMap.Entry<String> entry = it.next();
if (entry.getIntValue() <= ticks) {
it.remove();
}

View file

@ -33,6 +33,7 @@ import org.geysermc.geyser.entity.type.living.animal.tameable.WolfEntity;
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<PaintingType> PAINTING_VARIANT = create("painting_variant", RegistryCache::paintings);
public static final JavaRegistryKey<TrimMaterial> TRIM_MATERIAL = create("trim_material", RegistryCache::trimMaterials);
public static final JavaRegistryKey<TrimPattern> TRIM_PATTERN = create("trim_pattern", RegistryCache::trimPatterns);
public static final JavaRegistryKey<GeyserInstrument> 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.
*/

View file

@ -108,7 +108,7 @@ public final class ItemTranslator {
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 final class ItemTranslator {
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()) {

View file

@ -42,6 +42,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.transaction.LegacySetIte
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.GeyserItemStack;
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.CooldownUtils;
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 class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
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> instrument = session.getPlayerInventory()
if (!session.getWorldCache().hasCooldown(session.getPlayerInventory().getItemInHand())) {
Holder<Instrument> 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);
}
}
}
}

View file

@ -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<ClientboundCooldown
@Override
public void translate(GeyserSession session, ClientboundCooldownPacket packet) {
Item item = Registries.JAVA_ITEMS.get().get(0); // FIXME
// If the cooldown group is a modded item, an item that Bedrock doesn't support custom cooldowns for, or a custom cooldown group,
// then the cooldown won't be translated correctly. The cooldown won't show up on Bedrock, but they are still unable to use the item.
Key cooldownGroup = packet.getCooldownGroup();
Item item = Registries.JAVA_ITEM_IDENTIFIERS.get(cooldownGroup.asString());
// Not every item, as of 1.19, appears to be server-driven. Just these two.
// Use a map here if it gets too big.
String cooldownCategory;
@ -58,6 +63,6 @@ public class JavaCooldownTranslator extends PacketTranslator<ClientboundCooldown
session.sendUpstreamPacket(bedrockPacket);
}
session.getWorldCache().setCooldown(item, packet.getCooldownTicks());
session.getWorldCache().setCooldown(cooldownGroup, packet.getCooldownTicks());
}
}

View file

@ -137,6 +137,18 @@ public class MessageTranslator {
return convertMessage(message, locale, true);
}
/**
* Convert a Java message to the legacy format ready for bedrock, for use in item tooltips
* (a gray color is applied).
*
* @param message Java message
* @param locale Locale to use for translation strings
* @return Parsed and formatted message for bedrock, in gray color
*/
public static String convertMessageForTooltip(Component message, String locale) {
return RESET + ChatColor.GRAY + convertMessageRaw(message, locale);
}
/**
* Convert a Java message to the legacy format ready for bedrock. Unlike {@link #convertMessage(Component, String)}
* this version does not add a leading color reset. In Bedrock some places have build-in colors.
@ -422,6 +434,15 @@ public class MessageTranslator {
return convertMessage(session, parsed);
}
/**
* Deserialize an NbtMap with a description text component (usually provided from a registry) into a Bedrock-formatted string.
*/
public static String deserializeDescriptionForTooltip(GeyserSession session, NbtMap tag) {
Object description = tag.get("description");
Component parsed = componentFromNbtTag(description);
return convertMessageForTooltip(parsed, session.locale());
}
public static Component componentFromNbtTag(Object nbtTag) {
return componentFromNbtTag(nbtTag, Style.empty());
}

View file

@ -27,6 +27,7 @@ package org.geysermc.geyser.util;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3f;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.data.LevelEvent;
import org.cloudburstmc.protocol.bedrock.data.SoundEvent;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
@ -162,6 +163,20 @@ public final class SoundUtils {
session.sendUpstreamPacket(soundPacket);
}
public static String readSoundEvent(NbtMap data, String context) {
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 + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject);
}
return soundEvent;
}
private SoundUtils() {
}
}