From d912715741d9cf3581131433232b205409137056 Mon Sep 17 00:00:00 2001 From: Gabriel Tofvesson Date: Fri, 25 Jun 2021 05:22:52 +0200 Subject: [PATCH] Implement compatibility layer for fake entities --- .../invtweaks/entity/EntityCreator.java | 153 +++++++++++++ src/dev/w1zzrd/invtweaks/entity/Reflect.java | 201 ++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 src/dev/w1zzrd/invtweaks/entity/EntityCreator.java create mode 100644 src/dev/w1zzrd/invtweaks/entity/Reflect.java diff --git a/src/dev/w1zzrd/invtweaks/entity/EntityCreator.java b/src/dev/w1zzrd/invtweaks/entity/EntityCreator.java new file mode 100644 index 0000000..57533e6 --- /dev/null +++ b/src/dev/w1zzrd/invtweaks/entity/EntityCreator.java @@ -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); + } +} diff --git a/src/dev/w1zzrd/invtweaks/entity/Reflect.java b/src/dev/w1zzrd/invtweaks/entity/Reflect.java new file mode 100644 index 0000000..1f714ba --- /dev/null +++ b/src/dev/w1zzrd/invtweaks/entity/Reflect.java @@ -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 Method findDeclaredMethod(final Class rootType, final String[] methodNames, final Object[] args) { + Class 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 Field findDeclaredField(final Class rootType, final Class expectedType, final String... fieldNames) { + Class 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 Constructor findDeclaredConstructor(final Class type, final Object[] args) { + for (final Constructor check : type.getDeclaredConstructors()) + if (argsMatch(check.getParameterTypes(), args)) + return (Constructor) 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 reflectGetStaticField(final Class target, final String... fieldNames) { + return (T) reflectGetStaticField(target, null, fieldNames); + } + + public static T reflectGetGenericStaticField(final Class target, final Class fieldType, final Class 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 reflectGetStaticField(final Class target, final Class 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 reflectGetField(final Object target, final Class 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 reflectConstruct(final Class targetType, final Object... args) { + final Constructor 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 { + R[] getDeclared(final Class t); + } + + private interface NameGetter { + String getName(final T t); + } +}