forked from Clones/Controlify
rewrite most of joystick mapping
This commit is contained in:
@ -21,6 +21,9 @@ import dev.isxander.controlify.utils.ToastUtils;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.CrashReport;
|
||||
import net.minecraft.CrashReportCategory;
|
||||
import net.minecraft.ReportedException;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@ -78,12 +81,14 @@ public class Controlify implements ControlifyApi {
|
||||
if (config().globalSettings().loadVibrationNatives)
|
||||
SDL2NativesManager.initialise();
|
||||
|
||||
boolean dirtyControllerConfig = false;
|
||||
// find already connected controllers
|
||||
for (int jid = 0; jid <= GLFW.GLFW_JOYSTICK_LAST; jid++) {
|
||||
if (GLFW.glfwJoystickPresent(jid)) {
|
||||
try {
|
||||
var controller = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||
var controllerOpt = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||
if (controllerOpt.isEmpty()) continue;
|
||||
var controller = controllerOpt.get();
|
||||
|
||||
LOGGER.info("Controller found: " + controller.name());
|
||||
|
||||
config().loadOrCreateControllerData(controller);
|
||||
@ -93,7 +98,7 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
if (controller.config().allowVibrations && !config().globalSettings().loadVibrationNatives) {
|
||||
controller.config().allowVibrations = false;
|
||||
dirtyControllerConfig = true;
|
||||
config().setDirty();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to initialize controller with jid " + jid, e);
|
||||
@ -101,10 +106,6 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
}
|
||||
|
||||
if (dirtyControllerConfig) {
|
||||
config().save();
|
||||
}
|
||||
|
||||
checkCompoundJoysticks();
|
||||
|
||||
if (Controller.CONTROLLERS.isEmpty()) {
|
||||
@ -113,6 +114,9 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
if (currentController() == Controller.DUMMY && config().isFirstLaunch()) {
|
||||
this.setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
|
||||
} else {
|
||||
// setCurrentController saves config
|
||||
config().saveIfDirty();
|
||||
}
|
||||
|
||||
// listen for new controllers
|
||||
@ -172,15 +176,10 @@ public class Controlify implements ControlifyApi {
|
||||
|
||||
for (var controller : Controller.CONTROLLERS.values()) {
|
||||
if (!outOfFocus)
|
||||
controller.updateState();
|
||||
else {
|
||||
controller.clearState();
|
||||
controller.rumbleManager().clearEffects();
|
||||
wrapControllerError(controller::updateState, "Updating controller state", controller);
|
||||
else
|
||||
wrapControllerError(controller::clearState, "Clearing controller state", controller);
|
||||
}
|
||||
controller.rumbleManager().tick();
|
||||
}
|
||||
|
||||
ControllerState state = currentController == null ? ControllerState.EMPTY : currentController.state();
|
||||
|
||||
if (switchableController != null && Blaze3D.getTime() - askSwitchTime <= 10000) {
|
||||
if (switchableController.state().hasAnyInput()) {
|
||||
@ -189,13 +188,23 @@ public class Controlify implements ControlifyApi {
|
||||
askSwitchToast.remove();
|
||||
askSwitchToast = null;
|
||||
}
|
||||
switchableController.clearState();
|
||||
switchableController = null;
|
||||
state = ControllerState.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
if (outOfFocus)
|
||||
wrapControllerError(() -> tickController(currentController, outOfFocus), "Ticking current controller", currentController);
|
||||
}
|
||||
|
||||
private void tickController(Controller<?, ?> controller, boolean outOfFocus) {
|
||||
ControllerState state = controller.state();
|
||||
|
||||
if (outOfFocus) {
|
||||
state = ControllerState.EMPTY;
|
||||
controller.rumbleManager().clearEffects();
|
||||
} else {
|
||||
controller.rumbleManager().tick();
|
||||
}
|
||||
|
||||
if (state.hasAnyInput())
|
||||
this.setInputMode(InputMode.CONTROLLER);
|
||||
@ -209,22 +218,31 @@ public class Controlify implements ControlifyApi {
|
||||
);
|
||||
this.setCurrentController(null);
|
||||
consecutiveInputSwitches = 0;
|
||||
}
|
||||
|
||||
if (currentController == null) {
|
||||
this.setInputMode(InputMode.KEYBOARD_MOUSE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.screen != null) {
|
||||
ScreenProcessorProvider.provide(client.screen).onControllerUpdate(currentController);
|
||||
if (minecraft.screen != null) {
|
||||
ScreenProcessorProvider.provide(minecraft.screen).onControllerUpdate(controller);
|
||||
}
|
||||
if (client.level != null) {
|
||||
if (minecraft.level != null) {
|
||||
this.inGameInputHandler().inputTick();
|
||||
}
|
||||
this.virtualMouseHandler().handleControllerInput(currentController);
|
||||
this.virtualMouseHandler().handleControllerInput(controller);
|
||||
|
||||
ControlifyEvents.CONTROLLER_STATE_UPDATED.invoker().onControllerStateUpdate(currentController);
|
||||
ControlifyEvents.CONTROLLER_STATE_UPDATED.invoker().onControllerStateUpdate(controller);
|
||||
}
|
||||
|
||||
public static void wrapControllerError(Runnable runnable, String errorTitle, Controller<?, ?> controller) {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Throwable e) {
|
||||
CrashReport crashReport = CrashReport.forThrowable(e, errorTitle);
|
||||
CrashReportCategory category = crashReport.addCategory("Affected controller");
|
||||
category.setDetail("Controller name", controller::name);
|
||||
category.setDetail("Controller identification", () -> controller.type().toString());
|
||||
category.setDetail("Controller type", () -> controller.getClass().getCanonicalName());
|
||||
throw new ReportedException(crashReport);
|
||||
}
|
||||
}
|
||||
|
||||
public ControlifyConfig config() {
|
||||
@ -232,14 +250,24 @@ public class Controlify implements ControlifyApi {
|
||||
}
|
||||
|
||||
private void onControllerHotplugged(int jid) {
|
||||
var controller = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||
var controllerOpt = Controller.createOrGet(jid, controllerHIDService.fetchType());
|
||||
if (controllerOpt.isEmpty()) return;
|
||||
var controller = controllerOpt.get();
|
||||
|
||||
LOGGER.info("Controller connected: " + controller.name());
|
||||
|
||||
config().loadOrCreateControllerData(currentController);
|
||||
|
||||
if (controller.config().allowVibrations && !config().globalSettings().loadVibrationNatives) {
|
||||
controller.config().allowVibrations = false;
|
||||
config().setDirty();
|
||||
}
|
||||
|
||||
this.askToSwitchController(controller);
|
||||
|
||||
checkCompoundJoysticks();
|
||||
|
||||
config().saveIfDirty();
|
||||
}
|
||||
|
||||
private void onControllerDisconnect(int jid) {
|
||||
@ -304,7 +332,12 @@ public class Controlify implements ControlifyApi {
|
||||
controller = Controller.DUMMY;
|
||||
|
||||
if (this.currentController == controller) return;
|
||||
|
||||
if (this.currentController != null)
|
||||
this.currentController.close();
|
||||
|
||||
this.currentController = controller;
|
||||
this.currentController.open();
|
||||
|
||||
if (switchableController == controller) {
|
||||
switchableController = null;
|
||||
|
@ -8,6 +8,7 @@ import dev.isxander.controlify.api.bind.ControllerBindingBuilder;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.mixins.compat.fapi.KeyBindingRegistryImplAccessor;
|
||||
import dev.isxander.controlify.mixins.feature.bind.KeyMappingAccessor;
|
||||
import dev.isxander.controlify.mixins.feature.bind.ToggleKeyMappingAccessor;
|
||||
@ -38,6 +39,7 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
public final ControllerBinding<T>
|
||||
WALK_FORWARD, WALK_BACKWARD, WALK_LEFT, WALK_RIGHT,
|
||||
LOOK_UP, LOOK_DOWN, LOOK_LEFT, LOOK_RIGHT,
|
||||
GAMEPAD_GYRO_BUTTON,
|
||||
JUMP, SNEAK,
|
||||
ATTACK, USE,
|
||||
SPRINT,
|
||||
@ -110,6 +112,15 @@ public class ControllerBindings<T extends ControllerState> {
|
||||
.defaultBind(GamepadBinds.RIGHT_STICK_RIGHT)
|
||||
.category(MOVEMENT_CATEGORY)
|
||||
.build());
|
||||
if (controller instanceof GamepadController gamepad && gamepad.hasGyro()) {
|
||||
register(GAMEPAD_GYRO_BUTTON = ControllerBindingBuilder.create(controller)
|
||||
.identifier("controlify", "gamepad_gyro_button")
|
||||
.defaultBind(new EmptyBind<>())
|
||||
.category(MOVEMENT_CATEGORY)
|
||||
.build());
|
||||
} else {
|
||||
GAMEPAD_GYRO_BUTTON = null;
|
||||
}
|
||||
register(JUMP = ControllerBindingBuilder.create(controller)
|
||||
.identifier("controlify", "jump")
|
||||
.defaultBind(GamepadBinds.A_BUTTON)
|
||||
|
@ -42,8 +42,8 @@ public class JoystickAxisBind implements IBind<JoystickState> {
|
||||
JoystickMapping mapping = joystick.mapping();
|
||||
|
||||
String type = joystick.type().identifier();
|
||||
String axis = mapping.axis(axisIndex).identifier();
|
||||
String direction = mapping.axis(axisIndex).getDirectionIdentifier(axisIndex, this.direction);
|
||||
String axis = mapping.axes()[axisIndex].identifier();
|
||||
String direction = mapping.axes()[axisIndex].getDirectionIdentifier(axisIndex, this.direction);
|
||||
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/axis_" + axis + "_" + direction + ".png");
|
||||
|
||||
RenderSystem.setShaderTexture(0, texture);
|
||||
|
@ -31,7 +31,7 @@ public class JoystickButtonBind implements IBind<JoystickState> {
|
||||
@Override
|
||||
public void draw(PoseStack matrices, int x, int centerY) {
|
||||
String type = joystick.type().identifier();
|
||||
String button = joystick.mapping().button(buttonIndex).identifier();
|
||||
String button = joystick.mapping().buttons()[buttonIndex].identifier();
|
||||
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/button_" + button + ".png");
|
||||
|
||||
RenderSystem.setShaderTexture(0, texture);
|
||||
|
@ -33,18 +33,18 @@ public class JoystickHatBind implements IBind<JoystickState> {
|
||||
@Override
|
||||
public void draw(PoseStack matrices, int x, int centerY) {
|
||||
String type = joystick.type().identifier();
|
||||
String button = joystick.mapping().button(hatIndex).identifier();
|
||||
String hat = joystick.mapping().hats()[hatIndex].identifier();
|
||||
String direction = "centered";
|
||||
if (hatState.isUp())
|
||||
direction = "up";
|
||||
else if (hatState.isDown())
|
||||
direction = "down";
|
||||
else if (hatState.isLeft())
|
||||
direction = "strong";
|
||||
direction = "left";
|
||||
else if (hatState.isRight())
|
||||
direction = "weak";
|
||||
direction = "right";
|
||||
|
||||
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/hat" + button + "_" + direction + ".png");
|
||||
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/hat" + hat + "_" + direction + ".png");
|
||||
|
||||
RenderSystem.setShaderTexture(0, texture);
|
||||
RenderSystem.setShaderColor(1, 1, 1, 1);
|
||||
|
@ -11,8 +11,8 @@ import org.quiltmc.json5.JsonReader;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public record ControllerType(String friendlyName, String identifier) {
|
||||
public static final ControllerType UNKNOWN = new ControllerType("Unknown", "unknown");
|
||||
public record ControllerType(String friendlyName, String identifier, boolean forceJoystick, boolean dontLoad) {
|
||||
public static final ControllerType UNKNOWN = new ControllerType("Unknown", "unknown", false, false);
|
||||
|
||||
private static Map<HIDIdentifier, ControllerType> typeMap = null;
|
||||
private static final ResourceLocation hidDbLocation = new ResourceLocation("controlify", "controllers/controller_identification.json5");
|
||||
@ -47,6 +47,8 @@ public record ControllerType(String friendlyName, String identifier) {
|
||||
while (reader.hasNext()) {
|
||||
String friendlyName = null;
|
||||
String identifier = null;
|
||||
boolean forceJoystick = false;
|
||||
boolean dontLoad = false;
|
||||
Set<HIDIdentifier> hids = new HashSet<>();
|
||||
|
||||
reader.beginObject();
|
||||
@ -77,6 +79,8 @@ public record ControllerType(String friendlyName, String identifier) {
|
||||
}
|
||||
reader.endArray();
|
||||
}
|
||||
case "force_joystick" -> forceJoystick = reader.nextBoolean();
|
||||
case "dont_load" -> dontLoad = reader.nextBoolean();
|
||||
default -> {
|
||||
Controlify.LOGGER.warn("Unknown key in HID DB: " + name + ". Skipping...");
|
||||
reader.skipValue();
|
||||
@ -90,7 +94,7 @@ public record ControllerType(String friendlyName, String identifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = new ControllerType(friendlyName, identifier);
|
||||
var type = new ControllerType(friendlyName, identifier, forceJoystick, dontLoad);
|
||||
for (var hid : hids) {
|
||||
typeMap.put(hid, type);
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class CompoundJoystickController implements JoystickController<JoystickCo
|
||||
this.buttonCount = joystickIds.stream().mapToInt(this::getButtonCountForJoystick).sum();
|
||||
this.hatCount = joystickIds.stream().mapToInt(this::getHatCountForJoystick).sum();
|
||||
|
||||
this.mapping = RPJoystickMapping.fromType(type());
|
||||
this.mapping = RPJoystickMapping.fromType(this);
|
||||
|
||||
this.config = new JoystickConfig(this);
|
||||
this.defaultConfig = new JoystickConfig(this);
|
||||
|
@ -9,7 +9,7 @@ import java.util.Optional;
|
||||
|
||||
public record CompoundJoystickInfo(Collection<String> joystickUids, String friendlyName) {
|
||||
public ControllerType type() {
|
||||
return new ControllerType(friendlyName, createUID(joystickUids));
|
||||
return new ControllerType(friendlyName, createUID(joystickUids), true, false);
|
||||
}
|
||||
|
||||
public boolean canBeUsed() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import dev.isxander.controlify.controller.ControllerConfig;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -21,7 +22,7 @@ public class JoystickConfig extends ControllerConfig {
|
||||
if (axis < 0)
|
||||
throw new IllegalArgumentException("Axis cannot be negative!");
|
||||
|
||||
deadzones.put(controller.mapping().axis(axis).identifier(), deadzone);
|
||||
deadzones.put(controller.mapping().axes()[axis].identifier(), deadzone);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -29,16 +30,18 @@ public class JoystickConfig extends ControllerConfig {
|
||||
if (axis < 0)
|
||||
throw new IllegalArgumentException("Axis cannot be negative!");
|
||||
|
||||
return deadzones.getOrDefault(controller.mapping().axis(axis).identifier(), 0.2f);
|
||||
return deadzones.getOrDefault(controller.mapping().axes()[axis].identifier(), 0.2f);
|
||||
}
|
||||
|
||||
void setup(JoystickController<?> controller) {
|
||||
this.controller = controller;
|
||||
if (this.deadzones == null) {
|
||||
deadzones = new HashMap<>();
|
||||
for (int i = 0; i < controller.axisCount(); i++) {
|
||||
if (controller.mapping().axis(i).requiresDeadzone())
|
||||
deadzones.put(controller.mapping().axis(i).identifier(), 0.2f);
|
||||
for (int i = 0; i < controller.mapping().axes().length; i++) {
|
||||
JoystickMapping.Axis axis = controller.mapping().axes()[i];
|
||||
|
||||
if (axis.requiresDeadzone())
|
||||
deadzones.put(axis.identifier(), 0.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,11 @@ import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMappi
|
||||
public interface JoystickController<T extends JoystickConfig> extends Controller<JoystickState, T> {
|
||||
JoystickMapping mapping();
|
||||
|
||||
@Deprecated
|
||||
int axisCount();
|
||||
@Deprecated
|
||||
int buttonCount();
|
||||
@Deprecated
|
||||
int hatCount();
|
||||
|
||||
@Override
|
||||
|
@ -1,8 +1,10 @@
|
||||
package dev.isxander.controlify.controller.joystick;
|
||||
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
|
||||
import dev.isxander.controlify.debug.DebugProperties;
|
||||
import dev.isxander.controlify.utils.ControllerUtils;
|
||||
import dev.isxander.yacl.api.NameableEnum;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@ -11,12 +13,13 @@ import org.lwjgl.glfw.GLFW;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class JoystickState implements ControllerState {
|
||||
public static final JoystickState EMPTY = new JoystickState(UnmappedJoystickMapping.INSTANCE, List.of(), List.of(), List.of(), List.of());
|
||||
public static final JoystickState EMPTY = new JoystickState(UnmappedJoystickMapping.EMPTY, List.of(), List.of(), List.of(), List.of());
|
||||
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
@ -54,7 +57,7 @@ public class JoystickState implements ControllerState {
|
||||
|
||||
@Override
|
||||
public boolean hasAnyInput() {
|
||||
return IntStream.range(0, axes().size()).anyMatch(i -> !mapping.axis(i).isAxisResting(axes().get(i)))
|
||||
return IntStream.range(0, axes().size()).anyMatch(i -> !mapping.axes()[i].isAxisResting(axes().get(i)))
|
||||
|| buttons().stream().anyMatch(Boolean::booleanValue)
|
||||
|| hats().stream().anyMatch(hat -> hat != HatState.CENTERED);
|
||||
}
|
||||
@ -70,68 +73,85 @@ public class JoystickState implements ControllerState {
|
||||
}
|
||||
|
||||
public static JoystickState fromJoystick(JoystickController<?> joystick, int joystickId) {
|
||||
if (DebugProperties.PRINT_JOY_INPUT_COUNT)
|
||||
Controlify.LOGGER.info("Printing joy input for " + joystick.name());
|
||||
|
||||
FloatBuffer axesBuffer = GLFW.glfwGetJoystickAxes(joystickId);
|
||||
List<Float> axes = new ArrayList<>();
|
||||
List<Float> rawAxes = new ArrayList<>();
|
||||
if (axesBuffer != null) {
|
||||
float[] inAxes = new float[axesBuffer.limit()];
|
||||
|
||||
if (DebugProperties.PRINT_JOY_INPUT_COUNT)
|
||||
Controlify.LOGGER.info("Axes count = " + inAxes.length);
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
while (axesBuffer.hasRemaining()) {
|
||||
var axisMapping = joystick.mapping().axis(i);
|
||||
var axis = axisMapping.modifyAxis(axesBuffer.get());
|
||||
var deadzone = axisMapping.requiresDeadzone();
|
||||
|
||||
rawAxes.add(axis);
|
||||
axes.add(deadzone ? ControllerUtils.deadzone(axis, joystick.config().getDeadzone(i)) : axis);
|
||||
|
||||
inAxes[i] = axesBuffer.get();
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer buttonBuffer = GLFW.glfwGetJoystickButtons(joystickId);
|
||||
List<Boolean> buttons = new ArrayList<>();
|
||||
if (buttonBuffer != null) {
|
||||
boolean[] inButtons = new boolean[buttonBuffer.limit()];
|
||||
|
||||
if (DebugProperties.PRINT_JOY_INPUT_COUNT)
|
||||
Controlify.LOGGER.info("Button count = " + inButtons.length);
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
while (buttonBuffer.hasRemaining()) {
|
||||
buttons.add(buttonBuffer.get() == GLFW.GLFW_PRESS);
|
||||
inButtons[i] = buttonBuffer.get() == GLFW.GLFW_PRESS;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
ByteBuffer hatBuffer = GLFW.glfwGetJoystickHats(joystickId);
|
||||
List<JoystickState.HatState> hats = new ArrayList<>();
|
||||
if (hatBuffer != null) {
|
||||
HatState[] inHats = new HatState[hatBuffer.limit()];
|
||||
|
||||
if (DebugProperties.PRINT_JOY_INPUT_COUNT)
|
||||
Controlify.LOGGER.info("Hat count = " + inHats.length);
|
||||
|
||||
{
|
||||
int i = 0;
|
||||
while (hatBuffer.hasRemaining()) {
|
||||
var state = switch (hatBuffer.get()) {
|
||||
case GLFW.GLFW_HAT_CENTERED -> JoystickState.HatState.CENTERED;
|
||||
case GLFW.GLFW_HAT_UP -> JoystickState.HatState.UP;
|
||||
case GLFW.GLFW_HAT_RIGHT -> JoystickState.HatState.RIGHT;
|
||||
case GLFW.GLFW_HAT_DOWN -> JoystickState.HatState.DOWN;
|
||||
case GLFW.GLFW_HAT_LEFT -> JoystickState.HatState.LEFT;
|
||||
case GLFW.GLFW_HAT_RIGHT_UP -> JoystickState.HatState.RIGHT_UP;
|
||||
case GLFW.GLFW_HAT_RIGHT_DOWN -> JoystickState.HatState.RIGHT_DOWN;
|
||||
case GLFW.GLFW_HAT_LEFT_UP -> JoystickState.HatState.LEFT_UP;
|
||||
case GLFW.GLFW_HAT_LEFT_DOWN -> JoystickState.HatState.LEFT_DOWN;
|
||||
case GLFW.GLFW_HAT_CENTERED -> HatState.CENTERED;
|
||||
case GLFW.GLFW_HAT_UP -> HatState.UP;
|
||||
case GLFW.GLFW_HAT_RIGHT -> HatState.RIGHT;
|
||||
case GLFW.GLFW_HAT_DOWN -> HatState.DOWN;
|
||||
case GLFW.GLFW_HAT_LEFT -> HatState.LEFT;
|
||||
case GLFW.GLFW_HAT_RIGHT_UP -> HatState.RIGHT_UP;
|
||||
case GLFW.GLFW_HAT_RIGHT_DOWN -> HatState.RIGHT_DOWN;
|
||||
case GLFW.GLFW_HAT_LEFT_UP -> HatState.LEFT_UP;
|
||||
case GLFW.GLFW_HAT_LEFT_DOWN -> HatState.LEFT_DOWN;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + hatBuffer.get());
|
||||
};
|
||||
hats.add(state);
|
||||
inHats[i] = state;
|
||||
}
|
||||
}
|
||||
|
||||
return new JoystickState(joystick.mapping(), axes, rawAxes, buttons, hats);
|
||||
JoystickMapping.JoystickData data = new JoystickMapping.JoystickData(inAxes, inButtons, inHats);
|
||||
JoystickMapping mapping = joystick.mapping();
|
||||
|
||||
JoystickMapping.Axis[] axes = mapping.axes();
|
||||
List<Float> rawAxes = new ArrayList<>(axes.length);
|
||||
List<Float> deadzoneAxes = new ArrayList<>(axes.length);
|
||||
for (int i = 0; i < axes.length; i++) {
|
||||
var axis = axes[i];
|
||||
float state = axis.getAxis(data);
|
||||
rawAxes.add(state);
|
||||
deadzoneAxes.add(axis.requiresDeadzone() ? ControllerUtils.deadzone(state, i) : state);
|
||||
}
|
||||
|
||||
List<Boolean> buttons = Arrays.stream(mapping.buttons()).map(button -> button.isPressed(data)).toList();
|
||||
List<HatState> hats = Arrays.stream(mapping.hats()).map(hat -> hat.getHatState(data)).toList();
|
||||
|
||||
return new JoystickState(joystick.mapping(), deadzoneAxes, rawAxes, buttons, hats);
|
||||
}
|
||||
|
||||
public static JoystickState empty(JoystickController<?> joystick) {
|
||||
var axes = new ArrayList<Float>();
|
||||
var buttons = new ArrayList<Boolean>();
|
||||
var hats = new ArrayList<HatState>();
|
||||
|
||||
for (int i = 0; i < joystick.axisCount(); i++) {
|
||||
axes.add(joystick.mapping().axis(i).restingValue());
|
||||
}
|
||||
for (int i = 0; i < joystick.buttonCount(); i++) {
|
||||
buttons.add(false);
|
||||
}
|
||||
for (int i = 0; i < joystick.hatCount(); i++) {
|
||||
hats.add(HatState.CENTERED);
|
||||
}
|
||||
var axes = Arrays.stream(joystick.mapping().axes()).map(JoystickMapping.Axis::restingValue).toList();
|
||||
var buttons = IntStream.range(0, joystick.mapping().buttons().length).mapToObj(i -> false).toList();
|
||||
var hats = IntStream.range(0, joystick.mapping().hats().length).mapToObj(i -> HatState.CENTERED).toList();
|
||||
|
||||
return new JoystickState(joystick.mapping(), axes, axes, buttons, hats);
|
||||
}
|
||||
|
@ -13,17 +13,12 @@ import java.util.Objects;
|
||||
|
||||
public class SingleJoystickController extends AbstractController<JoystickState, JoystickConfig> implements JoystickController<JoystickConfig> {
|
||||
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||
private final int axisCount, buttonCount, hatCount;
|
||||
private final JoystickMapping mapping;
|
||||
|
||||
public SingleJoystickController(int joystickId, ControllerHIDService.ControllerHIDInfo hidInfo) {
|
||||
super(joystickId, hidInfo);
|
||||
|
||||
this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity();
|
||||
this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity();
|
||||
this.hatCount = GLFW.glfwGetJoystickHats(joystickId).capacity();
|
||||
|
||||
this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(type()));
|
||||
this.mapping = Objects.requireNonNull(RPJoystickMapping.fromType(this));
|
||||
|
||||
this.config = new JoystickConfig(this);
|
||||
this.defaultConfig = new JoystickConfig(this);
|
||||
@ -57,17 +52,17 @@ public class SingleJoystickController extends AbstractController<JoystickState,
|
||||
|
||||
@Override
|
||||
public int axisCount() {
|
||||
return axisCount;
|
||||
return mapping().axes().length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int buttonCount() {
|
||||
return buttonCount;
|
||||
return mapping.buttons().length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hatCount() {
|
||||
return hatCount;
|
||||
return mapping.hats().length;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,14 +1,13 @@
|
||||
package dev.isxander.controlify.controller.joystick.mapping;
|
||||
|
||||
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public interface JoystickMapping {
|
||||
Axis axis(int axis);
|
||||
|
||||
Button button(int button);
|
||||
|
||||
Hat hat(int hat);
|
||||
Axis[] axes();
|
||||
Button[] buttons();
|
||||
Hat[] hats();
|
||||
|
||||
interface Axis {
|
||||
String identifier();
|
||||
@ -17,7 +16,7 @@ public interface JoystickMapping {
|
||||
|
||||
boolean requiresDeadzone();
|
||||
|
||||
float modifyAxis(float value);
|
||||
float getAxis(JoystickData data);
|
||||
|
||||
boolean isAxisResting(float value);
|
||||
|
||||
@ -30,11 +29,18 @@ public interface JoystickMapping {
|
||||
String identifier();
|
||||
|
||||
Component name();
|
||||
|
||||
boolean isPressed(JoystickData data);
|
||||
}
|
||||
|
||||
interface Hat {
|
||||
JoystickState.HatState getHatState(JoystickData data);
|
||||
|
||||
String identifier();
|
||||
|
||||
Component name();
|
||||
}
|
||||
|
||||
record JoystickData(float[] axes, boolean[] buttons, JoystickState.HatState[] hats) {
|
||||
}
|
||||
}
|
||||
|
@ -1,123 +1,282 @@
|
||||
package dev.isxander.controlify.controller.joystick.mapping;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||
import dev.isxander.controlify.controller.ControllerType;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.phys.Vec2;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.quiltmc.json5.JsonReader;
|
||||
import org.quiltmc.json5.JsonToken;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class RPJoystickMapping implements JoystickMapping {
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
private final Map<Integer, AxisMapping> axisMappings;
|
||||
private final Map<Integer, ButtonMapping> buttonMappings;
|
||||
private final Map<Integer, HatMapping> hatMappings;
|
||||
private final AxisMapping[] axes;
|
||||
private final ButtonMapping[] buttons;
|
||||
private final HatMapping[] hats;
|
||||
|
||||
public RPJoystickMapping(JsonObject object, ControllerType type) {
|
||||
axisMappings = new HashMap<>();
|
||||
object.getAsJsonArray("axes").forEach(element -> {
|
||||
var axis = element.getAsJsonObject();
|
||||
List<Integer> ids = axis.getAsJsonArray("ids").asList().stream().map(JsonElement::getAsInt).toList();
|
||||
public RPJoystickMapping(JsonReader reader, ControllerType type) throws IOException {
|
||||
AxisMapping[] axes = null;
|
||||
ButtonMapping[] buttons = null;
|
||||
HatMapping[] hats = null;
|
||||
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String name = reader.nextName();
|
||||
switch (name) {
|
||||
case "axes" -> {
|
||||
if (axes != null)
|
||||
throw new IllegalStateException("Axes defined twice.");
|
||||
axes = readAxes(reader, type);
|
||||
}
|
||||
case "buttons" -> {
|
||||
if (buttons != null)
|
||||
throw new IllegalStateException("Buttons defined twice.");
|
||||
buttons = readButtons(reader, type);
|
||||
}
|
||||
case "hats" -> {
|
||||
if (hats != null)
|
||||
throw new IllegalStateException("Hats defined twice.");
|
||||
hats = readHats(reader, type);
|
||||
}
|
||||
default -> {
|
||||
Controlify.LOGGER.warn("Unknown field in joystick mapping: " + name + ". Expected values: ['axes', 'buttons', 'hats']");
|
||||
reader.skipValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
|
||||
this.axes = axes;
|
||||
this.buttons = buttons;
|
||||
this.hats = hats;
|
||||
}
|
||||
|
||||
private AxisMapping[] readAxes(JsonReader reader, ControllerType type) throws IOException {
|
||||
List<AxisMapping> axes = new ArrayList<>();
|
||||
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
List<Integer> ids = new ArrayList<>();
|
||||
Vec2 inpRange = null;
|
||||
Vec2 outRange = null;
|
||||
if (axis.has("range")) {
|
||||
var rangeElement = axis.get("range");
|
||||
if (rangeElement.isJsonArray()) {
|
||||
var rangeArray = rangeElement.getAsJsonArray();
|
||||
outRange = new Vec2(rangeArray.get(0).getAsFloat(), rangeArray.get(1).getAsFloat());
|
||||
boolean deadzone = false;
|
||||
float restState = 0f;
|
||||
String identifier = null;
|
||||
List<String[]> axisNames = new ArrayList<>();
|
||||
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String name = reader.nextName();
|
||||
switch (name) {
|
||||
case "ids" -> {
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
ids.add(reader.nextInt());
|
||||
}
|
||||
reader.endArray();
|
||||
}
|
||||
case "identifier" -> {
|
||||
identifier = reader.nextString();
|
||||
}
|
||||
case "range" -> {
|
||||
if (reader.peek() == JsonToken.BEGIN_ARRAY) {
|
||||
reader.beginArray();
|
||||
outRange = new Vec2((float) reader.nextDouble(), (float) reader.nextDouble());
|
||||
inpRange = new Vec2(-1, 1);
|
||||
} else if (rangeElement.isJsonObject()) {
|
||||
var rangeObject = rangeElement.getAsJsonObject();
|
||||
reader.endArray();
|
||||
} else {
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String rangeName = reader.nextName();
|
||||
|
||||
var inpRangeArray = rangeObject.getAsJsonArray("in");
|
||||
inpRange = new Vec2(inpRangeArray.get(0).getAsFloat(), inpRangeArray.get(1).getAsFloat());
|
||||
|
||||
var outRangeArray = rangeObject.getAsJsonArray("out");
|
||||
outRange = new Vec2(outRangeArray.get(0).getAsFloat(), outRangeArray.get(1).getAsFloat());
|
||||
switch (rangeName) {
|
||||
case "in" -> {
|
||||
reader.beginArray();
|
||||
inpRange = new Vec2((float) reader.nextDouble(), (float) reader.nextDouble());
|
||||
reader.endArray();
|
||||
}
|
||||
case "out" -> {
|
||||
reader.beginArray();
|
||||
outRange = new Vec2((float) reader.nextDouble(), (float) reader.nextDouble());
|
||||
reader.endArray();
|
||||
}
|
||||
default -> {
|
||||
reader.skipValue();
|
||||
Controlify.LOGGER.info("Unknown axis range property: " + rangeName + ". Expected are ['in', 'out']");
|
||||
}
|
||||
}
|
||||
var restState = axis.get("rest").getAsFloat();
|
||||
var deadzone = axis.get("deadzone").getAsBoolean();
|
||||
var identifier = axis.get("identifier").getAsString();
|
||||
|
||||
var axisNames = axis.getAsJsonArray("axis_names").asList().stream()
|
||||
.map(JsonElement::getAsJsonArray)
|
||||
.map(JsonArray::asList)
|
||||
.map(list -> list.stream().map(JsonElement::getAsString).toList())
|
||||
.toList();
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
}
|
||||
case "rest" -> {
|
||||
restState = (float) reader.nextDouble();
|
||||
}
|
||||
case "deadzone" -> {
|
||||
deadzone = reader.nextBoolean();
|
||||
}
|
||||
case "axis_names" -> {
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
reader.beginArray();
|
||||
axisNames.add(new String[] { reader.nextString(), reader.nextString() });
|
||||
reader.endArray();
|
||||
}
|
||||
reader.endArray();
|
||||
}
|
||||
default -> {
|
||||
reader.skipValue();
|
||||
Controlify.LOGGER.info("Unknown axis property: " + name + ". Expected are ['identifier', 'axis_names', 'ids', 'range', 'rest', 'deadzone']");
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
|
||||
for (var id : ids) {
|
||||
axisMappings.put(id, new AxisMapping(ids, identifier, inpRange, outRange, restState, deadzone, type.identifier(), axisNames));
|
||||
axes.add(new AxisMapping(id, identifier, inpRange, outRange, restState, deadzone, type.identifier(), axisNames.get(ids.indexOf(id))));
|
||||
}
|
||||
});
|
||||
}
|
||||
reader.endArray();
|
||||
|
||||
buttonMappings = new HashMap<>();
|
||||
object.getAsJsonArray("buttons").forEach(element -> {
|
||||
var button = element.getAsJsonObject();
|
||||
buttonMappings.put(button.get("button").getAsInt(), new ButtonMapping(button.get("name").getAsString(), type.identifier()));
|
||||
});
|
||||
return axes.toArray(new AxisMapping[0]);
|
||||
}
|
||||
|
||||
hatMappings = new HashMap<>();
|
||||
object.getAsJsonArray("hats").forEach(element -> {
|
||||
var hat = element.getAsJsonObject();
|
||||
hatMappings.put(hat.get("hat").getAsInt(), new HatMapping(hat.get("name").getAsString(), type.identifier()));
|
||||
});
|
||||
private ButtonMapping[] readButtons(JsonReader reader, ControllerType type) throws IOException {
|
||||
List<ButtonMapping> buttons = new ArrayList<>();
|
||||
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
int id = -1;
|
||||
String btnName = null;
|
||||
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String name = reader.nextName();
|
||||
switch (name) {
|
||||
case "button" -> id = reader.nextInt();
|
||||
case "name" -> btnName = reader.nextString();
|
||||
default -> {
|
||||
reader.skipValue();
|
||||
Controlify.LOGGER.info("Unknown button property: " + name + ". Expected are ['button', 'name']");
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
|
||||
buttons.add(new ButtonMapping(id, btnName, type.identifier()));
|
||||
}
|
||||
reader.endArray();
|
||||
|
||||
return buttons.toArray(new ButtonMapping[0]);
|
||||
}
|
||||
|
||||
private HatMapping[] readHats(JsonReader reader, ControllerType type) throws IOException {
|
||||
List<HatMapping> hats = new ArrayList<>();
|
||||
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
int id = -1;
|
||||
String hatName = null;
|
||||
HatMapping.EmulatedAxis axis = null;
|
||||
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String name = reader.nextName();
|
||||
switch (name) {
|
||||
case "hat" -> id = reader.nextInt();
|
||||
case "name" -> hatName = reader.nextString();
|
||||
case "emulated_axis" -> {
|
||||
int axisId = -1;
|
||||
Map<Float, JoystickState.HatState> states = new HashMap<>();
|
||||
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String emulatedName = reader.nextName();
|
||||
for (var hatState : JoystickState.HatState.values()) {
|
||||
if (hatState.name().equalsIgnoreCase(emulatedName)) {
|
||||
states.put((float) reader.nextDouble(), hatState);
|
||||
}
|
||||
}
|
||||
|
||||
if (emulatedName.equalsIgnoreCase("axis")) {
|
||||
axisId = reader.nextInt();
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
|
||||
if (axisId == -1) {
|
||||
Controlify.LOGGER.error("No axis id defined for emulated hat " + hatName + "! Skipping.");
|
||||
continue;
|
||||
}
|
||||
if (states.size() != JoystickState.HatState.values().length) {
|
||||
Controlify.LOGGER.error("Not all hat states are defined for emulated hat " + hatName + "! Skipping.");
|
||||
continue;
|
||||
}
|
||||
|
||||
axis = new HatMapping.EmulatedAxis(axisId, states);
|
||||
}
|
||||
default -> {
|
||||
reader.skipValue();
|
||||
Controlify.LOGGER.info("Unknown hat property: " + name + ". Expected are ['hat', 'name']");
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
|
||||
hats.add(new HatMapping(id, hatName, type.identifier(), axis));
|
||||
}
|
||||
reader.endArray();
|
||||
|
||||
return hats.toArray(new HatMapping[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Axis axis(int axis) {
|
||||
if (!axisMappings.containsKey(axis))
|
||||
return UnmappedJoystickMapping.INSTANCE.axis(axis);
|
||||
return axisMappings.get(axis);
|
||||
public Axis[] axes() {
|
||||
return axes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button button(int button) {
|
||||
if (!buttonMappings.containsKey(button))
|
||||
return UnmappedJoystickMapping.INSTANCE.button(button);
|
||||
return buttonMappings.get(button);
|
||||
public Button[] buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hat hat(int hat) {
|
||||
if (!hatMappings.containsKey(hat))
|
||||
return UnmappedJoystickMapping.INSTANCE.hat(hat);
|
||||
return hatMappings.get(hat);
|
||||
public Hat[] hats() {
|
||||
return hats;
|
||||
}
|
||||
|
||||
public static JoystickMapping fromType(ControllerType type) {
|
||||
var resource = Minecraft.getInstance().getResourceManager().getResource(new ResourceLocation("controlify", "mappings/" + type.identifier() + ".json"));
|
||||
public static JoystickMapping fromType(JoystickController<?> joystick) {
|
||||
var resource = Minecraft.getInstance().getResourceManager().getResource(new ResourceLocation("controlify", "mappings/" + joystick.type().identifier() + ".json"));
|
||||
if (resource.isEmpty()) {
|
||||
Controlify.LOGGER.warn("No joystick mapping found for controller: '" + type.identifier() + "'");
|
||||
return UnmappedJoystickMapping.INSTANCE;
|
||||
Controlify.LOGGER.warn("No joystick mapping found for controller: '" + joystick.type().identifier() + "'");
|
||||
return new UnmappedJoystickMapping(joystick.joystickId());
|
||||
}
|
||||
|
||||
try (var reader = resource.get().openAsReader()) {
|
||||
return new RPJoystickMapping(gson.fromJson(reader, JsonObject.class), type);
|
||||
try (var reader = JsonReader.json5(resource.get().openAsReader())) {
|
||||
return new RPJoystickMapping(reader, joystick.type());
|
||||
} catch (Exception e) {
|
||||
Controlify.LOGGER.error("Failed to load joystick mapping for controller: '" + type.identifier() + "'", e);
|
||||
return UnmappedJoystickMapping.INSTANCE;
|
||||
Controlify.LOGGER.error("Failed to load joystick mapping for controller: '" + joystick.type().identifier() + "'", e);
|
||||
return new UnmappedJoystickMapping(joystick.joystickId());
|
||||
}
|
||||
}
|
||||
|
||||
private record AxisMapping(List<Integer> ids, String identifier, Vec2 inpRange, Vec2 outRange, float restingValue, boolean requiresDeadzone, String typeId, List<List<String>> axisNames) implements Axis {
|
||||
private record AxisMapping(int id, String identifier, Vec2 inpRange, Vec2 outRange, float restingValue, boolean requiresDeadzone, String typeId, String[] axisNames) implements Axis {
|
||||
@Override
|
||||
public float modifyAxis(float value) {
|
||||
if (inpRange() == null || outRange() == null)
|
||||
return value;
|
||||
public float getAxis(JoystickData data) {
|
||||
float rawAxis = data.axes()[id];
|
||||
|
||||
return (value + (outRange().x - inpRange().x)) / (inpRange().y - inpRange().x) * (outRange().y - outRange().x);
|
||||
if (inpRange() == null || outRange() == null)
|
||||
return rawAxis;
|
||||
|
||||
return (rawAxis + (outRange().x - inpRange().x)) / (inpRange().y - inpRange().x) * (outRange().y - outRange().x);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -132,23 +291,40 @@ public class RPJoystickMapping implements JoystickMapping {
|
||||
|
||||
@Override
|
||||
public String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction) {
|
||||
return this.axisNames().get(ids.indexOf(axis)).get(direction.ordinal());
|
||||
return this.axisNames()[direction.ordinal()];
|
||||
}
|
||||
}
|
||||
|
||||
private record ButtonMapping(String identifier, String typeId) implements Button {
|
||||
private record ButtonMapping(int id, String identifier, String typeId) implements Button {
|
||||
@Override
|
||||
public boolean isPressed(JoystickData data) {
|
||||
return data.buttons()[id];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component name() {
|
||||
return Component.translatable("controlify.joystick_mapping." + typeId() + ".button." + identifier());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private record HatMapping(String identifier, String typeId) implements Hat {
|
||||
private record HatMapping(int hatId, String identifier, String typeId, @Nullable EmulatedAxis emulatedAxis) implements Hat {
|
||||
@Override
|
||||
public JoystickState.HatState getHatState(JoystickData data) {
|
||||
if (emulatedAxis() != null) {
|
||||
var axis = emulatedAxis();
|
||||
var axisValue = data.axes()[axis.axisId()];
|
||||
return emulatedAxis().states().get(axisValue);
|
||||
}
|
||||
|
||||
return data.hats()[hatId()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component name() {
|
||||
return Component.translatable("controlify.joystick_mapping." + typeId() + ".hat." + identifier());
|
||||
}
|
||||
|
||||
private record EmulatedAxis(int axisId, Map<Float, JoystickState.HatState> states) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,65 @@
|
||||
package dev.isxander.controlify.controller.joystick.mapping;
|
||||
|
||||
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class UnmappedJoystickMapping implements JoystickMapping {
|
||||
public static final UnmappedJoystickMapping INSTANCE = new UnmappedJoystickMapping();
|
||||
public static final UnmappedJoystickMapping EMPTY = new UnmappedJoystickMapping(0, 0, 0);
|
||||
|
||||
@Override
|
||||
public Axis axis(int axis) {
|
||||
return new UnmappedAxis(axis);
|
||||
private final UnmappedAxis[] axes;
|
||||
private final UnmappedButton[] buttons;
|
||||
private final UnmappedHat[] hats;
|
||||
|
||||
private UnmappedJoystickMapping(int axisCount, int buttonCount, int hatCount) {
|
||||
this.axes = new UnmappedAxis[axisCount];
|
||||
for (int i = 0; i < axisCount; i++) {
|
||||
this.axes[i] = new UnmappedAxis(i);
|
||||
}
|
||||
|
||||
this.buttons = new UnmappedButton[axisCount];
|
||||
for (int i = 0; i < buttonCount; i++) {
|
||||
this.buttons[i] = new UnmappedButton(i);
|
||||
}
|
||||
|
||||
this.hats = new UnmappedHat[hatCount];
|
||||
for (int i = 0; i < hatCount; i++) {
|
||||
this.hats[i] = new UnmappedHat(i);
|
||||
}
|
||||
}
|
||||
|
||||
public UnmappedJoystickMapping(int joystickId) {
|
||||
this(
|
||||
GLFW.glfwGetJoystickAxes(joystickId).limit(),
|
||||
GLFW.glfwGetJoystickButtons(joystickId).limit(),
|
||||
GLFW.glfwGetJoystickHats(joystickId).limit()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Button button(int button) {
|
||||
return new UnmappedButton(button);
|
||||
public Axis[] axes() {
|
||||
return axes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hat hat(int hat) {
|
||||
return new UnmappedHat(hat);
|
||||
public Button[] buttons() {
|
||||
return buttons;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hat[] hats() {
|
||||
return hats;
|
||||
}
|
||||
|
||||
private record UnmappedAxis(int axis) implements Axis {
|
||||
@Override
|
||||
public float getAxis(JoystickData data) {
|
||||
return data.axes()[axis];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String identifier() {
|
||||
@ -38,11 +76,6 @@ public class UnmappedJoystickMapping implements JoystickMapping {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float modifyAxis(float value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAxisResting(float value) {
|
||||
return value == restingValue();
|
||||
@ -60,6 +93,11 @@ public class UnmappedJoystickMapping implements JoystickMapping {
|
||||
}
|
||||
|
||||
private record UnmappedButton(int button) implements Button {
|
||||
@Override
|
||||
public boolean isPressed(JoystickData data) {
|
||||
return data.buttons()[button];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "button-" + button;
|
||||
@ -72,6 +110,11 @@ public class UnmappedJoystickMapping implements JoystickMapping {
|
||||
}
|
||||
|
||||
private record UnmappedHat(int hat) implements Hat {
|
||||
@Override
|
||||
public JoystickState.HatState getHatState(JoystickData data) {
|
||||
return data.hats()[hat];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String identifier() {
|
||||
return "hat-" + hat;
|
||||
|
@ -1,12 +1,19 @@
|
||||
package dev.isxander.controlify.debug;
|
||||
|
||||
public class DebugProperties {
|
||||
// Renders debug overlay for vmouse snapping
|
||||
public static final boolean DEBUG_SNAPPING = boolProp("controlify.debug.snapping", false);
|
||||
// Forces all gamepads to be treated as a regular joystick
|
||||
public static final boolean FORCE_JOYSTICK = boolProp("controlify.debug.force_joystick", false);
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
private static boolean boolProp(String name, boolean def) {
|
||||
public class DebugProperties {
|
||||
/* Renders debug overlay for vmouse snapping */
|
||||
public static final boolean DEBUG_SNAPPING = boolProp("controlify.debug.snapping", false, false);
|
||||
/* Forces all gamepads to be treated as a regular joystick */
|
||||
public static final boolean FORCE_JOYSTICK = boolProp("controlify.debug.force_joystick", false, false);
|
||||
/* Prints joystick input counts for making joystick mappings */
|
||||
public static final boolean PRINT_JOY_INPUT_COUNT = boolProp("controlify.debug.print_joy_input_count", false, true);
|
||||
/* Print gyro data if supported */
|
||||
public static final boolean PRINT_GYRO = boolProp("controlify.debug.print_gyro", false, false);
|
||||
|
||||
private static boolean boolProp(String name, boolean defProd, boolean defDev) {
|
||||
boolean def = FabricLoader.getInstance().isDevelopmentEnvironment() ? defDev : defProd;
|
||||
return Boolean.parseBoolean(System.getProperty(name, Boolean.toString(def)));
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,8 @@ public class MultiPlayerGameModeMixin {
|
||||
}
|
||||
|
||||
private void startRumble(BlockState state) {
|
||||
stopRumble();
|
||||
|
||||
var effect = ContinuousRumbleEffect.builder()
|
||||
.byTick(tick -> new RumbleState(
|
||||
0.02f + Easings.easeInQuad(Math.min(1, state.getBlock().defaultDestroyTime() / 20f)) * 0.25f,
|
||||
|
@ -1,9 +1,12 @@
|
||||
package dev.isxander.controlify.rumble;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class BasicRumbleEffect implements RumbleEffect {
|
||||
@ -11,6 +14,7 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
||||
private int tick = 0;
|
||||
private boolean finished;
|
||||
private int priority = 0;
|
||||
private BooleanSupplier earlyFinishCondition = () -> false;
|
||||
|
||||
public BasicRumbleEffect(RumbleState[] keyframes) {
|
||||
this.keyframes = keyframes;
|
||||
@ -19,7 +23,7 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
||||
@Override
|
||||
public void tick() {
|
||||
tick++;
|
||||
if (tick >= keyframes.length)
|
||||
if (tick >= keyframes.length || earlyFinishCondition.getAsBoolean())
|
||||
finished = true;
|
||||
}
|
||||
|
||||
@ -55,6 +59,12 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
||||
return keyframes;
|
||||
}
|
||||
|
||||
public BasicRumbleEffect earlyFinish(BooleanSupplier condition) {
|
||||
var current = earlyFinishCondition;
|
||||
this.earlyFinishCondition = () -> current.getAsBoolean() || condition.getAsBoolean();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
@ -76,6 +86,22 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
||||
"priority=" + this.priority() + ']';
|
||||
}
|
||||
|
||||
public BasicRumbleEffect join(BasicRumbleEffect other) {
|
||||
return BasicRumbleEffect.join(this, other);
|
||||
}
|
||||
|
||||
public BasicRumbleEffect repeat(int count) {
|
||||
Validate.isTrue(count > 0, "count must be greater than 0");
|
||||
|
||||
if (count == 1) return this;
|
||||
|
||||
BasicRumbleEffect effect = this;
|
||||
for (int i = 0; i < count - 1; i++) {
|
||||
effect = BasicRumbleEffect.join(effect, this);
|
||||
}
|
||||
return effect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a rumble effect where the state is determined by the tick.
|
||||
*
|
||||
@ -133,19 +159,8 @@ public final class BasicRumbleEffect implements RumbleEffect {
|
||||
return new BasicRumbleEffect(states);
|
||||
}
|
||||
|
||||
public BasicRumbleEffect join(BasicRumbleEffect other) {
|
||||
return BasicRumbleEffect.join(this, other);
|
||||
}
|
||||
|
||||
public BasicRumbleEffect repeat(int count) {
|
||||
Validate.isTrue(count > 0, "count must be greater than 0");
|
||||
|
||||
if (count == 1) return this;
|
||||
|
||||
BasicRumbleEffect effect = this;
|
||||
for (int i = 0; i < count - 1; i++) {
|
||||
effect = BasicRumbleEffect.join(effect, this);
|
||||
}
|
||||
return effect;
|
||||
public static BooleanSupplier finishOnScreenChange() {
|
||||
Screen screen = Minecraft.getInstance().screen;
|
||||
return () -> screen != Minecraft.getInstance().screen;
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
"fabricloader": ">=0.14.0",
|
||||
"minecraft": "~1.19.4",
|
||||
"java": ">=17",
|
||||
"yet-another-config-lib": ">=2.4.0"
|
||||
"yet-another-config-lib": "^2.4.0"
|
||||
},
|
||||
"breaks": {
|
||||
"midnightcontrols": "*"
|
||||
|
Reference in New Issue
Block a user