1
0
forked from Clones/Controlify

Migrate to libsdl4j, SDL download screen, use gamecontrollerdb.txt, calibration now detects joystick triggers

This commit is contained in:
isXander
2023-09-03 23:41:18 +01:00
parent 7dfd178444
commit 1cb4a40bac
39 changed files with 1222 additions and 543 deletions

View File

@ -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() {

View File

@ -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);
}

View File

@ -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;

View File

@ -0,0 +1,4 @@
package dev.isxander.controlify.gui.screen;
public interface DontInteruptScreen {
}

View File

@ -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();
}
}

View File

@ -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) {
}
}

View File

@ -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 -> {

View File

@ -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}$");