v1.6.4: Added support for 1.20.5, 1.20.6 and 1.21 #30
10 changed files with 53 additions and 596 deletions
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
|
@ -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>
|
10
pom.xml
10
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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue