Implemented Reflection caching, closes #8
This commit is contained in:
parent
8d11483afe
commit
616e215797
8 changed files with 718 additions and 414 deletions
|
@ -27,6 +27,8 @@ import java.awt.*;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional functionality on top of Bukkit's MapPalette
|
* Additional functionality on top of Bukkit's MapPalette
|
||||||
*/
|
*/
|
||||||
|
@ -50,14 +52,12 @@ public class MapColorPalette {
|
||||||
MCSDBubbleFormat bubbleData = new MCSDBubbleFormat();
|
MCSDBubbleFormat bubbleData = new MCSDBubbleFormat();
|
||||||
try {
|
try {
|
||||||
String bub_path_postfix;
|
String bub_path_postfix;
|
||||||
if (ReflectionUtil.supports(17)) {
|
if (supports(17)) {
|
||||||
bub_path_postfix = "map_1_17.bub";
|
bub_path_postfix = "map_1_17.bub";
|
||||||
} else if (ReflectionUtil.supports(16)) {
|
} else if (supports(16)) {
|
||||||
bub_path_postfix = "map_1_16.bub";
|
bub_path_postfix = "map_1_16.bub";
|
||||||
} else if (ReflectionUtil.supports(12)) {
|
|
||||||
bub_path_postfix = "map_1_12.bub";
|
|
||||||
} else {
|
} 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;
|
String bub_path = "/tech/sbdevelopment/mapreflectionapi/libs/bkcommonlib/internal/resources/map/" + bub_path_postfix;
|
||||||
InputStream input = MapColorPalette.class.getResourceAsStream(bub_path);
|
InputStream input = MapColorPalette.class.getResourceAsStream(bub_path);
|
||||||
|
|
|
@ -35,6 +35,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.UpdateManager;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.supports;
|
||||||
|
|
||||||
public class MapReflectionAPI extends JavaPlugin {
|
public class MapReflectionAPI extends JavaPlugin {
|
||||||
private static MapReflectionAPI instance;
|
private static MapReflectionAPI instance;
|
||||||
private static MapManager mapManager;
|
private static MapManager mapManager;
|
||||||
|
@ -67,7 +69,7 @@ public class MapReflectionAPI extends JavaPlugin {
|
||||||
getLogger().info("MapReflectionAPI v" + getDescription().getVersion());
|
getLogger().info("MapReflectionAPI v" + getDescription().getVersion());
|
||||||
getLogger().info("Made by © Copyright SBDevelopment 2023");
|
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!");
|
getLogger().severe("MapReflectionAPI only supports Minecraft 1.12 - 1.19.4!");
|
||||||
Bukkit.getPluginManager().disablePlugin(this);
|
Bukkit.getPluginManager().disablePlugin(this);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -27,6 +27,8 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link MapSender} sends the Map packets to players.
|
* The {@link MapSender} sends the Map packets to players.
|
||||||
*/
|
*/
|
||||||
|
@ -82,8 +84,8 @@ public class MapSender {
|
||||||
}, 0, 2);
|
}, 0, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Class<?> packetPlayOutMapClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutMap");
|
private static final Class<?> packetPlayOutMapClass = 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<?> worldMapData = supports(17) ? getNMSClass("world.level.saveddata.maps", "WorldMap$b") : null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a map to a player
|
* Send a map to a player
|
||||||
|
@ -110,7 +112,7 @@ public class MapSender {
|
||||||
|
|
||||||
final int id = -id0;
|
final int id = -id0;
|
||||||
Object packet;
|
Object packet;
|
||||||
if (ReflectionUtil.supports(17)) { //1.17+
|
if (supports(17)) { //1.17+
|
||||||
Object updateData = ReflectionUtil.callConstructor(worldMapData,
|
Object updateData = ReflectionUtil.callConstructor(worldMapData,
|
||||||
content.minX, //X pos
|
content.minX, //X pos
|
||||||
content.minY, //Y pos
|
content.minY, //Y pos
|
||||||
|
@ -126,7 +128,7 @@ public class MapSender {
|
||||||
new ReflectionUtil.CollectionParam<>(), //Icons
|
new ReflectionUtil.CollectionParam<>(), //Icons
|
||||||
updateData
|
updateData
|
||||||
);
|
);
|
||||||
} else if (ReflectionUtil.supports(14)) { //1.16-1.14
|
} else if (supports(14)) { //1.16-1.14
|
||||||
packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
|
packet = ReflectionUtil.callConstructor(packetPlayOutMapClass,
|
||||||
id, //ID
|
id, //ID
|
||||||
(byte) 0, //Scale, 0 = 1 block per pixel
|
(byte) 0, //Scale, 0 = 1 block per pixel
|
||||||
|
@ -153,7 +155,7 @@ public class MapSender {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReflectionUtil.sendPacket(player, packet);
|
sendPacket(player, packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|
|
@ -38,6 +38,8 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MapWrapper} wraps one image.
|
* A {@link MapWrapper} wraps one image.
|
||||||
*/
|
*/
|
||||||
|
@ -48,7 +50,7 @@ public class MapWrapper extends AbstractMapWrapper {
|
||||||
private static final Material MAP_MATERIAL;
|
private static final Material MAP_MATERIAL;
|
||||||
|
|
||||||
static {
|
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;
|
this.content = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Class<?> craftStackClass = ReflectionUtil.getCraftClass("inventory.CraftItemStack");
|
private static final Class<?> craftStackClass = getCraftClass("inventory.CraftItemStack");
|
||||||
private static final Class<?> setSlotPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutSetSlot");
|
private static final Class<?> setSlotPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutSetSlot");
|
||||||
private static final Class<?> entityClass = ReflectionUtil.getNMSClass("world.entity", "Entity");
|
private static final Class<?> entityClass = getNMSClass("world.entity", "Entity");
|
||||||
private static final Class<?> dataWatcherClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher");
|
private static final Class<?> dataWatcherClass = getNMSClass("network.syncher", "DataWatcher");
|
||||||
private static final Class<?> entityMetadataPacketClass = ReflectionUtil.getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata");
|
private static final Class<?> entityMetadataPacketClass = getNMSClass("network.protocol.game", "PacketPlayOutEntityMetadata");
|
||||||
private static final Class<?> entityItemFrameClass = ReflectionUtil.getNMSClass("world.entity.decoration", "EntityItemFrame");
|
private static final Class<?> entityItemFrameClass = getNMSClass("world.entity.decoration", "EntityItemFrame");
|
||||||
private static final Class<?> dataWatcherItemClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$Item");
|
private static final Class<?> dataWatcherItemClass = getNMSClass("network.syncher", "DataWatcher$Item");
|
||||||
|
|
||||||
protected MapController controller = new MapController() {
|
protected MapController controller = new MapController() {
|
||||||
private final Map<UUID, Integer> viewers = new HashMap<>();
|
private final Map<UUID, Integer> viewers = new HashMap<>();
|
||||||
|
@ -163,21 +165,21 @@ public class MapWrapper extends AbstractMapWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
String inventoryMenuName;
|
String inventoryMenuName;
|
||||||
if (ReflectionUtil.supports(20)) { //1.20
|
if (supports(20)) { //1.20
|
||||||
inventoryMenuName = "bQ";
|
inventoryMenuName = "bQ";
|
||||||
} else if (ReflectionUtil.supports(19)) { //1.19
|
} else if (supports(19)) { //1.19
|
||||||
inventoryMenuName = ReflectionUtil.VER_MINOR == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT
|
inventoryMenuName = PATCH_NUMBER == 3 ? "bO" : "bT"; //1.19.4 = bO, >= 1.19.3 = bT
|
||||||
} else if (ReflectionUtil.supports(18)) { //1.18
|
} else if (supports(18)) { //1.18
|
||||||
inventoryMenuName = ReflectionUtil.VER_MINOR == 1 ? "bV" : "bU"; //1.18.1 = ap, 1.18(.2) = ao
|
inventoryMenuName = PATCH_NUMBER == 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(17)) { //1.17, same as 1.18(.2)
|
||||||
inventoryMenuName = "bU";
|
inventoryMenuName = "bU";
|
||||||
} else { //1.12-1.16
|
} else { //1.12-1.16
|
||||||
inventoryMenuName = "defaultContainer";
|
inventoryMenuName = "defaultContainer";
|
||||||
}
|
}
|
||||||
Object inventoryMenu = ReflectionUtil.getField(ReflectionUtil.getHandle(player), inventoryMenuName);
|
Object inventoryMenu = ReflectionUtil.getField(getHandle(player), inventoryMenuName);
|
||||||
|
|
||||||
ItemStack stack;
|
ItemStack stack;
|
||||||
if (ReflectionUtil.supports(13)) {
|
if (supports(13)) {
|
||||||
stack = new ItemStack(MAP_MATERIAL, 1);
|
stack = new ItemStack(MAP_MATERIAL, 1);
|
||||||
} else {
|
} else {
|
||||||
stack = new ItemStack(MAP_MATERIAL, 1, (short) getMapId(player));
|
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 nmsStack = createCraftItemStack(stack, (short) getMapId(player));
|
||||||
|
|
||||||
Object packet;
|
Object packet;
|
||||||
if (ReflectionUtil.supports(17)) { //1.17+
|
if (supports(17)) { //1.17+
|
||||||
int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, ReflectionUtil.supports(18) ? "j" : "getStateId");
|
int stateId = (int) ReflectionUtil.callMethod(inventoryMenu, supports(18) ? "j" : "getStateId");
|
||||||
|
|
||||||
packet = ReflectionUtil.callConstructor(setSlotPacketClass,
|
packet = ReflectionUtil.callConstructor(setSlotPacketClass,
|
||||||
0, //0 = Player inventory
|
0, //0 = Player inventory
|
||||||
|
@ -203,7 +205,7 @@ public class MapWrapper extends AbstractMapWrapper {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReflectionUtil.sendPacketSync(player, packet);
|
sendPacketSync(player, packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -280,14 +282,14 @@ public class MapWrapper extends AbstractMapWrapper {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ItemFrame getItemFrameById(World world, int entityId) {
|
public ItemFrame getItemFrameById(World world, int entityId) {
|
||||||
Object worldHandle = ReflectionUtil.getHandle(world);
|
Object worldHandle = getHandle(world);
|
||||||
Object nmsEntity = ReflectionUtil.callMethod(worldHandle, ReflectionUtil.supports(18) ? "a" : "getEntity", entityId);
|
Object nmsEntity = ReflectionUtil.callMethod(worldHandle, supports(18) ? "a" : "getEntity", entityId);
|
||||||
if (nmsEntity == null) return null;
|
if (nmsEntity == null) return null;
|
||||||
|
|
||||||
Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity");
|
Object craftEntity = ReflectionUtil.callMethod(nmsEntity, "getBukkitEntity");
|
||||||
if (craftEntity == null) return null;
|
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 (itemFrameClass == null) return null;
|
||||||
|
|
||||||
if (craftEntity.getClass().isAssignableFrom(itemFrameClass))
|
if (craftEntity.getClass().isAssignableFrom(itemFrameClass))
|
||||||
|
@ -301,19 +303,19 @@ public class MapWrapper extends AbstractMapWrapper {
|
||||||
|
|
||||||
Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack);
|
Object nmsStack = ReflectionUtil.callMethod(craftStackClass, "asNMSCopy", stack);
|
||||||
|
|
||||||
if (ReflectionUtil.supports(13)) {
|
if (supports(13)) {
|
||||||
String nbtObjectName;
|
String nbtObjectName;
|
||||||
if (ReflectionUtil.supports(20)) { //1.20
|
if (supports(20)) { //1.20
|
||||||
nbtObjectName = "w";
|
nbtObjectName = "w";
|
||||||
} else if (ReflectionUtil.supports(19)) { //1.19
|
} else if (supports(19)) { //1.19
|
||||||
nbtObjectName = "v";
|
nbtObjectName = "v";
|
||||||
} else if (ReflectionUtil.supports(18)) { //1.18
|
} else if (supports(18)) { //1.18
|
||||||
nbtObjectName = ReflectionUtil.VER_MINOR == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u
|
nbtObjectName = PATCH_NUMBER == 1 ? "t" : "u"; //1.18.1 = t, 1.18(.2) = u
|
||||||
} else { //1.13 - 1.17
|
} else { //1.13 - 1.17
|
||||||
nbtObjectName = "getOrCreateTag";
|
nbtObjectName = "getOrCreateTag";
|
||||||
}
|
}
|
||||||
Object nbtObject = ReflectionUtil.callMethod(nmsStack, nbtObjectName);
|
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;
|
return nmsStack;
|
||||||
}
|
}
|
||||||
|
@ -322,17 +324,17 @@ public class MapWrapper extends AbstractMapWrapper {
|
||||||
Object nmsStack = createCraftItemStack(stack, mapId);
|
Object nmsStack = createCraftItemStack(stack, mapId);
|
||||||
|
|
||||||
String dataWatcherObjectName;
|
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";
|
dataWatcherObjectName = "g";
|
||||||
} else if (ReflectionUtil.supports(19)) { //1.19-1.19.2
|
} else if (supports(19)) { //1.19-1.19.2
|
||||||
dataWatcherObjectName = "ao";
|
dataWatcherObjectName = "ao";
|
||||||
} else if (ReflectionUtil.supports(18)) { //1.18
|
} else if (supports(18)) { //1.18
|
||||||
dataWatcherObjectName = ReflectionUtil.VER_MINOR == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao
|
dataWatcherObjectName = PATCH_NUMBER == 1 ? "ap" : "ao"; //1.18.1 = ap, 1.18(.2) = ao
|
||||||
} else if (ReflectionUtil.supports(17)) { //1.17
|
} else if (supports(17)) { //1.17
|
||||||
dataWatcherObjectName = "ao";
|
dataWatcherObjectName = "ao";
|
||||||
} else if (ReflectionUtil.supports(14)) { //1.14 - 1.16
|
} else if (supports(14)) { //1.14 - 1.16
|
||||||
dataWatcherObjectName = "ITEM";
|
dataWatcherObjectName = "ITEM";
|
||||||
} else if (ReflectionUtil.supports(13)) { //1.13
|
} else if (supports(13)) { //1.13
|
||||||
dataWatcherObjectName = "e";
|
dataWatcherObjectName = "e";
|
||||||
} else { //1.12
|
} else { //1.12
|
||||||
dataWatcherObjectName = "c";
|
dataWatcherObjectName = "c";
|
||||||
|
@ -342,8 +344,8 @@ public class MapWrapper extends AbstractMapWrapper {
|
||||||
ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>();
|
ReflectionUtil.ListParam list = new ReflectionUtil.ListParam<>();
|
||||||
|
|
||||||
Object packet;
|
Object packet;
|
||||||
if (ReflectionUtil.supports(19, 2)) { //1.19.3
|
if (supports(19, 2)) { //1.19.3
|
||||||
Class<?> dataWatcherRecordClass = ReflectionUtil.getNMSClass("network.syncher", "DataWatcher$b");
|
Class<?> dataWatcherRecordClass = getNMSClass("network.syncher", "DataWatcher$b");
|
||||||
// Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter
|
// Sadly not possible to use ReflectionUtil (in its current state), because of the Object parameter
|
||||||
Object dataWatcherItem;
|
Object dataWatcherItem;
|
||||||
try {
|
try {
|
||||||
|
@ -374,7 +376,7 @@ public class MapWrapper extends AbstractMapWrapper {
|
||||||
ReflectionUtil.setDeclaredField(packet, "b", list);
|
ReflectionUtil.setDeclaredField(packet, "b", list);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReflectionUtil.sendPacketSync(player, packet);
|
sendPacketSync(player, packet);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ import tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*;
|
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtil.*;
|
||||||
|
import static tech.sbdevelopment.mapreflectionapi.utils.ReflectionUtils.*;
|
||||||
|
|
||||||
public class PacketListener implements Listener {
|
public class PacketListener implements Listener {
|
||||||
private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap");
|
private static final Class<?> packetPlayOutMapClass = getNMSClass("network.protocol.game", "PacketPlayOutMap");
|
||||||
|
@ -92,15 +93,15 @@ public class PacketListener implements Listener {
|
||||||
Enum<?> actionEnum;
|
Enum<?> actionEnum;
|
||||||
Enum<?> hand;
|
Enum<?> hand;
|
||||||
Object pos;
|
Object pos;
|
||||||
if (ReflectionUtil.supports(17)) {
|
if (supports(17)) {
|
||||||
Object action = getDeclaredField(packetPlayInEntity, "b");
|
Object action = getDeclaredField(packetPlayInEntity, "b");
|
||||||
actionEnum = (Enum<?>) callDeclaredMethod(action, "a"); //action type
|
actionEnum = (Enum<?>) callDeclaredMethod(action, "a"); //action type
|
||||||
hand = hasField(action, "a") ? (Enum<?>) getDeclaredField(action, "a") : null;
|
hand = hasField(action, "a") ? (Enum<?>) getDeclaredField(action, "a") : null;
|
||||||
pos = hasField(action, "b") ? getDeclaredField(action, "b") : null;
|
pos = hasField(action, "b") ? getDeclaredField(action, "b") : null;
|
||||||
} else {
|
} else {
|
||||||
actionEnum = (Enum<?>) callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "b" : "a"); //1.13 = b, 1.12 = a
|
actionEnum = (Enum<?>) callDeclaredMethod(packetPlayInEntity, 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
|
hand = (Enum<?>) callDeclaredMethod(packetPlayInEntity, supports(13) ? "c" : "b"); //1.13 = c, 1.12 = b
|
||||||
pos = callDeclaredMethod(packetPlayInEntity, ReflectionUtil.supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c
|
pos = callDeclaredMethod(packetPlayInEntity, supports(13) ? "d" : "c"); //1.13 = d, 1.12 = c
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Bukkit.getScheduler().callSyncMethod(MapReflectionAPI.getInstance(), () -> {
|
if (Bukkit.getScheduler().callSyncMethod(MapReflectionAPI.getInstance(), () -> {
|
||||||
|
@ -115,8 +116,8 @@ public class PacketListener implements Listener {
|
||||||
} else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) {
|
} else if (packet.getClass().isAssignableFrom(packetPlayInSetCreativeSlotClass)) {
|
||||||
Object packetPlayInSetCreativeSlot = packetPlayInSetCreativeSlotClass.cast(packet);
|
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
|
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, ReflectionUtil.supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack
|
Object nmsStack = ReflectionUtil.callDeclaredMethod(packetPlayInSetCreativeSlot, supports(18) ? "c" : "getItemStack"); //1.18 = c, 1.17 = getItemStack
|
||||||
ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack);
|
ItemStack craftStack = (ItemStack) ReflectionUtil.callMethod(craftStackClass, "asBukkitCopy", nmsStack);
|
||||||
|
|
||||||
boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread();
|
boolean async = !MapReflectionAPI.getInstance().getServer().isPrimaryThread();
|
||||||
|
@ -142,18 +143,18 @@ public class PacketListener implements Listener {
|
||||||
|
|
||||||
private Channel getChannel(Player player) {
|
private Channel getChannel(Player player) {
|
||||||
Object playerHandle = getHandle(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 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, 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
|
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, ReflectionUtil.supports(18) ? "m" : ReflectionUtil.supports(17) ? "k" : "channel"); //1.19 & 1.18 = m, 1.17 = k, 1.16 = channel
|
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) {
|
private Vector vec3DToVector(Object vec3d) {
|
||||||
if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0);
|
if (!(vec3d.getClass().isAssignableFrom(vec3DClass))) return new Vector(0, 0, 0);
|
||||||
|
|
||||||
Object vec3dNMS = vec3DClass.cast(vec3d);
|
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 x = (double) getDeclaredField(vec3dNMS, supports(19) ? "c" : 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 y = (double) getDeclaredField(vec3dNMS, supports(19) ? "d" : 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 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);
|
return new Vector(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,205 +18,33 @@
|
||||||
|
|
||||||
package tech.sbdevelopment.mapreflectionapi.utils;
|
package tech.sbdevelopment.mapreflectionapi.utils;
|
||||||
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
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.Constructor;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <b>ReflectionUtil</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.
|
|
||||||
*
|
|
||||||
* @author Crypto Morin, Stijn Bannink
|
|
||||||
* @version 2.1
|
|
||||||
*/
|
|
||||||
public class ReflectionUtil {
|
public class ReflectionUtil {
|
||||||
/**
|
private static final Map<String, Constructor<?>> constructorCache = new HashMap<>();
|
||||||
* We use reflection mainly to avoid writing a new class for version barrier.
|
private static final Map<String, Method> methodCache = new HashMap<>();
|
||||||
* The version barrier is for NMS that uses the Minecraft version as the main package name.
|
private static final Map<String, Field> fieldCache = new HashMap<>();
|
||||||
* <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.
|
|
||||||
*/
|
|
||||||
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.
|
|
||||||
* <p>
|
|
||||||
* 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 <T> VersionHandler<T> 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.
|
|
||||||
* <p>
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class converted to {@link List}
|
* Helper class converted to {@link List}
|
||||||
*
|
*
|
||||||
* @param <E> The storage type
|
* @param <E> The storage type
|
||||||
*/
|
*/
|
||||||
public static class ListParam<E> extends ArrayList<E> {
|
public static class ListParam<E> extends ArrayList<E> {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class converted to {@link Collection}
|
* Helper class converted to {@link Collection}
|
||||||
*
|
*
|
||||||
* @param <E> The storage type
|
* @param <E> The storage type
|
||||||
*/
|
*/
|
||||||
public static class CollectionParam<E> extends ArrayList<E> {
|
public static class CollectionParam<E> extends ArrayList<E> {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?> wrapperToPrimitive(Class<?> clazz) {
|
private static Class<?> wrapperToPrimitive(Class<?> clazz) {
|
||||||
if (clazz == Boolean.class) return boolean.class;
|
if (clazz == Boolean.class) return boolean.class;
|
||||||
|
@ -254,9 +82,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object callConstructorNull(Class<?> clazz, Class<?> paramClass) {
|
public static Object callConstructorNull(Class<?> clazz, Class<?> paramClass) {
|
||||||
try {
|
try {
|
||||||
|
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);
|
Constructor<?> con = clazz.getConstructor(paramClass);
|
||||||
con.setAccessible(true);
|
con.setAccessible(true);
|
||||||
|
constructorCache.put(cacheKey, con);
|
||||||
return con.newInstance(clazz.cast(null));
|
return con.newInstance(clazz.cast(null));
|
||||||
|
}
|
||||||
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
|
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
|
||||||
InvocationTargetException ex) {
|
InvocationTargetException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
|
@ -267,9 +103,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object callFirstConstructor(Class<?> clazz, Object... params) {
|
public static Object callFirstConstructor(Class<?> clazz, Object... params) {
|
||||||
try {
|
try {
|
||||||
|
String cacheKey = "FirstConstructor:" + clazz.getName();
|
||||||
|
|
||||||
|
if (constructorCache.containsKey(cacheKey)) {
|
||||||
|
Constructor<?> cachedConstructor = constructorCache.get(cacheKey);
|
||||||
|
return cachedConstructor.newInstance(params);
|
||||||
|
} else {
|
||||||
Constructor<?> con = clazz.getConstructors()[0];
|
Constructor<?> con = clazz.getConstructors()[0];
|
||||||
con.setAccessible(true);
|
con.setAccessible(true);
|
||||||
|
constructorCache.put(cacheKey, con);
|
||||||
return con.newInstance(params);
|
return con.newInstance(params);
|
||||||
|
}
|
||||||
} catch (IllegalAccessException | InstantiationException |
|
} catch (IllegalAccessException | InstantiationException |
|
||||||
InvocationTargetException ex) {
|
InvocationTargetException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
|
@ -280,9 +124,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object callConstructor(Class<?> clazz, Object... params) {
|
public static Object callConstructor(Class<?> clazz, Object... params) {
|
||||||
try {
|
try {
|
||||||
|
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));
|
Constructor<?> con = clazz.getConstructor(toParamTypes(params));
|
||||||
con.setAccessible(true);
|
con.setAccessible(true);
|
||||||
|
constructorCache.put(cacheKey, con);
|
||||||
return con.newInstance(params);
|
return con.newInstance(params);
|
||||||
|
}
|
||||||
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
|
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
|
||||||
InvocationTargetException ex) {
|
InvocationTargetException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
|
@ -293,9 +145,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object callDeclaredConstructor(Class<?> clazz, Object... params) {
|
public static Object callDeclaredConstructor(Class<?> clazz, Object... params) {
|
||||||
try {
|
try {
|
||||||
|
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));
|
Constructor<?> con = clazz.getDeclaredConstructor(toParamTypes(params));
|
||||||
con.setAccessible(true);
|
con.setAccessible(true);
|
||||||
|
constructorCache.put(cacheKey, con);
|
||||||
return con.newInstance(params);
|
return con.newInstance(params);
|
||||||
|
}
|
||||||
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
|
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException |
|
||||||
InvocationTargetException ex) {
|
InvocationTargetException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
|
@ -306,9 +166,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object callMethod(Class<?> clazz, String method, Object... params) {
|
public static Object callMethod(Class<?> clazz, String method, Object... params) {
|
||||||
try {
|
try {
|
||||||
|
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));
|
Method m = clazz.getMethod(method, toParamTypes(params));
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
|
methodCache.put(cacheKey, m);
|
||||||
return m.invoke(null, params);
|
return m.invoke(null, params);
|
||||||
|
}
|
||||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -318,9 +186,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object callMethod(Object obj, String method, Object... params) {
|
public static Object callMethod(Object obj, String method, Object... params) {
|
||||||
try {
|
try {
|
||||||
|
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));
|
Method m = obj.getClass().getMethod(method, toParamTypes(params));
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
|
methodCache.put(cacheKey, m);
|
||||||
return m.invoke(obj, params);
|
return m.invoke(obj, params);
|
||||||
|
}
|
||||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -330,9 +206,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object callDeclaredMethod(Object obj, String method, Object... params) {
|
public static Object callDeclaredMethod(Object obj, String method, Object... params) {
|
||||||
try {
|
try {
|
||||||
|
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));
|
Method m = obj.getClass().getDeclaredMethod(method, toParamTypes(params));
|
||||||
m.setAccessible(true);
|
m.setAccessible(true);
|
||||||
|
methodCache.put(cacheKey, m);
|
||||||
return m.invoke(obj, params);
|
return m.invoke(obj, params);
|
||||||
|
}
|
||||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -341,8 +225,15 @@ public class ReflectionUtil {
|
||||||
|
|
||||||
public static boolean hasField(Object packet, String field) {
|
public static boolean hasField(Object packet, String field) {
|
||||||
try {
|
try {
|
||||||
packet.getClass().getDeclaredField(field);
|
String cacheKey = "HasField:" + packet.getClass().getName() + ":" + field;
|
||||||
|
|
||||||
|
if (fieldCache.containsKey(cacheKey)) {
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
packet.getClass().getDeclaredField(field);
|
||||||
|
fieldCache.put(cacheKey, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} catch (NoSuchFieldException ex) {
|
} catch (NoSuchFieldException ex) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -351,9 +242,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object getField(Object object, String field) {
|
public static Object getField(Object object, String field) {
|
||||||
try {
|
try {
|
||||||
|
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);
|
Field f = object.getClass().getField(field);
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
|
fieldCache.put(cacheKey, f);
|
||||||
return f.get(object);
|
return f.get(object);
|
||||||
|
}
|
||||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -363,9 +262,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object getDeclaredField(Class<?> clazz, String field) {
|
public static Object getDeclaredField(Class<?> clazz, String field) {
|
||||||
try {
|
try {
|
||||||
|
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);
|
Field f = clazz.getDeclaredField(field);
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
|
fieldCache.put(cacheKey, f);
|
||||||
return f.get(null);
|
return f.get(null);
|
||||||
|
}
|
||||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -375,9 +282,17 @@ public class ReflectionUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Object getDeclaredField(Object object, String field) {
|
public static Object getDeclaredField(Object object, String field) {
|
||||||
try {
|
try {
|
||||||
|
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);
|
Field f = object.getClass().getDeclaredField(field);
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
|
fieldCache.put(cacheKey, f);
|
||||||
return f.get(object);
|
return f.get(object);
|
||||||
|
}
|
||||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -386,152 +301,19 @@ public class ReflectionUtil {
|
||||||
|
|
||||||
public static void setDeclaredField(Object object, String field, Object value) {
|
public static void setDeclaredField(Object object, String field, Object value) {
|
||||||
try {
|
try {
|
||||||
|
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);
|
Field f = object.getClass().getDeclaredField(field);
|
||||||
f.setAccessible(true);
|
f.setAccessible(true);
|
||||||
|
fieldCache.put(cacheKey, f);
|
||||||
f.set(object, value);
|
f.set(object, value);
|
||||||
|
}
|
||||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||||
ex.printStackTrace();
|
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<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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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<T> {
|
|
||||||
private int version;
|
|
||||||
private T handle;
|
|
||||||
|
|
||||||
private VersionHandler(int version, T handle) {
|
|
||||||
if (supports(version)) {
|
|
||||||
this.version = version;
|
|
||||||
this.handle = handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public VersionHandler<T> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <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.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.
|
||||||
|
* <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.
|
||||||
|
* E.g. {@code v1_17_R1} to {@code 1}
|
||||||
|
* <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.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: <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");
|
||||||
|
|
||||||
|
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 <T> VersionHandler<T> v(int version, T handle) {
|
||||||
|
return new VersionHandler<>(version, 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 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<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<T> {
|
||||||
|
private int version;
|
||||||
|
private T handle;
|
||||||
|
|
||||||
|
private VersionHandler(int version, T handle) {
|
||||||
|
if (supports(version)) {
|
||||||
|
this.version = version;
|
||||||
|
this.handle = handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public VersionHandler<T> 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<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Loading…
Add table
Reference in a new issue