ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java

1658 lines
50 KiB
Java

/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.utility;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.wrappers.EnumWrappers;
import io.netty.buffer.Unpooled;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
/**
* Methods and constants specifically used in conjuction with reflecting Minecraft object.
*
* @author Kristian
*/
public final class MinecraftReflection {
private static final ClassSource CLASS_SOURCE = ClassSource.fromClassLoader();
/**
* Regular expression that matches a canonical Java class.
*/
private static final String CANONICAL_REGEX = "(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*\\.)+\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*";
private static final String MINECRAFT_CLASS_NAME_REGEX = "net\\.minecraft\\." + CANONICAL_REGEX;
/**
* Represents a regular expression that will match the version string in a package: org.bukkit.craftbukkit.v1_6_R2 ->
* v1_6_R2
*/
private static final Pattern PACKAGE_VERSION_MATCHER = Pattern.compile(".*\\.(v\\d+_\\d+_\\w*\\d+)");
// Cache of getBukkitEntity
private static final Map<Class<?>, MethodAccessor> BUKKIT_ENTITY_CACHE = new HashMap<>();
/**
* The Entity package in Forge 1.5.2
*/
private static final String FORGE_ENTITY_PACKAGE = "net.minecraft.entity";
// Package private for the purpose of unit testing
static CachedPackage minecraftPackage;
static CachedPackage craftbukkitPackage;
static CachedPackage libraryPackage;
/**
* Regular expression computed dynamically.
*/
private static String DYNAMIC_PACKAGE_MATCHER = null;
/**
* The package name of all the classes that belongs to the native code in Minecraft.
*/
private static String MINECRAFT_PREFIX_PACKAGE = "net.minecraft.server";
private static String MINECRAFT_FULL_PACKAGE = null;
private static String CRAFTBUKKIT_PACKAGE = null;
// fuzzy matcher for minecraft class objects
private static AbstractFuzzyMatcher<Class<?>> fuzzyMatcher;
// The NMS version
private static String packageVersion;
// net.minecraft.server
private static Class<?> itemStackArrayClass;
// Whether we are using netty
private static Boolean cachedWatcherObject;
// ---- ItemStack conversions
private static Object itemStackAir = null;
private static Boolean nullEnforced = null;
private static MethodAccessor asNMSCopy = null;
private static MethodAccessor asCraftMirror = null;
private static MethodAccessor isEmpty = null;
private MinecraftReflection() {
// No need to make this constructable.
}
/**
* Retrieve a regular expression that can match Minecraft package objects.
*
* @return Minecraft package matcher.
*/
public static String getMinecraftObjectRegex() {
if (DYNAMIC_PACKAGE_MATCHER == null) {
getMinecraftPackage();
}
return DYNAMIC_PACKAGE_MATCHER;
}
/**
* Retrieve a abstract fuzzy class matcher for Minecraft objects.
*
* @return A matcher for Minecraft objects.
*/
public static AbstractFuzzyMatcher<Class<?>> getMinecraftObjectMatcher() {
if (fuzzyMatcher == null) {
fuzzyMatcher = FuzzyMatchers.matchRegex(getMinecraftObjectRegex());
}
return fuzzyMatcher;
}
/**
* Retrieve the name of the Minecraft server package.
*
* @return Full canonical name of the Minecraft server package.
*/
public static String getMinecraftPackage() {
// Speed things up
if (MINECRAFT_FULL_PACKAGE != null) {
return MINECRAFT_FULL_PACKAGE;
}
try {
// get the bukkit version we're running on
Server craftServer = Bukkit.getServer();
CRAFTBUKKIT_PACKAGE = craftServer.getClass().getPackage().getName();
// Parse the package version
Matcher packageMatcher = PACKAGE_VERSION_MATCHER.matcher(CRAFTBUKKIT_PACKAGE);
if (packageMatcher.matches()) {
packageVersion = packageMatcher.group(1);
} else {
MinecraftVersion version = new MinecraftVersion(craftServer);
// Just assume R1 - it's probably fine (warn anyway)
packageVersion = "v" + version.getMajor() + "_" + version.getMinor() + "_R1";
ProtocolLogger.log(Level.SEVERE, "Assuming package version: " + packageVersion);
}
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
// total rework of the NMS structure in 1.17 (at least there's no versioning)
MINECRAFT_FULL_PACKAGE = MINECRAFT_PREFIX_PACKAGE = "net.minecraft";
setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX);
} else {
// extract the server version from the return type of "getHandle" in CraftEntity
Method getHandle = getCraftEntityClass().getMethod("getHandle");
MINECRAFT_FULL_PACKAGE = getHandle.getReturnType().getPackage().getName();
// Pretty important invariant
if (!MINECRAFT_FULL_PACKAGE.startsWith(MINECRAFT_PREFIX_PACKAGE)) {
// See if we got the Forge entity package
if (MINECRAFT_FULL_PACKAGE.equals(FORGE_ENTITY_PACKAGE)) {
// Use the standard NMS versioned package
MINECRAFT_FULL_PACKAGE = CachedPackage.combine(MINECRAFT_PREFIX_PACKAGE, packageVersion);
} else {
// Assume they're the same instead
MINECRAFT_PREFIX_PACKAGE = MINECRAFT_FULL_PACKAGE;
}
// The package is usually flat, so go with that assumption
String matcher =
(MINECRAFT_PREFIX_PACKAGE.length() > 0 ? Pattern.quote(MINECRAFT_PREFIX_PACKAGE + ".") : "") + CANONICAL_REGEX;
// We'll still accept the default location, however
setDynamicPackageMatcher("(" + matcher + ")|(" + MINECRAFT_CLASS_NAME_REGEX + ")");
} else {
// Use the standard matcher
setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX);
}
}
return MINECRAFT_FULL_PACKAGE;
} catch (NoSuchMethodException exception) {
throw new IllegalStateException("Cannot find getHandle() in CraftEntity", exception);
}
}
/**
* Retrieve the package version of the underlying CraftBukkit server.
*
* @return The craftbukkit package version.
*/
public static String getPackageVersion() {
getMinecraftPackage();
return packageVersion;
}
/**
* Update the dynamic package matcher.
*
* @param regex - the Minecraft package regex.
*/
private static void setDynamicPackageMatcher(String regex) {
DYNAMIC_PACKAGE_MATCHER = regex;
// Ensure that the matcher is regenerated
fuzzyMatcher = null;
}
/**
* Used during debugging and testing.
*
* @param minecraftPackage - the current Minecraft package.
* @param craftBukkitPackage - the current CraftBukkit package.
*/
static void setMinecraftPackage(String minecraftPackage, String craftBukkitPackage) {
MINECRAFT_FULL_PACKAGE = minecraftPackage;
CRAFTBUKKIT_PACKAGE = craftBukkitPackage;
// Make sure it exists
if (getMinecraftServerClass() == null) {
throw new IllegalArgumentException("Cannot find MinecraftServer for package " + minecraftPackage);
}
// Standard matcher
setDynamicPackageMatcher(MINECRAFT_CLASS_NAME_REGEX);
}
/**
* Retrieve the name of the root CraftBukkit package.
*
* @return Full canonical name of the root CraftBukkit package.
*/
public static String getCraftBukkitPackage() {
// Ensure it has been initialized
if (CRAFTBUKKIT_PACKAGE == null) {
getMinecraftPackage();
}
return CRAFTBUKKIT_PACKAGE;
}
/**
* Dynamically retrieve the Bukkit entity from a given entity.
*
* @param nmsObject - the NMS entity.
* @return A bukkit entity.
* @throws RuntimeException If we were unable to retrieve the Bukkit entity.
*/
public static Object getBukkitEntity(Object nmsObject) {
if (nmsObject == null) {
return null;
}
// We will have to do this dynamically, unfortunately
try {
Class<?> clazz = nmsObject.getClass();
MethodAccessor accessor = BUKKIT_ENTITY_CACHE.get(clazz);
if (accessor == null) {
MethodAccessor created = Accessors.getMethodAccessor(clazz, "getBukkitEntity");
accessor = BUKKIT_ENTITY_CACHE.putIfAbsent(clazz, created);
// We won the race
if (accessor == null) {
accessor = created;
}
}
return accessor.invoke(nmsObject);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot get Bukkit entity from " + nmsObject, e);
}
}
/**
* Retrieve the Bukkit player from a given PlayerConnection.
*
* @param playerConnection The PlayerConnection.
* @return A bukkit player.
* @throws RuntimeException If we were unable to retrieve the Bukkit player.
*/
public static Player getBukkitPlayerFromConnection(Object playerConnection) {
try {
return (Player) getBukkitEntity(MinecraftFields.getPlayerFromConnection(playerConnection));
} catch (Exception e) {
throw new IllegalArgumentException("Cannot get Bukkit entity from connection " + playerConnection, e);
}
}
/**
* Determine if a given object can be found within the package net.minecraft.server.
*
* @param obj - the object to test.
* @return TRUE if it can, FALSE otherwise.
*/
public static boolean isMinecraftObject(Object obj) {
if (obj == null) {
return false;
}
// Doesn't matter if we don't check for the version here
return obj.getClass().getName().startsWith(MINECRAFT_PREFIX_PACKAGE);
}
/**
* Determine if the given class is found within the package net.minecraft.server, or any equivalent package.
*
* @param clazz - the class to test.
* @return TRUE if it can, FALSE otherwise.
*/
public static boolean isMinecraftClass(Class<?> clazz) {
if (clazz == null) {
throw new IllegalArgumentException("clazz cannot be NULL.");
}
return getMinecraftObjectMatcher().isMatch(clazz, null);
}
/**
* Determine if a given object is found in net.minecraft.server, and has the given name.
*
* @param obj - the object to test.
* @param className - the class name to test.
* @return TRUE if it can, FALSE otherwise.
*/
public static boolean isMinecraftObject(Object obj, String className) {
if (obj == null) {
return false;
}
String javaName = obj.getClass().getName();
return javaName.startsWith(MINECRAFT_PREFIX_PACKAGE) && javaName.endsWith(className);
}
/**
* Determine if a given Object is compatible with a given Class. That is, whether or not the Object is an instance of
* that Class or one of its subclasses. If either is null, false is returned.
*
* @param clazz Class to test for, may be null
* @param object the Object to test, may be null
* @return True if it is, false if not
* @see Class#isAssignableFrom(Class)
*/
public static boolean is(Class<?> clazz, Object object) {
if (clazz == null || object == null) {
return false;
}
return clazz.isAssignableFrom(object.getClass());
}
/**
* Equivalent to {@link #is(Class, Object)} but we don't call getClass again
*/
public static boolean is(Class<?> clazz, Class<?> test) {
if (clazz == null || test == null) {
return false;
}
return clazz.isAssignableFrom(test);
}
/**
* Determine if a given object is a BlockPosition.
*
* @param obj - the object to test.
* @return TRUE if it can, FALSE otherwise.
*/
public static boolean isBlockPosition(Object obj) {
return is(getBlockPositionClass(), obj);
}
/**
* Determine if the given object is an NMS ChunkCoordIntPar.
*
* @param obj - the object.
* @return TRUE if it can, FALSE otherwise.
*/
public static boolean isChunkCoordIntPair(Object obj) {
return is(getChunkCoordIntPair(), obj);
}
/**
* Determine if the given object is actually a Minecraft packet.
*
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isPacketClass(Object obj) {
return is(getPacketClass(), obj);
}
/**
* Determine if the given object is assignable to a NetServerHandler (PlayerConnection)
*
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isServerHandler(Object obj) {
return is(getPlayerConnectionClass(), obj);
}
/**
* Determine if the given object is actually a Minecraft packet.
*
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isMinecraftEntity(Object obj) {
return is(getEntityClass(), obj);
}
/**
* Determine if the given object is a NMS ItemStack.
*
* @param value - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isItemStack(Object value) {
return is(getItemStackClass(), value);
}
/**
* Determine if the given object is a CraftPlayer class.
*
* @param value - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isCraftPlayer(Object value) {
return is(getCraftPlayerClass(), value);
}
/**
* Determine if the given object is a Minecraft player entity.
*
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isMinecraftPlayer(Object obj) {
return is(getEntityPlayerClass(), obj);
}
/**
* Determine if the given object is a data watcher object.
*
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isDataWatcher(Object obj) {
return is(getDataWatcherClass(), obj);
}
/**
* Determine if the given object is an IntHashMap object.
*
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isIntHashMap(Object obj) {
return is(getIntHashMapClass(), obj);
}
/**
* Determine if the given object is a CraftItemStack instancey.
*
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isCraftItemStack(Object obj) {
return is(getCraftItemStackClass(), obj);
}
/**
* Retrieve the EntityPlayer (NMS) class.
*
* @return The entity class.
*/
public static Class<?> getEntityPlayerClass() {
try {
return getMinecraftClass("server.level.EntityPlayer", "server.level.ServerPlayer", "EntityPlayer");
} catch (RuntimeException e) {
try {
// Grab CraftPlayer's handle
Method getHandle = FuzzyReflection
.fromClass(getCraftBukkitClass("entity.CraftPlayer"))
.getMethodByName("getHandle");
// EntityPlayer is the return type
return setMinecraftClass("EntityPlayer", getHandle.getReturnType());
} catch (IllegalArgumentException e1) {
throw new RuntimeException("Could not find EntityPlayer class.", e1);
}
}
}
/**
* Retrieve the EntityHuman class.
*
* @return The entity human class.
*/
public static Class<?> getEntityHumanClass() {
// Assume its the direct superclass
return getEntityPlayerClass().getSuperclass();
}
/**
* Retrieve the GameProfile class.
*
* @return The game profile class.
*/
public static Class<?> getGameProfileClass() {
return getClass("com.mojang.authlib.GameProfile");
}
/**
* Retrieve the entity (NMS) class.
*
* @return The entity class.
*/
public static Class<?> getEntityClass() {
try {
return getMinecraftClass("world.entity.Entity", "Entity");
} catch (RuntimeException e) {
return fallbackMethodReturn("Entity", "entity.CraftEntity", "getHandle");
}
}
/**
* Retrieve the CraftChatMessage.
*
* @return The CraftChatMessage class.
*/
public static Class<?> getCraftChatMessage() {
return getCraftBukkitClass("util.CraftChatMessage");
}
/**
* Retrieve the WorldServer (NMS) class.
*
* @return The WorldServer class.
*/
public static Class<?> getWorldServerClass() {
try {
return getMinecraftClass("server.level.WorldServer", "server.level.ServerLevel", "WorldServer");
} catch (RuntimeException e) {
return fallbackMethodReturn("WorldServer", "CraftWorld", "getHandle");
}
}
/**
* Retrieve the World (NMS) class.
*
* @return The world class.
*/
public static Class<?> getNmsWorldClass() {
try {
return getMinecraftClass("world.level.World", "world.level.Level", "World");
} catch (RuntimeException e) {
return setMinecraftClass("World", getWorldServerClass().getSuperclass());
}
}
/**
* Fallback on the return value of a named method in order to get a NMS class.
*
* @param nmsClass - the expected name of the Minecraft class.
* @param craftClass - a CraftBukkit class to look at.
* @param methodName - the method we will use.
* @return The return value of this method, which will be saved to the package cache.
*/
private static Class<?> fallbackMethodReturn(String nmsClass, String craftClass, String methodName) {
Class<?> result = FuzzyReflection.fromClass(getCraftBukkitClass(craftClass))
.getMethodByName(methodName)
.getReturnType();
// Save the result
return setMinecraftClass(nmsClass, result);
}
/**
* Retrieve the packet class.
*
* @return The packet class.
*/
public static Class<?> getPacketClass() {
return getMinecraftClass("network.protocol.Packet", "Packet");
}
public static Class<?> getByteBufClass() {
return getClass("io.netty.buffer.ByteBuf");
}
/**
* Retrieve the EnumProtocol class.
*
* @return The Enum protocol class.
*/
public static Class<?> getEnumProtocolClass() {
return getMinecraftClass("network.EnumProtocol", "network.ConnectionProtocol", "EnumProtocol");
}
/**
* Retrieve the IChatBaseComponent class.
*
* @return The IChatBaseComponent.
*/
public static Class<?> getIChatBaseComponentClass() {
return getMinecraftClass("network.chat.IChatBaseComponent", "network.chat.IChatbaseComponent", "network.chat.Component", "IChatBaseComponent");
}
public static Class<?> getIChatBaseComponentArrayClass() {
return getArrayClass(getIChatBaseComponentClass());
}
/**
* Retrieve the NMS chat component text class.
*
* @return The chat component class.
*/
public static Class<?> getChatComponentTextClass() {
return getMinecraftClass("network.chat.ChatComponentText", "network.chat.TextComponent", "ChatComponentText");
}
/**
* Attempt to find the ChatSerializer class.
*
* @return The serializer class.
* @throws IllegalStateException If the class could not be found or deduced.
*/
public static Class<?> getChatSerializerClass() {
return getMinecraftClass("network.chat.IChatBaseComponent$ChatSerializer", "network.chat.Component$Serializer", "IChatBaseComponent$ChatSerializer");
}
/**
* Retrieve the ServerPing class.
*
* @return The ServerPing class.
*/
public static Class<?> getServerPingClass() {
return getMinecraftClass("network.protocol.status.ServerPing", "network.protocol.status.ServerStatus", "ServerPing");
}
/**
* Retrieve the ServerPingServerData class.
*
* @return The ServerPingServerData class.
*/
public static Class<?> getServerPingServerDataClass() {
return getMinecraftClass("network.protocol.status.ServerPing$ServerData", "network.protocol.status.ServerStatus$Version", "ServerPing$ServerData");
}
/**
* Retrieve the ServerPingPlayerSample class.
*
* @return The ServerPingPlayerSample class.
*/
public static Class<?> getServerPingPlayerSampleClass() {
return getMinecraftClass(
"network.protocol.status.ServerPing$ServerPingPlayerSample",
"network.protocol.status.ServerStatus$Players",
"ServerPing$ServerPingPlayerSample");
}
/**
* Retrieve the MinecraftServer class.
*
* @return MinecraftServer class.
*/
public static Class<?> getMinecraftServerClass() {
try {
return getMinecraftClass("server.MinecraftServer", "MinecraftServer");
} catch (RuntimeException e) {
// Reset cache and try again
setMinecraftClass("MinecraftServer", null);
useFallbackServer();
return getMinecraftClass("MinecraftServer");
}
}
/**
* Retrieve the NMS statistics class.
*
* @return The statistics class.
*/
public static Class<?> getStatisticClass() {
return getMinecraftClass("stats.Statistic", "stats.Stat", "Statistic");
}
/**
* Retrieve the NMS statistic list class.
*
* @return The statistic list class.
*/
public static Class<?> getStatisticListClass() {
return getMinecraftClass("stats.StatisticList", "stats.Stats", "StatisticList");
}
/**
* Retrieve the player list class (or ServerConfigurationManager),
*
* @return The player list class.
*/
public static Class<?> getPlayerListClass() {
try {
return getMinecraftClass("server.players.PlayerList", "PlayerList");
} catch (RuntimeException e) {
// Reset cache and try again
setMinecraftClass("PlayerList", null);
useFallbackServer();
return getMinecraftClass("PlayerList");
}
}
/**
* Retrieve the PlayerConnection class.
*
* @return The PlayerConnection class.
*/
public static Class<?> getPlayerConnectionClass() {
return getMinecraftClass("server.network.PlayerConnection", "server.network.ServerGamePacketListenerImpl", "PlayerConnection");
}
/**
* Retrieve the NetworkManager class.
*
* @return The NetworkManager class.
*/
public static Class<?> getNetworkManagerClass() {
return getMinecraftClass("network.NetworkManager", "network.Connection", "NetworkManager");
}
/**
* Retrieve the NMS ItemStack class.
*
* @return The ItemStack class.
*/
public static Class<?> getItemStackClass() {
try {
return getMinecraftClass("world.item.ItemStack", "ItemStack");
} catch (RuntimeException e) {
// Use the handle reference
return setMinecraftClass("ItemStack", FuzzyReflection.fromClass(getCraftItemStackClass(), true)
.getFieldByName("handle")
.getType());
}
}
/**
* Retrieve the Block (NMS) class.
*
* @return Block (NMS) class.
*/
public static Class<?> getBlockClass() {
return getMinecraftClass("world.level.block.Block", "Block");
}
public static Class<?> getItemClass() {
return getNullableNMS("world.item.Item", "Item");
}
public static Class<?> getFluidTypeClass() {
return getNullableNMS("world.level.material.FluidType", "world.level.material.Fluid", "FluidType");
}
public static Class<?> getParticleTypeClass() {
return getNullableNMS("core.particles.ParticleType", "core.particles.SimpleParticleType", "ParticleType");
}
/**
* Retrieve the WorldType class.
*
* @return The WorldType class.
*/
public static Class<?> getWorldTypeClass() {
return getMinecraftClass("WorldType");
}
/**
* Retrieve the DataWatcher class.
*
* @return The DataWatcher class.
*/
public static Class<?> getDataWatcherClass() {
return getMinecraftClass("network.syncher.DataWatcher", "network.syncher.SynchedEntityData", "DataWatcher");
}
/**
* Retrieves the BlockPosition class.
*
* @return The BlockPosition class.
*/
public static Class<?> getBlockPositionClass() {
return getMinecraftClass("core.BlockPosition", "core.BlockPos", "BlockPosition");
}
/**
* Retrieves the Vec3D class.
*
* @return The Vec3D class.
*/
public static Class<?> getVec3DClass() {
return getMinecraftClass("world.phys.Vec3D", "world.phys.Vec3", "Vec3D");
}
/**
* Retrieve the ChunkCoordIntPair class.
*
* @return The ChunkCoordIntPair class.
*/
public static Class<?> getChunkCoordIntPair() {
return getMinecraftClass("world.level.ChunkCoordIntPair", "world.level.ChunkPos", "ChunkCoordIntPair");
}
/**
* Retrieve the DataWatcher Item class.
*
* @return The class
*/
public static Class<?> getDataWatcherItemClass() {
return getMinecraftClass("network.syncher.DataWatcher$Item", "network.syncher.SynchedEntityData$DataItem", "DataWatcher$Item", "DataWatcher$WatchableObject");
}
public static Class<?> getDataWatcherObjectClass() {
return getNullableNMS("network.syncher.DataWatcherObject", "network.syncher.EntityDataAccessor", "DataWatcherObject");
}
public static boolean watcherObjectExists() {
if (cachedWatcherObject == null) {
cachedWatcherObject = getDataWatcherObjectClass() != null;
}
return cachedWatcherObject;
}
public static Class<?> getDataWatcherSerializerClass() {
return getNullableNMS("network.syncher.DataWatcherSerializer", "network.syncher.EntityDataSerializer", "DataWatcherSerializer");
}
public static Class<?> getDataWatcherRegistryClass() {
return getMinecraftClass("network.syncher.DataWatcherRegistry", "network.syncher.EntityDataSerializers", "DataWatcherRegistry");
}
public static Class<?> getMinecraftKeyClass() {
return getMinecraftClass("resources.MinecraftKey", "resources.ResourceLocation", "MinecraftKey");
}
public static Class<?> getMobEffectListClass() {
return getMinecraftClass("world.effect.MobEffectList", "MobEffectList", "world.effect.MobEffect");
}
public static Class<?> getSoundEffectClass() {
return getNullableNMS("sounds.SoundEffect", "sounds.SoundEvent", "SoundEffect");
}
/**
* Retrieve the ServerConnection abstract class.
*
* @return The ServerConnection class.
*/
public static Class<?> getServerConnectionClass() {
return getMinecraftClass("server.network.ServerConnection", "server.network.ServerConnectionListener", "ServerConnection");
}
/**
* Retrieve the NBT base class.
*
* @return The NBT base class.
*/
public static Class<?> getNBTBaseClass() {
return getMinecraftClass("nbt.NBTBase", "nbt.Tag", "NBTBase");
}
/**
* Retrieve the NBT read limiter class.
*
* @return The NBT read limiter.
*/
public static Class<?> getNBTReadLimiterClass() {
return getMinecraftClass("nbt.NBTReadLimiter", "nbt.NbtAccounter", "NBTReadLimiter");
}
/**
* Retrieve the NBT Compound class.
*
* @return The NBT Compond class.
*/
public static Class<?> getNBTCompoundClass() {
return getMinecraftClass("nbt.NBTTagCompound", "nbt.CompoundTag", "NBTTagCompound");
}
/**
* Retrieve the EntityTracker (NMS) class.
*
* @return EntityTracker class.
*/
public static Class<?> getEntityTrackerClass() {
return getMinecraftClass("server.level.PlayerChunkMap$EntityTracker", "server.level.ChunkMap$TrackedEntity", "EntityTracker");
}
/**
* Retrieve the attribute snapshot class.
* <p>
* This stores the final value of an attribute, along with all the associated computational steps.
*
* @return The attribute snapshot class.
*/
public static Class<?> getAttributeSnapshotClass() {
return getMinecraftClass(
"network.protocol.game.PacketPlayOutUpdateAttributes$AttributeSnapshot",
"network.protocol.game.ClientboundUpdateAttributesPacket$AttributeSnapshot",
"AttributeSnapshot",
"PacketPlayOutUpdateAttributes$AttributeSnapshot");
}
/**
* Retrieve the IntHashMap class.
*
* @return IntHashMap class.
*/
public static Class<?> getIntHashMapClass() {
return getNullableNMS("IntHashMap");
}
/**
* Retrieve the attribute modifier class.
*
* @return Attribute modifier class.
*/
public static Class<?> getAttributeModifierClass() {
return getMinecraftClass("world.entity.ai.attributes.AttributeModifier", "AttributeModifier");
}
/**
* Retrieve the net.minecraft.server.MobEffect class.
*
* @return The mob effect class.
*/
public static Class<?> getMobEffectClass() {
return getMinecraftClass("world.effect.MobEffect", "world.effect.MobEffectInstance", "MobEffect");
}
/**
* Retrieve the packet data serializer class that overrides ByteBuf.
*
* @return The data serializer class.
*/
public static Class<?> getPacketDataSerializerClass() {
return getMinecraftClass("network.PacketDataSerializer", "network.FriendlyByteBuf", "PacketDataSerializer");
}
/**
* Retrieve the NBTCompressedStreamTools class.
*
* @return The NBTCompressedStreamTools class.
*/
public static Class<?> getNbtCompressedStreamToolsClass() {
return getMinecraftClass("nbt.NBTCompressedStreamTools", "nbt.NbtIo", "NBTCompressedStreamTools");
}
/**
* Retrieve the NMS tile entity class.
*
* @return The tile entity class.
*/
public static Class<?> getTileEntityClass() {
return getMinecraftClass("world.level.block.entity.TileEntity", "world.level.block.entity.BlockEntity", "TileEntity");
}
/**
* Retrieve the Gson class used by Minecraft.
*
* @return The Gson class.
*/
public static Class<?> getMinecraftGsonClass() {
return getMinecraftLibraryClass("com.google.gson.Gson");
}
/**
* Retrieve the ItemStack[] class.
*
* @return The ItemStack[] class.
*/
public static Class<?> getItemStackArrayClass() {
if (itemStackArrayClass == null) {
itemStackArrayClass = getArrayClass(getItemStackClass());
}
return itemStackArrayClass;
}
/**
* Retrieve the array class of a given component type.
*
* @param componentType - type of each element in the array.
* @return The class of the array.
*/
public static Class<?> getArrayClass(Class<?> componentType) {
// A bit of a hack, but it works
return Array.newInstance(componentType, 0).getClass();
}
/**
* Retrieve the CraftItemStack class.
*
* @return The CraftItemStack class.
*/
public static Class<?> getCraftItemStackClass() {
return getCraftBukkitClass("inventory.CraftItemStack");
}
/**
* Retrieve the CraftPlayer class.
*
* @return CraftPlayer class.
*/
public static Class<?> getCraftPlayerClass() {
return getCraftBukkitClass("entity.CraftPlayer");
}
/**
* Retrieve the CraftWorld class.
*
* @return The CraftWorld class.
*/
public static Class<?> getCraftWorldClass() {
return getCraftBukkitClass("CraftWorld");
}
/**
* Retrieve the CraftEntity class.
*
* @return CraftEntity class.
*/
public static Class<?> getCraftEntityClass() {
return getCraftBukkitClass("entity.CraftEntity");
}
/**
* Retrieve the CraftChatMessage introduced in 1.7.2
*
* @return The CraftChatMessage class.
*/
public static Class<?> getCraftMessageClass() {
return getCraftBukkitClass("util.CraftChatMessage");
}
/**
* Retrieve the PlayerInfoData class in 1.8.
*
* @return The PlayerInfoData class
*/
public static Class<?> getPlayerInfoDataClass() {
return getMinecraftClass(
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData",
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate",
"PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
}
/**
* Retrieves the entity use action class in 1.17.
*
* @return The EntityUseAction class
*/
public static Class<?> getEnumEntityUseActionClass() {
Class<?> packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass();
FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(packetClass, true);
try {
return fuzzyReflection.getFieldByType("^.*(EnumEntityUseAction)").getType();
} catch (IllegalArgumentException ignored) {
return fuzzyReflection.getFieldByType("^.*(Action)").getType();
}
}
/**
* Get a method accessor to get the actual use action out of the wrapping EnumEntityUseAction in 1.17.
*
* @return a method accessor to get the actual use action
*/
public static MethodAccessor getEntityUseActionEnumMethodAccessor() {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeExact(EnumWrappers.getEntityUseActionClass())
.build()));
}
/**
* Get a field accessor for the hand in the wrapping EnumEntityUseAction in 1.17.
*
* @param enumEntityUseAction the object instance of the action, the field is not present in attack.
* @return a field accessor for the hand in the wrapping EnumEntityUseAction
*/
public static FieldAccessor getHandEntityUseActionEnumFieldAccessor(Object enumEntityUseAction) {
FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true);
return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder()
.typeExact(EnumWrappers.getHandClass())
.build()));
}
/**
* Get a field accessor for the vec3d in the wrapping EnumEntityUseAction in 1.17.
*
* @param enumEntityUseAction the object instance of the action, the field is not present in attack.
* @return a field accessor for the hand in the wrapping EnumEntityUseAction
*/
public static FieldAccessor getVec3EntityUseActionEnumFieldAccessor(Object enumEntityUseAction) {
FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true);
return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getVec3DClass())
.build()));
}
/**
* Determine if the given object is a PlayerInfoData.
*
* @param obj - the given object.
* @return TRUE if it is, FALSE otherwise.
*/
public static boolean isPlayerInfoData(Object obj) {
return is(getPlayerInfoDataClass(), obj);
}
/**
* Retrieve the IBlockData class in 1.8.
*
* @return The IBlockData class
*/
public static Class<?> getIBlockDataClass() {
return getMinecraftClass("world.level.block.state.IBlockData", "world.level.block.state.BlockState", "IBlockData");
}
/**
* Retrieve the MultiBlockChangeInfo class in 1.8.
*
* @return The MultiBlockChangeInfo class
*/
public static Class<?> getMultiBlockChangeInfoClass() {
return getMinecraftClass("MultiBlockChangeInfo", "PacketPlayOutMultiBlockChange$MultiBlockChangeInfo");
}
/**
* Retrieve the MultiBlockChangeInfo array class in 1.8.
*
* @return The MultiBlockChangeInfo array class
*/
public static Class<?> getMultiBlockChangeInfoArrayClass() {
return getArrayClass(getMultiBlockChangeInfoClass());
}
/**
* Retrieve the PacketPlayOutGameStateChange.a class, aka GameState in 1.16
*
* @return The GameState class
*/
public static Class<?> getGameStateClass() {
// it's called "a" so there's not a lot we can do to identify it
Class<?> packetClass = PacketType.Play.Server.GAME_STATE_CHANGE.getPacketClass();
return packetClass.getClasses()[0];
}
public static boolean signUpdateExists() {
return getNullableNMS("PacketPlayOutUpdateSign") != null;
}
public static Class<?> getNonNullListClass() {
return getMinecraftClass("core.NonNullList", "NonNullList");
}
public static MethodAccessor getNonNullListCreateAccessor() {
try {
Class<?> nonNullListType = MinecraftReflection.getNonNullListClass();
Method method = FuzzyReflection.fromClass(nonNullListType).getMethod(FuzzyMethodContract.newBuilder()
.returnTypeExact(nonNullListType)
.requireModifier(Modifier.STATIC)
.build());
return Accessors.getMethodAccessor(method);
} catch (Exception ex) {
return null;
}
}
public static Class<?> getCraftSoundClass() {
return getCraftBukkitClass("CraftSound");
}
public static Class<?> getSectionPositionClass() {
return getMinecraftClass("core.SectionPosition", "core.SectionPos", "SectionPosition");
}
/**
* Retrieves the Bukkit equivalent of a NMS ItemStack. This method should preserve NBT data and will never return null
* when supplied with a valid ItemStack. Empty ItemStacks are treated as AIR.
*
* @param generic NMS ItemStack
* @return The Bukkit equivalent
*/
public static ItemStack getBukkitItemStack(Object generic) {
if (generic == null) {
// Convert null to AIR - 1.11 behavior
return new ItemStack(Material.AIR);
}
if (generic instanceof ItemStack) {
ItemStack bukkit = (ItemStack) generic;
// They're probably looking for the CraftItemStack
// If it's one already our work is done
if (is(getCraftItemStackClass(), generic)) {
return bukkit;
}
// If not, convert it to one
Object nmsStack = getMinecraftItemStack(bukkit);
return getBukkitItemStack(nmsStack);
}
if (!is(getItemStackClass(), generic)) {
// We can't do anything with non-ItemStacks
throw new IllegalArgumentException(generic + " is not an ItemStack!");
}
try {
// Check null enforcement - 1.11 behavior
if (nullEnforced == null) {
isEmpty = Accessors.getMethodAccessor(getItemStackClass().getMethod("isEmpty"));
nullEnforced = true;
}
if (nullEnforced) {
if ((boolean) isEmpty.invoke(generic)) {
return new ItemStack(Material.AIR);
}
}
} catch (ReflectiveOperationException ex) {
nullEnforced = false;
}
if (asCraftMirror == null) {
try {
Method asMirrorMethod = getCraftItemStackClass().getMethod("asCraftMirror", getItemStackClass());
asCraftMirror = Accessors.getMethodAccessor(asMirrorMethod);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to obtain CraftItemStack.asCraftMirror", ex);
}
}
try {
// Convert to a craft mirror to preserve NBT data
return (ItemStack) asCraftMirror.invoke(nullEnforced, generic);
} catch (IllegalStateException ex) {
throw new RuntimeException("Failed to obtain craft mirror of " + generic, ex);
}
}
/**
* Retrieves the NMS equivalent of a Bukkit ItemStack. This method will never return null and should preserve NBT
* data. Null inputs are treated as empty (AIR) ItemStacks.
*
* @param specific Bukkit ItemStack
* @return The NMS equivalent
*/
public static Object getMinecraftItemStack(ItemStack specific) {
if (asNMSCopy == null) {
try {
Method asNmsCopyMethod = getCraftItemStackClass().getMethod("asNMSCopy", ItemStack.class);
asNMSCopy = Accessors.getMethodAccessor(asNmsCopyMethod);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Failed to obtain CraftItemStack.asNMSCopy", ex);
}
}
if (is(getCraftItemStackClass(), specific)) {
// If it's already a CraftItemStack, use its handle
Object unwrapped = BukkitUnwrapper.getInstance().unwrapItem(specific);
if (unwrapped != null) {
return unwrapped;
} else {
if (itemStackAir == null) {
// Easiest way to get the Material.AIR ItemStack?
itemStackAir = getMinecraftItemStack(new ItemStack(Material.AIR));
}
return itemStackAir;
}
}
try {
// If not, grab a NMS copy
return asNMSCopy.invoke(null, specific);
} catch (IllegalStateException ex) {
throw new RuntimeException("Failed to make NMS copy of " + specific, ex);
}
}
/**
* Retrieve the given class by name.
*
* @param className - name of the class.
* @return The class.
*/
private static Class<?> getClass(String className) {
try {
return getClassSource().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Cannot find class " + className, e);
}
}
/**
* Retrieve the class object of a specific CraftBukkit class.
*
* @param className - the specific CraftBukkit class.
* @return Class object.
* @throws RuntimeException If we are unable to find the given class.
*/
public static Class<?> getCraftBukkitClass(String className) {
if (craftbukkitPackage == null) {
craftbukkitPackage = new CachedPackage(getCraftBukkitPackage(), getClassSource());
}
return craftbukkitPackage.getPackageClass(className)
.orElseThrow(() -> new RuntimeException("Failed to find CraftBukkit class: " + className));
}
/**
* Retrieve the class object of a specific Minecraft class.
*
* @param className - the specific Minecraft class.
* @return Class object.
* @throws RuntimeException If we are unable to find the given class.
*/
public static Class<?> getMinecraftClass(String className) {
if (minecraftPackage == null) {
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
}
return minecraftPackage.getPackageClass(className)
.orElseThrow(() -> new RuntimeException("Failed to find NMS class: " + className));
}
public static Class<?> getNullableNMS(String className, String... aliases) {
try {
return getMinecraftClass(className, aliases);
} catch (RuntimeException ex) {
return null;
}
}
/**
* Set the class object for the specific Minecraft class.
*
* @param className - name of the Minecraft class.
* @param clazz - the new class object.
* @return The provided clazz object.
*/
private static Class<?> setMinecraftClass(String className, Class<?> clazz) {
if (minecraftPackage == null) {
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
}
minecraftPackage.setPackageClass(className, clazz);
return clazz;
}
/**
* Retrieve the current class source.
*
* @return The class source.
*/
private static ClassSource getClassSource() {
return CLASS_SOURCE;
}
/**
* Retrieve the first class that matches a specified Minecraft name.
*
* @param className - the specific Minecraft class.
* @param aliases - alternative names for this Minecraft class.
* @return Class object.
* @throws RuntimeException If we are unable to find any of the given classes.
*/
public static Class<?> getMinecraftClass(String className, String... aliases) {
if (minecraftPackage == null) {
minecraftPackage = new CachedPackage(getMinecraftPackage(), getClassSource());
}
return minecraftPackage.getPackageClass(className).orElseGet(() -> {
Class<?> resolved = null;
for (String alias : aliases) {
// try to resolve the class and stop searching if we found it
resolved = minecraftPackage.getPackageClass(alias).orElse(null);
if (resolved != null) {
break;
}
}
// if we resolved the class cache it and return the result
if (resolved != null) {
minecraftPackage.setPackageClass(className, resolved);
return resolved;
}
// unable to find the class
throw new RuntimeException(String.format("Unable to find %s (%s)", className, String.join(", ", aliases)));
});
}
/**
* Retrieve the class object of a specific Minecraft library class.
*
* @param className - the specific library Minecraft class.
* @return Class object.
* @throws RuntimeException If we are unable to find the given class.
*/
public static Class<?> getMinecraftLibraryClass(String className) {
if (libraryPackage == null) {
libraryPackage = new CachedPackage("", getClassSource());
}
return libraryPackage.getPackageClass(className)
.orElseThrow(() -> new RuntimeException("Failed to find class: " + className));
}
/**
* Set the class object for the specific library class.
*
* @param className - name of the Minecraft library class.
* @param clazz - the new class object.
* @return The provided clazz object.
*/
private static Class<?> setMinecraftLibraryClass(String className, Class<?> clazz) {
if (libraryPackage == null) {
libraryPackage = new CachedPackage("", getClassSource());
}
libraryPackage.setPackageClass(className, clazz);
return clazz;
}
/**
* Dynamically retrieve the NetworkManager name.
*
* @return Name of the NetworkManager class.
*/
public static String getNetworkManagerName() {
return getNetworkManagerClass().getSimpleName();
}
/**
* Retrieve an instance of the packet data serializer wrapper.
*
* @param buffer - the buffer.
* @return The instance.
*/
public static Object getPacketDataSerializer(Object buffer) {
try {
// TODO: move this to MinecraftMethods, or at least, cache the constructor accessor
Class<?> packetSerializer = getPacketDataSerializerClass();
return packetSerializer.getConstructor(getByteBufClass()).newInstance(buffer);
} catch (Exception e) {
throw new RuntimeException("Cannot construct packet serializer.", e);
}
}
public static Object createPacketDataSerializer(int initialSize) {
// validate the initial size
if (initialSize <= 0) {
initialSize = 256;
}
Object buffer = Unpooled.buffer(initialSize);
return getPacketDataSerializer(buffer);
}
public static Class<?> getNbtTagTypes() {
return getMinecraftClass("nbt.NBTTagTypes", "nbt.TagTypes", "NBTTagTypes");
}
public static Class<?> getChatDeserializer() {
return getMinecraftClass("util.ChatDeserializer", "util.GsonHelper", "ChatDeserializer");
}
public static Class<?> getChatMutableComponentClass() {
return getMinecraftClass("network.chat.IChatMutableComponent", "network.chat.MutableComponent");
}
public static Class<?> getDimensionManager() {
return getMinecraftClass("world.level.dimension.DimensionManager", "world.level.dimension.DimensionType", "DimensionManager");
}
public static Class<?> getMerchantRecipeList() {
return getMinecraftClass("world.item.trading.MerchantRecipeList", "world.item.trading.MerchantOffers", "MerchantRecipeList");
}
public static Class<?> getResourceKey() {
return getMinecraftClass("resources.ResourceKey", "ResourceKey");
}
public static Class<?> getEntityTypes() {
return getMinecraftClass("world.entity.EntityTypes", "world.entity.EntityType", "EntityTypes");
}
public static Class<?> getParticleParam() {
return getMinecraftClass("core.particles.ParticleParam", "core.particles.ParticleOptions", "ParticleParam");
}
public static Class<?> getSectionPosition() {
return getMinecraftClass("core.SectionPosition", "core.SectionPos", "SectionPosition");
}
public static Class<?> getChunkProviderServer() {
return getMinecraftClass("server.level.ChunkProviderServer", "server.level.ServerChunkCache", "ChunkProviderServer");
}
public static Class<?> getPlayerChunkMap() {
return getMinecraftClass("server.level.PlayerChunkMap", "server.level.ChunkMap", "PlayerChunkMap");
}
public static Class<?> getIRegistry() {
return getNullableNMS("core.IRegistry", "core.Registry", "IRegistry");
}
public static Class<?> getAttributeBase() {
return getMinecraftClass("world.entity.ai.attributes.AttributeBase", "world.entity.ai.attributes.Attribute", "AttributeBase");
}
public static Class<?> getProfilePublicKeyClass() {
return getMinecraftClass("world.entity.player.ProfilePublicKey");
}
public static Class<?> getSaltedSignatureClass() {
try {
return getMinecraftClass("SaltedSignature");
} catch (RuntimeException runtimeException) {
Class<?> minecraftEncryption = getMinecraftClass("util.MinecraftEncryption", "util.Crypt", "MinecraftEncryption");
FuzzyMethodContract constructorContract = FuzzyMethodContract.newBuilder()
.parameterCount(2)
.parameterExactType(Long.TYPE, 0)
.parameterExactType(byte[].class, 1)
.build();
for (Class<?> subclass : minecraftEncryption.getClasses()) {
FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(subclass, true);
List<Constructor<?>> constructors = fuzzyReflection.getConstructorList(constructorContract);
if (!constructors.isEmpty()) {
return setMinecraftClass("SaltedSignature", subclass);
}
}
Class<?> messageSigClass = getMinecraftClass("network.chat.MessageSignature", "MessageSignature");
FuzzyClassContract signatureContract = FuzzyClassContract.newBuilder()
.constructor(FuzzyMethodContract.newBuilder()
.parameterCount(2)
.parameterSuperOf(Long.TYPE, 0)
.parameterSuperOf(byte[].class, 1)
.build())
.build();
FuzzyFieldContract fuzzyFieldContract = FuzzyFieldContract.newBuilder()
.typeMatches(getMinecraftObjectMatcher().and(signatureContract))
.build();
Class<?> signatureClass = FuzzyReflection.fromClass(messageSigClass, true)
.getField(fuzzyFieldContract)
.getType();
return setMinecraftClass("SaltedSignature", signatureClass);
}
}
public static Class<?> getProfilePublicKeyDataClass() {
return getProfilePublicKeyClass().getClasses()[0];
}
public static Class<?> getFastUtilClass(String className) {
return getLibraryClass("it.unimi.dsi.fastutil." + className);
}
public static Class<?> getInt2ObjectMapClass() {
return getFastUtilClass("ints.Int2ObjectMap");
}
public static Class<?> getIntArrayListClass() {
return getFastUtilClass("ints.IntArrayList");
}
public static Class<?> getLibraryClass(String classname) {
try {
return getMinecraftLibraryClass(classname);
} catch (RuntimeException ex) {
Class<?> clazz = getMinecraftLibraryClass("org.bukkit.craftbukkit.libs." + classname);
setMinecraftLibraryClass(classname, clazz);
return clazz;
}
}
/**
* Fallback method that can determine the MinecraftServer and the ServerConfigurationManager.
*/
private static void useFallbackServer() {
// Get the first constructor that matches CraftServer(MINECRAFT_OBJECT, ANY)
Constructor<?> selected = FuzzyReflection.fromClass(getCraftBukkitClass("CraftServer"))
.getConstructor(FuzzyMethodContract.newBuilder()
.parameterMatches(getMinecraftObjectMatcher(), 0)
.parameterCount(2)
.build());
Class<?>[] params = selected.getParameterTypes();
// Jackpot - two classes at the same time!
setMinecraftClass("MinecraftServer", params[0]);
setMinecraftClass("PlayerList", params[1]);
}
public static Class<?> getLevelChunkPacketDataClass() {
return getNullableNMS("network.protocol.game.ClientboundLevelChunkPacketData");
}
public static Class<?> getLightUpdatePacketDataClass() {
return getNullableNMS("network.protocol.game.ClientboundLightUpdatePacketData");
}
public static Class<?> getBlockEntityTypeClass() {
return getMinecraftClass("world.level.block.entity.BlockEntityType", "world.level.block.entity.TileEntityTypes", "TileEntityTypes");
}
public static Class<?> getBlockEntityInfoClass() {
try {
return getMinecraftClass("BlockEntityInfo");
} catch (RuntimeException expected) {
Class<?> infoClass = (Class<?>) ((ParameterizedType) FuzzyReflection.fromClass(getLevelChunkPacketDataClass(),
true).getFieldListByType(List.class).get(0).getGenericType()).getActualTypeArguments()[0];
setMinecraftClass("BlockEntityInfo", infoClass);
return infoClass;
}
}
}