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