Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interpolated Looking implementation #4505

Open
wants to merge 1 commit into
base: 1.19.4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions src/api/java/baritone/api/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -757,20 +757,40 @@ public final class Settings {
*/
public final Setting<Boolean> 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<Boolean> smoothLook = new Setting<>(false);
//
///**
// * Same as {@link #smoothLook} but for elytra flying.
// */
//public final Setting<Boolean> elytraSmoothLook = new Setting<>(false);
//
///**
// * The number of ticks to average across for {@link #smoothLook};
// */
//public final Setting<Integer> 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
* <p>
* TODO: It might be better to use some kind of angular speed instead of a fixed duration for all angles.
* <p>
* 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<Boolean> smoothLook = new Setting<>(false);
public final Setting<Boolean> interpolatedLook = new Setting<>(false);

/**
* Same as {@link #smoothLook} but for elytra flying.
* Controls time it takes to complete an {@link #interpolatedLook} "cycle".
* <p>
* In ticks.
*/
public final Setting<Boolean> elytraSmoothLook = new Setting<>(false);
public final Setting<Integer> interpolatedLookLength = new Setting<>(10);

/**
* The number of ticks to average across for {@link #smoothLook};
*/
public final Setting<Integer> smoothLookTicks = new Setting<>(5);

/**
* When true, the player will remain with its existing look direction as often as possible.
Expand Down
71 changes: 71 additions & 0 deletions src/api/java/baritone/api/utils/RotationUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -261,6 +262,76 @@ public static Optional<Rotation> 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.
* <p>
* 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<Rotation> reachable(LocalPlayer entity, BlockPos pos, double blockReachDistance) {
return reachable(entity, pos, blockReachDistance, false);
Expand Down
5 changes: 5 additions & 0 deletions src/api/java/baritone/api/utils/VecUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use google codestyle guidelines plz

return new Vec3(A.x / B.x, A.y / B.y, A.z / B.z);
}
}
184 changes: 147 additions & 37 deletions src/main/java/baritone/behavior/LookBehavior.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -53,8 +57,16 @@ public final class LookBehavior extends Behavior implements ILookBehavior {

private final AimProcessor processor;

private final Deque<Float> smoothYawBuffer;
private final Deque<Float> 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<Float> smoothYawBuffer;
//private final Deque<Float> smoothPitchBuffer;

public LookBehavior(Baritone baritone) {
super(baritone);
Expand Down Expand Up @@ -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) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is it? 💀. Commits the commented code is pretty bullshit

// 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.
}
}

Expand All @@ -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() {
Expand Down
Loading