From e80368d6a611fd0fdeab05ef8721f97eb4123a77 Mon Sep 17 00:00:00 2001 From: Ic3Tank <61137113+IceTank@users.noreply.github.com> Date: Fri, 6 Mar 2026 21:11:35 +0100 Subject: [PATCH 1/3] Fix freecam causing a stuck loading screen when changing dimensions --- .../ClientPlayNetworkHandlerMixin.java | 28 +++++++++++++++++++ .../com/lambda/event/events/PlayerEvent.kt | 20 +++++++++++++ .../lambda/module/modules/player/Freecam.kt | 17 +++++++++++ 3 files changed, 65 insertions(+) diff --git a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java index a449c4380..0f1a3be9d 100644 --- a/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/com/lambda/mixin/network/ClientPlayNetworkHandlerMixin.java @@ -20,6 +20,7 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.ChatEvent; import com.lambda.event.events.InventoryEvent; +import com.lambda.event.events.PlayerEvent; import com.lambda.event.events.WorldEvent; import com.lambda.interaction.managers.inventory.InventoryManager; import com.lambda.module.modules.movement.Velocity; @@ -27,13 +28,19 @@ import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.network.ClientPlayNetworkHandler; import net.minecraft.client.network.PlayerListEntry; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityPosition; import net.minecraft.network.packet.s2c.play.*; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Set; @Mixin(ClientPlayNetworkHandler.class) public class ClientPlayNetworkHandlerMixin { @@ -125,4 +132,25 @@ void onSendMessage(String content, Operation original) { if (!EventFlow.post(event).isCanceled()) original.call(event.getMessage()); } + + @Inject(method = "onPlayerRespawn", at = @At("TAIL")) + void onPlayerRespawn(PlayerRespawnS2CPacket packet, CallbackInfo ci) { + EventFlow.post(new PlayerEvent.World.Respawn(packet)); + } + + @Inject(method = "onPlayerPositionLook", at = @At("TAIL")) + void onPlayerPositionLook(PlayerPositionLookS2CPacket packet, CallbackInfo ci) { + EventFlow.post(new PlayerEvent.World.PositionLook(packet)); + } + + @Inject(method = "setPosition", at = @At("TAIL")) + private static void onSetPosition(EntityPosition pos, Set flags, Entity entity, boolean bl, + CallbackInfoReturnable cir) { + if (cir.getReturnValue() == false) { + var player = MinecraftClient.getInstance().player; + assert player != null; + var event = new PlayerEvent.World.SetPosition(player.getEntityPos(), player.getVelocity(), player.getYaw(), player.getPitch()); + EventFlow.post(event); + } + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lambda/event/events/PlayerEvent.kt b/src/main/kotlin/com/lambda/event/events/PlayerEvent.kt index bb7be5edd..d6f1c8e43 100644 --- a/src/main/kotlin/com/lambda/event/events/PlayerEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/PlayerEvent.kt @@ -20,6 +20,8 @@ package com.lambda.event.events import com.lambda.event.Event import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable +import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket +import net.minecraft.network.packet.s2c.play.PlayerRespawnS2CPacket import net.minecraft.screen.ScreenHandler import net.minecraft.screen.slot.SlotActionType import net.minecraft.util.Hand @@ -27,6 +29,7 @@ import net.minecraft.util.hit.BlockHitResult import net.minecraft.util.hit.EntityHitResult import net.minecraft.util.math.BlockPos import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d /** * Represents various events that can be triggered by the player during gameplay. @@ -138,4 +141,21 @@ sealed class PlayerEvent { val action: SlotActionType, val screenHandler: ScreenHandler, ) : ICancellable by Cancellable() + + sealed class World { + data class Respawn( + val packet: PlayerRespawnS2CPacket + ) : Event + + data class PositionLook( + val packet: PlayerPositionLookS2CPacket + ) : Event + + data class SetPosition( + val position: Vec3d, + val velocity: Vec3d, + val yaw: Float, + val pitch: Float + ) : Event + } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt index 5e14e4cfb..54cf30151 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Freecam.kt @@ -31,6 +31,7 @@ import com.lambda.interaction.managers.rotating.visibilty.lookAt import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.threading.runSafeAutomated +import com.lambda.util.Communication.info import com.lambda.util.Describable import com.lambda.util.NamedEnum import com.lambda.util.extension.rotation @@ -83,6 +84,7 @@ object Freecam : Module( private var rotation: Rotation = Rotation.ZERO private var velocity: Vec3d = Vec3d.ZERO + private var loading = false @JvmStatic fun updateCam() { @@ -123,6 +125,21 @@ object Freecam : Module( } } + listen { + loading = true + info("Respawned, waiting for position look packet to update freecam position...") + } + + listen { + info("Received position look packet, updating freecam position") + info("New position: ${it.position}") + if (loading) { + loading = false + position = player.eyePos + rotation = player.rotation + } + } + listen { rotation = rotation.withDelta( it.deltaYaw * SENSITIVITY_FACTOR, From ebef16510e2b38601432cb7325ecc3edebdd2158 Mon Sep 17 00:00:00 2001 From: Ic3Tank <61137113+IceTank@users.noreply.github.com> Date: Tue, 12 May 2026 01:38:36 +0200 Subject: [PATCH 2/3] Move freecam module --- .../lambda/module/modules/render/Freecam.kt | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 src/main/kotlin/com/lambda/module/modules/render/Freecam.kt diff --git a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt new file mode 100644 index 000000000..22ec72112 --- /dev/null +++ b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt @@ -0,0 +1,196 @@ +/* + * Copyright 2026 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.render + +import com.lambda.Lambda +import com.lambda.event.events.MovementEvent +import com.lambda.event.events.PlayerEvent +import com.lambda.event.events.RenderEvent +import com.lambda.event.events.TickEvent +import com.lambda.event.listener.SafeListener.Companion.listen +import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest +import com.lambda.interaction.managers.rotating.Rotation +import com.lambda.interaction.managers.rotating.RotationConfig +import com.lambda.interaction.managers.rotating.RotationMode +import com.lambda.interaction.managers.rotating.visibilty.lookAt +import com.lambda.module.Module +import com.lambda.module.tag.ModuleTag +import com.lambda.threading.runSafeAutomated +import com.lambda.util.Communication.info +import com.lambda.util.Describable +import com.lambda.util.NamedEnum +import com.lambda.util.extension.rotation +import com.lambda.util.math.interpolate +import com.lambda.util.math.plus +import com.lambda.util.math.times +import com.lambda.util.player.MovementUtils.calcMoveRad +import com.lambda.util.player.MovementUtils.cancel +import com.lambda.util.player.MovementUtils.handledByBaritone +import com.lambda.util.player.MovementUtils.isInputting +import com.lambda.util.player.MovementUtils.movementVector +import com.lambda.util.player.MovementUtils.newMovementInput +import com.lambda.util.player.MovementUtils.roundedForward +import com.lambda.util.player.MovementUtils.roundedStrafing +import com.lambda.util.player.MovementUtils.verticalMovement +import com.lambda.util.world.raycast.RayCastUtils.orMiss +import net.minecraft.client.option.Perspective +import net.minecraft.util.hit.BlockHitResult +import net.minecraft.util.hit.HitResult +import net.minecraft.util.math.BlockPos +import net.minecraft.util.math.Direction +import net.minecraft.util.math.Vec3d + +object Freecam : Module( + name = "Freecam", + description = "Move your camera freely", + tag = ModuleTag.RENDER, + autoDisable = true, +) { + private val speed by setting("Speed", 0.5, 0.1..1.0, 0.1, "Freecam movement speed", unit = "m/s") + private val sprint by setting("Sprint Multiplier", 3.0, 0.1..10.0, 0.1, description = "Set below 1.0 to fly slower on sprint.") + private val reach by setting("Reach", 10.0, 1.0..100.0, 1.0, "Freecam reach distance") + private val rotateMode by setting("Rotate Mode", FreecamRotationMode.None, "Rotation mode") + .onValueChange { _, it -> if (it == FreecamRotationMode.LookAtTarget) mc.crosshairTarget = BlockHitResult.createMissed(Vec3d.ZERO, Direction.UP, BlockPos.ORIGIN) } + private val relative by setting("Relative", false, "Moves freecam relative to player position") + .onValueChange { _, it -> if (it) lastPlayerPosition = player.pos } + private val keepYLevel by setting("Keep Y Level", false, "Don't change the camera y-level on player movement") { relative } + + override val rotationConfig = RotationConfig.Instant(RotationMode.Lock) + + private var lastPerspective = Perspective.FIRST_PERSON + private var lastPlayerPosition: Vec3d = Vec3d.ZERO + private var prevPosition: Vec3d = Vec3d.ZERO + private var position: Vec3d = Vec3d.ZERO + private val lerpPos: Vec3d + get() { + val tickProgress = Lambda.mc.gameRenderer.camera.lastTickProgress + return prevPosition.interpolate(tickProgress, position) + } + + private var rotation: Rotation = Rotation.Companion.ZERO + private var velocity: Vec3d = Vec3d.ZERO + private var loading = false + + @JvmStatic + fun updateCam() { + Lambda.mc.gameRenderer.apply { + camera.setRotation(rotation.yawF, rotation.pitchF) + camera.setPos(lerpPos.x, lerpPos.y, lerpPos.z) + } + } + + /** + * @see net.minecraft.entity.Entity.changeLookDirection + */ + private const val SENSITIVITY_FACTOR = 0.15 + + init { + onEnable { + lastPerspective = mc.options.perspective + position = player.eyePos + rotation = player.rotation + velocity = Vec3d.ZERO + lastPlayerPosition = player.pos + } + + onDisable { + mc.options.perspective = lastPerspective + } + + listen { + when (rotateMode) { + FreecamRotationMode.None -> return@listen + FreecamRotationMode.KeepRotation -> rotationRequest { rotation(rotation) }.submit() + FreecamRotationMode.LookAtTarget -> + mc.crosshairTarget?.let { + runSafeAutomated { + rotationRequest { rotation(lookAt(it.pos)) }.submit() + } + } + } + } + + listen { + loading = true + info("Respawned, waiting for position look packet to update freecam position...") + } + + listen { + info("Received position look packet, updating freecam position") + info("New position: ${it.position}") + if (loading) { + loading = false + position = player.eyePos + rotation = player.rotation + } + } + + listen { + rotation = rotation.withDelta( + it.deltaYaw * SENSITIVITY_FACTOR, + it.deltaPitch * SENSITIVITY_FACTOR + ) + it.cancel() + } + + listen { event -> + mc.options.perspective = Perspective.FIRST_PERSON + + // Don't block baritone from working + if (!event.input.handledByBaritone) { + // Reset actual input + event.input.cancel() + } + + // Create new input for freecam + val input = newMovementInput(assumeBaritone = false, slowdownCheck = false) + val sprintModifier = if (mc.options.sprintKey.isPressed) sprint else 1.0 + val moveDir = calcMoveRad(rotation.yawF, input.roundedForward, input.roundedStrafing) + var moveVec = movementVector(moveDir, input.verticalMovement) * speed * sprintModifier + if (!input.isInputting) moveVec *= Vec3d(0.0, 1.0, 0.0) + + // Apply movement + velocity + moveVec + velocity *= 0.6 + + // Update position + prevPosition = position + position += velocity + + if (relative) { + val delta = player.pos.subtract(lastPlayerPosition) + position += if (keepYLevel) Vec3d(delta.x, 0.0, delta.z) else delta + lastPlayerPosition = player.pos + } + } + + listen({ 1 }) { event -> // Higher priority then RotationManager to run before RotationManager modifies mc.crosshairTarget + mc.crosshairTarget = rotation + .rayCast(reach, lerpPos) + .orMiss // Can't be null (otherwise mc will spam "Null returned as 'hitResult', this shouldn't happen!") + + mc.crosshairTarget?.let { if (it.type != HitResult.Type.MISS) event.cancel() } + } + } + + enum class FreecamRotationMode(override val displayName: String, override val description: String) : NamedEnum, Describable { + None("None", "No rotation changes"), + LookAtTarget("Look At Target", "Look at the block or entity under your crosshair"), + KeepRotation("Keep Rotation", "Look in the same direction as the camera"); + } +} \ No newline at end of file From 634d68988eada52d06fa3517b4e3f6832226d3c8 Mon Sep 17 00:00:00 2001 From: Ic3Tank <61137113+IceTank@users.noreply.github.com> Date: Tue, 12 May 2026 02:03:11 +0200 Subject: [PATCH 3/3] fix merge --- src/main/kotlin/com/lambda/module/modules/render/Freecam.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt index 7732681c1..ec5ec2ead 100644 --- a/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt +++ b/src/main/kotlin/com/lambda/module/modules/render/Freecam.kt @@ -180,7 +180,6 @@ object Freecam : Module( } listen { - rotation = rotation.withDelta(it.deltaYaw * SENSITIVITY_FACTOR, it.deltaPitch * SENSITIVITY_FACTOR) rotation = rotation.withDelta( it.deltaYaw * SENSITIVITY_FACTOR, it.deltaPitch * SENSITIVITY_FACTOR @@ -259,7 +258,6 @@ object Freecam : Module( } listen({ 1 }) { event -> // Higher priority then RotationManager to run before RotationManager modifies mc.crosshairTarget - mc.crosshairTarget = rotation.rayCast(reach, lerpPos).orMiss // Can't be null (otherwise mc will spam "Null returned as 'hitResult', this shouldn't happen!") mc.crosshairTarget = rotation .rayCast(reach, lerpPos) .orMiss // Can't be null (otherwise mc will spam "Null returned as 'hitResult', this shouldn't happen!")