Skip to content

Commit 9554ee9

Browse files
IceTankbeanbag44emyfops
authored
Feature (Module): Add BetterFirework Module (#154)
This Module adds better Fireworks utility. Features include: * Auto takeoff while standing or falling by interacting with fireworks * Auto takeoff on middle click (pick block key) * Auto firework use on middle click * Silent firework use from inventory with middle click --------- Co-authored-by: beanbag44 <pidgeonmaster64@gmail.com> Co-authored-by: Emy 💜 <swag@emy.systems>
1 parent 70f95ac commit 9554ee9

File tree

3 files changed

+276
-0
lines changed

3 files changed

+276
-0
lines changed

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

Lines changed: 13 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,18 @@ void injectFastPlace(CallbackInfo ci) {
189190
itemUseCooldown = Interact.getPlaceDelay();
190191
}
191192

193+
@WrapMethod(method = "doItemUse")
194+
void injectItemUse(Operation<Void> original) {
195+
if (BetterFirework.INSTANCE.isDisabled() || !BetterFirework.onInteract())
196+
original.call();
197+
}
198+
199+
@WrapMethod(method = "doItemPick")
200+
void injectItemPick(Operation<Void> original) {
201+
if (BetterFirework.INSTANCE.isDisabled() || !BetterFirework.onPick())
202+
original.call();
203+
}
204+
192205
@WrapMethod(method = "getTargetMillisPerTick")
193206
float getTargetMillisPerTick(float millis, Operation<Float> original) {
194207
var length = TimerManager.INSTANCE.getLength();

src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ data class Bind(
176176
if (modifiers and GLFW_MOD_NUM_LOCK != 0) add(KeyCode.NumLock)
177177
}
178178

179+
val isMouseBind: Boolean
180+
get() = mouse >= 0
181+
182+
val isKeyBind: Boolean
183+
get() = key > 0
184+
179185
val name: String
180186
get() {
181187
if (mouse < 0 && modifiers <= 0 && key <= 0) return "Unbound"
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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.config.groups.HotbarSettings
21+
import com.lambda.config.groups.InventorySettings
22+
import com.lambda.config.settings.collections.SetSetting.Companion.immutableSet
23+
import com.lambda.config.settings.complex.Bind
24+
import com.lambda.context.SafeContext
25+
import com.lambda.event.events.KeyboardEvent
26+
import com.lambda.event.events.MouseEvent
27+
import com.lambda.event.events.TickEvent
28+
import com.lambda.event.listener.SafeListener.Companion.listen
29+
import com.lambda.interaction.material.StackSelection.Companion.selectStack
30+
import com.lambda.interaction.request.hotbar.HotbarRequest
31+
import com.lambda.interaction.request.inventory.InventoryRequest.Companion.inventoryRequest
32+
import com.lambda.module.Module
33+
import com.lambda.module.tag.ModuleTag
34+
import com.lambda.threading.runSafe
35+
import com.lambda.util.KeyCode
36+
import com.lambda.util.Mouse
37+
import com.lambda.util.NamedEnum
38+
import com.lambda.util.player.SlotUtils.hotbar
39+
import com.lambda.util.player.SlotUtils.hotbarAndStorage
40+
import net.minecraft.client.network.ClientPlayerEntity
41+
import net.minecraft.entity.effect.StatusEffects
42+
import net.minecraft.item.Items
43+
import net.minecraft.network.packet.c2s.play.ClientCommandC2SPacket
44+
import net.minecraft.network.packet.c2s.play.HandSwingC2SPacket
45+
import net.minecraft.util.Hand
46+
import net.minecraft.util.hit.HitResult
47+
48+
object BetterFirework : Module(
49+
name = "BetterFirework",
50+
description = "Automatic takeoff with fireworks",
51+
tag = ModuleTag.MOVEMENT,
52+
) {
53+
private var activateButton by setting("Activate Key", Bind(0, 0, Mouse.Middle.ordinal), "Button to activate Firework").group(Group.General)
54+
private var midFlightActivationKey by setting("Mid-Flight Activation Key", Bind(0, 0, KeyCode.Unbound.code), "Firework use key for mid flight activation").group(Group.General)
55+
private var middleClickCancel by setting("Middle Click Cancel", false, description = "Cancel pick block action on middle mouse click") { activateButton.key != KeyCode.Unbound.code }.group(Group.General)
56+
private var fireworkInteract by setting("Right Click Fly", true, "Automatically start flying when right clicking fireworks")
57+
private var fireworkInteractCancel by setting("Right Click Cancel", false, "Cancel block interactions while holding fireworks") { fireworkInteract }
58+
59+
private var clientSwing by setting("Swing", true, "Swing hand client side").group(Group.General)
60+
private var invUse by setting("Inventory", true, "Use fireworks from inventory") { activateButton.key != KeyCode.Unbound.code }.group(Group.General)
61+
62+
override val hotbarConfig = HotbarSettings(this, Group.Hotbar, vis = { false }).apply {
63+
::sequenceStageMask.edit { immutableSet(setOf(TickEvent.Pre)); defaultValue(mutableSetOf(TickEvent.Pre)) }
64+
}
65+
66+
override val inventoryConfig = InventorySettings(this, Group.Inventory, vis = { false }).apply {
67+
::tickStageMask.edit { immutableSet(setOf(TickEvent.Pre)); defaultValue(mutableSetOf(TickEvent.Pre)) }
68+
}
69+
70+
private enum class Group(override val displayName: String) : NamedEnum {
71+
General("General"),
72+
Hotbar("Hotbar"),
73+
Inventory("Inventory")
74+
}
75+
76+
private var takeoffState = TakeoffState.None
77+
78+
val ClientPlayerEntity.canTakeoff: Boolean
79+
get() = isOnGround || canOpenElytra
80+
81+
val ClientPlayerEntity.canOpenElytra: Boolean
82+
get() = !abilities.flying && !isClimbing && !isGliding && !isTouchingWater && !isOnGround && !hasVehicle() && !hasStatusEffect(StatusEffects.LEVITATION)
83+
84+
init {
85+
listen<TickEvent.Pre> {
86+
when (takeoffState) {
87+
TakeoffState.None -> {}
88+
89+
TakeoffState.Jumping -> {
90+
player.jump()
91+
takeoffState = TakeoffState.StartFlying
92+
}
93+
94+
TakeoffState.StartFlying -> {
95+
if (player.canOpenElytra) {
96+
player.startGliding()
97+
connection.sendPacket(ClientCommandC2SPacket(player, ClientCommandC2SPacket.Mode.START_FALL_FLYING))
98+
}
99+
startFirework(invUse)
100+
takeoffState = TakeoffState.None
101+
}
102+
}
103+
}
104+
listen<MouseEvent.Click> {
105+
if (!it.isPressed) {
106+
return@listen
107+
}
108+
if (it.satisfies(activateButton)) {
109+
if (activateButton.mouse == mc.options.pickItemKey.boundKey.code) {
110+
return@listen
111+
}
112+
runSafe {
113+
if (takeoffState != TakeoffState.None) {
114+
return@listen // Prevent using multiple times
115+
}
116+
if (player.canOpenElytra || player.isGliding) {
117+
// If already gliding use another firework
118+
takeoffState = TakeoffState.StartFlying
119+
} else if (player.canTakeoff) {
120+
takeoffState = TakeoffState.Jumping
121+
}
122+
}
123+
}
124+
if (it.satisfies(midFlightActivationKey)) {
125+
runSafe {
126+
if (player.isGliding)
127+
takeoffState = TakeoffState.StartFlying
128+
}
129+
}
130+
}
131+
listen<KeyboardEvent.Press> {
132+
if (!it.isPressed) {
133+
return@listen
134+
}
135+
if (it.satisfies(activateButton)) {
136+
if (activateButton.key != mc.options.pickItemKey.boundKey.code) {
137+
runSafe {
138+
if (takeoffState == TakeoffState.None) {
139+
if (player.canOpenElytra || player.isGliding) {
140+
// If already gliding use another firework
141+
takeoffState = TakeoffState.StartFlying
142+
} else if (player.canTakeoff) {
143+
takeoffState = TakeoffState.Jumping
144+
}
145+
}
146+
}
147+
}
148+
}
149+
if (it.satisfies(midFlightActivationKey)) {
150+
runSafe {
151+
if (player.isGliding)
152+
takeoffState = TakeoffState.StartFlying
153+
}
154+
}
155+
}
156+
}
157+
158+
/**
159+
* Returns true if the mc item interaction should be canceled
160+
*/
161+
@JvmStatic
162+
fun onInteract() =
163+
runSafe {
164+
when {
165+
!fireworkInteract ||
166+
player.inventory.selectedStack?.item != Items.FIREWORK_ROCKET ||
167+
player.isGliding || // No need to do special magic if we are already holding fireworks and flying
168+
(mc.crosshairTarget != null && mc.crosshairTarget!!.type != HitResult.Type.MISS && !fireworkInteractCancel) -> false
169+
else -> {
170+
mc.itemUseCooldown += 4
171+
val cancelInteract = player.canTakeoff || fireworkInteractCancel
172+
if (player.canTakeoff) {
173+
takeoffState = TakeoffState.Jumping
174+
} else if (player.canOpenElytra) {
175+
takeoffState = TakeoffState.StartFlying
176+
}
177+
cancelInteract
178+
}
179+
}
180+
} ?: false
181+
182+
/**
183+
* Returns true when the pick interaction should be canceled.
184+
*/
185+
@JvmStatic
186+
fun onPick() =
187+
runSafe {
188+
when {
189+
(mc.crosshairTarget?.type == HitResult.Type.BLOCK && !middleClickCancel) ||
190+
(!activateButton.isMouseBind || activateButton.mouse != mc.options.pickItemKey.boundKey.code) ||
191+
takeoffState != TakeoffState.None -> false // Prevent using multiple times
192+
else -> {
193+
if (player.canOpenElytra || player.isGliding) {
194+
// If already gliding use another firework
195+
takeoffState = TakeoffState.StartFlying
196+
} else if (player.canTakeoff) {
197+
takeoffState = TakeoffState.Jumping
198+
}
199+
middleClickCancel
200+
}
201+
}
202+
} ?: false
203+
204+
fun SafeContext.sendSwing() {
205+
if (clientSwing) {
206+
player.swingHand(Hand.MAIN_HAND)
207+
} else {
208+
connection.sendPacket(HandSwingC2SPacket(Hand.MAIN_HAND))
209+
}
210+
}
211+
212+
/**
213+
* Use a firework from the hotbar or inventory if possible.
214+
* Return true if a firework has been used
215+
*/
216+
fun SafeContext.startFirework(silent: Boolean) {
217+
val stack = selectStack(count = 1) { isItem(Items.FIREWORK_ROCKET) }
218+
219+
stack.bestItemMatch(player.hotbar)
220+
?.let {
221+
val request = HotbarRequest(player.hotbar.indexOf(it), this@BetterFirework, keepTicks = 0)
222+
.submit(queueIfClosed = false)
223+
if (request.done) {
224+
interaction.interactItem(player, Hand.MAIN_HAND)
225+
sendSwing()
226+
}
227+
return
228+
}
229+
230+
if (!silent) return
231+
232+
stack.bestItemMatch(player.hotbarAndStorage)
233+
?.let {
234+
val swapSlotId = player.hotbarAndStorage.indexOf(it)
235+
val hotbarSlotToSwapWith = player.hotbar.find { slot -> slot.isEmpty }?.let { slot -> player.hotbar.indexOf(slot) } ?: 8
236+
237+
inventoryRequest {
238+
swap(swapSlotId, hotbarSlotToSwapWith)
239+
action {
240+
val request = HotbarRequest(hotbarSlotToSwapWith, this@BetterFirework, keepTicks = 0, nowOrNothing = true)
241+
.submit(queueIfClosed = false)
242+
if (request.done) {
243+
interaction.interactItem(player, Hand.MAIN_HAND)
244+
sendSwing()
245+
}
246+
}
247+
swap(swapSlotId, hotbarSlotToSwapWith)
248+
}.submit()
249+
}
250+
}
251+
252+
enum class TakeoffState {
253+
None,
254+
Jumping,
255+
StartFlying
256+
}
257+
}

0 commit comments

Comments
 (0)