diff --git a/pom.xml b/pom.xml index 30ccd623f7..02d7aea8a2 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,11 @@ codemc-repo https://repo.codemc.io/repository/maven-public/ + + + opencollab-snapshot + https://repo.opencollab.dev/main/ + @@ -238,7 +243,7 @@ com.github.mcchampions.dough dough-api - 0facd94827 + 7136585ffd io.papermc @@ -414,5 +419,13 @@ b7a2bd8 compile + + + + org.geysermc.geyser + api + 2.4.2-SNAPSHOT + provided + diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/IntegrationsManager.java b/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/IntegrationsManager.java index afb588854b..2c875681f1 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/IntegrationsManager.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/integrations/IntegrationsManager.java @@ -12,6 +12,7 @@ import java.util.logging.Level; import lombok.Getter; +import me.qscbm.slimefun4.integrations.GeyserIntegration; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Server; @@ -138,6 +139,10 @@ private void onServerLoad() { * This method is called when the {@link Server} has finished loading all its {@link Plugin Plugins}. */ private void onServerStart() { + // Geyser Integration (custom skulls) + load("Geyser-Spigot", integration -> { + new GeyserIntegration().register(); + }); try { // Load Protection plugin integrations protectionManager = new ProtectionManager(plugin); diff --git a/src/main/java/me/qscbm/slimefun4/integrations/GeyserIntegration.java b/src/main/java/me/qscbm/slimefun4/integrations/GeyserIntegration.java new file mode 100644 index 0000000000..2dc8b438b3 --- /dev/null +++ b/src/main/java/me/qscbm/slimefun4/integrations/GeyserIntegration.java @@ -0,0 +1,58 @@ +package me.qscbm.slimefun4.integrations; + +import io.github.bakedlibs.dough.config.Config; +import io.github.thebusybiscuit.slimefun4.api.items.SlimefunItem; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import me.qscbm.slimefun4.utils.NBTUtils; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.SkullMeta; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class GeyserIntegration { + public void register() { + Slimefun.logger().info("开始加载自定义粘液科技Geyser支持"); + + Config config = new Config(new File("plugins/Geyser-Spigot/custom-skulls.yml")); + List l = config.getStringList("skin-hashes"); + int successCount = 0; + int errorCount = 0; + for (SlimefunItem slimefunItem : Slimefun.getRegistry().getAllSlimefunItems()) { + ItemStack itemStack = slimefunItem.getItem(); + if (itemStack.getType() != Material.PLAYER_HEAD) { + continue; + } + ItemMeta m = itemStack.getItemMeta(); + if (!(m instanceof SkullMeta meta)) { + errorCount++; + continue; + } + String textureCode = NBTUtils.getTexture(meta); + if (textureCode == null) { + errorCount++; + continue; + } + String[] ts = textureCode.split("/"); + String hash = ts[ts.length - 1]; + if (!l.contains(hash)) { + l.add(hash); + } + successCount++; + } + String[] i = {"player-names", "player-uuids", "player-profiles"}; + for (String t : i) { + if (config.getStringList(t).isEmpty()) { + config.setValue(t, new ArrayList<>()); + } + } + config.setValue("skin-hashes", l); + config.save(); + Slimefun.logger().info("成功加载" + successCount + "个自定义头颅"); + Slimefun.logger().info("加载失败" + errorCount + "个自定义头颅"); + Slimefun.logger().warning("完成加载自定义粘液科技Geyser支持,如果不生效请重启服务器"); + } +} diff --git a/src/main/java/me/qscbm/slimefun4/utils/NBTUtils.java b/src/main/java/me/qscbm/slimefun4/utils/NBTUtils.java new file mode 100644 index 0000000000..3ebde93b1c --- /dev/null +++ b/src/main/java/me/qscbm/slimefun4/utils/NBTUtils.java @@ -0,0 +1,105 @@ +package me.qscbm.slimefun4.utils; + +import com.destroystokyo.paper.profile.ProfileProperty; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import io.github.bakedlibs.dough.reflection.ReflectionUtils; +import io.github.thebusybiscuit.slimefun4.implementation.Slimefun; +import org.bukkit.Bukkit; +import org.bukkit.inventory.meta.SkullMeta; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Base64; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NBTUtils { + public static final String CRAFTBUKKIT_PACKAGE_NAME = Bukkit.getServer().getClass().getPackage().getName(); + + public static final String VERSION; + + private static final Class CRAFT_SKULL_META_CLAZZ; + + private static final Field SKULL_PROFILE; + + private static final Class RESOLVABLE_PROFILE; + + private static Method RESOLVABLE_PROFILE_GAME_PROFILE_GETTER = null; + + private static final Method PROPERTY_NAME_GETTER; + private static final Method PROPERTY_VALUE_GETTER; + + static { + String detectedVersion = CRAFTBUKKIT_PACKAGE_NAME.substring(CRAFTBUKKIT_PACKAGE_NAME.lastIndexOf('.') + 1); + if (!detectedVersion.startsWith("v")) { + // Paper or something... + detectedVersion = VersionUtils.getBukkitVersion(); + } + VERSION = detectedVersion; + try { + CRAFT_SKULL_META_CLAZZ = Class.forName(CRAFTBUKKIT_PACKAGE_NAME + ".inventory." + "CraftMetaSkull"); + SKULL_PROFILE = CRAFT_SKULL_META_CLAZZ.getDeclaredField("profile"); + SKULL_PROFILE.setAccessible(true); + } catch (ClassNotFoundException | NoSuchFieldException e) { + throw new RuntimeException(e); + } + Class resolvableProfile; + try { + resolvableProfile = Class.forName("net.minecraft.world.item.component.ResolvableProfile"); + RESOLVABLE_PROFILE_GAME_PROFILE_GETTER = ReflectionUtils.getMethod(resolvableProfile, "f"); + } catch (ClassNotFoundException e) { + resolvableProfile = null; + } + RESOLVABLE_PROFILE = resolvableProfile; + PROPERTY_NAME_GETTER = ReflectionUtils.getMethod(Property.class, "getName"); + PROPERTY_VALUE_GETTER = ReflectionUtils.getMethod(Property.class, "getValue"); + } + + public static String getTexture(SkullMeta skullMeta) { + try { + Object pro = SKULL_PROFILE.get(skullMeta); + if (RESOLVABLE_PROFILE != null && RESOLVABLE_PROFILE.isInstance(pro)) { + if (RESOLVABLE_PROFILE_GAME_PROFILE_GETTER != null) { + pro = RESOLVABLE_PROFILE_GAME_PROFILE_GETTER.invoke(pro); + } + } + + if (pro == null) { + return null; + } + GameProfile profile = (GameProfile) pro; + Collection properties = profile.getProperties().values(); + for (Property prop : properties) { + String texture = null; + if (PROPERTY_NAME_GETTER != null) { + if ("textures".equals(PROPERTY_NAME_GETTER.invoke(prop))) { + texture = new String(Base64.getDecoder().decode((String) PROPERTY_VALUE_GETTER.invoke(prop))); + } + } else { + if ("textures".equals(prop.name())) { + texture = new String(Base64.getDecoder().decode(prop.value())); + } + } + return getMatch(texture, "\\{\"url\":\"(.*?)\"\\}"); + } + return null; + } catch (IllegalAccessException | InvocationTargetException e) { + Slimefun.logger().warning(e.getMessage()); + return null; + } + } + + @SuppressWarnings("SameParameterValue") + private static String getMatch(String string, String regex) { + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(string); + if (matcher.find()) { + return matcher.group(1); + } else { + return null; + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 098a30a4b8..3cf20c0369 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -20,6 +20,7 @@ softdepend: - ItemsAdder - Vault - Orebfuscator + - Geyser-Spigot # We hook into these plugins too, but they depend on Slimefun. loadBefore: