From 155278130f69eafaf7cc5d63afc0ee53461f4ed2 Mon Sep 17 00:00:00 2001 From: LambdAurora Date: Tue, 30 Jun 2020 19:19:29 +0200 Subject: [PATCH] Add analog movements and WIP action ring. --- .../client/LambdaControlsConfig.java | 8 +- .../client/LambdaControlsModMenu.java | 1 - .../lambdacontrols/client/LambdaInput.java | 53 +++++++--- .../client/VirtualMouseSkin.java | 3 +- .../client/compat/CompatHandler.java | 1 - .../client/compat/HQMCompat.java | 6 +- .../client/compat/LambdaControlsCompat.java | 4 +- .../client/compat/OkZoomerCompat.java | 2 + .../client/compat/ReiCompat.java | 8 +- .../client/controller/ButtonBinding.java | 34 +++++-- .../client/controller/InputHandlers.java | 35 +++++-- .../client/controller/InputManager.java | 48 ++++++--- .../client/controller/MovementHandler.java | 98 +++++++++++++++++++ .../client/controller/PressAction.java | 6 +- .../client/mixin/ClientPlayerEntityMixin.java | 7 ++ .../mixin/ControlsOptionsScreenMixin.java | 1 - .../client/mixin/HandledScreenMixin.java | 3 - .../client/ring/KeyBindingRingAction.java | 46 +++++++++ .../client/ring/LambdaRing.java | 42 ++++++++ .../client/ring/RingAction.java | 55 +++++++++++ .../client/ring/RingButtonMode.java | 64 ++++++++++++ .../{KeyBindingRing.java => RingPage.java} | 6 +- 22 files changed, 461 insertions(+), 70 deletions(-) create mode 100644 fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/MovementHandler.java create mode 100644 fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/KeyBindingRingAction.java create mode 100644 fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/LambdaRing.java create mode 100644 fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingAction.java create mode 100644 fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingButtonMode.java rename fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/{KeyBindingRing.java => RingPage.java} (75%) diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaControlsConfig.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaControlsConfig.java index 24b064f..4e03200 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaControlsConfig.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaControlsConfig.java @@ -61,12 +61,12 @@ public class LambdaControlsConfig protected final FileConfig config = FileConfig.builder("config/lambdacontrols.toml").concurrent().defaultResource("/config.toml").build(); private final LambdaControlsClient mod; private ControlsMode controlsMode; - private ControllerType controllerType; + private ControllerType controllerType; // Gameplay. - private boolean shouldRenderReacharoundOutline; - private int[] reacharoundOutlineColor; + private boolean shouldRenderReacharoundOutline; + private int[] reacharoundOutlineColor; // Controller settings - private double deadZone; + private double deadZone; private double rotationSpeed; private double mouseSpeed; private boolean unfocusedInput; diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaControlsModMenu.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaControlsModMenu.java index 644e19d..dca0736 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaControlsModMenu.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaControlsModMenu.java @@ -11,7 +11,6 @@ package me.lambdaurora.lambdacontrols.client; import io.github.prospector.modmenu.api.ConfigScreenFactory; import io.github.prospector.modmenu.api.ModMenuApi; -import me.lambdaurora.lambdacontrols.LambdaControlsConstants; import me.lambdaurora.lambdacontrols.client.gui.LambdaControlsSettingsScreen; /** diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaInput.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaInput.java index ff06e0b..bae20eb 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaInput.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/LambdaInput.java @@ -32,6 +32,7 @@ import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget; +import net.minecraft.client.gui.screen.pack.ResourcePackScreen; import net.minecraft.client.gui.screen.world.WorldListWidget; import net.minecraft.client.gui.widget.AbstractPressableButtonWidget; import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget; @@ -63,24 +64,25 @@ import static org.lwjgl.glfw.GLFW.*; * Represents the LambdaControls' input handler. * * @author LambdAurora - * @version 1.3.2 + * @version 1.4.0 * @since 1.0.0 */ public class LambdaInput { - private static final Map BUTTON_COOLDOWNS = new HashMap<>(); + private static final Map BUTTON_COOLDOWNS = new HashMap<>(); private final LambdaControlsConfig config; // Cooldowns - private int actionGuiCooldown = 0; - private boolean ignoreNextARelease = false; - private double targetYaw = 0.0; - private double targetPitch = 0.0; - private float prevXAxis = 0.F; - private float prevYAxis = 0.F; - private int targetMouseX = 0; - private int targetMouseY = 0; - private float mouseSpeedX = 0.F; - private float mouseSpeedY = 0.F; + private int actionGuiCooldown = 0; + private boolean ignoreNextARelease = false; + private double targetYaw = 0.0; + private double targetPitch = 0.0; + private float prevXAxis = 0.F; + private float prevYAxis = 0.F; + private int targetMouseX = 0; + private int targetMouseY = 0; + private float mouseSpeedX = 0.F; + private float mouseSpeedY = 0.F; + private int inventoryInteractionCooldown = 0; public LambdaInput(@NotNull LambdaControlsClient mod) { @@ -157,6 +159,9 @@ public class LambdaInput screen.focusedBinding = null; } } + + if (this.inventoryInteractionCooldown > 0) + this.inventoryInteractionCooldown--; } /** @@ -214,6 +219,7 @@ public class LambdaInput ((MouseAccessor) client.mouse).lambdacontrols_onCursorPos(client.getWindow().getHandle(), 0, 0); INPUT_MANAGER.resetMouseTarget(client); } + this.inventoryInteractionCooldown = 5; } private void fetchButtonInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) @@ -329,9 +335,11 @@ public class LambdaInput double mouseX = client.mouse.getX() * (double) client.getWindow().getScaledWidth() / (double) client.getWindow().getWidth(); double mouseY = client.mouse.getY() * (double) client.getWindow().getScaledHeight() / (double) client.getWindow().getHeight(); if (action == 0) { - client.currentScreen.mouseClicked(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1); + Screen.wrapScreenError(() -> client.currentScreen.mouseClicked(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1), + "mouseClicked event handler", client.currentScreen.getClass().getCanonicalName()); } else if (action == 1) { - client.currentScreen.mouseReleased(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1); + Screen.wrapScreenError(() -> client.currentScreen.mouseReleased(mouseX, mouseY, GLFW.GLFW_MOUSE_BUTTON_1), + "mouseReleased event handler", client.currentScreen.getClass().getCanonicalName()); } this.actionGuiCooldown = 5; } else { @@ -355,6 +363,9 @@ public class LambdaInput if (client.interactionManager == null || client.player == null) return false; + if (this.inventoryInteractionCooldown > 0) + return true; + if (button == GLFW.GLFW_GAMEPAD_BUTTON_B) { client.player.closeHandledScreen(); return true; @@ -457,6 +468,17 @@ public class LambdaInput BUTTON_COOLDOWNS.put(axisAsButton(axis, false), 5); } } + + float axisValue = absValue < this.config.getDeadZone() ? 0.f : (float) (absValue - this.config.getDeadZone()); + axisValue /= (1.0 - this.config.getDeadZone()); + if (currentPlusState) + InputManager.BUTTON_VALUES.put(axisAsButton(axis, true), axisValue); + else + InputManager.BUTTON_VALUES.put(axisAsButton(axis, true), 0.f); + if (currentMinusState) + InputManager.BUTTON_VALUES.put(axisAsButton(axis, false), axisValue); + else + InputManager.BUTTON_VALUES.put(axisAsButton(axis, false), 0.f); } double deadZone = this.config.getDeadZone(); @@ -481,6 +503,7 @@ public class LambdaInput if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) { CreativeInventoryScreen screen = (CreativeInventoryScreen) client.currentScreen; CreativeInventoryScreenAccessor accessor = (CreativeInventoryScreenAccessor) screen; + // @TODO allow rebinding to left stick if (accessor.lambdacontrols_hasScrollbar() && absValue >= deadZone) { screen.mouseScrolled(0.0, 0.0, -value); } @@ -677,7 +700,7 @@ public class LambdaInput public static boolean isScreenInteractive(@NotNull Screen screen) { - return !(screen instanceof AdvancementsScreen || screen instanceof HandledScreen || LambdaControlsCompat.requireMouseOnScreen(screen)); + return !(screen instanceof AdvancementsScreen || screen instanceof HandledScreen || screen instanceof ResourcePackScreen || LambdaControlsCompat.requireMouseOnScreen(screen)); } // Inspired from https://github.com/MrCrayfish/Controllable/blob/1.14.X/src/main/java/com/mrcrayfish/controllable/client/ControllerInput.java#L686. diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/VirtualMouseSkin.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/VirtualMouseSkin.java index 83c310d..6739188 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/VirtualMouseSkin.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/VirtualMouseSkin.java @@ -31,7 +31,8 @@ public enum VirtualMouseSkin implements Nameable private String name; - VirtualMouseSkin(String name) { + VirtualMouseSkin(String name) + { this.name = name; } diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/CompatHandler.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/CompatHandler.java index 7ce00ab..93fb4ff 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/CompatHandler.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/CompatHandler.java @@ -13,7 +13,6 @@ import me.lambdaurora.lambdacontrols.client.LambdaControlsClient; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.ingame.HandledScreen; -import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.screen.slot.Slot; import net.minecraft.util.hit.BlockHitResult; import org.jetbrains.annotations.NotNull; diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/HQMCompat.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/HQMCompat.java index 812af19..9f383f9 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/HQMCompat.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/HQMCompat.java @@ -18,7 +18,7 @@ import java.util.Optional; /** * Represents HQM compatibility handler. - * + *

