From e1d53ee4634be9c8593a530f673e109aad6df338 Mon Sep 17 00:00:00 2001 From: Motschen Date: Tue, 3 Oct 2023 17:36:30 +0200 Subject: [PATCH] Completely rewritten joystick input - Joystick input is now processed using polar coordinates, resulting in better accuracy and fixing tons of issues (Fixes #135, #138, #186 & #180) - Camera movement is now way smoother by including the previous stick values in the calculation (Fixes #217 & #167) - updateMappings() is now called asyncronously (Closes #219) --- .../client/MidnightControlsConfig.java | 4 +- .../client/MidnightInput.java | 126 ++++++++++++------ .../client/controller/Controller.java | 9 +- .../client/controller/MovementHandler.java | 13 +- .../client/gui/TouchscreenOverlay.java | 1 + .../client/util/MathUtil.java | 23 ++++ 6 files changed, 124 insertions(+), 52 deletions(-) create mode 100644 src/main/java/eu/midnightdust/midnightcontrols/client/util/MathUtil.java diff --git a/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java b/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java index af96a6b..f1f551c 100644 --- a/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java +++ b/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightControlsConfig.java @@ -63,7 +63,7 @@ public class MidnightControlsConfig extends MidnightConfig { @Entry(category = "controller", name = "midnightcontrols.menu.left_dead_zone", isSlider = true, min = 0.05, max = 1) public static double leftDeadZone = 0.25; @Entry(category = "controller", name = "midnightcontrols.menu.invert_right_y_axis") public static boolean invertRightYAxis = false; @Entry(category = "controller", name = "midnightcontrols.menu.invert_right_x_axis") public static boolean invertRightXAxis = false; - @Entry(category = "controller", name = "midnightcontrols.menu.rotation_speed", isSlider = true, min = 0, max = 100, precision = 10) public static double rotationSpeed = 40.0; //used for x-axis, name kept for compatibility + @Entry(category = "controller", name = "midnightcontrols.menu.rotation_speed", isSlider = true, min = 0, max = 100, precision = 10) public static double rotationSpeed = 35.0; //used for x-axis, name kept for compatibility @Entry(category = "controller", name = "midnightcontrols.menu.y_axis_rotation_speed", isSlider = true, min = 0, max = 100, precision = 10) public static double yAxisRotationSpeed = rotationSpeed; @Entry(category = "screens", name = "midnightcontrols.menu.mouse_speed", isSlider = true, min = 0, max = 150, precision = 10) public static double mouseSpeed = 25.0; @Entry(category = "screens", name = "midnightcontrols.menu.joystick_as_mouse") public static boolean joystickAsMouse = false; @@ -101,7 +101,7 @@ public class MidnightControlsConfig extends MidnightConfig { @Entry(category = "controller", name = "Max analog value: Left Y", isSlider = true, min = .25f, max = 1.f) public static double maxAnalogValueLeftY = maxAnalogValues[1]; @Entry(category = "controller", name = "Max analog value: Right X", isSlider = true, min = .25f, max = 1.f) public static double maxAnalogValueRightX = maxAnalogValues[2]; @Entry(category = "controller", name = "Max analog value: Right Y", isSlider = true, min = .25f, max = 1.f) public static double maxAnalogValueRightY = maxAnalogValues[3]; - @Entry(category = "controller", name = "Trigger button fix") public static boolean triggerFix = true; + @Entry(category = "controller", name = "Trigger button fix") public static boolean triggerFix = false; @Entry(category = "gameplay", name = "Enable Hints") public static boolean enableHints = true; @Entry(category = "screens", name = "Enable Shortcut in Controls Options") public static boolean shortcutInControls = true; @Entry(category = "misc", name = "Ring Bindings (WIP)") public static List ringBindings = new ArrayList<>(); diff --git a/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightInput.java b/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightInput.java index 2dd875c..80308ca 100644 --- a/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightInput.java +++ b/src/main/java/eu/midnightdust/midnightcontrols/client/MidnightInput.java @@ -23,6 +23,7 @@ import eu.midnightdust.midnightcontrols.client.gui.widget.ControllerControlsWidg import eu.midnightdust.midnightcontrols.client.mixin.*; import eu.midnightdust.midnightcontrols.client.ring.RingPage; import eu.midnightdust.midnightcontrols.client.util.HandledScreenAccessor; +import eu.midnightdust.midnightcontrols.client.util.MathUtil; import eu.midnightdust.midnightcontrols.client.util.MouseAccessor; import dev.lambdaurora.spruceui.navigation.NavigationDirection; import dev.lambdaurora.spruceui.screen.SpruceScreen; @@ -35,7 +36,6 @@ import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.Element; import net.minecraft.client.gui.ParentElement; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.TitleScreen; import net.minecraft.client.gui.screen.advancement.AdvancementTab; import net.minecraft.client.gui.screen.advancement.AdvancementsScreen; import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; @@ -46,8 +46,6 @@ import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget; import net.minecraft.client.gui.screen.world.WorldListWidget; import net.minecraft.client.gui.widget.*; -import net.minecraft.client.input.Input; -import net.minecraft.client.util.InputUtil; import net.minecraft.screen.slot.Slot; import net.minecraft.text.TranslatableTextContent; import net.minecraft.util.math.MathHelper; @@ -256,12 +254,33 @@ public class MidnightInput { InputManager.STATES.put(btn, state); } } + MathUtil.PolarUtil polarLeft = new MathUtil.PolarUtil(); + MathUtil.PolarUtil polarRight = new MathUtil.PolarUtil(); private void fetchAxeInput(@NotNull MinecraftClient client, @NotNull GLFWGamepadState gamepadState, boolean leftJoycon) { var buffer = gamepadState.axes(); + + polarLeft.calculate(buffer.get(GLFW_GAMEPAD_AXIS_LEFT_X), buffer.get(GLFW_GAMEPAD_AXIS_LEFT_Y), 1, MidnightControlsConfig.leftDeadZone); + float leftX = polarLeft.polarX; + float leftY = polarLeft.polarY; + polarRight.calculate(buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_X), buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_Y), 1, MidnightControlsConfig.rightDeadZone); + float rightX = polarRight.polarX; + float rightY = polarRight.polarY; + for (int i = 0; i < buffer.limit(); i++) { int axis = leftJoycon ? ButtonBinding.controller2Button(i) : i; float value = buffer.get(); + + if (MidnightControlsConfig.analogMovement) { + switch (i) { + case GLFW_GAMEPAD_AXIS_LEFT_X -> value = leftX; + case GLFW_GAMEPAD_AXIS_LEFT_Y -> value = leftY; + } + } + switch (i) { + case GLFW_GAMEPAD_AXIS_RIGHT_X -> value = rightX; + case GLFW_GAMEPAD_AXIS_RIGHT_Y -> value = rightY; + } float absValue = Math.abs(value); if (i == GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y) @@ -271,24 +290,26 @@ public class MidnightInput { if (!(client.currentScreen instanceof RingScreen || (MidnightControlsCompat.isEmotecraftPresent() && EmotecraftCompat.isEmotecraftScreen(client.currentScreen)))) this.handleAxe(client, axis, value, absValue, state); } if (client.currentScreen instanceof RingScreen || (MidnightControlsCompat.isEmotecraftPresent() && EmotecraftCompat.isEmotecraftScreen(client.currentScreen))) { - float x = Math.abs(buffer.get(GLFW_GAMEPAD_AXIS_LEFT_X)) > MidnightControlsConfig.leftDeadZone ? buffer.get(GLFW_GAMEPAD_AXIS_LEFT_X) : 0; - float y = Math.abs(buffer.get(GLFW_GAMEPAD_AXIS_LEFT_Y)) > MidnightControlsConfig.leftDeadZone ? buffer.get(GLFW_GAMEPAD_AXIS_LEFT_Y) : 0; + float x = leftX; + float y = leftY; + if (x == 0 && y == 0) { - x = Math.abs(buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_X)) > MidnightControlsConfig.rightDeadZone ? buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_X) : 0; - y = Math.abs(buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_Y)) > MidnightControlsConfig.rightDeadZone ? buffer.get(GLFW_GAMEPAD_AXIS_RIGHT_Y) : 0; + x = rightX; + y = rightY; } int index = -1; - if (x < 0) { - if (y < 0) index = 0; - if (y == 0) index = 3; - if (y > 0) index = 5; - } else if (x == 0) { - if (y < 0) index = 1; - if (y > 0) index = 6; - } else if (x > 0) { - if (y < 0) index = 2; - if (y == 0) index = 4; - if (y > 0) index = 7; + float border = 0.3f; + if (x < -border) { + index = 3; + if (y < -border) index = 0; + else if (y > border) index = 5; + } else if (x > border) { + index = 4; + if (y < -border) index = 2; + else if (y > border) index = 7; + } else { + if (y < -border) index = 1; + else if (y > border) index = 6; } if (client.currentScreen instanceof RingScreen && index > -1) RingPage.selected = index; if (MidnightControlsCompat.isEmotecraftPresent() && EmotecraftCompat.isEmotecraftScreen(client.currentScreen)) EmotecraftCompat.handleEmoteSelector(index); @@ -448,7 +469,6 @@ public class MidnightInput { private void handleAxe(@NotNull MinecraftClient client, int axis, float value, float absValue, int state) { int asButtonState = value > .5f ? 1 : (value < -.5f ? 2 : 0); - if (axis == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axis == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER || axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER) || axis == ButtonBinding.controller2Button(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER)) { @@ -468,8 +488,12 @@ public class MidnightInput { } { - boolean currentPlusState = asButtonState == 1; - boolean currentMinusState = asButtonState == 2; + boolean currentPlusState = value > getDeadZoneValue(axis); + boolean currentMinusState = value < -getDeadZoneValue(axis); + if (!MidnightControlsConfig.analogMovement && (axis == GLFW_GAMEPAD_AXIS_LEFT_X || axis == GLFW_GAMEPAD_AXIS_LEFT_Y)) { + currentPlusState = asButtonState == 1; + currentMinusState = asButtonState == 2; + } var previousPlusState = InputManager.STATES.getOrDefault(ButtonBinding.axisAsButton(axis, true), ButtonState.NONE); var previousMinusState = InputManager.STATES.getOrDefault(ButtonBinding.axisAsButton(axis, false), ButtonState.NONE); @@ -495,9 +519,13 @@ public class MidnightInput { } } - double deadZone = this.getDeadZoneValue(axis); - float axisValue = absValue < deadZone ? 0.f : (float) (absValue - deadZone); - axisValue /= (1.0 - deadZone); + float axisValue = absValue; + if (!MidnightControlsConfig.analogMovement) { + double deadZone = this.getDeadZoneValue(axis); + axisValue = (float) (absValue - deadZone); + axisValue /= (1.0 - deadZone); + axisValue *= deadZone; + } axisValue = (float) Math.min(axisValue / MidnightControlsConfig.getAxisMaxValue(axis), 1); if (currentPlusState) @@ -597,8 +625,6 @@ public class MidnightInput { } } - absValue -= deadZone; - absValue /= (1.0 - deadZone); absValue = (float) MathHelper.clamp(absValue / MidnightControlsConfig.getAxisMaxValue(axis), 0.f, 1.f); if (client.currentScreen == null) { // Handles the look direction. @@ -746,6 +772,10 @@ public class MidnightInput { } return true; } + private double prevX = 0; + private double prevY = 0; + private double xValue; + private int xState; /** * Handles the look direction input. @@ -757,22 +787,38 @@ public class MidnightInput { */ public void handleLook(@NotNull MinecraftClient client, int axis, float value, int state) { // Handles the look direction. - if (client.player != null) { - double powValue = Math.pow(value, 2.0); - if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y) { - if (state == 2) { - this.targetPitch = -MidnightControlsConfig.getRightYAxisSign() * (MidnightControlsConfig.yAxisRotationSpeed * powValue) * 0.11D; - } else if (state == 1) { - this.targetPitch = MidnightControlsConfig.getRightYAxisSign() * (MidnightControlsConfig.yAxisRotationSpeed * powValue) * 0.11D; - } + if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) { + xValue = value; + xState = state; + return; + } + if (axis == GLFW_GAMEPAD_AXIS_RIGHT_Y && client.player != null) { + double yStep = (MidnightControlsConfig.yAxisRotationSpeed / 100) * 0.6000000238418579 + 0.20000000298023224; + double xStep = (MidnightControlsConfig.rotationSpeed / 100) * 0.6000000238418579 + 0.20000000298023224; + + double cursorDeltaX = 2 * xValue - this.prevX; + double cursorDeltaY = 2 * value - this.prevY; + boolean slowdown = client.options.getPerspective().isFirstPerson() && client.player.isUsingSpyglass(); + double x = cursorDeltaX * xStep * (slowdown ? xStep : 1); + double y = cursorDeltaY * yStep * (slowdown ? yStep : 1); + + double powXValue = Math.pow(x, 2.0); + double powYValue = Math.pow(y, 2.0); + + if (state == 2) { + this.targetPitch = -MidnightControlsConfig.getRightYAxisSign() * (MidnightControlsConfig.yAxisRotationSpeed * powYValue) * 0.11D; + } else if (state == 1) { + this.targetPitch = MidnightControlsConfig.getRightYAxisSign() * (MidnightControlsConfig.yAxisRotationSpeed * powYValue) * 0.11D; } - if (axis == GLFW_GAMEPAD_AXIS_RIGHT_X) { - if (state == 2) { - this.targetYaw = -MidnightControlsConfig.getRightXAxisSign() * (MidnightControlsConfig.rotationSpeed * powValue) * 0.11D; - } else if (state == 1) { - this.targetYaw = MidnightControlsConfig.getRightXAxisSign() * (MidnightControlsConfig.rotationSpeed * powValue) * 0.11D; - } + + if (xState == 2) { + this.targetYaw = -MidnightControlsConfig.getRightXAxisSign() * (MidnightControlsConfig.rotationSpeed * powXValue) * 0.11D; + } else if (xState == 1) { + this.targetYaw = MidnightControlsConfig.getRightXAxisSign() * (MidnightControlsConfig.rotationSpeed * powXValue) * 0.11D; } + + this.prevY = value; + this.prevX = xValue; } } diff --git a/src/main/java/eu/midnightdust/midnightcontrols/client/controller/Controller.java b/src/main/java/eu/midnightdust/midnightcontrols/client/controller/Controller.java index 87c397b..df1ab8f 100644 --- a/src/main/java/eu/midnightdust/midnightcontrols/client/controller/Controller.java +++ b/src/main/java/eu/midnightdust/midnightcontrols/client/controller/Controller.java @@ -32,6 +32,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import static org.lwjgl.BufferUtils.createByteBuffer; @@ -79,7 +80,7 @@ public record Controller(int id) implements Nameable { * @return the controller's name */ @Override - public String getName() { + public @NotNull String getName() { var name = this.isGamepad() ? GLFW.glfwGetGamepadName(this.id) : GLFW.glfwGetJoystickName(this.id); return name == null ? String.valueOf(this.id()) : name; } @@ -145,6 +146,9 @@ public record Controller(int id) implements Nameable { * Updates the controller mappings. */ public static void updateMappings() { + CompletableFuture.supplyAsync(Controller::updateMappingsSync); + } + private static boolean updateMappingsSync() { try { MidnightControlsClient.get().log("Updating controller mappings..."); File databaseFile = new File("config/gamecontrollerdatabase.txt"); @@ -161,7 +165,7 @@ public record Controller(int id) implements Nameable { var database = ioResourceToBuffer(databaseFile.getPath(), 1024); if (database != null) GLFW.glfwUpdateGamepadMappings(database); if (!MidnightControlsClient.MAPPINGS_FILE.exists()) - return; + return false; var buffer = ioResourceToBuffer(MidnightControlsClient.MAPPINGS_FILE.getPath(), 1024); if (buffer != null) GLFW.glfwUpdateGamepadMappings(buffer); } catch (IOException e) { @@ -199,5 +203,6 @@ public record Controller(int id) implements Nameable { controller.isGamepad())); } } + return true; } } diff --git a/src/main/java/eu/midnightdust/midnightcontrols/client/controller/MovementHandler.java b/src/main/java/eu/midnightdust/midnightcontrols/client/controller/MovementHandler.java index b7d45dd..aed250f 100644 --- a/src/main/java/eu/midnightdust/midnightcontrols/client/controller/MovementHandler.java +++ b/src/main/java/eu/midnightdust/midnightcontrols/client/controller/MovementHandler.java @@ -12,6 +12,7 @@ package eu.midnightdust.midnightcontrols.client.controller; import eu.midnightdust.midnightcontrols.client.ButtonState; import eu.midnightdust.midnightcontrols.client.MidnightControlsClient; import eu.midnightdust.midnightcontrols.client.MidnightControlsConfig; +import eu.midnightdust.midnightcontrols.client.util.MathUtil; import net.minecraft.client.MinecraftClient; import net.minecraft.client.input.Input; import net.minecraft.client.network.ClientPlayerEntity; @@ -43,6 +44,7 @@ public final class MovementHandler implements PressAction { private float slowdownFactor = 1.f; private float movementForward = 0.f; private float movementSideways = 0.f; + private MathUtil.PolarUtil polarUtil = new MathUtil.PolarUtil(); private MovementHandler() { } @@ -53,8 +55,6 @@ public final class MovementHandler implements PressAction { * @param player The client player. */ public void applyMovement(@NotNull ClientPlayerEntity player) { - double movementR, movementTheta; - if (!this.shouldOverrideMovement) return; player.input.pressingForward = this.pressingForward; @@ -62,12 +62,9 @@ public final class MovementHandler implements PressAction { player.input.pressingLeft = this.pressingLeft; player.input.pressingRight = this.pressingRight; - // Do an implicit square here - movementR = this.slowdownFactor * (Math.pow(this.movementSideways, 2) + Math.pow(this.movementForward, 2)); - movementR = MathHelper.clamp(movementR, 0.f, 1.f); - movementTheta = Math.atan2(this.movementForward, this.movementSideways); - player.input.movementForward = (float) (movementR * Math.sin(movementTheta)); - player.input.movementSideways = (float) (movementR * Math.cos(movementTheta)); + polarUtil.calculate(this.movementSideways, this.movementForward, this.slowdownFactor); + player.input.movementForward = polarUtil.polarY; + player.input.movementSideways = polarUtil.polarX; this.shouldOverrideMovement = false; } diff --git a/src/main/java/eu/midnightdust/midnightcontrols/client/gui/TouchscreenOverlay.java b/src/main/java/eu/midnightdust/midnightcontrols/client/gui/TouchscreenOverlay.java index e89cbd9..5cce0da 100644 --- a/src/main/java/eu/midnightdust/midnightcontrols/client/gui/TouchscreenOverlay.java +++ b/src/main/java/eu/midnightdust/midnightcontrols/client/gui/TouchscreenOverlay.java @@ -432,6 +432,7 @@ public class TouchscreenOverlay extends Screen { if (result instanceof BlockHitResult blockHit && firstHitResult instanceof BlockHitResult firstBlock && blockHit.getBlockPos().equals(firstBlock.getBlockPos())) { if (MidnightControlsConfig.debug) System.out.println(blockHit.getBlockPos().toString()); if (client.interactionManager.updateBlockBreakingProgress(blockHit.getBlockPos(), blockHit.getSide())) { + client.particleManager.addBlockBreakingParticles(blockHit.getBlockPos(), blockHit.getSide()); client.player.swingHand(Hand.MAIN_HAND); } else client.interactionManager.cancelBlockBreaking(); firstHitResult = TouchUtils.getTargettedObject(mouseX, mouseY); diff --git a/src/main/java/eu/midnightdust/midnightcontrols/client/util/MathUtil.java b/src/main/java/eu/midnightdust/midnightcontrols/client/util/MathUtil.java new file mode 100644 index 0000000..36c3d8c --- /dev/null +++ b/src/main/java/eu/midnightdust/midnightcontrols/client/util/MathUtil.java @@ -0,0 +1,23 @@ +package eu.midnightdust.midnightcontrols.client.util; + +import net.minecraft.util.math.MathHelper; + +public class MathUtil { + public static class PolarUtil { + public float polarX; + public float polarY; + public PolarUtil() {} + + public void calculate(float x, float y, float speedFactor) { + calculate(x, y, speedFactor, 0); + } + public void calculate(float x, float y, float speedFactor, double deadZone) { + double inputR = Math.pow(x, 2) + Math.pow(y, 2); + inputR = (Math.abs(speedFactor * MathHelper.clamp(inputR,0.f,1.f))); + inputR = inputR < deadZone ? 0f : (inputR-deadZone) / (1f-deadZone); + double inputTheta = Math.atan2(y, x); + polarX = (float) (inputR *Math.cos(inputTheta)); + polarY = (float) (inputR *Math.sin(inputTheta)); + } + } +}