diff --git a/src/main/java/dev/isxander/controlify/Controlify.java b/src/main/java/dev/isxander/controlify/Controlify.java index 18738cb..2afa4e3 100644 --- a/src/main/java/dev/isxander/controlify/Controlify.java +++ b/src/main/java/dev/isxander/controlify/Controlify.java @@ -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 calibrationQueue = new ArrayDeque<>(); + private final Queue> 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; diff --git a/src/main/java/dev/isxander/controlify/bindings/Bind.java b/src/main/java/dev/isxander/controlify/bindings/Bind.java deleted file mode 100644 index a07eaf7..0000000 --- a/src/main/java/dev/isxander/controlify/bindings/Bind.java +++ /dev/null @@ -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 state; - private final String identifier; - - Bind(BiFunction state, String identifier) { - this.state = state; - this.identifier = identifier; - } - - Bind(Function 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; - } -} diff --git a/src/main/java/dev/isxander/controlify/bindings/BindingSupplier.java b/src/main/java/dev/isxander/controlify/bindings/BindingSupplier.java index 1786522..73e7436 100644 --- a/src/main/java/dev/isxander/controlify/bindings/BindingSupplier.java +++ b/src/main/java/dev/isxander/controlify/bindings/BindingSupplier.java @@ -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 { + ControllerBinding get(Controller controller); } diff --git a/src/main/java/dev/isxander/controlify/bindings/CompoundBind.java b/src/main/java/dev/isxander/controlify/bindings/CompoundBind.java deleted file mode 100644 index 0c31ad4..0000000 --- a/src/main/java/dev/isxander/controlify/bindings/CompoundBind.java +++ /dev/null @@ -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 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 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); - } -} diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBinding.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBinding.java index 4f1e7c8..7289e98 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBinding.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBinding.java @@ -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 { + private final Controller controller; + private IBind bind; + private final IBind defaultBind; private final ResourceLocation id; private final Component name, description; private final KeyMappingOverride override; - private static final Map> pressedBinds = new HashMap<>(); + private static final Map, Set>> pressedBinds = new HashMap<>(); - public ControllerBinding(Controller controller, IBind defaultBind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride) { + public ControllerBinding(Controller controller, IBind 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 controller, IBind defaultBind, ResourceLocation id) { + this(controller, defaultBind, id, null, () -> false); + } + + public ControllerBinding(Controller controller, GamepadBind defaultBind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride) { + this(controller, controller instanceof GamepadController ? (IBind) defaultBind : new EmptyBind<>(), id, override, toggleOverride); + } + + public ControllerBinding(Controller 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 currentBind() { return bind; } - public void setCurrentBind(IBind bind) { + public void setCurrentBind(IBind bind) { this.bind = bind; } - public IBind defaultBind() { + public IBind 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 getBinds(IBind bind) { - if (bind instanceof CompoundBind compoundBind) { - return compoundBind.binds(); - } else { - return Set.of((Bind) bind); - } + private static Set> getBinds(IBind bind) { + return Set.of(bind); } public record KeyMappingOverride(KeyMapping keyMapping, BooleanSupplier toggleable) { diff --git a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java index 5b5de48..f784c06 100644 --- a/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java +++ b/src/main/java/dev/isxander/controlify/bindings/ControllerBindings.java @@ -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 { + public final ControllerBinding 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 registry = new LinkedHashMap<>(); + private final Map> registry = new LinkedHashMap<>(); - private final Controller controller; + private final Controller controller; - public ControllerBindings(Controller controller) { + public ControllerBindings(Controller 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 register(ControllerBinding binding) { registry.put(binding.id(), binding); return controller -> controller.bindings().get(binding.id()); } - public ControllerBinding get(ResourceLocation id) { + public ControllerBinding get(ResourceLocation id) { return registry.get(id); } - public Map registry() { + public Map> 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(); diff --git a/src/main/java/dev/isxander/controlify/bindings/EmptyBind.java b/src/main/java/dev/isxander/controlify/bindings/EmptyBind.java new file mode 100644 index 0000000..e0245b1 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/EmptyBind.java @@ -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 implements IBind { + 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 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; + } +} diff --git a/src/main/java/dev/isxander/controlify/bindings/GamepadBind.java b/src/main/java/dev/isxander/controlify/bindings/GamepadBind.java new file mode 100644 index 0000000..e39bd22 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/GamepadBind.java @@ -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 { + 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 state; + private final String identifier; + private final Map textureLocations; + + GamepadBind(Function 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 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 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; + } +} diff --git a/src/main/java/dev/isxander/controlify/bindings/IBind.java b/src/main/java/dev/isxander/controlify/bindings/IBind.java index 8dd3326..d4e1819 100644 --- a/src/main/java/dev/isxander/controlify/bindings/IBind.java +++ b/src/main/java/dev/isxander/controlify/bindings/IBind.java @@ -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 { + float state(S state); + default boolean held(S state, Controller 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 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 IBind fromJson(JsonObject json, Controller 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) GamepadBind.fromJson(json); + } else if (controller instanceof JoystickController joystick) { + return (IBind) 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 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<>(); } } diff --git a/src/main/java/dev/isxander/controlify/bindings/JoystickAxisBind.java b/src/main/java/dev/isxander/controlify/bindings/JoystickAxisBind.java new file mode 100644 index 0000000..b4b4562 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/JoystickAxisBind.java @@ -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 { + 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 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 + } +} diff --git a/src/main/java/dev/isxander/controlify/bindings/JoystickButtonBind.java b/src/main/java/dev/isxander/controlify/bindings/JoystickButtonBind.java new file mode 100644 index 0000000..d13a9d9 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/JoystickButtonBind.java @@ -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 { + 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 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); + } +} diff --git a/src/main/java/dev/isxander/controlify/bindings/JoystickHatBind.java b/src/main/java/dev/isxander/controlify/bindings/JoystickHatBind.java new file mode 100644 index 0000000..778fb73 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/bindings/JoystickHatBind.java @@ -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 { + 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 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); + } +} diff --git a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java index b6ee87a..c79bfc5 100644 --- a/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java +++ b/src/main/java/dev/isxander/controlify/config/ControlifyConfig.java @@ -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")); } diff --git a/src/main/java/dev/isxander/controlify/config/gui/BindButtonController.java b/src/main/java/dev/isxander/controlify/config/gui/GamepadBindController.java similarity index 54% rename from src/main/java/dev/isxander/controlify/config/gui/BindButtonController.java rename to src/main/java/dev/isxander/controlify/config/gui/GamepadBindController.java index 08ce866..ec9b6b4 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/BindButtonController.java +++ b/src/main/java/dev/isxander/controlify/config/gui/GamepadBindController.java @@ -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> { + private final Option> option; + private final GamepadController controller; -public class BindButtonController implements Controller { - private final Option option; - private final dev.isxander.controlify.controller.Controller controller; - - public BindButtonController(Option option, dev.isxander.controlify.controller.Controller controller) { + public GamepadBindController(Option> option, GamepadController controller) { this.option = option; this.controller = controller; } @Override - public Option option() { + public Option> option() { return this.option; } @@ -43,26 +42,18 @@ public class BindButtonController implements Controller { return new BindButtonWidget(this, yaclScreen, dimension); } - public static class BindButtonWidget extends ControllerWidget implements ComponentProcessorProvider, ComponentProcessor { + public static class BindButtonWidget extends ControllerWidget implements ComponentProcessorProvider, ComponentProcessor { private boolean awaitingControllerInput = false; private final Component awaitingText = Component.translatable("controlify.gui.bind_input_awaiting").withStyle(ChatFormatting.ITALIC); - private final Set pressedBinds = new LinkedHashSet<>(); - public BindButtonWidget(BindButtonController control, YACLScreen screen, Dimension dim) { + public BindButtonWidget(GamepadBindController control, YACLScreen screen, Dimension 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 { } @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; } diff --git a/src/main/java/dev/isxander/controlify/config/gui/JoystickBindController.java b/src/main/java/dev/isxander/controlify/config/gui/JoystickBindController.java new file mode 100644 index 0000000..d12ce29 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/config/gui/JoystickBindController.java @@ -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> { + private final Option> option; + private final JoystickController controller; + + public JoystickBindController(Option> option, JoystickController controller) { + this.option = option; + this.controller = controller; + } + + @Override + public Option> option() { + return this.option; + } + + @Override + public Component formatValue() { + return Component.empty(); + } + + @Override + public AbstractWidget provideWidget(YACLScreen yaclScreen, Dimension dimension) { + return new BindButtonWidget(this, yaclScreen, dimension); + } + + public static class BindButtonWidget extends ControllerWidget 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 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(); + } + } +} diff --git a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java index 2288065..87c15f6 100644 --- a/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java +++ b/src/main/java/dev/isxander/controlify/config/gui/YACLHelper.java @@ -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>) (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 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>) (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>) (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()); diff --git a/src/main/java/dev/isxander/controlify/controller/AbstractController.java b/src/main/java/dev/isxander/controlify/controller/AbstractController.java new file mode 100644 index 0000000..9e1c5d5 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/AbstractController.java @@ -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 implements Controller { + private final int joystickId; + protected String name; + private final String uid; + private final String guid; + private final ControllerType type; + + private final ControllerBindings 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 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(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); + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/AxesState.java b/src/main/java/dev/isxander/controlify/controller/AxesState.java deleted file mode 100644 index 03cbe77..0000000 --- a/src/main/java/dev/isxander/controlify/controller/AxesState.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/dev/isxander/controlify/controller/ButtonState.java b/src/main/java/dev/isxander/controlify/controller/ButtonState.java deleted file mode 100644 index fc159df..0000000 --- a/src/main/java/dev/isxander/controlify/controller/ButtonState.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/dev/isxander/controlify/controller/Controller.java b/src/main/java/dev/isxander/controlify/controller/Controller.java index a6822a4..b7ee7ba 100644 --- a/src/main/java/dev/isxander/controlify/controller/Controller.java +++ b/src/main/java/dev/isxander/controlify/controller/Controller.java @@ -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 CONTROLLERS = new HashMap<>(); - public static final Controller DUMMY = new Controller(-1, "DUMMY", "DUMMY", false, UUID.randomUUID().toString(), ControllerType.UNKNOWN); +public interface Controller { + 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 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> 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 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 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() { + + } + }; } diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java new file mode 100644 index 0000000..3b09256 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/ControllerConfig.java @@ -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); +} diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerState.java b/src/main/java/dev/isxander/controlify/controller/ControllerState.java index 3416dc0..56378d3 100644 --- a/src/main/java/dev/isxander/controlify/controller/ControllerState.java +++ b/src/main/java/dev/isxander/controlify/controller/ControllerState.java @@ -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 axes(); + List rawAxes(); + + List buttons(); + + boolean hasAnyInput(); + + ControllerState EMPTY = new ControllerState() { + @Override + public List axes() { + return List.of(); + } + + @Override + public List rawAxes() { + return List.of(); + } + + @Override + public List buttons() { + return List.of(); + } + + @Override + public boolean hasAnyInput() { + return false; + } + }; } diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerType.java b/src/main/java/dev/isxander/controlify/controller/ControllerType.java index df032b2..49d1346 100644 --- a/src/main/java/dev/isxander/controlify/controller/ControllerType.java +++ b/src/main/java/dev/isxander/controlify/controller/ControllerType.java @@ -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 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> 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) { diff --git a/src/main/java/dev/isxander/controlify/controller/ControllerTheme.java b/src/main/java/dev/isxander/controlify/controller/gamepad/BuiltinGamepadTheme.java similarity index 61% rename from src/main/java/dev/isxander/controlify/controller/ControllerTheme.java rename to src/main/java/dev/isxander/controlify/controller/gamepad/BuiltinGamepadTheme.java index ae0568f..e612a86 100644 --- a/src/main/java/dev/isxander/controlify/controller/ControllerTheme.java +++ b/src/main/java/dev/isxander/controlify/controller/gamepad/BuiltinGamepadTheme.java @@ -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()); } } diff --git a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadConfig.java b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadConfig.java new file mode 100644 index 0000000..b22685c --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadConfig.java @@ -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); + }; + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java new file mode 100644 index 0000000..dc3964e --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadController.java @@ -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 { + 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; + } + +} diff --git a/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadState.java b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadState.java new file mode 100644 index 0000000..6ff3043 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/gamepad/GamepadState.java @@ -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 unnamedAxes; + private final List unnamedRawAxes; + private final List 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 axes() { + return unnamedAxes; + } + + @Override + public List rawAxes() { + return unnamedRawAxes; + } + + @Override + public List 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); + } + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java b/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java index dda7df9..2e3caf7 100644 --- a/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java +++ b/src/main/java/dev/isxander/controlify/controller/hid/ControllerHIDService.java @@ -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()); } diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickConfig.java b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickConfig.java new file mode 100644 index 0000000..7da06d4 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickConfig.java @@ -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 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; + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java new file mode 100644 index 0000000..66b2bc7 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickController.java @@ -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 { + 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); + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/JoystickState.java b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickState.java new file mode 100644 index 0000000..2e3819e --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/JoystickState.java @@ -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 axes; + private final List rawAxes; + private final List buttons; + private final List hats; + + private JoystickState(JoystickMapping mapping, List axes, List rawAxes, List buttons, List hats) { + this.mapping = mapping; + this.axes = axes; + this.rawAxes = rawAxes; + this.buttons = buttons; + this.hats = hats; + } + + @Override + public List axes() { + return axes; + } + + @Override + public List rawAxes() { + return rawAxes; + } + + @Override + public List buttons() { + return buttons; + } + + public List 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 axes = new ArrayList<>(); + List 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 buttons = new ArrayList<>(); + if (buttonBuffer != null) { + while (buttonBuffer.hasRemaining()) { + buttons.add(buttonBuffer.get() == GLFW.GLFW_PRESS); + } + } + + ByteBuffer hatBuffer = GLFW.glfwGetJoystickHats(joystick.joystickId()); + List 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()); + } + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/DataJoystickMapping.java b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/DataJoystickMapping.java new file mode 100644 index 0000000..3517ed8 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/DataJoystickMapping.java @@ -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 axisMappings; + private final Map buttonMappings; + private final Map hatMappings; + + public DataJoystickMapping(JsonObject object, ControllerType type) { + axisMappings = new HashMap<>(); + object.getAsJsonArray("axes").forEach(element -> { + var axis = element.getAsJsonObject(); + List 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 ids, String identifier, Vec2 inpRange, Vec2 outRange, float restState, boolean requiresDeadzone, String typeId, List> 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()); + } + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/JoystickMapping.java b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/JoystickMapping.java new file mode 100644 index 0000000..399e6a4 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/JoystickMapping.java @@ -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(); + } +} diff --git a/src/main/java/dev/isxander/controlify/controller/joystick/mapping/UnmappedJoystickMapping.java b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/UnmappedJoystickMapping.java new file mode 100644 index 0000000..dfcdb05 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/controller/joystick/mapping/UnmappedJoystickMapping.java @@ -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); + } + } +} diff --git a/src/main/java/dev/isxander/controlify/event/ControlifyEvents.java b/src/main/java/dev/isxander/controlify/event/ControlifyEvents.java index 24d5ee5..8a0ef3e 100644 --- a/src/main/java/dev/isxander/controlify/event/ControlifyEvents.java +++ b/src/main/java/dev/isxander/controlify/event/ControlifyEvents.java @@ -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 diff --git a/src/main/java/dev/isxander/controlify/gui/ButtonRenderer.java b/src/main/java/dev/isxander/controlify/gui/ButtonRenderer.java deleted file mode 100644 index c4e6f5c..0000000 --- a/src/main/java/dev/isxander/controlify/gui/ButtonRenderer.java +++ /dev/null @@ -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) { } -} diff --git a/src/main/java/dev/isxander/controlify/gui/DrawSize.java b/src/main/java/dev/isxander/controlify/gui/DrawSize.java new file mode 100644 index 0000000..a9afa9c --- /dev/null +++ b/src/main/java/dev/isxander/controlify/gui/DrawSize.java @@ -0,0 +1,4 @@ +package dev.isxander.controlify.gui; + +public record DrawSize(int width, int height) { +} diff --git a/src/main/java/dev/isxander/controlify/gui/screen/ControllerDeadzoneCalibrationScreen.java b/src/main/java/dev/isxander/controlify/gui/screen/ControllerDeadzoneCalibrationScreen.java index 369581b..66ddad2 100644 --- a/src/main/java/dev/isxander/controlify/gui/screen/ControllerDeadzoneCalibrationScreen.java +++ b/src/main/java/dev/isxander/controlify/gui/screen/ControllerDeadzoneCalibrationScreen.java @@ -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 diff --git a/src/main/java/dev/isxander/controlify/ingame/ControllerPlayerMovement.java b/src/main/java/dev/isxander/controlify/ingame/ControllerPlayerMovement.java index 78d303a..9469ba7 100644 --- a/src/main/java/dev/isxander/controlify/ingame/ControllerPlayerMovement.java +++ b/src/main/java/dev/isxander/controlify/ingame/ControllerPlayerMovement.java @@ -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; } diff --git a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java index 99fcb71..0637320 100644 --- a/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java +++ b/src/main/java/dev/isxander/controlify/ingame/InGameInputHandler.java @@ -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; } diff --git a/src/main/java/dev/isxander/controlify/ingame/guide/ButtonActionSupplier.java b/src/main/java/dev/isxander/controlify/ingame/guide/ButtonActionSupplier.java index 5307976..3db4990 100644 --- a/src/main/java/dev/isxander/controlify/ingame/guide/ButtonActionSupplier.java +++ b/src/main/java/dev/isxander/controlify/ingame/guide/ButtonActionSupplier.java @@ -10,5 +10,5 @@ import java.util.Optional; @FunctionalInterface public interface ButtonActionSupplier { - Optional supply(Minecraft client, LocalPlayer player, ClientLevel level, HitResult hitResult, Controller controller); + Optional supply(Minecraft client, LocalPlayer player, ClientLevel level, HitResult hitResult, Controller controller); } diff --git a/src/main/java/dev/isxander/controlify/ingame/guide/InGameButtonGuide.java b/src/main/java/dev/isxander/controlify/ingame/guide/InGameButtonGuide.java index a896173..f2f7678 100644 --- a/src/main/java/dev/isxander/controlify/ingame/guide/InGameButtonGuide.java +++ b/src/main/java/dev/isxander/controlify/ingame/guide/InGameButtonGuide.java @@ -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 leftGuides = new ArrayList<>(); private final List rightGuides = new ArrayList<>(); - public InGameButtonGuide(Controller controller, LocalPlayer localPlayer) { + public InGameButtonGuide(Controller controller, LocalPlayer localPlayer) { this.controller = controller; this.player = localPlayer; diff --git a/src/main/java/dev/isxander/controlify/mixins/core/GLXMixin.java b/src/main/java/dev/isxander/controlify/mixins/core/GLXMixin.java new file mode 100644 index 0000000..4d88553 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/mixins/core/GLXMixin.java @@ -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 cir) { + GLFW.glfwInitHint(GLFW.GLFW_JOYSTICK_HAT_BUTTONS, GLFW.GLFW_FALSE); + } +} diff --git a/src/main/java/dev/isxander/controlify/mixins/core/MinecraftMixin.java b/src/main/java/dev/isxander/controlify/mixins/core/MinecraftMixin.java index 2b32e17..1876191 100644 --- a/src/main/java/dev/isxander/controlify/mixins/core/MinecraftMixin.java +++ b/src/main/java/dev/isxander/controlify/mixins/core/MinecraftMixin.java @@ -25,9 +25,16 @@ public abstract class MinecraftMixin { @Shadow public abstract ToastComponent getToasts(); + @ModifyExpressionValue(method = "", 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 = "", 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")) diff --git a/src/main/java/dev/isxander/controlify/screenop/ComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/ComponentProcessor.java index 0060ce0..5bdff1b 100644 --- a/src/main/java/dev/isxander/controlify/screenop/ComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/ComponentProcessor.java @@ -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) { } } diff --git a/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java index 676654a..7f11ad7 100644 --- a/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/ScreenProcessor.java @@ -23,7 +23,7 @@ public class ScreenProcessor { 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 { } } - protected void handleComponentNavigation(Controller controller) { + protected void handleComponentNavigation(Controller controller) { if (screen.getFocused() == null) setInitialFocus(); @@ -59,28 +59,17 @@ public class ScreenProcessor { 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 { } } - 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 { screen.onClose(); } - protected void handleVMouseNavigation(Controller controller) { + protected void handleVMouseNavigation(Controller controller) { } diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AbstractButtonComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AbstractButtonComponentProcessor.java index 5a1c7db..d209885 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AbstractButtonComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/AbstractButtonComponentProcessor.java @@ -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(); diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/CreativeModeInventoryScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/CreativeModeInventoryScreenProcessor.java index 59ae98c..7063275 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/CreativeModeInventoryScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/CreativeModeInventoryScreenProcessor.java @@ -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 controller) { var accessor = (CreativeModeInventoryScreenAccessor) screen; if (controller.bindings().GUI_NEXT_TAB.justPressed()) { diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/JoinMultiplayerScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/JoinMultiplayerScreenProcessor.java index 047a04e..b23d8a8 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/JoinMultiplayerScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/JoinMultiplayerScreenProcessor.java @@ -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 controller) { if (screen.getFocused() instanceof Button && controller.bindings().GUI_BACK.justPressed()) { screen.setFocused(list); } diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/LanguageSelectionListComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/LanguageSelectionListComponentProcessor.java index c3c11d0..7b6c020 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/LanguageSelectionListComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/LanguageSelectionListComponentProcessor.java @@ -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(); diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SelectWorldScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SelectWorldScreenProcessor.java index e3505f7..af23bd9 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SelectWorldScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SelectWorldScreenProcessor.java @@ -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 controller) { if (screen.getFocused() != null && screen.getFocused() instanceof Button) { if (controller.bindings().GUI_BACK.justPressed()) { screen.setFocused(((SelectWorldScreenAccessor) screen).getList()); diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/ServerSelectionListEntryComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/ServerSelectionListEntryComponentProcessor.java index 83f2018..2d93783 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/ServerSelectionListEntryComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/ServerSelectionListEntryComponentProcessor.java @@ -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; diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SliderComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SliderComponentProcessor.java index 78ab591..73fd576 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SliderComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/SliderComponentProcessor.java @@ -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); } diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/WorldListEntryComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/WorldListEntryComponentProcessor.java index 44c2de7..beff4e5 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/WorldListEntryComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/vanilla/WorldListEntryComponentProcessor.java @@ -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()); diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/yacl/CyclingControllerElementComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/yacl/CyclingControllerElementComponentProcessor.java index ff2feec..6368d2b 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/yacl/CyclingControllerElementComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/yacl/CyclingControllerElementComponentProcessor.java @@ -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; + } } } diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/yacl/SliderControllerElementComponentProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/yacl/SliderControllerElementComponentProcessor.java index 85172be..27099b5 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/yacl/SliderControllerElementComponentProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/yacl/SliderControllerElementComponentProcessor.java @@ -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; diff --git a/src/main/java/dev/isxander/controlify/screenop/compat/yacl/YACLScreenProcessor.java b/src/main/java/dev/isxander/controlify/screenop/compat/yacl/YACLScreenProcessor.java index ae5ea9c..7f5c8a9 100644 --- a/src/main/java/dev/isxander/controlify/screenop/compat/yacl/YACLScreenProcessor.java +++ b/src/main/java/dev/isxander/controlify/screenop/compat/yacl/YACLScreenProcessor.java @@ -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 { @@ -10,7 +10,7 @@ public class YACLScreenProcessor extends ScreenProcessor { } @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; diff --git a/src/main/java/dev/isxander/controlify/utils/ControllerUtils.java b/src/main/java/dev/isxander/controlify/utils/ControllerUtils.java new file mode 100644 index 0000000..7aa3f71 --- /dev/null +++ b/src/main/java/dev/isxander/controlify/utils/ControllerUtils.java @@ -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); + } +} diff --git a/src/main/java/dev/isxander/controlify/virtualmouse/VirtualMouseHandler.java b/src/main/java/dev/isxander/controlify/virtualmouse/VirtualMouseHandler.java index 2772be1..52bc24a 100644 --- a/src/main/java/dev/isxander/controlify/virtualmouse/VirtualMouseHandler.java +++ b/src/main/java/dev/isxander/controlify/virtualmouse/VirtualMouseHandler.java @@ -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()); diff --git a/src/main/resources/hiddb.json5 b/src/main/resources/assets/controlify/hiddb.json5 similarity index 73% rename from src/main/resources/hiddb.json5 rename to src/main/resources/assets/controlify/hiddb.json5 index 7678bc8..2922c46 100644 --- a/src/main/resources/hiddb.json5 +++ b/src/main/resources/assets/controlify/hiddb.json5 @@ -1,6 +1,9 @@ // THIS FILE IS PARSED BY LENIENT GSON PARSER AND IS NOT JSON5 COMPLIANT! -{ - "xbox_one": { +[ + { + "name": "Xbox One Controller", + "identifier": "xbox_one", + "vendor": 1118, // 0x45e "product": [ 767, // 0x2ff @@ -17,7 +20,10 @@ 648 // 0x288 ] }, - "dualshock4": { + { + "name": "Dualshock 4 Controller", + "identifier": "dualshock4", + "vendor": 1356, // 0x54c "product": [ 1476, // 0x5c4 @@ -25,10 +31,13 @@ 2976 // 0xba0 ] }, - "steam_deck": { + { + "name": "Steam Deck", + "identifier": "steam_deck", + "vendor": 10462, // 0x28de "product": [ 4613 // 0x1205 ] } -} +] diff --git a/src/main/resources/assets/controlify/lang/en_us.json b/src/main/resources/assets/controlify/lang/en_us.json index 9752e40..4f6b55f 100644 --- a/src/main/resources/assets/controlify/lang/en_us.json +++ b/src/main/resources/assets/controlify/lang/en_us.json @@ -34,6 +34,8 @@ "controlify.gui.left_stick_deadzone.tooltip": "How far the left joystick needs to be pushed before registering input.", "controlify.gui.right_stick_deadzone": "Right Stick Deadzone", "controlify.gui.right_stick_deadzone.tooltip": "How far the right joystick needs to be pushed before registering input.", + "controlify.gui.joystick_axis_deadzone": "%s Deadzone", + "controlify.gui.joystick_axis_deadzone.tooltip": "How far '%s' axis needs to be pushed before registering input.", "controlify.gui.stickdrift_warning": "Warning: Setting this too low will cause stickdrift! This is where the internals of your controller become mis-calibrated and register small amounts of input when there shouldn't be.", "controlify.gui.auto_calibration": "Automatic Deadzone Calibration", "controlify.gui.auto_calibration.tooltip": "Automatically calibrate the deadzone of your controller.", @@ -73,6 +75,10 @@ "controlify.binding.controlify.walk_backward": "Walk Backward", "controlify.binding.controlify.strafe_left": "Strafe Left", "controlify.binding.controlify.strafe_right": "Strafe Right", + "controlify.binding.controlify.look_up": "Look Up", + "controlify.binding.controlify.look_down": "Look Down", + "controlify.binding.controlify.look_left": "Look Left", + "controlify.binding.controlify.look_right": "Look Right", "controlify.binding.controlify.jump": "Jump", "controlify.binding.controlify.sneak": "Sneak", "controlify.binding.controlify.attack": "Attack", @@ -93,6 +99,10 @@ "controlify.binding.controlify.pick_block": "Pick Block", "controlify.binding.controlify.toggle_hud_visibility": "Toggle HUD Visibility", "controlify.binding.controlify.show_player_list": "Show Player List", + "controlify.binding.controlify.vmouse_move_up": "VMouse Move Up", + "controlify.binding.controlify.vmouse_move_down": "VMouse Move Down", + "controlify.binding.controlify.vmouse_move_left": "VMouse Move Left", + "controlify.binding.controlify.vmouse_move_right": "VMouse Move Right", "controlify.binding.controlify.vmouse_lclick": "VMouse LClick", "controlify.binding.controlify.vmouse_rclick": "VMouse RClick", "controlify.binding.controlify.vmouse_shift_click": "VMouse Shift Click", @@ -101,6 +111,12 @@ "controlify.binding.controlify.vmouse_escape": "VMouse Key Escape", "controlify.binding.controlify.vmouse_shift": "VMouse Key Shift", "controlify.binding.controlify.vmouse_toggle": "Toggle Virtual Mouse", + "controlify.binding.controlify.gui_navi_down": "GUI Navi Down", + "controlify.binding.controlify.gui_navi_up": "GUI Navi Up", + "controlify.binding.controlify.gui_navi_left": "GUI Navi Left", + "controlify.binding.controlify.gui_navi_right": "GUI Navi Right", + "controlify.binding.controlify.yacl_cycle_opt_forward": "YACL Cycle Option Forward", + "controlify.binding.controlify.yacl_cycle_opt_backward": "YACL Cycle Option Backward", "controlify.guide.inventory": "Open Inventory", "controlify.guide.swim_up": "Swim Up", @@ -124,6 +140,42 @@ "controlify.guide.interact": "Interact", "controlify.guide.pick_block": "Pick Block", + "controlify.joystick_mapping.unmapped.axis": "Axis #%s", + "controlify.joystick_mapping.unmapped.button": "Button #%s", + "controlify.joystick_mapping.unmapped.hat": "Hat #%s", + "controlify.joystick_mapping.unmapped.axis_direction.negative": "(Negative)", + "controlify.joystick_mapping.unmapped.axis_direction.positive": "(Positive)", + "controlify.joystick_mapping.xbox_one.axis.left_stick": "Left Stick", + "controlify.joystick_mapping.xbox_one.axis.left_stick.left": "Left", + "controlify.joystick_mapping.xbox_one.axis.left_stick.right": "Right", + "controlify.joystick_mapping.xbox_one.axis.left_stick.up": "Up", + "controlify.joystick_mapping.xbox_one.axis.left_stick.down": "Down", + "controlify.joystick_mapping.xbox_one.axis.right_stick": "Right Stick", + "controlify.joystick_mapping.xbox_one.axis.right_stick.left": "Left", + "controlify.joystick_mapping.xbox_one.axis.right_stick.right": "Right", + "controlify.joystick_mapping.xbox_one.axis.right_stick.up": "Up", + "controlify.joystick_mapping.xbox_one.axis.right_stick.down": "Down", + "controlify.joystick_mapping.xbox_one.axis.left_trigger": "Left Trigger", + "controlify.joystick_mapping.xbox_one.axis.left_trigger.up": "Up", + "controlify.joystick_mapping.xbox_one.axis.left_trigger.down": "Down", + "controlify.joystick_mapping.xbox_one.axis.right_trigger": "Right Trigger", + "controlify.joystick_mapping.xbox_one.axis.right_trigger.up": "Up", + "controlify.joystick_mapping.xbox_one.axis.right_trigger.down": "Down", + "controlify.joystick_mapping.xbox_one.button.a": "A", + "controlify.joystick_mapping.xbox_one.button.b": "B", + "controlify.joystick_mapping.xbox_one.button.x": "X", + "controlify.joystick_mapping.xbox_one.button.y": "Y", + "controlify.joystick_mapping.xbox_one.button.left_bumper": "Left Bumper", + "controlify.joystick_mapping.xbox_one.button.right_bumper": "Right Bumper", + "controlify.joystick_mapping.xbox_one.button.left_stick": "Left Stick Press", + "controlify.joystick_mapping.xbox_one.button.right_stick": "Right Stick Press", + "controlify.joystick_mapping.xbox_one.button.back": "Back", + "controlify.joystick_mapping.xbox_one.button.start": "Start", + "controlify.joystick_mapping.xbox_one.button.guide": "Guide", + "controlify.joystick_mapping.xbox_one.hat.dpad": "D-Pad", + + + "controlify.calibration.title": "Controller Calibration for '%s'", "controlify.calibration.info": "This process will optimize settings for your controller to prevent stick drift. Stick drift happens in your controller thumbsticks and outputs slightly wrong values when you aren't touching them at all. Deadzones are used to prevent this.\n\nThis will only take a few seconds.", "controlify.calibration.wait": "Please do not touch your controller thumbsticks until the progress bar is complete. This process will only take a few seconds.", @@ -138,5 +190,10 @@ "controlify.beta.button": "Open Issue Tracker...", "controlify.error.hid": "Controller Detection Disabled", - "controlify.error.hid.desc": "Controlify could not start the controller detection system used to identify and distinguish between multiple controllers. This means controller config will not be able to be saved between play sessions. This is likely due to a system fault and you should check logs for further information." + "controlify.error.hid.desc": "Controlify could not start the controller detection system used to identify and distinguish between multiple controllers. This means controller config will not be able to be saved between play sessions. This is likely due to a system fault and you should check logs for further information.", + + "controlify.hat_state.up": "Up", + "controlify.hat_state.down": "Down", + "controlify.hat_state.left": "Left", + "controlify.hat_state.right": "Right" } diff --git a/src/main/resources/assets/controlify/mappings/xbox_one.json b/src/main/resources/assets/controlify/mappings/xbox_one.json new file mode 100644 index 0000000..1d74741 --- /dev/null +++ b/src/main/resources/assets/controlify/mappings/xbox_one.json @@ -0,0 +1,92 @@ +{ + "axes": [ + { + "ids": [0, 1], + "identifier": "left_stick", + "deadzone": true, + "rest": 0.0, + "axis_names": [ + ["right", "left"], + ["down", "up"] + ] + }, + { + "ids": [2, 3], + "identifier": "right_stick", + "deadzone": true, + "axis_names": [ + ["right", "left"], + ["down", "up"] + ], + "rest": 0.0 + }, + { + "ids": [4], + "identifier": "left_trigger", + "deadzone": false, + "rest": 0.0, + "range": [0.0, 1.0], + "axis_names": [ + ["down", "up"] + ] + }, + { + "ids": [5], + "identifier": "right_trigger", + "deadzone": false, + "rest": 0.0, + "range": [0.0, 1.0], + "axis_names": [ + ["down", "up"] + ] + } + ], + "buttons": [ + { + "button": 0, + "name": "a" + }, + { + "button": 1, + "name": "b" + }, + { + "button": 2, + "name": "x" + }, + { + "button": 3, + "name": "y" + }, + { + "button": 4, + "name": "left_bumper" + }, + { + "button": 5, + "name": "right_bumper" + }, + { + "button": 6, + "name": "back" + }, + { + "button": 7, + "name": "start" + }, + { + "button": 8, + "name": "left_stick" + }, + { + "button": 9, + "name": "right_stick" + } + ], + "hats": [ + { + "hat": 0, + "name": "dpad" + } + ] +} diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/a_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/a_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/a_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/a_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/b_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/b_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/b_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/b_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/back.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/back.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/back.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/back.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_down.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/dpad_down.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_down.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/dpad_down.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/dpad_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/dpad_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/dpad_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/dpad_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_up.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/dpad_up.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/dpad_up.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/dpad_up.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_bumper.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_bumper.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_bumper.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_bumper.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_down.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_down.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_down.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_down.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_press.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_press.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_press.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_press.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_up.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_up.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_stick_up.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_stick_up.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_trigger.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_trigger.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/left_trigger.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/left_trigger.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_bumper.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_bumper.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_bumper.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_bumper.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_down.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_down.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_down.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_down.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_press.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_press.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_press.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_press.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_up.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_up.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_stick_up.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_stick_up.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_trigger.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_trigger.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/right_trigger.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/right_trigger.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/start.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/start.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/start.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/start.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/x_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/x_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/x_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/x_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/y_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/y_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/dualshock4/y_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/dualshock4/y_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_bumper_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_bumper_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_bumper_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_bumper_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_bumper_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_bumper_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_bumper_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_bumper_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_circle_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_circle_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_circle_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_circle_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_dpad_down.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_dpad_down.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_dpad_down.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_dpad_down.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_dpad_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_dpad_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_dpad_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_dpad_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_dpad_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_dpad_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_dpad_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_dpad_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_dpad_up.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_dpad_up.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_dpad_up.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_dpad_up.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_face_button_down.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_face_button_down.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_face_button_down.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_face_button_down.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_face_button_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_face_button_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_face_button_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_face_button_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_face_button_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_face_button_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_face_button_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_face_button_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_face_button_up.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_face_button_up.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_face_button_up.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_face_button_up.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_home_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_home_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_home_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_home_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_left_trigger.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_left_trigger.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_left_trigger.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_left_trigger.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_right_trigger.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_right_trigger.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_right_trigger.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_right_trigger.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_select_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_select_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_select_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_select_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_sl_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_sl_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_sl_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_sl_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_sr_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_sr_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_sr_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_sr_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_start_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_start_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_start_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_start_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_stick_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_stick_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_stick_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_stick_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_stick_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_stick_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_stick_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_stick_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_touchpad.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_touchpad.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/switch/switch_touchpad.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/switch/switch_touchpad.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/a_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/a_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/a_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/a_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/b_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/b_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/b_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/b_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/back.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/back.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/back.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/back.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/dpad_down.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/dpad_down.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/dpad_down.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/dpad_down.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/dpad_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/dpad_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/dpad_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/dpad_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/dpad_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/dpad_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/dpad_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/dpad_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/dpad_up.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/dpad_up.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/dpad_up.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/dpad_up.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/guide.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/guide.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/guide.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/guide.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_bumper.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_bumper.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_bumper.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_bumper.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_down.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_down.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_down.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_down.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_press.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_press.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_press.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_press.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_up.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_up.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_stick_up.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_stick_up.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_trigger.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_trigger.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/left_trigger.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/left_trigger.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_bumper.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_bumper.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_bumper.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_bumper.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_down.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_down.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_down.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_down.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_left.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_left.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_left.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_left.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_press.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_press.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_press.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_press.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_right.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_right.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_right.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_right.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_up.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_up.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_stick_up.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_stick_up.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_trigger.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_trigger.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/right_trigger.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/right_trigger.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/start.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/start.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/start.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/start.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/touchpad.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/touchpad.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/touchpad.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/touchpad.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/x_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/x_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/x_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/x_button.png diff --git a/src/main/resources/assets/controlify/textures/gui/buttons/xbox/y_button.png b/src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/y_button.png similarity index 100% rename from src/main/resources/assets/controlify/textures/gui/buttons/xbox/y_button.png rename to src/main/resources/assets/controlify/textures/gui/gamepad_buttons/xbox/y_button.png diff --git a/src/main/resources/controlify.mixins.json b/src/main/resources/controlify.mixins.json index 3d9c581..3a9966b 100644 --- a/src/main/resources/controlify.mixins.json +++ b/src/main/resources/controlify.mixins.json @@ -4,6 +4,7 @@ "minVersion": "0.8", "compatibilityLevel": "JAVA_17", "mixins": [ + "core.GLXMixin" ], "client": [ "core.ClientPacketListenerMixin",