diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index 3f45f7ac5..052653e07 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -775,7 +775,8 @@ public final class Settings { public final Setting elytraFreeLook = new Setting<>(true); /** - * Forces the client-sided yaw rotation to an average of the last {@link #smoothLookTicks} of server-sided rotations. + * Forces the client-sided yaw rotation to an average of the last + * {@link #smoothLookTicks} server-sided rotations. */ public final Setting smoothLook = new Setting<>(false); @@ -789,6 +790,21 @@ public final class Settings { */ public final Setting smoothLookTicks = new Setting<>(5); + /** + * Interpolates player rotation from the current look direction to the target direction. + *

+ * This uses {@link #interpolatedLookLength} as a maximum angular speed, so larger + * turns take more ticks than smaller turns. + */ + public final Setting interpolatedLook = new Setting<>(false); + + /** + * Maximum angular speed, in degrees per tick, for {@link #interpolatedLook}. + * The historical name is retained for config compatibility. + */ + public final Setting interpolatedLookLength = new Setting<>(10); + + /** * When true, the player will remain with its existing look direction as often as possible. * Although, in some cases this can get it stuck, hence this setting to disable that behavior. diff --git a/src/api/java/baritone/api/behavior/ILookBehavior.java b/src/api/java/baritone/api/behavior/ILookBehavior.java index d78e7f8b3..b6c7d55f4 100644 --- a/src/api/java/baritone/api/behavior/ILookBehavior.java +++ b/src/api/java/baritone/api/behavior/ILookBehavior.java @@ -39,6 +39,42 @@ public interface ILookBehavior extends IBehavior { */ void updateTarget(Rotation rotation, boolean blockInteract); + /** + * Updates the current {@link ILookBehavior} target and optionally restarts + * any active interpolation. + * + * @param rotation The target rotations + * @param blockInteract Whether the target rotations are needed for a block interaction + * @param restartInterpolation Whether active interpolation should restart + * from its current sample + * @see #updateTarget(Rotation, boolean) + */ + default void updateTarget( + Rotation rotation, + boolean blockInteract, + boolean restartInterpolation) { + updateTarget(rotation, blockInteract); + } + + /** + * Updates the current {@link ILookBehavior} target and optionally uses it + * without aim processing. + * + * @param rotation The target rotations + * @param blockInteract Whether the target rotations are needed for a block interaction + * @param restartInterpolation Whether active interpolation should restart + * from its current sample + * @param exactRotation Whether the target rotation should skip aim processing + * @see #updateTarget(Rotation, boolean, boolean) + */ + default void updateTarget( + Rotation rotation, + boolean blockInteract, + boolean restartInterpolation, + boolean exactRotation) { + updateTarget(rotation, blockInteract, restartInterpolation); + } + /** * The aim processor instance for this {@link ILookBehavior}, which is responsible for applying additional, * deterministic transformations to the target rotation set by {@link #updateTarget}. diff --git a/src/api/java/baritone/api/utils/IInputOverrideHandler.java b/src/api/java/baritone/api/utils/IInputOverrideHandler.java index 3cfb74385..35ea3b628 100644 --- a/src/api/java/baritone/api/utils/IInputOverrideHandler.java +++ b/src/api/java/baritone/api/utils/IInputOverrideHandler.java @@ -19,6 +19,8 @@ import baritone.api.behavior.IBehavior; import baritone.api.utils.input.Input; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; /** * @author Brady @@ -31,4 +33,16 @@ public interface IInputOverrideHandler extends IBehavior { void setInputForceState(Input input, boolean forced); void clearAllKeys(); + + default boolean isBreakingBlock(BlockPos pos) { + return false; + } + + default void setBlockBreakTarget(BlockPos pos) { + // Optional hook for handlers that support targeted block breaking. + } + + default void setBlockPlaceTarget(BlockPos pos, Direction side) { + // Optional hook for handlers that support targeted block placing. + } } diff --git a/src/api/java/baritone/api/utils/IPlayerContext.java b/src/api/java/baritone/api/utils/IPlayerContext.java index de6d7cd58..8d4514ccf 100644 --- a/src/api/java/baritone/api/utils/IPlayerContext.java +++ b/src/api/java/baritone/api/utils/IPlayerContext.java @@ -22,6 +22,7 @@ import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.SlabBlock; @@ -116,15 +117,26 @@ static double eyeHeight(boolean ifSneaking) { * * @return The position of the highlighted block */ - default Optional getSelectedBlock() { + default Optional getSelectedBlockHitResult() { HitResult result = objectMouseOver(); if (result != null && result.getType() == HitResult.Type.BLOCK) { - return Optional.of(((BlockHitResult) result).getBlockPos()); + return Optional.of((BlockHitResult) result); } return Optional.empty(); } + default Optional getSelectedBlock() { + return getSelectedBlockHitResult().map(BlockHitResult::getBlockPos); + } + default boolean isLookingAt(BlockPos pos) { return getSelectedBlock().equals(Optional.of(pos)); } + + default boolean isLookingAt(BlockPos pos, Direction side) { + return getSelectedBlockHitResult() + .filter(hit -> hit.getBlockPos().equals(pos)) + .filter(hit -> hit.getDirection() == side) + .isPresent(); + } } diff --git a/src/api/java/baritone/api/utils/RotationUtils.java b/src/api/java/baritone/api/utils/RotationUtils.java index 2c2ee9147..c9c47af1f 100644 --- a/src/api/java/baritone/api/utils/RotationUtils.java +++ b/src/api/java/baritone/api/utils/RotationUtils.java @@ -19,6 +19,7 @@ import baritone.api.BaritoneAPI; import baritone.api.IBaritone; +import java.util.Optional; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -32,8 +33,6 @@ import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; -import java.util.Optional; - /** * @author Brady * @since 9/25/2018 @@ -52,6 +51,14 @@ public final class RotationUtils { public static final double RAD_TO_DEG = 180.0 / Math.PI; public static final float RAD_TO_DEG_F = (float) RAD_TO_DEG; + private static final double ARC_EPSILON = 1.0E-8; + private static final double POLE_AVOIDANCE_PITCH = 85.0; + private static final double POLE_AVOIDANCE_MARGIN = 1.0; + private static final double LATITUDE_DISTANCE_STEP = 5.0; + private static final double MINOR_ARC_EPSILON = 1.0E-4; + private static final double TWO_PI = Math.PI * 2.0; + private static final Vec3 UP = new Vec3(0, 1, 0); + /** * Offsets from the root block position to the center of each side. */ @@ -265,6 +272,286 @@ public static Optional reachableCenter(IPlayerContext ctx, BlockPos po return reachableOffset(ctx, pos, VecUtils.calculateBlockCenter(ctx.world(), pos), blockReachDistance, wouldSneak); } + /** + * Interpolates along an arc from {@code start} to {@code end} around {@code origin}. + */ + public static Vec3 alerp(Vec3 start, Vec3 end, Vec3 origin, double t) { + if (t <= 0) { + return start; + } + if (t >= 1) { + return end; + } + if (start.distanceToSqr(end) < ARC_EPSILON) { + return start; + } + + Vec3 startOffset = start.subtract(origin); + Vec3 endOffset = end.subtract(origin); + if (startOffset.lengthSqr() < ARC_EPSILON || endOffset.lengthSqr() < ARC_EPSILON) { + return lerp(start, end, t); + } + + Vec3 normal = startOffset.cross(endOffset); + if (normal.lengthSqr() < ARC_EPSILON) { + return colinearAlerp(start, end, origin, t, startOffset, endOffset); + } + + Vec3 midpoint = start.add(end).scale(0.5); + normal = normal.normalize(); + Vec3 pathDirection = end.subtract(start).normalize(); + Vec3 toCenter = pathDirection.cross(normal).normalize(); + Vec3 originToMid = midpoint.subtract(origin); + double projectionLength = originToMid.dot(toCenter); + Vec3 center = midpoint.subtract(toCenter.scale(projectionLength)); + double radius = start.distanceTo(center); + if (radius < ARC_EPSILON) { + return lerp(start, end, t); + } + Vec3 centerToStart = start.subtract(center); + Vec3 centerToEnd = end.subtract(center); + double angleStart = Math.atan2( + centerToStart.dot(toCenter), + centerToStart.dot(pathDirection)); + double angleEnd = Math.atan2( + centerToEnd.dot(toCenter), + centerToEnd.dot(pathDirection)); + double angle = angleStart + wrapRadians(angleEnd - angleStart) * t; + Vec3 arcDirection = pathDirection.scale(Math.cos(angle)) + .add(toCenter.scale(Math.sin(angle))); + + return center.add(arcDirection.scale(radius)); + } + + private static Vec3 lerp(Vec3 start, Vec3 end, double t) { + return start.add(end.subtract(start).scale(t)); + } + + private static Vec3 colinearAlerp( + Vec3 start, + Vec3 end, + Vec3 origin, + double t, + Vec3 startOffset, + Vec3 endOffset) { + if (startOffset.dot(endOffset) >= 0) { + return lerp(start, end, t); + } + + Vec3 startDirection = startOffset.normalize(); + Vec3 perpendicular = perpendicular(startDirection); + double startRadius = startOffset.length(); + double endRadius = endOffset.length(); + double radius = startRadius + (endRadius - startRadius) * t; + double angle = Math.PI * t; + Vec3 arcDirection = startDirection.scale(Math.cos(angle)) + .add(perpendicular.scale(Math.sin(angle))); + + return origin.add(arcDirection.scale(radius)); + } + + private static Vec3 perpendicular(Vec3 vector) { + Vec3 axis = Math.abs(vector.x) < Math.abs(vector.y) + ? new Vec3(1, 0, 0) + : new Vec3(0, 1, 0); + Vec3 perpendicular = vector.cross(axis); + if (perpendicular.lengthSqr() < ARC_EPSILON) { + perpendicular = vector.cross(new Vec3(0, 0, 1)); + } + return perpendicular.normalize(); + } + + private static double wrapRadians(double angle) { + double wrapped = angle % TWO_PI; + if (wrapped <= -Math.PI) { + wrapped += TWO_PI; + } else if (wrapped > Math.PI) { + wrapped -= TWO_PI; + } + return wrapped; + } + + private static double clamp(double value, double min, double max) { + return Math.max(min, Math.min(max, value)); + } + + public static final class RotationArc { + + private final Rotation startRotation; + private final Rotation targetRotation; + private final int length; + private final boolean latitudeArc; + private int stage; + + public RotationArc(Rotation startRotation, Rotation targetRotation, int length) { + this.startRotation = startRotation; + this.targetRotation = targetRotation; + this.length = Math.max(1, length); + this.latitudeArc = shouldUseLatitudeArc(startRotation, targetRotation); + } + + public static RotationArc fromAngularSpeed( + Rotation startRotation, + Rotation targetRotation, + double degreesPerTick) { + return new RotationArc( + startRotation, + targetRotation, + ticksForAngularSpeed(startRotation, targetRotation, degreesPerTick)); + } + + public Rotation getCurrentRotation() { + return this.arcAt(this.stage / (double) this.length); + } + + public Rotation getCurrentRotation(float partialTicks) { + return this.arcAt((this.stage - 1 + clamp(partialTicks, 0, 1)) / (double) this.length); + } + + public Rotation advance() { + if (this.stage < this.length) { + this.stage++; + } + return this.getCurrentRotation(); + } + + public boolean isComplete() { + return this.stage >= this.length; + } + + public Rotation arcAt(double t) { + if (t <= 0) { + return this.startRotation; + } + if (t >= 1) { + return this.targetRotation; + } + final Vec3 source = calcLookDirectionFromRotation(this.startRotation); + final Vec3 target = calcLookDirectionFromRotation(this.targetRotation); + if (source.distanceToSqr(target) < 1.0E-8) { + return this.targetRotation; + } + if (this.latitudeArc) { + return latitudeArcAt(this.startRotation, this.targetRotation, t); + } + return calcRotationFromVec3d( + Vec3.ZERO, + alerp(source, target, Vec3.ZERO, t), + this.startRotation); + } + + private static int ticksForAngularSpeed( + Rotation startRotation, + Rotation targetRotation, + double degreesPerTick) { + if (degreesPerTick <= ARC_EPSILON) { + return 1; + } + + double angularDistance = angularDistance(startRotation, targetRotation); + + if (angularDistance < ARC_EPSILON) { + return 1; + } + return Math.max(1, (int) Math.ceil(angularDistance / degreesPerTick)); + } + + private static double angularDistance(Rotation startRotation, Rotation targetRotation) { + if (shouldUseLatitudeArc(startRotation, targetRotation)) { + return latitudeAngularDistance(startRotation, targetRotation); + } + + Vec3 source = calcLookDirectionFromRotation(startRotation); + Vec3 target = calcLookDirectionFromRotation(targetRotation); + return Math.toDegrees(angularDistance(source, target)); + } + + private static double latitudeAngularDistance( + Rotation startRotation, + Rotation targetRotation) { + double yawDelta = Rotation.normalizeYaw( + targetRotation.getYaw() - startRotation.getYaw()); + double pitchDelta = targetRotation.getPitch() - startRotation.getPitch(); + int segments = Math.max(1, (int) Math.ceil( + Math.max(Math.abs(yawDelta), Math.abs(pitchDelta)) / LATITUDE_DISTANCE_STEP)); + + Vec3 previous = calcLookDirectionFromRotation(startRotation); + double distance = 0; + for (int i = 1; i <= segments; i++) { + Vec3 current = calcLookDirectionFromRotation( + latitudeArcAt(startRotation, targetRotation, i / (double) segments)); + distance += angularDistance(previous, current); + previous = current; + } + return Math.toDegrees(distance); + } + + private static Rotation latitudeArcAt( + Rotation startRotation, + Rotation targetRotation, + double t) { + double yawDelta = Rotation.normalizeYaw( + targetRotation.getYaw() - startRotation.getYaw()); + double pitchDelta = targetRotation.getPitch() - startRotation.getPitch(); + return new Rotation( + (float) (startRotation.getYaw() + yawDelta * t), + (float) (startRotation.getPitch() + pitchDelta * t) + ).clamp(); + } + + private static boolean shouldUseLatitudeArc( + Rotation startRotation, + Rotation targetRotation) { + Vec3 source = calcLookDirectionFromRotation(startRotation); + Vec3 target = calcLookDirectionFromRotation(targetRotation); + if (source.distanceToSqr(target) < ARC_EPSILON) { + return false; + } + + // With roll locked, yaw becomes unstable when a great-circle arc crosses a view pole. + double endpointPitch = Math.max( + Math.abs(startRotation.getPitch()), + Math.abs(targetRotation.getPitch())); + double polePitch = maxPolePitchOnGreatCircle(source, target); + return polePitch >= POLE_AVOIDANCE_PITCH + && polePitch > endpointPitch + POLE_AVOIDANCE_MARGIN; + } + + private static double maxPolePitchOnGreatCircle(Vec3 source, Vec3 target) { + double maxY = Math.max(Math.abs(source.y), Math.abs(target.y)); + Vec3 normal = source.cross(target); + if (normal.lengthSqr() < ARC_EPSILON) { + return Math.toDegrees(Math.asin(clamp(maxY, -1, 1))); + } + + normal = normal.normalize(); + Vec3 poleProjection = UP.subtract(normal.scale(UP.dot(normal))); + if (poleProjection.lengthSqr() < ARC_EPSILON) { + return Math.toDegrees(Math.asin(clamp(maxY, -1, 1))); + } + + Vec3 poleward = poleProjection.normalize(); + if (isOnMinorArc(source, target, poleward)) { + maxY = Math.max(maxY, Math.abs(poleward.y)); + } + Vec3 oppositePoleward = poleward.scale(-1); + if (isOnMinorArc(source, target, oppositePoleward)) { + maxY = Math.max(maxY, Math.abs(oppositePoleward.y)); + } + return Math.toDegrees(Math.asin(clamp(maxY, -1, 1))); + } + + private static boolean isOnMinorArc(Vec3 source, Vec3 target, Vec3 sample) { + double total = angularDistance(source, target); + double split = angularDistance(source, sample) + angularDistance(sample, target); + return Math.abs(split - total) < MINOR_ARC_EPSILON; + } + + private static double angularDistance(Vec3 source, Vec3 target) { + return Math.acos(clamp(source.dot(target), -1, 1)); + } + } + @Deprecated public static Optional reachable(LocalPlayer entity, BlockPos pos, double blockReachDistance) { return reachable(entity, pos, blockReachDistance, false); diff --git a/src/launch/java/baritone/launch/mixins/MixinClientPlayerEntity.java b/src/launch/java/baritone/launch/mixins/MixinClientPlayerEntity.java index 24e807f62..83800bafd 100644 --- a/src/launch/java/baritone/launch/mixins/MixinClientPlayerEntity.java +++ b/src/launch/java/baritone/launch/mixins/MixinClientPlayerEntity.java @@ -22,15 +22,18 @@ import baritone.api.event.events.PlayerUpdateEvent; import baritone.api.event.events.SprintStateEvent; import baritone.api.event.events.type.EventState; +import baritone.api.utils.Rotation; import baritone.behavior.LookBehavior; import net.minecraft.client.KeyMapping; import net.minecraft.client.player.LocalPlayer; import net.minecraft.world.entity.player.Abilities; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; /** * @author Brady @@ -54,6 +57,32 @@ private void onPreUpdate(CallbackInfo ci) { } } + @Inject( + method = "getViewYRot", + at = @At("HEAD"), + cancellable = true + ) + @SuppressWarnings({"unused", "PMD.UnusedPrivateMethod"}) + private void getViewYRot(float partialTicks, CallbackInfoReturnable cir) { + Rotation visualRotation = this.getVisualRotation(partialTicks); + if (visualRotation != null) { + cir.setReturnValue(visualRotation.getYaw()); + } + } + + @Inject( + method = "getViewXRot", + at = @At("HEAD"), + cancellable = true + ) + @SuppressWarnings({"unused", "PMD.UnusedPrivateMethod"}) + private void getViewXRot(float partialTicks, CallbackInfoReturnable cir) { + Rotation visualRotation = this.getVisualRotation(partialTicks); + if (visualRotation != null) { + cir.setReturnValue(visualRotation.getPitch()); + } + } + @Redirect( method = "aiStep", at = @At( @@ -120,4 +149,14 @@ private boolean tryToStartFallFlying(final LocalPlayer instance) { } return instance.tryToStartFallFlying(); } + + @Unique + private Rotation getVisualRotation(float partialTicks) { + IBaritone baritone = BaritoneAPI.getProvider() + .getBaritoneForPlayer((LocalPlayer) (Object) this); + if (baritone == null) { + return null; + } + return ((LookBehavior) baritone.getLookBehavior()).getVisualRotation(partialTicks); + } } diff --git a/src/launch/java/baritone/launch/mixins/MixinMinecraft.java b/src/launch/java/baritone/launch/mixins/MixinMinecraft.java index 4f9925cc7..a39e06a76 100644 --- a/src/launch/java/baritone/launch/mixins/MixinMinecraft.java +++ b/src/launch/java/baritone/launch/mixins/MixinMinecraft.java @@ -23,6 +23,7 @@ import baritone.api.event.events.TickEvent; import baritone.api.event.events.WorldEvent; import baritone.api.event.events.type.EventState; +import baritone.utils.PlayerControlGuard; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.multiplayer.ClientLevel; @@ -172,7 +173,9 @@ private void postLoadWorld(ClientLevel world, CallbackInfo ci) { ) private boolean passEvents(Screen screen) { // allow user input is only the primary baritone - return (BaritoneAPI.getProvider().getPrimaryBaritone().getPathingBehavior().isPathing() && player != null) || screen.passEvents; + return (BaritoneAPI.getProvider().getPrimaryBaritone().getPathingBehavior().isPathing() + && PlayerControlGuard.canControl(player, screen)) + || screen.passEvents; } // TODO diff --git a/src/main/java/baritone/behavior/LookBehavior.java b/src/main/java/baritone/behavior/LookBehavior.java index 98e12aeff..a1d6689e0 100644 --- a/src/main/java/baritone/behavior/LookBehavior.java +++ b/src/main/java/baritone/behavior/LookBehavior.java @@ -22,25 +22,35 @@ import baritone.api.behavior.ILookBehavior; import baritone.api.behavior.look.IAimProcessor; import baritone.api.behavior.look.ITickableAimProcessor; -import baritone.api.event.events.*; +import baritone.api.event.events.PacketEvent; +import baritone.api.event.events.PlayerUpdateEvent; +import baritone.api.event.events.RotationMoveEvent; +import baritone.api.event.events.TickEvent; +import baritone.api.event.events.WorldEvent; +import baritone.api.event.events.type.EventState; import baritone.api.utils.IPlayerContext; import baritone.api.utils.Rotation; +import baritone.api.utils.RotationUtils; +import baritone.api.utils.RotationUtils.RotationArc; import baritone.behavior.look.ForkableRandom; -import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; - import java.util.ArrayDeque; import java.util.Deque; import java.util.Optional; +import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; +import net.minecraft.world.phys.Vec3; public final class LookBehavior extends Behavior implements ILookBehavior { + private static final double ORIGIN_EPSILON = 1.0E-8; + /** * The current look target, may be {@code null}. */ private Target target; /** - * The rotation known to the server. Returned by {@link #getEffectiveRotation()} for use in {@link IPlayerContext}. + * The rotation known to the server. Returned by {@link #getEffectiveRotation()} for use + * in {@link IPlayerContext}. */ private Rotation serverRotation; @@ -55,6 +65,15 @@ public final class LookBehavior extends Behavior implements ILookBehavior { private final Deque smoothYawBuffer; private final Deque smoothPitchBuffer; + private RotationArc interpolationArc; + private Target.Mode interpolationMode; + private Vec3 interpolationOrigin; + private RotationArc pendingRenderArc; + private RotationArc renderArc; + private Rotation previousArcSample; + private Rotation currentArcSample; + private float cachedVisualPartialTicks = Float.NaN; + private Rotation cachedVisualRotation; public LookBehavior(Baritone baritone) { super(baritone); @@ -65,7 +84,28 @@ public LookBehavior(Baritone baritone) { @Override public void updateTarget(Rotation rotation, boolean blockInteract) { - this.target = new Target(rotation, Target.Mode.resolve(ctx, blockInteract)); + this.updateTarget(rotation, blockInteract, false); + } + + @Override + public void updateTarget( + Rotation rotation, + boolean blockInteract, + boolean restartInterpolation) { + this.updateTarget(rotation, blockInteract, restartInterpolation, false); + } + + @Override + public void updateTarget( + Rotation rotation, + boolean blockInteract, + boolean restartInterpolation, + boolean exactRotation) { + this.target = new Target( + rotation, + Target.Mode.resolve(ctx, blockInteract), + restartInterpolation, + exactRotation); } @Override @@ -84,6 +124,14 @@ public void onTick(TickEvent event) { public void onPlayerUpdate(PlayerUpdateEvent event) { if (this.target == null) { + if (event.getState() == EventState.PRE) { + this.resetInterpolation(); + } + return; + } + + if (Baritone.settings().interpolatedLook.value) { + this.updateInterpolatedTarget(event); return; } @@ -95,29 +143,37 @@ public void onPlayerUpdate(PlayerUpdateEvent event) { } this.prevRotation = new Rotation(ctx.player().getYRot(), ctx.player().getXRot()); - final Rotation actual = this.processor.peekRotation(this.target.rotation); + final Rotation actual = this.target.rotation(this.processor); ctx.player().setYRot(actual.getYaw()); ctx.player().setXRot(actual.getPitch()); break; } case POST: { - // Reset the player's rotations back to their original values if (this.prevRotation != null) { + final int smoothLookTicks = Baritone.settings().smoothLookTicks.value; this.smoothYawBuffer.addLast(this.target.rotation.getYaw()); - while (this.smoothYawBuffer.size() > Baritone.settings().smoothLookTicks.value) { + while (this.smoothYawBuffer.size() > smoothLookTicks) { this.smoothYawBuffer.removeFirst(); } this.smoothPitchBuffer.addLast(this.target.rotation.getPitch()); - while (this.smoothPitchBuffer.size() > Baritone.settings().smoothLookTicks.value) { + while (this.smoothPitchBuffer.size() > smoothLookTicks) { this.smoothPitchBuffer.removeFirst(); } if (this.target.mode == Target.Mode.SERVER) { ctx.player().setYRot(this.prevRotation.getYaw()); ctx.player().setXRot(this.prevRotation.getPitch()); - } else if (ctx.player().isFallFlying() ? Baritone.settings().elytraSmoothLook.value : Baritone.settings().smoothLook.value) { - ctx.player().setYRot((float) this.smoothYawBuffer.stream().mapToDouble(d -> d).average().orElse(this.prevRotation.getYaw())); + } else if (ctx.player().isFallFlying() + ? Baritone.settings().elytraSmoothLook.value + : Baritone.settings().smoothLook.value) { + ctx.player().setYRot((float) this.smoothYawBuffer.stream() + .mapToDouble(d -> d) + .average() + .orElse(this.prevRotation.getYaw())); if (ctx.player().isFallFlying()) { - ctx.player().setXRot((float) this.smoothPitchBuffer.stream().mapToDouble(d -> d).average().orElse(this.prevRotation.getPitch())); + ctx.player().setXRot((float) this.smoothPitchBuffer.stream() + .mapToDouble(d -> d) + .average() + .orElse(this.prevRotation.getPitch())); } } //ctx.player().xRotO = prevRotation.getPitch(); @@ -133,6 +189,151 @@ public void onPlayerUpdate(PlayerUpdateEvent event) { } } + private void updateInterpolatedTarget(PlayerUpdateEvent event) { + switch (event.getState()) { + case PRE: { + if (this.target.mode == Target.Mode.NONE) { + this.resetInterpolation(); + return; + } + if (this.target.mode == Target.Mode.SERVER) { + this.clearRenderArc(); + } + + this.prevRotation = new Rotation(ctx.player().getYRot(), ctx.player().getXRot()); + final Rotation start = this.startingRotation(this.target.mode); + final Rotation actual = this.target.rotation(this.processor); + this.updateInterpolation( + start, + actual, + ctx.playerHead(), + this.target.restartInterpolation, + this.target.mode); + break; + } + case POST: { + if (this.prevRotation != null + && this.previousArcSample != null + && this.currentArcSample != null) { + if (this.target.mode == Target.Mode.SERVER) { + this.clearRenderArc(); + this.applyRotation(this.prevRotation); + } else { + this.applyVisualArc(); + } + this.previousArcSample = null; + this.currentArcSample = null; + } else if (this.prevRotation != null && this.target.mode == Target.Mode.SERVER) { + ctx.player().setYRot(this.prevRotation.getYaw()); + ctx.player().setXRot(this.prevRotation.getPitch()); + } + this.prevRotation = null; + this.target = null; + break; + } + default: + break; + } + } + + private Rotation startingRotation(Target.Mode mode) { + if (mode == Target.Mode.CLIENT) { + return this.prevRotation; + } + return ctx.playerRotations(); + } + + private void updateInterpolation( + Rotation start, + Rotation actual, + Vec3 origin, + boolean restartArc, + Target.Mode mode) { + final int interpolationSpeed = Baritone.settings().interpolatedLookLength.value; + Rotation arcStart = start; + + if (this.interpolationArc != null && this.interpolationMode != mode) { + this.interpolationArc = null; + this.interpolationOrigin = null; + this.pendingRenderArc = null; + } + + if (this.interpolationArc != null + && (restartArc || this.interpolationOriginMoved(origin))) { + arcStart = this.interpolationArc.getCurrentRotation(); + this.interpolationArc = null; + } + + if (this.interpolationArc == null) { + this.interpolationArc = RotationArc.fromAngularSpeed( + arcStart, + actual, + interpolationSpeed); + this.interpolationMode = mode; + this.interpolationOrigin = origin; + } + + this.previousArcSample = this.interpolationArc.getCurrentRotation(); + this.currentArcSample = this.interpolationArc.advance(); + this.pendingRenderArc = this.interpolationArc; + this.applyRotation(this.currentArcSample); + this.serverRotation = this.currentArcSample; + if (this.interpolationArc.isComplete()) { + this.interpolationArc = null; + this.interpolationOrigin = null; + } + } + + private boolean interpolationOriginMoved(Vec3 origin) { + return this.interpolationOrigin != null + && this.interpolationOrigin.distanceToSqr(origin) > ORIGIN_EPSILON; + } + + private void applyRotation(Rotation rotation) { + ctx.player().setYRot(rotation.getYaw()); + ctx.player().setXRot(rotation.getPitch()); + } + + private void applyVisualArc() { + this.renderArc = this.pendingRenderArc; + this.pendingRenderArc = null; + this.cachedVisualPartialTicks = Float.NaN; + this.cachedVisualRotation = null; + ctx.player().yRotO = this.previousArcSample.getYaw(); + ctx.player().xRotO = this.previousArcSample.getPitch(); + this.applyRotation(this.currentArcSample); + } + + private void clearRenderArc() { + this.renderArc = null; + this.pendingRenderArc = null; + this.cachedVisualPartialTicks = Float.NaN; + this.cachedVisualRotation = null; + } + + public Rotation getVisualRotation(float partialTicks) { + if (this.renderArc == null) { + return null; + } + if (this.cachedVisualRotation == null || this.cachedVisualPartialTicks != partialTicks) { + this.cachedVisualPartialTicks = partialTicks; + this.cachedVisualRotation = this.renderArc.getCurrentRotation(partialTicks); + } + return this.cachedVisualRotation; + } + + private void resetInterpolation() { + this.interpolationArc = null; + this.interpolationMode = null; + this.interpolationOrigin = null; + this.pendingRenderArc = null; + this.renderArc = null; + this.previousArcSample = null; + this.currentArcSample = null; + this.cachedVisualPartialTicks = Float.NaN; + this.cachedVisualRotation = null; + } + @Override public void onSendPacket(PacketEvent event) { if (!(event.getPacket() instanceof ServerboundMovePlayerPacket)) { @@ -140,7 +341,8 @@ public void onSendPacket(PacketEvent event) { } final ServerboundMovePlayerPacket packet = (ServerboundMovePlayerPacket) event.getPacket(); - if (packet instanceof ServerboundMovePlayerPacket.Rot || packet instanceof ServerboundMovePlayerPacket.PosRot) { + if (packet instanceof ServerboundMovePlayerPacket.Rot + || packet instanceof ServerboundMovePlayerPacket.PosRot) { this.serverRotation = new Rotation(packet.getYRot(0.0f), packet.getXRot(0.0f)); } } @@ -149,11 +351,12 @@ public void onSendPacket(PacketEvent event) { public void onWorldEvent(WorldEvent event) { this.serverRotation = null; this.target = null; + this.resetInterpolation(); } public void pig() { if (this.target != null) { - final Rotation actual = this.processor.peekRotation(this.target.rotation); + final Rotation actual = this.target.rotation(this.processor); ctx.player().setYRot(actual.getYaw()); } } @@ -169,7 +372,7 @@ public Optional getEffectiveRotation() { @Override public void onPlayerRotationMove(RotationMoveEvent event) { if (this.target != null) { - final Rotation actual = this.processor.peekRotation(this.target.rotation); + final Rotation actual = this.target.rotation(this.processor); event.setYaw(actual.getYaw()); event.setPitch(actual.getPitch()); } @@ -214,8 +417,8 @@ public final Rotation peekRotation(final Rotation rotation) { float desiredYaw = rotation.getYaw(); float desiredPitch = rotation.getPitch(); - // In other words, the target doesn't care about the pitch, so it used playerRotations().getPitch() - // and it's safe to adjust it to a normal level + // In other words, the target doesn't care about the pitch, so it used + // playerRotations().getPitch() and it's safe to adjust it to a normal level if (desiredPitch == prev.getPitch()) { desiredPitch = nudgeToLevel(desiredPitch); } @@ -232,8 +435,10 @@ public final Rotation peekRotation(final Rotation rotation) { @Override public final void tick() { // randomLooking - this.randomYawOffset = (this.rand.nextDouble() - 0.5) * Baritone.settings().randomLooking.value; - this.randomPitchOffset = (this.rand.nextDouble() - 0.5) * Baritone.settings().randomLooking.value; + this.randomYawOffset = + (this.rand.nextDouble() - 0.5) * Baritone.settings().randomLooking.value; + this.randomPitchOffset = + (this.rand.nextDouble() - 0.5) * Baritone.settings().randomLooking.value; // randomLooking113 double random = this.rand.nextDouble() - 0.5; @@ -278,7 +483,9 @@ protected Rotation getPrevRotation() { protected abstract Rotation getPrevRotation(); /** - * Nudges the player's pitch to a regular level. (Between {@code -20} and {@code 10}, increments are by {@code 1}) + * Nudges the player's pitch to a regular level. + * + *

Between {@code -20} and {@code 10}, increments are by {@code 1}. */ private float nudgeToLevel(float pitch) { if (pitch < -20) { @@ -302,8 +509,10 @@ private double angleToMouse(float angleDelta) { private float mouseToAngle(double mouseDelta) { // casting float literals to double gets us the precise values used by mc - final double f = ctx.minecraft().options.sensitivity().get() * (double) 0.6f + (double) 0.2f; - return (float) (mouseDelta * f * f * f * 8.0d) * 0.15f; // yes, one double and one float scaling factor + final double f = ctx.minecraft().options.sensitivity().get() + * (double) 0.6f + + (double) 0.2f; + return (float) (mouseDelta * f * f * f * 8.0d) * 0.15f; } } @@ -311,10 +520,22 @@ private static class Target { public final Rotation rotation; public final Mode mode; - - public Target(Rotation rotation, Mode mode) { + public final boolean restartInterpolation; + public final boolean exactRotation; + + public Target( + Rotation rotation, + Mode mode, + boolean restartInterpolation, + boolean exactRotation) { this.rotation = rotation; this.mode = mode; + this.restartInterpolation = restartInterpolation; + this.exactRotation = exactRotation; + } + + public Rotation rotation(AimProcessor processor) { + return this.exactRotation ? this.rotation : processor.peekRotation(this.rotation); } enum Mode { @@ -342,9 +563,11 @@ static Mode resolve(IPlayerContext ctx, boolean blockInteract) { // always need to set angles while flying return settings.elytraFreeLook.value ? SERVER : CLIENT; } else if (settings.freeLook.value) { - // Regardless of if antiCheatCompatibility is enabled, if a blockInteract is requested then the player - // rotation needs to be set somehow, otherwise Baritone will halt since objectMouseOver() will just be - // whatever the player is mousing over visually. Let's just settle for setting it silently. + // Regardless of if antiCheatCompatibility is enabled, if a + // blockInteract is requested then the player rotation needs to be + // set somehow, otherwise Baritone will halt since objectMouseOver() + // will just be whatever the player is mousing over visually. Let's + // just settle for setting it silently. if (blockInteract) { return blockFreeLook ? SERVER : CLIENT; } @@ -356,4 +579,5 @@ static Mode resolve(IPlayerContext ctx, boolean blockInteract) { } } } + } diff --git a/src/main/java/baritone/behavior/PathingBehavior.java b/src/main/java/baritone/behavior/PathingBehavior.java index d8df46681..a933f55b5 100644 --- a/src/main/java/baritone/behavior/PathingBehavior.java +++ b/src/main/java/baritone/behavior/PathingBehavior.java @@ -36,6 +36,7 @@ import baritone.process.ElytraProcess; import baritone.utils.PathRenderer; import baritone.utils.PathingCommandContext; +import baritone.utils.PlayerControlGuard; import baritone.utils.pathing.Favoring; import java.util.ArrayList; import java.util.Comparator; @@ -100,6 +101,14 @@ public void onTick(TickEvent event) { return; } + if (!PlayerControlGuard.canControl(ctx)) { + pausedThisTick = true; + unpausedLastTick = false; + baritone.getInputOverrideHandler().clearAllKeys(); + baritone.getInputOverrideHandler().getBlockBreakHelper().stopBreakingBlock(); + baritone.getPathingControlManager().preTick(); + return; + } expectedSegmentStart = pathStart(); baritone.getPathingControlManager().preTick(); tickPath(); diff --git a/src/main/java/baritone/pathing/movement/Movement.java b/src/main/java/baritone/pathing/movement/Movement.java index 739c8ee89..b4a900c93 100644 --- a/src/main/java/baritone/pathing/movement/Movement.java +++ b/src/main/java/baritone/pathing/movement/Movement.java @@ -30,6 +30,7 @@ import net.minecraft.core.Direction; import net.minecraft.world.entity.item.FallingBlockEntity; import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; public abstract class Movement implements IMovement, MovementHelper { @@ -113,6 +114,37 @@ protected boolean playerInValidPosition() { return getValidPositions().contains(ctx.playerFeet()) || getValidPositions().contains(((PathingBehavior) baritone.getPathingBehavior()).pathStart()); } + protected boolean leftClickBlock(MovementState state, BlockPos pos) { + if (!ctx.isLookingAt(pos)) { + return false; + } + state.setBlockBreakTarget(pos) + .setInput(Input.CLICK_LEFT, true); + return true; + } + + protected boolean leftClickSelectedBlock(MovementState state) { + Optional selected = ctx.getSelectedBlock(); + if (!selected.isPresent()) { + return false; + } + state.setBlockBreakTarget(selected.get()) + .setInput(Input.CLICK_LEFT, true); + return true; + } + + protected boolean rightClickBlock(MovementState state, BlockPos pos) { + Optional selected = ctx.getSelectedBlockHitResult() + .filter(hit -> hit.getBlockPos().equals(pos)); + if (!selected.isPresent()) { + return false; + } + BlockHitResult hit = selected.get(); + state.setBlockPlaceTarget(hit.getBlockPos(), hit.getDirection()) + .setInput(Input.CLICK_RIGHT, true); + return true; + } + /** * Handles the execution of the latest Movement * State, and offers a Status to the calling class. @@ -127,20 +159,32 @@ public MovementStatus update() { currentState.setInput(Input.JUMP, true); } if (ctx.player().isInWall()) { - ctx.getSelectedBlock().ifPresent(pos -> MovementHelper.switchToBestToolFor(ctx, BlockStateInterface.get(ctx, pos))); + ctx.getSelectedBlock().ifPresent(pos -> { + MovementHelper.switchToBestToolFor(ctx, BlockStateInterface.get(ctx, pos)); + currentState.setBlockBreakTarget(pos); + }); currentState.setInput(Input.CLICK_LEFT, true); } // If the movement target has to force the new rotations, or we aren't using silent move, then force the rotations - currentState.getTarget().getRotation().ifPresent(rotation -> + MovementState.MovementTarget target = currentState.getTarget(); + target.getRotation().ifPresent(rotation -> baritone.getLookBehavior().updateTarget( rotation, - currentState.getTarget().hasToForceRotations())); + target.hasToForceRotations(), + target.shouldRestartInterpolation(), + target.isExactRotation())); baritone.getInputOverrideHandler().clearAllKeys(); + currentState.getBlockBreakTarget().ifPresent( + baritone.getInputOverrideHandler()::setBlockBreakTarget); + currentState.getBlockPlaceTarget().ifPresent(pos -> + currentState.getBlockPlaceSide().ifPresent(side -> + baritone.getInputOverrideHandler().setBlockPlaceTarget(pos, side))); currentState.getInputStates().forEach((input, forced) -> { baritone.getInputOverrideHandler().setInputForceState(input, forced); }); currentState.getInputStates().clear(); + currentState.clearBlockInteractionTargets(); // If the current status indicates a completed movement if (currentState.getStatus().isComplete()) { @@ -162,13 +206,24 @@ protected boolean prepared(MovementState state) { if (!MovementHelper.canWalkThrough(ctx, blockPos)) { // can't break air, so don't try somethingInTheWay = true; MovementHelper.switchToBestToolFor(ctx, BlockStateInterface.get(ctx, blockPos)); - Optional reachable = RotationUtils.reachable(ctx, blockPos, ctx.playerController().getBlockReachDistance()); + if (baritone.getInputOverrideHandler().isBreakingBlock(blockPos)) { + state.setTarget(new MovementState.MovementTarget( + ctx.playerRotations(), + true, + true, + true)) + .setBlockBreakTarget(blockPos) + .setInput(Input.CLICK_LEFT, true); + return false; + } + Optional reachable = RotationUtils.reachable( + ctx, + blockPos, + ctx.playerController().getBlockReachDistance()); if (reachable.isPresent()) { Rotation rotTowardsBlock = reachable.get(); state.setTarget(new MovementState.MovementTarget(rotTowardsBlock, true)); - if (ctx.isLookingAt(blockPos) || ctx.playerRotations().isReallyCloseTo(rotTowardsBlock)) { - state.setInput(Input.CLICK_LEFT, true); - } + leftClickBlock(state, blockPos); return false; } //get rekt minecraft diff --git a/src/main/java/baritone/pathing/movement/MovementHelper.java b/src/main/java/baritone/pathing/movement/MovementHelper.java index 8b513f143..fd071daa4 100644 --- a/src/main/java/baritone/pathing/movement/MovementHelper.java +++ b/src/main/java/baritone/pathing/movement/MovementHelper.java @@ -821,14 +821,16 @@ static PlaceResult attemptToPlaceABlock(MovementState state, IBaritone baritone, } } } - if (ctx.getSelectedBlock().isPresent()) { - BlockPos selectedBlock = ctx.getSelectedBlock().get(); - Direction side = ((BlockHitResult) ctx.objectMouseOver()).getDirection(); + Optional selected = ctx.getSelectedBlockHitResult(); + if (selected.isPresent()) { + BlockPos selectedBlock = selected.get().getBlockPos(); + Direction side = selected.get().getDirection(); // only way for selectedBlock.equals(placeAt) to be true is if it's replaceable if (selectedBlock.equals(placeAt) || (MovementHelper.canPlaceAgainst(ctx, selectedBlock) && selectedBlock.relative(side).equals(placeAt))) { if (wouldSneak) { state.setInput(Input.SNEAK, true); } + state.setBlockPlaceTarget(selectedBlock, side); ((Baritone) baritone).getInventoryBehavior().selectThrowawayForLocation(true, placeAt.getX(), placeAt.getY(), placeAt.getZ()); return PlaceResult.READY_TO_PLACE; } diff --git a/src/main/java/baritone/pathing/movement/MovementState.java b/src/main/java/baritone/pathing/movement/MovementState.java index 73539698a..da2ed9da9 100644 --- a/src/main/java/baritone/pathing/movement/MovementState.java +++ b/src/main/java/baritone/pathing/movement/MovementState.java @@ -20,6 +20,8 @@ import baritone.api.pathing.movement.MovementStatus; import baritone.api.utils.Rotation; import baritone.api.utils.input.Input; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import java.util.HashMap; import java.util.Map; @@ -30,6 +32,9 @@ public class MovementState { private MovementStatus status; private MovementTarget target = new MovementTarget(); private final Map inputState = new HashMap<>(); + private BlockPos blockBreakTarget; + private BlockPos blockPlaceTarget; + private Direction blockPlaceSide; public MovementState setStatus(MovementStatus status) { this.status = status; @@ -58,6 +63,35 @@ public Map getInputStates() { return this.inputState; } + public MovementState setBlockBreakTarget(BlockPos pos) { + this.blockBreakTarget = pos; + return this; + } + + public Optional getBlockBreakTarget() { + return Optional.ofNullable(this.blockBreakTarget); + } + + public MovementState setBlockPlaceTarget(BlockPos pos, Direction side) { + this.blockPlaceTarget = pos; + this.blockPlaceSide = side; + return this; + } + + public Optional getBlockPlaceTarget() { + return Optional.ofNullable(this.blockPlaceTarget); + } + + public Optional getBlockPlaceSide() { + return Optional.ofNullable(this.blockPlaceSide); + } + + public void clearBlockInteractionTargets() { + this.blockBreakTarget = null; + this.blockPlaceTarget = null; + this.blockPlaceSide = null; + } + public static class MovementTarget { /** @@ -68,17 +102,38 @@ public static class MovementTarget { /** * Whether or not this target must force rotations. *

- * {@code true} if we're trying to place or break blocks, {@code false} if we're trying to look at the movement location + * {@code true} if we're trying to place or break blocks, {@code false} + * if we're trying to look at the movement location */ private boolean forceRotations; + private boolean restartInterpolation; + private boolean exactRotation; + public MovementTarget() { this(null, false); } public MovementTarget(Rotation rotation, boolean forceRotations) { + this(rotation, forceRotations, false); + } + + public MovementTarget( + Rotation rotation, + boolean forceRotations, + boolean restartInterpolation) { + this(rotation, forceRotations, restartInterpolation, false); + } + + public MovementTarget( + Rotation rotation, + boolean forceRotations, + boolean restartInterpolation, + boolean exactRotation) { this.rotation = rotation; this.forceRotations = forceRotations; + this.restartInterpolation = restartInterpolation; + this.exactRotation = exactRotation; } public final Optional getRotation() { @@ -88,5 +143,13 @@ public final Optional getRotation() { public boolean hasToForceRotations() { return this.forceRotations; } + + public boolean shouldRestartInterpolation() { + return this.restartInterpolation; + } + + public boolean isExactRotation() { + return this.exactRotation; + } } } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementFall.java b/src/main/java/baritone/pathing/movement/movements/MovementFall.java index c537fc2ca..9700a4e82 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementFall.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementFall.java @@ -110,8 +110,8 @@ public MovementState updateState(MovementState state) { targetRotation = new Rotation(toDest.getYaw(), 90.0F); - if (ctx.isLookingAt(dest) || ctx.isLookingAt(dest.below())) { - state.setInput(Input.CLICK_RIGHT, true); + if (!rightClickBlock(state, dest)) { + rightClickBlock(state, dest.below()); } } } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java index bbda3b982..450e45093 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementPillar.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementPillar.java @@ -221,10 +221,11 @@ public MovementState updateState(MovementState state) { .map(rot -> new MovementState.MovementTarget(rot, true)) .ifPresent(state::setTarget); state.setInput(Input.JUMP, false); // breaking is like 5x slower when you're jumping - state.setInput(Input.CLICK_LEFT, true); + leftClickBlock(state, src); blockIsThere = false; - } else if (ctx.player().isCrouching() && (ctx.isLookingAt(src.below()) || ctx.isLookingAt(src)) && ctx.player().position().y > dest.getY() + 0.1) { - state.setInput(Input.CLICK_RIGHT, true); + } else if (ctx.player().isCrouching() && ctx.player().position().y > dest.getY() + 0.1) { + rightClickBlock(state, src.below()); + rightClickBlock(state, src); } } } diff --git a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java index bbfd67d4c..cd6ff8449 100644 --- a/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java +++ b/src/main/java/baritone/pathing/movement/movements/MovementTraverse.java @@ -228,8 +228,15 @@ public MovementState updateState(MovementState state) { boolean canOpen = !(Blocks.IRON_DOOR.equals(pb0.getBlock()) || Blocks.IRON_DOOR.equals(pb1.getBlock())); if (notPassable && canOpen) { - return state.setTarget(new MovementState.MovementTarget(RotationUtils.calcRotationFromVec3d(ctx.playerHead(), VecUtils.calculateBlockCenter(ctx.world(), positionsToBreak[0]), ctx.playerRotations()), true)) - .setInput(Input.CLICK_RIGHT, true); + BlockPos blocked = pb0.getBlock() instanceof DoorBlock ? positionsToBreak[0] : positionsToBreak[1]; + state.setTarget(new MovementState.MovementTarget( + RotationUtils.calcRotationFromVec3d( + ctx.playerHead(), + VecUtils.calculateBlockCenter(ctx.world(), blocked), + ctx.playerRotations()), + true)); + rightClickBlock(state, blocked); + return state; } } @@ -240,7 +247,9 @@ public MovementState updateState(MovementState state) { if (blocked != null) { Optional rotation = RotationUtils.reachable(ctx, blocked); if (rotation.isPresent()) { - return state.setTarget(new MovementState.MovementTarget(rotation.get(), true)).setInput(Input.CLICK_RIGHT, true); + state.setTarget(new MovementState.MovementTarget(rotation.get(), true)); + rightClickBlock(state, blocked); + return state; } } } @@ -316,7 +325,7 @@ public MovementState updateState(MovementState state) { } } else if (ctx.playerRotations().isReallyCloseTo(state.getTarget().rotation)) { // well i guess theres something in the way - return state.setInput(Input.CLICK_LEFT, true); + leftClickSelectedBlock(state); } return state; } @@ -342,12 +351,12 @@ public MovementState updateState(MovementState state) { } else { state.setTarget(new MovementState.MovementTarget(backToFace, true)); } - if (ctx.isLookingAt(goalLook)) { - return state.setInput(Input.CLICK_RIGHT, true); // wait to right click until we are able to place + if (rightClickBlock(state, goalLook)) { + return state; // wait to right click until we are able to place } // Out.log("Trying to look at " + goalLook + ", actually looking at" + Baritone.whatAreYouLookingAt()); if (ctx.playerRotations().isReallyCloseTo(state.getTarget().rotation)) { - state.setInput(Input.CLICK_LEFT, true); + leftClickSelectedBlock(state); } return state; } diff --git a/src/main/java/baritone/process/BackfillProcess.java b/src/main/java/baritone/process/BackfillProcess.java index af191ae1e..a629bb411 100644 --- a/src/main/java/baritone/process/BackfillProcess.java +++ b/src/main/java/baritone/process/BackfillProcess.java @@ -77,6 +77,10 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { case NO_OPTION: continue; case READY_TO_PLACE: + fake.getBlockPlaceTarget().ifPresent(pos -> + fake.getBlockPlaceSide().ifPresent(side -> + baritone.getInputOverrideHandler() + .setBlockPlaceTarget(pos, side))); baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); case ATTEMPTING: diff --git a/src/main/java/baritone/process/BuilderProcess.java b/src/main/java/baritone/process/BuilderProcess.java index b99d41e0a..0c6726bfa 100644 --- a/src/main/java/baritone/process/BuilderProcess.java +++ b/src/main/java/baritone/process/BuilderProcess.java @@ -103,6 +103,7 @@ public final class BuilderProcess extends BaritoneProcessHelper implements IBuil private int layer; private int numRepeats; private List approxPlaceable; + private Placement heldPlacement; public int stopAtHeight = 0; public BuilderProcess(Baritone baritone) { @@ -169,6 +170,7 @@ public boolean partOfMask(int x, int y, int z, BlockState current) { this.numRepeats = 0; this.observedCompleted = new LongOpenHashSet(); this.incorrectPositions = null; + this.heldPlacement = null; } public void resume() { @@ -177,6 +179,7 @@ public void resume() { public void pause() { paused = true; + this.heldPlacement = null; } @Override @@ -312,12 +315,19 @@ private Optional> toBreakNearPlayer(BuilderCalcu public static class Placement { private final int hotbarSelection; + private final BlockPos target; private final BlockPos placeAgainst; private final Direction side; private final Rotation rot; - public Placement(int hotbarSelection, BlockPos placeAgainst, Direction side, Rotation rot) { + public Placement( + int hotbarSelection, + BlockPos target, + BlockPos placeAgainst, + Direction side, + Rotation rot) { this.hotbarSelection = hotbarSelection; + this.target = target; this.placeAgainst = placeAgainst; this.side = side; this.rot = rot; @@ -353,6 +363,34 @@ private Optional searchForPlacables(BuilderCalculationContext bcc, Li return Optional.empty(); } + private Optional revalidateHeldPlacement(BuilderCalculationContext bcc) { + if (this.heldPlacement == null) { + return Optional.empty(); + } + BlockPos target = this.heldPlacement.target; + BlockState current = bcc.bsi.get0(target); + BlockState desired = bcc.getSchematic(target.getX(), target.getY(), target.getZ(), current); + if (desired == null + || !MovementHelper.isReplaceable( + target.getX(), + target.getY(), + target.getZ(), + current, + bcc.bsi) + || valid(current, desired, false)) { + this.heldPlacement = null; + return Optional.empty(); + } + Optional placement = possibleToPlace( + desired, + target.getX(), + target.getY(), + target.getZ(), + bcc.bsi); + this.heldPlacement = placement.orElse(null); + return placement; + } + public boolean placementPlausible(BlockPos pos, BlockState state) { VoxelShape voxelshape = state.getCollisionShape(ctx.world(), pos); return voxelshape.isEmpty() || ctx.world().isUnobstructed(null, voxelshape.move(pos.getX(), pos.getY(), pos.getZ())); @@ -386,7 +424,12 @@ private Optional possibleToPlace(BlockState toPlace, int x, int y, in 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(), + new BetterBlockPos(x, y, z), + placeAgainstPos, + against.getOpposite(), + rot)); } } } @@ -518,6 +561,7 @@ public int lengthZ() { if (Baritone.settings().buildInLayers.value && layer * Baritone.settings().layerHeight.value < stopAtHeight) { logDirect("Starting layer " + layer); layer++; + this.heldPlacement = null; return onTick(calcFailed, isSafeToCancel, recursions + 1); } Vec3i repeat = Baritone.settings().buildRepeat.value; @@ -534,6 +578,7 @@ public int lengthZ() { // build repeat time layer = 0; origin = new BlockPos(origin).offset(repeat); + this.heldPlacement = null; if (!Baritone.settings().buildRepeatSneaky.value) { schematic.reset(); } @@ -546,11 +591,11 @@ public int lengthZ() { Optional> toBreak = toBreakNearPlayer(bcc); if (toBreak.isPresent() && isSafeToCancel && ctx.player().isOnGround()) { + this.heldPlacement = null; // we'd like to pause to break this block // only change look direction if it's safe (don't want to fuck up an in progress parkour for example Rotation rot = toBreak.get().getB(); BetterBlockPos pos = toBreak.get().getA(); - baritone.getLookBehavior().updateTarget(rot, true); MovementHelper.switchToBestToolFor(ctx, bcc.get(pos)); if (ctx.player().isCrouching()) { // really horrible bug where a block is visible for breaking while sneaking but not otherwise @@ -558,23 +603,39 @@ public int lengthZ() { // and is unable since it's unsneaked in the intermediary tick baritone.getInputOverrideHandler().setInputForceState(Input.SNEAK, true); } - if (ctx.isLookingAt(pos) || ctx.playerRotations().isReallyCloseTo(rot)) { + if (baritone.getInputOverrideHandler().isBreakingBlock(pos)) { + baritone.getLookBehavior().updateTarget(ctx.playerRotations(), true, true, true); + baritone.getInputOverrideHandler().setBlockBreakTarget(pos); + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); + } else { + baritone.getLookBehavior().updateTarget(rot, true); + } + if (ctx.isLookingAt(pos)) { + baritone.getInputOverrideHandler().setBlockBreakTarget(pos); baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } List desirableOnHotbar = new ArrayList<>(); - Optional toPlace = searchForPlacables(bcc, desirableOnHotbar); + Optional toPlace = revalidateHeldPlacement(bcc); + if (!toPlace.isPresent()) { + toPlace = searchForPlacables(bcc, desirableOnHotbar); + } if (toPlace.isPresent() && isSafeToCancel && ctx.player().isOnGround() && ticks <= 0) { + this.heldPlacement = toPlace.get(); 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().setBlockPlaceTarget( + toPlace.get().placeAgainst, + toPlace.get().side); + if (ctx.isLookingAt(toPlace.get().placeAgainst, toPlace.get().side)) { baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); } return new PathingCommand(null, PathingCommandType.CANCEL_AND_SET_GOAL); } + this.heldPlacement = null; if (Baritone.settings().allowInventory.value) { ArrayList usefulSlots = new ArrayList<>(); @@ -992,6 +1053,7 @@ public void onLostControl() { numRepeats = 0; paused = false; observedCompleted = null; + heldPlacement = null; } @Override diff --git a/src/main/java/baritone/process/FarmProcess.java b/src/main/java/baritone/process/FarmProcess.java index 05b893a27..c6141335a 100644 --- a/src/main/java/baritone/process/FarmProcess.java +++ b/src/main/java/baritone/process/FarmProcess.java @@ -104,6 +104,22 @@ public FarmProcess(Baritone baritone) { super(baritone); } + private void leftClickBlock(BlockPos pos) { + baritone.getInputOverrideHandler().setBlockBreakTarget(pos); + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); + } + + private void rightClickBlock(BlockPos pos, Direction side) { + baritone.getInputOverrideHandler().setBlockPlaceTarget(pos, side); + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); + } + + private void rightClickSelectedBlock(BlockPos pos) { + ctx.getSelectedBlockHitResult() + .filter(hit -> hit.getBlockPos().equals(pos)) + .ifPresent(hit -> rightClickBlock(hit.getBlockPos(), hit.getDirection())); + } + @Override public boolean isActive() { return active; @@ -278,7 +294,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { baritone.getLookBehavior().updateTarget(rot.get(), true); MovementHelper.switchToBestToolFor(ctx, ctx.world().getBlockState(pos)); if (ctx.isLookingAt(pos)) { - baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); + leftClickBlock(pos); } return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); } @@ -295,8 +311,8 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), rot.get(), blockReachDistance); if (result instanceof BlockHitResult && ((BlockHitResult) result).getDirection() == Direction.UP) { baritone.getLookBehavior().updateTarget(rot.get(), true); - if (ctx.isLookingAt(pos)) { - baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); + if (ctx.isLookingAt(pos, Direction.UP)) { + rightClickBlock(pos, Direction.UP); } return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); } @@ -316,8 +332,8 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { HitResult result = RayTraceUtils.rayTraceTowards(ctx.player(), rot.get(), blockReachDistance); if (result instanceof BlockHitResult && ((BlockHitResult) result).getDirection() == dir) { baritone.getLookBehavior().updateTarget(rot.get(), true); - if (ctx.isLookingAt(pos)) { - baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); + if (ctx.isLookingAt(pos, dir)) { + rightClickBlock(pos, dir); } return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); } @@ -332,7 +348,7 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { if (rot.isPresent() && isSafeToCancel && baritone.getInventoryBehavior().throwaway(true, this::isBoneMeal)) { baritone.getLookBehavior().updateTarget(rot.get(), true); if (ctx.isLookingAt(pos)) { - baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); + rightClickSelectedBlock(pos); } return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); } diff --git a/src/main/java/baritone/process/GetToBlockProcess.java b/src/main/java/baritone/process/GetToBlockProcess.java index 1352232d4..6a0aece17 100644 --- a/src/main/java/baritone/process/GetToBlockProcess.java +++ b/src/main/java/baritone/process/GetToBlockProcess.java @@ -35,6 +35,7 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; import java.util.*; @@ -214,7 +215,13 @@ private boolean rightClick() { Optional reachable = RotationUtils.reachable(ctx, pos, ctx.playerController().getBlockReachDistance()); if (reachable.isPresent()) { baritone.getLookBehavior().updateTarget(reachable.get(), true); - if (knownLocations.contains(ctx.getSelectedBlock().orElse(null))) { + Optional selected = ctx.getSelectedBlockHitResult() + .filter(hit -> knownLocations.contains(hit.getBlockPos())); + if (selected.isPresent()) { + BlockHitResult hit = selected.get(); + baritone.getInputOverrideHandler().setBlockPlaceTarget( + hit.getBlockPos(), + hit.getDirection()); baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_RIGHT, true); // TODO find some way to right click even if we're in an ESC menu System.out.println(ctx.player().containerMenu); if (!(ctx.player().containerMenu instanceof InventoryMenu)) { diff --git a/src/main/java/baritone/process/MineProcess.java b/src/main/java/baritone/process/MineProcess.java index ff905c02f..bb8dcadb0 100644 --- a/src/main/java/baritone/process/MineProcess.java +++ b/src/main/java/baritone/process/MineProcess.java @@ -124,11 +124,22 @@ public PathingCommand onTick(boolean calcFailed, boolean isSafeToCancel) { BlockPos pos = shaft.get(); BlockState state = baritone.bsi.get0(pos); if (!MovementHelper.avoidBreaking(baritone.bsi, pos.getX(), pos.getY(), pos.getZ(), state)) { + MovementHelper.switchToBestToolFor(ctx, ctx.world().getBlockState(pos)); + if (isSafeToCancel && baritone.getInputOverrideHandler().isBreakingBlock(pos)) { + baritone.getLookBehavior().updateTarget( + ctx.playerRotations(), + true, + true, + true); + baritone.getInputOverrideHandler().setBlockBreakTarget(pos); + baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); + return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); + } Optional rot = RotationUtils.reachable(ctx, pos); if (rot.isPresent() && isSafeToCancel) { baritone.getLookBehavior().updateTarget(rot.get(), true); - MovementHelper.switchToBestToolFor(ctx, ctx.world().getBlockState(pos)); - if (ctx.isLookingAt(pos) || ctx.playerRotations().isReallyCloseTo(rot.get())) { + if (ctx.isLookingAt(pos)) { + baritone.getInputOverrideHandler().setBlockBreakTarget(pos); baritone.getInputOverrideHandler().setInputForceState(Input.CLICK_LEFT, true); } return new PathingCommand(null, PathingCommandType.REQUEST_PAUSE); diff --git a/src/main/java/baritone/utils/BaritoneMath.java b/src/main/java/baritone/utils/BaritoneMath.java index be546f248..aa00b5c47 100644 --- a/src/main/java/baritone/utils/BaritoneMath.java +++ b/src/main/java/baritone/utils/BaritoneMath.java @@ -34,4 +34,11 @@ public static int fastFloor(final double v) { public static int fastCeil(final double v) { return FLOOR_DOUBLE_I - (int) (FLOOR_DOUBLE_D - v); } + + public static double normalize(double value, double maxValue) { + if (maxValue == 0) { + return 0; + } + return value / maxValue; + } } diff --git a/src/main/java/baritone/utils/BlockBreakHelper.java b/src/main/java/baritone/utils/BlockBreakHelper.java index 0c5cf6f00..8132454ba 100644 --- a/src/main/java/baritone/utils/BlockBreakHelper.java +++ b/src/main/java/baritone/utils/BlockBreakHelper.java @@ -20,6 +20,7 @@ import baritone.api.BaritoneAPI; import baritone.api.utils.IPlayerContext; import baritone.utils.accessor.IPlayerControllerMP; +import net.minecraft.core.BlockPos; import net.minecraft.world.InteractionHand; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult; @@ -34,6 +35,7 @@ public final class BlockBreakHelper { private final IPlayerContext ctx; private boolean wasHitting; + private BlockPos breakingBlock; private int breakDelayTimer = 0; BlockBreakHelper(IPlayerContext ctx) { @@ -45,26 +47,40 @@ public void stopBreakingBlock() { if (ctx.player() != null && wasHitting) { ctx.playerController().setHittingBlock(false); ctx.playerController().resetBlockRemoving(); - wasHitting = false; + clearBreakingBlock(); } } - public void tick(boolean isLeftClick) { + public boolean isBreakingBlock(BlockPos pos) { + return wasHitting && pos.equals(breakingBlock) && ctx.isLookingAt(pos); + } + + public void tick(boolean isLeftClick, BlockPos target) { if (breakDelayTimer > 0) { breakDelayTimer--; return; } HitResult trace = ctx.objectMouseOver(); boolean isBlockTrace = trace != null && trace.getType() == HitResult.Type.BLOCK; + if (target != null + && (!isBlockTrace || !((BlockHitResult) trace).getBlockPos().equals(target))) { + clearBreakingBlock(); + return; + } if (isLeftClick && isBlockTrace) { + BlockHitResult blockTrace = (BlockHitResult) trace; ctx.playerController().setHittingBlock(wasHitting); if (ctx.playerController().hasBrokenBlock()) { ctx.playerController().syncHeldItem(); - ctx.playerController().clickBlock(((BlockHitResult) trace).getBlockPos(), ((BlockHitResult) trace).getDirection()); + ctx.playerController().clickBlock( + blockTrace.getBlockPos(), + blockTrace.getDirection()); ctx.player().swing(InteractionHand.MAIN_HAND); } else { - if (ctx.playerController().onPlayerDamageBlock(((BlockHitResult) trace).getBlockPos(), ((BlockHitResult) trace).getDirection())) { + if (ctx.playerController().onPlayerDamageBlock( + blockTrace.getBlockPos(), + blockTrace.getDirection())) { ctx.player().swing(InteractionHand.MAIN_HAND); } if (ctx.playerController().hasBrokenBlock()) { // block broken this tick @@ -76,12 +92,22 @@ public void tick(boolean isLeftClick) { } // if true, we're breaking a block. if false, we broke the block this tick wasHitting = !ctx.playerController().hasBrokenBlock(); + if (wasHitting) { + breakingBlock = blockTrace.getBlockPos(); + } else { + clearBreakingBlock(); + } // this value will be reset by the MC client handling mouse keys // since we're not spoofing the click keybind to the client, the client will stop the break if isDestroyingBlock is true // we store and restore this value on the next tick to determine if we're breaking a block ctx.playerController().setHittingBlock(false); } else { - wasHitting = false; + clearBreakingBlock(); } } + + private void clearBreakingBlock() { + wasHitting = false; + breakingBlock = null; + } } diff --git a/src/main/java/baritone/utils/BlockPlaceHelper.java b/src/main/java/baritone/utils/BlockPlaceHelper.java index ca8d50523..2475485c3 100644 --- a/src/main/java/baritone/utils/BlockPlaceHelper.java +++ b/src/main/java/baritone/utils/BlockPlaceHelper.java @@ -19,6 +19,8 @@ import baritone.Baritone; import baritone.api.utils.IPlayerContext; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.phys.BlockHitResult; @@ -35,22 +37,70 @@ public class BlockPlaceHelper { this.ctx = playerContext; } - public void tick(boolean rightClickRequested) { - if (rightClickTimer > 0) { - rightClickTimer--; + public void tick(boolean rightClickRequested, BlockPos target, Direction side) { + if (this.isCoolingDown()) { return; } - HitResult mouseOver = ctx.objectMouseOver(); - if (!rightClickRequested || ctx.player().isHandsBusy() || mouseOver == null || mouseOver.getType() != HitResult.Type.BLOCK) { + + BlockHitResult blockHit = this.targetedBlockHit(rightClickRequested, target, side); + if (blockHit == null) { return; } + rightClickTimer = Baritone.settings().rightClickSpeed.value - BASE_PLACE_DELAY; + this.tryRightClick(blockHit); + } + + private boolean isCoolingDown() { + if (rightClickTimer > 0) { + rightClickTimer--; + return true; + } + return false; + } + + private BlockHitResult targetedBlockHit( + boolean rightClickRequested, + BlockPos target, + Direction side) { + if (!rightClickRequested || ctx.player().isHandsBusy()) { + return null; + } + + HitResult mouseOver = ctx.objectMouseOver(); + if (mouseOver == null + || mouseOver.getType() != HitResult.Type.BLOCK) { + return null; + } + + BlockHitResult blockHit = (BlockHitResult) mouseOver; + if (!this.matchesTarget(blockHit, target, side)) { + return null; + } + + return blockHit; + } + + private boolean matchesTarget(BlockHitResult blockHit, BlockPos target, Direction side) { + return target == null + || (blockHit.getBlockPos().equals(target) && blockHit.getDirection() == side); + } + + private void tryRightClick(BlockHitResult blockHit) { for (InteractionHand hand : InteractionHand.values()) { - if (ctx.playerController().processRightClickBlock(ctx.player(), ctx.world(), hand, (BlockHitResult) mouseOver) == InteractionResult.SUCCESS) { + if (ctx.playerController().processRightClickBlock( + ctx.player(), + ctx.world(), + hand, + blockHit) == InteractionResult.SUCCESS) { ctx.player().swing(hand); return; } - if (!ctx.player().getItemInHand(hand).isEmpty() && ctx.playerController().processRightClick(ctx.player(), ctx.world(), hand) == InteractionResult.SUCCESS) { + if (!ctx.player().getItemInHand(hand).isEmpty() + && ctx.playerController().processRightClick( + ctx.player(), + ctx.world(), + hand) == InteractionResult.SUCCESS) { return; } } diff --git a/src/main/java/baritone/utils/InputOverrideHandler.java b/src/main/java/baritone/utils/InputOverrideHandler.java index 38a32f515..5a47264a1 100755 --- a/src/main/java/baritone/utils/InputOverrideHandler.java +++ b/src/main/java/baritone/utils/InputOverrideHandler.java @@ -24,6 +24,9 @@ import baritone.api.utils.input.Input; import baritone.behavior.Behavior; import net.minecraft.client.player.KeyboardInput; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import java.util.HashMap; import java.util.Map; @@ -45,6 +48,9 @@ public final class InputOverrideHandler extends Behavior implements IInputOverri private final BlockBreakHelper blockBreakHelper; private final BlockPlaceHelper blockPlaceHelper; + private BlockPos blockBreakTarget; + private BlockPos blockPlaceTarget; + private Direction blockPlaceSide; public InputOverrideHandler(Baritone baritone) { super(baritone); @@ -80,6 +86,25 @@ public final void setInputForceState(Input input, boolean forced) { @Override public final void clearAllKeys() { this.inputForceStateMap.clear(); + this.blockBreakTarget = null; + this.blockPlaceTarget = null; + this.blockPlaceSide = null; + } + + @Override + public boolean isBreakingBlock(BlockPos pos) { + return this.blockBreakHelper.isBreakingBlock(pos); + } + + @Override + public void setBlockBreakTarget(BlockPos pos) { + this.blockBreakTarget = pos; + } + + @Override + public void setBlockPlaceTarget(BlockPos pos, Direction side) { + this.blockPlaceTarget = pos; + this.blockPlaceSide = side; } @Override @@ -87,11 +112,18 @@ public final void onTick(TickEvent event) { if (event.getType() == TickEvent.Type.OUT) { return; } + if (!PlayerControlGuard.canControl(ctx)) { + stopControllingPlayer(); + return; + } if (isInputForcedDown(Input.CLICK_LEFT)) { setInputForceState(Input.CLICK_RIGHT, false); } - blockBreakHelper.tick(isInputForcedDown(Input.CLICK_LEFT)); - blockPlaceHelper.tick(isInputForcedDown(Input.CLICK_RIGHT)); + blockBreakHelper.tick(isInputForcedDown(Input.CLICK_LEFT), blockBreakTarget); + blockPlaceHelper.tick( + isInputForcedDown(Input.CLICK_RIGHT), + blockPlaceTarget, + blockPlaceSide); if (inControl()) { if (ctx.player().input.getClass() != PlayerMovementInput.class) { @@ -106,6 +138,26 @@ public final void onTick(TickEvent event) { // gotta do it this way, or else it constantly thinks you're beginning a double tap W sprint lol } + @Override + public void onPlayerDeath() { + stopControllingPlayer(); + } + + private void stopControllingPlayer() { + clearAllKeys(); + blockBreakHelper.stopBreakingBlock(); + restorePlayerInput(); + } + + private void restorePlayerInput() { + LocalPlayer player = ctx.player(); + if (player != null + && player.input != null + && player.input.getClass() == PlayerMovementInput.class) { + player.input = new KeyboardInput(ctx.minecraft().options); + } + } + private boolean inControl() { for (Input input : new Input[]{Input.MOVE_FORWARD, Input.MOVE_BACK, Input.MOVE_LEFT, Input.MOVE_RIGHT, Input.SNEAK, Input.JUMP}) { if (isInputForcedDown(input)) { diff --git a/src/main/java/baritone/utils/PathingControlManager.java b/src/main/java/baritone/utils/PathingControlManager.java index 2205d62e7..1d86ac1d9 100644 --- a/src/main/java/baritone/utils/PathingControlManager.java +++ b/src/main/java/baritone/utils/PathingControlManager.java @@ -86,6 +86,10 @@ public Optional mostRecentCommand() { public void preTick() { inControlLastTick = inControlThisTick; inControlThisTick = null; + if (!PlayerControlGuard.canControl(baritone.getPlayerContext())) { + command = null; + return; + } PathingBehavior p = baritone.getPathingBehavior(); command = executeProcesses(); if (command == null) { @@ -126,6 +130,10 @@ public void preTick() { } private void postTick() { + if (!PlayerControlGuard.canControl(baritone.getPlayerContext())) { + command = null; + return; + } // if we did this in pretick, it would suck // we use the time between ticks as calculation time // therefore, we only cancel and recalculate after the tick for the current path has executed diff --git a/src/main/java/baritone/utils/PlayerControlGuard.java b/src/main/java/baritone/utils/PlayerControlGuard.java new file mode 100644 index 000000000..b4660fb1e --- /dev/null +++ b/src/main/java/baritone/utils/PlayerControlGuard.java @@ -0,0 +1,42 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Baritone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.utils; + +import baritone.api.utils.IPlayerContext; +import net.minecraft.client.gui.screens.DeathScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.player.LocalPlayer; + +public final class PlayerControlGuard { + + private PlayerControlGuard() { + } + + public static boolean canControl(IPlayerContext ctx) { + return canControl(ctx.player(), ctx.minecraft().screen) + && ctx.world() != null + && !ctx.minecraft().isPaused(); + } + + public static boolean canControl(LocalPlayer player, Screen screen) { + if (player == null || !player.isAlive()) { + return false; + } + return screen == null || (!screen.isPauseScreen() && !(screen instanceof DeathScreen)); + } +} diff --git a/src/test/java/baritone/utils/RotationUtilsTest.java b/src/test/java/baritone/utils/RotationUtilsTest.java new file mode 100644 index 000000000..c6bd1b3e1 --- /dev/null +++ b/src/test/java/baritone/utils/RotationUtilsTest.java @@ -0,0 +1,104 @@ +/* + * This file is part of Baritone. + * + * Baritone is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Baritone is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package baritone.utils; + +import baritone.api.utils.Rotation; +import baritone.api.utils.RotationUtils; +import baritone.api.utils.RotationUtils.RotationArc; +import net.minecraft.world.phys.Vec3; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class RotationUtilsTest { + + private static final double EPSILON = 1.0E-10; + + @Test + public void testAlerpIdenticalPoints() { + Vec3 point = new Vec3(1, 2, 3); + + assertVecEquals(point, RotationUtils.alerp(point, point, Vec3.ZERO, 0.5)); + } + + @Test + public void testAlerpZeroVectorFallsBackToLine() { + assertVecEquals( + new Vec3(0.5, 0, 0), + RotationUtils.alerp(Vec3.ZERO, new Vec3(2, 0, 0), Vec3.ZERO, 0.25)); + } + + @Test + public void testAlerpSameRayFallsBackToLine() { + assertVecEquals( + new Vec3(1.5, 0, 0), + RotationUtils.alerp(new Vec3(1, 0, 0), new Vec3(2, 0, 0), Vec3.ZERO, 0.5)); + } + + @Test + public void testAlerpOppositeRayUsesStableHalfCircle() { + assertVecEquals( + new Vec3(0, 0, 1), + RotationUtils.alerp(new Vec3(1, 0, 0), new Vec3(-1, 0, 0), Vec3.ZERO, 0.5)); + } + + @Test + public void testRotationArcUsesAngularSpeed() { + assertEquals( + 1, + countTicks(RotationArc.fromAngularSpeed( + new Rotation(0, 0), + new Rotation(10, 0), + 30))); + assertEquals( + 3, + countTicks(RotationArc.fromAngularSpeed( + new Rotation(0, 0), + new Rotation(90, 0), + 30))); + assertEquals( + 4, + countTicks(RotationArc.fromAngularSpeed( + new Rotation(0, 0), + new Rotation(91, 0), + 30))); + } + + @Test + public void testRotationArcAvoidsViewPole() { + RotationArc arc = new RotationArc(new Rotation(0, 80), new Rotation(180, 80), 10); + + assertEquals(90, arc.arcAt(0.5).getYaw(), 1.0E-4); + assertEquals(80, arc.arcAt(0.5).getPitch(), 1.0E-4); + } + + private static void assertVecEquals(Vec3 expected, Vec3 actual) { + assertEquals(expected.x, actual.x, EPSILON); + assertEquals(expected.y, actual.y, EPSILON); + assertEquals(expected.z, actual.z, EPSILON); + } + + private static int countTicks(RotationArc arc) { + int ticks = 0; + while (!arc.isComplete()) { + arc.advance(); + ticks++; + } + return ticks; + } +}