diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/chat/ChatConfig.kt b/src/main/java/at/hannibal2/skyhanni/config/features/chat/ChatConfig.kt index 5e89445696a8..7e520ab5db60 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/chat/ChatConfig.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/features/chat/ChatConfig.kt @@ -69,6 +69,7 @@ class ChatConfig { desc = "Right click a chat message to copy it. Holding Shift will copy the message with " + "Shwords applied, and holding Ctrl will copy only one line." ) + @SearchTag("control") @ConfigEditorBoolean @FeatureToggle var copyChat: Boolean = false diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/HideNotClickableConfig.kt b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/HideNotClickableConfig.kt index f3b5f2d7e1d4..9ef5f52c8d7e 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/HideNotClickableConfig.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/HideNotClickableConfig.kt @@ -8,6 +8,7 @@ import com.google.gson.annotations.Expose import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorBoolean import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorSlider import io.github.notenoughupdates.moulconfig.annotations.ConfigOption +import io.github.notenoughupdates.moulconfig.annotations.SearchTag class HideNotClickableConfig { @@ -42,8 +43,10 @@ class HideNotClickableConfig { @Expose @ConfigOption( name = "Bypass With Key", - desc = "Add the ability to bypass not clickable items when holding the control/command key.", + desc = "Add the ability to bypass not clickable items when holding the Control key " + + "(Command on macOS)", ) + @SearchTag("cmd ctrl") @ConfigEditorBoolean var itemsBypass: Boolean = true diff --git a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.kt b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.kt index a127befe0702..1693a2b4710c 100644 --- a/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.kt +++ b/src/main/java/at/hannibal2/skyhanni/config/features/inventory/InventoryConfig.kt @@ -17,6 +17,9 @@ import io.github.notenoughupdates.moulconfig.annotations.ConfigEditorDraggableLi import io.github.notenoughupdates.moulconfig.annotations.ConfigOption import io.github.notenoughupdates.moulconfig.annotations.SearchTag +private const val EVOLVING_ITEMS_SEARCH_TAG = + "Time Pocket, Bottle of Jyrre, Dark Cacao Truffle, Discrite, Moby-Duck, Rosewater Flask" + class InventoryConfig { @Expose @Category(name = "SkyBlock Guide", desc = "Help find stuff to do in SkyBlock.") @@ -170,7 +173,7 @@ class InventoryConfig { @Expose @ConfigOption(name = "Evolving Items", desc = "") @Accordion - @SearchTag("Time Pocket, Bottle of Jyrre, Dark Cacao Truffle, Discrite, Moby-Duck") + @SearchTag(EVOLVING_ITEMS_SEARCH_TAG) val evolvingItems: EvolvingItemsConfig = EvolvingItemsConfig() @Expose @@ -179,9 +182,9 @@ class InventoryConfig { val trade: TradeConfig = TradeConfig() @Expose - @ConfigOption(name = "Item Number", desc = "Showing the item number as a stack size for these items.") + @ConfigOption(name = "Item Number", desc = "Show the item number as a stack size for these items.") @ConfigEditorDraggableList - @SearchTag("Time Pocket, Bottle of Jyrre, Dark Cacao Truffle, Discrite, Moby-Duck") + @SearchTag(EVOLVING_ITEMS_SEARCH_TAG) val itemNumberAsStackSize: MutableList = mutableListOf( ItemNumberEntry.NEW_YEAR_CAKE, ItemNumberEntry.RANCHERS_BOOTS_SPEED, @@ -228,9 +231,10 @@ class InventoryConfig { @Expose @ConfigOption( name = "Quick Craft Confirmation", - desc = "Require Ctrl+Click to craft items that aren't often quick crafted " + - "(e.g. armor, weapons, accessories). Sack items can be crafted normally.", + desc = "Require Ctrl+Click (Cmd+Click on macOS) to craft items that aren't often quick " + + "crafted (e.g. armor, weapons, accessories). Sack items can be crafted normally.", ) + @SearchTag("command control") @ConfigEditorBoolean @FeatureToggle var quickCraftingConfirmation: Boolean = false diff --git a/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt b/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt index 7685a41017ee..c71de86014e9 100644 --- a/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt +++ b/src/main/java/at/hannibal2/skyhanni/events/NeuRepositoryReloadEvent.kt @@ -3,7 +3,9 @@ package at.hannibal2.skyhanni.events import at.hannibal2.skyhanni.data.repo.AbstractRepoManager import at.hannibal2.skyhanni.data.repo.AbstractRepoReloadEvent +import at.hannibal2.skyhanni.skyhannimodule.PrimaryFunction +@PrimaryFunction("onNeuRepoReload") class NeuRepositoryReloadEvent( override val manager: AbstractRepoManager ) : AbstractRepoReloadEvent(manager) diff --git a/src/main/java/at/hannibal2/skyhanni/features/chat/StashCompact.kt b/src/main/java/at/hannibal2/skyhanni/features/chat/StashCompact.kt index 9b7ee4d1f609..aff31ebc4806 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/chat/StashCompact.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/chat/StashCompact.kt @@ -130,7 +130,7 @@ object StashCompact { pickupStashPattern.matchMatcher(event.message) { event.blockedReason = REASON - ChatUtils.deleteNextMessage(REASON) { StringUtils.isEmpty(it) } + ChatUtils.deleteNextMessage(REASON) { StringUtils.isEmpty(it.string) } val currentType = currentType ?: return@matchMatcher val currentMessage = currentMessages[currentType] ?: return@matchMatcher @@ -145,7 +145,7 @@ object StashCompact { if (!config.hideAddedMessages) return genericAddedToStashPattern.matchMatcher(event.message) { event.blockedReason = REASON - ChatUtils.deleteNextMessage(REASON) { StringUtils.isEmpty(it) } + ChatUtils.deleteNextMessage(REASON) { StringUtils.isEmpty(it.string) } } } diff --git a/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt b/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt index 95c5cd5d2390..9f5859958242 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/garden/composter/ComposterOverlay.kt @@ -14,11 +14,8 @@ import at.hannibal2.skyhanni.data.SackApi.isMissingSackItem import at.hannibal2.skyhanni.data.jsonobjects.repo.GardenJson import at.hannibal2.skyhanni.data.model.ComposterUpgrade import at.hannibal2.skyhanni.data.model.TabWidget -import at.hannibal2.skyhanni.events.ConfigLoadEvent import at.hannibal2.skyhanni.events.DebugDataCollectEvent -import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent -import at.hannibal2.skyhanni.events.IslandChangeEvent -import at.hannibal2.skyhanni.events.NeuRepositoryReloadEvent +import at.hannibal2.skyhanni.events.IslandJoinEvent import at.hannibal2.skyhanni.events.RepositoryReloadEvent import at.hannibal2.skyhanni.events.WidgetUpdateEvent import at.hannibal2.skyhanni.events.minecraft.ToolTipTextEvent @@ -134,7 +131,7 @@ object ComposterOverlay { } } - @HandleEvent(InventoryFullyOpenedEvent::class, onlyOnIsland = IslandType.GARDEN) + @HandleEvent(onlyOnIsland = IslandType.GARDEN) fun onInventoryFullyOpened() { if (inInventory) displayDirty = true } @@ -508,7 +505,7 @@ object ComposterOverlay { add("") if (selected) { - add(internalName.createBuyTipLine("Control + ")) + add(internalName.createBuyTipLine("${KeyboardManager.getModifierKeyName()} + ")) } else { add("§eClick to select for profit calculations!") } @@ -600,15 +597,15 @@ object ComposterOverlay { return price } - @HandleEvent(NeuRepositoryReloadEvent::class) + @HandleEvent fun onNeuRepoReload() { updateOrganicMatterFactors() } // hopefully fix the display not working properly @HandleEvent - fun onIslandSwap(event: IslandChangeEvent) { - if (event.newIsland != IslandType.GARDEN) return + fun onIslandJoin(event: IslandJoinEvent) { + if (event.island != IslandType.GARDEN) return updateOrganicMatterFactors() } @@ -620,7 +617,7 @@ object ComposterOverlay { updateOrganicMatterFactors() } - @HandleEvent(ConfigLoadEvent::class) + @HandleEvent fun onConfigLoad() { with(config) { ConditionalUtils.onToggle(minimumOrganicMatter) { @@ -708,7 +705,7 @@ object ComposterOverlay { } @HandleEvent - fun onDebug(event: DebugDataCollectEvent) { + fun onDebugDataCollect(event: DebugDataCollectEvent) { event.title("Garden Composter") event.addIrrelevant { diff --git a/src/main/java/at/hannibal2/skyhanni/features/mining/BlockStrengthGuide.kt b/src/main/java/at/hannibal2/skyhanni/features/mining/BlockStrengthGuide.kt index 4eb7f5d0d817..5f15281e6961 100644 --- a/src/main/java/at/hannibal2/skyhanni/features/mining/BlockStrengthGuide.kt +++ b/src/main/java/at/hannibal2/skyhanni/features/mining/BlockStrengthGuide.kt @@ -7,10 +7,7 @@ import at.hannibal2.skyhanni.data.hotx.HotmData import at.hannibal2.skyhanni.data.hotx.HotmReward import at.hannibal2.skyhanni.data.model.SkyblockStat import at.hannibal2.skyhanni.events.GuiContainerEvent -import at.hannibal2.skyhanni.events.InventoryCloseEvent import at.hannibal2.skyhanni.events.InventoryFullyOpenedEvent -import at.hannibal2.skyhanni.events.IslandChangeEvent -import at.hannibal2.skyhanni.events.minecraft.SkyHanniTickEvent import at.hannibal2.skyhanni.features.dungeon.DungeonApi import at.hannibal2.skyhanni.features.nether.kuudra.KuudraApi import at.hannibal2.skyhanni.features.rift.RiftApi @@ -241,7 +238,7 @@ object BlockStrengthGuide { if (!showExtraInfos) { add(Renderable.placeholder(0, 5)) - addString("§eHold control-key to show extra infos!") + addString("§eHold ${KeyboardManager.getModifierKeyName()} key to show extra infos!") } }, ) @@ -513,7 +510,7 @@ object BlockStrengthGuide { } } - @HandleEvent(SkyHanniTickEvent::class) + @HandleEvent fun onTick() { val now = KeyboardManager.isModifierKeyDown() if (showExtraInfos != now) { @@ -522,14 +519,14 @@ object BlockStrengthGuide { } } - @HandleEvent(InventoryCloseEvent::class) + @HandleEvent fun onInventoryClose() { if (!statsOpened) return shouldBlockSHMenu = false } - @HandleEvent(IslandChangeEvent::class) - fun onIslandChange() { + @HandleEvent + fun onWorldChange() { shouldBlockSHMenu = false } diff --git a/src/main/java/at/hannibal2/skyhanni/test/graph/GraphNodeEditor.kt b/src/main/java/at/hannibal2/skyhanni/test/graph/GraphNodeEditor.kt index 7e660205b4d2..3a7e9ce4f02a 100644 --- a/src/main/java/at/hannibal2/skyhanni/test/graph/GraphNodeEditor.kt +++ b/src/main/java/at/hannibal2/skyhanni/test/graph/GraphNodeEditor.kt @@ -3,7 +3,6 @@ package at.hannibal2.skyhanni.test.graph import at.hannibal2.skyhanni.api.event.HandleEvent import at.hannibal2.skyhanni.data.model.graph.Graph import at.hannibal2.skyhanni.data.model.graph.GraphNodeTag -import at.hannibal2.skyhanni.events.GuiRenderEvent import at.hannibal2.skyhanni.skyhannimodule.SkyHanniModule import at.hannibal2.skyhanni.utils.ChatUtils import at.hannibal2.skyhanni.utils.GraphUtils.distanceSqToPlayer @@ -38,8 +37,8 @@ object GraphNodeEditor { private var lastUpdate = SimpleTimeMark.farPast() private val tagsToShow: MutableList = GraphNodeTag.entries.toMutableList() - @HandleEvent(GuiRenderEvent.GuiOnTopRenderEvent::class) - fun onRenderOverlay() { + @HandleEvent + fun onGuiRenderTop() { doRender() } @@ -246,7 +245,7 @@ object GraphNodeEditor { } add("§eClick to select/deselect this node!") - add("§eControl-Click to edit the tags for this node!") + add("§e${KeyboardManager.getModifierKeyName()}-Click to edit the tags for this node!") }, onLeftClick = { diff --git a/src/main/java/at/hannibal2/skyhanni/utils/ChatUtils.kt b/src/main/java/at/hannibal2/skyhanni/utils/ChatUtils.kt index 046bd6c6ab52..d406d046a337 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/ChatUtils.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/ChatUtils.kt @@ -30,13 +30,16 @@ import net.minecraft.client.GuiMessage import net.minecraft.client.Minecraft import net.minecraft.network.chat.Component import net.minecraft.network.chat.MutableComponent -import java.lang.UnsupportedOperationException import java.util.LinkedList import java.util.Queue import kotlin.reflect.KProperty0 import kotlin.time.Duration.Companion.milliseconds import kotlin.time.times +private const val DEBUG_PREFIX = "[SkyHanni Debug] §7" +private const val USER_ERROR_PREFIX = "§c[SkyHanni] " +private const val CHAT_PREFIX = "[SkyHanni] " + @SkyHanniModule object ChatUtils { @@ -44,10 +47,6 @@ object ChatUtils { private val log = SkyHanniLogger("chat/mod_sent") var lastButtonClicked = 0L - private const val DEBUG_PREFIX = "[SkyHanni Debug] §7" - private const val USER_ERROR_PREFIX = "§c[SkyHanni] " - private const val CHAT_PREFIX = "[SkyHanni] " - /** * Sends a debug message to the chat and the console. * This is only sent if the debug feature is enabled. @@ -359,14 +358,14 @@ object ChatUtils { } } - private var deleteNext: Pair Boolean>? = null + private var deleteNext: Pair Boolean>? = null @HandleEvent(priority = HandleEvent.HIGH) fun onChat(event: SkyHanniChatEvent.Allow) { val (reason, predicate) = deleteNext ?: return this.deleteNext = null - if (predicate(event.message)) { + if (predicate(event.chatComponent)) { event.blockedReason = reason } } @@ -379,7 +378,7 @@ object ChatUtils { fun deleteNextMessage( reason: String, - predicate: (String) -> Boolean, + predicate: (Component) -> Boolean, ) { deleteNext = reason to predicate } @@ -441,6 +440,7 @@ object ChatUtils { ) { val hint = if (SkyHanniMod.feature.chat.hideClickableHint) "" else "\n§e[CLICK to $actionName or disable this feature]" + val modifier = KeyboardManager.getModifierKeyName() clickableChat( "$message$hint", onClick = { @@ -450,7 +450,8 @@ object ChatUtils { action() } }, - hover = "§eClick to $actionName!\n§eShift-Click or Control-Click to disable this feature!", + hover = "§eClick to $actionName!\n" + + "§eShift-Click or $modifier-Click to disable this feature!", oneTimeClick = oneTimeClick, replaceSameMessage = true, ) diff --git a/src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt b/src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt index 7cb6a775cd01..d1ef4ca30ff1 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/KeyboardManager.kt @@ -1,15 +1,18 @@ package at.hannibal2.skyhanni.utils import at.hannibal2.skyhanni.events.inventory.AttemptedInventoryCloseEvent +import at.hannibal2.skyhanni.events.minecraft.KeyDownEvent +import at.hannibal2.skyhanni.events.minecraft.KeyPressEvent import at.hannibal2.skyhanni.test.command.ErrorManager import at.hannibal2.skyhanni.utils.compat.MouseCompat import com.mojang.blaze3d.platform.InputConstants import io.github.notenoughupdates.moulconfig.common.IMinecraft import net.minecraft.client.KeyMapping import net.minecraft.client.Minecraft +import net.minecraft.client.input.InputQuirks +import net.minecraft.client.input.KeyEvent import org.apache.commons.lang3.SystemUtils import org.lwjgl.glfw.GLFW -import net.minecraft.client.input.KeyEvent object KeyboardManager { @@ -17,31 +20,70 @@ object KeyboardManager { const val RIGHT_MOUSE = GLFW.GLFW_MOUSE_BUTTON_RIGHT const val MIDDLE_MOUSE = GLFW.GLFW_MOUSE_BUTTON_MIDDLE - private var lastClickedMouseButton = -1 + /** + * Represents whether either the left or right Super key (also known as Windows key) is down. + * On macOS, this is the Command key. + */ + private fun isSuperKeyDown() = + GLFW.GLFW_KEY_LEFT_SUPER.isKeyHeld() || GLFW.GLFW_KEY_RIGHT_SUPER.isKeyHeld() - // A mac-only key, represents Windows key on Windows (but different key code) - private fun isCommandKeyDown() = GLFW.GLFW_KEY_LEFT_SUPER.isKeyHeld() || GLFW.GLFW_KEY_RIGHT_SUPER.isKeyHeld() + /** + * Represents whether either the left or right Alt key is down. + * On macOS, this is the Option key. + */ + fun isMenuKeyDown() = + GLFW.GLFW_KEY_LEFT_ALT.isKeyHeld() || GLFW.GLFW_KEY_RIGHT_ALT.isKeyHeld() - // Windows: Alt key Mac: Option key - fun isMenuKeyDown() = GLFW.GLFW_KEY_LEFT_ALT.isKeyHeld() || GLFW.GLFW_KEY_RIGHT_ALT.isKeyHeld() + /** + * Represents whether either the left or right Control (Ctrl) key is down, + * regardless of platform. + */ + fun isControlKeyDown() = + GLFW.GLFW_KEY_LEFT_CONTROL.isKeyHeld() || GLFW.GLFW_KEY_RIGHT_CONTROL.isKeyHeld() - fun isControlKeyDown() = GLFW.GLFW_KEY_LEFT_CONTROL.isKeyHeld() || GLFW.GLFW_KEY_RIGHT_CONTROL.isKeyHeld() + /** + * Represents whether the operating system's modifier key is down. + * On macOS, this is Command (Cmd), while on other platforms it is Control (Ctrl). + */ + fun isModifierKeyDown() = + if (InputQuirks.REPLACE_CTRL_KEY_WITH_CMD_KEY) isSuperKeyDown() else isControlKeyDown() + /** + * Represents whether the user is trying to use the operating system's "delete word" shortcut. + * On macOS, this is Option+Backspace, while on other platforms it is Ctrl+Backspace. + */ fun isDeleteWordDown() = GLFW.GLFW_KEY_BACKSPACE.isKeyHeld() && if (SystemUtils.IS_OS_MAC) isMenuKeyDown() else isControlKeyDown() + /** + * Represents whether the user is trying to use the operating system's "delete line" shortcut. + * On macOS, this is Cmd+Shift+Backspace, while on other platforms it is Ctrl+Shift+Backspace. + */ fun isDeleteLineDown() = - GLFW.GLFW_KEY_BACKSPACE.isKeyHeld() && if (SystemUtils.IS_OS_MAC) isCommandKeyDown() else isControlKeyDown() && isShiftKeyDown() - - fun isShiftKeyDown() = GLFW.GLFW_KEY_LEFT_SHIFT.isKeyHeld() || GLFW.GLFW_KEY_RIGHT_SHIFT.isKeyHeld() + GLFW.GLFW_KEY_BACKSPACE.isKeyHeld() && isModifierKeyDown() && isShiftKeyDown() - fun isPastingKeysDown() = isModifierKeyDown() && GLFW.GLFW_KEY_V.isKeyHeld() + /** + * Represents whether either the left or right Shift key is down. + */ + fun isShiftKeyDown() = + GLFW.GLFW_KEY_LEFT_SHIFT.isKeyHeld() || GLFW.GLFW_KEY_RIGHT_SHIFT.isKeyHeld() - fun isCopyingKeysDown() = isModifierKeyDown() && GLFW.GLFW_KEY_C.isKeyHeld() + /** + * Represents whether the user is trying to use the operating system's "copy" shortcut. + * On macOS, this is Cmd+C, while on other platforms it is Ctrl+C. + */ + fun isCopyingKeysDown() = + isModifierKeyDown() && GLFW.GLFW_KEY_C.isKeyHeld() - fun isModifierKeyDown() = if (SystemUtils.IS_OS_MAC) isCommandKeyDown() else isControlKeyDown() + /** + * Represents whether the user is trying to use the operating system's "paste" shortcut. + * On macOS, this is Cmd+V, while on other platforms it is Ctrl+V. + */ + fun isPastingKeysDown() = + isModifierKeyDown() && GLFW.GLFW_KEY_V.isKeyHeld() - private fun Int.matchesClosureKey() = Minecraft.getInstance().options.keyInventory.matches(KeyEvent(this, this, 0)) + private fun Int.matchesClosureKey() = + Minecraft.getInstance().options.keyInventory.matches(KeyEvent(this, this, 0)) @JvmStatic fun checkIsInventoryClosure(keycode: Int): Boolean { @@ -54,17 +96,15 @@ object KeyboardManager { return AttemptedInventoryCloseEvent().post() } - /** - * TODO make use of this function unnecessary: Try to avoid using `isModifierKeyDown` as the only option, - * allow the user to set a different option instead and just set the default key to isModifierKeyDown - */ - fun getModifierKeyName(): String = if (SystemUtils.IS_OS_MAC) "Command" else "Control" - - /* - The delay below is here to make sure the Text input features in graph editor - and in renderable calls have time to react first, and lock this key press event properly - */ + fun getModifierKeyName(short: Boolean = false): String = + if (InputQuirks.REPLACE_CTRL_KEY_WITH_CMD_KEY) { + if (short) "Cmd" else "Command" + } else { + if (short) "Ctrl" else "Control" + } + // The delay below is here to make sure the Text input features in graph editor + // and in renderable calls have time to react first, and lock this key press event properly. fun KeyMapping.isActive(): Boolean { try { if (key.value.isKeyHeld()) return true @@ -76,31 +116,29 @@ object KeyboardManager { ) return false } - return this.isDown || this.consumeClick() + return isDown || consumeClick() } fun Int.isKeyHeld(): Boolean = when { - this < -1 -> ErrorManager.skyHanniError("Error while checking if a key is pressed. Keycode is invalid: $this") + this < -1 -> ErrorManager.skyHanniError( + "Error while checking if a key is pressed. Key code is invalid: $this", + ) this == -1 -> false this in 0..5 -> MouseCompat.isButtonDown(this) else -> InputConstants.isKeyDown(Minecraft.getInstance().window, this) } - private val lockedKeys = mutableMapOf() + private val lockedKeys = mutableSetOf() /** - * Can only be used once per click, since the function locks itself until the key is no longer held. - * Do not use in KeyPressEvent, since it won't be unlocked again, use KeyDownEvent instead. - * */ - fun Int.isKeyClicked(): Boolean = if (this.isKeyHeld()) { - if (lockedKeys[this] != true) { - lockedKeys[this] = true - true - } else { - false - } + * Can only be used once per click, since the function locks itself until the key is no longer + * held. Do not use in [KeyPressEvent], since it won't be unlocked again – use [KeyDownEvent] + * instead. + */ + fun Int.isKeyClicked(): Boolean = if (isKeyHeld()) { + lockedKeys.add(this) } else { - lockedKeys[this] = false + lockedKeys.remove(this) false }