Skip to content

Commit 3c48997

Browse files
committed
working, but flawed grim rocket boost
1 parent 063701c commit 3c48997

4 files changed

Lines changed: 286 additions & 56 deletions

File tree

src/main/java/com/lambda/mixin/entity/LivingEntityMixin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ private void injectTravelGliding(Vec3d movementInput, CallbackInfo ci) {
199199
if (lambda$instance != Lambda.getMc().player) return;
200200
final var grimMode = ElytraFly.getGrimControlMode();
201201
if (ElytraFly.getGrimControlMode().isEnabled() &&
202-
!grimMode.getFlipFlopMode().isFlipFlopping().invoke(grimMode.getHasFirework()) &&
202+
!grimMode.getFlipFlopMode().isFlipFlopping().invoke(ElytraFly.getHasFirework()) &&
203203
!grimMode.getMoving()
204204
) ci.cancel();
205205
}

src/main/kotlin/com/lambda/module/modules/movement/elytrafly/ElytraFly.kt

Lines changed: 154 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,23 @@ import com.lambda.module.modules.movement.elytrafly.modes.GeneralElytraFly
3636
import com.lambda.module.modules.movement.elytrafly.modes.GrimControlElytraFly
3737
import com.lambda.module.tag.ModuleTag
3838
import com.lambda.threading.runSafe
39+
import com.lambda.util.Timer
3940
import com.lambda.util.extension.isElytraFlying
41+
import com.lambda.util.extension.prevPos
4042
import 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
4149
import net.minecraft.network.packet.s2c.play.PlayerPositionLookS2CPacket
4250
import net.minecraft.sound.SoundEvents
4351
import net.minecraft.util.math.Vec3d
4452
import 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

4757
object 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 }),
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2026 Lambda
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.lambda.module.modules.movement.elytrafly
19+
20+
import net.minecraft.util.math.MathHelper
21+
import net.minecraft.util.math.Vec3d
22+
import kotlin.math.asin
23+
import kotlin.math.cos
24+
import kotlin.math.max
25+
import kotlin.math.min
26+
import kotlin.math.sin
27+
28+
object GrimFireworkBox {
29+
fun computeFireworksBounds(
30+
lastKnownClientVelocity: Vec3d,
31+
currentRotation: Vec3d,
32+
lastSentPitch: Float,
33+
lastSentYaw: Float,
34+
lastMovementIncludedPosition: Boolean,
35+
rescaleAmount: Double,
36+
gravity: Double
37+
): DoubleArray? {
38+
val simulatedVelocity = calculateGlidingVelocity(lastKnownClientVelocity, currentRotation, gravity)
39+
40+
val currentLook = currentRotation.normalize()
41+
val lastLook = Vec3d.fromPolar(lastSentPitch, lastSentYaw).normalize()
42+
val antiTickSkipping = if (lastMovementIncludedPosition) 0.05 else 0.0
43+
44+
var minX = min(-antiTickSkipping, currentLook.x) + min(-antiTickSkipping, lastLook.x)
45+
var minY = min(-antiTickSkipping, currentLook.y) + min(-antiTickSkipping, lastLook.y)
46+
var minZ = min(-antiTickSkipping, currentLook.z) + min(-antiTickSkipping, lastLook.z)
47+
var maxX = max(antiTickSkipping, currentLook.x) + max(antiTickSkipping, lastLook.x)
48+
var maxY = max(antiTickSkipping, currentLook.y) + max(antiTickSkipping, lastLook.y)
49+
var maxZ = max(antiTickSkipping, currentLook.z) + max(antiTickSkipping, lastLook.z)
50+
51+
if (rescaleAmount <= 0.0) return null
52+
53+
minX = max(-rescaleAmount, minX * rescaleAmount)
54+
minY = max(-rescaleAmount, minY * rescaleAmount)
55+
minZ = max(-rescaleAmount, minZ * rescaleAmount)
56+
maxX = min(rescaleAmount, maxX * rescaleAmount)
57+
maxY = min(rescaleAmount, maxY * rescaleAmount)
58+
maxZ = min(rescaleAmount, maxZ * rescaleAmount)
59+
60+
return doubleArrayOf(
61+
simulatedVelocity.x + min(0.0, minX - lastKnownClientVelocity.x),
62+
simulatedVelocity.y + min(0.0, minY - lastKnownClientVelocity.y),
63+
simulatedVelocity.z + min(0.0, minZ - lastKnownClientVelocity.z),
64+
simulatedVelocity.x + max(0.0, maxX - lastKnownClientVelocity.x),
65+
simulatedVelocity.y + max(0.0, maxY - lastKnownClientVelocity.y),
66+
simulatedVelocity.z + max(0.0, maxZ - lastKnownClientVelocity.z)
67+
)
68+
}
69+
70+
fun calculateGlidingVelocity(oldVelocity: Vec3d, rotation: Vec3d, gravity: Double): Vec3d {
71+
val horizontalLookLength = rotation.horizontalLength()
72+
val horizontalSpeed = oldVelocity.horizontalLength()
73+
val pitch = asin(MathHelper.clamp(-rotation.y, -1.0, 1.0)).toFloat()
74+
val pitchCosSquared = MathHelper.square(cos(pitch.toDouble()))
75+
76+
var velocity = oldVelocity.add(0.0, gravity * (pitchCosSquared * 0.75 - 1.0), 0.0)
77+
78+
if (velocity.y < 0 && horizontalLookLength > 0) {
79+
val lift = velocity.y * -0.1 * pitchCosSquared
80+
velocity = velocity.add(rotation.x * lift / horizontalLookLength, lift, rotation.z * lift / horizontalLookLength)
81+
}
82+
83+
if (pitch < 0 && horizontalLookLength > 0) {
84+
val dive = horizontalSpeed * -sin(pitch.toDouble()) * 0.04
85+
velocity = velocity.add(-rotation.x * dive / horizontalLookLength, dive * 3.2, -rotation.z * dive / horizontalLookLength)
86+
}
87+
88+
if (horizontalLookLength > 0) {
89+
val targetScale = horizontalSpeed / horizontalLookLength
90+
velocity = velocity.add((rotation.x * targetScale - velocity.x) * 0.1, 0.0, (rotation.z * targetScale - velocity.z) * 0.1)
91+
}
92+
93+
return velocity.multiply(0.99, 0.98, 0.99)
94+
}
95+
}

0 commit comments

Comments
 (0)