Implement compatibility layer for fake entities

This commit is contained in:
Gabriel Tofvesson 2021-06-25 05:22:52 +02:00
parent 3b25897490
commit d912715741
2 changed files with 354 additions and 0 deletions

View File

@ -0,0 +1,153 @@
package dev.w1zzrd.invtweaks.entity;
import org.bukkit.entity.Player;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import static dev.w1zzrd.invtweaks.entity.Reflect.*;
public final class EntityCreator {
private EntityCreator() { throw new UnsupportedOperationException("Functional class"); }
private static Package getNativeMonsterPackage(final Player from) {
// Given player wll be an instance of CraftPlayer
final Package bukkitEntityPackage = from.getClass().getPackage();
final Class<?> craftShulker = loadClass(bukkitEntityPackage, "CraftShulker");
assert craftShulker != null;
// CraftShulker constructor accepts minecraft EntityShulker instance as second argument
final Class<?> nativeEntityShulker = craftShulker.getDeclaredConstructors()[0].getParameterTypes()[1];
// EntityShulker is classified squarely as a monster, so it should be grouped with all other hostiles
return nativeEntityShulker.getPackage();
}
private static Package getNativePacketPackage(final Player from) {
final Method sendPacket = findDeclaredMethod(
reflectGetField(reflectGetField(from, "entity"), "playerConnection", "networkManager").getClass(),
new String[]{ "sendPacket" },
new Object[]{ null }
);
return sendPacket.getParameterTypes()[0].getPackage();
}
private static Object getMinecraftServerFromWorld(final Object worldServer) {
return reflectGetField(worldServer, "server", "D");
}
private static Object getWorldServerFromPlayer(final Player from) {
return reflectGetField(from.getWorld(), "world");
}
private static Object getMonsterEntityType(final Class<?> entityClass) {
final Class<?> type_EntityTypes = entityClass.getDeclaredConstructors()[0].getParameterTypes()[0];
return reflectGetGenericStaticField(type_EntityTypes, type_EntityTypes, entityClass);
}
private static Object createFakeMonster(final Player target, final String entityClassName) {
final Package versionPackage = getNativeMonsterPackage(target);
final Class<?> type_Entity = loadClass(versionPackage, entityClassName);
final Object nativeWorld = getWorldServerFromPlayer(target);
assert type_Entity != null;
final Object entityType = getMonsterEntityType(type_Entity);
return reflectConstruct(type_Entity, entityType, nativeWorld);
}
public static Object createFakeSlime(final Player target) {
return createFakeMonster(target, "EntitySlime");
}
public static Object createFakeShulker(final Player target) {
return createFakeMonster(target, "EntityShulker");
}
public static void sendPacket(final Player target, final Object packet) {
reflectInvoke(reflectGetField(reflectGetField(target, "entity"), "playerConnection", "networkManager"), new String[]{ "sendPacket" }, packet);
}
public static void sendEntitySpawnPacket(final Player target, final Object entity) {
final Package versionPackage = getNativePacketPackage(target);
sendPacket(target, reflectConstruct(loadClass(versionPackage, "PacketPlayOutSpawnEntityLiving", "game.PacketPlayOutSpawnEntityLiving"), entity));
}
public static void sendEntityMetadataPacket(final Player target, final Object entity) {
final Package versionPackage = getNativePacketPackage(target);
Object constr1;
try {
constr1 = reflectConstruct(
loadClass(versionPackage, "PacketPlayOutEntityMetadata", "game.PacketPlayOutEntityMetadata"),
getEntityID(entity),
reflectGetField(entity, "dataWatcher", "Y")
);
} catch (Throwable t) {
constr1 = reflectConstruct(
loadClass(versionPackage, "PacketPlayOutEntityMetadata", "game.PacketPlayOutEntityMetadata"),
getEntityID(entity),
reflectGetField(entity, "dataWatcher", "Y"),
true
);
}
sendPacket(
target,
constr1
);
}
public static void sendEntityDespawnPacket(final Player target, final int entityID) {
final Package versionPackage = getNativePacketPackage(target);
sendPacket(target, reflectConstruct(loadClass(versionPackage, "PacketPlayOutEntityDestroy", "game.PacketPlayOutEntityDestroy"), entityID));
}
public static int getEntityID(final Object entity) {
return (Integer)reflectInvoke(entity, new String[]{ "getId" });
}
public static void setEntityInvisible(final Object entity, final boolean invisible) {
reflectInvoke(entity, new String[]{ "setInvisible" }, invisible);
}
public static void setEntityInvulnerable(final Object entity, final boolean invulnerable) {
reflectInvoke(entity, new String[]{ "setInvulnerable" }, invulnerable);
}
public static void setEntityGlowing(final Object entity, final boolean isGlowing) {
reflectInvoke(entity, new String[]{ "setGlowingTag", "i" }, isGlowing);
}
public static void setEntityLocation(final Object entity, final double x, final double y, final double z, final float yaw, final float pitch) {
reflectInvoke(entity, new String[]{ "setLocation" }, x, y, z, yaw, pitch);
}
public static void setEntityCollision(final Object entity, final boolean collision) {
reflectSetField(entity, boolean.class, collision, "collides");
}
public static void setEntityCustomName(final Object entity, final String name) {
final Package versionPackage = entity.getClass().getPackage();
final Method setCustomName = findDeclaredMethod(entity.getClass(), new String[]{ "setCustomName" }, new Object[]{ null });
final Package chatPackage = setCustomName.getParameterTypes()[0].getPackage();
setEntityCustomName(entity, reflectConstruct(loadClass(chatPackage, "ChatComponentText"), name));
}
public static void setEntityCustomName(final Object entity, final Object chatBaseComponent) {
reflectInvoke(entity, new String[]{ "setCustomName" }, chatBaseComponent);
}
public static void setEntityCustomNameVisible(final Object entity, final boolean visible) {
reflectInvoke(entity, new String[] { "setCustomNameVisible" }, visible);
}
public static void setSlimeSize(final Object slime, final int size) {
reflectInvoke(slime, new String[]{ "setSize" }, size, true);
}
}

