diff --git a/gradle.properties b/gradle.properties index 7c226e7..4b355bb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.configuration-cache=true project_jvm_version=8 project_group=com.viaversion -project_version=1.5.3 +project_version=1.5.4-SNAPSHOT project_description=Provides additional features for ViaRewind for Paper servers. publishing_dev_id=FlorianMichael diff --git a/src/main/java/com/viaversion/viarewind/legacysupport/feature/BlockCollisionChanges.java b/src/main/java/com/viaversion/viarewind/legacysupport/feature/BlockCollisionChanges.java index af8e738..75a5911 100644 --- a/src/main/java/com/viaversion/viarewind/legacysupport/feature/BlockCollisionChanges.java +++ b/src/main/java/com/viaversion/viarewind/legacysupport/feature/BlockCollisionChanges.java @@ -21,10 +21,12 @@ import com.viaversion.viarewind.legacysupport.util.ReflectionUtil; import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; +import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,30 +59,46 @@ public static void fixCarpet(final Logger logger, final ProtocolVersion serverVe public static void fixLadder(final Logger logger, final ProtocolVersion serverVersion) { try { - if(serverVersion.newerThanOrEqualTo(ProtocolVersion.v1_20_5)) - { + if (serverVersion.newerThanOrEqualTo(ProtocolVersion.v1_20_5)) { final Class blockLadderClass = getNMSBlockClass("BlockLadder"); - final Field shapesField = getFieldAccessible(blockLadderClass, "d"); // SHAPES field - shapesField.setAccessible(true); - - Class blockClass = Class.forName("net.minecraft.world.level.block.Block"); - Method boxZMethod = blockClass.getDeclaredMethod("boxZ", double.class, double.class, double.class); - boxZMethod.setAccessible(true); - Object voxelShape = boxZMethod.invoke(null, 16.0, 14.0, 16.0); - - Class shapesClass = Class.forName("net.minecraft.world.phys.shapes.Shapes"); - Class voxelShapeInterface = Class.forName("net.minecraft.world.phys.shapes.VoxelShape"); - Method rotateHorizontalMethod = shapesClass.getDeclaredMethod("rotateHorizontal", voxelShapeInterface); - rotateHorizontalMethod.setAccessible(true); - @SuppressWarnings("unchecked") - Map rotatedMap = (Map) rotateHorizontalMethod.invoke(null, voxelShape); - - @SuppressWarnings("unchecked") - Map shapes = (Map) shapesField.get(null); - shapes.clear(); - shapes.putAll(rotatedMap); - } - else + + final Map overrides = new HashMap(); + overrides.put("EAST", new double[]{0.0D, 0.0D, 0.0D, 0.125D, 1.0D, 1.0D}); + overrides.put("WEST", new double[]{0.875D, 0.0D, 0.0D, 1.0D, 1.0D, 1.0D}); + overrides.put("SOUTH", new double[]{0.0D, 0.0D, 0.0D, 1.0D, 1.0D, 0.125D}); + overrides.put("NORTH", new double[]{0.0D, 0.0D, 0.875D, 1.0D, 1.0D, 1.0D}); + + Map shapesMap = null; + for (Field field : blockLadderClass.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + continue; + } + if (!Map.class.isAssignableFrom(field.getType())) { + continue; + } + field.setAccessible(true); + final Object value = field.get(null); + if (value instanceof Map) { + @SuppressWarnings("unchecked") + final Map casted = (Map) value; + shapesMap = casted; + break; + } + } + + boolean updated = false; + if (shapesMap != null) { + updated = updateShapesMap(shapesMap, overrides); + } + + if (!updated) { + updated = updateShapeConstants(blockLadderClass, overrides); + } + + if (!updated) { + throw new IllegalStateException("Could not adjust ladder shapes for modern versions"); + } + } else { final boolean pre1_12_2 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_12_2); final boolean pre1_13_2 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_13_2); @@ -188,4 +206,201 @@ private static void setVoxelShapeArray(final Object voxelShapeArray, final doubl } initCache.invoke(voxelShapeArray); } + + private static boolean updateShapesMap(final Map shapes, final Map overrides) throws ReflectiveOperationException { + if (shapes == null) { + return false; + } + + final Class directionClass = Class.forName("net.minecraft.core.Direction"); + final Class shapesClass = Class.forName("net.minecraft.world.phys.shapes.Shapes"); + final Class blockClass = Class.forName("net.minecraft.world.level.block.Block"); + + Method shapesBoxMethod = ReflectionUtil.findMethod(shapesClass, new String[]{"box", "a"}, double.class, double.class, double.class, double.class, double.class, double.class); + if (shapesBoxMethod != null) { + shapesBoxMethod.setAccessible(true); + } + + Method blockBoxMethod = ReflectionUtil.findMethod(blockClass, new String[]{"box", "a"}, double.class, double.class, double.class, double.class, double.class, double.class); + if (blockBoxMethod != null) { + blockBoxMethod.setAccessible(true); + } + + Method shapesCreateMethod = null; + Constructor aabbConstructor = null; + if (shapesBoxMethod == null) { + final Class aabbClass = Class.forName("net.minecraft.world.phys.AABB"); + shapesCreateMethod = ReflectionUtil.findMethod(shapesClass, new String[]{"create", "a"}, aabbClass); + if (shapesCreateMethod != null) { + shapesCreateMethod.setAccessible(true); + aabbConstructor = aabbClass.getDeclaredConstructor(double.class, double.class, double.class, double.class, double.class, double.class); + aabbConstructor.setAccessible(true); + } + } + + final Method directionValueOf = directionClass.getMethod("valueOf", String.class); + directionValueOf.setAccessible(true); + + boolean updated = false; + try { + for (Map.Entry entry : overrides.entrySet()) { + final Object direction = directionValueOf.invoke(null, entry.getKey()); + final Object voxelShape = createVoxelShape(shapesBoxMethod, blockBoxMethod, shapesCreateMethod, aabbConstructor, entry.getValue()); + shapes.put(direction, voxelShape); + updated = true; + } + } catch (UnsupportedOperationException ex) { + return false; + } + return updated; + } + + private static boolean updateShapeConstants(final Class blockLadderClass, final Map overrides) throws ReflectiveOperationException { + final Class voxelShapeInterface = Class.forName("net.minecraft.world.phys.shapes.VoxelShape"); + + boolean updated = false; + for (Field field : blockLadderClass.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + continue; + } + field.setAccessible(true); + final Object shapeObject = field.get(null); + if (shapeObject == null) { + continue; + } + if (!voxelShapeInterface.isAssignableFrom(shapeObject.getClass()) + && !shapeObject.getClass().getSimpleName().contains("VoxelShape")) { + continue; + } + + final double[] currentBounds = getBoundingBoxValues(shapeObject); + if (currentBounds == null) { + continue; + } + + final String orientation = detectOrientation(currentBounds); + if (orientation == null) { + continue; + } + + final double[] override = overrides.get(orientation); + if (override == null) { + continue; + } + + setBoundingBox(shapeObject, override); + updated = true; + } + return updated; + } + + private static double[] getBoundingBoxValues(final Object boundingBox) throws ReflectiveOperationException { + switch (boundingBox.getClass().getSimpleName()) { + case "AxisAlignedBB": + return getAxisAlignedBBValues(boundingBox); + case "VoxelShapeArray": + case "ArrayVoxelShape": + return getVoxelShapeArrayValues(boundingBox); + case "AABBVoxelShape": + return getAABBVoxelShapeValues(boundingBox); + default: + return null; + } + } + + private static double[] getAABBVoxelShapeValues(final Object boundingBox) throws ReflectiveOperationException { + for (Field field : boundingBox.getClass().getFields()) { + if (field.getType().getSimpleName().equals("AxisAlignedBB")) { + return getBoundingBoxValues(field.get(boundingBox)); + } + } + return null; + } + + private static double[] getAxisAlignedBBValues(final Object boundingBox) throws ReflectiveOperationException { + final Field[] doubleFields = Arrays.stream(boundingBox.getClass().getDeclaredFields()).filter(f -> f.getType() == double.class && !Modifier.isStatic(f.getModifiers())).toArray(Field[]::new); + + if (doubleFields.length < 6) { + throw new IllegalStateException("Invalid field count for " + boundingBox.getClass().getName() + ": " + doubleFields.length); + } + + final double[] values = new double[6]; + for (int i = 0; i < 6; i++) { + final Field currentField = doubleFields[i]; + currentField.setAccessible(true); + values[i] = currentField.getDouble(boundingBox); + } + return values; + } + + private static double[] getVoxelShapeArrayValues(final Object voxelShapeArray) throws ReflectiveOperationException { + final Field[] doubleListFields = Arrays.stream(voxelShapeArray.getClass().getDeclaredFields()).filter(f -> f.getType().getSimpleName().equals("DoubleList")).toArray(Field[]::new); + + if (doubleListFields.length < 3) { + throw new IllegalStateException("Invalid field count for " + voxelShapeArray.getClass().getName() + ": " + doubleListFields.length); + } + + final double[] values = new double[6]; + + for (int i = 0; i < 3; i++) { + final Field field = doubleListFields[i]; + field.setAccessible(true); + final Object doubleList = field.get(voxelShapeArray); + if (doubleList == null) { + return null; + } + + final Method getDouble = ReflectionUtil.findMethod(doubleList.getClass(), new String[]{"getDouble", "get"}, int.class); + if (getDouble == null) { + return null; + } + getDouble.setAccessible(true); + + values[i] = ((Number) getDouble.invoke(doubleList, 0)).doubleValue(); + values[i + 3] = ((Number) getDouble.invoke(doubleList, 1)).doubleValue(); + } + + return values; + } + + private static String detectOrientation(final double[] bounds) { + final double epsilon = 1.0E-3; + final double minX = bounds[0]; + final double minZ = bounds[2]; + final double maxX = bounds[3]; + final double maxZ = bounds[5]; + + if (minX <= epsilon && maxX <= 0.5D) { + return "EAST"; + } + if (maxX >= 1.0D - epsilon && minX >= 0.5D) { + return "WEST"; + } + if (minZ <= epsilon && maxZ <= 0.5D) { + return "SOUTH"; + } + if (maxZ >= 1.0D - epsilon && minZ >= 0.5D) { + return "NORTH"; + } + return null; + } + + private static Object createVoxelShape(final Method shapesBoxMethod, final Method blockBoxMethod, + final Method shapesCreateMethod, final Constructor aabbConstructor, + final double[] values) throws ReflectiveOperationException { + if (shapesBoxMethod != null) { + return shapesBoxMethod.invoke(null, values[0], values[1], values[2], values[3], values[4], values[5]); + } + if (shapesCreateMethod != null && aabbConstructor != null) { + final Object aabb = aabbConstructor.newInstance(values[0], values[1], values[2], values[3], values[4], values[5]); + return shapesCreateMethod.invoke(null, aabb); + } + if (blockBoxMethod != null) { + final double scale = 16.0D; + return blockBoxMethod.invoke(null, + values[0] * scale, values[1] * scale, values[2] * scale, + values[3] * scale, values[4] * scale, values[5] * scale); + } + throw new IllegalStateException("Unable to create voxel shape for ladder bounding box."); + } }