ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java

1801 lines
59 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.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<Class<?>, EquivalentConverter<Object>> genericConverters;
private static List<Unwrapper> 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<Object> 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 <TType> - type that can be converted.
* @deprecated Replaced by {@link Converters#ignoreNull(EquivalentConverter)}
*/
@Deprecated
public static abstract class IgnoreNullConverter<TType> implements EquivalentConverter<TType> {
@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 <TType> - instance types it converts.
*/
private static abstract class WorldSpecificConverter<TType> implements EquivalentConverter<TType> {
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 <K, V> EquivalentConverter<Map<K, V>> getMapConverter(EquivalentConverter<K> keyConverter,
EquivalentConverter<V> valConverter) {
return new EquivalentConverter<Map<K, V>>() {
@Override
public Map<K, V> getSpecific(Object generic) {
Map<Object, Object> genericMap = (Map<Object, Object>) generic;
Map<K, V> newMap;
try {
newMap = (Map<K, V>) genericMap.getClass().newInstance();
} catch (ReflectiveOperationException ex) {
newMap = new HashMap<>();
}
for (Map.Entry<Object, Object> entry : genericMap.entrySet()) {
newMap.put(keyConverter.getSpecific(entry.getKey()), valConverter.getSpecific(entry.getValue()));
}
return newMap;
}
@Override
public Object getGeneric(Map<K, V> specific) {
Map<Object, Object> newMap;
try {
newMap = specific.getClass().newInstance();
} catch (ReflectiveOperationException ex) {
newMap = new HashMap<>();
}
for (Map.Entry<K, V> entry : specific.entrySet()) {
newMap.put(keyConverter.getGeneric(entry.getKey()), valConverter.getGeneric(entry.getValue()));
}
return newMap;
}
@Override
public Class<Map<K, V>> getSpecificType() {
return null;
}
};
}
private static final Map<Class<?>, Supplier<List<Object>>> LIST_SUPPLIERS = new ConcurrentHashMap<>();
private static <T> Object getGenericList(Class<?> listClass, List<T> specific, EquivalentConverter<T> itemConverter) {
List<Object> newList;
Supplier<List<Object>> supplier = LIST_SUPPLIERS.get(listClass);
if (supplier == null) {
try {
Constructor<?> ctor = listClass.getConstructor();
newList = (List<Object>) ctor.newInstance();
supplier = () -> {
try {
return (List<Object>) 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 <T> List<T> getSpecificList(Object generic, EquivalentConverter<T> itemConverter) {
if (generic instanceof Collection) {
List<T> items = new ArrayList<>();
// Copy everything to a new list
for (Object item : (Collection<Object>) generic) {
T result = itemConverter.getSpecific(item);
if (item != null)
items.add(result);
}
return items;
}
// Not valid
return null;
}
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> listClass, final EquivalentConverter<T> itemConverter) {
return ignoreNull(new EquivalentConverter<List<T>>() {
@Override
public List<T> getSpecific(Object generic) {
return getSpecificList(generic, itemConverter);
}
@Override
public Object getGeneric(List<T> specific) {
return getGenericList(listClass, specific, itemConverter);
}
@Override
public Class<List<T>> getSpecificType() {
// Damn you Java
Class<?> dummy = List.class;
return (Class<List<T>>) dummy;
}
});
}
/**
* Retrieve an equivalent converter for a list of generic items.
* @param <T> Type
* @param itemConverter - an equivalent converter for the generic type.
* @return An equivalent converter.
*/
public static <T> EquivalentConverter<List<T>> getListConverter(final EquivalentConverter<T> itemConverter) {
// Convert to and from the wrapper
return ignoreNull(new EquivalentConverter<List<T>>() {
@Override
public List<T> getSpecific(Object generic) {
return getSpecificList(generic, itemConverter);
}
@Override
public Object getGeneric(List<T> specific) {
return getGenericList(specific.getClass(), specific, itemConverter);
}
@Override
public Class<List<T>> getSpecificType() {
// Damn you Java
Class<?> dummy = List.class;
return (Class<List<T>>) dummy;
}
});
}
@SuppressWarnings("rawtypes")
public static <A, B> EquivalentConverter<Pair<A, B>> getPairConverter(final EquivalentConverter<A> firstConverter,
final EquivalentConverter<B> secondConverter) {
return ignoreNull(new EquivalentConverter<Pair<A, B>>() {
@Override
public Object getGeneric(Pair<A, B> 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<A, B> 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<Pair<A, B>> getSpecificType() {
Class<?> dummy = Pair.class;
return (Class<Pair<A, B>>) dummy;
}
});
}
/**
* @param leftConverter convert the left value if available
* @param rightConverter convert the right value if available
* @return converter for Mojang either class
* @param <A> converted left type
* @param <B> converted right type
*/
public static <A, B> EquivalentConverter<Either<A, B>> getEitherConverter(EquivalentConverter<A> leftConverter,
EquivalentConverter<B> rightConverter) {
return ignoreNull(new EquivalentConverter<Either<A, B>>() {
@Override
public Object getGeneric(Either<A, B> 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<A, B> getSpecific(Object generic) {
com.mojang.datafixers.util.Either<A, B> mjEither = (com.mojang.datafixers.util.Either<A, B>) generic;
return mjEither.map(
left -> new Left<>(leftConverter.getSpecific(left)),
right -> new Right<>(rightConverter.getSpecific(right))
);
}
@Override
public Class<Either<A, B>> getSpecificType() {
Class<?> dummy = Either.class;
return (Class<Either<A, B>>) dummy;
}
});
}
/**
* Retrieve an equivalent converter for a set of generic items.
* @param <T> Element type
* @param itemConverter - an equivalent converter for the generic type.
* @return An equivalent converter.
*/
@SuppressWarnings("unchecked")
public static <T> EquivalentConverter<Set<T>> getSetConverter(final EquivalentConverter<T> itemConverter) {
// Convert to and from the wrapper
return ignoreNull(new EquivalentConverter<Set<T>>() {
@Override
public Set<T> getSpecific(Object generic) {
if (generic instanceof Collection) {
Set<T> items = new HashSet<>();
// Copy everything to a new list
for (Object item : (Collection<Object>) generic) {
T result = itemConverter.getSpecific(item);
if (item != null)
items.add(result);
}
return items;
}
// Not valid
return null;
}
@Override
public Object getGeneric(Set<T> specific) {
Set<Object> newList;
try {
newList = (Set<Object>) 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<Set<T>> getSpecificType() {
// Damn you Java
Class<?> dummy = Set.class;
return (Class<Set<T>>) dummy;
}
});
}
/**
* Retrieve an equivalent converter for an array of generic items.
* @param <T> Type
* <p>
* 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 <T> EquivalentConverter<Iterable<? extends T>> getArrayConverter(final Class<?> genericItemType,
final EquivalentConverter<T> itemConverter) {
// Convert to and from the wrapper
return ignoreNull(new EquivalentConverter<Iterable<? extends T>>() {
@Override
public List<T> getSpecific(Object generic) {
if (generic instanceof Object[]) {
ImmutableList.Builder<T> 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<? extends T> specific) {
List<T> 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<Iterable<? extends T>> getSpecificType() {
// Damn you Java
Class<?> dummy = Iterable.class;
return (Class<Iterable<? extends T>>) dummy;
}
});
}
/**
* Retrieve a converter for wrapped game profiles.
* @return Wrapped game profile converter.
*/
public static EquivalentConverter<WrappedGameProfile> getWrappedGameProfileConverter() {
return ignoreNull(handle(WrappedGameProfile::getHandle, WrappedGameProfile::fromHandle, WrappedGameProfile.class));
}
/**
* Retrieve a converter for wrapped chat components.
* @return Wrapped chat component.
*/
public static EquivalentConverter<WrappedChatComponent> getWrappedChatComponentConverter() {
return ignoreNull(handle(WrappedChatComponent::getHandle, WrappedChatComponent::fromHandle, WrappedChatComponent.class));
}
/**
* Retrieve a converter for wrapped block data.
* @return Wrapped block data.
*/
public static EquivalentConverter<WrappedBlockData> 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<WrappedAttribute> getWrappedAttributeConverter() {
return ignoreNull(handle(WrappedAttribute::getHandle, WrappedAttribute::fromHandle, WrappedAttribute.class));
}
public static EquivalentConverter<WrappedProfilePublicKey> getWrappedProfilePublicKeyConverter() {
return ignoreNull(handle(WrappedProfilePublicKey::getHandle, WrappedProfilePublicKey::new, WrappedProfilePublicKey.class));
}
public static EquivalentConverter<WrappedProfileKeyData> getWrappedPublicKeyDataConverter() {
return ignoreNull(handle(WrappedProfileKeyData::getHandle, WrappedProfileKeyData::new, WrappedProfileKeyData.class));
}
public static EquivalentConverter<WrappedRemoteChatSessionData> 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<WrappedSaltedSignature> getWrappedSignatureConverter() {
return ignoreNull(handle(WrappedSaltedSignature::getHandle, WrappedSaltedSignature::new, WrappedSaltedSignature.class));
}
/**
* @return converter for an encoded cryptographic message signature
*/
public static EquivalentConverter<WrappedMessageSignature> getWrappedMessageSignatureConverter() {
return ignoreNull(handle(WrappedMessageSignature::getHandle, WrappedMessageSignature::new, WrappedMessageSignature.class));
}
public static EquivalentConverter<WrappedLevelChunkData.ChunkData> getWrappedChunkDataConverter() {
return ignoreNull(handle(WrappedLevelChunkData.ChunkData::getHandle, WrappedLevelChunkData.ChunkData::new, WrappedLevelChunkData.ChunkData.class));
}
public static EquivalentConverter<WrappedLevelChunkData.LightData> getWrappedLightDataConverter() {
return ignoreNull(handle(WrappedLevelChunkData.LightData::getHandle, WrappedLevelChunkData.LightData::new, WrappedLevelChunkData.LightData.class));
}
public static EquivalentConverter<PacketContainer> 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<WrappedWatchableObject> getWatchableObjectConverter() {
return ignoreNull(new EquivalentConverter<WrappedWatchableObject>() {
@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<WrappedWatchableObject> getSpecificType() {
return WrappedWatchableObject.class;
}
});
}
/**
* Retrieve a converter for data values in 1.19.3+.
* @return A data value converter.
*/
public static EquivalentConverter<WrappedDataValue> getDataValueConverter() {
return ignoreNull(new EquivalentConverter<WrappedDataValue>() {
@Override
public Object getGeneric(WrappedDataValue specific) {
return specific.getHandle();
}
@Override
public WrappedDataValue getSpecific(Object generic) {
return new WrappedDataValue(generic);
}
@Override
public Class<WrappedDataValue> getSpecificType() {
return WrappedDataValue.class;
}
});
}
/**
* Retrieve a converter for the NMS DataWatcher class and our wrapper.
* @return A DataWatcher converter.
*/
public static EquivalentConverter<WrappedDataWatcher> getDataWatcherConverter() {
return ignoreNull(new EquivalentConverter<WrappedDataWatcher>() {
@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<WrappedDataWatcher> 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<WorldType> getWorldTypeConverter() {
// Check that we can actually use this converter
if (!hasWorldType)
return null;
final Class<?> worldType = MinecraftReflection.getWorldTypeClass();
return ignoreNull(new EquivalentConverter<WorldType>() {
@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<WorldType> 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<NbtBase<?>> getNbtConverter() {
return ignoreNull(new EquivalentConverter<NbtBase<?>>() {
@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<NbtBase<?>> getSpecificType() {
// Damn you Java AGAIN
Class<?> dummy = NbtBase.class;
return (Class<NbtBase<?>>) 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<Entity> getEntityConverter(World world) {
final WeakReference<ProtocolManager> managerRef = new WeakReference<>(ProtocolLibrary.getProtocolManager());
return new WorldSpecificConverter<Entity>(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<Entity> getSpecificType() {
return Entity.class;
}
};
}
private static MethodAccessor getEntityTypeName;
private static MethodAccessor entityTypeFromName;
public static EquivalentConverter<EntityType> getEntityTypeConverter() {
return ignoreNull(new EquivalentConverter<EntityType>() {
@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<EntityType> getSpecificType() {
return EntityType.class;
}
});
}
/**
* Retrieve the converter used to convert NMS ItemStacks to Bukkit's ItemStack.
* @return Item stack converter.
*/
public static EquivalentConverter<ItemStack> getItemStackConverter() {
return new EquivalentConverter<ItemStack>() {
@Override
public ItemStack getSpecific(Object generic) {
return MinecraftReflection.getBukkitItemStack(generic);
}
@Override
public Object getGeneric(ItemStack specific) {
return MinecraftReflection.getMinecraftItemStack(specific);
}
@Override
public Class<ItemStack> 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<WrappedServerPing> getWrappedServerPingConverter() {
return ignoreNull(handle(WrappedServerPing::getHandle, WrappedServerPing::fromHandle, WrappedServerPing.class));
}
/**
* Retrieve the converter for a statistic.
* @return Statistic converter.
*/
public static EquivalentConverter<WrappedStatistic> 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<Material> 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<Material>() {
@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<Material> 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<World> getWorldConverter() {
return ignoreNull(new EquivalentConverter<World>() {
@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<World> 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<PotionEffect> getPotionEffectConverter() {
return ignoreNull(new EquivalentConverter<PotionEffect>() {
@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<Integer> ints = mobEffectModifier.withTarget(generic).withType(int.class);
StructureModifier<Boolean> 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<PotionEffect> getSpecificType() {
return PotionEffect.class;
}
});
}
private static Constructor<?> vec3dConstructor;
private static StructureModifier<Object> vec3dModifier;
/**
* Retrieve the converter used to convert between a Vector and the equivalent NMS Vec3d.
* @return The Vector converter.
*/
public static EquivalentConverter<Vector> getVectorConverter() {
return ignoreNull(new EquivalentConverter<Vector>() {
@Override
public Class<Vector> 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<Double> 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<String, Sound> soundIndex = null;
public static EquivalentConverter<Sound> 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<Sound>() {
@Override
public Class<Sound> 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<Sound>() {
@Override
public Class<Sound> 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<WrappedParticle> getParticleConverter() {
return ignoreNull(handle(WrappedParticle::getHandle, WrappedParticle::fromHandle, WrappedParticle.class));
}
public static EquivalentConverter<Advancement> getAdvancementConverter() {
return ignoreNull(new EquivalentConverter<Advancement>() {
@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<Advancement> 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<Object> 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<Class<?>, EquivalentConverter<Object>> getConvertersForGeneric() {
if (genericConverters == null) {
ImmutableMap.Builder<Class<?>, EquivalentConverter<Object>> 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<Class<?>, EquivalentConverter<?>> entry : EnumWrappers.getFromNativeMap().entrySet()) {
addConverter(builder, entry::getKey, entry::getValue);
}
genericConverters = builder.build();
}
return genericConverters;
}
private static void addConverter(ImmutableMap.Builder<Class<?>, EquivalentConverter<Object>> builder,
Supplier<Class<?>> getClass, Supplier<EquivalentConverter> 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<Unwrapper> getUnwrappers() {
if (unwrappers == null) {
ImmutableList.Builder<Unwrapper> builder = ImmutableList.builder();
for (Map.Entry<Class<?>, EquivalentConverter<Object>> 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<PotionEffectType> getEffectTypeConverter() {
return ignoreNull(new EquivalentConverter<PotionEffectType>() {
@Override
public Class<PotionEffectType> 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<Dimension> dimensionConverter;
private static FauxEnumConverter<DimensionImpl> 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<World> getWorldKeyConverter() {
return ignoreNull(new EquivalentConverter<World>() {
@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<World> 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<World> getDimensionConverter() {
return ignoreNull(new EquivalentConverter<World>() {
@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<World> 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<Integer> getDimensionIDConverter() {
return ignoreNull(new EquivalentConverter<Integer>() {
@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<Integer> 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<List<MerchantRecipe>> getMerchantRecipeListConverter() {
return ignoreNull(new EquivalentConverter<List<MerchantRecipe>>() {
@Override
public Object getGeneric(List<MerchantRecipe> 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<Object>)merchantRecipeListConstructor.invoke(), List::add, List::addAll);
}
@Override
public List<MerchantRecipe> 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<Object>)generic).stream().map(o -> (MerchantRecipe)nmsMerchantRecipeToBukkit.invoke(o)).collect(Collectors.toList());
}
@Override
public Class<List<MerchantRecipe>> getSpecificType() {
// Damn you Java
Class<?> dummy = List.class;
return (Class<List<MerchantRecipe>>) dummy;
}
});
}
private static MethodAccessor sectionPositionCreate;
private static Class<?> sectionPositionClass;
public static EquivalentConverter<BlockPosition> getSectionPositionConverter() {
return ignoreNull(new EquivalentConverter<BlockPosition>() {
@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<Integer> 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<BlockPosition> getSpecificType() {
return BlockPosition.class;
}
});
}
private static Field gameStateMapField;
private static Field gameStateIdField;
public static EquivalentConverter<Integer> getGameStateConverter() {
return new EquivalentConverter<Integer>() {
@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<Integer, Object> map = (Map<Integer, Object>) 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<Integer> getSpecificType() {
return Integer.class;
}
};
}
}