From f60cc0e0746bea90168e8f70003373ebbd0ec280 Mon Sep 17 00:00:00 2001 From: liv <~@l1v.in> Date: Sun, 28 Mar 2021 00:34:18 -0400 Subject: [PATCH] enh: Added ability to have multiple keys for keybinds (#2149) Co-authored-by: Xiaro <62033805+Xiaro@users.noreply.github.com> --- .../client/command/commands/BindCommand.kt | 21 ++- .../client/command/commands/MacroCommand.kt | 52 +++--- .../client/gui/clickgui/KamiClickGui.kt | 2 +- .../kamiblue/client/gui/hudgui/KamiHudGui.kt | 2 +- .../client/manager/managers/MacroManager.kt | 6 +- .../module/modules/client/CommandConfig.kt | 1 - .../settings/impl/other/BindSetting.kt | 42 ++--- .../kotlin/org/kamiblue/client/util/Bind.kt | 161 ++++++++++++------ .../org/kamiblue/client/util/KeyboardUtils.kt | 36 +++- 9 files changed, 208 insertions(+), 115 deletions(-) diff --git a/src/main/kotlin/org/kamiblue/client/command/commands/BindCommand.kt b/src/main/kotlin/org/kamiblue/client/command/commands/BindCommand.kt index 136d934b..085a83a6 100644 --- a/src/main/kotlin/org/kamiblue/client/command/commands/BindCommand.kt +++ b/src/main/kotlin/org/kamiblue/client/command/commands/BindCommand.kt @@ -1,12 +1,9 @@ package org.kamiblue.client.command.commands -import net.minecraft.util.text.TextFormatting import org.kamiblue.client.command.ClientCommand import org.kamiblue.client.module.ModuleManager -import org.kamiblue.client.module.modules.client.CommandConfig import org.kamiblue.client.util.KeyboardUtils import org.kamiblue.client.util.text.MessageSendHelper -import org.kamiblue.client.util.text.format import org.kamiblue.client.util.text.formatValue object BindCommand : ClientCommand( @@ -16,12 +13,14 @@ object BindCommand : ClientCommand( init { literal("list") { execute("List used module binds") { - val modules = ModuleManager.modules.filter { it.bind.value.key > 0 }.sortedBy { it.bind.toString() } + val binds = ModuleManager.modules.asSequence() + .filter { it.bind.value.key in 1..255 } + .map { "${formatValue(it.bind)} ${it.name}" } + .sorted() + .toList() - MessageSendHelper.sendChatMessage("Used binds: ${formatValue(modules.size)}") - modules.forEach { - MessageSendHelper.sendRawChatMessage("${formatValue(it.bind)} ${it.name}") - } + MessageSendHelper.sendChatMessage("Used binds: ${formatValue(binds.size)}") + binds.forEach(MessageSendHelper::sendRawChatMessage) } } @@ -41,15 +40,15 @@ object BindCommand : ClientCommand( val module = moduleArg.value val bind = bindArg.value - if (bind.equals("none", true)) { + if (bind.equals("None", true)) { module.bind.resetValue() MessageSendHelper.sendChatMessage("Reset bind for ${module.name}!") return@execute } - val key = KeyboardUtils.getKey(bind) - if (key <= 0) { + + if (key !in 1..255) { KeyboardUtils.sendUnknownKeyError(bind) } else { module.bind.setValue(bind) diff --git a/src/main/kotlin/org/kamiblue/client/command/commands/MacroCommand.kt b/src/main/kotlin/org/kamiblue/client/command/commands/MacroCommand.kt index ece178c5..e7d9b8f1 100644 --- a/src/main/kotlin/org/kamiblue/client/command/commands/MacroCommand.kt +++ b/src/main/kotlin/org/kamiblue/client/command/commands/MacroCommand.kt @@ -15,22 +15,23 @@ object MacroCommand : ClientCommand( literal("list") { string("key") { keyArg -> execute("List macros for a key") { - val key = KeyboardUtils.getKey(keyArg.value) + val input = keyArg.value + val key = KeyboardUtils.getKey(input) - if (key < 1) { - KeyboardUtils.sendUnknownKeyError(keyArg.value) + if (key !in 1..255) { + KeyboardUtils.sendUnknownKeyError(input) return@execute } - val macros = MacroManager.macros.filter { it.key == key } - val formattedName = formatValue(KeyboardUtils.getKeyName(key)) + val macros = MacroManager.macros[key] + val formattedName = formatValue(KeyboardUtils.getDisplayName(key) ?: "Unknown") - if (macros.isEmpty()) { + if (macros.isNullOrEmpty()) { MessageSendHelper.sendChatMessage("&cYou have no macros for the key $formattedName") } else { MessageSendHelper.sendChatMessage("You have has the following macros for $formattedName: ") - for ((_, value) in macros) { - MessageSendHelper.sendRawChatMessage("$formattedName $value") + for (macro in macros) { + MessageSendHelper.sendRawChatMessage("$formattedName $macro") } } } @@ -41,47 +42,52 @@ object MacroCommand : ClientCommand( MessageSendHelper.sendChatMessage("&cYou have no macros") } else { MessageSendHelper.sendChatMessage("You have the following macros: ") - for ((key, value) in MacroManager.macros.entries.sortedBy { it.key }) { - MessageSendHelper.sendRawChatMessage("${formatValue(KeyboardUtils.getKeyName(key))} $value") + for ((key, value) in MacroManager.macros) { + val formattedName = formatValue(KeyboardUtils.getDisplayName(key) ?: "Unknown") + MessageSendHelper.sendRawChatMessage("$formattedName $value") } } - } } literal("clear") { string("key") { keyArg -> execute("Clear macros for a key") { - val key = KeyboardUtils.getKey(keyArg.value) + val input = keyArg.value + val key = KeyboardUtils.getKey(input) - if (key < 1) { - KeyboardUtils.sendUnknownKeyError(keyArg.value) + if (key !in 1..255) { + KeyboardUtils.sendUnknownKeyError(input) return@execute } + val formattedName = formatValue(KeyboardUtils.getDisplayName(key) ?: "Unknown") + MacroManager.removeMacro(key) MacroManager.saveMacros() MacroManager.loadMacros() - MessageSendHelper.sendChatMessage("Cleared macros for ${formatValue(KeyboardUtils.getKeyName(key))}") + MessageSendHelper.sendChatMessage("Cleared macros for $formattedName") } } } string("key") { keyArg -> - greedy("command / message") { greedyArg -> + greedy("command / message") { macroArg -> execute("Set a command / message for a key") { - val key = KeyboardUtils.getKey(keyArg.value) + val input = keyArg.value + val key = KeyboardUtils.getKey(input) - if (key < 1) { - KeyboardUtils.sendUnknownKeyError(keyArg.value) + if (key !in 1..255) { + KeyboardUtils.sendUnknownKeyError(input) return@execute } - MacroManager.addMacroToKey(key, greedyArg.value) + val macro = macroArg.value + val formattedName = formatValue(KeyboardUtils.getDisplayName(key) ?: "Unknown") + + MacroManager.addMacroToKey(key, macro) MacroManager.saveMacros() - MessageSendHelper.sendChatMessage("Added macro ${formatValue(greedyArg.value)} for key " + - formatValue(KeyboardUtils.getKeyName(key)) - ) + MessageSendHelper.sendChatMessage("Added macro ${formatValue(macro)} for key $formattedName") } } } diff --git a/src/main/kotlin/org/kamiblue/client/gui/clickgui/KamiClickGui.kt b/src/main/kotlin/org/kamiblue/client/gui/clickgui/KamiClickGui.kt index fec9c108..5adc4327 100644 --- a/src/main/kotlin/org/kamiblue/client/gui/clickgui/KamiClickGui.kt +++ b/src/main/kotlin/org/kamiblue/client/gui/clickgui/KamiClickGui.kt @@ -56,7 +56,7 @@ object KamiClickGui : AbstractKamiGui() { } override fun keyTyped(typedChar: Char, keyCode: Int) { - if (keyCode == Keyboard.KEY_ESCAPE || keyCode == ClickGUI.bind.value.key && !searching) { + if (keyCode == Keyboard.KEY_ESCAPE || keyCode == ClickGUI.bind.value.key && !searching && settingWindow?.listeningChild == null) { ClickGUI.disable() } else { super.keyTyped(typedChar, keyCode) diff --git a/src/main/kotlin/org/kamiblue/client/gui/hudgui/KamiHudGui.kt b/src/main/kotlin/org/kamiblue/client/gui/hudgui/KamiHudGui.kt index bd04dcca..35ab1545 100644 --- a/src/main/kotlin/org/kamiblue/client/gui/hudgui/KamiHudGui.kt +++ b/src/main/kotlin/org/kamiblue/client/gui/hudgui/KamiHudGui.kt @@ -78,7 +78,7 @@ object KamiHudGui : AbstractKamiGui() { } override fun keyTyped(typedChar: Char, keyCode: Int) { - if (keyCode == Keyboard.KEY_ESCAPE || HudEditor.bind.value.isDown(keyCode) && !searching) { + if (keyCode == Keyboard.KEY_ESCAPE || HudEditor.bind.value.isDown(keyCode) && !searching && settingWindow?.listeningChild == null) { HudEditor.disable() } else { super.keyTyped(typedChar, keyCode) diff --git a/src/main/kotlin/org/kamiblue/client/manager/managers/MacroManager.kt b/src/main/kotlin/org/kamiblue/client/manager/managers/MacroManager.kt index a3ac4309..026dd835 100644 --- a/src/main/kotlin/org/kamiblue/client/manager/managers/MacroManager.kt +++ b/src/main/kotlin/org/kamiblue/client/manager/managers/MacroManager.kt @@ -13,14 +13,16 @@ import org.lwjgl.input.Keyboard import java.io.File import java.io.FileReader import java.io.FileWriter +import java.util.* +import kotlin.collections.ArrayList object MacroManager : Manager { - private var macroMap = LinkedHashMap>() + private var macroMap = TreeMap>() val isEmpty get() = macroMap.isEmpty() val macros: Map> get() = macroMap private val gson = GsonBuilder().setPrettyPrinting().create() - private val type = object : TypeToken>>() {}.type + private val type = object : TypeToken>>() {}.type private val file get() = File(KamiMod.DIRECTORY + "macros.json") init { diff --git a/src/main/kotlin/org/kamiblue/client/module/modules/client/CommandConfig.kt b/src/main/kotlin/org/kamiblue/client/module/modules/client/CommandConfig.kt index cfc9c4f6..803cce55 100644 --- a/src/main/kotlin/org/kamiblue/client/module/modules/client/CommandConfig.kt +++ b/src/main/kotlin/org/kamiblue/client/module/modules/client/CommandConfig.kt @@ -22,7 +22,6 @@ internal object CommandConfig : Module( var prefix by setting("Prefix", ";", { false }) val toggleMessages by setting("Toggle Messages", false) private val customTitle = setting("Window Title", true) - val modifierEnabled by setting("Modifier Enabled", true, { false }) private val timer = TickTimer() private val prevTitle = Display.getTitle() diff --git a/src/main/kotlin/org/kamiblue/client/setting/settings/impl/other/BindSetting.kt b/src/main/kotlin/org/kamiblue/client/setting/settings/impl/other/BindSetting.kt index 5a32ba5f..9728f510 100644 --- a/src/main/kotlin/org/kamiblue/client/setting/settings/impl/other/BindSetting.kt +++ b/src/main/kotlin/org/kamiblue/client/setting/settings/impl/other/BindSetting.kt @@ -4,7 +4,8 @@ import com.google.gson.JsonElement import com.google.gson.JsonPrimitive import org.kamiblue.client.setting.settings.ImmutableSetting import org.kamiblue.client.util.Bind -import org.lwjgl.input.Keyboard +import org.kamiblue.client.util.KeyboardUtils +import java.util.* class BindSetting( name: String, @@ -13,38 +14,37 @@ class BindSetting( description: String = "" ) : ImmutableSetting(name, value, visibility, { _, input -> input }, description) { - override val defaultValue: Bind = Bind(value.ctrl, value.alt, value.shift, value.key) + override val defaultValue: Bind = Bind(TreeSet(value.modifierKeys), value.key) override fun resetValue() { - value.setBind(defaultValue.ctrl, defaultValue.alt, defaultValue.shift, defaultValue.key) + value.setBind(defaultValue.modifierKeys, defaultValue.key) } override fun setValue(valueIn: String) { - var string = valueIn - - if (string.equals("None", ignoreCase = true)) { - value.setBind(ctrlIn = false, altIn = false, shiftIn = false, keyIn = 0) + if (valueIn.equals("None", ignoreCase = true)) { + value.clear() return } - val ctrl = string.startsWith("Ctrl+") - if (ctrl) { - string = string.substring(5) - } + val splitNames = valueIn.split('+') + val lastKey = KeyboardUtils.getKey(splitNames.last()) - val alt = string.startsWith("Alt+") - if (alt) { - string = string.substring(4) + // Don't clear if the string is fucked + if (lastKey !in 1..255) { + println("Invalid last key") + return } - val shift = string.startsWith("Shift+") - if (shift) { - string = string.substring(6) - } + val modifierKeys = TreeSet(Bind.keyComparator) + for (index in 0 until splitNames.size - 1) { + val name = splitNames[index] + val key = KeyboardUtils.getKey(name) - val key = Keyboard.getKeyIndex(string.toUpperCase()) + if (key !in 1..255) continue + modifierKeys.add(key) + } - value.setBind(ctrl, alt, shift, key) + value.setBind(modifierKeys, lastKey) } override fun write() = JsonPrimitive(value.toString()) @@ -53,4 +53,4 @@ class BindSetting( setValue(jsonElement?.asString ?: "None") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/kamiblue/client/util/Bind.kt b/src/main/kotlin/org/kamiblue/client/util/Bind.kt index 6a442762..02b68298 100644 --- a/src/main/kotlin/org/kamiblue/client/util/Bind.kt +++ b/src/main/kotlin/org/kamiblue/client/util/Bind.kt @@ -1,83 +1,138 @@ package org.kamiblue.client.util -import org.kamiblue.client.module.modules.client.CommandConfig import org.lwjgl.input.Keyboard +import java.util.* +import kotlin.collections.ArrayList +import kotlin.collections.HashMap class Bind( - ctrlIn: Boolean, - altIn: Boolean, - shiftIn: Boolean, + modifierKeysIn: TreeSet, keyIn: Int ) { constructor() : this(0) - constructor(keyIn: Int) : this(false, false, false, keyIn) + constructor(key: Int) : this(TreeSet(keyComparator), key) - var ctrl = ctrlIn; private set - var alt = altIn; private set - var shift = shiftIn; private set - var key = keyIn; private set + constructor(vararg modifierKeys: Int, key: Int) : this(TreeSet(keyComparator).apply { modifierKeys.forEach { add(it) } }, key) - val isEmpty: Boolean get() = !ctrl && !shift && !alt && key < 0 + val modifierKeys = modifierKeysIn + var key = keyIn; private set + private var cachedName = getName() - fun clear() { - ctrl = false - shift = false - alt = false - key = -1 - } + val isEmpty get() = key !in 1..255 - fun isDown(keyIn: Int): Boolean { + fun isDown(eventKey: Int): Boolean { return !isEmpty - && (!CommandConfig.modifierEnabled || shift == isShiftDown() && ctrl == isCtrlDown() && alt == isAltDown()) - && key == keyIn + && key == eventKey + && synchronized(this) { modifierKeys.all { isModifierKeyDown(eventKey, it) } } } - fun setBind(ctrlIn: Boolean, altIn: Boolean, shiftIn: Boolean, keyIn: Int) { - ctrl = ctrlIn - alt = altIn - shift = shiftIn - key = keyIn - } + private fun isModifierKeyDown(eventKey: Int, modifierKey: Int) = + eventKey != modifierKey + && when (modifierKey) { + Keyboard.KEY_LCONTROL, Keyboard.KEY_RCONTROL -> { + Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL) + } + Keyboard.KEY_LMENU, Keyboard.KEY_RMENU -> { + Keyboard.isKeyDown(Keyboard.KEY_LMENU) || Keyboard.isKeyDown(Keyboard.KEY_RMENU) + } + Keyboard.KEY_LSHIFT, Keyboard.KEY_RSHIFT -> { + Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT) + } + Keyboard.KEY_LMETA, Keyboard.KEY_RMETA -> { + Keyboard.isKeyDown(Keyboard.KEY_LMETA) || Keyboard.isKeyDown(Keyboard.KEY_RMETA) + } + in 0..255 -> { + Keyboard.isKeyDown(modifierKey) + } + else -> { + false + } + } fun setBind(keyIn: Int) { - ctrl = isCtrlDown() - alt = isAltDown() - shift = isShiftDown() - key = keyIn - } + val cache = ArrayList() + + for (key in Keyboard.KEYBOARD_SIZE - 1 downTo 0) { + if (key == keyIn) continue + if (!Keyboard.isKeyDown(key)) continue + cache.add(key) + } - private fun isShiftDown(): Boolean { - val eventKey = Keyboard.getEventKey() - return eventKey != Keyboard.KEY_LSHIFT && eventKey != Keyboard.KEY_RSHIFT - && (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT)) + setBind(cache, keyIn) } - private fun isCtrlDown(): Boolean { - val eventKey = Keyboard.getEventKey() - return eventKey != Keyboard.KEY_LCONTROL && eventKey != Keyboard.KEY_RCONTROL - && (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)) + fun setBind(modifierKeysIn: Collection, keyIn: Int) { + synchronized(this) { + modifierKeys.clear() + modifierKeys.addAll(modifierKeysIn) + key = keyIn + cachedName = getName() + } } - private fun isAltDown(): Boolean { - val eventKey = Keyboard.getEventKey() - return eventKey != Keyboard.KEY_LMENU && eventKey != Keyboard.KEY_RMENU - && (Keyboard.isKeyDown(Keyboard.KEY_LMENU) || Keyboard.isKeyDown(Keyboard.KEY_RMENU)) + fun clear() { + synchronized(this) { + modifierKeys.clear() + key = 0 + cachedName = getName() + } } override fun toString(): String { - return if (isEmpty) "None" - else { - StringBuffer().apply { - if (ctrl) append("Ctrl+") - if (alt) append("Alt+") - if (shift) append("Shift+") - append( - if (key in 0..255) Keyboard.getKeyName(key).toLowerCase().capitalize() - else "None" - ) - }.toString() + return cachedName + } + + private fun getName(): String { + return if (isEmpty) { + "None" + } else { + StringBuilder().run { + for (key in modifierKeys) { + val name = modifierName[key] ?: KeyboardUtils.getDisplayName(key) ?: continue + append(name) + append('+') + } + append(KeyboardUtils.getDisplayName(key)) + toString() + } + } + } + + companion object { + private val modifierName: Map = hashMapOf( + Keyboard.KEY_LCONTROL to "Ctrl", + Keyboard.KEY_RCONTROL to "Ctrl", + Keyboard.KEY_LMENU to "Alt", + Keyboard.KEY_RMENU to "Alt", + Keyboard.KEY_LSHIFT to "Shift", + Keyboard.KEY_RSHIFT to "Shift", + Keyboard.KEY_LMETA to "Meta", + Keyboard.KEY_RMETA to "Meta" + ) + + private val priorityMap: Map = HashMap().apply { + val priorityKey = arrayOf( + Keyboard.KEY_LCONTROL, Keyboard.KEY_RCONTROL, + Keyboard.KEY_LMENU, Keyboard.KEY_RMENU, + Keyboard.KEY_LSHIFT, Keyboard.KEY_RSHIFT, + Keyboard.KEY_LMETA, Keyboard.KEY_RMETA + ) + + for ((index, key) in priorityKey.withIndex()) { + this[key] = index / 2 + } + + val sortedKeys = KeyboardUtils.allKeys.sortedBy { Keyboard.getKeyName(it) } + + for ((index, key) in sortedKeys.withIndex()) { + this.putIfAbsent(key, index + priorityKey.size / 2) + } + } + + val keyComparator = compareBy { + priorityMap[it] ?: -1 } } } \ No newline at end of file diff --git a/src/main/kotlin/org/kamiblue/client/util/KeyboardUtils.kt b/src/main/kotlin/org/kamiblue/client/util/KeyboardUtils.kt index 4a6bda55..d70d2999 100644 --- a/src/main/kotlin/org/kamiblue/client/util/KeyboardUtils.kt +++ b/src/main/kotlin/org/kamiblue/client/util/KeyboardUtils.kt @@ -3,8 +3,36 @@ package org.kamiblue.client.util import org.kamiblue.client.util.text.MessageSendHelper import org.kamiblue.client.util.text.formatValue import org.lwjgl.input.Keyboard +import java.util.* +import kotlin.collections.HashMap object KeyboardUtils { + val allKeys = IntArray(Keyboard.KEYBOARD_SIZE) { it } + + private val displayNames = Array(Keyboard.KEYBOARD_SIZE) { + Keyboard.getKeyName(it)?.toLowerCase()?.capitalize() + } + + private val keyMap: Map = HashMap().apply { + // LWJGL names + for (key in 0 until Keyboard.KEYBOARD_SIZE) { + val name = Keyboard.getKeyName(key) ?: continue + this[name.toLowerCase(Locale.ROOT)] = key + } + + // Display names + for ((index, name) in displayNames.withIndex()) { + if (name == null) continue + this[name.toLowerCase(Locale.ROOT)] = index + } + + // Modifier names + this["ctrl"] = Keyboard.KEY_LCONTROL + this["alt"] = Keyboard.KEY_LMENU + this["shift"] = Keyboard.KEY_LSHIFT + this["meta"] = Keyboard.KEY_LMETA + } + fun sendUnknownKeyError(bind: String) { MessageSendHelper.sendErrorMessage("Unknown key [${formatValue(bind)}]! " + "Right shift is ${formatValue("rshift")}, " + @@ -15,10 +43,14 @@ object KeyboardUtils { } fun getKey(keyName: String): Int { - return Keyboard.getKeyIndex(keyName.toUpperCase()) + return keyMap[keyName.toLowerCase(Locale.ROOT)] ?: 0 } - fun getKeyName(keycode: Int): String { + fun getKeyName(keycode: Int): String? { return Keyboard.getKeyName(keycode) } + + fun getDisplayName(keycode: Int): String? { + return displayNames.getOrNull(keycode) + } } \ No newline at end of file