From d34dceccb1f04b24881168cbb7357527082d9c71 Mon Sep 17 00:00:00 2001 From: TheThouZands Date: Thu, 19 Sep 2024 17:32:27 -0500 Subject: [PATCH] Interpolated Looking implementation Replaced old, in many cases not working "smoothlook" with "interpolatedLook", which forces global player rotation in both axis to follow a "path" from source looking position(rotation) to target, removing jittering when, for instance, mining blocks; it WILL hinder baritone's default mining speed, but the main purpose is to make actions seem slightly more humanly possible, time of the interpolation is controlled by interpolatedLookLength, in ticks. --- src/api/java/baritone/api/Settings.java | 38 +++- .../baritone/api/utils/RotationUtils.java | 71 +++++++ src/api/java/baritone/api/utils/VecUtils.java | 5 + .../java/baritone/behavior/LookBehavior.java | 184 ++++++++++++++---- .../java/baritone/utils/BaritoneMath.java | 5 + 5 files changed, 257 insertions(+), 46 deletions(-) diff --git a/src/api/java/baritone/api/Settings.java b/src/api/java/baritone/api/Settings.java index d9cb501ef..005b5c191 100644 --- a/src/api/java/baritone/api/Settings.java +++ b/src/api/java/baritone/api/Settings.java @@ -757,20 +757,40 @@ 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} of server-sided rotations. + // */ + //public final Setting smoothLook = new Setting<>(false); + // + ///** + // * Same as {@link #smoothLook} but for elytra flying. + // */ + //public final Setting elytraSmoothLook = new Setting<>(false); + // + ///** + // * The number of ticks to average across for {@link #smoothLook}; + // */ + //public final Setting smoothLookTicks = new Setting<>(5); + + // Old implementation settings... + + /** + * Forces global player rotation in both axis to follow a "path" from source looking position(rotation) to target, removing jittering when, for instance, mining blocks. + * Uses {@link #interpolatedLookLength} to determine the amount of ticks it takes to complete the path + *

+ * TODO: It might be better to use some kind of angular speed instead of a fixed duration for all angles. + *

