From ab8f8bcc59c4e190826cbb9840365824a25f14ba 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] feat(ElytraAltitudeControl): Add feature to use timer when chunks near are loading slowly. --- .../modules/movement/ElytraAltitudeControl.kt | 236 +++++++++++------- .../lambda/module/modules/movement/Timer.kt | 19 +- 2 files changed, 162 insertions(+), 93 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 da17e36f4..a9a259a9b 100644 --- a/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt +++ b/src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt @@ -18,6 +18,7 @@ package com.lambda.module.modules.movement import com.lambda.config.groups.RotationSettings +import com.lambda.context.SafeContext import com.lambda.event.events.TickEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.interaction.managers.rotating.Rotation @@ -29,15 +30,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,6 +83,10 @@ 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 @@ -88,93 +95,18 @@ object ElytraAltitudeControl : Module( var lastCycleFinish = TimeSource.Monotonic.markNow() var lastY = 0.0 - val usageDelay = Timer() + val usageDelay = com.lambda.util.Timer() init { listen { - if (!player.isGliding) return@listen - run { + if (player.isGliding) { when (controlState) { - ControlState.AttitudeControl -> { - if (disableOnFirework && player.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(Rotation(player.yaw, outputPitch.toFloat()), this@ElytraAltitudeControl).submit() - - if (usageDelay.timePassed(2.seconds) && !player.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(Rotation(player.yaw, pitch40DownAngle), this@ElytraAltitudeControl).submit() - if (player.flySpeed() > pitch40SpeedThreshold) { - state = Pitch40State.PitchUp - } - } - Pitch40State.PitchUp -> { - lastAngle -= 5f - RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).submit() - if (lastAngle <= pitch40UpStartAngle) { - state = Pitch40State.FlyUp - if (pitch40UseFireworkOnUpTrajectory) { - runSafe { - startFirework(true) - } - } - } - } - Pitch40State.FlyUp -> { - lastAngle += pitch40AngleChangeRate - RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).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 { @@ -185,11 +117,146 @@ object ElytraAltitudeControl : Module( controlState = ControlState.AttitudeControl lastAngle = pitch40UpStartAngle } + + onDisable { + if (useTimerOnChunkLoad) { + Timer.timer = timerReturnValue.toDouble() + } + } } - val ClientPlayerEntity.hasFirework: Boolean + 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(Rotation(player.yaw, pitch40DownAngle), this@ElytraAltitudeControl).submit() + if (player.flySpeed() > pitch40SpeedThreshold) { + state = Pitch40State.PitchUp + } + } + Pitch40State.PitchUp -> { + lastAngle -= 5f + RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).submit() + if (lastAngle <= pitch40UpStartAngle) { + state = Pitch40State.FlyUp + if (pitch40UseFireworkOnUpTrajectory) { + runSafe { + startFirework(true) + } + } + } + } + Pitch40State.FlyUp -> { + lastAngle += pitch40AngleChangeRate + RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).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 == this.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 @@ -234,7 +301,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) + } + } }