@@ -23,14 +23,17 @@ import com.lambda.module.Module
2323import com.lambda.module.modules.movement.BetterFirework.startFirework
2424import com.lambda.module.tag.ModuleTag
2525import com.lambda.threading.runSafe
26+ import com.lambda.util.Communication.info
2627import com.lambda.util.NamedEnum
2728import com.lambda.util.SpeedUnit
2829import com.lambda.util.Timer
2930import com.lambda.util.world.fastEntitySearch
3031import net.minecraft.client.network.ClientPlayerEntity
3132import net.minecraft.entity.projectile.FireworkRocketEntity
33+ import net.minecraft.text.Text.literal
3234import net.minecraft.util.math.Vec3d
3335import kotlin.time.Duration.Companion.seconds
36+ import kotlin.time.TimeSource
3437
3538object ElytraAttitudeControl : Module(
3639 name = " ElytraAttitudeControl" ,
@@ -49,7 +52,7 @@ object ElytraAttitudeControl : Module(
4952 val altitudeControllerI by setting(" Altitude Control I" , 0.04 , 0.0 .. 1.0 , 0.05 ).group(Group .AltitudeControl )
5053 val altitudeControllerConst by setting(" Altitude Control Const" , 0.0 , 0.0 .. 10.0 , 0.1 ).group(Group .AltitudeControl )
5154
52- val targetSpeed by setting(" Target Speed" , 28 .0 , 0.1 .. 50.0 , 0.1 , unit = " m/s" , description = " Adjusts pitch to control speed" )
55+ val targetSpeed by setting(" Target Speed" , 20 .0 , 0.1 .. 50.0 , 0.1 , unit = " m/s" , description = " Adjusts pitch to control speed" )
5356 { controlValue == Mode .Speed }
5457 val horizontalSpeed by setting(" Horizontal Speed" , false , description = " Uses horizontal speed instead of total speed for speed control" )
5558 { controlValue == Mode .Speed }
@@ -71,53 +74,129 @@ object ElytraAttitudeControl : Module(
7174 { altitudeControllerP }, { altitudeControllerD }, { altitudeControllerI },
7275 { altitudeControllerConst })
7376
77+ val usePitch40OnHeight by setting(" Use Pitch 40 On Height" , false , " Use Pitch 40 to gain height and speed" )
78+ val logHeightGain by setting(" Log Height Gain" , false , " Logs the height gained each cycle to the chat" )
79+ { usePitch40OnHeight }.group(Group .Pitch40Control )
80+ val minHeightForPitch40 by setting(" Min Height For Pitch 40" , 120 , 0 .. 256 , 10 , unit = " blocks" , description = " Minimum height to use Pitch 40" )
81+ { usePitch40OnHeight }.group(Group .Pitch40Control )
82+ val pitch40ExitHeight by setting(" Exit height" , 190 , 0 .. 256 , 10 , unit = " blocks" , description = " Height to exit Pitch 40 mode" )
83+ { usePitch40OnHeight }.group(Group .Pitch40Control )
84+ val pitch40UpStartAngle by setting(" Up Start Angle" , - 49f , - 90f .. 0f , .5f , description = " Start angle when going back up. negative pitch = looking up" )
85+ { usePitch40OnHeight }.group(Group .Pitch40Control )
86+ val pitch40DownAngle by setting(" Down Angle" , 33f , 0f .. 90f , .5f , description = " Angle to dive down at to gain speed" )
87+ { usePitch40OnHeight }.group(Group .Pitch40Control )
88+ val pitch40AngleChangeRate by setting(" Angle Change Rate" , 0.5f , 0.1f .. 5f , 0.01f , description = " Rate at which to increase pitch while in the fly up curve" )
89+ { usePitch40OnHeight }.group(Group .Pitch40Control )
90+ val pitch40SpeedThreshold by setting(" Speed Threshold" , 41f , 10f .. 100f , .5f , description = " Speed at which to start pitching up" )
91+ { usePitch40OnHeight }.group(Group .Pitch40Control )
92+ val pitch40UseFireworkOnUpTrajectory by setting(" Use Firework On Up Trajectory" , false , " Use fireworks when converting speed to altitude in the Pitch 40 maneuver" )
93+ { usePitch40OnHeight }.group(Group .Pitch40Control )
94+
95+ var controlState = ControlState .AttitudeControl
96+ var state = Pitch40State .GainSpeed
97+ var lastAngle = pitch40UpStartAngle
98+ var lastCycleFinish = TimeSource .Monotonic .markNow()
99+ var lastY = 0.0
100+
74101 val usageDelay = Timer ()
75102
76103 init {
77104 listen<TickEvent .Pre > {
78105 if (! player.isGliding) return @listen
79- if (disableOnFirework && player.hasFirework) return @listen
106+ run {
107+ when (controlState) {
108+ ControlState .AttitudeControl -> {
109+ if (disableOnFirework && player.hasFirework) {
110+ return @run
111+ }
112+ if (usePitch40OnHeight) {
113+ if (player.y < minHeightForPitch40) {
114+ controlState = ControlState .Pitch40Fly
115+ lastY = player.pos.y
116+ return @run
117+ }
118+ }
119+ val outputPitch = when (controlValue) {
120+ Mode .Speed -> {
121+ speedController.getOutput(targetSpeed, player.flySpeed(horizontalSpeed).toDouble())
122+ }
123+ Mode .Altitude -> {
124+ - 1 * altitudeController.getOutput(targetAltitude.toDouble(), player.y) // Negative because in minecraft pitch > 0 is looking down not up
125+ }
126+ }.coerceIn(- maxPitchAngle, maxPitchAngle)
127+ // lookAt(Rotation(player.yaw, newPitch.toFloat())).requestBy(this@ElytraAutopilot) // TODO: Use this when rotation system accepts pitch changes
128+ player.pitch = outputPitch.toFloat()
80129
81- val outputPitch = when (controlValue) {
82- Mode .Speed -> {
83- var speed = player.pos.subtract(lastPos)
84- if (horizontalSpeed) {
85- speed = Vec3d (speed.x, 0.0 , speed.z)
130+ if (usageDelay.timePassed(2 .seconds) && ! player.hasFirework) {
131+ if (useFireworkOnHeight && minHeight > player.y) {
132+ usageDelay.reset()
133+ runSafe {
134+ startFirework(true )
135+ }
136+ }
137+ if (useFireworkOnSpeed && minSpeed > player.flySpeed()) {
138+ usageDelay.reset()
139+ runSafe {
140+ startFirework(true )
141+ }
142+ }
143+ }
86144 }
145+ ControlState .Pitch40Fly -> {
146+ when (state) {
147+ Pitch40State .GainSpeed -> {
148+ player.pitch = pitch40DownAngle
149+ if (player.flySpeed() > pitch40SpeedThreshold) {
150+ state = Pitch40State .PitchUp
151+ }
152+ }
153+ Pitch40State .PitchUp -> {
154+ lastAngle - = 5f
155+ player.pitch = lastAngle
156+ if (lastAngle <= pitch40UpStartAngle) {
157+ state = Pitch40State .FlyUp
158+ if (pitch40UseFireworkOnUpTrajectory) {
159+ runSafe {
160+ startFirework(true )
161+ }
162+ }
163+ }
164+ }
165+ Pitch40State .FlyUp -> {
166+ lastAngle + = pitch40AngleChangeRate
167+ player.pitch = lastAngle
168+ if (lastAngle >= 0f ) {
169+ state = Pitch40State .GainSpeed
170+ if (logHeightGain) {
171+ var timeDelta = lastCycleFinish.elapsedNow().inWholeMilliseconds
172+ var heightDelta = player.pos.y - lastY
173+ var heightPerMinute = (heightDelta) / (timeDelta / 1000.0 ) * 60.0
174+ info(literal(" Height gained this cycle: %.2f in %.2f seconds (%.2f blocks/min)" .format(heightDelta, timeDelta / 1000.0 , heightPerMinute)))
175+ }
87176
88- speedController.getOutput(targetSpeed, SpeedUnit .MetersPerSecond .convertFromMinecraft(speed.length()))
89- }
90- Mode .Altitude -> {
91- val currentAltitude = player.y
92- - 1 * altitudeController.getOutput(targetAltitude.toDouble(), currentAltitude) // Negative because in minecraft pitch > 0 is looking down not up
93- }
94- }
95- val newPitch = outputPitch.coerceIn(- maxPitchAngle, maxPitchAngle)
96- // lookAt(Rotation(player.yaw, newPitch.toFloat())).requestBy(this@ElytraAutopilot) // TODO: Use this when rotation system accepts pitch changes
97- player.pitch = newPitch.toFloat()
98-
99- lastPos = player.pos
100-
101- if (usageDelay.timePassed(2 .seconds) && ! player.hasFirework) {
102- if (useFireworkOnHeight && minHeight > player.y) {
103- usageDelay.reset()
104- runSafe {
105- startFirework(true )
106- }
107- }
108- if (useFireworkOnSpeed && minSpeed > SpeedUnit .MetersPerSecond .convertFromMinecraft(player.velocity.length())) {
109- usageDelay.reset()
110- runSafe {
111- startFirework(true )
177+ lastCycleFinish = TimeSource .Monotonic .markNow()
178+ lastY = player.pos.y
179+ if (pitch40ExitHeight < player.y) {
180+ controlState = ControlState .AttitudeControl
181+ speedController.reset()
182+ altitudeController.reset()
183+ }
184+ }
185+ }
186+ }
112187 }
113188 }
114189 }
190+ lastPos = player.pos
115191 }
116192
117193 onEnable {
118194 speedController.reset()
119195 altitudeController.reset()
120196 lastPos = player.pos
197+ state = Pitch40State .GainSpeed
198+ controlState = ControlState .AttitudeControl
199+ lastAngle = pitch40UpStartAngle
121200 }
122201 }
123202
@@ -143,13 +222,36 @@ object ElytraAttitudeControl : Module(
143222 }
144223 }
145224
225+ /* *
226+ * Get the player's current speed in meters per second.
227+ */
228+ fun ClientPlayerEntity.flySpeed (onlyHorizontal : Boolean = false): Float {
229+ var delta = this .pos.subtract(lastPos)
230+ if (onlyHorizontal) {
231+ delta = Vec3d (delta.x, 0.0 , delta.z)
232+ }
233+ return SpeedUnit .MetersPerSecond .convertFromMinecraft(delta.length()).toFloat()
234+ }
235+
146236 enum class Mode {
147237 Speed ,
148238 Altitude ;
149239 }
150240
241+ enum class ControlState {
242+ AttitudeControl ,
243+ Pitch40Fly
244+ }
245+
151246 enum class Group (override val displayName : String ) : NamedEnum {
152247 SpeedControl (" Speed Control" ),
153- AltitudeControl (" Altitude Control" );
248+ AltitudeControl (" Altitude Control" ),
249+ Pitch40Control (" Pitch 40 Control" ),
250+ }
251+
252+ enum class Pitch40State {
253+ GainSpeed ,
254+ PitchUp ,
255+ FlyUp ,
154256 }
155257}
0 commit comments