Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,22 @@ package com.lambda.module.modules.movement

import com.lambda.config.AutomationConfig.Companion.setDefaultAutomationConfig
import com.lambda.config.applyEdits
import com.lambda.context.SafeContext
import com.lambda.event.events.ClientEvent
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.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.Timer
import com.lambda.util.math.distCenter
import com.lambda.util.player.hasFirework
import net.minecraft.client.network.ClientPlayerEntity
import net.minecraft.text.Text.literal
import net.minecraft.client.world.ClientWorld
import net.minecraft.util.math.ChunkPos
import net.minecraft.util.math.Vec3d
import kotlin.time.Duration.Companion.seconds
import kotlin.time.TimeSource
Expand Down Expand Up @@ -79,13 +81,16 @@ 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)

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 {
Expand All @@ -94,90 +99,22 @@ object ElytraAltitudeControl : Module(
}
}

listen<ClientEvent.TimerUpdate> {
val timerValue = getTimerValue()
if (timerValue != null) {
it.speed = timerValue
}
}

listen<TickEvent.Pre> {
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 { pitch(outputPitch) }.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 { 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()
}

lastPos = player.pos
}
lastPos = player.pos
}

onEnable {
Expand All @@ -190,6 +127,125 @@ object ElytraAltitudeControl : Module(
}
}

private fun SafeContext.getTimerValue(): Double? {
if (!useTimerOnChunkLoad) return null
return nearestUnloadedChunk(world, player)
?.distCenter(player.pos)
?.let {
if (it <= timerMinChunkDistance * 16.0) {
val speedFactor = 0.1f + (it / (timerMinChunkDistance * 16.0)) * 0.9f
return@let speedFactor.coerceIn(0.1, 1.0)
} else {
return@let null
}
}
}

private fun SafeContext.updateAltitudeControls() {
if (disableOnFirework && player.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 {
yaw(player.yaw)
pitch(outputPitch)
}.submit()

if (usageDelay.delayIfPassed(2.seconds) && !player.hasFirework) {
if (useFireworkOnHeight && minHeight > player.y)
startFirework(true)

if (useFireworkOnSpeed && minSpeed > player.flySpeed())
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)
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("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()
}
}
}
}
}

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..<scanRangeInt) {
for (z in -scanRangeInt..<scanRangeInt) {
val chunkPos = ChunkPos(playerChunk.x + x, playerChunk.z + z)

if (world.chunkManager.isChunkLoaded(chunkPos.x, chunkPos.z))
continue

val distance = distanceToChunk(chunkPos, player).toDouble()
if (distance < nearestDistance) {
nearestDistance = distance
nearestChunk = chunkPos
}
}
}

return nearestChunk
}

fun distanceToChunk(chunkPos: ChunkPos, player: ClientPlayerEntity): Float {
val playerPos = player.pos
val chunkCenter = Vec3d((chunkPos.startX + 8).toDouble(), playerPos.y, (chunkPos.startZ + 8).toDouble())
return playerPos.distanceTo(chunkCenter).toFloat()
}

class PIController(val valueP: () -> Double, val valueD: () -> Double, val valueI: () -> Double, val constant: () -> Double) {
var accumulator = 0.0 // Integral term accumulator
var lastDiff = 0.0
Expand Down Expand Up @@ -234,12 +290,13 @@ object ElytraAltitudeControl : Module(
SpeedControl("Speed Control"),
AltitudeControl("Altitude Control"),
Pitch40Control("Pitch 40 Control"),
Rotation("Rotation")
Rotation("Rotation"),
TimerControls("Timer Controls"),
}

enum class Pitch40State {
GainSpeed,
PitchUp,
FlyUp,
}
}
}
19 changes: 10 additions & 9 deletions src/main/kotlin/com/lambda/module/modules/movement/Timer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClientEvent.TimerUpdate> {
it.speed = timer.coerceAtLeast(0.05)
}
}
init {
listen<ClientEvent.TimerUpdate> {
it.speed = timer.coerceAtLeast(0.05)
}
}
}