+ * it WILL hinder mining speed, but the main goal here is that neither the client nor the server sees the default jittery-ness of baritone */ - public final Setting smoothLook = new Setting<>(false); + public final Setting interpolatedLook = new Setting<>(false); /** - * Same as {@link #smoothLook} but for elytra flying. + * Controls time it takes to complete an {@link #interpolatedLook} "cycle". + *

+ * In ticks. */ - public final Setting elytraSmoothLook = new Setting<>(false); + public final Setting interpolatedLookLength = new Setting<>(10); - /** - * The number of ticks to average across for {@link #smoothLook}; - */ - public final Setting smoothLookTicks = new Setting<>(5); /** * When true, the player will remain with its existing look direction as often as possible. diff --git a/src/api/java/baritone/api/utils/RotationUtils.java b/src/api/java/baritone/api/utils/RotationUtils.java index bfab3fa87..01f35a3c1 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 baritone.api.utils.VecUtils; import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -261,6 +262,76 @@ public static Optional reachableCenter(IPlayerContext ctx, BlockPos po return reachableOffset(ctx, pos, VecUtils.calculateBlockCenter(ctx.world(), pos), blockReachDistance, wouldSneak); } + /** + * Arc interpolator, creates a radius with a center as near as possible from "origin" which touches + * both A and B. + *

+ * It's an alternative to normal lerp, where if A is in front of origin and B opposite or + * near opposite to the former, the line would pass straight through origin + * + * @param A Location to return at t = 0 + * @param B Location to return at t = 1 + * @param Origin Location the center should approximate + * @param t Interpolator value + * @return Range of locations forming an arc outwards from Origin, controlled by t + */ + public static Vec3 alerp(Vec3 A, Vec3 B, Vec3 Origin, double t) { + Vec3 midpoint = VecUtils.vDivide(A.add(B), new Vec3(2, 2, 2)); + + Vec3 normal = ((A.subtract(Origin)).cross(B.subtract(Origin))).normalize(); + + Vec3 AB = (B.subtract(A)).normalize(); + + Vec3 toCenter = (AB.cross(normal)).normalize(); + + Vec3 originToMid = midpoint.subtract(Origin); + double projectionLength = originToMid.dot(toCenter); + + Vec3 center = midpoint.subtract(new Vec3(projectionLength, projectionLength, projectionLength).multiply(toCenter)); + + double radius = A.distanceTo(center); + + Vec3 vecToA = A.subtract(center); + Vec3 vecToB = B.subtract(center); + + double angleA = Mth.atan2(vecToA.dot(toCenter), vecToA.dot(AB)); + double angleB = Mth.atan2(vecToB.dot(toCenter), vecToB.dot(AB)); + + // Ensure we're using the shorter arc + if (Mth.abs((float) (angleB - angleA)) > Mth.PI) { + if (angleA < angleB) { + angleA += 2 * Mth.PI; + } else { + angleB += 2 * Mth.PI; + } + } + + // Interpolate the angle + double angle = (1 - t) * angleA + t * angleB; + + // Calculate the interpolated point + Vec3 asdainside = (new Vec3(Mth.cos((float) angle), Mth.cos((float) angle), Mth.cos((float) angle)).multiply(AB)).add(new Vec3(Mth.sin((float) angle), Mth.sin((float) angle), Mth.sin((float) angle)).multiply(toCenter)); + + return center.add((new Vec3(radius, radius, radius)).multiply(asdainside)); + } + /* + Using Slerp is also possible, but might be slightly more prone to doing a neck break, and IDK how to implement it lol + btw it uses quaternions, so doing a translation back and forth might be more expensive than just using the position to rotation function already implemented here. + I might be wrong in a lot of things though, I come from c++, and java is not exactly different, but it's also not exactly the same; I literally have 0 java + expertise, and I'm just rawdogging this without tutorials... + */ + + /* + didn't realize that in lookBehavior values in the Target struct and the current lookpos of the player were already angles with no trace of the original position until + after I wrote this function; at least if anything happens it should be un-pair proof (if somehow the distance between A, B and the origin is different it will still work as intended) + */ + + /* + also funny story, I created a whole library of vector operations in VecUtils and used them for this function until I realized that the Vec3 library already had + most of them, so I undid all that work except for vDivide, which was NOT on Vec3.class, although I do think my implementation did make it all a bit more understandable + but that's very subjective. + */ + @Deprecated public static Optional reachable(LocalPlayer entity, BlockPos pos, double blockReachDistance) { return reachable(entity, pos, blockReachDistance, false); diff --git a/src/api/java/baritone/api/utils/VecUtils.java b/src/api/java/baritone/api/utils/VecUtils.java index 4ea94b95a..a05fd1fcb 100644 --- a/src/api/java/baritone/api/utils/VecUtils.java +++ b/src/api/java/baritone/api/utils/VecUtils.java @@ -19,6 +19,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.BaseFireBlock; @@ -120,4 +121,8 @@ public static double entityDistanceToCenter(Entity entity, BlockPos pos) { public static double entityFlatDistanceToCenter(Entity entity, BlockPos pos) { return distanceToCenter(pos, entity.position().x, pos.getY() + 0.5, entity.position().z); } + + public static Vec3 vDivide (Vec3 A, Vec3 B) { + return new Vec3(A.x / B.x, A.y / B.y, A.z / B.z); + } } diff --git a/src/main/java/baritone/behavior/LookBehavior.java b/src/main/java/baritone/behavior/LookBehavior.java index 98e12aeff..4be2c25ef 100644 --- a/src/main/java/baritone/behavior/LookBehavior.java +++ b/src/main/java/baritone/behavior/LookBehavior.java @@ -25,7 +25,11 @@ import baritone.api.event.events.*; import baritone.api.utils.IPlayerContext; import baritone.api.utils.Rotation; +import baritone.api.utils.RotationUtils; +import baritone.api.utils.VecUtils; import baritone.behavior.look.ForkableRandom; +import baritone.utils.BaritoneMath; +import net.minecraft.world.phys.Vec3; import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket; import java.util.ArrayDeque; @@ -53,8 +57,16 @@ public final class LookBehavior extends Behavior implements ILookBehavior { private final AimProcessor processor; - private final Deque smoothYawBuffer; - private final Deque smoothPitchBuffer; + /** + * the interpolation's current stage, to be used by {@link #onPlayerUpdate(PlayerUpdateEvent)} whenever {@link Settings#interpolatedLook} is true. + */ + private static int stage = 0; + private static Vec3 Source; + private static Vec3 Destiny; + private static Vec3 Center; + + //private final Deque smoothYawBuffer; + //private final Deque smoothPitchBuffer; public LookBehavior(Baritone baritone) { super(baritone); @@ -87,49 +99,146 @@ public void onPlayerUpdate(PlayerUpdateEvent event) { return; } - switch (event.getState()) { - case PRE: { - if (this.target.mode == Target.Mode.NONE) { - // Just return for PRE, we still want to set target to null on POST - return; - } + if (Baritone.settings().interpolatedLook.value = false) { + switch (event.getState()) { + //PRE: onPlayerUpdate was called before rotation data was sent to the server + case PRE: { + if (this.target.mode == Target.Mode.NONE) { + // Just return for PRE, we still want to set target to null on POST + return; + } - this.prevRotation = new Rotation(ctx.player().getYRot(), ctx.player().getXRot()); - final Rotation actual = this.processor.peekRotation(this.target.rotation); - ctx.player().setYRot(actual.getYaw()); - ctx.player().setXRot(actual.getPitch()); - break; + this.prevRotation = new Rotation(ctx.player().getYRot(), ctx.player().getXRot()); + final Rotation actual = this.processor.peekRotation(this.target.rotation); + ctx.player().setYRot(actual.getYaw()); + ctx.player().setXRot(actual.getPitch()); + break; + } + //POST: onPlayerUpdate was called after rotation data was sent to the server + case POST: { + // Reset the player's rotations back to their original values + if (this.prevRotation != null) { + 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) { + // 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())); + // } + //} + /** TODO: reimplement elytra and falling */ + //ctx.player().xRotO = prevRotation.getPitch(); + //ctx.player().yRotO = prevRotation.getYaw(); + this.prevRotation = null; + } + // The target is done being used for this game tick, so it can be invalidated + this.target = null; + break; + } + default: + break; } - case POST: { - // Reset the player's rotations back to their original values - if (this.prevRotation != null) { - this.smoothYawBuffer.addLast(this.target.rotation.getYaw()); - while (this.smoothYawBuffer.size() > Baritone.settings().smoothLookTicks.value) { - this.smoothYawBuffer.removeFirst(); + }else { + // interpolatedLook == true + switch (event.getState()) { + case PRE: { + if(this.target.mode == Target.Mode.NONE) { + // same security case as above + return; } - this.smoothPitchBuffer.addLast(this.target.rotation.getPitch()); - while (this.smoothPitchBuffer.size() > Baritone.settings().smoothLookTicks.value) { - this.smoothPitchBuffer.removeFirst(); + + this.prevRotation = new Rotation(ctx.player().getYRot(), ctx.player().getXRot()); + final Rotation actual = this.processor.peekRotation(this.target.rotation); + + if (LookBehavior.stage == 0) { + LookBehavior.Center = ctx.playerHead(); + LookBehavior.Source = (RotationUtils.calcLookDirectionFromRotation(this.prevRotation)).add(LookBehavior.Center); + LookBehavior.Destiny = (RotationUtils.calcLookDirectionFromRotation(actual)).add(LookBehavior.Center); + + RotationUtils.alerp(LookBehavior.Source, LookBehavior.Destiny, LookBehavior.Center, BaritoneMath.normalize(LookBehavior.stage, Baritone.settings().interpolatedLookLength.value)); //debug, CO&PA + LookBehavior.stage ++; + /* + At this point, the camera rotation should still be the same as before, be it that Baritone was traveling or just exited from another mining phase. + */ + break; + + } else if (0 < LookBehavior.stage && LookBehavior.stage < Baritone.settings().interpolatedLookLength.value) { + final Rotation InterpD = RotationUtils.calcRotationFromVec3d(LookBehavior.Center, RotationUtils.alerp(LookBehavior.Source, LookBehavior.Destiny, LookBehavior.Center, BaritoneMath.normalize(LookBehavior.stage, Baritone.settings().interpolatedLookLength.value)), new Rotation(0, 0)); // love:slash:hate relationship with relative vectors + ctx.player().setYRot(InterpD.getYaw()); + ctx.player().setXRot(InterpD.getPitch()); + LookBehavior.stage ++; + /* + here we enter the main "loop", based on the look stage the player should be looking at any point between the path projected by alerp; at the moment of writing this I'm just assuming that Baritone does a check + to see if the player is looking at the block before starting to do the mining action, and is not some timer-hard-coded value. + */ + break; + + } else if (LookBehavior.stage == Baritone.settings().interpolatedLookLength.value) { + final Rotation InterpD = RotationUtils.calcRotationFromVec3d(LookBehavior.Center, RotationUtils.alerp(LookBehavior.Source, LookBehavior.Destiny, LookBehavior.Center, BaritoneMath.normalize(LookBehavior.stage, Baritone.settings().interpolatedLookLength.value)), new Rotation(0, 0)); // love:slash:hate relationship with relative vectors + ctx.player().setYRot(InterpD.getYaw()); + ctx.player().setXRot(InterpD.getPitch()); + LookBehavior.stage = 0; + LookBehavior.Source = null; + LookBehavior.Destiny = null; + /* + we've reached the final part of the "loop", after setting rotations we do some cleanup to be able to enter the next mining phase. + Center in theory shouldn't need to be nullified, but let's see... + */ + break; + + } else { + // wtf + LookBehavior.stage = 0; + LookBehavior.Source = null; + LookBehavior.Destiny = null; + // I hate code once debug everywhere + break; + // I really don't know how to debug this edge case } - 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())); - if (ctx.player().isFallFlying()) { - ctx.player().setXRot((float) this.smoothPitchBuffer.stream().mapToDouble(d -> d).average().orElse(this.prevRotation.getPitch())); + } + case POST: { + // Same as before, I really do wish that both PRE and POST are both done before frame generation, but that would be just not to trigger epilepsy + if (this.prevRotation != null) { + 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) { + // 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())); + // } + //} + /** TODO: reimplement elytra and falling, again, for interpolatedLook */ + //ctx.player().xRotO = prevRotation.getPitch(); + //ctx.player().yRotO = prevRotation.getYaw(); + if (LookBehavior.stage == Baritone.settings().interpolatedLookLength.value) { + this.prevRotation = null; } } - //ctx.player().xRotO = prevRotation.getPitch(); - //ctx.player().yRotO = prevRotation.getYaw(); - this.prevRotation = null; + // The target is done being used for this game tick, so it can be invalidated + this.target = null; + break; } - // The target is done being used for this game tick, so it can be invalidated - this.target = null; - break; + default: + break; } - default: - break; + /* + in paper(not the server software) this should work, but imma add a failed attempts counter... + */ + + //ATT1: at 10:40PM, 9/18/24 = Compile successful, executing... Stuck looking at 135.1, 0,1 + + /* + I think I had this problem before when testing alerp in a grapher, sometimes the compilers(ig?) decide that a couple of formulas are wrong because f me, in some others it does work perfectly. + I will re-explore all the solutions per grapher. + */ + /* + After an embarrassingly long amount of time, I just realized I converted the angle to a vector before doing an alerp, and I need to add that vector to the player head position, otherwise the two points are near (0,0,0). + Realized this while debugging, and at the same time I saw that the Static modifier is too powerful(not), it is kept on memory after exiting one server and entering another (including embedded, local or internal server). + */ + + //ATT2: at 3:05PM, 9/19/24 = Compile successful, executing... works. } } @@ -149,6 +258,7 @@ public void onSendPacket(PacketEvent event) { public void onWorldEvent(WorldEvent event) { this.serverRotation = null; this.target = null; + LookBehavior.stage = 0; } public void pig() { diff --git a/src/main/java/baritone/utils/BaritoneMath.java b/src/main/java/baritone/utils/BaritoneMath.java index be546f248..b67b52437 100644 --- a/src/main/java/baritone/utils/BaritoneMath.java +++ b/src/main/java/baritone/utils/BaritoneMath.java @@ -34,4 +34,9 @@ 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; + } }