1
0
forked from Clones/Controlify

joystick axis rendering (no textures), improve config error handling and fix joystick deadzones being unordered

This commit is contained in:
isXander
2023-02-17 00:46:40 +00:00
parent fa1a3331e6
commit 8e31472c07
89 changed files with 169 additions and 104 deletions

View File

@ -125,7 +125,10 @@ public class ControllerBindings<T extends ControllerState> {
public void fromJson(JsonObject json) { public void fromJson(JsonObject json) {
for (var binding : registry().values()) { for (var binding : registry().values()) {
var bind = json.get(binding.id().toString()).getAsJsonObject(); var bind = json.get(binding.id().toString()).getAsJsonObject();
if (bind == null) continue; if (bind == null) {
Controlify.LOGGER.warn("Unknown control: " + binding.id() + " in config file. Skipping!");
continue;
}
binding.setCurrentBind(IBind.fromJson(bind, controller)); binding.setCurrentBind(IBind.fromJson(bind, controller));
} }
} }

View File

@ -72,7 +72,7 @@ public enum GamepadBind implements IBind<GamepadState> {
public void draw(PoseStack matrices, int x, int centerY, Controller<GamepadState, ?> controller) { public void draw(PoseStack matrices, int x, int centerY, Controller<GamepadState, ?> controller) {
ResourceLocation texture; ResourceLocation texture;
if (((GamepadConfig)controller.config()).theme == BuiltinGamepadTheme.DEFAULT) { if (((GamepadConfig)controller.config()).theme == BuiltinGamepadTheme.DEFAULT) {
texture = new ResourceLocation("controlify", "textures/gui/gamepad_buttons/" + controller.type().identifier() + "/" + identifier + ".png"); texture = new ResourceLocation("controlify", "textures/gui/gamepad/" + controller.type().identifier() + "/" + identifier + ".png");
} else { } else {
texture = textureLocations.get(((GamepadConfig)controller.config()).theme); texture = textureLocations.get(((GamepadConfig)controller.config()).theme);
} }

View File

@ -32,14 +32,10 @@ public interface IBind<S extends ControllerState> {
case JoystickButtonBind.BIND_ID -> JoystickButtonBind.fromJson(json, joystick); case JoystickButtonBind.BIND_ID -> JoystickButtonBind.fromJson(json, joystick);
case JoystickHatBind.BIND_ID -> JoystickHatBind.fromJson(json, joystick); case JoystickHatBind.BIND_ID -> JoystickHatBind.fromJson(json, joystick);
case JoystickAxisBind.BIND_ID -> JoystickAxisBind.fromJson(json, joystick); case JoystickAxisBind.BIND_ID -> JoystickAxisBind.fromJson(json, joystick);
default -> { default -> throw new IllegalStateException("Unknown bind type for joystick: " + type);
Controlify.LOGGER.error("Unknown bind type: " + type);
yield new EmptyBind<>();
}
}; };
} }
Controlify.LOGGER.error("Could not parse bind for controller: " + controller.name()); throw new IllegalStateException("Unknown controller type: " + controller.getClass().getName());
return new EmptyBind<>();
} }
} }

View File

