/* * 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.wrappers; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.wrappers.Either.Left; import com.comphenix.protocol.wrappers.Either.Right; import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.stream.Collectors; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLogger; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.PacketConstructor; import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.EnumWrappers.Dimension; import com.comphenix.protocol.wrappers.EnumWrappers.FauxEnumConverter; import com.comphenix.protocol.wrappers.nbt.NbtBase; import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.WorldType; import org.bukkit.advancement.Advancement; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.MerchantRecipe; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; import org.bukkit.util.Vector; import static com.comphenix.protocol.utility.MinecraftReflection.getCraftBukkitClass; import static com.comphenix.protocol.utility.MinecraftReflection.getMinecraftClass; import static com.comphenix.protocol.wrappers.Converters.handle; import static com.comphenix.protocol.wrappers.Converters.ignoreNull; /** * Contains several useful equivalent converters for normal Bukkit types. * * @author Kristian */ @SuppressWarnings("unchecked") public class BukkitConverters { // Check whether or not certain classes exists private static boolean hasWorldType = false; private static boolean hasAttributeSnapshot = false; // The static maps private static Map, EquivalentConverter> genericConverters; private static List unwrappers; // Used to access the world type private static Method worldTypeName; private static Method worldTypeGetType; // Used for potion effect conversion private static volatile Constructor mobEffectConstructor; private static volatile StructureModifier mobEffectModifier; // Used for fetching the CraftWorld associated with a WorldServer private static FieldAccessor craftWorldField; static { try { MinecraftReflection.getWorldTypeClass(); hasWorldType = true; } catch (Exception e) { } try { MinecraftReflection.getAttributeSnapshotClass(); hasAttributeSnapshot = true; } catch (Exception e) { } // Fetch CraftWorld field try { craftWorldField = Accessors.getFieldAccessor( MinecraftReflection.getNmsWorldClass(), MinecraftReflection.getCraftWorldClass(), true); } catch (Exception e) { e.printStackTrace(); } } /** * Represents a typical equivalence converter. * * @author Kristian * @param - type that can be converted. * @deprecated Replaced by {@link Converters#ignoreNull(EquivalentConverter)} */ @Deprecated public static abstract class IgnoreNullConverter implements EquivalentConverter { @Override public final Object getGeneric(TType specific) { if (specific != null) return getGenericValue(specific); else return null; } /** * Retrieve a copy of the actual generic value. * @param specific - the specific type- * @return A copy of the specific type. */ public abstract Object getGenericValue(TType specific); @Override public final TType getSpecific(Object generic) { if (generic != null) return getSpecificValue(generic); else return null; } /** * Retrieve a copy of the specific type using an instance of the generic type. * @param generic - generic type. * @return A copy of the specific type. */ public abstract TType getSpecificValue(Object generic); @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj instanceof EquivalentConverter) { EquivalentConverter that = (EquivalentConverter) obj; return Objects.equal(this.getSpecificType(), that.getSpecificType()); } return false; } @Override public int hashCode() { return Objects.hashCode(this.getSpecificType()); } } /** * Represents a converter that is only valid in a given world. * * @author Kristian * @param - instance types it converts. */ private static abstract class WorldSpecificConverter implements EquivalentConverter { protected World world; /** * Initialize a new world-specificn converter. * @param world - the given world. */ WorldSpecificConverter(World world) { super(); this.world = world; } @Override public boolean equals(Object obj) { if (obj == this) return true; // Add another constraint if (obj instanceof WorldSpecificConverter && super.equals(obj)) { WorldSpecificConverter that = (WorldSpecificConverter) obj; return Objects.equal(this.world, that.world); } return false; } @Override public int hashCode() { return Objects.hashCode(this.getSpecificType(), this.world); } } public static EquivalentConverter> getMapConverter(EquivalentConverter keyConverter, EquivalentConverter valConverter) { return new EquivalentConverter>() { @Override public Map getSpecific(Object generic) { Map genericMap = (Map) generic; Map newMap; try { newMap = (Map) genericMap.getClass().newInstance(); } catch (ReflectiveOperationException ex) { newMap = new HashMap<>(); } for (Map.Entry entry : genericMap.entrySet()) { newMap.put(keyConverter.getSpecific(entry.getKey()), valConverter.getSpecific(entry.getValue())); } return newMap; } @Override public Object getGeneric(Map specific) { Map newMap; try { newMap = specific.getClass().newInstance(); } catch (ReflectiveOperationException ex) { newMap = new HashMap<>(); } for (Map.Entry entry : specific.entrySet()) { newMap.put(keyConverter.getGeneric(entry.getKey()), valConverter.getGeneric(entry.getValue())); } return newMap; } @Override public Class> getSpecificType() { return null; } }; } private static final Map, Supplier>> LIST_SUPPLIERS = new ConcurrentHashMap<>(); private static Object getGenericList(Class listClass, List specific, EquivalentConverter itemConverter) { List newList; Supplier> supplier = LIST_SUPPLIERS.get(listClass); if (supplier == null) { try { Constructor ctor = listClass.getConstructor(); newList = (List) ctor.newInstance(); supplier = () -> { try { return (List) ctor.newInstance(); } catch (ReflectiveOperationException ex) { throw new RuntimeException(ex); } }; } catch (ReflectiveOperationException ex) { // ex.printStackTrace(); supplier = ArrayList::new; newList = new ArrayList<>(); } LIST_SUPPLIERS.put(listClass, supplier); } else { newList = supplier.get(); } // Convert each object for (T position : specific) { if (position != null) { Object converted = itemConverter.getGeneric(position); if (converted != null) { newList.add(converted); } } else { newList.add(null); } } return newList; } private static List getSpecificList(Object generic, EquivalentConverter itemConverter) { if (generic instanceof Collection) { List items = new ArrayList<>(); // Copy everything to a new list for (Object item : (Collection) generic) { T result = itemConverter.getSpecific(item); if (item != null) items.add(result); } return items; } // Not valid return null; } public static EquivalentConverter> getListConverter(final Class listClass, final EquivalentConverter itemConverter) { return ignoreNull(new EquivalentConverter>() { @Override public List getSpecific(Object generic) { return getSpecificList(generic, itemConverter); } @Override public Object getGeneric(List specific) { return getGenericList(listClass, specific, itemConverter); } @Override public Class> getSpecificType() { // Damn you Java Class dummy = List.class; return (Class>) dummy; } }); } /** * Retrieve an equivalent converter for a list of generic items. * @param Type * @param itemConverter - an equivalent converter for the generic type. * @return An equivalent converter. */ public static EquivalentConverter> getListConverter(final EquivalentConverter itemConverter) { // Convert to and from the wrapper return ignoreNull(new EquivalentConverter>() { @Override public List getSpecific(Object generic) { return getSpecificList(generic, itemConverter); } @Override public Object getGeneric(List specific) { return getGenericList(specific.getClass(), specific, itemConverter); } @Override public Class> getSpecificType() { // Damn you Java Class dummy = List.class; return (Class>) dummy; } }); } @SuppressWarnings("rawtypes") public static EquivalentConverter> getPairConverter(final EquivalentConverter firstConverter, final EquivalentConverter secondConverter) { return ignoreNull(new EquivalentConverter>() { @Override public Object getGeneric(Pair specific) { Object first = firstConverter.getGeneric(specific.getFirst()); Object second = secondConverter.getGeneric(specific.getSecond()); return new com.mojang.datafixers.util.Pair(first, second); } @Override public Pair getSpecific(Object generic) { com.mojang.datafixers.util.Pair mjPair = (com.mojang.datafixers.util.Pair) generic; A first = firstConverter.getSpecific(mjPair.getFirst()); B second = secondConverter.getSpecific(mjPair.getSecond()); return new Pair(first, second); } @Override public Class> getSpecificType() { Class dummy = Pair.class; return (Class>) dummy; } }); } /** * @param leftConverter convert the left value if available * @param rightConverter convert the right value if available * @return converter for Mojang either class * @param converted left type * @param converted right type */ public static EquivalentConverter> getEitherConverter(EquivalentConverter leftConverter, EquivalentConverter rightConverter) { return ignoreNull(new EquivalentConverter>() { @Override public Object getGeneric(Either specific) { return specific.map( left -> com.mojang.datafixers.util.Either.left(leftConverter.getGeneric(left)), right -> com.mojang.datafixers.util.Either.right(rightConverter.getGeneric(right)) ); } @Override public Either getSpecific(Object generic) { com.mojang.datafixers.util.Either mjEither = (com.mojang.datafixers.util.Either) generic; return mjEither.map( left -> new Left<>(leftConverter.getSpecific(left)), right -> new Right<>(rightConverter.getSpecific(right)) ); } @Override public Class> getSpecificType() { Class dummy = Either.class; return (Class>) dummy; } }); } /** * Retrieve an equivalent converter for a set of generic items. * @param Element type * @param itemConverter - an equivalent converter for the generic type. * @return An equivalent converter. */ @SuppressWarnings("unchecked") public static EquivalentConverter> getSetConverter(final EquivalentConverter itemConverter) { // Convert to and from the wrapper return ignoreNull(new EquivalentConverter>() { @Override public Set getSpecific(Object generic) { if (generic instanceof Collection) { Set items = new HashSet<>(); // Copy everything to a new list for (Object item : (Collection) generic) { T result = itemConverter.getSpecific(item); if (item != null) items.add(result); } return items; } // Not valid return null; } @Override public Object getGeneric(Set specific) { Set newList; try { newList = (Set) specific.getClass().newInstance(); } catch (ReflectiveOperationException ex) { newList = new HashSet<>(); } // Convert each object for (T position : specific) { if (position != null) { Object converted = itemConverter.getGeneric(position); if (converted != null) { newList.add(converted); } } else { newList.add(null); } } return newList; } @Override public Class> getSpecificType() { // Damn you Java Class dummy = Set.class; return (Class>) dummy; } }); } /** * Retrieve an equivalent converter for an array of generic items. * @param Type *

* The array is wrapped in a list. * @param genericItemType - the generic item type. * @param itemConverter - an equivalent converter for the generic type. * @return An equivalent converter. */ public static EquivalentConverter> getArrayConverter(final Class genericItemType, final EquivalentConverter itemConverter) { // Convert to and from the wrapper return ignoreNull(new EquivalentConverter>() { @Override public List getSpecific(Object generic) { if (generic instanceof Object[]) { ImmutableList.Builder builder = ImmutableList.builder(); // Copy everything to a new list for (Object item : (Object[]) generic) { T result = itemConverter.getSpecific(item); builder.add(result); } return builder.build(); } // Not valid return null; } @Override public Object getGeneric(Iterable specific) { List list = Lists.newArrayList(specific); Object[] output = (Object[]) Array.newInstance(genericItemType, list.size()); // Convert each object for (int i = 0; i < output.length; i++) { Object converted = itemConverter.getGeneric(list.get(i)); output[i] = converted; } return output; } @Override public Class> getSpecificType() { // Damn you Java Class dummy = Iterable.class; return (Class>) dummy; } }); } /** * Retrieve a converter for wrapped game profiles. * @return Wrapped game profile converter. */ public static EquivalentConverter getWrappedGameProfileConverter() { return ignoreNull(handle(WrappedGameProfile::getHandle, WrappedGameProfile::fromHandle, WrappedGameProfile.class)); } /** * Retrieve a converter for wrapped chat components. * @return Wrapped chat component. */ public static EquivalentConverter getWrappedChatComponentConverter() { return ignoreNull(handle(WrappedChatComponent::getHandle, WrappedChatComponent::fromHandle, WrappedChatComponent.class)); } /** * Retrieve a converter for wrapped block data. * @return Wrapped block data. */ public static EquivalentConverter getWrappedBlockDataConverter() { return ignoreNull(handle(WrappedBlockData::getHandle, WrappedBlockData::fromHandle, WrappedBlockData.class)); } /** * Retrieve a converter for wrapped attribute snapshots. * @return Wrapped attribute snapshot converter. */ public static EquivalentConverter getWrappedAttributeConverter() { return ignoreNull(handle(WrappedAttribute::getHandle, WrappedAttribute::fromHandle, WrappedAttribute.class)); } public static EquivalentConverter getWrappedProfilePublicKeyConverter() { return ignoreNull(handle(WrappedProfilePublicKey::getHandle, WrappedProfilePublicKey::new, WrappedProfilePublicKey.class)); } public static EquivalentConverter getWrappedPublicKeyDataConverter() { return ignoreNull(handle(WrappedProfileKeyData::getHandle, WrappedProfileKeyData::new, WrappedProfileKeyData.class)); } public static EquivalentConverter getWrappedRemoteChatSessionDataConverter() { return ignoreNull(handle(WrappedRemoteChatSessionData::getHandle, WrappedRemoteChatSessionData::new, WrappedRemoteChatSessionData.class)); } /** * @return converter for cryptographic signature data that are used in login and chat packets */ public static EquivalentConverter getWrappedSignatureConverter() { return ignoreNull(handle(WrappedSaltedSignature::getHandle, WrappedSaltedSignature::new, WrappedSaltedSignature.class)); } /** * @return converter for an encoded cryptographic message signature */ public static EquivalentConverter getWrappedMessageSignatureConverter() { return ignoreNull(handle(WrappedMessageSignature::getHandle, WrappedMessageSignature::new, WrappedMessageSignature.class)); } public static EquivalentConverter getWrappedChunkDataConverter() { return ignoreNull(handle(WrappedLevelChunkData.ChunkData::getHandle, WrappedLevelChunkData.ChunkData::new, WrappedLevelChunkData.ChunkData.class)); } public static EquivalentConverter getWrappedLightDataConverter() { return ignoreNull(handle(WrappedLevelChunkData.LightData::getHandle, WrappedLevelChunkData.LightData::new, WrappedLevelChunkData.LightData.class)); } public static EquivalentConverter getPacketContainerConverter() { return ignoreNull(handle(PacketContainer::getHandle, PacketContainer::fromPacket, PacketContainer.class)); } /** * Retrieve a converter for watchable objects and the respective wrapper. * @return A watchable object converter. */ public static EquivalentConverter getWatchableObjectConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(WrappedWatchableObject specific) { return specific.getHandle(); } @Override public WrappedWatchableObject getSpecific(Object generic) { if (MinecraftReflection.is(MinecraftReflection.getDataWatcherItemClass(), generic)) return new WrappedWatchableObject(generic); else if (generic instanceof WrappedWatchableObject) return (WrappedWatchableObject) generic; else throw new IllegalArgumentException("Unrecognized type " + generic.getClass()); } @Override public Class getSpecificType() { return WrappedWatchableObject.class; } }); } /** * Retrieve a converter for data values in 1.19.3+. * @return A data value converter. */ public static EquivalentConverter getDataValueConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(WrappedDataValue specific) { return specific.getHandle(); } @Override public WrappedDataValue getSpecific(Object generic) { return new WrappedDataValue(generic); } @Override public Class getSpecificType() { return WrappedDataValue.class; } }); } /** * Retrieve a converter for the NMS DataWatcher class and our wrapper. * @return A DataWatcher converter. */ public static EquivalentConverter getDataWatcherConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(WrappedDataWatcher specific) { return specific.getHandle(); } @Override public WrappedDataWatcher getSpecific(Object generic) { if (MinecraftReflection.isDataWatcher(generic)) return new WrappedDataWatcher(generic); else if (generic instanceof WrappedDataWatcher) return (WrappedDataWatcher) generic; else throw new IllegalArgumentException("Unrecognized type " + generic.getClass()); } @Override public Class getSpecificType() { return WrappedDataWatcher.class; } }); } /** * Retrieve a converter for Bukkit's world type enum and the NMS equivalent. * @return A world type enum converter. */ public static EquivalentConverter getWorldTypeConverter() { // Check that we can actually use this converter if (!hasWorldType) return null; final Class worldType = MinecraftReflection.getWorldTypeClass(); return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(WorldType specific) { try { // Deduce getType method by parameters alone if (worldTypeGetType == null) { worldTypeGetType = FuzzyReflection.fromClass(worldType). getMethodByReturnTypeAndParameters("getType", worldType, new Class[]{String.class}); } // Convert to the Bukkit world type return worldTypeGetType.invoke(this, specific.getName()); } catch (Exception e) { throw new FieldAccessException("Cannot find the WorldType.getType() method.", e); } } @Override public WorldType getSpecific(Object generic) { try { if (worldTypeName == null) { try { worldTypeName = worldType.getMethod("name"); } catch (Exception e) { // Assume the first method is the one worldTypeName = FuzzyReflection.fromClass(worldType). getMethodByReturnTypeAndParameters("name", String.class, new Class[]{}); } } // Dynamically call the namne method String name = (String) worldTypeName.invoke(generic); return WorldType.getByName(name); } catch (Exception e) { throw new FieldAccessException("Cannot call the name method in WorldType.", e); } } @Override public Class getSpecificType() { return WorldType.class; } }); } /** * Retrieve an equivalent converter for net.minecraft.server NBT classes and their wrappers. * @return An equivalent converter for NBT. */ public static EquivalentConverter> getNbtConverter() { return ignoreNull(new EquivalentConverter>() { @Override public Object getGeneric(NbtBase specific) { return NbtFactory.fromBase(specific).getHandle(); } @Override public NbtBase getSpecific(Object generic) { return NbtFactory.fromNMS(generic, null); } @Override @SuppressWarnings("unchecked") public Class> getSpecificType() { // Damn you Java AGAIN Class dummy = NbtBase.class; return (Class>) dummy; } }); } /** * Retrieve a converter for NMS entities and Bukkit entities. * @param world - the current world. * @return A converter between the underlying NMS entity and Bukkit's wrapper. */ public static EquivalentConverter getEntityConverter(World world) { final WeakReference managerRef = new WeakReference<>(ProtocolLibrary.getProtocolManager()); return new WorldSpecificConverter(world) { @Override public Object getGeneric(Entity specific) { // Simple enough return specific.getEntityId(); } @Override public Entity getSpecific(Object generic) { try { Integer id = (Integer) generic; ProtocolManager manager = managerRef.get(); // Use the entity ID to get a reference to the entity if (id != null && id >= 0 && manager != null) { return manager.getEntityFromID(world, id); } else { return null; } } catch (FieldAccessException e) { throw new RuntimeException("Cannot retrieve entity from ID.", e); } } @Override public Class getSpecificType() { return Entity.class; } }; } private static MethodAccessor getEntityTypeName; private static MethodAccessor entityTypeFromName; public static EquivalentConverter getEntityTypeConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(EntityType specific) { if (entityTypeFromName == null) { Class entityTypesClass = MinecraftReflection.getEntityTypes(); entityTypeFromName = Accessors.getMethodAccessor( FuzzyReflection .fromClass(entityTypesClass, false) .getMethod(FuzzyMethodContract .newBuilder() .returnDerivedOf(Optional.class) .parameterExactArray(new Class[]{ String.class }) .build())); } Optional opt = (Optional) entityTypeFromName.invoke(null, specific.getName()); return opt.orElse(null); } @Override public EntityType getSpecific(Object generic) { if (getEntityTypeName == null) { Class entityTypesClass = MinecraftReflection.getEntityTypes(); getEntityTypeName = Accessors.getMethodAccessor( FuzzyReflection .fromClass(entityTypesClass, false) .getMethod(FuzzyMethodContract .newBuilder() .returnTypeExact(MinecraftReflection.getMinecraftKeyClass()) .parameterExactArray(new Class[]{ entityTypesClass }) .build())); } MinecraftKey key = MinecraftKey.fromHandle(getEntityTypeName.invoke(null, generic)); return EntityType.fromName(key.getKey()); } @Override public Class getSpecificType() { return EntityType.class; } }); } /** * Retrieve the converter used to convert NMS ItemStacks to Bukkit's ItemStack. * @return Item stack converter. */ public static EquivalentConverter getItemStackConverter() { return new EquivalentConverter() { @Override public ItemStack getSpecific(Object generic) { return MinecraftReflection.getBukkitItemStack(generic); } @Override public Object getGeneric(ItemStack specific) { return MinecraftReflection.getMinecraftItemStack(specific); } @Override public Class getSpecificType() { return ItemStack.class; } }; } /** * Retrieve the converter for the ServerPing packet in {@link PacketType.Status.Server#SERVER_INFO}. * @return Server ping converter. */ public static EquivalentConverter getWrappedServerPingConverter() { return ignoreNull(handle(WrappedServerPing::getHandle, WrappedServerPing::fromHandle, WrappedServerPing.class)); } /** * Retrieve the converter for a statistic. * @return Statistic converter. */ public static EquivalentConverter getWrappedStatisticConverter() { return ignoreNull(handle(WrappedStatistic::getHandle, WrappedStatistic::fromHandle, WrappedStatistic.class)); } private static MethodAccessor BLOCK_FROM_MATERIAL; private static MethodAccessor MATERIAL_FROM_BLOCK; /** * Retrieve a converter for block instances. * @return A converter for block instances. */ public static EquivalentConverter getBlockConverter() { if (BLOCK_FROM_MATERIAL == null || MATERIAL_FROM_BLOCK == null) { Class magicNumbers = MinecraftReflection.getCraftBukkitClass("util.CraftMagicNumbers"); Class block = MinecraftReflection.getBlockClass(); FuzzyReflection fuzzy = FuzzyReflection.fromClass(magicNumbers); FuzzyMethodContract.Builder builder = FuzzyMethodContract .newBuilder() .requireModifier(Modifier.STATIC) .returnTypeExact(Material.class) .parameterExactArray(block); MATERIAL_FROM_BLOCK = Accessors.getMethodAccessor(fuzzy.getMethod(builder.build())); builder = FuzzyMethodContract .newBuilder() .requireModifier(Modifier.STATIC) .returnTypeExact(block) .parameterExactArray(Material.class); BLOCK_FROM_MATERIAL = Accessors.getMethodAccessor(fuzzy.getMethod(builder.build())); } return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(Material specific) { return BLOCK_FROM_MATERIAL.invoke(null, specific); } @Override public Material getSpecific(Object generic) { return (Material) MATERIAL_FROM_BLOCK.invoke(null, generic); } @Override public Class getSpecificType() { return Material.class; } }); } /** * Retrieve the converter used to convert between a NMS World and a Bukkit world. * @return The world converter. */ public static EquivalentConverter getWorldConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(World specific) { return BukkitUnwrapper.getInstance().unwrapItem(specific); } @Override public World getSpecific(Object generic) { return (World) craftWorldField.get(generic); } @Override public Class getSpecificType() { return World.class; } }); } /** * Retrieve the converter used to convert between a PotionEffect and the equivalent NMS Mobeffect. * @return The potion effect converter. */ public static EquivalentConverter getPotionEffectConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(PotionEffect specific) { // Locate the constructor if (mobEffectConstructor == null) { try { mobEffectConstructor = MinecraftReflection.getMobEffectClass(). getConstructor(int.class, int.class, int.class, boolean.class); } catch (Exception e) { throw new RuntimeException("Cannot find mob effect constructor (int, int, int, boolean).", e); } } // Create the generic value try { return mobEffectConstructor.newInstance( specific.getType().getId(), specific.getDuration(), specific.getAmplifier(), specific.isAmbient()); } catch (Exception e) { throw new RuntimeException("Cannot construct MobEffect.", e); } } @Override public PotionEffect getSpecific(Object generic) { if (mobEffectModifier == null) { mobEffectModifier = new StructureModifier<>(MinecraftReflection.getMobEffectClass()); } StructureModifier ints = mobEffectModifier.withTarget(generic).withType(int.class); StructureModifier bools = mobEffectModifier.withTarget(generic).withType(boolean.class); return new PotionEffect( PotionEffectType.getById(ints.read(0)), /* effectId */ ints.read(1), /* duration */ ints.read(2), /* amplification */ bools.read(1) /* ambient */ ); } @Override public Class getSpecificType() { return PotionEffect.class; } }); } private static Constructor vec3dConstructor; private static StructureModifier vec3dModifier; /** * Retrieve the converter used to convert between a Vector and the equivalent NMS Vec3d. * @return The Vector converter. */ public static EquivalentConverter getVectorConverter() { return ignoreNull(new EquivalentConverter() { @Override public Class getSpecificType() { return Vector.class; } @Override public Object getGeneric(Vector specific) { if (vec3dConstructor == null) { try { vec3dConstructor = MinecraftReflection.getVec3DClass().getConstructor( double.class, double.class, double.class); } catch (Throwable ex) { throw new RuntimeException("Could not find Vec3d constructor (double, double, double)"); } } try { return vec3dConstructor.newInstance(specific.getX(), specific.getY(), specific.getZ()); } catch (Throwable ex) { throw new RuntimeException("Could not construct Vec3d.", ex); } } @Override public Vector getSpecific(Object generic) { if (vec3dModifier == null) { vec3dModifier = new StructureModifier<>(MinecraftReflection.getVec3DClass()); } StructureModifier doubles = vec3dModifier.withTarget(generic).withType(double.class); return new Vector( doubles.read(0), /* x */ doubles.read(1), /* y */ doubles.read(2) /* z */ ); } }); } static MethodAccessor getSound = null; static MethodAccessor getSoundEffect = null; static FieldAccessor soundKey = null; static MethodAccessor getSoundEffectByKey = null; static MethodAccessor getSoundEffectBySound = null; static MethodAccessor getSoundByEffect = null; static Map soundIndex = null; public static EquivalentConverter getSoundConverter() { // Try to create sound converter for new versions greater 1.16.4 if (MinecraftVersion.NETHER_UPDATE_4.atOrAbove()) { if (getSoundEffectByKey == null || getSoundEffectBySound == null || getSoundByEffect == null) { Class craftSound = MinecraftReflection.getCraftSoundClass(); FuzzyReflection fuzzy = FuzzyReflection.fromClass(craftSound, true); getSoundEffectByKey = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters( "getSoundEffect", MinecraftReflection.getSoundEffectClass(), String.class )); getSoundEffectBySound = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters( "getSoundEffect", MinecraftReflection.getSoundEffectClass(), Sound.class )); getSoundByEffect = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters( "getBukkit", Sound.class, MinecraftReflection.getSoundEffectClass() )); } return ignoreNull(new EquivalentConverter() { @Override public Class getSpecificType() { return Sound.class; } @Override public Object getGeneric(Sound specific) { return getSoundEffectBySound.invoke(null, specific); } @Override public Sound getSpecific(Object generic) { try { return (Sound) getSoundByEffect.invoke(null, generic); } catch (IllegalStateException ex) { if (ex.getCause() instanceof NullPointerException) { // "null" sounds cause NPEs inside getSoundByEffect return null; } throw ex; } } }); } // Fall back to sound converter from legacy versions before 1.16.4 if (getSound == null || getSoundEffect == null) { Class craftSound = MinecraftReflection.getCraftSoundClass(); FuzzyReflection fuzzy = FuzzyReflection.fromClass(craftSound, true); getSound = Accessors.getMethodAccessor( fuzzy.getMethodByReturnTypeAndParameters("getSound", String.class, Sound.class)); getSoundEffect = Accessors.getMethodAccessor(fuzzy.getMethodByReturnTypeAndParameters("getSoundEffect", MinecraftReflection.getSoundEffectClass(), String.class)); } return ignoreNull(new EquivalentConverter() { @Override public Class getSpecificType() { return Sound.class; } @Override public Object getGeneric(Sound specific) { // Getting the SoundEffect is easy, Bukkit provides us the methods String key = (String) getSound.invoke(null, specific); return getSoundEffect.invoke(null, key); } @Override public Sound getSpecific(Object generic) { // Getting the Sound is a bit more complicated... if (soundKey == null) { Class soundEffect = generic.getClass(); FuzzyReflection fuzzy = FuzzyReflection.fromClass(soundEffect, true); soundKey = Accessors.getFieldAccessor( fuzzy.getFieldByType("key", MinecraftReflection.getMinecraftKeyClass())); } MinecraftKey minecraftKey = MinecraftKey.fromHandle(soundKey.get(generic)); String key = minecraftKey.getKey(); // Use our index if it already exists if (soundIndex != null) { return soundIndex.get(key); } // If it doesn't, try to guess the enum name try { return Sound.valueOf(minecraftKey.getEnumFormat()); } catch (IllegalArgumentException ignored) { } // Worst case we index all the sounds and use it later soundIndex = new ConcurrentHashMap<>(); for (Sound sound : Sound.values()) { String index = (String) getSound.invoke(null, sound); soundIndex.put(index, sound); } return soundIndex.get(key); } }); } public static EquivalentConverter getParticleConverter() { return ignoreNull(handle(WrappedParticle::getHandle, WrappedParticle::fromHandle, WrappedParticle.class)); } public static EquivalentConverter getAdvancementConverter() { return ignoreNull(new EquivalentConverter() { @Override public Advancement getSpecific(Object generic) { try { return (Advancement) getCraftBukkitClass("advancement.CraftAdvancement") .getConstructor(getMinecraftClass("advancements.Advancement", "Advancement")) .newInstance(generic); } catch (ReflectiveOperationException ex) { throw new RuntimeException(ex); } } @Override public Object getGeneric(Advancement specific) { return BukkitUnwrapper.getInstance().unwrapItem(specific); } @Override public Class getSpecificType() { return Advancement.class; } }); } /** * Retrieve an equivalent unwrapper for the converter. * @param nativeType - the native NMS type the converter produces. * @param converter - the converter. * @return The equivalent unwrapper. */ public static Unwrapper asUnwrapper(final Class nativeType, final EquivalentConverter converter) { return wrappedObject -> { Class type = PacketConstructor.getClass(wrappedObject); // Ensure the type is correct before we test if (converter.getSpecificType().isAssignableFrom(type)) { if (wrappedObject instanceof Class) return nativeType; else return converter.getGeneric(wrappedObject); } return null; }; } /** * Retrieve every converter that is associated with a generic class. * @return Every converter with a unique generic class. */ @SuppressWarnings("rawtypes") public static Map, EquivalentConverter> getConvertersForGeneric() { if (genericConverters == null) { ImmutableMap.Builder, EquivalentConverter> builder = ImmutableMap.builder(); addConverter(builder, MinecraftReflection::getDataWatcherClass, BukkitConverters::getDataWatcherConverter); addConverter(builder, MinecraftReflection::getItemStackClass, BukkitConverters::getItemStackConverter); addConverter(builder, MinecraftReflection::getNBTBaseClass, BukkitConverters::getNbtConverter); addConverter(builder, MinecraftReflection::getNBTCompoundClass, BukkitConverters::getNbtConverter); addConverter(builder, MinecraftReflection::getDataWatcherItemClass, BukkitConverters::getWatchableObjectConverter); addConverter(builder, MinecraftReflection::getMobEffectClass, BukkitConverters::getPotionEffectConverter); addConverter(builder, MinecraftReflection::getNmsWorldClass, BukkitConverters::getWorldConverter); addConverter(builder, MinecraftReflection::getWorldTypeClass, BukkitConverters::getWorldTypeConverter); addConverter(builder, MinecraftReflection::getAttributeSnapshotClass, BukkitConverters::getWrappedAttributeConverter); addConverter(builder, MinecraftReflection::getBlockClass, BukkitConverters::getBlockConverter); addConverter(builder, MinecraftReflection::getGameProfileClass, BukkitConverters::getWrappedGameProfileConverter); addConverter(builder, MinecraftReflection::getServerPingClass, BukkitConverters::getWrappedServerPingConverter); addConverter(builder, MinecraftReflection::getStatisticClass, BukkitConverters::getWrappedStatisticConverter); addConverter(builder, MinecraftReflection::getIBlockDataClass, BukkitConverters::getWrappedBlockDataConverter); for (Entry, EquivalentConverter> entry : EnumWrappers.getFromNativeMap().entrySet()) { addConverter(builder, entry::getKey, entry::getValue); } genericConverters = builder.build(); } return genericConverters; } private static void addConverter(ImmutableMap.Builder, EquivalentConverter> builder, Supplier> getClass, Supplier getConverter) { try { Class clazz = getClass.get(); if (clazz != null) { EquivalentConverter converter = getConverter.get(); if (converter != null) { builder.put(clazz, converter); } } } catch (Exception ex) { ProtocolLogger.debug("Exception registering converter", ex); } } /** * Retrieve every NMS to/from Bukkit converter as unwrappers. * @return Every unwrapper. */ public static List getUnwrappers() { if (unwrappers == null) { ImmutableList.Builder builder = ImmutableList.builder(); for (Map.Entry, EquivalentConverter> entry : getConvertersForGeneric().entrySet()) { builder.add(asUnwrapper(entry.getKey(), entry.getValue())); } unwrappers = builder.build(); } return unwrappers; } private static MethodAccessor getMobEffectId = null; private static MethodAccessor getMobEffect = null; public static EquivalentConverter getEffectTypeConverter() { return ignoreNull(new EquivalentConverter() { @Override public Class getSpecificType() { return PotionEffectType.class; } @Override public Object getGeneric(PotionEffectType specific) { Class clazz = MinecraftReflection.getMobEffectListClass(); if (getMobEffect == null) { FuzzyReflection fuzzy = FuzzyReflection.fromClass(clazz, false); getMobEffect = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() .parameterExactArray(int.class) .returnTypeExact(clazz) .requireModifier(Modifier.STATIC) .build())); } int id = specific.getId(); return getMobEffect.invoke(null, id); } @Override public PotionEffectType getSpecific(Object generic) { Class clazz = MinecraftReflection.getMobEffectListClass(); if (getMobEffectId == null) { FuzzyReflection fuzzy = FuzzyReflection.fromClass(clazz, false); getMobEffectId = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() .parameterExactArray(clazz) .returnTypeExact(int.class) .requireModifier(Modifier.STATIC) .build())); } int id = (int) getMobEffectId.invoke(null, generic); return PotionEffectType.getById(id); } }); } private static Class dimensionManager; private static FauxEnumConverter dimensionConverter; private static FauxEnumConverter dimensionImplConverter; private static MethodAccessor dimensionFromId = null; private static MethodAccessor idFromDimension = null; private static FieldAccessor worldKeyField = null; private static MethodAccessor getServer = null; private static MethodAccessor getWorldServer = null; private static MethodAccessor getWorld = null; public static EquivalentConverter getWorldKeyConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(World specific) { Object nmsWorld = getWorldConverter().getGeneric(specific); if (worldKeyField == null) { Class worldClass = MinecraftReflection.getNmsWorldClass(); Class resourceKeyClass = MinecraftReflection.getResourceKey(); FuzzyReflection fuzzy = FuzzyReflection.fromClass(nmsWorld.getClass(), true); worldKeyField = Accessors.getFieldAccessor(fuzzy.getParameterizedField(resourceKeyClass, worldClass)); } return worldKeyField.get(nmsWorld); } @Override public World getSpecific(Object generic) { if (getServer == null) { getServer = Accessors.getMethodAccessor(Bukkit.getServer().getClass(), "getServer"); } Object server = getServer.invoke(Bukkit.getServer()); if (getWorldServer == null) { FuzzyReflection fuzzy = FuzzyReflection.fromClass(server.getClass(), false); getWorldServer = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract .newBuilder() .parameterExactArray(generic.getClass()) .returnTypeExact(MinecraftReflection.getWorldServerClass()) .build())); } Object worldServer = getWorldServer.invoke(server, generic); if (getWorld == null) { getWorld = Accessors.getMethodAccessor(worldServer.getClass(), "getWorld"); } return (World) getWorld.invoke(worldServer); } @Override public Class getSpecificType() { return World.class; } }); } enum DimensionImpl { OVERWORLD_IMPL(0), THE_NETHER_IMPL(-1), THE_END_IMPL(1); int id; DimensionImpl(int id) { this.id = id; } static DimensionImpl fromId(int id) { switch (id) { case 0: return OVERWORLD_IMPL; case -1: return THE_NETHER_IMPL; case 1: return THE_END_IMPL; default: throw new IllegalArgumentException("Invalid dimension " + id); } } } private static FieldAccessor dimensionKey; private static MethodAccessor worldHandleAccessor; private static MethodAccessor worldHandleDimensionManagerAccessor; public static EquivalentConverter getDimensionConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(World specific) { return getWorldHandleDimensionManagerAccessor().invoke(getWorldHandleAccessor().invoke(specific)); } @Override public World getSpecific(Object generic) { for (World world : Bukkit.getWorlds()) { if (getGeneric(world) == generic) { return world; } } throw new IllegalArgumentException(); } @Override public Class getSpecificType() { return World.class; } }); } private static MethodAccessor getWorldHandleAccessor() { if (worldHandleAccessor == null) { Method handleMethod = FuzzyReflection.fromClass(MinecraftReflection.getCraftWorldClass()) .getMethod(FuzzyMethodContract.newBuilder() .nameExact("getHandle") // i guess this will never change .returnTypeExact(MinecraftReflection.getWorldServerClass()) .build()); worldHandleAccessor = Accessors.getMethodAccessor(handleMethod); } return worldHandleAccessor; } private static MethodAccessor getWorldHandleDimensionManagerAccessor() { if (worldHandleDimensionManagerAccessor == null) { Method dimensionGetter = FuzzyReflection.fromClass(MinecraftReflection.getWorldServerClass()) .getMethod(FuzzyMethodContract.newBuilder() .returnTypeExact(MinecraftReflection.getDimensionManager()) .build()); worldHandleDimensionManagerAccessor = Accessors.getMethodAccessor(dimensionGetter); } return worldHandleDimensionManagerAccessor; } public static EquivalentConverter getDimensionIDConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(Integer specific) { if (dimensionManager == null) { dimensionManager = MinecraftReflection.getDimensionManager(); } if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { World world = null; if (specific == 0) { world = Bukkit.getWorlds().get(0); } else if (specific == -1) { for (World world1 : Bukkit.getWorlds()) { if (world1.getEnvironment() == World.Environment.NETHER) { world = world1; break; } } } else if (specific == 1) { for (World world1 : Bukkit.getWorlds()) { if (world1.getEnvironment() == World.Environment.THE_END) { world = world1; break; } } } if (world != null) { try { return getWorldHandleDimensionManagerAccessor().invoke(getWorldHandleAccessor().invoke(world)); } catch (Exception ignored) { // method not available, fall through } } throw new IllegalArgumentException(); } if (MinecraftVersion.NETHER_UPDATE_2.atOrAbove()) { if (dimensionImplConverter == null) { dimensionImplConverter = new FauxEnumConverter<>(DimensionImpl.class, dimensionManager); } DimensionImpl dimension = DimensionImpl.fromId(specific); return dimensionImplConverter.getGeneric(dimension); } else if (MinecraftVersion.NETHER_UPDATE.atOrAbove()) { if (dimensionConverter == null) { dimensionConverter = new FauxEnumConverter<>(Dimension.class, dimensionManager); } Dimension dimension = Dimension.fromId(specific); return dimensionConverter.getGeneric(dimension); } else { if (dimensionFromId == null) { FuzzyReflection reflection = FuzzyReflection.fromClass(dimensionManager, false); FuzzyMethodContract contract = FuzzyMethodContract .newBuilder() .requireModifier(Modifier.STATIC) .parameterExactType(int.class) .returnTypeExact(dimensionManager) .build(); dimensionFromId = Accessors.getMethodAccessor(reflection.getMethod(contract)); } return dimensionFromId.invoke(null, specific); } } @Override public Integer getSpecific(Object generic) { if (dimensionManager == null) { dimensionManager = MinecraftReflection.getDimensionManager(); } if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { if (dimensionKey == null) { FuzzyReflection fuzzy = FuzzyReflection.fromClass(dimensionManager, false); dimensionKey = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract .newBuilder() .typeExact(MinecraftReflection.getMinecraftKeyClass()) .banModifier(Modifier.STATIC) .build())); } MinecraftKey key = MinecraftKey.fromHandle(dimensionKey.get(generic)); switch (key.getKey()) { case "overworld": return Dimension.OVERWORLD.getId(); case "the_nether": return Dimension.THE_NETHER.getId(); case "the_end": return Dimension.THE_END.getId(); default: throw new IllegalArgumentException("id not supported for extra dimensions"); } } if (MinecraftVersion.NETHER_UPDATE_2.atOrAbove()) { if (dimensionImplConverter == null) { dimensionImplConverter = new FauxEnumConverter<>(DimensionImpl.class, dimensionManager); } DimensionImpl dimension = dimensionImplConverter.getSpecific(generic); return dimension.id; } else if (MinecraftVersion.NETHER_UPDATE.atOrAbove()) { if (dimensionConverter == null) { dimensionConverter = new FauxEnumConverter<>(Dimension.class, dimensionManager); } Dimension dimension = dimensionConverter.getSpecific(generic); return dimension.getId(); } else { if (idFromDimension == null) { FuzzyReflection reflection = FuzzyReflection.fromClass(dimensionManager, false); FuzzyMethodContract contract = FuzzyMethodContract .newBuilder() .banModifier(Modifier.STATIC) .returnTypeExact(int.class) .parameterCount(0) .build(); idFromDimension = Accessors.getMethodAccessor(reflection.getMethod(contract)); } return (Integer) idFromDimension.invoke(generic); } } @Override public Class getSpecificType() { return Integer.class; } }); } private static ConstructorAccessor merchantRecipeListConstructor = null; private static MethodAccessor bukkitMerchantRecipeToCraft = null; private static MethodAccessor craftMerchantRecipeToNMS = null; private static MethodAccessor nmsMerchantRecipeToBukkit = null; /** * Creates a converter from a MerchantRecipeList (which is just an ArrayList of MerchantRecipe wrapper) * to a {@link List} of {@link MerchantRecipe}. Primarily for the packet OPEN_WINDOW_MERCHANT which is present * in 1.13+. * * @return The MerchantRecipeList converter. */ public static EquivalentConverter> getMerchantRecipeListConverter() { return ignoreNull(new EquivalentConverter>() { @Override public Object getGeneric(List specific) { if (merchantRecipeListConstructor == null) { Class merchantRecipeListClass = MinecraftReflection.getMerchantRecipeList(); merchantRecipeListConstructor = Accessors.getConstructorAccessor(merchantRecipeListClass); Class craftMerchantRecipeClass = MinecraftReflection.getCraftBukkitClass("inventory.CraftMerchantRecipe"); FuzzyReflection reflection = FuzzyReflection.fromClass(craftMerchantRecipeClass, false); bukkitMerchantRecipeToCraft = Accessors.getMethodAccessor(reflection.getMethodByName("fromBukkit")); craftMerchantRecipeToNMS = Accessors.getMethodAccessor(reflection.getMethodByName("toMinecraft")); } return specific.stream().map(recipe -> craftMerchantRecipeToNMS.invoke(bukkitMerchantRecipeToCraft.invoke(null, recipe))) .collect(() -> (List)merchantRecipeListConstructor.invoke(), List::add, List::addAll); } @Override public List getSpecific(Object generic) { if (nmsMerchantRecipeToBukkit == null) { Class merchantRecipeClass = MinecraftReflection.getMinecraftClass( "world.item.trading.MerchantRecipe", "world.item.trading.MerchantOffer","MerchantRecipe" ); FuzzyReflection reflection = FuzzyReflection.fromClass(merchantRecipeClass, false); nmsMerchantRecipeToBukkit = Accessors.getMethodAccessor(reflection.getMethodByName("asBukkit")); } return ((List)generic).stream().map(o -> (MerchantRecipe)nmsMerchantRecipeToBukkit.invoke(o)).collect(Collectors.toList()); } @Override public Class> getSpecificType() { // Damn you Java Class dummy = List.class; return (Class>) dummy; } }); } private static MethodAccessor sectionPositionCreate; private static Class sectionPositionClass; public static EquivalentConverter getSectionPositionConverter() { return ignoreNull(new EquivalentConverter() { @Override public Object getGeneric(BlockPosition specific) { if (sectionPositionClass == null) { sectionPositionClass = MinecraftReflection.getSectionPosition(); } if (sectionPositionCreate == null) { sectionPositionCreate = Accessors.getMethodAccessor( FuzzyReflection.fromClass(sectionPositionClass).getMethod(FuzzyMethodContract .newBuilder() .requireModifier(Modifier.STATIC) .returnTypeExact(sectionPositionClass) .parameterExactArray(int.class, int.class, int.class) .build()) ); } return sectionPositionCreate.invoke(null, specific.x, specific.y, specific.z); } @Override public BlockPosition getSpecific(Object generic) { StructureModifier modifier = new StructureModifier<>(generic.getClass()).withTarget(generic).withType(int.class); return new BlockPosition(modifier.readSafely(0), modifier.readSafely(1), modifier.readSafely(2)); } @Override public Class getSpecificType() { return BlockPosition.class; } }); } private static Field gameStateMapField; private static Field gameStateIdField; public static EquivalentConverter getGameStateConverter() { return new EquivalentConverter() { @Override public Object getGeneric(Integer specific) { if (specific == null) { specific = 0; } if (MinecraftVersion.NETHER_UPDATE.atOrAbove()) { if (gameStateMapField == null) { Class stateClass = MinecraftReflection.getGameStateClass(); gameStateMapField = FuzzyReflection .fromClass(stateClass, true) .getField(FuzzyFieldContract .newBuilder() .typeDerivedOf(Map.class) .build()); gameStateMapField.setAccessible(true); } try { Map map = (Map) gameStateMapField.get(null); return map.get(specific); } catch (ReflectiveOperationException ex) { throw new RuntimeException(ex); } } else { return specific; } } @Override public Integer getSpecific(Object generic) { if (generic == null) { return 0; } if (MinecraftVersion.NETHER_UPDATE.atOrAbove()) { if (gameStateIdField == null) { Class stateClass = MinecraftReflection.getGameStateClass(); gameStateIdField = FuzzyReflection .fromClass(stateClass, true) .getField(FuzzyFieldContract .newBuilder() .typeExact(int.class) .build()); gameStateIdField.setAccessible(true); } try { return (Integer) gameStateIdField.get(generic); } catch (ReflectiveOperationException ex) { throw new RuntimeException(ex); } } else { return (Integer) generic; } } @Override public Class getSpecificType() { return Integer.class; } }; } }