diff --git a/core/src/main/java/org/geysermc/geyser/item/hashing/ComponentHasher.java b/core/src/main/java/org/geysermc/geyser/item/hashing/ComponentHasher.java index fcc01515a..f8abb46ab 100644 --- a/core/src/main/java/org/geysermc/geyser/item/hashing/ComponentHasher.java +++ b/core/src/main/java/org/geysermc/geyser/item/hashing/ComponentHasher.java @@ -41,6 +41,9 @@ import net.kyori.adventure.text.format.TextDecoration; import java.util.function.Supplier; +/** + * This interface contains various {@link MinecraftHasher}s used to encode properties of {@link Component}s. Usually, you'll only need {@link ComponentHasher#COMPONENT}. + */ public interface ComponentHasher { MinecraftHasher COMPONENT = MinecraftHasher.lazyInitialize(new Supplier<>() { diff --git a/core/src/main/java/org/geysermc/geyser/item/hashing/DataComponentHashers.java b/core/src/main/java/org/geysermc/geyser/item/hashing/DataComponentHashers.java index 9d1a71d4a..1161b1033 100644 --- a/core/src/main/java/org/geysermc/geyser/item/hashing/DataComponentHashers.java +++ b/core/src/main/java/org/geysermc/geyser/item/hashing/DataComponentHashers.java @@ -182,7 +182,7 @@ public class DataComponentHashers { register(DataComponentTypes.MAP_DECORATIONS, MinecraftHasher.NBT_MAP); register(DataComponentTypes.CHARGED_PROJECTILES, RegistryHasher.ITEM_STACK.list()); - register(DataComponentTypes.BUNDLE_CONTENTS, RegistryHasher.ITEM_STACK.list()); + register(DataComponentTypes.BUNDLE_CONTENTS, RegistryHasher.ITEM_STACK.list()); // TODO test data component removal registerMap(DataComponentTypes.POTION_CONTENTS, builder -> builder .optional("potion", RegistryHasher.POTION, PotionContents::getPotionId, -1) @@ -285,20 +285,16 @@ public class DataComponentHashers { hashers.put(component, hasher); } - public static MinecraftHasher hasherOrEmpty(DataComponentType component) { + public static MinecraftHasher hasher(DataComponentType component) { MinecraftHasher hasher = (MinecraftHasher) hashers.get(component); if (hasher == null) { - return MinecraftHasher.UNIT.cast(value -> Unit.INSTANCE); + throw new IllegalStateException("Unregistered hasher for component " + component + "!"); } return hasher; } public static HashCode hash(GeyserSession session, DataComponentType component, T value) { - MinecraftHasher hasher = (MinecraftHasher) hashers.get(component); - if (hasher == null) { - throw new IllegalStateException("Unregistered hasher for component " + component + "!"); - } - return hasher.hash(value, new MinecraftHashEncoder(session)); + return hasher(component).hash(value, new MinecraftHashEncoder(session)); } public static HashedStack hashStack(GeyserSession session, ItemStack stack) { diff --git a/core/src/main/java/org/geysermc/geyser/item/hashing/MapBuilder.java b/core/src/main/java/org/geysermc/geyser/item/hashing/MapBuilder.java index 74a2f6559..a767a8f1f 100644 --- a/core/src/main/java/org/geysermc/geyser/item/hashing/MapBuilder.java +++ b/core/src/main/java/org/geysermc/geyser/item/hashing/MapBuilder.java @@ -27,14 +27,29 @@ package org.geysermc.geyser.item.hashing; import java.util.function.UnaryOperator; +/** + * {@link MapBuilder}s can be used to define map-like structures to encode a {@link Type} using a {@link MapHasher}. + * + * @param the type to encode. + */ @FunctionalInterface -public interface MapBuilder extends UnaryOperator> { +public interface MapBuilder extends UnaryOperator> { - default MapBuilder cast() { - return builder -> builder.accept(this, casted -> (T) casted); + /** + * Casts this map builder to a {@link Casted}. This cast is done unsafely, only use this if you are sure the object being encoded is of the type being cast to! + * + * @param the type to cast to. + */ + default MapBuilder cast() { + return builder -> builder.accept(this, casted -> (Type) casted); } - static MapBuilder empty() { + /** + * Returns a map builder that doesn't contain anything. + * + * @param the type to encode. + */ + static MapBuilder empty() { return builder -> builder; } } diff --git a/core/src/main/java/org/geysermc/geyser/item/hashing/MapHasher.java b/core/src/main/java/org/geysermc/geyser/item/hashing/MapHasher.java index a760f6c87..2733eae70 100644 --- a/core/src/main/java/org/geysermc/geyser/item/hashing/MapHasher.java +++ b/core/src/main/java/org/geysermc/geyser/item/hashing/MapHasher.java @@ -33,88 +33,168 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; +/** + * {@link MapHasher}s are used to encode a {@link Type} to a map-like structure, which is then hashed using a {@link MinecraftHashEncoder}. + * + *

{@link MapHasher}s store the {@link Type} they are encoding, but it isn't directly accessible. Instead, extractor functions are used to extract specific properties of the {@link Type}.

+ * + * @param the type this {@link MapHasher} encodes. + */ @SuppressWarnings("UnstableApiUsage") -public class MapHasher { +public class MapHasher { private static final boolean DEBUG = false; private final MinecraftHashEncoder encoder; - private final T object; + private final Type object; private final Map map; private final Map unhashed; - MapHasher(T object, MinecraftHashEncoder encoder) { + MapHasher(Type object, MinecraftHashEncoder encoder) { this(object, encoder, new HashMap<>(), DEBUG ? new HashMap<>() : null); } - private MapHasher(T object, MinecraftHashEncoder encoder, Map map, Map unhashed) { + private MapHasher(Type object, MinecraftHashEncoder encoder, Map map, Map unhashed) { this.encoder = encoder; this.object = object; this.map = map; this.unhashed = unhashed; } - public MapHasher accept(String key, HashCode hash) { + private MapHasher accept(String key, HashCode hash) { map.put(encoder.string(key), hash); return this; } - public MapHasher acceptConstant(String key, MinecraftHasher hasher, V value) { + /** + * Adds a constant {@link Value} to the map. + * + * @param key the key to put the constant in. + * @param hasher the hasher used to hash a {@link Value}. + * @param value the {@link Value}. + * @param the type of the value. + */ + public MapHasher acceptConstant(String key, MinecraftHasher hasher, Value value) { if (unhashed != null) { unhashed.put(key, value); } return accept(key, hasher.hash(value, encoder)); } - public MapHasher accept(String key, MinecraftHasher hasher, Function extractor) { + /** + * Extracts a {@link Value} from a {@link Type} using the {@code extractor}, and adds it to the map. + * + * @param key the key to put the {@link Value} in. + * @param hasher the hasher used to hash a {@link Value}. + * @param extractor the function that extracts a {@link Value} from a {@link Type}. + * @param the type of the value. + */ + public MapHasher accept(String key, MinecraftHasher hasher, Function extractor) { return acceptConstant(key, hasher, extractor.apply(object)); } - // Adds keys and values from the builder directly to this map (document me properly) - public MapHasher accept(MapBuilder builder) { + /** + * Applies the {@link MapBuilder} to this {@link MapHasher}, essentially adding all the keys it defines here. + */ + public MapHasher accept(MapBuilder builder) { builder.apply(this); return this; } - // Adds keys and values from the builder directly to this map (document me properly) - public MapHasher accept(MapBuilder builder, Function extractor) { + /** + * Extracts a {@link Value} from a {@link Type} using the {@code extractor}, and applies the given {@link MapBuilder} for it to this {@link MapHasher}, + * essentially adding the keys it defines here. + * + * @param builder the {@link MapBuilder} that encodes a {@link Value}. + * @param extractor the function that extracts a {@link Value} from a {@link Type}. + * @param the type of the value. + */ + public MapHasher accept(MapBuilder builder, Function extractor) { builder.apply(new MapHasher<>(extractor.apply(object), encoder, map, unhashed)); return this; } - // Adds keys and values from the builder directly to this map (document me properly) - public MapHasher accept(Function> builderExtractor, Function extractor) { - builderExtractor.apply(extractor.apply(object)).apply(this); + /** + * Extracts a {@link Value} from a {@link Type} using the {@code extractor}, then dispatches a {@link MapBuilder} from the {@link Value} using the {@code builderDispatcher}, + * and applies it to this {@link MapHasher}, essentially adding the keys it defines here. + * + * @param extractor the function that extracts a {@link Value} from a {@link Type}. + * @param builderDispatcher the function that dispatches a {@link MapBuilder} from a {@link Value}. + * @param the type of the value. + */ + public MapHasher accept(Function extractor, Function> builderDispatcher) { + builderDispatcher.apply(extractor.apply(object)).apply(this); return this; } - public MapHasher optionalNullable(String key, MinecraftHasher hasher, Function extractor) { - V value = extractor.apply(object); + /** + * Extracts a {@link Value} from a {@link Type} using the {@code extractor}, and adds it to the map if it is not null. + * + * @param key the key to put the {@link Value} in. + * @param hasher the hasher used to hash a {@link Value}. + * @param extractor the function that extracts a {@link Value} from a {@link Type}. + * @param the type of the value. + */ + public MapHasher optionalNullable(String key, MinecraftHasher hasher, Function extractor) { + Value value = extractor.apply(object); if (value != null) { acceptConstant(key, hasher, value); } return this; } - public MapHasher optional(String key, MinecraftHasher hasher, Function> extractor) { - Optional value = extractor.apply(object); + /** + * Extracts an {@link Optional} of a {@link Value} from a {@link Type} using the {@code extractor}, and adds it to the map if it is present. + * + * @param key the key to put the {@link Value} in. + * @param hasher the hasher used to hash a {@link Value}. + * @param extractor the function that extracts a {@link Value} from a {@link Type}. + * @param the type of the value. + */ + public MapHasher optional(String key, MinecraftHasher hasher, Function> extractor) { + Optional value = extractor.apply(object); value.ifPresent(v -> acceptConstant(key, hasher, v)); return this; } - public MapHasher optional(String key, MinecraftHasher hasher, Function extractor, V defaultValue) { - V value = extractor.apply(object); + /** + * Extracts a {@link Value} from a {@link Type} using the {@code extractor}, and adds it to the map if it's not equal to {@code defaultValue}. + * + * @param key the key to put the {@link Value} in. + * @param hasher the hasher used to hash a {@link Value}. + * @param extractor the function that extracts a {@link Value} from a {@link Type}. + * @param defaultValue the default {@link Value}. The {@link Value} won't be added to the map if it equals the default. + * @param the type of the value. + */ + public MapHasher optional(String key, MinecraftHasher hasher, Function extractor, Value defaultValue) { + Value value = extractor.apply(object); if (!value.equals(defaultValue)) { acceptConstant(key, hasher, value); } return this; } - public MapHasher acceptList(String key, MinecraftHasher valueHasher, Function> extractor) { + /** + * Extracts a list of {@link Value}s from a {@link Type}, and adds it to the map. + * + * @param key the key to put the list of {@link Value}s in. + * @param valueHasher the hasher used to hash a single {@link Value}. + * @param extractor the function that extracts a list of {@link Value}s from a {@link Type}. + * @param the type of the value. + */ + public MapHasher acceptList(String key, MinecraftHasher valueHasher, Function> extractor) { return acceptConstant(key, valueHasher.list(), extractor.apply(object)); } - public MapHasher optionalList(String key, MinecraftHasher valueHasher, Function> extractor) { - List list = extractor.apply(object); + /** + * Extracts a list of {@link Value}s from a {@link Type}, and adds it to the map if it is not empty. + * + * @param key the key to put the list of {@link Value}s in. + * @param valueHasher the hasher used to hash a single {@link Value}. + * @param extractor the function that extracts a list of {@link Value}s from a {@link Type}. + * @param the type of the value. + */ + public MapHasher optionalList(String key, MinecraftHasher valueHasher, Function> extractor) { + List list = extractor.apply(object); if (!list.isEmpty()) { acceptConstant(key, valueHasher.list(), list); } diff --git a/core/src/main/java/org/geysermc/geyser/item/hashing/MinecraftHasher.java b/core/src/main/java/org/geysermc/geyser/item/hashing/MinecraftHasher.java index d8d0d370f..06a2aa677 100644 --- a/core/src/main/java/org/geysermc/geyser/item/hashing/MinecraftHasher.java +++ b/core/src/main/java/org/geysermc/geyser/item/hashing/MinecraftHasher.java @@ -41,6 +41,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.Filterable; import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemAttributeModifiers; import org.geysermc.mcprotocollib.protocol.data.game.item.component.Unit; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; @@ -48,7 +49,6 @@ import java.util.UUID; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; -import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -69,7 +69,7 @@ import java.util.stream.IntStream; * * *

On top of these there are more methods, as well as static helper methods in this class, to create hashers. - * One worth pointing out is {@link MinecraftHasher#mapBuilder(MapBuilder)}, which hashes an object into a map-like structure. Read the documentation there and on {@link MapBuilder} on how to use it.

+ * One worth pointing out is {@link MinecraftHasher#mapBuilder(MapBuilder)}, which hashes an object into a map-like structure. Read the documentation there, on {@link MapBuilder}, and on {@link MapHasher} on how to use it.

* *

As of right now, hashers are used to create hash codes for data components, which Minecraft requires to be sent in inventory transactions. You'll find all the hashers for data components in * {@link DataComponentHashers}. In the future, hashers may be used elsewhere as well. If necessary, this system can even be refactored to write to different data structures, such as NBT or JSON files, as well.

@@ -116,7 +116,9 @@ public interface MinecraftHasher { MinecraftHasher KEY = STRING.cast(Key::asString); - MinecraftHasher TAG = STRING.cast(key -> "#" + key.asString()); + MinecraftHasher TAG = STRING.cast(key -> '#' + key.asString()); + + MinecraftHasher KEY_REMOVAL = STRING.cast(key -> '!' + key.asString()); MinecraftHasher UUID = INT_ARRAY.cast(uuid -> { long mostSignificant = uuid.getMostSignificantBits(); @@ -223,7 +225,7 @@ public interface MinecraftHasher { default MinecraftHasher dispatch(String typeKey, Function typeExtractor, Function> mapDispatch) { return mapBuilder(builder -> builder .accept(typeKey, this, typeExtractor) - .accept(mapDispatch, typeExtractor)); + .accept(typeExtractor, mapDispatch)); } /** @@ -243,17 +245,14 @@ public interface MinecraftHasher { return (value, encoder) -> memoized.get().hash(value, encoder); } - // TODO currently unused, has to be used for mob effects, check - static MinecraftHasher recursive(UnaryOperator> delegate) { - return new Recursive<>(delegate); - } - /** * Uses {@link Enum#name()} (lowercased) as {@code toName} function in {@link MinecraftHasher#fromIdEnum(Enum[], Function)}. * *

Please be aware that you are using literal enum constants as string values here, meaning that if there is a typo in a constant, or a constant changes name, things * may break. Use cautiously.

* + * @param values the array of {@link EnumConstant}s. + * @param the enum. * @see MinecraftHasher#fromIdEnum(Enum[], Function) */ static > MinecraftHasher fromIdEnum(EnumConstant[] values) { @@ -278,6 +277,7 @@ public interface MinecraftHasher { *

Please be aware that you are using literal enum constants as string values here, meaning that if there is a typo in a constant, or a constant changes name, things * may break. Use cautiously.

* + * @param the enum. * @see MinecraftHasher#fromEnum(Function) */ static > MinecraftHasher fromEnum() { @@ -296,7 +296,7 @@ public interface MinecraftHasher { } /** - * Creates a hasher that uses a {@link MapBuilder} to hash a {@link Type} into a map. + * Creates a hasher that uses a {@link MapBuilder}, which uses a {@link MapHasher} to hash a {@link Type} into a map. * * @param builder the builder that creates a map from a {@link Type}. * @param the type to hash. @@ -306,20 +306,33 @@ public interface MinecraftHasher { } /** - * Creates a hasher that hashes {@link K} keys and {@link V} values into a map, using the {@code keyHasher} and {@code valueHasher} respectively. - * - *

Note that the key hasher is usually expected to hash into a string, but this is not necessarily a requirement. It may be once different encoders are used to encode to NBT or JSON, - * but this is not the case yet.

+ * Creates a hasher that uses {@link MinecraftHasher#mapSet(MinecraftHasher, MinecraftHasher)} to hash {@link K} keys and {@link V} values into a map, + * using the {@code keyHasher} and {@code valueHasher} respectively. * * @param keyHasher the hasher that hashes objects of type {@link K}. * @param valueHasher the hasher that hashes objects of type {@link V}. * @param the key type. * @param the value type. + * @see MinecraftHasher#mapSet(MinecraftHasher, MinecraftHasher) */ static MinecraftHasher> map(MinecraftHasher keyHasher, MinecraftHasher valueHasher) { - return (map, encoder) -> encoder.map(map.entrySet().stream() - .map(entry -> Map.entry(keyHasher.hash(entry.getKey(), encoder), valueHasher.hash(entry.getValue(), encoder))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + return MinecraftHasher.>mapSet(keyHasher.cast(Map.Entry::getKey), valueHasher.cast(Map.Entry::getValue)).cast(Map::entrySet); + } + + /** + * Creates a hasher that hashes a set of {@link Type} entries into a map of keys and values, using {@code keyHasher} and {@code valueHasher} respectively. + * + *

Note that the key hasher is usually expected to hash into a string, but this is not necessarily a requirement. It may be once different encoders are used to encode to NBT or JSON, + * but this is not the case yet.

+ * + * @param keyHasher the hasher that hashes a {@link Type} into a key to be used in the map. + * @param valueHasher the hasher that hashes a {@link Type} into a value to be used in the map. + * @param the entry type. + * @see MinecraftHasher#map(MinecraftHasher, MinecraftHasher) + */ + static MinecraftHasher> mapSet(MinecraftHasher keyHasher, MinecraftHasher valueHasher) { + return (set, encoder) -> encoder.map(set.stream() + .collect(Collectors.toMap(value -> keyHasher.hash(value, encoder), value -> valueHasher.hash(value, encoder)))); } /** @@ -360,17 +373,4 @@ public interface MinecraftHasher { static MinecraftHasher dispatch(Function> hashDispatch) { return (value, encoder) -> hashDispatch.apply(value).hash(value, encoder); } - - class Recursive implements MinecraftHasher { - private final Supplier> delegate; - - public Recursive(UnaryOperator> delegate) { - this.delegate = Suppliers.memoize(() -> delegate.apply(this)); - } - - @Override - public HashCode hash(T value, MinecraftHashEncoder encoder) { - return delegate.get().hash(value, encoder); - } - } } diff --git a/core/src/main/java/org/geysermc/geyser/item/hashing/RegistryHasher.java b/core/src/main/java/org/geysermc/geyser/item/hashing/RegistryHasher.java index 71824aee2..94cfd8109 100644 --- a/core/src/main/java/org/geysermc/geyser/item/hashing/RegistryHasher.java +++ b/core/src/main/java/org/geysermc/geyser/item/hashing/RegistryHasher.java @@ -69,10 +69,12 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.InstrumentCo import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemAttributeModifiers; import org.geysermc.mcprotocollib.protocol.data.game.item.component.ItemEnchantments; import org.geysermc.mcprotocollib.protocol.data.game.item.component.JukeboxPlayable; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.MobEffectDetails; import org.geysermc.mcprotocollib.protocol.data.game.item.component.MobEffectInstance; import org.geysermc.mcprotocollib.protocol.data.game.item.component.ProvidesTrimMaterial; import org.geysermc.mcprotocollib.protocol.data.game.item.component.SuspiciousStewEffect; import org.geysermc.mcprotocollib.protocol.data.game.item.component.ToolData; +import org.geysermc.mcprotocollib.protocol.data.game.item.component.Unit; import org.geysermc.mcprotocollib.protocol.data.game.level.sound.BuiltinSound; import org.geysermc.mcprotocollib.protocol.data.game.level.sound.CustomSound; import org.geysermc.mcprotocollib.protocol.data.game.level.sound.Sound; @@ -86,20 +88,20 @@ import java.util.function.Function; import java.util.stream.IntStream; /** - * {@link RegistryHasher}s are hashers that hash a network, integer ID to an identifier. {@link RegistryHasher}s can be created using static utility methods in this class, and all registry hashers should be kept in here. + * {@link RegistryHasher}s are hashers that hash a network integer ID to a namespaced identifier. {@link RegistryHasher}s can be created using static utility methods in this class, and all registry hashers should be kept in here. * - *

The {@link Type} parameter is only used for registry hashers that are able to encode {@link Holder}s, and must be left as a {@code ?} if this functionality is not in use. This makes it clear the hasher is not + *

The {@link DirectType} parameter is only used for registry hashers that are able to encode {@link Holder}s, and must be left as a {@code ?} if this functionality is not in use. This makes it clear the hasher is not * supposed to be able to encode holders.

* - *

To create a hasher that can encode a {@link Holder}, a direct hasher should be created that directly hashes a {@link Type} (in case of a custom holder), and {@link RegistryHasher#registry(JavaRegistryKey, MinecraftHasher)} - * should be used to create the registry hasher. {@link RegistryHasher#holder()} can then be used to obtain a hasher that encodes a holder of {@link Type}.

+ *

To create a hasher that can encode a {@link Holder}, a direct hasher should be created that hashes a {@link DirectType} (in case of a custom holder), and {@link RegistryHasher#registry(JavaRegistryKey, MinecraftHasher)} + * should be used to create the registry hasher. {@link RegistryHasher#holder()} can then be used to obtain a hasher that encodes a holder of {@link DirectType}.

* *

Along with {@link RegistryHasher}s, this class also contains a bunch of hashers for various Minecraft objects. For organisational purposes, these are grouped in various sections with comments.

* - * @param the type this hasher hashes. Only used for registry hashers that can hash holders. + * @param the type this hasher hashes. Only used for registry hashers that can hash holders. */ @SuppressWarnings("UnstableApiUsage") -public interface RegistryHasher extends MinecraftHasher { +public interface RegistryHasher extends MinecraftHasher { // Java registries @@ -122,6 +124,8 @@ public interface RegistryHasher extends MinecraftHasher { RegistryHasher POTION = enumIdRegistry(Potion.values()); + RegistryHasher VILLAGER_TYPE = enumIdRegistry(VillagerVariant.values()); + // Java data-driven registries MinecraftHasher BUILTIN_SOUND = KEY.cast(sound -> MinecraftKey.key(sound.getName())); @@ -175,8 +179,6 @@ public interface RegistryHasher extends MinecraftHasher { RegistryHasher BANNER_PATTERN = registry(JavaRegistries.BANNER_PATTERN, DIRECT_BANNER_PATTERN); - RegistryHasher VILLAGER_TYPE = enumIdRegistry(VillagerVariant.values()); - RegistryHasher WOLF_VARIANT = registry(JavaRegistries.WOLF_VARIANT); RegistryHasher WOLF_SOUND_VARIANT = registry(JavaRegistries.WOLF_SOUND_VARIANT); @@ -221,26 +223,36 @@ public interface RegistryHasher extends MinecraftHasher { // Widely used Minecraft types + MinecraftHasher> DATA_COMPONENT_KEY = MinecraftHasher.either(KEY, + component -> component.getValue() == null ? null : component.getType().getKey(), KEY_REMOVAL, component -> component.getType().getKey()); + @SuppressWarnings({"unchecked", "rawtypes"}) // Java generics :( - MinecraftHasher> DATA_COMPONENT = (component, encoder) -> { - MinecraftHasher hasher = DataComponentHashers.hasherOrEmpty(component.getType()); + MinecraftHasher> DATA_COMPONENT_VALUE = (component, encoder) -> { + if (component.getValue() == null) { + return UNIT.hash(Unit.INSTANCE, encoder); + } + MinecraftHasher hasher = DataComponentHashers.hasher(component.getType()); return hasher.hash(component.getValue(), encoder); }; - MinecraftHasher DATA_COMPONENTS = MinecraftHasher.map(RegistryHasher.DATA_COMPONENT_TYPE, DATA_COMPONENT).cast(DataComponents::getDataComponents); // TODO component removals (needs unit value and ! component prefix) + MinecraftHasher DATA_COMPONENTS = MinecraftHasher.mapSet(DATA_COMPONENT_KEY, DATA_COMPONENT_VALUE).cast(components -> components.getDataComponents().values()); MinecraftHasher ITEM_STACK = MinecraftHasher.mapBuilder(builder -> builder .accept("id", ITEM, ItemStack::getId) .accept("count", INT, ItemStack::getAmount) .optionalNullable("components", DATA_COMPONENTS, ItemStack::getDataComponentsPatch)); + // Encoding of hidden effects is unfortunately not possible + MapBuilder MOB_EFFECT_DETAILS = builder -> builder + .optional("amplifier", BYTE, instance -> (byte) instance.getAmplifier(), (byte) 0) + .optional("duration", INT, MobEffectDetails::getDuration, 0) + .optional("ambient", BOOL, MobEffectDetails::isAmbient, false) + .optional("show_particles", BOOL, MobEffectDetails::isShowParticles, true) + .accept("show_icon", BOOL, MobEffectDetails::isShowIcon); // Yes, this is not an optional. I checked. Maybe it will be in the future and break everything! + MinecraftHasher MOB_EFFECT_INSTANCE = MinecraftHasher.mapBuilder(builder -> builder .accept("id", RegistryHasher.EFFECT, MobEffectInstance::getEffect) - .optional("amplifier", BYTE, instance -> (byte) instance.getDetails().getAmplifier(), (byte) 0) - .optional("duration", INT, instance -> instance.getDetails().getDuration(), 0) - .optional("ambient", BOOL, instance -> instance.getDetails().isAmbient(), false) - .optional("show_particles", BOOL, instance -> instance.getDetails().isShowParticles(), true) - .accept("show_icon", BOOL, instance -> instance.getDetails().isShowIcon())); // TODO check this, also hidden effect but is recursive + .accept(MOB_EFFECT_DETAILS, MobEffectInstance::getDetails)); MinecraftHasher ATTRIBUTE_MODIFIER_OPERATION = MinecraftHasher.fromEnum(operation -> switch (operation) { case ADD -> "add_value"; @@ -344,7 +356,7 @@ public interface RegistryHasher extends MinecraftHasher { } /** - * Creates a hasher that using {@link RegistryHasher#registry(JavaRegistryKey)} that is also able to encode {@link Holder}s by using the {@code directHasher}. + * Creates a hasher that encodes network IDs using {@link RegistryHasher#registry(JavaRegistryKey)}, and is also able to encode {@link Holder}s by using the {@code directHasher}. * *

A hasher that encodes {@link Holder}s can be obtained by using {@link RegistryHasher#holder()}

* @@ -358,13 +370,27 @@ public interface RegistryHasher extends MinecraftHasher { return new RegistryHasherWithDirectHasher<>(registry(registry), directHasher); } - default MinecraftHasher> holder() { - if (this instanceof RegistryHasher.RegistryHasherWithDirectHasher withDirect) { + /** + * Creates a hasher that encodes a {@link Holder} of {@link DirectType}. If the holder has an ID, the {@link RegistryHasher} is used to encode it. If the holder is custom, + * a direct hasher specified in {@link RegistryHasher#registry(JavaRegistryKey, MinecraftHasher)} is used to encode it. + * + *

This method can only be used if this hasher has a direct hasher attached to it. That is only the case if {@link DirectType} is not {@code ?}. If this hasher doesn't have + * a direct hasher, a {@link IllegalStateException} will be thrown upon use.

+ * + * @throws IllegalStateException when this hasher does not have a direct hasher attached to it. + */ + default MinecraftHasher> holder() { + if (this instanceof RegistryHasher.RegistryHasherWithDirectHasher withDirect) { return withDirect.holderHasher; } throw new IllegalStateException("Tried to create a holder hasher on a registry hasher that does not have a direct hasher specified"); } + /** + * Creates a hasher that hashes a {@link HolderSet} of the registry. {@link HolderSet}s can encode as a hash-prefixed tag, a single namespaced ID, or a list of namespaced IDs. + * + *

The hasher throws a {@link IllegalStateException} if the holder set does not have a tag nor a list of IDs. This should never happen.

+ */ default MinecraftHasher holderSet() { return (holder, encoder) -> { if (holder.getLocation() != null) { @@ -379,15 +405,41 @@ public interface RegistryHasher extends MinecraftHasher { }; } - // TODO note that this only works if the enum constants match + /** + * Creates a hasher that uses {@link Enum#name()} (lowercased) to create a key in the {@code minecraft} namespace, and then hashes it. + * + *

Please be aware that you are using literal enum constants as key paths here, meaning that if there is a typo in a constant, or a constant changes name, things + * may break. Use cautiously.

+ * + * @param the enum. + */ static > MinecraftHasher enumRegistry() { return KEY.cast(constant -> MinecraftKey.key(constant.name().toLowerCase(Locale.ROOT))); } + /** + * Uses {@link Enum#name()} (lowercased) to create a function that creates a {@link Key} from a {@link EnumConstant}, and uses this as {@code toKey} + * function in {@link RegistryHasher#enumIdRegistry(Enum[], Function)}. + * + *

Please be aware that you are using literal enum constants as key paths here, meaning that if there is a typo in a constant, or a constant changes name, things + * may break. Use cautiously.

+ * + * @param values the array of {@link EnumConstant}s. + * @param the enum. + * @see RegistryHasher#enumIdRegistry(Enum[], Function) + */ static > RegistryHasher enumIdRegistry(EnumConstant[] values) { return enumIdRegistry(values, constant -> MinecraftKey.key(constant.name().toLowerCase(Locale.ROOT))); } + /** + * Creates a hasher that looks up a network ID in the array of {@link EnumConstant}s, and then uses {@code toKey} to turn the constant into a key, which it then hashes. + * + * @param values the array of {@link EnumConstant}s. + * @param toKey the function that turns a {@link EnumConstant} into a {@link Key}. + * @param the enum. + * @see MinecraftHasher#fromIdEnum(Enum[]) + */ static > RegistryHasher enumIdRegistry(EnumConstant[] values, Function toKey) { MinecraftHasher hasher = KEY.cast(i -> toKey.apply(values[i])); return hasher::hash; diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java index 73c738d1c..9ee2ba1ae 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/entity/player/JavaPlayerPositionTranslator.java @@ -84,7 +84,7 @@ public class JavaPlayerPositionTranslator extends PacketTranslator