Skip to content

Commit ab8f8bc

Browse files
committed
feat(ElytraAltitudeControl): Add feature to use timer when chunks near are loading slowly.
1 parent 2599039 commit ab8f8bc

File tree

2 files changed

+162
-93
lines changed

2 files changed

+162
-93
lines changed

src/main/kotlin/com/lambda/module/modules/movement/ElytraAltitudeControl.kt

Lines changed: 152 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package com.lambda.module.modules.movement
1919

2020
import com.lambda.config.groups.RotationSettings
21+
import com.lambda.context.SafeContext
2122
import com.lambda.event.events.TickEvent
2223
import com.lambda.event.listener.SafeListener.Companion.listen
2324
import com.lambda.interaction.managers.rotating.Rotation
@@ -29,15 +30,17 @@ import com.lambda.threading.runSafe
2930
import com.lambda.util.Communication.info
3031
import com.lambda.util.NamedEnum
3132
import com.lambda.util.SpeedUnit
32-
import com.lambda.util.Timer
3333
import com.lambda.util.world.fastEntitySearch
3434
import net.minecraft.client.network.ClientPlayerEntity
35+
import net.minecraft.client.world.ClientWorld
3536
import net.minecraft.entity.projectile.FireworkRocketEntity
3637
import net.minecraft.text.Text.literal
38+
import net.minecraft.util.math.ChunkPos
3739
import net.minecraft.util.math.Vec3d
3840
import kotlin.time.Duration.Companion.seconds
3941
import kotlin.time.TimeSource
4042

43+
4144
object ElytraAltitudeControl : Module(
4245
name = "ElytraAttitudeControl",
4346
description = "Automatically control attitude or speed while elytra flying",
@@ -80,6 +83,10 @@ object ElytraAltitudeControl : Module(
8083
val pitch40SpeedThreshold by setting("Speed Threshold", 41f, 10f..100f, .5f, description = "Speed at which to start pitching up") { usePitch40OnHeight }.group(Group.Pitch40Control)
8184
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)
8285

86+
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)
87+
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)
89+
8390
override val rotationConfig = RotationSettings(this, Group.Rotation)
8491

