Skip to content
Merged
16 changes: 10 additions & 6 deletions src/main/java/com/lambda/mixin/input/KeyboardMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Void> original) {
EventFlow.post(new KeyboardEvent.Press(key, scancode, action, modifiers));
original.call(window, key, scancode, action, modifiers);
}

@Inject(method = "onKey", at = @At("RETURN"))
Expand All @@ -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<Void> 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);
}
}
33 changes: 14 additions & 19 deletions src/main/java/com/lambda/mixin/input/MouseMixin.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,49 +21,44 @@
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 {
@Shadow private double x;

@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<Void> 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<Void> 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<Void> 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"))
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/com/lambda/config/Configurable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
143 changes: 109 additions & 34 deletions src/main/kotlin/com/lambda/config/settings/complex/KeybindSetting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,55 @@
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
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<KeyCode>(
) : AbstractSetting<Bind>(
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) {
Expand All @@ -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)) {
Expand All @@ -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<Any>()

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)
}
}
16 changes: 3 additions & 13 deletions src/main/kotlin/com/lambda/event/events/KeyboardEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
}

/**
Expand Down
Loading