AimProcessor API

This commit is contained in:
Brady 2023-06-21 17:35:47 -05:00
parent 367ce5fa17
commit 7e426bd2e8
No known key found for this signature in database
GPG Key ID: 73A788379A197567
4 changed files with 283 additions and 53 deletions

View File

@ -17,6 +17,8 @@
package baritone.api.behavior;
import baritone.api.Settings;
import baritone.api.behavior.look.IAimProcessor;
import baritone.api.utils.Rotation;
/**
@ -27,10 +29,25 @@ public interface ILookBehavior extends IBehavior {
/**
* Updates the current {@link ILookBehavior} target to target the specified rotations on the next tick. If any sort
* of block interaction is required, {@code blockInteract} should be {@code true}.
* of block interaction is required, {@code blockInteract} should be {@code true}. It is not guaranteed that the
* rotations set by the caller will be the exact rotations expressed by the client (This is due to settings like
* {@link Settings#randomLooking}). If the rotations produced by this behavior are required, then the
* {@link #getAimProcessor() aim processor} should be used.
*
* @param rotation The target rotations
* @param blockInteract Whether the target rotations are needed for a block interaction
*/
void updateTarget(Rotation rotation, boolean blockInteract);
/**
* The aim processor instance for this {@link ILookBehavior}, which is responsible for applying additional, deterministic
* transformations to the target rotation set by {@link #updateTarget}. Whenever {@link IAimProcessor#nextRotation(Rotation)}
* is called on the instance returned by this method, the returned value always reflects what would happen in the
* upcoming tick. In other words, it is a pure function, and no internal state changes. If simulation of the
* rotation states beyond the next tick is required, then a {@link IAimProcessor#fork(int) fork} should be created.
*
* @return The aim processor
* @see IAimProcessor#fork(int)
*/
IAimProcessor getAimProcessor();
}

View File

@ -0,0 +1,44 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package baritone.api.behavior.look;
import baritone.api.utils.Rotation;
/**
* @author Brady
*/
public interface IAimProcessor {
/**
* Returns the actual rotation that will be used when the desired rotation is requested. This is not guaranteed to
* return the same value for a given input.
*
* @param desired The desired rotation to set
* @return The actual rotation
*/
Rotation nextRotation(Rotation desired);
/**
* Returns a copy of this {@link IAimProcessor} which has its own internal state and updates on each call to
* {@link #nextRotation(Rotation)}.
*
* @param ticksAdvanced The number of ticks to advance ahead of time
* @return The forked processor
*/
IAimProcessor fork(int ticksAdvanced);
}

View File

