diff --git a/build.gradle b/build.gradle index bc2f13b..86a7eb0 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { defaultTasks "build" group "eu.decentsoftware.holograms" -version "2.8.11" +version "2.8.12" description "A lightweight yet very powerful hologram plugin with many features and configuration options." repositories { diff --git a/src/main/java/eu/decentsoftware/holograms/api/utils/items/SkullUtils.java b/src/main/java/eu/decentsoftware/holograms/api/utils/items/SkullUtils.java index 2b21404..8b8ee62 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/utils/items/SkullUtils.java +++ b/src/main/java/eu/decentsoftware/holograms/api/utils/items/SkullUtils.java @@ -4,9 +4,11 @@ import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.PropertyMap; import eu.decentsoftware.holograms.api.utils.Log; +import eu.decentsoftware.holograms.api.utils.reflect.ReflectionUtil; import eu.decentsoftware.holograms.api.utils.reflect.Version; import lombok.NonNull; import lombok.experimental.UtilityClass; +import org.bukkit.Bukkit; import org.bukkit.SkullType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -18,6 +20,7 @@ import java.io.BufferedReader; import java.io.InputStreamReader; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -42,9 +45,24 @@ public final class SkullUtils { private static Method SET_PROFILE_METHOD; private static boolean INITIALIZED = false; + private static Constructor RESOLVABLE_PROFILE_CONSTRUCTOR; + private static Field GAME_PROFILE_FIELD_RESOLVABLE_PROFILE; + private static Method PROPERTY_VALUE_METHOD; private static Function VALUE_RESOLVER; + static { + try { + Class resolvableProfileClass = ReflectionUtil.getNMClass("world.item.component.ResolvableProfile"); + RESOLVABLE_PROFILE_CONSTRUCTOR = resolvableProfileClass == null ? null : resolvableProfileClass.getConstructor(GameProfile.class); + + // find the game profile field in the resolvable profile class + GAME_PROFILE_FIELD_RESOLVABLE_PROFILE = ReflectionUtil.findField(resolvableProfileClass, GameProfile.class); + } catch ( NoSuchMethodException ignored) { + // old version, no resolvable profile class. + } + } + /** * Get the Base64 texture of the given skull ItemStack. * @@ -58,16 +76,17 @@ public static String getSkullTexture(@NonNull ItemStack itemStack) { if (!(meta instanceof SkullMeta)) { return null; } + // private ResolvableProfile profile; if (PROFILE_FIELD == null) { PROFILE_FIELD = meta.getClass().getDeclaredField("profile"); PROFILE_FIELD.setAccessible(true); } + Object profileObject = PROFILE_FIELD.get(meta); + if (profileObject == null) return null; - GameProfile profile = (GameProfile) PROFILE_FIELD.get(meta); - if (profile == null) { - return null; - } + GameProfile profile = (GameProfile) (GAME_PROFILE_FIELD_RESOLVABLE_PROFILE == null ? profileObject : GAME_PROFILE_FIELD_RESOLVABLE_PROFILE.get(profileObject)); + if (profile == null) return null; if (VALUE_RESOLVER == null) { try { @@ -128,12 +147,11 @@ public static void setSkullTexture(@NonNull ItemStack itemStack, @NonNull String PropertyMap properties = profile.getProperties(); properties.put("textures", property); - if (SET_PROFILE_METHOD == null && !INITIALIZED) { try { // This method only exists in versions 1.16 and up. For older versions, we use reflection // to set the profile field directly. - SET_PROFILE_METHOD = meta.getClass().getDeclaredMethod("setProfile", GameProfile.class); + SET_PROFILE_METHOD = meta.getClass().getDeclaredMethod("setProfile", RESOLVABLE_PROFILE_CONSTRUCTOR == null ? GameProfile.class : RESOLVABLE_PROFILE_CONSTRUCTOR.getDeclaringClass()); SET_PROFILE_METHOD.setAccessible(true); } catch (NoSuchMethodException e) { // Server is running an older version. @@ -142,7 +160,7 @@ public static void setSkullTexture(@NonNull ItemStack itemStack, @NonNull String } if (SET_PROFILE_METHOD != null) { - SET_PROFILE_METHOD.invoke(meta, profile); + SET_PROFILE_METHOD.invoke(meta, RESOLVABLE_PROFILE_CONSTRUCTOR == null ? profile : RESOLVABLE_PROFILE_CONSTRUCTOR.newInstance(profile)); } else { if (PROFILE_FIELD == null) { PROFILE_FIELD = meta.getClass().getDeclaredField("profile"); diff --git a/src/main/java/eu/decentsoftware/holograms/api/utils/reflect/ReflectionUtil.java b/src/main/java/eu/decentsoftware/holograms/api/utils/reflect/ReflectionUtil.java index 244d234..522dc27 100644 --- a/src/main/java/eu/decentsoftware/holograms/api/utils/reflect/ReflectionUtil.java +++ b/src/main/java/eu/decentsoftware/holograms/api/utils/reflect/ReflectionUtil.java @@ -71,6 +71,28 @@ public static T getFieldValue(final @NotNull Object object, final @NotNull S return null; } + /** + * Find a field with in a class with a specific type. + *

+ * If the field is not found, this method will return null. + * + * @param clazz The class to get the field from. + * @param type The class type of the field. + * @return The field, or null if the field was not found. + */ + public static Field findField(Class clazz, Class type) { + if (clazz == null) return null; + + Field[] methods = clazz.getDeclaredFields(); + for (Field method : methods) { + if (!method.getType().equals(type)) continue; + + method.setAccessible(true); + return method; + } + return null; + } + /** * Get a field with the given name from the given class. *