1
0
forked from Clones/Controlify

Another dramatic improvement to gyro control

This commit is contained in:
isXander
2023-06-12 22:03:01 +01:00
parent 4df60549c6
commit 3f820e1c01
8 changed files with 138 additions and 53 deletions

View File

@ -9,7 +9,7 @@ import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.ControllerState; import dev.isxander.controlify.controller.ControllerState;
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager; import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
import dev.isxander.controlify.debug.DebugProperties; import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen; import dev.isxander.controlify.gui.screen.ControllerCalibrationScreen;
import dev.isxander.controlify.gui.screen.SDLOnboardingScreen; import dev.isxander.controlify.gui.screen.SDLOnboardingScreen;
import dev.isxander.controlify.reacharound.ReachAroundHandler; import dev.isxander.controlify.reacharound.ReachAroundHandler;
import dev.isxander.controlify.screenop.ScreenProcessorProvider; import dev.isxander.controlify.screenop.ScreenProcessorProvider;
@ -251,7 +251,7 @@ public class Controlify implements ControlifyApi {
if (!calibrationQueue.isEmpty() && !(minecraft.screen instanceof SDLOnboardingScreen)) { if (!calibrationQueue.isEmpty() && !(minecraft.screen instanceof SDLOnboardingScreen)) {
Screen screen = minecraft.screen; Screen screen = minecraft.screen;
while (!calibrationQueue.isEmpty()) { while (!calibrationQueue.isEmpty()) {
screen = new ControllerDeadzoneCalibrationScreen(calibrationQueue.poll(), screen); screen = new ControllerCalibrationScreen(calibrationQueue.poll(), screen);
} }
minecraft.setScreen(screen); minecraft.setScreen(screen);
} }

View File

@ -12,10 +12,12 @@ public class GamepadConfig extends ControllerConfig {
private transient float rightStickDeadzoneY = rightStickDeadzone; private transient float rightStickDeadzoneY = rightStickDeadzone;
public float gyroLookSensitivity = 0f; public float gyroLookSensitivity = 0f;
public boolean relativeGyroMode = false;
public boolean gyroRequiresButton = true; public boolean gyroRequiresButton = true;
public boolean flickStick = false; public boolean flickStick = false;
public boolean invertGyroX = false; public boolean invertGyroX = false;
public boolean invertGyroY = false; public boolean invertGyroY = false;
public GamepadState.GyroState gyroCalibration = GamepadState.GyroState.ORIGIN;
public BuiltinGamepadTheme theme = BuiltinGamepadTheme.DEFAULT; public BuiltinGamepadTheme theme = BuiltinGamepadTheme.DEFAULT;

View File

@ -22,7 +22,7 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
private final RumbleManager rumbleManager; private final RumbleManager rumbleManager;
private GamepadState.GyroState absoluteGyro = GamepadState.GyroState.ORIGIN; private GamepadState.GyroState absoluteGyro = GamepadState.GyroState.ORIGIN;
private final GamepadDrivers drivers; public final GamepadDrivers drivers;
private final Set<Driver> uniqueDrivers; private final Set<Driver> uniqueDrivers;
private int antiSnapbackTicksL, antiSnapbackTicksR; private int antiSnapbackTicksL, antiSnapbackTicksR;
@ -85,9 +85,9 @@ public class GamepadController extends AbstractController<GamepadState, GamepadC
} }
} }
// todo: make this configurable // TODO: Add some sort of gyro filtering
GamepadState.GyroState gyroState = drivers.gyroDriver().getGyroState().deadzone(0.05f); GamepadState.GyroState gyroState = drivers.gyroDriver().getGyroState().subtracted(config().gyroCalibration);
this.absoluteGyro = this.absoluteGyro.add(gyroState); this.absoluteGyro = this.absoluteGyro.added(gyroState);
state = new GamepadState(deadzoneAxesState, basicState.axes(), basicState.buttons(), gyroState, absoluteGyro); state = new GamepadState(deadzoneAxesState, basicState.axes(), basicState.buttons(), gyroState, absoluteGyro);
} }

View File

