diff --git a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java b/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java index f0777e9..3963b7b 100644 --- a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java +++ b/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java @@ -27,6 +27,8 @@ import java.awt.*; import java.io.InputStream; import java.util.Arrays; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports; + /** * Additional functionality on top of Bukkit's MapPalette */ @@ -50,14 +52,12 @@ public class MapColorPalette { MCSDBubbleFormat bubbleData = new MCSDBubbleFormat(); try { String bub_path_postfix; - if (ReflectionUtil.supports(17)) { + if (supports(17)) { bub_path_postfix = "map_1_17.bub"; - } else if (ReflectionUtil.supports(16)) { + } else if (supports(16)) { bub_path_postfix = "map_1_16.bub"; - } else if (ReflectionUtil.supports(12)) { - bub_path_postfix = "map_1_12.bub"; } else { - bub_path_postfix = "map_1_8_8.bub"; + bub_path_postfix = "map_1_12.bub"; } String bub_path = "/tech/sbdevelopment/mapreflectionapi/libs/bkcommonlib/internal/resources/map/" + bub_path_postfix; InputStream input = MapColorPalette.class.getResourceAsStream(bub_path); diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java index 70b7368..7461be4 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java @@ -35,6 +35,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager; import java.util.logging.Level; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports; + public class MapReflectionAPI extends JavaPlugin { private static MapReflectionAPI instance; private static MapManager mapManager; @@ -67,7 +69,7 @@ public class MapReflectionAPI extends JavaPlugin { getLogger().info("MapReflectionAPI v" + getDescription().getVersion()); getLogger().info("Made by © Copyright SBDevelopment 2023"); - if (!ReflectionUtil.supports(12)) { + if (!supports(12)) { getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.19.4!"); Bukkit.getPluginManager().disablePlugin(this); return; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java index 56fcaed..2cb8844 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java @@ -27,6 +27,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.util.ArrayList; import java.util.List; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; + /** * The {@link MapSender} sends the Map packets to players. */ @@ -82,8 +84,8 @@ public class MapSender { }, 0, 2); } - private static final Class packetPlayOutMapClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutMap"); - private static final Class worldMapData = ReflectionUtil.supports(17) ? ReflectionUtil.getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null; + private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); + private static final Class worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null; /** * Send a map to a player @@ -110,7 +112,7 @@ public class MapSender { final int id = -id0; Object packet; - if (ReflectionUtil.supports(17)) { //1.17+ + if (supports(17)) { //1.17+ Object updateData = ReflectionUtil.callConstructor(worldMapData, content.minX, //X pos content.minY, //Y pos @@ -126,7 +128,7 @@ public class MapSender { new ReflectionUtil.CollectionParam<>(), //Icons updateData ); - } else if (ReflectionUtil.supports(14)) { //1.16-1.14 + } else if (supports(14)) { //1.16-1.14 packet = ReflectionUtil.callConstructor(packetPlayOutMapClass, id, //ID (byte) 0, //Scale, 0 = 1 block per pixel @@ -153,7 +155,7 @@ public class MapSender { ); } - ReflectionUtil.sendPacket(player, packet); + sendPacket(player, packet); } @Data diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java index 7e8e82a..4dca45d 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java @@ -38,6 +38,8 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; + /** * A {@link MapWrapper} wraps one image. */ @@ -48,7 +50,7 @@ public class MapWrapper extends AbstractMapWrapper { private static final Material MAP_MATERIAL; static { - MAP_MATERIAL = ReflectionUtil.supports(13) ? Material.FILLED_MAP : Material.MAP; + MAP_MATERIAL = supports(13) ? Material.FILLED_MAP : Material.MAP; } /** @@ -60,13 +62,13 @@ public class MapWrapper extends AbstractMapWrapper { this.content = image; } - private static final Class craftStackClass = ReflectionUtil.getCraftClass("inventory.CraftItemStack"); - private static final Class setSlotPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutSetSlot"); - private static final Class entityClass = ReflectionUtil.getNMSClass("world.entity", "Entity"); - private static final Class dataWatcherClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher"); - private static final Class entityMetadataPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata"); - private static final Class entityItemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame"); - private static final Class dataWatcherItemClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$Item"); + private static final Class craftStackClass = getCraftClass("inventory.CraftItemStack"); + private static final Class setSlotPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutSetSlot"); + private static final Class entityClass = getNMSClass("world.entity", "Entity"); + private static final Class dataWatcherClass = getNMSClass("network.syncher", "DataWatcher"); + private static final Class entityMetadataPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata"); + private static final Class entityItemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); + private static final Class dataWatcherItemClass = getNMSClass("network.syncher", "DataWatcher$Item"); protected MapController controller = new MapController() { private final Map viewers = new HashMap<>(); @@ -163,21 +165,21 @@ public class MapWrapper extends AbstractMapWrapper { } String inventoryMenuName; - if (ReflectionUtil.supports(20)) { //1.20 + if (supports(20)) { //1.20 inventoryMenuName = "bQ"; - } else if (ReflectionUtil.supports(19)) { //1.19 - inventoryMenuName = ReflectionUtil.VER_MINOR == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT - } else if (ReflectionUtil.supports(18)) { //1.18 - inventoryMenuName = ReflectionUtil.VER_MINOR == 1 ? "bV" : "bU"; //1.18.1 = ap, 1.18(.2) = ao - } else if (ReflectionUtil.supports(17)) { //1.17, same as 1.18(.2) + } else if (supports(19)) { //1.19 + inventoryMenuName = PATCH_NUMBER == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT + } else if (supports(18)) { //1.18 + inventoryMenuName = PATCH_NUMBER == 1 ? "bV" : "bU"; //1.18.1 = ap, 1.18(.2) = ao + } else if (supports(17)) { //1.17, same as 1.18(.2) inventoryMenuName = "bU"; } else { //1.12-1.16 inventoryMenuName = "defaultContainer"; } - Object inventoryMenu = ReflectionUtil.getField(ReflectionUtil.getHandle(player), inventoryMenuName); + Object inventoryMenu = ReflectionUtil.getField(getHandle(player), inventoryMenuName); ItemStack stack; - if (ReflectionUtil.supports(13)) { + if (supports(13)) { stack = new ItemStack(MAP_MATERIAL, 1); } else { stack = new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player)); @@ -186,8 +188,8 @@ public class MapWrapper extends AbstractMapWrapper { Object nmsStack = createCraftItemStack(stack, (short) getMapId(player)); Object packet; - if (ReflectionUtil.supports(17)) { //1.17+ - int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, ReflectionUtil.supports(18) ? "j" : "getStateId"); + if (supports(17)) { //1.17+ + int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, supports(18) ? "j" : "getStateId"); packet = ReflectionUtil.callConstructor(setSlotPacketClass, 0, //0 = Player inventory @@ -203,7 +205,7 @@ public class MapWrapper extends AbstractMapWrapper { ); } - ReflectionUtil.sendPacketSync(player, packet); + sendPacketSync(player, packet); } @Override @@ -280,14 +282,14 @@ public class MapWrapper extends AbstractMapWrapper { @Override public ItemFrame getItemFrameById(World world, int entityId) { - Object worldHandle = ReflectionUtil.getHandle(world); - Object nmsEntity = ReflectionUtil.callMethod(worldHandle, ReflectionUtil.supports(18) ? "a" : "getEntity", entityId); + Object worldHandle = getHandle(world); + Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId); if (nmsEntity == null) return null; Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity"); if (craftEntity == null) return null; - Class itemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame"); + Class itemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame"); if (itemFrameClass == null) return null; if (craftEntity.getClass().isAssignableFrom(itemFrameClass)) @@ -301,19 +303,19 @@ public class MapWrapper extends AbstractMapWrapper { Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack); - if (ReflectionUtil.supports(13)) { + if (supports(13)) { String nbtObjectName; - if (ReflectionUtil.supports(20)) { //1.20 + if (supports(20)) { //1.20 nbtObjectName = "w"; - } else if (ReflectionUtil.supports(19)) { //1.19 + } else if (supports(19)) { //1.19 nbtObjectName = "v"; - } else if (ReflectionUtil.supports(18)) { //1.18 - nbtObjectName = ReflectionUtil.VER_MINOR == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u + } else if (supports(18)) { //1.18 + nbtObjectName = PATCH_NUMBER == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u } else { //1.13 - 1.17 nbtObjectName = "getOrCreateTag"; } Object nbtObject = ReflectionUtil.callMethod(nmsStack, nbtObjectName); - ReflectionUtil.callMethod(nbtObject, ReflectionUtil.supports(18) ? "a" : "setInt", "map", mapId); + ReflectionUtil.callMethod(nbtObject, supports(18) ? "a" : "setInt", "map", mapId); } return nmsStack; } @@ -322,17 +324,17 @@ public class MapWrapper extends AbstractMapWrapper { Object nmsStack = createCraftItemStack(stack, mapId); String dataWatcherObjectName; - if (ReflectionUtil.supports(19, 3)) { //1.19.3 and 1.20(.1) + if (supports(19, 3)) { //1.19.3 and 1.20(.1) dataWatcherObjectName = "g"; - } else if (ReflectionUtil.supports(19)) { //1.19-1.19.2 + } else if (supports(19)) { //1.19-1.19.2 dataWatcherObjectName = "ao"; - } else if (ReflectionUtil.supports(18)) { //1.18 - dataWatcherObjectName = ReflectionUtil.VER_MINOR == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao - } else if (ReflectionUtil.supports(17)) { //1.17 + } else if (supports(18)) { //1.18 + dataWatcherObjectName = PATCH_NUMBER == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao + } else if (supports(17)) { //1.17 dataWatcherObjectName = "ao"; - } else if (ReflectionUtil.supports(14)) { //1.14 - 1.16 + } else if (supports(14)) { //1.14 - 1.16 dataWatcherObjectName = "ITEM"; - } else if (ReflectionUtil.supports(13)) { //1.13 + } else if (supports(13)) { //1.13 dataWatcherObjectName = "e"; } else { //1.12 dataWatcherObjectName = "c"; @@ -342,8 +344,8 @@ public class MapWrapper extends AbstractMapWrapper { ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>(); Object packet; - if (ReflectionUtil.supports(19, 2)) { //1.19.3 - Class dataWatcherRecordClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$b"); + if (supports(19, 2)) { //1.19.3 + Class dataWatcherRecordClass = getNMSClass("network.syncher", "DataWatcher$b"); // Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter Object dataWatcherItem; try { @@ -374,7 +376,7 @@ public class MapWrapper extends AbstractMapWrapper { ReflectionUtil.setDeclaredField(packet, "b", list); } - ReflectionUtil.sendPacketSync(player, packet); + sendPacketSync(player, packet); } }; diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java index b4f5ed0..c7d15d4 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java @@ -39,6 +39,7 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil; import java.util.concurrent.TimeUnit; import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*; +import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*; public class PacketListener implements Listener { private static final Class packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap"); @@ -92,15 +93,15 @@ public class PacketListener implements Listener { Enum actionEnum; Enum hand; Object pos; - if (ReflectionUtil.supports(17)) { + if (supports(17)) { Object action = getDeclaredField(packetPlayInEntity, "b"); actionEnum = (Enum) callDeclaredMethod(action, "a"); //action type hand = hasField(action, "a") ? (Enum) getDeclaredField(action, "a") : null; pos = hasField(action, "b") ? getDeclaredField(action, "b") : null; } else { - actionEnum = (Enum) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a - hand = (Enum) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b - pos = callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c + actionEnum = (Enum) callDeclaredMethod(packetPlayInEntity, supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a + hand = (Enum) callDeclaredMethod(packetPlayInEntity, supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b + pos = callDeclaredMethod(packetPlayInEntity, supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c } if (Bukkit.getScheduler().callSyncMethod(MapReflectionAPI.getInstance(), () -> { @@ -115,8 +116,8 @@ public class PacketListener implements Listener { } else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) { Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet); - int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, ReflectionUtil.supports(19, 3) ? "a" : ReflectionUtil.supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a - Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, ReflectionUtil.supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack + int slot = (int) ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(19, 3) ? "a" : supports(13) ? "b" : "a"); //1.19.4 = a, 1.19.3 - 1.13 = b, 1.12 = a + Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack); boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread(); @@ -142,18 +143,18 @@ public class PacketListener implements Listener { private Channel getChannel(Player player) { Object playerHandle = getHandle(player); - Object playerConnection = getDeclaredField(playerHandle, ReflectionUtil.supports(20) ? "c" : ReflectionUtil.supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection - Object networkManager = getDeclaredField(playerConnection, ReflectionUtil.supports(19, 3) ? "h" : ReflectionUtil.supports(19) ? "b" : ReflectionUtil.supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager - return (Channel) getDeclaredField(networkManager, ReflectionUtil.supports(18) ? "m" : ReflectionUtil.supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel + Object playerConnection = getDeclaredField(playerHandle, supports(20) ? "c" : supports(17) ? "b" : "playerConnection"); //1.20 = c, 1.17-1.19 = b, 1.16 = playerConnection + Object networkManager = getDeclaredField(playerConnection, supports(19, 3) ? "h" : supports(19) ? "b" : supports(17) ? "a" : "networkManager"); //1.20 & 1.19.4 = h, >= 1.19.3 = b, 1.18 = a, 1.16 = networkManager + return (Channel) getDeclaredField(networkManager, supports(18) ? "m" : supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel } private Vector vec3DToVector(Object vec3d) { if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0); Object vec3dNMS = vec3DClass.cast(vec3d); - double x = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "c" : ReflectionUtil.supports(17) ? "b" : "x"); //1.19 = c, 1.18 = b, 1.16 = x - double y = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "d" : ReflectionUtil.supports(17) ? "c" : "y"); //1.19 = d, 1.18 = c, 1.16 = y - double z = (double) ReflectionUtil.getDeclaredField(vec3dNMS, ReflectionUtil.supports(19) ? "e" : ReflectionUtil.supports(17) ? "d" : "z"); //1.19 = e, 1.18 = d, 1.16 = z + double x = (double) getDeclaredField(vec3dNMS, supports(19) ? "c" : supports(17) ? "b" : "x"); //1.19 = c, 1.18 = b, 1.16 = x + double y = (double) getDeclaredField(vec3dNMS, supports(19) ? "d" : supports(17) ? "c" : "y"); //1.19 = d, 1.18 = c, 1.16 = y + double z = (double) getDeclaredField(vec3dNMS, supports(19) ? "e" : supports(17) ? "d" : "z"); //1.19 = e, 1.18 = d, 1.16 = z return new Vector(x, y, z); } diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java index d0288c6..b998d74 100644 --- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java @@ -18,205 +18,33 @@ package tech.sbdevelopment.mapreflectionapi.utils; -import org.bukkit.World; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.annotation.Nonnull; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.CompletableFuture; -/** - * ReflectionUtil - Reflection handler for NMS and CraftBukkit.
- * Caches the packet related methods and is asynchronous. - *

- * This class does not handle null checks as most of the requests are from the - * other utility classes that already handle null checks. - *

- * Clientbound Packets are considered fake - * updates to the client without changing the actual data. Since all the data is handled - * by the server. - * - * @author Crypto Morin, Stijn Bannink - * @version 2.1 - */ public class ReflectionUtil { - /** - * We use reflection mainly to avoid writing a new class for version barrier. - * The version barrier is for NMS that uses the Minecraft version as the main package name. - *

- * E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1} - * but in 1.14 it's in {@code net.minecraft.server.v1_14_R1} - * In order to maintain cross-version compatibility we cannot import these classes. - *

- * Performance is not a concern for these specific statically initialized values. - */ - public static final String VERSION; - - static { // This needs to be right below VERSION because of initialization order. - // This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion() - // which allows easier testing as well. - String found = null; - for (Package pack : Package.getPackages()) { - String name = pack.getName(); - - // .v because there are other packages. - if (name.startsWith("org.bukkit.craftbukkit.v")) { - found = pack.getName().split("\\.")[3]; - - // Just a final guard to make sure it finds this important class. - // As a protection for forge+bukkit implementation that tend to mix versions. - // The real CraftPlayer should exist in the package. - // Note: Doesn't seem to function properly. Will need to separate the version - // handler for NMS and CraftBukkit for softwares like catmc. - try { - Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer"); - break; - } catch (ClassNotFoundException e) { - found = null; - } - } - } - if (found == null) - throw new IllegalArgumentException("Failed to parse server version. Could not find any package starting with name: 'org.bukkit.craftbukkit.v'"); - VERSION = found; - } - - /** - * The raw minor version number. - * E.g. {@code v1_17_R1} to {@code 17} - * - * @since 4.0.0 - */ - public static final int VER = Integer.parseInt(VERSION.substring(1).split("_")[1]); - /** - * The raw minor version number. - * E.g. {@code v1_18_R2} to {@code 2} - * - * @since 4.0.0 - */ - public static final int VER_MINOR = toInt(VERSION.substring(1).split("_")[2].substring(1), 0); - /** - * Mojang remapped their NMS in 1.17 https://www.spigotmc.org/threads/spigot-bungeecord-1-17.510208/#post-4184317 - */ - public static final String - CRAFTBUKKIT = "org.bukkit.craftbukkit." + VERSION + '.', - NMS = v(17, "net.minecraft.").orElse("net.minecraft.server." + VERSION + '.'); - /** - * A nullable public accessible field only available in {@code EntityPlayer}. - * This can be null if the player is offline. - */ - private static final MethodHandle PLAYER_CONNECTION; - /** - * Responsible for getting the NMS handler {@code EntityPlayer} object for the player. - * {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}. - * Used mainly for handling packet related operations. - *

- * This is also where the famous player {@code ping} field comes from! - */ - private static final MethodHandle GET_HANDLE; - private static final MethodHandle GET_HANDLE_WORLD; - /** - * Sends a packet to the player's client through a {@code NetworkManager} which - * is where {@code ProtocolLib} controls packets by injecting channels! - */ - private static final MethodHandle SEND_PACKET; - - static { - Class entityPlayer = getNMSClass("server.level", "EntityPlayer"); - Class worldServer = getNMSClass("server.level", "WorldServer"); - Class craftPlayer = getCraftClass("entity.CraftPlayer"); - Class craftWorld = getCraftClass("CraftWorld"); - Class playerConnection = getNMSClass("server.network", "PlayerConnection"); - - MethodHandles.Lookup lookup = MethodHandles.lookup(); - MethodHandle sendPacket = null; - MethodHandle getHandle = null; - MethodHandle getHandleWorld = null; - MethodHandle connection = null; - - try { - connection = lookup.findGetter(entityPlayer, - supports(20) ? "c" : supports(17) ? "b" : "playerConnection", playerConnection); - getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer)); - getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer)); - sendPacket = lookup.findVirtual(playerConnection, - v(18, "a").orElse("sendPacket"), - MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet"))); - } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { - ex.printStackTrace(); - } - - PLAYER_CONNECTION = connection; - SEND_PACKET = sendPacket; - GET_HANDLE = getHandle; - GET_HANDLE_WORLD = getHandleWorld; - } - - private ReflectionUtil() { - } - - /** - * This method is purely for readability. - * No performance is gained. - * - * @since 5.0.0 - */ - public static VersionHandler v(int version, T handle) { - return new VersionHandler<>(version, handle); - } - - /** - * Checks whether the server version is equal or greater than the given version. - * - * @param version the version to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @since 4.0.0 - */ - public static boolean supports(int version) { - return VER >= version; - } - - /** - * Checks whether the server version is equal or greater than the given version. - *

- * PAY ATTENTION! The minor version is based on the NMS version. - * This means that v1_19_R3 has major version 19 and minor version 3. - * - * @param major the major version to compare the server version with. - * @param minor the minor version to compare the server version with. - * @return true if the version is equal or newer, otherwise false. - * @since 4.0.0 - */ - public static boolean supports(int major, int minor) { - return (VER == major && VER_MINOR >= minor) || VER > major; - } + private static final Map> constructorCache = new HashMap<>(); + private static final Map methodCache = new HashMap<>(); + private static final Map fieldCache = new HashMap<>(); /** * Helper class converted to {@link List} * * @param The storage type */ - public static class ListParam extends ArrayList { - - } + public static class ListParam extends ArrayList {} /** * Helper class converted to {@link Collection} * * @param The storage type */ - public static class CollectionParam extends ArrayList { - - } + public static class CollectionParam extends ArrayList {} private static Class wrapperToPrimitive(Class clazz) { if (clazz == Boolean.class) return boolean.class; @@ -254,9 +82,17 @@ public class ReflectionUtil { @Nullable public static Object callConstructorNull(Class clazz, Class paramClass) { try { - Constructor con = clazz.getConstructor(paramClass); - con.setAccessible(true); - return con.newInstance(clazz.cast(null)); + String cacheKey = "ConstructorNull:" + clazz.getName() + ":" + paramClass.getName(); + + if (constructorCache.containsKey(cacheKey)) { + Constructor cachedConstructor = constructorCache.get(cacheKey); + return cachedConstructor.newInstance(clazz.cast(null)); + } else { + Constructor con = clazz.getConstructor(paramClass); + con.setAccessible(true); + constructorCache.put(cacheKey, con); + return con.newInstance(clazz.cast(null)); + } } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { ex.printStackTrace(); @@ -267,9 +103,17 @@ public class ReflectionUtil { @Nullable public static Object callFirstConstructor(Class clazz, Object... params) { try { - Constructor con = clazz.getConstructors()[0]; - con.setAccessible(true); - return con.newInstance(params); + String cacheKey = "FirstConstructor:" + clazz.getName(); + + if (constructorCache.containsKey(cacheKey)) { + Constructor cachedConstructor = constructorCache.get(cacheKey); + return cachedConstructor.newInstance(params); + } else { + Constructor con = clazz.getConstructors()[0]; + con.setAccessible(true); + constructorCache.put(cacheKey, con); + return con.newInstance(params); + } } catch (IllegalAccessException | InstantiationException | InvocationTargetException ex) { ex.printStackTrace(); @@ -280,9 +124,17 @@ public class ReflectionUtil { @Nullable public static Object callConstructor(Class clazz, Object... params) { try { - Constructor con = clazz.getConstructor(toParamTypes(params)); - con.setAccessible(true); - return con.newInstance(params); + String cacheKey = "Constructor:" + clazz.getName() + ":" + Arrays.hashCode(params); + + if (constructorCache.containsKey(cacheKey)) { + Constructor cachedConstructor = constructorCache.get(cacheKey); + return cachedConstructor.newInstance(params); + } else { + Constructor con = clazz.getConstructor(toParamTypes(params)); + con.setAccessible(true); + constructorCache.put(cacheKey, con); + return con.newInstance(params); + } } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { ex.printStackTrace(); @@ -293,9 +145,17 @@ public class ReflectionUtil { @Nullable public static Object callDeclaredConstructor(Class clazz, Object... params) { try { - Constructor con = clazz.getDeclaredConstructor(toParamTypes(params)); - con.setAccessible(true); - return con.newInstance(params); + String cacheKey = "DeclaredConstructor:" + clazz.getName() + ":" + Arrays.hashCode(params); + + if (constructorCache.containsKey(cacheKey)) { + Constructor cachedConstructor = constructorCache.get(cacheKey); + return cachedConstructor.newInstance(params); + } else { + Constructor con = clazz.getDeclaredConstructor(toParamTypes(params)); + con.setAccessible(true); + constructorCache.put(cacheKey, con); + return con.newInstance(params); + } } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { ex.printStackTrace(); @@ -306,9 +166,17 @@ public class ReflectionUtil { @Nullable public static Object callMethod(Class clazz, String method, Object... params) { try { - Method m = clazz.getMethod(method, toParamTypes(params)); - m.setAccessible(true); - return m.invoke(null, params); + String cacheKey = "Method:" + clazz.getName() + ":" + method + ":" + Arrays.hashCode(params); + + if (methodCache.containsKey(cacheKey)) { + Method cachedMethod = methodCache.get(cacheKey); + return cachedMethod.invoke(null, params); + } else { + Method m = clazz.getMethod(method, toParamTypes(params)); + m.setAccessible(true); + methodCache.put(cacheKey, m); + return m.invoke(null, params); + } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); return null; @@ -318,9 +186,17 @@ public class ReflectionUtil { @Nullable public static Object callMethod(Object obj, String method, Object... params) { try { - Method m = obj.getClass().getMethod(method, toParamTypes(params)); - m.setAccessible(true); - return m.invoke(obj, params); + String cacheKey = "Method:" + obj.getClass().getName() + ":" + method + ":" + Arrays.hashCode(params); + + if (methodCache.containsKey(cacheKey)) { + Method cachedMethod = methodCache.get(cacheKey); + return cachedMethod.invoke(obj, params); + } else { + Method m = obj.getClass().getMethod(method, toParamTypes(params)); + m.setAccessible(true); + methodCache.put(cacheKey, m); + return m.invoke(obj, params); + } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); return null; @@ -330,9 +206,17 @@ public class ReflectionUtil { @Nullable public static Object callDeclaredMethod(Object obj, String method, Object... params) { try { - Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params)); - m.setAccessible(true); - return m.invoke(obj, params); + String cacheKey = "DeclaredMethod:" + obj.getClass().getName() + ":" + method + ":" + Arrays.hashCode(params); + + if (methodCache.containsKey(cacheKey)) { + Method cachedMethod = methodCache.get(cacheKey); + return cachedMethod.invoke(obj, params); + } else { + Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params)); + m.setAccessible(true); + methodCache.put(cacheKey, m); + return m.invoke(obj, params); + } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); return null; @@ -341,8 +225,15 @@ public class ReflectionUtil { public static boolean hasField(Object packet, String field) { try { - packet.getClass().getDeclaredField(field); - return true; + String cacheKey = "HasField:" + packet.getClass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + return true; + } else { + packet.getClass().getDeclaredField(field); + fieldCache.put(cacheKey, null); + return true; + } } catch (NoSuchFieldException ex) { return false; } @@ -351,9 +242,17 @@ public class ReflectionUtil { @Nullable public static Object getField(Object object, String field) { try { - Field f = object.getClass().getField(field); - f.setAccessible(true); - return f.get(object); + String cacheKey = "Field:" + object.getClass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + return cachedField.get(object); + } else { + Field f = object.getClass().getField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + return f.get(object); + } } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); return null; @@ -363,9 +262,17 @@ public class ReflectionUtil { @Nullable public static Object getDeclaredField(Class clazz, String field) { try { - Field f = clazz.getDeclaredField(field); - f.setAccessible(true); - return f.get(null); + String cacheKey = "DeclaredField:" + clazz.getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + return cachedField.get(null); + } else { + Field f = clazz.getDeclaredField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + return f.get(null); + } } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); return null; @@ -375,9 +282,17 @@ public class ReflectionUtil { @Nullable public static Object getDeclaredField(Object object, String field) { try { - Field f = object.getClass().getDeclaredField(field); - f.setAccessible(true); - return f.get(object); + String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + return cachedField.get(object); + } else { + Field f = object.getClass().getDeclaredField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + return f.get(object); + } } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); return null; @@ -386,152 +301,19 @@ public class ReflectionUtil { public static void setDeclaredField(Object object, String field, Object value) { try { - Field f = object.getClass().getDeclaredField(field); - f.setAccessible(true); - f.set(object, value); + String cacheKey = "DeclaredField:" + object.getClass().getName() + ":" + field; + + if (fieldCache.containsKey(cacheKey)) { + Field cachedField = fieldCache.get(cacheKey); + cachedField.set(object, value); + } else { + Field f = object.getClass().getDeclaredField(field); + f.setAccessible(true); + fieldCache.put(cacheKey, f); + f.set(object, value); + } } catch (NoSuchFieldException | IllegalAccessException ex) { ex.printStackTrace(); } } - - /** - * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility. - * - * @param newPackage the 1.17 package name. - * @param name the name of the class. - * @return the NMS class or null if not found. - * @since 4.0.0 - */ - @javax.annotation.Nullable - public static Class getNMSClass(@Nonnull String newPackage, @Nonnull String name) { - if (supports(17)) name = newPackage + '.' + name; - return getNMSClass(name); - } - - /** - * Get a NMS (net.minecraft.server) class. - * - * @param name the name of the class. - * @return the NMS class or null if not found. - * @since 1.0.0 - */ - @javax.annotation.Nullable - public static Class getNMSClass(@Nonnull String name) { - try { - return Class.forName(NMS + name); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - /** - * Sends a packet to the player asynchronously if they're online. - * Packets are thread-safe. - * - * @param player the player to send the packet to. - * @param packets the packets to send. - * @return the async thread handling the packet. - * @see #sendPacketSync(Player, Object...) - * @since 1.0.0 - */ - @Nonnull - public static CompletableFuture sendPacket(@Nonnull Player player, @Nonnull Object... packets) { - return CompletableFuture.runAsync(() -> sendPacketSync(player, packets)) - .exceptionally(ex -> { - ex.printStackTrace(); - return null; - }); - } - - /** - * Sends a packet to the player synchronously if they're online. - * - * @param player the player to send the packet to. - * @param packets the packets to send. - * @see #sendPacket(Player, Object...) - * @since 2.0.0 - */ - public static void sendPacketSync(@Nonnull Player player, @Nonnull Object... packets) { - try { - Object handle = GET_HANDLE.invoke(player); - Object connection = PLAYER_CONNECTION.invoke(handle); - - // Checking if the connection is not null is enough. There is no need to check if the player is online. - if (connection != null) { - for (Object packet : packets) SEND_PACKET.invoke(connection, packet); - } - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - } - - @javax.annotation.Nullable - public static Object getHandle(@Nonnull Player player) { - Objects.requireNonNull(player, "Cannot get handle of null player"); - try { - return GET_HANDLE.invoke(player); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - } - - @javax.annotation.Nullable - public static Object getHandle(@Nonnull World world) { - Objects.requireNonNull(world, "Cannot get handle of null world"); - try { - return GET_HANDLE_WORLD.invoke(world); - } catch (Throwable throwable) { - throwable.printStackTrace(); - return null; - } - } - - /** - * Get a CraftBukkit (org.bukkit.craftbukkit) class. - * - * @param name the name of the class to load. - * @return the CraftBukkit class or null if not found. - * @since 1.0.0 - */ - @javax.annotation.Nullable - public static Class getCraftClass(@Nonnull String name) { - try { - return Class.forName(CRAFTBUKKIT + name); - } catch (ClassNotFoundException ex) { - ex.printStackTrace(); - return null; - } - } - - public static final class VersionHandler { - private int version; - private T handle; - - private VersionHandler(int version, T handle) { - if (supports(version)) { - this.version = version; - this.handle = handle; - } - } - - public VersionHandler v(int version, T handle) { - if (version == this.version) - throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version); - if (version > this.version && supports(version)) { - this.version = version; - this.handle = handle; - } - return this; - } - - public T orElse(T handle) { - return this.version == 0 ? handle : this.handle; - } - } - - private static int toInt(String string, int def) { - return string.isBlank() ? def : Integer.parseInt(string); - } } \ No newline at end of file diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java new file mode 100644 index 0000000..5a02c40 --- /dev/null +++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java @@ -0,0 +1,515 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Crypto Morin + * + * 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 tech.sbdevelopment.mapreflectionapi.utils; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.entity.Player; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ReflectionUtils - Reflection handler for NMS and CraftBukkit.
+ * Caches the packet related methods and is asynchronous. + *

+ * This class does not handle null checks as most of the requests are from the + * other utility classes that already handle null checks. + *

+ * Clientbound Packets are considered fake + * updates to the client without changing the actual data. Since all the data is handled + * by the server. + *

+ * A useful resource used to compare mappings is Mini's Mapping Viewer + * + * @author Crypto Morin + * @version 7.0.0 + */ +public final class ReflectionUtils { + /** + * We use reflection mainly to avoid writing a new class for version barrier. + * The version barrier is for NMS that uses the Minecraft version as the main package name. + *

+ * E.g. EntityPlayer in 1.15 is in the class {@code net.minecraft.server.v1_15_R1} + * but in 1.14 it's in {@code net.minecraft.server.v1_14_R1} + * In order to maintain cross-version compatibility we cannot import these classes. + *

+ * Performance is not a concern for these specific statically initialized values. + *

+ * Versions Legacy + */ + public static final String NMS_VERSION; + + static { // This needs to be right below VERSION because of initialization order. + // This package loop is used to avoid implementation-dependant strings like Bukkit.getVersion() or Bukkit.getBukkitVersion() + // which allows easier testing as well. + String found = null; + for (Package pack : Package.getPackages()) { + String name = pack.getName(); + + // .v because there are other packages. + if (name.startsWith("org.bukkit.craftbukkit.v")) { + found = pack.getName().split("\\.")[3]; + + // Just a final guard to make sure it finds this important class. + // As a protection for forge+bukkit implementation that tend to mix versions. + // The real CraftPlayer should exist in the package. + // Note: Doesn't seem to function properly. Will need to separate the version + // handler for NMS and CraftBukkit for softwares like catmc. + try { + Class.forName("org.bukkit.craftbukkit." + found + ".entity.CraftPlayer"); + break; + } catch (ClassNotFoundException e) { + found = null; + } + } + } + if (found == null) + throw new IllegalArgumentException("Failed to parse server version. Could not find any package starting with name: 'org.bukkit.craftbukkit.v'"); + NMS_VERSION = found; + } + + /** + * The raw minor version number. + * E.g. {@code v1_17_R1} to {@code 17} + * + * @see #supports(int) + * @since 4.0.0 + */ + public static final int MINOR_NUMBER; + /** + * The raw patch version number. + * E.g. {@code v1_17_R1} to {@code 1} + *

+ * I'd not recommend developers to support individual patches at all. You should always support the latest patch. + * For example, between v1.14.0, v1.14.1, v1.14.2, v1.14.3 and v1.14.4 you should only support v1.14.4 + *

+ * This can be used to warn server owners when your plugin will break on older patches. + * + * @see #supportsPatch(int) + * @since 7.0.0 + */ + public static final int PATCH_NUMBER; + + static { + String[] split = NMS_VERSION.substring(1).split("_"); + if (split.length < 1) { + throw new IllegalStateException("Version number division error: " + Arrays.toString(split) + ' ' + getVersionInformation()); + } + + String minorVer = split[1]; + try { + MINOR_NUMBER = Integer.parseInt(minorVer); + if (MINOR_NUMBER < 0) + throw new IllegalStateException("Negative minor number? " + minorVer + ' ' + getVersionInformation()); + } catch (Throwable ex) { + throw new RuntimeException("Failed to parse minor number: " + minorVer + ' ' + getVersionInformation(), ex); + } + + // Bukkit.getBukkitVersion() = "1.12.2-R0.1-SNAPSHOT" + Matcher bukkitVer = Pattern.compile("^\\d+\\.\\d+\\.(\\d+)").matcher(Bukkit.getBukkitVersion()); + if (bukkitVer.find()) { // matches() won't work, we just want to match the start using "^" + try { + // group(0) gives the whole matched string, we just want the captured group. + PATCH_NUMBER = Integer.parseInt(bukkitVer.group(1)); + } catch (Throwable ex) { + throw new RuntimeException("Failed to parse minor number: " + bukkitVer + ' ' + getVersionInformation(), ex); + } + } else { + // 1.8-R0.1-SNAPSHOT + PATCH_NUMBER = 0; + } + } + + /** + * Gets the full version information of the server. Useful for including in errors. + * @since 7.0.0 + */ + public static String getVersionInformation() { + return "(NMS: " + NMS_VERSION + " | " + + "Minecraft: " + Bukkit.getVersion() + " | " + + "Bukkit: " + Bukkit.getBukkitVersion() + ')'; + } + + /** + * Gets the latest known patch number of the given minor version. + * For example: 1.14 -> 4, 1.17 -> 10 + * The latest version is expected to get newer patches, so make sure to account for unexpected results. + * + * @param minorVersion the minor version to get the patch number of. + * @return the patch number of the given minor version if recognized, otherwise null. + * @since 7.0.0 + */ + public static Integer getLatestPatchNumberOf(int minorVersion) { + if (minorVersion <= 0) throw new IllegalArgumentException("Minor version must be positive: " + minorVersion); + + // https://minecraft.fandom.com/wiki/Java_Edition_version_history + // There are many ways to do this, but this is more visually appealing. + int[] patches = { + /* 1 */ 1, + /* 2 */ 5, + /* 3 */ 2, + /* 4 */ 7, + /* 5 */ 2, + /* 6 */ 4, + /* 7 */ 10, + /* 8 */ 8, // I don't think they released a server version for 1.8.9 + /* 9 */ 4, + + /* 10 */ 2,// ,_ _ _, + /* 11 */ 2,// \o-o/ + /* 12 */ 2,// ,(.-.), + /* 13 */ 2,// _/ |) (| \_ + /* 14 */ 4,// /\=-=/\ + /* 15 */ 2,// ,| \=/ |, + /* 16 */ 5,// _/ \ | / \_ + /* 17 */ 1,// \_!_/ + /* 18 */ 2, + /* 19 */ 4, + /* 20 */ 0, + }; + + if (minorVersion > patches.length) return null; + return patches[minorVersion - 1]; + } + + /** + * Mojang remapped their NMS in 1.17: Spigot Thread + */ + public static final String + CRAFTBUKKIT_PACKAGE = "org.bukkit.craftbukkit." + NMS_VERSION + '.', + NMS_PACKAGE = v(17, "net.minecraft.").orElse("net.minecraft.server." + NMS_VERSION + '.'); + /** + * A nullable public accessible field only available in {@code EntityPlayer}. + * This can be null if the player is offline. + */ + private static final MethodHandle PLAYER_CONNECTION; + /** + * Responsible for getting the NMS handler {@code EntityPlayer} object for the player. + * {@code CraftPlayer} is simply a wrapper for {@code EntityPlayer}. + * Used mainly for handling packet related operations. + *

+ * This is also where the famous player {@code ping} field comes from! + */ + private static final MethodHandle GET_HANDLE; + /** + * Responsible for getting the NMS handler {@code WorldServer} object for the world. + * {@code CraftWorld} is simply a wrapper for {@code WorldServer}. + */ + private static final MethodHandle GET_HANDLE_WORLD; + /** + * Sends a packet to the player's client through a {@code NetworkManager} which + * is where {@code ProtocolLib} controls packets by injecting channels! + */ + private static final MethodHandle SEND_PACKET; + + static { + Class entityPlayer = getNMSClass("server.level", "EntityPlayer"); + Class worldServer = getNMSClass("server.level", "WorldServer"); + Class craftPlayer = getCraftClass("entity.CraftPlayer"); + Class craftWorld = getCraftClass("CraftWorld"); + Class playerConnection = getNMSClass("server.network", "PlayerConnection"); + + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodHandle sendPacket = null, getHandle = null, getHandleWorld = null, connection = null; + + try { + connection = lookup.findGetter(entityPlayer, + v(20, "c").v(17, "b").orElse("playerConnection"), playerConnection); + getHandle = lookup.findVirtual(craftPlayer, "getHandle", MethodType.methodType(entityPlayer)); + getHandleWorld = lookup.findVirtual(craftWorld, "getHandle", MethodType.methodType(worldServer)); + sendPacket = lookup.findVirtual(playerConnection, + v(18, "a").orElse("sendPacket"), + MethodType.methodType(void.class, getNMSClass("network.protocol", "Packet"))); + } catch (NoSuchMethodException | NoSuchFieldException | IllegalAccessException ex) { + ex.printStackTrace(); + } + + PLAYER_CONNECTION = connection; + SEND_PACKET = sendPacket; + GET_HANDLE = getHandle; + GET_HANDLE_WORLD = getHandleWorld; + } + + private ReflectionUtils() { + } + + /** + * This method is purely for readability. + * No performance is gained. + * + * @since 5.0.0 + */ + public static VersionHandler v(int version, T handle) { + return new VersionHandler<>(version, handle); + } + + public static CallableVersionHandler v(int version, Callable handle) { + return new CallableVersionHandler<>(version, handle); + } + + /** + * Checks whether the server version is equal or greater than the given version. + * + * @param minorNumber the version to compare the server version with. + * @return true if the version is equal or newer, otherwise false. + * @see #MINOR_NUMBER + * @since 4.0.0 + */ + public static boolean supports(int minorNumber) { + return MINOR_NUMBER >= minorNumber; + } + + /** + * Checks whether the server version is equal or greater than the given version. + * + * @param patchNumber the version to compare the server version with. + * @return true if the version is equal or newer, otherwise false. + * @see #PATCH_NUMBER + * @since 7.0.0 + */ + public static boolean supportsPatch(int patchNumber) { + return PATCH_NUMBER >= patchNumber; + } + + /** + * Checks whether the server version is equal or greater than the given version. + * If minorNumber matches, it will check if patchNumber is equal or greater, + * if minorNumber does not match, it will check if minorNumber is greater. + * + * @param minorNumber the minor version to compare the server version with. + * @param patchNumber the patch version to compare the server version with. + * @see #MINOR_NUMBER + * @see #PATCH_NUMBER + */ + public static boolean supports(int minorNumber, int patchNumber) { + return (MINOR_NUMBER == minorNumber && supportsPatch(patchNumber)) || MINOR_NUMBER > minorNumber; + } + + /** + * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility. + * + * @param newPackage the 1.17 package name. + * @param name the name of the class. + * @return the NMS class or null if not found. + * @since 4.0.0 + */ + @Nullable + public static Class getNMSClass(@Nonnull String newPackage, @Nonnull String name) { + if (supports(17)) name = newPackage + '.' + name; + return getNMSClass(name); + } + + /** + * Get a NMS (net.minecraft.server) class. + * + * @param name the name of the class. + * @return the NMS class or null if not found. + * @since 1.0.0 + */ + @Nullable + public static Class getNMSClass(@Nonnull String name) { + try { + return Class.forName(NMS_PACKAGE + name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + /** + * Sends a packet to the player asynchronously if they're online. + * Packets are thread-safe. + * + * @param player the player to send the packet to. + * @param packets the packets to send. + * @return the async thread handling the packet. + * @see #sendPacketSync(Player, Object...) + * @since 1.0.0 + */ + @Nonnull + public static CompletableFuture sendPacket(@Nonnull Player player, @Nonnull Object... packets) { + return CompletableFuture.runAsync(() -> sendPacketSync(player, packets)) + .exceptionally(ex -> { + ex.printStackTrace(); + return null; + }); + } + + /** + * Sends a packet to the player synchronously if they're online. + * + * @param player the player to send the packet to. + * @param packets the packets to send. + * @see #sendPacket(Player, Object...) + * @since 2.0.0 + */ + public static void sendPacketSync(@Nonnull Player player, @Nonnull Object... packets) { + try { + Object handle = GET_HANDLE.invoke(player); + Object connection = PLAYER_CONNECTION.invoke(handle); + + // Checking if the connection is not null is enough. There is no need to check if the player is online. + if (connection != null) { + for (Object packet : packets) SEND_PACKET.invoke(connection, packet); + } + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + } + + @Nullable + public static Object getHandle(@Nonnull Player player) { + Objects.requireNonNull(player, "Cannot get handle of null player"); + try { + return GET_HANDLE.invoke(player); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return null; + } + } + + @Nullable + public static Object getHandle(@Nonnull World world) { + Objects.requireNonNull(world, "Cannot get handle of null world"); + try { + return GET_HANDLE_WORLD.invoke(world); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return null; + } + } + + @Nullable + public static Object getConnection(@Nonnull Player player) { + Objects.requireNonNull(player, "Cannot get connection of null player"); + try { + Object handle = GET_HANDLE.invoke(player); + return PLAYER_CONNECTION.invoke(handle); + } catch (Throwable throwable) { + throwable.printStackTrace(); + return null; + } + } + + /** + * Get a CraftBukkit (org.bukkit.craftbukkit) class. + * + * @param name the name of the class to load. + * @return the CraftBukkit class or null if not found. + * @since 1.0.0 + */ + @Nullable + public static Class getCraftClass(@Nonnull String name) { + try { + return Class.forName(CRAFTBUKKIT_PACKAGE + name); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + public static Class getArrayClass(String clazz, boolean nms) { + clazz = "[L" + (nms ? NMS_PACKAGE : CRAFTBUKKIT_PACKAGE) + clazz + ';'; + try { + return Class.forName(clazz); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + public static Class toArrayClass(Class clazz) { + try { + return Class.forName("[L" + clazz.getName() + ';'); + } catch (ClassNotFoundException ex) { + ex.printStackTrace(); + return null; + } + } + + public static final class VersionHandler { + private int version; + private T handle; + + private VersionHandler(int version, T handle) { + if (supports(version)) { + this.version = version; + this.handle = handle; + } + } + + public VersionHandler v(int version, T handle) { + if (version == this.version) + throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version); + if (version > this.version && supports(version)) { + this.version = version; + this.handle = handle; + } + return this; + } + + public T orElse(T handle) { + return this.version == 0 ? handle : this.handle; + } + } + + public static final class CallableVersionHandler { + private int version; + private Callable handle; + + private CallableVersionHandler(int version, Callable handle) { + if (supports(version)) { + this.version = version; + this.handle = handle; + } + } + + public CallableVersionHandler v(int version, Callable handle) { + if (version == this.version) + throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version); + if (version > this.version && supports(version)) { + this.version = version; + this.handle = handle; + } + return this; + } + + public T orElse(Callable handle) { + try { + return (this.version == 0 ? handle : this.handle).call(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub b/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub deleted file mode 100644 index 1e22e3a..0000000 Binary files a/src/main/resources/com/bergerkiller/bukkit/common/internal/resources/map/map_1_8_8.bub and /dev/null differ