From bd6a24a2426501ff1ed0b085a8dd9b6503642854 Mon Sep 17 00:00:00 2001
From: SBDeveloper <support@sbdevelopment.tech>
Date: Sun, 30 Jun 2024 20:49:09 +0200
Subject: [PATCH] Bumped XSeries, moved to new reflection API of XSeries

---
 .idea/misc.xml                                |   2 +-
 pom.xml                                       |  10 +-
 .../bukkit/common/map/MapColorPalette.java    |   2 +-
 .../mapreflectionapi/MapReflectionAPI.java    |   2 +-
 .../mapreflectionapi/api/MapManager.java      |   5 +-
 .../mapreflectionapi/api/MapSender.java       |  33 +-
 .../mapreflectionapi/api/MapWrapper.java      |   8 +-
 .../listeners/PacketListener.java             |   3 +-
 .../utils/ReflectionUtil.java                 |  18 +
 .../utils/ReflectionUtils.java                | 566 ------------------
 10 files changed, 53 insertions(+), 596 deletions(-)
 delete mode 100644 src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java

diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5cacde7..285bc57 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -30,7 +30,7 @@
       </set>
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/out" />
   </component>
 </project>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index fe21f7f..005712b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,6 +81,10 @@
                                     <pattern>org.bstats</pattern>
                                     <shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.bstats</shadedPattern>
                                 </relocation>
+                                <relocation>
+                                    <pattern>com.cryptomorin.xseries</pattern>
+                                    <shadedPattern>tech.sbdevelopment.mapreflectionapi.libs.xseries</shadedPattern>
+                                </relocation>
                             </relocations>
                         </configuration>
                     </execution>
@@ -177,13 +181,17 @@
             <version>1.18.34</version>
             <scope>provided</scope>
         </dependency>
-
         <dependency>
             <groupId>org.bstats</groupId>
             <artifactId>bstats-bukkit</artifactId>
             <version>3.0.2</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.github.cryptomorin</groupId>
+            <artifactId>XSeries</artifactId>
+            <version>11.2.0</version>
+        </dependency>
 
         <!-- Libraries below are provided by CraftBukkit -->
         <dependency>
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 3963b7b..f3ea19a 100644
--- a/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java
+++ b/src/main/java/com/bergerkiller/bukkit/common/map/MapColorPalette.java
@@ -27,7 +27,7 @@ import java.awt.*;
 import java.io.InputStream;
 import java.util.Arrays;
 
-import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports;
+import static com.cryptomorin.xseries.reflection.XReflection.supports;
 
 /**
  * Additional functionality on top of Bukkit's MapPalette
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java
index bc41d31..0d76789 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/MapReflectionAPI.java
@@ -34,7 +34,7 @@ import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager;
 
 import java.util.logging.Level;
 
-import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports;
+import static com.cryptomorin.xseries.reflection.XReflection.supports;
 
 public class MapReflectionAPI extends JavaPlugin {
     private static MapReflectionAPI instance;
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java
index 60fbc15..c3c5c60 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapManager.java
@@ -33,7 +33,8 @@ import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
+import static com.cryptomorin.xseries.reflection.XReflection.*;
+import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle;
 
 /**
  * The {@link MapManager} manages all the maps. It also contains functions for wrapping.
@@ -185,7 +186,7 @@ public class MapManager {
      * @return The found {@link ItemFrame}, or <code>null</code>
      */
     public ItemFrame getItemFrameById(World world, int entityId) {
-        Object worldHandle = getHandle(world);
+        Object worldHandle = ReflectionUtil.getHandle(world);
         Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId);
         if (nmsEntity == null) return null;
 
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java
index 0caefd0..9e46c98 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapSender.java
@@ -27,7 +27,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
 import java.util.ArrayList;
 import java.util.List;
 
