diff --git a/src/main/java/baritone/pathing/movement/movements/MovementFall.java b/src/main/java/baritone/pathing/movement/movements/MovementFall.java index cb8711757..24bb3f10d 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementFall.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementFall.java @@ -30,6 +30,7 @@ import baritone.pathing.movement.MovementState; import baritone.pathing.movement.MovementState.MovementTarget; import baritone.utils.pathing.MutableMoveResult; +import baritone.utils.reflection.InteractabilityHelper; import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -105,6 +106,9 @@ public MovementState updateState(MovementState state) { targetRotation = new Rotation(toDest.getYaw(), 90.0F); + boolean clickable = InteractabilityHelper.hasRightClickAction(ctx.world().getBlockState(dest.below())); + state.setInput(Input.SNEAK, clickable); + if (ctx.isLookingAt(dest) || ctx.isLookingAt(dest.below())) { state.setInput(Input.CLICK_RIGHT, true); } @@ -119,7 +123,12 @@ public MovementState updateState(MovementState state) { if (isWater) { // only match water, not flowing water (which we cannot pick up with a bucket) if (Inventory.isHotbarSlot(ctx.player().getInventory().findSlotMatchingItem(STACK_BUCKET_EMPTY))) { ctx.player().getInventory().selected = ctx.player().getInventory().findSlotMatchingItem(STACK_BUCKET_EMPTY); - if (ctx.player().getDeltaMovement().y >= 0) { + + // Note: this can not happen if there is water below, so no risk of drowning + boolean clickable = InteractabilityHelper.hasRightClickAction(ctx.world().getBlockState(dest.below())); + state.setInput(Input.SNEAK, clickable); + + if (ctx.player().getDeltaMovement().y >= 0 || ctx.player().isCrouching()) { return state.setInput(Input.CLICK_RIGHT, true); } else { return state; diff --git a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java index c46cf68bd..e3dafcdd8 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementParkour.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementParkour.java @@ -28,6 +28,7 @@ import baritone.pathing.movement.MovementState; import baritone.utils.BlockStateInterface; import baritone.utils.pathing.MutableMoveResult; +import baritone.utils.reflection.InteractabilityHelper; import net.minecraft.core.Direction; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; @@ -190,7 +191,11 @@ public static void cost(CalculationContext context, int x, int y, int z, Directi if (againstX == destX - xDiff && againstZ == destZ - zDiff) { // we can't turn around that fast continue; } - if (MovementHelper.canPlaceAgainst(context.bsi, againstX, againstY, againstZ)) { + if (!MovementHelper.canPlaceAgainst(context.bsi, againstX, againstY, againstZ)) { + continue; + } + // TODO Reflection during movement evaluation! This is BAD! + if (!InteractabilityHelper.hasRightClickAction(context.bsi.get0(againstX, againstY, againstZ))) { res.x = destX; res.y = y; res.z = destZ; diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java index 5a93d12ec..809f258db 100644 --- a/src/main/java/baritone/process/BuilderProcess.java +++ b/src/main/java/baritone/process/BuilderProcess.java @@ -41,6 +41,7 @@ import baritone.utils.BaritoneProcessHelper; import baritone.utils.BlockStateInterface; import baritone.utils.PathingCommandContext; +import baritone.utils.reflection.InteractabilityHelper; import baritone.utils.schematic.MapArtSchematic; import baritone.utils.schematic.SelectionSchematic; import baritone.utils.schematic.SchematicSystem; @@ -315,12 +316,14 @@ public static class Placement { private final BlockPos placeAgainst; private final Direction side; private final Rotation rot; + private final boolean sneak; - public Placement(int hotbarSelection, BlockPos placeAgainst, Direction side, Rotation rot) { + public Placement(int hotbarSelection, BlockPos placeAgainst, Direction side, Rotation rot, boolean sneak) { this.hotbarSelection = hotbarSelection; this.placeAgainst = placeAgainst; this.side = side; this.rot = rot; + this.sneak = sneak; } } @@ -375,18 +378,20 @@ private Optional possibleToPlace(BlockState toPlace, int x, int y, in if (shape.isEmpty()) { continue; } + boolean wouldSneak = InteractabilityHelper.hasRightClickAction(placeAgainstState); + Vec3 eyePosition = wouldSneak ? RayTraceUtils.inferSneakingEyePosition(ctx.player()) : ctx.playerHead(); AABB aabb = shape.bounds(); for (Vec3 placementMultiplier : aabbSideMultipliers(against)) { double placeX = placeAgainstPos.x + aabb.minX * placementMultiplier.x + aabb.maxX * (1 - placementMultiplier.x); double placeY = placeAgainstPos.y + aabb.minY * placementMultiplier.y + aabb.maxY * (1 - placementMultiplier.y); double placeZ = placeAgainstPos.z + aabb.minZ * placementMultiplier.z + aabb.maxZ * (1 - placementMultiplier.z); - Rotation rot = RotationUtils.calcRotationFromVec3d(RayTraceUtils.inferSneakingEyePosition(ctx.player()), new Vec3(placeX, placeY, placeZ), ctx.playerRotations()); + Rotation rot = RotationUtils.calcRotationFromVec3d(eyePosition, new Vec3(placeX, placeY, placeZ), ctx.playerRotations()); Rotation actualRot = baritone.getLookBehavior().getAimProcessor().peekRotation(rot); - HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), actualRot, ctx.playerController().getBlockReachDistance(), true); + HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), actualRot, ctx.playerController().getBlockReachDistance(), wouldSneak); if (result != null && result.getType() == HitResult.Type.BLOCK && ((BlockHitResult) result).getBlockPos().equals(placeAgainstPos) && ((BlockHitResult) result).getDirection() == against.getOpposite()) { OptionalInt hotbar = hasAnyItemThatWouldPlace(toPlace, result, actualRot); if (hotbar.isPresent()) { - return Optional.of(new Placement(hotbar.getAsInt(), placeAgainstPos, against.getOpposite(), rot)); + return Optional.of(new Placement(hotbar.getAsInt(), placeAgainstPos, against.getOpposite(), rot, wouldSneak)); } } } @@ -569,8 +574,8 @@ public int lengthZ() { Rotation rot = toPlace.get().rot; baritone.getLookBehavior().updateTarget(rot, true); ctx.player().getInventory().selected = toPlace.get().hotbarSelection; - baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); - if ((ctx.isLookingAt(toPlace.get().placeAgainst) && ((BlockHitResult) ctx.objectMouseOver()).getDirection().equals(toPlace.get().side)) || ctx.playerRotations().isReallyCloseTo(rot)) { + baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, toPlace.get().sneak); + if (ctx.player().isCrouching() == toPlace.get().sneak && (ctx.isLookingAt(toPlace.get().placeAgainst) && ((BlockHitResult) ctx.objectMouseOver()).getDirection().equals(toPlace.get().side)) || ctx.playerRotations().isReallyCloseTo(rot)) { baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); diff --git a/src/main/java/baritone/utils/reflection/InteractabilityHelper.java b/src/main/java/baritone/utils/reflection/InteractabilityHelper.java new file mode 100644 index 000000000..bbb85e337 --- /dev/null +++ b/src/main/java/baritone/utils/reflection/InteractabilityHelper.java @@ -0,0 +1,56 @@ +package baritone.utils.reflection; + +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.core.BlockPos; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * @author ZacSharp + * @since 3/26/2025 + */ +public class InteractabilityHelper { + + // Note that this can/must not be invoked (type error) + private static final Method BLOCK_USE; + + static { + Map blockMethods = ReflectionHelper.getMarkedMethods(DummyBlock.class); + BLOCK_USE = blockMethods.get("onInteract"); + } + + private InteractabilityHelper() {} + + public static boolean hasRightClickAction(BlockState state) { + return hasRightClickAction(state.getBlock()); + } + + public static boolean hasRightClickAction(Block block) { + return ReflectionHelper.getBySignature(block.getClass(), BLOCK_USE).getDeclaringClass() != BlockBehaviour.class; + } + + private static T raise(E ex) throws E { + throw ex; + } + + private static final class DummyBlock extends Block { + private DummyBlock() { + super(raise(new UnsupportedOperationException())); + } + + @Override + @ReflectionHelper.Marker("onInteract") + public InteractionResult use(BlockState p_60503_, Level p_60504_, BlockPos p_60505_, Player p_60506_, InteractionHand p_60507_, BlockHitResult p_60508_) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/java/baritone/utils/reflection/ReflectionHelper.java b/src/main/java/baritone/utils/reflection/ReflectionHelper.java new file mode 100644 index 000000000..59eff5f33 --- /dev/null +++ b/src/main/java/baritone/utils/reflection/ReflectionHelper.java @@ -0,0 +1,60 @@ +package baritone.utils.reflection; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +/** + * @author ZacSharp + * @since 3/26/2025 + */ +public class ReflectionHelper { + + private ReflectionHelper() {} + + // Note: This approach works with public/protected/package-private methods only. + // If we need fields or private methods that should be possible with some mixin + // trickery like a custom injection point with the name we need as its target. + /** + * Get a map with all declared in {@code class} with the {@code Marker} annotation + * using the annotation value as the map key. + */ + public static Map getMarkedMethods(Class cls) { + Map methods = new HashMap<>(); + for (Method method : cls.getDeclaredMethods()) { + Marker marker = method.getAnnotation(Marker.class); + if (marker == null) { + continue; + } + methods.put(marker.value(), method); + } + return methods; + } + + /** + * Gets a public method of {@code cls} with the same name and parameter + * types as {@code sig}, or null if such a method does not exist. + * + * @param the class to search in + * @param a method with the same signature as the sought method + * @return the found method or null + */ + public static Method getBySignature(Class cls, Method sig) { + try { + return cls.getMethod(sig.getName(), sig.getParameterTypes()); + } catch (NoSuchMethodException ex) { + return null; + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public static @interface Marker { + String value(); + } +}