11/*
2- * Copyright 2025 Lambda
2+ * Copyright 2026 Lambda
33 *
44 * This program is free software: you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,26 +21,23 @@ import com.lambda.config.groups.RotationSettings
2121import com.lambda.context.SafeContext
2222import com.lambda.event.events.TickEvent
2323import com.lambda.event.listener.SafeListener.Companion.listen
24- import com.lambda.interaction.managers.rotating.Rotation
25- import com.lambda.interaction.managers.rotating.RotationRequest
24+ import com.lambda.interaction.managers.rotating.IRotationRequest.Companion.rotationRequest
2625import com.lambda.module.Module
2726import com.lambda.module.modules.movement.BetterFirework.startFirework
2827import com.lambda.module.tag.ModuleTag
29- import com.lambda.threading.runSafe
3028import com.lambda.util.Communication.info
3129import com.lambda.util.NamedEnum
3230import com.lambda.util.SpeedUnit
31+ import com.lambda.util.math.dist
3332import com.lambda.util.world.fastEntitySearch
3433import net.minecraft.client.network.ClientPlayerEntity
3534import net.minecraft.client.world.ClientWorld
3635import net.minecraft.entity.projectile.FireworkRocketEntity
37- import net.minecraft.text.Text.literal
3836import net.minecraft.util.math.ChunkPos
3937import net.minecraft.util.math.Vec3d
4038import kotlin.time.Duration.Companion.seconds
4139import kotlin.time.TimeSource
4240
43-
4441object ElytraAltitudeControl : Module(
4542 name = " ElytraAttitudeControl" ,
4643 description = " Automatically control attitude or speed while elytra flying" ,
@@ -85,7 +82,7 @@ object ElytraAltitudeControl : Module(
8582
8683 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 )
8784 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 )
88- 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 )
85+ 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 )
8986
9087 override val rotationConfig = RotationSettings (this , Group .Rotation )
9188
@@ -97,13 +94,17 @@ object ElytraAltitudeControl : Module(
9794
9895 val usageDelay = com.lambda.util.Timer ()
9996
97+ val SafeContext .hasFirework: Boolean
98+ get() = fastEntitySearch<FireworkRocketEntity >(4.0 ) { it.shooter == this .player }.any()
99+
100100 init {
101101 listen<TickEvent .Pre > {
102102 if (player.isGliding) {
103103 when (controlState) {
104104 ControlState .AttitudeControl -> updateAltitudeControls()
105105 ControlState .Pitch40Fly -> updatePitch40Controls()
106106 }
107+
107108 updateTimerUsage()
108109 lastPos = player.pos
109110 }
@@ -119,83 +120,89 @@ object ElytraAltitudeControl : Module(
119120 }
120121
121122 onDisable {
122- if (useTimerOnChunkLoad) {
123- Timer .timer = timerReturnValue.toDouble()
124- }
123+ if (useTimerOnChunkLoad)
124+ Timer .timer = timerReturnValue
125125 }
126126 }
127127
128128 private fun SafeContext.updateAltitudeControls () {
129- if (disableOnFirework && hasFirework) {
130- return
131- }
129+ if (disableOnFirework && hasFirework) return
130+
132131 if (usePitch40OnHeight) {
133132 if (player.y < minHeightForPitch40) {
134133 controlState = ControlState .Pitch40Fly
135134 lastY = player.pos.y
136135 return
137136 }
138137 }
138+
139139 val outputPitch = when (controlValue) {
140- Mode .Speed -> {
141- speedController.getOutput(targetSpeed, player.flySpeed(horizontalSpeed).toDouble())
142- }
143- Mode .Altitude -> {
144- - 1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up
145- }
140+ Mode .Speed -> speedController.getOutput(targetSpeed, player.flySpeed(horizontalSpeed).toDouble())
141+ Mode .Altitude -> - 1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up
146142 }.coerceIn(- maxPitchAngle, maxPitchAngle)
147- RotationRequest (Rotation (player.yaw, outputPitch.toFloat()), this @ElytraAltitudeControl).submit()
148143
149- if (usageDelay.timePassed(2 .seconds) && ! hasFirework) {
150- if (useFireworkOnHeight && minHeight > player.y) {
151- usageDelay.reset()
152- runSafe {
153- startFirework(true )
154- }
155- }
156- if (useFireworkOnSpeed && minSpeed > player.flySpeed()) {
157- usageDelay.reset()
158- runSafe {
159- startFirework(true )
160- }
161- }
144+ rotationRequest {
145+ yaw(player.yaw)
146+ pitch(outputPitch)
147+ }.submit()
148+
149+ if (usageDelay.delayIfPassed(2 .seconds) && ! hasFirework) {
150+ if (useFireworkOnHeight && minHeight > player.y)
151+ startFirework(true )
152+
153+ if (useFireworkOnSpeed && minSpeed > player.flySpeed())
154+ startFirework(true )
162155 }
163156 }
164157
165158 private fun SafeContext.updatePitch40Controls () {
166159 when (state) {
167160 Pitch40State .GainSpeed -> {
168- RotationRequest (Rotation (player.yaw, pitch40DownAngle), this @ElytraAltitudeControl).submit()
169- if (player.flySpeed() > pitch40SpeedThreshold) {
161+ rotationRequest {
162+ yaw(player.yaw)
163+ pitch(pitch40DownAngle)
164+ }.submit()
165+
166+ if (player.flySpeed() > pitch40SpeedThreshold)
170167 state = Pitch40State .PitchUp
171- }
172168 }
173169 Pitch40State .PitchUp -> {
174170 lastAngle - = 5f
175- RotationRequest (Rotation (player.yaw, lastAngle), this @ElytraAltitudeControl).submit()
171+
172+ rotationRequest {
173+ yaw(player.yaw)
174+ pitch(lastAngle)
175+ }.submit()
176+
176177 if (lastAngle <= pitch40UpStartAngle) {
177178 state = Pitch40State .FlyUp
178- if (pitch40UseFireworkOnUpTrajectory) {
179- runSafe {
180- startFirework(true )
181- }
182- }
179+
180+ if (pitch40UseFireworkOnUpTrajectory)
181+ startFirework(true )
183182 }
184183 }
185184 Pitch40State .FlyUp -> {
186185 lastAngle + = pitch40AngleChangeRate
187- RotationRequest (Rotation (player.yaw, lastAngle), this @ElytraAltitudeControl).submit()
186+
187+ rotationRequest {
188+ yaw(player.yaw)
189+ pitch(lastAngle)
190+ }.submit()
191+
188192 if (lastAngle >= 0f ) {
189193 state = Pitch40State .GainSpeed
194+
190195 if (logHeightGain) {
191196 val timeDelta = lastCycleFinish.elapsedNow().inWholeMilliseconds
192197 val heightDelta = player.pos.y - lastY
193198 val heightPerMinute = (heightDelta) / (timeDelta / 1000.0 ) * 60.0
194- info(literal(" Height gained this cycle: %.2f in %.2f seconds (%.2f blocks/min)" .format(heightDelta, timeDelta / 1000.0 , heightPerMinute)))
199+
200+ info(" Height gained this cycle: %.2f in %.2f seconds (%.2f blocks/min)" .format(heightDelta, timeDelta / 1000.0 , heightPerMinute))
195201 }
196202
197203 lastCycleFinish = TimeSource .Monotonic .markNow()
198204 lastY = player.pos.y
205+
199206 if (pitch40ExitHeight < player.y) {
200207 controlState = ControlState .AttitudeControl
201208 speedController.reset()
@@ -208,27 +215,22 @@ object ElytraAltitudeControl : Module(
208215
209216 private fun SafeContext.updateTimerUsage () {
210217 if (useTimerOnChunkLoad) {
211- val nearestChunkDistance = getNearestUnloadedChunkDistance()
212- if (nearestChunkDistance != - 1 && nearestChunkDistance / 16.0 <= timerMinChunkDistance) {
213- val speedFactor = 0.1f + (nearestChunkDistance.toFloat() / timerMinChunkDistance.toFloat() * 16.0 ) * 0.9f
214- Timer .enable()
215- Timer .timer = speedFactor.coerceIn(0.1 , 1.0 )
216- } else {
217- if (Timer .isEnabled) {
218- Timer .timer = timerReturnValue.toDouble()
218+ nearestUnloadedChunk(world, player)
219+ ?.dist(player.pos)
220+ ?.let {
221+ if (it / 16.0 <= timerMinChunkDistance) {
222+ val speedFactor = 0.1f + (it / timerMinChunkDistance * 16.0 ) * 0.9f
223+ Timer .enable()
224+ Timer .timer = speedFactor.coerceIn(0.1 , 1.0 )
225+ }
226+ }
227+ ? : run {
228+ if (Timer .isEnabled)
229+ Timer .timer = timerReturnValue
219230 }
220- }
221231 }
222232 }
223233
224- val hasFirework: Boolean
225- get() = runSafe { return fastEntitySearch<FireworkRocketEntity >(4.0 ) { it.shooter == this .player }.any() } ? : false
226-
227- private fun SafeContext.getNearestUnloadedChunkDistance (): Int {
228- val nearestChunk: ChunkPos ? = nearestUnloadedChunk(world, player)
229- return if (nearestChunk != null ) distanceToChunk(nearestChunk, player).toInt() else - 1
230- }
231-
232234 fun nearestUnloadedChunk (world : ClientWorld , player : ClientPlayerEntity ): ChunkPos ? {
233235 val scanRangeInt = 25
234236 var nearestChunk: ChunkPos ? = null
@@ -238,21 +240,23 @@ object ElytraAltitudeControl : Module(
238240 for (x in - scanRangeInt.. < scanRangeInt) {
239241 for (z in - scanRangeInt.. < scanRangeInt) {
240242 val chunkPos = ChunkPos (playerChunk.x + x, playerChunk.z + z)
241- if (world.chunkManager.isChunkLoaded(chunkPos.x, chunkPos.z)) {
243+
244+ if (world.chunkManager.isChunkLoaded(chunkPos.x, chunkPos.z))
242245 continue
243- }
246+
244247 val distance = distanceToChunk(chunkPos, player).toDouble()
245248 if (distance < nearestDistance) {
246249 nearestDistance = distance
247250 nearestChunk = chunkPos
248251 }
249252 }
250253 }
254+
251255 return nearestChunk
252256 }
253257
254258 fun distanceToChunk (chunkPos : ChunkPos , player : ClientPlayerEntity ): Float {
255- val playerPos = player.getPos()
259+ val playerPos = player.pos
256260 val chunkCenter = Vec3d ((chunkPos.startX + 8 ).toDouble(), playerPos.y, (chunkPos.startZ + 8 ).toDouble())
257261 return playerPos.distanceTo(chunkCenter).toFloat()
258262 }
@@ -310,4 +314,4 @@ object ElytraAltitudeControl : Module(
310314 PitchUp ,
311315 FlyUp ,
312316 }
313- }
317+ }
0 commit comments