From 468bd0916b5ea77e92f77ce90720e1db3a48fb04 Mon Sep 17 00:00:00 2001 From: Constructor Date: Fri, 5 Sep 2025 06:50:54 +0200 Subject: [PATCH 1/9] Fix sneak flags and rotation flags --- .../mixin/entity/ClientPlayerEntityMixin.java | 65 ++++---------- .../lambda/event/events/PlayerPacketEvent.kt | 1 - .../lambda/interaction/PlayerPacketManager.kt | 87 ++++++------------- .../interaction/request/rotating/Rotation.kt | 34 +++----- .../request/rotating/RotationManager.kt | 10 +-- .../lambda/module/modules/movement/Speed.kt | 7 +- 6 files changed, 64 insertions(+), 140 deletions(-) diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index c47dddad4..caba52092 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -37,7 +37,6 @@ import net.minecraft.client.world.ClientWorld; import net.minecraft.entity.MovementType; import net.minecraft.util.Hand; -import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -60,63 +59,43 @@ public ClientPlayerEntityMixin(ClientWorld world, GameProfile profile) { super(world, profile); } - @Shadow protected abstract void autoJump(float dx, float dz); - - /** - * Post movement events and applies the modified player velocity - */ - @Inject(method = "move", at = @At("HEAD"), cancellable = true) - void onMove(MovementType movementType, Vec3d movement, CallbackInfo ci) { - ClientPlayerEntity self = (ClientPlayerEntity) (Object) this; - if (self != Lambda.getMc().player) return; - - ci.cancel(); - - float prevX = (float) self.getX(); - float prevZ = (float) self.getZ(); - + @Redirect(method = "move", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/AbstractClientPlayerEntity;move(Lnet/minecraft/entity/MovementType;Lnet/minecraft/util/math/Vec3d;)V")) + private void emitMovementEvents(AbstractClientPlayerEntity instance, MovementType movementType, Vec3d movement) { EventFlow.post(new MovementEvent.Player.Pre(movementType, movement)); super.move(movementType, movement); EventFlow.post(new MovementEvent.Player.Post(movementType, movement)); - - float deltaX = (float) self.getX() - prevX; - float deltaZ = (float) self.getZ() - prevZ; - - this.autoJump(deltaX, deltaZ); - this.distanceMoved = this.distanceMoved + MathHelper.hypot(deltaX, deltaZ) * 0.6F; } @Redirect(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick()V")) void processMovement(Input input) { input.tick(); RotationManager.processRotations(); - RotationManager.BaritoneProcessor.processPlayerMovement(input); + RotationManager.BaritoneProcessor.processInputs(input); EventFlow.post(new MovementEvent.InputUpdate(input)); } /** - * Posts the {@link MovementEvent.Sprint} event - *
{@code
-     * if (this.isSprinting()) {
-     *     boolean bl8 = !this.input.hasForwardMovement() || !this.canSprint();
-     *     boolean bl9 = bl8 || this.horizontalCollision && !this.collidedSoftly || this.isTouchingWater() && !this.isSubmergedInWater();
-     *     if (this.isSwimming()) {
-     *         if (!this.isOnGround() && !this.input.sneaking && bl8 || !this.isTouchingWater()) {
-     *             this.setSprinting(false);
-     *         }
-     *     } else if (bl9) {
-     *         this.setSprinting(false);
-     *     }
-     * }
-     * }
+ * Overwrites the movement packet update function to use our code */ + @Inject(method = "sendMovementPackets", at = @At(value = "HEAD"), cancellable = true) + void sendLambdaMovement(CallbackInfo ci) { + ci.cancel(); + PlayerPacketManager.sendPlayerPackets(); + autoJumpEnabled = Lambda.getMc().options.getAutoJump().getValue(); + } + + @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;sendSneakingPacket()V")) + void sendSneakingPacket(ClientPlayerEntity entity) { + PlayerPacketManager.sendSneakPackets(); + } + @Redirect(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isSprinting()Z")) boolean isSprinting(ClientPlayerEntity entity) { return EventFlow.post(new MovementEvent.Sprint(entity.isSprinting())).getSprint(); } @Inject(method = "isSneaking", at = @At(value = "HEAD"), cancellable = true) - void redirectSneaking(CallbackInfoReturnable cir) { + void injectSneakingInput(CallbackInfoReturnable cir) { ClientPlayerEntity self = (ClientPlayerEntity) (Object) this; if (self != Lambda.getMc().player) return; @@ -124,16 +103,6 @@ void redirectSneaking(CallbackInfoReturnable cir) { cir.setReturnValue(EventFlow.post(new MovementEvent.Sneak(self.input.playerInput.sneak())).getSneak()); } - /** - * Overwrites the movement packet update function to use our code - */ - @Inject(method = "sendMovementPackets", at = @At(value = "HEAD"), cancellable = true) - void sendBegin(CallbackInfo ci) { - ci.cancel(); - PlayerPacketManager.sendPlayerPackets(); - autoJumpEnabled = Lambda.getMc().options.getAutoJump().getValue(); - } - @WrapMethod(method = "tick") void onTick(Operation original) { EventFlow.post(TickEvent.Player.Pre.INSTANCE); diff --git a/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt b/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt index 9a98d011d..fea59387d 100644 --- a/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/PlayerPacketEvent.kt @@ -30,7 +30,6 @@ sealed class PlayerPacketEvent { var rotation: Rotation, var onGround: Boolean, var isSprinting: Boolean, - var isSneaking: Boolean, var isCollidingHorizontally: Boolean, ) : ICancellable by Cancellable() diff --git a/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt b/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt index 72d94f117..d9fdadca8 100644 --- a/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt +++ b/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt @@ -26,11 +26,6 @@ import com.lambda.interaction.request.rotating.RotationManager import com.lambda.threading.runSafe import com.lambda.util.collections.LimitedOrderedSet import com.lambda.util.math.approximate -import com.lambda.util.math.component1 -import com.lambda.util.math.component2 -import com.lambda.util.math.component3 -import com.lambda.util.player.MovementUtils.motionX -import com.lambda.util.player.MovementUtils.motionZ import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.Full import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket.LookAndOnGround @@ -46,6 +41,7 @@ object PlayerPacketManager { var lastSprint = false var lastSneak = false var lastOnGround = false + var lastHorizontalCollision = false private var sendTicks = 0 @@ -57,7 +53,6 @@ object PlayerPacketManager { RotationManager.activeRotation, player.isOnGround, player.isSprinting, - player.isSneaking, player.horizontalCollision, ).post { updatePlayerPackets(this) @@ -65,72 +60,59 @@ object PlayerPacketManager { } } + @JvmStatic + fun sendSneakPackets() { + runSafe { + val sneaking = player.isSneaking + if (sneaking == lastSneak) return@runSafe + val mode = if (sneaking) { + ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY + } else { + ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY + } + connection.sendPacket(ClientCommandC2SPacket(player, mode)) + lastSneak = sneaking + } + } + private fun SafeContext.updatePlayerPackets(new: PlayerPacketEvent.Pre) { configurations.add(new) reportSprint(lastSprint, new.isSprinting) - reportSneak(lastSneak, new.isSneaking) if (mc.cameraEntity != player) return - val rotation = new.rotation val position = new.position - val (yaw, pitch) = rotation.float + val (yaw, pitch) = new.rotation val onGround = new.onGround val isCollidingHorizontally = new.isCollidingHorizontally - if (player.hasVehicle()) { - connection.sendPacket( - Full( - player.motionX, - -999.0, - player.motionZ, - yaw, - pitch, - onGround, - isCollidingHorizontally, - ) - ) - - return - } - val updatePosition = position.approximate(lastPosition, 2.0E-4) || ++sendTicks >= 20 - // has to be different in float precision - val updateRotation = !rotation.equalFloat(lastRotation) - - val (x, y, z) = position - - val packet = when { - updatePosition && updateRotation -> - Full(x, y, z, yaw, pitch, onGround, isCollidingHorizontally) - - updatePosition -> - PositionAndOnGround(x, y, z, onGround, isCollidingHorizontally) + val updateRotation = lastRotation.yaw != yaw || lastRotation.pitch != pitch - updateRotation -> - LookAndOnGround(yaw, pitch, onGround, isCollidingHorizontally) - - lastOnGround != onGround -> + when { + updatePosition && updateRotation -> Full(position, yaw.toFloat(), pitch.toFloat(), onGround, isCollidingHorizontally) + updatePosition -> PositionAndOnGround(position, onGround, isCollidingHorizontally) + updateRotation -> LookAndOnGround(yaw.toFloat(), pitch.toFloat(), onGround, isCollidingHorizontally) + lastOnGround != onGround || lastHorizontalCollision != isCollidingHorizontally -> { OnGroundOnly(onGround, isCollidingHorizontally) - + } else -> null - } - - packet?.let { + }?.let { PlayerPacketEvent.Send(it).postChecked { connection.sendPacket(this.packet) if (updatePosition) { - sendTicks = 0 lastPosition = position + sendTicks = 0 } if (updateRotation) { - lastRotation = rotation + lastRotation = new.rotation } lastOnGround = onGround + lastHorizontalCollision = isCollidingHorizontally } } @@ -152,19 +134,6 @@ object PlayerPacketManager { connection.sendPacket(ClientCommandC2SPacket(player, state)) lastSprint = new } - - fun SafeContext.reportSneak(previous: Boolean, new: Boolean) { - if (previous == new) return - - val state = if (new) { - ClientCommandC2SPacket.Mode.PRESS_SHIFT_KEY - } else { - ClientCommandC2SPacket.Mode.RELEASE_SHIFT_KEY - } - - connection.sendPacket(ClientCommandC2SPacket(player, state)) - lastSneak = new - } } diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt index bd141cccd..7d17bbe20 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt @@ -108,42 +108,31 @@ data class Rotation(val yaw: Double, val pitch: Double) { fun Rotation.lerp(other: Rotation, delta: Double): Rotation { // Calculate the wrapped difference to ensure we take the shortest path val yawDiff = wrap(other.yaw - this.yaw) - val pitchDiff = wrap(other.pitch - this.pitch) + val pitchDiff = other.pitch - this.pitch - // Apply the delta to the wrapped difference - val yaw = wrap(this.yaw + delta * yawDiff) - val pitch = wrap(this.pitch + delta * pitchDiff) + // Apply without wrapping the absolute yaw (keep it continuous) + val yaw = this.yaw + delta * yawDiff + val pitch = (this.pitch + delta * pitchDiff).coerceIn(-90.0, 90.0) return Rotation(yaw, pitch) } fun Rotation.slerp(other: Rotation, speed: Double): Rotation { val yawDiff = wrap(other.yaw - yaw) - val pitchDiff = wrap(other.pitch - pitch) + val pitchDiff = other.pitch - pitch - val diff = hypot(yawDiff, pitchDiff) + val diff = hypot(yawDiff, pitchDiff).let { if (it == 0.0) 1.0 else it } val yawSpeed = abs(yawDiff / diff) * speed val pitchSpeed = abs(pitchDiff / diff) * speed - val yaw = wrap(yaw + yawDiff.coerceIn(-yawSpeed, yawSpeed)) - val pitch = wrap(pitch + pitchDiff.coerceIn(-pitchSpeed, pitchSpeed)) + // Apply without wrapping the absolute yaw (keep it continuous) + val yaw = yaw + yawDiff.coerceIn(-yawSpeed, yawSpeed) + val pitch = (pitch + pitchDiff.coerceIn(-pitchSpeed, pitchSpeed)).coerceIn(-90.0, 90.0) return Rotation(yaw, pitch) } - fun Rotation.fixSensitivity(prev: Rotation): Rotation { - val f = mc.options.mouseSensitivity.value * 0.6 + 0.2 - val gcd = f * f * f * 8.0 * 0.15F - - val r1 = Vec2d(prev.yaw, prev.pitch) - val r2 = Vec2d(this.yaw, this.pitch) - val delta = ((r2 - r1) / gcd).roundToInt() * gcd - val fixed = r1 + delta - - return Rotation(fixed.x, fixed.y.coerceIn(-90.0, 90.0)) - } - fun Vec3d.rotationTo(vec: Vec3d): Rotation { val diffX = vec.x - x val diffY = vec.y - y @@ -152,8 +141,9 @@ data class Rotation(val yaw: Double, val pitch: Double) { val yawRad = atan2(diffZ, diffX) val pitchRad = -atan2(diffY, hypot(diffX, diffZ)) - val yaw = wrap(yawRad.toDegree() - 90.0) - val pitch = wrap(pitchRad.toDegree()) + // Target yaw can be normalized; our slerp keeps absolute yaw continuous + val yaw = yawRad.toDegree() - 90.0 + val pitch = pitchRad.toDegree().coerceIn(-90.0, 90.0) return Rotation(yaw, pitch) } diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt index dbc0d474c..b1c212b3e 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt @@ -29,7 +29,6 @@ import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe import com.lambda.interaction.request.RequestHandler import com.lambda.interaction.request.rotating.Rotation.Companion.slerp -import com.lambda.interaction.request.rotating.Rotation.Companion.wrap import com.lambda.interaction.request.rotating.visibilty.lookAt import com.lambda.module.modules.client.Baritone import com.lambda.threading.runGameScheduled @@ -124,7 +123,6 @@ object RotationManager : RequestHandler( prevServerRotation = serverRotation serverRotation = activeRotation/*.fixSensitivity(prevServerRotation)*/ - // Handle LOCK mode if (activeRequest?.rotationMode == RotationMode.Lock) { mc.player?.yaw = serverRotation.yawF mc.player?.pitch = serverRotation.pitchF @@ -141,7 +139,8 @@ object RotationManager : RequestHandler( val speedMultiplier = if (request.keepTicks < 0) 1.0 else request.speedMultiplier val turnSpeed = request.turnSpeed * speedMultiplier - serverRotation.slerp(rotationTo, turnSpeed).wrap() + // Important: do NOT wrap the result yaw; keep it continuous to match vanilla packets + serverRotation.slerp(rotationTo, turnSpeed) } ?: player.rotation } @@ -219,10 +218,11 @@ object RotationManager : RequestHandler( } @JvmStatic - fun processPlayerMovement(input: Input) = runSafe { + fun processInputs(input: Input) = runSafe { // The yaw relative to which the movement was constructed val baritoneYaw = baritoneContext?.target?.targetRotation?.value?.yaw - val strafeEvent = RotationEvent.StrafeInput(baritoneYaw ?: player.yaw.toDouble(), input) + val baseYaw = baritoneYaw ?: activeRotation.yaw + val strafeEvent = RotationEvent.StrafeInput(baseYaw, input) val movementYaw = strafeEvent.post().strafeYaw // No changes are needed, when we don't modify the yaw used to move the player diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index 25ab7cc7b..e59fb6657 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -139,10 +139,7 @@ object Speed : Module( if (!input.isInputting) return@listen - run { - if (!diagonal) return@run - if (player.isOnGround && input.playerInput.jump) return@run - + if (diagonal || !player.isOnGround || !input.playerInput.jump) { val forward = input.roundedForward.toFloat() var strafe = input.roundedStrafing.toFloat() @@ -155,7 +152,7 @@ object Speed : Module( val moveYaw = calcMoveYaw(yaw, input.roundedForward, input.roundedStrafing) lookAt( - Rotation(moveYaw, 0.0) + Rotation(moveYaw, player.pitch.toDouble()) ).requestBy(rotationConfig) } From 84713f5810bb222d5bef4df9fe6a884eb0d701f6 Mon Sep 17 00:00:00 2001 From: Constructor Date: Fri, 5 Sep 2025 07:11:17 +0200 Subject: [PATCH 2/9] For rotation testing --- .../module/modules/debug/RotationTest.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt diff --git a/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt new file mode 100644 index 000000000..c2e7145e7 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/debug/RotationTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.module.modules.debug + +import com.lambda.config.groups.RotationSettings +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.request.rotating.visibilty.lookAtHit +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.util.Communication.info +import com.lambda.util.combat.DamageUtils.fallDamage +import com.lambda.util.combat.DamageUtils.isFallDeadly +import net.minecraft.util.hit.HitResult +import net.minecraft.util.math.Vec3d + +object RotationTest : Module( + name = "RotationTest", + tag = ModuleTag.DEBUG, +) { + var rotation = RotationSettings(this) + var hitPos: HitResult? = null + + init { + onEnable { + hitPos = mc.crosshairTarget + } + + listen { + hitPos?.let { lookAtHit(it)?.requestBy(rotation) } + } + } +} From cc65408a0b307e44d05222a40a78d501725d72c9 Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 6 Sep 2025 02:37:37 +0100 Subject: [PATCH 3/9] interpolate pitch from last pitch like vanilla mc does --- .../mixin/render/LivingEntityRendererMixin.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java index 3f83d2733..979e9fd62 100644 --- a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java @@ -23,10 +23,11 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.client.render.entity.LivingEntityRenderer; import net.minecraft.entity.LivingEntity; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import java.util.Objects; +import static com.lambda.util.math.LinearKt.lerp; // This mixin's purpose is to set the player's pitch the current render pitch to correctly show the rotation // regardless of the camera position @@ -49,8 +50,11 @@ public class LivingEntityRendererMixin { */ @WrapOperation(method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getLerpedPitch(F)F")) private float wrapGetLerpedPitch(LivingEntity instance, float v, Operation original) { - return (instance == Lambda.getMc().player) - ? Objects.requireNonNullElse(RotationManager.getHeadPitch(), original.call(instance, v)) - : original.call(instance, v); + @Nullable + Float headPitch = RotationManager.getHeadPitch(); + if (instance != Lambda.getMc().player || headPitch == null) + return original.call(instance, v); + + return lerp(v, RotationManager.INSTANCE.getPrevServerRotation().getPitchF(), headPitch); } } From d1175fc028fed39002aad042dd46da9e0267e23d Mon Sep 17 00:00:00 2001 From: beanbag44 Date: Sat, 6 Sep 2025 03:05:37 +0100 Subject: [PATCH 4/9] default base yaw to the players yaw so movement calculations arent directed towards the direction of activeRotation --- .../com/lambda/interaction/request/rotating/RotationManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt index b1c212b3e..358de3fc6 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt @@ -221,7 +221,7 @@ object RotationManager : RequestHandler( fun processInputs(input: Input) = runSafe { // The yaw relative to which the movement was constructed val baritoneYaw = baritoneContext?.target?.targetRotation?.value?.yaw - val baseYaw = baritoneYaw ?: activeRotation.yaw + val baseYaw = baritoneYaw ?: player.yaw.toDouble() val strafeEvent = RotationEvent.StrafeInput(baseYaw, input) val movementYaw = strafeEvent.post().strafeYaw From 9ccbc3a03ed15a41414d81befe8c194303f64e84 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 7 Sep 2025 05:30:08 +0200 Subject: [PATCH 5/9] Fix movement input correction --- .../mixin/baritone/MixinLookBehavior.java | 2 +- .../mixin/entity/ClientPlayerEntityMixin.java | 2 +- .../lambda/interaction/PlayerPacketManager.kt | 3 +- .../request/rotating/RotationManager.kt | 179 ++++++++++-------- 4 files changed, 104 insertions(+), 82 deletions(-) diff --git a/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java b/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java index 599f7906a..0ba82c42d 100644 --- a/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java +++ b/src/main/java/com/lambda/mixin/baritone/MixinLookBehavior.java @@ -36,7 +36,7 @@ void onTargetUpdate(Rotation rotation, boolean blockInteract, CallbackInfo ci) { LookBehavior instance = ((LookBehavior) (Object) this); if (instance.baritone != BaritoneUtils.getPrimary()) return; - RotationManager.BaritoneProcessor.handleBaritoneRotation(rotation.getYaw(), rotation.getPitch()); + RotationManager.handleBaritoneRotation(rotation.getYaw(), rotation.getPitch()); ci.cancel(); } diff --git a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java index caba52092..c31ed4b87 100644 --- a/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java +++ b/src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java @@ -70,7 +70,7 @@ private void emitMovementEvents(AbstractClientPlayerEntity instance, MovementTyp void processMovement(Input input) { input.tick(); RotationManager.processRotations(); - RotationManager.BaritoneProcessor.processInputs(input); + RotationManager.redirectStrafeInputs(input); EventFlow.post(new MovementEvent.InputUpdate(input)); } diff --git a/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt b/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt index d9fdadca8..b092a05c6 100644 --- a/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt +++ b/src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt @@ -22,6 +22,7 @@ import com.lambda.event.EventFlow.post import com.lambda.event.EventFlow.postChecked import com.lambda.event.events.PlayerPacketEvent import com.lambda.interaction.request.rotating.Rotation +import com.lambda.interaction.request.rotating.Rotation.Companion.rotation import com.lambda.interaction.request.rotating.RotationManager import com.lambda.threading.runSafe import com.lambda.util.collections.LimitedOrderedSet @@ -87,7 +88,7 @@ object PlayerPacketManager { val onGround = new.onGround val isCollidingHorizontally = new.isCollidingHorizontally - val updatePosition = position.approximate(lastPosition, 2.0E-4) || ++sendTicks >= 20 + val updatePosition = position.approximate(lastPosition) || ++sendTicks >= 20 val updateRotation = lastRotation.yaw != yaw || lastRotation.pitch != pitch when { diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt index 358de3fc6..a99213a45 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt @@ -22,7 +22,6 @@ import com.lambda.context.SafeContext import com.lambda.event.EventFlow.post import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.PacketEvent -import com.lambda.event.events.RotationEvent import com.lambda.event.events.TickEvent import com.lambda.event.events.UpdateManagerEvent import com.lambda.event.listener.SafeListener.Companion.listen @@ -39,11 +38,13 @@ import com.lambda.util.math.MathUtils.toRadian import com.lambda.util.math.Vec2d import com.lambda.util.math.lerp import net.minecraft.client.input.Input +import net.minecraft.client.input.KeyboardInput import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket +import net.minecraft.util.PlayerInput import net.minecraft.util.math.Vec2f +import kotlin.math.PI +import kotlin.math.atan2 import kotlin.math.cos -import kotlin.math.round -import kotlin.math.sign import kotlin.math.sin object RotationManager : RequestHandler( @@ -119,6 +120,102 @@ object RotationManager : RequestHandler( } } + @JvmStatic + fun handleBaritoneRotation(yaw: Float, pitch: Float) { + lookAt(Rotation(yaw, pitch)).requestBy(Baritone.rotation) + } + + @JvmStatic + fun redirectStrafeInputs(input: Input) = runSafe { + val movementYaw = movementYaw ?: return@runSafe + val playerYaw = player.yaw + + if (movementYaw.minus(playerYaw).rem(360f).let { it * it } < 0.001f) return@runSafe + + val originalStrafe = input.movementVector.x + val originalForward = input.movementVector.y + + if (originalStrafe == 0.0f && originalForward == 0.0f) return@runSafe + + val deltaYawRad = (playerYaw - movementYaw).toRadian() + + val cos = cos(deltaYawRad) + val sin = sin(deltaYawRad) + // This is the IDEAL movement vector in the server-side entity's frame of reference + val newStrafe = originalStrafe * cos - originalForward * sin + val newForward = originalStrafe * sin + originalForward * cos + + // --- ANGLE SNAPPING LOGIC --- + // Instead of simple thresholds, we find the closest of the 8 possible directions. + + // Get the angle of the ideal vector. atan2 gives us an angle in radians. + // Note: Minecraft input vector's +Y is forward, +X is left. + val angle = atan2(newStrafe.toDouble(), newForward.toDouble()) + + // Define the boundaries for our 8 sectors (in radians). Each sector is 45 degrees (PI/4). + val sector = (PI / 4.0).toFloat() + val boundary = (PI / 8.0).toFloat() // The halfway point between sectors (22.5 degrees) + + var pressForward = false + var pressBackward = false + var pressLeft = false + var pressRight = false + + // Determine which 45-degree sector the angle falls into and set the corresponding keys. + if (angle > -boundary && angle <= boundary) { + // Forward + pressForward = true + } else if (angle > boundary && angle <= boundary + sector) { + // Forward-Left + pressForward = true + pressLeft = true + } else if (angle > boundary + sector && angle <= boundary + 2 * sector) { + // Left + pressLeft = true + } else if (angle > boundary + 2 * sector && angle <= boundary + 3 * sector) { + // Backward-Left + pressBackward = true + pressLeft = true + } else if (angle > boundary + 3 * sector || angle <= -(boundary + 3 * sector)) { + // Backward + pressBackward = true + } else if (angle > -(boundary + 3 * sector) && angle <= -(boundary + 2 * sector)) { + // Backward-Right + pressBackward = true + pressRight = true + } else if (angle > -(boundary + 2 * sector) && angle <= -(boundary + sector)) { + // Right + pressRight = true + } else if (angle > -(boundary + sector) && angle <= -boundary) { + // Forward-Right + pressForward = true + pressRight = true + } + + // --- Update Minecraft's input objects --- + input.playerInput = PlayerInput( + pressForward, + pressBackward, + pressLeft, + pressRight, + input.playerInput.jump(), + input.playerInput.sneak(), + input.playerInput.sprint() + ) + + val f = getMovementMultiplier(input.playerInput.forward(), input.playerInput.backward()) + val g = getMovementMultiplier(input.playerInput.left(), input.playerInput.right()) + input.movementVector = Vec2f(g, f).normalize() + } + + private fun getMovementMultiplier(positive: Boolean, negative: Boolean): Float { + return if (positive == negative) { + 0.0f + } else { + if (positive) 1.0f else -1.0f + } + } + fun onRotationSend() { prevServerRotation = serverRotation serverRotation = activeRotation/*.fixSensitivity(prevServerRotation)*/ @@ -196,81 +293,5 @@ object RotationManager : RequestHandler( return Vec2d(rot.yaw, rot.pitch) } - object BaritoneProcessor { - private var baritoneContext: RotationRequest? = null - - private val movementYawList = arrayOf( - 0.0, 45.0, - 90.0, 135.0, - 180.0, 225.0, - 270.0, 315.0, - ) - - @JvmStatic - fun handleBaritoneRotation(yaw: Float, pitch: Float) { - baritoneContext = lookAt(Rotation(yaw, pitch)).requestBy(Baritone.rotation) - } - - init { - listenUnsafe { - baritoneContext = null - } - } - - @JvmStatic - fun processInputs(input: Input) = runSafe { - // The yaw relative to which the movement was constructed - val baritoneYaw = baritoneContext?.target?.targetRotation?.value?.yaw - val baseYaw = baritoneYaw ?: player.yaw.toDouble() - val strafeEvent = RotationEvent.StrafeInput(baseYaw, input) - val movementYaw = strafeEvent.post().strafeYaw - - // No changes are needed, when we don't modify the yaw used to move the player - // val config = currentContext?.config ?: return@runSafe - // if (config.rotationMode == RotationMode.SILENT && !input.handledByBaritone && baritoneContext == null) return@runSafe - - // Sign it to remove previous speed modifier - input.hasForwardMovement() - val signForward = sign(input.movementVector.y) - val signStrafe = sign(input.movementVector.x) - - // No changes are needed when no inputs are pressed - if (signForward <= 1.0E-5f && signStrafe <= 1.0E-5F) return@runSafe - - // Actual yaw used by the physics engine - var actualYaw = activeRotation.yaw - - if (activeRequest?.rotationMode == RotationMode.Silent) { - actualYaw = player.yaw.toDouble() - } - - val yawRad = (movementYaw - actualYaw).toRadian() - - val cosDelta = cos(yawRad) - val sinDelta = sin(yawRad) - - val newX = signStrafe * cosDelta - signForward * sinDelta - val newZ = signForward * cosDelta + signStrafe * sinDelta - - // Apply new movement - input.apply { - movementVector = Vec2f( - round(newX).toFloat(), - round(newZ).toFloat(), - ) - } - - baritoneYaw ?: return@runSafe - - // Makes baritone movement safe - // when yaw difference is too big to compensate it by modifying keyboard input - val minYawDist = movementYawList - .map { activeRotation.yaw + it } // all possible movement directions (including diagonals) - .minOf { Rotation.angleDifference(it, baritoneYaw) } - - if (minYawDist > 5.0) input.movementVector = Vec2f.ZERO - } - } - override fun preEvent() = UpdateManagerEvent.Rotation.post() } From 9fab08c00a910027786e2772f618095838442b9f Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 7 Sep 2025 05:36:49 +0200 Subject: [PATCH 6/9] Cleanup --- .../request/rotating/RotationManager.kt | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt index a99213a45..15480e90a 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt @@ -38,7 +38,6 @@ import com.lambda.util.math.MathUtils.toRadian import com.lambda.util.math.Vec2d import com.lambda.util.math.lerp import net.minecraft.client.input.Input -import net.minecraft.client.input.KeyboardInput import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket import net.minecraft.util.PlayerInput import net.minecraft.util.math.Vec2f @@ -141,15 +140,9 @@ object RotationManager : RequestHandler( val cos = cos(deltaYawRad) val sin = sin(deltaYawRad) - // This is the IDEAL movement vector in the server-side entity's frame of reference val newStrafe = originalStrafe * cos - originalForward * sin val newForward = originalStrafe * sin + originalForward * cos - // --- ANGLE SNAPPING LOGIC --- - // Instead of simple thresholds, we find the closest of the 8 possible directions. - - // Get the angle of the ideal vector. atan2 gives us an angle in radians. - // Note: Minecraft input vector's +Y is forward, +X is left. val angle = atan2(newStrafe.toDouble(), newForward.toDouble()) // Define the boundaries for our 8 sectors (in radians). Each sector is 45 degrees (PI/4). @@ -192,7 +185,6 @@ object RotationManager : RequestHandler( pressRight = true } - // --- Update Minecraft's input objects --- input.playerInput = PlayerInput( pressForward, pressBackward, @@ -203,18 +195,13 @@ object RotationManager : RequestHandler( input.playerInput.sprint() ) - val f = getMovementMultiplier(input.playerInput.forward(), input.playerInput.backward()) - val g = getMovementMultiplier(input.playerInput.left(), input.playerInput.right()) - input.movementVector = Vec2f(g, f).normalize() + val x = multiplier(input.playerInput.left(), input.playerInput.right()) + val y = multiplier(input.playerInput.forward(), input.playerInput.backward()) + input.movementVector = Vec2f(x, y).normalize() } - private fun getMovementMultiplier(positive: Boolean, negative: Boolean): Float { - return if (positive == negative) { - 0.0f - } else { - if (positive) 1.0f else -1.0f - } - } + private fun multiplier(positive: Boolean, negative: Boolean) = + ((if (positive) 1 else 0) - (if (negative) 1 else 0)).toFloat() fun onRotationSend() { prevServerRotation = serverRotation From 1767c2263e177ad60020dadb9f3945a03871e5ec Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 7 Sep 2025 06:04:00 +0200 Subject: [PATCH 7/9] Fix speed module --- .../lambda/module/modules/movement/Speed.kt | 78 +++---------------- 1 file changed, 12 insertions(+), 66 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt index e59fb6657..165c11f53 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Speed.kt @@ -57,10 +57,6 @@ object Speed : Module( // Grim private val diagonal by setting("Diagonal", true) { mode == Mode.GRIM_STRAFE } - private val grimEntityBoost by setting("Entity Boost", 1.0, 0.0..2.0, 0.01) { mode == Mode.GRIM_STRAFE } - private val grimCollideMultiplier by setting("Entity Collide Multiplier", 0.5, 0.0..1.0, 0.01) { mode == Mode.GRIM_STRAFE && grimEntityBoost > 0.0 } - private val grimBoatBoost by setting("Boat Boost", 0.4, 0.0..1.0, 0.01) { mode == Mode.GRIM_STRAFE } - private val grimMaxSpeed by setting("Max Speed", 1.0, 0.2..1.0, 0.01) { mode == Mode.GRIM_STRAFE } // NCP private val strict by setting("Strict", true) { mode == Mode.NCP_STRAFE } @@ -68,15 +64,11 @@ object Speed : Module( private val ncpAutoJump by setting("Auto Jump", false) { mode == Mode.NCP_STRAFE } private val ncpTimerBoost by setting("Timer Boost", 1.08, 1.0..1.1, 0.01) { mode == Mode.NCP_STRAFE } - // Grim private val rotationConfig = RotationConfig.Instant(RotationMode.Sync) - private var prevTickJumping = false - - // NCP + // NCP state variables const val NCP_BASE_SPEED = 0.2873 private const val NCP_AIR_DECAY = 0.9937 - private var ncpPhase = NCPPhase.SLOWDOWN private var ncpSpeed = NCP_BASE_SPEED private var lastDistance = 0.0 @@ -99,9 +91,8 @@ object Speed : Module( return@listen } - when (mode) { - Mode.GRIM_STRAFE -> handleGrim() - Mode.NCP_STRAFE -> handleStrafe() + if (mode == Mode.NCP_STRAFE) { + handleStrafe() } } @@ -119,40 +110,19 @@ object Speed : Module( if (mode == Mode.NCP_STRAFE && shouldWork()) it.cancel() } - listen(Int.MIN_VALUE) { - if (mode != Mode.GRIM_STRAFE || !shouldWork()) return@listen - - // Delay jumping key state by 1 tick to let the rotation predict jump timing - it.input.apply { - val jump = jumping - jumping = prevTickJumping - prevTickJumping = jump - } - } - listen { - if (mode != Mode.GRIM_STRAFE) return@listen - if (!shouldWork()) return@listen + if (mode != Mode.GRIM_STRAFE || !shouldWork()) return@listen - var yaw = player.yaw val input = newMovementInput() - if (!input.isInputting) return@listen - if (diagonal || !player.isOnGround || !input.playerInput.jump) { - val forward = input.roundedForward.toFloat() - var strafe = input.roundedStrafing.toFloat() - - if (strafe == 0f) strafe = -1f - if (forward == 0f) strafe *= -1 - - yaw -= 45 * strafe - } - - val moveYaw = calcMoveYaw(yaw, input.roundedForward, input.roundedStrafing) + val intendedMoveYaw = calcMoveYaw(player.yaw, input.roundedForward, input.roundedStrafing) + val targetYaw = if (diagonal && !(input.playerInput.jump && player.isOnGround)) { + intendedMoveYaw - 45.0f + } else intendedMoveYaw lookAt( - Rotation(moveYaw, player.pitch.toDouble()) + Rotation(targetYaw, player.pitch.toDouble()) ).requestBy(rotationConfig) } @@ -161,25 +131,6 @@ object Speed : Module( } } - private fun SafeContext.handleGrim() { - if (!isInputting) return - if (player.moveDelta > grimMaxSpeed) return - - var boostAmount = 0.0 - - boostAmount += entitySearch(3.0) { - player.boundingBox.expand(1.0) in it.boundingBox && it !is ArmorStandEntity - }.sumOf { 0.08 * grimEntityBoost } - - if (grimBoatBoost > 0.0) { - boostAmount += entitySearch(4.0) { - player.boundingBox in it.boundingBox.expand(0.01) - }.sumOf { grimBoatBoost } - } - - addSpeed(boostAmount) - } - private fun SafeContext.handleStrafe() { val shouldJump = player.input.playerInput.jump || (ncpAutoJump && isInputting) @@ -227,13 +178,8 @@ object Speed : Module( if (player.abilities.flying || player.isElytraFlying || player.isTouchingWater || player.isInLava) return false return when (mode) { - Mode.GRIM_STRAFE -> { - !player.input.handledByBaritone && !TargetStrafe.isActive - } - - Mode.NCP_STRAFE -> { - !player.isSneaking - } + Mode.GRIM_STRAFE -> !player.input.handledByBaritone + Mode.NCP_STRAFE -> !player.isSneaking } } @@ -241,4 +187,4 @@ object Speed : Module( ncpPhase = NCPPhase.SLOWDOWN ncpSpeed = NCP_BASE_SPEED } -} +} \ No newline at end of file From 0990a2188618cb8afe825f6cc1577806ce8aa465 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 7 Sep 2025 06:10:06 +0200 Subject: [PATCH 8/9] Even more cleanup --- .../render/LivingEntityRendererMixin.java | 10 ++-- .../request/rotating/RotationManager.kt | 60 ++++++++++--------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java index 979e9fd62..ce93b0ff5 100644 --- a/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java +++ b/src/main/java/com/lambda/mixin/render/LivingEntityRendererMixin.java @@ -49,12 +49,12 @@ public class LivingEntityRendererMixin { * } */ @WrapOperation(method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;getLerpedPitch(F)F")) - private float wrapGetLerpedPitch(LivingEntity instance, float v, Operation original) { - @Nullable + private float wrapGetLerpedPitch(LivingEntity livingEntity, float v, Operation original) { Float headPitch = RotationManager.getHeadPitch(); - if (instance != Lambda.getMc().player || headPitch == null) - return original.call(instance, v); + if (livingEntity != Lambda.getMc().player || headPitch == null) { + return original.call(livingEntity, v); + } - return lerp(v, RotationManager.INSTANCE.getPrevServerRotation().getPitchF(), headPitch); + return lerp(v, RotationManager.getPrevServerRotation().getPitchF(), headPitch); } } diff --git a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt index 15480e90a..d14bbcead 100644 --- a/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt +++ b/src/main/kotlin/com/lambda/interaction/request/rotating/RotationManager.kt @@ -55,6 +55,7 @@ object RotationManager : RequestHandler( ) { var activeRotation = Rotation.ZERO var serverRotation = Rotation.ZERO + @JvmStatic var prevServerRotation = Rotation.ZERO var activeRequest: RotationRequest? = null @@ -155,34 +156,35 @@ object RotationManager : RequestHandler( var pressRight = false // Determine which 45-degree sector the angle falls into and set the corresponding keys. - if (angle > -boundary && angle <= boundary) { - // Forward - pressForward = true - } else if (angle > boundary && angle <= boundary + sector) { - // Forward-Left - pressForward = true - pressLeft = true - } else if (angle > boundary + sector && angle <= boundary + 2 * sector) { - // Left - pressLeft = true - } else if (angle > boundary + 2 * sector && angle <= boundary + 3 * sector) { - // Backward-Left - pressBackward = true - pressLeft = true - } else if (angle > boundary + 3 * sector || angle <= -(boundary + 3 * sector)) { - // Backward - pressBackward = true - } else if (angle > -(boundary + 3 * sector) && angle <= -(boundary + 2 * sector)) { - // Backward-Right - pressBackward = true - pressRight = true - } else if (angle > -(boundary + 2 * sector) && angle <= -(boundary + sector)) { - // Right - pressRight = true - } else if (angle > -(boundary + sector) && angle <= -boundary) { - // Forward-Right - pressForward = true - pressRight = true + when { + angle > -boundary && angle <= boundary -> { + pressForward = true + } + angle > boundary && angle <= boundary + sector -> { + pressForward = true + pressLeft = true + } + angle > boundary + sector && angle <= boundary + 2 * sector -> { + pressLeft = true + } + angle > boundary + 2 * sector && angle <= boundary + 3 * sector -> { + pressBackward = true + pressLeft = true + } + angle > boundary + 3 * sector || angle <= -(boundary + 3 * sector) -> { + pressBackward = true + } + angle > -(boundary + 3 * sector) && angle <= -(boundary + 2 * sector) -> { + pressBackward = true + pressRight = true + } + angle > -(boundary + 2 * sector) && angle <= -(boundary + sector) -> { + pressRight = true + } + angle > -(boundary + sector) && angle <= -boundary -> { + pressForward = true + pressRight = true + } } input.playerInput = PlayerInput( @@ -205,7 +207,7 @@ object RotationManager : RequestHandler( fun onRotationSend() { prevServerRotation = serverRotation - serverRotation = activeRotation/*.fixSensitivity(prevServerRotation)*/ + serverRotation = activeRotation if (activeRequest?.rotationMode == RotationMode.Lock) { mc.player?.yaw = serverRotation.yawF From 06b5589b0e724377221a531cd834f99639591469 Mon Sep 17 00:00:00 2001 From: Constructor Date: Sun, 7 Sep 2025 06:16:54 +0200 Subject: [PATCH 9/9] Remove wrong test --- src/test/kotlin/RotationTest.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/test/kotlin/RotationTest.kt b/src/test/kotlin/RotationTest.kt index a02c136c5..d103eb346 100644 --- a/src/test/kotlin/RotationTest.kt +++ b/src/test/kotlin/RotationTest.kt @@ -226,25 +226,6 @@ class RotationTest { assertTrue(result3.pitch < rotation2.pitch) } - @Test - fun `test slerp with angle wrapping`() { - // Test slerp across the -180/180 boundary - val rotation1 = Rotation(170.0, 0.0) - val rotation2 = Rotation(-170.0, 0.0) - - // With a very high speed, should go directly to rotation2 - val result = rotation1.slerp(rotation2, 1000.0) - assertEquals(rotation2.yaw, result.yaw, 0.001) - assertEquals(rotation2.pitch, result.pitch, 0.001) - - // With a limited speed, should move in the correct direction (clockwise) - val partialResult = rotation1.slerp(rotation2, 10.0) - - // The yaw should be greater than 170 (moving towards 180/-180) - // or less than -170 (already crossed the boundary) - assertTrue(partialResult.yaw > 170.0 || partialResult.yaw < -170.0) - } - @Test fun `test dist method`() { val rotation1 = Rotation(0.0, 0.0)