1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Implement hashers for new object component type (#5935)

* Implement hashers for new object component type

* Remove extra whitespace
This commit is contained in:
Eclipse
2025-10-27 23:40:17 +00:00
committed by GitHub
parent 7375b748d7
commit 38893a9476
6 changed files with 135 additions and 8 deletions

View File

@@ -28,6 +28,7 @@ package org.geysermc.geyser.item.hashing;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.KeybindComponent;
import net.kyori.adventure.text.NBTComponent;
import net.kyori.adventure.text.ObjectComponent;
import net.kyori.adventure.text.ScoreComponent;
import net.kyori.adventure.text.SelectorComponent;
import net.kyori.adventure.text.TextComponent;
@@ -39,6 +40,9 @@ import net.kyori.adventure.text.format.ShadowColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.object.ObjectContents;
import net.kyori.adventure.text.object.PlayerHeadObjectContents;
import org.geysermc.geyser.item.hashing.data.ObjectContentsType;
import java.util.function.Supplier;
@@ -54,6 +58,17 @@ public interface ComponentHasher {
}
});
MinecraftHasher<PlayerHeadObjectContents.ProfileProperty> PROFILE_PROPERTY = MinecraftHasher.mapBuilder(builder -> builder
.accept("name", MinecraftHasher.STRING, PlayerHeadObjectContents.ProfileProperty::name)
.accept("value", MinecraftHasher.STRING, PlayerHeadObjectContents.ProfileProperty::value)
.accept("signature", MinecraftHasher.STRING, PlayerHeadObjectContents.ProfileProperty::signature));
MinecraftHasher<PlayerHeadObjectContents> RESOLVABLE_PROFILE = MinecraftHasher.mapBuilder(builder -> builder
.optionalNullable("name", MinecraftHasher.STRING, PlayerHeadObjectContents::name)
.optionalNullable("id", MinecraftHasher.UUID, PlayerHeadObjectContents::id)
.optionalList("properties", PROFILE_PROPERTY, PlayerHeadObjectContents::profileProperties)
.optionalNullable("texture", MinecraftHasher.KEY, PlayerHeadObjectContents::texture));
MinecraftHasher<NamedTextColor> NAMED_COLOR = MinecraftHasher.STRING.cast(NamedTextColor::toString);
MinecraftHasher<TextColor> DIRECT_COLOR = MinecraftHasher.STRING.cast(TextColor::asHexString);
@@ -151,6 +166,13 @@ public interface ComponentHasher {
.optional("interpret", MinecraftHasher.BOOL, NBTComponent::interpret, false)
.optionalNullable("separator", COMPONENT, NBTComponent::separator)); // TODO source key, needs kyori update?
MinecraftHasher<ObjectContentsType> OBJECT_CONTENTS_TYPE = MinecraftHasher.fromEnum(ObjectContentsType::getName);
MapBuilder<ObjectContents> OBJECT_CONTENTS = MapBuilder.dispatch("object", OBJECT_CONTENTS_TYPE, ObjectContentsType::fromContents, ObjectContentsType::mapBuilder);
MinecraftHasher<ObjectComponent> OBJECT_COMPONENT = component(builder -> builder
.accept(OBJECT_CONTENTS, ObjectComponent::contents));
MinecraftHasher<Component> ACTUAL_COMPONENT = (component, encoder) -> {
if (component instanceof TextComponent text) {
return TEXT_COMPONENT.hash(text, encoder);
@@ -164,9 +186,10 @@ public interface ComponentHasher {
return SELECTOR_COMPONENT.hash(selector, encoder);
} else if (component instanceof NBTComponent<?,?> nbt) {
return NBT_COMPONENT.hash(nbt, encoder);
} else if (component instanceof ObjectComponent object) {
return OBJECT_COMPONENT.hash(object, encoder);
}
// TODO support object component hashing!
throw new IllegalStateException("Unimplemented component hasher: " + component);
throw new UnsupportedOperationException("Unimplemented component hasher: " + component);
};
private static <T extends Component> MinecraftHasher<T> component(MapBuilder<T> componentBuilder) {

View File

@@ -57,6 +57,34 @@ public interface MapBuilder<Type> extends UnaryOperator<MapHasher<Type>> {
return builder -> builder;
}
/**
* Delegates to {@link MapBuilder#dispatch(String, MinecraftHasher, Function, Function)}, uses {@code "type"} as the {@code typeKey}.
*
* @see MapBuilder#dispatch(String, MinecraftHasher, Function, Function)
*/
static <Type, Dispatched> MapBuilder<Dispatched> dispatch(MinecraftHasher<Type> typeHasher, Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> hashDispatch) {
return dispatch("type", typeHasher, typeExtractor, hashDispatch);
}
/**
* Creates a map builder that dispatches a {@link Type} from a {@link Dispatched} using {@code typeExtractor}, puts this as the {@code typeKey} key in the map using the given {@code typeHasher},
* and uses a {@link MapBuilder} provided by {@code mapDispatch} to build the rest of the map.
*
* <p>This can be used to create map builders that build an abstract type or interface into a map with different keys depending on the type.</p>
*
* @param typeKey the key to store the {@link Type} in.
* @param typeHasher the hasher used to encode the {@link Type}.
* @param typeExtractor the function that extracts a {@link Type} from a {@link Dispatched}.
* @param mapDispatch the function that provides a {@link MapBuilder} based on a {@link Type}.
* @param <Type> the type of the {@code typeKey}.
* @param <Dispatched> the type of the new map builder.
*/
static <Type, Dispatched> MapBuilder<Dispatched> dispatch(String typeKey, MinecraftHasher<Type> typeHasher, Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> mapDispatch) {
return builder -> builder
.accept(typeKey, typeHasher, typeExtractor)
.accept(typeExtractor, mapDispatch);
}
/**
* Returns a function that creates a map builder from an NBT map. The builder simply adds all keys from the NBT map.
*

View File

@@ -216,6 +216,8 @@ public interface MinecraftHasher<Type> {
* Delegates to {@link MinecraftHasher#dispatch(String, Function, Function)}, uses {@code "type"} as the {@code typeKey}.
*
* @see MinecraftHasher#dispatch(String, Function, Function)
* @see MapBuilder#dispatch(MinecraftHasher, Function, Function)
* @see MapBuilder#dispatch(String, MinecraftHasher, Function, Function)
*/
default <Dispatched> MinecraftHasher<Dispatched> dispatch(Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> hashDispatch) {
return dispatch("type", typeExtractor, hashDispatch);
@@ -227,15 +229,17 @@ public interface MinecraftHasher<Type> {
*
* <p>This can be used to create hashers that hash an abstract type or interface into a map with different keys depending on the type.</p>
*
* <p>Internally this simply delegates to and wraps {@link MapBuilder#dispatch(String, MinecraftHasher, Function, Function)} in {@link MinecraftHasher#mapBuilder(MapBuilder)},
* using {@code this} as {@code typeHasher}.</p>
*
* @param typeKey the key to store the {@link Type} in.
* @param typeExtractor the function that extracts a {@link Type} from a {@link Dispatched}.
* @param mapDispatch the function that provides a {@link MapBuilder} based on a {@link Type}.
* @param <Dispatched> the type of the new hasher.
* @see MapBuilder#dispatch(String, MinecraftHasher, Function, Function)
*/
default <Dispatched> MinecraftHasher<Dispatched> dispatch(String typeKey, Function<Dispatched, Type> typeExtractor, Function<Type, MapBuilder<Dispatched>> mapDispatch) {
return mapBuilder(builder -> builder
.accept(typeKey, this, typeExtractor)
.accept(typeExtractor, mapDispatch));
return mapBuilder(MapBuilder.dispatch(typeKey, this, typeExtractor, mapDispatch));
}
/**

View File

@@ -304,7 +304,7 @@ public interface RegistryHasher<DirectType> extends MinecraftHasher<Integer> {
MinecraftHasher<ConsumeEffectType> CONSUME_EFFECT_TYPE = enumRegistry();
MinecraftHasher<ConsumeEffect> CONSUME_EFFECT = CONSUME_EFFECT_TYPE.dispatch(ConsumeEffectType::fromEffect, type -> type.getBuilder().cast());
MinecraftHasher<ConsumeEffect> CONSUME_EFFECT = CONSUME_EFFECT_TYPE.dispatch(ConsumeEffectType::fromEffect, ConsumeEffectType::mapBuilder);
MinecraftHasher<SuspiciousStewEffect> SUSPICIOUS_STEW_EFFECT = MinecraftHasher.mapBuilder(builder -> builder
.accept("id", EFFECT_ID, SuspiciousStewEffect::getMobEffectId)

View File

@@ -25,7 +25,6 @@
package org.geysermc.geyser.item.hashing.data;
import lombok.Getter;
import org.geysermc.geyser.item.hashing.MapBuilder;
import org.geysermc.geyser.item.hashing.MinecraftHasher;
import org.geysermc.geyser.item.hashing.RegistryHasher;
@@ -44,7 +43,6 @@ public enum ConsumeEffectType {
.accept("sound", RegistryHasher.SOUND_EVENT, ConsumeEffect.PlaySound::sound));
private final Class<? extends ConsumeEffect> clazz;
@Getter
private final MapBuilder<? extends ConsumeEffect> builder;
<T extends ConsumeEffect> ConsumeEffectType(Class<T> clazz) {
@@ -57,6 +55,10 @@ public enum ConsumeEffectType {
this.builder = builder;
}
public MapBuilder<ConsumeEffect> mapBuilder() {
return builder.cast();
}
public static ConsumeEffectType fromEffect(ConsumeEffect effect) {
Class<? extends ConsumeEffect> clazz = effect.getClass();
for (ConsumeEffectType type : values()) {

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2025 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.item.hashing.data;
import lombok.Getter;
import net.kyori.adventure.text.object.ObjectContents;
import net.kyori.adventure.text.object.PlayerHeadObjectContents;
import net.kyori.adventure.text.object.SpriteObjectContents;
import org.geysermc.geyser.item.hashing.ComponentHasher;
import org.geysermc.geyser.item.hashing.MapBuilder;
import org.geysermc.geyser.item.hashing.MinecraftHasher;
import java.util.function.Function;
public enum ObjectContentsType {
ATLAS("atlas", SpriteObjectContents.class,
builder -> builder
.optional("atlas", MinecraftHasher.KEY, SpriteObjectContents::atlas, SpriteObjectContents.DEFAULT_ATLAS)
.accept("sprite", MinecraftHasher.KEY, SpriteObjectContents::sprite)),
PLAYER("player", PlayerHeadObjectContents.class,
builder -> builder
.accept("player", ComponentHasher.RESOLVABLE_PROFILE, Function.identity())
.optional("hat", MinecraftHasher.BOOL, PlayerHeadObjectContents::hat, true));
@Getter
private final String name;
private final MapBuilder<? extends ObjectContents> builder;
@SuppressWarnings("unused") // So Java knows what T we are talking about
<T extends ObjectContents> ObjectContentsType(String name, Class<T> clazz, MapBuilder<T> builder) {
this.name = name;
this.builder = builder;
}
public MapBuilder<ObjectContents> mapBuilder() {
return builder.cast();
}
public static ObjectContentsType fromContents(ObjectContents contents) {
if (contents instanceof SpriteObjectContents) {
return ATLAS;
} else if (contents instanceof PlayerHeadObjectContents) {
return PLAYER;
}
throw new UnsupportedOperationException("Don't know how to hash object contents of type " + contents.getClass());
}
}