Add wrapper for villager data

Fixes #637
This commit is contained in:
Dan Mulloy 2019-08-03 12:35:58 -04:00
parent 916434251d
commit dd9eac3d6d
5 changed files with 190 additions and 8 deletions

View File

@ -82,6 +82,7 @@ public class BukkitCloner implements Cloner {
fromWrapper(MinecraftReflection::getIChatBaseComponentClass, WrappedChatComponent::fromHandle);
fromManual(ComponentConverter::getBaseComponentArrayClass, source ->
ComponentConverter.clone((BaseComponent[]) source));
fromWrapper(WrappedVillagerData::getNmsClass, WrappedVillagerData::fromHandle);
}
private Function<Object, Object> findCloner(Class<?> type) {

View File

@ -60,19 +60,24 @@ public class ImmutableDetector implements Cloner {
static {
add(MinecraftReflection::getGameProfileClass);
add(MinecraftReflection::getDataWatcherSerializerClass);
add(() -> MinecraftReflection.getMinecraftClass("SoundEffect"));
add(MinecraftReflection::getBlockClass);
add(MinecraftReflection::getItemClass);
add("SoundEffect");
if (MinecraftVersion.atOrAbove(MinecraftVersion.AQUATIC_UPDATE)) {
add(() -> MinecraftReflection.getMinecraftClass("Particle"));
add(MinecraftReflection::getFluidTypeClass);
add(MinecraftReflection::getParticleTypeClass);
add("Particle");
}
if (MinecraftVersion.atOrAbove(MinecraftVersion.VILLAGE_UPDATE)) {
add(() -> MinecraftReflection.getMinecraftClass("EntityTypes"));
add("EntityTypes");
add("VillagerType");
add("VillagerProfession");
}
// TODO automatically detect the technically-not-an-enum enums that Mojang is so fond of
// Would also probably go in tandem with having the FieldCloner use this
}
private static void add(Supplier<Class<?>> getClass) {
@ -83,6 +88,15 @@ public class ImmutableDetector implements Cloner {
}
} catch (RuntimeException ignored) { }
}
private static void add(String className) {
try {
Class<?> clazz = MinecraftReflection.getMinecraftClass(className);
if (clazz != null) {
immutableNMS.add(clazz);
}
} catch (RuntimeException ignored) { }
}
@Override
public boolean canClone(Object source) {

View File

@ -1,19 +1,22 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.Maps;
import org.apache.commons.lang.Validate;
import org.bukkit.GameMode;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* Represents a generic enum converter.
* @author Kristian
@ -693,4 +696,98 @@ public abstract class EnumWrappers {
this.genericType = genericType;
}
}
/**
* Used for classes where it's an enum in everything but name
* @param <T> Generic type
*/
public static class FauxEnumConverter<T extends Enum<T>> implements EquivalentConverter<T> {
private final Class<T> specificClass;
private final Class<?> genericClass;
private final Map<Object, T> lookup;
public FauxEnumConverter(Class<T> specific, Class<?> generic) {
Validate.notNull(specific,"specific class cannot be null");
Validate.notNull(generic,"generic class cannot be null");
this.specificClass = specific;
this.genericClass = generic;
this.lookup = new HashMap<>();
}
@Override
public Object getGeneric(T specific) {
Validate.notNull(specific, "specific object cannot be null");
return Accessors
.getFieldAccessor(genericClass, specific
.name(), false)
.get(null);
}
@Override
public T getSpecific(Object generic) {
Validate.notNull(generic, "generic object cannot be null");
return lookup.computeIfAbsent(generic, x -> {
for (Field field : genericClass.getFields()) {
try {
if (!field.isAccessible()) {
field.setAccessible(true);
}
if (field.get(null) == generic) {
return Enum.valueOf(specificClass, field.getName().toUpperCase());
}
} catch (ReflectiveOperationException ignored) { }
}
throw new IllegalArgumentException("Could not find ProtocolLib wrapper for " + generic);
});
}
@Override
public Class<T> getSpecificType() {
return specificClass;
}
}
public static class IndexedEnumConverter<T extends Enum<T>> implements EquivalentConverter<T> {
private Class<T> specificClass;
private Class<?> genericClass;
public IndexedEnumConverter(Class<T> specificClass, Class<?> genericClass) {
this.specificClass = specificClass;
this.genericClass = genericClass;
}
@Override
public Object getGeneric(T specific) {
int ordinal = specific.ordinal();
for (Object elem : genericClass.getEnumConstants()) {
if (((Enum<?>) elem).ordinal() == ordinal) {
return elem;
}
}
return null;
}
@Override
public T getSpecific(Object generic) {
int ordinal = ((Enum<?>) generic).ordinal();
for (T elem : specificClass.getEnumConstants()) {
if (elem.ordinal() == ordinal) {
return elem;
}
}
return null;
}
@Override
public Class<T> getSpecificType() {
return specificClass;
}
}
}