@ -1,13 +1,18 @@
package dev.isxander.controlify.bindings; package dev.isxander.controlify.bindings;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.joystick.JoystickController; import dev.isxander.controlify.controller.joystick.JoystickController;
import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.controller.joystick.JoystickState;
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
import dev.isxander.controlify.controller.joystick.mapping.UnmappedJoystickMapping;
import dev.isxander.controlify.gui.DrawSize; import dev.isxander.controlify.gui.DrawSize;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiComponent;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import java.util.Objects; import java.util.Objects;
@ -35,22 +40,32 @@ public class JoystickAxisBind implements IBind<JoystickState> {
@Override @Override
public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) { public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) {
var font = Minecraft.getInstance().font; if (controller != joystick) return;
font.drawShadow(matrices, getTempButtonName(), x + 1.5f, centerY - font.lineHeight / 2f, 0xFFFFFF); JoystickMapping mapping = joystick.mapping();
String type = joystick.type().identifier();
String axis = mapping.axis(axisIndex).identifier();
String direction = mapping.axis(axisIndex).getDirectionIdentifier(axisIndex, this.direction);
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/axis_" + axis + "_" + direction + ".png");
RenderSystem.setShaderTexture(0, texture);
RenderSystem.setShaderColor(1, 1, 1, 1);
GuiComponent.blit(matrices, x, centerY - 11, 0, 0, 22, 22, 22, 22);
if (mapping instanceof UnmappedJoystickMapping) {
var text = Integer.toString(axisIndex + 1);
var font = Minecraft.getInstance().font;
GuiComponent.drawCenteredString(matrices, font, text, x + 11, centerY - font.lineHeight / 2, 0xFFFFFF);
}
} }
@Override @Override
public DrawSize drawSize() { public DrawSize drawSize() {
var font = Minecraft.getInstance().font; int width = 22;
return new DrawSize(font.width(getTempButtonName()) + 3, font.lineHeight); if (joystick.mapping() instanceof UnmappedJoystickMapping)
} width = Math.max(width, Minecraft.getInstance().font.width(Integer.toString(axisIndex + 1)));
private Component getTempButtonName() { return new DrawSize(width, 22);
var axis = joystick.mapping().axis(axisIndex);
return Component.empty()
.append(axis.name())
.append(" ")
.append(axis.getDirectionName(axisIndex, direction));
} }
@Override @Override

View File

@ -1,13 +1,16 @@
package dev.isxander.controlify.bindings; package dev.isxander.controlify.bindings;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.joystick.JoystickController; import dev.isxander.controlify.controller.joystick.JoystickController;
import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.controller.joystick.JoystickState;
import dev.isxander.controlify.gui.DrawSize; import dev.isxander.controlify.gui.DrawSize;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiComponent;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import java.util.Objects; import java.util.Objects;
@ -29,19 +32,20 @@ public class JoystickButtonBind implements IBind<JoystickState> {
@Override @Override
public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) { public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) {
var font = Minecraft.getInstance().font; if (controller != joystick) return;
font.drawShadow(matrices, getTempButtonName(), x + 1.5f, centerY - font.lineHeight / 2f, 0xFFFFFF); String type = joystick.type().identifier();
String button = joystick.mapping().button(buttonIndex).identifier();
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/button_" + button + ".png");
RenderSystem.setShaderTexture(0, texture);
RenderSystem.setShaderColor(1, 1, 1, 1);
GuiComponent.blit(matrices, x, centerY - 11, 0, 0, 22, 22, 22, 22);
} }
@Override @Override
public DrawSize drawSize() { public DrawSize drawSize() {
var font = Minecraft.getInstance().font; return new DrawSize(22, 22);
return new DrawSize(font.width(getTempButtonName()) + 3, font.lineHeight);
}
private Component getTempButtonName() {
return joystick.mapping().button(buttonIndex).name();
} }
@Override @Override

View File

@ -1,13 +1,16 @@
package dev.isxander.controlify.bindings; package dev.isxander.controlify.bindings;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.controller.joystick.JoystickController; import dev.isxander.controlify.controller.joystick.JoystickController;
import dev.isxander.controlify.controller.joystick.JoystickState; import dev.isxander.controlify.controller.joystick.JoystickState;
import dev.isxander.controlify.gui.DrawSize; import dev.isxander.controlify.gui.DrawSize;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiComponent;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import java.util.Objects; import java.util.Objects;
@ -31,21 +34,30 @@ public class JoystickHatBind implements IBind<JoystickState> {
@Override @Override
public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) { public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) {
var font = Minecraft.getInstance().font; if (controller != joystick) return;
font.drawShadow(matrices, getTempButtonName(), x + 1.5f, centerY - font.lineHeight / 2f, 0xFFFFFF);
String type = joystick.type().identifier();
String button = joystick.mapping().button(hatIndex).identifier();
String direction = "centered";
if (hatState.isUp())
direction = "up";
else if (hatState.isDown())
direction = "down";
else if (hatState.isLeft())
direction = "left";
else if (hatState.isRight())
direction = "right";
var texture = new ResourceLocation("controlify", "textures/gui/joystick/" + type + "/hat" + button + "_" + direction + ".png");
RenderSystem.setShaderTexture(0, texture);
RenderSystem.setShaderColor(1, 1, 1, 1);
GuiComponent.blit(matrices, x, centerY - 11, 0, 0, 22, 22, 22, 22);
} }
@Override @Override
public DrawSize drawSize() { public DrawSize drawSize() {
var font = Minecraft.getInstance().font; return new DrawSize(22, 22);
return new DrawSize(font.width(getTempButtonName()) + 3, font.lineHeight);
}
private Component getTempButtonName() {
return Component.empty()
.append(joystick.mapping().hat(hatIndex).name())
.append(" ")
.append(hatState.getDisplayName());
} }
@Override @Override

