forked from Clones/Controlify
rewrite most of joystick mapping
This commit is contained in:
@ -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;
|
||||
|
||||
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());
|
||||
inpRange = new Vec2(-1, 1);
|
||||
} else if (rangeElement.isJsonObject()) {
|
||||
var rangeObject = rangeElement.getAsJsonObject();
|
||||
|
||||
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());
|
||||
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();
|
||||
}
|
||||
}
|
||||
var restState = axis.get("rest").getAsFloat();
|
||||
var deadzone = axis.get("deadzone").getAsBoolean();
|
||||
var identifier = axis.get("identifier").getAsString();
|
||||
}
|
||||
reader.endObject();
|
||||
|
||||
var axisNames = axis.getAsJsonArray("axis_names").asList().stream()
|
||||
.map(JsonElement::getAsJsonArray)
|
||||
.map(JsonArray::asList)
|
||||
.map(list -> list.stream().map(JsonElement::getAsString).toList())
|
||||
.toList();
|
||||
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;
|
||||
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);
|
||||
reader.endArray();
|
||||
} else {
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String rangeName = reader.nextName();
|
||||
|
||||
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']");
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
Reference in New Issue
Block a user