View File

@ -0,0 +1,201 @@
package dev.w1zzrd.invtweaks.entity;
import java.lang.reflect.*;
import java.util.Objects;
public final class Reflect {
private Reflect() { throw new UnsupportedOperationException("Functional class"); }
public static boolean contains(final String[] array, final String find) {
for (final String check : array)
if (check.equals(find))
return true;
return false;
}
public static <T> Method findDeclaredMethod(final Class<T> rootType, final String[] methodNames, final Object[] args) {
Class<? super T> current = rootType;
do {
for (final Method check : current.getDeclaredMethods())
if (contains(methodNames, check.getName()) && argsMatch(check.getParameterTypes(), args))
return check;
current = current.getSuperclass();
} while (true);
}
public static <T> Field findDeclaredField(final Class<T> rootType, final Class<?> expectedType, final String... fieldNames) {
Class<? super T> current = rootType;
do {
for (final Field check : current.getDeclaredFields())
if (contains(fieldNames, check.getName()) && (expectedType == null || check.getType().equals(expectedType)))
return check;
current = current.getSuperclass();
} while (true);
}
public static <T> Constructor<T> findDeclaredConstructor(final Class<T> type, final Object[] args) {
for (final Constructor<?> check : type.getDeclaredConstructors())
if (argsMatch(check.getParameterTypes(), args))
return (Constructor<T>) check;
return null;
}
public static Object reflectInvoke(final Object target, final String[] methodNames, final Object... args) {
final Method targetMethod = findDeclaredMethod(target.getClass(), methodNames, args);
targetMethod.setAccessible(true);
try {
return targetMethod.invoke(target, args);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
public static void reflectSetStaticField(final Class<?> target, final Object value, final String... fieldNames) {
reflectSetStaticField(target, null, value, fieldNames);
}
public static void reflectSetStaticField(final Class<?> target, final Class<?> expectedType, final Object value, final String... fieldNames) {
final Field targetField = findDeclaredField(target, expectedType, fieldNames);
targetField.setAccessible(true);
try {
targetField.set(null, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static <T> T reflectGetStaticField(final Class<?> target, final String... fieldNames) {
return (T) reflectGetStaticField(target, null, fieldNames);
}
public static <T, R> T reflectGetGenericStaticField(final Class<?> target, final Class<T> fieldType, final Class<R> genericType) {
for (final Field check : target.getDeclaredFields()) {
if (fieldType.isAssignableFrom(check.getType())) {
final Type checkFieldType = check.getGenericType();
if (checkFieldType instanceof final ParameterizedType pCFT && pCFT.getActualTypeArguments().length != 0) {
for (final Type typeArg : pCFT.getActualTypeArguments()) {
if (typeArg == genericType) {
try {
return (T)check.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}
}
}
}
return null;
}
public static <T> T reflectGetStaticField(final Class<?> target, final Class<T> expectedType, final String... fieldNames) {
final Field targetField = findDeclaredField(target, expectedType, fieldNames);
targetField.setAccessible(true);
try {
return (T)targetField.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static void reflectSetField(final Object target, final Object value, final String... fieldNames) {
reflectSetField(target, null, value, fieldNames);
}
public static void reflectSetField(final Object target, final Class<?> expectedType, final Object value, final String... fieldNames) {
final Field targetField = findDeclaredField(target.getClass(), expectedType, fieldNames);
targetField.setAccessible(true);
try {
targetField.set(target, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static Object reflectGetField(final Object target, final String... fieldNames) {
return reflectGetField(target, null, fieldNames);
}
public static <T> T reflectGetField(final Object target, final Class<T> expectedType, final String... fieldNames) {
final Field targetField = findDeclaredField(target.getClass(), expectedType, fieldNames);
targetField.setAccessible(true);
try {
return (T)targetField.get(target);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static <T> T reflectConstruct(final Class<T> targetType, final Object... args) {
final Constructor<T> targetConstructor = findDeclaredConstructor(targetType, args);
assert targetConstructor != null;
targetConstructor.setAccessible(true);
try {
return targetConstructor.newInstance(args);
} catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static Class<?> loadClass(final Package from, final String... names) {
for (final String possibleName : names)
try {
return Class.forName(from.getName() + "." + possibleName);
} catch (ClassNotFoundException e) {
}
return null;
}
private static boolean argsMatch(final Class<?>[] types, final Object[] args) {
if (types.length != args.length)
return false;
for (int i = 0; i < args.length; ++i)
if (isNotSoftAssignable(types[i], args[i]))
return false;
return true;
}
private static boolean isNotSoftAssignable(final Class<?> type, final Object arg) {
return (arg == null && type.isPrimitive()) || (arg != null && !type.isAssignableFrom(arg.getClass()) && !isBoxedPrimitive(type, arg.getClass()));
}
private static boolean isBoxedPrimitive(final Class<?> primitive, final Class<?> objectType) {
return (primitive == boolean.class && objectType == Boolean.class) ||
(primitive == byte.class && objectType == Byte.class) ||
(primitive == short.class && objectType == Short.class) ||
(primitive == int.class && objectType == Integer.class) ||
(primitive == long.class && objectType == Long.class) ||
(primitive == float.class && objectType == Float.class) ||
(primitive == double.class && objectType == Double.class);
}
private interface DeclarationGetter<T, R> {
R[] getDeclared(final Class<? super T> t);
}
private interface NameGetter<T> {
String getName(final T t);
}
}