@ -3,7 +3,6 @@ package dev.isxander.controlify.controller.gamepad;
import dev.isxander.controlify.controller.ControllerState; import dev.isxander.controlify.controller.ControllerState;
import dev.isxander.controlify.utils.ControllerUtils; import dev.isxander.controlify.utils.ControllerUtils;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@ -206,10 +205,22 @@ public final class GamepadState implements ControllerState {
public record GyroState(float pitch, float yaw, float roll) { public record GyroState(float pitch, float yaw, float roll) {
public static GyroState ORIGIN = new GyroState(0, 0, 0); public static GyroState ORIGIN = new GyroState(0, 0, 0);
public GyroState add(GyroState other) { public GyroState added(GyroState other) {
return new GyroState(pitch + other.pitch, yaw + other.yaw, roll + other.roll); return new GyroState(pitch + other.pitch, yaw + other.yaw, roll + other.roll);
} }
public GyroState subtracted(GyroState other) {
return new GyroState(pitch - other.pitch, yaw - other.yaw, roll - other.roll);
}
public GyroState multiplied(float scalar) {
return new GyroState(pitch * scalar, yaw * scalar, roll * scalar);
}
public GyroState divided(float scalar) {
return new GyroState(pitch / scalar, yaw / scalar, roll / scalar);
}
public GyroState deadzone(float deadzone) { public GyroState deadzone(float deadzone) {
return new GyroState( return new GyroState(
Math.max(pitch - deadzone, 0) + Math.min(pitch + deadzone, 0), Math.max(pitch - deadzone, 0) + Math.min(pitch + deadzone, 0),

View File

@ -3,6 +3,8 @@ package dev.isxander.controlify.gui.screen;
import dev.isxander.controlify.Controlify; import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.ControllerManager; import dev.isxander.controlify.ControllerManager;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.gamepad.GamepadController;
import dev.isxander.controlify.controller.gamepad.GamepadState;
import net.minecraft.ChatFormatting; import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Button;
@ -16,7 +18,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
public class ControllerDeadzoneCalibrationScreen extends Screen { public class ControllerCalibrationScreen extends Screen {
private static final int CALIBRATION_TIME = 100; private static final int CALIBRATION_TIME = 100;
private static final ResourceLocation GUI_BARS_LOCATION = new ResourceLocation("textures/gui/bars.png"); private static final ResourceLocation GUI_BARS_LOCATION = new ResourceLocation("textures/gui/bars.png");
@ -30,13 +32,14 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
protected boolean calibrating = false, calibrated = false; protected boolean calibrating = false, calibrated = false;
protected int calibrationTicks = 0; protected int calibrationTicks = 0;
private final Map<Integer, double[]> calibrationData = new HashMap<>(); private final Map<Integer, double[]> deadzoneCalibration = new HashMap<>();
private GamepadState.GyroState accumulatedGyroVelocity = GamepadState.GyroState.ORIGIN;
public ControllerDeadzoneCalibrationScreen(Controller<?, ?> controller, Screen parent) { public ControllerCalibrationScreen(Controller<?, ?> controller, Screen parent) {
this(controller, () -> parent); this(controller, () -> parent);
} }
public ControllerDeadzoneCalibrationScreen(Controller<?, ?> controller, Supplier<Screen> parent) { public ControllerCalibrationScreen(Controller<?, ?> controller, Supplier<Screen> parent) {
super(Component.translatable("controlify.calibration.title")); super(Component.translatable("controlify.calibration.title"));
this.controller = controller; this.controller = controller;
this.parent = parent; this.parent = parent;
@ -117,15 +120,19 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
if (stateChanged()) { if (stateChanged()) {
calibrationTicks = 0; calibrationTicks = 0;
calibrationData.clear(); deadzoneCalibration.clear();
accumulatedGyroVelocity = GamepadState.GyroState.ORIGIN;
} }
if (calibrationTicks < CALIBRATION_TIME) { if (calibrationTicks < CALIBRATION_TIME) {
calibrate(calibrationTicks); processDeadzoneData(calibrationTicks);
processGyroData();
calibrationTicks++; calibrationTicks++;
} else { } else {
applyDeadzones(); applyDeadzones();
generateGyroCalibration();
calibrating = false; calibrating = false;
calibrated = true; calibrated = true;
readyButton.active = true; readyButton.active = true;
@ -136,22 +143,34 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
} }
} }
private void calibrate(int tick) { private void processDeadzoneData(int tick) {
var axes = controller.state().rawAxes(); var axes = controller.state().rawAxes();
for (int i = 0; i < axes.size(); i++) { for (int i = 0; i < axes.size(); i++) {
var axis = Math.abs(axes.get(i)); var axis = Math.abs(axes.get(i));
calibrationData.computeIfAbsent(i, k -> new double[CALIBRATION_TIME])[tick] = axis; deadzoneCalibration.computeIfAbsent(i, k -> new double[CALIBRATION_TIME])[tick] = axis;
}
}
private void processGyroData() {
if (controller instanceof GamepadController gamepad && gamepad.hasGyro()) {
accumulatedGyroVelocity = accumulatedGyroVelocity.added(gamepad.drivers.gyroDriver().getGyroState());
} }
} }
private void applyDeadzones() { private void applyDeadzones() {
calibrationData.forEach((i, data) -> { deadzoneCalibration.forEach((i, data) -> {
var max = Arrays.stream(data).max().orElseThrow(); var max = Arrays.stream(data).max().orElseThrow();
controller.config().setDeadzone(i, (float) max + 0.05f); controller.config().setDeadzone(i, (float) max + 0.05f);
}); });
} }
private void generateGyroCalibration() {
if (controller instanceof GamepadController gamepad && gamepad.hasGyro()) {
gamepad.config().gyroCalibration = accumulatedGyroVelocity.divided(CALIBRATION_TIME);
}
}
private boolean stateChanged() { private boolean stateChanged() {
var amt = 0.4f; var amt = 0.4f;

View File

@ -272,7 +272,7 @@ public class ControllerConfigScreenFactory {
.description(OptionDescription.createBuilder() .description(OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.auto_calibration.tooltip")) .text(Component.translatable("controlify.gui.auto_calibration.tooltip"))
.build()) .build())
.action((screen, button) -> Minecraft.getInstance().setScreen(new ControllerDeadzoneCalibrationScreen(controller, () -> { .action((screen, button) -> Minecraft.getInstance().setScreen(new ControllerCalibrationScreen(controller, () -> {
deadzoneOpts.forEach(Option::forgetPendingValue); deadzoneOpts.forEach(Option::forgetPendingValue);
return screen; return screen;
}))) })))
@ -432,6 +432,17 @@ public class ControllerConfigScreenFactory {
o.requestSetDefault(); o.requestSetDefault();
})) }))
.build()); .build());
var relativeModeOpt = Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.gyro_behaviour"))
.description(val -> OptionDescription.createBuilder()
.text(Component.translatable("controlify.gui.gyro_behaviour.tooltip"))
.text(val ? Component.translatable("controlify.gui.gyro_behaviour.relative.tooltip") : Component.translatable("controlify.gui.gyro_behaviour.absolute.tooltip"))
.build())
.binding(gpCfgDef.relativeGyroMode, () -> gpCfg.relativeGyroMode, v -> gpCfg.relativeGyroMode = v)
.controller(opt -> BooleanControllerBuilder.create(opt)
.valueFormatter(v -> v ? Component.translatable("controlify.gui.gyro_behaviour.relative") : Component.translatable("controlify.gui.gyro_behaviour.absolute")))
.build();
gyroGroup.option(relativeModeOpt);
gyroGroup.option(Util.make(() -> { gyroGroup.option(Util.make(() -> {
var opt = Option.<Boolean>createBuilder() var opt = Option.<Boolean>createBuilder()
.name(Component.translatable("controlify.gui.gyro_invert_x")) .name(Component.translatable("controlify.gui.gyro_invert_x"))
@ -461,6 +472,14 @@ public class ControllerConfigScreenFactory {
.binding(gpCfgDef.gyroRequiresButton, () -> gpCfg.gyroRequiresButton, v -> gpCfg.gyroRequiresButton = v) .binding(gpCfgDef.gyroRequiresButton, () -> gpCfg.gyroRequiresButton, v -> gpCfg.gyroRequiresButton = v)
.controller(TickBoxControllerBuilder::create) .controller(TickBoxControllerBuilder::create)
.available(gyroSensitivity.pendingValue() > 0) .available(gyroSensitivity.pendingValue() > 0)
.listener((o, val) -> {
if (val) {
relativeModeOpt.setAvailable(gyroSensitivity.pendingValue() > 0);
} else {
relativeModeOpt.setAvailable(false);
relativeModeOpt.requestSet(false);
}
})
.build(); .build();
gyroOptions.add(opt); gyroOptions.add(opt);
return opt; return opt;

View File

@ -6,6 +6,7 @@ import dev.isxander.controlify.api.ingameinput.LookInputModifier;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.api.event.ControlifyEvents; import dev.isxander.controlify.api.event.ControlifyEvents;
import dev.isxander.controlify.controller.gamepad.GamepadController; import dev.isxander.controlify.controller.gamepad.GamepadController;
import dev.isxander.controlify.controller.gamepad.GamepadState;
import dev.isxander.controlify.utils.Animator; import dev.isxander.controlify.utils.Animator;
import dev.isxander.controlify.utils.Easings; import dev.isxander.controlify.utils.Easings;
import dev.isxander.controlify.utils.NavigationHelper; import dev.isxander.controlify.utils.NavigationHelper;
@ -17,8 +18,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionHand;
import org.joml.Vector2f; import net.minecraft.world.entity.player.Player;
import org.joml.Vector2fc;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction; import java.util.function.BiFunction;
@ -30,6 +30,9 @@ public class InGameInputHandler {
private double lookInputX, lookInputY; private double lookInputX, lookInputY;
private boolean shouldShowPlayerList; private boolean shouldShowPlayerList;
private GamepadState.GyroState gyroInput = GamepadState.GyroState.ORIGIN;
private boolean wasAiming;
private final NavigationHelper dropRepeatHelper; private final NavigationHelper dropRepeatHelper;
public InGameInputHandler(Controller<?, ?> controller) { public InGameInputHandler(Controller<?, ?> controller) {
@ -130,53 +133,68 @@ public class InGameInputHandler {
return; return;
} }
var isAiming = isAiming(player);
float impulseY = 0f;
float impulseX = 0f;
// flick stick - turn 90 degrees immediately upon turning // flick stick - turn 90 degrees immediately upon turning
// should be paired with gyro controls // should be paired with gyro controls
if (gamepad != null && gamepad.config().flickStick) { if (gamepad != null && gamepad.config().flickStick) {
var turnAngle = 90 / 0.15f; // Entity#turn multiplies cursor delta by 0.15 to get rotation var turnAngle = 90 / 0.15f; // Entity#turn multiplies cursor delta by 0.15 to get rotation
AtomicReference<Float> lastAngle = new AtomicReference<>(0f); float flick = controller.bindings().LOOK_DOWN.justPressed() || controller.bindings().LOOK_RIGHT.justPressed() ? 1 : controller.bindings().LOOK_UP.justPressed() || controller.bindings().LOOK_LEFT.justPressed() ? -1 : 0;
Vector2fc flickVec = new Vector2f(
controller.bindings().LOOK_RIGHT.justPressed() ? 1 : controller.bindings().LOOK_LEFT.justPressed() ? -1 : 0,
controller.bindings().LOOK_DOWN.justPressed() ? 1 : controller.bindings().LOOK_UP.justPressed() ? -1 : 0
);
if (!flickVec.equals(0, 0)) { if (flick != 0f) {
AtomicReference<Float> lastAngle = new AtomicReference<>(0f);
Animator.INSTANCE.play(new Animator.AnimationInstance(10, Easings::easeOutExpo) Animator.INSTANCE.play(new Animator.AnimationInstance(10, Easings::easeOutExpo)
.addConsumer(angle -> { .addConsumer(angle -> {
player.turn((angle - lastAngle.get()) * flickVec.x(), (angle - lastAngle.get()) * flickVec.y()); player.turn((angle - lastAngle.get()) * flick, 0);
lastAngle.set(angle); lastAngle.set(angle);
}, 0, turnAngle)); }, 0, turnAngle));
} }
} else {
// normal look input
impulseY = controller.bindings().LOOK_DOWN.state() - controller.bindings().LOOK_UP.state();
impulseX = controller.bindings().LOOK_RIGHT.state() - controller.bindings().LOOK_LEFT.state();
impulseX *= Math.abs(impulseX);
impulseY *= Math.abs(impulseY);
return; if (controller.config().reduceAimingSensitivity && player != null && player.isUsingItem()) {
} float aimMultiplier = switch (player.getUseItem().getUseAnimation()) {
case BOW, CROSSBOW, SPEAR -> 0.6f;
// normal look input case SPYGLASS -> 0.2f;
var impulseY = controller.bindings().LOOK_DOWN.state() - controller.bindings().LOOK_UP.state(); default -> 1f;
var impulseX = controller.bindings().LOOK_RIGHT.state() - controller.bindings().LOOK_LEFT.state(); };
impulseX *= Math.abs(impulseX); impulseX *= aimMultiplier;
impulseY *= Math.abs(impulseY); impulseY *= aimMultiplier;
}
if (controller.config().reduceAimingSensitivity && player != null && player.isUsingItem()) {
float aimMultiplier = switch (player.getUseItem().getUseAnimation()) {
case BOW, CROSSBOW, SPEAR -> 0.6f;
case SPYGLASS -> 0.2f;
default -> 1f;
};
impulseX *= aimMultiplier;
impulseY *= aimMultiplier;
} }
// gyro input // gyro input
if (gamepad != null if (gamepad != null && gamepad.hasGyro()) {
&& gamepad.hasGyro() boolean useGyro = false;
&& (!gamepad.config().gyroRequiresButton || gamepad.bindings().GAMEPAD_GYRO_BUTTON.held())
) {
var gyroDelta = gamepad.absoluteGyroState().deadzone(0.05f);
impulseY += -gyroDelta.pitch() * gamepad.config().gyroLookSensitivity * (gamepad.config().invertGyroY ? -1 : 1); if (gamepad.config().gyroRequiresButton) {
impulseX += (-gyroDelta.roll() + -gyroDelta.yaw()) * gamepad.config().gyroLookSensitivity * (gamepad.config().invertGyroX ? -1 : 1); if (gamepad.bindings().GAMEPAD_GYRO_BUTTON.justPressed() || (isAiming && !wasAiming))
gyroInput = GamepadState.GyroState.ORIGIN;
if (gamepad.bindings().GAMEPAD_GYRO_BUTTON.held() || isAiming) {
if (gamepad.config().relativeGyroMode)
gyroInput = gyroInput.added(gamepad.state().gyroDelta().multiplied(0.1f));
else
gyroInput = gamepad.state().gyroDelta();
useGyro = true;
}
} else {
gyroInput = gamepad.state().gyroDelta();
useGyro = true;
}
if (useGyro) {
impulseY += -gyroInput.pitch() * gamepad.config().gyroLookSensitivity * (gamepad.config().invertGyroY ? -1 : 1);
impulseX += (-gyroInput.roll() + -gyroInput.yaw()) * gamepad.config().gyroLookSensitivity * (gamepad.config().invertGyroX ? -1 : 1);
}
} }
LookInputModifier lookInputModifier = ControlifyEvents.LOOK_INPUT_MODIFIER.invoker(); LookInputModifier lookInputModifier = ControlifyEvents.LOOK_INPUT_MODIFIER.invoker();
@ -185,6 +203,8 @@ public class InGameInputHandler {
lookInputX = impulseX * controller.config().horizontalLookSensitivity * 65f; lookInputX = impulseX * controller.config().horizontalLookSensitivity * 65f;
lookInputY = impulseY * controller.config().verticalLookSensitivity * 65f; lookInputY = impulseY * controller.config().verticalLookSensitivity * 65f;
wasAiming = isAiming;
} }
public void processPlayerLook(float deltaTime) { public void processPlayerLook(float deltaTime) {
@ -197,6 +217,13 @@ public class InGameInputHandler {
return this.shouldShowPlayerList; return this.shouldShowPlayerList;
} }
private boolean isAiming(Player player) {
return player.isUsingItem() && switch (player.getUseItem().getUseAnimation()) {
case BOW, CROSSBOW, SPEAR, SPYGLASS -> true;
default -> false;
};
}
public record FunctionalLookInputModifier(BiFunction<Float, Controller<?, ?>, Float> x, BiFunction<Float, Controller<?, ?>, Float> y) implements LookInputModifier { public record FunctionalLookInputModifier(BiFunction<Float, Controller<?, ?>, Float> x, BiFunction<Float, Controller<?, ?>, Float> y) implements LookInputModifier {
@Override @Override
public float modifyX(float x, Controller<?, ?> controller) { public float modifyX(float x, Controller<?, ?> controller) {

View File

@ -80,6 +80,12 @@
"controlify.gui.group.gyro.no_gyro.tooltip": "This controller does not support Gyro. You must have a DualSense™ controller or other compatible controller to use this feature.", "controlify.gui.group.gyro.no_gyro.tooltip": "This controller does not support Gyro. You must have a DualSense™ controller or other compatible controller to use this feature.",
"controlify.gui.gyro_look_sensitivity": "Look Sensitivity", "controlify.gui.gyro_look_sensitivity": "Look Sensitivity",
"controlify.gui.gyro_look_sensitivity.tooltip": "How much the camera moves based on gyroscope rotation.\nThe pitch (rotating your controller forward/backward) is used for looking up and down, whilst both the roll (rotating your controller left/right) and yaw (rotating your controller clockwise/anticlockwise) are used for looking left and right.", "controlify.gui.gyro_look_sensitivity.tooltip": "How much the camera moves based on gyroscope rotation.\nThe pitch (rotating your controller forward/backward) is used for looking up and down, whilst both the roll (rotating your controller left/right) and yaw (rotating your controller clockwise/anticlockwise) are used for looking left and right.",
"controlify.gui.gyro_behaviour": "Gyro Behaviour",
"controlify.gui.gyro_behaviour.tooltip": "How the gyroscope input should be interpreted as look input.",
"controlify.gui.gyro_behaviour.absolute": "Absolute",
"controlify.gui.gyro_behaviour.absolute.tooltip": "Absolute: Equivalent to moving the mouse in a direction.",
"controlify.gui.gyro_behaviour.relative": "Relative",
"controlify.gui.gyro_behaviour.relative.tooltip": "Relative: Equivalent to moving a thumbstick in a direction.",
"controlify.gui.gyro_invert_x": "Invert X", "controlify.gui.gyro_invert_x": "Invert X",
"controlify.gui.gyro_invert_x.tooltip": "Invert the left/right rotation of the gyroscope look direction.", "controlify.gui.gyro_invert_x.tooltip": "Invert the left/right rotation of the gyroscope look direction.",
"controlify.gui.gyro_invert_y": "Invert Y", "controlify.gui.gyro_invert_y": "Invert Y",
@ -156,6 +162,7 @@
"controlify.controller_theme.dualshock4": "PS4", "controlify.controller_theme.dualshock4": "PS4",
"controlify.controller_theme.steam_deck": "Steam Deck", "controlify.controller_theme.steam_deck": "Steam Deck",
"controlify.binding.controlify.gamepad_gyro_button": "Activate Gyroscope",
"controlify.binding.controlify.walk_forward": "Walk Forward", "controlify.binding.controlify.walk_forward": "Walk Forward",
"controlify.binding.controlify.walk_backward": "Walk Backward", "controlify.binding.controlify.walk_backward": "Walk Backward",
"controlify.binding.controlify.strafe_left": "Strafe Left", "controlify.binding.controlify.strafe_left": "Strafe Left",
@ -251,8 +258,8 @@
"controlify.guide.container.quick_move": "Quick Move", "controlify.guide.container.quick_move": "Quick Move",
"controlify.calibration.title": "Controller Calibration for '%s'", "controlify.calibration.title": "Controller Calibration for '%s'",
"controlify.calibration.info": "This process will optimize settings for your controller to prevent stick drift. Stick drift happens in your controller thumbsticks and outputs slightly wrong values when you aren't touching them at all. Deadzones are used to prevent this.\nShaking your controller lightly (without touching thumbsticks) can also aid in getting a more precise calibration.\n\nThis will only take a few seconds.", "controlify.calibration.info": "This calibration optimizes your controller settings to remove stickdrift and create an accurate reading for the gyroscope (if your controller supports it). Before hitting start, make sure to place your controller down on a flat surface and you are not touching the thumbsticks.\n\nThis will only take a few seconds.",
"controlify.calibration.wait": "Please do not touch your controller thumbsticks until the progress bar is complete. Shaking your controller lightly can improve calibration.\nThis process will only take a few seconds.", "controlify.calibration.wait": "Please do not touch your controller thumbsticks until the progress bar is complete. Make sure to not touch your controller.\nThis process will only take a few seconds.",
"controlify.calibration.complete": "Calibration complete! You can now use your controller. Press done to return to the game.", "controlify.calibration.complete": "Calibration complete! You can now use your controller. Press done to return to the game.",
"controlify.calibration.ready": "Ready", "controlify.calibration.ready": "Ready",
"controlify.calibration.done": "Done", "controlify.calibration.done": "Done",