forked from Clones/Controlify
Migrate to libsdl4j, SDL download screen, use gamecontrollerdb.txt
, calibration now detects joystick triggers
This commit is contained in:
@ -5,6 +5,8 @@ import dev.isxander.controlify.ControllerManager;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
@ -15,11 +17,17 @@ import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ControllerCalibrationScreen extends Screen {
|
||||
/**
|
||||
* Controller calibration screen does a few things:
|
||||
* <ul>
|
||||
* <li>Calculates deadzones</li>
|
||||
* <li>Does gyroscope calibration</li>
|
||||
* <li>Detects triggers on unmapped joysticks</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ControllerCalibrationScreen extends Screen implements DontInteruptScreen {
|
||||
private static final int CALIBRATION_TIME = 100;
|
||||
private static final ResourceLocation GUI_BARS_LOCATION = new ResourceLocation("textures/gui/bars.png");
|
||||
|
||||
@ -33,7 +41,7 @@ public class ControllerCalibrationScreen extends Screen {
|
||||
protected boolean calibrating = false, calibrated = false;
|
||||
protected int calibrationTicks = 0;
|
||||
|
||||
private final Map<Integer, double[]> deadzoneCalibration = new HashMap<>();
|
||||
private final double[] axisData;
|
||||
private GamepadState.GyroState accumulatedGyroVelocity = new GamepadState.GyroState();
|
||||
|
||||
public ControllerCalibrationScreen(Controller<?, ?> controller, Screen parent) {
|
||||
@ -44,6 +52,7 @@ public class ControllerCalibrationScreen extends Screen {
|
||||
super(Component.translatable("controlify.calibration.title"));
|
||||
this.controller = controller;
|
||||
this.parent = parent;
|
||||
this.axisData = new double[controller.axisCount() * CALIBRATION_TIME];
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -125,17 +134,17 @@ public class ControllerCalibrationScreen extends Screen {
|
||||
|
||||
if (stateChanged()) {
|
||||
calibrationTicks = 0;
|
||||
deadzoneCalibration.clear();
|
||||
Arrays.fill(axisData, 0);
|
||||
accumulatedGyroVelocity = new GamepadState.GyroState();
|
||||
}
|
||||
|
||||
if (calibrationTicks < CALIBRATION_TIME) {
|
||||
processDeadzoneData(calibrationTicks);
|
||||
processAxisData(calibrationTicks);
|
||||
processGyroData();
|
||||
|
||||
calibrationTicks++;
|
||||
} else {
|
||||
applyDeadzones();
|
||||
calibrateAxis();
|
||||
generateGyroCalibration();
|
||||
|
||||
calibrating = false;
|
||||
@ -145,17 +154,16 @@ public class ControllerCalibrationScreen extends Screen {
|
||||
|
||||
controller.config().deadzonesCalibrated = true;
|
||||
controller.config().delayedCalibration = false;
|
||||
Controlify.instance().config().save();
|
||||
// no need to save because of setCurrentController
|
||||
|
||||
Controlify.instance().setCurrentController(controller);
|
||||
}
|
||||
}
|
||||
|
||||
private void processDeadzoneData(int tick) {
|
||||
private void processAxisData(int tick) {
|
||||
var axes = controller.state().rawAxes();
|
||||
|
||||
for (int i = 0; i < axes.size(); i++) {
|
||||
var axis = Math.abs(axes.get(i));
|
||||
deadzoneCalibration.computeIfAbsent(i, k -> new double[CALIBRATION_TIME])[tick] = axis;
|
||||
}
|
||||
System.arraycopy(axes.stream().mapToDouble(a -> a).toArray(), 0, axisData, tick * axes.size(), axes.size());
|
||||
}
|
||||
|
||||
private void processGyroData() {
|
||||
@ -164,11 +172,30 @@ public class ControllerCalibrationScreen extends Screen {
|
||||
}
|
||||
}
|
||||
|
||||
private void applyDeadzones() {
|
||||
deadzoneCalibration.forEach((i, data) -> {
|
||||
var max = Arrays.stream(data).max().orElseThrow();
|
||||
controller.config().setDeadzone(i, (float) max + 0.08f);
|
||||
});
|
||||
private void calibrateAxis() {
|
||||
int axisCount = controller.axisCount();
|
||||
for (int axis = 0; axis < axisCount; axis++) {
|
||||
boolean triggerAxis = true;
|
||||
float maxAbs = 0;
|
||||
|
||||
for (int tick = 0; tick < CALIBRATION_TIME; tick++) {
|
||||
float axisValue = (float) axisData[tick * axisCount + axis];
|
||||
|
||||
if (axisValue != -1) {
|
||||
triggerAxis = false;
|
||||
}
|
||||
|
||||
maxAbs = Math.max(maxAbs, Math.abs(axisValue));
|
||||
}
|
||||
|
||||
if (triggerAxis && controller instanceof JoystickController<?> joystick && joystick.mapping() instanceof UnmappedJoystickMapping mapping) {
|
||||
joystick.config().setDeadzone(axis, 0.0f);
|
||||
joystick.config().setTriggerAxis(axis, true);
|
||||
mapping.setTriggerAxes(axis, true);
|
||||
} else {
|
||||
controller.config().setDeadzone(axis, maxAbs + 0.08f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateGyroCalibration() {
|
||||
|
@ -7,7 +7,7 @@ import dev.isxander.controlify.api.buttonguide.ButtonGuideApi;
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonGuidePredicate;
|
||||
import dev.isxander.controlify.api.buttonguide.ButtonRenderPosition;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.driver.SDL2NativesManager;
|
||||
import dev.isxander.controlify.gui.components.FakePositionPlainTextButton;
|
||||
import dev.isxander.controlify.screenop.ScreenControllerEventListener;
|
||||
import dev.isxander.controlify.utils.Animator;
|
||||
@ -57,23 +57,12 @@ public class ControllerCarouselScreen extends Screen implements ScreenController
|
||||
public static Screen createConfigScreen(Screen parent) {
|
||||
var controlify = Controlify.instance();
|
||||
|
||||
if (!controlify.config().globalSettings().vibrationOnboarded) {
|
||||
return new SDLOnboardingScreen(() -> new ControllerCarouselScreen(parent), yes -> {
|
||||
if (yes) {
|
||||
SDL2NativesManager.initialise();
|
||||
|
||||
if (controlify.config().globalSettings().delegateSetup) {
|
||||
controlify.discoverControllers();
|
||||
controlify.config().globalSettings().delegateSetup = false;
|
||||
controlify.config().save();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (Controlify.instance().config().globalSettings().delegateSetup) {
|
||||
if (controlify.config().globalSettings().delegateSetup) {
|
||||
controlify.discoverControllers();
|
||||
controlify.config().globalSettings().delegateSetup = false;
|
||||
controlify.config().save();
|
||||
}
|
||||
|
||||
return new ControllerCarouselScreen(parent);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.api.bind.ControllerBinding;
|
||||
import dev.isxander.controlify.bindings.BindContext;
|
||||
|
@ -0,0 +1,4 @@
|
||||
package dev.isxander.controlify.gui.screen;
|
||||
|
||||
public interface DontInteruptScreen {
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.driver.SDL2NativesManager;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.MultiLineTextWidget;
|
||||
import net.minecraft.client.gui.components.PlainTextButton;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.network.chat.CommonComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.HoverEvent;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class DownloadingSDLScreen extends Screen implements DontInteruptScreen {
|
||||
private static final ResourceLocation GUI_BARS_LOCATION = new ResourceLocation("textures/gui/bars.png");
|
||||
|
||||
private final Screen screenOnFinish;
|
||||
private final Path nativePath;
|
||||
|
||||
private long receivedBytes;
|
||||
private final long totalBytes;
|
||||
private final DecimalFormat format = new DecimalFormat("0.00 MB");
|
||||
|
||||
public DownloadingSDLScreen(Screen screenOnFinish, long totalBytes, Path nativePath) {
|
||||
super(Component.translatable("controlify.downloading_sdl.title"));
|
||||
this.screenOnFinish = screenOnFinish;
|
||||
this.nativePath = nativePath;
|
||||
this.totalBytes = totalBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
Component filePathText = Component.literal(nativePath.getFileName().toString())
|
||||
.withStyle(ChatFormatting.BLUE);
|
||||
addRenderableWidget(new PlainTextButton(
|
||||
width / 2 - font.width(filePathText) / 2,
|
||||
(int) (30 + 9 * 2.5f + 40 + 5 * 2f + 10),
|
||||
font.width(filePathText),
|
||||
font.lineHeight,
|
||||
filePathText,
|
||||
btn -> Util.getPlatform().openFile(nativePath.toFile()),
|
||||
font
|
||||
));
|
||||
|
||||
addRenderableWidget(new MultiLineTextWidget(
|
||||
width / 2 - (width - 50) / 2,
|
||||
(int) (30 + 9 * 2.5f + 40 + 5 * 2f + 10 + 9*3),
|
||||
Component.translatable("controlify.downloading_sdl.info"),
|
||||
font
|
||||
).setMaxWidth(width - 20).setCentered(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
|
||||
renderDirtBackground(graphics);
|
||||
|
||||
super.render(graphics, mouseX, mouseY, delta);
|
||||
|
||||
graphics.pose().pushPose();
|
||||
graphics.pose().translate(width / 2f - font.width(this.getTitle()) / 2f * 2.5f, 30, 0);
|
||||
graphics.pose().scale(2.5f, 2.5f, 1f);
|
||||
|
||||
graphics.drawString(font, this.getTitle(), 0, 0, -1);
|
||||
|
||||
graphics.pose().popPose();
|
||||
|
||||
drawProgressBar(graphics, width / 2, (int) (30 + 9 * 2.5f + 40), (float) ((double) receivedBytes / totalBytes));
|
||||
|
||||
String totalString = format.format(totalBytes / 1024f / 1024f);
|
||||
graphics.drawString(
|
||||
font,
|
||||
totalString,
|
||||
(int) (width / 2f + 182 * 2f / 2 - font.width(totalString)),
|
||||
(int) (30 + 9 * 2f + 40 + 5 * 2f + 4),
|
||||
11184810 // light gray
|
||||
);
|
||||
|
||||
String receivedString = format.format(receivedBytes / 1024f / 1024f);
|
||||
graphics.drawString(
|
||||
font,
|
||||
receivedString,
|
||||
(int) (width / 2f - 182 * 2f / 2),
|
||||
(int) (30 + 9 * 2f + 40 + 5 * 2f + 4),
|
||||
11184810 // light gray
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added() {
|
||||
CompletableFuture<Boolean> askNativesFuture = Controlify.instance().askNatives();
|
||||
if (askNativesFuture.isDone()) {
|
||||
minecraft.setScreen(screenOnFinish);
|
||||
}
|
||||
}
|
||||
|
||||
public void updateDownloadProgress(long receivedBytes) {
|
||||
this.receivedBytes = receivedBytes;
|
||||
}
|
||||
|
||||
public void finishDownload() {
|
||||
minecraft.setScreen(screenOnFinish);
|
||||
}
|
||||
|
||||
public void failDownload(Throwable th) {
|
||||
finishDownload();
|
||||
}
|
||||
|
||||
private static void drawProgressBar(GuiGraphics graphics, int centerX, int y, float progress) {
|
||||
int x = (int) (centerX - 182 * 2f / 2);
|
||||
|
||||
graphics.pose().pushPose();
|
||||
graphics.pose().translate(x, y, 0);
|
||||
graphics.pose().scale(2f, 2f, 1f);
|
||||
|
||||
graphics.blit(GUI_BARS_LOCATION, 0, 0, 0, 30, 182, 5);
|
||||
graphics.blit(GUI_BARS_LOCATION, 0, 0, 0, 35, (int)(progress * 182), 5);
|
||||
|
||||
graphics.pose().popPose();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public class ModConfigOpenerScreen extends Screen {
|
||||
private final Screen lastScreen;
|
||||
|
||||
public ModConfigOpenerScreen(Screen lastScreen) {
|
||||
super(Component.empty());
|
||||
this.lastScreen = lastScreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added() {
|
||||
// need to make sure fabric api has registered all its events
|
||||
// because calling setScreen before this will cause fapi to freak
|
||||
// out that it has no remove event and crash the whole game lol
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
this.init(minecraft, minecraft.getWindow().getGuiScaledWidth(), minecraft.getWindow().getGuiScaledHeight());
|
||||
|
||||
Controlify.instance().askNatives()
|
||||
.whenComplete((result, error) ->
|
||||
minecraft.setScreen(ControllerCarouselScreen.createConfigScreen(lastScreen))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void triggerImmediateNarration(boolean useTranslationsCache) {
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.gui.screen;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.sdl2.SDL2NativesManager;
|
||||
import dev.isxander.controlify.driver.SDL2NativesManager;
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanConsumer;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
@ -12,7 +12,7 @@ import net.minecraft.network.chat.Component;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SDLOnboardingScreen extends ConfirmScreen {
|
||||
public class SDLOnboardingScreen extends ConfirmScreen implements DontInteruptScreen {
|
||||
public SDLOnboardingScreen(Supplier<Screen> lastScreen, BooleanConsumer onAnswered) {
|
||||
super(
|
||||
yes -> {
|
||||
|
@ -22,7 +22,7 @@ import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SubmitUnknownControllerScreen extends Screen {
|
||||
public class SubmitUnknownControllerScreen extends Screen implements DontInteruptScreen {
|
||||
public static final String SUBMISSION_URL = "https://api-controlify.isxander.dev/api/v1/submit";
|
||||
public static final Pattern NAME_PATTERN = Pattern.compile("^[\\w\\- ]{3,32}$");
|
||||
|
||||
|
Reference in New Issue
Block a user