View File

@ -105,8 +105,14 @@ public class ControlifyConfig {
} }
private void applyControllerConfig(Controller<?, ?> controller, JsonObject object) { private void applyControllerConfig(Controller<?, ?> controller, JsonObject object) {
controller.setConfig(GSON, object.getAsJsonObject("config")); try {
controller.bindings().fromJson(object.getAsJsonObject("bindings")); controller.setConfig(GSON, object.getAsJsonObject("config"));
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
} catch (Exception e) {
Controlify.LOGGER.error("Failed to load controller data for " + controller.uid() + ". Resetting to default!", e);
controller.resetConfig();
save();
}
} }
public GlobalSettings globalSettings() { public GlobalSettings globalSettings() {

View File

@ -27,6 +27,7 @@ import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@ -190,7 +191,8 @@ public class YACLHelper {
.collect(Collectors.toMap( .collect(Collectors.toMap(
i -> joystick.mapping().axis(i).identifier(), i -> joystick.mapping().axis(i).identifier(),
i -> i, i -> i,
(x, y) -> x (x, y) -> x,
LinkedHashMap::new
)) ))
.values(); .values();
var jsCfg = joystick.config(); var jsCfg = joystick.config();

View File

@ -97,6 +97,11 @@ public abstract class AbstractController<S extends ControllerState, C extends Co
return this.defaultConfig; return this.defaultConfig;
} }
@Override
public void resetConfig() {
this.config = defaultConfig();
}
@Override @Override
public void setConfig(Gson gson, JsonElement json) { public void setConfig(Gson gson, JsonElement json) {
C newConfig = gson.fromJson(json, new TypeToken<C>(getClass()){}.getType()); C newConfig = gson.fromJson(json, new TypeToken<C>(getClass()){}.getType());

View File

@ -5,6 +5,7 @@ import com.google.gson.JsonElement;
import dev.isxander.controlify.bindings.ControllerBindings; import dev.isxander.controlify.bindings.ControllerBindings;
import dev.isxander.controlify.controller.gamepad.GamepadController; import dev.isxander.controlify.controller.gamepad.GamepadController;
import dev.isxander.controlify.controller.joystick.JoystickController; import dev.isxander.controlify.controller.joystick.JoystickController;
import dev.isxander.controlify.debug.DebugProperties;
import org.hid4java.HidDevice; import org.hid4java.HidDevice;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
@ -24,6 +25,7 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
C config(); C config();
C defaultConfig(); C defaultConfig();
void resetConfig();
void setConfig(Gson gson, JsonElement json); void setConfig(Gson gson, JsonElement json);
ControllerType type(); ControllerType type();
@ -39,7 +41,7 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
return CONTROLLERS.get(joystickId); return CONTROLLERS.get(joystickId);
} }
if (GLFW.glfwJoystickIsGamepad(joystickId)) { if (GLFW.glfwJoystickIsGamepad(joystickId) && !DebugProperties.FORCE_JOYSTICK) {
GamepadController controller = new GamepadController(joystickId, device); GamepadController controller = new GamepadController(joystickId, device);
CONTROLLERS.put(joystickId, controller); CONTROLLERS.put(joystickId, controller);
return controller; return controller;
@ -94,6 +96,11 @@ public interface Controller<S extends ControllerState, C extends ControllerConfi
return config; return config;
} }
@Override
public void resetConfig() {
}
@Override @Override
public void setConfig(Gson gson, JsonElement json) { public void setConfig(Gson gson, JsonElement json) {

View File

@ -6,17 +6,12 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class JoystickConfig extends ControllerConfig { public class JoystickConfig extends ControllerConfig {
private final Map<String, Float> deadzones; private Map<String, Float> deadzones;
private transient JoystickController controller; private transient JoystickController controller;
public JoystickConfig(JoystickController controller) { public JoystickConfig(JoystickController controller) {
this.controller = controller; setup(controller);
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);
}
} }
@Override @Override
@ -35,7 +30,14 @@ public class JoystickConfig extends ControllerConfig {
return deadzones.getOrDefault(controller.mapping().axis(axis).identifier(), 0.2f); return deadzones.getOrDefault(controller.mapping().axis(axis).identifier(), 0.2f);
} }
void setController(JoystickController controller) { void setup(JoystickController controller) {
this.controller = 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);
}
}
} }
} }

