@@ -36,13 +36,23 @@ import com.lambda.module.modules.movement.elytrafly.modes.GeneralElytraFly
3636import com.lambda.module.modules.movement.elytrafly.modes.GrimControlElytraFly
3737import com.lambda.module.tag.ModuleTag
3838import com.lambda.threading.runSafe
39+ import com.lambda.util.Timer
3940import com.lambda.util.extension.isElytraFlying
41+ import com.lambda.util.extension.prevPos
4042import com.lambda.util.player.MovementUtils.addSpeed
43+ import com.lambda.util.player.PlayerUtils.hasFirework
44+ import net.minecraft.component.DataComponentTypes
45+ import net.minecraft.entity.MovementType
46+ import net.minecraft.item.Items
47+ import net.minecraft.network.packet.c2s.play.PlayerInteractItemC2SPacket
48+ import net.minecraft.network.packet.c2s.play.PlayerMoveC2SPacket
4149import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket
4250import net.minecraft.sound.SoundEvents
4351import net.minecraft.util.math.Vec3d
4452import kotlin.math.abs
45- import kotlin.math.sin
53+ import kotlin.math.max
54+ import kotlin.math.min
55+ import kotlin.time.Duration.Companion.seconds
4656
4757object ElytraFly : Module(
4858 name = " ElytraFly" ,
@@ -57,8 +67,9 @@ object ElytraFly : Module(
5767
5868 private val boostSpeed by setting(" Boost" , 0.0 , 0.0 .. 0.5 , 0.005 , description = " Speed to add when flying" )
5969 private val rocketSpeed by setting(" Rocket Speed" , 1.0 , 0.0 .. 2.0 , 0.01 , description = " Speed multiplier that the rocket gives you" )
60- private val angledRocketBoost by setting(" Angled Rocket Boost" , true , description = " Automatically scale the firework rocket boost based on your pitch angle" )
61- private val maxAngledBoost by setting(" Max Angled Boost" , 0.75 , 0.0 .. 5.0 , 0.01 , description = " Additional speed added on top of your base rocket speed when flying diagonally at exactly 45 degrees" ) { angledRocketBoost }
70+ private val grimRocketBoost by setting(" Grim Rocket Boost" , true , description = " Automatically scale the firework rocket boost based on your pitch angle" )
71+ private val maxGrimBoost by setting(" Max Grim Boost" , 10.0 , 0.0 .. 10.0 , 0.01 , description = " Maximum additional speed the firework boost can add on top of the base rocket speed" ) { grimRocketBoost }
72+ private val safetyMargin by setting(" Safety Margin" , 0.2 , 0.0 .. 2.0 , 0.01 , " The time (in seconds) to shorten the firework use delay to account for ping variation" , " s" )
6273 private val mute by setting(" Mute Elytra" , false , " Mutes the elytra sound when gliding" )
6374 @JvmStatic val fakeFly by setting(" Fake Fly" , false , " Rapidly swaps the chestplate and elytra to give the appearance the player is flying without an elytra. May also reduce durability loss" )
6475
@@ -67,6 +78,7 @@ object ElytraFly : Module(
6778 private const val GRIM_CONTROL_TAB = " Grim Control"
6879// private const val PACKET_TAB = "Packet"
6980 private const val GENERAL_TAB = " None"
81+ private const val EXPLOIT_RESCALE = 1.65
7082
7183 @Tab(GENERAL_TAB ) @JvmStatic val generalMode by configBlock(GeneralElytraFly (this ))
7284 .withEdits { forEachSetting { visibility { old -> { old() && mode == FlyMode .General } } } }
@@ -77,16 +89,112 @@ object ElytraFly : Module(
7789// @Tab(CONTROL_TAB) @JvmStatic val controlMode by configBlock(ControlElytraFly(this))
7890// @Tab(PACKET_TAB) @JvmStatic val packetMode by configBlock(PacketElytraFly(this))
7991
92+ // Last sent movement packet state, used to rebuild Grim's firework prediction box.
93+ private var lastMovementIncludedPosition = true
94+ private var lastKnownClientVelocity = Vec3d .ZERO
95+ private var hasMovementState = false
96+ private var targetVelocity: Vec3d ? = null
97+
98+ // Shared firework tracking. Vanilla can briefly drop the firework entity for a tick,
99+ // so once a firework is used we hold this true until its flight duration runs out.
100+ private val fireworkTimer = Timer ()
101+ private var lastFireworkDuration = - 1.0
102+ @JvmStatic var hasFirework = false
103+ private var fireworkConfirmed = false
104+ private var fireworkReady = false
105+
80106 init {
81107 setDefaultAutomationConfig()
82108 .withEdits {
83109 hotbarConfig::tickStageMask.editSetting { defaultValue(TickEvent .ALL_STAGES .toMutableList()) }
84110 hideAllExcept(::inventoryConfig, ::rotationConfig)
85111 }
86112
87- onEnable { mode.elytraFly.onEnableListeners.forEach { it() } }
113+ onEnable {
114+ mode.elytraFly.onEnableListeners.forEach { it() }
115+ lastKnownClientVelocity = Vec3d .ZERO
116+ hasMovementState = false
117+ targetVelocity = null
118+ hasFirework = false
119+ fireworkConfirmed = false
120+ fireworkReady = false
121+ fireworkTimer.reset()
122+ lastFireworkDuration = - 1.0
123+ }
88124 onDisable { mode.elytraFly.onDisableListeners.forEach { it() } }
89125
126+ listen<TickEvent .Pre >(priority = { 1 }) {
127+ if (! player.isGliding) {
128+ hasFirework = false
129+ fireworkConfirmed = false
130+ fireworkReady = false
131+ return @listen
132+ }
133+ if (fireworkTimer.timePassed((lastFireworkDuration - safetyMargin).seconds)) {
134+ hasFirework = false
135+ fireworkConfirmed = false
136+ fireworkReady = false
137+ } else if (player.hasFirework) {
138+ fireworkConfirmed = true
139+ }
140+ }
141+
142+ listen<PacketEvent .Send .Pre > { event ->
143+ val packet = event.packet
144+ if (packet !is PlayerInteractItemC2SPacket ) return @listen
145+ val stack = player.getStackInHand(packet.hand)
146+ if (stack.item != Items .FIREWORK_ROCKET ) return @listen
147+ fireworkTimer.reset()
148+ lastFireworkDuration = (stack.get(DataComponentTypes .FIREWORKS )?.flightDuration ? : 1 ) * 0.5 + 0.5
149+ hasFirework = true
150+ }
151+
152+ listen<TickEvent .Pre > {
153+ if (! grimRocketBoost) return @listen
154+ if (! player.isGliding || ! hasFirework || ! fireworkConfirmed || ! fireworkReady || ! hasMovementState) return @listen
155+
156+ val aiming = Vec3d .fromPolar(
157+ RotationManager .movementPitch ? : player.pitch,
158+ RotationManager .movementYaw ? : player.yaw,
159+ )
160+ val serverRot = RotationManager .serverRotation
161+ val bounds = GrimFireworkBox .computeFireworksBounds(
162+ lastKnownClientVelocity,
163+ aiming,
164+ serverRot.pitchF,
165+ serverRot.yawF,
166+ lastMovementIncludedPosition,
167+ EXPLOIT_RESCALE ,
168+ player.finalGravity,
169+ ) ? : return @listen
170+ val claimed = farthestPointInBox(bounds, aiming)?.let { limitSpeed(it) } ? : return @listen
171+
172+ targetVelocity = claimed
173+ player.velocity = claimed
174+ }
175+
176+ listen<PacketEvent .Send .Post > { event ->
177+ val packet = event.packet as ? PlayerMoveC2SPacket ? : return @listen
178+ if (packet.changesPosition()) {
179+ val prevPos = player.prevPos
180+ val next = Vec3d (packet.getX(prevPos.x), packet.getY(prevPos.y), packet.getZ(prevPos.z))
181+ val delta = next.subtract(prevPos)
182+ if (hasMovementState) lastKnownClientVelocity = delta
183+ if (fireworkConfirmed) fireworkReady = true
184+ }
185+ lastMovementIncludedPosition = packet.changesPosition()
186+ hasMovementState = true
187+ }
188+
189+ listen<MovementEvent .Entity .Pre > { event ->
190+ if (event.entity != player || ! player.isGliding) return @listen
191+ val velocity = targetVelocity ? : return @listen
192+ targetVelocity = null
193+ player.velocity = velocity
194+ player.move(MovementType .SELF , velocity)
195+ event.cancel()
196+ }
197+
90198 listen<PacketEvent .Receive .Pre > { event ->
91199 if (event.packet !is PlayerPositionLookS2CPacket ) return @listen
92200 mode.elytraFly.onFlagListeners.forEach { it() }
@@ -107,32 +215,19 @@ object ElytraFly : Module(
107215
108216 @JvmStatic
109217 fun getFireworkTargetVelocity (rotPitch : Float , rotYaw : Float ): Vec3d {
110- var targetSpeed = 1.5 * rocketSpeed
111- if (angledRocketBoost) {
112- val scale = sin(Math .toRadians(abs(rotPitch) * 2.0 ))
113- targetSpeed = (1.5 * rocketSpeed) + (maxAngledBoost * scale)
114- }
115218 val vec = Vec3d .fromPolar(rotPitch, rotYaw)
116- val d = targetSpeed
219+ val d = 1.5 * rocketSpeed
117220 val e = 0.1 * rocketSpeed
118221 return vec.multiply(d + e * 2 )
119222 }
120223
121224 @JvmStatic
122225 fun boostRocket () =
123226 runSafe {
124- val rot = RotationManager .activeRotation
125-
126- var targetSpeed = 1.5 * rocketSpeed
127- if (angledRocketBoost) {
128- val scale = sin(Math .toRadians(abs(rot.pitch) * 2.0 ))
129- targetSpeed = (1.5 * rocketSpeed) + (maxAngledBoost * scale)
130- }
131-
132- val vec = Vec3d .fromPolar(rot.pitchF, rot.yawF)
227+ val vec = player.rotationVector
133228 val velocity = player.velocity
134229
135- val d = targetSpeed
230+ val d = 1.5 * rocketSpeed
136231 val e = 0.1 * rocketSpeed
137232
138233 player.velocity = velocity.add(
@@ -142,6 +237,45 @@ object ElytraFly : Module(
142237 )
143238 }
144239
240+ private fun farthestPointInBox (bounds : DoubleArray , aim : Vec3d ): Vec3d ? {
241+ val center = Vec3d (
242+ (bounds[0 ] + bounds[3 ]) / 2.0 ,
243+ (bounds[1 ] + bounds[4 ]) / 2.0 ,
244+ (bounds[2 ] + bounds[5 ]) / 2.0 ,
245+ )
246+ val direction = aim.normalize()
247+ var near = Double .NEGATIVE_INFINITY
248+ var far = Double .POSITIVE_INFINITY
249+ repeat(3 ) { axis ->
250+ val origin = if (axis == 0 ) center.x else if (axis == 1 ) center.y else center.z
251+ val min = bounds[axis]
252+ val max = bounds[axis + 3 ]
253+ val d = if (axis == 0 ) direction.x else if (axis == 1 ) direction.y else direction.z
254+ if (abs(d) < 1e- 12 ) {
255+ if (origin !in min.. max) return null
256+ return @repeat
257+ }
258+ var entry = (min - origin) / d
259+ var exit = (max - origin) / d
260+ if (entry > exit) {
261+ val tmp = entry
262+ entry = exit
263+ exit = tmp
264+ }
265+ near = max(near, entry)
266+ far = min(far, exit)
267+ }
268+ if (near > far) return null
269+ return center.add(direction.multiply(far))
270+ }
271+
272+ private fun limitSpeed (velocity : Vec3d ): Vec3d {
273+ val maxSpeed = 1.7 * rocketSpeed + maxGrimBoost
274+ val length = velocity.length()
275+ if (length <= maxSpeed) return velocity
276+ return velocity.multiply(maxSpeed / length)
277+ }
278+
145279 enum class FlyMode (private val elytraFlyGetter : () -> ElytraFlyMode ) {
146280 Bounce ({ bounceMode }),
147281// Control({ controlMode }),
0 commit comments