Skip to content

Commit 1fed80c

Browse files
committed
Add BetterFirework Module
1 parent 5533bd2 commit 1fed80c

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

src/main/java/com/lambda/mixin/MinecraftClientMixin.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.lambda.event.events.TickEvent;
2525
import com.lambda.gui.DearImGui;
2626
import com.lambda.gui.components.ClickGuiLayout;
27+
import com.lambda.module.modules.movement.BetterFirework;
2728
import com.lambda.module.modules.player.Interact;
2829
import com.lambda.module.modules.player.InventoryMove;
2930
import com.lambda.module.modules.player.PacketMine;
@@ -189,6 +190,20 @@ void injectFastPlace(CallbackInfo ci) {
189190
itemUseCooldown = Interact.getPlaceDelay();
190191
}
191192

193+
@Inject(method = "doItemUse", at = @At(value = "HEAD"), cancellable = true)
194+
void injectItemUse(CallbackInfo ci) {
195+
if (!BetterFirework.INSTANCE.isEnabled()) return;
196+
197+
if (BetterFirework.INSTANCE.onInteract()) ci.cancel();
198+
}
199+
200+
@Inject(method = "doItemPick", at = @At(value = "HEAD"), cancellable = true)
201+
void injectItemPick(CallbackInfo ci) {
202+
if (!BetterFirework.INSTANCE.isEnabled()) return;
203+
204+
if (BetterFirework.INSTANCE.onPick()) ci.cancel();
205+
}
206+
192207
@WrapMethod(method = "getTargetMillisPerTick")
193208
float getTargetMillisPerTick(float millis, Operation<Float> original) {
194209
var length = TimerManager.INSTANCE.getLength();
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright 2025 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
19+
20+
import com.lambda.context.SafeContext
21+
import com.lambda.event.events.TickEvent
22+
import com.lambda.event.listener.SafeListener.Companion.listen
23+
import com.lambda.module.Module
24+
import com.lambda.module.tag.ModuleTag
25+
import com.lambda.threading.runSafe
26+
import net.minecraft.client.network.ClientPlayerEntity
27+
import net.minecraft.item.ItemStack
28+
import net.minecraft.item.Items
29+
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket
30+
import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket
31+
import net.minecraft.network.packet.c2s.play.PlayerInteractItemC2SPacket
32+
import net.minecraft.network.packet.c2s.play.UpdateSelectedSlotC2SPacket
33+
import net.minecraft.screen.PlayerScreenHandler
34+
import net.minecraft.screen.slot.SlotActionType
35+
import net.minecraft.util.Hand
36+
import net.minecraft.util.hit.HitResult
37+
38+
object BetterFirework : Module(
39+
name = "BetterFirework",
40+
description = "Improves firework usage",
41+
tag = ModuleTag.MOVEMENT,
42+
) {
43+
private var autoTakeoff by setting("Auto Takeoff", true)
44+
private var silent by setting("Silent", true)
45+
private var swing by setting("Swing ", true)
46+
private var middleClick by setting("Middle click", true)
47+
private var middleClickCancel by setting("Middle Click Cancel", false, visibility = { middleClick })
48+
49+
private var state = BetterFireworkState.IDLE
50+
private var openDelay = 0
51+
52+
init {
53+
listen<TickEvent.Pre> {
54+
if (openDelay > 0) {
55+
openDelay--
56+
if (openDelay == 0 && state == BetterFireworkState.WAIT_AIRBORNE) {
57+
state = BetterFireworkState.IDLE
58+
startFlying()
59+
}
60+
}
61+
}
62+
}
63+
64+
fun onInteract(): Boolean {
65+
if (!autoTakeoff) return false
66+
67+
runSafe {
68+
if (player.inventory.selectedStack?.item == Items.FIREWORK_ROCKET) {
69+
if (mc.crosshairTarget != null && mc.crosshairTarget!!.type != HitResult.Type.MISS) {
70+
return false
71+
}
72+
73+
mc.itemUseCooldown += 4
74+
return startFlying()
75+
}
76+
}
77+
return false
78+
}
79+
80+
// Do on pick so mc does not select blocks on middle click after we start flying
81+
fun onPick() =
82+
runSafe {
83+
if (!middleClick) return false
84+
if (mc.crosshairTarget?.type == HitResult.Type.BLOCK && !middleClickCancel) {
85+
return false
86+
}
87+
if (player.isGliding) {
88+
startFirework(silent)
89+
} else {
90+
startFlying()
91+
}
92+
return true
93+
} ?: false // Edouardo: :3333333
94+
95+
fun SafeContext.startFlying(): Boolean {
96+
if (player.isOnGround) {
97+
state = BetterFireworkState.WAIT_AIRBORNE
98+
player.jump()
99+
openDelay = 1; // Magic number
100+
return true
101+
}
102+
103+
if (player.isGliding) return false
104+
105+
if (canOpenElytra(player)) {
106+
connection.sendPacket(ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.START_FALL_FLYING))
107+
startFirework(silent)
108+
return true
109+
}
110+
111+
return false
112+
}
113+
114+
fun canOpenElytra(player: ClientPlayerEntity): Boolean {
115+
return !player.abilities.flying && !player.hasVehicle() && !player.isClimbing && player.checkGliding()
116+
}
117+
118+
fun SafeContext.sendSwing() {
119+
if (swing) {
120+
player.swingHand(Hand.MAIN_HAND)
121+
} else {
122+
connection.sendPacket(HandSwingC2SPacket(Hand.MAIN_HAND))
123+
}
124+
}
125+
126+
fun SafeContext.startFirework(silent: Boolean): Boolean {
127+
val fireworkSlot = getFireworkAtHotbar()
128+
if (fireworkSlot != -1) {
129+
player.networkHandler.sendPacket(UpdateSelectedSlotC2SPacket(fireworkSlot))
130+
player.networkHandler.sendPacket(PlayerInteractItemC2SPacket(Hand.MAIN_HAND, 0, player.yaw, player.pitch))
131+
sendSwing()
132+
player.networkHandler.sendPacket(UpdateSelectedSlotC2SPacket(player.getInventory().selectedSlot))
133+
return true
134+
} else if (silent) {
135+
val fireworkIndex = getFireworkInInventoryScreen()
136+
if (fireworkIndex != -1) {
137+
interaction.clickSlot(player.playerScreenHandler.syncId, fireworkIndex, 0, SlotActionType.SWAP, mc.player)
138+
139+
if (player.getInventory().selectedSlot != 0) {
140+
player.networkHandler.sendPacket(UpdateSelectedSlotC2SPacket(0))
141+
player.networkHandler.sendPacket(PlayerInteractItemC2SPacket(Hand.MAIN_HAND, 0, player.yaw, player.pitch))
142+
sendSwing()
143+
player.networkHandler.sendPacket(UpdateSelectedSlotC2SPacket(player.getInventory().selectedSlot))
144+
} else {
145+
player.networkHandler.sendPacket(PlayerInteractItemC2SPacket(Hand.MAIN_HAND, 0, player.getYaw(), player.getPitch()))
146+
sendSwing()
147+
}
148+
149+
interaction.clickSlot(player.playerScreenHandler.syncId, fireworkIndex, 0, SlotActionType.SWAP, mc.player)
150+
return true
151+
}
152+
}
153+
return false
154+
}
155+
156+
private fun SafeContext.getFireworkAtHotbar(): Int {
157+
for (i in 0..8) {
158+
val itemStack: ItemStack = player.getInventory().getStack(i)
159+
if (itemStack.item !== Items.FIREWORK_ROCKET) continue
160+
return i
161+
}
162+
return -1
163+
}
164+
165+
private fun SafeContext.getFireworkInInventoryScreen(): Int {
166+
val screenHandler: PlayerScreenHandler = player.playerScreenHandler
167+
for (i in PlayerScreenHandler.INVENTORY_START..<PlayerScreenHandler.INVENTORY_END) {
168+
val itemStack = screenHandler.getSlot(i).stack
169+
if (itemStack.item !== Items.FIREWORK_ROCKET) continue
170+
return i
171+
}
172+
return -1
173+
}
174+
175+
enum class BetterFireworkState {
176+
IDLE,
177+
WAIT_AIRBORNE
178+
}
179+
}

0 commit comments

Comments
 (0)