@ -20,13 +20,11 @@ package baritone.behavior;
import baritone.Baritone;
import baritone.api.Settings;
import baritone.api.behavior.ILookBehavior;
import baritone.api.event.events.PacketEvent;
import baritone.api.event.events.PlayerUpdateEvent;
import baritone.api.event.events.RotationMoveEvent;
import baritone.api.event.events.WorldEvent;
import baritone.api.utils.Helper;
import baritone.api.behavior.look.IAimProcessor;
import baritone.api.event.events.*;
import baritone.api.utils.IPlayerContext;
import baritone.api.utils.Rotation;
import baritone.behavior.look.ForkableRandom;
import net.minecraft.network.play.client.CPacketPlayer;
import java.util.Optional;
@ -44,14 +42,17 @@ public final class LookBehavior extends Behavior implements ILookBehavior {
private Rotation serverRotation;
/**
* The last player rotation. Used when free looking
* The last player rotation. Used to restore the player's angle when using free look.
*
* @see Settings#freeLook
*/
private Rotation prevRotation;
private final AimProcessor processor;
public LookBehavior(Baritone baritone) {
super(baritone);
this.processor = new AimProcessor(baritone.getPlayerContext());
}
@Override
@ -59,6 +60,19 @@ public final class LookBehavior extends Behavior implements ILookBehavior {
this.target = new Target(rotation, blockInteract);
}
@Override
public IAimProcessor getAimProcessor() {
return this.processor;
}
@Override
public void onTick(TickEvent event) {
if (event.getType() == TickEvent.Type.IN) {
// Unlike forked AimProcessors, the root one needs to be manually updated each game tick
this.processor.tick();
}
}
@Override
public void onPlayerUpdate(PlayerUpdateEvent event) {
if (this.target == null) {
@ -67,34 +81,16 @@ public final class LookBehavior extends Behavior implements ILookBehavior {
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 (this.target.mode == Target.Mode.SERVER) {
this.prevRotation = new Rotation(ctx.player().rotationYaw, ctx.player().rotationPitch);
}
final float oldYaw = ctx.playerRotations().getYaw();
final float oldPitch = ctx.playerRotations().getPitch();
float desiredYaw = this.target.rotation.getYaw();
float desiredPitch = this.target.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
if (desiredPitch == oldPitch) {
desiredPitch = nudgeToLevel(desiredPitch);
}
desiredYaw += (Math.random() - 0.5) * Baritone.settings().randomLooking.value;
desiredPitch += (Math.random() - 0.5) * Baritone.settings().randomLooking.value;
ctx.player().rotationYaw = calculateMouseMove(oldYaw, desiredYaw);
ctx.player().rotationPitch = calculateMouseMove(oldPitch, desiredPitch);
if (this.target.mode == Target.Mode.CLIENT) {
// The target can be invalidated now since it won't be needed for RotationMoveEvent
this.target = null;
}
final Rotation actual = this.processor.nextRotation(this.target.rotation);
ctx.player().rotationYaw = actual.getYaw();
ctx.player().rotationPitch = actual.getPitch();
break;
}
case POST: {
@ -133,7 +129,8 @@ public final class LookBehavior extends Behavior implements ILookBehavior {
public void pig() {
if (this.target != null) {
ctx.player().rotationYaw = this.target.rotation.getYaw();
final Rotation actual = this.processor.nextRotation(this.target.rotation);
ctx.player().rotationYaw = actual.getYaw();
}
}
@ -148,37 +145,124 @@ public final class LookBehavior extends Behavior implements ILookBehavior {
@Override
public void onPlayerRotationMove(RotationMoveEvent event) {
if (this.target != null) {
event.setYaw(this.target.rotation.getYaw());
event.setPitch(this.target.rotation.getPitch());
final Rotation actual = this.target.rotation;
event.setYaw(actual.getYaw());
event.setPitch(actual.getPitch());
}
}
/**
* Nudges the player's pitch to a regular level. (Between {@code -20} and {@code 10}, increments are by {@code 1})
*/
private static float nudgeToLevel(float pitch) {
if (pitch < -20) {
return pitch + 1;
} else if (pitch > 10) {
return pitch - 1;
private static final class AimProcessor extends AbstractAimProcessor {
public AimProcessor(final IPlayerContext ctx) {
super(ctx);
}
@Override
protected Rotation getPrevRotation() {
// Implementation will use LookBehavior.serverRotation
return ctx.playerRotations();
}
return pitch;
}
private float calculateMouseMove(float current, float target) {
final float delta = target - current;
final int deltaPx = angleToMouse(delta);
return current + mouseToAngle(deltaPx);
}
private static abstract class AbstractAimProcessor implements IAimProcessor {
private int angleToMouse(float angleDelta) {
final float minAngleChange = mouseToAngle(1);
return Math.round(angleDelta / minAngleChange);
}
protected final IPlayerContext ctx;
private final ForkableRandom rand;
private double randomYawOffset;
private double randomPitchOffset;
private float mouseToAngle(int mouseDelta) {
final float f = ctx.minecraft().gameSettings.mouseSensitivity * 0.6f + 0.2f;
return mouseDelta * f * f * f * 8.0f * 0.15f;
public AbstractAimProcessor(IPlayerContext ctx) {
this.ctx = ctx;
this.rand = new ForkableRandom();
}
private AbstractAimProcessor(final AbstractAimProcessor source) {
this.ctx = source.ctx;
this.rand = source.rand.fork();
this.randomYawOffset = source.randomYawOffset;
this.randomPitchOffset = source.randomPitchOffset;
}
public void tick() {
this.randomYawOffset = (this.rand.nextDouble() - 0.5) * Baritone.settings().randomLooking.value;
this.randomPitchOffset = (this.rand.nextDouble() - 0.5) * Baritone.settings().randomLooking.value;
}
@Override
public Rotation nextRotation(final Rotation rotation) {
final Rotation prev = this.getPrevRotation();
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
if (desiredPitch == prev.getPitch()) {
desiredPitch = nudgeToLevel(desiredPitch);
}
desiredYaw += this.randomYawOffset;
desiredPitch += this.randomPitchOffset;
return new Rotation(
this.calculateMouseMove(prev.getYaw(), desiredYaw),
this.calculateMouseMove(prev.getPitch(), desiredPitch)
);
}
@Override
public final IAimProcessor fork(final int ticksAdvanced) {
final AbstractAimProcessor fork = new AbstractAimProcessor(this) {
private Rotation prev = AbstractAimProcessor.this.getPrevRotation();
@Override
public Rotation nextRotation(Rotation rotation) {
final Rotation actual = super.nextRotation(rotation);
this.tick();
return (this.prev = actual);
}
@Override
protected Rotation getPrevRotation() {
return this.prev;
}
};
for (int i = 0; i < ticksAdvanced; i++) {
fork.tick();
}
return fork;
}
protected abstract Rotation getPrevRotation();
/**
* 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) {
return pitch + 1;
} else if (pitch > 10) {
return pitch - 1;
}
return pitch;
}
private float calculateMouseMove(float current, float target) {
final float delta = target - current;
final int deltaPx = angleToMouse(delta);
return current + mouseToAngle(deltaPx);
}
private int angleToMouse(float angleDelta) {
final float minAngleChange = mouseToAngle(1);
return Math.round(angleDelta / minAngleChange);
}
private float mouseToAngle(int mouseDelta) {
final float f = ctx.minecraft().gameSettings.mouseSensitivity * 0.6f + 0.2f;
return mouseDelta * f * f * f * 8.0f * 0.15f;
}
}
private static class Target {

View File

@ -0,0 +1,85 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package baritone.behavior.look;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.LongSupplier;
/**
* Implementation of Xoroshiro256++
* <p>
* Extended to produce random double-precision floating point numbers, and allow copies to be spawned via {@link #fork},
* which share the same internal state as the source object.
*
* @author Brady
*/
public final class ForkableRandom {
private static final double DOUBLE_UNIT = 0x1.0p-53;
private final long[] s;
public ForkableRandom() {
this(System.nanoTime() ^ System.currentTimeMillis());
}
public ForkableRandom(long seedIn) {
final AtomicLong seed = new AtomicLong(seedIn);
final LongSupplier splitmix64 = () -> {
long z = seed.addAndGet(0x9e3779b97f4a7c15L);
z = (z ^ (z >>> 30)) * 0xbf58476d1ce4e5b9L;
z = (z ^ (z >>> 27)) * 0x94d049bb133111ebL;
return z ^ (z >>> 31);
};
this.s = new long[] {
splitmix64.getAsLong(),
splitmix64.getAsLong(),
splitmix64.getAsLong(),
splitmix64.getAsLong()
};
}
private ForkableRandom(long[] s) {
this.s = s;
}
public double nextDouble() {
return (this.next() >>> 11) * DOUBLE_UNIT;
}
public long next() {
final long result = rotl(this.s[0] + this.s[3], 23) + this.s[0];
final long t = this.s[1] << 17;
this.s[2] ^= this.s[0];
this.s[3] ^= this.s[1];
this.s[1] ^= this.s[2];
this.s[0] ^= this.s[3];
this.s[2] ^= t;
this.s[3] = rotl(this.s[3], 45);
return result;
}
public ForkableRandom fork() {
return new ForkableRandom(Arrays.copyOf(this.s, 4));
}
private static long rotl(long x, int k) {
return (x << k) | (x >>> (64 - k));
}
}