diff --git a/src/main/java/com/lambda/mixin/input/KeyboardMixin.java b/src/main/java/com/lambda/mixin/input/KeyboardMixin.java index ffa136983..3c2368116 100644 --- a/src/main/java/com/lambda/mixin/input/KeyboardMixin.java +++ b/src/main/java/com/lambda/mixin/input/KeyboardMixin.java @@ -20,6 +20,8 @@ import com.lambda.event.EventFlow; import com.lambda.event.events.KeyboardEvent; import com.lambda.module.modules.player.InventoryMove; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.client.Keyboard; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; @@ -30,9 +32,10 @@ @Mixin(Keyboard.class) public class KeyboardMixin { - @Inject(method = "onKey", at = @At("HEAD")) - private void onKey(long window, int key, int scancode, int action, int modifiers, CallbackInfo ci) { + @WrapMethod(method = "onKey") + private void onKey(long window, int key, int scancode, int action, int modifiers, Operation original) { EventFlow.post(new KeyboardEvent.Press(key, scancode, action, modifiers)); + original.call(window, key, scancode, action, modifiers); } @Inject(method = "onKey", at = @At("RETURN")) @@ -42,12 +45,13 @@ private void onKeyTail(long window, int key, int scancode, int action, int modif KeyBinding.setKeyPressed(fromCode, action != 0); } - @Inject(method = "onChar", at = @At("HEAD")) - private void onChar(long window, int codePoint, int modifiers, CallbackInfo ci) { + @WrapMethod(method = "onChar") + private void onChar(long window, int codePoint, int modifiers, Operation original) { char[] chars = Character.toChars(codePoint); - for (char c : chars) { + for (char c : chars) EventFlow.post(new KeyboardEvent.Char(c)); - } + + original.call(window, codePoint, modifiers); } } diff --git a/src/main/java/com/lambda/mixin/input/MouseMixin.java b/src/main/java/com/lambda/mixin/input/MouseMixin.java index 9802da2de..f72f697b3 100644 --- a/src/main/java/com/lambda/mixin/input/MouseMixin.java +++ b/src/main/java/com/lambda/mixin/input/MouseMixin.java @@ -21,15 +21,15 @@ import com.lambda.event.events.MouseEvent; import com.lambda.module.modules.render.Zoom; import com.lambda.util.math.Vec2d; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.minecraft.client.Mouse; import net.minecraft.client.option.GameOptions; import net.minecraft.client.option.SimpleOption; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Mouse.class) public class MouseMixin { @@ -37,33 +37,28 @@ public class MouseMixin { @Shadow private double y; - @Inject(method = "onMouseButton(JIII)V", at = @At("HEAD"), cancellable = true) - private void onMouseButton(long window, int button, int action, int mods, CallbackInfo ci) { - Vec2d position = new Vec2d(x, y); - - if (EventFlow.post(new MouseEvent.Click(button, action, mods, position)).isCanceled()) { - ci.cancel(); - } + @WrapMethod(method = "onMouseButton(JIII)V") + private void onMouseButton(long window, int button, int action, int mods, Operation original) { + if (!EventFlow.post(new MouseEvent.Click(button, action, mods)).isCanceled()) + original.call(window, button, action, mods); } - @Inject(method = "onMouseScroll(JDD)V", at = @At("HEAD"), cancellable = true) - private void onMouseScroll(long window, double horizontal, double vertical, CallbackInfo ci) { + @WrapMethod(method = "onMouseScroll(JDD)V") + private void onMouseScroll(long window, double horizontal, double vertical, Operation original) { Vec2d delta = new Vec2d(horizontal, vertical); - if (EventFlow.post(new MouseEvent.Scroll(delta)).isCanceled()) { - ci.cancel(); - } + if (!EventFlow.post(new MouseEvent.Scroll(delta)).isCanceled()) + original.call(window, horizontal, vertical); } - @Inject(method = "onCursorPos(JDD)V", at = @At("HEAD"), cancellable = true) - private void onCursorPos(long window, double x, double y, CallbackInfo ci) { + @WrapMethod(method = "onCursorPos(JDD)V") + private void onCursorPos(long window, double x, double y, Operation original) { if (x + y == this.x + this.y) return; Vec2d position = new Vec2d(x, y); - if (EventFlow.post(new MouseEvent.Move(position)).isCanceled()) { - ci.cancel(); - } + if (!EventFlow.post(new MouseEvent.Move(position)).isCanceled()) + original.call(window, x, y); } @Redirect(method = "updateMouse", at = @At(value = "FIELD", target = "Lnet/minecraft/client/option/GameOptions;smoothCameraEnabled:Z")) diff --git a/src/main/kotlin/com/lambda/config/Configurable.kt b/src/main/kotlin/com/lambda/config/Configurable.kt index 51dac7ec8..cc7d4ffee 100644 --- a/src/main/kotlin/com/lambda/config/Configurable.kt +++ b/src/main/kotlin/com/lambda/config/Configurable.kt @@ -30,6 +30,7 @@ import com.lambda.config.settings.collections.MapSetting import com.lambda.config.settings.collections.SetSetting import com.lambda.config.settings.comparable.BooleanSetting import com.lambda.config.settings.comparable.EnumSetting +import com.lambda.config.settings.complex.Bind import com.lambda.config.settings.complex.BlockPosSetting import com.lambda.config.settings.complex.BlockSetting import com.lambda.config.settings.complex.ColorSetting @@ -381,7 +382,7 @@ abstract class Configurable( */ fun setting( name: String, - defaultValue: KeyCode, + defaultValue: Bind, description: String = "", visibility: () -> Boolean = { true }, ) = KeybindSetting(name, defaultValue, description, visibility).register() diff --git a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt index e071399f5..a4b232031 100644 --- a/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt +++ b/src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt @@ -18,14 +18,20 @@ package com.lambda.config.settings.complex import com.google.gson.reflect.TypeToken +import com.lambda.brigadier.CommandResult.Companion.failure +import com.lambda.brigadier.CommandResult.Companion.success +import com.lambda.brigadier.argument.boolean import com.lambda.brigadier.argument.value import com.lambda.brigadier.argument.word -import com.lambda.brigadier.execute +import com.lambda.brigadier.executeWithResult +import com.lambda.brigadier.optional import com.lambda.brigadier.required import com.lambda.config.AbstractSetting +import com.lambda.config.settings.complex.Bind.Companion.mouseBind import com.lambda.gui.dsl.ImGuiBuilder +import com.lambda.util.InputUtils import com.lambda.util.KeyCode -import com.lambda.util.KeyboardUtils +import com.lambda.util.Mouse import com.lambda.util.StringUtils.capitalize import com.lambda.util.extension.CommandBuilder import imgui.ImGui.isMouseClicked @@ -33,27 +39,34 @@ import imgui.flag.ImGuiCol import imgui.flag.ImGuiHoveredFlags import imgui.flag.ImGuiMouseButton import net.minecraft.command.CommandRegistryAccess -import org.lwjgl.glfw.GLFW +import org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_SHIFT +import org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_SUPER +import org.lwjgl.glfw.GLFW.GLFW_MOD_ALT +import org.lwjgl.glfw.GLFW.GLFW_MOD_CAPS_LOCK +import org.lwjgl.glfw.GLFW.GLFW_MOD_CONTROL +import org.lwjgl.glfw.GLFW.GLFW_MOD_NUM_LOCK +import org.lwjgl.glfw.GLFW.GLFW_MOD_SHIFT +import org.lwjgl.glfw.GLFW.GLFW_MOD_SUPER class KeybindSetting( override var name: String, - defaultValue: KeyCode, + defaultValue: Bind, description: String, visibility: () -> Boolean, -) : AbstractSetting( +) : AbstractSetting( name, defaultValue, - TypeToken.get(KeyCode::class.java).type, + TypeToken.get(Bind::class.java).type, description, visibility ) { private var listening = false override fun ImGuiBuilder.buildLayout() { - val key = value - val scancode = if (key == KeyCode.UNBOUND) -69 else GLFW.glfwGetKeyScancode(key.code) - val translated = if (key == KeyCode.UNBOUND) key else KeyCode.virtualMapUS(key.code, scancode) - val preview = if (listening) "$name: Press any key…" else "$name: $translated" + val bind = value + val preview = + if (listening) "$name: Press any key…" + else bind.name if (listening) { withStyleColor(ImGuiCol.Button, 0.20f, 0.50f, 1.00f, 1.00f) { @@ -68,16 +81,8 @@ class KeybindSetting( } lambdaTooltip { - if (!listening) { - description.ifBlank { "Click to set. Right-click to unbind. Esc cancels. Backspace/Delete unbinds." } - } else { - "Listening… Press a key to bind. Esc to cancel. Backspace/Delete to unbind." - } - } - - onItemClick(ImGuiMouseButton.Right) { - value = KeyCode.UNBOUND - listening = false + if (!listening) description.ifBlank { "Click to set. Esc cancels. Backspace/Delete unbinds." } + else "Listening… Press a key to bind. Esc to cancel. Backspace/Delete to unbind." } if (listening && !isAnyItemHovered && isMouseClicked(ImGuiMouseButton.Left)) { @@ -86,38 +91,108 @@ class KeybindSetting( sameLine() smallButton("Unbind") { - value = KeyCode.UNBOUND + value = Bind.EMPTY listening = false } onItemHover(ImGuiHoveredFlags.Stationary) { lambdaTooltip("Clear binding") } - val poll = KeyboardUtils.lastEvent - if (listening && poll.isPressed) { - when (val key = poll.translated) { - KeyCode.ESCAPE -> listening = false - KeyCode.BACKSPACE, KeyCode.DELETE -> { - value = KeyCode.UNBOUND + if (listening) { + InputUtils.newMouseEvent() + ?.let { + value = Bind(0, it.modifiers, it.button) listening = false + return } - else -> { - value = key - listening = false + + InputUtils.newKeyboardEvent() + ?.let { + val isModKey = it.keyCode in GLFW_KEY_LEFT_SHIFT..GLFW_KEY_RIGHT_SUPER + + // If a mod key is pressed first ignore it unless it was released without any other keys + if ((it.isPressed && !isModKey) || (it.isReleased && isModKey)) { + when (it.translated) { + KeyCode.ESCAPE -> {} + KeyCode.BACKSPACE, KeyCode.DELETE -> value = Bind.EMPTY + else -> value = Bind(it.keyCode, it.modifiers, -1) + } + + listening = false + } + + return } - } } } override fun CommandBuilder.buildCommand(registry: CommandRegistryAccess) { - required(word(name)) { parameter -> + required(word(name)) { name -> suggests { _, builder -> KeyCode.entries.forEach { builder.suggest(it.name.capitalize()) } + (1..10).forEach { builder.suggest(it) } builder.buildFuture() } - execute { - trySetValue(KeyCode.valueOf(parameter().value())) + optional(boolean("mouse button")) { isMouseButton -> + executeWithResult { + val isMouse = if (isMouseButton != null) isMouseButton().value() else false + var bind = Bind.EMPTY + if (isMouse) { + val num = try { + name().value().toInt() + } catch(_: NumberFormatException) { + return@executeWithResult failure("${name().value()} doesn't match with a mouse button") + } + bind = mouseBind(num) + } else { + bind = try { + Bind(KeyCode.valueOf(name().value()).code, 0) + } catch(_: IllegalArgumentException) { + return@executeWithResult failure("${name().value()} doesn't match with a bind") + } + } + + trySetValue(bind) + return@executeWithResult success() + } } } } } + +data class Bind( + val key: Int, + val modifiers: Int, + val mouse: Int = -1, +) { + val truemods = buildList { + if (modifiers and GLFW_MOD_SHIFT != 0) add(KeyCode.LEFT_SHIFT) + if (modifiers and GLFW_MOD_CONTROL != 0) add(KeyCode.LEFT_CONTROL) + if (modifiers and GLFW_MOD_ALT != 0) add(KeyCode.LEFT_ALT) + if (modifiers and GLFW_MOD_SUPER != 0) add(KeyCode.LEFT_SUPER) + if (modifiers and GLFW_MOD_CAPS_LOCK != 0) add(KeyCode.CAPS_LOCK) + if (modifiers and GLFW_MOD_NUM_LOCK != 0) add(KeyCode.NUM_LOCK) + } + + val name: String + get() { + if (mouse < 0 && modifiers <= 0 && key <= 0) return "Unbound" + + val list = mutableListOf() + + if (mouse >= 0) list.add(Mouse.entries[mouse]) + if (modifiers > 0) list.add(truemods.joinToString(separator = "+") { it.name }) + if (key > 0) list.add(KeyCode.fromKeyCode(key)) + + return list.joinToString(separator = "+") { it.toString() } + } + + override fun toString() = + "Key Code: $key, Modifiers: ${truemods.joinToString(separator = "+") { it.name }}, Mouse Button: ${Mouse.entries.getOrNull(mouse) ?: "None"}" + + companion object { + val EMPTY = Bind(0, 0) + + fun mouseBind(code: Int) = Bind(0, 0, code) + } +} diff --git a/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt b/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt index 146305a9f..e14e971b5 100644 --- a/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt @@ -17,14 +17,9 @@ package com.lambda.event.events +import com.lambda.config.settings.complex.Bind import com.lambda.event.Event import com.lambda.util.KeyCode -import org.lwjgl.glfw.GLFW.GLFW_MOD_ALT -import org.lwjgl.glfw.GLFW.GLFW_MOD_CAPS_LOCK -import org.lwjgl.glfw.GLFW.GLFW_MOD_CONTROL -import org.lwjgl.glfw.GLFW.GLFW_MOD_NUM_LOCK -import org.lwjgl.glfw.GLFW.GLFW_MOD_SHIFT -import org.lwjgl.glfw.GLFW.GLFW_MOD_SUPER import org.lwjgl.glfw.GLFW.GLFW_PRESS import org.lwjgl.glfw.GLFW.GLFW_RELEASE @@ -51,15 +46,10 @@ sealed class KeyboardEvent { val translated: KeyCode get() = KeyCode.virtualMapUS(keyCode, scanCode) - val isPressed = action == GLFW_PRESS + val isPressed = action >= GLFW_PRESS val isReleased = action == GLFW_RELEASE - val hasShift = modifiers and GLFW_MOD_SHIFT != 0 - val hasControl = modifiers and GLFW_MOD_CONTROL != 0 - val hasAlt = modifiers and GLFW_MOD_ALT != 0 - val hasSuper = modifiers and GLFW_MOD_SUPER != 0 - val hasCapsLock = modifiers and GLFW_MOD_CAPS_LOCK != 0 - val hasNumLock = modifiers and GLFW_MOD_NUM_LOCK != 0 + fun satisfies(bind: Bind) = bind.key == keyCode && bind.modifiers and modifiers == bind.modifiers } /** diff --git a/src/main/kotlin/com/lambda/event/events/MouseEvent.kt b/src/main/kotlin/com/lambda/event/events/MouseEvent.kt index f732c7db9..427969e39 100644 --- a/src/main/kotlin/com/lambda/event/events/MouseEvent.kt +++ b/src/main/kotlin/com/lambda/event/events/MouseEvent.kt @@ -17,16 +17,10 @@ package com.lambda.event.events +import com.lambda.config.settings.complex.Bind import com.lambda.event.callback.Cancellable import com.lambda.event.callback.ICancellable -import com.lambda.util.Mouse import com.lambda.util.math.Vec2d -import org.lwjgl.glfw.GLFW.GLFW_MOD_ALT -import org.lwjgl.glfw.GLFW.GLFW_MOD_CAPS_LOCK -import org.lwjgl.glfw.GLFW.GLFW_MOD_CONTROL -import org.lwjgl.glfw.GLFW.GLFW_MOD_NUM_LOCK -import org.lwjgl.glfw.GLFW.GLFW_MOD_SHIFT -import org.lwjgl.glfw.GLFW.GLFW_MOD_SUPER sealed class MouseEvent { /** @@ -35,35 +29,16 @@ sealed class MouseEvent { * @property button The button that was clicked * @property action The action performed (e.g., press or release) * @property modifiers An integer representing any modifiers (e.g., shift or ctrl) active during the event - * @property position The x and y position of the mouse on the screen */ data class Click( val button: Int, val action: Int, val modifiers: Int, - val position: Vec2d, ) : ICancellable by Cancellable() { - constructor(button: Mouse.Button, action: Mouse.Action, modifiers: Int, position: Vec2d) : this( - button.ordinal, - action.ordinal, - modifiers, - position - ) - - val isMainButton = button <= 2 - val isSideButton = button > 2 - val isLeftButton = button == 0 - val isRightButton = button == 1 - val isMiddleButton = button == 2 - - val hasShift = hasModifier(GLFW_MOD_SHIFT) - val hasControl = hasModifier(GLFW_MOD_CONTROL) - val hasAlt = hasModifier(GLFW_MOD_ALT) - val hasSuper = hasModifier(GLFW_MOD_SUPER) - val hasCapsLock = hasModifier(GLFW_MOD_CAPS_LOCK) - val hasNumLock = hasModifier(GLFW_MOD_NUM_LOCK) + val isReleased = action == 0 + val isPressed = action == 1 - fun hasModifier(mod: Int) = modifiers and mod == mod + fun satisfies(bind: Bind) = bind.modifiers and modifiers == bind.modifiers && bind.mouse == button } /** diff --git a/src/main/kotlin/com/lambda/module/HudModule.kt b/src/main/kotlin/com/lambda/module/HudModule.kt index c84b379b8..5c26f37cd 100644 --- a/src/main/kotlin/com/lambda/module/HudModule.kt +++ b/src/main/kotlin/com/lambda/module/HudModule.kt @@ -17,9 +17,9 @@ package com.lambda.module +import com.lambda.config.settings.complex.Bind import com.lambda.gui.Layout import com.lambda.module.tag.ModuleTag -import com.lambda.util.KeyCode import java.awt.Color abstract class HudModule( @@ -29,7 +29,7 @@ abstract class HudModule( val customWindow: Boolean = false, alwaysListening: Boolean = false, enabledByDefault: Boolean = false, - defaultKeybind: KeyCode = KeyCode.UNBOUND, + defaultKeybind: Bind = Bind.EMPTY, ) : Module(name, description, tag, alwaysListening, enabledByDefault, defaultKeybind), Layout { val backgroundColor by setting("Background Color", Color(0, 0, 0, 0)) val outline by setting("Show Outline", false) diff --git a/src/main/kotlin/com/lambda/module/Module.kt b/src/main/kotlin/com/lambda/module/Module.kt index 2a6f42003..fc9d16525 100644 --- a/src/main/kotlin/com/lambda/module/Module.kt +++ b/src/main/kotlin/com/lambda/module/Module.kt @@ -25,11 +25,13 @@ import com.lambda.config.Configuration import com.lambda.config.configurations.ModuleConfig import com.lambda.context.Automated import com.lambda.context.AutomationConfig +import com.lambda.config.settings.complex.Bind import com.lambda.context.SafeContext import com.lambda.event.Muteable import com.lambda.event.events.ClientEvent import com.lambda.event.events.ConnectionEvent import com.lambda.event.events.KeyboardEvent +import com.lambda.event.events.MouseEvent import com.lambda.event.listener.Listener import com.lambda.event.listener.SafeListener import com.lambda.event.listener.SafeListener.Companion.listen @@ -38,6 +40,7 @@ import com.lambda.module.tag.ModuleTag import com.lambda.sound.LambdaSound import com.lambda.sound.SoundManager.play import com.lambda.util.KeyCode +import com.lambda.util.Mouse import com.lambda.util.Nameable /** @@ -114,7 +117,7 @@ abstract class Module( val tag: ModuleTag, private val alwaysListening: Boolean = false, enabledByDefault: Boolean = false, - defaultKeybind: KeyCode = KeyCode.UNBOUND, + defaultKeybind: Bind = Bind.EMPTY, autoDisable: Boolean = false ) : Nameable, Muteable, Configurable(ModuleConfig), Automated by AutomationConfig { private val isEnabledSetting = setting("Enabled", enabledByDefault) { false } @@ -134,10 +137,18 @@ abstract class Module( init { listen(alwaysListen = true) { event -> - if (Lambda.mc.options.commandKey.isPressed) return@listen - if (keybind == KeyCode.UNBOUND) return@listen - if (event.translated != keybind) return@listen - if (Lambda.mc.currentScreen != null) return@listen + if (mc.options.commandKey.isPressed + || Lambda.mc.currentScreen != null + || !event.satisfies(keybind)) return@listen + + if (event.isPressed) toggle() + else if (event.isReleased && disableOnRelease) disable() + } + + listen(alwaysListen = true) { event -> + if (mc.options.commandKey.isPressed + || mc.currentScreen != null + || !event.satisfies(keybind)) return@listen if (event.isPressed) toggle() else if (event.isReleased && disableOnRelease) disable() diff --git a/src/main/kotlin/com/lambda/module/hud/ModuleList.kt b/src/main/kotlin/com/lambda/module/hud/ModuleList.kt index 587087c50..d0522dddb 100644 --- a/src/main/kotlin/com/lambda/module/hud/ModuleList.kt +++ b/src/main/kotlin/com/lambda/module/hud/ModuleList.kt @@ -39,7 +39,7 @@ object ModuleList : HudModule( enabled.forEach { text(it.name); sameLine() - val color = if (it.keybind == KeyCode.UNBOUND) Color.RED else Color.GREEN + val color = if (it.keybind.key == 0 && it.keybind.mouse == -1) Color.RED else Color.GREEN withStyleColor(ImGuiCol.Text, color) { text(" [${it.keybind.name}]") } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt index 9a5a16f24..34805829b 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/ClickFriend.kt @@ -17,6 +17,7 @@ package com.lambda.module.modules.player +import com.lambda.config.settings.complex.Bind import com.lambda.event.events.MouseEvent import com.lambda.event.listener.SafeListener.Companion.listen import com.lambda.friend.FriendManager @@ -26,49 +27,32 @@ import com.lambda.friend.FriendManager.unfriend import com.lambda.module.Module import com.lambda.module.tag.ModuleTag import com.lambda.util.Communication.info -import com.lambda.util.Mouse import com.lambda.util.world.raycast.RayCastUtils.entityResult import net.minecraft.client.network.OtherClientPlayerEntity -import org.lwjgl.glfw.GLFW.GLFW_MOD_ALT -import org.lwjgl.glfw.GLFW.GLFW_MOD_CAPS_LOCK -import org.lwjgl.glfw.GLFW.GLFW_MOD_CONTROL -import org.lwjgl.glfw.GLFW.GLFW_MOD_NUM_LOCK -import org.lwjgl.glfw.GLFW.GLFW_MOD_SHIFT -import org.lwjgl.glfw.GLFW.GLFW_MOD_SUPER +import org.lwjgl.glfw.GLFW object ClickFriend : Module( name = "ClickFriend", description = "Add or remove friends with a single click", tag = ModuleTag.PLAYER, ) { - private val friendButton by setting("Friend Button", Mouse.Button.Middle, description = "Button to press to befriend a player") - private val friendAction by setting("Action", Mouse.Action.Release, description = "What mouse action should add or remove the player") - private val comboUnfriend by setting("Combo Unfriend", false, description = "Press a key and right click a player to unfriend") - private val modUnfriend by setting("Combo Key", MouseMod.Shift, description = "The key to press to activate the unfriend combo") { comboUnfriend } + private val friendBind by setting("Friend Bind", Bind(0, 0, GLFW.GLFW_MOUSE_BUTTON_MIDDLE), "Bind to press to befriend a player") + private val unfriendBind by setting("Unfriend Bind", friendBind, "Bind to press to unfriend a player") init { listen { if (mc.currentScreen != null) return@listen - if (it.button != friendButton.ordinal || it.action != friendAction.ordinal) return@listen val target = mc.crosshairTarget?.entityResult?.entity as? OtherClientPlayerEntity ?: return@listen - if (!it.hasModifier(modUnfriend.modifiers) && comboUnfriend && target.isFriend) return@listen - when { - target.isFriend && target.unfriend() -> info(FriendManager.unfriendedText(target.name)) - !target.isFriend && target.befriend() -> info(FriendManager.befriendedText(target.name)) + it.satisfies(friendBind) && !target.isFriend && target.befriend() -> + info(FriendManager.befriendedText(target.name)) + + it.satisfies(unfriendBind) && target.isFriend && target.unfriend() -> + info(FriendManager.unfriendedText(target.name)) } } } - - private enum class MouseMod(val modifiers: Int) { - Shift(GLFW_MOD_SHIFT), - Control(GLFW_MOD_CONTROL), - Alt(GLFW_MOD_ALT), - Super(GLFW_MOD_SUPER), - Caps(GLFW_MOD_CAPS_LOCK), - NumLock(GLFW_MOD_NUM_LOCK); - } } diff --git a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt index a653a957b..c64f64928 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/InventoryMove.kt @@ -27,7 +27,7 @@ import com.lambda.interaction.request.rotating.RotationMode import com.lambda.interaction.request.rotating.visibilty.lookAt import com.lambda.module.Module import com.lambda.module.tag.ModuleTag -import com.lambda.util.KeyboardUtils.isKeyPressed +import com.lambda.util.InputUtils.isKeyPressed import com.lambda.util.math.MathUtils.toFloatSign import net.minecraft.client.gui.screen.ChatScreen import net.minecraft.client.gui.screen.Screen diff --git a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt index fec4193d3..8b4059e6e 100644 --- a/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt +++ b/src/main/kotlin/com/lambda/module/modules/player/Scaffold.kt @@ -38,7 +38,7 @@ import com.lambda.threading.runSafeAutomated import com.lambda.util.BlockUtils.blockPos import com.lambda.util.BlockUtils.blockState import com.lambda.util.KeyCode -import com.lambda.util.KeyboardUtils.isKeyPressed +import com.lambda.util.InputUtils.isKeyPressed import com.lambda.util.NamedEnum import com.lambda.util.math.distSq import net.minecraft.util.math.BlockPos diff --git a/src/main/kotlin/com/lambda/util/Communication.kt b/src/main/kotlin/com/lambda/util/Communication.kt index 81ea3d0cc..9da68ad56 100644 --- a/src/main/kotlin/com/lambda/util/Communication.kt +++ b/src/main/kotlin/com/lambda/util/Communication.kt @@ -179,8 +179,8 @@ object Communication { } literal("Keybind: ") color(ClickGuiLayout.primaryColor) { - if (module.keybind.code != -1) { - literal(module.keybind.code.toString()) + if (module.keybind.key != 0 || module.keybind.mouse > -1) { + literal(module.keybind.name) } else { literal("Unbound") } diff --git a/src/main/kotlin/com/lambda/util/InputUtils.kt b/src/main/kotlin/com/lambda/util/InputUtils.kt new file mode 100644 index 000000000..c5f6d7e41 --- /dev/null +++ b/src/main/kotlin/com/lambda/util/InputUtils.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2025 Lambda + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.lambda.util + +import com.lambda.Lambda.mc +import com.lambda.context.SafeContext +import com.lambda.core.Loadable +import com.lambda.event.events.KeyboardEvent +import com.lambda.event.events.MouseEvent +import org.lwjgl.glfw.GLFW +import org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_ALT +import org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_CONTROL +import org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_SHIFT +import org.lwjgl.glfw.GLFW.GLFW_KEY_LEFT_SUPER +import org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_ALT +import org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_CONTROL +import org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_SHIFT +import org.lwjgl.glfw.GLFW.GLFW_KEY_RIGHT_SUPER +import org.lwjgl.glfw.GLFW.GLFW_MOUSE_BUTTON_1 +import org.lwjgl.glfw.GLFW.GLFW_MOUSE_BUTTON_8 +import org.lwjgl.glfw.GLFW.GLFW_PRESS +import org.lwjgl.glfw.GLFW.glfwGetKey +import org.lwjgl.glfw.GLFW.glfwGetMouseButton + +object InputUtils : Loadable { + private val keys = KeyCode.entries.map { it.code } + private val scancodes = keys.associateWith { GLFW.glfwGetKeyScancode(it) } + + private val mouses = GLFW_MOUSE_BUTTON_1..GLFW_MOUSE_BUTTON_8 + + private val modMap = mapOf( + GLFW_KEY_LEFT_SHIFT to 0x1, + GLFW_KEY_RIGHT_SHIFT to 0x1, + GLFW_KEY_LEFT_CONTROL to 0x2, + GLFW_KEY_RIGHT_CONTROL to 0x2, + GLFW_KEY_LEFT_ALT to 0x4, + GLFW_KEY_RIGHT_ALT to 0x4, + GLFW_KEY_LEFT_SUPER to 0x8, + GLFW_KEY_RIGHT_SUPER to 0x8, + ) + + /** + * Returns whether any of the key-codes (not scan-codes) are being pressed + */ + fun SafeContext.isKeyPressed(vararg keys: Int) = + keys.any { glfwGetKey(mc.window.handle, it) >= GLFW_PRESS } + + /** + * Creates a new event from the currently pressed keys + */ + fun newKeyboardEvent(): KeyboardEvent.Press? { + val pressedKeys = keys + .associateWith { glfwGetKey(mc.window.handle, it) } + .filter { (_, state) -> state >= GLFW_PRESS } + + val mods = pressedKeys.keys + .filter { it in GLFW_KEY_LEFT_SHIFT..GLFW_KEY_RIGHT_SUPER } + .foldRight(0) { v, acc -> acc or modMap.getValue(v) } + + val key = pressedKeys + .firstNotNullOfOrNull { (key, state) -> key to state } ?: return null + + val scancode = scancodes.getValue(key.first) + + return KeyboardEvent.Press(key.first, scancode, key.second, mods) + } + + /** + * Creates a new mouse event from the current glfw states. + */ + fun newMouseEvent(): MouseEvent.Click? { + val mods = (GLFW_KEY_LEFT_SHIFT..GLFW_KEY_RIGHT_SUPER) + .filter { glfwGetKey(mc.window.handle, it) >= GLFW_PRESS } + .foldRight(0) { v, acc -> acc or modMap.getValue(v) } + + val mouse = mouses + .firstOrNull { glfwGetMouseButton(mc.window.handle, it) == GLFW_PRESS } + ?: return null + + return MouseEvent.Click(mouse, GLFW_PRESS, mods) + } +} diff --git a/src/main/kotlin/com/lambda/util/KeyboardUtils.kt b/src/main/kotlin/com/lambda/util/KeyboardUtils.kt deleted file mode 100644 index e2f02ac60..000000000 --- a/src/main/kotlin/com/lambda/util/KeyboardUtils.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2025 Lambda - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package com.lambda.util - -import com.lambda.context.SafeContext -import com.lambda.core.Loadable -import com.lambda.event.events.KeyboardEvent -import com.lambda.event.listener.UnsafeListener.Companion.listenUnsafe -import net.minecraft.client.util.InputUtil - -object KeyboardUtils : Loadable { - var lastEvent: KeyboardEvent.Press = KeyboardEvent.Press(0, 0, -1, 0) - - /** - * Returns whether any of the key-codes (not scan-codes) are being pressed - */ - fun SafeContext.isKeyPressed(vararg keys: Int) = - keys.any { InputUtil.isKeyPressed(mc.window.handle, it) } - - init { - // hacking imgui jni lib rn because it's missing a lot of native functions including i/o stuff - listenUnsafe { lastEvent = it } - } -} diff --git a/src/main/kotlin/com/lambda/util/Mouse.kt b/src/main/kotlin/com/lambda/util/Mouse.kt index 5a93735ba..e82054fbd 100644 --- a/src/main/kotlin/com/lambda/util/Mouse.kt +++ b/src/main/kotlin/com/lambda/util/Mouse.kt @@ -17,50 +17,13 @@ package com.lambda.util -import org.lwjgl.glfw.GLFW - -class Mouse { - enum class Button { - Left, - Right, - Middle, - Button4, - Button5, - Button6, - Button7, - Button8; - - val isMainButton get() = ordinal == GLFW.GLFW_MOUSE_BUTTON_LEFT || ordinal == GLFW.GLFW_MOUSE_BUTTON_RIGHT - - companion object { - private val mouseCodeMap = entries.associateBy { it.ordinal } - private val nameMap = entries.associateBy { it.name.lowercase() } - - @Throws(IllegalArgumentException::class) - fun fromMouseCode(code: Int) = - mouseCodeMap[code] ?: throw IllegalArgumentException("Mouse code $code not found in mouseCodeMap.") - - @Throws(IllegalArgumentException::class) - fun fromMouseName(name: String) = - nameMap[name.lowercase()] ?: throw IllegalArgumentException("Mouse name '$name' not found in nameMap.") - } - } - - enum class Action { - Release, - Click; - - companion object { - private val mouseActionMap = entries.associateBy { it.ordinal } - private val nameMap = entries.associateBy { it.name.lowercase() } - - @Throws(IllegalArgumentException::class) - fun fromActionCode(code: Int) = - mouseActionMap[code] ?: throw IllegalArgumentException("Action code $code not found in mouseActionMap.") - - @Throws(IllegalArgumentException::class) - fun fromActionName(name: String) = - nameMap[name.lowercase()] ?: throw IllegalArgumentException("Action name '$name' not found in nameMap.") - } - } +enum class Mouse { + Left, + Right, + Middle, + Button4, + Button5, + Button6, + Button7, + Button8; }