forked from Clones/Controlify
joystick support
This commit is contained in:
@ -1,17 +1,18 @@
|
||||
package dev.isxander.controlify;
|
||||
|
||||
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.screenop.ScreenProcessorProvider;
|
||||
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.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.ingame.guide.InGameButtonGuide;
|
||||
import dev.isxander.controlify.ingame.InGameInputHandler;
|
||||
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||
import dev.isxander.controlify.virtualmouse.VirtualMouseHandler;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.toasts.SystemToast;
|
||||
@ -27,7 +28,7 @@ public class Controlify {
|
||||
public static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static Controlify instance = null;
|
||||
|
||||
private Controller currentController;
|
||||
private Controller<?, ?> currentController;
|
||||
private InGameInputHandler inGameInputHandler;
|
||||
public InGameButtonGuide inGameButtonGuide;
|
||||
private VirtualMouseHandler virtualMouseHandler;
|
||||
@ -36,12 +37,15 @@ public class Controlify {
|
||||
|
||||
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();
|
||||
|
||||
inGameInputHandler = new InGameInputHandler(Controller.DUMMY); // initialize with dummy controller before connection in case of no controllers
|
||||
config().load();
|
||||
|
||||
controllerHIDService = new ControllerHIDService();
|
||||
|
||||
// find already connected controllers
|
||||
@ -49,7 +53,7 @@ public class Controlify {
|
||||
if (GLFW.glfwJoystickPresent(i)) {
|
||||
int jid = i;
|
||||
controllerHIDService.awaitNextController(device -> {
|
||||
setCurrentController(Controller.create(jid, device));
|
||||
setCurrentController(Controller.createOrGet(jid, device));
|
||||
LOGGER.info("Controller found: " + currentController.name());
|
||||
|
||||
if (!config().loadOrCreateControllerData(currentController)) {
|
||||
@ -61,13 +65,11 @@ public class Controlify {
|
||||
|
||||
controllerHIDService.start();
|
||||
|
||||
config().load();
|
||||
|
||||
// listen for new controllers
|
||||
GLFW.glfwSetJoystickCallback((jid, event) -> {
|
||||
if (event == GLFW.GLFW_CONNECTED) {
|
||||
controllerHIDService.awaitNextController(device -> {
|
||||
setCurrentController(Controller.create(jid, device));
|
||||
setCurrentController(Controller.createOrGet(jid, device));
|
||||
LOGGER.info("Controller connected: " + currentController.name());
|
||||
this.setCurrentInputMode(InputMode.CONTROLLER);
|
||||
|
||||
@ -82,11 +84,10 @@ public class Controlify {
|
||||
Component.translatable("controlify.toast.controller_connected.description", currentController.name())
|
||||
));
|
||||
});
|
||||
|
||||
} else if (event == GLFW.GLFW_DISCONNECTED) {
|
||||
var controller = Controller.CONTROLLERS.remove(jid);
|
||||
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());
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
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();
|
||||
}
|
||||
|
||||
@ -154,11 +158,11 @@ public class Controlify {
|
||||
return config;
|
||||
}
|
||||
|
||||
public Controller currentController() {
|
||||
public Controller<?, ?> currentController() {
|
||||
return currentController;
|
||||
}
|
||||
|
||||
public void setCurrentController(Controller controller) {
|
||||
public void setCurrentController(Controller<?, ?> controller) {
|
||||
if (this.currentController == controller) return;
|
||||
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;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BindingSupplier {
|
||||
ControllerBinding get(Controller controller);
|
||||
public interface BindingSupplier<T extends ControllerState> {
|
||||
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.ControllerState;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.locale.Language;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@ -13,17 +14,17 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
public class ControllerBinding {
|
||||
private final Controller controller;
|
||||
private IBind bind;
|
||||
private final IBind defaultBind;
|
||||
public class ControllerBinding<T extends ControllerState> {
|
||||
private final Controller<T, ?> controller;
|
||||
private IBind<T> bind;
|
||||
private final IBind<T> defaultBind;
|
||||
private final ResourceLocation id;
|
||||
private final Component name, description;
|
||||
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.bind = this.defaultBind = defaultBind;
|
||||
this.id = id;
|
||||
@ -33,22 +34,38 @@ public class ControllerBinding {
|
||||
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);
|
||||
}
|
||||
|
||||
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() {
|
||||
return bind.held(controller.state(), controller);
|
||||
}
|
||||
|
||||
public boolean prevHeld() {
|
||||
return bind.held(controller.prevState(), controller);
|
||||
}
|
||||
|
||||
public boolean justPressed() {
|
||||
if (hasBindPressed(this)) return false;
|
||||
|
||||
if (held() && !bind.held(controller.prevState(), controller)) {
|
||||
if (held() && !prevHeld()) {
|
||||
addPressedBind(this);
|
||||
return true;
|
||||
} else {
|
||||
@ -59,7 +76,7 @@ public class ControllerBinding {
|
||||
public boolean justReleased() {
|
||||
if (hasBindPressed(this)) return false;
|
||||
|
||||
if (!held() && bind.held(controller.prevState(), controller)) {
|
||||
if (!held() && prevHeld()) {
|
||||
addPressedBind(this);
|
||||
return true;
|
||||
} else {
|
||||
@ -67,15 +84,15 @@ public class ControllerBinding {
|
||||
}
|
||||
}
|
||||
|
||||
public IBind currentBind() {
|
||||
public IBind<T> currentBind() {
|
||||
return bind;
|
||||
}
|
||||
|
||||
public void setCurrentBind(IBind bind) {
|
||||
public void setCurrentBind(IBind<T> bind) {
|
||||
this.bind = bind;
|
||||
}
|
||||
|
||||
public IBind defaultBind() {
|
||||
public IBind<T> defaultBind() {
|
||||
return defaultBind;
|
||||
}
|
||||
|
||||
@ -97,27 +114,23 @@ public class ControllerBinding {
|
||||
|
||||
// FIXME: very hack solution please remove me
|
||||
|
||||
public static void clearPressedBinds(Controller controller) {
|
||||
public static void clearPressedBinds(Controller<?, ?> controller) {
|
||||
if (pressedBinds.containsKey(controller)) {
|
||||
pressedBinds.get(controller).clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasBindPressed(ControllerBinding binding) {
|
||||
private static boolean hasBindPressed(ControllerBinding<?> binding) {
|
||||
var pressed = pressedBinds.getOrDefault(binding.controller, Set.of());
|
||||
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));
|
||||
}
|
||||
|
||||
private static Set<Bind> getBinds(IBind bind) {
|
||||
if (bind instanceof CompoundBind compoundBind) {
|
||||
return compoundBind.binds();
|
||||
} else {
|
||||
return Set.of((Bind) bind);
|
||||
}
|
||||
private static Set<IBind<?>> getBinds(IBind<?> bind) {
|
||||
return Set.of(bind);
|
||||
}
|
||||
|
||||
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.InputMode;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.controller.ControllerState;
|
||||
import dev.isxander.controlify.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.mixins.feature.bind.KeyMappingAccessor;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
@ -12,9 +13,10 @@ import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class ControllerBindings {
|
||||
public final ControllerBinding
|
||||
public class ControllerBindings<T extends ControllerState> {
|
||||
public final ControllerBinding<T>
|
||||
WALK_FORWARD, WALK_BACKWARD, WALK_LEFT, WALK_RIGHT,
|
||||
LOOK_UP, LOOK_DOWN, LOOK_LEFT, LOOK_RIGHT,
|
||||
JUMP, SNEAK,
|
||||
ATTACK, USE,
|
||||
SPRINT,
|
||||
@ -27,51 +29,71 @@ public class ControllerBindings {
|
||||
OPEN_CHAT,
|
||||
GUI_PRESS, GUI_BACK,
|
||||
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,
|
||||
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;
|
||||
var options = Minecraft.getInstance().options;
|
||||
|
||||
register(WALK_FORWARD = new ControllerBinding(controller, Bind.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_LEFT = new ControllerBinding(controller, Bind.LEFT_STICK_LEFT, new ResourceLocation("controlify", "strafe_left")));
|
||||
register(WALK_RIGHT = new ControllerBinding(controller, Bind.LEFT_STICK_RIGHT, new ResourceLocation("controlify", "strafe_right")));
|
||||
register(JUMP = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "jump"), options.keyJump, () -> false));
|
||||
register(SNEAK = new ControllerBinding(controller, Bind.RIGHT_STICK_PRESS, new ResourceLocation("controlify", "sneak"), options.keyShift, () -> controller.config().toggleSneak));
|
||||
register(ATTACK = new ControllerBinding(controller, Bind.RIGHT_TRIGGER, new ResourceLocation("controlify", "attack"), options.keyAttack, () -> false));
|
||||
register(USE = new ControllerBinding(controller, Bind.LEFT_TRIGGER, new ResourceLocation("controlify", "use"), options.keyUse, () -> false));
|
||||
register(SPRINT = new ControllerBinding(controller, Bind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "sprint"), options.keySprint, () -> controller.config().toggleSprint));
|
||||
register(DROP = new ControllerBinding(controller, Bind.DPAD_DOWN, new ResourceLocation("controlify", "drop"), options.keyDrop, () -> false));
|
||||
register(NEXT_SLOT = new ControllerBinding(controller, Bind.RIGHT_BUMPER, new ResourceLocation("controlify", "next_slot")));
|
||||
register(PREV_SLOT = new ControllerBinding(controller, Bind.LEFT_BUMPER, new ResourceLocation("controlify", "prev_slot")));
|
||||
register(PAUSE = new ControllerBinding(controller, Bind.START, new ResourceLocation("controlify", "pause")));
|
||||
register(INVENTORY = new ControllerBinding(controller, Bind.Y_BUTTON, new ResourceLocation("controlify", "inventory"), options.keyInventory, () -> false));
|
||||
register(CHANGE_PERSPECTIVE = new ControllerBinding(controller, Bind.BACK, new ResourceLocation("controlify", "change_perspective"), options.keyTogglePerspective, () -> false));
|
||||
register(SWAP_HANDS = new ControllerBinding(controller, Bind.X_BUTTON, new ResourceLocation("controlify", "swap_hands"), options.keySwapOffhand, () -> false));
|
||||
register(OPEN_CHAT = new ControllerBinding(controller, Bind.DPAD_UP, new ResourceLocation("controlify", "open_chat"), options.keyChat, () -> false));
|
||||
register(GUI_PRESS = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "gui_press")));
|
||||
register(GUI_BACK = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "gui_back")));
|
||||
register(GUI_NEXT_TAB = new ControllerBinding(controller, Bind.RIGHT_BUMPER, new ResourceLocation("controlify", "gui_next_tab")));
|
||||
register(GUI_PREV_TAB = new ControllerBinding(controller, Bind.LEFT_BUMPER, new ResourceLocation("controlify", "gui_prev_tab")));
|
||||
register(PICK_BLOCK = new ControllerBinding(controller, Bind.DPAD_LEFT, new ResourceLocation("controlify", "pick_block"), options.keyPickItem, () -> false));
|
||||
register(TOGGLE_HUD_VISIBILITY = new ControllerBinding(controller, Bind.NONE, new ResourceLocation("controlify", "toggle_hud_visibility")));
|
||||
register(SHOW_PLAYER_LIST = new ControllerBinding(controller, Bind.DPAD_RIGHT, new ResourceLocation("controlify", "show_player_list"), options.keyPlayerList, () -> false));
|
||||
register(VMOUSE_LCLICK = new ControllerBinding(controller, Bind.A_BUTTON, new ResourceLocation("controlify", "vmouse_lclick")));
|
||||
register(VMOUSE_RCLICK = new ControllerBinding(controller, Bind.X_BUTTON, new ResourceLocation("controlify", "vmouse_rclick")));
|
||||
register(VMOUSE_SHIFT_CLICK = new ControllerBinding(controller, Bind.Y_BUTTON, new ResourceLocation("controlify", "vmouse_shift_click")));
|
||||
register(VMOUSE_SCROLL_UP = new ControllerBinding(controller, Bind.RIGHT_STICK_FORWARD, new ResourceLocation("controlify", "vmouse_scroll_up")));
|
||||
register(VMOUSE_SCROLL_DOWN = new ControllerBinding(controller, Bind.RIGHT_STICK_BACKWARD, new ResourceLocation("controlify", "vmouse_scroll_down")));
|
||||
register(VMOUSE_ESCAPE = new ControllerBinding(controller, Bind.B_BUTTON, new ResourceLocation("controlify", "vmouse_escape")));
|
||||
register(VMOUSE_SHIFT = new ControllerBinding(controller, Bind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "vmouse_shift")));
|
||||
register(VMOUSE_TOGGLE = new ControllerBinding(controller, Bind.BACK, new ResourceLocation("controlify", "vmouse_toggle")));
|
||||
register(WALK_FORWARD = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_FORWARD, new ResourceLocation("controlify", "walk_forward")));
|
||||
register(WALK_BACKWARD = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_BACKWARD, new ResourceLocation("controlify", "walk_backward")));
|
||||
register(WALK_LEFT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_LEFT, new ResourceLocation("controlify", "strafe_left")));
|
||||
register(WALK_RIGHT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_RIGHT, new ResourceLocation("controlify", "strafe_right")));
|
||||
register(LOOK_UP = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_FORWARD, new ResourceLocation("controlify", "look_up")));
|
||||
register(LOOK_DOWN = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_BACKWARD, new ResourceLocation("controlify", "look_down")));
|
||||
register(LOOK_LEFT = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_LEFT, new ResourceLocation("controlify", "look_left")));
|
||||
register(LOOK_RIGHT = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_RIGHT, new ResourceLocation("controlify", "look_right")));
|
||||
register(JUMP = new ControllerBinding<>(controller, GamepadBind.A_BUTTON, new ResourceLocation("controlify", "jump"), options.keyJump, () -> false));
|
||||
register(SNEAK = new ControllerBinding<>(controller, GamepadBind.RIGHT_STICK_PRESS, new ResourceLocation("controlify", "sneak"), options.keyShift, () -> controller.config().toggleSneak));
|
||||
register(ATTACK = new ControllerBinding<>(controller, GamepadBind.RIGHT_TRIGGER, new ResourceLocation("controlify", "attack"), options.keyAttack, () -> false));
|
||||
register(USE = new ControllerBinding<>(controller, GamepadBind.LEFT_TRIGGER, new ResourceLocation("controlify", "use"), options.keyUse, () -> false));
|
||||
register(SPRINT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_PRESS, new ResourceLocation("controlify", "sprint"), options.keySprint, () -> controller.config().toggleSprint));
|
||||
register(DROP = new ControllerBinding<>(controller, GamepadBind.DPAD_DOWN, new ResourceLocation("controlify", "drop"), options.keyDrop, () -> false));
|
||||
register(NEXT_SLOT = new ControllerBinding<>(controller, GamepadBind.RIGHT_BUMPER, new ResourceLocation("controlify", "next_slot")));
|
||||
register(PREV_SLOT = new ControllerBinding<>(controller, GamepadBind.LEFT_BUMPER, new ResourceLocation("controlify", "prev_slot")));
|
||||
register(PAUSE = new ControllerBinding<>(controller, GamepadBind.START, new ResourceLocation("controlify", "pause")));
|
||||
register(INVENTORY = new ControllerBinding<>(controller, GamepadBind.Y_BUTTON, new ResourceLocation("controlify", "inventory"), options.keyInventory, () -> false));
|
||||
register(CHANGE_PERSPECTIVE = new ControllerBinding<>(controller, GamepadBind.BACK, new ResourceLocation("controlify", "change_perspective"), options.keyTogglePerspective, () -> false));
|
||||
register(SWAP_HANDS = new ControllerBinding<>(controller, GamepadBind.X_BUTTON, new ResourceLocation("controlify", "swap_hands"), options.keySwapOffhand, () -> false));
|
||||
register(OPEN_CHAT = new ControllerBinding<>(controller, GamepadBind.DPAD_UP, new ResourceLocation("controlify", "open_chat"), options.keyChat, () -> false));
|
||||
register(GUI_PRESS = new ControllerBinding<>(controller, GamepadBind.A_BUTTON, new ResourceLocation("controlify", "gui_press")));
|
||||
register(GUI_BACK = new ControllerBinding<>(controller, GamepadBind.B_BUTTON, new ResourceLocation("controlify", "gui_back")));
|
||||
register(GUI_NEXT_TAB = new ControllerBinding<>(controller, GamepadBind.RIGHT_BUMPER, new ResourceLocation("controlify", "gui_next_tab")));
|
||||
register(GUI_PREV_TAB = new ControllerBinding<>(controller, GamepadBind.LEFT_BUMPER, new ResourceLocation("controlify", "gui_prev_tab")));
|
||||
register(PICK_BLOCK = new ControllerBinding<>(controller, GamepadBind.DPAD_LEFT, new ResourceLocation("controlify", "pick_block"), options.keyPickItem, () -> false));
|
||||
register(TOGGLE_HUD_VISIBILITY = new ControllerBinding<>(controller, new EmptyBind<>(), new ResourceLocation("controlify", "toggle_hud_visibility")));
|
||||
register(SHOW_PLAYER_LIST = new ControllerBinding<>(controller, GamepadBind.DPAD_RIGHT, new ResourceLocation("controlify", "show_player_list"), options.keyPlayerList, () -> false));
|
||||
register(VMOUSE_MOVE_UP = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_FORWARD, new ResourceLocation("controlify", "vmouse_move_up")));
|
||||
register(VMOUSE_MOVE_DOWN = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_BACKWARD, new ResourceLocation("controlify", "vmouse_move_down")));
|
||||
register(VMOUSE_MOVE_LEFT = new ControllerBinding<>(controller, GamepadBind.LEFT_STICK_LEFT, new ResourceLocation("controlify", "vmouse_move_left")));
|
||||
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);
|
||||
|
||||
@ -79,16 +101,16 @@ public class ControllerBindings {
|
||||
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);
|
||||
return controller -> controller.bindings().get(binding.id());
|
||||
}
|
||||
|
||||
public ControllerBinding get(ResourceLocation id) {
|
||||
public ControllerBinding<T> get(ResourceLocation id) {
|
||||
return registry.get(id);
|
||||
}
|
||||
|
||||
public Map<ResourceLocation, ControllerBinding> registry() {
|
||||
public Map<ResourceLocation, ControllerBinding<T>> registry() {
|
||||
return Collections.unmodifiableMap(registry);
|
||||
}
|
||||
|
||||
@ -102,13 +124,13 @@ public class ControllerBindings {
|
||||
|
||||
public void fromJson(JsonObject json) {
|
||||
for (var binding : registry().values()) {
|
||||
var bind = json.get(binding.id().toString());
|
||||
var bind = json.get(binding.id().toString()).getAsJsonObject();
|
||||
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;
|
||||
|
||||
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
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;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
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.ButtonRenderer;
|
||||
import dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.controller.*;
|
||||
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 {
|
||||
float state(ControllerState state, Controller controller);
|
||||
default boolean held(ControllerState state, Controller controller) {
|
||||
return state(state, controller) > controller.config().buttonActivationThreshold;
|
||||
public interface IBind<S extends ControllerState> {
|
||||
float state(S state);
|
||||
default boolean held(S state, Controller<S, ?> controller) {
|
||||
return state(state) > controller.config().buttonActivationThreshold;
|
||||
}
|
||||
|
||||
void draw(PoseStack matrices, int x, int centerY, Controller controller);
|
||||
ButtonRenderer.DrawSize drawSize();
|
||||
void draw(PoseStack matrices, int x, int centerY, Controller<S, ?> controller);
|
||||
DrawSize drawSize();
|
||||
|
||||
JsonElement toJson();
|
||||
JsonObject toJson();
|
||||
|
||||
static IBind fromJson(JsonElement json) {
|
||||
if (json.isJsonArray()) {
|
||||
return new CompoundBind(json.getAsJsonArray().asList().stream().map(element -> Bind.fromIdentifier(element.getAsString())).toArray(Bind[]::new));
|
||||
} else {
|
||||
return Bind.fromIdentifier(json.getAsString());
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T extends ControllerState> IBind<T> fromJson(JsonObject json, Controller<T, ?> controller) {
|
||||
var type = json.get("type").getAsString();
|
||||
if (type.equals(EmptyBind.BIND_ID))
|
||||
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) {
|
||||
if (binds.size() == 1) return binds.stream().findAny().orElseThrow();
|
||||
return new CompoundBind(binds.toArray(new Bind[0]));
|
||||
}
|
||||
static IBind create(Bind... binds) {
|
||||
if (binds.length == 1) return binds[0];
|
||||
return new CompoundBind(binds);
|
||||
Controlify.LOGGER.error("Could not parse bind for controller: " + controller.name());
|
||||
return new EmptyBind<>();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
private JsonObject generateControllerConfig(Controller controller) {
|
||||
private JsonObject generateControllerConfig(Controller<?, ?> controller) {
|
||||
JsonObject object = new JsonObject();
|
||||
|
||||
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();
|
||||
if (controllerData.has(uid)) {
|
||||
Controlify.LOGGER.info("Loading controller data for " + uid);
|
||||
applyControllerConfig(controller, controllerData.getAsJsonObject(uid));
|
||||
return true;
|
||||
} else {
|
||||
Controlify.LOGGER.info("New controller found, creating controller data for " + uid);
|
||||
save();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyControllerConfig(Controller controller, JsonObject object) {
|
||||
controller.setConfig(GSON.fromJson(object.getAsJsonObject("config"), Controller.ControllerConfig.class));
|
||||
private void applyControllerConfig(Controller<?, ?> controller, JsonObject object) {
|
||||
controller.setConfig(GSON, object.getAsJsonObject("config"));
|
||||
controller.bindings().fromJson(object.getAsJsonObject("bindings"));
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
package dev.isxander.controlify.config.gui;
|
||||
|
||||
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.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadState;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessorProvider;
|
||||
@ -16,20 +18,17 @@ import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
public class GamepadBindController implements Controller<IBind<GamepadState>> {
|
||||
private final Option<IBind<GamepadState>> option;
|
||||
private final GamepadController controller;
|
||||
|
||||
public class BindButtonController implements Controller<IBind> {
|
||||
private final Option<IBind> option;
|
||||
private final dev.isxander.controlify.controller.Controller controller;
|
||||
|
||||
public BindButtonController(Option<IBind> option, dev.isxander.controlify.controller.Controller controller) {
|
||||
public GamepadBindController(Option<IBind<GamepadState>> option, GamepadController controller) {
|
||||
this.option = option;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Option<IBind> option() {
|
||||
public Option<IBind<GamepadState>> option() {
|
||||
return this.option;
|
||||
}
|
||||
|
||||
@ -43,26 +42,18 @@ public class BindButtonController implements Controller<IBind> {
|
||||
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 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawValueText(PoseStack matrices, int mouseX, int mouseY, float delta) {
|
||||
if (awaitingControllerInput) {
|
||||
if (pressedBinds.isEmpty()) {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
@ -95,41 +86,31 @@ public class BindButtonController implements Controller<IBind> {
|
||||
}
|
||||
|
||||
@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) {
|
||||
return awaitingControllerInput = true;
|
||||
}
|
||||
|
||||
if (!awaitingControllerInput) return false;
|
||||
|
||||
if (pressedBinds.stream().anyMatch(bind -> !bind.held(controller.state(), controller))) {
|
||||
// finished
|
||||
awaitingControllerInput = false;
|
||||
control.option().requestSet(IBind.create(pressedBinds));
|
||||
pressedBinds.clear();
|
||||
} else {
|
||||
for (var bind : Bind.values()) {
|
||||
if (bind.held(controller.state(), controller) && !bind.held(controller.prevState(), controller)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
var gamepad = control.controller;
|
||||
|
||||
for (var bind : GamepadBind.values()) {
|
||||
if (bind.held(gamepad.state(), gamepad) && !bind.held(gamepad.prevState(), gamepad)) {
|
||||
control.option().requestSet(bind);
|
||||
awaitingControllerInput = false;
|
||||
gamepad.consumeButtonState();
|
||||
return true;
|
||||
}
|
||||
control.controller.consumeButtonState();
|
||||
}
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
@ -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.bindings.IBind;
|
||||
import dev.isxander.controlify.config.GlobalSettings;
|
||||
import dev.isxander.controlify.controller.ControllerTheme;
|
||||
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.yacl.api.*;
|
||||
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.network.chat.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class YACLHelper {
|
||||
public static Screen generateConfigScreen(Screen parent) {
|
||||
if (Controlify.instance().currentController() == null) {
|
||||
@ -41,11 +49,11 @@ public class YACLHelper {
|
||||
var globalSettings = Controlify.instance().config().globalSettings();
|
||||
var globalCategory = ConfigCategory.createBuilder()
|
||||
.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"))
|
||||
.tooltip(Component.translatable("controlify.gui.current_controller.tooltip"))
|
||||
.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)
|
||||
.build())
|
||||
.option(Option.createBuilder(boolean.class)
|
||||
@ -114,14 +122,22 @@ public class YACLHelper {
|
||||
.tooltip(Component.translatable("controlify.gui.vmouse_sensitivity.tooltip"))
|
||||
.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))))
|
||||
.build())
|
||||
.option(Option.createBuilder(ControllerTheme.class)
|
||||
.name(Component.translatable("controlify.gui.controller_theme"))
|
||||
.tooltip(Component.translatable("controlify.gui.controller_theme.tooltip"))
|
||||
.binding(controller.type().theme(), () -> config.theme, v -> config.theme = v)
|
||||
.controller(EnumController::new)
|
||||
.instant(true)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
if (controller instanceof GamepadController gamepad) {
|
||||
var gamepadConfig = gamepad.config();
|
||||
var defaultGamepadConfig = gamepad.defaultConfig();
|
||||
|
||||
basicGroup.option(Option.createBuilder(BuiltinGamepadTheme.class)
|
||||
.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)
|
||||
.name(Component.translatable("controlify.gui.custom_name"))
|
||||
.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"))
|
||||
.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)))
|
||||
.build())
|
||||
.option(Option.createBuilder(float.class)
|
||||
.name(Component.translatable("controlify.gui.left_stick_deadzone"))
|
||||
.tooltip(Component.translatable("controlify.gui.left_stick_deadzone.tooltip"))
|
||||
.build());
|
||||
|
||||
if (controller instanceof GamepadController gamepad) {
|
||||
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))
|
||||
.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))))
|
||||
.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(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())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
advancedGroup
|
||||
.option(ButtonOption.createBuilder()
|
||||
.name(Component.translatable("controlify.gui.auto_calibration"))
|
||||
.tooltip(Component.translatable("controlify.gui.auto_calibration.tooltip"))
|
||||
@ -170,19 +224,26 @@ public class YACLHelper {
|
||||
|
||||
var controlsGroup = OptionGroup.createBuilder()
|
||||
.name(Component.translatable("controlify.gui.group.controls"));
|
||||
for (var control : controller.bindings().registry().values()) {
|
||||
controlsGroup.option(Option.createBuilder(IBind.class)
|
||||
.name(control.name())
|
||||
.binding(control.defaultBind(), control::currentBind, control::setCurrentBind)
|
||||
.controller(opt -> new BindButtonController(opt, controller))
|
||||
.tooltip(control.description())
|
||||
.instant(true)
|
||||
.listener((opt, bind) -> { // yacl instant options have a bug where they don't save
|
||||
opt.applyValue();
|
||||
controlify.config().save();
|
||||
})
|
||||
.build());
|
||||
if (controller instanceof GamepadController gamepad) {
|
||||
for (var binding : gamepad.bindings().registry().values()) {
|
||||
controlsGroup.option(Option.createBuilder((Class<IBind<GamepadState>>) (Class<?>) IBind.class)
|
||||
.name(binding.name())
|
||||
.binding(binding.defaultBind(), binding::currentBind, binding::setCurrentBind)
|
||||
.controller(opt -> new GamepadBindController(opt, gamepad))
|
||||
.tooltip(binding.description())
|
||||
.build());
|
||||
}
|
||||
} else if (controller instanceof JoystickController joystick) {
|
||||
for (var binding : joystick.bindings().registry().values()) {
|
||||
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());
|
||||
|
||||
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;
|
||||
|
||||
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.controller.hid.HIDIdentifier;
|
||||
import dev.isxander.controlify.controller.gamepad.GamepadController;
|
||||
import dev.isxander.controlify.controller.joystick.JoystickController;
|
||||
import org.hid4java.HidDevice;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.glfw.GLFWGamepadState;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class Controller {
|
||||
public static final Map<Integer, Controller> CONTROLLERS = new HashMap<>();
|
||||
public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false, UUID.randomUUID().toString(), ControllerType.UNKNOWN);
|
||||
public interface Controller<S extends ControllerState, C extends ControllerConfig> {
|
||||
String uid();
|
||||
int joystickId();
|
||||
String guid();
|
||||
|
||||
private final int joystickId;
|
||||
private final String guid;
|
||||
private final String name;
|
||||
private final boolean gamepad;
|
||||
private final String uid;
|
||||
private final ControllerType type;
|
||||
ControllerBindings<S> bindings();
|
||||
|
||||
private ControllerState state = ControllerState.EMPTY;
|
||||
private ControllerState prevState = ControllerState.EMPTY;
|
||||
S state();
|
||||
S prevState();
|
||||
|
||||
private final ControllerBindings bindings = new ControllerBindings(this);
|
||||
private ControllerConfig config, defaultConfig;
|
||||
C config();
|
||||
C defaultConfig();
|
||||
void setConfig(Gson gson, JsonElement json);
|
||||
|
||||
public Controller(int joystickId, String guid, String name, boolean gamepad, String uid, 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();
|
||||
}
|
||||
ControllerType type();
|
||||
|
||||
public ControllerState state() {
|
||||
return state;
|
||||
}
|
||||
String name();
|
||||
|
||||
public ControllerState prevState() {
|
||||
return prevState;
|
||||
}
|
||||
void updateState();
|
||||
|
||||
public void updateState() {
|
||||
if (!connected()) {
|
||||
state = prevState = ControllerState.EMPTY;
|
||||
return;
|
||||
Map<Integer, Controller<?, ?>> CONTROLLERS = new HashMap<>();
|
||||
|
||||
static Controller<?, ?> createOrGet(int joystickId, @Nullable HidDevice device) {
|
||||
if (CONTROLLERS.containsKey(joystickId)) {
|
||||
return CONTROLLERS.get(joystickId);
|
||||
}
|
||||
|
||||
prevState = state;
|
||||
|
||||
AxesState rawAxesState = AxesState.fromController(this);
|
||||
AxesState axesState = rawAxesState
|
||||
.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++ + ")";
|
||||
if (GLFW.glfwJoystickIsGamepad(joystickId)) {
|
||||
GamepadController controller = new GamepadController(joystickId, device);
|
||||
CONTROLLERS.put(joystickId, controller);
|
||||
return controller;
|
||||
}
|
||||
|
||||
Controller controller = new Controller(id, guid, name, gamepad, uid, type);
|
||||
|
||||
CONTROLLERS.put(id, controller);
|
||||
|
||||
JoystickController controller = new JoystickController(joystickId, device);
|
||||
CONTROLLERS.put(joystickId, controller);
|
||||
return controller;
|
||||
}
|
||||
|
||||
public class ControllerConfig {
|
||||
public float horizontalLookSensitivity = 1f;
|
||||
public float verticalLookSensitivity = 0.9f;
|
||||
Controller<?, ?> DUMMY = new Controller<>() {
|
||||
private final ControllerBindings<ControllerState> bindings = new ControllerBindings<>(this);
|
||||
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
|
||||
public float leftTriggerDeadzone = 0.0f;
|
||||
public float rightTriggerDeadzone = 0.0f;
|
||||
@Override
|
||||
public float getDeadzone(int axis) {
|
||||
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;
|
||||
public boolean toggleSprint = true;
|
||||
public boolean toggleSneak = true;
|
||||
@Override
|
||||
public ControllerConfig config() {
|
||||
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;
|
||||
|
||||
public record ControllerState(AxesState axes, AxesState rawAxes, ButtonState buttons) {
|
||||
public static final ControllerState EMPTY = new ControllerState(AxesState.EMPTY, AxesState.EMPTY, ButtonState.EMPTY);
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public boolean hasAnyInput() {
|
||||
return !this.axes().equals(AxesState.EMPTY) || !this.buttons().equals(ButtonState.EMPTY);
|
||||
}
|
||||
public interface ControllerState {
|
||||
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.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonArray;
|
||||
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.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public enum ControllerType {
|
||||
UNKNOWN("Unknown Controller", ControllerTheme.XBOX_ONE),
|
||||
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);
|
||||
public class ControllerType {
|
||||
public static final ControllerType UNKNOWN = new ControllerType("Unknown", "unknown");
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setLenient().create();
|
||||
private static Map<HIDIdentifier, ControllerType> typeMap = null;
|
||||
private static final ResourceLocation hidDbLocation = new ResourceLocation("controlify", "hiddb.json5");
|
||||
|
||||
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.theme = theme;
|
||||
this.identifier = identifier;
|
||||
}
|
||||
|
||||
public String friendlyName() {
|
||||
return friendlyName;
|
||||
}
|
||||
|
||||
public ControllerTheme theme() {
|
||||
return theme;
|
||||
public String identifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public static ControllerType getTypeForHID(HIDIdentifier hid) {
|
||||
@ -40,18 +45,27 @@ public enum ControllerType {
|
||||
|
||||
typeMap = new HashMap<>();
|
||||
try {
|
||||
try (var hidDb = ControllerType.class.getResourceAsStream("/hiddb.json5")) {
|
||||
var json = GSON.fromJson(new InputStreamReader(hidDb), JsonObject.class);
|
||||
for (var type : ControllerType.values()) {
|
||||
if (!json.has(type.name().toLowerCase())) continue;
|
||||
List<IoSupplier<InputStream>> dbs = Minecraft.getInstance().getResourceManager().listPacks()
|
||||
.map(pack -> pack.getResource(PackType.CLIENT_RESOURCES, hidDbLocation))
|
||||
.filter(Objects::nonNull)
|
||||
.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();
|
||||
for (var productIdEntry : themeJson.getAsJsonArray("product")) {
|
||||
int productId = productIdEntry.getAsInt();
|
||||
typeMap.put(new HIDIdentifier(vendorId, productId), type);
|
||||
ControllerType type = new ControllerType(typeObject.get("name").getAsString(), typeObject.get("identifier").getAsString());
|
||||
|
||||
int vendorId = typeObject.get("vendor").getAsInt();
|
||||
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) {
|
||||
|
@ -1,15 +1,16 @@
|
||||
package dev.isxander.controlify.controller;
|
||||
package dev.isxander.controlify.controller.gamepad;
|
||||
|
||||
import dev.isxander.yacl.api.NameableEnum;
|
||||
import net.minecraft.network.chat.Component;
|
||||
|
||||
public enum ControllerTheme implements NameableEnum {
|
||||
XBOX_ONE("xbox"),
|
||||
public enum BuiltinGamepadTheme implements NameableEnum {
|
||||
DEFAULT("default"),
|
||||
XBOX_ONE("xbox_one"),
|
||||
DUALSHOCK4("dualshock4");
|
||||
|
||||
private final String id;
|
||||
|
||||
ControllerTheme(String id) {
|
||||
BuiltinGamepadTheme(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@ -19,6 +20,6 @@ public enum ControllerTheme implements NameableEnum {
|
||||
|
||||
@Override
|
||||
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 (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 {
|
||||
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
|
||||
public interface ControllerStateUpdate {
|
||||
void onControllerStateUpdate(Controller controller);
|
||||
void onControllerStateUpdate(Controller<?, ?> controller);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ControllerBindRegistry {
|
||||
void onRegisterControllerBinds(ControllerBindings bindings, Controller controller);
|
||||
void onRegisterControllerBinds(ControllerBindings<?> bindings, Controller<?, ?> controller);
|
||||
}
|
||||
|
||||
@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
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.util.Mth;
|
||||
|
||||
import java.math.RoundingMode;
|
||||
|
||||
public class ControllerDeadzoneCalibrationScreen extends Screen {
|
||||
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 MultiLineLabel waitLabel, infoLabel, completeLabel;
|
||||
@ -26,7 +24,7 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
|
||||
protected boolean calibrating = false, calibrated = false;
|
||||
protected int calibrationTicks = 0;
|
||||
|
||||
public ControllerDeadzoneCalibrationScreen(Controller controller, Screen parent) {
|
||||
public ControllerDeadzoneCalibrationScreen(Controller<?, ?> controller, Screen parent) {
|
||||
super(Component.translatable("controlify.calibration.title"));
|
||||
this.controller = controller;
|
||||
this.parent = parent;
|
||||
@ -110,42 +108,20 @@ public class ControllerDeadzoneCalibrationScreen extends Screen {
|
||||
}
|
||||
|
||||
private void useCurrentStateAsDeadzone() {
|
||||
var rawAxes = controller.state().rawAxes();
|
||||
var axes = controller.state().axes();
|
||||
|
||||
var minDeadzoneLS = Math.max(rawAxes.leftStickX(), rawAxes.leftStickY()) + 0.08f;
|
||||
var deadzoneLS = (float)Mth.clamp(0.05 * Math.ceil(minDeadzoneLS / 0.05), 0, 0.95);
|
||||
|
||||
var minDeadzoneRS = Math.max(rawAxes.rightStickX(), rawAxes.rightStickY()) + 0.08f;
|
||||
var deadzoneRS = (float)Mth.clamp(0.05 * Math.ceil(minDeadzoneRS / 0.05), 0, 0.95);
|
||||
|
||||
controller.config().leftStickDeadzone = deadzoneLS;
|
||||
controller.config().rightStickDeadzone = deadzoneRS;
|
||||
for (int i = 0; i < axes.size(); i++) {
|
||||
var axis = axes.get(i);
|
||||
var minDeadzone = axis + 0.08f;
|
||||
controller.config().setDeadzone(i, (float)Mth.clamp(0.05 * Math.ceil(minDeadzone / 0.05), 0, 0.95));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean stateChanged() {
|
||||
var amt = 0.0001f;
|
||||
|
||||
var lsX = controller.state().rawAxes().leftStickX();
|
||||
var prevLsX = controller.prevState().rawAxes().leftStickX();
|
||||
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;
|
||||
|
||||
return controller.state().axes().stream()
|
||||
.anyMatch(axis -> Math.abs(axis - controller.prevState().axes().get(controller.state().axes().indexOf(axis))) > amt);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -6,12 +6,10 @@ import net.minecraft.client.player.Input;
|
||||
import net.minecraft.client.player.LocalPlayer;
|
||||
|
||||
public class ControllerPlayerMovement extends Input {
|
||||
private final Controller controller;
|
||||
private final Controller<?, ?> controller;
|
||||
private final LocalPlayer player;
|
||||
|
||||
private boolean shiftToggled = false;
|
||||
|
||||
public ControllerPlayerMovement(Controller controller, LocalPlayer player) {
|
||||
public ControllerPlayerMovement(Controller<?, ?> controller, LocalPlayer player) {
|
||||
this.controller = controller;
|
||||
this.player = player;
|
||||
}
|
||||
|
@ -7,12 +7,12 @@ import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.player.KeyboardInput;
|
||||
|
||||
public class InGameInputHandler {
|
||||
private final Controller controller;
|
||||
private final Controller<?, ?> controller;
|
||||
private final Minecraft minecraft;
|
||||
|
||||
private double lookInputX, lookInputY;
|
||||
|
||||
public InGameInputHandler(Controller controller) {
|
||||
public InGameInputHandler(Controller<?, ?> controller) {
|
||||
this.controller = controller;
|
||||
this.minecraft = Minecraft.getInstance();
|
||||
|
||||
@ -51,10 +51,12 @@ public class InGameInputHandler {
|
||||
}
|
||||
|
||||
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()) {
|
||||
lookInputX = axes.rightStickX() * Math.abs(axes.rightStickX()) * controller.config().horizontalLookSensitivity;
|
||||
lookInputY = axes.rightStickY() * Math.abs(axes.rightStickY()) * controller.config().verticalLookSensitivity;
|
||||
lookInputX = impulseX * Math.abs(impulseX) * controller.config().horizontalLookSensitivity;
|
||||
lookInputY = impulseY * Math.abs(impulseY) * controller.config().verticalLookSensitivity;
|
||||
} else {
|
||||
lookInputX = lookInputY = 0;
|
||||
}
|
||||
|
@ -10,5 +10,5 @@ import java.util.Optional;
|
||||
|
||||
@FunctionalInterface
|
||||
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.*;
|
||||
|
||||
public class InGameButtonGuide implements ButtonGuideRegistry {
|
||||
private final Controller controller;
|
||||
private final Controller<?, ?> controller;
|
||||
private final LocalPlayer player;
|
||||
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> rightGuides = new ArrayList<>();
|
||||
|
||||
public InGameButtonGuide(Controller controller, LocalPlayer localPlayer) {
|
||||
public InGameButtonGuide(Controller<?, ?> controller, LocalPlayer localPlayer) {
|
||||
this.controller = controller;
|
||||
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();
|
||||
|
||||
@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))
|
||||
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"))
|
||||
|
@ -5,14 +5,14 @@ import dev.isxander.controlify.controller.Controller;
|
||||
public interface ComponentProcessor {
|
||||
ComponentProcessor EMPTY = new ComponentProcessor(){};
|
||||
|
||||
default boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller controller) {
|
||||
default boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||
default boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
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);
|
||||
}
|
||||
|
||||
public void onControllerUpdate(Controller controller) {
|
||||
public void onControllerUpdate(Controller<?, ?> controller) {
|
||||
if (!Controlify.instance().virtualMouseHandler().isVirtualMouseEnabled()) {
|
||||
handleComponentNavigation(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)
|
||||
setInitialFocus();
|
||||
|
||||
@ -59,28 +59,17 @@ public class ScreenProcessor<T extends Screen> {
|
||||
|
||||
boolean repeatEventAvailable = ++lastMoved >= controller.config().screenRepeatNavigationDelay;
|
||||
|
||||
var axes = controller.state().axes();
|
||||
var prevAxes = controller.prevState().axes();
|
||||
var buttons = controller.state().buttons();
|
||||
var prevButtons = controller.prevState().buttons();
|
||||
var bindings = controller.bindings();
|
||||
|
||||
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);
|
||||
} 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);
|
||||
} 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);
|
||||
} 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);
|
||||
} 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) {
|
||||
@ -97,7 +86,7 @@ public class ScreenProcessor<T extends Screen> {
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleButtons(Controller controller) {
|
||||
protected void handleButtons(Controller<?, ?> controller) {
|
||||
var focusTree = getFocusTree();
|
||||
while (!focusTree.isEmpty()) {
|
||||
var focused = focusTree.poll();
|
||||
@ -111,7 +100,7 @@ public class ScreenProcessor<T extends Screen> {
|
||||
screen.onClose();
|
||||
}
|
||||
|
||||
protected void handleVMouseNavigation(Controller controller) {
|
||||
protected void handleVMouseNavigation(Controller<?, ?> controller) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.components.AbstractButton;
|
||||
|
||||
@ -14,7 +14,7 @@ public class AbstractButtonComponentProcessor implements ComponentProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||
button.playDownSound(Minecraft.getInstance().getSoundManager());
|
||||
button.onPress();
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.CreativeModeInventoryScreenAccessor;
|
||||
import net.minecraft.client.gui.screens.inventory.CreativeModeInventoryScreen;
|
||||
import net.minecraft.world.item.CreativeModeTabs;
|
||||
@ -12,7 +12,7 @@ public class CreativeModeInventoryScreenProcessor extends ScreenProcessor<Creati
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleVMouseNavigation(Controller controller) {
|
||||
protected void handleVMouseNavigation(Controller<?, ?> controller) {
|
||||
var accessor = (CreativeModeInventoryScreenAccessor) screen;
|
||||
|
||||
if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen;
|
||||
import net.minecraft.client.gui.screens.multiplayer.ServerSelectionList;
|
||||
@ -15,7 +15,7 @@ public class JoinMultiplayerScreenProcessor extends ScreenProcessor<JoinMultipla
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleButtons(Controller controller) {
|
||||
protected void handleButtons(Controller<?, ?> controller) {
|
||||
if (screen.getFocused() instanceof Button && controller.bindings().GUI_BACK.justPressed()) {
|
||||
screen.setFocused(list);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.OptionsSubScreenAccessor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
@ -14,7 +14,7 @@ public class LanguageSelectionListComponentProcessor implements ComponentProcess
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
var languageManager = minecraft.getLanguageManager();
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.SelectWorldScreenAccessor;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||
@ -12,7 +12,7 @@ public class SelectWorldScreenProcessor extends ScreenProcessor<SelectWorldScree
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleButtons(Controller controller) {
|
||||
protected void handleButtons(Controller<?, ?> controller) {
|
||||
if (screen.getFocused() != null && screen.getFocused() instanceof Button) {
|
||||
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||
screen.setFocused(((SelectWorldScreenAccessor) screen).getList());
|
||||
|
@ -1,13 +1,13 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.JoinMultiplayerScreenAccessor;
|
||||
|
||||
public class ServerSelectionListEntryComponentProcessor implements ComponentProcessor {
|
||||
@Override
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||
screen.screen.setFocused(((JoinMultiplayerScreenAccessor) screen.screen).getSelectButton());
|
||||
return true;
|
||||
|
@ -1,8 +1,8 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import net.minecraft.client.gui.components.AbstractSliderButton;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
@ -25,21 +25,19 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller controller) {
|
||||
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
if (!this.canChangeValueGetter.get()) return false;
|
||||
|
||||
var canSliderChange = ++lastSliderChange > SLIDER_CHANGE_DELAY;
|
||||
|
||||
var axes = controller.state().axes();
|
||||
var buttons = controller.state().buttons();
|
||||
if (axes.leftStickX() > 0.5f || buttons.dpadRight()) {
|
||||
if (controller.bindings().GUI_NAVI_RIGHT.held()) {
|
||||
if (canSliderChange) {
|
||||
component.keyPressed(GLFW.GLFW_KEY_RIGHT, 0, 0);
|
||||
lastSliderChange = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (axes.leftStickX() < -0.5f || buttons.dpadLeft()) {
|
||||
} else if (controller.bindings().GUI_NAVI_LEFT.held()) {
|
||||
if (canSliderChange) {
|
||||
component.keyPressed(GLFW.GLFW_KEY_LEFT, 0, 0);
|
||||
lastSliderChange = 0;
|
||||
@ -52,7 +50,7 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
if (!this.canChangeValueGetter.get()) return false;
|
||||
|
||||
if (controller.bindings().GUI_BACK.justPressed()) {
|
||||
@ -64,7 +62,7 @@ public class SliderComponentProcessor implements ComponentProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusGained(ScreenProcessor<?> screen, Controller controller) {
|
||||
public void onFocusGained(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
System.out.println("navigated!");
|
||||
this.canChangeValueSetter.accept(false);
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
package dev.isxander.controlify.screenop.compat.vanilla;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.mixins.feature.screenop.vanilla.SelectWorldScreenAccessor;
|
||||
import net.minecraft.client.gui.screens.worldselection.SelectWorldScreen;
|
||||
|
||||
public class WorldListEntryComponentProcessor implements ComponentProcessor {
|
||||
@Override
|
||||
public boolean overrideControllerButtons(ScreenProcessor screen, Controller controller) {
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
if (controller.bindings().GUI_PRESS.justPressed()) {
|
||||
var selectWorldScreen = (SelectWorldScreen) screen.screen;
|
||||
selectWorldScreen.setFocused(((SelectWorldScreenAccessor) selectWorldScreen).getSelectButton());
|
||||
|
@ -1,33 +1,44 @@
|
||||
package dev.isxander.controlify.screenop.compat.yacl;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.yacl.gui.controllers.cycling.CyclingControllerElement;
|
||||
|
||||
public class CyclingControllerElementComponentProcessor implements ComponentProcessor {
|
||||
private final CyclingControllerElement cyclingController;
|
||||
private int lastInput = 0;
|
||||
private boolean prevLeft, prevRight;
|
||||
|
||||
|
||||
public CyclingControllerElementComponentProcessor(CyclingControllerElement cyclingController) {
|
||||
this.cyclingController = cyclingController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller controller) {
|
||||
var rightStickX = controller.state().axes().rightStickX();
|
||||
var rightStickY = controller.state().axes().rightStickY();
|
||||
var input = Math.abs(rightStickX) > Math.abs(rightStickY) ? rightStickX : rightStickY;
|
||||
public boolean overrideControllerNavigation(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
var left = controller.bindings().GUI_NAVI_LEFT.held();
|
||||
var right = controller.bindings().GUI_NAVI_RIGHT.held();
|
||||
|
||||
var inputI = Math.abs(input) < controller.config().buttonActivationThreshold ? 0 : input > 0 ? 1 : -1;
|
||||
if (inputI != 0 && inputI != lastInput) {
|
||||
cyclingController.cycleValue(input > 0 ? 1 : -1);
|
||||
if (left && !prevLeft) {
|
||||
prevLeft = true;
|
||||
prevRight = false;
|
||||
|
||||
cyclingController.cycleValue(-1);
|
||||
|
||||
lastInput = inputI;
|
||||
return true;
|
||||
}
|
||||
lastInput = inputI;
|
||||
} else if (right && !prevRight) {
|
||||
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;
|
||||
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.screenop.ComponentProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.yacl.gui.controllers.slider.SliderControllerElement;
|
||||
|
||||
public class SliderControllerElementComponentProcessor implements ComponentProcessor {
|
||||
private final SliderControllerElement slider;
|
||||
private int ticksSinceIncrement = 0;
|
||||
private int lastInput = 0;
|
||||
private boolean prevLeft, prevRight;
|
||||
|
||||
public SliderControllerElementComponentProcessor(SliderControllerElement element) {
|
||||
this.slider = element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller controller) {
|
||||
public boolean overrideControllerButtons(ScreenProcessor<?> screen, Controller<?, ?> controller) {
|
||||
ticksSinceIncrement++;
|
||||
|
||||
var rightStickX = controller.state().axes().rightStickX();
|
||||
var rightStickY = controller.state().axes().rightStickY();
|
||||
var input = Math.abs(rightStickX) > Math.abs(rightStickY) ? rightStickX : rightStickY;
|
||||
var left = controller.bindings().GUI_NAVI_LEFT.held();
|
||||
var right = controller.bindings().GUI_NAVI_RIGHT.held();
|
||||
|
||||
if (Math.abs(input) > controller.config().buttonActivationThreshold) {
|
||||
if (ticksSinceIncrement > controller.config().screenRepeatNavigationDelay || input != lastInput) {
|
||||
slider.incrementValue(lastInput = input > 0 ? 1 : -1);
|
||||
if (left || right) {
|
||||
if (ticksSinceIncrement > controller.config().screenRepeatNavigationDelay || left != prevLeft || right != prevRight) {
|
||||
slider.incrementValue(left ? -1 : 1);
|
||||
ticksSinceIncrement = 0;
|
||||
prevLeft = left;
|
||||
prevRight = right;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
this.lastInput = 0;
|
||||
this.prevLeft = false;
|
||||
this.prevRight = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.isxander.controlify.screenop.compat.yacl;
|
||||
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessor;
|
||||
import dev.isxander.yacl.gui.YACLScreen;
|
||||
|
||||
public class YACLScreenProcessor extends ScreenProcessor<YACLScreen> {
|
||||
@ -10,7 +10,7 @@ public class YACLScreenProcessor extends ScreenProcessor<YACLScreen> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleComponentNavigation(Controller controller) {
|
||||
protected void handleComponentNavigation(Controller<?, ?> controller) {
|
||||
if (controller.bindings().GUI_NEXT_TAB.justPressed()) {
|
||||
var idx = screen.getCurrentCategoryIdx() + 1;
|
||||
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 dev.isxander.controlify.Controlify;
|
||||
import dev.isxander.controlify.InputMode;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.controller.Controller;
|
||||
import dev.isxander.controlify.screenop.ScreenProcessorProvider;
|
||||
import dev.isxander.controlify.event.ControlifyEvents;
|
||||
import dev.isxander.controlify.mixins.feature.virtualmouse.KeyboardHandlerAccessor;
|
||||
import dev.isxander.controlify.mixins.feature.virtualmouse.MouseHandlerAccessor;
|
||||
@ -52,7 +52,7 @@ public class VirtualMouseHandler {
|
||||
ControlifyEvents.INPUT_MODE_CHANGED.register(this::onInputModeChanged);
|
||||
}
|
||||
|
||||
public void handleControllerInput(Controller controller) {
|
||||
public void handleControllerInput(Controller<?, ?> controller) {
|
||||
if (controller.bindings().VMOUSE_TOGGLE.justPressed()) {
|
||||
toggleVirtualMouse();
|
||||
}
|
||||
@ -61,10 +61,10 @@ public class VirtualMouseHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
var leftStickX = controller.state().axes().leftStickX();
|
||||
var leftStickY = controller.state().axes().leftStickY();
|
||||
var prevLeftStickX = controller.prevState().axes().leftStickX();
|
||||
var prevLeftStickY = controller.prevState().axes().leftStickY();
|
||||
var impulseY = controller.bindings().VMOUSE_MOVE_DOWN.state() - controller.bindings().VMOUSE_MOVE_UP.state();
|
||||
var impulseX = controller.bindings().VMOUSE_MOVE_RIGHT.state() - controller.bindings().VMOUSE_MOVE_LEFT.state();
|
||||
var prevImpulseY = controller.bindings().VMOUSE_MOVE_DOWN.prevState() - controller.bindings().VMOUSE_MOVE_UP.prevState();
|
||||
var prevImpulseX = controller.bindings().VMOUSE_MOVE_RIGHT.prevState() - controller.bindings().VMOUSE_MOVE_LEFT.prevState();
|
||||
|
||||
if (minecraft.screen != null && minecraft.screen instanceof ISnapBehaviour snapBehaviour) {
|
||||
snapPoints = snapBehaviour.getSnapPoints();
|
||||
@ -73,8 +73,8 @@ public class VirtualMouseHandler {
|
||||
}
|
||||
|
||||
// if just released stick, snap to nearest snap point
|
||||
if (leftStickX == 0 && leftStickY == 0) {
|
||||
if ((prevLeftStickX != 0 || prevLeftStickY != 0))
|
||||
if (impulseX == 0 && impulseY == 0) {
|
||||
if ((prevImpulseX != 0 || prevImpulseY != 0))
|
||||
snapToClosestPoint();
|
||||
} else {
|
||||
snapping = false;
|
||||
@ -84,8 +84,8 @@ public class VirtualMouseHandler {
|
||||
|
||||
// quadratic function to make small movements smaller
|
||||
// abs to keep sign
|
||||
targetX += leftStickX * Mth.abs(leftStickX) * 20f * sensitivity;
|
||||
targetY += leftStickY * Mth.abs(leftStickY) * 20f * sensitivity;
|
||||
targetX += impulseX * Mth.abs(impulseX) * 20f * sensitivity;
|
||||
targetY += impulseY * Mth.abs(impulseY) * 20f * sensitivity;
|
||||
|
||||
targetX = Mth.clamp(targetX, 0, minecraft.getWindow().getWidth());
|
||||
targetY = Mth.clamp(targetY, 0, minecraft.getWindow().getHeight());
|
||||
|
Reference in New Issue
Block a user