View File

@ -64,6 +64,6 @@ public class JoystickController extends AbstractController<JoystickState, Joysti
@Override @Override
public void setConfig(Gson gson, JsonElement json) { public void setConfig(Gson gson, JsonElement json) {
super.setConfig(gson, json); super.setConfig(gson, json);
this.config.setController(this); this.config.setup(this);
} }
} }

View File

@ -133,9 +133,8 @@ public class DataJoystickMapping implements JoystickMapping {
} }
@Override @Override
public Component getDirectionName(int axis, JoystickAxisBind.AxisDirection direction) { public String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction) {
var directionId = axisNames().get(ids.indexOf(axis)).get(direction.ordinal()); return this.axisNames().get(ids.indexOf(axis)).get(direction.ordinal());
return Component.translatable("controlify.joystick_mapping." + typeId() + ".axis." + identifier() + "." + directionId);
} }
} }
@ -144,6 +143,8 @@ public class DataJoystickMapping implements JoystickMapping {
public Component name() { public Component name() {
return Component.translatable("controlify.joystick_mapping." + typeId() + ".button." + identifier()); return Component.translatable("controlify.joystick_mapping." + typeId() + ".button." + identifier());
} }
} }
private record HatMapping(String identifier, String typeId) implements Hat { private record HatMapping(String identifier, String typeId) implements Hat {

View File

@ -21,7 +21,7 @@ public interface JoystickMapping {
boolean isAxisResting(float value); boolean isAxisResting(float value);
Component getDirectionName(int axis, JoystickAxisBind.AxisDirection direction); String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction);
} }
interface Button { interface Button {

View File

@ -24,36 +24,36 @@ public class UnmappedJoystickMapping implements JoystickMapping {
private record UnmappedAxis(int axis) implements Axis { private record UnmappedAxis(int axis) implements Axis {
@Override @Override
public String identifier() { public String identifier() {
return "axis-" + axis; return "axis-" + axis;
}
@Override
public Component name() {
return Component.translatable("controlify.joystick_mapping.unmapped.axis", axis + 1);
}
@Override
public boolean requiresDeadzone() {
return true;
}
@Override
public float modifyAxis(float value) {
return value;
}
@Override
public boolean isAxisResting(float value) {
return value == 0;
}
@Override
public Component getDirectionName(int axis, JoystickAxisBind.AxisDirection direction) {
return Component.translatable("controlify.joystick_mapping.unmapped.axis_direction." + direction.name().toLowerCase());
}
} }
@Override
public Component name() {
return Component.translatable("controlify.joystick_mapping.unmapped.axis", axis + 1);
}
@Override
public boolean requiresDeadzone() {
return true;
}
@Override
public float modifyAxis(float value) {
return value;
}
@Override
public boolean isAxisResting(float value) {
return value == 0;
}
@Override
public String getDirectionIdentifier(int axis, JoystickAxisBind.AxisDirection direction) {
return direction.name().toLowerCase();
}
}
private record UnmappedButton(int button) implements Button { private record UnmappedButton(int button) implements Button {
@Override @Override
public String identifier() { public String identifier() {

View File

@ -0,0 +1,12 @@
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);
private static boolean boolProp(String name, boolean def) {
return Boolean.parseBoolean(System.getProperty(name, Boolean.toString(def)));
}
}

View File

@ -6,6 +6,7 @@ import com.mojang.datafixers.util.Pair;
import dev.isxander.controlify.Controlify; import dev.isxander.controlify.Controlify;
import dev.isxander.controlify.InputMode; import dev.isxander.controlify.InputMode;
import dev.isxander.controlify.controller.Controller; import dev.isxander.controlify.controller.Controller;
import dev.isxander.controlify.debug.DebugProperties;
import dev.isxander.controlify.screenop.ScreenProcessorProvider; import dev.isxander.controlify.screenop.ScreenProcessorProvider;
import dev.isxander.controlify.event.ControlifyEvents; import dev.isxander.controlify.event.ControlifyEvents;
import dev.isxander.controlify.mixins.feature.virtualmouse.KeyboardHandlerAccessor; import dev.isxander.controlify.mixins.feature.virtualmouse.KeyboardHandlerAccessor;
@ -26,7 +27,6 @@ import java.util.Comparator;
import java.util.Set; import java.util.Set;
public class VirtualMouseHandler { public class VirtualMouseHandler {
private static final boolean DEBUG_SNAPPING = FabricLoader.getInstance().isDevelopmentEnvironment();
private static final ResourceLocation CURSOR_TEXTURE = new ResourceLocation("controlify", "textures/gui/virtual_mouse.png"); private static final ResourceLocation CURSOR_TEXTURE = new ResourceLocation("controlify", "textures/gui/virtual_mouse.png");
private double targetX, targetY; private double targetX, targetY;
@ -201,7 +201,7 @@ public class VirtualMouseHandler {
public void renderVirtualMouse(PoseStack matrices) { public void renderVirtualMouse(PoseStack matrices) {
if (!virtualMouseEnabled) return; if (!virtualMouseEnabled) return;
if (DEBUG_SNAPPING) { if (DebugProperties.DEBUG_SNAPPING) {
for (var snapPoint : snapPoints) { for (var snapPoint : snapPoints) {
GuiComponent.fill(matrices, snapPoint.position().x() - snapPoint.range(), snapPoint.position().y() - snapPoint.range(), snapPoint.position().x() + snapPoint.range(), snapPoint.position().y() + snapPoint.range(), 0x33FFFFFF); GuiComponent.fill(matrices, snapPoint.position().x() - snapPoint.range(), snapPoint.position().y() - snapPoint.range(), snapPoint.position().x() + snapPoint.range(), snapPoint.position().y() + snapPoint.range(), 0x33FFFFFF);
GuiComponent.fill(matrices, snapPoint.position().x() - 1, snapPoint.position().y() - 1, snapPoint.position().x() + 1, snapPoint.position().y() + 1, snapPoint.equals(lastSnappedPoint) ? 0xFFFFFF00 : 0xFFFF0000); GuiComponent.fill(matrices, snapPoint.position().x() - 1, snapPoint.position().y() - 1, snapPoint.position().x() + 1, snapPoint.position().y() + 1, snapPoint.equals(lastSnappedPoint) ? 0xFFFFFF00 : 0xFFFF0000);

View File

@ -1,25 +1,25 @@
// THIS FILE IS PARSED BY LENIENT GSON PARSER AND IS NOT JSON5 COMPLIANT! // THIS FILE IS PARSED BY LENIENT GSON PARSER AND IS NOT JSON5 COMPLIANT!
[ [
{ // {
"name": "Xbox One Controller", // "name": "Xbox One Controller",
"identifier": "xbox_one", // "identifier": "xbox_one",
//
"vendor": 1118, // 0x45e // "vendor": 1118, // 0x45e
"product": [ // "product": [
767, // 0x2ff // 767, // 0x2ff
746, // 0x2ea // 746, // 0x2ea
2834, // 0xb12 // 2834, // 0xb12
733, // 0x2dd // 733, // 0x2dd
739, // 0x2e3 // 739, // 0x2e3
742, // 0x2e6 // 742, // 0x2e6
765, // 0x2fd // 765, // 0x2fd
721, // 0x2d1 // 721, // 0x2d1
649, // 0x289 // 649, // 0x289
514, // 0x202 // 514, // 0x202
645, // 0x285 // 645, // 0x285
648 // 0x288 // 648 // 0x288
] // ]
}, // },
{ {
"name": "Dualshock 4 Controller", "name": "Dualshock 4 Controller",
"identifier": "dualshock4", "identifier": "dualshock4",