View File

@ -0,0 +1,68 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.utility.MinecraftReflection;
public class WrappedVillagerData extends AbstractWrapper implements ClonableWrapper {
private static final Class<?> NMS_CLASS = MinecraftReflection.getMinecraftClass("VillagerData");
private static final Class<?> TYPE_CLASS = MinecraftReflection.getMinecraftClass("VillagerType");
private static final Class<?> PROF_CLASS = MinecraftReflection.getMinecraftClass("VillagerProfession");
private static final EquivalentConverter<Type> TYPE_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Type.class, TYPE_CLASS);
private static final EquivalentConverter<Profession> PROF_CONVERTER = new EnumWrappers.FauxEnumConverter<>(Profession.class, PROF_CLASS);
public enum Type {
DESERT, JUNGLE, PLAINS, SAVANNA, SNOW, SWAMP, TAIGA;
}
public enum Profession {
NONE, ARMORER, BUTCHER, CARTOGRAPHER, CLERIC, FARMER, FISHERMAN,
FLETCHER, LEATHERWORKER, LIBRARIAN, MASON, NITWIT, SHEPHERD,
TOOLSMITH, WEAPONSMITH;
}
private StructureModifier<Object> modifier;
private WrappedVillagerData(Object handle) {
super(NMS_CLASS);
setHandle(handle);
modifier = new StructureModifier<>(NMS_CLASS).withTarget(handle);
}
public static WrappedVillagerData fromHandle(Object handle) {
return new WrappedVillagerData(handle);
}
public static WrappedVillagerData fromValues(Type type, Profession profession, int level) {
Object genericType = TYPE_CONVERTER.getGeneric(type);
Object genericProf = PROF_CONVERTER.getGeneric(profession);
Object handle = Accessors.getConstructorAccessor(NMS_CLASS, TYPE_CLASS, PROF_CLASS, int.class)
.invoke(genericType, genericProf, level);
return fromHandle(handle);
}
public static Class<?> getNmsClass() {
return NMS_CLASS;
}
public int getLevel() {
return modifier.<Integer>withType(int.class).read(0);
}
public Type getType() {
return modifier.withType(TYPE_CLASS, TYPE_CONVERTER).read(0);
}
public Profession getProfession() {
return modifier.withType(PROF_CLASS, PROF_CONVERTER).read(0);
}
@Override
public WrappedVillagerData deepClone() {
return WrappedVillagerData.fromValues(getType(), getProfession(), getLevel());
}
}

View File

@ -535,7 +535,9 @@ public class PacketContainerTest {
"String"),
new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.get(Float.class)), 1.0F),
new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.getChatComponentSerializer(true)),
com.google.common.base.Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle()))
com.google.common.base.Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle())),
new WrappedWatchableObject(new WrappedDataWatcherObject(0, Registry.get(VillagerData.class)),
new VillagerData(VillagerType.SNOW, VillagerProfession.ARMORER, 69))
));
} else if (type == PacketType.Play.Server.CHAT) {
constructed.getChatComponents().write(0, ComponentConverter.fromBaseComponent(TEST_COMPONENT));