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 @@ -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
Expand All @@ -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",
Expand Down Expand Up @@ -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
Expand All @@ -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<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(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 {
Expand All @@ -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<FireworkRocketEntity>(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..<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.getPos()
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,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 {
Expand Down
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)
}
}
}
Loading