Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
65 changes: 17 additions & 48 deletions src/main/java/com/lambda/mixin/entity/ClientPlayerEntityMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -60,80 +59,50 @@ 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.redirectStrafeInputs(input);
EventFlow.post(new MovementEvent.InputUpdate(input));
}

/**
* Posts the {@link MovementEvent.Sprint} event
* <pre>{@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);
* }
* }
* }</pre>
* 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<Boolean> cir) {
void injectSneakingInput(CallbackInfoReturnable<Boolean> cir) {
ClientPlayerEntity self = (ClientPlayerEntity) (Object) this;
if (self != Lambda.getMc().player) return;

if (self.input == null) return;
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<Void> original) {
EventFlow.post(TickEvent.Player.Pre.INSTANCE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -48,9 +49,12 @@ public class LivingEntityRendererMixin {
* }</pre>
*/
@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<Float> original) {
return (instance == Lambda.getMc().player)
? Objects.requireNonNullElse(RotationManager.getHeadPitch(), original.call(instance, v))
: original.call(instance, v);
private float wrapGetLerpedPitch(LivingEntity livingEntity, float v, Operation<Float> original) {
Float headPitch = RotationManager.getHeadPitch();
if (livingEntity != Lambda.getMc().player || headPitch == null) {
return original.call(livingEntity, v);
}

return lerp(v, RotationManager.getPrevServerRotation().getPitchF(), headPitch);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
90 changes: 30 additions & 60 deletions src/main/kotlin/com/lambda/interaction/PlayerPacketManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,11 @@ 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
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
Expand All @@ -46,6 +42,7 @@ object PlayerPacketManager {
var lastSprint = false
var lastSneak = false
var lastOnGround = false
var lastHorizontalCollision = false

private var sendTicks = 0

Expand All @@ -57,80 +54,66 @@ object PlayerPacketManager {
RotationManager.activeRotation,
player.isOnGround,
player.isSprinting,
player.isSneaking,
player.horizontalCollision,
).post {
updatePlayerPackets(this)
}
}
}

@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 updatePosition = position.approximate(lastPosition) || ++sendTicks >= 20
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
}
}

Expand All @@ -152,19 +135,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
}
}


34 changes: 12 additions & 22 deletions src/main/kotlin/com/lambda/interaction/request/rotating/Rotation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
Loading