* This is bad. * * @author LambdAurora @@ -27,8 +27,8 @@ import java.util.Optional; */ public class HQMCompat implements CompatHandler { - public static final String GUI_BASE_CLASS_PATH = "hardcorequesting.client.interfaces.GuiBase"; - private Optional> guiBaseClass; + public static final String GUI_BASE_CLASS_PATH = "hardcorequesting.client.interfaces.GuiBase"; + private Optional> guiBaseClass; @Override public void handle(@NotNull LambdaControlsClient mod) diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsCompat.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsCompat.java index 9bf07c7..4bb011d 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsCompat.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/LambdaControlsCompat.java @@ -14,7 +14,6 @@ import me.lambdaurora.lambdacontrols.client.controller.InputManager; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.util.hit.BlockHitResult; import org.aperlambda.lambdacommon.utils.LambdaReflection; import org.jetbrains.annotations.NotNull; @@ -132,7 +131,8 @@ public class LambdaControlsCompat * @param screen The screen. * @return True if the handle was fired and succeed, else false. */ - public static boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) { + public static boolean handleMenuBack(@NotNull MinecraftClient client, @NotNull Screen screen) + { for (CompatHandler handler : HANDLERS) { if (handler.handleMenuBack(client, screen)) return true; diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/OkZoomerCompat.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/OkZoomerCompat.java index 3aada37..75b7ec4 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/OkZoomerCompat.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/OkZoomerCompat.java @@ -36,5 +36,7 @@ public class OkZoomerCompat implements CompatHandler .category(ButtonBinding.MISC_CATEGORY) .linkKeybind(OkZoomerClientMod.zoomKeyBinding) .register(); + + // @TODO Zoom in and out } } diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/ReiCompat.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/ReiCompat.java index d73554c..ba2f0de 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/ReiCompat.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/compat/ReiCompat.java @@ -39,7 +39,7 @@ import static org.lwjgl.glfw.GLFW.*; * Represents a compatibility handler for REI. * * @author LambdAurora - * @version 1.3.2 + * @version 1.4.0 * @since 1.2.0 */ public class ReiCompat implements CompatHandler @@ -79,7 +79,7 @@ public class ReiCompat implements CompatHandler InputManager.registerBinding(new ButtonBinding.Builder(new Identifier("rei", "show_usage")) .buttons(GLFW_GAMEPAD_BUTTON_RIGHT_THUMB) .filter((client, binding) -> InputHandlers.inInventory(client, binding) || isViewingScreen(client.currentScreen)) - .action((client, button, action) -> { + .action((client, button, value, action) -> { if (action != ButtonState.RELEASE) return false; Optional overlay = ScreenHelper.getOptionalOverlay(); @@ -130,7 +130,7 @@ public class ReiCompat implements CompatHandler private static PressAction handlePage(boolean next) { - return (client, button, action) -> { + return (client, button, value, action) -> { if (action == ButtonState.RELEASE) return false; @@ -160,7 +160,7 @@ public class ReiCompat implements CompatHandler */ private static PressAction handleTab(boolean next) { - return (client, button, action) -> { + return (client, button, value, action) -> { if (action != ButtonState.RELEASE) return false; diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/ButtonBinding.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/ButtonBinding.java index 9d5b2ef..1cd9fb1 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/ButtonBinding.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/ButtonBinding.java @@ -13,7 +13,6 @@ import me.lambdaurora.lambdacontrols.client.ButtonState; import net.minecraft.client.MinecraftClient; import net.minecraft.client.options.GameOptions; import net.minecraft.client.options.KeyBinding; -import net.minecraft.client.resource.language.I18n; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; import org.aperlambda.lambdacommon.Identifier; @@ -33,7 +32,7 @@ import static org.lwjgl.glfw.GLFW.*; * Represents a button binding. * * @author LambdAurora - * @version 1.3.0 + * @version 1.4.0 * @since 1.0.0 */ public class ButtonBinding implements Nameable @@ -45,21 +44,25 @@ public class ButtonBinding implements Nameable public static final ButtonCategory MISC_CATEGORY; public static final ButtonBinding ATTACK = new Builder("attack").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, true)).onlyInGame().register(); - public static final ButtonBinding BACK = new Builder("back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false)).onlyInGame().register(); + public static final ButtonBinding BACK = new Builder("back").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, false)) + .action(MovementHandler.HANDLER).onlyInGame().register(); public static final ButtonBinding CHAT = new Builder("chat").buttons(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT).onlyInGame().cooldown(true).register(); public static final ButtonBinding DROP_ITEM = new Builder("drop_item").buttons(GLFW_GAMEPAD_BUTTON_B).onlyInGame().cooldown(true).register(); - public static final ButtonBinding FORWARD = new Builder("forward").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true)).onlyInGame().register(); + public static final ButtonBinding FORWARD = new Builder("forward").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_Y, true)) + .action(MovementHandler.HANDLER).onlyInGame().register(); public static final ButtonBinding HOTBAR_LEFT = new Builder("hotbar_left").buttons(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) .action(InputHandlers.handleHotbar(false)).onlyInGame().cooldown(true).register(); public static final ButtonBinding HOTBAR_RIGHT = new Builder("hotbar_right").buttons(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) .action(InputHandlers.handleHotbar(true)).onlyInGame().cooldown(true).register(); public static final ButtonBinding INVENTORY = new Builder("inventory").buttons(GLFW_GAMEPAD_BUTTON_Y).onlyInGame().cooldown(true).register(); public static final ButtonBinding JUMP = new Builder("jump").buttons(GLFW_GAMEPAD_BUTTON_A).onlyInGame().register(); - public static final ButtonBinding LEFT = new Builder("left").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false)).onlyInGame().register(); + public static final ButtonBinding LEFT = new Builder("left").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, false)) + .action(MovementHandler.HANDLER).onlyInGame().register(); public static final ButtonBinding PAUSE_GAME = new Builder("pause_game").buttons(GLFW_GAMEPAD_BUTTON_START).action(InputHandlers::handlePauseGame).cooldown(true).register(); public static final ButtonBinding PICK_BLOCK = new Builder("pick_block").buttons(GLFW_GAMEPAD_BUTTON_DPAD_LEFT).onlyInGame().cooldown(true).register(); public static final ButtonBinding PLAYER_LIST = new Builder("player_list").buttons(GLFW_GAMEPAD_BUTTON_BACK).onlyInGame().register(); - public static final ButtonBinding RIGHT = new Builder("right").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true)).register(); + public static final ButtonBinding RIGHT = new Builder("right").buttons(axisAsButton(GLFW_GAMEPAD_AXIS_LEFT_X, true)) + .action(MovementHandler.HANDLER).register(); public static final ButtonBinding SCREENSHOT = new Builder("screenshot").buttons(GLFW_GAMEPAD_BUTTON_DPAD_UP, GLFW_GAMEPAD_BUTTON_A) .action(InputHandlers::handleScreenshot).cooldown(true).register(); public static final ButtonBinding SLOT_DOWN = new Builder("slot_down").buttons(GLFW_GAMEPAD_BUTTON_DPAD_DOWN) @@ -229,7 +232,7 @@ public class ButtonBinding implements Nameable * @param client The client instance. * @param state The state. */ - public void handle(@NotNull MinecraftClient client, @NotNull ButtonState state) + public void handle(@NotNull MinecraftClient client, float value, @NotNull ButtonState state) { if (state == ButtonState.REPEAT && this.hasCooldown && this.cooldown != 0) return; @@ -238,7 +241,7 @@ public class ButtonBinding implements Nameable } for (int i = this.actions.size() - 1; i >= 0; i--) { - if (this.actions.get(i).press(client, this, state)) + if (this.actions.get(i).press(client, this, value, state)) break; } } @@ -254,8 +257,7 @@ public class ButtonBinding implements Nameable * * @return The translation key. */ - public @NotNull - String getTranslationKey() + public @NotNull String getTranslationKey() { return "lambdacontrols.action." + this.getName(); } @@ -283,6 +285,18 @@ public class ButtonBinding implements Nameable return positive ? 100 + axis : 200 + axis; } + /** + * Returns whether the specified button is an axis or not. + * + * @param button The button. + * @return True if the button is an axis, else false. + */ + public static boolean isAxis(int button) + { + button %= 500; + return button >= 100; + } + /** * Returns the second Joycon's specified button code. * diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputHandlers.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputHandlers.java index f810387..9683d87 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputHandlers.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputHandlers.java @@ -10,6 +10,7 @@ package me.lambdaurora.lambdacontrols.client.controller; import me.lambdaurora.lambdacontrols.client.ButtonState; +import me.lambdaurora.lambdacontrols.client.LambdaInput; import me.lambdaurora.lambdacontrols.client.mixin.AdvancementsScreenAccessor; import me.lambdaurora.lambdacontrols.client.mixin.CreativeInventoryScreenAccessor; import me.lambdaurora.lambdacontrols.client.mixin.RecipeBookWidgetAccessor; @@ -38,7 +39,7 @@ import java.util.stream.Collectors; * Represents some input handlers. * * @author LambdAurora - * @version 1.3.0 + * @version 1.4.0 * @since 1.1.0 */ public class InputHandlers @@ -49,7 +50,7 @@ public class InputHandlers public static PressAction handleHotbar(boolean next) { - return (client, button, action) -> { + return (client, button, value, action) -> { if (action == ButtonState.RELEASE) return false; @@ -74,6 +75,8 @@ public class InputHandlers RecipeBookWidgetAccessor recipeBook = (RecipeBookWidgetAccessor) ((InventoryScreen) client.currentScreen).getRecipeBookWidget(); List tabs = recipeBook.getTabButtons(); RecipeGroupButtonWidget currentTab = recipeBook.getCurrentTab(); + if (currentTab == null) + return false; int nextTab = tabs.indexOf(currentTab) + (next ? 1 : -1); if (nextTab < 0) nextTab = tabs.size() - 1; @@ -83,10 +86,13 @@ public class InputHandlers recipeBook.setCurrentTab(currentTab = tabs.get(nextTab)); currentTab.setToggled(true); recipeBook.lambdacontrols_refreshResults(true); + return true; } else if (client.currentScreen instanceof AdvancementsScreen) { AdvancementsScreenAccessor screen = (AdvancementsScreenAccessor) client.currentScreen; List tabs = screen.getTabs().values().stream().distinct().collect(Collectors.toList()); AdvancementTab tab = screen.getSelectedTab(); + if (tab == null) + return false; for (int i = 0; i < tabs.size(); i++) { if (tabs.get(i).equals(tab)) { int nextTab = i + (next ? 1 : -1); @@ -98,12 +104,13 @@ public class InputHandlers break; } } + return true; } return false; }; } - public static boolean handlePauseGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, @NotNull ButtonState action) + public static boolean handlePauseGame(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, float value, @NotNull ButtonState action) { if (action == ButtonState.PRESS) { // If in game, then pause the game. @@ -125,15 +132,15 @@ public class InputHandlers * @param action The action done on the binding. * @return True if handled, else false. */ - public static boolean handleScreenshot(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, @NotNull ButtonState action) + public static boolean handleScreenshot(@NotNull MinecraftClient client, @NotNull ButtonBinding binding, float value, @NotNull ButtonState action) { - if (action == ButtonState.PRESS) + if (action == ButtonState.RELEASE) ScreenshotUtils.saveScreenshot(client.runDirectory, client.getWindow().getFramebufferWidth(), client.getWindow().getFramebufferHeight(), client.getFramebuffer(), text -> client.execute(() -> client.inGameHud.getChatHud().addMessage(text))); return true; } - public static boolean handleToggleSneak(@NotNull MinecraftClient client, @NotNull ButtonBinding button, @NotNull ButtonState action) + public static boolean handleToggleSneak(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) { if (client.player != null && !client.player.abilities.flying) { button.asKeyBinding().filter(binding -> action == ButtonState.PRESS).ifPresent(binding -> ((KeyBindingAccessor) binding).lambdacontrols_handlePressState(!binding.isPressed())); @@ -144,7 +151,7 @@ public class InputHandlers public static PressAction handleInventorySlotPad(int direction) { - return (client, binding, action) -> { + return (client, binding, value, action) -> { if (!(client.currentScreen instanceof HandledScreen && action != ButtonState.RELEASE)) return false; @@ -235,6 +242,20 @@ public class InputHandlers return client.currentScreen == null; } + /** + * Returns whether the client is in a non-interactive screen (which means require mouse input) or not. + * + * @param client The client instance. + * @param binding The affected binding. + * @return True if the client is in a non-interactive screen, else false. + */ + public static boolean inNonInteractiveScreens(@NotNull MinecraftClient client, @NotNull ButtonBinding binding) + { + if (client.currentScreen == null) + return false; + return !LambdaInput.isScreenInteractive(client.currentScreen); + } + /** * Returns whether the client is in an inventory or not. * diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputManager.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputManager.java index de7b40b..5ba0498 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputManager.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/InputManager.java @@ -19,6 +19,7 @@ import net.minecraft.client.options.KeyBinding; import net.minecraft.client.util.InputUtil; import net.minecraft.util.math.MathHelper; import org.aperlambda.lambdacommon.Identifier; +import org.aperlambda.lambdacommon.utils.Pair; import org.aperlambda.lambdacommon.utils.function.PairPredicate; import org.jetbrains.annotations.NotNull; import org.lwjgl.glfw.GLFW; @@ -32,7 +33,7 @@ import java.util.stream.Stream; * Represents an input manager for controllers. * * @author LambdAurora - * @version 1.3.0 + * @version 1.4.0 * @since 1.1.0 */ public class InputManager @@ -41,6 +42,7 @@ public class InputManager private static final List BINDINGS = new ArrayList<>(); private static final List CATEGORIES = new ArrayList<>(); public static final Map STATES = new HashMap<>(); + public static final Map BUTTON_VALUES = new HashMap<>(); private int prevTargetMouseX = 0; private int prevTargetMouseY = 0; private int targetMouseX = 0; @@ -244,6 +246,23 @@ public class InputManager return state; } + public static float getBindingValue(@NotNull ButtonBinding binding, @NotNull ButtonState state) + { + if (state.isUnpressed()) + return 0.f; + + float value = 0.f; + for (int btn : binding.getButton()) { + if (ButtonBinding.isAxis(btn)) { + value = BUTTON_VALUES.getOrDefault(btn, 1.f); + break; + } else { + value = 1.f; + } + } + return value; + } + /** * Returns whether the button has duplicated bindings. * @@ -317,7 +336,7 @@ public class InputManager public static void updateBindings(@NotNull MinecraftClient client) { List skipButtons = new ArrayList<>(); - Map states = new HashMap<>(); + Map> states = new HashMap<>(); for (ButtonBinding binding : BINDINGS) { ButtonState state = binding.isAvailable(client) ? getBindingState(binding) : ButtonState.NONE; if (skipButtons.stream().anyMatch(btn -> containsButton(binding.getButton(), btn))) { @@ -330,12 +349,15 @@ public class InputManager binding.update(); if (binding.pressed) Arrays.stream(binding.getButton()).forEach(skipButtons::add); - states.put(binding, state); + + float value = getBindingValue(binding, state); + + states.put(binding, Pair.of(state, value)); } states.forEach((binding, state) -> { - if (state != ButtonState.NONE) { - binding.handle(client, state); + if (state.key != ButtonState.NONE) { + binding.handle(client, state.value, state.key); } }); } @@ -363,12 +385,12 @@ public class InputManager /** * Returns a new key binding instance. - * @param id The identifier of the key binding. - * @param type The type. - * @param code The code. + * + * @param id The identifier of the key binding. + * @param type The type. + * @param code The code. * @param category The category of the key binding. * @return The key binding. - * * @see #makeKeyBinding(Identifier, InputUtil.Type, int, String) */ public static @NotNull KeyBinding makeKeyBinding(@NotNull net.minecraft.util.Identifier id, InputUtil.Type type, int code, @NotNull String category) @@ -378,12 +400,12 @@ public class InputManager /** * Returns a new key binding instance. - * @param id The identifier of the key binding. - * @param type The type. - * @param code The code. + * + * @param id The identifier of the key binding. + * @param type The type. + * @param code The code. * @param category The category of the key binding. * @return The key binding. - * * @see #makeKeyBinding(net.minecraft.util.Identifier, InputUtil.Type, int, String) */ public static @NotNull KeyBinding makeKeyBinding(@NotNull Identifier id, InputUtil.Type type, int code, @NotNull String category) diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/MovementHandler.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/MovementHandler.java new file mode 100644 index 0000000..b2e1f30 --- /dev/null +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/MovementHandler.java @@ -0,0 +1,98 @@ +/* + * Copyright © 2020 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols.client.controller; + +import me.lambdaurora.lambdacontrols.client.ButtonState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.ClientPlayerEntity; +import org.jetbrains.annotations.NotNull; + +/** + * Represents the movement handler. + * + * @author LambdAurora + * @version 1.4.0 + * @since 1.4.0 + */ +public final class MovementHandler implements PressAction +{ + public static final MovementHandler HANDLER = new MovementHandler(); + private boolean shouldOverrideMovement = false; + private boolean pressingForward = false; + private boolean pressingBack = false; + private boolean pressingLeft = false; + private boolean pressingRight = false; + private float movementForward = 0.f; + private float movementSideways = 0.f; + + private MovementHandler() + { + } + + /** + * Applies movement input of this handler to the player's input. + * + * @param player The client player. + */ + public void applyMovement(@NotNull ClientPlayerEntity player) + { + if (!this.shouldOverrideMovement) + return; + player.input.pressingForward = this.pressingForward; + player.input.pressingBack = this.pressingBack; + player.input.pressingLeft = this.pressingLeft; + player.input.pressingRight = this.pressingRight; + player.input.movementForward = this.movementForward; + player.input.movementSideways = this.movementSideways; + this.shouldOverrideMovement = false; + } + + @Override + public boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action) + { + if (client.currentScreen != null || client.player == null) + return this.shouldOverrideMovement = false; + + int direction = 0; + if (button == ButtonBinding.FORWARD || button == ButtonBinding.LEFT) + direction = 1; + else if (button == ButtonBinding.BACK || button == ButtonBinding.RIGHT) + direction = -1; + + if (direction == 0) + return false; + + this.shouldOverrideMovement = true; + + value = (float) Math.pow(value, 2); + + if (button == ButtonBinding.FORWARD || button == ButtonBinding.BACK) { + // Handle forward movement. + this.pressingForward = direction > 0; + this.pressingBack = direction < 0; + this.movementForward = direction * value; + + // Slowing down if sneaking. + if (client.player.input.sneaking) + this.movementForward *= 0.3D; + } else { + // Handle sideways movement. + this.pressingLeft = direction > 0; + this.pressingRight = direction < 0; + this.movementSideways = direction * value; + + // Slowing down if sneaking. + if (client.player.input.sneaking) + this.movementSideways *= 0.3D; + } + + return true; + } +} diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/PressAction.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/PressAction.java index 7651b1e..4bf5f15 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/PressAction.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/controller/PressAction.java @@ -18,13 +18,13 @@ import org.jetbrains.annotations.NotNull; * Represents a press action callback. * * @author LambdAurora - * @version 1.1.0 + * @version 1.4.0 * @since 1.0.0 */ @FunctionalInterface public interface PressAction { - PressAction DEFAULT_ACTION = (client, button, action) -> { + PressAction DEFAULT_ACTION = (client, button, value, action) -> { if (action == ButtonState.REPEAT || client.currentScreen != null) return false; button.asKeyBinding().ifPresent(binding -> ((KeyBindingAccessor) binding).lambdacontrols_handlePressState(button.isButtonDown())); @@ -37,5 +37,5 @@ public interface PressAction * @param client The client instance. * @param action The action done. */ - boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, @NotNull ButtonState action); + boolean press(@NotNull MinecraftClient client, @NotNull ButtonBinding button, float value, @NotNull ButtonState action); } diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/ClientPlayerEntityMixin.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/ClientPlayerEntityMixin.java index 9381860..29abd71 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/ClientPlayerEntityMixin.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/ClientPlayerEntityMixin.java @@ -11,6 +11,7 @@ package me.lambdaurora.lambdacontrols.client.mixin; import com.mojang.authlib.GameProfile; import me.lambdaurora.lambdacontrols.client.LambdaControlsClient; +import me.lambdaurora.lambdacontrols.client.controller.MovementHandler; import net.minecraft.client.MinecraftClient; import net.minecraft.client.input.Input; import net.minecraft.client.network.AbstractClientPlayerEntity; @@ -69,6 +70,12 @@ public abstract class ClientPlayerEntityMixin extends AbstractClientPlayerEntity } } + @Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/input/Input;tick(Z)V", shift = At.Shift.AFTER)) + public void onInputUpdate(CallbackInfo ci) + { + MovementHandler.HANDLER.applyMovement((ClientPlayerEntity) (Object) this); + } + @Inject(method = "tickMovement", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;isCamera()Z")) public void onTickMovement(CallbackInfo ci) { diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/ControlsOptionsScreenMixin.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/ControlsOptionsScreenMixin.java index 329e8e0..75f9e75 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/ControlsOptionsScreenMixin.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/ControlsOptionsScreenMixin.java @@ -17,7 +17,6 @@ import net.minecraft.client.gui.screen.options.GameOptionsScreen; import net.minecraft.client.gui.widget.AbstractButtonWidget; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.options.GameOptions; -import net.minecraft.client.resource.language.I18n; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; import org.spongepowered.asm.mixin.Mixin; diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/HandledScreenMixin.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/HandledScreenMixin.java index 7d47d36..8187aa4 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/HandledScreenMixin.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/mixin/HandledScreenMixin.java @@ -22,15 +22,12 @@ import net.minecraft.screen.slot.SlotActionType; import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import java.util.Arrays; - /** * Represents the mixin for the class ContainerScreen. */ diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/KeyBindingRingAction.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/KeyBindingRingAction.java new file mode 100644 index 0000000..311e76b --- /dev/null +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/KeyBindingRingAction.java @@ -0,0 +1,46 @@ +/* + * Copyright © 2020 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols.client.ring; + +import me.lambdaurora.lambdacontrols.client.util.KeyBindingAccessor; +import net.minecraft.client.options.KeyBinding; +import org.jetbrains.annotations.NotNull; + +public class KeyBindingRingAction extends RingAction +{ + public final KeyBinding binding; + + public KeyBindingRingAction(@NotNull KeyBinding binding) + { + this.binding = binding; + } + + @Override + public @NotNull String getName() + { + return this.binding.getTranslationKey(); + } + + @Override + public void onAction(@NotNull RingButtonMode mode) + { + KeyBindingAccessor accessor = (KeyBindingAccessor) this.binding; + switch (mode) { + case PRESS: + case HOLD: + accessor.lambdacontrols_handlePressState(this.activated); + break; + case TOGGLE: + accessor.lambdacontrols_handlePressState(!this.binding.isPressed()); + this.activated = !this.binding.isPressed(); + break; + } + } +} diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/LambdaRing.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/LambdaRing.java new file mode 100644 index 0000000..f4dff00 --- /dev/null +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/LambdaRing.java @@ -0,0 +1,42 @@ +/* + * Copyright © 2020 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols.client.ring; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a key binding ring. + * + * @author LambdAurora + * @version 1.4.0 + * @since 1.4.0 + */ +public class LambdaRing +{ + private final List pages = new ArrayList<>(Collections.singletonList(new RingPage())); + private int currentPage = 0; + + public LambdaRing() + { + } + + public @NotNull RingPage getCurrentPage() + { + if (this.currentPage >= this.pages.size()) + this.currentPage = this.pages.size() - 1; + else if (this.currentPage < 0) + this.currentPage = 0; + return this.pages.get(this.currentPage); + } +} diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingAction.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingAction.java new file mode 100644 index 0000000..5fd883e --- /dev/null +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingAction.java @@ -0,0 +1,55 @@ +/* + * Copyright © 2020 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols.client.ring; + +import net.minecraft.text.TranslatableText; +import org.aperlambda.lambdacommon.utils.Nameable; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a ring action. + * + * @author LambdAurora + * @version 1.4.0 + * @since 1.4.0 + */ +public abstract class RingAction implements Nameable +{ + protected boolean activated = false; + + /** + * Gets the translated name of the ring action. + * + * @return The translated name. + */ + public TranslatableText getTranslatedName() + { + return new TranslatableText(this.getName()); + } + + /** + * Returns whether the action is activated or not. + * + * @return True if the action is activated, else false. + */ + public boolean isActivated() + { + return this.activated; + } + + public void activate(@NotNull RingButtonMode mode) + { + this.activated = !this.activated; + + this.onAction(mode); + } + + public abstract void onAction(@NotNull RingButtonMode mode); +} diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingButtonMode.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingButtonMode.java new file mode 100644 index 0000000..3e45703 --- /dev/null +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingButtonMode.java @@ -0,0 +1,64 @@ +/* + * Copyright © 2020 LambdAurora + * + * This file is part of LambdaControls. + * + * Licensed under the MIT license. For more information, + * see the LICENSE file. + */ + +package me.lambdaurora.lambdacontrols.client.ring; + +import net.minecraft.client.resource.language.I18n; +import org.aperlambda.lambdacommon.utils.Nameable; +import org.jetbrains.annotations.NotNull; + +/** + * Represents the mode of a ring button. + * + * @author LambdAurora + * @version 1.4.0 + * @since 1.4.0 + */ +public enum RingButtonMode implements Nameable +{ + PRESS("press"), + HOLD("hold"), + TOGGLE("toggle"); + + private String name; + + RingButtonMode(@NotNull String name) + { + this.name = name; + } + + /** + * Returns the next ring button mode available. + * + * @return The next ring button mode. + */ + public RingButtonMode next() + { + RingButtonMode[] v = values(); + if (v.length == this.ordinal() + 1) + return v[0]; + return v[this.ordinal() + 1]; + } + + /** + * Gets the translated name of this ring button mode. + * + * @return The translated name of this ring button mode. + */ + public String getTranslatedName() + { + return I18n.translate("lambdacontrols.ring.button_mode." + this.getName()); + } + + @Override + public @NotNull String getName() + { + return this.name; + } +} diff --git a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/KeyBindingRing.java b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingPage.java similarity index 75% rename from fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/KeyBindingRing.java rename to fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingPage.java index ddc8a3e..a3b9c58 100644 --- a/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/KeyBindingRing.java +++ b/fabric/src/main/java/me/lambdaurora/lambdacontrols/client/ring/RingPage.java @@ -10,13 +10,15 @@ package me.lambdaurora.lambdacontrols.client.ring; /** - * Represents a key binding ring. + * Represents a ring page. * * @author LambdAurora * @version 1.4.0 * @since 1.4.0 */ -public class KeyBindingRing +public class RingPage { + private RingAction[] actions = new RingAction[8]; + }