8592
var controlState = ControlState.AttitudeControl
@@ -88,93 +95,18 @@ object ElytraAltitudeControl : Module(
8895
var lastCycleFinish = TimeSource.Monotonic.markNow()
8996
var lastY = 0.0
9097

91-
val usageDelay = Timer()
98+
val usageDelay = com.lambda.util.Timer()
9299

93100
init {
94101
listen<TickEvent.Pre> {
95-
if (!player.isGliding) return@listen
96-
run {
102+
if (player.isGliding) {
97103
when (controlState) {
98-
ControlState.AttitudeControl -> {
99-
if (disableOnFirework && player.hasFirework) {
100-
return@run
101-
}
102-
if (usePitch40OnHeight) {
103-
if (player.y < minHeightForPitch40) {
104-
controlState = ControlState.Pitch40Fly
105-
lastY = player.pos.y
106-
return@run
107-
}
108-
}
109-
val outputPitch = when (controlValue) {
110-
Mode.Speed -> {
111-
speedController.getOutput(targetSpeed, player.flySpeed(horizontalSpeed).toDouble())
112-
}
113-
Mode.Altitude -> {
114-
-1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up
115-
}
116-
}.coerceIn(-maxPitchAngle, maxPitchAngle)
117-
RotationRequest(Rotation(player.yaw, outputPitch.toFloat()), this@ElytraAltitudeControl).submit()
118-
119-
if (usageDelay.timePassed(2.seconds) && !player.hasFirework) {
120-
if (useFireworkOnHeight && minHeight > player.y) {
121-
usageDelay.reset()
122-
runSafe {
123-
startFirework(true)
124-
}
125-
}
126-
if (useFireworkOnSpeed && minSpeed > player.flySpeed()) {
127-
usageDelay.reset()
128-
runSafe {
129-
startFirework(true)
130-
}
131-
}
132-
}
133-
}
134-
ControlState.Pitch40Fly -> when (state) {
135-
Pitch40State.GainSpeed -> {
136-
RotationRequest(Rotation(player.yaw, pitch40DownAngle), this@ElytraAltitudeControl).submit()
137-
if (player.flySpeed() > pitch40SpeedThreshold) {
138-
state = Pitch40State.PitchUp
139-
}
140-
}
141-
Pitch40State.PitchUp -> {
142-
lastAngle -= 5f
143-
RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).submit()
144-
if (lastAngle <= pitch40UpStartAngle) {
145-
state = Pitch40State.FlyUp
146-
if (pitch40UseFireworkOnUpTrajectory) {
147-
runSafe {
148-
startFirework(true)
149-
}
150-
}
151-
}
152-
}
153-
Pitch40State.FlyUp -> {
154-
lastAngle += pitch40AngleChangeRate
155-
RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).submit()
156-
if (lastAngle >= 0f) {
157-
state = Pitch40State.GainSpeed
158-
if (logHeightGain) {
159-
var timeDelta = lastCycleFinish.elapsedNow().inWholeMilliseconds
160-
var heightDelta = player.pos.y - lastY
161-
var heightPerMinute = (heightDelta) / (timeDelta / 1000.0) * 60.0
162-
info(literal("Height gained this cycle: %.2f in %.2f seconds (%.2f blocks/min)".format(heightDelta, timeDelta / 1000.0, heightPerMinute)))
163-
}
164-
165-
lastCycleFinish = TimeSource.Monotonic.markNow()
166-
lastY = player.pos.y
167-
if (pitch40ExitHeight < player.y) {
168-
controlState = ControlState.AttitudeControl
169-
speedController.reset()
170-
altitudeController.reset()
171-
}
172-
}
173-
}
174-
}
104+
ControlState.AttitudeControl -> updateAltitudeControls()
105+
ControlState.Pitch40Fly -> updatePitch40Controls()
175106
}
107+
updateTimerUsage()
108+
lastPos = player.pos
176109
}
177-
lastPos = player.pos
178110
}
179111

180112
onEnable {
@@ -185,11 +117,146 @@ object ElytraAltitudeControl : Module(
185117
controlState = ControlState.AttitudeControl
186118
lastAngle = pitch40UpStartAngle
187119
}
120+
121+
onDisable {
122+
if (useTimerOnChunkLoad) {
123+
Timer.timer = timerReturnValue.toDouble()
124+
}
125+
}
188126
}
189127

190-
val ClientPlayerEntity.hasFirework: Boolean
128+
private fun SafeContext.updateAltitudeControls() {
129+
if (disableOnFirework && hasFirework) {
130+
return
131+
}
132+
if (usePitch40OnHeight) {
133+
if (player.y < minHeightForPitch40) {
134+
controlState = ControlState.Pitch40Fly
135+
lastY = player.pos.y
136+
return
137+
}
138+
}
139+
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+
}
146+
}.coerceIn(-maxPitchAngle, maxPitchAngle)
147+
RotationRequest(Rotation(player.yaw, outputPitch.toFloat()), this@ElytraAltitudeControl).submit()
148+
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+
}
162+
}
163+
}
164+
165+
private fun SafeContext.updatePitch40Controls() {
166+
when (state) {
167+
Pitch40State.GainSpeed -> {
168+
RotationRequest(Rotation(player.yaw, pitch40DownAngle), this@ElytraAltitudeControl).submit()
169+
if (player.flySpeed() > pitch40SpeedThreshold) {
170+
state = Pitch40State.PitchUp
171+
}
172+
}
173+
Pitch40State.PitchUp -> {
174+
lastAngle -= 5f
175+
RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).submit()
176+
if (lastAngle <= pitch40UpStartAngle) {
177+
state = Pitch40State.FlyUp
178+
if (pitch40UseFireworkOnUpTrajectory) {
179+
runSafe {
180+
startFirework(true)
181+
}
182+
}
183+
}
184+
}
185+
Pitch40State.FlyUp -> {
186+
lastAngle += pitch40AngleChangeRate
187+
RotationRequest(Rotation(player.yaw, lastAngle), this@ElytraAltitudeControl).submit()
188+
if (lastAngle >= 0f) {
189+
state = Pitch40State.GainSpeed
190+
if (logHeightGain) {
191+
val timeDelta = lastCycleFinish.elapsedNow().inWholeMilliseconds
192+
val heightDelta = player.pos.y - lastY
193+
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)))
195+
}
196+
197+
lastCycleFinish = TimeSource.Monotonic.markNow()
198+
lastY = player.pos.y
199+
if (pitch40ExitHeight < player.y) {
200+
controlState = ControlState.AttitudeControl
201+
speedController.reset()
202+
altitudeController.reset()
203+
}
204+
}
205+
}
206+
}
207+
}
208+
209+
private fun SafeContext.updateTimerUsage() {
210+
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()
219+
}
220+
}
221+
}
222+
}
223+
224+
val hasFirework: Boolean
191225
get() = runSafe { return fastEntitySearch<FireworkRocketEntity>(4.0) { it.shooter == this.player }.any() } ?: false
192226

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+
232+
fun nearestUnloadedChunk(world: ClientWorld, player: ClientPlayerEntity): ChunkPos? {
233+
val scanRangeInt = 25
234+
var nearestChunk: ChunkPos? = null
235+
var nearestDistance = Double.MAX_VALUE
236+
val playerChunk = player.chunkPos
237+
238+
for (x in -scanRangeInt..<scanRangeInt) {
239+
for (z in -scanRangeInt..<scanRangeInt) {
240+
val chunkPos = ChunkPos(playerChunk.x + x, playerChunk.z + z)
241+
if (world.chunkManager.isChunkLoaded(chunkPos.x, chunkPos.z)) {
242+
continue
243+
}
244+
val distance = distanceToChunk(chunkPos, player).toDouble()
245+
if (distance < nearestDistance) {
246+
nearestDistance = distance
247+
nearestChunk = chunkPos
248+
}
249+
}
250+
}
251+
return nearestChunk
252+
}
253+
254+
fun distanceToChunk(chunkPos: ChunkPos, player: ClientPlayerEntity): Float {
255+
val playerPos = player.getPos()
256+
val chunkCenter = Vec3d((chunkPos.startX + 8).toDouble(), playerPos.y, (chunkPos.startZ + 8).toDouble())
257+
return playerPos.distanceTo(chunkCenter).toFloat()
258+
}
259+
193260
class PIController(val valueP: () -> Double, val valueD: () -> Double, val valueI: () -> Double, val constant: () -> Double) {
194261
var accumulator = 0.0 // Integral term accumulator
195262
var lastDiff = 0.0
@@ -234,7 +301,8 @@ object ElytraAltitudeControl : Module(
234301
SpeedControl("Speed Control"),
235302
AltitudeControl("Altitude Control"),
236303
Pitch40Control("Pitch 40 Control"),
237-
Rotation("Rotation")
304+
Rotation("Rotation"),
305+
TimerControls("Timer Controls"),
238306
}
239307

240308
enum class Pitch40State {

src/main/kotlin/com/lambda/module/modules/movement/Timer.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@ import com.lambda.module.Module
2323
import com.lambda.module.tag.ModuleTag
2424

2525
object Timer : Module(
26-
name = "Timer",
27-
description = "Modify client tick speed.",
28-
tag = ModuleTag.MOVEMENT,
26+
name = "Timer",
27+
description = "Modify client tick speed.",
28+
tag = ModuleTag.MOVEMENT,
2929
) {
30-
private val timer by setting("Timer", 1.0, 0.0..10.0, 0.01)
30+
@JvmStatic
31+
var timer by setting("Timer", 1.0, 0.0..10.0, 0.01)
3132

32-
init {
33-
listen<ClientEvent.TimerUpdate> {
34-
it.speed = timer.coerceAtLeast(0.05)
35-
}
36-
}
33+
init {
34+
listen<ClientEvent.TimerUpdate> {
35+
it.speed = timer.coerceAtLeast(0.05)
36+
}
37+
}
3738
}

0 commit comments

Comments
 (0)