-import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
+import static com.cryptomorin.xseries.reflection.XReflection.*;
+import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.sendPacket;
 
 /**
  * The {@link MapSender} sends the Map packets to players.
@@ -111,6 +112,8 @@ public class MapSender {
             return;
         }
 
+        int id = -id0;
+
         Object packet;
         if (supports(20, 4)) { //1.20.5+
             Object updateData = ReflectionUtil.callConstructor(worldMapData,
@@ -139,26 +142,16 @@ public class MapSender {
                     content.array //Data
             );
 
-            if (supports(21)) { //1.21+
-                packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
-                        ReflectionUtil.callConstructor(mapId, -id0), //ID
-                        (byte) 0, //Scale, 0 = 1 block per pixel
-                        false, //Show icons
-                        new ReflectionUtil.CollectionParam<>(), //Icons
-                        updateData
-                );
-            } else {
-                packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
-                        -id0, //ID
-                        (byte) 0, //Scale, 0 = 1 block per pixel
-                        false, //Show icons
-                        new ReflectionUtil.CollectionParam<>(), //Icons
-                        updateData
-                );
-            }
+            packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
+                    id, //ID
+                    (byte) 0, //Scale, 0 = 1 block per pixel
+                    false, //Show icons
+                    new ReflectionUtil.CollectionParam<>(), //Icons
+                    updateData
+            );
         } else if (supports(14)) { //1.16-1.14
             packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
-                    -id0, //ID
+                    id, //ID
                     (byte) 0, //Scale, 0 = 1 block per pixel
                     false, //Tracking position
                     false, //Locked
@@ -171,7 +164,7 @@ public class MapSender {
             );
         } else { //1.13-
             packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
-                    -id0, //ID
+                    id, //ID
                     (byte) 0, //Scale, 0 = 1 block per pixel
                     false, //???
                     new ReflectionUtil.CollectionParam<>(), //Icons
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java
index def2c14..32396b9 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/api/MapWrapper.java
@@ -41,7 +41,9 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
-import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
+import static com.cryptomorin.xseries.reflection.XReflection.*;
+import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle;
+import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.sendPacket;
 
 /**
  * A {@link MapWrapper} wraps one image.
@@ -207,7 +209,7 @@ public class MapWrapper extends AbstractMapWrapper {
                 );
             }
 
-            sendPacketSync(player, packet);
+            sendPacket(player, packet);
         }
 
         @Override
@@ -371,7 +373,7 @@ public class MapWrapper extends AbstractMapWrapper {
                 ReflectionUtil.setDeclaredField(packet, "b", list);
             }
 
-            sendPacketSync(player, packet);
+            sendPacket(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 e7914fb..5cf1957 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/listeners/PacketListener.java
@@ -38,8 +38,9 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
 
 import java.util.concurrent.TimeUnit;
 
+import static com.cryptomorin.xseries.reflection.minecraft.MinecraftConnection.getHandle;
 import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*;
-import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
+import static com.cryptomorin.xseries.reflection.XReflection.*;
 
 public class PacketListener implements Listener {
     private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap");
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java
index ad81288..87f7e57 100644
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java
+++ b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtil.java
@@ -18,6 +18,7 @@
 
 package tech.sbdevelopment.mapreflectionapi.utils;
 
+import org.bukkit.World;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -27,6 +28,9 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.*;
 
+import static com.cryptomorin.xseries.reflection.XReflection.getCraftClass;
+import static com.cryptomorin.xseries.reflection.XReflection.getNMSClass;
+
 public class ReflectionUtil {
     private static final Map<String, Constructor<?>> constructorCache = new HashMap<>();
     private static final Map<String, Method> methodCache = new HashMap<>();
@@ -71,6 +75,20 @@ public class ReflectionUtil {
                 .toArray(Class<?>[]::new);
     }
 
+    @Nullable
+    public static Object getHandle(@NotNull World world) {
+        Class<?> worldServer = getNMSClass("server.level", "WorldServer");
+        Class<?> craftWorld = getCraftClass("CraftWorld");
+        try {
+            Method m = craftWorld.getMethod("getHandle", worldServer);
+            m.setAccessible(true);
+            return m.invoke(null, world);
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
+            ex.printStackTrace();
+            return null;
+        }
+    }
+
     @Nullable
     public static Class<?> getClass(@NotNull String name) {
         try {
diff --git a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java b/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java
deleted file mode 100644
index 912a2c4..0000000
--- a/src/main/java/tech/sbdevelopment/mapreflectionapi/utils/ReflectionUtils.java
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * 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;
-
-/**
- * <b>ReflectionUtils</b> - Reflection handler for NMS and CraftBukkit.<br>
- * Caches the packet related methods and is asynchronous.
- * <p>
- * This class does not handle null checks as most of the requests are from the
- * other utility classes that already handle null checks.
- * <p>
- * <a href="https://wiki.vg/Protocol">Clientbound Packets</a> are considered fake
- * updates to the client without changing the actual data. Since all the data is handled
- * by the server.
- * <p>
- * A useful resource used to compare mappings is <a href="https://minidigger.github.io/MiniMappingViewer/#/spigot">Mini's Mapping Viewer</a>
- *
- * @author Crypto Morin
- * @version 7.1.0.0.1
- */
-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.
-     * <p>
-     * 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.
-     * <p>
-     * Performance is not a concern for these specific statically initialized values.
-     * <p>
-     * <a href="https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-legacy/">Versions Legacy</a>
-     */
-    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. Refers to the <a href="https://en.wikipedia.org/wiki/Software_versioning">major.minor.patch version scheme</a>.
-     * E.g.
-     * <ul>
-     *     <li>{@code v1.20.4} to {@code 4}</li>
-     *     <li>{@code v1.18.2} to {@code 2}</li>
-     *     <li>{@code v1.19.1} to {@code 1}</li>
-     * </ul>
-     * <p>
-     * 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
-     * <p>
-     * 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.wiki/w/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 */ 4,
-        };
-
-        if (minorVersion > patches.length) return null;
-        return patches[minorVersion - 1];
-    }
-
-    /**
-     * Mojang remapped their NMS in 1.17: <a href="https://www.spigotmc.org/threads/spigot-bungeecord-1-17.510208/#post-4184317">Spigot Thread</a>
-     */
-    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.
-     * <p>
-     * 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");
-        Class<?> playerCommonConnection;
-        if (supports(20) && supportsPatch(2)) {
-            // The packet send method has been abstracted from ServerGamePacketListenerImpl to ServerCommonPacketListenerImpl in 1.20.2
-            playerCommonConnection = getNMSClass("server.network", "ServerCommonPacketListenerImpl");
-        } else {
-            playerCommonConnection = 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(playerCommonConnection,
-                    v(20, 2, "b").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() {
-    }
-
-    /**
-     * Gives the {@code handle} object if the server version is equal or greater than the given version.
-     * This method is purely for readability and should be always used with {@link VersionHandler#orElse(Object)}.
-     *
-     * @see #v(int, int, Object)
-     * @see VersionHandler#orElse(Object)
-     * @since 5.0.0
-     */
-    public static <T> VersionHandler<T> v(int version, T handle) {
-        return new VersionHandler<>(version, handle);
-    }
-
-    /**
-     * Overload for {@link #v(int, T)} that supports patch versions
-     *
-     * @since 9.5.0
-     */
-    public static <T> VersionHandler<T> v(int version, int patch, T handle) {
-        return new VersionHandler<>(version, patch, handle);
-    }
-
-    public static <T> CallableVersionHandler<T> v(int version, Callable<T> 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 minorNumber the minor version to compare the server version with.
-     * @param patchNumber the patch number to compare the server version with.
-     * @return true if the version is equal or newer, otherwise false.
-     * @see #MINOR_NUMBER
-     * @see #PATCH_NUMBER
-     * @since 7.1.0
-     */
-    public static boolean supports(int minorNumber, int patchNumber) {
-        return (MINOR_NUMBER == minorNumber && supportsPatch(patchNumber)) || 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;
-    }
-
-    /**
-     * Get a NMS (net.minecraft.server) class which accepts a package for 1.17 compatibility.
-     *
-     * @param packageName the 1.17+ package name of this class.
-     * @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(@Nullable String packageName, @Nonnull String name) {
-        if (packageName != null && supports(17)) name = packageName + '.' + name;
-
-        try {
-            return Class.forName(NMS_PACKAGE + name);
-        } catch (ClassNotFoundException ex) {
-            ex.printStackTrace();
-            return null;
-        }
-    }
-
-    /**
-     * Get a NMS {@link #NMS_PACKAGE} 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) {
-        return getNMSClass(null, name);
-    }
-
-    /**
-     * 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<Void> 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;
-        }
-    }
-
-    /**
-     * @deprecated Use {@link #toArrayClass(Class)} instead.
-     */
-    @Deprecated
-    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;
-        }
-    }
-
-    /**
-     * Gives an array version of a class. For example if you wanted {@code EntityPlayer[]} you'd use:
-     * <pre>{@code
-     *     Class EntityPlayer = ReflectionUtils.getNMSClass("...", "EntityPlayer");
-     *     Class EntityPlayerArray = ReflectionUtils.toArrayClass(EntityPlayer);
-     * }</pre>
-     *
-     * @param clazz the class to get the array version of. You could use for multi-dimensions arrays too.
-     */
-    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<T> {
-        private int version, patch;
-        private T handle;
-
-        private VersionHandler(int version, T handle) {
-            this(version, 0, handle);
-        }
-
-        private VersionHandler(int version, int patch, T handle) {
-            if (supports(version) && supportsPatch(patch)) {
-                this.version = version;
-                this.patch = patch;
-                this.handle = handle;
-            }
-        }
-
-        public VersionHandler<T> v(int version, T handle) {
-            return v(version, 0, handle);
-        }
-
-        public VersionHandler<T> v(int version, int patch, T handle) {
-            if (version == this.version && patch == this.patch)
-                throw new IllegalArgumentException("Cannot have duplicate version handles for version: " + version + '.' + patch);
-            if (version > this.version && supports(version) && patch >= this.patch && supportsPatch(patch)) {
-                this.version = version;
-                this.patch = patch;
-                this.handle = handle;
-            }
-            return this;
-        }
-
-        /**
-         * If none of the previous version checks matched, it'll return this object.
-         */
-        public T orElse(T handle) {
-            return this.version == 0 ? handle : this.handle;
-        }
-    }
-
-    public static final class CallableVersionHandler<T> {
-        private int version;
-        private Callable<T> handle;
-
-        private CallableVersionHandler(int version, Callable<T> handle) {
-            if (supports(version)) {
-                this.version = version;
-                this.handle = handle;
-            }
-        }
-
-        public CallableVersionHandler<T> v(int version, Callable<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(Callable<T> handle) {
-            try {
-                return (this.version == 0 ? handle : this.handle).call();
-            } catch (Exception e) {
-                e.printStackTrace();
-                return null;
-            }
-        }
-    }
-}
\ No newline at end of file