From a43261ab63f5efbbd41994d91f234b0d925e1520 Mon Sep 17 00:00:00 2001 From: xUS4R <142347533+xUS4R@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:26:27 -0500 Subject: [PATCH 1/3] 1.5.4 - Fix ladder bounding boxes! and bump to 1.5.4 --- gradle.properties | 2 +- .../feature/BlockCollisionChanges.java | 255 ++++++++++++++++-- 2 files changed, 233 insertions(+), 24 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7c226e7..ca4e088 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 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..3a9117d 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,13 @@ 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.EnumMap; +import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,30 +60,126 @@ 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}); + + Field shapesField = null; + for (Field field : blockLadderClass.getDeclaredFields()) { + if (!Modifier.isStatic(field.getModifiers())) { + continue; + } + if (!Map.class.isAssignableFrom(field.getType())) { + continue; + } + field.setAccessible(true); + shapesField = field; + break; + } + + if (shapesField != null) { + final Object rawShapes = shapesField.get(null); + ReflectionUtil.removeFinal(shapesField); + + 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 Class directionClassFinal = directionClass; + Map shapes; + try { + final Constructor enumMapConstructor = EnumMap.class.getDeclaredConstructor(Class.class); + enumMapConstructor.setAccessible(true); + @SuppressWarnings("unchecked") + final Map enumMap = (Map) enumMapConstructor.newInstance(directionClassFinal); + shapes = enumMap; + } catch (ReflectiveOperationException ignored) { + shapes = new HashMap<>(); + } + + if (rawShapes instanceof Map) { + shapes.putAll((Map) rawShapes); + } + + final Method directionValueOf = directionClass.getMethod("valueOf", String.class); + directionValueOf.setAccessible(true); + + 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); + } + + shapesField.set(null, shapes); + } else { + 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; + } + + if (!updated) { + throw new IllegalStateException("Could not adjust ladder shapes using fallback path 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 +287,114 @@ private static void setVoxelShapeArray(final Object voxelShapeArray, final doubl } initCache.invoke(voxelShapeArray); } + + 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."); + } } From cf212482ba2ff51d434d6c5d31dd5994e3225262 Mon Sep 17 00:00:00 2001 From: xUS4R <142347533+xUS4R@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:59:33 -0500 Subject: [PATCH 2/3] 1.5.4 - Ladder fix for 1.21.10! --- .../feature/BlockCollisionChanges.java | 200 +++++++++--------- 1 file changed, 103 insertions(+), 97 deletions(-) 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 3a9117d..75a5911 100644 --- a/src/main/java/com/viaversion/viarewind/legacysupport/feature/BlockCollisionChanges.java +++ b/src/main/java/com/viaversion/viarewind/legacysupport/feature/BlockCollisionChanges.java @@ -26,7 +26,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; -import java.util.EnumMap; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; @@ -69,7 +68,7 @@ public static void fixLadder(final Logger logger, final ProtocolVersion serverVe 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}); - Field shapesField = null; + Map shapesMap = null; for (Field field : blockLadderClass.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers())) { continue; @@ -78,106 +77,26 @@ public static void fixLadder(final Logger logger, final ProtocolVersion serverVe continue; } field.setAccessible(true); - shapesField = field; - break; - } - - if (shapesField != null) { - final Object rawShapes = shapesField.get(null); - ReflectionUtil.removeFinal(shapesField); - - 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 Class directionClassFinal = directionClass; - Map shapes; - try { - final Constructor enumMapConstructor = EnumMap.class.getDeclaredConstructor(Class.class); - enumMapConstructor.setAccessible(true); + final Object value = field.get(null); + if (value instanceof Map) { @SuppressWarnings("unchecked") - final Map enumMap = (Map) enumMapConstructor.newInstance(directionClassFinal); - shapes = enumMap; - } catch (ReflectiveOperationException ignored) { - shapes = new HashMap<>(); + final Map casted = (Map) value; + shapesMap = casted; + break; } + } - if (rawShapes instanceof Map) { - shapes.putAll((Map) rawShapes); - } - - final Method directionValueOf = directionClass.getMethod("valueOf", String.class); - directionValueOf.setAccessible(true); - - 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); - } + boolean updated = false; + if (shapesMap != null) { + updated = updateShapesMap(shapesMap, overrides); + } - shapesField.set(null, shapes); - } else { - 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; - } + if (!updated) { + updated = updateShapeConstants(blockLadderClass, overrides); + } - if (!updated) { - throw new IllegalStateException("Could not adjust ladder shapes using fallback path for modern versions"); - } + if (!updated) { + throw new IllegalStateException("Could not adjust ladder shapes for modern versions"); } } else { @@ -288,6 +207,93 @@ 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": From 0828b795ee92bb295d9dd592b2dbbd7fe5f3f90e Mon Sep 17 00:00:00 2001 From: FlorianMichael Date: Tue, 28 Oct 2025 20:15:05 +0100 Subject: [PATCH 3/3] Don't directly bump towards the release --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ca4e088..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.4 +project_version=1.5.4-SNAPSHOT project_description=Provides additional features for ViaRewind for Paper servers. publishing_dev_id=FlorianMichael