From c07f5970ab79d73641a955d80c8c430458206cf8 Mon Sep 17 00:00:00 2001 From: Ic3Tank <61137113+IceTank@users.noreply.github.com> Date: Tue, 30 Dec 2025 23:33:17 +0100 Subject: [PATCH 1/3] feat(ElytraAltitudeControl): Add feature to use timer when chunks near are loading slowly. --- .../modules/movement/ElytraAltitudeControl.kt | 237 ++++++++++++------ .../lambda/module/modules/movement/Timer.kt | 19 +- 2 files changed, 164 insertions(+), 92 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index 9e8ca0753..2341040ad 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -17,6 +17,8 @@ package com.lambda.module.modules.movement +import com.lambda.config.groups.RotationSettings +import com.lambda.context.SafeContext import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig import com.lambda.config.applyEdits import com.lambda.event.events.TickEvent @@ -29,15 +31,17 @@ import com.lambda.threading.runSafe import com.lambda.util.Communication.info import com.lambda.util.NamedEnum import com.lambda.util.SpeedUnit -import com.lambda.util.Timer import com.lambda.util.world.fastEntitySearch import net.minecraft.client.network.ClientPlayerEntity +import net.minecraft.client.world.ClientWorld import net.minecraft.entity.projectile.FireworkRocketEntity import net.minecraft.text.Text.literal +import net.minecraft.util.math.ChunkPos import net.minecraft.util.math.Vec3d import kotlin.time.Duration.Companion.seconds import kotlin.time.TimeSource + object ElytraAltitudeControl : Module( name = "ElytraAttitudeControl", description = "Automatically control attitude or speed while elytra flying", @@ -80,13 +84,19 @@ object ElytraAltitudeControl : Module( val pitch40SpeedThreshold by setting("Speed Threshold", 41f, 10f..100f, .5f, description = "Speed at which to start pitching up") { usePitch40OnHeight }.group(Group.Pitch40Control) val pitch40UseFireworkOnUpTrajectory by setting("Use Firework On Up Trajectory", false, "Use fireworks when converting speed to altitude in the Pitch 40 maneuver") { usePitch40OnHeight }.group(Group.Pitch40Control) + val useTimerOnChunkLoad by setting("Use Timer On Slow Chunk Loading", false, "Slows down the game when chunks load slow to keep momentum").group(Group.TimerControls) + val timerMinChunkDistance by setting("Min Chunk Distance", 4, 1..20, 1, "Min unloaded chunk distance to start timer effect", unit = " chunks") { useTimerOnChunkLoad }.group(Group.TimerControls) + val timerReturnValue by setting("Timer Return Value", 1.0f, 0.0f..1.0f, 0.05f, description = "Timer speed to return when above min chunk distance") { useTimerOnChunkLoad }.group(Group.TimerControls) + + override val rotationConfig = RotationSettings(this, Group.Rotation) + var controlState = ControlState.AttitudeControl var state = Pitch40State.GainSpeed var lastAngle = pitch40UpStartAngle var lastCycleFinish = TimeSource.Monotonic.markNow() var lastY = 0.0 - val usageDelay = Timer() + val usageDelay = com.lambda.util.Timer() init { setDefaultAutomationConfig { @@ -96,89 +106,14 @@ object ElytraAltitudeControl : Module( } listen { - if (!player.isGliding) return@listen - run { + if (player.isGliding) { when (controlState) { - ControlState.AttitudeControl -> { - if (disableOnFirework && hasFirework) { - return@run - } - if (usePitch40OnHeight) { - if (player.y < minHeightForPitch40) { - controlState = ControlState.Pitch40Fly - lastY = player.pos.y - return@run - } - } - val outputPitch = when (controlValue) { - Mode.Speed -> { - speedController.getOutput(targetSpeed, player.flySpeed(horizontalSpeed).toDouble()) - } - Mode.Altitude -> { - -1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up - } - }.coerceIn(-maxPitchAngle, maxPitchAngle) - rotationRequest { pitch(outputPitch) }.submit() - - if (usageDelay.timePassed(2.seconds) && !hasFirework) { - if (useFireworkOnHeight && minHeight > player.y) { - usageDelay.reset() - runSafe { - startFirework(true) - } - } - if (useFireworkOnSpeed && minSpeed > player.flySpeed()) { - usageDelay.reset() - runSafe { - startFirework(true) - } - } - } - } - ControlState.Pitch40Fly -> when (state) { - Pitch40State.GainSpeed -> { - rotationRequest { pitch(pitch40DownAngle) }.submit() - if (player.flySpeed() > pitch40SpeedThreshold) { - state = Pitch40State.PitchUp - } - } - Pitch40State.PitchUp -> { - lastAngle -= 5f - rotationRequest { pitch(lastAngle) }.submit() - if (lastAngle <= pitch40UpStartAngle) { - state = Pitch40State.FlyUp - if (pitch40UseFireworkOnUpTrajectory) { - runSafe { - startFirework(true) - } - } - } - } - Pitch40State.FlyUp -> { - lastAngle += pitch40AngleChangeRate - rotationRequest { pitch(lastAngle) }.submit() - if (lastAngle >= 0f) { - state = Pitch40State.GainSpeed - if (logHeightGain) { - var timeDelta = lastCycleFinish.elapsedNow().inWholeMilliseconds - var heightDelta = player.pos.y - lastY - var heightPerMinute = (heightDelta) / (timeDelta / 1000.0) * 60.0 - info(literal("Height gained this cycle: %.2f in %.2f seconds (%.2f blocks/min)".format(heightDelta, timeDelta / 1000.0, heightPerMinute))) - } - - lastCycleFinish = TimeSource.Monotonic.markNow() - lastY = player.pos.y - if (pitch40ExitHeight < player.y) { - controlState = ControlState.AttitudeControl - speedController.reset() - altitudeController.reset() - } - } - } - } + ControlState.AttitudeControl -> updateAltitudeControls() + ControlState.Pitch40Fly -> updatePitch40Controls() } + updateTimerUsage() + lastPos = player.pos } - lastPos = player.pos } onEnable { @@ -189,11 +124,146 @@ object ElytraAltitudeControl : Module( controlState = ControlState.AttitudeControl lastAngle = pitch40UpStartAngle } + + onDisable { + if (useTimerOnChunkLoad) { + Timer.timer = timerReturnValue.toDouble() + } + } + } + + private fun SafeContext.updateAltitudeControls() { + if (disableOnFirework && hasFirework) { + return + } + if (usePitch40OnHeight) { + if (player.y < minHeightForPitch40) { + controlState = ControlState.Pitch40Fly + lastY = player.pos.y + return + } + } + val outputPitch = when (controlValue) { + Mode.Speed -> { + speedController.getOutput(targetSpeed, player.flySpeed(horizontalSpeed).toDouble()) + } + Mode.Altitude -> { + -1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up + } + }.coerceIn(-maxPitchAngle, maxPitchAngle) + RotationRequest(Rotation(player.yaw, outputPitch.toFloat()), this@ElytraAltitudeControl).submit() + + if (usageDelay.timePassed(2.seconds) && !hasFirework) { + if (useFireworkOnHeight && minHeight > player.y) { + usageDelay.reset() + runSafe { + startFirework(true) + } + } + if (useFireworkOnSpeed && minSpeed > player.flySpeed()) { + usageDelay.reset() + runSafe { + startFirework(true) + } + } + } + } + + private fun SafeContext.updatePitch40Controls() { + when (state) { + Pitch40State.GainSpeed -> { + rotationRequest { pitch(pitch40DownAngle) }.submit() + if (player.flySpeed() > pitch40SpeedThreshold) { + state = Pitch40State.PitchUp + } + } + Pitch40State.PitchUp -> { + lastAngle -= 5f + rotationRequest { pitch(lastAngle) }.submit() + if (lastAngle <= pitch40UpStartAngle) { + state = Pitch40State.FlyUp + if (pitch40UseFireworkOnUpTrajectory) { + runSafe { + startFirework(true) + } + } + } + } + Pitch40State.FlyUp -> { + lastAngle += pitch40AngleChangeRate + rotationRequest { pitch(lastAngle) }.submit() + if (lastAngle >= 0f) { + state = Pitch40State.GainSpeed + if (logHeightGain) { + val timeDelta = lastCycleFinish.elapsedNow().inWholeMilliseconds + val heightDelta = player.pos.y - lastY + val heightPerMinute = (heightDelta) / (timeDelta / 1000.0) * 60.0 + info(literal("Height gained this cycle: %.2f in %.2f seconds (%.2f blocks/min)".format(heightDelta, timeDelta / 1000.0, heightPerMinute))) + } + + lastCycleFinish = TimeSource.Monotonic.markNow() + lastY = player.pos.y + if (pitch40ExitHeight < player.y) { + controlState = ControlState.AttitudeControl + speedController.reset() + altitudeController.reset() + } + } + } + } + } + + private fun SafeContext.updateTimerUsage() { + if (useTimerOnChunkLoad) { + val nearestChunkDistance = getNearestUnloadedChunkDistance() + if (nearestChunkDistance != -1 && nearestChunkDistance / 16.0 <= timerMinChunkDistance) { + val speedFactor = 0.1f + (nearestChunkDistance.toFloat() / timerMinChunkDistance.toFloat() * 16.0) * 0.9f + Timer.enable() + Timer.timer = speedFactor.coerceIn(0.1, 1.0) + } else { + if (Timer.isEnabled) { + Timer.timer = timerReturnValue.toDouble() + } + } + } } val hasFirework: Boolean get() = runSafe { return fastEntitySearch(4.0) { it.shooter == player }.any() } ?: false + private fun SafeContext.getNearestUnloadedChunkDistance(): Int { + val nearestChunk: ChunkPos? = nearestUnloadedChunk(world, player) + return if (nearestChunk != null) distanceToChunk(nearestChunk, player).toInt() else -1 + } + + fun nearestUnloadedChunk(world: ClientWorld, player: ClientPlayerEntity): ChunkPos? { + val scanRangeInt = 25 + var nearestChunk: ChunkPos? = null + var nearestDistance = Double.MAX_VALUE + val playerChunk = player.chunkPos + + for (x in -scanRangeInt.. Double, val valueD: () -> Double, val valueI: () -> Double, val constant: () -> Double) { var accumulator = 0.0 // Integral term accumulator var lastDiff = 0.0 @@ -238,7 +308,8 @@ object ElytraAltitudeControl : Module( SpeedControl("Speed Control"), AltitudeControl("Altitude Control"), Pitch40Control("Pitch 40 Control"), - Rotation("Rotation") + Rotation("Rotation"), + TimerControls("Timer Controls"), } enum class Pitch40State { diff --git a/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt b/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt index d7dcce08a..af4b2503c 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/Timer.kt @@ -23,15 +23,16 @@ import com.lambda.module.Module import com.lambda.module.tag.ModuleTag object Timer : Module( - name = "Timer", - description = "Modify client tick speed.", - tag = ModuleTag.MOVEMENT, + name = "Timer", + description = "Modify client tick speed.", + tag = ModuleTag.MOVEMENT, ) { - private val timer by setting("Timer", 1.0, 0.0..10.0, 0.01) + @JvmStatic + var timer by setting("Timer", 1.0, 0.0..10.0, 0.01) - init { - listen { - it.speed = timer.coerceAtLeast(0.05) - } - } + init { + listen { + it.speed = timer.coerceAtLeast(0.05) + } + } } From 7ae42238a5f360800bd57f3d13a0153c31d899da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emy=20=F0=9F=92=9C?= Date: Tue, 27 Jan 2026 12:01:46 -0500 Subject: [PATCH 2/3] nit and refactor --- .../modules/movement/ElytraAltitudeControl.kt | 112 ++++++++---------- 1 file changed, 52 insertions(+), 60 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index 2341040ad..3b8cfae5f 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Lambda + * 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 @@ -27,21 +27,19 @@ import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotat import com.lambda.module.Module import com.lambda.module.modules.movement.BetterFirework.startFirework import com.lambda.module.tag.ModuleTag -import com.lambda.threading.runSafe import com.lambda.util.Communication.info import com.lambda.util.NamedEnum import com.lambda.util.SpeedUnit +import com.lambda.util.math.dist import com.lambda.util.world.fastEntitySearch import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.client.world.ClientWorld import net.minecraft.entity.projectile.FireworkRocketEntity -import net.minecraft.text.Text.literal import net.minecraft.util.math.ChunkPos import net.minecraft.util.math.Vec3d import kotlin.time.Duration.Companion.seconds import kotlin.time.TimeSource - object ElytraAltitudeControl : Module( name = "ElytraAttitudeControl", description = "Automatically control attitude or speed while elytra flying", @@ -86,7 +84,7 @@ object ElytraAltitudeControl : Module( val useTimerOnChunkLoad by setting("Use Timer On Slow Chunk Loading", false, "Slows down the game when chunks load slow to keep momentum").group(Group.TimerControls) val timerMinChunkDistance by setting("Min Chunk Distance", 4, 1..20, 1, "Min unloaded chunk distance to start timer effect", unit = " chunks") { useTimerOnChunkLoad }.group(Group.TimerControls) - val timerReturnValue by setting("Timer Return Value", 1.0f, 0.0f..1.0f, 0.05f, description = "Timer speed to return when above min chunk distance") { useTimerOnChunkLoad }.group(Group.TimerControls) + val timerReturnValue by setting("Timer Return Value", 1.0, 0.0..1.0, 0.05, description = "Timer speed to return when above min chunk distance") { useTimerOnChunkLoad }.group(Group.TimerControls) override val rotationConfig = RotationSettings(this, Group.Rotation) @@ -98,6 +96,9 @@ object ElytraAltitudeControl : Module( val usageDelay = com.lambda.util.Timer() + val SafeContext.hasFirework: Boolean + get() = fastEntitySearch(4.0) { it.shooter == this.player }.any() + init { setDefaultAutomationConfig { applyEdits { @@ -111,6 +112,7 @@ object ElytraAltitudeControl : Module( ControlState.AttitudeControl -> updateAltitudeControls() ControlState.Pitch40Fly -> updatePitch40Controls() } + updateTimerUsage() lastPos = player.pos } @@ -126,16 +128,14 @@ object ElytraAltitudeControl : Module( } onDisable { - if (useTimerOnChunkLoad) { - Timer.timer = timerReturnValue.toDouble() - } + if (useTimerOnChunkLoad) + Timer.timer = timerReturnValue } } private fun SafeContext.updateAltitudeControls() { - if (disableOnFirework && hasFirework) { - return - } + if (disableOnFirework && hasFirework) return + if (usePitch40OnHeight) { if (player.y < minHeightForPitch40) { controlState = ControlState.Pitch40Fly @@ -143,29 +143,23 @@ object ElytraAltitudeControl : Module( return } } + val outputPitch = when (controlValue) { - Mode.Speed -> { - speedController.getOutput(targetSpeed, player.flySpeed(horizontalSpeed).toDouble()) - } - Mode.Altitude -> { - -1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up - } + Mode.Speed -> speedController.getOutput(targetSpeed, player.flySpeed(horizontalSpeed).toDouble()) + Mode.Altitude -> -1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up }.coerceIn(-maxPitchAngle, maxPitchAngle) - RotationRequest(Rotation(player.yaw, outputPitch.toFloat()), this@ElytraAltitudeControl).submit() - if (usageDelay.timePassed(2.seconds) && !hasFirework) { - if (useFireworkOnHeight && minHeight > player.y) { - usageDelay.reset() - runSafe { - startFirework(true) - } - } - if (useFireworkOnSpeed && minSpeed > player.flySpeed()) { - usageDelay.reset() - runSafe { - startFirework(true) - } - } + rotationRequest { + yaw(player.yaw) + pitch(outputPitch) + }.submit() + + if (usageDelay.delayIfPassed(2.seconds) && !hasFirework) { + if (useFireworkOnHeight && minHeight > player.y) + startFirework(true) + + if (useFireworkOnSpeed && minSpeed > player.flySpeed()) + startFirework(true) } } @@ -173,20 +167,18 @@ object ElytraAltitudeControl : Module( when (state) { Pitch40State.GainSpeed -> { rotationRequest { pitch(pitch40DownAngle) }.submit() - if (player.flySpeed() > pitch40SpeedThreshold) { + + if (player.flySpeed() > pitch40SpeedThreshold) state = Pitch40State.PitchUp - } } Pitch40State.PitchUp -> { lastAngle -= 5f rotationRequest { pitch(lastAngle) }.submit() if (lastAngle <= pitch40UpStartAngle) { state = Pitch40State.FlyUp - if (pitch40UseFireworkOnUpTrajectory) { - runSafe { - startFirework(true) - } - } + + if (pitch40UseFireworkOnUpTrajectory) + startFirework(true) } } Pitch40State.FlyUp -> { @@ -194,15 +186,18 @@ object ElytraAltitudeControl : Module( rotationRequest { pitch(lastAngle) }.submit() if (lastAngle >= 0f) { state = Pitch40State.GainSpeed + if (logHeightGain) { val timeDelta = lastCycleFinish.elapsedNow().inWholeMilliseconds val heightDelta = player.pos.y - lastY val heightPerMinute = (heightDelta) / (timeDelta / 1000.0) * 60.0 - info(literal("Height gained this cycle: %.2f in %.2f seconds (%.2f blocks/min)".format(heightDelta, timeDelta / 1000.0, heightPerMinute))) + + info("Height gained this cycle: %.2f in %.2f seconds (%.2f blocks/min)".format(heightDelta, timeDelta / 1000.0, heightPerMinute)) } lastCycleFinish = TimeSource.Monotonic.markNow() lastY = player.pos.y + if (pitch40ExitHeight < player.y) { controlState = ControlState.AttitudeControl speedController.reset() @@ -215,27 +210,22 @@ object ElytraAltitudeControl : Module( private fun SafeContext.updateTimerUsage() { if (useTimerOnChunkLoad) { - val nearestChunkDistance = getNearestUnloadedChunkDistance() - if (nearestChunkDistance != -1 && nearestChunkDistance / 16.0 <= timerMinChunkDistance) { - val speedFactor = 0.1f + (nearestChunkDistance.toFloat() / timerMinChunkDistance.toFloat() * 16.0) * 0.9f - Timer.enable() - Timer.timer = speedFactor.coerceIn(0.1, 1.0) - } else { - if (Timer.isEnabled) { - Timer.timer = timerReturnValue.toDouble() + nearestUnloadedChunk(world, player) + ?.dist(player.pos) + ?.let { + if (it / 16.0 <= timerMinChunkDistance) { + val speedFactor = 0.1f + (it / timerMinChunkDistance * 16.0) * 0.9f + Timer.enable() + Timer.timer = speedFactor.coerceIn(0.1, 1.0) + } + } + ?: run { + if (Timer.isEnabled) + Timer.timer = timerReturnValue } - } } } - val hasFirework: Boolean - get() = runSafe { return fastEntitySearch(4.0) { it.shooter == player }.any() } ?: false - - private fun SafeContext.getNearestUnloadedChunkDistance(): Int { - val nearestChunk: ChunkPos? = nearestUnloadedChunk(world, player) - return if (nearestChunk != null) distanceToChunk(nearestChunk, player).toInt() else -1 - } - fun nearestUnloadedChunk(world: ClientWorld, player: ClientPlayerEntity): ChunkPos? { val scanRangeInt = 25 var nearestChunk: ChunkPos? = null @@ -245,9 +235,10 @@ object ElytraAltitudeControl : Module( for (x in -scanRangeInt.. Date: Tue, 27 Jan 2026 12:37:45 -0500 Subject: [PATCH 3/3] fixed timer value based on distance to unloaded chunk --- .../modules/movement/ElytraAltitudeControl.kt | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt index 3b8cfae5f..a39f9e67b 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -30,7 +30,7 @@ import com.lambda.module.tag.ModuleTag import com.lambda.util.Communication.info import com.lambda.util.NamedEnum import com.lambda.util.SpeedUnit -import com.lambda.util.math.dist +import com.lambda.util.math.distCenter import com.lambda.util.world.fastEntitySearch import net.minecraft.client.network.ClientPlayerEntity import net.minecraft.client.world.ClientWorld @@ -113,7 +113,25 @@ object ElytraAltitudeControl : Module( ControlState.Pitch40Fly -> updatePitch40Controls() } - updateTimerUsage() + if (useTimerOnChunkLoad) { + nearestUnloadedChunk(world, player) + ?.distCenter(player.pos) + ?.let { + if (it <= timerMinChunkDistance * 16.0) { + val speedFactor = 0.1f + (it / (timerMinChunkDistance * 16.0)) * 0.9f + Timer.enable() + Timer.timer = speedFactor.coerceIn(0.1, 1.0) + } + } + ?: run { + // FixMe: + // When the timer is changed in an unloaded chunk and the player stop gliding, + // the timer value is never set back. + if (Timer.isEnabled) + Timer.timer = timerReturnValue + } + } + lastPos = player.pos } } @@ -208,24 +226,6 @@ object ElytraAltitudeControl : Module( } } - private fun SafeContext.updateTimerUsage() { - if (useTimerOnChunkLoad) { - nearestUnloadedChunk(world, player) - ?.dist(player.pos) - ?.let { - if (it / 16.0 <= timerMinChunkDistance) { - val speedFactor = 0.1f + (it / timerMinChunkDistance * 16.0) * 0.9f - Timer.enable() - Timer.timer = speedFactor.coerceIn(0.1, 1.0) - } - } - ?: run { - if (Timer.isEnabled) - Timer.timer = timerReturnValue - } - } - } - fun nearestUnloadedChunk(world: ClientWorld, player: ClientPlayerEntity): ChunkPos? { val scanRangeInt = 25 var nearestChunk: ChunkPos? = null