From fa9a0da21d5044f7f0187dc7a93d0485136e642a Mon Sep 17 00:00:00 2001 From: boholder Date: Fri, 18 Aug 2023 09:34:56 +0800 Subject: [PATCH 01/10] Apply KeystrokeChecker on NarratorMenu. Replace more if condition with KeystrokeChecker. Add isNotPressing method to KeystrokeChecker. Simplify KeystrokeChecker methods name. Replace logic with KeystrokeChecker in NarratorMenu. Simplify KeystrokeChecker methods name. --- .../features/narrator_menu/NarratorMenu.java | 19 ++++--------- .../minecraft_access/utils/TimeUtils.java | 18 ++++++++----- .../minecraft_access/utils/TimeUtilsTest.java | 27 +++++++++++++------ 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java index b98f3aaf..4e16acf0 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java @@ -31,8 +31,6 @@ public class NarratorMenu { private static MinecraftClient minecraftClient; private static final TimeUtils.KeystrokeChecker narratorMenuKeyCondition; private static final TimeUtils.KeystrokeChecker narratorMenuHotKeyCondition; - private static boolean isMenuKeyPressedPreviousTick = false; - private static boolean isHotKeyPressedPreviousTick = false; private static boolean isHotKeySwitchedPreviousTick = false; private static boolean isNarratorMenuJustClosed = false; private int hotKeyFunctionIndex = 0; @@ -92,13 +90,10 @@ public void update() { if (minecraftClient == null) return; if (minecraftClient.player == null) return; - KeyBindingsHandler kbh = KeyBindingsHandler.getInstance(); - boolean isNarratorMenuKeyPressed = KeyUtils.isAnyPressed(kbh.narratorMenuKey); - // With Narrator Menu opened, listen to number keys pressing for executing corresponding functions if (minecraftClient.currentScreen instanceof NarratorMenuGUI) { // Close the menu if the F4 key is pressed while the menu is opening - if (isNarratorMenuKeyPressed) { + if (narratorMenuKeyCondition.isPressing()) { isNarratorMenuJustClosed = true; minecraftClient.currentScreen.close(); return; @@ -115,20 +110,18 @@ public void update() { if (minecraftClient.currentScreen != null) return; - boolean isNarratorMenuHotKeyPressed = KeyUtils.isAnyPressed(kbh.narratorMenuHotKey); - // F3 + F4 triggers game mode changing function in vanilla game, will not open the menu under this situation. boolean isF3KeyNotPressed = !KeyUtils.isF3Pressed(); // The F4 is pressed before and released at current tick // To make the narrator menu open AFTER release the F4 key - boolean openTheMenuScreen = !isNarratorMenuKeyPressed && isMenuKeyPressedPreviousTick && !isNarratorMenuJustClosed; + boolean openTheMenuScreen = narratorMenuKeyCondition.isReleased() && !isNarratorMenuJustClosed; // Opposite to menu open, executes immediately, // but will not execute twice until release and press the key again - boolean triggerHotKey = isNarratorMenuHotKeyPressed && !isHotKeyPressedPreviousTick; + boolean triggerHotKey = narratorMenuHotKeyCondition.isPressed(); - if (isNarratorMenuKeyPressed && triggerHotKey) { + if (narratorMenuKeyCondition.isPressing() && triggerHotKey) { // for prevent the menu open this time after release the F4 key // the user intend to switch the function, not open the menu isHotKeySwitchedPreviousTick = true; @@ -155,12 +148,10 @@ public void update() { // update the states for next tick narratorMenuKeyCondition.updateStateForNextTick(); narratorMenuHotKeyCondition.updateStateForNextTick(); - isMenuKeyPressedPreviousTick = isNarratorMenuKeyPressed; - isHotKeyPressedPreviousTick = isNarratorMenuHotKeyPressed; // clean the states when F4 is released if (openTheMenuScreen) isHotKeySwitchedPreviousTick = false; - if (!isNarratorMenuKeyPressed) isNarratorMenuJustClosed = false; + if (narratorMenuKeyCondition.isNotPressing()) isNarratorMenuJustClosed = false; } catch (Exception e) { MainClass.errorLog("An error occurred in NarratorMenu."); diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtils.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtils.java index 6d5b5fbc..6fa21f77 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtils.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtils.java @@ -102,23 +102,27 @@ public KeystrokeChecker(BooleanSupplier condition) { * Invoke this method at the end of feature logic. */ public void updateStateForNextTick() { - hasKeyPressed = isKeyPressing(); + hasKeyPressed = isPressing(); } - public boolean isKeyPressing() { + public boolean isPressing() { return condition.getAsBoolean(); } - public boolean hasKeyPressedPreviousTick() { + public boolean isNotPressing() { + return !isPressing(); + } + + public boolean hasPressedPreviousTick() { return hasKeyPressed; } - public boolean isKeyReleased() { - return !isKeyPressing() && hasKeyPressedPreviousTick(); + public boolean isReleased() { + return !isPressing() && hasPressedPreviousTick(); } - public boolean isKeyPressed() { - return isKeyPressing() && !hasKeyPressedPreviousTick(); + public boolean isPressed() { + return isPressing() && !hasPressedPreviousTick(); } } } diff --git a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java index 325d0e4f..0ab618b3 100644 --- a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java +++ b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java @@ -38,18 +38,18 @@ public MockKeystroke(boolean initPressed) { void testMockKeystrokeWorks() { var m = new MockKeystroke(true); var k = new TimeUtils.KeystrokeChecker(m.supplier); - assertThat(k.isKeyPressing()).isEqualTo(true); + assertThat(k.isPressing()).isEqualTo(true); m.revertKeystrokeResult(); - assertThat(k.isKeyPressing()).isEqualTo(false); + assertThat(k.isPressing()).isEqualTo(false); } @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testIsKeyPressing(MockKeystroke keystroke, boolean expected) { TimeUtils.KeystrokeChecker checker = new TimeUtils.KeystrokeChecker(keystroke.supplier); - assertThat(checker.isKeyPressing()).isEqualTo(expected); + assertThat(checker.isPressing()).isEqualTo(expected); keystroke.revertKeystrokeResult(); - assertThat(checker.isKeyPressing()) + assertThat(checker.isPressing()) .as("keystroke condition should be reverted") .isEqualTo(!expected); } @@ -61,6 +61,17 @@ static Stream provideKeystrokeCheckerTestCases() { ); } + @ParameterizedTest + @MethodSource("provideKeystrokeCheckerTestCases") + void testIsKeyNotPressing(MockKeystroke keystroke, boolean expected) { + TimeUtils.KeystrokeChecker checker = new TimeUtils.KeystrokeChecker(keystroke.supplier); + assertThat(checker.isNotPressing()).isEqualTo(!expected); + keystroke.revertKeystrokeResult(); + assertThat(checker.isNotPressing()) + .as("keystroke condition should be reverted") + .isEqualTo(expected); + } + @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testHasKeyPressed(MockKeystroke keyStroke, boolean expected) { @@ -70,12 +81,12 @@ void testHasKeyPressed(MockKeystroke keyStroke, boolean expected) { checker.updateStateForNextTick(); // tick 1 - assertThat(checker.hasKeyPressedPreviousTick()).isEqualTo(expected); + assertThat(checker.hasPressedPreviousTick()).isEqualTo(expected); keyStroke.revertKeystrokeResult(); checker.updateStateForNextTick(); // tick 2 - assertThat(checker.hasKeyPressedPreviousTick()) + assertThat(checker.hasPressedPreviousTick()) .as("keystroke condition should be reverted") .isEqualTo(!expected); } @@ -90,7 +101,7 @@ void testIsKeyReleased(MockKeystroke keyStroke, boolean expected) { // tick 1 keyStroke.revertKeystrokeResult(); - assertThat(checker.isKeyReleased()) + assertThat(checker.isReleased()) .as("pressed key should be released now, vice versa") .isEqualTo(expected); } @@ -105,7 +116,7 @@ void testIsKeyPressed(MockKeystroke keyStroke, boolean expected) { // tick 1 keyStroke.revertKeystrokeResult(); - assertThat(checker.isKeyPressed()) + assertThat(checker.isPressed()) .as("released key should be pressed now, vice versa") .isEqualTo(!expected); } From edc9b2b0cbe91e4169bbf69bb4250eccbe8cb2b5 Mon Sep 17 00:00:00 2001 From: boholder Date: Fri, 18 Aug 2023 10:40:53 +0800 Subject: [PATCH 02/10] Replace keystroke condition in MouseKeySimulation. --- .../features/MouseKeySimulation.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java index 418059ab..e955efe3 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java @@ -23,10 +23,7 @@ public class MouseKeySimulation { private static final MouseKeySimulation instance; private boolean enabled; - /** - * index 0,1,2 for left, middle, right mouse key - */ - private final boolean[] isMouseKeyPressedPreviousTick = new boolean[]{false, false, false}; + private static final TimeUtils.KeystrokeChecker[] mouseKeystrokes = new TimeUtils.KeystrokeChecker[3]; private TimeUtils.Interval scrollUpDelay; private TimeUtils.Interval scrollDownDelay; @@ -36,6 +33,12 @@ public class MouseKeySimulation { } catch (Exception e) { throw new RuntimeException("Exception occurred in creating AttackAndUseSimulation instance"); } + + // config keystroke conditions + KeyBindingsHandler kbh = KeyBindingsHandler.getInstance(); + mouseKeystrokes[0] = new TimeUtils.KeystrokeChecker(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationLeftMouseKey)); + mouseKeystrokes[1] = new TimeUtils.KeystrokeChecker(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationMiddleMouseKey)); + mouseKeystrokes[2] = new TimeUtils.KeystrokeChecker(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationRightMouseKey)); } public static synchronized MouseKeySimulation getInstance() { @@ -80,23 +83,20 @@ private void execute() { }); Set.of( - new MouseKeyDTO(KeyUtils.isAnyPressed(kbh.mouseSimulationLeftMouseKey), 0, MouseUtils::leftDown, MouseUtils::leftUp), - new MouseKeyDTO(KeyUtils.isAnyPressed(kbh.mouseSimulationMiddleMouseKey), 1, MouseUtils::middleDown, MouseUtils::middleUp), - new MouseKeyDTO(KeyUtils.isAnyPressed(kbh.mouseSimulationRightMouseKey), 2, MouseUtils::rightDown, MouseUtils::rightUp) + new MouseKeyDTO(mouseKeystrokes[0], MouseUtils::leftDown, MouseUtils::leftUp), + new MouseKeyDTO(mouseKeystrokes[1], MouseUtils::middleDown, MouseUtils::middleUp), + new MouseKeyDTO(mouseKeystrokes[2], MouseUtils::rightDown, MouseUtils::rightUp) ).forEach(dto -> { - if (dto.keyPressed && !isMouseKeyPressedPreviousTick[dto.keyPressedPreviouslyIndex]) { - // key pressed + if (dto.keystroke.isPressed()) { dto.keyDown.run(); - } else if (!dto.keyPressed && isMouseKeyPressedPreviousTick[dto.keyPressedPreviouslyIndex]) { - // key released + } else if (dto.keystroke.isReleased()) { dto.keyUp.run(); } - // update state - isMouseKeyPressedPreviousTick[dto.keyPressedPreviouslyIndex] = dto.keyPressed; + dto.keystroke.updateStateForNextTick(); }); } - private record MouseKeyDTO(Boolean keyPressed, int keyPressedPreviouslyIndex, Runnable keyDown, Runnable keyUp) { + private record MouseKeyDTO(TimeUtils.KeystrokeChecker keystroke, Runnable keyDown, Runnable keyUp) { } } From 560c862705fbaf50dc87ada5106806e51ec978ef Mon Sep 17 00:00:00 2001 From: boholder Date: Fri, 18 Aug 2023 10:45:29 +0800 Subject: [PATCH 03/10] Arrange condition util classes. New KeystrokeTiming class for reusing. Rename KeystrokeChecker to Keystroke. Move Interval and KeystrokeChecker to new subpackage. Move Interval to independent file. Move KeystrokeChecker to independent file. --- .../features/CameraControls.java | 10 +- .../features/MouseKeySimulation.java | 29 ++-- .../features/ReadCrosshair.java | 6 +- .../inventory_controls/InventoryControls.java | 10 +- .../features/narrator_menu/NarratorMenu.java | 9 +- .../point_of_interest/LockingHandler.java | 10 +- .../features/point_of_interest/POIBlocks.java | 6 +- .../point_of_interest/POIEntities.java | 6 +- .../mixin/AnimatedResultButtonMixin.java | 4 +- .../minecraft_access/utils/TimeUtils.java | 128 ------------------ .../utils/condition/Interval.java | 74 ++++++++++ .../utils/condition/Keystroke.java | 53 ++++++++ .../utils/condition/KeystrokeTiming.java | 31 +++++ .../minecraft_access/utils/TimeUtilsTest.java | 17 +-- 14 files changed, 209 insertions(+), 184 deletions(-) delete mode 100644 common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtils.java create mode 100644 common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Interval.java create mode 100644 common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java create mode 100644 common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/KeystrokeTiming.java diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/CameraControls.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/CameraControls.java index 057adf6f..8dffe381 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/CameraControls.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/CameraControls.java @@ -2,10 +2,8 @@ import com.github.khanshoaib3.minecraft_access.MainClass; import com.github.khanshoaib3.minecraft_access.config.config_maps.CameraControlsConfigMap; -import com.github.khanshoaib3.minecraft_access.utils.KeyBindingsHandler; -import com.github.khanshoaib3.minecraft_access.utils.KeyUtils; -import com.github.khanshoaib3.minecraft_access.utils.PlayerPositionUtils; -import com.github.khanshoaib3.minecraft_access.utils.TimeUtils; +import com.github.khanshoaib3.minecraft_access.utils.*; +import com.github.khanshoaib3.minecraft_access.utils.condition.Interval; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.MinecraftClient; @@ -36,7 +34,7 @@ public class CameraControls { private float normalRotatingDeltaAngle; private float modifiedRotatingDeltaAngle; - private TimeUtils.Interval interval; + private Interval interval; public CameraControls() { loadConfigurations(); @@ -66,7 +64,7 @@ private void loadConfigurations() { float delta90Degrees = 600f; // 90 / 0.15 CameraControlsConfigMap map = MainClass.config.getConfigMap().getCameraControlsConfigMap(); - interval = TimeUtils.Interval.inMilliseconds(map.getDelayInMilliseconds(), interval); + interval = Interval.inMilliseconds(map.getDelayInMilliseconds(), interval); float normalRotatingAngle = map.getNormalRotatingAngle(); float modifiedRotatingAngle = map.getModifiedRotatingAngle(); normalRotatingDeltaAngle = delta90Degrees / (90 / normalRotatingAngle); diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java index e955efe3..f5fa95a3 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java @@ -2,10 +2,9 @@ import com.github.khanshoaib3.minecraft_access.MainClass; import com.github.khanshoaib3.minecraft_access.config.config_maps.MouseSimulationConfigMap; -import com.github.khanshoaib3.minecraft_access.utils.KeyBindingsHandler; -import com.github.khanshoaib3.minecraft_access.utils.KeyUtils; -import com.github.khanshoaib3.minecraft_access.utils.MouseUtils; -import com.github.khanshoaib3.minecraft_access.utils.TimeUtils; +import com.github.khanshoaib3.minecraft_access.utils.*; +import com.github.khanshoaib3.minecraft_access.utils.condition.Interval; +import com.github.khanshoaib3.minecraft_access.utils.condition.Keystroke; import net.minecraft.client.MinecraftClient; import org.apache.commons.lang3.tuple.Triple; @@ -23,9 +22,9 @@ public class MouseKeySimulation { private static final MouseKeySimulation instance; private boolean enabled; - private static final TimeUtils.KeystrokeChecker[] mouseKeystrokes = new TimeUtils.KeystrokeChecker[3]; - private TimeUtils.Interval scrollUpDelay; - private TimeUtils.Interval scrollDownDelay; + private static final Keystroke[] mouseKeystrokes = new Keystroke[3]; + private Interval scrollUpDelay; + private Interval scrollDownDelay; static { try { @@ -36,9 +35,9 @@ public class MouseKeySimulation { // config keystroke conditions KeyBindingsHandler kbh = KeyBindingsHandler.getInstance(); - mouseKeystrokes[0] = new TimeUtils.KeystrokeChecker(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationLeftMouseKey)); - mouseKeystrokes[1] = new TimeUtils.KeystrokeChecker(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationMiddleMouseKey)); - mouseKeystrokes[2] = new TimeUtils.KeystrokeChecker(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationRightMouseKey)); + mouseKeystrokes[0] = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationLeftMouseKey)); + mouseKeystrokes[1] = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationMiddleMouseKey)); + mouseKeystrokes[2] = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationRightMouseKey)); } public static synchronized MouseKeySimulation getInstance() { @@ -66,16 +65,16 @@ public void update() { private void loadConfigurations() { MouseSimulationConfigMap map = MainClass.config.getConfigMap().getMouseSimulationConfigMap(); this.enabled = map.isEnabled(); - this.scrollUpDelay = TimeUtils.Interval.inMilliseconds(map.getScrollDelayInMilliseconds(), this.scrollUpDelay); - this.scrollDownDelay = TimeUtils.Interval.inMilliseconds(map.getScrollDelayInMilliseconds(), this.scrollDownDelay); + this.scrollUpDelay = Interval.inMilliseconds(map.getScrollDelayInMilliseconds(), this.scrollUpDelay); + this.scrollDownDelay = Interval.inMilliseconds(map.getScrollDelayInMilliseconds(), this.scrollDownDelay); } private void execute() { KeyBindingsHandler kbh = KeyBindingsHandler.getInstance(); Set.of( - Triple.of(KeyUtils.isAnyPressed(kbh.mouseSimulationScrollUpKey), scrollUpDelay, MouseUtils::scrollUp), - Triple.of(KeyUtils.isAnyPressed(kbh.mouseSimulationScrollDownKey), scrollDownDelay, MouseUtils::scrollDown) + Triple.of(KeyUtils.isAnyPressed(kbh.mouseSimulationScrollUpKey), scrollUpDelay, MouseUtils::scrollUp), + Triple.of(KeyUtils.isAnyPressed(kbh.mouseSimulationScrollDownKey), scrollDownDelay, MouseUtils::scrollDown) ).forEach(t -> { if (t.getLeft() && t.getMiddle().isReady()) { t.getRight().run(); @@ -97,6 +96,6 @@ private void execute() { }); } - private record MouseKeyDTO(TimeUtils.KeystrokeChecker keystroke, Runnable keyDown, Runnable keyUp) { + private record MouseKeyDTO(Keystroke keystroke, Runnable keyDown, Runnable keyUp) { } } diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/ReadCrosshair.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/ReadCrosshair.java index 533fdd42..412201d3 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/ReadCrosshair.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/ReadCrosshair.java @@ -5,8 +5,8 @@ import com.github.khanshoaib3.minecraft_access.config.config_maps.RCPartialSpeakingConfigMap; import com.github.khanshoaib3.minecraft_access.config.config_maps.ReadCrosshairConfigMap; import com.github.khanshoaib3.minecraft_access.mixin.MobSpawnerLogicAccessor; +import com.github.khanshoaib3.minecraft_access.utils.condition.Interval; import com.github.khanshoaib3.minecraft_access.utils.PlayerPositionUtils; -import com.github.khanshoaib3.minecraft_access.utils.TimeUtils; import net.minecraft.block.*; import net.minecraft.block.entity.BeehiveBlockEntity; import net.minecraft.block.entity.BlockEntity; @@ -46,7 +46,7 @@ public class ReadCrosshair { private String previousQuery; private boolean speakSide; private boolean speakingConsecutiveBlocks; - private TimeUtils.Interval repeatSpeakingInterval; + private Interval repeatSpeakingInterval; private boolean enablePartialSpeaking; private boolean partialSpeakingWhitelistMode; private boolean partialSpeakingFuzzyMode; @@ -105,7 +105,7 @@ private void loadConfigurations() { // affirmation for easier use this.speakingConsecutiveBlocks = !rcMap.isDisableSpeakingConsecutiveBlocks(); long interval = rcMap.getRepeatSpeakingInterval(); - this.repeatSpeakingInterval = TimeUtils.Interval.inMilliseconds(interval, this.repeatSpeakingInterval); + this.repeatSpeakingInterval = Interval.inMilliseconds(interval, this.repeatSpeakingInterval); this.enablePartialSpeaking = rcpMap.isEnabled(); this.partialSpeakingFuzzyMode = rcpMap.isPartialSpeakingFuzzyMode(); diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/InventoryControls.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/InventoryControls.java index fc577814..e73a1ee7 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/InventoryControls.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/inventory_controls/InventoryControls.java @@ -3,10 +3,8 @@ import com.github.khanshoaib3.minecraft_access.MainClass; import com.github.khanshoaib3.minecraft_access.config.config_maps.InventoryControlsConfigMap; import com.github.khanshoaib3.minecraft_access.mixin.*; -import com.github.khanshoaib3.minecraft_access.utils.KeyBindingsHandler; -import com.github.khanshoaib3.minecraft_access.utils.KeyUtils; -import com.github.khanshoaib3.minecraft_access.utils.MouseUtils; -import com.github.khanshoaib3.minecraft_access.utils.TimeUtils; +import com.github.khanshoaib3.minecraft_access.utils.*; +import com.github.khanshoaib3.minecraft_access.utils.condition.Interval; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.ingame.*; import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider; @@ -49,7 +47,7 @@ public class InventoryControls { private boolean autoOpenRecipeBook; private String rowAndColumnFormat; - private TimeUtils.Interval interval; + private Interval interval; private MinecraftClient minecraftClient; private HandledScreenAccessor previousScreen = null; @@ -165,7 +163,7 @@ private void loadConfigurations() { InventoryControlsConfigMap map = MainClass.config.getConfigMap().getInventoryControlsConfigMap(); autoOpenRecipeBook = map.isAutoOpenRecipeBook(); rowAndColumnFormat = map.getRowAndColumnFormat(); - interval = TimeUtils.Interval.inMilliseconds(map.getDelayInMilliseconds(), interval); + interval = Interval.inMilliseconds(map.getDelayInMilliseconds(), interval); } /** diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java index 4e16acf0..592e3021 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java @@ -6,6 +6,7 @@ import com.github.khanshoaib3.minecraft_access.features.ReadCrosshair; import com.github.khanshoaib3.minecraft_access.screen_reader.ScreenReaderController; import com.github.khanshoaib3.minecraft_access.utils.*; +import com.github.khanshoaib3.minecraft_access.utils.condition.Keystroke; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; @@ -29,8 +30,8 @@ */ public class NarratorMenu { private static MinecraftClient minecraftClient; - private static final TimeUtils.KeystrokeChecker narratorMenuKeyCondition; - private static final TimeUtils.KeystrokeChecker narratorMenuHotKeyCondition; + private static final Keystroke narratorMenuKeyCondition; + private static final Keystroke narratorMenuHotKeyCondition; private static boolean isHotKeySwitchedPreviousTick = false; private static boolean isNarratorMenuJustClosed = false; private int hotKeyFunctionIndex = 0; @@ -41,8 +42,8 @@ public class NarratorMenu { // config keystroke conditions KeyBindingsHandler kbh = KeyBindingsHandler.getInstance(); - narratorMenuKeyCondition = new TimeUtils.KeystrokeChecker(() -> KeyUtils.isAnyPressed(kbh.narratorMenuKey)); - narratorMenuHotKeyCondition = new TimeUtils.KeystrokeChecker(() -> KeyUtils.isAnyPressed(kbh.narratorMenuHotKey)); + narratorMenuKeyCondition = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.narratorMenuKey)); + narratorMenuHotKeyCondition = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.narratorMenuHotKey)); } /** diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/LockingHandler.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/LockingHandler.java index 86896a67..b5428902 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/LockingHandler.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/LockingHandler.java @@ -3,10 +3,8 @@ import com.github.khanshoaib3.minecraft_access.MainClass; import com.github.khanshoaib3.minecraft_access.config.config_maps.POILockingConfigMap; import com.github.khanshoaib3.minecraft_access.config.config_maps.POIMarkingConfigMap; -import com.github.khanshoaib3.minecraft_access.utils.KeyBindingsHandler; -import com.github.khanshoaib3.minecraft_access.utils.KeyUtils; -import com.github.khanshoaib3.minecraft_access.utils.PositionUtils; -import com.github.khanshoaib3.minecraft_access.utils.TimeUtils; +import com.github.khanshoaib3.minecraft_access.utils.*; +import com.github.khanshoaib3.minecraft_access.utils.condition.Interval; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.client.MinecraftClient; @@ -38,7 +36,7 @@ public class LockingHandler { public boolean isLockedOntoLadder = false; public boolean isLockedOntoEyeOfEnderTarget = false; // The block where the eye of ender disappears public String lockedOnBlockEntries = ""; - private TimeUtils.Interval interval; + private Interval interval; private boolean lockOnBlocks; private boolean speakDistance; @@ -75,7 +73,7 @@ private void loadConfigurations() { this.lockOnBlocks = map.isLockOnBlocks(); this.speakDistance = map.isSpeakDistance(); this.unlockingSound = map.isUnlockingSound(); - this.interval = TimeUtils.Interval.inMilliseconds(map.getDelay(), this.interval); + this.interval = Interval.inMilliseconds(map.getDelay(), this.interval); } private void mainLogic() { diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/POIBlocks.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/POIBlocks.java index 91c50d07..15264ab0 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/POIBlocks.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/POIBlocks.java @@ -3,7 +3,7 @@ import com.github.khanshoaib3.minecraft_access.MainClass; import com.github.khanshoaib3.minecraft_access.config.config_maps.POIBlocksConfigMap; import com.github.khanshoaib3.minecraft_access.config.config_maps.POIMarkingConfigMap; -import com.github.khanshoaib3.minecraft_access.utils.TimeUtils; +import com.github.khanshoaib3.minecraft_access.utils.condition.Interval; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import net.minecraft.block.*; @@ -47,7 +47,7 @@ public class POIBlocks { private boolean playSound; private float volume; private boolean playSoundForOtherBlocks; - private TimeUtils.Interval interval; + private Interval interval; private static final List> blockList = Lists.newArrayList(); private static final List> oreBlockList = Lists.newArrayList(); @@ -156,7 +156,7 @@ private void loadConfigurations() { this.playSound = poiBlocksConfigMap.isPlaySound(); this.volume = poiBlocksConfigMap.getVolume(); this.playSoundForOtherBlocks = poiBlocksConfigMap.isPlaySoundForOtherBlocks(); - this.interval = TimeUtils.Interval.inMilliseconds(poiBlocksConfigMap.getDelay(), this.interval); + this.interval = Interval.inMilliseconds(poiBlocksConfigMap.getDelay(), this.interval); } private void checkBlock(BlockPos blockPos, int val) { diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/POIEntities.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/POIEntities.java index a97cdfc9..a2431366 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/POIEntities.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/point_of_interest/POIEntities.java @@ -3,7 +3,7 @@ import com.github.khanshoaib3.minecraft_access.MainClass; import com.github.khanshoaib3.minecraft_access.config.config_maps.POIEntitiesConfigMap; import com.github.khanshoaib3.minecraft_access.config.config_maps.POIMarkingConfigMap; -import com.github.khanshoaib3.minecraft_access.utils.TimeUtils; +import com.github.khanshoaib3.minecraft_access.utils.condition.Interval; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.Entity; import net.minecraft.entity.EyeOfEnderEntity; @@ -34,7 +34,7 @@ public class POIEntities { private int range; private boolean playSound; private float volume; - private TimeUtils.Interval interval; + private Interval interval; private boolean enabled; private static final POIEntities instance; @@ -141,7 +141,7 @@ private void loadConfigurations() { this.range = map.getRange(); this.playSound = map.isPlaySound(); this.volume = map.getVolume(); - this.interval = TimeUtils.Interval.inMilliseconds(map.getDelay(), this.interval); + this.interval = Interval.inMilliseconds(map.getDelay(), this.interval); } public void setMarking(boolean marking) { diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/AnimatedResultButtonMixin.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/AnimatedResultButtonMixin.java index 47af5428..65be2847 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/AnimatedResultButtonMixin.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/AnimatedResultButtonMixin.java @@ -1,8 +1,8 @@ package com.github.khanshoaib3.minecraft_access.mixin; import com.github.khanshoaib3.minecraft_access.MainClass; +import com.github.khanshoaib3.minecraft_access.utils.condition.Interval; import com.github.khanshoaib3.minecraft_access.utils.MouseUtils; -import com.github.khanshoaib3.minecraft_access.utils.TimeUtils; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.screen.recipebook.AnimatedResultButton; import net.minecraft.client.resource.language.I18n; @@ -23,7 +23,7 @@ public class AnimatedResultButtonMixin { String minecraft_access$previousItemName = ""; @Unique - private final TimeUtils.Interval minecraft_access$interval = TimeUtils.Interval.inMilliseconds(5000); + private final Interval minecraft_access$interval = Interval.inMilliseconds(5000); // @Inject(at = @At("HEAD"), method = "appendNarrations", cancellable = true) // Pre 1.19.3 @Inject(at = @At("HEAD"), method = "appendClickableNarrations", cancellable = true) // From 1.19.3 diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtils.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtils.java deleted file mode 100644 index 6fa21f77..00000000 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtils.java +++ /dev/null @@ -1,128 +0,0 @@ -package com.github.khanshoaib3.minecraft_access.utils; - -import java.util.function.BooleanSupplier; - -public class TimeUtils { - /** - * An auto-refresh countdown timer for controlling interval execution of features. - */ - public static class Interval { - private long lastRunTime; - private final long delay; - private boolean isRunning; - private final boolean disabled; - - private Interval(long lastRunTime, long delayInNanoTime) { - this.lastRunTime = lastRunTime; - this.delay = delayInNanoTime; - this.disabled = delayInNanoTime == 0; - this.isRunning = false; - } - - /** - * Build or update instance according to delay config. - * - * @param delay config value - * @param previous the interval class variable - */ - public static Interval inMilliseconds(long delay, Interval... previous) { - if (previous == null || previous.length == 0 || previous[0] == null) { - // 1 milliseconds = 1*10^6 nanoseconds - return new Interval(System.nanoTime(), delay * 1000_000); - } else { - Interval interval = previous[0]; - boolean configChanged = delay * 1000_000 != interval.delay; - return configChanged ? Interval.inMilliseconds(delay) : interval; - } - } - - public void reset() { - lastRunTime = System.nanoTime(); - } - - /** - * Check if the delay has cooled down. (Will auto-reset the timer if true) - */ - public boolean isReady() { - if (disabled) return false; - - if (System.nanoTime() - lastRunTime > delay) { - reset(); - return true; - } else { - return false; - } - } - - /** - * Check if the delay has cooled down. (This will not auto-reset the timer, that will have to be done by calling the start() method) - * - * @return true if the delay timer has stopped or cooled down. - */ - public boolean hasEnded() { - if (!this.isRunning) return true; - if (!this.isReady()) return false; - - this.isRunning = false; - return true; - } - - /** - * Starts or resets the delay timer. This is recommended to be used when there is a key input. - */ - public void start() { - this.isRunning = true; - reset(); - } - } - - /** - * A state machine that helps with complex keystroke condition checking, - * such as "when-the-key-is-released", "only-trigger-once-before-release-key", "double-strike-within-interval". - */ - public static class KeystrokeChecker { - /** - * Save the state of keystroke at the previous tick. - */ - private boolean hasKeyPressed = false; - /** - * Expression that checking if the key (combination) is pressed now. - */ - private final BooleanSupplier condition; - - /** - * @param condition Expression that checking if the key (combination) is pressed now. - */ - public KeystrokeChecker(BooleanSupplier condition) { - this.condition = condition; - } - - /** - * Update state according to the condition result. - * Invoke this method at the end of feature logic. - */ - public void updateStateForNextTick() { - hasKeyPressed = isPressing(); - } - - public boolean isPressing() { - return condition.getAsBoolean(); - } - - public boolean isNotPressing() { - return !isPressing(); - } - - public boolean hasPressedPreviousTick() { - return hasKeyPressed; - } - - public boolean isReleased() { - return !isPressing() && hasPressedPreviousTick(); - } - - public boolean isPressed() { - return isPressing() && !hasPressedPreviousTick(); - } - } -} diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Interval.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Interval.java new file mode 100644 index 00000000..daffda40 --- /dev/null +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Interval.java @@ -0,0 +1,74 @@ +package com.github.khanshoaib3.minecraft_access.utils.condition; + +/** + * An auto-refresh countdown timer for controlling interval execution of features. + */ +public class Interval { + private long lastRunTime; + private final long delay; + private boolean isRunning; + private final boolean disabled; + + private Interval(long lastRunTime, long delayInNanoTime) { + this.lastRunTime = lastRunTime; + this.delay = delayInNanoTime; + this.disabled = delayInNanoTime == 0; + this.isRunning = false; + } + + /** + * Build or update instance according to delay config. + * + * @param delay config value + * @param previous the interval class variable + */ + public static Interval inMilliseconds(long delay, Interval... previous) { + if (previous == null || previous.length == 0 || previous[0] == null) { + // 1 milliseconds = 1*10^6 nanoseconds + return new Interval(System.nanoTime(), delay * 1000_000); + } else { + Interval interval = previous[0]; + boolean configChanged = delay * 1000_000 != interval.delay; + return configChanged ? Interval.inMilliseconds(delay) : interval; + } + } + + public void reset() { + lastRunTime = System.nanoTime(); + } + + /** + * Check if the delay has cooled down. (Will auto-reset the timer if true) + */ + public boolean isReady() { + if (disabled) return false; + + if (System.nanoTime() - lastRunTime > delay) { + reset(); + return true; + } else { + return false; + } + } + + /** + * Check if the delay has cooled down. (This will not auto-reset the timer, that will have to be done by calling the start() method) + * + * @return true if the delay timer has stopped or cooled down. + */ + public boolean hasEnded() { + if (!this.isRunning) return true; + if (!this.isReady()) return false; + + this.isRunning = false; + return true; + } + + /** + * Starts or resets the delay timer. This is recommended to be used when there is a key input. + */ + public void start() { + this.isRunning = true; + reset(); + } +} diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java new file mode 100644 index 00000000..d1a3d2d9 --- /dev/null +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java @@ -0,0 +1,53 @@ +package com.github.khanshoaib3.minecraft_access.utils.condition; + +import java.util.function.BooleanSupplier; + +/** + * A state machine that helps with complex keystroke condition checking, + * such as "when-the-key-is-released", "only-trigger-once-before-release-key", "double-strike-within-interval". + */ +public class Keystroke { + /** + * Save the state of keystroke at the previous tick. + */ + private boolean hasKeyPressed = false; + /** + * Expression that checking if the key (combination) is pressed now. + */ + private final BooleanSupplier condition; + + /** + * @param condition Expression that checking if the key (combination) is pressed now. + */ + public Keystroke(BooleanSupplier condition) { + this.condition = condition; + } + + /** + * Update state according to the condition result. + * Invoke this method at the end of feature logic. + */ + public void updateStateForNextTick() { + hasKeyPressed = isPressing(); + } + + public boolean isPressing() { + return condition.getAsBoolean(); + } + + public boolean isNotPressing() { + return !isPressing(); + } + + public boolean hasPressedPreviousTick() { + return hasKeyPressed; + } + + public boolean isReleased() { + return !isPressing() && hasPressedPreviousTick(); + } + + public boolean isPressed() { + return isPressing() && !hasPressedPreviousTick(); + } +} diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/KeystrokeTiming.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/KeystrokeTiming.java new file mode 100644 index 00000000..b8715dbb --- /dev/null +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/KeystrokeTiming.java @@ -0,0 +1,31 @@ +package com.github.khanshoaib3.minecraft_access.utils.condition; + +import java.util.function.BooleanSupplier; + +/** + * For checking keystroke conditions that related to time, + * like "interval/rate-limitation-on-feature-execution", "double/triple-click" + */ +public abstract class KeystrokeTiming extends Keystroke { + protected Interval interval; + + /** + * @param condition Expression that checking if the key (combination) is pressed now. + * @param interval The interval setting, the meaning is to be determined. + */ + public KeystrokeTiming(BooleanSupplier condition, Interval interval) { + super(condition); + this.interval = interval; + } + + /** + * Getter, for operating inner Interval instance. + */ + public Interval interval() { + return this.interval; + } + + public void setInterval(Interval interval) { + this.interval = interval; + } +} diff --git a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java index 0ab618b3..9882643e 100644 --- a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java +++ b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java @@ -1,5 +1,6 @@ package com.github.khanshoaib3.minecraft_access.utils; +import com.github.khanshoaib3.minecraft_access.utils.condition.Keystroke; import org.jetbrains.annotations.TestOnly; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -12,10 +13,10 @@ import static org.assertj.core.api.Assertions.assertThat; -class TimeUtilsTest { +class KeystrokeTest { @Nested - class KeystrokeCheckerTest { + class OriginalKeystrokeTest { /** * Combining a changeable boolean variable with supplier. */ @@ -37,7 +38,7 @@ public MockKeystroke(boolean initPressed) { @Test void testMockKeystrokeWorks() { var m = new MockKeystroke(true); - var k = new TimeUtils.KeystrokeChecker(m.supplier); + var k = new Keystroke(m.supplier); assertThat(k.isPressing()).isEqualTo(true); m.revertKeystrokeResult(); assertThat(k.isPressing()).isEqualTo(false); @@ -46,7 +47,7 @@ void testMockKeystrokeWorks() { @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testIsKeyPressing(MockKeystroke keystroke, boolean expected) { - TimeUtils.KeystrokeChecker checker = new TimeUtils.KeystrokeChecker(keystroke.supplier); + Keystroke checker = new Keystroke(keystroke.supplier); assertThat(checker.isPressing()).isEqualTo(expected); keystroke.revertKeystrokeResult(); assertThat(checker.isPressing()) @@ -64,7 +65,7 @@ static Stream provideKeystrokeCheckerTestCases() { @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testIsKeyNotPressing(MockKeystroke keystroke, boolean expected) { - TimeUtils.KeystrokeChecker checker = new TimeUtils.KeystrokeChecker(keystroke.supplier); + Keystroke checker = new Keystroke(keystroke.supplier); assertThat(checker.isNotPressing()).isEqualTo(!expected); keystroke.revertKeystrokeResult(); assertThat(checker.isNotPressing()) @@ -75,7 +76,7 @@ void testIsKeyNotPressing(MockKeystroke keystroke, boolean expected) { @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testHasKeyPressed(MockKeystroke keyStroke, boolean expected) { - TimeUtils.KeystrokeChecker checker = new TimeUtils.KeystrokeChecker(keyStroke.supplier); + Keystroke checker = new Keystroke(keyStroke.supplier); // tick 0 checker.updateStateForNextTick(); @@ -94,7 +95,7 @@ void testHasKeyPressed(MockKeystroke keyStroke, boolean expected) { @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testIsKeyReleased(MockKeystroke keyStroke, boolean expected) { - TimeUtils.KeystrokeChecker checker = new TimeUtils.KeystrokeChecker(keyStroke.supplier); + Keystroke checker = new Keystroke(keyStroke.supplier); // tick 0 checker.updateStateForNextTick(); @@ -109,7 +110,7 @@ void testIsKeyReleased(MockKeystroke keyStroke, boolean expected) { @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testIsKeyPressed(MockKeystroke keyStroke, boolean expected) { - TimeUtils.KeystrokeChecker checker = new TimeUtils.KeystrokeChecker(keyStroke.supplier); + Keystroke checker = new Keystroke(keyStroke.supplier); // tick 0 checker.updateStateForNextTick(); From 3068a9641867d7f4192b64b2be40812457777b15 Mon Sep 17 00:00:00 2001 From: boholder Date: Fri, 18 Aug 2023 11:24:44 +0800 Subject: [PATCH 04/10] Make mouse sim scroll up/down use Keystroke. --- .../features/MouseKeySimulation.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java index f5fa95a3..b4f67903 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java @@ -8,6 +8,7 @@ import net.minecraft.client.MinecraftClient; import org.apache.commons.lang3.tuple.Triple; +import java.util.Arrays; import java.util.Set; /** @@ -22,7 +23,7 @@ public class MouseKeySimulation { private static final MouseKeySimulation instance; private boolean enabled; - private static final Keystroke[] mouseKeystrokes = new Keystroke[3]; + private static final Keystroke[] mouseKeystrokes = new Keystroke[5]; private Interval scrollUpDelay; private Interval scrollDownDelay; @@ -38,6 +39,8 @@ public class MouseKeySimulation { mouseKeystrokes[0] = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationLeftMouseKey)); mouseKeystrokes[1] = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationMiddleMouseKey)); mouseKeystrokes[2] = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationRightMouseKey)); + mouseKeystrokes[3] = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationScrollUpKey)); + mouseKeystrokes[4] = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.mouseSimulationScrollDownKey)); } public static synchronized MouseKeySimulation getInstance() { @@ -73,10 +76,10 @@ private void execute() { KeyBindingsHandler kbh = KeyBindingsHandler.getInstance(); Set.of( - Triple.of(KeyUtils.isAnyPressed(kbh.mouseSimulationScrollUpKey), scrollUpDelay, MouseUtils::scrollUp), - Triple.of(KeyUtils.isAnyPressed(kbh.mouseSimulationScrollDownKey), scrollDownDelay, MouseUtils::scrollDown) + Triple.of(mouseKeystrokes[3], scrollUpDelay, MouseUtils::scrollUp), + Triple.of(mouseKeystrokes[4], scrollDownDelay, MouseUtils::scrollDown) ).forEach(t -> { - if (t.getLeft() && t.getMiddle().isReady()) { + if (t.getLeft().isPressing() && t.getMiddle().isReady()) { t.getRight().run(); } }); @@ -92,8 +95,9 @@ private void execute() { dto.keyUp.run(); } - dto.keystroke.updateStateForNextTick(); }); + + Arrays.stream(mouseKeystrokes).forEach(Keystroke::updateStateForNextTick); } private record MouseKeyDTO(Keystroke keystroke, Runnable keyDown, Runnable keyUp) { From 160f9c1d40c7d06d3d1905329c7786effce1cfed Mon Sep 17 00:00:00 2001 From: boholder Date: Fri, 18 Aug 2023 11:26:43 +0800 Subject: [PATCH 05/10] Add DoubleClick class for the camera straight up/down. --- .../utils/condition/DoubleClick.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/DoubleClick.java diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/DoubleClick.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/DoubleClick.java new file mode 100644 index 00000000..58daed7b --- /dev/null +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/DoubleClick.java @@ -0,0 +1,18 @@ +package com.github.khanshoaib3.minecraft_access.utils.condition; + +import java.util.function.BooleanSupplier; + +public class DoubleClick extends KeystrokeTiming{ + /** + * @param condition Expression that checking if the key (combination) is pressed now. + * @param interval The maximum interval between first and second keystroke. + */ + public DoubleClick(BooleanSupplier condition, Interval interval) { + super(condition, interval); + } + + @Override + public void updateStateForNextTick() { + super.updateStateForNextTick(); + } +} From fda95c97f550590d2647728844eb64ad5116a9a5 Mon Sep 17 00:00:00 2001 From: boholder Date: Fri, 18 Aug 2023 15:39:55 +0800 Subject: [PATCH 06/10] Add trigger timing for amplifying Keystroke class's ability. --- .../features/MouseKeySimulation.java | 2 - .../utils/condition/Keystroke.java | 46 +++++++++++++++++++ ...{TimeUtilsTest.java => KeystrokeTest.java} | 27 ++++++++--- 3 files changed, 66 insertions(+), 9 deletions(-) rename common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/{TimeUtilsTest.java => KeystrokeTest.java} (83%) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java index b4f67903..74477d3e 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/MouseKeySimulation.java @@ -73,8 +73,6 @@ private void loadConfigurations() { } private void execute() { - KeyBindingsHandler kbh = KeyBindingsHandler.getInstance(); - Set.of( Triple.of(mouseKeystrokes[3], scrollUpDelay, MouseUtils::scrollUp), Triple.of(mouseKeystrokes[4], scrollDownDelay, MouseUtils::scrollDown) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java index d1a3d2d9..194f29f5 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java @@ -1,6 +1,8 @@ package com.github.khanshoaib3.minecraft_access.utils.condition; +import java.util.Optional; import java.util.function.BooleanSupplier; +import java.util.function.Function; /** * A state machine that helps with complex keystroke condition checking, @@ -11,16 +13,38 @@ public class Keystroke { * Save the state of keystroke at the previous tick. */ private boolean hasKeyPressed = false; + /** * Expression that checking if the key (combination) is pressed now. */ private final BooleanSupplier condition; /** + * For checking feature triggering condition, + * like "only-trigger-once-before-release". + */ + private final TriggeredAt timing; + + /** + * Use this class as a condition checker. + * Suitable for complex cases that there are other conditions that determine the logic is triggered or not. + * * @param condition Expression that checking if the key (combination) is pressed now. */ public Keystroke(BooleanSupplier condition) { + this(condition, TriggeredAt.PRESSING); + } + + /** + * Let this class handles feature triggering controls. + * Suitable for simple cases that the keystroke is the only condition that triggers the logic. + * + * @param condition Expression that checking if the key (combination) is pressed now. + * @param timing When the corresponding logic is triggered. + */ + public Keystroke(BooleanSupplier condition, TriggeredAt timing) { this.condition = condition; + this.timing = Optional.ofNullable(timing).orElse(TriggeredAt.PRESSING); } /** @@ -50,4 +74,26 @@ public boolean isReleased() { public boolean isPressed() { return isPressing() && !hasPressedPreviousTick(); } + + public boolean isTriggered() { + return this.timing.check(this); + } + + public enum TriggeredAt { + PRESSING(Keystroke::isPressing), + NOT_PRESSING(Keystroke::isNotPressing), + PRESSED(Keystroke::isPressed), + RELEASED(Keystroke::isReleased), + ; + + private final Function triggerCondition; + + TriggeredAt(Function condition) { + this.triggerCondition = condition; + } + + public boolean check(Keystroke keystroke) { + return this.triggerCondition.apply(keystroke); + } + } } diff --git a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java similarity index 83% rename from common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java rename to common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java index 9882643e..b13d993a 100644 --- a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/TimeUtilsTest.java +++ b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java @@ -47,11 +47,15 @@ void testMockKeystrokeWorks() { @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testIsKeyPressing(MockKeystroke keystroke, boolean expected) { - Keystroke checker = new Keystroke(keystroke.supplier); - assertThat(checker.isPressing()).isEqualTo(expected); + Keystroke checker = new Keystroke(keystroke.supplier, Keystroke.TriggeredAt.PRESSING); + assertThat(checker.isPressing()) + .isEqualTo(checker.isTriggered()) + .isEqualTo(expected); + keystroke.revertKeystrokeResult(); assertThat(checker.isPressing()) .as("keystroke condition should be reverted") + .isEqualTo(checker.isTriggered()) .isEqualTo(!expected); } @@ -65,37 +69,44 @@ static Stream provideKeystrokeCheckerTestCases() { @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testIsKeyNotPressing(MockKeystroke keystroke, boolean expected) { - Keystroke checker = new Keystroke(keystroke.supplier); - assertThat(checker.isNotPressing()).isEqualTo(!expected); + Keystroke checker = new Keystroke(keystroke.supplier, Keystroke.TriggeredAt.NOT_PRESSING); + assertThat(checker.isNotPressing()) + .isEqualTo(checker.isTriggered()) + .isEqualTo(!expected); + keystroke.revertKeystrokeResult(); assertThat(checker.isNotPressing()) .as("keystroke condition should be reverted") + .isEqualTo(checker.isTriggered()) .isEqualTo(expected); } @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testHasKeyPressed(MockKeystroke keyStroke, boolean expected) { - Keystroke checker = new Keystroke(keyStroke.supplier); + Keystroke checker = new Keystroke(keyStroke.supplier, Keystroke.TriggeredAt.PRESSED); // tick 0 checker.updateStateForNextTick(); // tick 1 - assertThat(checker.hasPressedPreviousTick()).isEqualTo(expected); + assertThat(checker.hasPressedPreviousTick()) + .isEqualTo(checker.isTriggered()) + .isEqualTo(expected); keyStroke.revertKeystrokeResult(); checker.updateStateForNextTick(); // tick 2 assertThat(checker.hasPressedPreviousTick()) .as("keystroke condition should be reverted") + .isEqualTo(checker.isTriggered()) .isEqualTo(!expected); } @ParameterizedTest @MethodSource("provideKeystrokeCheckerTestCases") void testIsKeyReleased(MockKeystroke keyStroke, boolean expected) { - Keystroke checker = new Keystroke(keyStroke.supplier); + Keystroke checker = new Keystroke(keyStroke.supplier, Keystroke.TriggeredAt.RELEASED); // tick 0 checker.updateStateForNextTick(); @@ -104,6 +115,7 @@ void testIsKeyReleased(MockKeystroke keyStroke, boolean expected) { keyStroke.revertKeystrokeResult(); assertThat(checker.isReleased()) .as("pressed key should be released now, vice versa") + .isEqualTo(checker.isTriggered()) .isEqualTo(expected); } @@ -119,6 +131,7 @@ void testIsKeyPressed(MockKeystroke keyStroke, boolean expected) { keyStroke.revertKeystrokeResult(); assertThat(checker.isPressed()) .as("released key should be pressed now, vice versa") + .isEqualTo(checker.isTriggered()) .isEqualTo(!expected); } } From 65bf2aa5ecdda1b64b1e8f0fc2e73a20c642f1b7 Mon Sep 17 00:00:00 2001 From: boholder Date: Fri, 18 Aug 2023 16:50:56 +0800 Subject: [PATCH 07/10] Add trigger count function in Keystroke. Add hasBeenTriggered method. Add pressed trigger count test. Add released trigger count test. Add not pressing trigger count test. Add pressing trigger count test. Change unit test structure for further cases. Add tests for Keystroke.TriggeredAt enums. Arrange logic order in KeystrokeTest. --- .../utils/condition/Keystroke.java | 48 ++- .../minecraft_access/utils/KeystrokeTest.java | 325 +++++++++++++----- 2 files changed, 276 insertions(+), 97 deletions(-) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java index 194f29f5..9f42b9c0 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java @@ -5,8 +5,7 @@ import java.util.function.Function; /** - * A state machine that helps with complex keystroke condition checking, - * such as "when-the-key-is-released", "only-trigger-once-before-release-key", "double-strike-within-interval". + * A state machine that helps with complex keystroke condition checking. */ public class Keystroke { /** @@ -25,6 +24,11 @@ public class Keystroke { */ private final TriggeredAt timing; + /** + * Times that logic has been triggered before reset (when opposite state appears). + */ + private int triggeredCount; + /** * Use this class as a condition checker. * Suitable for complex cases that there are other conditions that determine the logic is triggered or not. @@ -45,6 +49,7 @@ public Keystroke(BooleanSupplier condition) { public Keystroke(BooleanSupplier condition, TriggeredAt timing) { this.condition = condition; this.timing = Optional.ofNullable(timing).orElse(TriggeredAt.PRESSING); + this.triggeredCount = 0; } /** @@ -53,6 +58,7 @@ public Keystroke(BooleanSupplier condition, TriggeredAt timing) { */ public void updateStateForNextTick() { hasKeyPressed = isPressing(); + if (this.timing.aboutToHappen(this)) this.triggeredCount = 0; } public boolean isPressing() { @@ -76,24 +82,44 @@ public boolean isPressed() { } public boolean isTriggered() { - return this.timing.check(this); + boolean happen = this.timing.happen(this); + boolean haveNotTriggered = this.triggeredCount == 0; + + if (happen && haveNotTriggered) { + this.triggeredCount += 1; + return true; + } + return false; + } + + public boolean hasBeenTriggered() { + return this.triggeredCount > 0; } public enum TriggeredAt { - PRESSING(Keystroke::isPressing), - NOT_PRESSING(Keystroke::isNotPressing), - PRESSED(Keystroke::isPressed), - RELEASED(Keystroke::isReleased), + PRESSING(Keystroke::isPressing, Keystroke::isNotPressing), + NOT_PRESSING(Keystroke::isNotPressing, Keystroke::isPressing), + PRESSED(Keystroke::isPressed, Keystroke::isNotPressing), + RELEASED(Keystroke::isReleased, Keystroke::isPressing), ; private final Function triggerCondition; - - TriggeredAt(Function condition) { - this.triggerCondition = condition; + /** + * the state ahead of triggering state + */ + private final Function preCondition; + + TriggeredAt(Function triggerCondition, Function preCondition) { + this.triggerCondition = triggerCondition; + this.preCondition = preCondition; } - public boolean check(Keystroke keystroke) { + public boolean happen(Keystroke keystroke) { return this.triggerCondition.apply(keystroke); } + + public boolean aboutToHappen(Keystroke keystroke) { + return this.preCondition.apply(keystroke); + } } } diff --git a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java index b13d993a..10389ba4 100644 --- a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java +++ b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java @@ -1,138 +1,291 @@ package com.github.khanshoaib3.minecraft_access.utils; import com.github.khanshoaib3.minecraft_access.utils.condition.Keystroke; -import org.jetbrains.annotations.TestOnly; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; class KeystrokeTest { - @Nested - class OriginalKeystrokeTest { - /** - * Combining a changeable boolean variable with supplier. - */ - @TestOnly - static class MockKeystroke { - Boolean pressed; - BooleanSupplier supplier; - - public void revertKeystrokeResult() { - this.pressed = !this.pressed; - } - - public MockKeystroke(boolean initPressed) { - this.pressed = initPressed; - this.supplier = () -> this.pressed; - } - } - - @Test - void testMockKeystrokeWorks() { - var m = new MockKeystroke(true); - var k = new Keystroke(m.supplier); - assertThat(k.isPressing()).isEqualTo(true); - m.revertKeystrokeResult(); - assertThat(k.isPressing()).isEqualTo(false); - } - - @ParameterizedTest - @MethodSource("provideKeystrokeCheckerTestCases") - void testIsKeyPressing(MockKeystroke keystroke, boolean expected) { - Keystroke checker = new Keystroke(keystroke.supplier, Keystroke.TriggeredAt.PRESSING); - assertThat(checker.isPressing()) - .isEqualTo(checker.isTriggered()) - .isEqualTo(expected); - - keystroke.revertKeystrokeResult(); - assertThat(checker.isPressing()) - .as("keystroke condition should be reverted") - .isEqualTo(checker.isTriggered()) - .isEqualTo(!expected); - } - - static Stream provideKeystrokeCheckerTestCases() { + static class InitKeystrokeConditionSameAsExpected implements ArgumentsProvider { + @Override + public Stream provideArguments(ExtensionContext extensionContext) throws Exception { return Stream.of( Arguments.of(new MockKeystroke(true), true), Arguments.of(new MockKeystroke(false), false) ); } + } + @Nested + class KeystrokeConditionCheckingTest { @ParameterizedTest - @MethodSource("provideKeystrokeCheckerTestCases") - void testIsKeyNotPressing(MockKeystroke keystroke, boolean expected) { - Keystroke checker = new Keystroke(keystroke.supplier, Keystroke.TriggeredAt.NOT_PRESSING); - assertThat(checker.isNotPressing()) - .isEqualTo(checker.isTriggered()) - .isEqualTo(!expected); + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsPressing(MockKeystroke keystroke, boolean expected) { + checkPressing(keystroke, expected, Keystroke::isPressing); + } - keystroke.revertKeystrokeResult(); - assertThat(checker.isNotPressing()) - .as("keystroke condition should be reverted") - .isEqualTo(checker.isTriggered()) - .isEqualTo(expected); + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsNotPressing(MockKeystroke keystroke, boolean expected) { + checkNotPressing(keystroke, expected, Keystroke::isNotPressing); } @ParameterizedTest - @MethodSource("provideKeystrokeCheckerTestCases") - void testHasKeyPressed(MockKeystroke keyStroke, boolean expected) { + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testHasPressedPreviousTick(MockKeystroke keyStroke, boolean expected) { Keystroke checker = new Keystroke(keyStroke.supplier, Keystroke.TriggeredAt.PRESSED); // tick 0 checker.updateStateForNextTick(); // tick 1 - assertThat(checker.hasPressedPreviousTick()) - .isEqualTo(checker.isTriggered()) - .isEqualTo(expected); + assertThat(checker.hasPressedPreviousTick()).isEqualTo(expected); keyStroke.revertKeystrokeResult(); checker.updateStateForNextTick(); // tick 2 assertThat(checker.hasPressedPreviousTick()) .as("keystroke condition should be reverted") - .isEqualTo(checker.isTriggered()) .isEqualTo(!expected); } @ParameterizedTest - @MethodSource("provideKeystrokeCheckerTestCases") - void testIsKeyReleased(MockKeystroke keyStroke, boolean expected) { - Keystroke checker = new Keystroke(keyStroke.supplier, Keystroke.TriggeredAt.RELEASED); + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsReleased(MockKeystroke keyStroke, boolean expected) { + checkReleased(keyStroke, expected, Keystroke::isReleased); + } - // tick 0 - checker.updateStateForNextTick(); + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsPressed(MockKeystroke keyStroke, boolean expected) { + checkPressed(keyStroke, expected, Keystroke::isPressed); + } + } - // tick 1 - keyStroke.revertKeystrokeResult(); - assertThat(checker.isReleased()) - .as("pressed key should be released now, vice versa") - .isEqualTo(checker.isTriggered()) - .isEqualTo(expected); + @Nested + class KeystrokeTriggerTest { + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsPressing(MockKeystroke keystroke, boolean expected) { + checkPressing(keystroke, expected, Keystroke::isTriggered); } @ParameterizedTest - @MethodSource("provideKeystrokeCheckerTestCases") - void testIsKeyPressed(MockKeystroke keyStroke, boolean expected) { - Keystroke checker = new Keystroke(keyStroke.supplier); + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsNotPressing(MockKeystroke keystroke, boolean expected) { + checkNotPressing(keystroke, expected, Keystroke::isTriggered); + } - // tick 0 - checker.updateStateForNextTick(); + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsReleased(MockKeystroke keyStroke, boolean expected) { + checkReleased(keyStroke, expected, Keystroke::isTriggered); + } - // tick 1 - keyStroke.revertKeystrokeResult(); - assertThat(checker.isPressed()) - .as("released key should be pressed now, vice versa") - .isEqualTo(checker.isTriggered()) - .isEqualTo(!expected); + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsPressed(MockKeystroke keyStroke, boolean expected) { + checkPressed(keyStroke, expected, Keystroke::isTriggered); + } + } + + @Nested + class KeystrokeTriggerCountTest { + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsPressing(MockKeystroke keystroke, boolean expected) { + checkPressing(keystroke, + k -> { + assertThat(k.isTriggered()).isEqualTo(expected); + if (expected) { + assertThat(k.hasBeenTriggered()).isTrue(); + assertThat(k.isTriggered()) + .as("can be triggered only once") + .isFalse(); + } + }, + k -> assertThat(k.isTriggered()).isEqualTo(!expected)); + } + + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsNotPressing(MockKeystroke keystroke, boolean expected) { + checkNotPressing(keystroke, + k -> { + assertThat(k.isTriggered()).isEqualTo(expected); + if (expected) { + assertThat(k.hasBeenTriggered()).isTrue(); + assertThat(k.isTriggered()) + .as("can be triggered only once") + .isFalse(); + } + }, + k -> assertThat(k.isTriggered()).isEqualTo(!expected)); } + + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsReleased(MockKeystroke keystroke, boolean expected) { + checkReleased(keystroke, + k -> { + assertThat(k.isTriggered()).isEqualTo(expected); + if (expected) { + assertThat(k.hasBeenTriggered()).isTrue(); + assertThat(k.isTriggered()) + .as("can be triggered only once") + .isFalse(); + } + }, + k -> assertThat(k.isTriggered()).isEqualTo(!expected)); + } + + @ParameterizedTest + @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) + void testIsPressed(MockKeystroke keystroke, boolean expected) { + checkPressed(keystroke, + k -> { + boolean exp = !expected; + assertThat(k.isTriggered()) + .as("originally released key should be pressed now (expected should be true), vice versa") + .isEqualTo(exp); + if (exp) { + assertThat(k.hasBeenTriggered()).isTrue(); + assertThat(k.isTriggered()) + .as("can be triggered only once") + .isFalse(); + } + }, + k -> assertThat(k.isTriggered()).isEqualTo(expected)); + } + } + + /** + * Combining a changeable boolean variable with supplier. + */ + static class MockKeystroke { + Boolean pressed; + BooleanSupplier supplier; + + public void revertKeystrokeResult() { + this.pressed = !this.pressed; + } + + public MockKeystroke(boolean initPressed) { + this.pressed = initPressed; + this.supplier = () -> this.pressed; + } + } + + @Test + void testMockKeystrokeWorks() { + var m = new MockKeystroke(true); + var k = new Keystroke(m.supplier); + assertThat(k.isPressing()).isTrue(); + m.revertKeystrokeResult(); + assertThat(k.isPressing()).isFalse(); + } + + private static void checkPressing(MockKeystroke keystroke, boolean expected, Function actual) { + checkPressing(keystroke, + k -> assertThat(actual.apply(k)).isEqualTo(expected), + k -> assertThat(actual.apply(k)) + .as("keystroke condition should be reverted") + .isEqualTo(!expected)); + } + + private static void checkPressing(MockKeystroke keystroke, Consumer trueAssertion, Consumer falseAssertion) { + Keystroke k = new Keystroke(keystroke.supplier, Keystroke.TriggeredAt.PRESSING); + + trueAssertion.accept(k); + k.updateStateForNextTick(); + + keystroke.revertKeystrokeResult(); + falseAssertion.accept(k); + k.updateStateForNextTick(); + + keystroke.revertKeystrokeResult(); + trueAssertion.accept(k); + } + + private static void checkNotPressing(MockKeystroke keystroke, boolean expected, Function actual) { + checkNotPressing(keystroke, + k -> assertThat(actual.apply(k)) + .as("keystroke condition should be reverted") + .isEqualTo(expected), + k -> assertThat(actual.apply(k)).isEqualTo(!expected)); + } + + private static void checkNotPressing(MockKeystroke keystroke, Consumer trueAssertion, Consumer falseAssertion) { + Keystroke k = new Keystroke(keystroke.supplier, Keystroke.TriggeredAt.NOT_PRESSING); + + falseAssertion.accept(k); + k.updateStateForNextTick(); + + keystroke.revertKeystrokeResult(); + trueAssertion.accept(k); + k.updateStateForNextTick(); + + keystroke.revertKeystrokeResult(); + falseAssertion.accept(k); + } + + private static void checkReleased(MockKeystroke keystroke, boolean expected, Function actual) { + checkReleased(keystroke, + k -> assertThat(actual.apply(k)) + .as("originally pressed key should be released now, vice versa") + .isEqualTo(expected), + k -> assertThat(actual.apply(k)).isEqualTo(!expected)); + } + + private static void checkReleased(MockKeystroke keystroke, Consumer trueAssertion, Consumer falseAssertion) { + Keystroke k = new Keystroke(keystroke.supplier, Keystroke.TriggeredAt.RELEASED); + + // tick 0 + k.updateStateForNextTick(); + + // tick 1 + keystroke.revertKeystrokeResult(); + trueAssertion.accept(k); + k.updateStateForNextTick(); + + // tick 2 + keystroke.revertKeystrokeResult(); + falseAssertion.accept(k); + } + + private static void checkPressed(MockKeystroke keystroke, boolean expected, Function actual) { + checkPressed(keystroke, + k -> assertThat(actual.apply(k)) + .as("originally released key should be pressed now, vice versa") + .isEqualTo(!expected), + k -> assertThat(actual.apply(k)).isEqualTo(expected)); + } + + private static void checkPressed(MockKeystroke keystroke, Consumer trueAssertion, Consumer falseAssertion) { + Keystroke k = new Keystroke(keystroke.supplier); + + // tick 0 + k.updateStateForNextTick(); + + // tick 1 + keystroke.revertKeystrokeResult(); + trueAssertion.accept(k); + k.updateStateForNextTick(); + + // tick 2 + keystroke.revertKeystrokeResult(); + falseAssertion.accept(k); } } From 7efc88f7ad2c14863ebcbfccb82bf490e1e550b8 Mon Sep 17 00:00:00 2001 From: boholder Date: Sat, 19 Aug 2023 14:11:43 +0800 Subject: [PATCH 08/10] Refactor NarratorMenu for readability. Make hasPressedPreviousTick private. Rename method. Make isTriggered a pure-function. Simplify keys name in NarratorMenu. --- .../features/narrator_menu/NarratorMenu.java | 140 ++++++++++-------- .../utils/condition/Keystroke.java | 16 +- .../minecraft_access/utils/KeystrokeTest.java | 59 +++----- 3 files changed, 105 insertions(+), 110 deletions(-) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java index 592e3021..cdad8346 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/features/narrator_menu/NarratorMenu.java @@ -23,6 +23,7 @@ import org.lwjgl.glfw.GLFW; import java.util.Arrays; +import java.util.Objects; import java.util.stream.Stream; /** @@ -30,10 +31,20 @@ */ public class NarratorMenu { private static MinecraftClient minecraftClient; - private static final Keystroke narratorMenuKeyCondition; - private static final Keystroke narratorMenuHotKeyCondition; - private static boolean isHotKeySwitchedPreviousTick = false; - private static boolean isNarratorMenuJustClosed = false; + private static final Keystroke menuKey; + private static final Keystroke hotKey; + /** + * Prevent the f4 menu open in this situation: + * press f4 + hot key to trigger function switch, first release the hot key, then release the f4 key. + * The user intend to switch the function, not open the menu. + */ + private static boolean hasFunctionSwitchedBeforeF4Released = false; + /** + * Prevent the f4 menu open again after f4 menu is just closed by pressing f4. + * The menu is closed by pressing the f4, and is opened by releasing the f4, + * so if you slowly press down the f4 while menu is opening, the menu will be opened again when you release the f4. + */ + private static boolean isMenuJustClosed = false; private int hotKeyFunctionIndex = 0; private static final boolean[] LAST_RUN_HAS_DONE_FLAG = new boolean[10]; @@ -42,8 +53,8 @@ public class NarratorMenu { // config keystroke conditions KeyBindingsHandler kbh = KeyBindingsHandler.getInstance(); - narratorMenuKeyCondition = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.narratorMenuKey)); - narratorMenuHotKeyCondition = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.narratorMenuHotKey)); + menuKey = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.narratorMenuKey)); + hotKey = new Keystroke(() -> KeyUtils.isAnyPressed(kbh.narratorMenuHotKey), Keystroke.TriggeredAt.PRESSED); } /** @@ -91,75 +102,86 @@ public void update() { if (minecraftClient == null) return; if (minecraftClient.player == null) return; - // With Narrator Menu opened, listen to number keys pressing for executing corresponding functions if (minecraftClient.currentScreen instanceof NarratorMenuGUI) { - // Close the menu if the F4 key is pressed while the menu is opening - if (narratorMenuKeyCondition.isPressing()) { - isNarratorMenuJustClosed = true; - minecraftClient.currentScreen.close(); - return; - } - - // for the little performance improvement, will not use KeyUtils here. - long handle = minecraftClient.getWindow().getHandle(); - Stream.of(MENU_FUNCTIONS) - .filter(f -> InputUtil.isKeyPressed(handle, f.numberKeyCode()) - || InputUtil.isKeyPressed(handle, f.keyPadKeyCode())) - .findFirst() - .ifPresent(f -> f.func().run()); + if (handleInMenuActions()) return; } + // other menus is opened if (minecraftClient.currentScreen != null) return; - // F3 + F4 triggers game mode changing function in vanilla game, will not open the menu under this situation. + // F3 + F4 triggers game mode changing function in vanilla game, + // will not open the menu under this situation. boolean isF3KeyNotPressed = !KeyUtils.isF3Pressed(); - // The F4 is pressed before and released at current tick - // To make the narrator menu open AFTER release the F4 key - boolean openTheMenuScreen = narratorMenuKeyCondition.isReleased() && !isNarratorMenuJustClosed; - - // Opposite to menu open, executes immediately, - // but will not execute twice until release and press the key again - boolean triggerHotKey = narratorMenuHotKeyCondition.isPressed(); - - if (narratorMenuKeyCondition.isPressing() && triggerHotKey) { - // for prevent the menu open this time after release the F4 key - // the user intend to switch the function, not open the menu - isHotKeySwitchedPreviousTick = true; - // switch to the next narrator menu function - hotKeyFunctionIndex = (hotKeyFunctionIndex + 1) % MENU_FUNCTIONS.length; - MenuFunction f = MENU_FUNCTIONS[hotKeyFunctionIndex]; - String functionName = I18n.translate(f.configKey()); - MainClass.speakWithNarrator(I18n.translate("minecraft_access.keys.other.narrator_menu_hot_key_switch", functionName), true); - - } else if (triggerHotKey) { - // in case pressing the key too frequently, only execute when last execution has done - if (LAST_RUN_HAS_DONE_FLAG[hotKeyFunctionIndex]) { - LAST_RUN_HAS_DONE_FLAG[hotKeyFunctionIndex] = false; - MENU_FUNCTIONS[hotKeyFunctionIndex].func.run(); - LAST_RUN_HAS_DONE_FLAG[hotKeyFunctionIndex] = true; - } - - } else if (openTheMenuScreen && isF3KeyNotPressed && !isHotKeySwitchedPreviousTick) { - Screen screen = new NarratorMenuGUI("f4_menu"); - minecraftClient.setScreen(screen); // post 1.18 -// minecraftClient.openScreen(screen); // pre 1.18 + if (menuKey.isPressing() && hotKey.canBeTriggered()) { + // Executes immediately, but will not execute twice until release and press the key again + hasFunctionSwitchedBeforeF4Released = true; + switchHotKeyFunction(); + } else if (hotKey.canBeTriggered()) { + executeHotKeyFunction(); + } else if (menuKey.isReleased() && !isMenuJustClosed && isF3KeyNotPressed && !hasFunctionSwitchedBeforeF4Released) { + // The F4 is pressed before and released at current tick + // To make the narrator menu open AFTER release the F4 key + openNarratorMenu(); } - // update the states for next tick - narratorMenuKeyCondition.updateStateForNextTick(); - narratorMenuHotKeyCondition.updateStateForNextTick(); - - // clean the states when F4 is released - if (openTheMenuScreen) isHotKeySwitchedPreviousTick = false; - if (narratorMenuKeyCondition.isNotPressing()) isNarratorMenuJustClosed = false; + if (menuKey.isReleased()) { + hasFunctionSwitchedBeforeF4Released = false; + isMenuJustClosed = false; + } + menuKey.updateStateForNextTick(); + hotKey.updateStateForNextTick(); } catch (Exception e) { MainClass.errorLog("An error occurred in NarratorMenu."); e.printStackTrace(); } } + /** + * @return return early if the menu is closed. + */ + private static boolean handleInMenuActions() { + // Close the menu if the F4 key is pressed while the menu is opening + if (menuKey.isPressing()) { + isMenuJustClosed = true; + Objects.requireNonNull(minecraftClient.currentScreen).close(); + return true; + } + + // With Narrator Menu opened, listen to number keys pressing for executing corresponding functions + // for the little performance improvement, will not use KeyUtils here. + long handle = minecraftClient.getWindow().getHandle(); + Stream.of(MENU_FUNCTIONS) + .filter(f -> InputUtil.isKeyPressed(handle, f.numberKeyCode()) + || InputUtil.isKeyPressed(handle, f.keyPadKeyCode())) + .findFirst() + .ifPresent(f -> f.func().run()); + return false; + } + + private void switchHotKeyFunction() { + hotKeyFunctionIndex = (hotKeyFunctionIndex + 1) % MENU_FUNCTIONS.length; + MenuFunction f = MENU_FUNCTIONS[hotKeyFunctionIndex]; + String functionName = I18n.translate(f.configKey()); + MainClass.speakWithNarrator(I18n.translate("minecraft_access.keys.other.narrator_menu_hot_key_switch", functionName), true); + } + + private void executeHotKeyFunction() { + // in case pressing the key too frequently, only execute when last execution has done + if (LAST_RUN_HAS_DONE_FLAG[hotKeyFunctionIndex]) { + LAST_RUN_HAS_DONE_FLAG[hotKeyFunctionIndex] = false; + MENU_FUNCTIONS[hotKeyFunctionIndex].func.run(); + LAST_RUN_HAS_DONE_FLAG[hotKeyFunctionIndex] = true; + } + } + + private void openNarratorMenu() { + Screen screen = new NarratorMenuGUI("f4_menu"); + minecraftClient.setScreen(screen); // post 1.18 +// minecraftClient.openScreen(screen); // pre 1.18 + } + public static void getBlockAndFluidTargetInformation() { try { if (minecraftClient.player == null) return; diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java index 9f42b9c0..c09dd5d8 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/utils/condition/Keystroke.java @@ -58,6 +58,7 @@ public Keystroke(BooleanSupplier condition, TriggeredAt timing) { */ public void updateStateForNextTick() { hasKeyPressed = isPressing(); + if (this.timing.happen(this)) this.triggeredCount += 1; if (this.timing.aboutToHappen(this)) this.triggeredCount = 0; } @@ -69,7 +70,7 @@ public boolean isNotPressing() { return !isPressing(); } - public boolean hasPressedPreviousTick() { + private boolean hasPressedPreviousTick() { return hasKeyPressed; } @@ -81,19 +82,10 @@ public boolean isPressed() { return isPressing() && !hasPressedPreviousTick(); } - public boolean isTriggered() { + public boolean canBeTriggered() { boolean happen = this.timing.happen(this); boolean haveNotTriggered = this.triggeredCount == 0; - - if (happen && haveNotTriggered) { - this.triggeredCount += 1; - return true; - } - return false; - } - - public boolean hasBeenTriggered() { - return this.triggeredCount > 0; + return happen && haveNotTriggered; } public enum TriggeredAt { diff --git a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java index 10389ba4..ff84a8ac 100644 --- a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java +++ b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java @@ -42,25 +42,6 @@ void testIsNotPressing(MockKeystroke keystroke, boolean expected) { checkNotPressing(keystroke, expected, Keystroke::isNotPressing); } - @ParameterizedTest - @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) - void testHasPressedPreviousTick(MockKeystroke keyStroke, boolean expected) { - Keystroke checker = new Keystroke(keyStroke.supplier, Keystroke.TriggeredAt.PRESSED); - - // tick 0 - checker.updateStateForNextTick(); - - // tick 1 - assertThat(checker.hasPressedPreviousTick()).isEqualTo(expected); - keyStroke.revertKeystrokeResult(); - checker.updateStateForNextTick(); - - // tick 2 - assertThat(checker.hasPressedPreviousTick()) - .as("keystroke condition should be reverted") - .isEqualTo(!expected); - } - @ParameterizedTest @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) void testIsReleased(MockKeystroke keyStroke, boolean expected) { @@ -79,25 +60,25 @@ class KeystrokeTriggerTest { @ParameterizedTest @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) void testIsPressing(MockKeystroke keystroke, boolean expected) { - checkPressing(keystroke, expected, Keystroke::isTriggered); + checkPressing(keystroke, expected, Keystroke::canBeTriggered); } @ParameterizedTest @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) void testIsNotPressing(MockKeystroke keystroke, boolean expected) { - checkNotPressing(keystroke, expected, Keystroke::isTriggered); + checkNotPressing(keystroke, expected, Keystroke::canBeTriggered); } @ParameterizedTest @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) void testIsReleased(MockKeystroke keyStroke, boolean expected) { - checkReleased(keyStroke, expected, Keystroke::isTriggered); + checkReleased(keyStroke, expected, Keystroke::canBeTriggered); } @ParameterizedTest @ArgumentsSource(InitKeystrokeConditionSameAsExpected.class) void testIsPressed(MockKeystroke keyStroke, boolean expected) { - checkPressed(keyStroke, expected, Keystroke::isTriggered); + checkPressed(keyStroke, expected, Keystroke::canBeTriggered); } } @@ -108,15 +89,15 @@ class KeystrokeTriggerCountTest { void testIsPressing(MockKeystroke keystroke, boolean expected) { checkPressing(keystroke, k -> { - assertThat(k.isTriggered()).isEqualTo(expected); + assertThat(k.canBeTriggered()).isEqualTo(expected); if (expected) { - assertThat(k.hasBeenTriggered()).isTrue(); - assertThat(k.isTriggered()) + k.updateStateForNextTick(); + assertThat(k.canBeTriggered()) .as("can be triggered only once") .isFalse(); } }, - k -> assertThat(k.isTriggered()).isEqualTo(!expected)); + k -> assertThat(k.canBeTriggered()).isEqualTo(!expected)); } @ParameterizedTest @@ -124,15 +105,15 @@ void testIsPressing(MockKeystroke keystroke, boolean expected) { void testIsNotPressing(MockKeystroke keystroke, boolean expected) { checkNotPressing(keystroke, k -> { - assertThat(k.isTriggered()).isEqualTo(expected); + assertThat(k.canBeTriggered()).isEqualTo(expected); if (expected) { - assertThat(k.hasBeenTriggered()).isTrue(); - assertThat(k.isTriggered()) + k.updateStateForNextTick(); + assertThat(k.canBeTriggered()) .as("can be triggered only once") .isFalse(); } }, - k -> assertThat(k.isTriggered()).isEqualTo(!expected)); + k -> assertThat(k.canBeTriggered()).isEqualTo(!expected)); } @ParameterizedTest @@ -140,15 +121,15 @@ void testIsNotPressing(MockKeystroke keystroke, boolean expected) { void testIsReleased(MockKeystroke keystroke, boolean expected) { checkReleased(keystroke, k -> { - assertThat(k.isTriggered()).isEqualTo(expected); + assertThat(k.canBeTriggered()).isEqualTo(expected); if (expected) { - assertThat(k.hasBeenTriggered()).isTrue(); - assertThat(k.isTriggered()) + k.updateStateForNextTick(); + assertThat(k.canBeTriggered()) .as("can be triggered only once") .isFalse(); } }, - k -> assertThat(k.isTriggered()).isEqualTo(!expected)); + k -> assertThat(k.canBeTriggered()).isEqualTo(!expected)); } @ParameterizedTest @@ -157,17 +138,17 @@ void testIsPressed(MockKeystroke keystroke, boolean expected) { checkPressed(keystroke, k -> { boolean exp = !expected; - assertThat(k.isTriggered()) + assertThat(k.canBeTriggered()) .as("originally released key should be pressed now (expected should be true), vice versa") .isEqualTo(exp); if (exp) { - assertThat(k.hasBeenTriggered()).isTrue(); - assertThat(k.isTriggered()) + k.updateStateForNextTick(); + assertThat(k.canBeTriggered()) .as("can be triggered only once") .isFalse(); } }, - k -> assertThat(k.isTriggered()).isEqualTo(expected)); + k -> assertThat(k.canBeTriggered()).isEqualTo(expected)); } } From 7a3d68efa36ddee08e253dc0b316ea9234ceba4f Mon Sep 17 00:00:00 2001 From: boholder Date: Sat, 19 Aug 2023 15:47:51 +0800 Subject: [PATCH 09/10] Remove unnecessary code according to gradle warning. --- .../khanshoaib3/minecraft_access/mixin/ScreenMixin.java | 4 ++-- .../khanshoaib3/minecraft_access/utils/KeystrokeTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/ScreenMixin.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/ScreenMixin.java index 7cf27b5a..2ab59ac4 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/ScreenMixin.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/ScreenMixin.java @@ -47,8 +47,8 @@ private void addElementNarrationsHead(NarrationMessageBuilder builder, CallbackI callbackInfo.cancel(); } - ImmutableList immutableList = (ImmutableList)this.selectables.stream().filter(Selectable::isNarratable).collect(ImmutableList.toImmutableList()); - Screen.SelectedElementNarrationData selectedElementNarrationData = this.findSelectedElementData(immutableList, this.selected); + ImmutableList immutableList = this.selectables.stream().filter(Selectable::isNarratable).collect(ImmutableList.toImmutableList()); + Screen.SelectedElementNarrationData selectedElementNarrationData = findSelectedElementData(immutableList, this.selected); if (selectedElementNarrationData != null) { if (selectedElementNarrationData.selectType.isFocused()) { this.selected = selectedElementNarrationData.selectable; diff --git a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java index ff84a8ac..601e0665 100644 --- a/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java +++ b/common/src/test/java/com/github/khanshoaib3/minecraft_access/utils/KeystrokeTest.java @@ -20,7 +20,7 @@ class KeystrokeTest { static class InitKeystrokeConditionSameAsExpected implements ArgumentsProvider { @Override - public Stream provideArguments(ExtensionContext extensionContext) throws Exception { + public Stream provideArguments(ExtensionContext extensionContext) { return Stream.of( Arguments.of(new MockKeystroke(true), true), Arguments.of(new MockKeystroke(false), false) From c5ff569e5b667f3df23331068e8cd363439e2ce3 Mon Sep 17 00:00:00 2001 From: boholder Date: Sat, 19 Aug 2023 16:15:36 +0800 Subject: [PATCH 10/10] Apply Keystroke on BookEditScreenMixin. --- .../mixin/BookEditScreenMixin.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/BookEditScreenMixin.java b/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/BookEditScreenMixin.java index a730b1cd..4252f191 100644 --- a/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/BookEditScreenMixin.java +++ b/common/src/main/java/com/github/khanshoaib3/minecraft_access/mixin/BookEditScreenMixin.java @@ -3,6 +3,7 @@ import com.github.khanshoaib3.minecraft_access.MainClass; import com.github.khanshoaib3.minecraft_access.utils.KeyUtils; import com.github.khanshoaib3.minecraft_access.utils.MouseUtils; +import com.github.khanshoaib3.minecraft_access.utils.condition.Keystroke; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -44,9 +45,9 @@ public abstract class BookEditScreenMixin { @Unique boolean minecraft_access$firstTimeInSignMenu = true; @Unique String minecraft_access$previousContent = ""; + @Unique private static final Keystroke minecraft_access$tabKey = new Keystroke(() -> KeyUtils.isAnyPressed(GLFW.GLFW_KEY_TAB)); + @Unique private static final Keystroke minecraft_access$spaceKey = new Keystroke(KeyUtils::isSpacePressed); - @Unique private boolean minecraft_access$isTabPressedPreviousTick = false; - @Unique private boolean minecraft_access$isSpacePressedPreviousTick = false; @Unique private int minecraft_access$currentFocusedButtonStateCode = 0; @Unique private static final int BUTTON_OFFSET = 3; @@ -56,19 +57,16 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta, Cal if (minecraftClient == null) return; if (minecraftClient.currentScreen == null) return; - boolean isSpacePressed = KeyUtils.isSpacePressed(); - boolean isTabPressed = KeyUtils.isAnyPressed(GLFW.GLFW_KEY_TAB); - // Switch between buttons with Tab key // Only switch once until release and press Tab again - if (isTabPressed && !minecraft_access$isTabPressedPreviousTick) { + if (minecraft_access$tabKey.isPressed()) { minecraft_access$switchMouseHoveredButton(); } // update the state - minecraft_access$isTabPressedPreviousTick = isTabPressed; + minecraft_access$tabKey.updateStateForNextTick(); - if (isSpacePressed && !minecraft_access$isSpacePressedPreviousTick) { - minecraft_access$isSpacePressedPreviousTick = true; + if (minecraft_access$spaceKey.isPressed()) { + minecraft_access$spaceKey.updateStateForNextTick(); if (this.signing) { if (this.cancelButton.isHovered()) { this.cancelButton.onPress(); @@ -90,7 +88,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta, Cal } } // update the state - minecraft_access$isSpacePressedPreviousTick = isSpacePressed; + minecraft_access$spaceKey.updateStateForNextTick(); if (this.signing) { String currentTitle = this.title.trim();