From 1fa441dec5c873b2bea118073b3130dd8aed4a49 Mon Sep 17 00:00:00 2001 From: kashike Date: Fri, 28 Jun 2019 18:05:22 -0700 Subject: [PATCH 1/2] build: version to 3.0.4-SNAPSHOT --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f45121a7..4ec7881b 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ subprojects { apply plugin: 'net.minecrell.licenser' group 'net.kyori' - version '3.0.3' + version '3.0.4-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 From 34b7ce27db67d25e8cbf7e23de8a4a0588891971 Mon Sep 17 00:00:00 2001 From: kashike Date: Sun, 30 Jun 2019 11:46:28 -0700 Subject: [PATCH 2/2] nbt renderer --- .../kyori/text/adapter/bukkit/Adapter.java | 5 + .../adapter/bukkit/CraftBukkitAdapter.java | 111 ++++++++++-------- .../text/adapter/bukkit/NbtRenderer.java | 96 +++++++++++++++ .../net/kyori/text/adapter/bukkit/Shiny.java | 69 +++++++++++ 4 files changed, 229 insertions(+), 52 deletions(-) create mode 100644 adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/NbtRenderer.java create mode 100644 adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/Shiny.java diff --git a/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/Adapter.java b/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/Adapter.java index 2f1a96a7..7ec74af8 100644 --- a/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/Adapter.java +++ b/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/Adapter.java @@ -24,6 +24,7 @@ package net.kyori.text.adapter.bukkit; import net.kyori.text.Component; +import net.kyori.text.serializer.gson.GsonComponentSerializer; import org.bukkit.command.CommandSender; import java.util.List; @@ -38,4 +39,8 @@ interface Adapter { * @param actionBar if action bar */ void sendComponent(final List viewers, final Component component, final boolean actionBar); + + static String asJson(final Component component) { + return GsonComponentSerializer.INSTANCE.serialize(component); + } } diff --git a/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/CraftBukkitAdapter.java b/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/CraftBukkitAdapter.java index 6a70c68e..ea710711 100644 --- a/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/CraftBukkitAdapter.java +++ b/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/CraftBukkitAdapter.java @@ -26,7 +26,6 @@ import com.google.gson.JsonDeserializer; import net.kyori.text.Component; import net.kyori.text.serializer.gson.GsonComponentSerializer; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -40,30 +39,28 @@ import java.util.List; final class CraftBukkitAdapter implements Adapter { - private static final Binding REFLECTION_BINDINGS = load(); + static final Binding BINDING = load(); private static Binding load() { try { - final Class server = Bukkit.getServer().getClass(); - if(!isCompatibleServer(server)) { + if(!Shiny.isCompatibleServer()) { throw new UnsupportedOperationException("Incompatible server version"); } - final String serverVersion = maybeVersion(server.getPackage().getName().substring("org.bukkit.craftbukkit".length())); - final Class craftPlayerClass = craftBukkitClass(serverVersion, "entity.CraftPlayer"); + final Class craftPlayerClass = Shiny.craftClass("entity.CraftPlayer"); final Method getHandleMethod = craftPlayerClass.getMethod("getHandle"); final Class entityPlayerClass = getHandleMethod.getReturnType(); final Field playerConnectionField = entityPlayerClass.getField("playerConnection"); final Class playerConnectionClass = playerConnectionField.getType(); - final Class packetClass = minecraftClass(serverVersion, "Packet"); + final Class packetClass = Shiny.vanillaClass("Packet"); final Method sendPacketMethod = playerConnectionClass.getMethod("sendPacket", packetClass); - final Class baseComponentClass = minecraftClass(serverVersion, "IChatBaseComponent"); - final Class chatPacketClass = minecraftClass(serverVersion, "PacketPlayOutChat"); + final Class baseComponentClass = Shiny.vanillaClass("IChatBaseComponent"); + final Class chatPacketClass = Shiny.vanillaClass("PacketPlayOutChat"); final Constructor chatPacketConstructor = chatPacketClass.getConstructor(baseComponentClass); - final Class titlePacketClass = optionalMinecraftClass(serverVersion, "PacketPlayOutTitle"); + final Class titlePacketClass = Shiny.maybeVanillaClass("PacketPlayOutTitle"); final Class titlePacketClassAction; final Constructor titlePacketConstructor; if(titlePacketClass != null) { - titlePacketClassAction = (Class) minecraftClass(serverVersion, "PacketPlayOutTitle$EnumTitleAction"); + titlePacketClassAction = (Class) Shiny.vanillaClass("PacketPlayOutTitle$EnumTitleAction"); titlePacketConstructor = titlePacketClass.getConstructor(titlePacketClassAction, baseComponentClass); } else { titlePacketClassAction = null; @@ -75,56 +72,32 @@ private static Binding load() { // fallback to the 1.7 class? .orElseGet(() -> { try { - return minecraftClass(serverVersion, "ChatSerializer"); + return Shiny.vanillaClass("ChatSerializer"); } catch(final ClassNotFoundException e) { throw new RuntimeException(e); } }); - final Method serializeMethod = Arrays.stream(chatSerializerClass.getMethods()) + final Method deserializeMethod = Arrays.stream(chatSerializerClass.getMethods()) .filter(m -> Modifier.isStatic(m.getModifiers())) .filter(m -> m.getReturnType().equals(baseComponentClass)) .filter(m -> m.getParameterCount() == 1 && m.getParameterTypes()[0].equals(String.class)) .min(Comparator.comparing(Method::getName)) // prefer the #a method + .orElseThrow(() -> new RuntimeException("Unable to find deserialize method")); + final Method serializeMethod = Arrays.stream(chatSerializerClass.getMethods()) + .filter(m -> Modifier.isStatic(m.getModifiers())) + .filter(m -> m.getReturnType().equals(String.class)) + .filter(m -> m.getParameterCount() == 1 && m.getParameterTypes()[0].equals(baseComponentClass)) + .min(Comparator.comparing(Method::getName)) // prefer the #a method .orElseThrow(() -> new RuntimeException("Unable to find serialize method")); - return new AliveBinding(getHandleMethod, playerConnectionField, sendPacketMethod, chatPacketConstructor, titlePacketClassAction, titlePacketConstructor, serializeMethod); + return new AliveBinding(getHandleMethod, playerConnectionField, sendPacketMethod, chatPacketConstructor, titlePacketClassAction, titlePacketConstructor, deserializeMethod, serializeMethod); } catch(final Throwable e) { return new DeadBinding(); } } - private static boolean isCompatibleServer(final Class serverClass) { - return serverClass.getPackage().getName().startsWith("org.bukkit.craftbukkit") - && serverClass.getSimpleName().equals("CraftServer"); - } - - private static Class craftBukkitClass(final String version, final String name) throws ClassNotFoundException { - return Class.forName("org.bukkit.craftbukkit." + version + name); - } - - private static Class minecraftClass(final String version, final String name) throws ClassNotFoundException { - return Class.forName("net.minecraft.server." + version + name); - } - - private static String maybeVersion(final String version) { - if(version.isEmpty()) { - return ""; - } else if(version.charAt(0) == '.') { - return version.substring(1) + '.'; - } - throw new IllegalArgumentException("Unknown version " + version); - } - - private static Class optionalMinecraftClass(final String version, final String name) { - try { - return minecraftClass(version, name); - } catch(final ClassNotFoundException e) { - return null; - } - } - @Override public void sendComponent(final List viewers, final Component component, final boolean actionBar) { - if(!REFLECTION_BINDINGS.valid()) { + if(!BINDING.valid()) { return; } Object packet = null; @@ -134,9 +107,9 @@ public void sendComponent(final List viewers, final Com try { final Player player = (Player) sender; if(packet == null) { - packet = REFLECTION_BINDINGS.createPacket(component, actionBar); + packet = BINDING.createPacket(component, actionBar); } - REFLECTION_BINDINGS.sendPacket(packet, player); + BINDING.sendPacket(packet, player); iterator.remove(); } catch(final Exception e) { e.printStackTrace(); @@ -145,12 +118,16 @@ public void sendComponent(final List viewers, final Com } } - private static abstract class Binding { + static abstract class Binding { abstract boolean valid(); abstract Object createPacket(final Component component, final boolean actionBar); abstract void sendPacket(final Object packet, final Player player); + + abstract Component asKyori(final Object object); + + abstract Object asVanilla(final Component component); } private static final class DeadBinding extends Binding { @@ -168,6 +145,16 @@ Object createPacket(final Component component, final boolean actionBar) { void sendPacket(final Object packet, final Player player) { throw new UnsupportedOperationException(); } + + @Override + Component asKyori(final Object object) { + throw new UnsupportedOperationException(); + } + + @Override + Object asVanilla(final Component component) { + throw new UnsupportedOperationException(); + } } private static final class AliveBinding extends Binding { @@ -178,9 +165,10 @@ private static final class AliveBinding extends Binding { private final Class titlePacketClassAction; private final Constructor titlePacketConstructor; private final boolean canMakeTitle; + private final Method deserializeMethod; private final Method serializeMethod; - AliveBinding(final Method getHandleMethod, final Field playerConnectionField, final Method sendPacketMethod, final Constructor chatPacketConstructor, final Class titlePacketClassAction, final Constructor titlePacketConstructor, final Method serializeMethod) { + AliveBinding(final Method getHandleMethod, final Field playerConnectionField, final Method sendPacketMethod, final Constructor chatPacketConstructor, final Class titlePacketClassAction, final Constructor titlePacketConstructor, final Method deserializeMethod, final Method serializeMethod) { this.getHandleMethod = getHandleMethod; this.playerConnectionField = playerConnectionField; this.sendPacketMethod = sendPacketMethod; @@ -188,6 +176,7 @@ private static final class AliveBinding extends Binding { this.titlePacketClassAction = titlePacketClassAction; this.titlePacketConstructor = titlePacketConstructor; this.canMakeTitle = this.titlePacketClassAction != null && this.titlePacketConstructor != null; + this.deserializeMethod = deserializeMethod; this.serializeMethod = serializeMethod; } @@ -198,7 +187,7 @@ boolean valid() { @Override Object createPacket(final Component component, final boolean actionBar) { - final String json = GsonComponentSerializer.INSTANCE.serialize(component); + final String json = Adapter.asJson(component); try { if(actionBar && this.canMakeTitle) { Enum constant; @@ -207,9 +196,9 @@ Object createPacket(final Component component, final boolean actionBar) { } catch(final IllegalArgumentException e) { constant = this.titlePacketClassAction.getEnumConstants()[2]; } - return this.titlePacketConstructor.newInstance(constant, this.serializeMethod.invoke(null, json)); + return this.titlePacketConstructor.newInstance(constant, this.deserializeMethod.invoke(null, json)); } else { - return this.chatPacketConstructor.newInstance(this.serializeMethod.invoke(null, json)); + return this.chatPacketConstructor.newInstance(this.deserializeMethod.invoke(null, json)); } } catch(final Exception e) { throw new UnsupportedOperationException("An exception was encountered while creating a packet for a component", e); @@ -225,5 +214,23 @@ void sendPacket(final Object packet, final Player player) { throw new UnsupportedOperationException("An exception was encountered while sending a packet for a component", e); } } + + @Override + Component asKyori(final Object object) { + try { + return GsonComponentSerializer.INSTANCE.deserialize((String) this.serializeMethod.invoke(null, object)); + } catch(final Throwable t) { + throw new UnsupportedOperationException("An exception was encountered while converting from vanilla Component", t); + } + } + + @Override + Object asVanilla(final Component component) { + try { + return this.deserializeMethod.invoke(null, Adapter.asJson(component)); + } catch(final Throwable t) { + throw new UnsupportedOperationException("An exception was encountered while converting to vanilla Component", t); + } + } } } diff --git a/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/NbtRenderer.java b/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/NbtRenderer.java new file mode 100644 index 00000000..d7a26a23 --- /dev/null +++ b/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/NbtRenderer.java @@ -0,0 +1,96 @@ +/* + * This file is part of text-extras, licensed under the MIT License. + * + * Copyright (c) 2018 KyoriPowered + * + * 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. + */ +package net.kyori.text.adapter.bukkit; + +import java.lang.reflect.Method; +import java.util.Optional; +import net.kyori.text.BlockNbtComponent; +import net.kyori.text.Component; +import net.kyori.text.EntityNbtComponent; +import net.kyori.text.renderer.AbstractDeepComponentRenderer; +import org.bukkit.entity.Entity; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +public final class NbtRenderer extends AbstractDeepComponentRenderer { + static final NbtRenderer RENDERER = tryMake(); + private final Class chatComponentContextualClass; + private final Method handle; + private final Method render; + private final Method createStack; + + private static @Nullable NbtRenderer tryMake() { + try { + final Class chatComponentContextualClass = Shiny.vanillaClass("ChatComponentContextual"); + final Class commandListenerWrapperClass = Shiny.vanillaClass("CommandListenerWrapper"); + final Class entityClass = Shiny.vanillaClass("Entity"); + final Method handle = Shiny.craftClass("entity.CraftEntity").getMethod("getHandle"); + final Method renderMethod = chatComponentContextualClass.getMethod("a", commandListenerWrapperClass, entityClass, int.class); + final Method createStackMethod = entityClass.getMethod("getCommandListener"); + return new NbtRenderer(chatComponentContextualClass, handle, renderMethod, createStackMethod); + } catch(final Throwable t) { + return null; + } + } + + private NbtRenderer(final Class chatComponentContextualClass, final Method handle, final Method render, final Method createStack) { + this.chatComponentContextualClass = chatComponentContextualClass; + this.handle = handle; + this.render = render; + this.createStack = createStack; + } + + public static Component tryRender(final Component component, final Entity context) { + if(RENDERER != null) { + return RENDERER.render(component, context); + } + return component; + } + + @Override + protected @NonNull Component render(@NonNull final BlockNbtComponent component, @NonNull final Entity context) { + Object vanilla = CraftBukkitAdapter.BINDING.asVanilla(component); + vanilla = this.render(context, vanilla); + return Optional.ofNullable(CraftBukkitAdapter.BINDING.asKyori(vanilla)).orElse(component); + } + + @Override + protected @NonNull Component render(@NonNull final EntityNbtComponent component, @NonNull final Entity context) { + Object vanilla = CraftBukkitAdapter.BINDING.asVanilla(component); + vanilla = this.render(context, vanilla); + return Optional.ofNullable(CraftBukkitAdapter.BINDING.asKyori(vanilla)).orElse(component); + } + + private Object render(final Entity viewer, final Object component) { + if(this.chatComponentContextualClass.isInstance(component)) { + try { + final Object stack = this.createStack.invoke(this.handle.invoke(viewer)); + return this.render.invoke(component, stack, null, 0); + } catch(final Throwable t) { + // TODO + } + } + return component; + } +} diff --git a/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/Shiny.java b/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/Shiny.java new file mode 100644 index 00000000..bf9a27dc --- /dev/null +++ b/adapter-bukkit/src/main/java/net/kyori/text/adapter/bukkit/Shiny.java @@ -0,0 +1,69 @@ +/* + * This file is part of text-extras, licensed under the MIT License. + * + * Copyright (c) 2018 KyoriPowered + * + * 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. + */ +package net.kyori.text.adapter.bukkit; + +import org.bukkit.Bukkit; + +@SuppressWarnings("SpellCheckingInspection") +final class Shiny { + private static final String PAKACGE_OBC = "org.bukkit.craftbukkit"; + private static final String PAKACGE_NMS = "net.minecraft.server"; + private static final String VERSION = getVersion(); + + static boolean isCompatibleServer() { + final Class server = Bukkit.getServer().getClass(); + return server.getPackage().getName().startsWith(PAKACGE_OBC) && server.getSimpleName().equals("CraftServer"); + } + + private static String getVersion() { + final Class server = Bukkit.getServer().getClass(); + final String version = server.getPackage().getName().substring("org.bukkit.craftbukkit".length()); + if(version.isEmpty()) { + return ""; + } else if(version.charAt(0) == '.') { + return version.substring(1) + '.'; + } + throw new IllegalArgumentException("Unknown version " + version); + } + + static Class craftClass(final String name) throws ClassNotFoundException { + return Class.forName(getPackage(PAKACGE_OBC) + name); + } + + static Class vanillaClass(final String name) throws ClassNotFoundException { + return Class.forName(getPackage(PAKACGE_NMS) + name); + } + + static Class maybeVanillaClass(final String name) { + try { + return vanillaClass(name); + } catch(final ClassNotFoundException e) { + return null; + } + } + + private static String getPackage(final String type) { + return type + '.' + VERSION; + } +}