joystick support
@ -1,17 +1,18 @@
|
|||||||
package dev.isxander.controlify;
|
package dev.isxander.controlify;
|
||||||
|
|
||||||
import com.mojang.logging.LogUtils;
|
import com.mojang.logging.LogUtils;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||||
import dev.isxander.controlify.config.ControlifyConfig;
|
import dev.isxander.controlify.config.ControlifyConfig;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
|
||||||
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
import dev.isxander.controlify.controller.hid.ControllerHIDService;
|
||||||
import dev.isxander.controlify.event.ControlifyEvents;
|
import dev.isxander.controlify.event.ControlifyEvents;
|
||||||
import dev.isxander.controlify.ingame.guide.InGameButtonGuide;
|
import dev.isxander.controlify.ingame.guide.InGameButtonGuide;
|
||||||
import dev.isxander.controlify.ingame.InGameInputHandler;
|
import dev.isxander.controlify.ingame.InGameInputHandler;
|
||||||
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.components.toasts.SystemToast;
|
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||||
@ -27,7 +28,7 @@ public class Controlify {
|
|||||||
public static final Logger LOGGER = LogUtils.getLogger();
|
public static final Logger LOGGER = LogUtils.getLogger();
|
||||||
private static Controlify instance = null;
|
private static Controlify instance = null;
|
||||||
|
|
||||||
private Controller currentController;
|
private Controller<?, ?> currentController;
|
||||||
private InGameInputHandler inGameInputHandler;
|
private InGameInputHandler inGameInputHandler;
|
||||||
public InGameButtonGuide inGameButtonGuide;
|
public InGameButtonGuide inGameButtonGuide;
|
||||||
private VirtualMouseHandler virtualMouseHandler;
|
private VirtualMouseHandler virtualMouseHandler;
|
||||||
@ -36,12 +37,15 @@ public class Controlify {
|
|||||||
|
|
||||||
private final ControlifyConfig config = new ControlifyConfig();
|
private final ControlifyConfig config = new ControlifyConfig();
|
||||||
|
|
||||||
private final Queue<Controller> calibrationQueue = new ArrayDeque<>();
|
private final Queue<Controller<?, ?>> calibrationQueue = new ArrayDeque<>();
|
||||||
|
|
||||||
|
public void initializeControllers() {
|
||||||
|
LOGGER.info("Discovering and initializing controllers...");
|
||||||
|
|
||||||
public void onInitializeInput() {
|
|
||||||
Minecraft minecraft = Minecraft.getInstance();
|
Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
|
||||||
inGameInputHandler = new InGameInputHandler(Controller.DUMMY); // initialize with dummy controller before connection in case of no controllers
|
config().load();
|
||||||
|
|
||||||
controllerHIDService = new ControllerHIDService();
|
controllerHIDService = new ControllerHIDService();
|
||||||
|
|
||||||
// find already connected controllers
|
// find already connected controllers
|
||||||
@ -49,7 +53,7 @@ public class Controlify {
|
|||||||
if (GLFW.glfwJoystickPresent(i)) {
|
if (GLFW.glfwJoystickPresent(i)) {
|
||||||
int jid = i;
|
int jid = i;
|
||||||
controllerHIDService.awaitNextController(device -> {
|
controllerHIDService.awaitNextController(device -> {
|
||||||
setCurrentController(Controller.create(jid, device));
|
setCurrentController(Controller.createOrGet(jid, device));
|
||||||
LOGGER.info("Controller found: " + currentController.name());
|
LOGGER.info("Controller found: " + currentController.name());
|
||||||
|
|
||||||
if (!config().loadOrCreateControllerData(currentController)) {
|
if (!config().loadOrCreateControllerData(currentController)) {
|
||||||
@ -61,13 +65,11 @@ public class Controlify {
|
|||||||
|
|
||||||
controllerHIDService.start();
|
controllerHIDService.start();
|
||||||
|
|
||||||
config().load();
|
|
||||||
|
|
||||||
// listen for new controllers
|
// listen for new controllers
|
||||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||||
if (event == GLFW.GLFW_CONNECTED) {
|
if (event == GLFW.GLFW_CONNECTED) {
|
||||||
controllerHIDService.awaitNextController(device -> {
|
controllerHIDService.awaitNextController(device -> {
|
||||||
setCurrentController(Controller.create(jid, device));
|
setCurrentController(Controller.createOrGet(jid, device));
|
||||||
LOGGER.info("Controller connected: " + currentController.name());
|
LOGGER.info("Controller connected: " + currentController.name());
|
||||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||||
|
|
||||||
@ -82,11 +84,10 @@ public class Controlify {
|
|||||||
Component.translatable("controlify.toast.controller_connected.description", currentController.name())
|
Component.translatable("controlify.toast.controller_connected.description", currentController.name())
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||||
var controller = Controller.CONTROLLERS.remove(jid);
|
var controller = Controller.CONTROLLERS.remove(jid);
|
||||||
if (controller != null) {
|
if (controller != null) {
|
||||||
setCurrentController(Controller.CONTROLLERS.values().stream().filter(Controller::connected).findFirst().orElse(null));
|
setCurrentController(Controller.CONTROLLERS.values().stream().findFirst().orElse(null));
|
||||||
LOGGER.info("Controller disconnected: " + controller.name());
|
LOGGER.info("Controller disconnected: " + controller.name());
|
||||||
this.setCurrentInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
this.setCurrentInputMode(currentController == null ? InputMode.KEYBOARD_MOUSE : InputMode.CONTROLLER);
|
||||||
|
|
||||||
@ -100,11 +101,14 @@ public class Controlify {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.virtualMouseHandler = new VirtualMouseHandler();
|
|
||||||
|
|
||||||
ClientTickEvents.START_CLIENT_TICK.register(this::tick);
|
ClientTickEvents.START_CLIENT_TICK.register(this::tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void initializeControlify() {
|
||||||
|
this.inGameInputHandler = new InGameInputHandler(Controller.DUMMY); // initialize with dummy controller before connection in case of no controller
|
||||||
|
this.virtualMouseHandler = new VirtualMouseHandler();
|
||||||
|
}
|
||||||
|
|
||||||
public void tick(Minecraft client) {
|
public void tick(Minecraft client) {
|
||||||
var minecraft = Minecraft.getInstance();
|
var minecraft = Minecraft.getInstance();
|
||||||
if (minecraft.getOverlay() == null) {
|
if (minecraft.getOverlay() == null) {
|
||||||
@ -124,7 +128,7 @@ public class Controlify {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Controller controller : Controller.CONTROLLERS.values()) {
|
for (var controller : Controller.CONTROLLERS.values()) {
|
||||||
controller.updateState();
|
controller.updateState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,11 +158,11 @@ public class Controlify {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Controller currentController() {
|
public Controller<?, ?> currentController() {
|
||||||
return currentController;
|
return currentController;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentController(Controller controller) {
|
public void setCurrentController(Controller<?, ?> controller) {
|
||||||
if (this.currentController == controller) return;
|
if (this.currentController == controller) return;
|
||||||
this.currentController = controller;
|
this.currentController = controller;
|
||||||
|
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
|
||||||
import dev.isxander.controlify.gui.ButtonRenderer;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
|
|
||||||
import java.util.function.BiFunction;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public enum Bind implements IBind {
|
|
||||||
A_BUTTON(state -> state.buttons().a(), "a_button"),
|
|
||||||
B_BUTTON(state -> state.buttons().b(), "b_button"),
|
|
||||||
X_BUTTON(state -> state.buttons().x(), "x_button"),
|
|
||||||
Y_BUTTON(state -> state.buttons().y(), "y_button"),
|
|
||||||
LEFT_BUMPER(state -> state.buttons().leftBumper(), "left_bumper"),
|
|
||||||
RIGHT_BUMPER(state -> state.buttons().rightBumper(), "right_bumper"),
|
|
||||||
LEFT_STICK_PRESS(state -> state.buttons().leftStick(), "left_stick_press"),
|
|
||||||
RIGHT_STICK_PRESS(state -> state.buttons().rightStick(), "right_stick_press"),
|
|
||||||
START(state -> state.buttons().start(), "start"),
|
|
||||||
BACK(state -> state.buttons().back(), "back"),
|
|
||||||
GUIDE(state -> state.buttons().guide(), "guide"), // the middle button
|
|
||||||
DPAD_UP(state -> state.buttons().dpadUp(), "dpad_up"),
|
|
||||||
DPAD_DOWN(state -> state.buttons().dpadDown(), "dpad_down"),
|
|
||||||
DPAD_LEFT(state -> state.buttons().dpadLeft(), "dpad_left"),
|
|
||||||
DPAD_RIGHT(state -> state.buttons().dpadRight(), "dpad_right"),
|
|
||||||
LEFT_TRIGGER((state, controller) -> state.axes().leftTrigger(), "left_trigger"),
|
|
||||||
RIGHT_TRIGGER((state, controller) -> state.axes().rightTrigger(), "right_trigger"),
|
|
||||||
LEFT_STICK_FORWARD((state, controller) -> -Math.min(0, state.axes().leftStickY()), "left_stick_up"),
|
|
||||||
LEFT_STICK_BACKWARD((state, controller) -> Math.max(0, state.axes().leftStickY()), "left_stick_down"),
|
|
||||||
LEFT_STICK_LEFT((state, controller) -> -Math.min(0, state.axes().leftStickX()), "left_stick_left"),
|
|
||||||
LEFT_STICK_RIGHT((state, controller) -> Math.max(0, state.axes().leftStickX()), "left_stick_right"),
|
|
||||||
RIGHT_STICK_FORWARD((state, controller) -> -Math.min(0, state.axes().rightStickY()), "right_stick_up"),
|
|
||||||
RIGHT_STICK_BACKWARD((state, controller) -> Math.max(0, state.axes().rightStickY()), "right_stick_down"),
|
|
||||||
RIGHT_STICK_LEFT((state, controller) -> -Math.min(0, state.axes().rightStickX()), "right_stick_left"),
|
|
||||||
RIGHT_STICK_RIGHT((state, controller) -> Math.max(0, state.axes().rightStickX()), "right_stick_right"),
|
|
||||||
NONE((state, controller) -> 0f, "none");
|
|
||||||
|
|
||||||
private final BiFunction<ControllerState, Controller, Float> state;
|
|
||||||
private final String identifier;
|
|
||||||
|
|
||||||
Bind(BiFunction<ControllerState, Controller, Float> state, String identifier) {
|
|
||||||
this.state = state;
|
|
||||||
this.identifier = identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bind(Function<ControllerState, Boolean> state, String identifier) {
|
|
||||||
this((state1, controller) -> state.apply(state1) ? 1f : 0f, identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float state(ControllerState state, Controller controller) {
|
|
||||||
return this.state.apply(state, controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(PoseStack matrices, int x, int centerY, Controller controller) {
|
|
||||||
if (this != NONE)
|
|
||||||
ButtonRenderer.drawButton(this, controller, matrices, x, centerY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ButtonRenderer.DrawSize drawSize() {
|
|
||||||
if (this == NONE) return new ButtonRenderer.DrawSize(0, 0);
|
|
||||||
|
|
||||||
return new ButtonRenderer.DrawSize(22, 22);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String identifier() {
|
|
||||||
return identifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourceLocation textureLocation(Controller controller) {
|
|
||||||
return new ResourceLocation("controlify", "textures/gui/buttons/" + controller.config().theme.id() + "/" + identifier + ".png");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JsonElement toJson() {
|
|
||||||
return new JsonPrimitive(identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Bind fromIdentifier(String identifier) {
|
|
||||||
for (Bind bind : values()) {
|
|
||||||
if (bind.identifier.equals(identifier)) return bind;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,9 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface BindingSupplier {
|
public interface BindingSupplier<T extends ControllerState> {
|
||||||
ControllerBinding get(Controller controller);
|
ControllerBinding<T> get(Controller<T, ?> controller);
|
||||||
}
|
}
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
|
||||||
import dev.isxander.controlify.gui.ButtonRenderer;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class CompoundBind implements IBind {
|
|
||||||
private final Set<Bind> binds;
|
|
||||||
|
|
||||||
CompoundBind(Bind... binds) {
|
|
||||||
this.binds = new LinkedHashSet<>(Arrays.asList(binds));
|
|
||||||
if (this.binds.contains(Bind.NONE)) throw new IllegalArgumentException("Cannot have NONE in a compound bind!");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<Bind> binds() {
|
|
||||||
return ImmutableSet.copyOf(binds);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float state(ControllerState state, Controller controller) {
|
|
||||||
return held(state, controller) ? 1f : 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean held(ControllerState state, Controller controller) {
|
|
||||||
return binds.stream().allMatch(bind -> bind.held(state, controller));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void draw(PoseStack matrices, int x, int centerY, Controller controller) {
|
|
||||||
var font = Minecraft.getInstance().font;
|
|
||||||
|
|
||||||
var iterator = binds.iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
var bind = iterator.next();
|
|
||||||
|
|
||||||
bind.draw(matrices, x, centerY, controller);
|
|
||||||
x += bind.drawSize().width();
|
|
||||||
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
font.drawShadow(matrices, "+", x + 1, centerY - font.lineHeight / 2f, 0xFFFFFF);
|
|
||||||
x += font.width("+") + 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ButtonRenderer.DrawSize drawSize() {
|
|
||||||
return new ButtonRenderer.DrawSize(
|
|
||||||
binds.stream().map(IBind::drawSize).mapToInt(ButtonRenderer.DrawSize::width).sum() + (binds.size() - 1) * (2 + Minecraft.getInstance().font.width("+")),
|
|
||||||
binds.stream().map(IBind::drawSize).mapToInt(ButtonRenderer.DrawSize::height).max().orElse(0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JsonElement toJson() {
|
|
||||||
var list = new JsonArray();
|
|
||||||
for (IBind bind : binds) {
|
|
||||||
list.add(bind.toJson());
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
return obj instanceof CompoundBind compoundBind && compoundBind.binds.equals(binds)
|
|
||||||
|| obj instanceof Bind bind && Set.of(bind).equals(binds);
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,6 +2,7 @@ package dev.isxander.controlify.bindings;
|
|||||||
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
import net.minecraft.locale.Language;
|
import net.minecraft.locale.Language;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
@ -13,17 +14,17 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
public class ControllerBinding {
|
public class ControllerBinding<T extends ControllerState> {
|
||||||
private final Controller controller;
|
private final Controller<T, ?> controller;
|
||||||
private IBind bind;
|
private IBind<T> bind;
|
||||||
private final IBind defaultBind;
|
private final IBind<T> defaultBind;
|
||||||
private final ResourceLocation id;
|
private final ResourceLocation id;
|
||||||
private final Component name, description;
|
private final Component name, description;
|
||||||
private final KeyMappingOverride override;
|
private final KeyMappingOverride override;
|
||||||
|
|
||||||
private static final Map<Controller, Set<Bind>> pressedBinds = new HashMap<>();
|
private static final Map<Controller<?, ?>, Set<IBind<?>>> pressedBinds = new HashMap<>();
|
||||||
|
|
||||||
public ControllerBinding(Controller controller, IBind defaultBind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride) {
|
public ControllerBinding(Controller<T, ?> controller, IBind<T> defaultBind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.bind = this.defaultBind = defaultBind;
|
this.bind = this.defaultBind = defaultBind;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
@ -33,22 +34,38 @@ public class ControllerBinding {
|
|||||||
this.override = override != null ? new KeyMappingOverride(override, toggleOverride) : null;
|
this.override = override != null ? new KeyMappingOverride(override, toggleOverride) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControllerBinding(Controller controller, IBind defaultBind, ResourceLocation id) {
|
public ControllerBinding(Controller<T, ?> controller, IBind<T> defaultBind, ResourceLocation id) {
|
||||||
|
this(controller, defaultBind, id, null, () -> false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerBinding(Controller<T, ?> controller, GamepadBind defaultBind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride) {
|
||||||
|
this(controller, controller instanceof GamepadController ? (IBind<T>) defaultBind : new EmptyBind<>(), id, override, toggleOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControllerBinding(Controller<T, ?> controller, GamepadBind defaultBind, ResourceLocation id) {
|
||||||
this(controller, defaultBind, id, null, () -> false);
|
this(controller, defaultBind, id, null, () -> false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float state() {
|
public float state() {
|
||||||
return bind.state(controller.state(), controller);
|
return bind.state(controller.state());
|
||||||
|
}
|
||||||
|
|
||||||
|
public float prevState() {
|
||||||
|
return bind.state(controller.prevState());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean held() {
|
public boolean held() {
|
||||||
return bind.held(controller.state(), controller);
|
return bind.held(controller.state(), controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean prevHeld() {
|
||||||
|
return bind.held(controller.prevState(), controller);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean justPressed() {
|
public boolean justPressed() {
|
||||||
if (hasBindPressed(this)) return false;
|
if (hasBindPressed(this)) return false;
|
||||||
|
|
||||||
if (held() && !bind.held(controller.prevState(), controller)) {
|
if (held() && !prevHeld()) {
|
||||||
addPressedBind(this);
|
addPressedBind(this);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -59,7 +76,7 @@ public class ControllerBinding {
|
|||||||
public boolean justReleased() {
|
public boolean justReleased() {
|
||||||
if (hasBindPressed(this)) return false;
|
if (hasBindPressed(this)) return false;
|
||||||
|
|
||||||
if (!held() && bind.held(controller.prevState(), controller)) {
|
if (!held() && prevHeld()) {
|
||||||
addPressedBind(this);
|
addPressedBind(this);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -67,15 +84,15 @@ public class ControllerBinding {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBind currentBind() {
|
public IBind<T> currentBind() {
|
||||||
return bind;
|
return bind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCurrentBind(IBind bind) {
|
public void setCurrentBind(IBind<T> bind) {
|
||||||
this.bind = bind;
|
this.bind = bind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBind defaultBind() {
|
public IBind<T> defaultBind() {
|
||||||
return defaultBind;
|
return defaultBind;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,27 +114,23 @@ public class ControllerBinding {
|
|||||||
|
|
||||||
// FIXME: very hack solution please remove me
|
// FIXME: very hack solution please remove me
|
||||||
|
|
||||||
public static void clearPressedBinds(Controller controller) {
|
public static void clearPressedBinds(Controller<?, ?> controller) {
|
||||||
if (pressedBinds.containsKey(controller)) {
|
if (pressedBinds.containsKey(controller)) {
|
||||||
pressedBinds.get(controller).clear();
|
pressedBinds.get(controller).clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasBindPressed(ControllerBinding binding) {
|
private static boolean hasBindPressed(ControllerBinding<?> binding) {
|
||||||
var pressed = pressedBinds.getOrDefault(binding.controller, Set.of());
|
var pressed = pressedBinds.getOrDefault(binding.controller, Set.of());
|
||||||
return pressed.containsAll(getBinds(binding.bind));
|
return pressed.containsAll(getBinds(binding.bind));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addPressedBind(ControllerBinding binding) {
|
private static void addPressedBind(ControllerBinding<?> binding) {
|
||||||
pressedBinds.computeIfAbsent(binding.controller, c -> new HashSet<>()).addAll(getBinds(binding.bind));
|
pressedBinds.computeIfAbsent(binding.controller, c -> new HashSet<>()).addAll(getBinds(binding.bind));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<Bind> getBinds(IBind bind) {
|
private static Set<IBind<?>> getBinds(IBind<?> bind) {
|
||||||
if (bind instanceof CompoundBind compoundBind) {
|
return Set.of(bind);
|
||||||
return compoundBind.binds();
|
|
||||||
} else {
|
|
||||||
return Set.of((Bind) bind);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record KeyMappingOverride(KeyMapping keyMapping, BooleanSupplier toggleable) {
|
public record KeyMappingOverride(KeyMapping keyMapping, BooleanSupplier toggleable) {
|
||||||
|
@ -4,6 +4,7 @@ import com.google.gson.JsonObject;
|
|||||||
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.controller.ControllerState;
|
||||||
import dev.isxander.controlify.event.ControlifyEvents;
|
import dev.isxander.controlify.event.ControlifyEvents;
|
||||||
import dev.isxander.controlify.mixins.feature.bind.KeyMappingAccessor;
|
import dev.isxander.controlify.mixins.feature.bind.KeyMappingAccessor;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
@ -12,9 +13,10 @@ import net.minecraft.resources.ResourceLocation;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ControllerBindings {
|
public class ControllerBindings<T extends ControllerState> {
|
||||||
public final ControllerBinding
|
public final ControllerBinding<T>
|
||||||
WALK_FORWARD, WALK_BACKWARD, WALK_LEFT, WALK_RIGHT,
|
WALK_FORWARD, WALK_BACKWARD, WALK_LEFT, WALK_RIGHT,
|
||||||
|
LOOK_UP, LOOK_DOWN, LOOK_LEFT, LOOK_RIGHT,
|
||||||
JUMP, SNEAK,
|
JUMP, SNEAK,
|
||||||
ATTACK, USE,
|
ATTACK, USE,
|
||||||
SPRINT,
|
SPRINT,
|
||||||
@ -27,51 +29,71 @@ public class ControllerBindings {
|
|||||||
OPEN_CHAT,
|
OPEN_CHAT,
|
||||||
GUI_PRESS, GUI_BACK,
|
GUI_PRESS, GUI_BACK,
|
||||||
GUI_NEXT_TAB, GUI_PREV_TAB,
|
GUI_NEXT_TAB, GUI_PREV_TAB,
|
||||||
VMOUSE_LCLICK, VMOUSE_RCLICK, VMOUSE_SHIFT_CLICK, VMOUSE_SCROLL_UP, VMOUSE_SCROLL_DOWN, VMOUSE_ESCAPE, VMOUSE_SHIFT, VMOUSE_TOGGLE,
|
|
||||||
PICK_BLOCK,
|
PICK_BLOCK,
|
||||||
TOGGLE_HUD_VISIBILITY,
|
TOGGLE_HUD_VISIBILITY,
|
||||||
SHOW_PLAYER_LIST;
|
SHOW_PLAYER_LIST,
|
||||||
|
VMOUSE_MOVE_UP, VMOUSE_MOVE_DOWN, VMOUSE_MOVE_LEFT, VMOUSE_MOVE_RIGHT,
|
||||||
|
VMOUSE_LCLICK, VMOUSE_RCLICK, VMOUSE_SHIFT_CLICK,
|
||||||
|
VMOUSE_SCROLL_UP, VMOUSE_SCROLL_DOWN,
|
||||||
|
VMOUSE_ESCAPE, VMOUSE_SHIFT,
|
||||||
|
VMOUSE_TOGGLE,
|
||||||
|
GUI_NAVI_UP, GUI_NAVI_DOWN, GUI_NAVI_LEFT, GUI_NAVI_RIGHT,
|
||||||
|
YACL_CYCLE_OPT_FORWARD, YACL_CYCLE_OPT_BACKWARD;
|
||||||
|
|
||||||
private final Map<ResourceLocation, ControllerBinding> registry = new LinkedHashMap<>();
|
private final Map<ResourceLocation, ControllerBinding<T>> registry = new LinkedHashMap<>();
|
||||||
|
|
||||||
private final Controller controller;
|
private final Controller<T, ?> controller;
|
||||||
|
|
||||||
public ControllerBindings(Controller controller) {
|
public ControllerBindings(Controller<T, ?> controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
var options = Minecraft.getInstance().options;
|
var options = Minecraft.getInstance().options;
|
||||||
|
|
||||||
register(WALK_FORWARD = new ControllerBinding(controller, Bind.LEFT_STICK_FORWARD, new ResourceLocation("controlify", "walk_forward")));
|
register(WALK_FORWARD = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_FORWARD, new ResourceLocation("controlify", "walk_forward")));
|
||||||
register(WALK_BACKWARD = new ControllerBinding(controller, Bind.LEFT_STICK_BACKWARD, new ResourceLocation("controlify", "walk_backward")));
|
register(WALK_BACKWARD = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_BACKWARD, new ResourceLocation("controlify", "walk_backward")));
|
||||||
register(WALK_LEFT = new ControllerBinding(controller, Bind.LEFT_STICK_LEFT, new ResourceLocation("controlify", "strafe_left")));
|
register(WALK_LEFT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_LEFT, new ResourceLocation("controlify", "strafe_left")));
|
||||||
register(WALK_RIGHT = new ControllerBinding(controller, Bind.LEFT_STICK_RIGHT, new ResourceLocation("controlify", "strafe_right")));
|
register(WALK_RIGHT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_RIGHT, new ResourceLocation("controlify", "strafe_right")));
|
||||||
register(JUMP = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "jump"), options.keyJump, () -> false));
|
register(LOOK_UP = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_FORWARD, new ResourceLocation("controlify", "look_up")));
|
||||||
register(SNEAK = new ControllerBinding(controller, Bind.RIGHT_STICK_PRESS, new ResourceLocation("controlify", "sneak"), options.keyShift, () -> controller.config().toggleSneak));
|
register(LOOK_DOWN = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_BACKWARD, new ResourceLocation("controlify", "look_down")));
|
||||||
register(ATTACK = new ControllerBinding(controller, Bind.RIGHT_TRIGGER, new ResourceLocation("controlify", "attack"), options.keyAttack, () -> false));
|
register(LOOK_LEFT = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_LEFT, new ResourceLocation("controlify", "look_left")));
|
||||||
register(USE = new ControllerBinding(controller, Bind.LEFT_TRIGGER, new ResourceLocation("controlify", "use"), options.keyUse, () -> false));
|
register(LOOK_RIGHT = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_RIGHT, new ResourceLocation("controlify", "look_right")));
|
||||||
register(SPRINT = new ControllerBinding(controller, Bind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "sprint"), options.keySprint, () -> controller.config().toggleSprint));
|
register(JUMP = new ControllerBinding<>(controller, GamepadBind.A_BUTTON, new ResourceLocation("controlify", "jump"), options.keyJump, () -> false));
|
||||||
register(DROP = new ControllerBinding(controller, Bind.DPAD_DOWN, new ResourceLocation("controlify", "drop"), options.keyDrop, () -> false));
|
register(SNEAK = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_PRESS, new ResourceLocation("controlify", "sneak"), options.keyShift, () -> controller.config().toggleSneak));
|
||||||
register(NEXT_SLOT = new ControllerBinding(controller, Bind.RIGHT_BUMPER, new ResourceLocation("controlify", "next_slot")));
|
register(ATTACK = new ControllerBinding<>(controller, GamepadBind.RIGHT_TRIGGER, new ResourceLocation("controlify", "attack"), options.keyAttack, () -> false));
|
||||||
register(PREV_SLOT = new ControllerBinding(controller, Bind.LEFT_BUMPER, new ResourceLocation("controlify", "prev_slot")));
|
register(USE = new ControllerBinding<>(controller, GamepadBind.LEFT_TRIGGER, new ResourceLocation("controlify", "use"), options.keyUse, () -> false));
|
||||||
register(PAUSE = new ControllerBinding(controller, Bind.START, new ResourceLocation("controlify", "pause")));
|
register(SPRINT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "sprint"), options.keySprint, () -> controller.config().toggleSprint));
|
||||||
register(INVENTORY = new ControllerBinding(controller, Bind.Y_BUTTON, new ResourceLocation("controlify", "inventory"), options.keyInventory, () -> false));
|
register(DROP = new ControllerBinding<>(controller, GamepadBind.DPAD_DOWN, new ResourceLocation("controlify", "drop"), options.keyDrop, () -> false));
|
||||||
register(CHANGE_PERSPECTIVE = new ControllerBinding(controller, Bind.BACK, new ResourceLocation("controlify", "change_perspective"), options.keyTogglePerspective, () -> false));
|
register(NEXT_SLOT = new ControllerBinding<>(controller, GamepadBind.RIGHT_BUMPER, new ResourceLocation("controlify", "next_slot")));
|
||||||
register(SWAP_HANDS = new ControllerBinding(controller, Bind.X_BUTTON, new ResourceLocation("controlify", "swap_hands"), options.keySwapOffhand, () -> false));
|
register(PREV_SLOT = new ControllerBinding<>(controller, GamepadBind.LEFT_BUMPER, new ResourceLocation("controlify", "prev_slot")));
|
||||||
register(OPEN_CHAT = new ControllerBinding(controller, Bind.DPAD_UP, new ResourceLocation("controlify", "open_chat"), options.keyChat, () -> false));
|
register(PAUSE = new ControllerBinding<>(controller, GamepadBind.START, new ResourceLocation("controlify", "pause")));
|
||||||
register(GUI_PRESS = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "gui_press")));
|
register(INVENTORY = new ControllerBinding<>(controller, GamepadBind.Y_BUTTON, new ResourceLocation("controlify", "inventory"), options.keyInventory, () -> false));
|
||||||
register(GUI_BACK = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "gui_back")));
|
register(CHANGE_PERSPECTIVE = new ControllerBinding<>(controller, GamepadBind.BACK, new ResourceLocation("controlify", "change_perspective"), options.keyTogglePerspective, () -> false));
|
||||||
register(GUI_NEXT_TAB = new ControllerBinding(controller, Bind.RIGHT_BUMPER, new ResourceLocation("controlify", "gui_next_tab")));
|
register(SWAP_HANDS = new ControllerBinding<>(controller, GamepadBind.X_BUTTON, new ResourceLocation("controlify", "swap_hands"), options.keySwapOffhand, () -> false));
|
||||||
register(GUI_PREV_TAB = new ControllerBinding(controller, Bind.LEFT_BUMPER, new ResourceLocation("controlify", "gui_prev_tab")));
|
register(OPEN_CHAT = new ControllerBinding<>(controller, GamepadBind.DPAD_UP, new ResourceLocation("controlify", "open_chat"), options.keyChat, () -> false));
|
||||||
register(PICK_BLOCK = new ControllerBinding(controller, Bind.DPAD_LEFT, new ResourceLocation("controlify", "pick_block"), options.keyPickItem, () -> false));
|
register(GUI_PRESS = new ControllerBinding<>(controller, GamepadBind.A_BUTTON, new ResourceLocation("controlify", "gui_press")));
|
||||||
register(TOGGLE_HUD_VISIBILITY = new ControllerBinding(controller, Bind.NONE, new ResourceLocation("controlify", "toggle_hud_visibility")));
|
register(GUI_BACK = new ControllerBinding<>(controller, GamepadBind.B_BUTTON, new ResourceLocation("controlify", "gui_back")));
|
||||||
register(SHOW_PLAYER_LIST = new ControllerBinding(controller, Bind.DPAD_RIGHT, new ResourceLocation("controlify", "show_player_list"), options.keyPlayerList, () -> false));
|
register(GUI_NEXT_TAB = new ControllerBinding<>(controller, GamepadBind.RIGHT_BUMPER, new ResourceLocation("controlify", "gui_next_tab")));
|
||||||
register(VMOUSE_LCLICK = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "vmouse_lclick")));
|
register(GUI_PREV_TAB = new ControllerBinding<>(controller, GamepadBind.LEFT_BUMPER, new ResourceLocation("controlify", "gui_prev_tab")));
|
||||||
register(VMOUSE_RCLICK = new ControllerBinding(controller, Bind.X_BUTTON, new ResourceLocation("controlify", "vmouse_rclick")));
|
register(PICK_BLOCK = new ControllerBinding<>(controller, GamepadBind.DPAD_LEFT, new ResourceLocation("controlify", "pick_block"), options.keyPickItem, () -> false));
|
||||||
register(VMOUSE_SHIFT_CLICK = new ControllerBinding(controller, Bind.Y_BUTTON, new ResourceLocation("controlify", "vmouse_shift_click")));
|
register(TOGGLE_HUD_VISIBILITY = new ControllerBinding<>(controller, new EmptyBind<>(), new ResourceLocation("controlify", "toggle_hud_visibility")));
|
||||||
register(VMOUSE_SCROLL_UP = new ControllerBinding(controller, Bind.RIGHT_STICK_FORWARD, new ResourceLocation("controlify", "vmouse_scroll_up")));
|
register(SHOW_PLAYER_LIST = new ControllerBinding<>(controller, GamepadBind.DPAD_RIGHT, new ResourceLocation("controlify", "show_player_list"), options.keyPlayerList, () -> false));
|
||||||
register(VMOUSE_SCROLL_DOWN = new ControllerBinding(controller, Bind.RIGHT_STICK_BACKWARD, new ResourceLocation("controlify", "vmouse_scroll_down")));
|
register(VMOUSE_MOVE_UP = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_FORWARD, new ResourceLocation("controlify", "vmouse_move_up")));
|
||||||
register(VMOUSE_ESCAPE = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "vmouse_escape")));
|
register(VMOUSE_MOVE_DOWN = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_BACKWARD, new ResourceLocation("controlify", "vmouse_move_down")));
|
||||||
register(VMOUSE_SHIFT = new ControllerBinding(controller, Bind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "vmouse_shift")));
|
register(VMOUSE_MOVE_LEFT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_LEFT, new ResourceLocation("controlify", "vmouse_move_left")));
|
||||||
register(VMOUSE_TOGGLE = new ControllerBinding(controller, Bind.BACK, new ResourceLocation("controlify", "vmouse_toggle")));
|
register(VMOUSE_MOVE_RIGHT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_RIGHT, new ResourceLocation("controlify", "vmouse_move_right")));
|
||||||
|
register(VMOUSE_LCLICK = new ControllerBinding<>(controller, GamepadBind.A_BUTTON, new ResourceLocation("controlify", "vmouse_lclick")));
|
||||||
|
register(VMOUSE_RCLICK = new ControllerBinding<>(controller, GamepadBind.X_BUTTON, new ResourceLocation("controlify", "vmouse_rclick")));
|
||||||
|
register(VMOUSE_SHIFT_CLICK = new ControllerBinding<>(controller, GamepadBind.Y_BUTTON, new ResourceLocation("controlify", "vmouse_shift_click")));
|
||||||
|
register(VMOUSE_SCROLL_UP = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_FORWARD, new ResourceLocation("controlify", "vmouse_scroll_up")));
|
||||||
|
register(VMOUSE_SCROLL_DOWN = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_BACKWARD, new ResourceLocation("controlify", "vmouse_scroll_down")));
|
||||||
|
register(VMOUSE_ESCAPE = new ControllerBinding<>(controller, GamepadBind.B_BUTTON, new ResourceLocation("controlify", "vmouse_escape")));
|
||||||
|
register(VMOUSE_SHIFT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "vmouse_shift")));
|
||||||
|
register(VMOUSE_TOGGLE = new ControllerBinding<>(controller, GamepadBind.BACK, new ResourceLocation("controlify", "vmouse_toggle")));
|
||||||
|
register(GUI_NAVI_UP = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_FORWARD, new ResourceLocation("controlify", "gui_navi_up")));
|
||||||
|
register(GUI_NAVI_DOWN = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_BACKWARD, new ResourceLocation("controlify", "gui_navi_down")));
|
||||||
|
register(GUI_NAVI_LEFT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_LEFT, new ResourceLocation("controlify", "gui_navi_left")));
|
||||||
|
register(GUI_NAVI_RIGHT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_RIGHT, new ResourceLocation("controlify", "gui_navi_right")));
|
||||||
|
register(YACL_CYCLE_OPT_FORWARD = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_RIGHT, new ResourceLocation("controlify", "yacl_cycle_opt_forward")));
|
||||||
|
register(YACL_CYCLE_OPT_BACKWARD = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_LEFT, new ResourceLocation("controlify", "yacl_cycle_opt_backward")));
|
||||||
|
|
||||||
ControlifyEvents.CONTROLLER_BIND_REGISTRY.invoker().onRegisterControllerBinds(this, controller);
|
ControlifyEvents.CONTROLLER_BIND_REGISTRY.invoker().onRegisterControllerBinds(this, controller);
|
||||||
|
|
||||||
@ -79,16 +101,16 @@ public class ControllerBindings {
|
|||||||
ControlifyEvents.INPUT_MODE_CHANGED.register(mode -> KeyMapping.releaseAll());
|
ControlifyEvents.INPUT_MODE_CHANGED.register(mode -> KeyMapping.releaseAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
public BindingSupplier register(ControllerBinding binding) {
|
public BindingSupplier<T> register(ControllerBinding<T> binding) {
|
||||||
registry.put(binding.id(), binding);
|
registry.put(binding.id(), binding);
|
||||||
return controller -> controller.bindings().get(binding.id());
|
return controller -> controller.bindings().get(binding.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControllerBinding get(ResourceLocation id) {
|
public ControllerBinding<T> get(ResourceLocation id) {
|
||||||
return registry.get(id);
|
return registry.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<ResourceLocation, ControllerBinding> registry() {
|
public Map<ResourceLocation, ControllerBinding<T>> registry() {
|
||||||
return Collections.unmodifiableMap(registry);
|
return Collections.unmodifiableMap(registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,13 +124,13 @@ public class ControllerBindings {
|
|||||||
|
|
||||||
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());
|
var bind = json.get(binding.id().toString()).getAsJsonObject();
|
||||||
if (bind == null) continue;
|
if (bind == null) continue;
|
||||||
binding.setCurrentBind(IBind.fromJson(bind));
|
binding.setCurrentBind(IBind.fromJson(bind, controller));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onControllerUpdate(Controller controller) {
|
public void onControllerUpdate(Controller<?, ?> controller) {
|
||||||
if (controller != this.controller) return;
|
if (controller != this.controller) return;
|
||||||
|
|
||||||
imitateVanillaClick();
|
imitateVanillaClick();
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
|
import dev.isxander.controlify.gui.DrawSize;
|
||||||
|
|
||||||
|
public class EmptyBind<T extends ControllerState> implements IBind<T> {
|
||||||
|
public static final String BIND_ID = "empty";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float state(T state) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(PoseStack matrices, int x, int centerY, Controller<T, ?> controller) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawSize drawSize() {
|
||||||
|
return new DrawSize(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
object.addProperty("type", BIND_ID);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
return obj instanceof EmptyBind;
|
||||||
|
}
|
||||||
|
}
|
110
src/main/java/dev/isxander/controlify/bindings/GamepadBind.java
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadConfig;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.BuiltinGamepadTheme;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||||
|
import dev.isxander.controlify.gui.DrawSize;
|
||||||
|
import net.minecraft.client.gui.GuiComponent;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public enum GamepadBind implements IBind<GamepadState> {
|
||||||
|
A_BUTTON(state -> state.gamepadButtons().a(), "a_button"),
|
||||||
|
B_BUTTON(state -> state.gamepadButtons().b(), "b_button"),
|
||||||
|
X_BUTTON(state -> state.gamepadButtons().x(), "x_button"),
|
||||||
|
Y_BUTTON(state -> state.gamepadButtons().y(), "y_button"),
|
||||||
|
LEFT_BUMPER(state -> state.gamepadButtons().leftBumper(), "left_bumper"),
|
||||||
|
RIGHT_BUMPER(state -> state.gamepadButtons().rightBumper(), "right_bumper"),
|
||||||
|
LEFT_STICK_PRESS(state -> state.gamepadButtons().leftStick(), "left_stick_press"),
|
||||||
|
RIGHT_STICK_PRESS(state -> state.gamepadButtons().rightStick(), "right_stick_press"),
|
||||||
|
START(state -> state.gamepadButtons().start(), "start"),
|
||||||
|
BACK(state -> state.gamepadButtons().back(), "back"),
|
||||||
|
GUIDE(state -> state.gamepadButtons().guide(), "guide"), // the middle button
|
||||||
|
DPAD_UP(state -> state.gamepadButtons().dpadUp(), "dpad_up"),
|
||||||
|
DPAD_DOWN(state -> state.gamepadButtons().dpadDown(), "dpad_down"),
|
||||||
|
DPAD_LEFT(state -> state.gamepadButtons().dpadLeft(), "dpad_left"),
|
||||||
|
DPAD_RIGHT(state -> state.gamepadButtons().dpadRight(), "dpad_right"),
|
||||||
|
LEFT_TRIGGER(state -> state.gamepadAxes().leftTrigger(), "left_trigger", true),
|
||||||
|
RIGHT_TRIGGER(state -> state.gamepadAxes().rightTrigger(), "right_trigger", true),
|
||||||
|
LEFT_STICK_FORWARD(state -> -Math.min(0, state.gamepadAxes().leftStickY()), "left_stick_up", true),
|
||||||
|
LEFT_STICK_BACKWARD(state -> Math.max(0, state.gamepadAxes().leftStickY()), "left_stick_down", true),
|
||||||
|
LEFT_STICK_LEFT(state -> -Math.min(0, state.gamepadAxes().leftStickX()), "left_stick_left", true),
|
||||||
|
LEFT_STICK_RIGHT(state -> Math.max(0, state.gamepadAxes().leftStickX()), "left_stick_right", true),
|
||||||
|
RIGHT_STICK_FORWARD(state -> -Math.min(0, state.gamepadAxes().rightStickY()), "right_stick_up", true),
|
||||||
|
RIGHT_STICK_BACKWARD(state -> Math.max(0, state.gamepadAxes().rightStickY()), "right_stick_down", true),
|
||||||
|
RIGHT_STICK_LEFT(state -> -Math.min(0, state.gamepadAxes().rightStickX()), "right_stick_left", true),
|
||||||
|
RIGHT_STICK_RIGHT(state -> Math.max(0, state.gamepadAxes().rightStickX()), "right_stick_right", true);
|
||||||
|
|
||||||
|
public static final String BIND_ID = "gamepad";
|
||||||
|
|
||||||
|
private final Function<GamepadState, Float> state;
|
||||||
|
private final String identifier;
|
||||||
|
private final Map<BuiltinGamepadTheme, ResourceLocation> textureLocations;
|
||||||
|
|
||||||
|
GamepadBind(Function<GamepadState, Float> state, String identifier, boolean jvmIsBad) {
|
||||||
|
this.state = state;
|
||||||
|
this.identifier = identifier;
|
||||||
|
|
||||||
|
this.textureLocations = new HashMap<>();
|
||||||
|
for (BuiltinGamepadTheme theme : BuiltinGamepadTheme.values()) {
|
||||||
|
if (theme == BuiltinGamepadTheme.DEFAULT) continue;
|
||||||
|
textureLocations.put(theme, new ResourceLocation("controlify", "textures/gui/gamepad_buttons/" + theme.id() + "/" + identifier + ".png"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GamepadBind(Function<GamepadState, Boolean> state, String identifier) {
|
||||||
|
this(state1 -> state.apply(state1) ? 1f : 0f, identifier, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float state(GamepadState state) {
|
||||||
|
return this.state.apply(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(PoseStack matrices, int x, int centerY, Controller<GamepadState, ?> controller) {
|
||||||
|
ResourceLocation texture;
|
||||||
|
if (((GamepadConfig)controller.config()).theme == BuiltinGamepadTheme.DEFAULT) {
|
||||||
|
texture = new ResourceLocation("controlify", "textures/gui/gamepad_buttons/" + controller.type().identifier() + "/" + identifier + ".png");
|
||||||
|
} else {
|
||||||
|
texture = textureLocations.get(((GamepadConfig)controller.config()).theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderSystem.setShaderTexture(0, texture);
|
||||||
|
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||||
|
|
||||||
|
GuiComponent.blit(matrices, x, centerY - 22 / 2, 0, 0, 22, 22, 22, 22);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawSize drawSize() {
|
||||||
|
return new DrawSize(22, 22);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String identifier() {
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
object.addProperty("type", BIND_ID);
|
||||||
|
object.addProperty("bind", identifier);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GamepadBind fromJson(JsonObject object) {
|
||||||
|
String name = object.get("bind").getAsString();
|
||||||
|
for (GamepadBind bind : values()) {
|
||||||
|
if (bind.identifier.equals(name)) return bind;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +1,45 @@
|
|||||||
package dev.isxander.controlify.bindings;
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonObject;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.controller.ControllerState;
|
import dev.isxander.controlify.controller.*;
|
||||||
import dev.isxander.controlify.gui.ButtonRenderer;
|
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
|
import dev.isxander.controlify.gui.DrawSize;
|
||||||
|
|
||||||
import java.util.Collection;
|
public interface IBind<S extends ControllerState> {
|
||||||
|
float state(S state);
|
||||||
public interface IBind {
|
default boolean held(S state, Controller<S, ?> controller) {
|
||||||
float state(ControllerState state, Controller controller);
|
return state(state) > controller.config().buttonActivationThreshold;
|
||||||
default boolean held(ControllerState state, Controller controller) {
|
|
||||||
return state(state, controller) > controller.config().buttonActivationThreshold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw(PoseStack matrices, int x, int centerY, Controller controller);
|
void draw(PoseStack matrices, int x, int centerY, Controller<S, ?> controller);
|
||||||
ButtonRenderer.DrawSize drawSize();
|
DrawSize drawSize();
|
||||||
|
|
||||||
JsonElement toJson();
|
JsonObject toJson();
|
||||||
|
|
||||||
static IBind fromJson(JsonElement json) {
|
@SuppressWarnings("unchecked")
|
||||||
if (json.isJsonArray()) {
|
static <T extends ControllerState> IBind<T> fromJson(JsonObject json, Controller<T, ?> controller) {
|
||||||
return new CompoundBind(json.getAsJsonArray().asList().stream().map(element -> Bind.fromIdentifier(element.getAsString())).toArray(Bind[]::new));
|
var type = json.get("type").getAsString();
|
||||||
} else {
|
if (type.equals(EmptyBind.BIND_ID))
|
||||||
return Bind.fromIdentifier(json.getAsString());
|
return new EmptyBind<>();
|
||||||
|
|
||||||
|
if (controller instanceof GamepadController && type.equals(GamepadBind.BIND_ID)) {
|
||||||
|
return (IBind<T>) GamepadBind.fromJson(json);
|
||||||
|
} else if (controller instanceof JoystickController joystick) {
|
||||||
|
return (IBind<T>) switch (type) {
|
||||||
|
case JoystickButtonBind.BIND_ID -> JoystickButtonBind.fromJson(json, joystick);
|
||||||
|
case JoystickHatBind.BIND_ID -> JoystickHatBind.fromJson(json, joystick);
|
||||||
|
case JoystickAxisBind.BIND_ID -> JoystickAxisBind.fromJson(json, joystick);
|
||||||
|
default -> {
|
||||||
|
Controlify.LOGGER.error("Unknown bind type: " + type);
|
||||||
|
yield new EmptyBind<>();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
static IBind create(Collection<Bind> binds) {
|
Controlify.LOGGER.error("Could not parse bind for controller: " + controller.name());
|
||||||
if (binds.size() == 1) return binds.stream().findAny().orElseThrow();
|
return new EmptyBind<>();
|
||||||
return new CompoundBind(binds.toArray(new Bind[0]));
|
|
||||||
}
|
|
||||||
static IBind create(Bind... binds) {
|
|
||||||
if (binds.length == 1) return binds[0];
|
|
||||||
return new CompoundBind(binds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,88 @@
|
|||||||
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||||
|
import dev.isxander.controlify.gui.DrawSize;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class JoystickAxisBind implements IBind<JoystickState> {
|
||||||
|
public static final String BIND_ID = "joystick_axis";
|
||||||
|
|
||||||
|
private final JoystickController joystick;
|
||||||
|
private final int axisIndex;
|
||||||
|
private final AxisDirection direction;
|
||||||
|
|
||||||
|
public JoystickAxisBind(JoystickController joystick, int axisIndex, AxisDirection direction) {
|
||||||
|
this.joystick = joystick;
|
||||||
|
this.axisIndex = axisIndex;
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float state(JoystickState state) {
|
||||||
|
var rawState = state.axes().get(axisIndex);
|
||||||
|
return switch (direction) {
|
||||||
|
case POSITIVE -> Math.max(0, rawState);
|
||||||
|
case NEGATIVE -> -Math.min(0, rawState);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) {
|
||||||
|
var font = Minecraft.getInstance().font;
|
||||||
|
font.drawShadow(matrices, getTempButtonName(), x + 1.5f, centerY - font.lineHeight / 2f, 0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawSize drawSize() {
|
||||||
|
var font = Minecraft.getInstance().font;
|
||||||
|
return new DrawSize(font.width(getTempButtonName()) + 3, font.lineHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component getTempButtonName() {
|
||||||
|
var axis = joystick.mapping().axis(axisIndex);
|
||||||
|
return Component.empty()
|
||||||
|
.append(axis.name())
|
||||||
|
.append(" ")
|
||||||
|
.append(axis.getDirectionName(axisIndex, direction));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
object.addProperty("type", BIND_ID);
|
||||||
|
object.addProperty("axis", axisIndex);
|
||||||
|
object.addProperty("direction", direction.name());
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
JoystickAxisBind that = (JoystickAxisBind) o;
|
||||||
|
return axisIndex == that.axisIndex && direction == that.direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(axisIndex, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JoystickAxisBind fromJson(JsonObject object, JoystickController joystick) {
|
||||||
|
var axisIndex = object.get("axis").getAsInt();
|
||||||
|
var direction = AxisDirection.valueOf(object.get("direction").getAsString());
|
||||||
|
return new JoystickAxisBind(joystick, axisIndex, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AxisDirection {
|
||||||
|
POSITIVE,
|
||||||
|
NEGATIVE
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||||
|
import dev.isxander.controlify.gui.DrawSize;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class JoystickButtonBind implements IBind<JoystickState> {
|
||||||
|
public static final String BIND_ID = "joystick_button";
|
||||||
|
|
||||||
|
private final JoystickController joystick;
|
||||||
|
private final int buttonIndex;
|
||||||
|
|
||||||
|
public JoystickButtonBind(JoystickController joystick, int buttonIndex) {
|
||||||
|
this.joystick = joystick;
|
||||||
|
this.buttonIndex = buttonIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float state(JoystickState state) {
|
||||||
|
return state.buttons().get(buttonIndex) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) {
|
||||||
|
var font = Minecraft.getInstance().font;
|
||||||
|
|
||||||
|
font.drawShadow(matrices, getTempButtonName(), x + 1.5f, centerY - font.lineHeight / 2f, 0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawSize drawSize() {
|
||||||
|
var font = Minecraft.getInstance().font;
|
||||||
|
return new DrawSize(font.width(getTempButtonName()) + 3, font.lineHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Component getTempButtonName() {
|
||||||
|
return joystick.mapping().button(buttonIndex).name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
object.addProperty("type", BIND_ID);
|
||||||
|
object.addProperty("button", buttonIndex);
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
JoystickButtonBind that = (JoystickButtonBind) o;
|
||||||
|
return buttonIndex == that.buttonIndex && joystick.uid().equals(that.joystick.uid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(buttonIndex, joystick.uid());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JoystickButtonBind fromJson(JsonObject object, JoystickController joystick) {
|
||||||
|
var buttonIndex = object.get("button").getAsInt();
|
||||||
|
return new JoystickButtonBind(joystick, buttonIndex);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,78 @@
|
|||||||
|
package dev.isxander.controlify.bindings;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||||
|
import dev.isxander.controlify.gui.DrawSize;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class JoystickHatBind implements IBind<JoystickState> {
|
||||||
|
public static final String BIND_ID = "joystick_hat";
|
||||||
|
|
||||||
|
private final JoystickController joystick;
|
||||||
|
private final int hatIndex;
|
||||||
|
private final JoystickState.HatState hatState;
|
||||||
|
|
||||||
|
public JoystickHatBind(JoystickController joystick, int hatIndex, JoystickState.HatState hatState) {
|
||||||
|
this.joystick = joystick;
|
||||||
|
this.hatIndex = hatIndex;
|
||||||
|
this.hatState = hatState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float state(JoystickState state) {
|
||||||
|
return state.hats().get(hatIndex) == hatState ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(PoseStack matrices, int x, int centerY, Controller<JoystickState, ?> controller) {
|
||||||
|
var font = Minecraft.getInstance().font;
|
||||||
|
font.drawShadow(matrices, getTempButtonName(), x + 1.5f, centerY - font.lineHeight / 2f, 0xFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawSize drawSize() {
|
||||||
|
var font = Minecraft.getInstance().font;
|
||||||
|
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
|
||||||
|
public JsonObject toJson() {
|
||||||
|
JsonObject object = new JsonObject();
|
||||||
|
object.addProperty("type", BIND_ID);
|
||||||
|
object.addProperty("hat", hatIndex);
|
||||||
|
object.addProperty("state", hatState.name());
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
JoystickHatBind that = (JoystickHatBind) o;
|
||||||
|
return hatIndex == that.hatIndex && hatState == that.hatState && joystick.uid().equals(that.joystick.uid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(hatIndex, hatState, joystick.uid());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JoystickHatBind fromJson(JsonObject object, JoystickController joystick) {
|
||||||
|
var hatIndex = object.get("hat").getAsInt();
|
||||||
|
var hatState = JoystickState.HatState.valueOf(object.get("state").getAsString());
|
||||||
|
return new JoystickHatBind(joystick, hatIndex, hatState);
|
||||||
|
}
|
||||||
|
}
|
@ -69,7 +69,7 @@ public class ControlifyConfig {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonObject generateControllerConfig(Controller controller) {
|
private JsonObject generateControllerConfig(Controller<?, ?> controller) {
|
||||||
JsonObject object = new JsonObject();
|
JsonObject object = new JsonObject();
|
||||||
|
|
||||||
object.add("config", GSON.toJsonTree(controller.config()));
|
object.add("config", GSON.toJsonTree(controller.config()));
|
||||||
@ -91,19 +91,21 @@ public class ControlifyConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean loadOrCreateControllerData(Controller controller) {
|
public boolean loadOrCreateControllerData(Controller<?, ?> controller) {
|
||||||
var uid = controller.uid();
|
var uid = controller.uid();
|
||||||
if (controllerData.has(uid)) {
|
if (controllerData.has(uid)) {
|
||||||
|
Controlify.LOGGER.info("Loading controller data for " + uid);
|
||||||
applyControllerConfig(controller, controllerData.getAsJsonObject(uid));
|
applyControllerConfig(controller, controllerData.getAsJsonObject(uid));
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
Controlify.LOGGER.info("New controller found, creating controller data for " + uid);
|
||||||
save();
|
save();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyControllerConfig(Controller controller, JsonObject object) {
|
private void applyControllerConfig(Controller<?, ?> controller, JsonObject object) {
|
||||||
controller.setConfig(GSON.fromJson(object.getAsJsonObject("config"), Controller.ControllerConfig.class));
|
controller.setConfig(GSON, object.getAsJsonObject("config"));
|
||||||
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package dev.isxander.controlify.config.gui;
|
package dev.isxander.controlify.config.gui;
|
||||||
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
import dev.isxander.controlify.bindings.Bind;
|
import dev.isxander.controlify.bindings.GamepadBind;
|
||||||
import dev.isxander.controlify.bindings.IBind;
|
import dev.isxander.controlify.bindings.IBind;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessorProvider;
|
import dev.isxander.controlify.screenop.ComponentProcessorProvider;
|
||||||
@ -16,20 +18,17 @@ import net.minecraft.ChatFormatting;
|
|||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
import java.util.LinkedHashSet;
|
public class GamepadBindController implements Controller<IBind<GamepadState>> {
|
||||||
import java.util.Set;
|
private final Option<IBind<GamepadState>> option;
|
||||||
|
private final GamepadController controller;
|
||||||
|
|
||||||
public class BindButtonController implements Controller<IBind> {
|
public GamepadBindController(Option<IBind<GamepadState>> option, GamepadController controller) {
|
||||||
private final Option<IBind> option;
|
|
||||||
private final dev.isxander.controlify.controller.Controller controller;
|
|
||||||
|
|
||||||
public BindButtonController(Option<IBind> option, dev.isxander.controlify.controller.Controller controller) {
|
|
||||||
this.option = option;
|
this.option = option;
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Option<IBind> option() {
|
public Option<IBind<GamepadState>> option() {
|
||||||
return this.option;
|
return this.option;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,26 +42,18 @@ public class BindButtonController implements Controller<IBind> {
|
|||||||
return new BindButtonWidget(this, yaclScreen, dimension);
|
return new BindButtonWidget(this, yaclScreen, dimension);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BindButtonWidget extends ControllerWidget<BindButtonController> implements ComponentProcessorProvider, ComponentProcessor {
|
public static class BindButtonWidget extends ControllerWidget<GamepadBindController> implements ComponentProcessorProvider, ComponentProcessor {
|
||||||
private boolean awaitingControllerInput = false;
|
private boolean awaitingControllerInput = false;
|
||||||
private final Component awaitingText = Component.translatable("controlify.gui.bind_input_awaiting").withStyle(ChatFormatting.ITALIC);
|
private final Component awaitingText = Component.translatable("controlify.gui.bind_input_awaiting").withStyle(ChatFormatting.ITALIC);
|
||||||
private final Set<Bind> pressedBinds = new LinkedHashSet<>();
|
|
||||||
|
|
||||||
public BindButtonWidget(BindButtonController control, YACLScreen screen, Dimension<Integer> dim) {
|
public BindButtonWidget(GamepadBindController control, YACLScreen screen, Dimension<Integer> dim) {
|
||||||
super(control, screen, dim);
|
super(control, screen, dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void drawValueText(PoseStack matrices, int mouseX, int mouseY, float delta) {
|
protected void drawValueText(PoseStack matrices, int mouseX, int mouseY, float delta) {
|
||||||
if (awaitingControllerInput) {
|
if (awaitingControllerInput) {
|
||||||
if (pressedBinds.isEmpty()) {
|
textRenderer.drawShadow(matrices, awaitingText, getDimension().xLimit() - textRenderer.width(awaitingText) - getXPadding(), getDimension().centerY() - textRenderer.lineHeight / 2f, 0xFFFFFF);
|
||||||
textRenderer.drawShadow(matrices, awaitingText, getDimension().xLimit() - textRenderer.width(awaitingText) - getXPadding(), getDimension().centerY() - textRenderer.lineHeight / 2f, 0xFFFFFF);
|
|
||||||
} else {
|
|
||||||
var bind = IBind.create(pressedBinds);
|
|
||||||
var plusSize = 2 + textRenderer.width("+");
|
|
||||||
bind.draw(matrices, getDimension().xLimit() - bind.drawSize().width() - getXPadding() - plusSize, getDimension().centerY(), control.controller);
|
|
||||||
textRenderer.drawShadow(matrices, "+", getDimension().xLimit() - getXPadding() - plusSize, getDimension().centerY() - textRenderer.lineHeight / 2f, 0xFFFFFF);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
var bind = control.option().pendingValue();
|
var bind = control.option().pendingValue();
|
||||||
bind.draw(matrices, getDimension().xLimit() - bind.drawSize().width(), getDimension().centerY(), control.controller);
|
bind.draw(matrices, getDimension().xLimit() - bind.drawSize().width(), getDimension().centerY(), control.controller);
|
||||||
@ -95,41 +86,31 @@ public class BindButtonController implements Controller<IBind> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, dev.isxander.controlify.controller.Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, dev.isxander.controlify.controller.Controller<?, ?> controller) {
|
||||||
|
if (controller != control.controller) return true;
|
||||||
|
|
||||||
if (controller.bindings().GUI_PRESS.justPressed() && !awaitingControllerInput) {
|
if (controller.bindings().GUI_PRESS.justPressed() && !awaitingControllerInput) {
|
||||||
return awaitingControllerInput = true;
|
return awaitingControllerInput = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!awaitingControllerInput) return false;
|
if (!awaitingControllerInput) return false;
|
||||||
|
|
||||||
if (pressedBinds.stream().anyMatch(bind -> !bind.held(controller.state(), controller))) {
|
var gamepad = control.controller;
|
||||||
// finished
|
|
||||||
awaitingControllerInput = false;
|
for (var bind : GamepadBind.values()) {
|
||||||
control.option().requestSet(IBind.create(pressedBinds));
|
if (bind.held(gamepad.state(), gamepad) && !bind.held(gamepad.prevState(), gamepad)) {
|
||||||
pressedBinds.clear();
|
control.option().requestSet(bind);
|
||||||
} else {
|
awaitingControllerInput = false;
|
||||||
for (var bind : Bind.values()) {
|
gamepad.consumeButtonState();
|
||||||
if (bind.held(controller.state(), controller) && !bind.held(controller.prevState(), controller)) {
|
return true;
|
||||||
if (bind == Bind.GUIDE) { // FIXME: guide cannot be used as reserve because Windows hooks into xbox button to open game bar, maybe START?
|
|
||||||
if (pressedBinds.isEmpty()) {
|
|
||||||
awaitingControllerInput = false;
|
|
||||||
control.option().requestSet(IBind.create(Bind.NONE));
|
|
||||||
pressedBinds.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pressedBinds.add(bind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
control.controller.consumeButtonState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, dev.isxander.controlify.controller.Controller controller) {
|
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, dev.isxander.controlify.controller.Controller<?, ?> controller) {
|
||||||
return awaitingControllerInput;
|
return awaitingControllerInput;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,164 @@
|
|||||||
|
package dev.isxander.controlify.config.gui;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import dev.isxander.controlify.bindings.*;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||||
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
|
import dev.isxander.controlify.screenop.ComponentProcessorProvider;
|
||||||
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
|
import dev.isxander.yacl.api.Controller;
|
||||||
|
import dev.isxander.yacl.api.Option;
|
||||||
|
import dev.isxander.yacl.api.utils.Dimension;
|
||||||
|
import dev.isxander.yacl.gui.AbstractWidget;
|
||||||
|
import dev.isxander.yacl.gui.YACLScreen;
|
||||||
|
import dev.isxander.yacl.gui.controllers.ControllerWidget;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class JoystickBindController implements Controller<IBind<JoystickState>> {
|
||||||
|
private final Option<IBind<JoystickState>> option;
|
||||||
|
private final JoystickController controller;
|
||||||
|
|
||||||
|
public JoystickBindController(Option<IBind<JoystickState>> option, JoystickController controller) {
|
||||||
|
this.option = option;
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Option<IBind<JoystickState>> option() {
|
||||||
|
return this.option;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component formatValue() {
|
||||||
|
return Component.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AbstractWidget provideWidget(YACLScreen yaclScreen, Dimension<Integer> dimension) {
|
||||||
|
return new BindButtonWidget(this, yaclScreen, dimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BindButtonWidget extends ControllerWidget<JoystickBindController> implements ComponentProcessorProvider, ComponentProcessor {
|
||||||
|
private boolean awaitingControllerInput = false;
|
||||||
|
private final Component awaitingText = Component.translatable("controlify.gui.bind_input_awaiting").withStyle(ChatFormatting.ITALIC);
|
||||||
|
|
||||||
|
public BindButtonWidget(JoystickBindController control, YACLScreen screen, Dimension<Integer> dim) {
|
||||||
|
super(control, screen, dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void drawValueText(PoseStack matrices, int mouseX, int mouseY, float delta) {
|
||||||
|
if (awaitingControllerInput) {
|
||||||
|
textRenderer.drawShadow(matrices, awaitingText, getDimension().xLimit() - textRenderer.width(awaitingText) - getXPadding(), getDimension().centerY() - textRenderer.lineHeight / 2f, 0xFFFFFF);
|
||||||
|
} else {
|
||||||
|
var bind = control.option().pendingValue();
|
||||||
|
bind.draw(matrices, getDimension().xLimit() - bind.drawSize().width(), getDimension().centerY(), control.controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
|
||||||
|
if (isFocused() && keyCode == GLFW.GLFW_KEY_ENTER && !awaitingControllerInput) {
|
||||||
|
awaitingControllerInput = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean mouseClicked(double mouseX, double mouseY, int button) {
|
||||||
|
if (getDimension().isPointInside((int)mouseX, (int)mouseY)) {
|
||||||
|
awaitingControllerInput = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ComponentProcessor componentProcessor() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, dev.isxander.controlify.controller.Controller<?, ?> controller) {
|
||||||
|
if (controller != control.controller) return true;
|
||||||
|
|
||||||
|
if (controller.bindings().GUI_PRESS.justPressed() && !awaitingControllerInput) {
|
||||||
|
return awaitingControllerInput = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!awaitingControllerInput) return false;
|
||||||
|
|
||||||
|
var joystick = control.controller;
|
||||||
|
|
||||||
|
var state = joystick.state();
|
||||||
|
var prevState = joystick.prevState();
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(state.buttons().size(), prevState.buttons().size()); i++) {
|
||||||
|
if (state.buttons().get(i) && !prevState.buttons().get(i)) {
|
||||||
|
control.option().requestSet(new JoystickButtonBind(joystick, i));
|
||||||
|
awaitingControllerInput = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(state.axes().size(), prevState.axes().size()); i++) {
|
||||||
|
var axis = state.axes().get(i);
|
||||||
|
var prevAxis = prevState.axes().get(i);
|
||||||
|
var activationThreshold = joystick.config().buttonActivationThreshold;
|
||||||
|
|
||||||
|
if (Math.abs(prevAxis) < activationThreshold) {
|
||||||
|
if (axis > activationThreshold) {
|
||||||
|
control.option().requestSet(new JoystickAxisBind(joystick, i, JoystickAxisBind.AxisDirection.POSITIVE));
|
||||||
|
awaitingControllerInput = false;
|
||||||
|
return true;
|
||||||
|
} else if (axis < -activationThreshold) {
|
||||||
|
control.option().requestSet(new JoystickAxisBind(joystick, i, JoystickAxisBind.AxisDirection.NEGATIVE));
|
||||||
|
awaitingControllerInput = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.min(state.hats().size(), prevState.hats().size()); i++) {
|
||||||
|
var hat = state.hats().get(i);
|
||||||
|
var prevHat = prevState.hats().get(i);
|
||||||
|
|
||||||
|
if (prevHat.isCentered() && !hat.isCentered()) {
|
||||||
|
control.option().requestSet(new JoystickHatBind(joystick, i, hat));
|
||||||
|
awaitingControllerInput = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, dev.isxander.controlify.controller.Controller<?, ?> controller) {
|
||||||
|
return awaitingControllerInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getHoveredControlWidth() {
|
||||||
|
return getUnhoveredControlWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getUnhoveredControlWidth() {
|
||||||
|
if (awaitingControllerInput)
|
||||||
|
return textRenderer.width(awaitingText);
|
||||||
|
|
||||||
|
return control.option().pendingValue().drawSize().width();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,8 +3,12 @@ package dev.isxander.controlify.config.gui;
|
|||||||
import dev.isxander.controlify.Controlify;
|
import dev.isxander.controlify.Controlify;
|
||||||
import dev.isxander.controlify.bindings.IBind;
|
import dev.isxander.controlify.bindings.IBind;
|
||||||
import dev.isxander.controlify.config.GlobalSettings;
|
import dev.isxander.controlify.config.GlobalSettings;
|
||||||
import dev.isxander.controlify.controller.ControllerTheme;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||||
|
import dev.isxander.controlify.controller.gamepad.BuiltinGamepadTheme;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickState;
|
||||||
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
import dev.isxander.controlify.gui.screen.ControllerDeadzoneCalibrationScreen;
|
||||||
import dev.isxander.yacl.api.*;
|
import dev.isxander.yacl.api.*;
|
||||||
import dev.isxander.yacl.gui.controllers.ActionController;
|
import dev.isxander.yacl.gui.controllers.ActionController;
|
||||||
@ -22,6 +26,10 @@ import net.minecraft.client.gui.screens.AlertScreen;
|
|||||||
import net.minecraft.client.gui.screens.Screen;
|
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.stream.Collectors;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class YACLHelper {
|
public class YACLHelper {
|
||||||
public static Screen generateConfigScreen(Screen parent) {
|
public static Screen generateConfigScreen(Screen parent) {
|
||||||
if (Controlify.instance().currentController() == null) {
|
if (Controlify.instance().currentController() == null) {
|
||||||
@ -41,11 +49,11 @@ public class YACLHelper {
|
|||||||
var globalSettings = Controlify.instance().config().globalSettings();
|
var globalSettings = Controlify.instance().config().globalSettings();
|
||||||
var globalCategory = ConfigCategory.createBuilder()
|
var globalCategory = ConfigCategory.createBuilder()
|
||||||
.name(Component.translatable("controlify.gui.category.global"))
|
.name(Component.translatable("controlify.gui.category.global"))
|
||||||
.option(Option.createBuilder(Controller.class)
|
.option(Option.createBuilder((Class<Controller<?, ?>>) (Class<?>) Controller.class)
|
||||||
.name(Component.translatable("controlify.gui.current_controller"))
|
.name(Component.translatable("controlify.gui.current_controller"))
|
||||||
.tooltip(Component.translatable("controlify.gui.current_controller.tooltip"))
|
.tooltip(Component.translatable("controlify.gui.current_controller.tooltip"))
|
||||||
.binding(Controlify.instance().currentController(), () -> Controlify.instance().currentController(), v -> Controlify.instance().setCurrentController(v))
|
.binding(Controlify.instance().currentController(), () -> Controlify.instance().currentController(), v -> Controlify.instance().setCurrentController(v))
|
||||||
.controller(opt -> new CyclingListController<>(opt, Controller.CONTROLLERS.values().stream().filter(Controller::connected).toList(), c -> Component.literal(c.name())))
|
.controller(opt -> new CyclingListController<>(opt, Controller.CONTROLLERS.values(), c -> Component.literal(c.name())))
|
||||||
.instant(true)
|
.instant(true)
|
||||||
.build())
|
.build())
|
||||||
.option(Option.createBuilder(boolean.class)
|
.option(Option.createBuilder(boolean.class)
|
||||||
@ -114,14 +122,22 @@ public class YACLHelper {
|
|||||||
.tooltip(Component.translatable("controlify.gui.vmouse_sensitivity.tooltip"))
|
.tooltip(Component.translatable("controlify.gui.vmouse_sensitivity.tooltip"))
|
||||||
.binding(def.virtualMouseSensitivity, () -> config.virtualMouseSensitivity, v -> config.virtualMouseSensitivity = v)
|
.binding(def.virtualMouseSensitivity, () -> config.virtualMouseSensitivity, v -> config.virtualMouseSensitivity = v)
|
||||||
.controller(opt -> new FloatSliderController(opt, 0.1f, 2f, 0.05f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
.controller(opt -> new FloatSliderController(opt, 0.1f, 2f, 0.05f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
.build())
|
.build());
|
||||||
.option(Option.createBuilder(ControllerTheme.class)
|
|
||||||
.name(Component.translatable("controlify.gui.controller_theme"))
|
if (controller instanceof GamepadController gamepad) {
|
||||||
.tooltip(Component.translatable("controlify.gui.controller_theme.tooltip"))
|
var gamepadConfig = gamepad.config();
|
||||||
.binding(controller.type().theme(), () -> config.theme, v -> config.theme = v)
|
var defaultGamepadConfig = gamepad.defaultConfig();
|
||||||
.controller(EnumController::new)
|
|
||||||
.instant(true)
|
basicGroup.option(Option.createBuilder(BuiltinGamepadTheme.class)
|
||||||
.build())
|
.name(Component.translatable("controlify.gui.controller_theme"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.controller_theme.tooltip"))
|
||||||
|
.binding(defaultGamepadConfig.theme, () -> gamepadConfig.theme, v -> gamepadConfig.theme = v)
|
||||||
|
.controller(EnumController::new)
|
||||||
|
.instant(true)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
basicGroup
|
||||||
.option(Option.createBuilder(String.class)
|
.option(Option.createBuilder(String.class)
|
||||||
.name(Component.translatable("controlify.gui.custom_name"))
|
.name(Component.translatable("controlify.gui.custom_name"))
|
||||||
.tooltip(Component.translatable("controlify.gui.custom_name.tooltip"))
|
.tooltip(Component.translatable("controlify.gui.custom_name.tooltip"))
|
||||||
@ -139,21 +155,59 @@ public class YACLHelper {
|
|||||||
.tooltip(Component.translatable("controlify.gui.screen_repeat_navi_delay.tooltip"))
|
.tooltip(Component.translatable("controlify.gui.screen_repeat_navi_delay.tooltip"))
|
||||||
.binding(def.screenRepeatNavigationDelay, () -> config.screenRepeatNavigationDelay, v -> config.screenRepeatNavigationDelay = v)
|
.binding(def.screenRepeatNavigationDelay, () -> config.screenRepeatNavigationDelay, v -> config.screenRepeatNavigationDelay = v)
|
||||||
.controller(opt -> new IntegerSliderController(opt, 1, 20, 1, v -> Component.translatable("controlify.gui.format.ticks", v)))
|
.controller(opt -> new IntegerSliderController(opt, 1, 20, 1, v -> Component.translatable("controlify.gui.format.ticks", v)))
|
||||||
.build())
|
.build());
|
||||||
.option(Option.createBuilder(float.class)
|
|
||||||
.name(Component.translatable("controlify.gui.left_stick_deadzone"))
|
if (controller instanceof GamepadController gamepad) {
|
||||||
.tooltip(Component.translatable("controlify.gui.left_stick_deadzone.tooltip"))
|
var gpCfg = gamepad.config();
|
||||||
|
var gpCfgDef = gamepad.defaultConfig();
|
||||||
|
advancedGroup
|
||||||
|
.option(Option.createBuilder(float.class)
|
||||||
|
.name(Component.translatable("controlify.gui.left_stick_deadzone"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.left_stick_deadzone.tooltip"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||||
|
.binding(
|
||||||
|
Math.max(gpCfgDef.leftStickDeadzoneX, gpCfgDef.leftStickDeadzoneY),
|
||||||
|
() -> Math.max(gpCfg.leftStickDeadzoneX, gpCfgDef.leftStickDeadzoneY),
|
||||||
|
v -> gpCfg.leftStickDeadzoneX = gpCfg.leftStickDeadzoneY = v
|
||||||
|
)
|
||||||
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
|
.build())
|
||||||
|
.option(Option.createBuilder(float.class)
|
||||||
|
.name(Component.translatable("controlify.gui.right_stick_deadzone"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.right_stick_deadzone.tooltip"))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||||
|
.binding(
|
||||||
|
Math.max(gpCfgDef.rightStickDeadzoneX, gpCfgDef.rightStickDeadzoneY),
|
||||||
|
() -> Math.max(gpCfg.rightStickDeadzoneX, gpCfgDef.rightStickDeadzoneY),
|
||||||
|
v -> gpCfg.rightStickDeadzoneX = gpCfg.rightStickDeadzoneY = v
|
||||||
|
)
|
||||||
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
|
.build());
|
||||||
|
} else if (controller instanceof JoystickController joystick) {
|
||||||
|
Collection<Integer> deadzoneAxes = IntStream.range(0, joystick.axisCount())
|
||||||
|
.filter(i -> joystick.mapping().axis(i).requiresDeadzone())
|
||||||
|
.boxed()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
i -> joystick.mapping().axis(i).identifier(),
|
||||||
|
i -> i,
|
||||||
|
(x, y) -> x
|
||||||
|
))
|
||||||
|
.values();
|
||||||
|
var jsCfg = joystick.config();
|
||||||
|
var jsCfgDef = joystick.defaultConfig();
|
||||||
|
|
||||||
|
for (int i : deadzoneAxes) {
|
||||||
|
advancedGroup.option(Option.createBuilder(float.class)
|
||||||
|
.name(Component.translatable("controlify.gui.joystick_axis_deadzone", joystick.mapping().axis(i).name()))
|
||||||
|
.tooltip(Component.translatable("controlify.gui.joystick_axis_deadzone.tooltip", joystick.mapping().axis(i).name()))
|
||||||
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
||||||
.binding(def.leftStickDeadzone, () -> config.leftStickDeadzone, v -> config.leftStickDeadzone = v)
|
.binding(jsCfgDef.getDeadzone(i), () -> jsCfg.getDeadzone(i), v -> jsCfg.setDeadzone(i, v))
|
||||||
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
||||||
.build())
|
.build());
|
||||||
.option(Option.createBuilder(float.class)
|
}
|
||||||
.name(Component.translatable("controlify.gui.right_stick_deadzone"))
|
}
|
||||||
.tooltip(Component.translatable("controlify.gui.right_stick_deadzone.tooltip"))
|
|
||||||
.tooltip(Component.translatable("controlify.gui.stickdrift_warning").withStyle(ChatFormatting.RED))
|
advancedGroup
|
||||||
.binding(def.rightStickDeadzone, () -> config.rightStickDeadzone, v -> config.rightStickDeadzone = v)
|
|
||||||
.controller(opt -> new FloatSliderController(opt, 0, 1, 0.01f, v -> Component.literal(String.format("%.0f%%", v*100))))
|
|
||||||
.build())
|
|
||||||
.option(ButtonOption.createBuilder()
|
.option(ButtonOption.createBuilder()
|
||||||
.name(Component.translatable("controlify.gui.auto_calibration"))
|
.name(Component.translatable("controlify.gui.auto_calibration"))
|
||||||
.tooltip(Component.translatable("controlify.gui.auto_calibration.tooltip"))
|
.tooltip(Component.translatable("controlify.gui.auto_calibration.tooltip"))
|
||||||
@ -170,19 +224,26 @@ public class YACLHelper {
|
|||||||
|
|
||||||
var controlsGroup = OptionGroup.createBuilder()
|
var controlsGroup = OptionGroup.createBuilder()
|
||||||
.name(Component.translatable("controlify.gui.group.controls"));
|
.name(Component.translatable("controlify.gui.group.controls"));
|
||||||
for (var control : controller.bindings().registry().values()) {
|
if (controller instanceof GamepadController gamepad) {
|
||||||
controlsGroup.option(Option.createBuilder(IBind.class)
|
for (var binding : gamepad.bindings().registry().values()) {
|
||||||
.name(control.name())
|
controlsGroup.option(Option.createBuilder((Class<IBind<GamepadState>>) (Class<?>) IBind.class)
|
||||||
.binding(control.defaultBind(), control::currentBind, control::setCurrentBind)
|
.name(binding.name())
|
||||||
.controller(opt -> new BindButtonController(opt, controller))
|
.binding(binding.defaultBind(), binding::currentBind, binding::setCurrentBind)
|
||||||
.tooltip(control.description())
|
.controller(opt -> new GamepadBindController(opt, gamepad))
|
||||||
.instant(true)
|
.tooltip(binding.description())
|
||||||
.listener((opt, bind) -> { // yacl instant options have a bug where they don't save
|
.build());
|
||||||
opt.applyValue();
|
}
|
||||||
controlify.config().save();
|
} else if (controller instanceof JoystickController joystick) {
|
||||||
})
|
for (var binding : joystick.bindings().registry().values()) {
|
||||||
.build());
|
controlsGroup.option(Option.createBuilder((Class<IBind<JoystickState>>) (Class<?>) IBind.class)
|
||||||
|
.name(binding.name())
|
||||||
|
.binding(binding.defaultBind(), binding::currentBind, binding::setCurrentBind)
|
||||||
|
.controller(opt -> new JoystickBindController(opt, joystick))
|
||||||
|
.tooltip(binding.description())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
category.group(controlsGroup.build());
|
category.group(controlsGroup.build());
|
||||||
|
|
||||||
yacl.category(category.build());
|
yacl.category(category.build());
|
||||||
|
@ -0,0 +1,123 @@
|
|||||||
|
package dev.isxander.controlify.controller;
|
||||||
|
|
||||||
|
import com.google.common.reflect.TypeToken;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import dev.isxander.controlify.Controlify;
|
||||||
|
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||||
|
import dev.isxander.controlify.controller.hid.HIDIdentifier;
|
||||||
|
import org.hid4java.HidDevice;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public abstract class AbstractController<S extends ControllerState, C extends ControllerConfig> implements Controller<S, C> {
|
||||||
|
private final int joystickId;
|
||||||
|
protected String name;
|
||||||
|
private final String uid;
|
||||||
|
private final String guid;
|
||||||
|
private final ControllerType type;
|
||||||
|
|
||||||
|
private final ControllerBindings<S> bindings;
|
||||||
|
protected C config, defaultConfig;
|
||||||
|
|
||||||
|
public AbstractController(int joystickId, @Nullable HidDevice hidDevice) {
|
||||||
|
if (joystickId > GLFW.GLFW_JOYSTICK_LAST || joystickId < 0)
|
||||||
|
throw new IllegalArgumentException("Joystick ID " + joystickId + " is out of range!");
|
||||||
|
if (!GLFW.glfwJoystickPresent(joystickId))
|
||||||
|
throw new IllegalArgumentException("Joystick " + joystickId + " is not present and cannot be initialised!");
|
||||||
|
|
||||||
|
this.joystickId = joystickId;
|
||||||
|
this.guid = GLFW.glfwGetJoystickGUID(joystickId);
|
||||||
|
|
||||||
|
if (hidDevice != null) {
|
||||||
|
this.uid = UUID.nameUUIDFromBytes(hidDevice.getPath().getBytes()).toString();
|
||||||
|
this.type = ControllerType.getTypeForHID(new HIDIdentifier(hidDevice.getVendorId(), hidDevice.getProductId()));
|
||||||
|
} else {
|
||||||
|
this.uid = "unidentified-guid-" + UUID.nameUUIDFromBytes(this.guid.getBytes());
|
||||||
|
this.type = ControllerType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
var joystickName = GLFW.glfwGetJoystickName(joystickId);
|
||||||
|
String name = type != ControllerType.UNKNOWN || joystickName == null ? type.friendlyName() : joystickName;
|
||||||
|
setName(name);
|
||||||
|
|
||||||
|
this.bindings = new ControllerBindings<>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int joystickId() {
|
||||||
|
return this.joystickId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
if (config().customName != null)
|
||||||
|
return config().customName;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setName(String name) {
|
||||||
|
String uniqueName = name;
|
||||||
|
int i = 0;
|
||||||
|
while (Controller.CONTROLLERS.values().stream().map(Controller::name).anyMatch(name::equalsIgnoreCase)) {
|
||||||
|
uniqueName = name + " (" + i++ + ")";
|
||||||
|
}
|
||||||
|
this.name = uniqueName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String uid() {
|
||||||
|
return this.uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String guid() {
|
||||||
|
return this.guid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerType type() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerBindings<S> bindings() {
|
||||||
|
return this.bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public C config() {
|
||||||
|
return this.config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public C defaultConfig() {
|
||||||
|
return this.defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConfig(Gson gson, JsonElement json) {
|
||||||
|
C newConfig = gson.fromJson(json, new TypeToken<C>(getClass()){}.getType());
|
||||||
|
if (newConfig != null) {
|
||||||
|
this.config = newConfig;
|
||||||
|
} else {
|
||||||
|
Controlify.LOGGER.error("Could not set config for controller " + name() + " (" + uid() + ")! Using default config instead.");
|
||||||
|
this.config = defaultConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
AbstractController<?, ?> that = (AbstractController<?, ?>) o;
|
||||||
|
return uid.equals(that.uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(uid);
|
||||||
|
}
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
package dev.isxander.controlify.controller;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
public record AxesState(
|
|
||||||
float leftStickX, float leftStickY,
|
|
||||||
float rightStickX, float rightStickY,
|
|
||||||
float leftTrigger, float rightTrigger
|
|
||||||
) {
|
|
||||||
public static AxesState EMPTY = new AxesState(0, 0, 0, 0, 0, 0);
|
|
||||||
|
|
||||||
public AxesState leftJoystickDeadZone(float deadZoneX, float deadZoneY) {
|
|
||||||
return new AxesState(
|
|
||||||
deadzone(leftStickX, deadZoneX),
|
|
||||||
deadzone(leftStickY, deadZoneY),
|
|
||||||
rightStickX, rightStickY, leftTrigger, rightTrigger
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AxesState rightJoystickDeadZone(float deadZoneX, float deadZoneY) {
|
|
||||||
return new AxesState(
|
|
||||||
leftStickX, leftStickY,
|
|
||||||
deadzone(rightStickX, deadZoneX),
|
|
||||||
deadzone(rightStickY, deadZoneY),
|
|
||||||
leftTrigger, rightTrigger
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AxesState leftTriggerDeadZone(float deadZone) {
|
|
||||||
return new AxesState(
|
|
||||||
leftStickX, leftStickY, rightStickX, rightStickY,
|
|
||||||
deadzone(leftTrigger, deadZone),
|
|
||||||
rightTrigger
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AxesState rightTriggerDeadZone(float deadZone) {
|
|
||||||
return new AxesState(
|
|
||||||
leftStickX, leftStickY, rightStickX, rightStickY,
|
|
||||||
leftTrigger,
|
|
||||||
deadzone(rightTrigger, deadZone)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float deadzone(float value, float deadzone) {
|
|
||||||
return (value - Math.copySign(Math.min(deadzone, Math.abs(value)), value)) / (1 - deadzone);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AxesState fromController(Controller controller) {
|
|
||||||
if (controller == null || !controller.connected())
|
|
||||||
return EMPTY;
|
|
||||||
|
|
||||||
var state = controller.getGamepadState();
|
|
||||||
var axes = state.axes();
|
|
||||||
|
|
||||||
float leftX = axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_X);
|
|
||||||
float leftY = axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y);
|
|
||||||
float rightX = axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X);
|
|
||||||
float rightY = axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y);
|
|
||||||
float leftTrigger = (axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER) + 1f) / 2f;
|
|
||||||
float rightTrigger = (axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER) + 1f) / 2f;
|
|
||||||
|
|
||||||
return new AxesState(leftX, leftY, rightX, rightY, leftTrigger, rightTrigger);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package dev.isxander.controlify.controller;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFW;
|
|
||||||
|
|
||||||
public record ButtonState(
|
|
||||||
boolean a, boolean b, boolean x, boolean y,
|
|
||||||
boolean leftBumper, boolean rightBumper,
|
|
||||||
boolean back, boolean start, boolean guide,
|
|
||||||
boolean dpadUp, boolean dpadDown, boolean dpadLeft, boolean dpadRight,
|
|
||||||
boolean leftStick, boolean rightStick
|
|
||||||
) {
|
|
||||||
public static ButtonState EMPTY = new ButtonState(
|
|
||||||
false, false, false, false,
|
|
||||||
false, false,
|
|
||||||
false, false, false,
|
|
||||||
false, false, false, false,
|
|
||||||
false, false
|
|
||||||
);
|
|
||||||
|
|
||||||
public static ButtonState fromController(Controller controller) {
|
|
||||||
if (controller == null || !controller.connected())
|
|
||||||
return EMPTY;
|
|
||||||
|
|
||||||
var state = controller.getGamepadState();
|
|
||||||
var buttons = state.buttons();
|
|
||||||
|
|
||||||
boolean a = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_A) == GLFW.GLFW_PRESS;
|
|
||||||
boolean b = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_B) == GLFW.GLFW_PRESS;
|
|
||||||
boolean x = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_X) == GLFW.GLFW_PRESS;
|
|
||||||
boolean y = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_Y) == GLFW.GLFW_PRESS;
|
|
||||||
boolean leftBumper = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) == GLFW.GLFW_PRESS;
|
|
||||||
boolean rightBumper = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) == GLFW.GLFW_PRESS;
|
|
||||||
boolean back = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_BACK) == GLFW.GLFW_PRESS;
|
|
||||||
boolean start = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_START) == GLFW.GLFW_PRESS;
|
|
||||||
boolean guide = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_GUIDE) == GLFW.GLFW_PRESS;
|
|
||||||
boolean dpadUp = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) == GLFW.GLFW_PRESS;
|
|
||||||
boolean dpadDown = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) == GLFW.GLFW_PRESS;
|
|
||||||
boolean dpadLeft = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT) == GLFW.GLFW_PRESS;
|
|
||||||
boolean dpadRight = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT) == GLFW.GLFW_PRESS;
|
|
||||||
boolean leftStick = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB) == GLFW.GLFW_PRESS;
|
|
||||||
boolean rightStick = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB) == GLFW.GLFW_PRESS;
|
|
||||||
|
|
||||||
return new ButtonState(a, b, x, y, leftBumper, rightBumper, back, start, guide, dpadUp, dpadDown, dpadLeft, dpadRight, leftStick, rightStick);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,193 +1,127 @@
|
|||||||
package dev.isxander.controlify.controller;
|
package dev.isxander.controlify.controller;
|
||||||
|
|
||||||
import dev.isxander.controlify.Controlify;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||||
import dev.isxander.controlify.controller.hid.HIDIdentifier;
|
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||||
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;
|
||||||
import org.lwjgl.glfw.GLFWGamepadState;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public final class Controller {
|
public interface Controller<S extends ControllerState, C extends ControllerConfig> {
|
||||||
public static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
String uid();
|
||||||
public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false, UUID.randomUUID().toString(), ControllerType.UNKNOWN);
|
int joystickId();
|
||||||
|
String guid();
|
||||||
|
|
||||||
private final int joystickId;
|
ControllerBindings<S> bindings();
|
||||||
private final String guid;
|
|
||||||
private final String name;
|
|
||||||
private final boolean gamepad;
|
|
||||||
private final String uid;
|
|
||||||
private final ControllerType type;
|
|
||||||
|
|
||||||
private ControllerState state = ControllerState.EMPTY;
|
S state();
|
||||||
private ControllerState prevState = ControllerState.EMPTY;
|
S prevState();
|
||||||
|
|
||||||
private final ControllerBindings bindings = new ControllerBindings(this);
|
C config();
|
||||||
private ControllerConfig config, defaultConfig;
|
C defaultConfig();
|
||||||
|
void setConfig(Gson gson, JsonElement json);
|
||||||
|
|
||||||
public Controller(int joystickId, String guid, String name, boolean gamepad, String uid, ControllerType type) {
|
ControllerType type();
|
||||||
this.joystickId = joystickId;
|
|
||||||
this.guid = guid;
|
|
||||||
this.name = name;
|
|
||||||
this.gamepad = gamepad;
|
|
||||||
this.uid = uid;
|
|
||||||
this.type = type;
|
|
||||||
this.config = new ControllerConfig();
|
|
||||||
this.defaultConfig = new ControllerConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControllerState state() {
|
String name();
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControllerState prevState() {
|
void updateState();
|
||||||
return prevState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateState() {
|
Map<Integer, Controller<?, ?>> CONTROLLERS = new HashMap<>();
|
||||||
if (!connected()) {
|
|
||||||
state = prevState = ControllerState.EMPTY;
|
static Controller<?, ?> createOrGet(int joystickId, @Nullable HidDevice device) {
|
||||||
return;
|
if (CONTROLLERS.containsKey(joystickId)) {
|
||||||
|
return CONTROLLERS.get(joystickId);
|
||||||
}
|
}
|
||||||
|
|
||||||
prevState = state;
|
if (GLFW.glfwJoystickIsGamepad(joystickId)) {
|
||||||
|
GamepadController controller = new GamepadController(joystickId, device);
|
||||||
AxesState rawAxesState = AxesState.fromController(this);
|
CONTROLLERS.put(joystickId, controller);
|
||||||
AxesState axesState = rawAxesState
|
return controller;
|
||||||
.leftJoystickDeadZone(config().leftStickDeadzone, config().leftStickDeadzone)
|
|
||||||
.rightJoystickDeadZone(config().rightStickDeadzone, config().rightStickDeadzone)
|
|
||||||
.leftTriggerDeadZone(config().leftTriggerDeadzone)
|
|
||||||
.rightTriggerDeadZone(config().rightTriggerDeadzone);
|
|
||||||
ButtonState buttonState = ButtonState.fromController(this);
|
|
||||||
state = new ControllerState(axesState, rawAxesState, buttonState);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void consumeButtonState() {
|
|
||||||
this.state = new ControllerState(state().axes(), state().rawAxes(), ButtonState.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControllerBindings bindings() {
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean connected() {
|
|
||||||
return GLFW.glfwJoystickPresent(joystickId);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLFWGamepadState getGamepadState() {
|
|
||||||
GLFWGamepadState state = GLFWGamepadState.create();
|
|
||||||
if (gamepad)
|
|
||||||
GLFW.glfwGetGamepadState(joystickId, state);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int id() {
|
|
||||||
return joystickId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String guid() {
|
|
||||||
return guid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String uid() {
|
|
||||||
return uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControllerType type() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String name() {
|
|
||||||
if (config().customName != null)
|
|
||||||
return config().customName;
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean gamepad() {
|
|
||||||
return gamepad;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControllerConfig config() {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ControllerConfig defaultConfig() {
|
|
||||||
return defaultConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConfig(ControllerConfig config) {
|
|
||||||
this.config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj == this) return true;
|
|
||||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
|
||||||
var that = (Controller) obj;
|
|
||||||
return Objects.equals(this.guid, that.guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(guid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Controller create(int id, @Nullable HidDevice device) {
|
|
||||||
if (id > GLFW.GLFW_JOYSTICK_LAST)
|
|
||||||
throw new IllegalArgumentException("Invalid joystick id: " + id);
|
|
||||||
if (CONTROLLERS.containsKey(id))
|
|
||||||
return CONTROLLERS.get(id);
|
|
||||||
|
|
||||||
String guid = GLFW.glfwGetJoystickGUID(id);
|
|
||||||
boolean gamepad = GLFW.glfwJoystickIsGamepad(id);
|
|
||||||
String fallbackName = gamepad ? GLFW.glfwGetGamepadName(id) : GLFW.glfwGetJoystickName(id);
|
|
||||||
String uid = device != null ? UUID.nameUUIDFromBytes(device.getPath().getBytes(StandardCharsets.UTF_8)).toString() : "unidentified-" + UUID.randomUUID();
|
|
||||||
ControllerType type = device != null ? ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())) : ControllerType.UNKNOWN;
|
|
||||||
String ogName = type != ControllerType.UNKNOWN || fallbackName == null ? type.friendlyName() : fallbackName;
|
|
||||||
String name = ogName;
|
|
||||||
int tries = 1;
|
|
||||||
while (CONTROLLERS.values().stream().map(Controller::name).anyMatch(name::equals)) {
|
|
||||||
name = ogName + " (" + tries++ + ")";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller controller = new Controller(id, guid, name, gamepad, uid, type);
|
JoystickController controller = new JoystickController(joystickId, device);
|
||||||
|
CONTROLLERS.put(joystickId, controller);
|
||||||
CONTROLLERS.put(id, controller);
|
|
||||||
|
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ControllerConfig {
|
Controller<?, ?> DUMMY = new Controller<>() {
|
||||||
public float horizontalLookSensitivity = 1f;
|
private final ControllerBindings<ControllerState> bindings = new ControllerBindings<>(this);
|
||||||
public float verticalLookSensitivity = 0.9f;
|
private final ControllerConfig config = new ControllerConfig() {
|
||||||
|
@Override
|
||||||
|
public void setDeadzone(int axis, float deadzone) {
|
||||||
|
|
||||||
public float leftStickDeadzone = 0.2f;
|
}
|
||||||
public float rightStickDeadzone = 0.2f;
|
|
||||||
|
|
||||||
// not sure if triggers need deadzones
|
@Override
|
||||||
public float leftTriggerDeadzone = 0.0f;
|
public float getDeadzone(int axis) {
|
||||||
public float rightTriggerDeadzone = 0.0f;
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public float buttonActivationThreshold = 0.5f;
|
@Override
|
||||||
|
public String uid() {
|
||||||
|
return "DUMMY";
|
||||||
|
}
|
||||||
|
|
||||||
public int screenRepeatNavigationDelay = 4;
|
@Override
|
||||||
|
public int joystickId() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public float virtualMouseSensitivity = 1f;
|
@Override
|
||||||
|
public String guid() {
|
||||||
|
return "DUMMY";
|
||||||
|
}
|
||||||
|
|
||||||
public ControllerTheme theme = type().theme();
|
@Override
|
||||||
|
public ControllerBindings<ControllerState> bindings() {
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean autoJump = false;
|
@Override
|
||||||
public boolean toggleSprint = true;
|
public ControllerConfig config() {
|
||||||
public boolean toggleSneak = true;
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
public String customName = null;
|
@Override
|
||||||
|
public ControllerConfig defaultConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean showGuide = true;
|
@Override
|
||||||
}
|
public void setConfig(Gson gson, JsonElement json) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerType type() {
|
||||||
|
return ControllerType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "DUMMY";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerState state() {
|
||||||
|
return ControllerState.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerState prevState() {
|
||||||
|
return ControllerState.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateState() {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package dev.isxander.controlify.controller;
|
||||||
|
|
||||||
|
public abstract class ControllerConfig {
|
||||||
|
public float horizontalLookSensitivity = 1f;
|
||||||
|
public float verticalLookSensitivity = 0.9f;
|
||||||
|
|
||||||
|
public float buttonActivationThreshold = 0.5f;
|
||||||
|
|
||||||
|
public int screenRepeatNavigationDelay = 4;
|
||||||
|
|
||||||
|
public float virtualMouseSensitivity = 1f;
|
||||||
|
|
||||||
|
public boolean autoJump = false;
|
||||||
|
public boolean toggleSprint = true;
|
||||||
|
public boolean toggleSneak = true;
|
||||||
|
|
||||||
|
public String customName = null;
|
||||||
|
|
||||||
|
public boolean showGuide = true;
|
||||||
|
|
||||||
|
public abstract void setDeadzone(int axis, float deadzone);
|
||||||
|
public abstract float getDeadzone(int axis);
|
||||||
|
}
|
@ -1,9 +1,35 @@
|
|||||||
package dev.isxander.controlify.controller;
|
package dev.isxander.controlify.controller;
|
||||||
|
|
||||||
public record ControllerState(AxesState axes, AxesState rawAxes, ButtonState buttons) {
|
import java.util.List;
|
||||||
public static final ControllerState EMPTY = new ControllerState(AxesState.EMPTY, AxesState.EMPTY, ButtonState.EMPTY);
|
import java.util.Set;
|
||||||
|
|
||||||
public boolean hasAnyInput() {
|
public interface ControllerState {
|
||||||
return !this.axes().equals(AxesState.EMPTY) || !this.buttons().equals(ButtonState.EMPTY);
|
List<Float> axes();
|
||||||
}
|
List<Float> rawAxes();
|
||||||
|
|
||||||
|
List<Boolean> buttons();
|
||||||
|
|
||||||
|
boolean hasAnyInput();
|
||||||
|
|
||||||
|
ControllerState EMPTY = new ControllerState() {
|
||||||
|
@Override
|
||||||
|
public List<Float> axes() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Float> rawAxes() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Boolean> buttons() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasAnyInput() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,37 +2,42 @@ package dev.isxander.controlify.controller;
|
|||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonArray;
|
||||||
import dev.isxander.controlify.controller.hid.HIDIdentifier;
|
import dev.isxander.controlify.controller.hid.HIDIdentifier;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.server.packs.PackType;
|
||||||
|
import net.minecraft.server.packs.resources.IoSupplier;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public enum ControllerType {
|
public class ControllerType {
|
||||||
UNKNOWN("Unknown Controller", ControllerTheme.XBOX_ONE),
|
public static final ControllerType UNKNOWN = new ControllerType("Unknown", "unknown");
|
||||||
XBOX_ONE("Xbox Controller", ControllerTheme.XBOX_ONE),
|
|
||||||
XBOX_360("Xbox 360 Controller", ControllerTheme.XBOX_ONE),
|
|
||||||
DUALSHOCK4("PS4 Controller", ControllerTheme.DUALSHOCK4),
|
|
||||||
STEAM_DECK("Steam Deck", ControllerTheme.XBOX_ONE);
|
|
||||||
|
|
||||||
private static final Gson GSON = new GsonBuilder().setLenient().create();
|
private static final Gson GSON = new GsonBuilder().setLenient().create();
|
||||||
private static Map<HIDIdentifier, ControllerType> typeMap = null;
|
private static Map<HIDIdentifier, ControllerType> typeMap = null;
|
||||||
|
private static final ResourceLocation hidDbLocation = new ResourceLocation("controlify", "hiddb.json5");
|
||||||
|
|
||||||
private final String friendlyName;
|
private final String friendlyName;
|
||||||
private final ControllerTheme theme;
|
private final String identifier;
|
||||||
|
|
||||||
ControllerType(String friendlyName, ControllerTheme theme) {
|
private ControllerType(String friendlyName, String identifier) {
|
||||||
this.friendlyName = friendlyName;
|
this.friendlyName = friendlyName;
|
||||||
this.theme = theme;
|
this.identifier = identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String friendlyName() {
|
public String friendlyName() {
|
||||||
return friendlyName;
|
return friendlyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControllerTheme theme() {
|
public String identifier() {
|
||||||
return theme;
|
return identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControllerType getTypeForHID(HIDIdentifier hid) {
|
public static ControllerType getTypeForHID(HIDIdentifier hid) {
|
||||||
@ -40,18 +45,27 @@ public enum ControllerType {
|
|||||||
|
|
||||||
typeMap = new HashMap<>();
|
typeMap = new HashMap<>();
|
||||||
try {
|
try {
|
||||||
try (var hidDb = ControllerType.class.getResourceAsStream("/hiddb.json5")) {
|
List<IoSupplier<InputStream>> dbs = Minecraft.getInstance().getResourceManager().listPacks()
|
||||||
var json = GSON.fromJson(new InputStreamReader(hidDb), JsonObject.class);
|
.map(pack -> pack.getResource(PackType.CLIENT_RESOURCES, hidDbLocation))
|
||||||
for (var type : ControllerType.values()) {
|
.filter(Objects::nonNull)
|
||||||
if (!json.has(type.name().toLowerCase())) continue;
|
.toList();
|
||||||
|
|
||||||
var themeJson = json.getAsJsonObject(type.name().toLowerCase());
|
for (var supplier : dbs) {
|
||||||
|
try (var hidDb = supplier.get()) {
|
||||||
|
var json = GSON.fromJson(new InputStreamReader(hidDb), JsonArray.class);
|
||||||
|
for (var typeElement : json) {
|
||||||
|
var typeObject = typeElement.getAsJsonObject();
|
||||||
|
|
||||||
int vendorId = themeJson.get("vendor").getAsInt();
|
ControllerType type = new ControllerType(typeObject.get("name").getAsString(), typeObject.get("identifier").getAsString());
|
||||||
for (var productIdEntry : themeJson.getAsJsonArray("product")) {
|
|
||||||
int productId = productIdEntry.getAsInt();
|
int vendorId = typeObject.get("vendor").getAsInt();
|
||||||
typeMap.put(new HIDIdentifier(vendorId, productId), type);
|
for (var productIdEntry : typeObject.getAsJsonArray("product")) {
|
||||||
|
int productId = productIdEntry.getAsInt();
|
||||||
|
typeMap.put(new HIDIdentifier(vendorId, productId), type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package dev.isxander.controlify.controller;
|
package dev.isxander.controlify.controller.gamepad;
|
||||||
|
|
||||||
import dev.isxander.yacl.api.NameableEnum;
|
import dev.isxander.yacl.api.NameableEnum;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
public enum ControllerTheme implements NameableEnum {
|
public enum BuiltinGamepadTheme implements NameableEnum {
|
||||||
XBOX_ONE("xbox"),
|
DEFAULT("default"),
|
||||||
|
XBOX_ONE("xbox_one"),
|
||||||
DUALSHOCK4("dualshock4");
|
DUALSHOCK4("dualshock4");
|
||||||
|
|
||||||
private final String id;
|
private final String id;
|
||||||
|
|
||||||
ControllerTheme(String id) {
|
BuiltinGamepadTheme(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +20,6 @@ public enum ControllerTheme implements NameableEnum {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Component getDisplayName() {
|
public Component getDisplayName() {
|
||||||
return Component.translatable("controlify.controller_theme." + name().toLowerCase());
|
return Component.translatable("controlify.controller_theme." + id().toLowerCase());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package dev.isxander.controlify.controller.gamepad;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
|
|
||||||
|
public class GamepadConfig extends ControllerConfig {
|
||||||
|
public float leftStickDeadzoneX = 0.2f;
|
||||||
|
public float leftStickDeadzoneY = 0.2f;
|
||||||
|
|
||||||
|
public float rightStickDeadzoneX = 0.2f;
|
||||||
|
public float rightStickDeadzoneY = 0.2f;
|
||||||
|
|
||||||
|
public BuiltinGamepadTheme theme = BuiltinGamepadTheme.DEFAULT;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDeadzone(int axis, float deadzone) {
|
||||||
|
switch (axis) {
|
||||||
|
case 0 -> leftStickDeadzoneX = deadzone;
|
||||||
|
case 1 -> leftStickDeadzoneY = deadzone;
|
||||||
|
case 2 -> rightStickDeadzoneX = deadzone;
|
||||||
|
case 3 -> rightStickDeadzoneY = deadzone;
|
||||||
|
default -> throw new IllegalArgumentException("Unknown axis: " + axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getDeadzone(int axis) {
|
||||||
|
return switch (axis) {
|
||||||
|
case 0 -> leftStickDeadzoneX;
|
||||||
|
case 1 -> leftStickDeadzoneY;
|
||||||
|
case 2 -> rightStickDeadzoneX;
|
||||||
|
case 3 -> rightStickDeadzoneY;
|
||||||
|
default -> throw new IllegalArgumentException("Unknown axis: " + axis);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package dev.isxander.controlify.controller.gamepad;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.AbstractController;
|
||||||
|
import org.hid4java.HidDevice;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.lwjgl.glfw.GLFWGamepadState;
|
||||||
|
|
||||||
|
public class GamepadController extends AbstractController<GamepadState, GamepadConfig> {
|
||||||
|
private GamepadState state = GamepadState.EMPTY;
|
||||||
|
private GamepadState prevState = GamepadState.EMPTY;
|
||||||
|
|
||||||
|
public GamepadController(int joystickId, HidDevice hidDevice) {
|
||||||
|
super(joystickId, hidDevice);
|
||||||
|
if (!GLFW.glfwJoystickIsGamepad(joystickId))
|
||||||
|
throw new IllegalArgumentException("Joystick " + joystickId + " is not a gamepad!");
|
||||||
|
|
||||||
|
if (!this.name.startsWith(type().friendlyName()))
|
||||||
|
setName(GLFW.glfwGetGamepadName(joystickId));
|
||||||
|
|
||||||
|
this.defaultConfig = new GamepadConfig();
|
||||||
|
this.config = new GamepadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GamepadState state() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GamepadState prevState() {
|
||||||
|
return prevState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateState() {
|
||||||
|
prevState = state;
|
||||||
|
|
||||||
|
GamepadState.AxesState rawAxesState = GamepadState.AxesState.fromController(this);
|
||||||
|
GamepadState.AxesState axesState = rawAxesState
|
||||||
|
.leftJoystickDeadZone(config().leftStickDeadzoneX, config().leftStickDeadzoneY)
|
||||||
|
.rightJoystickDeadZone(config().rightStickDeadzoneX, config().rightStickDeadzoneY);
|
||||||
|
GamepadState.ButtonState buttonState = GamepadState.ButtonState.fromController(this);
|
||||||
|
state = new GamepadState(axesState, rawAxesState, buttonState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void consumeButtonState() {
|
||||||
|
this.state = new GamepadState(state().gamepadAxes(), state().rawGamepadAxes(), GamepadState.ButtonState.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWGamepadState getGamepadState() {
|
||||||
|
GLFWGamepadState state = GLFWGamepadState.create();
|
||||||
|
GLFW.glfwGetGamepadState(joystickId(), state);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,217 @@
|
|||||||
|
package dev.isxander.controlify.controller.gamepad;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
|
import dev.isxander.controlify.utils.ControllerUtils;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public final class GamepadState implements ControllerState {
|
||||||
|
public static final GamepadState EMPTY = new GamepadState(AxesState.EMPTY, AxesState.EMPTY, ButtonState.EMPTY);
|
||||||
|
private final AxesState gamepadAxes;
|
||||||
|
private final AxesState rawGamepadAxes;
|
||||||
|
private final ButtonState gamepadButtons;
|
||||||
|
|
||||||
|
private final List<Float> unnamedAxes;
|
||||||
|
private final List<Float> unnamedRawAxes;
|
||||||
|
private final List<Boolean> unnamedButtons;
|
||||||
|
|
||||||
|
public GamepadState(AxesState gamepadAxes, AxesState rawGamepadAxes, ButtonState gamepadButtons) {
|
||||||
|
this.gamepadAxes = gamepadAxes;
|
||||||
|
this.rawGamepadAxes = rawGamepadAxes;
|
||||||
|
this.gamepadButtons = gamepadButtons;
|
||||||
|
|
||||||
|
this.unnamedAxes = List.of(
|
||||||
|
gamepadAxes.leftStickX(),
|
||||||
|
gamepadAxes.leftStickY(),
|
||||||
|
gamepadAxes.rightStickX(),
|
||||||
|
gamepadAxes.rightStickY(),
|
||||||
|
gamepadAxes.leftTrigger(),
|
||||||
|
gamepadAxes.rightTrigger()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.unnamedRawAxes = List.of(
|
||||||
|
rawGamepadAxes.leftStickX(),
|
||||||
|
rawGamepadAxes.leftStickY(),
|
||||||
|
rawGamepadAxes.rightStickX(),
|
||||||
|
rawGamepadAxes.rightStickY(),
|
||||||
|
rawGamepadAxes.leftTrigger(),
|
||||||
|
rawGamepadAxes.rightTrigger()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.unnamedButtons = List.of(
|
||||||
|
gamepadButtons.a(),
|
||||||
|
gamepadButtons.b(),
|
||||||
|
gamepadButtons.x(),
|
||||||
|
gamepadButtons.y(),
|
||||||
|
gamepadButtons.leftBumper(),
|
||||||
|
gamepadButtons.rightBumper(),
|
||||||
|
gamepadButtons.back(),
|
||||||
|
gamepadButtons.start(),
|
||||||
|
gamepadButtons.leftStick(),
|
||||||
|
gamepadButtons.rightStick(),
|
||||||
|
gamepadButtons.dpadUp(),
|
||||||
|
gamepadButtons.dpadDown(),
|
||||||
|
gamepadButtons.dpadLeft(),
|
||||||
|
gamepadButtons.dpadRight()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Float> axes() {
|
||||||
|
return unnamedAxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Float> rawAxes() {
|
||||||
|
return unnamedRawAxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Boolean> buttons() {
|
||||||
|
return unnamedButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasAnyInput() {
|
||||||
|
return !this.gamepadAxes().equals(AxesState.EMPTY) || !this.gamepadButtons().equals(ButtonState.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AxesState gamepadAxes() {
|
||||||
|
return gamepadAxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AxesState rawGamepadAxes() {
|
||||||
|
return rawGamepadAxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ButtonState gamepadButtons() {
|
||||||
|
return gamepadButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == this) return true;
|
||||||
|
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||||
|
var that = (GamepadState) obj;
|
||||||
|
return Objects.equals(this.gamepadAxes, that.gamepadAxes) &&
|
||||||
|
Objects.equals(this.rawGamepadAxes, that.rawGamepadAxes) &&
|
||||||
|
Objects.equals(this.gamepadButtons, that.gamepadButtons);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(gamepadAxes, rawGamepadAxes, gamepadButtons);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "GamepadState[" +
|
||||||
|
"gamepadAxes=" + gamepadAxes + ", " +
|
||||||
|
"rawGamepadAxes=" + rawGamepadAxes + ", " +
|
||||||
|
"gamepadButtons=" + gamepadButtons + ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public record AxesState(
|
||||||
|
float leftStickX, float leftStickY,
|
||||||
|
float rightStickX, float rightStickY,
|
||||||
|
float leftTrigger, float rightTrigger
|
||||||
|
) {
|
||||||
|
public static AxesState EMPTY = new AxesState(0, 0, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
public AxesState leftJoystickDeadZone(float deadZoneX, float deadZoneY) {
|
||||||
|
return new AxesState(
|
||||||
|
ControllerUtils.deadzone(leftStickX, deadZoneX),
|
||||||
|
ControllerUtils.deadzone(leftStickY, deadZoneY),
|
||||||
|
rightStickX, rightStickY, leftTrigger, rightTrigger
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AxesState rightJoystickDeadZone(float deadZoneX, float deadZoneY) {
|
||||||
|
return new AxesState(
|
||||||
|
leftStickX, leftStickY,
|
||||||
|
ControllerUtils.deadzone(rightStickX, deadZoneX),
|
||||||
|
ControllerUtils.deadzone(rightStickY, deadZoneY),
|
||||||
|
leftTrigger, rightTrigger
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AxesState leftTriggerDeadZone(float deadZone) {
|
||||||
|
return new AxesState(
|
||||||
|
leftStickX, leftStickY, rightStickX, rightStickY,
|
||||||
|
ControllerUtils.deadzone(leftTrigger, deadZone),
|
||||||
|
rightTrigger
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AxesState rightTriggerDeadZone(float deadZone) {
|
||||||
|
return new AxesState(
|
||||||
|
leftStickX, leftStickY, rightStickX, rightStickY,
|
||||||
|
leftTrigger,
|
||||||
|
ControllerUtils.deadzone(rightTrigger, deadZone)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AxesState fromController(GamepadController controller) {
|
||||||
|
if (controller == null)
|
||||||
|
return EMPTY;
|
||||||
|
|
||||||
|
var state = controller.getGamepadState();
|
||||||
|
var axes = state.axes();
|
||||||
|
|
||||||
|
float leftX = axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_X);
|
||||||
|
float leftY = axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_Y);
|
||||||
|
float rightX = axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_X);
|
||||||
|
float rightY = axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_Y);
|
||||||
|
float leftTrigger = (axes.get(GLFW.GLFW_GAMEPAD_AXIS_LEFT_TRIGGER) + 1f) / 2f;
|
||||||
|
float rightTrigger = (axes.get(GLFW.GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER) + 1f) / 2f;
|
||||||
|
|
||||||
|
return new AxesState(leftX, leftY, rightX, rightY, leftTrigger, rightTrigger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ButtonState(
|
||||||
|
boolean a, boolean b, boolean x, boolean y,
|
||||||
|
boolean leftBumper, boolean rightBumper,
|
||||||
|
boolean back, boolean start, boolean guide,
|
||||||
|
boolean dpadUp, boolean dpadDown, boolean dpadLeft, boolean dpadRight,
|
||||||
|
boolean leftStick, boolean rightStick
|
||||||
|
) {
|
||||||
|
public static ButtonState EMPTY = new ButtonState(
|
||||||
|
false, false, false, false,
|
||||||
|
false, false,
|
||||||
|
false, false, false,
|
||||||
|
false, false, false, false,
|
||||||
|
false, false
|
||||||
|
);
|
||||||
|
|
||||||
|
public static ButtonState fromController(GamepadController controller) {
|
||||||
|
if (controller == null)
|
||||||
|
return EMPTY;
|
||||||
|
|
||||||
|
var state = controller.getGamepadState();
|
||||||
|
var buttons = state.buttons();
|
||||||
|
|
||||||
|
boolean a = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_A) == GLFW.GLFW_PRESS;
|
||||||
|
boolean b = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_B) == GLFW.GLFW_PRESS;
|
||||||
|
boolean x = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_X) == GLFW.GLFW_PRESS;
|
||||||
|
boolean y = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_Y) == GLFW.GLFW_PRESS;
|
||||||
|
boolean leftBumper = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) == GLFW.GLFW_PRESS;
|
||||||
|
boolean rightBumper = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) == GLFW.GLFW_PRESS;
|
||||||
|
boolean back = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_BACK) == GLFW.GLFW_PRESS;
|
||||||
|
boolean start = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_START) == GLFW.GLFW_PRESS;
|
||||||
|
boolean guide = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_GUIDE) == GLFW.GLFW_PRESS;
|
||||||
|
boolean dpadUp = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_UP) == GLFW.GLFW_PRESS;
|
||||||
|
boolean dpadDown = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_DOWN) == GLFW.GLFW_PRESS;
|
||||||
|
boolean dpadLeft = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_LEFT) == GLFW.GLFW_PRESS;
|
||||||
|
boolean dpadRight = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_DPAD_RIGHT) == GLFW.GLFW_PRESS;
|
||||||
|
boolean leftStick = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_LEFT_THUMB) == GLFW.GLFW_PRESS;
|
||||||
|
boolean rightStick = buttons.get(GLFW.GLFW_GAMEPAD_BUTTON_RIGHT_THUMB) == GLFW.GLFW_PRESS;
|
||||||
|
|
||||||
|
return new ButtonState(a, b, x, y, leftBumper, rightBumper, back, start, guide, dpadUp, dpadDown, dpadLeft, dpadRight, leftStick, rightStick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,12 @@ public class ControllerHIDService implements HidServicesListener {
|
|||||||
|
|
||||||
if (isController(device)) {
|
if (isController(device)) {
|
||||||
if (deviceQueue.peek() != null) {
|
if (deviceQueue.peek() != null) {
|
||||||
deviceQueue.poll().accept(device);
|
try {
|
||||||
|
deviceQueue.poll().accept(device);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Controlify.LOGGER.error("Failed to handle controller device attach event.", e);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Controlify.LOGGER.error("Unhandled controller: " + ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())).friendlyName());
|
Controlify.LOGGER.error("Unhandled controller: " + ControllerType.getTypeForHID(new HIDIdentifier(device.getVendorId(), device.getProductId())).friendlyName());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package dev.isxander.controlify.controller.joystick;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class JoystickConfig extends ControllerConfig {
|
||||||
|
private final Map<String, Float> deadzones;
|
||||||
|
|
||||||
|
private transient JoystickController controller;
|
||||||
|
|
||||||
|
public JoystickConfig(JoystickController controller) {
|
||||||
|
this.controller = 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
|
||||||
|
public void setDeadzone(int axis, float deadzone) {
|
||||||
|
if (axis < 0)
|
||||||
|
throw new IllegalArgumentException("Axis cannot be negative!");
|
||||||
|
|
||||||
|
deadzones.put(controller.mapping().axis(axis).identifier(), deadzone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getDeadzone(int axis) {
|
||||||
|
if (axis < 0)
|
||||||
|
throw new IllegalArgumentException("Axis cannot be negative!");
|
||||||
|
|
||||||
|
return deadzones.getOrDefault(controller.mapping().axis(axis).identifier(), 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setController(JoystickController controller) {
|
||||||
|
this.controller = controller;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package dev.isxander.controlify.controller.joystick;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import dev.isxander.controlify.controller.AbstractController;
|
||||||
|
import dev.isxander.controlify.controller.joystick.mapping.DataJoystickMapping;
|
||||||
|
import dev.isxander.controlify.controller.joystick.mapping.JoystickMapping;
|
||||||
|
import org.hid4java.HidDevice;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class JoystickController extends AbstractController<JoystickState, JoystickConfig> {
|
||||||
|
private JoystickState state = JoystickState.EMPTY, prevState = JoystickState.EMPTY;
|
||||||
|
private final int axisCount, buttonCount, hatCount;
|
||||||
|
private final JoystickMapping mapping;
|
||||||
|
|
||||||
|
public JoystickController(int joystickId, @Nullable HidDevice hidDevice) {
|
||||||
|
super(joystickId, hidDevice);
|
||||||
|
|
||||||
|
this.axisCount = GLFW.glfwGetJoystickAxes(joystickId).capacity();
|
||||||
|
this.buttonCount = GLFW.glfwGetJoystickButtons(joystickId).capacity();
|
||||||
|
this.hatCount = GLFW.glfwGetJoystickHats(joystickId).capacity();
|
||||||
|
|
||||||
|
this.mapping = Objects.requireNonNull(DataJoystickMapping.fromType(type()));
|
||||||
|
|
||||||
|
this.config = new JoystickConfig(this);
|
||||||
|
this.defaultConfig = new JoystickConfig(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JoystickState state() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JoystickState prevState() {
|
||||||
|
return prevState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateState() {
|
||||||
|
prevState = state;
|
||||||
|
state = JoystickState.fromJoystick(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JoystickMapping mapping() {
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int axisCount() {
|
||||||
|
return axisCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int buttonCount() {
|
||||||
|
return buttonCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hatCount() {
|
||||||
|
return hatCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConfig(Gson gson, JsonElement json) {
|
||||||
|
super.setConfig(gson, json);
|
||||||
|
this.config.setController(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
package dev.isxander.controlify.controller.joystick;
|
||||||
|
|
||||||
|
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.utils.ControllerUtils;
|
||||||
|
import dev.isxander.yacl.api.NameableEnum;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.FloatBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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());
|
||||||
|
|
||||||
|
private final JoystickMapping mapping;
|
||||||
|
|
||||||
|
private final List<Float> axes;
|
||||||
|
private final List<Float> rawAxes;
|
||||||
|
private final List<Boolean> buttons;
|
||||||
|
private final List<HatState> hats;
|
||||||
|
|
||||||
|
private JoystickState(JoystickMapping mapping, List<Float> axes, List<Float> rawAxes, List<Boolean> buttons, List<HatState> hats) {
|
||||||
|
this.mapping = mapping;
|
||||||
|
this.axes = axes;
|
||||||
|
this.rawAxes = rawAxes;
|
||||||
|
this.buttons = buttons;
|
||||||
|
this.hats = hats;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Float> axes() {
|
||||||
|
return axes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Float> rawAxes() {
|
||||||
|
return rawAxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Boolean> buttons() {
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HatState> hats() {
|
||||||
|
return hats;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasAnyInput() {
|
||||||
|
return IntStream.range(0, axes().size()).anyMatch(i -> !mapping.axis(i).isAxisResting(axes().get(i)))
|
||||||
|
|| buttons().stream().anyMatch(Boolean::booleanValue)
|
||||||
|
|| hats().stream().anyMatch(hat -> hat != HatState.CENTERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JoystickState fromJoystick(JoystickController joystick) {
|
||||||
|
FloatBuffer axesBuffer = GLFW.glfwGetJoystickAxes(joystick.joystickId());
|
||||||
|
List<Float> axes = new ArrayList<>();
|
||||||
|
List<Float> rawAxes = new ArrayList<>();
|
||||||
|
if (axesBuffer != null) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buttonBuffer = GLFW.glfwGetJoystickButtons(joystick.joystickId());
|
||||||
|
List<Boolean> buttons = new ArrayList<>();
|
||||||
|
if (buttonBuffer != null) {
|
||||||
|
while (buttonBuffer.hasRemaining()) {
|
||||||
|
buttons.add(buttonBuffer.get() == GLFW.GLFW_PRESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer hatBuffer = GLFW.glfwGetJoystickHats(joystick.joystickId());
|
||||||
|
List<JoystickState.HatState> hats = new ArrayList<>();
|
||||||
|
if (hatBuffer != null) {
|
||||||
|
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;
|
||||||
|
default -> throw new IllegalStateException("Unexpected value: " + hatBuffer.get());
|
||||||
|
};
|
||||||
|
hats.add(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JoystickState(joystick.mapping(), axes, rawAxes, buttons, hats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HatState implements NameableEnum {
|
||||||
|
CENTERED,
|
||||||
|
UP,
|
||||||
|
RIGHT,
|
||||||
|
DOWN,
|
||||||
|
LEFT,
|
||||||
|
RIGHT_UP,
|
||||||
|
RIGHT_DOWN,
|
||||||
|
LEFT_UP,
|
||||||
|
LEFT_DOWN;
|
||||||
|
|
||||||
|
public boolean isCentered() {
|
||||||
|
return this == CENTERED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRight() {
|
||||||
|
return this == RIGHT || this == RIGHT_UP || this == RIGHT_DOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUp() {
|
||||||
|
return this == UP || this == RIGHT_UP || this == LEFT_UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLeft() {
|
||||||
|
return this == LEFT || this == LEFT_UP || this == LEFT_DOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDown() {
|
||||||
|
return this == DOWN || this == RIGHT_DOWN || this == LEFT_DOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getDisplayName() {
|
||||||
|
return Component.translatable("controlify.hat_state." + this.name().toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
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 net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.phys.Vec2;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class DataJoystickMapping 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;
|
||||||
|
|
||||||
|
public DataJoystickMapping(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();
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
|
||||||
|
for (var id : ids) {
|
||||||
|
axisMappings.put(id, new AxisMapping(ids, identifier, inpRange, outRange, restState, deadzone, type.identifier(), axisNames));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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()));
|
||||||
|
});
|
||||||
|
|
||||||
|
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()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Axis axis(int axis) {
|
||||||
|
if (!axisMappings.containsKey(axis))
|
||||||
|
return UnmappedJoystickMapping.INSTANCE.axis(axis);
|
||||||
|
return axisMappings.get(axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Button button(int button) {
|
||||||
|
if (!buttonMappings.containsKey(button))
|
||||||
|
return UnmappedJoystickMapping.INSTANCE.button(button);
|
||||||
|
return buttonMappings.get(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Hat hat(int hat) {
|
||||||
|
if (!hatMappings.containsKey(hat))
|
||||||
|
return UnmappedJoystickMapping.INSTANCE.hat(hat);
|
||||||
|
return hatMappings.get(hat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JoystickMapping fromType(ControllerType type) {
|
||||||
|
var resource = Minecraft.getInstance().getResourceManager().getResource(new ResourceLocation("controlify", "mappings/" + type.identifier() + ".json"));
|
||||||
|
if (resource.isEmpty()) {
|
||||||
|
Controlify.LOGGER.warn("No joystick mapping found for controller: '" + type.identifier() + "'");
|
||||||
|
return UnmappedJoystickMapping.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var reader = resource.get().openAsReader()) {
|
||||||
|
return new DataJoystickMapping(gson.fromJson(reader, JsonObject.class), type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Controlify.LOGGER.error("Failed to load joystick mapping for controller: '" + type.identifier() + "'", e);
|
||||||
|
return UnmappedJoystickMapping.INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record AxisMapping(List<Integer> ids, String identifier, Vec2 inpRange, Vec2 outRange, float restState, boolean requiresDeadzone, String typeId, List<List<String>> axisNames) implements Axis {
|
||||||
|
@Override
|
||||||
|
public float modifyAxis(float value) {
|
||||||
|
if (inpRange() == null || outRange() == null)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
return (value + (outRange().x - inpRange().x)) / (inpRange().y - inpRange().x) * (outRange().y - outRange().x);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAxisResting(float value) {
|
||||||
|
return value == restState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component name() {
|
||||||
|
return Component.translatable("controlify.joystick_mapping." + typeId() + ".axis." + identifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getDirectionName(int axis, JoystickAxisBind.AxisDirection direction) {
|
||||||
|
var directionId = axisNames().get(ids.indexOf(axis)).get(direction.ordinal());
|
||||||
|
return Component.translatable("controlify.joystick_mapping." + typeId() + ".axis." + identifier() + "." + directionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record ButtonMapping(String identifier, String typeId) implements Button {
|
||||||
|
@Override
|
||||||
|
public Component name() {
|
||||||
|
return Component.translatable("controlify.joystick_mapping." + typeId() + ".button." + identifier());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record HatMapping(String identifier, String typeId) implements Hat {
|
||||||
|
@Override
|
||||||
|
public Component name() {
|
||||||
|
return Component.translatable("controlify.joystick_mapping." + typeId() + ".hat." + identifier());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package dev.isxander.controlify.controller.joystick.mapping;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
public interface JoystickMapping {
|
||||||
|
Axis axis(int axis);
|
||||||
|
|
||||||
|
Button button(int button);
|
||||||
|
|
||||||
|
Hat hat(int hat);
|
||||||
|
|
||||||
|
interface Axis {
|
||||||
|
String identifier();
|
||||||
|
|
||||||
|
Component name();
|
||||||
|
|
||||||
|
boolean requiresDeadzone();
|
||||||
|
|
||||||
|
float modifyAxis(float value);
|
||||||
|
|
||||||
|
boolean isAxisResting(float value);
|
||||||
|
|
||||||
|
Component getDirectionName(int axis, JoystickAxisBind.AxisDirection direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Button {
|
||||||
|
String identifier();
|
||||||
|
|
||||||
|
Component name();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Hat {
|
||||||
|
String identifier();
|
||||||
|
|
||||||
|
Component name();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package dev.isxander.controlify.controller.joystick.mapping;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.bindings.JoystickAxisBind;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
|
||||||
|
public class UnmappedJoystickMapping implements JoystickMapping {
|
||||||
|
public static final UnmappedJoystickMapping INSTANCE = new UnmappedJoystickMapping();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Axis axis(int axis) {
|
||||||
|
return new UnmappedAxis(axis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Button button(int button) {
|
||||||
|
return new UnmappedButton(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Hat hat(int hat) {
|
||||||
|
return new UnmappedHat(hat);
|
||||||
|
}
|
||||||
|
|
||||||
|
private record UnmappedAxis(int axis) implements Axis {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String identifier() {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record UnmappedButton(int button) implements Button {
|
||||||
|
@Override
|
||||||
|
public String identifier() {
|
||||||
|
return "button-" + button;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component name() {
|
||||||
|
return Component.translatable("controlify.joystick_mapping.unmapped.button", button + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record UnmappedHat(int hat) implements Hat {
|
||||||
|
@Override
|
||||||
|
public String identifier() {
|
||||||
|
return "hat-" + hat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component name() {
|
||||||
|
return Component.translatable("controlify.joystick_mapping.unmapped.hat", hat + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,12 +45,12 @@ public class ControlifyEvents {
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ControllerStateUpdate {
|
public interface ControllerStateUpdate {
|
||||||
void onControllerStateUpdate(Controller controller);
|
void onControllerStateUpdate(Controller<?, ?> controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ControllerBindRegistry {
|
public interface ControllerBindRegistry {
|
||||||
void onRegisterControllerBinds(ControllerBindings bindings, Controller controller);
|
void onRegisterControllerBinds(ControllerBindings<?> bindings, Controller<?, ?> controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package dev.isxander.controlify.gui;
|
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
|
||||||
import dev.isxander.controlify.bindings.Bind;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import net.minecraft.client.gui.GuiComponent;
|
|
||||||
|
|
||||||
public class ButtonRenderer {
|
|
||||||
public static final int BUTTON_SIZE = 22;
|
|
||||||
|
|
||||||
public static void drawButton(Bind button, Controller controller, PoseStack poseStack, int x, int centerY) {
|
|
||||||
RenderSystem.setShaderTexture(0, button.textureLocation(controller));
|
|
||||||
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
|
||||||
|
|
||||||
GuiComponent.blit(poseStack, x, centerY - BUTTON_SIZE / 2, 0, 0, BUTTON_SIZE, BUTTON_SIZE, BUTTON_SIZE, BUTTON_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public record DrawSize(int width, int height) { }
|
|
||||||
}
|
|
4
src/main/java/dev/isxander/controlify/gui/DrawSize.java
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package dev.isxander.controlify.gui;
|
||||||
|
|
||||||
|
public record DrawSize(int width, int height) {
|
||||||
|
}
|
@ -11,12 +11,10 @@ import net.minecraft.network.chat.Component;
|
|||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.util.Mth;
|
import net.minecraft.util.Mth;
|
||||||
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
|
|
||||||
public class ControllerDeadzoneCalibrationScreen extends Screen {
|
public class ControllerDeadzoneCalibrationScreen extends Screen {
|
||||||
private static final ResourceLocation GUI_BARS_LOCATION = new ResourceLocation("textures/gui/bars.png");
|
private static final ResourceLocation GUI_BARS_LOCATION = new ResourceLocation("textures/gui/bars.png");
|
||||||
|
|
||||||
protected final Controller controller;
|
protected final Controller<?, ?> controller;
|
||||||
private final Screen parent;
|
private final Screen parent;
|
||||||
|
|
||||||
private MultiLineLabel waitLabel, infoLabel, completeLabel;
|
private MultiLineLabel waitLabel, infoLabel, completeLabel;
|
||||||
@ -26,7 +24,7 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
|
|||||||
protected boolean calibrating = false, calibrated = false;
|
protected boolean calibrating = false, calibrated = false;
|
||||||
protected int calibrationTicks = 0;
|
protected int calibrationTicks = 0;
|
||||||
|
|
||||||
public ControllerDeadzoneCalibrationScreen(Controller controller, Screen parent) {
|
public ControllerDeadzoneCalibrationScreen(Controller<?, ?> controller, Screen parent) {
|
||||||
super(Component.translatable("controlify.calibration.title"));
|
super(Component.translatable("controlify.calibration.title"));
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
@ -110,42 +108,20 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void useCurrentStateAsDeadzone() {
|
private void useCurrentStateAsDeadzone() {
|
||||||
var rawAxes = controller.state().rawAxes();
|
var axes = controller.state().axes();
|
||||||
|
|
||||||
var minDeadzoneLS = Math.max(rawAxes.leftStickX(), rawAxes.leftStickY()) + 0.08f;
|
for (int i = 0; i < axes.size(); i++) {
|
||||||
var deadzoneLS = (float)Mth.clamp(0.05 * Math.ceil(minDeadzoneLS / 0.05), 0, 0.95);
|
var axis = axes.get(i);
|
||||||
|
var minDeadzone = axis + 0.08f;
|
||||||
var minDeadzoneRS = Math.max(rawAxes.rightStickX(), rawAxes.rightStickY()) + 0.08f;
|
controller.config().setDeadzone(i, (float)Mth.clamp(0.05 * Math.ceil(minDeadzone / 0.05), 0, 0.95));
|
||||||
var deadzoneRS = (float)Mth.clamp(0.05 * Math.ceil(minDeadzoneRS / 0.05), 0, 0.95);
|
}
|
||||||
|
|
||||||
controller.config().leftStickDeadzone = deadzoneLS;
|
|
||||||
controller.config().rightStickDeadzone = deadzoneRS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean stateChanged() {
|
private boolean stateChanged() {
|
||||||
var amt = 0.0001f;
|
var amt = 0.0001f;
|
||||||
|
|
||||||
var lsX = controller.state().rawAxes().leftStickX();
|
return controller.state().axes().stream()
|
||||||
var prevLsX = controller.prevState().rawAxes().leftStickX();
|
.anyMatch(axis -> Math.abs(axis - controller.prevState().axes().get(controller.state().axes().indexOf(axis))) > amt);
|
||||||
if (Math.abs(lsX - prevLsX) > amt)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var lsY = controller.state().rawAxes().leftStickY();
|
|
||||||
var prevLsY = controller.prevState().rawAxes().leftStickY();
|
|
||||||
if (Math.abs(lsY - prevLsY) > amt)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var rsX = controller.state().rawAxes().rightStickX();
|
|
||||||
var prevRsX = controller.prevState().rawAxes().rightStickX();
|
|
||||||
if (Math.abs(rsX - prevRsX) > amt)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
var rsY = controller.state().rawAxes().rightStickY();
|
|
||||||
var prevRsY = controller.prevState().rawAxes().rightStickY();
|
|
||||||
if (Math.abs(rsY - prevRsY) > amt)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -6,12 +6,10 @@ import net.minecraft.client.player.Input;
|
|||||||
import net.minecraft.client.player.LocalPlayer;
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
|
||||||
public class ControllerPlayerMovement extends Input {
|
public class ControllerPlayerMovement extends Input {
|
||||||
private final Controller controller;
|
private final Controller<?, ?> controller;
|
||||||
private final LocalPlayer player;
|
private final LocalPlayer player;
|
||||||
|
|
||||||
private boolean shiftToggled = false;
|
public ControllerPlayerMovement(Controller<?, ?> controller, LocalPlayer player) {
|
||||||
|
|
||||||
public ControllerPlayerMovement(Controller controller, LocalPlayer player) {
|
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.player = player;
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@ import net.minecraft.client.Minecraft;
|
|||||||
import net.minecraft.client.player.KeyboardInput;
|
import net.minecraft.client.player.KeyboardInput;
|
||||||
|
|
||||||
public class InGameInputHandler {
|
public class InGameInputHandler {
|
||||||
private final Controller controller;
|
private final Controller<?, ?> controller;
|
||||||
private final Minecraft minecraft;
|
private final Minecraft minecraft;
|
||||||
|
|
||||||
private double lookInputX, lookInputY;
|
private double lookInputX, lookInputY;
|
||||||
|
|
||||||
public InGameInputHandler(Controller controller) {
|
public InGameInputHandler(Controller<?, ?> controller) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.minecraft = Minecraft.getInstance();
|
this.minecraft = Minecraft.getInstance();
|
||||||
|
|
||||||
@ -51,10 +51,12 @@ public class InGameInputHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void handlePlayerLookInput() {
|
protected void handlePlayerLookInput() {
|
||||||
var axes = controller.state().axes();
|
var impulseY = controller.bindings().LOOK_DOWN.state() - controller.bindings().LOOK_UP.state();
|
||||||
|
var impulseX = controller.bindings().LOOK_RIGHT.state() - controller.bindings().LOOK_LEFT.state();
|
||||||
|
|
||||||
if (minecraft.mouseHandler.isMouseGrabbed() && minecraft.isWindowActive()) {
|
if (minecraft.mouseHandler.isMouseGrabbed() && minecraft.isWindowActive()) {
|
||||||
lookInputX = axes.rightStickX() * Math.abs(axes.rightStickX()) * controller.config().horizontalLookSensitivity;
|
lookInputX = impulseX * Math.abs(impulseX) * controller.config().horizontalLookSensitivity;
|
||||||
lookInputY = axes.rightStickY() * Math.abs(axes.rightStickY()) * controller.config().verticalLookSensitivity;
|
lookInputY = impulseY * Math.abs(impulseY) * controller.config().verticalLookSensitivity;
|
||||||
} else {
|
} else {
|
||||||
lookInputX = lookInputY = 0;
|
lookInputX = lookInputY = 0;
|
||||||
}
|
}
|
||||||
|
@ -10,5 +10,5 @@ import java.util.Optional;
|
|||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ButtonActionSupplier {
|
public interface ButtonActionSupplier {
|
||||||
Optional<GuideAction> supply(Minecraft client, LocalPlayer player, ClientLevel level, HitResult hitResult, Controller controller);
|
Optional<GuideAction> supply(Minecraft client, LocalPlayer player, ClientLevel level, HitResult hitResult, Controller<?, ?> controller);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import net.minecraft.world.phys.*;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class InGameButtonGuide implements ButtonGuideRegistry {
|
public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||||
private final Controller controller;
|
private final Controller<?, ?> controller;
|
||||||
private final LocalPlayer player;
|
private final LocalPlayer player;
|
||||||
private final Minecraft minecraft = Minecraft.getInstance();
|
private final Minecraft minecraft = Minecraft.getInstance();
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ public class InGameButtonGuide implements ButtonGuideRegistry {
|
|||||||
private final List<GuideAction> leftGuides = new ArrayList<>();
|
private final List<GuideAction> leftGuides = new ArrayList<>();
|
||||||
private final List<GuideAction> rightGuides = new ArrayList<>();
|
private final List<GuideAction> rightGuides = new ArrayList<>();
|
||||||
|
|
||||||
public InGameButtonGuide(Controller controller, LocalPlayer localPlayer) {
|
public InGameButtonGuide(Controller<?, ?> controller, LocalPlayer localPlayer) {
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
this.player = localPlayer;
|
this.player = localPlayer;
|
||||||
|
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package dev.isxander.controlify.mixins.core;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.platform.GLX;
|
||||||
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
import java.util.function.LongSupplier;
|
||||||
|
|
||||||
|
@Mixin(value = GLX.class, remap = false)
|
||||||
|
public class GLXMixin {
|
||||||
|
@Inject(method = "_initGlfw", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwInit()Z", shift = At.Shift.BEFORE))
|
||||||
|
private static void addInitHints(CallbackInfoReturnable<LongSupplier> cir) {
|
||||||
|
GLFW.glfwInitHint(GLFW.GLFW_JOYSTICK_HAT_BUTTONS, GLFW.GLFW_FALSE);
|
||||||
|
}
|
||||||
|
}
|
@ -25,9 +25,16 @@ public abstract class MinecraftMixin {
|
|||||||
|
|
||||||
@Shadow public abstract ToastComponent getToasts();
|
@Shadow public abstract ToastComponent getToasts();
|
||||||
|
|
||||||
|
@ModifyExpressionValue(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/packs/resources/ReloadableResourceManager;createReload(Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;Ljava/util/concurrent/CompletableFuture;Ljava/util/List;)Lnet/minecraft/server/packs/resources/ReloadInstance;"))
|
||||||
|
private ReloadInstance onInputInitialized(ReloadInstance resourceReload) {
|
||||||
|
// Controllers need to be initialized extremely late due to the data-driven nature of controllers.
|
||||||
|
resourceReload.done().thenRun(() -> Controlify.instance().initializeControllers());
|
||||||
|
return resourceReload;
|
||||||
|
}
|
||||||
|
|
||||||
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V", shift = At.Shift.AFTER))
|
@Inject(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/KeyboardHandler;setup(J)V", shift = At.Shift.AFTER))
|
||||||
private void onInputInitialized(CallbackInfo ci) {
|
private void onInputInitialized(CallbackInfo ci) {
|
||||||
Controlify.instance().onInitializeInput();
|
Controlify.instance().initializeControlify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;turnPlayer()V"))
|
@Inject(method = "runTick", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MouseHandler;turnPlayer()V"))
|
||||||
|
@ -5,14 +5,14 @@ import dev.isxander.controlify.controller.Controller;
|
|||||||
public interface ComponentProcessor {
|
public interface ComponentProcessor {
|
||||||
ComponentProcessor EMPTY = new ComponentProcessor(){};
|
ComponentProcessor EMPTY = new ComponentProcessor(){};
|
||||||
|
|
||||||
default boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller controller) {
|
default boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
default boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
default void onFocusGained(ScreenProcessor<?> screen, Controller controller) {
|
default void onFocusGained(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ public class ScreenProcessor<T extends Screen> {
|
|||||||
ControlifyEvents.VIRTUAL_MOUSE_TOGGLED.register(this::onVirtualMouseToggled);
|
ControlifyEvents.VIRTUAL_MOUSE_TOGGLED.register(this::onVirtualMouseToggled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onControllerUpdate(Controller controller) {
|
public void onControllerUpdate(Controller<?, ?> controller) {
|
||||||
if (!Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
|
if (!Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
|
||||||
handleComponentNavigation(controller);
|
handleComponentNavigation(controller);
|
||||||
handleButtons(controller);
|
handleButtons(controller);
|
||||||
@ -43,7 +43,7 @@ public class ScreenProcessor<T extends Screen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleComponentNavigation(Controller controller) {
|
protected void handleComponentNavigation(Controller<?, ?> controller) {
|
||||||
if (screen.getFocused() == null)
|
if (screen.getFocused() == null)
|
||||||
setInitialFocus();
|
setInitialFocus();
|
||||||
|
|
||||||
@ -59,28 +59,17 @@ public class ScreenProcessor<T extends Screen> {
|
|||||||
|
|
||||||
boolean repeatEventAvailable = ++lastMoved >= controller.config().screenRepeatNavigationDelay;
|
boolean repeatEventAvailable = ++lastMoved >= controller.config().screenRepeatNavigationDelay;
|
||||||
|
|
||||||
var axes = controller.state().axes();
|
var bindings = controller.bindings();
|
||||||
var prevAxes = controller.prevState().axes();
|
|
||||||
var buttons = controller.state().buttons();
|
|
||||||
var prevButtons = controller.prevState().buttons();
|
|
||||||
|
|
||||||
FocusNavigationEvent.ArrowNavigation event = null;
|
FocusNavigationEvent.ArrowNavigation event = null;
|
||||||
if (axes.leftStickX() > 0.5f && (repeatEventAvailable || prevAxes.leftStickX() <= 0.5f)) {
|
if (bindings.GUI_NAVI_RIGHT.held() && (repeatEventAvailable || !bindings.GUI_NAVI_RIGHT.prevHeld())) {
|
||||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.RIGHT);
|
event = accessor.invokeCreateArrowEvent(ScreenDirection.RIGHT);
|
||||||
} else if (axes.leftStickX() < -0.5f && (repeatEventAvailable || prevAxes.leftStickX() >= -0.5f)) {
|
} else if (bindings.GUI_NAVI_LEFT.held() && (repeatEventAvailable || !bindings.GUI_NAVI_LEFT.prevHeld())) {
|
||||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.LEFT);
|
event = accessor.invokeCreateArrowEvent(ScreenDirection.LEFT);
|
||||||
} else if (axes.leftStickY() < -0.5f && (repeatEventAvailable || prevAxes.leftStickY() >= -0.5f)) {
|
} else if (bindings.GUI_NAVI_UP.held() && (repeatEventAvailable || !bindings.GUI_NAVI_UP.prevHeld())) {
|
||||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.UP);
|
event = accessor.invokeCreateArrowEvent(ScreenDirection.UP);
|
||||||
} else if (axes.leftStickY() > 0.5f && (repeatEventAvailable || prevAxes.leftStickY() <= 0.5f)) {
|
} else if (bindings.GUI_NAVI_DOWN.held() && (repeatEventAvailable || !bindings.GUI_NAVI_DOWN.prevHeld())) {
|
||||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.DOWN);
|
event = accessor.invokeCreateArrowEvent(ScreenDirection.DOWN);
|
||||||
} else if (buttons.dpadUp() && (repeatEventAvailable || !prevButtons.dpadUp())) {
|
|
||||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.UP);
|
|
||||||
} else if (buttons.dpadDown() && (repeatEventAvailable || !prevButtons.dpadDown())) {
|
|
||||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.DOWN);
|
|
||||||
} else if (buttons.dpadLeft() && (repeatEventAvailable || !prevButtons.dpadLeft())) {
|
|
||||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.LEFT);
|
|
||||||
} else if (buttons.dpadRight() && (repeatEventAvailable || !prevButtons.dpadRight())) {
|
|
||||||
event = accessor.invokeCreateArrowEvent(ScreenDirection.RIGHT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
@ -97,7 +86,7 @@ public class ScreenProcessor<T extends Screen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleButtons(Controller controller) {
|
protected void handleButtons(Controller<?, ?> controller) {
|
||||||
var focusTree = getFocusTree();
|
var focusTree = getFocusTree();
|
||||||
while (!focusTree.isEmpty()) {
|
while (!focusTree.isEmpty()) {
|
||||||
var focused = focusTree.poll();
|
var focused = focusTree.poll();
|
||||||
@ -111,7 +100,7 @@ public class ScreenProcessor<T extends Screen> {
|
|||||||
screen.onClose();
|
screen.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handleVMouseNavigation(Controller controller) {
|
protected void handleVMouseNavigation(Controller<?, ?> controller) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.gui.components.AbstractButton;
|
import net.minecraft.client.gui.components.AbstractButton;
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ public class AbstractButtonComponentProcessor implements ComponentProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
if (controller.bindings().GUI_PRESS.justPressed()) {
|
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||||
button.playDownSound(Minecraft.getInstance().getSoundManager());
|
button.playDownSound(Minecraft.getInstance().getSoundManager());
|
||||||
button.onPress();
|
button.onPress();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||||
|
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.CreativeModeInventoryScreenAccessor;
|
import dev.isxander.controlify.mixins.feature.screenop.vanilla.CreativeModeInventoryScreenAccessor;
|
||||||
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
||||||
import net.minecraft.world.item.CreativeModeTabs;
|
import net.minecraft.world.item.CreativeModeTabs;
|
||||||
@ -12,7 +12,7 @@ public class CreativeModeInventoryScreenProcessor extends ScreenProcessor<Creati
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleVMouseNavigation(Controller controller) {
|
protected void handleVMouseNavigation(Controller<?, ?> controller) {
|
||||||
var accessor = (CreativeModeInventoryScreenAccessor) screen;
|
var accessor = (CreativeModeInventoryScreenAccessor) screen;
|
||||||
|
|
||||||
if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
|
if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||||
|
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
|
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
|
||||||
import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList;
|
import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList;
|
||||||
@ -15,7 +15,7 @@ public class JoinMultiplayerScreenProcessor extends ScreenProcessor<JoinMultipla
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleButtons(Controller controller) {
|
protected void handleButtons(Controller<?, ?> controller) {
|
||||||
if (screen.getFocused() instanceof Button && controller.bindings().GUI_BACK.justPressed()) {
|
if (screen.getFocused() instanceof Button && controller.bindings().GUI_BACK.justPressed()) {
|
||||||
screen.setFocused(list);
|
screen.setFocused(list);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.OptionsSubScreenAccessor;
|
import dev.isxander.controlify.mixins.feature.screenop.vanilla.OptionsSubScreenAccessor;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ public class LanguageSelectionListComponentProcessor implements ComponentProcess
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
if (controller.bindings().GUI_PRESS.justPressed()) {
|
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||||
var minecraft = Minecraft.getInstance();
|
var minecraft = Minecraft.getInstance();
|
||||||
var languageManager = minecraft.getLanguageManager();
|
var languageManager = minecraft.getLanguageManager();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||||
|
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.SelectWorldScreenAccessor;
|
import dev.isxander.controlify.mixins.feature.screenop.vanilla.SelectWorldScreenAccessor;
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||||
@ -12,7 +12,7 @@ public class SelectWorldScreenProcessor extends ScreenProcessor<SelectWorldScree
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleButtons(Controller controller) {
|
protected void handleButtons(Controller<?, ?> controller) {
|
||||||
if (screen.getFocused() != null && screen.getFocused() instanceof Button) {
|
if (screen.getFocused() != null && screen.getFocused() instanceof Button) {
|
||||||
if (controller.bindings().GUI_BACK.justPressed()) {
|
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||||
screen.setFocused(((SelectWorldScreenAccessor) screen).getList());
|
screen.setFocused(((SelectWorldScreenAccessor) screen).getList());
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.JoinMultiplayerScreenAccessor;
|
import dev.isxander.controlify.mixins.feature.screenop.vanilla.JoinMultiplayerScreenAccessor;
|
||||||
|
|
||||||
public class ServerSelectionListEntryComponentProcessor implements ComponentProcessor {
|
public class ServerSelectionListEntryComponentProcessor implements ComponentProcessor {
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
if (controller.bindings().GUI_PRESS.justPressed()) {
|
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||||
screen.screen.setFocused(((JoinMultiplayerScreenAccessor) screen.screen).getSelectButton());
|
screen.screen.setFocused(((JoinMultiplayerScreenAccessor) screen.screen).getSelectButton());
|
||||||
return true;
|
return true;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
@ -25,21 +25,19 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller controller) {
|
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
if (!this.canChangeValueGetter.get()) return false;
|
if (!this.canChangeValueGetter.get()) return false;
|
||||||
|
|
||||||
var canSliderChange = ++lastSliderChange > SLIDER_CHANGE_DELAY;
|
var canSliderChange = ++lastSliderChange > SLIDER_CHANGE_DELAY;
|
||||||
|
|
||||||
var axes = controller.state().axes();
|
if (controller.bindings().GUI_NAVI_RIGHT.held()) {
|
||||||
var buttons = controller.state().buttons();
|
|
||||||
if (axes.leftStickX() > 0.5f || buttons.dpadRight()) {
|
|
||||||
if (canSliderChange) {
|
if (canSliderChange) {
|
||||||
component.keyPressed(GLFW.GLFW_KEY_RIGHT, 0, 0);
|
component.keyPressed(GLFW.GLFW_KEY_RIGHT, 0, 0);
|
||||||
lastSliderChange = 0;
|
lastSliderChange = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if (axes.leftStickX() < -0.5f || buttons.dpadLeft()) {
|
} else if (controller.bindings().GUI_NAVI_LEFT.held()) {
|
||||||
if (canSliderChange) {
|
if (canSliderChange) {
|
||||||
component.keyPressed(GLFW.GLFW_KEY_LEFT, 0, 0);
|
component.keyPressed(GLFW.GLFW_KEY_LEFT, 0, 0);
|
||||||
lastSliderChange = 0;
|
lastSliderChange = 0;
|
||||||
@ -52,7 +50,7 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
if (!this.canChangeValueGetter.get()) return false;
|
if (!this.canChangeValueGetter.get()) return false;
|
||||||
|
|
||||||
if (controller.bindings().GUI_BACK.justPressed()) {
|
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||||
@ -64,7 +62,7 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFocusGained(ScreenProcessor<?> screen, Controller controller) {
|
public void onFocusGained(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
System.out.println("navigated!");
|
System.out.println("navigated!");
|
||||||
this.canChangeValueSetter.accept(false);
|
this.canChangeValueSetter.accept(false);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.SelectWorldScreenAccessor;
|
import dev.isxander.controlify.mixins.feature.screenop.vanilla.SelectWorldScreenAccessor;
|
||||||
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||||
|
|
||||||
public class WorldListEntryComponentProcessor implements ComponentProcessor {
|
public class WorldListEntryComponentProcessor implements ComponentProcessor {
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
if (controller.bindings().GUI_PRESS.justPressed()) {
|
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||||
var selectWorldScreen = (SelectWorldScreen) screen.screen;
|
var selectWorldScreen = (SelectWorldScreen) screen.screen;
|
||||||
selectWorldScreen.setFocused(((SelectWorldScreenAccessor) selectWorldScreen).getSelectButton());
|
selectWorldScreen.setFocused(((SelectWorldScreenAccessor) selectWorldScreen).getSelectButton());
|
||||||
|
@ -1,33 +1,44 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.yacl;
|
package dev.isxander.controlify.screenop.compat.yacl;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import dev.isxander.yacl.gui.controllers.cycling.CyclingControllerElement;
|
import dev.isxander.yacl.gui.controllers.cycling.CyclingControllerElement;
|
||||||
|
|
||||||
public class CyclingControllerElementComponentProcessor implements ComponentProcessor {
|
public class CyclingControllerElementComponentProcessor implements ComponentProcessor {
|
||||||
private final CyclingControllerElement cyclingController;
|
private final CyclingControllerElement cyclingController;
|
||||||
private int lastInput = 0;
|
private int lastInput = 0;
|
||||||
|
private boolean prevLeft, prevRight;
|
||||||
|
|
||||||
|
|
||||||
public CyclingControllerElementComponentProcessor(CyclingControllerElement cyclingController) {
|
public CyclingControllerElementComponentProcessor(CyclingControllerElement cyclingController) {
|
||||||
this.cyclingController = cyclingController;
|
this.cyclingController = cyclingController;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller controller) {
|
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
var rightStickX = controller.state().axes().rightStickX();
|
var left = controller.bindings().GUI_NAVI_LEFT.held();
|
||||||
var rightStickY = controller.state().axes().rightStickY();
|
var right = controller.bindings().GUI_NAVI_RIGHT.held();
|
||||||
var input = Math.abs(rightStickX) > Math.abs(rightStickY) ? rightStickX : rightStickY;
|
|
||||||
|
|
||||||
var inputI = Math.abs(input) < controller.config().buttonActivationThreshold ? 0 : input > 0 ? 1 : -1;
|
if (left && !prevLeft) {
|
||||||
if (inputI != 0 && inputI != lastInput) {
|
prevLeft = true;
|
||||||
cyclingController.cycleValue(input > 0 ? 1 : -1);
|
prevRight = false;
|
||||||
|
|
||||||
|
cyclingController.cycleValue(-1);
|
||||||
|
|
||||||
lastInput = inputI;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
} else if (right && !prevRight) {
|
||||||
lastInput = inputI;
|
prevLeft = false;
|
||||||
|
prevRight = true;
|
||||||
|
|
||||||
return false;
|
cyclingController.cycleValue(1);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
prevLeft = left;
|
||||||
|
prevRight = right;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,37 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.yacl;
|
package dev.isxander.controlify.screenop.compat.yacl;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||||
import dev.isxander.controlify.controller.Controller;
|
|
||||||
import dev.isxander.yacl.gui.controllers.slider.SliderControllerElement;
|
import dev.isxander.yacl.gui.controllers.slider.SliderControllerElement;
|
||||||
|
|
||||||
public class SliderControllerElementComponentProcessor implements ComponentProcessor {
|
public class SliderControllerElementComponentProcessor implements ComponentProcessor {
|
||||||
private final SliderControllerElement slider;
|
private final SliderControllerElement slider;
|
||||||
private int ticksSinceIncrement = 0;
|
private int ticksSinceIncrement = 0;
|
||||||
private int lastInput = 0;
|
private boolean prevLeft, prevRight;
|
||||||
|
|
||||||
public SliderControllerElementComponentProcessor(SliderControllerElement element) {
|
public SliderControllerElementComponentProcessor(SliderControllerElement element) {
|
||||||
this.slider = element;
|
this.slider = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||||
ticksSinceIncrement++;
|
ticksSinceIncrement++;
|
||||||
|
|
||||||
var rightStickX = controller.state().axes().rightStickX();
|
var left = controller.bindings().GUI_NAVI_LEFT.held();
|
||||||
var rightStickY = controller.state().axes().rightStickY();
|
var right = controller.bindings().GUI_NAVI_RIGHT.held();
|
||||||
var input = Math.abs(rightStickX) > Math.abs(rightStickY) ? rightStickX : rightStickY;
|
|
||||||
|
|
||||||
if (Math.abs(input) > controller.config().buttonActivationThreshold) {
|
if (left || right) {
|
||||||
if (ticksSinceIncrement > controller.config().screenRepeatNavigationDelay || input != lastInput) {
|
if (ticksSinceIncrement > controller.config().screenRepeatNavigationDelay || left != prevLeft || right != prevRight) {
|
||||||
slider.incrementValue(lastInput = input > 0 ? 1 : -1);
|
slider.incrementValue(left ? -1 : 1);
|
||||||
ticksSinceIncrement = 0;
|
ticksSinceIncrement = 0;
|
||||||
|
prevLeft = left;
|
||||||
|
prevRight = right;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.lastInput = 0;
|
this.prevLeft = false;
|
||||||
|
this.prevRight = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package dev.isxander.controlify.screenop.compat.yacl;
|
package dev.isxander.controlify.screenop.compat.yacl;
|
||||||
|
|
||||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||||
import dev.isxander.yacl.gui.YACLScreen;
|
import dev.isxander.yacl.gui.YACLScreen;
|
||||||
|
|
||||||
public class YACLScreenProcessor extends ScreenProcessor<YACLScreen> {
|
public class YACLScreenProcessor extends ScreenProcessor<YACLScreen> {
|
||||||
@ -10,7 +10,7 @@ public class YACLScreenProcessor extends ScreenProcessor<YACLScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleComponentNavigation(Controller controller) {
|
protected void handleComponentNavigation(Controller<?, ?> controller) {
|
||||||
if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
|
if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
|
||||||
var idx = screen.getCurrentCategoryIdx() + 1;
|
var idx = screen.getCurrentCategoryIdx() + 1;
|
||||||
if (idx >= screen.config.categories().size()) idx = 0;
|
if (idx >= screen.config.categories().size()) idx = 0;
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package dev.isxander.controlify.utils;
|
||||||
|
|
||||||
|
public class ControllerUtils {
|
||||||
|
public static float deadzone(float value, float deadzone) {
|
||||||
|
return (value - Math.copySign(Math.min(deadzone, Math.abs(value)), value)) / (1 - deadzone);
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,8 @@ import com.mojang.blaze3d.vertex.PoseStack;
|
|||||||
import com.mojang.datafixers.util.Pair;
|
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.screenop.ScreenProcessorProvider;
|
|
||||||
import dev.isxander.controlify.controller.Controller;
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
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;
|
||||||
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||||
@ -52,7 +52,7 @@ public class VirtualMouseHandler {
|
|||||||
ControlifyEvents.INPUT_MODE_CHANGED.register(this::onInputModeChanged);
|
ControlifyEvents.INPUT_MODE_CHANGED.register(this::onInputModeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleControllerInput(Controller controller) {
|
public void handleControllerInput(Controller<?, ?> controller) {
|
||||||
if (controller.bindings().VMOUSE_TOGGLE.justPressed()) {
|
if (controller.bindings().VMOUSE_TOGGLE.justPressed()) {
|
||||||
toggleVirtualMouse();
|
toggleVirtualMouse();
|
||||||
}
|
}
|
||||||
@ -61,10 +61,10 @@ public class VirtualMouseHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var leftStickX = controller.state().axes().leftStickX();
|
var impulseY = controller.bindings().VMOUSE_MOVE_DOWN.state() - controller.bindings().VMOUSE_MOVE_UP.state();
|
||||||
var leftStickY = controller.state().axes().leftStickY();
|
var impulseX = controller.bindings().VMOUSE_MOVE_RIGHT.state() - controller.bindings().VMOUSE_MOVE_LEFT.state();
|
||||||
var prevLeftStickX = controller.prevState().axes().leftStickX();
|
var prevImpulseY = controller.bindings().VMOUSE_MOVE_DOWN.prevState() - controller.bindings().VMOUSE_MOVE_UP.prevState();
|
||||||
var prevLeftStickY = controller.prevState().axes().leftStickY();
|
var prevImpulseX = controller.bindings().VMOUSE_MOVE_RIGHT.prevState() - controller.bindings().VMOUSE_MOVE_LEFT.prevState();
|
||||||
|
|
||||||
if (minecraft.screen != null && minecraft.screen instanceof ISnapBehaviour snapBehaviour) {
|
if (minecraft.screen != null && minecraft.screen instanceof ISnapBehaviour snapBehaviour) {
|
||||||
snapPoints = snapBehaviour.getSnapPoints();
|
snapPoints = snapBehaviour.getSnapPoints();
|
||||||
@ -73,8 +73,8 @@ public class VirtualMouseHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if just released stick, snap to nearest snap point
|
// if just released stick, snap to nearest snap point
|
||||||
if (leftStickX == 0 && leftStickY == 0) {
|
if (impulseX == 0 && impulseY == 0) {
|
||||||
if ((prevLeftStickX != 0 || prevLeftStickY != 0))
|
if ((prevImpulseX != 0 || prevImpulseY != 0))
|
||||||
snapToClosestPoint();
|
snapToClosestPoint();
|
||||||
} else {
|
} else {
|
||||||
snapping = false;
|
snapping = false;
|
||||||
@ -84,8 +84,8 @@ public class VirtualMouseHandler {
|
|||||||
|
|
||||||
// quadratic function to make small movements smaller
|
// quadratic function to make small movements smaller
|
||||||
// abs to keep sign
|
// abs to keep sign
|
||||||
targetX += leftStickX * Mth.abs(leftStickX) * 20f * sensitivity;
|
targetX += impulseX * Mth.abs(impulseX) * 20f * sensitivity;
|
||||||
targetY += leftStickY * Mth.abs(leftStickY) * 20f * sensitivity;
|
targetY += impulseY * Mth.abs(impulseY) * 20f * sensitivity;
|
||||||
|
|
||||||
targetX = Mth.clamp(targetX, 0, minecraft.getWindow().getWidth());
|
targetX = Mth.clamp(targetX, 0, minecraft.getWindow().getWidth());
|
||||||
targetY = Mth.clamp(targetY, 0, minecraft.getWindow().getHeight());
|
targetY = Mth.clamp(targetY, 0, minecraft.getWindow().getHeight());
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
// 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!
|
||||||
{
|
[
|
||||||
"xbox_one": {
|
{
|
||||||
|
"name": "Xbox One Controller",
|
||||||
|
"identifier": "xbox_one",
|
||||||
|
|
||||||
"vendor": 1118, // 0x45e
|
"vendor": 1118, // 0x45e
|
||||||
"product": [
|
"product": [
|
||||||
767, // 0x2ff
|
767, // 0x2ff
|
||||||
@ -17,7 +20,10 @@
|
|||||||
648 // 0x288
|
648 // 0x288
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dualshock4": {
|
{
|
||||||
|
"name": "Dualshock 4 Controller",
|
||||||
|
"identifier": "dualshock4",
|
||||||
|
|
||||||
"vendor": 1356, // 0x54c
|
"vendor": 1356, // 0x54c
|
||||||
"product": [
|
"product": [
|
||||||
1476, // 0x5c4
|
1476, // 0x5c4
|
||||||
@ -25,10 +31,13 @@
|
|||||||
2976 // 0xba0
|
2976 // 0xba0
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"steam_deck": {
|
{
|
||||||
|
"name": "Steam Deck",
|
||||||
|
"identifier": "steam_deck",
|
||||||
|
|
||||||
"vendor": 10462, // 0x28de
|
"vendor": 10462, // 0x28de
|
||||||
"product": [
|
"product": [
|
||||||
4613 // 0x1205
|
4613 // 0x1205
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
]
|
@ -34,6 +34,8 @@
|
|||||||
"controlify.gui.left_stick_deadzone.tooltip": "How far the left joystick needs to be pushed before registering input.",
|
"controlify.gui.left_stick_deadzone.tooltip": "How far the left joystick needs to be pushed before registering input.",
|
||||||
"controlify.gui.right_stick_deadzone": "Right Stick Deadzone",
|
"controlify.gui.right_stick_deadzone": "Right Stick Deadzone",
|
||||||
"controlify.gui.right_stick_deadzone.tooltip": "How far the right joystick needs to be pushed before registering input.",
|
"controlify.gui.right_stick_deadzone.tooltip": "How far the right joystick needs to be pushed before registering input.",
|
||||||
|
"controlify.gui.joystick_axis_deadzone": "%s Deadzone",
|
||||||
|
"controlify.gui.joystick_axis_deadzone.tooltip": "How far '%s' axis needs to be pushed before registering input.",
|
||||||
"controlify.gui.stickdrift_warning": "Warning: Setting this too low will cause stickdrift! This is where the internals of your controller become mis-calibrated and register small amounts of input when there shouldn't be.",
|
"controlify.gui.stickdrift_warning": "Warning: Setting this too low will cause stickdrift! This is where the internals of your controller become mis-calibrated and register small amounts of input when there shouldn't be.",
|
||||||
"controlify.gui.auto_calibration": "Automatic Deadzone Calibration",
|
"controlify.gui.auto_calibration": "Automatic Deadzone Calibration",
|
||||||
"controlify.gui.auto_calibration.tooltip": "Automatically calibrate the deadzone of your controller.",
|
"controlify.gui.auto_calibration.tooltip": "Automatically calibrate the deadzone of your controller.",
|
||||||
@ -73,6 +75,10 @@
|
|||||||
"controlify.binding.controlify.walk_backward": "Walk Backward",
|
"controlify.binding.controlify.walk_backward": "Walk Backward",
|
||||||
"controlify.binding.controlify.strafe_left": "Strafe Left",
|
"controlify.binding.controlify.strafe_left": "Strafe Left",
|
||||||
"controlify.binding.controlify.strafe_right": "Strafe Right",
|
"controlify.binding.controlify.strafe_right": "Strafe Right",
|
||||||
|
"controlify.binding.controlify.look_up": "Look Up",
|
||||||
|
"controlify.binding.controlify.look_down": "Look Down",
|
||||||
|
"controlify.binding.controlify.look_left": "Look Left",
|
||||||
|
"controlify.binding.controlify.look_right": "Look Right",
|
||||||
"controlify.binding.controlify.jump": "Jump",
|
"controlify.binding.controlify.jump": "Jump",
|
||||||
"controlify.binding.controlify.sneak": "Sneak",
|
"controlify.binding.controlify.sneak": "Sneak",
|
||||||
"controlify.binding.controlify.attack": "Attack",
|
"controlify.binding.controlify.attack": "Attack",
|
||||||
@ -93,6 +99,10 @@
|
|||||||
"controlify.binding.controlify.pick_block": "Pick Block",
|
"controlify.binding.controlify.pick_block": "Pick Block",
|
||||||
"controlify.binding.controlify.toggle_hud_visibility": "Toggle HUD Visibility",
|
"controlify.binding.controlify.toggle_hud_visibility": "Toggle HUD Visibility",
|
||||||
"controlify.binding.controlify.show_player_list": "Show Player List",
|
"controlify.binding.controlify.show_player_list": "Show Player List",
|
||||||
|
"controlify.binding.controlify.vmouse_move_up": "VMouse Move Up",
|
||||||
|
"controlify.binding.controlify.vmouse_move_down": "VMouse Move Down",
|
||||||
|
"controlify.binding.controlify.vmouse_move_left": "VMouse Move Left",
|
||||||
|
"controlify.binding.controlify.vmouse_move_right": "VMouse Move Right",
|
||||||
"controlify.binding.controlify.vmouse_lclick": "VMouse LClick",
|
"controlify.binding.controlify.vmouse_lclick": "VMouse LClick",
|
||||||
"controlify.binding.controlify.vmouse_rclick": "VMouse RClick",
|
"controlify.binding.controlify.vmouse_rclick": "VMouse RClick",
|
||||||
"controlify.binding.controlify.vmouse_shift_click": "VMouse Shift Click",
|
"controlify.binding.controlify.vmouse_shift_click": "VMouse Shift Click",
|
||||||
@ -101,6 +111,12 @@
|
|||||||
"controlify.binding.controlify.vmouse_escape": "VMouse Key Escape",
|
"controlify.binding.controlify.vmouse_escape": "VMouse Key Escape",
|
||||||
"controlify.binding.controlify.vmouse_shift": "VMouse Key Shift",
|
"controlify.binding.controlify.vmouse_shift": "VMouse Key Shift",
|
||||||
"controlify.binding.controlify.vmouse_toggle": "Toggle Virtual Mouse",
|
"controlify.binding.controlify.vmouse_toggle": "Toggle Virtual Mouse",
|
||||||
|
"controlify.binding.controlify.gui_navi_down": "GUI Navi Down",
|
||||||
|
"controlify.binding.controlify.gui_navi_up": "GUI Navi Up",
|
||||||
|
"controlify.binding.controlify.gui_navi_left": "GUI Navi Left",
|
||||||
|
"controlify.binding.controlify.gui_navi_right": "GUI Navi Right",
|
||||||
|
"controlify.binding.controlify.yacl_cycle_opt_forward": "YACL Cycle Option Forward",
|
||||||
|
"controlify.binding.controlify.yacl_cycle_opt_backward": "YACL Cycle Option Backward",
|
||||||
|
|
||||||
"controlify.guide.inventory": "Open Inventory",
|
"controlify.guide.inventory": "Open Inventory",
|
||||||
"controlify.guide.swim_up": "Swim Up",
|
"controlify.guide.swim_up": "Swim Up",
|
||||||
@ -124,6 +140,42 @@
|
|||||||
"controlify.guide.interact": "Interact",
|
"controlify.guide.interact": "Interact",
|
||||||
"controlify.guide.pick_block": "Pick Block",
|
"controlify.guide.pick_block": "Pick Block",
|
||||||
|
|
||||||
|
"controlify.joystick_mapping.unmapped.axis": "Axis #%s",
|
||||||
|
"controlify.joystick_mapping.unmapped.button": "Button #%s",
|
||||||
|
"controlify.joystick_mapping.unmapped.hat": "Hat #%s",
|
||||||
|
"controlify.joystick_mapping.unmapped.axis_direction.negative": "(Negative)",
|
||||||
|
"controlify.joystick_mapping.unmapped.axis_direction.positive": "(Positive)",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.left_stick": "Left Stick",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.left_stick.left": "Left",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.left_stick.right": "Right",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.left_stick.up": "Up",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.left_stick.down": "Down",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.right_stick": "Right Stick",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.right_stick.left": "Left",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.right_stick.right": "Right",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.right_stick.up": "Up",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.right_stick.down": "Down",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.left_trigger": "Left Trigger",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.left_trigger.up": "Up",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.left_trigger.down": "Down",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.right_trigger": "Right Trigger",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.right_trigger.up": "Up",
|
||||||
|
"controlify.joystick_mapping.xbox_one.axis.right_trigger.down": "Down",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.a": "A",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.b": "B",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.x": "X",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.y": "Y",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.left_bumper": "Left Bumper",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.right_bumper": "Right Bumper",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.left_stick": "Left Stick Press",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.right_stick": "Right Stick Press",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.back": "Back",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.start": "Start",
|
||||||
|
"controlify.joystick_mapping.xbox_one.button.guide": "Guide",
|
||||||
|
"controlify.joystick_mapping.xbox_one.hat.dpad": "D-Pad",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"controlify.calibration.title": "Controller Calibration for '%s'",
|
"controlify.calibration.title": "Controller Calibration for '%s'",
|
||||||
"controlify.calibration.info": "This process will optimize settings for your controller to prevent stick drift. Stick drift happens in your controller thumbsticks and outputs slightly wrong values when you aren't touching them at all. Deadzones are used to prevent this.\n\nThis will only take a few seconds.",
|
"controlify.calibration.info": "This process will optimize settings for your controller to prevent stick drift. Stick drift happens in your controller thumbsticks and outputs slightly wrong values when you aren't touching them at all. Deadzones are used to prevent this.\n\nThis will only take a few seconds.",
|
||||||
"controlify.calibration.wait": "Please do not touch your controller thumbsticks until the progress bar is complete. This process will only take a few seconds.",
|
"controlify.calibration.wait": "Please do not touch your controller thumbsticks until the progress bar is complete. This process will only take a few seconds.",
|
||||||
@ -138,5 +190,10 @@
|
|||||||
"controlify.beta.button": "Open Issue Tracker...",
|
"controlify.beta.button": "Open Issue Tracker...",
|
||||||
|
|
||||||
"controlify.error.hid": "Controller Detection Disabled",
|
"controlify.error.hid": "Controller Detection Disabled",
|
||||||
"controlify.error.hid.desc": "Controlify could not start the controller detection system used to identify and distinguish between multiple controllers. This means controller config will not be able to be saved between play sessions. This is likely due to a system fault and you should check logs for further information."
|
"controlify.error.hid.desc": "Controlify could not start the controller detection system used to identify and distinguish between multiple controllers. This means controller config will not be able to be saved between play sessions. This is likely due to a system fault and you should check logs for further information.",
|
||||||
|
|
||||||
|
"controlify.hat_state.up": "Up",
|
||||||
|
"controlify.hat_state.down": "Down",
|
||||||
|
"controlify.hat_state.left": "Left",
|
||||||
|
"controlify.hat_state.right": "Right"
|
||||||
}
|
}
|
||||||
|
92
src/main/resources/assets/controlify/mappings/xbox_one.json
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"axes": [
|
||||||
|
{
|
||||||
|
"ids": [0, 1],
|
||||||
|
"identifier": "left_stick",
|
||||||
|
"deadzone": true,
|
||||||
|
"rest": 0.0,
|
||||||
|
"axis_names": [
|
||||||
|
["right", "left"],
|
||||||
|
["down", "up"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ids": [2, 3],
|
||||||
|
"identifier": "right_stick",
|
||||||
|
"deadzone": true,
|
||||||
|
"axis_names": [
|
||||||
|
["right", "left"],
|
||||||
|
["down", "up"]
|
||||||
|
],
|
||||||
|
"rest": 0.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ids": [4],
|
||||||
|
"identifier": "left_trigger",
|
||||||
|
"deadzone": false,
|
||||||
|
"rest": 0.0,
|
||||||
|
"range": [0.0, 1.0],
|
||||||
|
"axis_names": [
|
||||||
|
["down", "up"]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ids": [5],
|
||||||
|
"identifier": "right_trigger",
|
||||||
|
"deadzone": false,
|
||||||
|
"rest": 0.0,
|
||||||
|
"range": [0.0, 1.0],
|
||||||
|
"axis_names": [
|
||||||
|
["down", "up"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"button": 0,
|
||||||
|
"name": "a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 1,
|
||||||
|
"name": "b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 2,
|
||||||
|
"name": "x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 3,
|
||||||
|
"name": "y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 4,
|
||||||
|
"name": "left_bumper"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 5,
|
||||||
|
"name": "right_bumper"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 6,
|
||||||
|
"name": "back"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 7,
|
||||||
|
"name": "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 8,
|
||||||
|
"name": "left_stick"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"button": 9,
|
||||||
|
"name": "right_stick"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hats": [
|
||||||
|
{
|
||||||
|
"hat": 0,
|
||||||
|
"name": "dpad"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 241 B |
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 205 B |
Before Width: | Height: | Size: 289 B After Width: | Height: | Size: 289 B |
Before Width: | Height: | Size: 298 B After Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
Before Width: | Height: | Size: 289 B After Width: | Height: | Size: 289 B |
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
Before Width: | Height: | Size: 281 B After Width: | Height: | Size: 281 B |
Before Width: | Height: | Size: 298 B After Width: | Height: | Size: 298 B |
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 296 B |
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 256 B After Width: | Height: | Size: 256 B |